cli@1.6.1
cli@1.6.11.6.1 (2026-10-11)
🩹 Fixes
- form_data and urlencoded fields keep their YAML order. ([](https://github.com/the-dev-tools/dev-tools/commit/))
Product updates, new features, and improvements to Dev Tools.
cli@1.6.1cli@1.6.0Secure cookies are now sent to http://localhost, 127.0.0.1, ::1 and *.localhost, as browsers do, so a local dev server's Secure session cookie keeps the session alive. Other http:// hosts still never get them.follow_redirects: false on a request step returns the 30x as is, so the redirect itself can be asserted. Every response now has response.url (the final URL after redirects) and response.redirects, e.g. '!(response.url contains "/login")'. The option survives YAML export; the desktop app doesn't store it yet.{{ now() }} now writes an RFC 3339 timestamp (2026-10-11T15:59:15.978+03:00) instead of Go's internal time format. uuid(), ulid(), now().Unix() and faker.* are documented in the YAML README.flow run reads ./.env and the flow file's directory's .env for {{ #env:NAME }} (the process environment wins), and before running lists every missing #env: name in one error instead of failing at the first step that reads one.OPENAI_API_KEY is the only key set (a configured provider, model, base_url or api_key_env still decides), and the no-key notice names both keys. A failing token check reports the budget that failed, not the first one, and ci summaries print > instead of ≤ when the p95 latency or max tokens is over its limit. (76e47463)cli@1.5.0devtools import har detects streaming responses. An entry whose response is text/event-stream becomes a streaming request, with its stream: preset (openai, anthropic, vercel-ai or sse) picked from the first recorded event, as Stresseur recordings do. Plain-text, base64 and missing bodies are handled (no body means sse), the import prints a per-preset summary, and exporting the workspace writes stream: <preset>. (d951f9de)stream:, stream_timeout_ms, expect: and the flow's judge:, quality: and iterations: are now stored with the workspace instead of being dropped on import, so they survive import → export. File-level settings are written back on each flow.
WebSocket flows no longer lose their connection: a ws_connection stayed open only until its step finished, so a later ws_send could fail with "use of closed network connection" and messages stopped being read. The connection now lasts for the whole flow, and its handshake has its own 30 s limit.
cli@1.4.0expect: block (the Stresseur syntax, unchanged) is validated on import and evaluated by devtools flow run after the flow's steps finish, so it never counts in step timings. Results are printed under the flow's table and written to the JSON report as each step's checks[] (kind, passed, score, reason, judge_model, cached, value, limit). (4b494b2f)schema/schema_at, max_latency_ms, the new max_ttft_ms, token budgets from usage, and a G-Eval judge (Anthropic or any OpenAI-compatible API). The judge uses injection-resistant prompting, logprob weighting or borderline re-sampling, and runs in parallel.STRESSEUR_JUDGE_API_KEY, ANTHROPIC_API_KEY or OPENAI_API_KEY, from the environment or ./.env. Without one, judge checks are skipped with a notice.--judge-cache, default .devtools/judge-cache.json.quality: { fail_below: … }.Server-Sent Events responses are now read as they arrive. A text/event-stream response streams automatically; a request step's stream: openai | anthropic | vercel-ai | sse picks the preset that assembles the text, and stream_timeout_ms bounds an open stream (default 30 s). Assertions, expect: and later steps can read response.text, response.events, response.event_count, response.ttft_ms and the streamed response.usage.
cli@1.3.1response.duration in assertions: is now the measured request time in ms. It was always 0, so response.duration < N never failed.(got response.duration = 5120) or (got response.body.name = "Outdoor"), instead of only the status code. Values under names like token or password are redacted. When a body field is compared on a non-JSON response, such as a 404 HTML page, the error now names the status and content type instead of only invalid operation: int(string).{{ Ask.response.body.answer }}, without depending on that step. The error names the step and the missing depends_on. Before, such a step ran at flow start with the template unfilled, and the flow still passed.cli@1.3.0ramping-vus (stages with graceful ramp-down), and the open-model constant-arrival-rate and ramping-arrival-rate, which start iterations on schedule whatever the response time and report dropped iterations once max_vus are busy. A load: entry can add think_time, thresholds (e.g. p95: <300ms, errors: <1%, per step under steps:), checked at the end and failing the run with exit code 99, and abort rules evaluated every metrics frame that stop a run early with exit code 108. --report frames:<url> streams every metrics frame, with per-step HDR histograms, and the final report to an HTTP endpoint (bearer token from DEVTOOLS_FRAMES_TOKEN, retried in the background, and the run stops if the endpoint is unreachable for 60s); --vus-scale/--rate-scale split one profile across machines, --load-file reads scenarios from a separate file with the same schema, and the summary now shows the request count it is based on. (c5002b3d)cli@1.2.2~/.devtools.yaml on first run; a config file is still read when present. (1dff1083)cli@1.2.1cmd.Root() returns its root command, and each release publishes devtools-go-src-<version>.tar.gz, the Go source of the CLI and the modules it imports with the generated code included, to build against. (03382c8b)cli@1.2.0cleanup: steps that always run after the flow's normal steps, whether they passed or failed, so data a test creates is deleted even when a step in the middle fails and the next run starts from the same state. Cleanup steps are request or graphql steps that can read earlier outputs ({{ PostProducts.response.body.id }}); they run in listed order (depends_on may reorder them among themselves), a step that reads a step which never ran is reported as skipped, and a failing cleanup step fails an otherwise passing flow while an earlier failure stays the reported error. Cleanup steps appear after the normal steps in the console table and in JSON and JUnit reports, and run after every iteration in load runs without being measured. (f204ce9e){{ cookies.csrftoken }}, or {{ cookies["XSRF-TOKEN"] }} for names that aren't identifiers. This makes double-submit CSRF protection (Django, Laravel, Angular, many Express apps) testable: copy the CSRF cookie into the header the server checks. Values are URL-decoded the way client code reads them, the latest value of each cookie wins, and a flow variable named cookies takes precedence. (60054c5f)cli@1.1.3Error: assertion failed: response.status == 200 (got 401: {"error":"Invalid credentials"}). Non-2xx responses include a short body excerpt; 2xx bodies are left out because they can carry tokens. The flag reference no longer prints under a failing flow, and the error no longer repeats four times. (c8520dd6)cli@1.1.2response.body.qty == 3, response.body.price > 100 and arithmetic work. Response numbers used to compare as text, so equality was always false and >/< failed with a type error. Values copied into later requests with {{ }} keep their exact original text, as before. (3cd2d960)desktop@1.1.3Heads up: on October 13, 2026, DevTools Studio becomes Stresseur Studio. It's the same app with the same flows, and it's still free, open source and local. You don't need to do anything.
Details: https://dev.tools/blog/devtools-studio-becomes-stresseur-studio/
desktop@1.1.2Heads up: on October 13, 2026, DevTools Studio becomes Stresseur Studio. It's the same app with the same flows, and it's still free, open source and local. You don't need to do anything.
Details: https://dev.tools/blog/devtools-studio-becomes-stresseur-studio/
desktop@1.1.1Heads up: on October 13, 2026, DevTools Studio becomes Stresseur Studio. It's the same app with the same flows, and it's still free, open source and local. You don't need to do anything.
Details: https://dev.tools/blog/devtools-studio-becomes-stresseur-studio/
cli@1.1.1desktop@1.1.0Heads up: on October 13, 2026, DevTools Studio becomes Stresseur Studio. It's the same app with the same flows, and it's still free, open source and local. You don't need to do anything.
Details: https://dev.tools/blog/devtools-studio-becomes-stresseur-studio/
---
flow run --vus/--duration and load: scenarios with percentile tables + load_report JSON), versioned yamlflow schema (version: 2), HTTP assertions in yamlflow files now enforced on import (previously silently dropped), run: blocks execute in dependency order with strict failure modes (failed dependencies skip-and-continue instead of aborting the run), run-flows GitHub Action for CI, and real build-version reporting in devtools version. (9a3152a3)cli@1.1.0flow run --vus/--duration and load: scenarios with percentile tables + load_report JSON), versioned yamlflow schema (version: 2), HTTP assertions in yamlflow files now enforced on import (previously silently dropped), run: blocks execute in dependency order with strict failure modes (failed dependencies skip-and-continue instead of aborting the run), run-flows GitHub Action for CI, and real build-version reporting in devtools version. (9a3152a3)desktop@1.0.3@nx/eslint patch made dependency installation fail hard on Windows CI, so 1.0.2 shipped without Windows (and, due to an expired Apple agreement, macOS) artifacts. The dead patch is removed; this release ships installers for all platforms again. Includes everything from 1.0.2, notably the fix for workspaces going blank after many file reorders (#44).desktop@1.0.2display_order, converging to the float32 limit after a few dozen inserts — at which point every file in the workspace disappeared from the UI and new flows could not be created. Order generation now uses fixed steps, and a startup migration repacks existing display_order values to small sequential numbers (preserving your current order), so previously broken workspaces recover automatically. (#44)cli@1.0.3@nx/eslint patch made dependency installation fail hard on Windows CI, so 1.0.2 shipped without win32-x64/win32-ia32 binaries. The dead patch is removed; this release ships all platforms again. Includes the file display_order repair migration from 1.0.2 (#44).cli@1.0.2files.display_order values that converged to float32 MAX (a bug in desktop order generation) back to small sequential numbers, preserving relative order. Databases shared with a broken desktop workspace recover automatically. (#44)desktop@1.0.1{{ http_1.response.body.done }}). Previously the check ran before children, so any expression referencing a not-yet-written variable failed the entire flow on the first iteration. Missing identifiers are now treated as "don't break" (loops are still bounded by iteration count). ForEach semantics also aligned with For: an expression that evaluates true exits the loop. (#42)StoreUnifiedResults, so SQLite rejected the row with FOREIGN KEY constraint failed (787) whenever GenerateFiles=true. Files are now stored first, and HTTP folder_id references are remapped before insertion.Iterations: 0 and an empty break expression until you closed and reopened the workspace.break_condition field on for / for_each steps in YAML workspaces, mirroring the desktop UI's "Break If" setting.cli@1.0.1{{ http_1.response.body.done }}). Previously the check ran before children, so any expression referencing a not-yet-written variable failed the entire flow on the first iteration. Missing identifiers are now treated as "don't break" (loops are still bounded by iteration count). ForEach semantics also aligned with For: an expression that evaluates true exits the loop. (#42)break_condition field on for / for_each steps in YAML workspaces, so loops in CLI-driven flows can exit on a runtime predicate without needing the UI.desktop@1.0.0# 1.0.0 (2026-04-24)
{{ }}: uuid(), uuid("v4"), uuid("v7"), ulid(), now().now(): .Unix(), .UnixMilli(), .UnixMicro(), .UnixNano().faker namespace for fake data — type faker. to browse 35 generators including name(), email(), phoneNumber(), url(), ipv4(), ipv6(), macAddress(), username(), password(), word(), sentence(), paragraph(), date(), timestamp(), uuid(), randomInt(min, max).cli@1.0.0# 1.0.0 (2026-04-24)
uuid(), uuid("v4"), uuid("v7"), ulid(), now() helpers inside {{ }}.now(): .Unix(), .UnixMilli(), .UnixMicro(), .UnixNano().faker.* namespace (35 generators — name(), email(), phoneNumber(), url(), ipv4(), word(), sentence(), paragraph(), date(), timestamp(), uuid(), randomInt(min, max), ...) for fake test data.desktop@0.5.0desktop@0.4.0desktop@0.3.2desktop@0.3.1desktop@0.3.0