windmill-cli 1.710.0 → 1.711.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/esm/main.js +435 -19
  2. package/package.json +1 -1
package/esm/main.js CHANGED
@@ -16772,7 +16772,7 @@ var init_OpenAPI = __esm(() => {
16772
16772
  PASSWORD: undefined,
16773
16773
  TOKEN: getEnv3("WM_TOKEN"),
16774
16774
  USERNAME: undefined,
16775
- VERSION: "1.710.0",
16775
+ VERSION: "1.711.0",
16776
16776
  WITH_CREDENTIALS: true,
16777
16777
  interceptors: {
16778
16778
  request: new Interceptors,
@@ -76130,7 +76130,7 @@ var init_local_path_scripts = __esm(async () => {
76130
76130
  });
76131
76131
 
76132
76132
  // src/commands/flow/flow.ts
76133
- import { sep as SEP20 } from "node:path";
76133
+ import { dirname as dirname17, sep as SEP20 } from "node:path";
76134
76134
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
76135
76135
  function normalizeOptionalString(value) {
76136
76136
  return typeof value === "string" && value.trim() === "" ? undefined : value ?? undefined;
@@ -76501,7 +76501,7 @@ async function preview2(opts, flowPath) {
76501
76501
  const isFlowDir = flowPath.endsWith(".flow") || flowPath.endsWith(".flow" + SEP20) || flowPath.endsWith("__flow") || flowPath.endsWith("__flow" + SEP20);
76502
76502
  if (!isFlowDir) {
76503
76503
  if (flowPath.endsWith("flow.yaml") || flowPath.endsWith("flow.json")) {
76504
- flowPath = flowPath.substring(0, flowPath.lastIndexOf(SEP20));
76504
+ flowPath = dirname17(flowPath);
76505
76505
  } else {
76506
76506
  throw new Error("Flow path must be a .flow/__flow directory or a flow.yaml file");
76507
76507
  }
@@ -76544,15 +76544,20 @@ async function preview2(opts, flowPath) {
76544
76544
  tempScriptRefs = await buildPreviewTempScriptRefs2(workspace, opts, resolvedCodebases, { kind: "flow", folder: flowPath });
76545
76545
  }
76546
76546
  const input = opts.data ? await resolve6(opts.data) : {};
76547
+ debug(`Flow value: ${JSON.stringify(localFlow.value, null, 2)}`);
76548
+ const flowWmPath = stripFlowSuffix(flowPath).replaceAll(SEP20, "/");
76549
+ if (opts.step) {
76550
+ await previewStep(opts.step, localFlow, flowWmPath, workspace, input, tempScriptRefs, opts.silent);
76551
+ return;
76552
+ }
76547
76553
  if (!opts.silent) {
76548
76554
  info(colors.yellow(`Running flow preview for ${flowPath}...`));
76549
76555
  }
76550
- debug(`Flow value: ${JSON.stringify(localFlow.value, null, 2)}`);
76551
76556
  const jobId = await runFlowPreview({
76552
76557
  workspace: workspace.workspaceId,
76553
76558
  requestBody: {
76554
76559
  value: localFlow.value,
76555
- path: flowPath.substring(0, flowPath.indexOf(".flow")).replaceAll(SEP20, "/"),
76560
+ path: flowWmPath,
76556
76561
  args: input,
76557
76562
  temp_script_refs: tempScriptRefs
76558
76563
  }
@@ -76575,6 +76580,151 @@ async function preview2(opts, flowPath) {
76575
76580
  info(JSON.stringify(result, null, 2));
76576
76581
  }
76577
76582
  }
76583
+ async function previewStep(stepId, localFlow, flowWmPath, workspace, baseArgs, tempScriptRefs, silent) {
76584
+ const module = findStepInFlowValue(localFlow.value, stepId);
76585
+ if (!module) {
76586
+ const available = collectStepIds(localFlow.value).join(", ") || "(none)";
76587
+ throw new Error(`Step '${stepId}' not found in flow. Available steps: ${available}`);
76588
+ }
76589
+ const args = stepId === "preprocessor" ? { _ENTRYPOINT_OVERRIDE: "preprocessor", ...baseArgs } : baseArgs;
76590
+ const moduleValue = module.value;
76591
+ let jobId;
76592
+ if (moduleValue?.type === "rawscript") {
76593
+ info(colors.yellow(`Previewing step '${stepId}' (rawscript, ${moduleValue.language})...`));
76594
+ jobId = await runScriptPreview({
76595
+ workspace: workspace.workspaceId,
76596
+ requestBody: {
76597
+ content: moduleValue.content ?? "",
76598
+ language: moduleValue.language,
76599
+ path: `${flowWmPath}/${stepId}`,
76600
+ flow_path: flowWmPath,
76601
+ args,
76602
+ temp_script_refs: tempScriptRefs
76603
+ }
76604
+ });
76605
+ } else if (moduleValue?.type === "script") {
76606
+ info(colors.yellow(`Previewing step '${stepId}' (script ${moduleValue.path})...`));
76607
+ const script = moduleValue.hash ? await getScriptByHash({
76608
+ workspace: workspace.workspaceId,
76609
+ hash: moduleValue.hash
76610
+ }) : await getScriptByPath({
76611
+ workspace: workspace.workspaceId,
76612
+ path: moduleValue.path
76613
+ });
76614
+ jobId = await runScriptPreview({
76615
+ workspace: workspace.workspaceId,
76616
+ requestBody: {
76617
+ content: script.content,
76618
+ language: script.language,
76619
+ path: moduleValue.path,
76620
+ flow_path: flowWmPath,
76621
+ args,
76622
+ temp_script_refs: tempScriptRefs
76623
+ }
76624
+ });
76625
+ } else if (moduleValue?.type === "flow") {
76626
+ info(colors.yellow(`Previewing step '${stepId}' (flow ${moduleValue.path})...`));
76627
+ jobId = await runFlowByPath({
76628
+ workspace: workspace.workspaceId,
76629
+ path: moduleValue.path,
76630
+ requestBody: args
76631
+ });
76632
+ } else {
76633
+ throw new Error(`Cannot preview step of type '${moduleValue?.type ?? "unknown"}'. Supported types: rawscript, script, flow.`);
76634
+ }
76635
+ const { result, success } = await pollForJobResult(workspace.workspaceId, jobId);
76636
+ if (!success) {
76637
+ if (silent) {
76638
+ console.log(JSON.stringify(result));
76639
+ } else {
76640
+ info(colors.red.bold(`Step '${stepId}' failed:`));
76641
+ info(JSON.stringify(result, null, 2));
76642
+ }
76643
+ process.exitCode = 1;
76644
+ return;
76645
+ }
76646
+ if (silent) {
76647
+ console.log(JSON.stringify(result));
76648
+ } else {
76649
+ info(colors.bold.underline.green(`Step '${stepId}' completed`));
76650
+ info(JSON.stringify(result, null, 2));
76651
+ }
76652
+ }
76653
+ function stripFlowSuffix(flowPath) {
76654
+ const stripped = flowPath.endsWith(SEP20) ? flowPath.slice(0, -SEP20.length) : flowPath;
76655
+ if (stripped.endsWith(".flow"))
76656
+ return stripped.slice(0, -".flow".length);
76657
+ if (stripped.endsWith("__flow"))
76658
+ return stripped.slice(0, -"__flow".length);
76659
+ return stripped;
76660
+ }
76661
+ function findStepInFlowValue(flowValue, stepId) {
76662
+ if (!flowValue)
76663
+ return;
76664
+ if (flowValue.failure_module?.id === stepId)
76665
+ return flowValue.failure_module;
76666
+ if (flowValue.preprocessor_module?.id === stepId)
76667
+ return flowValue.preprocessor_module;
76668
+ return findStepInModules(flowValue.modules ?? [], stepId);
76669
+ }
76670
+ function findStepInModules(modules, stepId) {
76671
+ for (const m of modules) {
76672
+ if (m?.id === stepId)
76673
+ return m;
76674
+ const v = m?.value;
76675
+ if (!v)
76676
+ continue;
76677
+ if (v.type === "forloopflow" || v.type === "whileloopflow") {
76678
+ const found = findStepInModules(v.modules ?? [], stepId);
76679
+ if (found)
76680
+ return found;
76681
+ } else if (v.type === "branchone") {
76682
+ for (const b of v.branches ?? []) {
76683
+ const found2 = findStepInModules(b.modules ?? [], stepId);
76684
+ if (found2)
76685
+ return found2;
76686
+ }
76687
+ const found = findStepInModules(v.default ?? [], stepId);
76688
+ if (found)
76689
+ return found;
76690
+ } else if (v.type === "branchall") {
76691
+ for (const b of v.branches ?? []) {
76692
+ const found = findStepInModules(b.modules ?? [], stepId);
76693
+ if (found)
76694
+ return found;
76695
+ }
76696
+ }
76697
+ }
76698
+ return;
76699
+ }
76700
+ function collectStepIds(flowValue) {
76701
+ const ids = [];
76702
+ const walkModules = (modules) => {
76703
+ for (const m of modules) {
76704
+ if (m?.id)
76705
+ ids.push(m.id);
76706
+ const v = m?.value;
76707
+ if (!v)
76708
+ continue;
76709
+ if (v.type === "forloopflow" || v.type === "whileloopflow") {
76710
+ walkModules(v.modules ?? []);
76711
+ } else if (v.type === "branchone") {
76712
+ for (const b of v.branches ?? [])
76713
+ walkModules(b.modules ?? []);
76714
+ walkModules(v.default ?? []);
76715
+ } else if (v.type === "branchall") {
76716
+ for (const b of v.branches ?? [])
76717
+ walkModules(b.modules ?? []);
76718
+ }
76719
+ }
76720
+ };
76721
+ if (flowValue?.preprocessor_module?.id)
76722
+ ids.push(flowValue.preprocessor_module.id);
76723
+ if (flowValue?.failure_module?.id)
76724
+ ids.push(flowValue.failure_module.id);
76725
+ walkModules(flowValue?.modules ?? []);
76726
+ return ids;
76727
+ }
76578
76728
  async function generateLocks(opts, folder) {
76579
76729
  warn(colors.yellow('This command is deprecated. Use "wmill generate-metadata" instead.'));
76580
76730
  const workspace = await resolveWorkspace(opts);
@@ -76711,7 +76861,7 @@ var init_flow = __esm(async () => {
76711
76861
  ]);
76712
76862
  import_yaml36 = __toESM(require_dist(), 1);
76713
76863
  alreadySynced3 = [];
76714
- command21 = new Command().description("flow related commands").option("--show-archived", "Enable archived flows in output").option("--json", "Output as JSON (for piping to jq)").action(list12).command("list", "list all flows").option("--show-archived", "Enable archived flows in output").option("--json", "Output as JSON (for piping to jq)").action(list12).command("get", "get a flow's details").arguments("<path:string>").option("--json", "Output as JSON (for piping to jq)").action(get9).command("push", "push a local flow spec. This overrides any remote versions.").arguments("<file_path:string> <remote_path:string>").option("--message <message:string>", "Deployment message").action(push11).command("run", "run a flow by path.").arguments("<path:string>").option("-d --data <data:string>", "Inputs specified as a JSON string or a file using @<filename> or stdin using @-.").option("-s --silent", "Do not ouput anything other then the final output. Useful for scripting.").action(run3).command("preview", "preview a local flow without deploying it. Runs the flow definition from local files and uses local PathScripts by default.").arguments("<flow_path:string>").option("-d --data <data:string>", "Inputs specified as a JSON string or a file using @<filename> or stdin using @-.").option("-s --silent", "Do not output anything other then the final output. Useful for scripting.").option("--remote", "Use deployed workspace scripts for PathScript steps instead of local files.").action(preview2).command("generate-locks", 'DEPRECATED: re-generate flow lock files. Use "wmill generate-metadata" instead.').arguments("[flow:file]").option("--yes", "Skip confirmation prompt").option("--dry-run", "Perform a dry run without making changes").option("-i --includes <patterns:file[]>", "Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string)").option("-e --excludes <patterns:file[]>", "Comma separated patterns to specify which file to NOT take into account.").action(generateLocks).command("new", "create a new empty flow").arguments("<flow_path:string>").option("--summary <summary:string>", "flow summary").option("--description <description:string>", "flow description").action(bootstrap2).command("bootstrap", "create a new empty flow (alias for new)").arguments("<flow_path:string>").option("--summary <summary:string>", "flow summary").option("--description <description:string>", "flow description").action(bootstrap2).command("history", "Show version history for a flow").arguments("<path:string>").option("--json", "Output as JSON (for piping to jq)").action(history2).command("show-version", "Show a specific version of a flow").arguments("<path:string> <version:string>").option("--json", "Output as JSON (for piping to jq)").action(showVersion).command("set-permissioned-as", "Set the on_behalf_of_email for a flow (requires admin or wm_deployers group)").arguments("<path:string> <email:string>").action(async (opts, flowPath, email) => {
76864
+ command21 = new Command().description("flow related commands").option("--show-archived", "Enable archived flows in output").option("--json", "Output as JSON (for piping to jq)").action(list12).command("list", "list all flows").option("--show-archived", "Enable archived flows in output").option("--json", "Output as JSON (for piping to jq)").action(list12).command("get", "get a flow's details").arguments("<path:string>").option("--json", "Output as JSON (for piping to jq)").action(get9).command("push", "push a local flow spec. This overrides any remote versions.").arguments("<file_path:string> <remote_path:string>").option("--message <message:string>", "Deployment message").action(push11).command("run", "run a flow by path.").arguments("<path:string>").option("-d --data <data:string>", "Inputs specified as a JSON string or a file using @<filename> or stdin using @-.").option("-s --silent", "Do not ouput anything other then the final output. Useful for scripting.").action(run3).command("preview", "preview a local flow without deploying it. Runs the flow definition from local files and uses local PathScripts by default. Pass --step <id> to run only one module in isolation (resolves nested steps inside branchone/branchall/forloopflow/whileloopflow plus the special preprocessor/failure modules; supported step types: rawscript, script, flow).").arguments("<flow_path:string>").option("-d --data <data:string>", "Inputs specified as a JSON string or a file using @<filename> or stdin using @-.").option("-s --silent", "Do not output anything other then the final output. Useful for scripting.").option("--remote", "Use deployed workspace scripts for PathScript steps instead of local files.").option("--step <step_id:string>", "Run only the named step instead of the whole flow. Honors --data as the step's args and --remote / local-PathScript resolution the same way the full-flow preview does.").action(preview2).command("generate-locks", 'DEPRECATED: re-generate flow lock files. Use "wmill generate-metadata" instead.').arguments("[flow:file]").option("--yes", "Skip confirmation prompt").option("--dry-run", "Perform a dry run without making changes").option("-i --includes <patterns:file[]>", "Comma separated patterns to specify which file to take into account (among files that are compatible with windmill). Patterns can include * (any string until '/') and ** (any string)").option("-e --excludes <patterns:file[]>", "Comma separated patterns to specify which file to NOT take into account.").action(generateLocks).command("new", "create a new empty flow").arguments("<flow_path:string>").option("--summary <summary:string>", "flow summary").option("--description <description:string>", "flow description").action(bootstrap2).command("bootstrap", "create a new empty flow (alias for new)").arguments("<flow_path:string>").option("--summary <summary:string>", "flow summary").option("--description <description:string>", "flow description").action(bootstrap2).command("history", "Show version history for a flow").arguments("<path:string>").option("--json", "Output as JSON (for piping to jq)").action(history2).command("show-version", "Show a specific version of a flow").arguments("<path:string> <version:string>").option("--json", "Output as JSON (for piping to jq)").action(showVersion).command("set-permissioned-as", "Set the on_behalf_of_email for a flow (requires admin or wm_deployers group)").arguments("<path:string> <email:string>").action(async (opts, flowPath, email) => {
76715
76865
  const workspace = await resolveWorkspace(opts);
76716
76866
  await requireLogin(opts);
76717
76867
  const remote = await getFlowByPath({
@@ -82984,7 +83134,7 @@ Once the flow has real content, **offer** to open the visual preview as a one-se
82984
83134
 
82985
83135
  After writing, tell the user which command fits what they want to do:
82986
83136
 
82987
- - \`wmill flow preview <flow_path>\` — **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.
83137
+ - \`wmill flow preview <flow_path>\` — **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. Add \`--step <step_id>\` to run only one module in isolation (see "Single-step vs whole-flow preview" below).
82988
83138
  - \`wmill flow run <path>\` — runs the flow **already deployed** in the workspace. Use only when the user explicitly wants to test the deployed version, not local edits.
82989
83139
  - \`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.
82990
83140
  - \`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".
@@ -83001,6 +83151,12 @@ Only use \`sync push\` when:
83001
83151
  - The user explicitly asks to deploy, publish, push, or ship.
83002
83152
  - The preview has already validated the change and the user wants it in the workspace.
83003
83153
 
83154
+ ### Single-step vs whole-flow preview
83155
+
83156
+ Use \`flow preview <flow_path> --step <step_id>\` when the user is iterating on one module and the flow's upstream steps aren't part of what they're trying to validate. It runs only that step's runnable (rawscript: the inline script; script: the PathScript, locally if available; flow: the subflow by path) and is much faster than running the whole flow when previous steps are slow or expensive. The step id is resolved by walking nested branchone/branchall/forloopflow/whileloopflow modules and includes the special \`preprocessor\` and \`failure\` modules.
83157
+
83158
+ Use \`flow preview <flow_path>\` (no \`--step\`) when steps depend on each other's outputs, when the user is validating the overall control flow, or when \`--step\` doesn't apply (branchone, branchall, forloopflow, whileloopflow, identity, and AI agent steps cannot themselves be tested in isolation — for branchone/branchall/forloopflow/whileloopflow, the *contained* steps can, by passing the inner step's id).
83159
+
83004
83160
  ### After writing — offer to run, don't wait passively
83005
83161
 
83006
83162
  This is about **programmatic execution** (\`wmill flow preview -d '<args>'\`), which actually runs the flow and has side effects. Visual preview (the \`preview\` skill) is offered separately — see "Visual preview" below.
@@ -84655,10 +84811,11 @@ flow related commands
84655
84811
  - \`flow run <path:string>\` - run a flow by path.
84656
84812
  - \`-d --data <data:string>\` - Inputs specified as a JSON string or a file using @<filename> or stdin using @-.
84657
84813
  - \`-s --silent\` - Do not ouput anything other then the final output. Useful for scripting.
84658
- - \`flow preview <flow_path:string>\` - preview a local flow without deploying it. Runs the flow definition from local files and uses local PathScripts by default.
84814
+ - \`flow preview <flow_path:string>\` - preview a local flow without deploying it. Runs the flow definition from local files and uses local PathScripts by default. Pass --step <id> to run only one module in isolation (resolves nested steps inside branchone/branchall/forloopflow/whileloopflow plus the special preprocessor/failure modules; supported step types: rawscript, script, flow).
84659
84815
  - \`-d --data <data:string>\` - Inputs specified as a JSON string or a file using @<filename> or stdin using @-.
84660
84816
  - \`-s --silent\` - Do not output anything other then the final output. Useful for scripting.
84661
84817
  - \`--remote\` - Use deployed workspace scripts for PathScript steps instead of local files.
84818
+ - \`--step <step_id:string>\` - Run only the named step instead of the whole flow. Honors --data as the step's args and --remote / local-PathScript resolution the same way the full-flow preview does.
84662
84819
  - \`flow new <flow_path:string>\` - create a new empty flow
84663
84820
  - \`--summary <summary:string>\` - flow summary
84664
84821
  - \`--description <description:string>\` - flow description
@@ -84874,6 +85031,42 @@ Validate Windmill flow, schedule, and trigger YAML files in a directory
84874
85031
  - \`--locks-required\` - Fail if scripts or flow inline scripts that need locks have no locks
84875
85032
  - \`-w, --watch\` - Watch for file changes and re-lint automatically
84876
85033
 
85034
+ ### object-storage
85035
+
85036
+ **Alias:** \`s3\`
85037
+
85038
+ **Subcommands:**
85039
+
85040
+ - \`object-storage list\` - List configured object storages for the workspace (default + secondary).
85041
+ - \`--json\` - Output as JSON (for piping to jq)
85042
+ - \`object-storage files [prefix:string]\` - List files in an object storage. Optionally filter by prefix.
85043
+ - \`--json\` - Output as JSON (for piping to jq)
85044
+ - \`--max-keys <maxKeys:number>\` - Page size (default 100)
85045
+ - \`--marker <marker:string>\` - Pagination marker from a previous response
85046
+ - \`--storage <storage:string>\` - Secondary storage name (omit for the workspace default)
85047
+ - \`object-storage upload <local_path:string> <file_key:string>\` - Upload a local file to object storage at the given file key.
85048
+ - \`--storage <storage:string>\` - Secondary storage name
85049
+ - \`--content-type <contentType:string>\` - Content-Type header to set on the object
85050
+ - \`--content-disposition <contentDisposition:string>\` - Content-Disposition header to set on the object
85051
+ - \`object-storage download <file_key:string> [output_path:string]\` - Download an object to a local file (or stdout). Default output path is the basename of the file key in the current directory.
85052
+ - \`--storage <storage:string>\` - Secondary storage name
85053
+ - \`--stdout\` - Write file contents to stdout instead of a file
85054
+ - \`object-storage delete <file_key:string>\` - Delete an object from object storage. Prompts for confirmation unless --yes is set.
85055
+ - \`--storage <storage:string>\` - Secondary storage name
85056
+ - \`--yes\` - Skip the confirmation prompt
85057
+ - \`object-storage move <src_file_key:string> <dest_file_key:string>\` - Move an object within the same storage (rename or relocate by key).
85058
+ - \`--storage <storage:string>\` - Secondary storage name
85059
+ - \`object-storage info <file_key:string>\` - Show metadata (size, mime, last-modified) for an object.
85060
+ - \`--json\` - Output as JSON (for piping to jq)
85061
+ - \`--storage <storage:string>\` - Secondary storage name
85062
+ - \`object-storage preview <file_key:string>\` - Preview the contents of an object (text/CSV). Use --bytes-from / --bytes-length to peek at a slice of binary files.
85063
+ - \`--storage <storage:string>\` - Secondary storage name
85064
+ - \`--mime <mime:string>\` - Override the detected mime type (e.g. text/csv)
85065
+ - \`--bytes-from <bytesFrom:number>\` - Start offset in bytes
85066
+ - \`--bytes-length <bytesLength:number>\` - Number of bytes to read
85067
+ - \`--csv-separator <csvSeparator:string>\` - CSV column separator (default ,)
85068
+ - \`--csv-header\` - Treat the first CSV row as a header
85069
+
84877
85070
  ### protection-rules
84878
85071
 
84879
85072
  **Subcommands:**
@@ -85210,6 +85403,26 @@ workspace related commands
85210
85403
  - \`--team-name <team_name:string>\` - Slack team name
85211
85404
  - \`workspace disconnect-slack\`
85212
85405
 
85406
+
85407
+
85408
+ # Object Storage CLI
85409
+
85410
+ \`wmill object-storage\` (alias \`wmill s3\`) exposes the workspace's object storage (S3-compatible: AWS S3, MinIO, GCS, R2, Azure Blob) over the per-workspace \`/job_helpers/*\` endpoints.
85411
+
85412
+ ## Key concepts (not obvious from per-command --help)
85413
+
85414
+ - **\`file_key\` is the path inside the bucket** (e.g. \`reports/2026-05/orders.csv\`), not a Windmill path. Do NOT pass \`u/...\` or \`f/...\` here — those are Windmill paths to scripts/flows/resources, unrelated to objects in the bucket.
85415
+ - **Scope is the active workspace.** Object storage is configured per-workspace (default storage + optional secondary storages). Switching workspaces switches which bucket the commands target.
85416
+ - **\`--storage <name>\` targets a secondary storage** configured on the workspace. Omit it to use the workspace's default object storage. Use \`wmill object-storage list\` to discover configured storages.
85417
+ - **\`preview\` vs \`download\`**: \`preview\` returns a peek (CSV first rows, text content, or a byte slice via \`--bytes-from\`/\`--bytes-length\`) without writing to disk. Use \`download\` when you want the full file on disk.
85418
+
85419
+ ## Choosing a subcommand
85420
+
85421
+ - Look at what's there: \`wmill object-storage files [prefix]\` (alias \`ls\`) — paginated, use \`--marker\` to continue.
85422
+ - Inspect one file: \`wmill object-storage info <file_key>\` for size/mime/last-modified, \`wmill object-storage preview <file_key>\` for content peek.
85423
+ - Move data in: \`wmill object-storage upload <local_path> <file_key>\` — set \`--content-type\` if the receiver cares (e.g. \`text/csv\`).
85424
+ - Move data out: \`wmill object-storage download <file_key> [output_path]\` — \`--stdout\` to pipe.
85425
+ - Reorganize: \`wmill object-storage move <src> <dest>\` (same storage), \`wmill object-storage delete <file_key>\` (interactive confirm unless \`--yes\`).
85213
85426
  `,
85214
85427
  preview: `---
85215
85428
  name: preview
@@ -87946,13 +88159,13 @@ await __promiseAll([
87946
88159
  var import_yaml39 = __toESM(require_dist(), 1);
87947
88160
  import { existsSync as existsSync10 } from "node:fs";
87948
88161
  import { writeFile as writeFile19 } from "node:fs/promises";
87949
- import { dirname as dirname17, join as join18 } from "node:path";
88162
+ import { dirname as dirname18, join as join18 } from "node:path";
87950
88163
  var PROTECTION_RULES_FILENAME = "protection-rules.yaml";
87951
88164
  function getProtectionRulesPath() {
87952
88165
  const wmillPath = getWmillYamlPath();
87953
88166
  if (!wmillPath)
87954
88167
  return null;
87955
- return join18(dirname17(wmillPath), PROTECTION_RULES_FILENAME);
88168
+ return join18(dirname18(wmillPath), PROTECTION_RULES_FILENAME);
87956
88169
  }
87957
88170
  async function readProtectionRulesFile(path21) {
87958
88171
  if (!existsSync10(path21))
@@ -93203,9 +93416,211 @@ async function run5(opts, sql) {
93203
93416
  var command40 = new Command().description("ducklake related commands").command("list", "list all ducklakes in the workspace").option("--json", "Output as JSON (for piping to jq)").action(list18).command("run", "run a SQL query on a ducklake").arguments("<sql:string>").option("-n --name <name:string>", "Ducklake name (default: main)").option("-s --silent", "Output only the final result as JSON. Useful for scripting.").action(run5);
93204
93417
  var ducklake_default = command40;
93205
93418
 
93419
+ // src/commands/object-storage/object-storage.ts
93420
+ init_mod3();
93421
+ init_mod6();
93422
+ init_colors2();
93423
+ init_log();
93424
+ init_services_gen();
93425
+ await __promiseAll([
93426
+ init_auth(),
93427
+ init_context(),
93428
+ init_confirm(),
93429
+ init_utils()
93430
+ ]);
93431
+ import { Buffer as Buffer5 } from "node:buffer";
93432
+ import { readFile as readFile3, writeFile as writeFile25 } from "node:fs/promises";
93433
+ import { basename as basename10 } from "node:path";
93434
+ function formatBytes(n2) {
93435
+ if (n2 == null)
93436
+ return "-";
93437
+ if (n2 < 1024)
93438
+ return `${n2}B`;
93439
+ if (n2 < 1024 * 1024)
93440
+ return `${(n2 / 1024).toFixed(1)}K`;
93441
+ if (n2 < 1024 * 1024 * 1024)
93442
+ return `${(n2 / (1024 * 1024)).toFixed(1)}M`;
93443
+ return `${(n2 / (1024 * 1024 * 1024)).toFixed(2)}G`;
93444
+ }
93445
+ async function listStorages(opts) {
93446
+ if (opts.json)
93447
+ setSilent(true);
93448
+ const workspace = await resolveWorkspace(opts);
93449
+ await requireLogin(opts);
93450
+ const names = await getSecondaryStorageNames({
93451
+ workspace: workspace.workspaceId,
93452
+ includeDefault: true
93453
+ });
93454
+ if (opts.json) {
93455
+ console.log(JSON.stringify(names));
93456
+ return;
93457
+ }
93458
+ if (names.length === 0) {
93459
+ info("No object storage configured for this workspace.");
93460
+ return;
93461
+ }
93462
+ for (const name of names) {
93463
+ console.log(name === "_default_" ? `${name} ${colors.dim("(default)")}` : name);
93464
+ }
93465
+ }
93466
+ async function listFiles(opts, prefix) {
93467
+ if (opts.json)
93468
+ setSilent(true);
93469
+ const workspace = await resolveWorkspace(opts);
93470
+ await requireLogin(opts);
93471
+ const result2 = await listStoredFiles({
93472
+ workspace: workspace.workspaceId,
93473
+ maxKeys: opts.maxKeys ?? 100,
93474
+ marker: opts.marker,
93475
+ prefix,
93476
+ storage: opts.storage
93477
+ });
93478
+ if (opts.json) {
93479
+ console.log(JSON.stringify(result2));
93480
+ return;
93481
+ }
93482
+ const files = result2.windmill_large_files ?? [];
93483
+ if (files.length === 0) {
93484
+ info("No files found.");
93485
+ return;
93486
+ }
93487
+ new Table2().header(["Key"]).padding(2).border(true).body(files.map((f3) => [f3.s3])).render();
93488
+ if (result2.next_marker) {
93489
+ info(`
93490
+ More results available. Use --marker '${result2.next_marker}' to paginate.`);
93491
+ }
93492
+ }
93493
+ async function upload(opts, localPath, fileKey) {
93494
+ const workspace = await resolveWorkspace(opts);
93495
+ await requireLogin(opts);
93496
+ const buf = await readFile3(localPath);
93497
+ const blob = new Blob([buf], { type: opts.contentType ?? "application/octet-stream" });
93498
+ await fileUpload({
93499
+ workspace: workspace.workspaceId,
93500
+ fileKey,
93501
+ storage: opts.storage,
93502
+ contentType: opts.contentType,
93503
+ contentDisposition: opts.contentDisposition,
93504
+ requestBody: blob
93505
+ });
93506
+ info(colors.green(`Uploaded ${localPath} -> ${fileKey}`));
93507
+ }
93508
+ async function download(opts, fileKey, outputPath) {
93509
+ if (opts.stdout)
93510
+ setSilent(true);
93511
+ const workspace = await resolveWorkspace(opts);
93512
+ await requireLogin(opts);
93513
+ const body = await fileDownload({
93514
+ workspace: workspace.workspaceId,
93515
+ fileKey,
93516
+ storage: opts.storage
93517
+ });
93518
+ let buf;
93519
+ if (typeof body === "string") {
93520
+ buf = Buffer5.from(body, "utf-8");
93521
+ } else if (body instanceof Blob) {
93522
+ buf = Buffer5.from(await body.arrayBuffer());
93523
+ } else if (body instanceof ArrayBuffer) {
93524
+ buf = Buffer5.from(body);
93525
+ } else if (body == null) {
93526
+ buf = Buffer5.alloc(0);
93527
+ } else {
93528
+ buf = Buffer5.from(JSON.stringify(body), "utf-8");
93529
+ }
93530
+ if (opts.stdout) {
93531
+ process.stdout.write(buf);
93532
+ return;
93533
+ }
93534
+ const dest = outputPath ?? basename10(fileKey);
93535
+ await writeFile25(dest, buf);
93536
+ info(colors.green(`Downloaded ${fileKey} -> ${dest}`));
93537
+ }
93538
+ async function del(opts, fileKey) {
93539
+ const workspace = await resolveWorkspace(opts);
93540
+ await requireLogin(opts);
93541
+ if (!opts.yes) {
93542
+ const confirmed = await Confirm.prompt({
93543
+ message: `Delete '${fileKey}' from object storage${opts.storage ? ` (storage: ${opts.storage})` : ""}?`,
93544
+ default: false
93545
+ });
93546
+ if (!confirmed) {
93547
+ info("Aborted.");
93548
+ return;
93549
+ }
93550
+ }
93551
+ await deleteS3File({
93552
+ workspace: workspace.workspaceId,
93553
+ fileKey,
93554
+ storage: opts.storage
93555
+ });
93556
+ info(colors.green(`Deleted ${fileKey}`));
93557
+ }
93558
+ async function move(opts, srcFileKey, destFileKey) {
93559
+ const workspace = await resolveWorkspace(opts);
93560
+ await requireLogin(opts);
93561
+ await moveS3File({
93562
+ workspace: workspace.workspaceId,
93563
+ srcFileKey,
93564
+ destFileKey,
93565
+ storage: opts.storage
93566
+ });
93567
+ info(colors.green(`Moved ${srcFileKey} -> ${destFileKey}`));
93568
+ }
93569
+ async function info2(opts, fileKey) {
93570
+ if (opts.json)
93571
+ setSilent(true);
93572
+ const workspace = await resolveWorkspace(opts);
93573
+ await requireLogin(opts);
93574
+ const metadata = await loadFileMetadata({
93575
+ workspace: workspace.workspaceId,
93576
+ fileKey,
93577
+ storage: opts.storage
93578
+ });
93579
+ if (opts.json) {
93580
+ console.log(JSON.stringify(metadata));
93581
+ return;
93582
+ }
93583
+ console.log(colors.bold("Key:") + " " + fileKey);
93584
+ console.log(colors.bold("Size:") + " " + formatBytes(metadata.size_in_bytes));
93585
+ console.log(colors.bold("Mime:") + " " + (metadata.mime_type ?? "-"));
93586
+ console.log(colors.bold("Last Modified:") + " " + (metadata.last_modified ? formatTimestamp(metadata.last_modified) : "-"));
93587
+ if (metadata.expires) {
93588
+ console.log(colors.bold("Expires:") + " " + formatTimestamp(metadata.expires));
93589
+ }
93590
+ if (metadata.version_id) {
93591
+ console.log(colors.bold("Version Id:") + " " + metadata.version_id);
93592
+ }
93593
+ }
93594
+ async function preview3(opts, fileKey) {
93595
+ const workspace = await resolveWorkspace(opts);
93596
+ await requireLogin(opts);
93597
+ const result2 = await loadFilePreview({
93598
+ workspace: workspace.workspaceId,
93599
+ fileKey,
93600
+ storage: opts.storage,
93601
+ fileMimeType: opts.mime,
93602
+ readBytesFrom: opts.bytesFrom ?? 0,
93603
+ readBytesLength: opts.bytesLength ?? 128 * 1024,
93604
+ csvSeparator: opts.csvSeparator,
93605
+ csvHasHeader: opts.csvHeader
93606
+ });
93607
+ if (result2.msg) {
93608
+ info(colors.yellow(result2.msg));
93609
+ }
93610
+ if (result2.content != null) {
93611
+ process.stdout.write(result2.content);
93612
+ if (!result2.content.endsWith(`
93613
+ `))
93614
+ process.stdout.write(`
93615
+ `);
93616
+ }
93617
+ }
93618
+ var command41 = new Command().alias("s3").description("Object storage (S3) related commands. Operates on the workspace's default object storage; use --storage to target a configured secondary storage.").action(listStorages).command("list", "List configured object storages for the workspace (default + secondary).").option("--json", "Output as JSON (for piping to jq)").action(listStorages).command("files", "List files in an object storage. Optionally filter by prefix.").alias("ls").arguments("[prefix:string]").option("--json", "Output as JSON (for piping to jq)").option("--max-keys <maxKeys:number>", "Page size (default 100)").option("--marker <marker:string>", "Pagination marker from a previous response").option("--storage <storage:string>", "Secondary storage name (omit for the workspace default)").action(listFiles).command("upload", "Upload a local file to object storage at the given file key.").arguments("<local_path:string> <file_key:string>").option("--storage <storage:string>", "Secondary storage name").option("--content-type <contentType:string>", "Content-Type header to set on the object").option("--content-disposition <contentDisposition:string>", "Content-Disposition header to set on the object").action(upload).command("download", "Download an object to a local file (or stdout). Default output path is the basename of the file key in the current directory.").arguments("<file_key:string> [output_path:string]").option("--storage <storage:string>", "Secondary storage name").option("--stdout", "Write file contents to stdout instead of a file").action(download).command("delete", "Delete an object from object storage. Prompts for confirmation unless --yes is set.").arguments("<file_key:string>").option("--storage <storage:string>", "Secondary storage name").option("--yes", "Skip the confirmation prompt").action(del).command("move", "Move an object within the same storage (rename or relocate by key).").arguments("<src_file_key:string> <dest_file_key:string>").option("--storage <storage:string>", "Secondary storage name").action(move).command("info", "Show metadata (size, mime, last-modified) for an object.").arguments("<file_key:string>").option("--json", "Output as JSON (for piping to jq)").option("--storage <storage:string>", "Secondary storage name").action(info2).command("preview", "Preview the contents of an object (text/CSV). Use --bytes-from / --bytes-length to peek at a slice of binary files.").arguments("<file_key:string>").option("--storage <storage:string>", "Secondary storage name").option("--mime <mime:string>", "Override the detected mime type (e.g. text/csv)").option("--bytes-from <bytesFrom:number>", "Start offset in bytes").option("--bytes-length <bytesLength:number>", "Number of bytes to read").option("--csv-separator <csvSeparator:string>", "CSV column separator (default ,)").option("--csv-header", "Treat the first CSV row as a header").action(preview3);
93619
+ var object_storage_default = command41;
93620
+
93206
93621
  // src/main.ts
93207
93622
  await init_context();
93208
- var VERSION = "1.710.0";
93623
+ var VERSION = "1.711.0";
93209
93624
  async function checkVersionSafe(cmd) {
93210
93625
  const mainCommand = cmd.getMainCommand();
93211
93626
  const upgradeCommand = mainCommand.getCommand("upgrade");
@@ -93222,7 +93637,7 @@ async function checkVersionSafe(cmd) {
93222
93637
  }
93223
93638
  mainCommand.version(`${currentVersion} (New version available: ${latestVersion}. Run '${mainCommand.getName()} upgrade' to upgrade to the latest version!)`);
93224
93639
  }
93225
- var command41 = new Command().name("wmill").action(() => info(`Welcome to Windmill CLI ${VERSION}. Use -h for help.`)).description("Windmill CLI").globalOption("--workspace <workspace:string>", "Specify the target workspace. This overrides the default workspace.").globalOption("--debug --verbose", "Show debug/verbose logs").globalOption("--show-diffs", "Show diff informations when syncing (may show sensitive informations)").globalOption("--token <token:string>", "Specify an API token. This will override any stored token.").globalOption("--base-url <baseUrl:string>", "Specify the base URL of the API. If used, --token and --workspace are required and no local remote/workspace already set will be used.").globalOption("--config-dir <configDir:string>", "Specify a custom config directory. Overrides WMILL_CONFIG_DIR environment variable and default ~/.config location.").env("HEADERS <headers:string>", `Specify headers to use for all requests. e.g: "HEADERS='h1: v1, h2: v2'"`).version(VERSION).versionOption(false).helpOption("-h, --help", "Show this help.", {
93640
+ var command42 = new Command().name("wmill").action(() => info(`Welcome to Windmill CLI ${VERSION}. Use -h for help.`)).description("Windmill CLI").globalOption("--workspace <workspace:string>", "Specify the target workspace. This overrides the default workspace.").globalOption("--debug --verbose", "Show debug/verbose logs").globalOption("--show-diffs", "Show diff informations when syncing (may show sensitive informations)").globalOption("--token <token:string>", "Specify an API token. This will override any stored token.").globalOption("--base-url <baseUrl:string>", "Specify the base URL of the API. If used, --token and --workspace are required and no local remote/workspace already set will be used.").globalOption("--config-dir <configDir:string>", "Specify a custom config directory. Overrides WMILL_CONFIG_DIR environment variable and default ~/.config location.").env("HEADERS <headers:string>", `Specify headers to use for all requests. e.g: "HEADERS='h1: v1, h2: v2'"`).version(VERSION).versionOption(false).helpOption("-h, --help", "Show this help.", {
93226
93641
  action: async function() {
93227
93642
  const self2 = this;
93228
93643
  const long = self2.getRawArgs().includes(`--${self2.getHelpOption()?.name}`);
@@ -93234,7 +93649,7 @@ var command41 = new Command().name("wmill").action(() => info(`Welcome to Windmi
93234
93649
  self2.showHelp({ long });
93235
93650
  self2.exit();
93236
93651
  }
93237
- }).command("init", init_default).command("refresh", refresh_default).command("app", app_default).command("flow", flow_default).command("script", script_default).command("workspace", workspace_default).command("resource", resource_default).command("resource-type", resource_type_default).command("user", user_default).command("variable", variable_default).command("hub", hub_default).command("folder", folder_default).command("schedule", schedule_default).command("trigger", trigger_default).command("dev", dev_default2).command("sync", sync_default).command("lint", lint_default).command("gitsync-settings", gitsync_settings_default).command("protection-rules", protection_rules_default).command("instance", instance_default).command("worker-groups", worker_groups_default).command("workers", workers_default).command("queues", queues_default).command("dependencies", dependencies_default).command("jobs", jobs_default).command("job", job_default).command("group", group_default).command("audit", audit_default).command("token", token_default).command("generate-metadata", generate_metadata_default).command("docs", docs_default).command("config", config_default).command("datatable", datatable_default).command("ducklake", ducklake_default).command("version --version", "Show version information").action(async (opts) => {
93652
+ }).command("init", init_default).command("refresh", refresh_default).command("app", app_default).command("flow", flow_default).command("script", script_default).command("workspace", workspace_default).command("resource", resource_default).command("resource-type", resource_type_default).command("user", user_default).command("variable", variable_default).command("hub", hub_default).command("folder", folder_default).command("schedule", schedule_default).command("trigger", trigger_default).command("dev", dev_default2).command("sync", sync_default).command("lint", lint_default).command("gitsync-settings", gitsync_settings_default).command("protection-rules", protection_rules_default).command("instance", instance_default).command("worker-groups", worker_groups_default).command("workers", workers_default).command("queues", queues_default).command("dependencies", dependencies_default).command("jobs", jobs_default).command("job", job_default).command("group", group_default).command("audit", audit_default).command("token", token_default).command("generate-metadata", generate_metadata_default).command("docs", docs_default).command("config", config_default).command("datatable", datatable_default).command("ducklake", ducklake_default).command("object-storage", object_storage_default).command("version --version", "Show version information").action(async (opts) => {
93238
93653
  console.log("CLI version: " + VERSION);
93239
93654
  try {
93240
93655
  const provider2 = new NpmProvider({ package: "windmill-cli" });
@@ -93264,20 +93679,20 @@ var command41 = new Command().name("wmill").action(() => info(`Welcome to Windmi
93264
93679
  error(e2);
93265
93680
  info("Try running with sudo and otherwise check the result of the command: npm uninstall windmill-cli && npm install -g windmill-cli");
93266
93681
  })).command("completions", new Command().description("Generate shell completions.").command("bash", new Command().description("Generate bash completions.").action(() => {
93267
- process.stdout.write(generateShellCompletions(command41, "bash") + `
93682
+ process.stdout.write(generateShellCompletions(command42, "bash") + `
93268
93683
  `);
93269
93684
  })).command("zsh", new Command().description("Generate zsh completions.").action(() => {
93270
- process.stdout.write(generateShellCompletions(command41, "zsh") + `
93685
+ process.stdout.write(generateShellCompletions(command42, "zsh") + `
93271
93686
  `);
93272
93687
  })).command("fish", new Command().description("Generate fish completions.").action(() => {
93273
- process.stdout.write(generateShellCompletions(command41, "fish") + `
93688
+ process.stdout.write(generateShellCompletions(command42, "fish") + `
93274
93689
  `);
93275
93690
  })));
93276
93691
  async function main2() {
93277
93692
  try {
93278
93693
  const args = process.argv.slice(2);
93279
93694
  if (args.length === 0) {
93280
- command41.showHelp();
93695
+ command42.showHelp();
93281
93696
  }
93282
93697
  const LOG_LEVEL = args.includes("--verbose") || args.includes("--debug") ? "DEBUG" : "INFO";
93283
93698
  setShowDiffs(args.includes("--show-diffs"));
@@ -93295,7 +93710,7 @@ async function main2() {
93295
93710
  const { warnIfPromptsStale: warnIfPromptsStale2 } = await init_freshness().then(() => exports_freshness);
93296
93711
  await warnIfPromptsStale2({ argv: process.argv }).catch(() => {});
93297
93712
  }
93298
- await command41.parse(args);
93713
+ await command42.parse(args);
93299
93714
  } catch (e2) {
93300
93715
  if (e2 && typeof e2 === "object" && "name" in e2 && e2.name === "ApiError") {
93301
93716
  const body = e2.body;
@@ -93331,7 +93746,7 @@ if (isMain()) {
93331
93746
  process.stdin.destroy();
93332
93747
  });
93333
93748
  }
93334
- var main_default = command41;
93749
+ var main_default = command42;
93335
93750
  export {
93336
93751
  add as workspaceAdd,
93337
93752
  workspace_default as workspace,
@@ -93347,6 +93762,7 @@ export {
93347
93762
  push4 as push,
93348
93763
  pull,
93349
93764
  protection_rules_default as protectionRules,
93765
+ object_storage_default as objectStorage,
93350
93766
  lint_default as lint,
93351
93767
  job_default as job,
93352
93768
  instance_default as instance,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windmill-cli",
3
- "version": "1.710.0",
3
+ "version": "1.711.0",
4
4
  "description": "CLI for Windmill",
5
5
  "license": "Apache 2.0",
6
6
  "type": "module",