* feat(cli): add `wmill flow dev` subcommand with per-flow reverse proxy and launch.json
Also generates .claude/launch.json for existing flow folders during `wmill init`.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: responsive dev layout and hide splitter for single-pane views
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: clamp flow graph height between minHeight and maxHeight
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat(cli): enhance app new with Claude Desktop integration and better defaults
- Add .claude/launch.json to generated app scaffold for Claude Code preview support
- Add "Open in Claude Desktop?" prompt that creates a CLI session and opens it
in Claude Desktop Code mode via the claude://resume deep link
- Improve default CSS template with body background, system fonts, and padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): handle both .flow and __flow suffixes in wmill dev
The flow detection in loadPaths only checked the configured suffix
(dotted or non-dotted), so users with nonDottedPaths=true who had
.flow folders (or vice versa) would see inline script edits treated
as standalone script changes instead of flow changes.
Now checks both suffix forms everywhere: type classification,
folder path extraction, path stripping, and loadWmPath lookup.
Also adds raw_app launch.json generation to init and sync pull.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(cli): update generated skills with dev workflow and preview commands
Update cli-commands, write-flow, and raw-app skills to document the new
local dev workflow (wmill dev --path, --proxy-port, .claude/launch.json).
Add wmill script preview and wmill flow preview to all script/flow skills
so agents know how to test without deploying.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(cli): include path in dev URL and use open.default for browser
- Append &path= to the printed/opened URL when --path is specified
- Use open.default(url) instead of open.openApp for more reliable browser opening
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): add Claude CLI/Desktop detection hints in wmill flow new
Show contextual instructions for previewing flows based on available tools.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: regenerate auto-generated CLI skills for new dev flags
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(cli): handle mixed flow suffixes in dev file watcher
The ignore() function uses isFlowPath() which only checks the configured
suffix (__flow or .flow), causing files in the other variant to be silently
ignored. Bypass the ignore check for any file inside a flow folder and
force flow type detection regardless of suffix configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(cli): drop default proxy in flow folders, open browser, add --no-browser
Manual `wmill dev` in a flow folder should not implicitly enable the
reverse proxy. Both proxy and legacy modes now open the browser; the
new --no-browser flag opts out. Claude Code launch.json templates pass
--no-browser so the IDE preview doesn't fight a system browser window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): gate dev broadcasts by --path and push currentLastEdit on connect
When --path (or auto-detected flow path) is set, drop file events for
any other path so the dev page stays locked to the requested resource
and currentLastEdit can never reflect an unrelated edit. The connection
handler proactively pushes currentLastEdit so the page renders without
waiting for the first file change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): prefer WebSocket for flow round-trip when wmill dev is connected
updateFlow used isInIframe priority, which routed Claude Code's iframe
preview through postMessage (no listener) and silently dropped flow
edits. Flip the priority: when the wmill dev WebSocket is open, use it
(covers standalone tabs and Claude Code's preview); fall back to
postMessage only when no WS is connected (the VS Code extension's iframe
URL has no `local=true`, so it never opens one). Also stop assigning
lastSent before a channel actually accepted the message, so a CONNECTING
WS doesn't silently swallow the first change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dev): url is source of truth for path; add workspace file picker
Drops the server-side --path gate added in 3c2d5155e1. The dev page now
filters by its URL's ?path= and the CLI is a dumb broadcaster, which
lets multiple tabs each watch different paths. When the URL has no
?path=, the page asks the CLI for a list of workspace items (flows,
scripts, raw_apps) via a new {type:'listPaths'} WS message and renders
a picker. Clicking a flow or script soft-updates the URL via
history.pushState and loads it; raw_apps surface a hint to use
`wmill app dev` since they don't render here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dev): picker uses homepage tree view with summaries
Replace the hand-rolled Button-list picker with a TreeView-style layout
that mirrors the Windmill homepage: folder/user tree grouping via
`groupItems`, item rows rendered through the shared `Row.svelte` (no
actions, no favourites, no link — just the visual), a `SearchItems`
fuzzy filter with the same search input styling and placeholder as the
homepage, and `group-open:` chevron toggling on native <details>.
The CLI's listWorkspacePaths now also reads summaries from each item's
metadata (flow.yaml for flows, <script>.script.yaml for scripts) in
parallel so the picker shows summaries as the primary row label, same
as the homepage. Raw apps have no standard manifest so they show the
path only.
Additional polish: title shows "<workspace> (local)" instead of
generic text, subtitle trimmed, item-wrapper owns the border-b so
Row's internal last:border-b-0 doesn't zero it out, summary border
gated on group-open: to avoid doubled lines when a folder is
collapsed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): wmill dev --no-browser was a no-op
Cliffy's `.option("--no-browser", ...)` creates an option named
`browser` (boolean, default undefined) that becomes `false` when the
flag is passed. The previous code checked `opts.noBrowser`, which
Cliffy never populates, so the guard silently no-op'd and the browser
always opened. Rename to `browser` and check `=== false` explicitly,
matching the `wmill app dev --no-open` convention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(dev): picker warns when wmill dev server is unreachable
Track WS state in Dev.svelte (connecting/open/closed) — 'closed' is
set on either the WS error or close event. When closed, the picker
replaces the toggle + search + tree with a warning Alert telling the
user to run `wmill dev` from the workspace root. Toggle and search are
hidden rather than rendered disabled because there's nothing to filter
anyway.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): rename wmill dev --no-browser to --no-open
Match the pre-existing `wmill app dev --no-open` flag. Having
`--no-browser` on one dev command and `--no-open` on the other was
just an oversight from my earlier change. All three launch.json
templates (init, flow new, sync pull) switch to `--no-open`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): wmill init creates root .claude/launch.json for the picker
Adds a workspace-root .claude/launch.json so Claude Code can launch
`wmill dev` from the project root and land on the file picker (no
--path → picker mode). Per-flow and per-raw_app launch.json files are
already generated by the existing scans. Skipped (with a gray log) if
the file already exists, so the user's customizations are preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add skipClaudeAssets wmill.yaml flag
When `skipClaudeAssets: true` is set in wmill.yaml, all generators
that previously wrote Claude-specific assets become no-ops:
- writeAiGuidanceFiles skips CLAUDE.md and .claude/skills/
(AGENTS.md is still written — vendor-neutral)
- wmill init skips the root .claude/launch.json + per-flow +
per-raw_app launch.json scans
- wmill sync pull skips the per-flow + per-raw_app launch.json scans
- wmill flow new skips the new flow's .claude/launch.json
- wmill app new skips the new raw_app's .claude/ folder + launch.json
The flag is added to SyncOptions, DEFAULT_SYNC_OPTIONS, and the
generated wmill.yaml template (commented out — opt-in).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): wmill init removes existing Claude assets when skipClaudeAssets is set
Re-running `wmill init` with `skipClaudeAssets: true` now removes
previously-generated Claude assets so the workspace state matches the
config. Narrow scope, no confirmation:
- per-flow / per-raw_app .claude/launch.json (each parent .claude/
collapsed if empty)
- root .claude/launch.json
- .claude/skills/ (wholly ours; safe to remove the subtree)
- root .claude/ collapsed if empty
- CLAUDE.md only if its content matches the default
("Instructions are in @AGENTS.md\n"); otherwise left in place
with a note
Each removal is logged in yellow under a single gray intro line that
prints lazily on the first removal — a clean tree stays silent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): wmill workspace add browser open silently no-ops
`open.openApp(open.apps.browser, { arguments: [url] })` resolves its
Promise even when the OS-level launch does nothing, so the CLI prints
"Opened browser for you" but no tab appears. Same pattern was already
fixed in `dev.ts` by commit 3272c29c2e — use `open.default(url)`,
which delegates to the native URL opener (`open` on macOS, `xdg-open`
on Linux, `start` on Windows) and actually rejects on failure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): wmill init workspace prompt no longer duplicates active profile name
Cliffy's Select.prompt renders `default: X` as `(X)` next to the
question header, which duplicates whichever workspace name the
default points to. Drop `default` and instead reorder the list so
the active profile is first (cursor-preselected by virtue of position)
and append "— active" to its label so the indicator lives where it's
contextually relevant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): expand preview-vs-run guidance for write-flow + all write-script-* skills
Both `wmill flow preview/run` and `wmill script preview/run` have the
same intent split — preview hits the local file, run hits the deployed
version, sync push deploys. The skills' "after writing" sections used
to terse-list the commands and just say "do not run them yourself",
which encouraged the wrong reflex of `sync push` + `run` to "test".
Rewrite the section in both `system_prompts/base/flow-base.md` (drives
write-flow) and the `script_cli_intro` block in
`system_prompts/generate.py` (drives all write-script-<lang>) to:
- explicitly list `preview` as the default for local iteration,
- spell out the few cases when `run` or `sync push` are appropriate,
- offer to test as a one-sentence next step (no multi-option menus),
- mark `preview` as safe to run autonomously.
Regenerate auto-generated/ + cli/src/guidance/skills.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): wmill dev — clearer mode names and accurate startup messaging
- Rename `startLegacyServer` to `startDirectServer`. "Legacy" implied
it was on the way out; the two modes (proxy vs direct WS) actually
serve different topologies and both stay. Add comments above each
section spelling out who they're for: proxy mode for embedders that
require a localhost origin (Claude Code preview), direct mode for
standalone browser tabs and the VS Code extension iframe.
- Replace the stale "Dev server will automatically point to the last
script edited locally" log line. Now print path-aware text:
- with --path (or auto-detected): "Watching <path> — edits will live
-reload in the dev page"
- without: "Open the dev page and pick a flow or script to preview —
edits will live-reload" plus a hint about --path
Mirror the same in proxy mode after the listen callback.
- Drop the redundant "Go to <url>" line when --no-open isn't passed
(maybeOpenBrowser already prints "Opened browser at <url>").
- Rename "Server listening on port 3001" to
"Dev WebSocket listening on ws://localhost:<port>/ws" so the line's
purpose is obvious.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli): drop per-folder .claude/launch.json generation
Stop creating `.claude/launch.json` inside every flow folder, raw_app
folder, and at `wmill flow new`/`wmill app new` time. The workspace-
root `.claude/launch.json` from `wmill init` stays — it's the picker
entry point and the one place where the deterministic "click → preview"
UX is high-value.
Removed from:
- `wmill init` — per-flow + per-raw_app scans
- `wmill sync pull` — per-flow + per-raw_app scans (also drops the
now-unused `node:fs` mkdirSync/writeFileSync import)
- `wmill flow new` — bootstrap no longer scaffolds `.claude/`
- `wmill app new` — same; also drops the `.claude/launch.json` lines
from the post-create directory listing
Skills already give the agent the right CLI commands, so per-folder
launch.json was redundant context. Existing files in user projects
keep working but won't be regenerated; `wmill init` with
`skipClaudeAssets: true` cleans them up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): wmill app new flags + tighten raw-app skill for AI agents
`wmill app new` is interactive by default, which hangs forever when an
AI agent tries to use it. Add flags so the wizard can be bypassed
end-to-end:
- `--summary <text>`, `--path <path>`, `--framework <react19|react18|
svelte5|vue>` (required for non-interactive)
- `--datatable <name>` (opt into the datatable wizard)
- `--schema <name>` (creates schema with CREATE SCHEMA IF NOT EXISTS
if it doesn't already exist; only valid with --datatable)
- `--overwrite` (replace existing directory without prompting)
- `--no-open-in-desktop` (suppress the Claude Desktop offer)
Mode is auto-detected: providing any of --summary/--path/--framework
puts the run into non-interactive mode where the datatable wizard,
overwrite prompt, and Claude Desktop prompt all skip silently (or fail
fast on conflict instead of waiting for stdin). Each provided flag is
validated upfront with a clear error message.
Skill side: rewrite `system_prompts/base/raw-app.md`'s "Creating a Raw
App" section so the AI agent knows it should run the command itself
with flags (not tell the user to run it interactively). Direct the
agent to use `AskUserQuestion` with one bundled call to gather any
missing summary/path/framework — refuse to invent values, refuse to
default. Anti-patterns spelled out explicitly.
AGENTS.md template (`cli/src/guidance/core.ts`) had a contradicting
line ("MUST ask the user to run wmill app new in its terminal first")
that was loaded eagerly into agent context and overrode the skill —
replaced with the same agent-driven guidance, pointing to the
raw-app skill for the full procedure.
Regenerate auto-generated/ + cli/src/guidance/skills.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): per-target preview launch.json + agent uses wmill flow new
Refactor the agent's dev/preview workflow:
- Drop root .claude/launch.json generation from `wmill init`. Sharing one
generic entry across sessions caused preview collisions; agents now add
per-target named entries (windmill: <wmill_path>) on demand.
- New `preview` skill in system_prompts/base/preview.md. Branches on
whether `mcp__Claude_Preview__*` MCP tools are available: with them,
add a per-target launch.json entry pinning its own port + --proxy-port
+ --path + --no-open and invoke the MCP preview tool; without them,
start `wmill dev --path <X> --no-open` directly and hand the URL the
CLI prints to the user. Never touch launch.json in the direct case.
- Agents must run `wmill flow new <path>` themselves to scaffold flows
(folder + flow.yaml with the right suffix), parallel to the existing
`wmill app new` rule. Missing path/summary trigger AskUserQuestion;
no inventing values.
- write-flow skill: 4-step Creating a Flow procedure that opens the
visual preview *before* editing flow.yaml so the user watches the
flow take shape via live reload.
- `wmill flow new` always prints the `wmill dev --path <X>` preview
hint; drop the Claude CLI/Desktop detection branches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): open app preview before editing in raw-app skill
Mirrors the flow skill's Step 3 — opening `wmill app dev` via the
preview skill before touching App.tsx so the user watches the app
take shape via live reload, instead of seeing the finished result
at the end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dev): guard WS replaceFlow with lockChanges to prevent echo
The postMessage handler at Dev.svelte:306-312 wraps replaceFlow with
lockChanges = true (cleared 500 ms later) so the $effect on
flowStore.val doesn't immediately re-serialize and re-send the freshly
received payload. The WebSocket handler did not, so on the initial
flow push (dev.ts:568-574 sends currentLastEdit on connect), the
client would echo back to handleFlowRoundTrip, which runs the
orphan-file scan. On content equality the write was a no-op, but the
scan could still delete files the server did not list.
Mirror the same lockChanges/timeout pattern in the WS replaceData
handler. Apply to both flow and script paths for symmetry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): correct wmill dev description + gate broadcasts server-side
Two related fixes:
1. The 'auto-pushes them to the remote workspace' wording in the
wmill dev description was wrong — the command never deploys, it
only broadcasts file changes over WS for live preview. Reworded
to call this out explicitly and point at 'wmill sync push' for
the deploy case.
2. Move the path filter out of the client (Dev.svelte:491-495) and
into broadcastChanges. Earlier the filter was client-side with
the comment 'server stays a dumb broadcaster' even though commit
3c2d5155 was titled 'gate dev broadcasts by --path'. Doing the
compare server-side aligns the implementation with the commit
narrative, cuts WS traffic when --path is set, and keeps the
per-tab semantics for the picker (each picker tab still gets the
full 'paths' listing on first connect).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): drop dead launch.json cleanup, fix description, narrow orphan scope
Three review fixes:
1. cleanupClaudeAssets removed both root and per-folder
.claude/launch.json files that this CLI never generates anymore.
Per the user's "feature hasn't been released yet" guidance, no
migration is needed — drop the dead scan and the root rm. Also
drop the now-unused nonDottedPaths argument (and its flowSuffix
/ rawAppSuffix locals).
2. The skipClaudeAssets description in template.ts listed
.claude/launch.json among the assets it skips, but launch.json
is no longer generated. Drop it from the description string.
3. The dev round-trip's orphan cleanup deleted any non-dot file in
a flow folder that wasn't in extractedPaths — including
README.md, fixtures, TODO.md, etc. Restrict the deletion to
files whose extension is in a known inline-script set
(.ts/.js/.py/.go/.sh/.sql/.ps1/.php/.rs/.java/.cs/.r/.graphql).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(cli, frontend): dedupe flow suffix helpers, use UI components
Five small follow-ups from the PR review:
1. dev.ts already had stripFolderSuffix() but three callsites were
reimplementing the same .flow/__flow if-else inline. Add an
isFlowFolderName(name) helper next to it and replace the duplicates
in startProxyServer's cwd check, the file-watcher localPath strip,
and normalizeWmPath.
2. Dev.svelte:866 was a <div onclick> with two svelte-ignore comments
for the missing a11y handlers. Replace with a real <button
type="button"> — kills the warnings, no visual change.
3. Dev.svelte:1283 was a raw <input type="text"> for the module
summary. Replace with the existing <TextInput> component (same one
the picker search at :1010 uses), per frontend/CLAUDE.md.
4. Dev.svelte:197 typed relativePaths as any[]; tighten to the actual
union (string | [number, string])[] — the python helper returns
tuples, the typescript one returns strings.
5. app/new.ts:822 fired exec("open <deeplink>") with no callback, so
an OS that refused the URL scheme silently failed and we still
logged "Opened in Claude Desktop!". Move the success log inside an
exec callback that surfaces the error and prints the deep link for
manual opening.
Plus a brief comment above parseWatchPath explaining its resync
contract (initial load + popstate + explicit pickPath, no generic
pushState listener).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): regenerate auto-gen for dev description; drop apostrophe to satisfy parser
generate.py:326 extracts .description() with the regex
[^"\']+ which bails on either quote type. Commit ff3a8e4ebd's new
description had an apostrophe inside double quotes ('wmill sync
push'), so the parser saw no description at all and the
auto-generated files dropped the line entirely — which is what
check-freshness caught on origin/main.
Quickest path to green CI: rephrase the description without the
inner apostrophe, then regenerate. The generator's regex is the
real bug but fixing it is out of scope here.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): seed app .claude/launch.json before opening Claude Desktop
When the user accepts "Open in Claude Desktop?" in wmill app new, write
a per-app .claude/launch.json (named "windmill: <appPath>") into the
freshly-created app folder before the deep link fires. Entry runs
'wmill app dev --no-open --port ${PORT:-4001}' from the app folder
(which is the cwd Claude Desktop opens with), so the user can hit play
right away to launch the preview.
Skip if .claude/launch.json already exists — never clobber user edits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix toggles positions
* fix(dev): gate picker mode on ?local= so VS Code iframe still renders content
The VS Code extension iframe loads the dev page without ?path= and
without ?local=true. After the picker rework, an empty watchPath
flipped pickerMode on, so the page rendered the picker UI even
though the extension was sending replaceScript / replaceFlow
postMessages — leaving the user stuck on the picker forever.
Picker mode only makes sense on the local dev page, where the wmill
dev WebSocket can supply the workspace listing. Anywhere else (VS
Code iframe, plain remote tab) the picker has no data source and no
purpose. Add an isLocalDevPage check so the picker only shows when
?local=true is present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(dev): mirror vscode extension's processFlowMessage round-trip
Three changes that bring our wmill dev round-trip into lockstep with
the windmill-vscode extension's processFlowMessage in src/extension.ts:
1. New cli/src/commands/dev/pathscript-restore.ts — verbatim port of
the extension's src/utils/pathscript-restore.ts. Adds AI-agent tool
walking that the previous local copy was missing (flows with
PathScript-shaped tools weren't being preserved across round-trip).
Header comment makes the cross-repo link explicit.
2. handleFlowRoundTrip rewritten to mirror processFlowMessage step-
for-step: reads failure_module + preprocessor_module from the
current flow.yaml, passes them to extractCurrentMapping, shares one
pathAssigner across all extraction calls, extracts inline scripts
from those special modules too, skips writing files whose content
starts with !inline (treats as pointer directives), and only
rewrites flow.yaml when the serialized YAML actually differs.
3. snapshotPathScripts / tagReplacedPathScripts callsites in loadPaths
were passing the FlowFile wrapper instead of FlowFile.value — the
helpers walk .modules / .failure_module / .preprocessor_module,
which only exist on .value, so PathScript snapshots silently
no-op'd on the file-watcher path. Pass .value at all four sites.
Deliberate divergence from the extension: orphan-cleanup keeps the
INLINE_SCRIPT_EXTS allow-list so README.md / fixtures aren't deleted.
The extension's version still over-deletes; that's tracked separately.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(skills): offer visual preview after create instead of auto-opening
Both write-flow and raw-app skills used to instruct the agent to open
the visual preview without asking right after wmill flow new /
wmill app new, on the rationale that live reload is most useful when
the page is already up. In practice this surprised users — opening
the dev page has side effects (browser window pop, possibly a
launch.json entry under MCP-preview Branch A) that warrant consent.
Change Step 3 in both skills from "open it without asking" to "offer
it as a one-sentence next step" — same pattern the same skills
already use for programmatic wmill flow preview offers. Two then-
necessary anti-patterns ("just open it", "open it before editing")
are dropped along with the auto-open instruction.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): probe both ip stacks before binding wmill dev proxy / app dev port
Node's default listen() has platform-dependent dual-stack behaviour.
If the requested port is already held on the IPv4 stack, listen() can
silently fall back to binding IPv6-only ([::1]:N). The OS then routes
new localhost connections to the older IPv4 listener, so the user
opens http://localhost:N and sees a stale prior server with no signal
that anything is wrong. Bit us in practice: a leftover wmill dev
--proxy-port 4000 served traffic for a freshly-started wmill app dev
--port 4000.
New helper at cli/src/utils/port-probe.ts probes both 0.0.0.0 and ::
before binding. On collision it walks upward to the next free port
(up to +20) and logs a prominent warning naming the holder when lsof
/ ss can find it:
Port 4000 is already in use (held by PID 91418 `bun`). Using
port 4001 instead.
Wired into:
- wmill dev --proxy-port: the resolved port flows into both
proxyServer.listen() and the &port=N parameter in the redirect
URL, so they always match. Bind explicitly to 0.0.0.0.
- wmill app dev --port: only when the user passed --port explicitly
(the default getPort.default(...) path already handles fallback).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(dev): pass placeholder via TextInput inputProps not as top-level prop
`<TextInput>`'s top-level Props don't include `placeholder` — native
input attributes go through the `inputProps` field. The previous
`<TextInput placeholder="Summary" .../>` failed `npm run check` with
"Object literal may only specify known properties, and
'\"placeholder\"' does not exist in type 'Props<\"input\">'.".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nit
* fix(cli): sequential port probe + sync dev test regex with renamed log
Two CI regressions on test-linux:
1. port-probe parallel race on Linux. isPortFreeOnBothStacks ran the
IPv4 and IPv6 binds via Promise.all. On Linux the default is
net.ipv6.bindv6only=0, so a bind(::, port) socket also takes the
IPv4 stack on the same port. Concurrent v4 + v6 binds then race for
v4 — one wins, the other gets EADDRINUSE on a port that is actually
free. Walks 20 ports up, all fail the same way, throws, child exits.
Tests that fetch http://localhost:port time out at 60s.
Doesn't repro on macOS (bindv6only=1 by default — what I tested
against). Probe sequentially so each bind fully releases before the
next starts.
2. dev_server.test 1 regex out of sync. Commit 018dc3861a renamed the
startup log from "Server listening on port N" to "Dev WebSocket
listening on ws://localhost:N/ws" but didn't update the test, which
times out at 30s waiting for the old string. Update the regex to
match the current log.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Update system_prompts/auto-generated/skills/write-script-graphql/SKILL.md
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* fix(cli): address dev/app PR review — bugs 1-7
Per code review:
1. app new.ts — wrap claude --session-id exec in try/finally so the
spinner setInterval is always cleared. On rejection control jumped
to the outer catch and the spinner kept writing \r forever, garbling
subsequent output.
2. app new.ts — make --overwrite actually wipe the dir before
re-creating. Previously logged "Overwriting" but only skipped the
prompt; leftover files from a different framework (e.g. App.tsx
from a prior react18 install when re-scaffolding as svelte5)
survived and produced a hybrid scaffold.
3. dev/dev.ts — anchor the flow-folder match on path segments. The
substring checks (cpath.includes(".flow/") / "__flow/") also fired
on names like notes_about__flow_design/readme.md. New
isInsideFlowFolder + findFlowFolderPrefix split on "/" and check
segment suffixes. Drops the now-unreachable script→flow fallback
inside the else branch.
4. dev/dev.ts — direct mode also routes through resolveBindPort so it
detects dual-stack collisions like the proxy mode does. Bare getPort
only probes one stack, defeating the whole point of port-probe.ts.
Also bind to BIND_HOST explicitly. Drops the unused getPort import.
5. dev/dev.ts — normalize opts.path once after mergeConfigWithConfigFile.
broadcastChanges compared against a non-normalized opts.path, so
--path f/foo/ or --path f/foo.flow silently dropped every broadcast.
Also pulls normalizeWmPath to module scope (was a closure inside dev()).
6. dev/dev.ts — guard the initial-state ws.send with readyState === OPEN,
matching the other branches' pattern.
7. dev/dev.ts — typo: "givena" → "given a".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): address dev/app PR review — items 8-10
8. dev/dev.ts — derive INLINE_SCRIPT_EXTS from exts so adding a new
script language to script.ts auto-extends orphan cleanup. Previously
.gql, .nu, .rb were missing — flows using those languages would
leave orphaned inline files behind. Excludes .yml because user
fixtures commonly use it in flow folders, and leaving a stale
.playbook.yml inline script is preferable to deleting a fixture.
Keeps .js for hand-written flows that aren't in the exts list.
9. app/new.ts — wrap Claude Desktop install probe + prompt in
process.platform === "darwin". The probe (ls /Applications/Claude.app)
and the open command both only work on macOS — the explicit guard
makes the platform scope grep-able.
10. app/new.ts — switch the deep-link spawn from exec(`open ${shell-
escaped url}`) to execFile("open", [deepLink]). sessionId is a UUID
and absAppDir is URI-encoded today so the old form was safe, but
execFile removes the shell entirely.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli,dev): cubic review — port-probe error semantics, pluralize spacing
[2] cli/src/utils/port-probe.ts — distinguish IPv6-unsupported from port
collision in isPortFree. Previously every error code returned false,
including EAFNOSUPPORT / EADDRNOTAVAIL on the IPv6 probe when the host
has no v6 stack at all (IPv4-only containers). resolveBindPort would
then walk all 20 ports getting the same error and throw. Treat only
EADDRINUSE / EACCES as "not free"; everything else as free.
[13] cli/src/commands/app/dev.ts — only probe both stacks when binding
to localhost. The dual-stack collision risk is specific to localhost
(which resolves to 127.0.0.1 + ::1); for an explicit IPv4 host there's
only one stack to worry about, so don't move the user's requested port
over a phantom v6 collision.
[14] frontend/src/lib/components/Dev.svelte — pluralize already inserts
a space between quantity and word, so " item" produced "3 items".
Drop the leading space in both call sites.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): cubic review — preview args, skill scopes
Source changes in base/ + generate.py, then regenerated auto-generated/
via python system_prompts/generate.py. Per cubic review:
[4]+[7] generate.py — "pick plausible args from the `main` signature"
was language-blind. SQL queries and Bash scripts use $1/$2 positional
parameters, not a main(...) signature. Reword to call out both shapes
explicitly so the wording survives across all 19 generated language
skills (postgresql, bash, mysql, …) instead of just the ones that
happen to have main().
[5] base/raw-app.md — the "CLI Commands" table said "Tell the user
they can run these commands (do NOT run them yourself)" while the
"Creating a Raw App" section above (added in this PR) tells the agent
to run `wmill app new` itself. Carve `wmill app new` out of the table
and add a one-line note pointing back to the create flow, so the
guidance no longer self-contradicts.
[10] base/preview.md — "These print a `Go to <url>` line on stdout"
was wrong for `wmill app dev`, which prints
"🚀 Dev server running at <url>". List both line shapes explicitly and
suggest a loose http:// match for URL capture.
[12] base/flow-base.md — "regenerate lock files for the flow you
modified" misstated the default scope. `wmill generate-metadata`
scans scripts, flows, and apps by default
(see cli/src/commands/generate-metadata/generate-metadata.ts:71-73).
Update wording to call out the default scope and how to narrow it.
Also folds the cubic [1] graphql safety wording (originally a one-off
edit on the auto-generated file in a895db7) back into generate.py
itself, so it survives regeneration and applies to all language skills.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): cubic round 2 — language-specific placeholder syntax
Round 1 wording was too narrow:
- "$1, $2 placeholders for SQL queries and Bash" was wrong for MySQL
(`?`), Snowflake (`?`), MSSQL (`@P1`), BigQuery (`@name`), and
PowerShell (which uses `param(...)`, not main()).
- The preview-skill URL match said "first `http://...` token" — remote
workspaces serve HTTPS, so the regex would miss them.
Source-only fixes in generate.py and base/preview.md, then regenerated
auto-generated/ via python system_prompts/generate.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flow): track maxHeight in FlowGraphV2 height effect
cubic [3]: updateHeight() reads both minHeight and maxHeight, but the
$effect only tracked minHeight. Changing maxHeight alone (e.g. when a
parent shrinks the cap during a layout transition) left height frozen
at the previously clamped value.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs(system_prompts): tool-agnostic wording in base/
cubic [11]: system_prompts/README.md says these prompts must NOT
contain tool usage instructions. Three base files violated this:
- base/flow-base.md (4× AskUserQuestion). Worst offender — leaks into
the frontend copilot via FLOW_BASE in prompts.ts (consumed by
getFlowPrompt in frontend/src/lib/components/copilot/chat/flow/
core.ts:1287). Frontend has no AskUserQuestion tool, so the wording
was both irrelevant and confusing there.
- base/raw-app.md (5× AskUserQuestion + 1× mcp__Claude_Preview__).
CLI-skill-only but covered by the same scope rule.
- base/preview.md (5× mcp__Claude_Preview__). CLI-skill-only, same.
Replaced with role descriptions: "ask the user (use a structured-
question tool if your runtime has one)" and "a tool that can embed a
localhost URL inside the IDE / chat surface". Kept one mention of
mcp__Claude_Preview__ in preview.md as an illustrative example, since
documentation of one runtime is fine — what's not fine is gating
behaviour on a specific tool name.
Source-only edits, then regenerated auto-generated/ via
python system_prompts/generate.py.
Verification: grep -r AskUserQuestion system_prompts/auto-generated/
now returns nothing. The remaining AskUserQuestion refs in
cli/src/guidance/core.ts are hand-written CLI-only AGENTS.md content
(not part of system_prompts), and Claude Code does have that tool, so
those are correctly scoped.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(flow): drop .no-splitter CSS hack, use snippets to skip Splitpanes
cubic [8]: the previous fix for "top pane is empty in aiagent / noEditor
mode" was a CSS rule that hid `:global(.splitpanes__splitter)` inside
.no-splitter. That cascaded into nested splitpanes too — the aiagent
left/right tabs panel (line 1043), the debug-console editor split
(line 877), and the doubly-nested debug panel (line 1472) all lost
their resize handles.
Refactor the layout instead. Extract top-pane and bottom-pane content
as snippets, then conditionally render either:
- just the bottom snippet (no Splitpanes wrapper) when the top pane
would be empty (aiagent or noEditor), or
- the original two-Pane Splitpanes layout otherwise.
This removes the splitter at its root rather than hiding it, so
nested splitters are unaffected. The bottom Pane's complex bind:size
getter/setter (which returned 100 when aiagent) collapses to a simple
binding now that the aiagent path no longer goes through the wrapping
Pane at all.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nit
* fix(flow,preview): cubic round 3 — FlowPathViewer regression + preview skill rewrite
[3149192182] FlowModuleComponent.svelte: my last refactor's
"aiagent || noEditor" condition stripped the FlowPathViewer for
noEditor + type === 'flow', because the top-pane snippet was no longer
rendered. The flow-viewer pane is the only thing that *does* show in
that mode, so it shouldn't have been collapsed. Tighten the condition
to "aiagent || (noEditor && type !== 'flow')".
[3149060930] system_prompts/base/preview.md: Branch A detection was
too broad — "can embed or open a localhost URL" is strictly weaker
than "can read .claude/launch.json and launch a configuration". Only
the Claude Desktop / Code MCP integration does the latter; most
embedders only do the former. Restructure preview.md around two
orthogonal axes:
1. Mode (proxy vs direct) — driven by "does the embedder need a
localhost URL?". Direct is the default; proxy is for embedders
that sandbox cross-origin loads.
2. Who starts the server — you spawn `wmill dev` yourself, OR a
launch.json-aware runtime (currently only the
`mcp__Claude_Preview__*` MCP family) launches it on demand.
The two compose into four common cases (regular browser tab, generic
preview pane, localhost-only preview pane, Claude MCP), each with a
clear instruction. The launch.json/MCP machinery is now scoped to a
single section gated on actually having that tool in your tool list.
Source-only edit in base/preview.md, then regenerated auto-generated/
via python system_prompts/generate.py.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix dev step display
* nit
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
* fix: only enable EE features in test backend when license key is available
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: skip EE tests without license key and exclude test-skills from test discovery
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: unskip passing tests and add duplicate (remote, workspaceId) check in addWorkspace
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(cli): migrate from Deno APIs to Node.js/Bun-compatible APIs
Replace Deno-specific APIs with Node.js equivalents across the entire CLI
codebase to enable running on Node.js/Bun. Switch build system from dnt
to bun, update imports from jsr:/npm: prefixed to bare specifiers, and
add package.json/tsconfig.json for the Node.js ecosystem.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* all
* test(cli): expand test coverage with new integration and unit tests
Add standalone_commands.test.ts covering folder list, schedule list,
resource-type list/push/update, script show/run/bootstrap, and user
commands. Add unit tests for filePathExtensionFromContentType and
removeExtensionToPath. Add git_unit, local_encryption_unit,
resource_folders_unit, and settings_unit test files. Fix schedule
cron expressions (6-field format), add includeSchedules flag, improve
test setup with pre-build and auto-cleanup, and support TEST_CLI_RUNTIME=node.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): replace Deno.readFile with node:fs in WASM loaders and add schema parsing tests
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(cli): switch WASM parsers from local files to npm packages
Use published windmill-parser-wasm-* npm packages instead of local
wasm/ files. A loadParser() helper uses createRequire to resolve the
.wasm binary from node_modules and passes it to init() via
readFileSync, avoiding fetch() and Deno.readFile() patches.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* test(cli): add coverage for --locks-required lint feature
Add 15 tests covering the lock-checking functionality merged from main:
- checkMissingLocks: standalone scripts (python, bun, bash), inline
lock file resolution (valid, empty, missing), flow inline rawscripts
(with/without locks, nested forloopflow), app inline scripts, raw
apps without backend folder
- runLint --locks-required integration: reports issues when locks
missing, skips checks when flag absent, passes when locks exist
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci(cli): replace Deno with Bun in CI workflows
- cli-tests.yml: remove Deno setup, use `bun test` instead of
`deno test`, add `bun install` step for dependency installation
- npm_on_release.yml: replace Deno setup with Bun setup for CLI
publishing
- build.sh: add `bun install` before building so CI has dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): pre-start backend in test preload and remove Deno test leftovers
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): normalize path separators for Windows compatibility
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* more tests + windows
* ci(cli): use Blacksmith runner for Windows tests
Switch test-windows job from windows-latest to blacksmith-16vcpu-windows-2025
for faster CI execution.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): fix Windows path separator expectations in unit tests
buildMetadataPath and extractResourceName normalize to forward slashes
internally, so tests should not expect platform-specific separators in
their output.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): fix Windows CI test failures for dev_server and script_run
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): set BUN_PATH and NODE_BIN_PATH for backend worker on Windows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci(cli): add SSH debug step on Windows test failure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): use native path separators for ignore check in dev mode on Windows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>