@clickraft/cli 0.6.1 → 0.7.1

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.
package/dist/index.js CHANGED
@@ -146,17 +146,35 @@ var EXIT_CODE_MAP = {
146
146
  retryable: false,
147
147
  hint: "Run `node-type list` for the current set of node types."
148
148
  },
149
- WORKFLOW_VERSION_REQUIRED: {
149
+ CANVAS_REV_MISMATCH: {
150
+ exitCode: 7,
151
+ category: "conflict",
152
+ retryable: false,
153
+ hint: "The workflow was updated elsewhere. Re-fetch and retry with the current canvas_rev."
154
+ },
155
+ CANVAS_REV_REQUIRED: {
150
156
  exitCode: 2,
151
157
  category: "usage",
152
158
  retryable: false,
153
- hint: 'Send `If-Match: "<version>"` from the last `workflow get` response.'
159
+ hint: "Include --canvas-rev or use --auto-rev."
154
160
  },
155
- WORKFLOW_VERSION_CONFLICT: {
161
+ WORKER_UNREACHABLE: {
162
+ exitCode: 6,
163
+ category: "network",
164
+ retryable: true,
165
+ hint: "Collaboration service temporarily unavailable. Retry."
166
+ },
167
+ IDEMPOTENCY_IN_PROGRESS: {
156
168
  exitCode: 7,
157
169
  category: "conflict",
158
170
  retryable: false,
159
- hint: "The workflow was updated elsewhere. Re-fetch and retry."
171
+ hint: "A request with this idempotency key is currently in-flight. Retry in a moment."
172
+ },
173
+ TOKEN_NAME_INVALID: {
174
+ exitCode: 2,
175
+ category: "usage",
176
+ retryable: false,
177
+ hint: "Agent token name is malformed."
160
178
  },
161
179
  WORKFLOW_NOT_FOUND: {
162
180
  exitCode: 2,
@@ -2881,6 +2899,170 @@ async function listTokens(options) {
2881
2899
  return data;
2882
2900
  }
2883
2901
 
2902
+ // src/schemas/workflows.ts
2903
+ import { z as z12 } from "zod";
2904
+ var WorkflowDeclaredParamSchema = z12.object({
2905
+ name: z12.string(),
2906
+ type: z12.string(),
2907
+ required: z12.boolean().optional(),
2908
+ defaultValue: z12.unknown().optional(),
2909
+ description: z12.unknown().optional()
2910
+ }).passthrough();
2911
+ var WorkflowSummarySchema = z12.object({
2912
+ id: z12.string().uuid(),
2913
+ name: z12.string(),
2914
+ tags: z12.array(z12.string()),
2915
+ declared_params: z12.array(WorkflowDeclaredParamSchema).nullable().default([]),
2916
+ canvas_rev: z12.number().int().nonnegative(),
2917
+ updated_at: z12.string()
2918
+ }).strict();
2919
+ var WorkflowsListResponseSchema = z12.object({
2920
+ items: z12.array(WorkflowSummarySchema),
2921
+ nextCursor: z12.string().nullable()
2922
+ }).strict();
2923
+ var WorkflowDetailSchema = z12.object({
2924
+ id: z12.string().uuid(),
2925
+ name: z12.string(),
2926
+ description: z12.string().nullable(),
2927
+ tags: z12.array(z12.string()),
2928
+ declared_params: z12.array(WorkflowDeclaredParamSchema).nullable().default([]),
2929
+ canvas_rev: z12.number().int().nonnegative(),
2930
+ nodes: z12.array(z12.unknown()),
2931
+ edges: z12.array(z12.unknown()),
2932
+ created_at: z12.string(),
2933
+ updated_at: z12.string()
2934
+ }).strict();
2935
+ var WorkflowCreateResponseSchema = z12.object({
2936
+ id: z12.string().uuid(),
2937
+ name: z12.string(),
2938
+ description: z12.string().nullable(),
2939
+ tags: z12.array(z12.string()),
2940
+ declared_params: z12.array(WorkflowDeclaredParamSchema).nullable().default([]),
2941
+ canvas_rev: z12.number().int().nonnegative(),
2942
+ created_at: z12.string(),
2943
+ updated_at: z12.string()
2944
+ }).strict();
2945
+ var WorkflowMutateResponseSchema = z12.object({
2946
+ canvas_rev: z12.number().int().nonnegative(),
2947
+ applied: z12.number().int().nonnegative(),
2948
+ workflow: WorkflowDetailSchema
2949
+ }).strict();
2950
+
2951
+ // src/commands/workflow/internal.ts
2952
+ async function buildWorkflowClient(opts) {
2953
+ const cfg = await loadConfig({
2954
+ flagProfile: opts.profile,
2955
+ flagApiBaseUrl: opts.apiBaseUrl,
2956
+ flagToken: opts.token
2957
+ });
2958
+ return new ApiClient({
2959
+ apiBaseUrl: cfg.apiBaseUrl,
2960
+ accessToken: cfg.accessToken,
2961
+ signal: opts.signal,
2962
+ cloudflareAccess: cfg.cloudflareAccess
2963
+ });
2964
+ }
2965
+
2966
+ // src/commands/workflow/get.ts
2967
+ async function getWorkflow(opts) {
2968
+ const client = opts.client ?? await buildWorkflowClient(opts);
2969
+ return client.request({
2970
+ method: "GET",
2971
+ path: `/workflows/${encodeURIComponent(opts.id)}`,
2972
+ responseSchema: WorkflowDetailSchema,
2973
+ signal: opts.signal
2974
+ });
2975
+ }
2976
+
2977
+ // src/commands/workflow/apply.ts
2978
+ async function applyWorkflow(opts) {
2979
+ const client = opts.client ?? await buildWorkflowClient(opts);
2980
+ try {
2981
+ return await patchWorkflow(client, opts);
2982
+ } catch (err) {
2983
+ if (!(err instanceof ApiError)) throw err;
2984
+ const isRevConflict = err.code === "CANVAS_REV_MISMATCH" || err.meta.status === 409 && err.meta.details != null;
2985
+ if (!isRevConflict || !opts.autoRev) throw err;
2986
+ const snapshot = await getWorkflow({ id: opts.id, client, signal: opts.signal });
2987
+ const retryResult = await patchWorkflow(client, {
2988
+ ...opts,
2989
+ canvasRev: snapshot.canvas_rev,
2990
+ // Fresh key — the body changed (different canvas_rev), so reusing
2991
+ // the caller's key would trigger IDEMPOTENCY_KEY_CONFLICT.
2992
+ idempotencyKey: void 0
2993
+ });
2994
+ return { ...retryResult, retriedWithRev: snapshot.canvas_rev };
2995
+ }
2996
+ }
2997
+ async function patchWorkflow(client, opts) {
2998
+ const data = await client.request({
2999
+ method: "PATCH",
3000
+ path: `/workflows/${encodeURIComponent(opts.id)}`,
3001
+ body: {
3002
+ canvas_rev: opts.canvasRev,
3003
+ operations: opts.operations
3004
+ },
3005
+ idempotencyKey: opts.idempotencyKey,
3006
+ responseSchema: WorkflowMutateResponseSchema,
3007
+ signal: opts.signal
3008
+ });
3009
+ return {
3010
+ canvasRev: data.canvas_rev,
3011
+ applied: data.applied,
3012
+ workflow: data.workflow
3013
+ };
3014
+ }
3015
+
3016
+ // src/commands/workflow/create.ts
3017
+ function toParamArray(input) {
3018
+ if (Array.isArray(input)) return input;
3019
+ return Object.entries(input).map(([key, val]) => {
3020
+ const def = typeof val === "object" && val !== null ? val : {};
3021
+ if (def.name !== void 0 && def.name !== key) {
3022
+ throw new ArgError(
3023
+ `declared_params key '${key}' conflicts with inner name '${String(def.name)}'.`
3024
+ );
3025
+ }
3026
+ return { name: key, ...def };
3027
+ });
3028
+ }
3029
+ async function createWorkflow(opts) {
3030
+ const client = opts.client ?? await buildWorkflowClient(opts);
3031
+ const body = { name: opts.name };
3032
+ if (opts.description !== void 0) body.description = opts.description;
3033
+ if (opts.tags !== void 0 && opts.tags.length > 0) body.tags = opts.tags;
3034
+ if (opts.declaredParams !== void 0) body.declared_params = toParamArray(opts.declaredParams);
3035
+ return client.request({
3036
+ method: "POST",
3037
+ path: "/workflows",
3038
+ body,
3039
+ responseSchema: WorkflowCreateResponseSchema,
3040
+ signal: opts.signal
3041
+ });
3042
+ }
3043
+
3044
+ // src/commands/workflow/list.ts
3045
+ async function listWorkflows(opts) {
3046
+ const client = opts.client ?? await buildWorkflowClient(opts);
3047
+ const data = await client.request({
3048
+ method: "GET",
3049
+ path: "/workflows",
3050
+ query: {
3051
+ cursor: opts.cursor,
3052
+ limit: opts.limit,
3053
+ tags: opts.tags?.join(","),
3054
+ declared_params_has_key: opts.declaredParamsHasKey
3055
+ },
3056
+ responseSchema: WorkflowsListResponseSchema,
3057
+ signal: opts.signal
3058
+ });
3059
+ return {
3060
+ items: data.items,
3061
+ nextCursor: data.nextCursor,
3062
+ hasMore: data.nextCursor !== null
3063
+ };
3064
+ }
3065
+
2884
3066
  // src/errors/format.ts
2885
3067
  var FALLBACK_HINT = "See `clickraft --help` or contact support.";
2886
3068
  function formatHumanError(err) {
@@ -3330,6 +3512,81 @@ function formatExpiresAt(iso) {
3330
3512
  const days = Math.round((ms - Date.now()) / (24 * 60 * 60 * 1e3));
3331
3513
  return `${date.toISOString()} (~${days}d)`;
3332
3514
  }
3515
+ function renderWorkflowCreateResult(result, out) {
3516
+ const lines = [
3517
+ `Created workflow "${result.name}"`,
3518
+ ` ID: ${result.id}`,
3519
+ ` Canvas rev: ${result.canvas_rev}`,
3520
+ ` Created: ${result.created_at.slice(0, 19)}`
3521
+ ];
3522
+ out.write(lines.join("\n") + "\n");
3523
+ }
3524
+ function renderWorkflowsList(result, out) {
3525
+ const { items } = result;
3526
+ if (items.length === 0) {
3527
+ out.write("No workflows.\n");
3528
+ return;
3529
+ }
3530
+ const MAX_TAGS = 3;
3531
+ const headers = ["ID", "NAME", "TAGS", "REV", "UPDATED"];
3532
+ const data = items.map((w) => {
3533
+ const shown = w.tags.slice(0, MAX_TAGS).join(", ");
3534
+ const extra = w.tags.length > MAX_TAGS ? ` +${w.tags.length - MAX_TAGS} more` : "";
3535
+ return [
3536
+ w.id.slice(0, 8),
3537
+ w.name,
3538
+ w.tags.length > 0 ? shown + extra : "\u2014",
3539
+ String(w.canvas_rev),
3540
+ w.updated_at.slice(0, 10)
3541
+ ];
3542
+ });
3543
+ const widths = headers.map(
3544
+ (header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
3545
+ );
3546
+ const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
3547
+ out.write(padRow(headers) + "\n");
3548
+ for (const cells of data) out.write(padRow(cells) + "\n");
3549
+ if (result.hasMore) {
3550
+ out.write("\nMore results available. Use --cursor to fetch the next page.\n");
3551
+ }
3552
+ }
3553
+ function renderWorkflowDetail(detail, out) {
3554
+ const pad = 17;
3555
+ const lines = [
3556
+ detail.name,
3557
+ `${"ID:".padEnd(pad)}${detail.id}`,
3558
+ `${"Canvas rev:".padEnd(pad)}${detail.canvas_rev}`,
3559
+ `${"Tags:".padEnd(pad)}${detail.tags.length > 0 ? detail.tags.join(", ") : "\u2014"}`,
3560
+ `${"Created:".padEnd(pad)}${detail.created_at.slice(0, 19)}`,
3561
+ `${"Updated:".padEnd(pad)}${detail.updated_at.slice(0, 19)}`,
3562
+ `${"Nodes:".padEnd(pad)}${detail.nodes.length}`,
3563
+ `${"Edges:".padEnd(pad)}${detail.edges.length}`
3564
+ ];
3565
+ if (detail.description) {
3566
+ lines.push("", detail.description);
3567
+ }
3568
+ const params = detail.declared_params;
3569
+ lines.push("", "Declared parameters:");
3570
+ if (!params || params.length === 0) {
3571
+ lines.push(" (none)");
3572
+ } else {
3573
+ for (const p of params) {
3574
+ const req = p.required ? "required" : "optional";
3575
+ const desc = p.description ? `: ${p.description}` : "";
3576
+ lines.push(` - ${p.name} (${p.type}, ${req})${desc}`);
3577
+ }
3578
+ }
3579
+ out.write(lines.join("\n") + "\n");
3580
+ }
3581
+ function renderWorkflowApplyResult(result, out) {
3582
+ const lines = [
3583
+ `Applied ${result.applied} operation${result.applied === 1 ? "" : "s"}. New canvas_rev: ${result.canvasRev}`
3584
+ ];
3585
+ if (result.retriedWithRev !== void 0) {
3586
+ lines.push(` (auto-rev: resolved conflict, retried with canvas_rev ${result.retriedWithRev})`);
3587
+ }
3588
+ out.write(lines.join("\n") + "\n");
3589
+ }
3333
3590
 
3334
3591
  // src/cli/dispatch.ts
3335
3592
  async function dispatch(options) {
@@ -3559,6 +3816,10 @@ function renderRootHelp(version) {
3559
3816
  " generate wait Wait for an existing job to reach a terminal state",
3560
3817
  " generate get Fetch the current state of a job",
3561
3818
  " jobs wait Alias of `generate wait`",
3819
+ " workflow create Create a new empty workflow",
3820
+ " workflow list List workflows in your organization",
3821
+ " workflow get Fetch a workflow snapshot by ID",
3822
+ " workflow apply Apply operations to a workflow (canvas mutation)",
3562
3823
  " upload <file> Upload a local file and return its public URL",
3563
3824
  "",
3564
3825
  "Options:",
@@ -3852,6 +4113,83 @@ function renderCommandHelp(command) {
3852
4113
  " --json",
3853
4114
  ""
3854
4115
  ].join("\n");
4116
+ case "workflow":
4117
+ return [
4118
+ "Usage: clickraft workflow <subcommand> [options]",
4119
+ "",
4120
+ "Subcommands:",
4121
+ " create Create a new empty workflow",
4122
+ " list List workflows in your organization",
4123
+ " get Fetch a workflow snapshot by ID",
4124
+ " apply Apply operations to a workflow (canvas mutation)",
4125
+ ""
4126
+ ].join("\n");
4127
+ case "workflow create":
4128
+ return [
4129
+ "Usage: clickraft workflow create --name <name> [options]",
4130
+ "",
4131
+ "Creates a new empty workflow.",
4132
+ "",
4133
+ "Options:",
4134
+ " --name <name> Workflow name (required unless --from-file)",
4135
+ " --description <text> Optional description",
4136
+ " --tags <tag> Tag (repeatable)",
4137
+ " --from-file <path> Read the full create payload as JSON from a file",
4138
+ " --profile <name>",
4139
+ " --api-base-url <url>",
4140
+ " --token <token>",
4141
+ " --json",
4142
+ ""
4143
+ ].join("\n");
4144
+ case "workflow list":
4145
+ return [
4146
+ "Usage: clickraft workflow list [options]",
4147
+ "",
4148
+ "Lists workflows in your organization. Supports cursor-based pagination.",
4149
+ "",
4150
+ "Options:",
4151
+ " --tags <csv> Filter by tags (comma-separated)",
4152
+ " --has-param <key> Filter to workflows that declare this param",
4153
+ " --limit <n> Max results per page (1-100, default: 20)",
4154
+ " --cursor <token> Pagination cursor from a previous response",
4155
+ " --profile <name>",
4156
+ " --api-base-url <url>",
4157
+ " --token <token>",
4158
+ " --json",
4159
+ ""
4160
+ ].join("\n");
4161
+ case "workflow get":
4162
+ return [
4163
+ "Usage: clickraft workflow get <id> [options]",
4164
+ "",
4165
+ "Fetches a workflow snapshot including nodes and edges.",
4166
+ "",
4167
+ "Options:",
4168
+ " --profile <name>",
4169
+ " --api-base-url <url>",
4170
+ " --token <token>",
4171
+ " --json",
4172
+ ""
4173
+ ].join("\n");
4174
+ case "workflow apply":
4175
+ return [
4176
+ "Usage: clickraft workflow apply <id> [options]",
4177
+ "",
4178
+ "Applies mutation operations to a workflow. Requires --canvas-rev or --auto-rev.",
4179
+ "",
4180
+ "Options:",
4181
+ " --from-file <path> JSON file with operations (array or { canvas_rev, operations })",
4182
+ " --op <json> Single operation as JSON string (repeatable)",
4183
+ " --canvas-rev <n> The canvas_rev preconditioning the mutation",
4184
+ " --auto-rev Fetch current canvas_rev first; retry once on 409",
4185
+ " --idempotency-key <key> Override the auto-generated idempotency key",
4186
+ " --dry-run Print the request payload without sending it",
4187
+ " --profile <name>",
4188
+ " --api-base-url <url>",
4189
+ " --token <token>",
4190
+ " --json",
4191
+ ""
4192
+ ].join("\n");
3855
4193
  default:
3856
4194
  return `Unknown command: ${path}
3857
4195
  Run \`clickraft --help\` for available commands.
@@ -3898,6 +4236,15 @@ function matchNewCommand(argv) {
3898
4236
  if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "models" };
3899
4237
  return { kind: "unknown-sub", verb: "models", sub };
3900
4238
  }
4239
+ if (verb === "workflow") {
4240
+ const sub = argv[1];
4241
+ if (sub === "create") return { kind: "cmd", path: "workflow.create", verbTokenCount: 2 };
4242
+ if (sub === "list") return { kind: "cmd", path: "workflow.list", verbTokenCount: 2 };
4243
+ if (sub === "get") return { kind: "cmd", path: "workflow.get", verbTokenCount: 2 };
4244
+ if (sub === "apply") return { kind: "cmd", path: "workflow.apply", verbTokenCount: 2 };
4245
+ if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "workflow" };
4246
+ return { kind: "unknown-sub", verb: "workflow", sub };
4247
+ }
3901
4248
  return null;
3902
4249
  }
3903
4250
  async function runNewCommand(newCmd, options, stdout, stderr) {
@@ -3958,6 +4305,14 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
3958
4305
  return await runProductList(argvAfterVerb, ctx, rc);
3959
4306
  case "models.list":
3960
4307
  return await runModelsList(argvAfterVerb, ctx, rc);
4308
+ case "workflow.create":
4309
+ return await runWorkflowCreate(argvAfterVerb, ctx, rc);
4310
+ case "workflow.list":
4311
+ return await runWorkflowList(argvAfterVerb, ctx, rc);
4312
+ case "workflow.get":
4313
+ return await runWorkflowGet(argvAfterVerb, ctx, rc);
4314
+ case "workflow.apply":
4315
+ return await runWorkflowApply(argvAfterVerb, ctx, rc);
3961
4316
  }
3962
4317
  } catch (err) {
3963
4318
  if (isAbortedError(err, options.signal)) {
@@ -4329,6 +4684,211 @@ function emitModelsList(result, ctx, rc, parsed) {
4329
4684
  }
4330
4685
  });
4331
4686
  }
4687
+ async function runWorkflowCreate(argv, ctx, rc) {
4688
+ const parsed = parseArgs(argv, {
4689
+ string: [...COMMON_RICH_STRING_FLAGS, "name", "description", "from-file"],
4690
+ boolean: [...COMMON_RICH_BOOL_FLAGS],
4691
+ array: ["tags"]
4692
+ });
4693
+ let name = parsed.string.name;
4694
+ let description = parsed.string.description;
4695
+ let tags = parsed.array.tags;
4696
+ let declaredParams;
4697
+ if (parsed.string["from-file"]) {
4698
+ const raw = await readJsonFile(parsed.string["from-file"]);
4699
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
4700
+ const obj = raw;
4701
+ name = name ?? (typeof obj.name === "string" ? obj.name : void 0);
4702
+ description = description ?? (typeof obj.description === "string" ? obj.description : void 0);
4703
+ if (!tags && Array.isArray(obj.tags)) tags = obj.tags;
4704
+ if (obj.declared_params != null && typeof obj.declared_params === "object") {
4705
+ declaredParams = obj.declared_params;
4706
+ }
4707
+ }
4708
+ }
4709
+ if (!name) throw new ArgError("--name is required (or provide it in --from-file).");
4710
+ const result = await createWorkflow({
4711
+ name,
4712
+ description,
4713
+ tags,
4714
+ declaredParams,
4715
+ profile: parsed.string.profile,
4716
+ apiBaseUrl: parsed.string["api-base-url"],
4717
+ token: parsed.string.token,
4718
+ signal: rc.signal
4719
+ });
4720
+ emitWorkflowCreate(result, ctx, rc);
4721
+ return 0;
4722
+ }
4723
+ async function runWorkflowList(argv, ctx, rc) {
4724
+ const parsed = parseArgs(argv, {
4725
+ string: [...COMMON_RICH_STRING_FLAGS, "tags", "has-param", "cursor", "limit"],
4726
+ boolean: [...COMMON_RICH_BOOL_FLAGS]
4727
+ });
4728
+ const limit = optionalInt(parsed.string.limit, "limit");
4729
+ if (limit !== void 0 && (limit < 1 || limit > 100)) {
4730
+ throw new ArgError("--limit must be between 1 and 100.");
4731
+ }
4732
+ const result = await listWorkflows({
4733
+ cursor: parsed.string.cursor,
4734
+ limit,
4735
+ tags: parsed.string.tags ? parsed.string.tags.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
4736
+ declaredParamsHasKey: parsed.string["has-param"],
4737
+ profile: parsed.string.profile,
4738
+ apiBaseUrl: parsed.string["api-base-url"],
4739
+ token: parsed.string.token,
4740
+ signal: rc.signal
4741
+ });
4742
+ ctx.nextCursor = result.nextCursor ?? void 0;
4743
+ emitWorkflowList(result, ctx, rc);
4744
+ return 0;
4745
+ }
4746
+ async function runWorkflowGet(argv, ctx, rc) {
4747
+ const parsed = parseArgs(argv, {
4748
+ string: [...COMMON_RICH_STRING_FLAGS],
4749
+ boolean: [...COMMON_RICH_BOOL_FLAGS]
4750
+ });
4751
+ const id = parsed.positional[0];
4752
+ if (!id) throw new ArgError("Missing required <id> argument.");
4753
+ const detail = await getWorkflow({
4754
+ id,
4755
+ profile: parsed.string.profile,
4756
+ apiBaseUrl: parsed.string["api-base-url"],
4757
+ token: parsed.string.token,
4758
+ signal: rc.signal
4759
+ });
4760
+ emitWorkflowDetail(detail, ctx, rc);
4761
+ return 0;
4762
+ }
4763
+ async function runWorkflowApply(argv, ctx, rc) {
4764
+ const parsed = parseArgs(argv, {
4765
+ string: [...COMMON_RICH_STRING_FLAGS, "canvas-rev", "from-file", "idempotency-key"],
4766
+ boolean: [...COMMON_RICH_BOOL_FLAGS, "auto-rev", "dry-run"],
4767
+ array: ["op"]
4768
+ });
4769
+ const id = parsed.positional[0];
4770
+ if (!id) throw new ArgError("Missing required <id> argument.");
4771
+ let operations = [];
4772
+ let fileCanvasRev;
4773
+ if (parsed.string["from-file"]) {
4774
+ const raw = await readJsonFile(parsed.string["from-file"]);
4775
+ if (Array.isArray(raw)) {
4776
+ operations = raw;
4777
+ } else if (typeof raw === "object" && raw !== null && "operations" in raw) {
4778
+ const obj = raw;
4779
+ operations = obj.operations;
4780
+ if (typeof obj.canvas_rev === "number") fileCanvasRev = obj.canvas_rev;
4781
+ } else {
4782
+ throw new ArgError(
4783
+ '--from-file must contain a JSON array or an object with an "operations" key.'
4784
+ );
4785
+ }
4786
+ }
4787
+ if (parsed.array.op) {
4788
+ for (const opStr of parsed.array.op) {
4789
+ try {
4790
+ operations.push(JSON.parse(opStr));
4791
+ } catch {
4792
+ throw new ArgError(`Invalid JSON in --op: ${opStr}`);
4793
+ }
4794
+ }
4795
+ }
4796
+ if (operations.length === 0) {
4797
+ throw new ArgError("No operations provided. Use --from-file or --op.");
4798
+ }
4799
+ if (operations.length > 100) {
4800
+ throw new ArgError("At most 100 operations per request.");
4801
+ }
4802
+ const autoRev = parsed.boolean["auto-rev"] === true;
4803
+ let canvasRev;
4804
+ if (parsed.string["canvas-rev"] !== void 0) {
4805
+ const n = optionalInt(parsed.string["canvas-rev"], "canvas-rev");
4806
+ if (n === void 0) throw new ArgError("--canvas-rev must be a non-negative integer.");
4807
+ canvasRev = n;
4808
+ } else if (autoRev) {
4809
+ const snapshot = await getWorkflow({
4810
+ id,
4811
+ profile: parsed.string.profile,
4812
+ apiBaseUrl: parsed.string["api-base-url"],
4813
+ token: parsed.string.token,
4814
+ signal: rc.signal
4815
+ });
4816
+ canvasRev = snapshot.canvas_rev;
4817
+ } else if (fileCanvasRev !== void 0) {
4818
+ canvasRev = fileCanvasRev;
4819
+ } else {
4820
+ throw new ArgError(
4821
+ "Must provide --canvas-rev, use --auto-rev, or include canvas_rev in --from-file."
4822
+ );
4823
+ }
4824
+ if (parsed.boolean["dry-run"] === true) {
4825
+ const payload = { canvas_rev: canvasRev, operations };
4826
+ rc.stdout.write(JSON.stringify(payload, null, 2) + "\n");
4827
+ return 0;
4828
+ }
4829
+ const result = await applyWorkflow({
4830
+ id,
4831
+ canvasRev,
4832
+ operations,
4833
+ idempotencyKey: parsed.string["idempotency-key"],
4834
+ autoRev,
4835
+ profile: parsed.string.profile,
4836
+ apiBaseUrl: parsed.string["api-base-url"],
4837
+ token: parsed.string.token,
4838
+ signal: rc.signal
4839
+ });
4840
+ emitWorkflowApply(result, ctx, rc);
4841
+ return 0;
4842
+ }
4843
+ function emitWorkflowCreate(result, ctx, rc) {
4844
+ if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4845
+ if (rc.jsonMode) {
4846
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4847
+ return;
4848
+ }
4849
+ renderWorkflowCreateResult(result, rc.stdout);
4850
+ }
4851
+ function emitWorkflowList(result, ctx, rc) {
4852
+ if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4853
+ if (rc.jsonMode) {
4854
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4855
+ return;
4856
+ }
4857
+ renderWorkflowsList(result, rc.stdout);
4858
+ }
4859
+ function emitWorkflowDetail(detail, ctx, rc) {
4860
+ if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4861
+ if (rc.jsonMode) {
4862
+ rc.stdout.write(JSON.stringify(wrapSuccess(detail, ctx)) + "\n");
4863
+ return;
4864
+ }
4865
+ renderWorkflowDetail(detail, rc.stdout);
4866
+ }
4867
+ function emitWorkflowApply(result, ctx, rc) {
4868
+ if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
4869
+ if (rc.jsonMode) {
4870
+ rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
4871
+ return;
4872
+ }
4873
+ renderWorkflowApplyResult(result, rc.stdout);
4874
+ }
4875
+ async function readJsonFile(filePath) {
4876
+ const { readFile: readFile3 } = await import("fs/promises");
4877
+ let text;
4878
+ try {
4879
+ text = await readFile3(filePath, "utf8");
4880
+ } catch (err) {
4881
+ const code = err.code;
4882
+ if (code === "ENOENT") throw new ArgError(`File not found: ${filePath}`);
4883
+ if (code === "EACCES") throw new ArgError(`Permission denied: ${filePath}`);
4884
+ throw new ArgError(`Failed to read file: ${filePath}`);
4885
+ }
4886
+ try {
4887
+ return JSON.parse(text);
4888
+ } catch {
4889
+ throw new ArgError(`File is not valid JSON: ${filePath}`);
4890
+ }
4891
+ }
4332
4892
 
4333
4893
  // src/index.ts
4334
4894
  var __filename2 = fileURLToPath(import.meta.url);