@clickraft/cli 0.6.0 → 0.7.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.
- package/CHANGELOG.md +4 -14
- package/README.md +22 -0
- package/dist/cli.js +552 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +552 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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:
|
|
159
|
+
hint: "Include --canvas-rev or use --auto-rev."
|
|
154
160
|
},
|
|
155
|
-
|
|
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: "
|
|
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,
|
|
@@ -2417,6 +2435,7 @@ var DEFAULT_SCOPES = [
|
|
|
2417
2435
|
"workflows:read",
|
|
2418
2436
|
"brand-models:read",
|
|
2419
2437
|
"products:read",
|
|
2438
|
+
"models:read",
|
|
2420
2439
|
"assets:write",
|
|
2421
2440
|
"balance:read"
|
|
2422
2441
|
];
|
|
@@ -2880,6 +2899,157 @@ async function listTokens(options) {
|
|
|
2880
2899
|
return data;
|
|
2881
2900
|
}
|
|
2882
2901
|
|
|
2902
|
+
// src/schemas/workflows.ts
|
|
2903
|
+
import { z as z12 } from "zod";
|
|
2904
|
+
var WorkflowDeclaredParamSchema = z12.object({
|
|
2905
|
+
type: z12.string(),
|
|
2906
|
+
required: z12.boolean().optional(),
|
|
2907
|
+
default: z12.unknown().optional(),
|
|
2908
|
+
description: z12.string().optional()
|
|
2909
|
+
}).strict();
|
|
2910
|
+
var WorkflowSummarySchema = z12.object({
|
|
2911
|
+
id: z12.string().uuid(),
|
|
2912
|
+
name: z12.string(),
|
|
2913
|
+
tags: z12.array(z12.string()),
|
|
2914
|
+
declared_params: z12.record(z12.string(), WorkflowDeclaredParamSchema).nullable(),
|
|
2915
|
+
canvas_rev: z12.number().int().nonnegative(),
|
|
2916
|
+
updated_at: z12.string()
|
|
2917
|
+
}).strict();
|
|
2918
|
+
var WorkflowsListResponseSchema = z12.object({
|
|
2919
|
+
items: z12.array(WorkflowSummarySchema),
|
|
2920
|
+
nextCursor: z12.string().nullable()
|
|
2921
|
+
}).strict();
|
|
2922
|
+
var WorkflowDetailSchema = z12.object({
|
|
2923
|
+
id: z12.string().uuid(),
|
|
2924
|
+
name: z12.string(),
|
|
2925
|
+
description: z12.string().nullable(),
|
|
2926
|
+
tags: z12.array(z12.string()),
|
|
2927
|
+
declared_params: z12.record(z12.string(), WorkflowDeclaredParamSchema).nullable(),
|
|
2928
|
+
canvas_rev: z12.number().int().nonnegative(),
|
|
2929
|
+
nodes: z12.array(z12.unknown()),
|
|
2930
|
+
edges: z12.array(z12.unknown()),
|
|
2931
|
+
created_at: z12.string(),
|
|
2932
|
+
updated_at: z12.string()
|
|
2933
|
+
}).strict();
|
|
2934
|
+
var WorkflowCreateResponseSchema = z12.object({
|
|
2935
|
+
id: z12.string().uuid(),
|
|
2936
|
+
name: z12.string(),
|
|
2937
|
+
description: z12.string().nullable(),
|
|
2938
|
+
tags: z12.array(z12.string()),
|
|
2939
|
+
declared_params: z12.record(z12.string(), WorkflowDeclaredParamSchema).nullable(),
|
|
2940
|
+
canvas_rev: z12.number().int().nonnegative(),
|
|
2941
|
+
created_at: z12.string(),
|
|
2942
|
+
updated_at: z12.string()
|
|
2943
|
+
}).strict();
|
|
2944
|
+
var WorkflowMutateResponseSchema = z12.object({
|
|
2945
|
+
canvas_rev: z12.number().int().nonnegative(),
|
|
2946
|
+
applied: z12.number().int().nonnegative(),
|
|
2947
|
+
workflow: WorkflowDetailSchema
|
|
2948
|
+
}).strict();
|
|
2949
|
+
|
|
2950
|
+
// src/commands/workflow/internal.ts
|
|
2951
|
+
async function buildWorkflowClient(opts) {
|
|
2952
|
+
const cfg = await loadConfig({
|
|
2953
|
+
flagProfile: opts.profile,
|
|
2954
|
+
flagApiBaseUrl: opts.apiBaseUrl,
|
|
2955
|
+
flagToken: opts.token
|
|
2956
|
+
});
|
|
2957
|
+
return new ApiClient({
|
|
2958
|
+
apiBaseUrl: cfg.apiBaseUrl,
|
|
2959
|
+
accessToken: cfg.accessToken,
|
|
2960
|
+
signal: opts.signal,
|
|
2961
|
+
cloudflareAccess: cfg.cloudflareAccess
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/commands/workflow/get.ts
|
|
2966
|
+
async function getWorkflow(opts) {
|
|
2967
|
+
const client = opts.client ?? await buildWorkflowClient(opts);
|
|
2968
|
+
return client.request({
|
|
2969
|
+
method: "GET",
|
|
2970
|
+
path: `/workflows/${encodeURIComponent(opts.id)}`,
|
|
2971
|
+
responseSchema: WorkflowDetailSchema,
|
|
2972
|
+
signal: opts.signal
|
|
2973
|
+
});
|
|
2974
|
+
}
|
|
2975
|
+
|
|
2976
|
+
// src/commands/workflow/apply.ts
|
|
2977
|
+
async function applyWorkflow(opts) {
|
|
2978
|
+
const client = opts.client ?? await buildWorkflowClient(opts);
|
|
2979
|
+
try {
|
|
2980
|
+
return await patchWorkflow(client, opts);
|
|
2981
|
+
} catch (err) {
|
|
2982
|
+
if (!(err instanceof ApiError)) throw err;
|
|
2983
|
+
const isRevConflict = err.code === "CANVAS_REV_MISMATCH" || err.meta.status === 409 && err.meta.details != null;
|
|
2984
|
+
if (!isRevConflict || !opts.autoRev) throw err;
|
|
2985
|
+
const snapshot = await getWorkflow({ id: opts.id, client, signal: opts.signal });
|
|
2986
|
+
const retryResult = await patchWorkflow(client, {
|
|
2987
|
+
...opts,
|
|
2988
|
+
canvasRev: snapshot.canvas_rev,
|
|
2989
|
+
// Fresh key — the body changed (different canvas_rev), so reusing
|
|
2990
|
+
// the caller's key would trigger IDEMPOTENCY_KEY_CONFLICT.
|
|
2991
|
+
idempotencyKey: void 0
|
|
2992
|
+
});
|
|
2993
|
+
return { ...retryResult, retriedWithRev: snapshot.canvas_rev };
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
async function patchWorkflow(client, opts) {
|
|
2997
|
+
const data = await client.request({
|
|
2998
|
+
method: "PATCH",
|
|
2999
|
+
path: `/workflows/${encodeURIComponent(opts.id)}`,
|
|
3000
|
+
body: {
|
|
3001
|
+
canvas_rev: opts.canvasRev,
|
|
3002
|
+
operations: opts.operations
|
|
3003
|
+
},
|
|
3004
|
+
idempotencyKey: opts.idempotencyKey,
|
|
3005
|
+
responseSchema: WorkflowMutateResponseSchema,
|
|
3006
|
+
signal: opts.signal
|
|
3007
|
+
});
|
|
3008
|
+
return {
|
|
3009
|
+
canvasRev: data.canvas_rev,
|
|
3010
|
+
applied: data.applied,
|
|
3011
|
+
workflow: data.workflow
|
|
3012
|
+
};
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
// src/commands/workflow/create.ts
|
|
3016
|
+
async function createWorkflow(opts) {
|
|
3017
|
+
const client = opts.client ?? await buildWorkflowClient(opts);
|
|
3018
|
+
const body = { name: opts.name };
|
|
3019
|
+
if (opts.description !== void 0) body.description = opts.description;
|
|
3020
|
+
if (opts.tags !== void 0 && opts.tags.length > 0) body.tags = opts.tags;
|
|
3021
|
+
if (opts.declaredParams !== void 0) body.declared_params = opts.declaredParams;
|
|
3022
|
+
return client.request({
|
|
3023
|
+
method: "POST",
|
|
3024
|
+
path: "/workflows",
|
|
3025
|
+
body,
|
|
3026
|
+
responseSchema: WorkflowCreateResponseSchema,
|
|
3027
|
+
signal: opts.signal
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// src/commands/workflow/list.ts
|
|
3032
|
+
async function listWorkflows(opts) {
|
|
3033
|
+
const client = opts.client ?? await buildWorkflowClient(opts);
|
|
3034
|
+
const data = await client.request({
|
|
3035
|
+
method: "GET",
|
|
3036
|
+
path: "/workflows",
|
|
3037
|
+
query: {
|
|
3038
|
+
cursor: opts.cursor,
|
|
3039
|
+
limit: opts.limit,
|
|
3040
|
+
tags: opts.tags?.join(","),
|
|
3041
|
+
declared_params_has_key: opts.declaredParamsHasKey
|
|
3042
|
+
},
|
|
3043
|
+
responseSchema: WorkflowsListResponseSchema,
|
|
3044
|
+
signal: opts.signal
|
|
3045
|
+
});
|
|
3046
|
+
return {
|
|
3047
|
+
items: data.items,
|
|
3048
|
+
nextCursor: data.nextCursor,
|
|
3049
|
+
hasMore: data.nextCursor !== null
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
3052
|
+
|
|
2883
3053
|
// src/errors/format.ts
|
|
2884
3054
|
var FALLBACK_HINT = "See `clickraft --help` or contact support.";
|
|
2885
3055
|
function formatHumanError(err) {
|
|
@@ -3329,6 +3499,81 @@ function formatExpiresAt(iso) {
|
|
|
3329
3499
|
const days = Math.round((ms - Date.now()) / (24 * 60 * 60 * 1e3));
|
|
3330
3500
|
return `${date.toISOString()} (~${days}d)`;
|
|
3331
3501
|
}
|
|
3502
|
+
function renderWorkflowCreateResult(result, out) {
|
|
3503
|
+
const lines = [
|
|
3504
|
+
`Created workflow "${result.name}"`,
|
|
3505
|
+
` ID: ${result.id}`,
|
|
3506
|
+
` Canvas rev: ${result.canvas_rev}`,
|
|
3507
|
+
` Created: ${result.created_at.slice(0, 19)}`
|
|
3508
|
+
];
|
|
3509
|
+
out.write(lines.join("\n") + "\n");
|
|
3510
|
+
}
|
|
3511
|
+
function renderWorkflowsList(result, out) {
|
|
3512
|
+
const { items } = result;
|
|
3513
|
+
if (items.length === 0) {
|
|
3514
|
+
out.write("No workflows.\n");
|
|
3515
|
+
return;
|
|
3516
|
+
}
|
|
3517
|
+
const MAX_TAGS = 3;
|
|
3518
|
+
const headers = ["ID", "NAME", "TAGS", "REV", "UPDATED"];
|
|
3519
|
+
const data = items.map((w) => {
|
|
3520
|
+
const shown = w.tags.slice(0, MAX_TAGS).join(", ");
|
|
3521
|
+
const extra = w.tags.length > MAX_TAGS ? ` +${w.tags.length - MAX_TAGS} more` : "";
|
|
3522
|
+
return [
|
|
3523
|
+
w.id.slice(0, 8),
|
|
3524
|
+
w.name,
|
|
3525
|
+
w.tags.length > 0 ? shown + extra : "\u2014",
|
|
3526
|
+
String(w.canvas_rev),
|
|
3527
|
+
w.updated_at.slice(0, 10)
|
|
3528
|
+
];
|
|
3529
|
+
});
|
|
3530
|
+
const widths = headers.map(
|
|
3531
|
+
(header, i) => Math.max(header.length, ...data.map((cells) => cells[i].length))
|
|
3532
|
+
);
|
|
3533
|
+
const padRow = (cells) => cells.map((cell, i) => cell.padEnd(widths[i])).join(" ").trimEnd();
|
|
3534
|
+
out.write(padRow(headers) + "\n");
|
|
3535
|
+
for (const cells of data) out.write(padRow(cells) + "\n");
|
|
3536
|
+
if (result.hasMore) {
|
|
3537
|
+
out.write("\nMore results available. Use --cursor to fetch the next page.\n");
|
|
3538
|
+
}
|
|
3539
|
+
}
|
|
3540
|
+
function renderWorkflowDetail(detail, out) {
|
|
3541
|
+
const pad = 17;
|
|
3542
|
+
const lines = [
|
|
3543
|
+
detail.name,
|
|
3544
|
+
`${"ID:".padEnd(pad)}${detail.id}`,
|
|
3545
|
+
`${"Canvas rev:".padEnd(pad)}${detail.canvas_rev}`,
|
|
3546
|
+
`${"Tags:".padEnd(pad)}${detail.tags.length > 0 ? detail.tags.join(", ") : "\u2014"}`,
|
|
3547
|
+
`${"Created:".padEnd(pad)}${detail.created_at.slice(0, 19)}`,
|
|
3548
|
+
`${"Updated:".padEnd(pad)}${detail.updated_at.slice(0, 19)}`,
|
|
3549
|
+
`${"Nodes:".padEnd(pad)}${detail.nodes.length}`,
|
|
3550
|
+
`${"Edges:".padEnd(pad)}${detail.edges.length}`
|
|
3551
|
+
];
|
|
3552
|
+
if (detail.description) {
|
|
3553
|
+
lines.push("", detail.description);
|
|
3554
|
+
}
|
|
3555
|
+
const params = detail.declared_params;
|
|
3556
|
+
lines.push("", "Declared parameters:");
|
|
3557
|
+
if (!params || Object.keys(params).length === 0) {
|
|
3558
|
+
lines.push(" (none)");
|
|
3559
|
+
} else {
|
|
3560
|
+
for (const [name, p] of Object.entries(params)) {
|
|
3561
|
+
const req = p.required ? "required" : "optional";
|
|
3562
|
+
const desc = p.description ? `: ${p.description}` : "";
|
|
3563
|
+
lines.push(` - ${name} (${p.type}, ${req})${desc}`);
|
|
3564
|
+
}
|
|
3565
|
+
}
|
|
3566
|
+
out.write(lines.join("\n") + "\n");
|
|
3567
|
+
}
|
|
3568
|
+
function renderWorkflowApplyResult(result, out) {
|
|
3569
|
+
const lines = [
|
|
3570
|
+
`Applied ${result.applied} operation${result.applied === 1 ? "" : "s"}. New canvas_rev: ${result.canvasRev}`
|
|
3571
|
+
];
|
|
3572
|
+
if (result.retriedWithRev !== void 0) {
|
|
3573
|
+
lines.push(` (auto-rev: resolved conflict, retried with canvas_rev ${result.retriedWithRev})`);
|
|
3574
|
+
}
|
|
3575
|
+
out.write(lines.join("\n") + "\n");
|
|
3576
|
+
}
|
|
3332
3577
|
|
|
3333
3578
|
// src/cli/dispatch.ts
|
|
3334
3579
|
async function dispatch(options) {
|
|
@@ -3558,6 +3803,10 @@ function renderRootHelp(version) {
|
|
|
3558
3803
|
" generate wait Wait for an existing job to reach a terminal state",
|
|
3559
3804
|
" generate get Fetch the current state of a job",
|
|
3560
3805
|
" jobs wait Alias of `generate wait`",
|
|
3806
|
+
" workflow create Create a new empty workflow",
|
|
3807
|
+
" workflow list List workflows in your organization",
|
|
3808
|
+
" workflow get Fetch a workflow snapshot by ID",
|
|
3809
|
+
" workflow apply Apply operations to a workflow (canvas mutation)",
|
|
3561
3810
|
" upload <file> Upload a local file and return its public URL",
|
|
3562
3811
|
"",
|
|
3563
3812
|
"Options:",
|
|
@@ -3851,6 +4100,83 @@ function renderCommandHelp(command) {
|
|
|
3851
4100
|
" --json",
|
|
3852
4101
|
""
|
|
3853
4102
|
].join("\n");
|
|
4103
|
+
case "workflow":
|
|
4104
|
+
return [
|
|
4105
|
+
"Usage: clickraft workflow <subcommand> [options]",
|
|
4106
|
+
"",
|
|
4107
|
+
"Subcommands:",
|
|
4108
|
+
" create Create a new empty workflow",
|
|
4109
|
+
" list List workflows in your organization",
|
|
4110
|
+
" get Fetch a workflow snapshot by ID",
|
|
4111
|
+
" apply Apply operations to a workflow (canvas mutation)",
|
|
4112
|
+
""
|
|
4113
|
+
].join("\n");
|
|
4114
|
+
case "workflow create":
|
|
4115
|
+
return [
|
|
4116
|
+
"Usage: clickraft workflow create --name <name> [options]",
|
|
4117
|
+
"",
|
|
4118
|
+
"Creates a new empty workflow.",
|
|
4119
|
+
"",
|
|
4120
|
+
"Options:",
|
|
4121
|
+
" --name <name> Workflow name (required unless --from-file)",
|
|
4122
|
+
" --description <text> Optional description",
|
|
4123
|
+
" --tags <tag> Tag (repeatable)",
|
|
4124
|
+
" --from-file <path> Read the full create payload as JSON from a file",
|
|
4125
|
+
" --profile <name>",
|
|
4126
|
+
" --api-base-url <url>",
|
|
4127
|
+
" --token <token>",
|
|
4128
|
+
" --json",
|
|
4129
|
+
""
|
|
4130
|
+
].join("\n");
|
|
4131
|
+
case "workflow list":
|
|
4132
|
+
return [
|
|
4133
|
+
"Usage: clickraft workflow list [options]",
|
|
4134
|
+
"",
|
|
4135
|
+
"Lists workflows in your organization. Supports cursor-based pagination.",
|
|
4136
|
+
"",
|
|
4137
|
+
"Options:",
|
|
4138
|
+
" --tags <csv> Filter by tags (comma-separated)",
|
|
4139
|
+
" --has-param <key> Filter to workflows that declare this param",
|
|
4140
|
+
" --limit <n> Max results per page (1-100, default: 20)",
|
|
4141
|
+
" --cursor <token> Pagination cursor from a previous response",
|
|
4142
|
+
" --profile <name>",
|
|
4143
|
+
" --api-base-url <url>",
|
|
4144
|
+
" --token <token>",
|
|
4145
|
+
" --json",
|
|
4146
|
+
""
|
|
4147
|
+
].join("\n");
|
|
4148
|
+
case "workflow get":
|
|
4149
|
+
return [
|
|
4150
|
+
"Usage: clickraft workflow get <id> [options]",
|
|
4151
|
+
"",
|
|
4152
|
+
"Fetches a workflow snapshot including nodes and edges.",
|
|
4153
|
+
"",
|
|
4154
|
+
"Options:",
|
|
4155
|
+
" --profile <name>",
|
|
4156
|
+
" --api-base-url <url>",
|
|
4157
|
+
" --token <token>",
|
|
4158
|
+
" --json",
|
|
4159
|
+
""
|
|
4160
|
+
].join("\n");
|
|
4161
|
+
case "workflow apply":
|
|
4162
|
+
return [
|
|
4163
|
+
"Usage: clickraft workflow apply <id> [options]",
|
|
4164
|
+
"",
|
|
4165
|
+
"Applies mutation operations to a workflow. Requires --canvas-rev or --auto-rev.",
|
|
4166
|
+
"",
|
|
4167
|
+
"Options:",
|
|
4168
|
+
" --from-file <path> JSON file with operations (array or { canvas_rev, operations })",
|
|
4169
|
+
" --op <json> Single operation as JSON string (repeatable)",
|
|
4170
|
+
" --canvas-rev <n> The canvas_rev preconditioning the mutation",
|
|
4171
|
+
" --auto-rev Fetch current canvas_rev first; retry once on 409",
|
|
4172
|
+
" --idempotency-key <key> Override the auto-generated idempotency key",
|
|
4173
|
+
" --dry-run Print the request payload without sending it",
|
|
4174
|
+
" --profile <name>",
|
|
4175
|
+
" --api-base-url <url>",
|
|
4176
|
+
" --token <token>",
|
|
4177
|
+
" --json",
|
|
4178
|
+
""
|
|
4179
|
+
].join("\n");
|
|
3854
4180
|
default:
|
|
3855
4181
|
return `Unknown command: ${path}
|
|
3856
4182
|
Run \`clickraft --help\` for available commands.
|
|
@@ -3897,6 +4223,15 @@ function matchNewCommand(argv) {
|
|
|
3897
4223
|
if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "models" };
|
|
3898
4224
|
return { kind: "unknown-sub", verb: "models", sub };
|
|
3899
4225
|
}
|
|
4226
|
+
if (verb === "workflow") {
|
|
4227
|
+
const sub = argv[1];
|
|
4228
|
+
if (sub === "create") return { kind: "cmd", path: "workflow.create", verbTokenCount: 2 };
|
|
4229
|
+
if (sub === "list") return { kind: "cmd", path: "workflow.list", verbTokenCount: 2 };
|
|
4230
|
+
if (sub === "get") return { kind: "cmd", path: "workflow.get", verbTokenCount: 2 };
|
|
4231
|
+
if (sub === "apply") return { kind: "cmd", path: "workflow.apply", verbTokenCount: 2 };
|
|
4232
|
+
if (sub === void 0 || sub.startsWith("-")) return { kind: "sub-help", verb: "workflow" };
|
|
4233
|
+
return { kind: "unknown-sub", verb: "workflow", sub };
|
|
4234
|
+
}
|
|
3900
4235
|
return null;
|
|
3901
4236
|
}
|
|
3902
4237
|
async function runNewCommand(newCmd, options, stdout, stderr) {
|
|
@@ -3957,6 +4292,14 @@ async function runNewCommand(newCmd, options, stdout, stderr) {
|
|
|
3957
4292
|
return await runProductList(argvAfterVerb, ctx, rc);
|
|
3958
4293
|
case "models.list":
|
|
3959
4294
|
return await runModelsList(argvAfterVerb, ctx, rc);
|
|
4295
|
+
case "workflow.create":
|
|
4296
|
+
return await runWorkflowCreate(argvAfterVerb, ctx, rc);
|
|
4297
|
+
case "workflow.list":
|
|
4298
|
+
return await runWorkflowList(argvAfterVerb, ctx, rc);
|
|
4299
|
+
case "workflow.get":
|
|
4300
|
+
return await runWorkflowGet(argvAfterVerb, ctx, rc);
|
|
4301
|
+
case "workflow.apply":
|
|
4302
|
+
return await runWorkflowApply(argvAfterVerb, ctx, rc);
|
|
3960
4303
|
}
|
|
3961
4304
|
} catch (err) {
|
|
3962
4305
|
if (isAbortedError(err, options.signal)) {
|
|
@@ -4328,6 +4671,211 @@ function emitModelsList(result, ctx, rc, parsed) {
|
|
|
4328
4671
|
}
|
|
4329
4672
|
});
|
|
4330
4673
|
}
|
|
4674
|
+
async function runWorkflowCreate(argv, ctx, rc) {
|
|
4675
|
+
const parsed = parseArgs(argv, {
|
|
4676
|
+
string: [...COMMON_RICH_STRING_FLAGS, "name", "description", "from-file"],
|
|
4677
|
+
boolean: [...COMMON_RICH_BOOL_FLAGS],
|
|
4678
|
+
array: ["tags"]
|
|
4679
|
+
});
|
|
4680
|
+
let name = parsed.string.name;
|
|
4681
|
+
let description = parsed.string.description;
|
|
4682
|
+
let tags = parsed.array.tags;
|
|
4683
|
+
let declaredParams;
|
|
4684
|
+
if (parsed.string["from-file"]) {
|
|
4685
|
+
const raw = await readJsonFile(parsed.string["from-file"]);
|
|
4686
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
|
|
4687
|
+
const obj = raw;
|
|
4688
|
+
name = name ?? (typeof obj.name === "string" ? obj.name : void 0);
|
|
4689
|
+
description = description ?? (typeof obj.description === "string" ? obj.description : void 0);
|
|
4690
|
+
if (!tags && Array.isArray(obj.tags)) tags = obj.tags;
|
|
4691
|
+
if (obj.declared_params && typeof obj.declared_params === "object") {
|
|
4692
|
+
declaredParams = obj.declared_params;
|
|
4693
|
+
}
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
if (!name) throw new ArgError("--name is required (or provide it in --from-file).");
|
|
4697
|
+
const result = await createWorkflow({
|
|
4698
|
+
name,
|
|
4699
|
+
description,
|
|
4700
|
+
tags,
|
|
4701
|
+
declaredParams,
|
|
4702
|
+
profile: parsed.string.profile,
|
|
4703
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
4704
|
+
token: parsed.string.token,
|
|
4705
|
+
signal: rc.signal
|
|
4706
|
+
});
|
|
4707
|
+
emitWorkflowCreate(result, ctx, rc);
|
|
4708
|
+
return 0;
|
|
4709
|
+
}
|
|
4710
|
+
async function runWorkflowList(argv, ctx, rc) {
|
|
4711
|
+
const parsed = parseArgs(argv, {
|
|
4712
|
+
string: [...COMMON_RICH_STRING_FLAGS, "tags", "has-param", "cursor", "limit"],
|
|
4713
|
+
boolean: [...COMMON_RICH_BOOL_FLAGS]
|
|
4714
|
+
});
|
|
4715
|
+
const limit = optionalInt(parsed.string.limit, "limit");
|
|
4716
|
+
if (limit !== void 0 && (limit < 1 || limit > 100)) {
|
|
4717
|
+
throw new ArgError("--limit must be between 1 and 100.");
|
|
4718
|
+
}
|
|
4719
|
+
const result = await listWorkflows({
|
|
4720
|
+
cursor: parsed.string.cursor,
|
|
4721
|
+
limit,
|
|
4722
|
+
tags: parsed.string.tags ? parsed.string.tags.split(",").map((s) => s.trim()).filter(Boolean) : void 0,
|
|
4723
|
+
declaredParamsHasKey: parsed.string["has-param"],
|
|
4724
|
+
profile: parsed.string.profile,
|
|
4725
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
4726
|
+
token: parsed.string.token,
|
|
4727
|
+
signal: rc.signal
|
|
4728
|
+
});
|
|
4729
|
+
ctx.nextCursor = result.nextCursor ?? void 0;
|
|
4730
|
+
emitWorkflowList(result, ctx, rc);
|
|
4731
|
+
return 0;
|
|
4732
|
+
}
|
|
4733
|
+
async function runWorkflowGet(argv, ctx, rc) {
|
|
4734
|
+
const parsed = parseArgs(argv, {
|
|
4735
|
+
string: [...COMMON_RICH_STRING_FLAGS],
|
|
4736
|
+
boolean: [...COMMON_RICH_BOOL_FLAGS]
|
|
4737
|
+
});
|
|
4738
|
+
const id = parsed.positional[0];
|
|
4739
|
+
if (!id) throw new ArgError("Missing required <id> argument.");
|
|
4740
|
+
const detail = await getWorkflow({
|
|
4741
|
+
id,
|
|
4742
|
+
profile: parsed.string.profile,
|
|
4743
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
4744
|
+
token: parsed.string.token,
|
|
4745
|
+
signal: rc.signal
|
|
4746
|
+
});
|
|
4747
|
+
emitWorkflowDetail(detail, ctx, rc);
|
|
4748
|
+
return 0;
|
|
4749
|
+
}
|
|
4750
|
+
async function runWorkflowApply(argv, ctx, rc) {
|
|
4751
|
+
const parsed = parseArgs(argv, {
|
|
4752
|
+
string: [...COMMON_RICH_STRING_FLAGS, "canvas-rev", "from-file", "idempotency-key"],
|
|
4753
|
+
boolean: [...COMMON_RICH_BOOL_FLAGS, "auto-rev", "dry-run"],
|
|
4754
|
+
array: ["op"]
|
|
4755
|
+
});
|
|
4756
|
+
const id = parsed.positional[0];
|
|
4757
|
+
if (!id) throw new ArgError("Missing required <id> argument.");
|
|
4758
|
+
let operations = [];
|
|
4759
|
+
let fileCanvasRev;
|
|
4760
|
+
if (parsed.string["from-file"]) {
|
|
4761
|
+
const raw = await readJsonFile(parsed.string["from-file"]);
|
|
4762
|
+
if (Array.isArray(raw)) {
|
|
4763
|
+
operations = raw;
|
|
4764
|
+
} else if (typeof raw === "object" && raw !== null && "operations" in raw) {
|
|
4765
|
+
const obj = raw;
|
|
4766
|
+
operations = obj.operations;
|
|
4767
|
+
if (typeof obj.canvas_rev === "number") fileCanvasRev = obj.canvas_rev;
|
|
4768
|
+
} else {
|
|
4769
|
+
throw new ArgError(
|
|
4770
|
+
'--from-file must contain a JSON array or an object with an "operations" key.'
|
|
4771
|
+
);
|
|
4772
|
+
}
|
|
4773
|
+
}
|
|
4774
|
+
if (parsed.array.op) {
|
|
4775
|
+
for (const opStr of parsed.array.op) {
|
|
4776
|
+
try {
|
|
4777
|
+
operations.push(JSON.parse(opStr));
|
|
4778
|
+
} catch {
|
|
4779
|
+
throw new ArgError(`Invalid JSON in --op: ${opStr}`);
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
}
|
|
4783
|
+
if (operations.length === 0) {
|
|
4784
|
+
throw new ArgError("No operations provided. Use --from-file or --op.");
|
|
4785
|
+
}
|
|
4786
|
+
if (operations.length > 100) {
|
|
4787
|
+
throw new ArgError("At most 100 operations per request.");
|
|
4788
|
+
}
|
|
4789
|
+
const autoRev = parsed.boolean["auto-rev"] === true;
|
|
4790
|
+
let canvasRev;
|
|
4791
|
+
if (parsed.string["canvas-rev"] !== void 0) {
|
|
4792
|
+
const n = optionalInt(parsed.string["canvas-rev"], "canvas-rev");
|
|
4793
|
+
if (n === void 0) throw new ArgError("--canvas-rev must be a non-negative integer.");
|
|
4794
|
+
canvasRev = n;
|
|
4795
|
+
} else if (autoRev) {
|
|
4796
|
+
const snapshot = await getWorkflow({
|
|
4797
|
+
id,
|
|
4798
|
+
profile: parsed.string.profile,
|
|
4799
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
4800
|
+
token: parsed.string.token,
|
|
4801
|
+
signal: rc.signal
|
|
4802
|
+
});
|
|
4803
|
+
canvasRev = snapshot.canvas_rev;
|
|
4804
|
+
} else if (fileCanvasRev !== void 0) {
|
|
4805
|
+
canvasRev = fileCanvasRev;
|
|
4806
|
+
} else {
|
|
4807
|
+
throw new ArgError(
|
|
4808
|
+
"Must provide --canvas-rev, use --auto-rev, or include canvas_rev in --from-file."
|
|
4809
|
+
);
|
|
4810
|
+
}
|
|
4811
|
+
if (parsed.boolean["dry-run"] === true) {
|
|
4812
|
+
const payload = { canvas_rev: canvasRev, operations };
|
|
4813
|
+
rc.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
4814
|
+
return 0;
|
|
4815
|
+
}
|
|
4816
|
+
const result = await applyWorkflow({
|
|
4817
|
+
id,
|
|
4818
|
+
canvasRev,
|
|
4819
|
+
operations,
|
|
4820
|
+
idempotencyKey: parsed.string["idempotency-key"],
|
|
4821
|
+
autoRev,
|
|
4822
|
+
profile: parsed.string.profile,
|
|
4823
|
+
apiBaseUrl: parsed.string["api-base-url"],
|
|
4824
|
+
token: parsed.string.token,
|
|
4825
|
+
signal: rc.signal
|
|
4826
|
+
});
|
|
4827
|
+
emitWorkflowApply(result, ctx, rc);
|
|
4828
|
+
return 0;
|
|
4829
|
+
}
|
|
4830
|
+
function emitWorkflowCreate(result, ctx, rc) {
|
|
4831
|
+
if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
|
|
4832
|
+
if (rc.jsonMode) {
|
|
4833
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
|
|
4834
|
+
return;
|
|
4835
|
+
}
|
|
4836
|
+
renderWorkflowCreateResult(result, rc.stdout);
|
|
4837
|
+
}
|
|
4838
|
+
function emitWorkflowList(result, ctx, rc) {
|
|
4839
|
+
if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
|
|
4840
|
+
if (rc.jsonMode) {
|
|
4841
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
|
|
4842
|
+
return;
|
|
4843
|
+
}
|
|
4844
|
+
renderWorkflowsList(result, rc.stdout);
|
|
4845
|
+
}
|
|
4846
|
+
function emitWorkflowDetail(detail, ctx, rc) {
|
|
4847
|
+
if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
|
|
4848
|
+
if (rc.jsonMode) {
|
|
4849
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(detail, ctx)) + "\n");
|
|
4850
|
+
return;
|
|
4851
|
+
}
|
|
4852
|
+
renderWorkflowDetail(detail, rc.stdout);
|
|
4853
|
+
}
|
|
4854
|
+
function emitWorkflowApply(result, ctx, rc) {
|
|
4855
|
+
if (rc.signal?.aborted) throw new AuthError("AUTH_FLOW_CANCELLED", "Cancelled.");
|
|
4856
|
+
if (rc.jsonMode) {
|
|
4857
|
+
rc.stdout.write(JSON.stringify(wrapSuccess(result, ctx)) + "\n");
|
|
4858
|
+
return;
|
|
4859
|
+
}
|
|
4860
|
+
renderWorkflowApplyResult(result, rc.stdout);
|
|
4861
|
+
}
|
|
4862
|
+
async function readJsonFile(filePath) {
|
|
4863
|
+
const { readFile: readFile3 } = await import("fs/promises");
|
|
4864
|
+
let text;
|
|
4865
|
+
try {
|
|
4866
|
+
text = await readFile3(filePath, "utf8");
|
|
4867
|
+
} catch (err) {
|
|
4868
|
+
const code = err.code;
|
|
4869
|
+
if (code === "ENOENT") throw new ArgError(`File not found: ${filePath}`);
|
|
4870
|
+
if (code === "EACCES") throw new ArgError(`Permission denied: ${filePath}`);
|
|
4871
|
+
throw new ArgError(`Failed to read file: ${filePath}`);
|
|
4872
|
+
}
|
|
4873
|
+
try {
|
|
4874
|
+
return JSON.parse(text);
|
|
4875
|
+
} catch {
|
|
4876
|
+
throw new ArgError(`File is not valid JSON: ${filePath}`);
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4331
4879
|
|
|
4332
4880
|
// src/index.ts
|
|
4333
4881
|
var __filename2 = fileURLToPath(import.meta.url);
|