diff --git a/cli/src/commands/app/dev.ts b/cli/src/commands/app/dev.ts index 628849c375..0f487ee725 100644 --- a/cli/src/commands/app/dev.ts +++ b/cli/src/commands/app/dev.ts @@ -5,6 +5,7 @@ import { sep as SEP } from "node:path"; import * as windmillUtils from "@windmill-labs/shared-utils"; import { yamlParseFile } from "../../utils/yaml.ts"; import * as getPort from "get-port"; +import { resolveBindPort } from "../../utils/port-probe.ts"; import * as open from "open"; import { GlobalOptions } from "../../types.ts"; import * as http from "node:http"; @@ -389,11 +390,23 @@ async function dev(opts: DevOptions, appFolder?: string) { // Dynamically import esbuild only when the dev command is called const esbuild = await import("esbuild"); - const port = opts.port ?? - (await getPort.default({ - port: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((p) => p + DEFAULT_PORT), - })); const host = opts.host ?? DEFAULT_HOST; + // Probe both IPv4 and IPv6 stacks only when binding to localhost — that's + // the case where the OS may route traffic to a leftover listener on the + // other stack (see cli/src/utils/port-probe.ts). For an explicit IP host + // there's only one stack to worry about, so don't move the user's + // requested port over a phantom v6 collision. + const probeBothStacks = host === DEFAULT_HOST; + const port = opts.port !== undefined + ? (probeBothStacks + ? await resolveBindPort(opts.port, "--port", { + info: (m) => log.info(m), + warn: (m) => log.warn(m), + }) + : opts.port) + : await getPort.default({ + port: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((p) => p + DEFAULT_PORT), + }); const shouldOpen = opts.open ?? true; // Detect frameworks to determine default entry point diff --git a/cli/src/commands/app/new.ts b/cli/src/commands/app/new.ts index 0ae5065251..2072c0312c 100644 --- a/cli/src/commands/app/new.ts +++ b/cli/src/commands/app/new.ts @@ -1,4 +1,4 @@ -import { stat, writeFile, mkdir } from "node:fs/promises"; +import { stat, writeFile, mkdir, rm } from "node:fs/promises"; import { Command } from "@cliffy/command"; import { colors } from "@cliffy/ansi/colors"; import { Confirm } from "@cliffy/prompt/confirm"; @@ -12,6 +12,7 @@ import { resolveWorkspace } from "../../core/context.ts"; import { requireLogin } from "../../core/auth.ts"; import * as wmill from "../../../gen/services.gen.ts"; import path from "node:path"; +import { execSync, exec, execFile } from "node:child_process"; import { buildFolderPath, loadNonDottedPathsSetting, @@ -99,7 +100,18 @@ import "./index.css"; createApp(App).mount('#root')`; -const indexCss = `.myclass { +const indexCss = `body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background-color: #f5f5f5; + color: #1a1a1a; +} + +#root { + padding: 24px; +} + +.myclass { border: 1px solid gray; padding: 2px; }`; @@ -239,7 +251,26 @@ interface DataConfig { schema?: string; } -async function newApp(opts: GlobalOptions) { +type FrameworkKey = "react19" | "react18" | "svelte5" | "vue"; + +interface NewAppOptions extends GlobalOptions { + /** App summary (short description). Skips the prompt when provided. */ + summary?: string; + /** App path (e.g., `f/folder/my_app`). Skips the prompt when provided. */ + path?: string; + /** Framework template. Skips the prompt when provided. */ + framework?: FrameworkKey; + /** Datatable name to wire up. Skip the datatable wizard entirely if not provided. */ + datatable?: string; + /** Schema to create when --datatable is set. If omitted, no schema is created. */ + schema?: string; + /** Overwrite the target directory if it already exists, without prompting. */ + overwrite?: boolean; + /** Suppress the "Open in Claude Desktop?" prompt. */ + openInDesktop?: boolean; +} + +async function newApp(opts: NewAppOptions) { log.info(colors.bold.cyan("Create a new Windmill Raw App")); log.info(""); @@ -284,17 +315,26 @@ async function newApp(opts: GlobalOptions) { ); } - // Ask for summary - const summary = await Input.prompt({ - message: "App summary (short description):", - minLength: 1, - validate: (value: string) => { - if (value.trim().length === 0) { - return "Summary cannot be empty"; - } - return true; - }, - }); + // Ask for summary (skipped if --summary is provided) + let summary: string; + if (opts.summary !== undefined) { + if (opts.summary.trim().length === 0) { + log.error(colors.red("--summary cannot be empty")); + return; + } + summary = opts.summary; + } else { + summary = await Input.prompt({ + message: "App summary (short description):", + minLength: 1, + validate: (value: string) => { + if (value.trim().length === 0) { + return "Summary cannot be empty"; + } + return true; + }, + }); + } // Build suggestions for path autocompletion const buildPathSuggestions = (input: string): string[] => { @@ -318,32 +358,55 @@ async function newApp(opts: GlobalOptions) { return suggestions; }; - // Ask for path with validation + // Ask for path with validation (skipped if --path is provided) let appPath: string; - while (true) { - appPath = await Input.prompt({ - message: "App path (e.g., f/my_folder/my_app or u/username/my_app):", - minLength: 1, - suggestions: buildPathSuggestions, - }); + if (opts.path !== undefined) { + const validation = validateAppPath(opts.path); + if (!validation.valid) { + log.error(colors.red(`Invalid --path: ${validation.error}`)); + return; + } + appPath = opts.path; + } else { + while (true) { + appPath = await Input.prompt({ + message: "App path (e.g., f/my_folder/my_app or u/username/my_app):", + minLength: 1, + suggestions: buildPathSuggestions, + }); - const validation = validateAppPath(appPath); - if (validation.valid) { - break; + const validation = validateAppPath(appPath); + if (validation.valid) { + break; + } + log.error(colors.red(`Invalid path: ${validation.error}`)); } - log.error(colors.red(`Invalid path: ${validation.error}`)); } - // Ask for framework - const framework = await Select.prompt({ - message: "Select a framework:", - options: [ - { name: "React 19 (Recommended)", value: "react19" }, - { name: "React 18", value: "react18" }, - { name: "Svelte 5", value: "svelte5" }, - { name: "Vue 3", value: "vue" }, - ], - }); + // Ask for framework (skipped if --framework is provided) + const VALID_FRAMEWORKS: FrameworkKey[] = ["react19", "react18", "svelte5", "vue"]; + let framework: string; + if (opts.framework !== undefined) { + if (!VALID_FRAMEWORKS.includes(opts.framework)) { + log.error( + colors.red( + `Invalid --framework: ${opts.framework}. Must be one of: ${VALID_FRAMEWORKS.join(", ")}` + ) + ); + return; + } + framework = opts.framework; + } else { + framework = await Select.prompt({ + message: "Select a framework:", + options: [ + { name: "React 19 (Recommended)", value: "react19" }, + { name: "React 18", value: "react18" }, + { name: "Svelte 5", value: "svelte5" }, + { name: "Vue 3", value: "vue" }, + ], + }); + } const template = templates[framework]; if (!template) { @@ -356,7 +419,48 @@ async function newApp(opts: GlobalOptions) { let createSchemaSQL: string | undefined; let schemaName: string | undefined; - if (datatables.length > 0) { + // Treat the run as non-interactive once any required-for-non-interactive flag is set. + // In that mode, skip all datatable/overwrite/desktop prompts unless the user opted in + // via the corresponding flag. + const nonInteractive = + opts.summary !== undefined || + opts.path !== undefined || + opts.framework !== undefined; + + if (opts.datatable !== undefined) { + // Non-interactive datatable + (optional) schema configuration + if (datatables.length > 0 && !datatables.includes(opts.datatable)) { + log.warn( + colors.yellow( + `--datatable '${opts.datatable}' is not in the workspace's datatable list (${datatables.join(", ")}). Continuing anyway.` + ) + ); + } + dataConfig.datatable = opts.datatable; + if (opts.schema !== undefined) { + if (!/^[a-z_][a-z0-9_]*$/.test(opts.schema)) { + log.error( + colors.red( + `--schema must start with a letter or underscore and contain only lowercase letters, numbers, and underscores: ${opts.schema}` + ) + ); + return; + } + schemaName = opts.schema; + dataConfig.schema = schemaName; + const existingSchemas = datatableSchemas.get(opts.datatable) ?? []; + if (!existingSchemas.includes(schemaName)) { + // Emit creation SQL only if the schema doesn't already exist + createSchemaSQL = `-- Create schema for ${summary} +-- This will be executed when you run 'wmill app dev' and confirm in the modal +CREATE SCHEMA IF NOT EXISTS ${schemaName}; +`; + } + } + dataConfig.tables = []; + } else if (nonInteractive) { + // Non-interactive run with no --datatable → skip datatable config silently + } else if (datatables.length > 0) { log.info(""); log.info(colors.bold.cyan("Data Configuration")); log.info( @@ -481,19 +585,37 @@ CREATE SCHEMA IF NOT EXISTS ${schemaName}; const appDir = path.join(process.cwd(), folderName); // Check if directory already exists + let dirExists = false; try { await stat(appDir); - const overwrite = await Confirm.prompt({ - message: `Directory '${folderName}' already exists. Overwrite?`, - default: false, - }); - if (!overwrite) { - log.info(colors.yellow("Aborted.")); - return; - } + dirExists = true; } catch { // Directory doesn't exist, which is good } + if (dirExists) { + if (opts.overwrite) { + log.warn(colors.yellow(`Overwriting existing '${folderName}' (--overwrite)`)); + } else if (nonInteractive) { + log.error( + colors.red( + `Directory '${folderName}' already exists. Pass --overwrite to replace it.` + ) + ); + return; + } else { + const overwrite = await Confirm.prompt({ + message: `Directory '${folderName}' already exists. Overwrite?`, + default: false, + }); + if (!overwrite) { + log.info(colors.yellow("Aborted.")); + return; + } + } + // Wipe before re-creating so leftover files from a different framework + // (e.g. App.tsx from react18 when re-scaffolding as svelte5) don't survive. + await rm(appDir, { recursive: true, force: true }); + } await mkdir(appDir, { recursive: true }); await mkdir(path.join(appDir, "backend"), { recursive: true }); @@ -662,10 +784,140 @@ This folder is for SQL migration files that will be applied to datatables during } log.info(""); log.info(colors.gray(" 4. wmill sync push (to deploy when ready)")); + + // Offer to open in Claude Desktop. macOS-only for now: the deep-link + // handler below uses `open `, and the install path probe checks + // /Applications/Claude.app. Both are Mac-specific. + let hasClaudeDesktop = false; + if (process.platform === "darwin") { + try { + execSync("ls /Applications/Claude.app", { stdio: "ignore" }); + hasClaudeDesktop = true; + } catch { + // Claude Desktop not installed + } + } + + if (hasClaudeDesktop && !nonInteractive && opts.openInDesktop !== false) { + log.info(""); + const openInDesktop = await Confirm.prompt({ + message: "Open in Claude Desktop?", + default: true, + }); + + if (openInDesktop) { + try { + const absAppDir = path.resolve(appDir); + + // Seed the app folder with a launch.json entry pointing at `wmill app dev` + // so the freshly-opened Claude Desktop session can launch the preview + // directly. Skip if the file already exists — never clobber user edits. + const claudeDir = path.join(absAppDir, ".claude"); + const launchPath = path.join(claudeDir, "launch.json"); + if (!await stat(launchPath).catch(() => null)) { + const launchJson = JSON.stringify({ + version: "0.0.1", + configurations: [{ + name: `windmill: ${appPath}`, + runtimeExecutable: "bash", + runtimeArgs: ["-c", "wmill app dev --no-open --port ${PORT:-4000}"], + port: 4000, + autoPort: true, + }], + }, null, 2) + "\n"; + await mkdir(claudeDir, { recursive: true }); + await writeFile(launchPath, launchJson, "utf-8"); + log.info(colors.gray(`Seeded ${path.relative(process.cwd(), launchPath)}`)); + } + + const sessionId = crypto.randomUUID(); + + // Create a persisted CLI session with welcome message (async to allow spinner) + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + let i = 0; + const spinner = setInterval(() => { + process.stdout.write(`\r${colors.gray(`${frames[i++ % frames.length]} Creating Claude session...`)}`); + }, 80); + + try { + await new Promise((resolve, reject) => { + exec( + `claude --session-id "${sessionId}" -p "Say: Your app is ready, click on preview to test it!"`, + { cwd: absAppDir }, + (error) => (error ? reject(error) : resolve()) + ); + }); + } finally { + // On exec rejection control jumps to the outer catch — without this + // finally the spinner keeps writing to stdout and garbles output. + clearInterval(spinner); + process.stdout.write("\r" + " ".repeat(40) + "\r"); + } + + // Import the session into Claude Desktop Code mode. Use execFile so + // the deep link doesn't pass through a shell — `sessionId` is a UUID + // and absAppDir is URI-encoded inside the URL today, but execFile + // removes shell escaping concerns entirely. + const deepLink = `claude://resume?session=${sessionId}&cwd=${encodeURIComponent(absAppDir)}`; + execFile("open", [deepLink], (err) => { + if (err) { + log.warn( + colors.yellow( + `Could not open Claude Desktop deep link (${err.message}). Open it manually: ${deepLink}` + ) + ); + } else { + log.info(colors.bold.green("Opened in Claude Desktop!")); + } + }); + } catch (error: unknown) { + const errorMessage = + error instanceof Error ? error.message : String(error); + log.warn( + colors.yellow( + `Could not open in Claude Desktop: ${errorMessage}` + ) + ); + log.info( + colors.gray( + "You can manually run: cd " + folderName + " && claude" + ) + ); + } + } + } } const command = new Command() .description("create a new raw app from a template") + .option( + "--summary ", + "App summary (short description). Skips the prompt when provided. Triggers non-interactive mode." + ) + .option( + "--path ", + "App path (e.g., f/folder/my_app or u/username/my_app). Skips the prompt when provided. Triggers non-interactive mode." + ) + .option( + "--framework ", + "Framework template: react19 | react18 | svelte5 | vue. Skips the prompt when provided. Triggers non-interactive mode." + ) + .option( + "--datatable ", + "Datatable to wire up. Without this flag in non-interactive mode, no datatable is configured." + ) + .option( + "--schema ", + "Schema to use with --datatable. Created (CREATE SCHEMA IF NOT EXISTS) if it doesn't already exist." + ) + .option( + "--overwrite", + "Overwrite the target directory if it already exists, without prompting." + ) + .option( + "--no-open-in-desktop", + "Do not prompt to open the new app in Claude Desktop." + ) .action(newApp as any); export default command; diff --git a/cli/src/commands/dev/dev.ts b/cli/src/commands/dev/dev.ts index b49aad2073..5480070f8c 100644 --- a/cli/src/commands/dev/dev.ts +++ b/cli/src/commands/dev/dev.ts @@ -2,12 +2,13 @@ import { Command } from "@cliffy/command"; import * as log from "../../core/log.ts"; import { sep as SEP } from "node:path"; import { yamlParseFile } from "../../utils/yaml.ts"; +import { stringify as yamlStringify } from "yaml"; import { WebSocket, WebSocketServer } from "ws"; -import * as getPort from "get-port"; import * as http from "node:http"; +import * as https from "node:https"; import * as open from "open"; -import { realpath } from "node:fs/promises"; +import { access, readdir, realpath, stat, unlink, writeFile } from "node:fs/promises"; import { readTextFile } from "../../utils/utils.ts"; import { watch } from "node:fs"; import { getTypeStrFromPath, GlobalOptions } from "../../types.ts"; @@ -15,6 +16,7 @@ import { ignoreF } from "../sync/sync.ts"; import { requireLogin } from "../../core/auth.ts"; import { resolveWorkspace } from "../../core/context.ts"; import { + GLOBAL_CONFIG_OPT, SyncOptions, mergeConfigWithConfigFile, } from "../../core/conf.ts"; @@ -23,18 +25,198 @@ import { inferContentTypeFromFilePath } from "../../utils/script_common.ts"; import { OpenFlow } from "../../../gen/types.gen.ts"; import { FlowFile } from "../flow/flow.ts"; import { replaceInlineScripts, replaceAllPathScriptsWithLocal } from "../../../windmill-utils-internal/src/inline-scripts/replacer.ts"; +import { extractInlineScripts, extractCurrentMapping } from "../../../windmill-utils-internal/src/inline-scripts/extractor.ts"; +import { newPathAssigner } from "../../../windmill-utils-internal/src/path-utils/path-assigner.ts"; import { parseMetadataFile } from "../../utils/metadata.ts"; import { - getFolderSuffixWithSep, getMetadataFileName, extractFolderPath, + getNonDottedPaths, + loadNonDottedPathsSetting, } from "../../utils/resource_folders.ts"; +import * as path from "node:path"; +import * as fs from "node:fs"; import { listSyncCodebases } from "../../utils/codebase.ts"; import { createPreviewLocalScriptReader } from "../../utils/local_path_scripts.ts"; +import { resolveBindPort, BIND_HOST } from "../../utils/port-probe.ts"; +import { + snapshotPathScripts, + tagReplacedPathScripts, + restorePathScripts, +} from "./pathscript-restore.ts"; const PORT = 3001; -async function dev(opts: GlobalOptions & SyncOptions) { + +type WmPathItem = { + path: string; + kind: "flow" | "script" | "raw_app"; + summary?: string; +}; + +const FLOW_SUFFIXES = [".flow", "__flow"] as const; +const APP_SUFFIXES = [".app", "__app", ".raw_app", "__raw_app"] as const; + +// Extensions the dev round-trip might have written into a flow folder as +// inline scripts. Derived from script.ts's `exts` so adding a new language +// there auto-extends orphan cleanup; otherwise stale inline scripts of that +// language would silently linger. Excludes `.yml` — user fixtures commonly +// use it in flow folders, and leaving a stale `.playbook.yml` inline script +// is preferable to deleting a fixture. `.js` is added explicitly for +// hand-written flows that aren't in the `exts` list. +// +// Anything else (README.md, fixtures, .env*, TODO.md…) is preserved during +// orphan cleanup so we don't trample user-added files. +const INLINE_SCRIPT_EXTS = new Set([ + // path.extname(".py") === "" (Node treats ".py" as a hidden filename, not + // an extension), so prefix with a dummy character before extracting. + ...exts.map((e) => path.extname("x" + e)).filter((e) => e !== ".yml"), + ".js", +]); + +function stripFolderSuffix(rel: string, suffixes: readonly string[]): string { + for (const s of suffixes) { + if (rel.endsWith(s)) return rel.slice(0, -s.length); + } + return rel; +} + +function isFlowFolderName(name: string): boolean { + return FLOW_SUFFIXES.some((s) => name.endsWith(s)); +} + +// Normalize a windmill path: strip trailing slash and any flow folder suffix +// so f/foo, f/foo/, f/foo.flow, and f/foo.flow/ all compare equal. +function normalizeWmPath(p: string): string { + return stripFolderSuffix(p.replace(/\/$/, ""), FLOW_SUFFIXES); +} + +// Anchor on path segments — substring matches like cpath.includes(".flow/") +// also fire on innocent names like "notes_about__flow_design/readme.md". +function isInsideFlowFolder(cpath: string): boolean { + return cpath.split("/").some(isFlowFolderName); +} + +// Return the path prefix up to and including the first flow-folder segment, +// with a trailing slash. Returns undefined if no segment matches. +function findFlowFolderPrefix(cpath: string): string | undefined { + const segs = cpath.split("/"); + for (let i = 0; i < segs.length; i++) { + if (isFlowFolderName(segs[i])) { + return segs.slice(0, i + 1).join("/") + "/"; + } + } + return undefined; +} + +async function listWorkspacePaths(): Promise { + // Walk first, capturing each item's metadata file path. Then read summaries in + // parallel — one tree pass plus N file reads is faster than a serialized walk. + const items: (WmPathItem & { _metaPath?: string })[] = []; + async function walk(dir: string, rel: string) { + let entries; + try { + entries = await readdir(dir, { withFileTypes: true }); + } catch { + return; + } + for (const entry of entries) { + if (entry.name.startsWith(".") || entry.name === "node_modules") continue; + const childRel = rel ? `${rel}/${entry.name}` : entry.name; + const childAbs = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (isFlowFolderName(entry.name)) { + items.push({ + path: stripFolderSuffix(childRel, FLOW_SUFFIXES), + kind: "flow", + _metaPath: path.join(childAbs, "flow.yaml"), + }); + continue; + } + if (APP_SUFFIXES.some((s) => entry.name.endsWith(s))) { + items.push({ path: stripFolderSuffix(childRel, APP_SUFFIXES), kind: "raw_app" }); + continue; + } + await walk(childAbs, childRel); + } else if (entry.isFile()) { + const matchedExt = exts.find((ext) => entry.name.endsWith(ext)); + if (matchedExt) { + const noExtAbs = childAbs.slice(0, -matchedExt.length); + items.push({ + path: childRel.slice(0, -matchedExt.length), + kind: "script", + _metaPath: noExtAbs + ".script.yaml", + }); + } + } + } + } + await walk(process.cwd(), ""); + + await Promise.all( + items.map(async (item) => { + if (!item._metaPath) return; + try { + const meta: any = await yamlParseFile(item._metaPath); + if (typeof meta?.summary === "string" && meta.summary.length > 0) { + item.summary = meta.summary; + } + } catch { + // No metadata file or unparseable — leave summary undefined + } + }) + ); + + items.sort((a, b) => a.path.localeCompare(b.path)); + return items.map(({ _metaPath, ...item }) => item); +} + +export interface DevOpts { + proxyPort?: number; + path?: string; + open?: boolean; +} + +export async function dev(opts: GlobalOptions & SyncOptions & DevOpts) { + // Auto-detect flow folder: if no --path and cwd is a flow folder, resolve path and chdir to workspace root + if (!opts.path) { + const cwd = process.cwd(); + const cwdBasename = path.basename(cwd); + + // Need to init nonDottedPaths before checking suffix + await loadNonDottedPathsSetting(); + + if (isFlowFolderName(cwdBasename)) { + GLOBAL_CONFIG_OPT.noCdToRoot = true; + + // Find workspace root + let searchDir = cwd; + let workspaceRoot: string | undefined; + while (true) { + const wmillYaml = path.join(searchDir, "wmill.yaml"); + if (fs.existsSync(wmillYaml)) { + workspaceRoot = searchDir; + break; + } + const parentDir = path.dirname(searchDir); + if (parentDir === searchDir) break; + searchDir = parentDir; + } + + if (workspaceRoot) { + const relPath = path.relative(workspaceRoot, cwd).replaceAll("\\", "/"); + opts.path = stripFolderSuffix(relPath, FLOW_SUFFIXES); + log.info(`Detected flow folder, path: ${opts.path}`); + process.chdir(workspaceRoot); + } + } + } + opts = await mergeConfigWithConfigFile(opts); + // Normalize once so broadcastChanges' equality check survives user input + // like --path f/foo/ or --path f/foo.flow (and the same set via wmill.yaml). + if (opts.path) { + opts.path = normalizeWmPath(opts.path); + } const workspace = await resolveWorkspace(opts); await requireLogin(opts); @@ -71,12 +253,13 @@ async function dev(opts: GlobalOptions & SyncOptions) { }); } - const flowFolderSuffix = getFolderSuffixWithSep("flow"); const flowMetadataFile = getMetadataFileName("flow", "yaml"); async function loadPaths(pathsToLoad: string[]) { - const paths = pathsToLoad.filter((path) => + const paths = pathsToLoad.filter((p) => exts.some( - (ext) => path.endsWith(ext) || path.endsWith(flowFolderSuffix + flowMetadataFile) + (ext) => p.endsWith(ext) + || p.endsWith(".flow/" + flowMetadataFile) + || p.endsWith("__flow/" + flowMetadataFile) ) ); if (paths.length == 0) { @@ -84,11 +267,26 @@ async function dev(opts: GlobalOptions & SyncOptions) { } const nativePath = (await realpath(paths[0])).replace(base + SEP, ""); const cpath = nativePath.replaceAll("\\", "/"); - if (!ignore(nativePath, false)) { - const typ = getTypeStrFromPath(cpath); + // Bypass ignore for paths inside flow folders — ignore() only checks the configured + // suffix (dotted or non-dotted), but the workspace may contain both kinds + const insideFlow = isInsideFlowFolder(cpath); + if (insideFlow || !ignore(nativePath, false)) { + let typ: string; + if (insideFlow) { + // Force flow type for any file inside a flow folder — getTypeStrFromPath + // only recognises the configured suffix (dotted or non-dotted) and would + // mis-classify or throw for the other variant + typ = "flow"; + } else { + typ = getTypeStrFromPath(cpath); + } log.info("Detected change in " + cpath + " (" + typ + ")"); if (typ == "flow") { - const localPath = extractFolderPath(cpath, "flow")!; + // Try extractFolderPath, fallback to segment-anchored extraction for + // mixed suffix cases (extractFolderPath only checks the configured suffix). + let localPath = extractFolderPath(cpath, "flow") ?? findFlowFolderPrefix(cpath); + if (!localPath) return; + const wmFlowPath = stripFolderSuffix(localPath.replace(/\/$/, ""), FLOW_SUFFIXES); const localFlow = (await yamlParseFile( localPath + "flow.yaml" )) as FlowFile; @@ -100,24 +298,29 @@ async function dev(opts: GlobalOptions & SyncOptions) { SEP, undefined, ); - // Replace PathScript modules with local file content so dev mode uses local versions + // Snapshot PathScript modules before replacement, then tag after. + // Helpers walk `flowValue` (modules/failure_module/preprocessor_module), + // so pass `.value`, not the FlowFile wrapper. + snapshotPathScripts(localFlow.value); const localScriptReader = createPreviewLocalScriptReader({ exts, defaultTs: opts.defaultTs, codebases, }); await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log); + tagReplacedPathScripts(localFlow.value); currentLastEdit = { type: "flow", flow: localFlow, uriPath: localPath, + path: wmFlowPath, }; - log.info("Updated " + localPath); + log.info("Updated " + wmFlowPath); broadcastChanges(currentLastEdit); } else if (typ == "script") { - const content = await readTextFile(cpath); const splitted = cpath.split("."); const wmPath = splitted[0]; + const content = await readTextFile(cpath); const lang = inferContentTypeFromFilePath(cpath, opts.defaultTs); const typed = (await parseMetadataFile( @@ -154,38 +357,249 @@ async function dev(opts: GlobalOptions & SyncOptions) { type: "flow"; flow: OpenFlow; uriPath: string; + path: string; }; + // Load a resource by its windmill path (e.g., "u/admin/my_script" or "f/my_flow") + async function loadWmPath(wmPath: string): Promise { + wmPath = normalizeWmPath(wmPath); + // Try as flow — check both dotted and non-dotted suffixes + let flowDir: string | undefined; + let flowYaml: string | undefined; + for (const suffix of [".flow", "__flow"]) { + const candidate = wmPath + suffix + "/"; + try { + await access(candidate + "flow.yaml"); + flowDir = candidate; + flowYaml = candidate + "flow.yaml"; + break; + } catch {} + } + try { + if (!flowDir || !flowYaml) throw new Error("not a flow"); + const localFlow = (await yamlParseFile(flowYaml)) as FlowFile; + await replaceInlineScripts( + localFlow.value.modules, + async (p: string) => await readTextFile(flowDir + p), + log, + flowDir, + SEP, + undefined, + ); + snapshotPathScripts(localFlow.value); + const localScriptReader = createPreviewLocalScriptReader({ + exts, + defaultTs: opts.defaultTs, + codebases, + }); + await replaceAllPathScriptsWithLocal(localFlow.value, localScriptReader, log); + tagReplacedPathScripts(localFlow.value); + const edit: LastEditFlow = { + type: "flow", + flow: localFlow, + uriPath: flowDir, + path: wmPath, + }; + currentLastEdit = edit; + return edit; + } catch { + // Not a flow, try as script + } + + // Try as script + for (const ext of exts) { + const filePath = wmPath + ext; + try { + await access(filePath); + const content = await readTextFile(filePath); + const lang = inferContentTypeFromFilePath(filePath, opts.defaultTs); + const typed = (await parseMetadataFile(removeExtensionToPath(filePath), undefined))?.payload; + const edit: LastEditScript = { + type: "script", + content, + path: wmPath, + language: lang, + tag: typed?.tag, + lock: typed?.lock, + }; + currentLastEdit = edit; + return edit; + } catch { + continue; + } + } + + log.error(`Could not find file for path: ${wmPath}`); + return undefined; + } + + // Handle flow edits from the dev UI — write changes back to disk + // Mirrors the windmill-vscode extension's `processFlowMessage` + // (src/extension.ts). Keep them in step — the dev page is the same code in + // both contexts, and divergence here means the same flow round-trips + // differently between VS Code and the local dev preview. + // + // Deliberate divergence: the orphan-cleanup pass is restricted to + // INLINE_SCRIPT_EXTS so unrelated files (README.md, fixtures, etc.) are + // not deleted. The extension's version doesn't filter and would delete + // them — that's a known issue tracked separately. + async function handleFlowRoundTrip(data: { flow: any; uriPath: string }) { + if (!data.uriPath || !data.flow?.value) return; + + let flowDir = data.uriPath; + if (!flowDir.endsWith("/")) flowDir += "/"; + if (flowDir.includes("://")) { + flowDir = new URL(flowDir).pathname; + } + + // Restore PathScripts BEFORE extracting so we don't write a file for the + // inlined body of a `type: 'script'` reference. + restorePathScripts(data.flow.value); + + const flowYamlPath = flowDir + "flow.yaml"; + let currentLoadedFlow: any[] | undefined; + let currentLoadedFailureModule: any | undefined; + let currentLoadedPreprocessorModule: any | undefined; + try { + const currentFlow = (await yamlParseFile(flowYamlPath)) as FlowFile; + currentLoadedFlow = currentFlow.value?.modules; + currentLoadedFailureModule = currentFlow.value?.failure_module; + currentLoadedPreprocessorModule = currentFlow.value?.preprocessor_module; + } catch { + // flow.yaml doesn't exist yet or is invalid + } + + const inlineScriptMapping: Record = {}; + extractCurrentMapping( + currentLoadedFlow, + inlineScriptMapping, + currentLoadedFailureModule, + currentLoadedPreprocessorModule, + ); + + // Share one pathAssigner across all extraction calls so failure / + // preprocessor modules don't collide on filenames with main modules. + const extractOptions = { skipInlineScriptSuffix: getNonDottedPaths() }; + const pathAssigner = newPathAssigner(opts.defaultTs ?? "bun", extractOptions); + + const allExtracted = extractInlineScripts( + data.flow.value.modules ?? [], + inlineScriptMapping, + "/", + opts.defaultTs ?? "bun", + pathAssigner, + extractOptions, + ); + if (data.flow.value.failure_module?.value?.type === "rawscript") { + allExtracted.push(...extractInlineScripts( + [data.flow.value.failure_module], + inlineScriptMapping, + "/", + opts.defaultTs ?? "bun", + pathAssigner, + extractOptions, + )); + } + if (data.flow.value.preprocessor_module?.value?.type === "rawscript") { + allExtracted.push(...extractInlineScripts( + [data.flow.value.preprocessor_module], + inlineScriptMapping, + "/", + opts.defaultTs ?? "bun", + pathAssigner, + extractOptions, + )); + } + + for (const s of allExtracted) { + const filePath = flowDir + s.path; + // `!inline foo.ts` is a YAML directive that points at another file — + // treat it as a placeholder, not as content to overwrite. + if (s.content.startsWith("!inline ")) { + try { + await stat(filePath); + } catch { + await writeFile(filePath, "", "utf-8"); + } + continue; + } + let needsWrite = true; + try { + const existing = await readTextFile(filePath); + if (existing === s.content) needsWrite = false; + } catch { + // File doesn't exist + } + if (needsWrite) { + await writeFile(filePath, s.content, "utf-8"); + log.info(`Wrote inline script: ${filePath}`); + } + } + + // Only rewrite flow.yaml when the serialized YAML actually differs from + // what's on disk. Avoids noisy mtime updates that re-trigger the watcher. + const flowYaml = yamlStringify(data.flow); + let currentYaml: string | undefined; + try { + currentYaml = await readTextFile(flowYamlPath); + } catch { + // File doesn't exist + } + if (currentYaml?.trimEnd() !== flowYaml.trimEnd()) { + await writeFile(flowYamlPath, flowYaml, "utf-8"); + log.info(`Wrote flow: ${flowYamlPath}`); + } + + // Orphan cleanup: extension does this unconditionally and overshoots, + // deleting README.md / fixtures / .env.local. We restrict to known + // inline-script extensions. + const extractedPaths = new Set(allExtracted.map((s) => s.path)); + try { + const dirFiles = await readdir(flowDir); + for (const file of dirFiles) { + if (file === "flow.yaml" || file === "flow.json" || file.startsWith(".")) continue; + if (!INLINE_SCRIPT_EXTS.has(path.extname(file))) continue; + if (!extractedPaths.has(file)) { + await unlink(flowDir + file); + log.info(`Removed orphaned file: ${flowDir + file}`); + } + } + } catch { + // Directory read failed + } + } + const connectedClients: Set = new Set(); - // Function to send a message to all connected clients + // Send a message to all connected clients, gated by --path when set so we + // don't spam clients (or risk yanking their view) with edits to unrelated files. function broadcastChanges(lastEdit: LastEditScript | LastEditFlow) { + if (opts.path && normalizeWmPath(lastEdit.path) !== opts.path) { + return; + } for (const client of connectedClients.values()) { client.send(JSON.stringify(lastEdit)); } } - async function startApp() { - const server = http.createServer((_req, res) => { - res.writeHead(200); - res.end(); - }); - const wss = new WebSocketServer({ server }); - - // WebSocket server event listeners + function setupDevWs(wss: WebSocketServer) { wss.on("connection", (ws: WebSocket) => { connectedClients.add(ws); - console.log("New client connected"); + console.log("New dev client connected"); - ws.on("open", () => { - if (currentLastEdit) { - broadcastChanges(currentLastEdit); + // Push the currently loaded edit so the page renders immediately on + // page load, without waiting for a file change to trigger a broadcast. + if (currentLastEdit && ws.readyState === WebSocket.OPEN) { + try { + ws.send(JSON.stringify(currentLastEdit)); + } catch (e) { + console.error("Failed to push initial state to new client:", e); } - }); + } ws.on("close", () => { connectedClients.delete(ws); - console.log("Client disconnected"); + console.log("Dev client disconnected"); }); ws.on("message", (message: WebSocket.RawData) => { @@ -199,48 +613,278 @@ async function dev(opts: GlobalOptions & SyncOptions) { if (data.type === "load") { loadPaths([data.path]); + } else if (data.type === "flow") { + handleFlowRoundTrip(data).catch((err) => { + log.error(`Failed to write flow changes: ${err}`); + }); + } else if (data.type === "loadWmPath") { + loadWmPath(data.path).then((edit) => { + if (edit && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(edit)); + } + }).catch((err) => { + log.error(`Failed to load path ${data.path}: ${err}`); + }); + } else if (data.type === "listPaths") { + listWorkspacePaths().then((items) => { + if (ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify({ type: "paths", items })); + } + }).catch((err) => { + log.error(`Failed to list paths: ${err}`); + }); } }); }); + } - // Start the server - const port = await getPort.default({ port: 3001 }); - const url = - `${workspace.remote}dev?workspace=${workspace.workspaceId}&local=true&wm_token=${workspace.token}` + - (port === PORT ? "" : `&port=${port}`); - - console.log(`Go to ${url}`); + function maybeOpenBrowser(url: string) { + if (opts.open === false) return; try { - open.openApp(open.apps.browser, { arguments: [url] }).catch((error) => { + open.default(url).catch((error) => { console.error( `Failed to open browser, please navigate to ${url}, error: ${error}` ); }); - console.log("Opened browser for you"); + console.log(`Opened browser at ${url}`); } catch (error) { console.error( `Failed to open browser, please navigate to ${url}, ${error}` ); } + } - console.log( - "Dev server will automatically point to the last script edited locally" - ); + // --- Proxy mode (when --proxy-port is set) --- + // + // Runs a localhost HTTP server that: + // - serves the dev page from `http://localhost:/` (forwarded + // to the remote workspace), so embedders that need a localhost origin + // can render it (e.g. Claude Code's port-detection preview), and + // - upgrades local /ws connections back to this same process for the + // live-reload channel. + // + // The simpler "direct" mode below works for standalone browser tabs and the + // VS Code extension's iframe — only embedders that demand a localhost origin + // need this proxy. - server.listen(port, () => { - console.log(`Server listening on port ${port}`); + async function startProxyServer(requestedPort: number) { + // Probe both IPv4 and IPv6 stacks before binding. If the requested port is + // taken on either, walk upward to the next free one so we don't silently + // collide with a leftover dev server (see cli/src/utils/port-probe.ts). + const proxyPort = await resolveBindPort(requestedPort, "--proxy-port", { + info: (m) => console.log(m), + warn: (m) => console.warn(m), + }); + + const remote = new URL(workspace.remote); + const isHttps = remote.protocol === "https:"; + const remoteHost = remote.hostname; + const remotePort = remote.port ? parseInt(remote.port) : (isHttps ? 443 : 80); + const httpModule = isHttps ? https : http; + + const devWss = new WebSocketServer({ noServer: true }); + setupDevWs(devWss); + + const proxyWss = new WebSocketServer({ noServer: true }); + + const proxyServer = http.createServer((clientReq, clientRes) => { + const parsedUrl = new URL(clientReq.url ?? "/", `http://localhost`); + if (parsedUrl.pathname === "/" || parsedUrl.pathname === "") { + let devUrl = `/dev?workspace=${workspace.workspaceId}&local=true&wm_token=${workspace.token}&port=${proxyPort}`; + if (opts.path) { + devUrl += `&path=${opts.path}`; + } + clientRes.writeHead(302, { Location: devUrl }); + clientRes.end(); + return; + } + + const fwdHeaders: Record = { + ...clientReq.headers, + host: remote.host, + }; + delete fwdHeaders["connection"]; + delete fwdHeaders["keep-alive"]; + delete fwdHeaders["transfer-encoding"]; + delete fwdHeaders["accept-encoding"]; + + const proxyOpts: http.RequestOptions = { + hostname: remoteHost, + port: remotePort, + path: clientReq.url, + method: clientReq.method, + headers: fwdHeaders, + }; + + const proxyReq = httpModule.request(proxyOpts, (proxyRes) => { + const setCookie = proxyRes.headers["set-cookie"]; + if (setCookie) { + proxyRes.headers["set-cookie"] = setCookie.map((cookie) => + cookie.replace(/domain=[^;]+/gi, "domain=localhost") + ); + } + clientRes.writeHead(proxyRes.statusCode ?? 502, proxyRes.headers); + proxyRes.pipe(clientRes, { end: true }); + }); + + proxyReq.on("error", (err) => { + console.error("Proxy error:", err.message); + clientRes.writeHead(502); + clientRes.end("Bad Gateway"); + }); + + clientReq.pipe(proxyReq, { end: true }); + }); + + // WebSocket upgrades + proxyServer.on("upgrade", (req, socket, head) => { + const pathname = req.url?.split("?")[0] ?? ""; + + if (pathname === "/ws_dev" || pathname === "/ws") { + devWss.handleUpgrade(req, socket, head, (ws) => { + devWss.emit("connection", ws, req); + }); + return; + } + + if (pathname.startsWith("/ws/") || pathname.startsWith("/ws_mp/") || pathname.startsWith("/ws_debug/")) { + const wsProtocol = isHttps ? "wss" : "ws"; + const remoteWsUrl = `${wsProtocol}://${remote.host}${req.url}`; + const remoteWs = new WebSocket(remoteWsUrl, { + headers: { + ...req.headers, + host: remote.host, + }, + }); + + remoteWs.on("open", () => { + proxyWss.handleUpgrade(req, socket, head, (clientWs) => { + clientWs.on("message", (data) => { + if (remoteWs.readyState === WebSocket.OPEN) { + remoteWs.send(data); + } + }); + remoteWs.on("message", (data) => { + if (clientWs.readyState === WebSocket.OPEN) { + clientWs.send(data); + } + }); + clientWs.on("close", () => remoteWs.close()); + remoteWs.on("close", () => clientWs.close()); + }); + }); + + remoteWs.on("error", (err) => { + console.error("WebSocket proxy error:", err.message); + socket.destroy(); + }); + return; + } + + socket.destroy(); + }); + + return new Promise((resolve) => { + proxyServer.listen(proxyPort, BIND_HOST, () => { + console.log(`Dev proxy listening on http://localhost:${proxyPort}`); + if (opts.path) { + console.log(`Watching ${opts.path} — edits will live-reload in the dev page`); + } else { + console.log( + "Open the dev page and pick a flow or script to preview — edits will live-reload" + ); + console.log("(pass --path to skip the picker)"); + } + maybeOpenBrowser(`http://localhost:${proxyPort}/`); + resolve(); + }); }); } - await Promise.all([startApp(), watchChanges()]); + // --- Direct mode (no localhost HTTP proxy) --- + // + // The browser loads the dev page from the remote workspace URL and opens a + // WebSocket directly to this localhost server. Used when: + // - the user runs `wmill dev` and opens a regular browser tab, or + // - the VS Code extension iframe loads the dev page (its iframe URL omits + // `local=true`, so it never opens this WS, but everything else still + // functions through the existing remote workspace connection). + // + // This is the simplest topology: a bare WebSocket server. The reverse-proxy + // mode (above) is only needed when something needs to embed the dev UI on a + // localhost origin (Claude Code's port-detection preview). + + async function startDirectServer() { + const server = http.createServer((_req, res) => { + res.writeHead(200); + res.end(); + }); + const wss = new WebSocketServer({ server }); + setupDevWs(wss); + + // Probe both IPv4 and IPv6 stacks before binding. Same dual-stack + // collision risk as proxy mode: a leftover wmill dev on [::]:3001 would + // silently steer localhost:3001 traffic to the wrong process if we only + // probed one stack (which is what getPort does). + const port = await resolveBindPort(PORT, "wmill dev", { + info: (m) => console.log(m), + warn: (m) => console.warn(m), + }); + const url = + `${workspace.remote}dev?workspace=${workspace.workspaceId}&local=true&wm_token=${workspace.token}` + + (port === PORT ? "" : `&port=${port}`) + + (opts.path ? `&path=${opts.path}` : ""); + + if (opts.open === false) { + console.log(`Go to ${url}`); + } + maybeOpenBrowser(url); + + if (opts.path) { + console.log(`Watching ${opts.path} — edits will live-reload in the dev page`); + } else { + console.log( + "Open the dev page and pick a flow or script to preview — edits will live-reload" + ); + } + + server.listen(port, BIND_HOST, () => { + console.log(`Dev WebSocket listening on ws://localhost:${port}/ws`); + }); + } + + // --- Start --- + + // If --path is set, load it immediately + if (opts.path) { + await loadWmPath(opts.path); + } + + const startServer = opts.proxyPort + ? () => startProxyServer(opts.proxyPort!) + : () => startDirectServer(); + + await Promise.all([startServer(), watchChanges()]); console.log("Stopped dev mode"); } const command = new Command() - .description("Launch a dev server that watches for local file changes and auto-pushes them to the remote workspace. Provides live reload for scripts and flows during development.") + .description("Watch local file changes and live-reload the dev page for preview. Does NOT deploy to the remote workspace — use wmill sync push for that.") .option( "--includes ", - "Filter paths givena glob pattern or path" + "Filter paths given a glob pattern or path" + ) + .option( + "--proxy-port ", + "Port for a localhost reverse proxy to the remote Windmill server" + ) + .option( + "--path ", + "Watch a specific windmill path (e.g., u/admin/my_script or f/my_flow)" + ) + .option( + "--no-open", + "Do not open the browser automatically" ) .action(dev as any); diff --git a/cli/src/commands/dev/pathscript-restore.ts b/cli/src/commands/dev/pathscript-restore.ts new file mode 100644 index 0000000000..b7105fe76a --- /dev/null +++ b/cli/src/commands/dev/pathscript-restore.ts @@ -0,0 +1,150 @@ +/** + * Snapshot / tag / restore PathScript modules across the dev-page round-trip. + * + * **This file is a port of the windmill-vscode extension's + * `src/utils/pathscript-restore.ts`.** Keep them in sync — divergence here + * means the same flow round-trips differently in VS Code and Claude Code's + * local dev preview, which is exactly the kind of bug we worked hard to + * make impossible. + * + * ## Why this exists + * + * The dev page can't render PathScript modules (`type: 'script'`, `path:` ref) + * directly — it has no way to fetch the referenced script's body. Before + * sending a flow to the page we inline every PathScript's content via + * `replaceAllPathScriptsWithLocal`, turning each into a rawscript shape the + * page can render in its editor pane. + * + * If we did nothing else, the page would round-trip those rawscripts back + * verbatim — silently overwriting the user's original `path:` references + * with frozen snapshots. Reusability gone. + * + * The protocol: + * 1. `snapshotPathScripts` — stash original PathScript values on each + * module (and AI-agent tool) before inlining. + * 2. *(Caller runs `replaceAllPathScriptsWithLocal` here.)* + * 3. `tagReplacedPathScripts` — move the snapshot inside `value{}` so it + * rides along through serialization. The dev page treats it as opaque. + * 4. *(Page round-trips the flow back over WS / postMessage.)* + * 5. `restorePathScripts` — swap `value` back to the saved snapshot. + * The user's edits to the inlined body are deliberately dropped (you + * edit a PathScript by opening its file directly, not through the + * flow editor). + * + * The `_originalPathScript` tag key is the contract between this file and + * the dev page's serialization. Don't rename it without coordinating. + */ + +const TAG_KEY = "_originalPathScript" as const; + +interface ModuleVisitor { + onModule(module: any): void; + onTool(tool: any): void; +} + +/** + * Recursively walks all modules in a flow value, visiting leaf modules and + * AI agent tools. Handles branchone, branchall, forloopflow, whileloopflow, + * and aiagent nesting. + */ +function walkModules(modules: any[], visitor: ModuleVisitor) { + for (const module of modules) { + if (!module.value) continue; + const val = module.value; + if (val.type === "forloopflow" || val.type === "whileloopflow") { + walkModules(val.modules, visitor); + } else if (val.type === "branchall") { + for (const branch of val.branches ?? []) { + walkModules(branch.modules, visitor); + } + } else if (val.type === "branchone") { + for (const branch of val.branches ?? []) { + walkModules(branch.modules, visitor); + } + if (val.default) { + walkModules(val.default, visitor); + } + } else if (val.type === "aiagent") { + for (const tool of val.tools ?? []) { + visitor.onTool(tool); + } + } else { + visitor.onModule(module); + } + } +} + +function walkFlow(flowValue: any, visitor: ModuleVisitor) { + if (flowValue?.modules) walkModules(flowValue.modules, visitor); + if (flowValue?.failure_module) walkModules([flowValue.failure_module], visitor); + if (flowValue?.preprocessor_module) walkModules([flowValue.preprocessor_module], visitor); +} + +/** + * Must be called BEFORE `replaceAllPathScriptsWithLocal` to snapshot the + * original PathScript values onto each module / AI-agent tool. + */ +export function snapshotPathScripts(flowValue: any) { + walkFlow(flowValue, { + onModule(module) { + if (module.value.type === "script") { + module[TAG_KEY] = JSON.parse(JSON.stringify(module.value)); + } + }, + onTool(tool) { + const tv = tool.value; + if (tv && "tool_type" in tv && tv.tool_type === "flowmodule" && tv.type === "script") { + tool[TAG_KEY] = JSON.parse(JSON.stringify(tv)); + } + }, + }); +} + +/** + * After `replaceAllPathScriptsWithLocal` has mutated the flow, call this to + * move each snapshot from `module[TAG_KEY]` into `module.value[TAG_KEY]` so + * it survives serialization to the dev page (the page only forwards what's + * inside `value`). + */ +export function tagReplacedPathScripts(flowValue: any) { + walkFlow(flowValue, { + onModule(module) { + if (module[TAG_KEY] && module.value.type === "rawscript") { + module.value[TAG_KEY] = module[TAG_KEY]; + delete module[TAG_KEY]; + } else if (module[TAG_KEY]) { + // Snapshotted but not replaced (local file not found) — clean up. + delete module[TAG_KEY]; + } + }, + onTool(tool) { + const tv = tool.value; + if (tool[TAG_KEY] && tv && "tool_type" in tv && tv.tool_type === "flowmodule" && tv.type === "rawscript") { + tv[TAG_KEY] = tool[TAG_KEY]; + delete tool[TAG_KEY]; + } else if (tool[TAG_KEY]) { + delete tool[TAG_KEY]; + } + }, + }); +} + +/** + * Restores PathScript modules in a flow returned from the dev page. + * Any module/tool with a `_originalPathScript` tag inside its `value` gets + * restored unconditionally; the tag is removed after restoration. + */ +export function restorePathScripts(flowValue: any) { + walkFlow(flowValue, { + onModule(module) { + if (module.value[TAG_KEY]) { + module.value = module.value[TAG_KEY]; + } + }, + onTool(tool) { + if (tool.value?.[TAG_KEY]) { + tool.value = tool.value[TAG_KEY]; + } + }, + }); +} diff --git a/cli/src/commands/flow/flow.ts b/cli/src/commands/flow/flow.ts index 0fa59c84ea..efd05c376a 100644 --- a/cli/src/commands/flow/flow.ts +++ b/cli/src/commands/flow/flow.ts @@ -733,6 +733,12 @@ export async function bootstrap( const metadataFile = getMetadataFileName("flow", "yaml"); const flowYamlPath = `${flowDirFullPath}/${metadataFile}`; writeFileSync(flowYamlPath, newFlowDefinitionYaml, { flag: "wx", encoding: "utf-8" }); + + log.info(colors.green(`Created flow at ${flowDirFullPath}`)); + + log.info(""); + log.info(colors.bold("To preview this flow:")); + log.info(colors.gray(` wmill dev --path ${flowPath}`)); } async function history( diff --git a/cli/src/commands/init/init.ts b/cli/src/commands/init/init.ts index 828da425a0..a77596ae9f 100644 --- a/cli/src/commands/init/init.ts +++ b/cli/src/commands/init/init.ts @@ -81,13 +81,20 @@ async function initAction(opts: InitOptions) { : undefined; } else { const activeProfile = await getActiveWorkspace(opts as GlobalOptions); + const orderedProfiles = activeProfile + ? [ + ...profiles.filter((p) => p.name === activeProfile.name), + ...profiles.filter((p) => p.name !== activeProfile.name), + ] + : profiles; const selectedName = await Select.prompt({ message: "Select workspace profile", - options: profiles.map((p) => ({ - name: `${p.name} (${p.workspaceId} on ${p.remote})`, + options: orderedProfiles.map((p) => ({ + name: `${p.name} (${p.workspaceId} on ${p.remote})${ + activeProfile?.name === p.name ? " — active" : "" + }`, value: p.name, })), - default: activeProfile?.name, }); selectedProfile = profiles.find((p) => p.name === selectedName); } @@ -234,14 +241,14 @@ async function initAction(opts: InitOptions) { } } - // Read nonDottedPaths from config to specialize generated skills + // Read nonDottedPaths from config let nonDottedPaths = true; // default for new inits try { const { readConfigFile } = await import("../../core/conf.ts"); const config = await readConfigFile(); nonDottedPaths = config.nonDottedPaths ?? true; } catch { - // If config can't be read, use default + // If config can't be read, use defaults } // Create guidance files (AGENTS.md, CLAUDE.md, and agent skills) diff --git a/cli/src/commands/sync/sync.ts b/cli/src/commands/sync/sync.ts index ccf2b2bc19..101a1cbe05 100644 --- a/cli/src/commands/sync/sync.ts +++ b/cli/src/commands/sync/sync.ts @@ -2559,6 +2559,7 @@ export async function pull( false, ); } + if (tracker.apps.length > 0) { log.info( colors.gray( diff --git a/cli/src/core/login.ts b/cli/src/core/login.ts index 516b3fb4c8..f6034b3add 100644 --- a/cli/src/core/login.ts +++ b/cli/src/core/login.ts @@ -82,7 +82,7 @@ export async function browserLogin( log.info(`Login by going to ${url}`); try { - open.openApp(open.apps.browser, { arguments: [url] }).catch((error) => { + open.default(url).catch((error) => { console.error( `Failed to open browser, please navigate to ${url}, error: ${error}` ); diff --git a/cli/src/guidance/core.ts b/cli/src/guidance/core.ts index c0de0a4a0a..ee519e170f 100644 --- a/cli/src/guidance/core.ts +++ b/cli/src/guidance/core.ts @@ -27,11 +27,12 @@ You MUST use the \`write-script-\` skill to write or modify scripts in ## Flow Writing Guide You MUST use the \`write-flow\` skill to create or modify flows. +When a new flow needs to be created, YOU run \`wmill flow new \` yourself (with \`--summary\` and optional \`--description\`) to scaffold the folder and \`flow.yaml\`, then edit \`flow.yaml\` to fill in modules and schema. Do NOT scaffold the folder + yaml by hand and do NOT tell the user to run \`wmill flow new\`. If path or summary are missing from the user's request, ask via \`AskUserQuestion\` (one call, all missing fields) — never invent them. See the \`write-flow\` skill for the procedure. ## Raw App Development You MUST use the \`raw-app\` skill to create or modify raw apps. -Whenever a new app needs to be created you MUST ask the user to run \`wmill app new\` in its terminal first. +When a new app needs to be created, YOU run \`wmill app new\` yourself with \`--summary\`, \`--path\`, and \`--framework\` flags (and any other relevant flags). Do NOT ask the user to run it. If you don't have the values for those flags, ask the user via \`AskUserQuestion\` (one call, all missing fields) — never invent them. See the \`raw-app\` skill for the full procedure. ## Triggers @@ -45,6 +46,10 @@ You MUST use the \`schedules\` skill to configure cron schedules. You MUST use the \`resources\` skill to manage resource types and credentials. +## Visual Preview + +You MUST use the \`preview\` skill any time the user wants to see/open/visualize/preview a flow, script, or app in the dev page — and after writing one, when offering visual verification. The skill picks between an MCP-embedded proxy (one named \`launch.json\` entry per target) and direct mode (URL handed to the user) based on what tools you have. + ## CLI Reference You MUST use the \`cli-commands\` skill to use the CLI. diff --git a/cli/src/guidance/skills.ts b/cli/src/guidance/skills.ts index 440567e680..6a5a31c974 100644 --- a/cli/src/guidance/skills.ts +++ b/cli/src/guidance/skills.ts @@ -33,6 +33,7 @@ export const SKILLS: SkillMetadata[] = [ { name: "schedules", description: "MUST use when configuring schedules." }, { name: "resources", description: "MUST use when managing resources." }, { name: "cli-commands", description: "MUST use when using the CLI, including debugging job failures and inspecting run history via `wmill job`." }, + { name: "preview", description: "MUST use when opening the Windmill dev page / visual preview of a flow, script, or app. Triggers on words like preview, open, navigate to, visualize, see the flow/app/script, and after writing a flow/script/app for visual verification." }, ]; // Skill content for each skill (loaded inline for bundling) @@ -44,11 +45,36 @@ description: MUST use when writing Bash scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -109,11 +135,36 @@ description: MUST use when writing BigQuery queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -136,11 +187,36 @@ description: MUST use when writing Bun/TypeScript scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -803,11 +879,36 @@ description: MUST use when writing Bun Native scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -1468,11 +1569,36 @@ description: MUST use when writing C# scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -1525,11 +1651,36 @@ description: MUST use when writing Deno/TypeScript scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2196,11 +2347,36 @@ description: MUST use when writing DuckDB queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2263,11 +2439,36 @@ description: MUST use when writing Go scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2337,11 +2538,36 @@ description: MUST use when writing GraphQL queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2398,11 +2624,36 @@ description: MUST use when writing Java scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2452,11 +2703,36 @@ description: MUST use when writing MS SQL Server queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2479,11 +2755,36 @@ description: MUST use when writing MySQL queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -2506,11 +2807,36 @@ description: MUST use when writing Native TypeScript scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -3138,11 +3464,36 @@ description: MUST use when writing PHP scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -3211,11 +3562,36 @@ description: MUST use when writing PostgreSQL queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -3238,11 +3614,36 @@ description: MUST use when writing PowerShell scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -3309,11 +3710,36 @@ description: MUST use when writing Python scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -4135,11 +4561,36 @@ description: MUST use when writing R scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -4236,11 +4687,36 @@ description: MUST use when writing Rust scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -4327,11 +4803,36 @@ description: MUST use when writing Snowflake queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate .script.yaml and .lock files -- \`wmill sync push\` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- \`wmill script preview \` — **default when iterating on a local script.** Runs the local file without deploying. +- \`wmill script run \` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — generate \`.script.yaml\` and \`.lock\` files for the script you modified. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use \`script preview\`. Do NOT push the script to then \`script run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`script run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run \`wmill script preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute \`wmill script preview -d ''\` directly — pick plausible args from the script's declared parameters. The shape varies by language: \`main(...)\` for code languages, the SQL dialect's own placeholder syntax (\`$1\` for PostgreSQL, \`?\` for MySQL/Snowflake, \`@P1\` for MSSQL, \`@name\` for BigQuery, etc.), positional \`$1\`, \`$2\`, … for Bash, \`param(...)\` for PowerShell. + +\`wmill script preview\` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than \`script preview\`'s run-and-print-result), use the \`preview\` skill. Use \`wmill resource-type list --schema\` to discover available resource types. @@ -4354,15 +4855,77 @@ description: MUST use when creating flows. # Windmill Flow Building Guide -## CLI Commands +## Creating a Flow + +**You — the AI agent — scaffold the flow yourself by running \`wmill flow new \` with the right flags. Do NOT hand-create the folder + \`flow.yaml\`, and do NOT tell the user to "run \`wmill flow new\` and follow the prompts".** + +\`wmill flow new\` creates the folder with the correct suffix (\`{{FLOW_SUFFIX}}\` or \`.flow\` depending on the workspace's \`nonDottedPaths\` setting), writes a minimal \`flow.yaml\` shell, and prints Claude-specific next-step hints. Scaffolding by hand skips all of that and often picks the wrong suffix. + +### Step 1 — Gather path + summary by asking the user + +You need two things: + +1. **path** — the windmill path, e.g. \`f/folder/my_flow\` or \`u/username/my_flow\`. +2. **summary** — a short description of the flow. + +If the user's request didn't supply both, ask for both in a single round-trip. Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and provide one or two example values for each (with an "Other" / free-form fallback). Do not guess paths or summaries. + +### Step 2 — Run the command yourself + +\`\`\`bash +wmill flow new f/folder/my_flow --summary "Short description" +\`\`\` + +Add \`--description "..."\` when the user provided a longer explanation worth preserving separately from the summary. + +### Step 3 — Fill in \`flow.yaml\` + +Open the generated \`flow.yaml\` (under the folder the command just created) and replace the empty \`value.modules\` + \`schema\` with the real flow definition. -Create a folder ending with \`{{FLOW_SUFFIX}}\` and add a \`flow.yaml\` file with the flow definition. For rawscript modules, use \`!inline path/to/script.ts\` for the content key. {{INLINE_SCRIPT_NAMING}} -After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate lock files for the flow you modified -- \`wmill sync push\` - Deploy to Windmill -Do NOT run these commands yourself. Instead, inform the user that they should run them. +Once the flow has real content, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a \`launch.json\` entry) and the user should consent. + +### Anti-patterns to avoid + +- ❌ Hand-creating the \`{{FLOW_SUFFIX}}\` folder + \`flow.yaml\` instead of running \`wmill flow new\`. You'll miss the suffix-setting resolution, the default shape, and the Claude hints. +- ❌ Telling the user to "run \`wmill flow new \`" — you can and should run it yourself. +- ❌ Inventing a path/summary instead of asking the user. + +## CLI Commands — running, previewing, deploying + +After writing, tell the user which command fits what they want to do: + +- \`wmill flow preview \` — **default when iterating on a local flow.** Runs the local \`flow.yaml\` against local inline scripts without deploying. Add \`--remote\` to use deployed workspace scripts for PathScript steps instead of local files. +- \`wmill flow run \` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — regenerate stale \`.lock\` and \`.script.yaml\` files. By default it scans **scripts, flows, and apps** across the workspace; pass \`--skip-flows --skip-apps\` (or run from a subdirectory) to limit the scope when you only care about the flow you edited. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the flow", "try it", "test it", "does it work" while there are **local edits to a \`flow.yaml\`**, use \`flow preview\`. Do NOT push the flow to then \`flow run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`flow run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local \`flow.yaml\` being edited (you're just invoking an existing flow). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to run, don't wait passively + +This is about **programmatic execution** (\`wmill flow preview -d ''\`), which actually runs the flow and has side effects. Visual preview (the \`preview\` skill) is offered separately — see "Visual preview" below. + +If the user hasn't already told you to run/test the flow, offer it as a one-sentence next step (e.g. "Want me to run \`wmill flow preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the flow in their original request, skip the offer and just execute \`wmill flow preview -d ''\` directly — pick plausible args from the flow's input schema. + +\`wmill flow preview\` is safe to run yourself (it does not deploy). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +### Visual preview + +To open the flow visually in the dev page (graph + live reload), use the \`preview\` skill. Always **offer** it as a one-sentence next step (e.g. "Want me to open the visual preview?") rather than opening it automatically — opening the dev page has side effects (browser window, possibly a \`launch.json\` entry under MCP-preview branches) the user should consent to. If the user already asked to see/preview/visualize the flow in their original request, skip the offer and just invoke the skill. ## OpenFlow Schema @@ -4671,11 +5234,70 @@ Raw apps let you build custom frontends with React, Svelte, or Vue that connect ## Creating a Raw App +**You — the AI agent — create the app yourself by running \`wmill app new\` with the right flags. Do NOT tell the user to "run \`wmill app new\` and follow the prompts" or wait for them to do it.** The bare \`wmill app new\` is an interactive wizard that hangs waiting for stdin in any non-TTY context (which includes you). Always pass flags. + +### Step 1 — Gather the three required values by asking the user + +You need three things to run the command: + +1. **summary** — a short description of the app +2. **path** — the windmill path, e.g. \`f/folder/my_app\` or \`u/username/my_app\` +3. **framework** — one of \`react19\` (recommended), \`react18\`, \`svelte5\`, \`vue\` + +If the user's request did not supply *every* one of these explicitly, ask. Do not guess values, do not invent paths, do not pick a framework on the user's behalf, do not "just use react19 because it's the default". + +Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and group all missing fields into a single round-trip so the user answers them at once: + +- For \`framework\` — multiple-choice with the four allowed values; mark \`react19\` as \`(Recommended)\` and put it first. +- For \`summary\` and \`path\` — provide one or two example values as multiple-choice options (the user can pick "Other" to type a free-form answer). + +Only proceed once you have concrete values for all three. If the user replies with something ambiguous, ask again rather than guessing. + +### Step 2 — Run the command yourself + +Once you have summary + path + framework, run it: + +\`\`\`bash +wmill app new \\ + --summary "Customer dashboard" \\ + --path f/sales/dashboard \\ + --framework react19 +\`\`\` + +That's the minimum. The datatable wizard and the "Open in Claude Desktop?" prompt are skipped silently because passing any of \`--summary\`/\`--path\`/\`--framework\` puts the command in non-interactive mode. + +### Optional flags + +Layer these in only when the user asked for them: + +| Flag | When to add it | +|---|---| +| \`--datatable \` | The user wants this app wired to a specific Windmill datatable. Without it, the app is created with no datatable. | +| \`--schema \` | Together with \`--datatable\`. Creates the schema with \`CREATE SCHEMA IF NOT EXISTS\` if it doesn't already exist. | +| \`--overwrite\` | The target directory already exists and the user said it's OK to replace. Without it, non-interactive mode aborts with an error so you don't clobber existing work. | +| \`--no-open-in-desktop\` | Already implied in non-interactive mode; only needed if you're somehow running interactively. | + +### Step 3 — Offer the visual preview + +After \`wmill app new\` and any initial edits to \`App.tsx\` / \`index.tsx\`, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a \`launch.json\` entry when an embedded preview tool is in play) the user should consent to. + +For apps the preview command runs from the app folder (\`cd __raw_app && wmill app dev …\`); the \`preview\` skill picks the proxy vs direct branch based on whether the runtime exposes a tool that can embed a localhost URL. If the user already asked to see/preview/visualize the app in their original request, skip the offer and just invoke the skill. + +### Anti-patterns to avoid + +- ❌ Running \`wmill app new\` with no flags (the prompt will hang). +- ❌ Telling the user to "run \`wmill app new\` and follow the prompts" — that's a step backwards from what you can do directly. +- ❌ Inventing a path/summary/framework instead of asking the user. +- ❌ Defaulting to \`react19\` because the user didn't say — even sensible defaults must be confirmed. +- ❌ Passing \`--overwrite\` automatically when the directory exists — confirm with the user first. + +### Interactive (only when a human is at the terminal) + \`\`\`bash wmill app new \`\`\` -This interactive command creates a complete app structure with your choice of frontend framework (React, Svelte, or Vue). +This is the wizard. It only works when run by a human in a real terminal. Don't call it this way from an agent. ## App Structure @@ -4899,12 +5521,13 @@ data: ## CLI Commands -Tell the user they can run these commands (do NOT run them yourself): +\`wmill app new\` is the exception: you run it yourself, with flags, per the "Creating a Raw App" section above. + +For everything else, tell the user which command fits their intent and let them run it — these touch the workspace or local lock files, and the user should consent each time: | Command | Description | |---------|-------------| -| \`wmill app new\` | Create a new raw app interactively | -| \`wmill app dev\` | Start dev server with live reload | +| \`wmill app dev\` | Start dev server with live reload (see the \`preview\` skill for the full open-the-app-in-the-IDE-pane procedure). | | \`wmill app generate-agents\` | Refresh AGENTS.md and DATATABLES.md | | \`wmill generate-metadata\` | Generate lock files for backend runnables | | \`wmill sync push\` | Deploy app to Windmill | @@ -5293,6 +5916,13 @@ app related commands - \`app lint [app_folder:string]\` - Lint a raw app folder to validate structure and buildability - \`--fix\` - Attempt to fix common issues (not implemented yet) - \`app new\` - create a new raw app from a template + - \`--summary \` - App summary (short description). Skips the prompt when provided. Triggers non-interactive mode. + - \`--path \` - App path (e.g., f/folder/my_app or u/username/my_app). Skips the prompt when provided. Triggers non-interactive mode. + - \`--framework \` - Framework template: react19 | react18 | svelte5 | vue. Skips the prompt when provided. Triggers non-interactive mode. + - \`--datatable \` - Datatable to wire up. Without this flag in non-interactive mode, no datatable is configured. + - \`--schema \` - Schema to use with --datatable. Created (CREATE SCHEMA IF NOT EXISTS) if it doesn't already exist. + - \`--overwrite\` - Overwrite the target directory if it already exists, without prompting. + - \`--no-open-in-desktop\` - Do not prompt to open the new app in Claude Desktop. - \`app generate-agents [app_folder:string]\` - regenerate AGENTS.md and DATATABLES.md from remote workspace - \`app set-permissioned-as \` - Set the on_behalf_of_email for an app (requires admin or wm_deployers group) @@ -5329,10 +5959,13 @@ workspace dependencies related commands ### dev -Launch a dev server that watches for local file changes and auto-pushes them to the remote workspace. Provides live reload for scripts and flows during development. +Watch local file changes and live-reload the dev page for preview. Does NOT deploy to the remote workspace — use wmill sync push for that. **Options:** -- \`--includes \` - Filter paths givena glob pattern or path +- \`--includes \` - Filter paths given a glob pattern or path +- \`--proxy-port \` - Port for a localhost reverse proxy to the remote Windmill server +- \`--path \` - Watch a specific windmill path (e.g., u/admin/my_script or f/my_flow) +- \`--no-open\` - Do not open the browser automatically ### docs @@ -5878,6 +6511,133 @@ workspace related commands - \`--team-name \` - Slack team name - \`workspace disconnect-slack\` +`, + "preview": `--- +name: preview +description: MUST use when opening the Windmill dev page / visual preview of a flow, script, or app. Triggers on words like preview, open, navigate to, visualize, see the flow/app/script, and after writing a flow/script/app for visual verification. +--- + +# Windmill Preview Workflow + +Use this skill any time the user wants to **see**, **open**, **navigate to**, **visualize**, or **preview** a flow, script, or app — and any time you've just finished writing one and want to offer visual verification. + +The Windmill dev page renders the flow graph / script editor, lets the user step through steps, and live-reloads on every save. It runs locally via \`wmill dev\` and is reached on a localhost port. + +## Two independent decisions + +### 1. Mode: proxy or direct? + +\`wmill dev\` runs in two modes; pick by asking what kind of URL whatever will display the preview needs. + +- **Proxy** (\`--proxy-port \`) — exposes the dev page on \`http://localhost:/\`. Use it when the embedder you'll hand the URL to **only accepts localhost URLs** (most in-IDE / in-chat preview embedders do, because they sandbox cross-origin loads). +- **Direct** (default) — the user's browser loads the dev page from the remote workspace's HTTPS URL; the local \`wmill dev\` only runs the WebSocket back-channel for live reload. Use it when the URL will be opened in a regular browser tab. + +Default to **direct** unless you have a specific embedder that needs localhost. + +### 2. Who starts the server? + +- **You start it** in the background. Spawn \`wmill dev …\` (or \`wmill app dev …\`) yourself, capture the URL it prints, do whatever's next (open a tab, hand the URL to an embedder). +- **The runtime starts it from \`.claude/launch.json\`.** Some runtimes (currently the Claude Desktop / Claude Code MCP preview integration — tools prefixed with \`mcp__Claude_Preview__\`) can read a \`launch.json\` configuration and launch the dev server on demand when you invoke their preview tool. **Only take this path if you actually have such a tool** — otherwise nothing reads the file and \`wmill dev\` never starts. + +The two decisions compose. The common cases: + +| Embedder | Needs localhost? | launch.json runtime? | What to do | +|---|---|---|---| +| Regular browser tab | No | n/a | Direct mode, you start it, give URL to user | +| IDE / chat preview pane that takes any URL | No | No | Direct mode, you start it, point the embedder at the printed URL | +| IDE / chat preview pane that only accepts localhost | Yes | No | Proxy mode, you start it, point the embedder at \`http://localhost:/\` | +| Claude Desktop / Code MCP preview | Yes | Yes | Proxy mode, write a \`launch.json\` entry, invoke the MCP tool | + +Never start the proxy "just in case" — it adds the localhost hop for no benefit when no embedder needs it. + +## Starting the server yourself + +Use this when no \`launch.json\`-aware runtime is available, regardless of mode. + +For flows / scripts: +\`\`\`bash +# Direct mode — gives you the remote dev-page URL +wmill dev --path --no-open + +# Proxy mode — gives you a localhost URL that 302s to the remote dev page +wmill dev --proxy-port 4000 --path --no-open +\`\`\` + +For apps: +\`\`\`bash +cd __raw_app && wmill app dev --no-open --port 4000 +\`\`\` + +Each command prints the URL on stdout. Line shapes differ: + +- \`wmill dev --no-open\` (direct) prints \`Go to \` with the full remote URL (workspace, token, path baked in). +- \`wmill dev --proxy-port\` prints \`Dev proxy listening on http://localhost:\` — the URL to hand to an embedder is \`http://localhost:/\`. +- \`wmill app dev --no-open\` prints \`🚀 Dev server running at \` — the local app server. + +Capture the URL with a loose match (the first \`https?://…\` token after startup) and either hand it to your embedder or relay it to the user: *"Preview is running — open \`\` in your browser."* Don't construct the URL yourself; you don't have the workspace ID or auth token. + +These commands are long-running — start them in the background, don't block waiting. + +## Letting \`launch.json\` start the server (Claude Desktop / Code MCP only) + +Take this path when **and only when** an \`mcp__Claude_Preview__*\` MCP tool is exposed in your tool list. Skip it otherwise — without an MCP tool reading the file, \`wmill dev\` never starts. + +**Each flow / script / app gets its own named entry** in the user's \`.claude/launch.json\` so multiple previews coexist without colliding — each entry pins a different port + path. Never reuse a generic "windmill" entry for different targets. + +### Step 1 — Reuse or add a per-target entry in \`.claude/launch.json\` + +Convention: name the entry \`windmill: \` (e.g. \`windmill: f/test/my_flow\`). + +- **Entry already exists** → reuse it; note its \`port\` for the next step. +- **Not there** → add one. Pick a port not already taken by another entry (start at 4000 and bump). Shape: + +For flows / scripts: +\`\`\`json +{ + "name": "windmill: f/test/my_flow", + "runtimeExecutable": "bash", + "runtimeArgs": ["-c", "wmill dev --proxy-port \${PORT:-4000} --path f/test/my_flow --no-open"], + "port": 4000, + "autoPort": true +} +\`\`\` + +For apps (\`*__raw_app/\`), \`wmill app dev\` is the equivalent — runs from the app folder, no \`--path\`: +\`\`\`json +{ + "name": "windmill: f/test/my_app", + "runtimeExecutable": "bash", + "runtimeArgs": ["-c", "cd f/test/my_app__raw_app && wmill app dev --no-open --port \${PORT:-4001}"], + "port": 4001, + "autoPort": true +} +\`\`\` + +If \`.claude/launch.json\` doesn't exist yet, create it with the standard shell \`{ "version": "0.0.1", "configurations": [...] }\`. + +### Step 2 — Invoke the MCP preview tool + +Point it at the entry you just added/found. Use \`http://localhost:/\` as the URL — the proxy's redirect at \`/\` is what appends the workspace ID, the auth token, and the path. Do **NOT** construct a \`/dev?...\` URL yourself. + +The MCP tool launches the configuration on demand, so you don't need to start the \`wmill dev\` process manually. + +## Non-visual alternative + +If the user wants a programmatic test rather than a visual one: +- Flow: \`wmill flow preview -d ''\` +- Script: \`wmill script preview -d ''\` + +Both print the job result, are safe to run yourself, and don't deploy. + +## Anti-patterns to avoid + +- ❌ Writing a \`.claude/launch.json\` entry when no \`mcp__Claude_Preview__*\` tool is in your tool list. Nothing will read the file; the server never starts. Spawn \`wmill dev\` yourself instead. +- ❌ Starting the proxy when no embedder needs a localhost URL. Direct mode is the right choice — the proxy is overhead with no purpose. +- ❌ Reusing a single generic \`launch.json\` entry for every preview target. Each flow/script/app gets its own named entry on its own port — that's how multiple sessions coexist without one preview clobbering another. +- ❌ Mutating an existing entry's \`--path\` to retarget it. Add a new entry instead. +- ❌ Constructing \`http://localhost:/dev?path=\` yourself. The proxy's \`/\` redirect is what appends the workspace ID and auth token; bypassing it gives a broken page. Always use \`http://localhost:/\`. +- ❌ Starting \`wmill dev\` in the foreground (you'll hang). Always background. +- ❌ Listing both "open in IDE pane" and "open in browser" as a menu — pick one based on context. `, }; diff --git a/cli/src/utils/port-probe.ts b/cli/src/utils/port-probe.ts new file mode 100644 index 0000000000..3495e5de96 --- /dev/null +++ b/cli/src/utils/port-probe.ts @@ -0,0 +1,150 @@ +/** + * Port collision detection + fallback for `wmill dev` and `wmill app dev`. + * + * Why this exists: Node's default listen() has platform-dependent dual-stack + * behaviour. If the requested IPv4 binding (0.0.0.0:N) is already taken by + * another process, Node may silently fall back to 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 the wrong 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`. + * + * The fix: probe both stacks before binding. Treat the port as taken if either + * 0.0.0.0 or :: refuses the bind. On collision, walk upward to the next free + * port and log the shift prominently. + */ + +import { createServer } from "node:net"; +import { execSync } from "node:child_process"; + +type Host = "0.0.0.0" | "::"; + +/** + * Try to bind a fresh server to (port, host) and immediately close it. + * + * Returns false ONLY when the port is genuinely held by another process + * (EADDRINUSE) or denied by permissions (EACCES). Other errors — most + * importantly EAFNOSUPPORT / EADDRNOTAVAIL on the IPv6 probe when the host + * has no IPv6 stack at all — return true: the stack we're probing simply + * isn't reachable, which is functionally indistinguishable from "free" for + * the dual-stack collision check. + */ +function isPortFree(port: number, host: Host): Promise { + return new Promise((resolve) => { + const s = createServer(); + s.once("error", (err: NodeJS.ErrnoException) => { + const code = err.code ?? ""; + // Anything that means "another process is holding this port" → not free. + // Anything else (no IPv6 stack on this host, etc.) → treat as free so we + // don't false-alarm on IPv4-only containers. + resolve(code !== "EADDRINUSE" && code !== "EACCES"); + }); + s.once("listening", () => s.close(() => resolve(true))); + s.listen(port, host); + }); +} + +/** + * A port counts as free only if BOTH IPv4 and IPv6 stacks accept the bind. + * If either is held by another process, the OS may route `localhost` traffic + * to that other process even when our listener succeeds on the free stack. + * + * Probes sequentially, not in parallel: on Linux the default is + * `net.ipv6.bindv6only=0`, which makes a `bind(::, port)` socket also occupy + * the IPv4 stack on the same port. Running both probes concurrently then + * causes one to lose the race with EADDRINUSE on a port that is actually + * free, producing false negatives. Sequential keeps each probe's bind fully + * released before the next starts. + */ +async function isPortFreeOnBothStacks(port: number): Promise { + if (!(await isPortFree(port, "0.0.0.0"))) return false; + if (!(await isPortFree(port, "::"))) return false; + return true; +} + +/** + * Best-effort lookup of the PID + command currently bound to . Returns + * undefined if nothing is found, the lookup fails, or the platform tooling + * isn't installed. Never throws. + */ +function findPortHolder(port: number): { pid: number; command: string } | undefined { + // macOS + Linux: lsof. -nP avoids DNS / port-name lookups, -sTCP:LISTEN + // narrows to the listening socket. + try { + const out = execSync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -F pc 2>/dev/null`, { + encoding: "utf-8", + stdio: ["ignore", "pipe", "ignore"], + }); + // -F pc emits records like: + // p91418 + // cbun + let pid: number | undefined; + let cmd: string | undefined; + for (const line of out.split("\n")) { + if (line.startsWith("p")) pid = parseInt(line.slice(1), 10); + else if (line.startsWith("c")) cmd = line.slice(1); + if (pid && cmd) return { pid, command: cmd }; + } + } catch { + // lsof missing or no holder — fall through. + } + + // Linux fallback: ss. -ltnp lists listening TCP sockets with PID/command. + try { + const out = execSync(`ss -ltnp 2>/dev/null | awk '$4 ~ /:${port}$/ { print $NF }'`, { + encoding: "utf-8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + // Format: users:(("bun",pid=91418,fd=23)) + const m = out.match(/\("([^"]+)",pid=(\d+)/); + if (m) return { pid: parseInt(m[2], 10), command: m[1] }; + } catch { + /* fall through */ + } + + return undefined; +} + +/** + * Resolve the port we should actually bind to. + * + * Walks upward from `requested` until a port is free on both stacks, capped + * at +20 to avoid silently scanning the whole 4xxx range. On shift, logs a + * prominent warning naming the holder if we can find it. + * + * Returns the chosen port (== requested when it was already free). + */ +export async function resolveBindPort( + requested: number, + flagLabel: string, + log: { info: (msg: string) => void; warn: (msg: string) => void }, +): Promise { + const MAX_SHIFT = 20; + for (let port = requested; port < requested + MAX_SHIFT; port++) { + if (await isPortFreeOnBothStacks(port)) { + if (port !== requested) { + const holder = findPortHolder(requested); + const holderHint = holder + ? ` (held by PID ${holder.pid} \`${holder.command}\`)` + : ""; + log.warn( + `Port ${requested} is already in use${holderHint}. Using port ${port} instead.`, + ); + log.info( + `If you need port ${requested} stable (e.g. a launch.json entry pinned to it), stop the holder and re-run with ${flagLabel} ${requested}.`, + ); + } + return port; + } + } + throw new Error( + `Could not find a free port in the range ${requested}-${requested + MAX_SHIFT - 1}. Stop a holder or pass ${flagLabel} .`, + ); +} + +/** + * The host string we bind to. Explicit IPv4 — `localhost` resolves to + * 127.0.0.1 first on every platform we care about, and binding both stacks + * relies on platform-specific IPV6_V6ONLY behaviour we don't want to debug. + */ +export const BIND_HOST = "0.0.0.0" as const; diff --git a/cli/test/dev_server.test.ts b/cli/test/dev_server.test.ts index 6c825a9bbd..558fea0241 100644 --- a/cli/test/dev_server.test.ts +++ b/cli/test/dev_server.test.ts @@ -127,7 +127,8 @@ test( let stdoutBuffer = ""; let port: number | null = null; - // Wait for "Server listening on port XXXX" message + // Wait for the dev WebSocket startup line — see startDirectServer + // in cli/src/commands/dev/dev.ts. const portMatch = await waitFor( async () => { try { @@ -144,7 +145,7 @@ test( // Reader may be exhausted } const match = stdoutBuffer.match( - /Server listening on port (\d+)/, + /Dev WebSocket listening on ws:\/\/localhost:(\d+)/, ); return match; }, diff --git a/frontend/src/lib/components/Dev.svelte b/frontend/src/lib/components/Dev.svelte index a94e1bc952..88fc0e02c9 100644 --- a/frontend/src/lib/components/Dev.svelte +++ b/frontend/src/lib/components/Dev.svelte @@ -18,7 +18,13 @@ } from '$lib/gen' import { inferArgs } from '$lib/infer' import { userStore, workspaceStore } from '$lib/stores' - import { emptySchema, readFieldsRecursively, sendUserToast, type StateStore } from '$lib/utils' + import { + emptySchema, + pluralize, + readFieldsRecursively, + sendUserToast, + type StateStore + } from '$lib/utils' import { Pane, Splitpanes } from 'svelte-splitpanes' import { onDestroy, onMount, setContext, untrack } from 'svelte' import DarkModeToggle from '$lib/components/sidebar/DarkModeToggle.svelte' @@ -38,7 +44,32 @@ import { GroupEditor, setGroupEditorContext } from './graph/groupEditor.svelte' import { dfs } from './flows/dfs' import { loadSchemaFromModule } from './flows/flowInfers' - import { CornerDownLeft, Play } from 'lucide-svelte' + import { + CornerDownLeft, + Play, + Folder, + FolderTree, + User, + Search, + ChevronDown, + ChevronUp, + Code2, + LayoutDashboard + } from 'lucide-svelte' + import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte' + import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte' + import FlowIcon from '$lib/components/home/FlowIcon.svelte' + import { + groupItems, + type ItemType, + type FolderItem, + type UserItem + } from '$lib/components/home/treeViewUtils' + import SearchItems from '$lib/components/SearchItems.svelte' + import TextInput from '$lib/components/text_input/TextInput.svelte' + import Row from '$lib/components/common/table/Row.svelte' + import Alert from '$lib/components/common/alert/Alert.svelte' + import { HOME_SEARCH_PLACEHOLDER } from '$lib/consts' import Toggle from './Toggle.svelte' import { setLicense } from '$lib/enterpriseUtils' import type { FlowCopilotContext } from './copilot/flow' @@ -112,6 +143,9 @@ let darkModeToggle: DarkModeToggle | undefined = $state() let darkMode: boolean = $state(document.documentElement.classList.contains('dark')) + let flowContainerWidth = $state(0) + let flowContainerHeight = $state(0) + let flowHorizontalSplit = $derived(flowContainerWidth < flowContainerHeight) let modeInitialized = $state(false) let paneWidth = $state(0) let compactPreview = $derived(paneWidth < 800) @@ -160,7 +194,56 @@ const href = window.location.href const indexQ = href.indexOf('?') const searchParams = indexQ > -1 ? new URLSearchParams(href.substring(indexQ)) : undefined - let relativePaths: any[] = $state([]) + let relativePaths: (string | [number, string])[] = $state([]) + + type WmPathItem = { + path: string + kind: 'flow' | 'script' | 'raw_app' + summary?: string + } + // watchPath is (re)synced on initial load, on popstate, and on explicit + // pickPath assignments. We don't listen for generic pushState events — + // the only pushState callsite is pickPath itself, and it updates watchPath + // directly. If a third caller starts pushing to history, add a resync there. + function parseWatchPath(): string | undefined { + const i = window.location.href.indexOf('?') + if (i < 0) return undefined + return new URLSearchParams(window.location.href.substring(i)).get('path') ?? undefined + } + const PATH_SUFFIX_RE = /(\.(flow|app|raw_app)|__(flow|app|raw_app))\/?$/ + let watchPath = $state(parseWatchPath()?.replace(PATH_SUFFIX_RE, '')) + let pickerItems: WmPathItem[] = $state([]) + // Picker only makes sense on the local dev page — that's the only context + // with a wmill dev WebSocket capable of returning the workspace listing. + // The VS Code extension iframe omits ?local=true and drives the page via + // postMessage (replaceScript / replaceFlow), so it must skip the picker. + const isLocalDevPage = !!searchParams?.has('local') + const pickerMode = $derived(isLocalDevPage && !watchPath) + let wsState: 'connecting' | 'open' | 'closed' = $state('connecting') + let pickerFilter = $state('') + let pickerKind: 'all' | 'flow' | 'script' | 'raw_app' = $state('all') + // Shape pickerItems into the homepage's ItemType so we can reuse `groupItems` + // for the folder/user tree structure. `kind` ('script'|'flow'|'raw_app') maps 1:1 + // onto ItemType['type']; missing fields (canWrite, edited_at, etc.) default to safe values. + const pickerTreeItems = $derived( + pickerItems.map( + (item) => + ({ + path: item.path, + summary: item.summary ?? '', + type: item.kind, + canWrite: true, + extra_perms: {}, + starred: false, + edited_at: '' + }) as unknown as ItemType + ) + ) + const pickerKindFilteredItems = $derived( + pickerKind === 'all' ? pickerTreeItems : pickerTreeItems.filter((i) => i.type === pickerKind) + ) + let pickerFilteredItems: (ItemType & { marked?: string })[] | undefined = $state(undefined) + const pickerGroups = $derived(groupItems(pickerFilteredItems ?? pickerKindFilteredItems)) if (searchParams?.has('local')) { connectWs() @@ -328,13 +411,40 @@ ) loadingCodebaseButton = false } + const onPopState = () => { + watchPath = parseWatchPath()?.replace(PATH_SUFFIX_RE, '') + if (watchPath && socket && socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify({ type: 'loadWmPath', path: watchPath })) + } + } + + onMount(() => { + window.addEventListener('popstate', onPopState) + }) + onDestroy(() => { window.removeEventListener('message', el) + window.removeEventListener('popstate', onPopState) if (socket && socket.readyState === WebSocket.OPEN) { socket?.close() } }) + function pickPath(item: WmPathItem) { + if (item.kind === 'raw_app') { + sendUserToast( + `raw_apps aren't previewable here. Run \`wmill app dev\` from inside the app folder.`, + false + ) + return + } + const url = new URL(window.location.href) + url.searchParams.set('path', item.path) + window.history.pushState({}, '', url.toString()) + watchPath = item.path + socket?.send(JSON.stringify({ type: 'loadWmPath', path: item.path })) + } + function connectWs() { try { if (socket) { @@ -345,8 +455,28 @@ } const port = searchParams?.get('port') || '3001' try { + wsState = 'connecting' socket = new WebSocket(`ws://localhost:${port}/ws`) + // On connect, request the watched path if any, otherwise ask for a list to render the picker + socket.addEventListener('open', () => { + if (!socket) return + wsState = 'open' + if (watchPath) { + socket.send(JSON.stringify({ type: 'loadWmPath', path: watchPath })) + } else { + socket.send(JSON.stringify({ type: 'listPaths' })) + } + }) + + socket.addEventListener('error', () => { + wsState = 'closed' + }) + + socket.addEventListener('close', () => { + wsState = 'closed' + }) + // Listen for messages socket.addEventListener('message', (event) => { replaceData(event.data) @@ -360,10 +490,29 @@ console.log('Received invalid JSON: ' + msg) return } - if (data.type == 'script') { - replaceScript(data) - } else if (data.type == 'flow') { - replaceFlow(data) + if (data.type === 'paths') { + pickerItems = data.items ?? [] + return + } + // Picker mode (URL has no path) — ignore live broadcasts so a random + // file change doesn't yank the page out of the picker. (When watchPath + // IS set the server gates broadcasts itself, so no further filter here.) + if (!watchPath) return + if (data.type == 'script' || data.type == 'flow') { + // Guard against the $effect on flowStore.val (re)serializing the + // just-received payload back over the same WS to handleFlowRoundTrip + // (which would re-run the orphan-file scan with the same content). + // Mirrors the postMessage handler above. + lockChanges = true + if (data.type == 'script') { + replaceScript(data) + } else { + replaceFlow(data) + } + timeout && clearTimeout(timeout) + timeout = window.setTimeout(() => { + lockChanges = false + }, 500) } else { sendUserToast(`Received invalid message type ${data.type}`, true) } @@ -571,14 +720,28 @@ setGroupEditorContext(groupEditor, canCreateGroup) let lastSent: OpenFlow | undefined = undefined + const isInIframe = window.parent !== window function updateFlow(flow: OpenFlow) { if (lockChanges) { return } - if (!deepEqual(flow, lastSent)) { - lastSent = $state.snapshot(flow) - window?.parent.postMessage({ type: 'flow', flow: lastSent, uriPath: lastUriPath }, '*') + if (deepEqual(flow, lastSent)) { + return } + const snapshot = $state.snapshot(flow) + // Prefer the WebSocket whenever a `wmill dev` session is connected — this covers + // both standalone browser tabs and Claude Code's iframe preview. The VS Code + // extension never opens this socket (its iframe URL omits `local=true`), so it + // falls through to the postMessage path it has always used. + if (socket && socket.readyState === WebSocket.OPEN) { + socket.send(JSON.stringify({ type: 'flow', flow: snapshot, uriPath: lastUriPath })) + lastSent = snapshot + } else if (isInIframe) { + window?.parent.postMessage({ type: 'flow', flow: snapshot, uriPath: lastUriPath }, '*') + lastSent = snapshot + } + // Else: no channel available yet (WS still connecting, not in an iframe). + // Don't mark `lastSent` so the next change will retry instead of being silently swallowed. } let reload = $state(0) @@ -702,7 +865,7 @@ const selectedModule = $derived( selectedId && flowStore.val?.value - ? findModuleInFlow(flowStore.val.value, selectedId) ?? undefined + ? (findModuleInFlow(flowStore.val.value, selectedId) ?? undefined) : undefined ) @@ -712,7 +875,185 @@
- {#if mode == 'script'} + {#snippet itemRow(item: ItemType & { marked?: string }, depth: number)} + {@const wmItem = pickerItems.find((p) => p.path === item.path)} + + {/snippet} + {#snippet treeNode(node: ItemType | FolderItem | UserItem, depth: number)} + {#if 'folderName' in node} +
+ +
0 ? `padding-left: ${depth * 16}px;` : ''} + > +
+ {#if depth === 0} + + {:else} + + {/if} +
+
+ {#if depth === 0}f/{/if}{node.folderName} +
+ ({pluralize(node.items.length, 'item')}) +
+
+
+
+
+
+ {#each node.items as child ('folderName' in child ? `f__${child.folderName}` : 'username' in child ? `u__${child.username}` : `i__${child.type}__${child.path}`)} + {@render treeNode(child, depth + 1)} + {/each} +
+ {:else if 'username' in node} +
+ +
0 ? `padding-left: ${depth * 16}px;` : ''} + > +
+ +
+
+ u/{node.username} +
+ ({pluralize(node.items.length, 'item')}) +
+
+
+
+
+
+ {#each node.items as child ('folderName' in child ? `f__${child.folderName}` : 'username' in child ? `u__${child.username}` : `i__${child.type}__${child.path}`)} + {@render treeNode(child, depth + 1)} + {/each} +
+ {:else} + {@render itemRow(node as ItemType & { marked?: string }, depth)} + {/if} + {/snippet} + + {#if pickerMode} +
+
+ +
+
+ {#if $userStore} + {$userStore?.username} on {$workspaceStore} + {:else} + Unable to login on {$workspaceStore} + {/if} +
+
+

+ {$workspaceStore} + (local) +

+

Click a flow or a script to preview it.

+ + `${item.path} ${item.summary ?? ''}`} + bind:filteredItems={pickerFilteredItems} + /> + + {#if wsState !== 'closed'} +
+ + {#snippet children({ item })} + + + + + {/snippet} + + +
+ + + +
+
+ {/if} + + {#if wsState === 'closed'} + + Start it from your workspace root with + wmill dev + to preview your flows, scripts, and apps. + + {:else if pickerItems.length === 0} +
No flows, scripts, or apps detected in this workspace.
+ {:else if pickerGroups.length === 0} +
No items match the search.
+ {:else} +
+ {#each pickerGroups as group ('folderName' in group ? `f__${group.folderName}` : 'username' in group ? `u__${group.username}` : `i__${group.type}__${group.path}`)} + {@render treeNode(group, 0)} + {/each} +
+ {/if} +
+
+ {:else if mode == 'script'}
@@ -855,7 +1196,11 @@
{:else} -
+
@@ -866,83 +1211,93 @@ {/if}
-
- { - showJobStatus = true - }} - /> -
- + - {#if flowStore.val?.value?.modules} -
- { - delete localModuleStates[id] - delete modulesTestStates.states[id] - }} - {flowHasChanged} - /> - {:else} -
Missing flow modules
- {/if} -
+
+ {#if flowStore.val?.value?.modules} +
+
+ { + showJobStatus = true + }} + /> +
+ { + delete localModuleStates[id] + delete modulesTestStates.states[id] + }} + {flowHasChanged} + controlsPosition="bottom" + /> + {:else} +
Missing flow modules
+ {/if} +
+ - {#key reload} - { - if (ev.detail.kind === 'preprocessor') { - stepsInputArgs.setStepArgs('preprocessor', ev.detail.args ?? {}) - selectionManager.selectId('preprocessor') - } else { - previewArgsStore.val = ev.detail.args ?? {} - flowPreviewButtons?.openPreview() - } - }} - onTestFlow={flowPreviewButtons?.runPreview} - {job} - isOwner={flowPreviewContent?.getIsOwner()} - {suspendStatus} - onOpenDetails={flowPreviewButtons?.openPreview} - previewOpen={flowPreviewButtons?.getPreviewOpen()} - /> - {/key} +
+ {#if selectedModule} +
+ {selectedModule.id} summary + +
+ {/if} + {#key reload} + { + if (ev.detail.kind === 'preprocessor') { + stepsInputArgs.setStepArgs('preprocessor', ev.detail.args ?? {}) + selectionManager.selectId('preprocessor') + } else { + previewArgsStore.val = ev.detail.args ?? {} + flowPreviewButtons?.openPreview() + } + }} + onTestFlow={flowPreviewButtons?.runPreview} + {job} + isOwner={flowPreviewContent?.getIsOwner()} + {suspendStatus} + onOpenDetails={flowPreviewButtons?.openPreview} + previewOpen={flowPreviewButtons?.getPreviewOpen()} + /> + {/key} +
- {#if selectedModule} -
- {selectedModule.id} summary - -
- {/if}
{/if} diff --git a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte index 7789d93f72..422a599d5b 100644 --- a/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte +++ b/frontend/src/lib/components/flows/content/FlowModuleComponent.svelte @@ -832,109 +832,44 @@ {/if}
- - {#if flowModule.value.type !== 'aiagent'} - - {#if flowModule.value.type === 'rawscript'} - {#if !noEditor} - {#key flowModule.id} -
- {#if assets?.length} - - {/if} - {#if isDebuggableScript && customUi?.editorBar?.debug != false} - - {/if} - {#if showDebugPanel && !showDebugConsole} - - {/if} -
- {#if debugConsoleVisible} - - -
- { - selected = 'test' - if (selectedId == flowModule.id) { - if (flowModule.value.type === 'rawscript' && editor) { - flowModule.value.content = editor.getCode() - } - await reload(flowModule) - modulePreview?.runTestWithStepArgs() - } - }} - on:change={async (event) => { - const content = event.detail - if (flowModule.value.type === 'rawscript') { - if (flowModule.value.content !== content) { - flowModule.value.content = content - } - await reload(flowModule) - if (debugMode && breakpointDecorations.length > 0) { - refreshBreakpointPositions() - } - } - }} - formatAction={() => { - reload(flowModule) - saveDraft() - }} - fixedOverflowWidgets={true} - args={Object.entries(flowModule.value.input_transforms).reduce( - (acc, [key, obj]) => { - acc[key] = obj.type === 'static' ? obj.value : undefined - return acc - }, - {} - )} - key={`flow-inline-${$workspaceStore}-${$pathStore}-${flowModule.id}`} - moduleId={flowModule.id} - preparedAssetsSqlQueries={preparedSqlQueries.current} - customTag={flowModule.value.tag} - /> -
-
- - (showDebugConsole = false)} - workspace={$workspaceStore} - jobId={debugSessionJobId ?? undefined} - /> - -
- {:else} + {#snippet topPaneContent()} + {#if flowModule.value.type === 'rawscript'} + {#if !noEditor} + {#key flowModule.id} +
+ {#if assets?.length} + + {/if} + {#if isDebuggableScript && customUi?.editorBar?.debug != false} + + {/if} + {#if showDebugPanel && !showDebugConsole} + + {/if} +
+ {#if debugConsoleVisible} + +
- {/if} - - {/key} - {/if} - {:else if flowModule.value.type === 'script'} - {#if !noEditor && (customUi?.hubCode != false || !flowModule?.value?.path?.startsWith('hub/'))} -
- {#key forceReload} - + + (showDebugConsole = false)} + workspace={$workspaceStore} + jobId={debugSessionJobId ?? undefined} /> - {/key} + + + {:else} +
+ { + selected = 'test' + if (selectedId == flowModule.id) { + if (flowModule.value.type === 'rawscript' && editor) { + flowModule.value.content = editor.getCode() + } + await reload(flowModule) + modulePreview?.runTestWithStepArgs() + } + }} + on:change={async (event) => { + const content = event.detail + if (flowModule.value.type === 'rawscript') { + if (flowModule.value.content !== content) { + flowModule.value.content = content + } + await reload(flowModule) + if (debugMode && breakpointDecorations.length > 0) { + refreshBreakpointPositions() + } + } + }} + formatAction={() => { + reload(flowModule) + saveDraft() + }} + fixedOverflowWidgets={true} + args={Object.entries(flowModule.value.input_transforms).reduce( + (acc, [key, obj]) => { + acc[key] = obj.type === 'static' ? obj.value : undefined + return acc + }, + {} + )} + key={`flow-inline-${$workspaceStore}-${$pathStore}-${flowModule.id}`} + moduleId={flowModule.id} + preparedAssetsSqlQueries={preparedSqlQueries.current} + customTag={flowModule.value.tag} + />
{/if} - {:else if flowModule.value.type === 'flow'} + + {/key} + {/if} + {:else if flowModule.value.type === 'script'} + {#if !noEditor && (customUi?.hubCode != false || !flowModule?.value?.path?.startsWith('hub/'))} +
{#key forceReload} - + {/key} - {/if} - +
+ {/if} + {:else if flowModule.value.type === 'flow'} + {#key forceReload} + + {/key} {/if} - { - if (flowModule.value.type === 'aiagent') { - return 100 - } - return editorSettingsPanelSize - }, - (v) => { - if (flowModule.value.type !== 'aiagent') { - editorSettingsPanelSize = v - } - } - } - minSize={20} - > - - -
- { - selected = event.detail - }} - wrapperClass="shrink-0" - > - {#if !preprocessorModule} - - {/if} - - {#if canShowChatTab && flowModule.value.type === 'aiagent'} - - {/if} - {#if !preprocessorModule && !isAgentTool} - - {/if} - - {#if visibleSelected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow' || flowModule.value.type == 'aiagent')} -
- + +
+ { + selected = event.detail + }} + wrapperClass="shrink-0" + > + {#if !preprocessorModule} + + {/if} + + {#if canShowChatTab && flowModule.value.type === 'aiagent'} + + {/if} + {#if !preprocessorModule && !isAgentTool} + + {/if} + + {#if visibleSelected === 'inputs' && (flowModule.value.type == 'rawscript' || flowModule.value.type == 'script' || flowModule.value.type == 'flow' || flowModule.value.type == 'aiagent')} +
+ + {#if reloadError} +
+ {/if} + - {#if reloadError} -
- {/if} - { + schema={flowStateStore.val[selectedId]?.schema ?? {}} + previousModuleId={previousModule?.id} + bind:args={ + () => { + // @ts-ignore + return flowModule?.value?.input_transforms + }, + (v) => { + if ( + typeof flowModule?.value === 'object' && + flowModule?.value !== null + ) { // @ts-ignore - return flowModule?.value?.input_transforms - }, - (v) => { - if ( - typeof flowModule?.value === 'object' && - flowModule?.value !== null - ) { - // @ts-ignore - flowModule.value.input_transforms = v - } + flowModule.value.input_transforms = v } } - extraLib={stepPropPicker.extraLib} - {enableAi} - {isAgentTool} - allowedAiTransforms={isAgentTool && flowModule.value.type === 'aiagent' - ? ['user_message'] - : undefined} - helperScript={retrieveDynCodeAndLang(flowModule.value)} - chatInputEnabled={flowStore.val.value?.chat_input_enabled ?? false} - /> -
+ } + extraLib={stepPropPicker.extraLib} + {enableAi} + {isAgentTool} + allowedAiTransforms={isAgentTool && flowModule.value.type === 'aiagent' + ? ['user_message'] + : undefined} + helperScript={retrieveDynCodeAndLang(flowModule.value)} + chatInputEnabled={flowStore.val.value?.chat_input_enabled ?? false} + /> + +
+ {:else if visibleSelected === 'test'} + {#if debugMode && isDebuggableScript} +
+
- {:else if visibleSelected === 'test'} - {#if debugMode && isDebuggableScript} -
- -
- {/if} - + {:else if visibleSelected === 'chat' && canShowChatTab && flowModule.value.type === 'aiagent'} +
+
+ { + setOmitOutputFromConversation(event.detail) + }} + options={{ + right: 'Omit assistant and tool messages from the flow conversation', + rightTooltip: + 'When enabled, this AI agent still runs normally, but its assistant response and tool-use messages are not stored in chat-mode conversation history.' + }} + /> +
+
+ {:else if visibleSelected === 'advanced'} + + - {:else if visibleSelected === 'chat' && canShowChatTab && flowModule.value.type === 'aiagent'} -
-
+ {#if !selectedId.includes('failure')} + + + + + + + + + + {#if flowModule.value['language'] === 'python3' || flowModule.value['language'] === 'deno'} + + {/if} + {/if} + + {#if advancedSelected === 'runtime'} + + + + + + + {/if} +
+ {#if advancedSelected === 'retries'} +
+ {#snippet header()} + + When enabled, the flow will continue to the next step even if this + step fails (after exhausting all retries, if any). This enables to + process the error in a branch one for instance. + + {/snippet} { - setOmitOutputFromConversation(event.detail) - }} + bind:checked={flowModule.continue_on_error} options={{ - right: 'Omit assistant and tool messages from the flow conversation', - rightTooltip: - 'When enabled, this AI agent still runs normally, but its assistant response and tool-use messages are not stored in chat-mode conversation history.' + left: 'Stop on error and propagate error up', + right: "Continue on error with error as step's return" }} />
-
- {:else if visibleSelected === 'advanced'} - - - {#if !selectedId.includes('failure')} - - - - - - - - - - {#if flowModule.value['language'] === 'python3' || flowModule.value['language'] === 'deno'} - - {/if} - {/if} - - {#if advancedSelected === 'runtime'} - - - - - - - {/if} -
- {#if advancedSelected === 'retries'} -
- {#snippet header()} - - When enabled, the flow will continue to the next step even if this - step fails (after exhausting all retries, if any). This enables to - process the error in a branch one for instance. - - {/snippet} - -
-
-
- {#snippet header()} - - If defined, upon error this step will be retried with a delay and a - maximum number of attempts as defined below. - - {/snippet} - -
- {:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'concurrency'} -
- {#snippet header()} - Allowed concurrency within a given timeframe - {/snippet} - {#if flowModule.value.type == 'rawscript'} - - -
+ + - - - Setting priority is only available for enterprise edition and not - available on the cloud. - -
- {:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'lifetime'} -
- -
- {:else if advancedSelected === 'cache'} -
- -
- {:else if advancedSelected === 'early-stop'} - - {:else if advancedSelected === 'skip'} - - {:else if advancedSelected === 'suspend'} -
- -
- {:else if advancedSelected === 'sleep'} -
- -
- {:else if advancedSelected === 'debounce'} -
- -
- {:else if advancedSelected === 'mock'} -
- -
- {:else if advancedSelected === 'same_worker'} -
- - If shared directory is set, will share a folder that will be mounted - on `./shared` for each of them to pass data between each other. - - -
- {:else if advancedSelected === 's3'} -
-

- S3 snippets - - Read/Write object from/to S3 and leverage Polars and DuckDB to run - efficient ETL processes. - -

-
-
-
- - {#snippet children({ item })} - {#if flowModule.value['language'] === 'deno'} - - {:else} - - - - {/if} - {/snippet} - -
- - -
- - {/if} -
- {/if} -
-
- {#if selected === 'test'} - - {#if stepHistoryLoader?.stepStates[flowModule.id]?.initial && !flowModule.mock?.enabled} - - -
{ - stepHistoryLoader?.resetInitial(flowModule.id) - }} - class="cursor-pointer h-full hover:bg-gray-500/20 dark:hover:bg-gray-500/20 dark:bg-gray-500/80 bg-gray-500/40 absolute top-0 left-0 w-full z-50" - > -
Run loaded from history
-
- {/if} - {#if showDebugPanel || hasDebugResult} - - - - - - - - {#if hasDebugResult} -
- -
- {:else} -
+ {#snippet header()} + + Concurrency keys are global, you can have them be workspace + specific using the variable `$workspace`. You can also use an + argument's value using `$args[name_of_arg]` - {#if $debugState.running && !$debugState.stopped} - Running... - {:else if $debugState.stopped} - Paused at breakpoint - {:else} - Waiting for debug session - {/if} -
- {/if} -
-
-
- - + + + {:else} + + The concurrency limit of a workspace script is only settable in the + script metadata itself. For hub scripts, this feature is non available + yet. + + {/if} + + {:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'timeout'} +
+ - - - {:else if debugMode && isDebuggableScript} -
- Click "Debug" in the toolbar to start debugging -
- {:else} - { - flowModule.mock = detail - flowModule = flowModule - refreshStateStore(flowStore) - }} - {testJob} - {scriptProgress} - mod={flowModule} - {testIsLoading} - disableMock={preprocessorModule || failureModule} - disableHistory={failureModule} - loadingJob={stepHistoryLoader?.stepStates[flowModule.id]?.loadingJobs} - tagLabel={customUi?.tagLabel} - bind:this={modulePreviewResultViewer} - /> - {/if} - - {/if} - - - +
+ {:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'priority'} +
+ + 0} + on:change={() => { + if (flowModule.priority) { + flowModule.priority = undefined + } else { + flowModule.priority = 100 + } + }} + options={{ + right: 'Enabled high priority flow step', + rightTooltip: `Jobs scheduled from this step when the flow is executed are labeled as high priority and take precedence over the other jobs in the jobs queue. ${ + !$enterpriseLicense + ? 'This is a feature only available on enterprise edition.' + : '' + }` + }} + /> + + + + Setting priority is only available for enterprise edition and not + available on the cloud. + +
+ {:else if advancedSelected === 'runtime' && advancedRuntimeSelected === 'lifetime'} +
+ +
+ {:else if advancedSelected === 'cache'} +
+ +
+ {:else if advancedSelected === 'early-stop'} + + {:else if advancedSelected === 'skip'} + + {:else if advancedSelected === 'suspend'} +
+ +
+ {:else if advancedSelected === 'sleep'} +
+ +
+ {:else if advancedSelected === 'debounce'} +
+ +
+ {:else if advancedSelected === 'mock'} +
+ +
+ {:else if advancedSelected === 'same_worker'} +
+ + If shared directory is set, will share a folder that will be mounted on + `./shared` for each of them to pass data between each other. + + +
+ {:else if advancedSelected === 's3'} +
+

+ S3 snippets + + Read/Write object from/to S3 and leverage Polars and DuckDB to run + efficient ETL processes. + +

+
+
+
+ + {#snippet children({ item })} + {#if flowModule.value['language'] === 'deno'} + + {:else} + + + + {/if} + {/snippet} + +
+ + +
+ + {/if} +
+ {/if} +
+
+ {#if selected === 'test'} + + {#if stepHistoryLoader?.stepStates[flowModule.id]?.initial && !flowModule.mock?.enabled} + + +
{ + stepHistoryLoader?.resetInitial(flowModule.id) + }} + class="cursor-pointer h-full hover:bg-gray-500/20 dark:hover:bg-gray-500/20 dark:bg-gray-500/80 bg-gray-500/40 absolute top-0 left-0 w-full z-50" + > +
Run loaded from history
+
+ {/if} + {#if showDebugPanel || hasDebugResult} + + + + + + + + {#if hasDebugResult} +
+ +
+ {:else} +
+ {#if $debugState.running && !$debugState.stopped} + Running... + {:else if $debugState.stopped} + Paused at breakpoint + {:else} + Waiting for debug session + {/if} +
+ {/if} +
+
+
+ + + +
+ {:else if debugMode && isDebuggableScript} +
+ Click "Debug" in the toolbar to start debugging +
+ {:else} + { + flowModule.mock = detail + flowModule = flowModule + refreshStateStore(flowStore) + }} + {testJob} + {scriptProgress} + mod={flowModule} + {testIsLoading} + disableMock={preprocessorModule || failureModule} + disableHistory={failureModule} + loadingJob={stepHistoryLoader?.stepStates[flowModule.id]?.loadingJobs} + tagLabel={customUi?.tagLabel} + bind:this={modulePreviewResultViewer} + /> + {/if} +
+ {/if} +
+ {/snippet} + + {#if flowModule.value.type === 'aiagent' || (noEditor && flowModule.value.type !== 'flow')} + +
+ {@render bottomPaneContent()} +
+ {:else} + + + {@render topPaneContent()} + + + {@render bottomPaneContent()} + + + {/if}
diff --git a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte index e4fd8e58dd..73ba81296f 100644 --- a/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte +++ b/frontend/src/lib/components/flows/map/FlowModuleSchemaMap.svelte @@ -98,6 +98,7 @@ suspendStatus?: StateStore> onDelete?: (id: string) => void flowHasChanged?: boolean + controlsPosition?: 'top' | 'bottom' } let { @@ -127,6 +128,7 @@ showJobStatus = false, suspendStatus = $bindable({ val: {} }), onDelete, + controlsPosition = 'top', flowHasChanged }: Props = $props() @@ -565,7 +567,7 @@ />
-
+
(noteMode = false)} onNotePositionUpdate={(noteId, position) => { // Update note position via NoteEditor context in edit mode diff --git a/frontend/src/lib/components/graph/FlowGraphV2.svelte b/frontend/src/lib/components/graph/FlowGraphV2.svelte index 3c7b0dd771..563a387184 100644 --- a/frontend/src/lib/components/graph/FlowGraphV2.svelte +++ b/frontend/src/lib/components/graph/FlowGraphV2.svelte @@ -198,6 +198,7 @@ diffBeforeFlow?: OpenFlow currentInputSchema?: Record markRemovedAsShadowed?: boolean + controlsPosition?: 'top' | 'bottom' outerDivClass?: string } @@ -271,6 +272,7 @@ onDuplicateMultiple = undefined, onMoveMultiple = undefined, movingIds = undefined, + controlsPosition = 'top', outerDivClass = '' }: Props = $props() @@ -754,12 +756,16 @@ } else { const minY = Math.min(...nodes.map((n) => n.position.y)) const maxBottom = Math.max(...nodes.map((n) => n.position.y + NODE.height + 100)) - height = Math.max(maxBottom - minY, minHeight) + const computed = maxBottom - minY + height = Math.max(Math.min(computed, maxHeight ?? computed), minHeight) } } $effect(() => { + // Track both bounds — updateHeight() reads both, so missing one (as + // maxHeight was) leaves height stale when only that bound changes. minHeight + maxHeight untrack(() => updateHeight()) }) @@ -1180,7 +1186,7 @@
{:else} n.type !== 'note') }} diff --git a/system_prompts/auto-generated/cli/cli-commands.md b/system_prompts/auto-generated/cli/cli-commands.md index a3f790ef01..f8bec5a868 100644 --- a/system_prompts/auto-generated/cli/cli-commands.md +++ b/system_prompts/auto-generated/cli/cli-commands.md @@ -35,6 +35,13 @@ app related commands - `app lint [app_folder:string]` - Lint a raw app folder to validate structure and buildability - `--fix` - Attempt to fix common issues (not implemented yet) - `app new` - create a new raw app from a template + - `--summary ` - App summary (short description). Skips the prompt when provided. Triggers non-interactive mode. + - `--path ` - App path (e.g., f/folder/my_app or u/username/my_app). Skips the prompt when provided. Triggers non-interactive mode. + - `--framework ` - Framework template: react19 | react18 | svelte5 | vue. Skips the prompt when provided. Triggers non-interactive mode. + - `--datatable ` - Datatable to wire up. Without this flag in non-interactive mode, no datatable is configured. + - `--schema ` - Schema to use with --datatable. Created (CREATE SCHEMA IF NOT EXISTS) if it doesn't already exist. + - `--overwrite` - Overwrite the target directory if it already exists, without prompting. + - `--no-open-in-desktop` - Do not prompt to open the new app in Claude Desktop. - `app generate-agents [app_folder:string]` - regenerate AGENTS.md and DATATABLES.md from remote workspace - `app set-permissioned-as ` - Set the on_behalf_of_email for an app (requires admin or wm_deployers group) @@ -71,10 +78,13 @@ workspace dependencies related commands ### dev -Launch a dev server that watches for local file changes and auto-pushes them to the remote workspace. Provides live reload for scripts and flows during development. +Watch local file changes and live-reload the dev page for preview. Does NOT deploy to the remote workspace — use wmill sync push for that. **Options:** -- `--includes ` - Filter paths givena glob pattern or path +- `--includes ` - Filter paths given a glob pattern or path +- `--proxy-port ` - Port for a localhost reverse proxy to the remote Windmill server +- `--path ` - Watch a specific windmill path (e.g., u/admin/my_script or f/my_flow) +- `--no-open` - Do not open the browser automatically ### docs diff --git a/system_prompts/auto-generated/flow.md b/system_prompts/auto-generated/flow.md index 49e2f73970..3b5b07ff76 100644 --- a/system_prompts/auto-generated/flow.md +++ b/system_prompts/auto-generated/flow.md @@ -1,14 +1,76 @@ # Windmill Flow Building Guide -## CLI Commands +## Creating a Flow + +**You — the AI agent — scaffold the flow yourself by running `wmill flow new ` with the right flags. Do NOT hand-create the folder + `flow.yaml`, and do NOT tell the user to "run `wmill flow new` and follow the prompts".** + +`wmill flow new` creates the folder with the correct suffix (`__flow` or `.flow` depending on the workspace's `nonDottedPaths` setting), writes a minimal `flow.yaml` shell, and prints Claude-specific next-step hints. Scaffolding by hand skips all of that and often picks the wrong suffix. + +### Step 1 — Gather path + summary by asking the user + +You need two things: + +1. **path** — the windmill path, e.g. `f/folder/my_flow` or `u/username/my_flow`. +2. **summary** — a short description of the flow. + +If the user's request didn't supply both, ask for both in a single round-trip. Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and provide one or two example values for each (with an "Other" / free-form fallback). Do not guess paths or summaries. + +### Step 2 — Run the command yourself + +```bash +wmill flow new f/folder/my_flow --summary "Short description" +``` + +Add `--description "..."` when the user provided a longer explanation worth preserving separately from the summary. + +### Step 3 — Fill in `flow.yaml` + +Open the generated `flow.yaml` (under the folder the command just created) and replace the empty `value.modules` + `schema` with the real flow definition. -Create a folder ending with `__flow` and add a `flow.yaml` file with the flow definition. For rawscript modules, use `!inline path/to/script.ts` for the content key. Inline script files should NOT include `.inline_script.` in their names (e.g. use `a.ts`, not `a.inline_script.ts`). -After writing, tell the user they can run: -- `wmill generate-metadata` - Generate lock files for the flow you modified -- `wmill sync push` - Deploy to Windmill -Do NOT run these commands yourself. Instead, inform the user that they should run them. +Once the flow has real content, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a `launch.json` entry) and the user should consent. + +### Anti-patterns to avoid + +- ❌ Hand-creating the `__flow` folder + `flow.yaml` instead of running `wmill flow new`. You'll miss the suffix-setting resolution, the default shape, and the Claude hints. +- ❌ Telling the user to "run `wmill flow new `" — you can and should run it yourself. +- ❌ Inventing a path/summary instead of asking the user. + +## CLI Commands — running, previewing, deploying + +After writing, tell the user which command fits what they want to do: + +- `wmill flow preview ` — **default when iterating on a local flow.** Runs the local `flow.yaml` against local inline scripts without deploying. Add `--remote` to use deployed workspace scripts for PathScript steps instead of local files. +- `wmill flow run ` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — regenerate stale `.lock` and `.script.yaml` files. By default it scans **scripts, flows, and apps** across the workspace; pass `--skip-flows --skip-apps` (or run from a subdirectory) to limit the scope when you only care about the flow you edited. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the flow", "try it", "test it", "does it work" while there are **local edits to a `flow.yaml`**, use `flow preview`. Do NOT push the flow to then `flow run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `flow run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local `flow.yaml` being edited (you're just invoking an existing flow). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to run, don't wait passively + +This is about **programmatic execution** (`wmill flow preview -d ''`), which actually runs the flow and has side effects. Visual preview (the `preview` skill) is offered separately — see "Visual preview" below. + +If the user hasn't already told you to run/test the flow, offer it as a one-sentence next step (e.g. "Want me to run `wmill flow preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the flow in their original request, skip the offer and just execute `wmill flow preview -d ''` directly — pick plausible args from the flow's input schema. + +`wmill flow preview` is safe to run yourself (it does not deploy). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +### Visual preview + +To open the flow visually in the dev page (graph + live reload), use the `preview` skill. Always **offer** it as a one-sentence next step (e.g. "Want me to open the visual preview?") rather than opening it automatically — opening the dev page has side effects (browser window, possibly a `launch.json` entry under MCP-preview branches) the user should consent to. If the user already asked to see/preview/visualize the flow in their original request, skip the offer and just invoke the skill. ## OpenFlow Schema diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index b9fdd48ec8..b9f1875095 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -31,15 +31,77 @@ The preprocessor receives a single parameter called \`event\`. export const FLOW_BASE = `# Windmill Flow Building Guide -## CLI Commands +## Creating a Flow + +**You — the AI agent — scaffold the flow yourself by running \`wmill flow new \` with the right flags. Do NOT hand-create the folder + \`flow.yaml\`, and do NOT tell the user to "run \`wmill flow new\` and follow the prompts".** + +\`wmill flow new\` creates the folder with the correct suffix (\`__flow\` or \`.flow\` depending on the workspace's \`nonDottedPaths\` setting), writes a minimal \`flow.yaml\` shell, and prints Claude-specific next-step hints. Scaffolding by hand skips all of that and often picks the wrong suffix. + +### Step 1 — Gather path + summary by asking the user + +You need two things: + +1. **path** — the windmill path, e.g. \`f/folder/my_flow\` or \`u/username/my_flow\`. +2. **summary** — a short description of the flow. + +If the user's request didn't supply both, ask for both in a single round-trip. Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and provide one or two example values for each (with an "Other" / free-form fallback). Do not guess paths or summaries. + +### Step 2 — Run the command yourself + +\`\`\`bash +wmill flow new f/folder/my_flow --summary "Short description" +\`\`\` + +Add \`--description "..."\` when the user provided a longer explanation worth preserving separately from the summary. + +### Step 3 — Fill in \`flow.yaml\` + +Open the generated \`flow.yaml\` (under the folder the command just created) and replace the empty \`value.modules\` + \`schema\` with the real flow definition. -Create a folder ending with \`__flow\` and add a \`flow.yaml\` file with the flow definition. For rawscript modules, use \`!inline path/to/script.ts\` for the content key. Inline script files should NOT include \`.inline_script.\` in their names (e.g. use \`a.ts\`, not \`a.inline_script.ts\`). -After writing, tell the user they can run: -- \`wmill generate-metadata\` - Generate lock files for the flow you modified -- \`wmill sync push\` - Deploy to Windmill -Do NOT run these commands yourself. Instead, inform the user that they should run them. +Once the flow has real content, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a \`launch.json\` entry) and the user should consent. + +### Anti-patterns to avoid + +- ❌ Hand-creating the \`__flow\` folder + \`flow.yaml\` instead of running \`wmill flow new\`. You'll miss the suffix-setting resolution, the default shape, and the Claude hints. +- ❌ Telling the user to "run \`wmill flow new \`" — you can and should run it yourself. +- ❌ Inventing a path/summary instead of asking the user. + +## CLI Commands — running, previewing, deploying + +After writing, tell the user which command fits what they want to do: + +- \`wmill flow preview \` — **default when iterating on a local flow.** Runs the local \`flow.yaml\` against local inline scripts without deploying. Add \`--remote\` to use deployed workspace scripts for PathScript steps instead of local files. +- \`wmill flow run \` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- \`wmill generate-metadata\` — regenerate stale \`.lock\` and \`.script.yaml\` files. By default it scans **scripts, flows, and apps** across the workspace; pass \`--skip-flows --skip-apps\` (or run from a subdirectory) to limit the scope when you only care about the flow you edited. +- \`wmill sync push\` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the flow", "try it", "test it", "does it work" while there are **local edits to a \`flow.yaml\`**, use \`flow preview\`. Do NOT push the flow to then \`flow run\` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use \`flow run\` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local \`flow.yaml\` being edited (you're just invoking an existing flow). + +Only use \`sync push\` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to run, don't wait passively + +This is about **programmatic execution** (\`wmill flow preview -d ''\`), which actually runs the flow and has side effects. Visual preview (the \`preview\` skill) is offered separately — see "Visual preview" below. + +If the user hasn't already told you to run/test the flow, offer it as a one-sentence next step (e.g. "Want me to run \`wmill flow preview\` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the flow in their original request, skip the offer and just execute \`wmill flow preview -d ''\` directly — pick plausible args from the flow's input schema. + +\`wmill flow preview\` is safe to run yourself (it does not deploy). \`wmill sync push\` and \`wmill generate-metadata\` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +### Visual preview + +To open the flow visually in the dev page (graph + live reload), use the \`preview\` skill. Always **offer** it as a one-sentence next step (e.g. "Want me to open the visual preview?") rather than opening it automatically — opening the dev page has side effects (browser window, possibly a \`launch.json\` entry under MCP-preview branches) the user should consent to. If the user already asked to see/preview/visualize the flow in their original request, skip the offer and just invoke the skill. ## OpenFlow Schema @@ -1794,6 +1856,13 @@ app related commands - \`app lint [app_folder:string]\` - Lint a raw app folder to validate structure and buildability - \`--fix\` - Attempt to fix common issues (not implemented yet) - \`app new\` - create a new raw app from a template + - \`--summary \` - App summary (short description). Skips the prompt when provided. Triggers non-interactive mode. + - \`--path \` - App path (e.g., f/folder/my_app or u/username/my_app). Skips the prompt when provided. Triggers non-interactive mode. + - \`--framework \` - Framework template: react19 | react18 | svelte5 | vue. Skips the prompt when provided. Triggers non-interactive mode. + - \`--datatable \` - Datatable to wire up. Without this flag in non-interactive mode, no datatable is configured. + - \`--schema \` - Schema to use with --datatable. Created (CREATE SCHEMA IF NOT EXISTS) if it doesn't already exist. + - \`--overwrite\` - Overwrite the target directory if it already exists, without prompting. + - \`--no-open-in-desktop\` - Do not prompt to open the new app in Claude Desktop. - \`app generate-agents [app_folder:string]\` - regenerate AGENTS.md and DATATABLES.md from remote workspace - \`app set-permissioned-as \` - Set the on_behalf_of_email for an app (requires admin or wm_deployers group) @@ -1830,10 +1899,13 @@ workspace dependencies related commands ### dev -Launch a dev server that watches for local file changes and auto-pushes them to the remote workspace. Provides live reload for scripts and flows during development. +Watch local file changes and live-reload the dev page for preview. Does NOT deploy to the remote workspace — use wmill sync push for that. **Options:** -- \`--includes \` - Filter paths givena glob pattern or path +- \`--includes \` - Filter paths given a glob pattern or path +- \`--proxy-port \` - Port for a localhost reverse proxy to the remote Windmill server +- \`--path \` - Watch a specific windmill path (e.g., u/admin/my_script or f/my_flow) +- \`--no-open\` - Do not open the browser automatically ### docs diff --git a/system_prompts/auto-generated/skills/cli-commands/SKILL.md b/system_prompts/auto-generated/skills/cli-commands/SKILL.md index 0d4d0f5616..ee59185423 100644 --- a/system_prompts/auto-generated/skills/cli-commands/SKILL.md +++ b/system_prompts/auto-generated/skills/cli-commands/SKILL.md @@ -40,6 +40,13 @@ app related commands - `app lint [app_folder:string]` - Lint a raw app folder to validate structure and buildability - `--fix` - Attempt to fix common issues (not implemented yet) - `app new` - create a new raw app from a template + - `--summary ` - App summary (short description). Skips the prompt when provided. Triggers non-interactive mode. + - `--path ` - App path (e.g., f/folder/my_app or u/username/my_app). Skips the prompt when provided. Triggers non-interactive mode. + - `--framework ` - Framework template: react19 | react18 | svelte5 | vue. Skips the prompt when provided. Triggers non-interactive mode. + - `--datatable ` - Datatable to wire up. Without this flag in non-interactive mode, no datatable is configured. + - `--schema ` - Schema to use with --datatable. Created (CREATE SCHEMA IF NOT EXISTS) if it doesn't already exist. + - `--overwrite` - Overwrite the target directory if it already exists, without prompting. + - `--no-open-in-desktop` - Do not prompt to open the new app in Claude Desktop. - `app generate-agents [app_folder:string]` - regenerate AGENTS.md and DATATABLES.md from remote workspace - `app set-permissioned-as ` - Set the on_behalf_of_email for an app (requires admin or wm_deployers group) @@ -76,10 +83,13 @@ workspace dependencies related commands ### dev -Launch a dev server that watches for local file changes and auto-pushes them to the remote workspace. Provides live reload for scripts and flows during development. +Watch local file changes and live-reload the dev page for preview. Does NOT deploy to the remote workspace — use wmill sync push for that. **Options:** -- `--includes ` - Filter paths givena glob pattern or path +- `--includes ` - Filter paths given a glob pattern or path +- `--proxy-port ` - Port for a localhost reverse proxy to the remote Windmill server +- `--path ` - Watch a specific windmill path (e.g., u/admin/my_script or f/my_flow) +- `--no-open` - Do not open the browser automatically ### docs diff --git a/system_prompts/auto-generated/skills/preview/SKILL.md b/system_prompts/auto-generated/skills/preview/SKILL.md new file mode 100644 index 0000000000..56bd54a201 --- /dev/null +++ b/system_prompts/auto-generated/skills/preview/SKILL.md @@ -0,0 +1,126 @@ +--- +name: preview +description: MUST use when opening the Windmill dev page / visual preview of a flow, script, or app. Triggers on words like preview, open, navigate to, visualize, see the flow/app/script, and after writing a flow/script/app for visual verification. +--- + +# Windmill Preview Workflow + +Use this skill any time the user wants to **see**, **open**, **navigate to**, **visualize**, or **preview** a flow, script, or app — and any time you've just finished writing one and want to offer visual verification. + +The Windmill dev page renders the flow graph / script editor, lets the user step through steps, and live-reloads on every save. It runs locally via `wmill dev` and is reached on a localhost port. + +## Two independent decisions + +### 1. Mode: proxy or direct? + +`wmill dev` runs in two modes; pick by asking what kind of URL whatever will display the preview needs. + +- **Proxy** (`--proxy-port `) — exposes the dev page on `http://localhost:/`. Use it when the embedder you'll hand the URL to **only accepts localhost URLs** (most in-IDE / in-chat preview embedders do, because they sandbox cross-origin loads). +- **Direct** (default) — the user's browser loads the dev page from the remote workspace's HTTPS URL; the local `wmill dev` only runs the WebSocket back-channel for live reload. Use it when the URL will be opened in a regular browser tab. + +Default to **direct** unless you have a specific embedder that needs localhost. + +### 2. Who starts the server? + +- **You start it** in the background. Spawn `wmill dev …` (or `wmill app dev …`) yourself, capture the URL it prints, do whatever's next (open a tab, hand the URL to an embedder). +- **The runtime starts it from `.claude/launch.json`.** Some runtimes (currently the Claude Desktop / Claude Code MCP preview integration — tools prefixed with `mcp__Claude_Preview__`) can read a `launch.json` configuration and launch the dev server on demand when you invoke their preview tool. **Only take this path if you actually have such a tool** — otherwise nothing reads the file and `wmill dev` never starts. + +The two decisions compose. The common cases: + +| Embedder | Needs localhost? | launch.json runtime? | What to do | +|---|---|---|---| +| Regular browser tab | No | n/a | Direct mode, you start it, give URL to user | +| IDE / chat preview pane that takes any URL | No | No | Direct mode, you start it, point the embedder at the printed URL | +| IDE / chat preview pane that only accepts localhost | Yes | No | Proxy mode, you start it, point the embedder at `http://localhost:/` | +| Claude Desktop / Code MCP preview | Yes | Yes | Proxy mode, write a `launch.json` entry, invoke the MCP tool | + +Never start the proxy "just in case" — it adds the localhost hop for no benefit when no embedder needs it. + +## Starting the server yourself + +Use this when no `launch.json`-aware runtime is available, regardless of mode. + +For flows / scripts: +```bash +# Direct mode — gives you the remote dev-page URL +wmill dev --path --no-open + +# Proxy mode — gives you a localhost URL that 302s to the remote dev page +wmill dev --proxy-port 4000 --path --no-open +``` + +For apps: +```bash +cd __raw_app && wmill app dev --no-open --port 4000 +``` + +Each command prints the URL on stdout. Line shapes differ: + +- `wmill dev --no-open` (direct) prints `Go to ` with the full remote URL (workspace, token, path baked in). +- `wmill dev --proxy-port` prints `Dev proxy listening on http://localhost:` — the URL to hand to an embedder is `http://localhost:/`. +- `wmill app dev --no-open` prints `🚀 Dev server running at ` — the local app server. + +Capture the URL with a loose match (the first `https?://…` token after startup) and either hand it to your embedder or relay it to the user: *"Preview is running — open `` in your browser."* Don't construct the URL yourself; you don't have the workspace ID or auth token. + +These commands are long-running — start them in the background, don't block waiting. + +## Letting `launch.json` start the server (Claude Desktop / Code MCP only) + +Take this path when **and only when** an `mcp__Claude_Preview__*` MCP tool is exposed in your tool list. Skip it otherwise — without an MCP tool reading the file, `wmill dev` never starts. + +**Each flow / script / app gets its own named entry** in the user's `.claude/launch.json` so multiple previews coexist without colliding — each entry pins a different port + path. Never reuse a generic "windmill" entry for different targets. + +### Step 1 — Reuse or add a per-target entry in `.claude/launch.json` + +Convention: name the entry `windmill: ` (e.g. `windmill: f/test/my_flow`). + +- **Entry already exists** → reuse it; note its `port` for the next step. +- **Not there** → add one. Pick a port not already taken by another entry (start at 4000 and bump). Shape: + +For flows / scripts: +```json +{ + "name": "windmill: f/test/my_flow", + "runtimeExecutable": "bash", + "runtimeArgs": ["-c", "wmill dev --proxy-port ${PORT:-4000} --path f/test/my_flow --no-open"], + "port": 4000, + "autoPort": true +} +``` + +For apps (`*__raw_app/`), `wmill app dev` is the equivalent — runs from the app folder, no `--path`: +```json +{ + "name": "windmill: f/test/my_app", + "runtimeExecutable": "bash", + "runtimeArgs": ["-c", "cd f/test/my_app__raw_app && wmill app dev --no-open --port ${PORT:-4001}"], + "port": 4001, + "autoPort": true +} +``` + +If `.claude/launch.json` doesn't exist yet, create it with the standard shell `{ "version": "0.0.1", "configurations": [...] }`. + +### Step 2 — Invoke the MCP preview tool + +Point it at the entry you just added/found. Use `http://localhost:/` as the URL — the proxy's redirect at `/` is what appends the workspace ID, the auth token, and the path. Do **NOT** construct a `/dev?...` URL yourself. + +The MCP tool launches the configuration on demand, so you don't need to start the `wmill dev` process manually. + +## Non-visual alternative + +If the user wants a programmatic test rather than a visual one: +- Flow: `wmill flow preview -d ''` +- Script: `wmill script preview -d ''` + +Both print the job result, are safe to run yourself, and don't deploy. + +## Anti-patterns to avoid + +- ❌ Writing a `.claude/launch.json` entry when no `mcp__Claude_Preview__*` tool is in your tool list. Nothing will read the file; the server never starts. Spawn `wmill dev` yourself instead. +- ❌ Starting the proxy when no embedder needs a localhost URL. Direct mode is the right choice — the proxy is overhead with no purpose. +- ❌ Reusing a single generic `launch.json` entry for every preview target. Each flow/script/app gets its own named entry on its own port — that's how multiple sessions coexist without one preview clobbering another. +- ❌ Mutating an existing entry's `--path` to retarget it. Add a new entry instead. +- ❌ Constructing `http://localhost:/dev?path=` yourself. The proxy's `/` redirect is what appends the workspace ID and auth token; bypassing it gives a broken page. Always use `http://localhost:/`. +- ❌ Starting `wmill dev` in the foreground (you'll hang). Always background. +- ❌ Listing both "open in IDE pane" and "open in browser" as a menu — pick one based on context. diff --git a/system_prompts/auto-generated/skills/raw-app/SKILL.md b/system_prompts/auto-generated/skills/raw-app/SKILL.md index c835745158..5ac3a69cff 100644 --- a/system_prompts/auto-generated/skills/raw-app/SKILL.md +++ b/system_prompts/auto-generated/skills/raw-app/SKILL.md @@ -9,11 +9,70 @@ Raw apps let you build custom frontends with React, Svelte, or Vue that connect ## Creating a Raw App +**You — the AI agent — create the app yourself by running `wmill app new` with the right flags. Do NOT tell the user to "run `wmill app new` and follow the prompts" or wait for them to do it.** The bare `wmill app new` is an interactive wizard that hangs waiting for stdin in any non-TTY context (which includes you). Always pass flags. + +### Step 1 — Gather the three required values by asking the user + +You need three things to run the command: + +1. **summary** — a short description of the app +2. **path** — the windmill path, e.g. `f/folder/my_app` or `u/username/my_app` +3. **framework** — one of `react19` (recommended), `react18`, `svelte5`, `vue` + +If the user's request did not supply *every* one of these explicitly, ask. Do not guess values, do not invent paths, do not pick a framework on the user's behalf, do not "just use react19 because it's the default". + +Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and group all missing fields into a single round-trip so the user answers them at once: + +- For `framework` — multiple-choice with the four allowed values; mark `react19` as `(Recommended)` and put it first. +- For `summary` and `path` — provide one or two example values as multiple-choice options (the user can pick "Other" to type a free-form answer). + +Only proceed once you have concrete values for all three. If the user replies with something ambiguous, ask again rather than guessing. + +### Step 2 — Run the command yourself + +Once you have summary + path + framework, run it: + +```bash +wmill app new \ + --summary "Customer dashboard" \ + --path f/sales/dashboard \ + --framework react19 +``` + +That's the minimum. The datatable wizard and the "Open in Claude Desktop?" prompt are skipped silently because passing any of `--summary`/`--path`/`--framework` puts the command in non-interactive mode. + +### Optional flags + +Layer these in only when the user asked for them: + +| Flag | When to add it | +|---|---| +| `--datatable ` | The user wants this app wired to a specific Windmill datatable. Without it, the app is created with no datatable. | +| `--schema ` | Together with `--datatable`. Creates the schema with `CREATE SCHEMA IF NOT EXISTS` if it doesn't already exist. | +| `--overwrite` | The target directory already exists and the user said it's OK to replace. Without it, non-interactive mode aborts with an error so you don't clobber existing work. | +| `--no-open-in-desktop` | Already implied in non-interactive mode; only needed if you're somehow running interactively. | + +### Step 3 — Offer the visual preview + +After `wmill app new` and any initial edits to `App.tsx` / `index.tsx`, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a `launch.json` entry when an embedded preview tool is in play) the user should consent to. + +For apps the preview command runs from the app folder (`cd __raw_app && wmill app dev …`); the `preview` skill picks the proxy vs direct branch based on whether the runtime exposes a tool that can embed a localhost URL. If the user already asked to see/preview/visualize the app in their original request, skip the offer and just invoke the skill. + +### Anti-patterns to avoid + +- ❌ Running `wmill app new` with no flags (the prompt will hang). +- ❌ Telling the user to "run `wmill app new` and follow the prompts" — that's a step backwards from what you can do directly. +- ❌ Inventing a path/summary/framework instead of asking the user. +- ❌ Defaulting to `react19` because the user didn't say — even sensible defaults must be confirmed. +- ❌ Passing `--overwrite` automatically when the directory exists — confirm with the user first. + +### Interactive (only when a human is at the terminal) + ```bash wmill app new ``` -This interactive command creates a complete app structure with your choice of frontend framework (React, Svelte, or Vue). +This is the wizard. It only works when run by a human in a real terminal. Don't call it this way from an agent. ## App Structure @@ -237,12 +296,13 @@ data: ## CLI Commands -Tell the user they can run these commands (do NOT run them yourself): +`wmill app new` is the exception: you run it yourself, with flags, per the "Creating a Raw App" section above. + +For everything else, tell the user which command fits their intent and let them run it — these touch the workspace or local lock files, and the user should consent each time: | Command | Description | |---------|-------------| -| `wmill app new` | Create a new raw app interactively | -| `wmill app dev` | Start dev server with live reload | +| `wmill app dev` | Start dev server with live reload (see the `preview` skill for the full open-the-app-in-the-IDE-pane procedure). | | `wmill app generate-agents` | Refresh AGENTS.md and DATATABLES.md | | `wmill generate-metadata` | Generate lock files for backend runnables | | `wmill sync push` | Deploy app to Windmill | diff --git a/system_prompts/auto-generated/skills/write-flow/SKILL.md b/system_prompts/auto-generated/skills/write-flow/SKILL.md index 1d1398f888..65222e0479 100644 --- a/system_prompts/auto-generated/skills/write-flow/SKILL.md +++ b/system_prompts/auto-generated/skills/write-flow/SKILL.md @@ -5,15 +5,77 @@ description: MUST use when creating flows. # Windmill Flow Building Guide -## CLI Commands +## Creating a Flow + +**You — the AI agent — scaffold the flow yourself by running `wmill flow new ` with the right flags. Do NOT hand-create the folder + `flow.yaml`, and do NOT tell the user to "run `wmill flow new` and follow the prompts".** + +`wmill flow new` creates the folder with the correct suffix (`__flow` or `.flow` depending on the workspace's `nonDottedPaths` setting), writes a minimal `flow.yaml` shell, and prints Claude-specific next-step hints. Scaffolding by hand skips all of that and often picks the wrong suffix. + +### Step 1 — Gather path + summary by asking the user + +You need two things: + +1. **path** — the windmill path, e.g. `f/folder/my_flow` or `u/username/my_flow`. +2. **summary** — a short description of the flow. + +If the user's request didn't supply both, ask for both in a single round-trip. Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and provide one or two example values for each (with an "Other" / free-form fallback). Do not guess paths or summaries. + +### Step 2 — Run the command yourself + +```bash +wmill flow new f/folder/my_flow --summary "Short description" +``` + +Add `--description "..."` when the user provided a longer explanation worth preserving separately from the summary. + +### Step 3 — Fill in `flow.yaml` + +Open the generated `flow.yaml` (under the folder the command just created) and replace the empty `value.modules` + `schema` with the real flow definition. -Create a folder ending with `__flow` and add a `flow.yaml` file with the flow definition. For rawscript modules, use `!inline path/to/script.ts` for the content key. Inline script files should NOT include `.inline_script.` in their names (e.g. use `a.ts`, not `a.inline_script.ts`). -After writing, tell the user they can run: -- `wmill generate-metadata` - Generate lock files for the flow you modified -- `wmill sync push` - Deploy to Windmill -Do NOT run these commands yourself. Instead, inform the user that they should run them. +Once the flow has real content, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a `launch.json` entry) and the user should consent. + +### Anti-patterns to avoid + +- ❌ Hand-creating the `__flow` folder + `flow.yaml` instead of running `wmill flow new`. You'll miss the suffix-setting resolution, the default shape, and the Claude hints. +- ❌ Telling the user to "run `wmill flow new `" — you can and should run it yourself. +- ❌ Inventing a path/summary instead of asking the user. + +## CLI Commands — running, previewing, deploying + +After writing, tell the user which command fits what they want to do: + +- `wmill flow preview ` — **default when iterating on a local flow.** Runs the local `flow.yaml` against local inline scripts without deploying. Add `--remote` to use deployed workspace scripts for PathScript steps instead of local files. +- `wmill flow run ` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — regenerate stale `.lock` and `.script.yaml` files. By default it scans **scripts, flows, and apps** across the workspace; pass `--skip-flows --skip-apps` (or run from a subdirectory) to limit the scope when you only care about the flow you edited. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the flow", "try it", "test it", "does it work" while there are **local edits to a `flow.yaml`**, use `flow preview`. Do NOT push the flow to then `flow run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `flow run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local `flow.yaml` being edited (you're just invoking an existing flow). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to run, don't wait passively + +This is about **programmatic execution** (`wmill flow preview -d ''`), which actually runs the flow and has side effects. Visual preview (the `preview` skill) is offered separately — see "Visual preview" below. + +If the user hasn't already told you to run/test the flow, offer it as a one-sentence next step (e.g. "Want me to run `wmill flow preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the flow in their original request, skip the offer and just execute `wmill flow preview -d ''` directly — pick plausible args from the flow's input schema. + +`wmill flow preview` is safe to run yourself (it does not deploy). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +### Visual preview + +To open the flow visually in the dev page (graph + live reload), use the `preview` skill. Always **offer** it as a one-sentence next step (e.g. "Want me to open the visual preview?") rather than opening it automatically — opening the dev page has side effects (browser window, possibly a `launch.json` entry under MCP-preview branches) the user should consent to. If the user already asked to see/preview/visualize the flow in their original request, skip the offer and just invoke the skill. ## OpenFlow Schema diff --git a/system_prompts/auto-generated/skills/write-script-bash/SKILL.md b/system_prompts/auto-generated/skills/write-script-bash/SKILL.md index 55f71ca172..ba4041c819 100644 --- a/system_prompts/auto-generated/skills/write-script-bash/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-bash/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Bash scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-bigquery/SKILL.md b/system_prompts/auto-generated/skills/write-script-bigquery/SKILL.md index 02b1705ee2..e2347163c3 100644 --- a/system_prompts/auto-generated/skills/write-script-bigquery/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-bigquery/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing BigQuery queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-bun/SKILL.md b/system_prompts/auto-generated/skills/write-script-bun/SKILL.md index 0e802b6fba..39d66a6433 100644 --- a/system_prompts/auto-generated/skills/write-script-bun/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-bun/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Bun/TypeScript scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md b/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md index 3e411d4d81..442a6d4a7b 100644 --- a/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-bunnative/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Bun Native scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-csharp/SKILL.md b/system_prompts/auto-generated/skills/write-script-csharp/SKILL.md index 9d1b481ddf..98f089443e 100644 --- a/system_prompts/auto-generated/skills/write-script-csharp/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-csharp/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing C# scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-deno/SKILL.md b/system_prompts/auto-generated/skills/write-script-deno/SKILL.md index a250d5d60a..08b18f2a40 100644 --- a/system_prompts/auto-generated/skills/write-script-deno/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-deno/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Deno/TypeScript scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-duckdb/SKILL.md b/system_prompts/auto-generated/skills/write-script-duckdb/SKILL.md index 78a349b975..c19544da17 100644 --- a/system_prompts/auto-generated/skills/write-script-duckdb/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-duckdb/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing DuckDB queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-go/SKILL.md b/system_prompts/auto-generated/skills/write-script-go/SKILL.md index 5d04844115..9ff4e581e8 100644 --- a/system_prompts/auto-generated/skills/write-script-go/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-go/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Go scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-graphql/SKILL.md b/system_prompts/auto-generated/skills/write-script-graphql/SKILL.md index 010bfafbf6..727bf5309a 100644 --- a/system_prompts/auto-generated/skills/write-script-graphql/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-graphql/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing GraphQL queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-java/SKILL.md b/system_prompts/auto-generated/skills/write-script-java/SKILL.md index e07600e5a2..132cd603f1 100644 --- a/system_prompts/auto-generated/skills/write-script-java/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-java/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Java scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-mssql/SKILL.md b/system_prompts/auto-generated/skills/write-script-mssql/SKILL.md index a8ce92ff44..7a64a03ea7 100644 --- a/system_prompts/auto-generated/skills/write-script-mssql/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-mssql/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing MS SQL Server queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-mysql/SKILL.md b/system_prompts/auto-generated/skills/write-script-mysql/SKILL.md index 0cfd005dcd..2c9044be23 100644 --- a/system_prompts/auto-generated/skills/write-script-mysql/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-mysql/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing MySQL queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-nativets/SKILL.md b/system_prompts/auto-generated/skills/write-script-nativets/SKILL.md index df981a2f90..7c0ea92a17 100644 --- a/system_prompts/auto-generated/skills/write-script-nativets/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-nativets/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Native TypeScript scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-php/SKILL.md b/system_prompts/auto-generated/skills/write-script-php/SKILL.md index 0667139b12..0f8cb2e4a9 100644 --- a/system_prompts/auto-generated/skills/write-script-php/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-php/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing PHP scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-postgresql/SKILL.md b/system_prompts/auto-generated/skills/write-script-postgresql/SKILL.md index cb12fabf28..e370b7a3f2 100644 --- a/system_prompts/auto-generated/skills/write-script-postgresql/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-postgresql/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing PostgreSQL queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-powershell/SKILL.md b/system_prompts/auto-generated/skills/write-script-powershell/SKILL.md index 2c5787fa98..eadfa56b86 100644 --- a/system_prompts/auto-generated/skills/write-script-powershell/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-powershell/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing PowerShell scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-python3/SKILL.md b/system_prompts/auto-generated/skills/write-script-python3/SKILL.md index 04c87ca9eb..a0f16fb732 100644 --- a/system_prompts/auto-generated/skills/write-script-python3/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-python3/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Python scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-rlang/SKILL.md b/system_prompts/auto-generated/skills/write-script-rlang/SKILL.md index 27c08d5ef8..429bc2600f 100644 --- a/system_prompts/auto-generated/skills/write-script-rlang/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-rlang/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing R scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-rust/SKILL.md b/system_prompts/auto-generated/skills/write-script-rust/SKILL.md index cdbfd93ea6..52b61a0c0a 100644 --- a/system_prompts/auto-generated/skills/write-script-rust/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-rust/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Rust scripts. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/auto-generated/skills/write-script-snowflake/SKILL.md b/system_prompts/auto-generated/skills/write-script-snowflake/SKILL.md index 16e06c082a..68c49ffa6e 100644 --- a/system_prompts/auto-generated/skills/write-script-snowflake/SKILL.md +++ b/system_prompts/auto-generated/skills/write-script-snowflake/SKILL.md @@ -5,11 +5,36 @@ description: MUST use when writing Snowflake queries. ## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types. diff --git a/system_prompts/base/flow-base.md b/system_prompts/base/flow-base.md index 024f7152a9..ad15c37b44 100644 --- a/system_prompts/base/flow-base.md +++ b/system_prompts/base/flow-base.md @@ -1,14 +1,76 @@ # Windmill Flow Building Guide -## CLI Commands +## Creating a Flow + +**You — the AI agent — scaffold the flow yourself by running `wmill flow new ` with the right flags. Do NOT hand-create the folder + `flow.yaml`, and do NOT tell the user to "run `wmill flow new` and follow the prompts".** + +`wmill flow new` creates the folder with the correct suffix (`__flow` or `.flow` depending on the workspace's `nonDottedPaths` setting), writes a minimal `flow.yaml` shell, and prints Claude-specific next-step hints. Scaffolding by hand skips all of that and often picks the wrong suffix. + +### Step 1 — Gather path + summary by asking the user + +You need two things: + +1. **path** — the windmill path, e.g. `f/folder/my_flow` or `u/username/my_flow`. +2. **summary** — a short description of the flow. + +If the user's request didn't supply both, ask for both in a single round-trip. Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and provide one or two example values for each (with an "Other" / free-form fallback). Do not guess paths or summaries. + +### Step 2 — Run the command yourself + +```bash +wmill flow new f/folder/my_flow --summary "Short description" +``` + +Add `--description "..."` when the user provided a longer explanation worth preserving separately from the summary. + +### Step 3 — Fill in `flow.yaml` + +Open the generated `flow.yaml` (under the folder the command just created) and replace the empty `value.modules` + `schema` with the real flow definition. -Create a folder ending with `__flow` and add a `flow.yaml` file with the flow definition. For rawscript modules, use `!inline path/to/script.ts` for the content key. Inline script files should NOT include `.inline_script.` in their names (e.g. use `a.ts`, not `a.inline_script.ts`). -After writing, tell the user they can run: -- `wmill generate-metadata` - Generate lock files for the flow you modified -- `wmill sync push` - Deploy to Windmill -Do NOT run these commands yourself. Instead, inform the user that they should run them. +Once the flow has real content, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a `launch.json` entry) and the user should consent. + +### Anti-patterns to avoid + +- ❌ Hand-creating the `__flow` folder + `flow.yaml` instead of running `wmill flow new`. You'll miss the suffix-setting resolution, the default shape, and the Claude hints. +- ❌ Telling the user to "run `wmill flow new `" — you can and should run it yourself. +- ❌ Inventing a path/summary instead of asking the user. + +## CLI Commands — running, previewing, deploying + +After writing, tell the user which command fits what they want to do: + +- `wmill flow preview ` — **default when iterating on a local flow.** Runs the local `flow.yaml` against local inline scripts without deploying. Add `--remote` to use deployed workspace scripts for PathScript steps instead of local files. +- `wmill flow run ` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — regenerate stale `.lock` and `.script.yaml` files. By default it scans **scripts, flows, and apps** across the workspace; pass `--skip-flows --skip-apps` (or run from a subdirectory) to limit the scope when you only care about the flow you edited. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the flow", "try it", "test it", "does it work" while there are **local edits to a `flow.yaml`**, use `flow preview`. Do NOT push the flow to then `flow run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `flow run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local `flow.yaml` being edited (you're just invoking an existing flow). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to run, don't wait passively + +This is about **programmatic execution** (`wmill flow preview -d ''`), which actually runs the flow and has side effects. Visual preview (the `preview` skill) is offered separately — see "Visual preview" below. + +If the user hasn't already told you to run/test the flow, offer it as a one-sentence next step (e.g. "Want me to run `wmill flow preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the flow in their original request, skip the offer and just execute `wmill flow preview -d ''` directly — pick plausible args from the flow's input schema. + +`wmill flow preview` is safe to run yourself (it does not deploy). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +### Visual preview + +To open the flow visually in the dev page (graph + live reload), use the `preview` skill. Always **offer** it as a one-sentence next step (e.g. "Want me to open the visual preview?") rather than opening it automatically — opening the dev page has side effects (browser window, possibly a `launch.json` entry under MCP-preview branches) the user should consent to. If the user already asked to see/preview/visualize the flow in their original request, skip the offer and just invoke the skill. ## OpenFlow Schema diff --git a/system_prompts/base/preview.md b/system_prompts/base/preview.md new file mode 100644 index 0000000000..585b1c8f6f --- /dev/null +++ b/system_prompts/base/preview.md @@ -0,0 +1,121 @@ +# Windmill Preview Workflow + +Use this skill any time the user wants to **see**, **open**, **navigate to**, **visualize**, or **preview** a flow, script, or app — and any time you've just finished writing one and want to offer visual verification. + +The Windmill dev page renders the flow graph / script editor, lets the user step through steps, and live-reloads on every save. It runs locally via `wmill dev` and is reached on a localhost port. + +## Two independent decisions + +### 1. Mode: proxy or direct? + +`wmill dev` runs in two modes; pick by asking what kind of URL whatever will display the preview needs. + +- **Proxy** (`--proxy-port `) — exposes the dev page on `http://localhost:/`. Use it when the embedder you'll hand the URL to **only accepts localhost URLs** (most in-IDE / in-chat preview embedders do, because they sandbox cross-origin loads). +- **Direct** (default) — the user's browser loads the dev page from the remote workspace's HTTPS URL; the local `wmill dev` only runs the WebSocket back-channel for live reload. Use it when the URL will be opened in a regular browser tab. + +Default to **direct** unless you have a specific embedder that needs localhost. + +### 2. Who starts the server? + +- **You start it** in the background. Spawn `wmill dev …` (or `wmill app dev …`) yourself, capture the URL it prints, do whatever's next (open a tab, hand the URL to an embedder). +- **The runtime starts it from `.claude/launch.json`.** Some runtimes (currently the Claude Desktop / Claude Code MCP preview integration — tools prefixed with `mcp__Claude_Preview__`) can read a `launch.json` configuration and launch the dev server on demand when you invoke their preview tool. **Only take this path if you actually have such a tool** — otherwise nothing reads the file and `wmill dev` never starts. + +The two decisions compose. The common cases: + +| Embedder | Needs localhost? | launch.json runtime? | What to do | +|---|---|---|---| +| Regular browser tab | No | n/a | Direct mode, you start it, give URL to user | +| IDE / chat preview pane that takes any URL | No | No | Direct mode, you start it, point the embedder at the printed URL | +| IDE / chat preview pane that only accepts localhost | Yes | No | Proxy mode, you start it, point the embedder at `http://localhost:/` | +| Claude Desktop / Code MCP preview | Yes | Yes | Proxy mode, write a `launch.json` entry, invoke the MCP tool | + +Never start the proxy "just in case" — it adds the localhost hop for no benefit when no embedder needs it. + +## Starting the server yourself + +Use this when no `launch.json`-aware runtime is available, regardless of mode. + +For flows / scripts: +```bash +# Direct mode — gives you the remote dev-page URL +wmill dev --path --no-open + +# Proxy mode — gives you a localhost URL that 302s to the remote dev page +wmill dev --proxy-port 4000 --path --no-open +``` + +For apps: +```bash +cd __raw_app && wmill app dev --no-open --port 4000 +``` + +Each command prints the URL on stdout. Line shapes differ: + +- `wmill dev --no-open` (direct) prints `Go to ` with the full remote URL (workspace, token, path baked in). +- `wmill dev --proxy-port` prints `Dev proxy listening on http://localhost:` — the URL to hand to an embedder is `http://localhost:/`. +- `wmill app dev --no-open` prints `🚀 Dev server running at ` — the local app server. + +Capture the URL with a loose match (the first `https?://…` token after startup) and either hand it to your embedder or relay it to the user: *"Preview is running — open `` in your browser."* Don't construct the URL yourself; you don't have the workspace ID or auth token. + +These commands are long-running — start them in the background, don't block waiting. + +## Letting `launch.json` start the server (Claude Desktop / Code MCP only) + +Take this path when **and only when** an `mcp__Claude_Preview__*` MCP tool is exposed in your tool list. Skip it otherwise — without an MCP tool reading the file, `wmill dev` never starts. + +**Each flow / script / app gets its own named entry** in the user's `.claude/launch.json` so multiple previews coexist without colliding — each entry pins a different port + path. Never reuse a generic "windmill" entry for different targets. + +### Step 1 — Reuse or add a per-target entry in `.claude/launch.json` + +Convention: name the entry `windmill: ` (e.g. `windmill: f/test/my_flow`). + +- **Entry already exists** → reuse it; note its `port` for the next step. +- **Not there** → add one. Pick a port not already taken by another entry (start at 4000 and bump). Shape: + +For flows / scripts: +```json +{ + "name": "windmill: f/test/my_flow", + "runtimeExecutable": "bash", + "runtimeArgs": ["-c", "wmill dev --proxy-port ${PORT:-4000} --path f/test/my_flow --no-open"], + "port": 4000, + "autoPort": true +} +``` + +For apps (`*__raw_app/`), `wmill app dev` is the equivalent — runs from the app folder, no `--path`: +```json +{ + "name": "windmill: f/test/my_app", + "runtimeExecutable": "bash", + "runtimeArgs": ["-c", "cd f/test/my_app__raw_app && wmill app dev --no-open --port ${PORT:-4001}"], + "port": 4001, + "autoPort": true +} +``` + +If `.claude/launch.json` doesn't exist yet, create it with the standard shell `{ "version": "0.0.1", "configurations": [...] }`. + +### Step 2 — Invoke the MCP preview tool + +Point it at the entry you just added/found. Use `http://localhost:/` as the URL — the proxy's redirect at `/` is what appends the workspace ID, the auth token, and the path. Do **NOT** construct a `/dev?...` URL yourself. + +The MCP tool launches the configuration on demand, so you don't need to start the `wmill dev` process manually. + +## Non-visual alternative + +If the user wants a programmatic test rather than a visual one: +- Flow: `wmill flow preview -d ''` +- Script: `wmill script preview -d ''` + +Both print the job result, are safe to run yourself, and don't deploy. + +## Anti-patterns to avoid + +- ❌ Writing a `.claude/launch.json` entry when no `mcp__Claude_Preview__*` tool is in your tool list. Nothing will read the file; the server never starts. Spawn `wmill dev` yourself instead. +- ❌ Starting the proxy when no embedder needs a localhost URL. Direct mode is the right choice — the proxy is overhead with no purpose. +- ❌ Reusing a single generic `launch.json` entry for every preview target. Each flow/script/app gets its own named entry on its own port — that's how multiple sessions coexist without one preview clobbering another. +- ❌ Mutating an existing entry's `--path` to retarget it. Add a new entry instead. +- ❌ Constructing `http://localhost:/dev?path=` yourself. The proxy's `/` redirect is what appends the workspace ID and auth token; bypassing it gives a broken page. Always use `http://localhost:/`. +- ❌ Starting `wmill dev` in the foreground (you'll hang). Always background. +- ❌ Listing both "open in IDE pane" and "open in browser" as a menu — pick one based on context. diff --git a/system_prompts/base/raw-app.md b/system_prompts/base/raw-app.md index ac1292a5df..4114c55a60 100644 --- a/system_prompts/base/raw-app.md +++ b/system_prompts/base/raw-app.md @@ -4,11 +4,70 @@ Raw apps let you build custom frontends with React, Svelte, or Vue that connect ## Creating a Raw App +**You — the AI agent — create the app yourself by running `wmill app new` with the right flags. Do NOT tell the user to "run `wmill app new` and follow the prompts" or wait for them to do it.** The bare `wmill app new` is an interactive wizard that hangs waiting for stdin in any non-TTY context (which includes you). Always pass flags. + +### Step 1 — Gather the three required values by asking the user + +You need three things to run the command: + +1. **summary** — a short description of the app +2. **path** — the windmill path, e.g. `f/folder/my_app` or `u/username/my_app` +3. **framework** — one of `react19` (recommended), `react18`, `svelte5`, `vue` + +If the user's request did not supply *every* one of these explicitly, ask. Do not guess values, do not invent paths, do not pick a framework on the user's behalf, do not "just use react19 because it's the default". + +Use whichever interactive question facility your runtime provides — a structured multi-choice tool if available, otherwise plain chat — and group all missing fields into a single round-trip so the user answers them at once: + +- For `framework` — multiple-choice with the four allowed values; mark `react19` as `(Recommended)` and put it first. +- For `summary` and `path` — provide one or two example values as multiple-choice options (the user can pick "Other" to type a free-form answer). + +Only proceed once you have concrete values for all three. If the user replies with something ambiguous, ask again rather than guessing. + +### Step 2 — Run the command yourself + +Once you have summary + path + framework, run it: + +```bash +wmill app new \ + --summary "Customer dashboard" \ + --path f/sales/dashboard \ + --framework react19 +``` + +That's the minimum. The datatable wizard and the "Open in Claude Desktop?" prompt are skipped silently because passing any of `--summary`/`--path`/`--framework` puts the command in non-interactive mode. + +### Optional flags + +Layer these in only when the user asked for them: + +| Flag | When to add it | +|---|---| +| `--datatable ` | The user wants this app wired to a specific Windmill datatable. Without it, the app is created with no datatable. | +| `--schema ` | Together with `--datatable`. Creates the schema with `CREATE SCHEMA IF NOT EXISTS` if it doesn't already exist. | +| `--overwrite` | The target directory already exists and the user said it's OK to replace. Without it, non-interactive mode aborts with an error so you don't clobber existing work. | +| `--no-open-in-desktop` | Already implied in non-interactive mode; only needed if you're somehow running interactively. | + +### Step 3 — Offer the visual preview + +After `wmill app new` and any initial edits to `App.tsx` / `index.tsx`, **offer** to open the visual preview as a one-sentence next step (e.g. "Want me to open the visual preview?"). Don't auto-open — opening the dev page has side effects (browser window, possibly a `launch.json` entry when an embedded preview tool is in play) the user should consent to. + +For apps the preview command runs from the app folder (`cd __raw_app && wmill app dev …`); the `preview` skill picks the proxy vs direct branch based on whether the runtime exposes a tool that can embed a localhost URL. If the user already asked to see/preview/visualize the app in their original request, skip the offer and just invoke the skill. + +### Anti-patterns to avoid + +- ❌ Running `wmill app new` with no flags (the prompt will hang). +- ❌ Telling the user to "run `wmill app new` and follow the prompts" — that's a step backwards from what you can do directly. +- ❌ Inventing a path/summary/framework instead of asking the user. +- ❌ Defaulting to `react19` because the user didn't say — even sensible defaults must be confirmed. +- ❌ Passing `--overwrite` automatically when the directory exists — confirm with the user first. + +### Interactive (only when a human is at the terminal) + ```bash wmill app new ``` -This interactive command creates a complete app structure with your choice of frontend framework (React, Svelte, or Vue). +This is the wizard. It only works when run by a human in a real terminal. Don't call it this way from an agent. ## App Structure @@ -232,12 +291,13 @@ data: ## CLI Commands -Tell the user they can run these commands (do NOT run them yourself): +`wmill app new` is the exception: you run it yourself, with flags, per the "Creating a Raw App" section above. + +For everything else, tell the user which command fits their intent and let them run it — these touch the workspace or local lock files, and the user should consent each time: | Command | Description | |---------|-------------| -| `wmill app new` | Create a new raw app interactively | -| `wmill app dev` | Start dev server with live reload | +| `wmill app dev` | Start dev server with live reload (see the `preview` skill for the full open-the-app-in-the-IDE-pane procedure). | | `wmill app generate-agents` | Refresh AGENTS.md and DATATABLES.md | | `wmill generate-metadata` | Generate lock files for backend runnables | | `wmill sync push` | Deploy app to Windmill | diff --git a/system_prompts/generate.py b/system_prompts/generate.py index aa9cf2b85b..f861c3fd3d 100644 --- a/system_prompts/generate.py +++ b/system_prompts/generate.py @@ -931,6 +931,11 @@ SKILL_DEFINITIONS = [ 'description': 'MUST use when using the CLI, including debugging job failures and inspecting run history via `wmill job`.', 'content_key': 'cli_commands', }, + { + 'name': 'preview', + 'description': 'MUST use when opening the Windmill dev page / visual preview of a flow, script, or app. Triggers on words like preview, open, navigate to, visualize, see the flow/app/script, and after writing a flow/script/app for visual verification.', + 'content_key': 'preview', + }, ] @@ -960,16 +965,42 @@ def generate_skills( 'schedules': read_markdown_file(base_dir / "schedules.md"), 'resources': read_markdown_file(base_dir / "resources.md"), 'cli_commands': cli_commands, + 'preview': read_markdown_file(base_dir / "preview.md"), } # CLI intro for script skills script_cli_intro = """## CLI Commands -Place scripts in a folder. After writing, tell the user they can run: -- `wmill generate-metadata` - Generate .script.yaml and .lock files -- `wmill sync push` - Deploy to Windmill +Place scripts in a folder. -Do NOT run these commands yourself. Instead, inform the user that they should run them. +After writing, tell the user which command fits what they want to do: + +- `wmill script preview ` — **default when iterating on a local script.** Runs the local file without deploying. +- `wmill script run ` — runs the script **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits. +- `wmill generate-metadata` — generate `.script.yaml` and `.lock` files for the script you modified. +- `wmill sync push` — deploy local changes to the workspace. Only suggest/run this when the user explicitly asks to deploy/publish/push — not when they say "run", "try", or "test". + +### Preview vs run — choose by intent, not habit + +If the user says "run the script", "try it", "test it", "does it work" while there are **local edits to the script file**, use `script preview`. Do NOT push the script to then `script run` it — pushing is a deploy, and deploying just to test overwrites the workspace version with untested changes. + +Only use `script run` when: +- The user explicitly says "run the deployed version" / "run what's on the server". +- There is no local script being edited (you're just invoking an existing script). + +Only use `sync push` when: +- The user explicitly asks to deploy, publish, push, or ship. +- The preview has already validated the change and the user wants it in the workspace. + +### After writing — offer to test, don't wait passively + +If the user hasn't already told you to run/test/preview the script, offer it as a one-sentence next step (e.g. "Want me to run `wmill script preview` with sample args?"). Do not present a multi-option menu. + +If the user already asked to test/run/try the script in their original request, skip the offer and just execute `wmill script preview -d ''` directly — pick plausible args from the script's declared parameters. The shape varies by language: `main(...)` for code languages, the SQL dialect's own placeholder syntax (`$1` for PostgreSQL, `?` for MySQL/Snowflake, `@P1` for MSSQL, `@name` for BigQuery, etc.), positional `$1`, `$2`, … for Bash, `param(...)` for PowerShell. + +`wmill script preview` does not deploy, but it still executes script code and may cause side effects; run it yourself when the user asked to test/preview (or after confirming that execution is intended). `wmill sync push` and `wmill generate-metadata` modify workspace state or local files — only run these when the user explicitly asks; otherwise tell them which to run. + +For a **visual** open-the-script-in-the-dev-page preview (rather than `script preview`'s run-and-print-result), use the `preview` skill. Use `wmill resource-type list --schema` to discover available resource types."""