@cargo-ai/cli 1.0.19 → 1.0.20

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/README.md CHANGED
@@ -98,25 +98,55 @@ cargo-ai orchestration workflow --help
98
98
 
99
99
  ### Domains and example commands
100
100
 
101
- | Domain | Description | Example commands |
102
- | ------------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
103
- | **init** | Workspace initialisation (user, workspace, datasets, etc.) | `cargo-ai init` |
104
- | **orchestration** | Workflows, plays, runs, batches, tools, templates | `cargo-ai orchestration workflow list`, `cargo-ai orchestration run list --workflow-uuid <uuid>` |
105
- | **workspaceManagement** | Workspaces, users, tokens, roles, folders | `cargo-ai workspaceManagement workspaces list`, `cargo-ai workspaceManagement token list` |
106
- | **storage** | Datasets, models, relationships, runs, records | `cargo-ai storage dataset list`, `cargo-ai storage model list` |
107
- | **connection** | Connectors and integrations | `cargo-ai connection connector list`, `cargo-ai connection integration list` |
108
- | **billing** | Usage and subscription | `cargo-ai billing subscription get`, `cargo-ai billing usage get-metrics --payload '{"from":"2025-01-01","to":"2025-01-31"}'` |
109
- | **segmentation** | Segments and changes | `cargo-ai segmentation segment list`, `cargo-ai segmentation change list --payload '{}'` |
110
- | **revenue-organization** | Allocations, capacities, members, territories | `cargo-ai revenue-organization member list`, `cargo-ai revenue-organization territory list` |
111
- | **expression** | Recipes and expression evaluation | `cargo-ai expression recipe list`, `cargo-ai expression eval evaluate --payload '{}'` |
112
- | **system-of-record** | System of record, client, logs | `cargo-ai system-of-record sor list`, `cargo-ai system-of-record log list --payload '{}'` |
113
- | **user-management** | Current user (no workspace context) | `cargo-ai user-management user get-current` |
114
- | **ai** | AI templates, agents, releases, chats, MCP, files | `cargo-ai ai template list`, `cargo-ai ai agent list`, `cargo-ai ai file list` |
115
- | **context** | Context repository, runtime sandbox, and knowledge graph | `cargo-ai context repository get`, `cargo-ai context runtime browse --path <path>`, `cargo-ai context graph get` |
116
- | **hosting** | Cargo Hosting apps (Vite SPAs), workers, and deployments | `cargo-ai hosting app list`, `cargo-ai hosting worker list`, `cargo-ai hosting deployment list --payload '{}'` |
101
+ | Domain | Description | Example commands |
102
+ | ------------------------ | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
103
+ | **init** | Workspace initialisation (user, workspace, datasets, etc.) | `cargo-ai init` |
104
+ | **orchestration** | Workflows, plays, runs, batches, tools, templates | `cargo-ai orchestration workflow list`, `cargo-ai orchestration run list --workflow-uuid <uuid>` |
105
+ | **workspaceManagement** | Workspaces, users, tokens, roles, folders | `cargo-ai workspaceManagement workspaces list`, `cargo-ai workspaceManagement token list` |
106
+ | **storage** | Datasets, models, relationships, runs, records | `cargo-ai storage dataset list`, `cargo-ai storage model list` |
107
+ | **connection** | Connectors and integrations | `cargo-ai connection connector list`, `cargo-ai connection integration list` |
108
+ | **billing** | Usage and subscription | `cargo-ai billing subscription get`, `cargo-ai billing usage get-metrics --payload '{"from":"2025-01-01","to":"2025-01-31"}'` |
109
+ | **segmentation** | Segments and changes | `cargo-ai segmentation segment list`, `cargo-ai segmentation change list --payload '{}'` |
110
+ | **revenue-organization** | Allocations, capacities, members, territories | `cargo-ai revenue-organization member list`, `cargo-ai revenue-organization territory list` |
111
+ | **expression** | Recipes and expression evaluation | `cargo-ai expression recipe list`, `cargo-ai expression eval evaluate --payload '{}'` |
112
+ | **system-of-record** | System of record, client, logs | `cargo-ai system-of-record sor list`, `cargo-ai system-of-record log list --payload '{}'` |
113
+ | **user-management** | Current user (no workspace context) | `cargo-ai user-management user get-current` |
114
+ | **ai** | AI templates, agents, releases, chats, MCP, files | `cargo-ai ai template list`, `cargo-ai ai agent list`, `cargo-ai ai file list` |
115
+ | **context** | Context repository, runtime sandbox, and knowledge graph | `cargo-ai context repository get`, `cargo-ai context runtime browse --path <path>`, `cargo-ai context graph get` |
116
+ | **hosting** | Cargo Hosting apps (Vite SPAs), workers, and deployments | `cargo-ai hosting app list`, `cargo-ai hosting worker list`, `cargo-ai hosting deployment list --payload '{}'` |
117
+ | **workflow** | Workflow SDK developer tools: per-workspace type sync/codegen | `cargo-ai workflow sync` (SDK deploy lives at `cargo-ai orchestration release deploy-draft --file <path>`) |
117
118
 
118
119
  Commands that accept complex payloads use a `--payload <json>` option (e.g. `cargo-ai orchestration play create --payload '{"name":"My Play",...}'`). Use `--help` on any subcommand for options.
119
120
 
121
+ ### `orchestration release deploy-draft`
122
+
123
+ Deploy a draft release of an **existing** workflow. Two input modes share the same command:
124
+
125
+ ```bash
126
+ # Compile a Workflow SDK module and deploy it
127
+ cargo-ai orchestration release deploy-draft --file ./my-workflow.ts
128
+
129
+ # Preview the compiled nodes + form fields without deploying
130
+ cargo-ai orchestration release deploy-draft --file ./my-workflow.ts --dry-run
131
+
132
+ # Pin an explicit version + description
133
+ cargo-ai orchestration release deploy-draft --file ./my-workflow.ts --workflow-uuid <uuid> --version 2.0.0 --description "New enrichment path"
134
+
135
+ # Power-user path: deploy raw wire-format JSON
136
+ cargo-ai orchestration release deploy-draft --workflow-uuid <uuid> --nodes '[…]' --form-fields '[…]'
137
+ ```
138
+
139
+ `--file <path>` loads a [`@cargo-ai/workflow-sdk`](../workflow-sdk/README.md) module (its `export default defineWorkflow(...)`) and sources `nodes` / `formFields` / `description` from the compiled output. With `--file`:
140
+
141
+ - `--workflow-uuid` is optional — when omitted, the command best-effort matches the compiled `slug` against existing workflows' `template.slug` and errors if the match is missing or ambiguous.
142
+ - `--version` defaults to the next minor bump over the latest existing release (or `1.0.0` for the first).
143
+ - `--description` defaults to the compiled workflow's description.
144
+ - `--nodes` / `--form-fields` are rejected (they're sourced from the module).
145
+
146
+ Without `--file`, `--workflow-uuid`, `--nodes`, and `--form-fields` are all required (raw JSON path). Constraint either way: there is **no** workflow-create endpoint, so the target workflow must already exist.
147
+
148
+ Other release verbs (`list`, `get <uuid>`, `get-deployed`, `get-draft`, `update-draft`) live under the same group — see `cargo-ai orchestration release --help`.
149
+
120
150
  ## Agent Skills
121
151
 
122
152
  [Cargo Skills](https://github.com/getcargohq/cargo-skills) teaches AI coding agents (Claude Code, Cursor, Windsurf, GitHub Copilot, etc.) how to use the Cargo CLI. Install the skill to let your agent build, run, and manage revenue automation workflows programmatically:
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAgBxC,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAmBN"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAexC,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAkBN"}
@@ -1,6 +1,5 @@
1
1
  import { registerActionCommands } from "./action.js";
2
2
  import { registerBatchCommands } from "./batch.js";
3
- import { registerDraftReleaseCommands } from "./draftRelease.js";
4
3
  import { registerNodeCommands } from "./node.js";
5
4
  import { registerPlayCommands } from "./play.js";
6
5
  import { registerQueryCommands } from "./query.js";
@@ -24,7 +23,6 @@ export function registerOrchestrationCommands(parent, getApi) {
24
23
  registerSpanCommands(orchestration, getApi);
25
24
  registerTraceCommands(orchestration, getApi);
26
25
  registerReleaseCommands(orchestration, getApi);
27
- registerDraftReleaseCommands(orchestration, getApi);
28
26
  registerToolCommands(orchestration, getApi);
29
27
  registerRecordCommands(orchestration, getApi);
30
28
  registerTemplateCommands(orchestration, getApi);
@@ -1 +1 @@
1
- {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/release.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA8DN"}
1
+ {"version":3,"file":"release.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/release.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAyCxC,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAkLN"}
@@ -1,8 +1,11 @@
1
- import { handleApiCall, outputJson } from "../runHandler.js";
1
+ import { existsSync } from "node:fs";
2
+ import { isAbsolute, resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, success, } from "../runHandler.js";
2
5
  export function registerReleaseCommands(parent, getApi) {
3
6
  const release = parent
4
7
  .command("release")
5
- .description("Manage workflow releases (list, get, get-deployed)");
8
+ .description("Manage workflow releases (list, get, get-deployed, get-draft, update-draft, deploy-draft)");
6
9
  release
7
10
  .command("list")
8
11
  .description("List releases")
@@ -37,4 +40,255 @@ export function registerReleaseCommands(parent, getApi) {
37
40
  }));
38
41
  outputJson(result);
39
42
  });
43
+ release
44
+ .command("get-draft")
45
+ .description("Get the draft release for a workflow")
46
+ .requiredOption("--workflow-uuid <uuid>", "Workflow UUID")
47
+ .action(async (opts) => {
48
+ const api = getApi();
49
+ const result = await handleApiCall(() => api.orchestration.draftRelease.get({
50
+ workflowUuid: opts.workflowUuid,
51
+ }));
52
+ outputJson(result);
53
+ });
54
+ release
55
+ .command("update-draft")
56
+ .description("Update the draft release of a workflow")
57
+ .requiredOption("--workflow-uuid <uuid>", "Workflow UUID")
58
+ .option("--parent-uuid <uuid>", "Parent release UUID")
59
+ .option("--nodes <json>", "Nodes (JSON array of node definitions)")
60
+ .option("--test-records <json>", "Test records (JSON array)")
61
+ .option("--form-fields <json>", "Form fields (JSON array, or 'null')")
62
+ .option("--options <json>", "Options (JSON object)")
63
+ .action(async (opts) => {
64
+ const api = getApi();
65
+ const result = await handleApiCall(() => api.orchestration.draftRelease.update({
66
+ workflowUuid: opts.workflowUuid,
67
+ parentUuid: opts.parentUuid,
68
+ nodes: opts.nodes !== undefined
69
+ ? parseJson(opts.nodes, "--nodes")
70
+ : undefined,
71
+ testRecords: opts.testRecords !== undefined
72
+ ? parseJson(opts.testRecords, "--test-records")
73
+ : undefined,
74
+ formFields: opts.formFields === "null"
75
+ ? null
76
+ : opts.formFields !== undefined
77
+ ? parseJson(opts.formFields, "--form-fields")
78
+ : undefined,
79
+ options: opts.options !== undefined
80
+ ? parseJson(opts.options, "--options")
81
+ : undefined,
82
+ }));
83
+ outputJson(result);
84
+ });
85
+ release
86
+ .command("deploy-draft")
87
+ .description("Deploy a draft release. Pass --file to compile a Workflow SDK module, or --nodes + --form-fields to deploy raw wire-format JSON.")
88
+ .option("--file <path>", "Path to a Workflow SDK module (its default export must be the compiled workflow returned by defineWorkflow()). When set, --nodes / --form-fields are sourced from the compiled output.")
89
+ .option("--workflow-uuid <uuid>", "Target workflow UUID. Required without --file; with --file, defaults to the existing workflow whose template.slug matches the compiled slug.")
90
+ .option("--version <version>", "Release version (e.g. '1.2.0'). With --file, defaults to the next minor bump over the latest existing release; otherwise required by the engine.")
91
+ .option("--nodes <json>", "Nodes (JSON array of node definitions). Required without --file; rejected with --file.")
92
+ .option("--form-fields <json>", "Form fields (JSON array, or 'null'). Required without --file; rejected with --file.")
93
+ .option("--description <text>", "Release description (with --file, defaults to the compiled workflow's description).")
94
+ .option("--options <json>", "Options (JSON object)")
95
+ .option("--dry-run", "With --file: compile and print the nodes + form fields without deploying. Ignored without --file.")
96
+ .action(async (opts) => {
97
+ if (opts.file !== undefined) {
98
+ await runFileDeploy(getApi, opts);
99
+ return;
100
+ }
101
+ await runJsonDeploy(getApi, opts);
102
+ });
103
+ }
104
+ // ---------- raw-JSON path (legacy `draft-release deploy`) ----------
105
+ async function runJsonDeploy(getApi, opts) {
106
+ if (opts.workflowUuid === undefined) {
107
+ failWith("--workflow-uuid is required when --file is not set", {
108
+ code: ExitCodes.InvalidUsage,
109
+ });
110
+ }
111
+ if (opts.nodes === undefined) {
112
+ failWith("--nodes is required when --file is not set", {
113
+ code: ExitCodes.InvalidUsage,
114
+ });
115
+ }
116
+ if (opts.formFields === undefined) {
117
+ failWith("--form-fields is required when --file is not set", {
118
+ code: ExitCodes.InvalidUsage,
119
+ });
120
+ }
121
+ // Pull required values into locals so the narrowing survives the closure
122
+ // we hand to `handleApiCall` below.
123
+ const workflowUuid = opts.workflowUuid;
124
+ const nodesJson = opts.nodes;
125
+ const formFieldsJson = opts.formFields;
126
+ const api = getApi();
127
+ const result = await handleApiCall(() => api.orchestration.draftRelease.deploy({
128
+ workflowUuid,
129
+ version: opts.version,
130
+ nodes: parseJson(nodesJson, "--nodes"),
131
+ formFields: formFieldsJson === "null"
132
+ ? null
133
+ : parseJson(formFieldsJson, "--form-fields"),
134
+ description: opts.description,
135
+ options: opts.options !== undefined
136
+ ? parseJson(opts.options, "--options")
137
+ : undefined,
138
+ }));
139
+ outputJson(result);
140
+ }
141
+ // ---------- SDK-file path (replaces `workflow deploy <file>`) ----------
142
+ async function runFileDeploy(getApi, opts) {
143
+ if (opts.file === undefined) {
144
+ failWith("--file is required for SDK deploy", {
145
+ code: ExitCodes.InvalidUsage,
146
+ });
147
+ }
148
+ if (opts.nodes !== undefined) {
149
+ failWith("--nodes is not allowed with --file (sourced from the module)", {
150
+ code: ExitCodes.InvalidUsage,
151
+ });
152
+ }
153
+ if (opts.formFields !== undefined) {
154
+ failWith("--form-fields is not allowed with --file (sourced from the module)", { code: ExitCodes.InvalidUsage });
155
+ }
156
+ const compiled = await loadCompiledWorkflow(opts.file);
157
+ if (opts.dryRun === true) {
158
+ outputJson({
159
+ slug: compiled.slug,
160
+ description: compiled.description,
161
+ nodes: compiled.nodes,
162
+ formFields: compiled.formFields,
163
+ });
164
+ return;
165
+ }
166
+ const api = getApi();
167
+ const workflowUuid = await resolveWorkflowUuid(api, compiled, opts.workflowUuid);
168
+ const version = opts.version !== undefined
169
+ ? opts.version
170
+ : await computeNextVersion(api, workflowUuid);
171
+ const description = opts.description !== undefined ? opts.description : compiled.description;
172
+ info(`Deploying "${compiled.slug}" → workflow ${workflowUuid} as v${version} (${String(compiled.nodes.length)} node(s))…`);
173
+ const payload = {
174
+ workflowUuid,
175
+ version,
176
+ nodes: compiled.nodes,
177
+ formFields: compiled.formFields,
178
+ description,
179
+ options: opts.options !== undefined
180
+ ? parseJson(opts.options, "--options")
181
+ : undefined,
182
+ };
183
+ const result = await handleApiCall(() => api.orchestration.draftRelease.deploy(payload));
184
+ success(`Deployed v${version}.`);
185
+ outputJson(result);
186
+ }
187
+ // Load a workflow module via the tsx ESM loader so `.ts` sources (and their
188
+ // imports of `@cargo-ai/workflow-sdk` / `zod`) are transpiled on the fly,
189
+ // returning its default export validated as a compiled workflow.
190
+ async function loadCompiledWorkflow(file) {
191
+ const absPath = isAbsolute(file) ? file : resolve(process.cwd(), file);
192
+ if (!existsSync(absPath)) {
193
+ failWith(`Workflow file not found: ${absPath}`, {
194
+ code: ExitCodes.InvalidUsage,
195
+ });
196
+ }
197
+ let mod;
198
+ try {
199
+ const { tsImport } = await import("tsx/esm/api");
200
+ mod = (await tsImport(pathToFileURL(absPath).href, import.meta.url));
201
+ }
202
+ catch (error) {
203
+ failWith(`Failed to load workflow module: ${error instanceof Error ? error.message : String(error)}`, { code: ExitCodes.GenericError });
204
+ }
205
+ const candidate = resolveDefaultExport(mod);
206
+ if (!isCompiledWorkflow(candidate)) {
207
+ failWith("Workflow module must `export default` a compiled workflow (the value returned by defineWorkflow()).", { code: ExitCodes.InvalidUsage });
208
+ }
209
+ return candidate;
210
+ }
211
+ // Peel the extra interop layer tsx adds when a TS source is transpiled to
212
+ // CJS: the ESM namespace's `default` is then the CJS `module.exports`
213
+ // (`{ __esModule: true, default: … }`), so the real value lives one level
214
+ // deeper. True-ESM modules expose it directly.
215
+ function resolveDefaultExport(mod) {
216
+ const top = mod.default;
217
+ if (top !== null && typeof top === "object") {
218
+ const inner = top;
219
+ if (inner["__esModule"] === true && "default" in inner) {
220
+ return inner["default"];
221
+ }
222
+ }
223
+ return top;
224
+ }
225
+ function isCompiledWorkflow(value) {
226
+ if (value === null || typeof value !== "object")
227
+ return false;
228
+ const v = value;
229
+ return (typeof v["slug"] === "string" &&
230
+ Array.isArray(v["nodes"]) &&
231
+ Array.isArray(v["formFields"]));
232
+ }
233
+ // Resolve the target workflow UUID. The platform `Workflow` has no free-form
234
+ // slug field — the closest stable identifier is `template.slug` — and there
235
+ // is no `workflow.create` endpoint, so an explicit `--workflow-uuid` always
236
+ // wins and slug matching is a best-effort convenience over existing
237
+ // workflows only.
238
+ async function resolveWorkflowUuid(api, compiled, explicitUuid) {
239
+ if (explicitUuid !== undefined)
240
+ return explicitUuid;
241
+ const { workflows } = await handleApiCall(() => api.orchestration.workflow.all());
242
+ const matches = workflows.filter((w) => {
243
+ const template = w.template;
244
+ return (template !== null &&
245
+ template !== undefined &&
246
+ template.slug === compiled.slug);
247
+ });
248
+ if (matches.length === 1) {
249
+ const match = matches[0];
250
+ if (match !== undefined) {
251
+ info(`Matched workflow ${match.uuid} by slug "${compiled.slug}".`);
252
+ return match.uuid;
253
+ }
254
+ }
255
+ if (matches.length > 1) {
256
+ failWith(`Multiple workflows match slug "${compiled.slug}". Pass --workflow-uuid to disambiguate.`, {
257
+ code: ExitCodes.InvalidUsage,
258
+ extra: { candidates: matches.map((w) => w.uuid) },
259
+ });
260
+ }
261
+ failWith(`No workflow matched slug "${compiled.slug}". Create the workflow in the app first (there is no create endpoint), then deploy with --workflow-uuid <uuid>.`, { code: ExitCodes.NotFound });
262
+ }
263
+ // Derive the next release version by bumping the minor of the highest
264
+ // existing semver, or `1.0.0` when there are no prior releases.
265
+ async function computeNextVersion(api, workflowUuid) {
266
+ const { releases } = await handleApiCall(() => api.orchestration.release.list({ workflowUuid }));
267
+ let best;
268
+ for (const release of releases) {
269
+ const parsed = parseSemver(release.version);
270
+ if (parsed === undefined)
271
+ continue;
272
+ if (best === undefined || compareSemver(parsed, best) > 0) {
273
+ best = parsed;
274
+ }
275
+ }
276
+ if (best === undefined)
277
+ return "1.0.0";
278
+ return `${String(best[0])}.${String(best[1] + 1)}.0`;
279
+ }
280
+ function parseSemver(value) {
281
+ if (value === null || value === undefined)
282
+ return undefined;
283
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(value.trim());
284
+ if (match === null)
285
+ return undefined;
286
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
287
+ }
288
+ function compareSemver(a, b) {
289
+ if (a[0] !== b[0])
290
+ return a[0] - b[0];
291
+ if (a[1] !== b[1])
292
+ return a[1] - b[1];
293
+ return a[2] - b[2];
40
294
  }
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { Api } from "../../api.js";
3
+ export declare function registerWorkflowCommands(parent: Command, getApi: () => Api): void;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAMxC,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAMN"}
@@ -0,0 +1,10 @@
1
+ import { registerSyncCommand } from "./sync.js";
2
+ // SDK deploy lives under `cargo-ai orchestration release deploy-draft --file
3
+ // <path>` (alongside the raw-JSON deploy path), so this group only ships the
4
+ // `sync` developer tool today.
5
+ export function registerWorkflowCommands(parent, getApi) {
6
+ const workflow = parent
7
+ .command("workflow")
8
+ .description("Workflow SDK developer tools (type sync, codegen)");
9
+ registerSyncCommand(workflow, getApi);
10
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Fixed input shape of every agent node. Matches the SDK's `AgentInput`
3
+ * type and the engine's `AiUtils.agentConfig` schema.
4
+ */
5
+ export declare const AGENT_INPUT_TYPE_SRC: string;
6
+ /**
7
+ * Print an integration action's JSON Schema config as a TS input type.
8
+ * Returns `"Record<string, unknown>"` when the schema is missing or not an
9
+ * object schema we can render.
10
+ */
11
+ export declare function printJsonSchemaInput(schema: unknown): string;
12
+ /**
13
+ * Print a tool release's `formFields` as a TS input type. Returns
14
+ * `undefined` when the fields can't be interpreted (caller falls back to
15
+ * `Record<string, unknown>`).
16
+ */
17
+ export declare function printFormFieldsInput(formFields: unknown): string | undefined;
18
+ //# sourceMappingURL=inputTypes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputTypes.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/inputTypes.ts"],"names":[],"mappings":"AAgCA;;;GAGG;AACH,eAAO,MAAM,oBAAoB,QAE+D,CAAC;AAEjG;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAM5D;AAgGD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAU5E"}
@@ -0,0 +1,175 @@
1
+ // TypeScript input-type printers for `cargo-ai workflow sync` codegen.
2
+ //
3
+ // Three sources of truth, three printers:
4
+ // - Integration actions expose their config as JSON Schema on
5
+ // `connection.integration.list` (`action.config.schema`).
6
+ // - Workspace tools expose their input as `formFields` on the tool
7
+ // workflow's deployed release (`orchestration.release.getDeployed`).
8
+ // - Agent nodes accept a single global shape (`{ prompt, output? }`) —
9
+ // the engine validates every agent node against `AiUtils.agentConfig`.
10
+ //
11
+ // All printers run in "input mode": each top-level property is widened to
12
+ // `Ref<T> | T` so callers can pass either a builder Ref or a literal.
13
+ // Anything unrecognized falls back to `unknown` — codegen must never abort
14
+ // on a schema edge case.
15
+ //
16
+ // Mirrors `packages/workflow-sdk/scripts/lib/jsonSchemaToTs.ts` (which is a
17
+ // build-time-only script the CLI can't import).
18
+ /**
19
+ * Fixed input shape of every agent node. Matches the SDK's `AgentInput`
20
+ * type and the engine's `AiUtils.agentConfig` schema.
21
+ */
22
+ export const AGENT_INPUT_TYPE_SRC = `{ prompt: Ref<string> | string; ` +
23
+ `output?: { type: "default" | "text" | "jsonSchema"; jsonSchema?: Record<string, unknown> } }`;
24
+ /**
25
+ * Print an integration action's JSON Schema config as a TS input type.
26
+ * Returns `"Record<string, unknown>"` when the schema is missing or not an
27
+ * object schema we can render.
28
+ */
29
+ export function printJsonSchemaInput(schema) {
30
+ if (schema === null || typeof schema !== "object") {
31
+ return "Record<string, unknown>";
32
+ }
33
+ const printed = printSchema(schema, true);
34
+ return printed === "unknown" ? "Record<string, unknown>" : printed;
35
+ }
36
+ function printSchema(schema, topLevel) {
37
+ if (Array.isArray(schema.type)) {
38
+ return uniqueUnion(schema.type.map((t) => printPrimitive(t, schema, false)));
39
+ }
40
+ if (schema.enum !== undefined && schema.enum.length > 0) {
41
+ return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
42
+ }
43
+ if (schema.const !== undefined) {
44
+ return JSON.stringify(schema.const);
45
+ }
46
+ if (schema.oneOf !== undefined && schema.oneOf.length > 0) {
47
+ return uniqueUnion(schema.oneOf.map((s) => printSchema(s, false)));
48
+ }
49
+ if (schema.anyOf !== undefined && schema.anyOf.length > 0) {
50
+ return uniqueUnion(schema.anyOf.map((s) => printSchema(s, false)));
51
+ }
52
+ if (schema.allOf !== undefined && schema.allOf.length > 0) {
53
+ // Conservative: render the head rather than a TS intersection.
54
+ return printSchema(schema.allOf[0], topLevel);
55
+ }
56
+ if (typeof schema.type !== "string")
57
+ return "unknown";
58
+ return printPrimitive(schema.type, schema, topLevel);
59
+ }
60
+ function printPrimitive(type, schema, topLevel) {
61
+ switch (type) {
62
+ case "string":
63
+ return "string";
64
+ case "number":
65
+ case "integer":
66
+ return "number";
67
+ case "boolean":
68
+ return "boolean";
69
+ case "null":
70
+ return "null";
71
+ case "array":
72
+ return printArray(schema);
73
+ case "object":
74
+ return printObject(schema, topLevel);
75
+ default:
76
+ return "unknown";
77
+ }
78
+ }
79
+ function printArray(schema) {
80
+ if (schema.items === undefined)
81
+ return "unknown[]";
82
+ if (Array.isArray(schema.items)) {
83
+ return `[${schema.items.map((s) => printSchema(s, false)).join(", ")}]`;
84
+ }
85
+ return `Array<${printSchema(schema.items, false)}>`;
86
+ }
87
+ function printObject(schema, topLevel) {
88
+ const props = schema.properties;
89
+ if (props === undefined) {
90
+ if (typeof schema.additionalProperties === "object" &&
91
+ schema.additionalProperties !== null) {
92
+ return `Record<string, ${printSchema(schema.additionalProperties, false)}>`;
93
+ }
94
+ return "Record<string, unknown>";
95
+ }
96
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
97
+ const fields = [];
98
+ for (const [key, child] of Object.entries(props)) {
99
+ const inner = printSchema(child, false);
100
+ // Only top-level properties are Ref-widened; nested values are plain.
101
+ const value = topLevel ? `Ref<${inner}> | ${inner}` : inner;
102
+ const opt = required.has(key) ? "" : "?";
103
+ fields.push(`${tsKey(key)}${opt}: ${value}`);
104
+ }
105
+ if (fields.length === 0)
106
+ return "Record<string, never>";
107
+ return `{ ${fields.join("; ")} }`;
108
+ }
109
+ /**
110
+ * Print a tool release's `formFields` as a TS input type. Returns
111
+ * `undefined` when the fields can't be interpreted (caller falls back to
112
+ * `Record<string, unknown>`).
113
+ */
114
+ export function printFormFieldsInput(formFields) {
115
+ if (!Array.isArray(formFields))
116
+ return undefined;
117
+ const fields = formFields.filter(isFormFieldLike);
118
+ if (fields.length === 0)
119
+ return "Record<string, never>";
120
+ const rendered = fields.map((f) => {
121
+ const inner = formFieldToTs(f);
122
+ const opt = f.isRequired === true ? "" : "?";
123
+ return `${tsKey(f.slug)}${opt}: Ref<${inner}> | ${inner}`;
124
+ });
125
+ return `{ ${rendered.join("; ")} }`;
126
+ }
127
+ function isFormFieldLike(value) {
128
+ if (value === null || typeof value !== "object")
129
+ return false;
130
+ const v = value;
131
+ return typeof v["slug"] === "string" && typeof v["kind"] === "string";
132
+ }
133
+ function formFieldToTs(field) {
134
+ switch (field.kind) {
135
+ case "string":
136
+ case "date":
137
+ return "string";
138
+ case "number":
139
+ return "number";
140
+ case "boolean":
141
+ return "boolean";
142
+ case "enum": {
143
+ const values = Array.isArray(field.enum)
144
+ ? field.enum.filter((v) => typeof v === "string")
145
+ : [];
146
+ if (values.length === 0)
147
+ return "string";
148
+ return values.map((v) => JSON.stringify(v)).join(" | ");
149
+ }
150
+ case "array": {
151
+ const nested = Array.isArray(field.fields)
152
+ ? field.fields.filter(isFormFieldLike)
153
+ : [];
154
+ if (nested.length === 0)
155
+ return "unknown[]";
156
+ const inner = nested
157
+ .map((f) => {
158
+ const opt = f.isRequired === true ? "" : "?";
159
+ return `${tsKey(f.slug)}${opt}: ${formFieldToTs(f)}`;
160
+ })
161
+ .join("; ");
162
+ return `Array<{ ${inner} }>`;
163
+ }
164
+ case "any":
165
+ default:
166
+ return "unknown";
167
+ }
168
+ }
169
+ function uniqueUnion(parts) {
170
+ const dedup = Array.from(new Set(parts));
171
+ return dedup.length === 1 ? dedup[0] : dedup.join(" | ");
172
+ }
173
+ function tsKey(key) {
174
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
175
+ }
@@ -0,0 +1,4 @@
1
+ import type { Command } from "commander";
2
+ import type { Api } from "../../api.js";
3
+ export declare function registerSyncCommand(parent: Command, getApi: () => Api): void;
4
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/sync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAgGxC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAsD5E"}
@@ -0,0 +1,454 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { handleApiCall, info, success } from "../runHandler.js";
4
+ import { AGENT_INPUT_TYPE_SRC, printFormFieldsInput, printJsonSchemaInput, } from "./inputTypes.js";
5
+ // Composite natives surfaced via dedicated SDK syntax / scope helpers —
6
+ // excluded so we don't double-register them. Mirrors the codegen exclusion
7
+ // list in `packages/workflow-sdk/scripts/generateNativeIntegration.ts`
8
+ // (everything in the `logic` category gets dedicated syntax instead of
9
+ // going through `native.<slug>`).
10
+ const COMPOSITE_NATIVE = new Set([
11
+ "start",
12
+ "end",
13
+ "agent",
14
+ "balance",
15
+ "branch",
16
+ "delay",
17
+ "filter",
18
+ "group",
19
+ "humanReview",
20
+ "memory",
21
+ "split",
22
+ "switch",
23
+ "tool",
24
+ "variables",
25
+ ]);
26
+ // Native actions the SDK already ships typed declarations + registrations
27
+ // for (platform-level, identical in every workspace) — skipped so the
28
+ // synced `.d.ts` doesn't redeclare `Native` properties with conflicting
29
+ // (looser) types. Source of truth lives in
30
+ // `packages/workflow-sdk/src/native/manifest.generated.ts`.
31
+ const BUNDLED_NATIVE = new Set([
32
+ "note",
33
+ "python",
34
+ "scoring",
35
+ "script",
36
+ ]);
37
+ export function registerSyncCommand(parent, getApi) {
38
+ parent
39
+ .command("sync")
40
+ .description("Generate per-workspace TypeScript types + eager registration for the Cargo Workflow SDK")
41
+ .option("--out <dir>", "Output directory for generated files (default: .cargo-ai)", ".cargo-ai")
42
+ .option("--cwd <dir>", "Working directory the --out path is resolved against (default: process.cwd())")
43
+ .action(async (opts) => {
44
+ const api = getApi();
45
+ const baseDir = opts.cwd !== undefined ? opts.cwd : process.cwd();
46
+ const outDir = resolve(baseDir, opts.out);
47
+ info(`Fetching workspace surface…`);
48
+ const payload = await fetchWorkspaceSurface(api);
49
+ mkdirSync(outDir, { recursive: true });
50
+ const typesPath = resolve(outDir, "cargo-types.d.ts");
51
+ writeFileSync(typesPath, renderTypes(payload));
52
+ success(`Wrote ${relativeToBase(baseDir, typesPath)}`);
53
+ const eagerPath = resolve(outDir, "cargo-register.ts");
54
+ writeFileSync(eagerPath, renderEagerRegistration(payload));
55
+ success(`Wrote ${relativeToBase(baseDir, eagerPath)}`);
56
+ info(``);
57
+ info([
58
+ `Next steps:`,
59
+ ` 1. Add ${relativeToBase(baseDir, outDir)} to your tsconfig include`,
60
+ ` so the .d.ts is picked up.`,
61
+ ` 2. Add \`import "./${relativeToBase(baseDir, eagerPath).replace(/\.ts$/, ".js")}";\``,
62
+ ` to your project's entry file (or before any defineWorkflow call)`,
63
+ ` so the workspace's integrations / tools / agents register at runtime.`,
64
+ ].join("\n"));
65
+ info(``);
66
+ info(`Synced ${String(payload.integrations.length)} integration(s), ${String(payload.tools.length)} tool(s), ${String(payload.agents.length)} agent(s), ${String(payload.native.length)} native action(s).`);
67
+ });
68
+ }
69
+ async function fetchWorkspaceSurface(api) {
70
+ const integrationsResult = await handleApiCall(() => api.connection.integration.list({}));
71
+ const nativeIntegrationResult = await handleApiCall(() => api.connection.nativeIntegration.get());
72
+ const orchestrationToolsResult = await handleApiCall(() => api.orchestration.tool.all());
73
+ const aiAgentsResult = await handleApiCall(() => api.ai.agent.all());
74
+ const integrations = collectIntegrations(integrationsResult.integrations);
75
+ const tools = collectTools(orchestrationToolsResult.tools);
76
+ await resolveToolInputTypes(api, tools);
77
+ const agents = collectAgents(aiAgentsResult);
78
+ const native = collectNative(nativeIntegrationResult.nativeIntegration.actions);
79
+ return { integrations, tools, agents, native };
80
+ }
81
+ function collectIntegrations(integrations) {
82
+ const out = [];
83
+ for (const integration of integrations) {
84
+ const actionEntries = Object.entries(integration.actions ?? {}).sort(([a], [b]) => a.localeCompare(b));
85
+ if (actionEntries.length === 0)
86
+ continue;
87
+ const actions = actionEntries.map(([slug, raw]) => {
88
+ const meta = raw;
89
+ return {
90
+ slug,
91
+ name: trimOrUndefined(meta.name),
92
+ description: trimOrUndefined(meta.description),
93
+ inputTypeSrc: printJsonSchemaInput(meta.config?.schema),
94
+ };
95
+ });
96
+ out.push({
97
+ slug: integration.slug,
98
+ name: trimOrUndefined(integration.name),
99
+ description: trimOrUndefined(integration.description),
100
+ actions,
101
+ });
102
+ }
103
+ out.sort((a, b) => a.slug.localeCompare(b.slug));
104
+ return out;
105
+ }
106
+ function collectTools(workspaceTools) {
107
+ const out = [];
108
+ const used = new Set();
109
+ for (const t of workspaceTools) {
110
+ out.push({
111
+ slug: pickSlug(t.name, t.uuid, used),
112
+ name: t.name,
113
+ description: trimOrUndefined(t.description),
114
+ uuid: t.uuid,
115
+ workflowUuid: t.workflowUuid,
116
+ updatedAt: dateToIso(t.updatedAt),
117
+ inputTypeSrc: "Record<string, unknown>",
118
+ });
119
+ }
120
+ out.sort((a, b) => a.slug.localeCompare(b.slug));
121
+ return out;
122
+ }
123
+ // The tools list endpoint doesn't carry input schemas — they live on each
124
+ // tool workflow's deployed release as `formFields`. Resolve them with one
125
+ // `release.getDeployed` call per tool (bounded concurrency). A tool without
126
+ // a deployed release (or whose release fetch fails) keeps the
127
+ // `Record<string, unknown>` fallback; sync must not abort over one tool.
128
+ const TOOL_RELEASE_CONCURRENCY = 5;
129
+ async function resolveToolInputTypes(api, tools) {
130
+ const queue = [...tools];
131
+ const workers = Array.from({ length: Math.min(TOOL_RELEASE_CONCURRENCY, queue.length) }, async () => {
132
+ for (;;) {
133
+ const tool = queue.shift();
134
+ if (tool === undefined)
135
+ return;
136
+ try {
137
+ const result = await api.orchestration.release.getDeployed({
138
+ workflowUuid: tool.workflowUuid,
139
+ });
140
+ const formFields = result.release?.formFields;
141
+ const printed = printFormFieldsInput(formFields);
142
+ if (printed !== undefined) {
143
+ tool.inputTypeSrc = printed;
144
+ }
145
+ }
146
+ catch {
147
+ // Not deployed / not accessible — keep the loose fallback type.
148
+ }
149
+ }
150
+ });
151
+ await Promise.all(workers);
152
+ }
153
+ function collectAgents(workspaceAgents) {
154
+ const out = [];
155
+ const used = new Set();
156
+ const allAgents = isAgentsResult(workspaceAgents)
157
+ ? workspaceAgents.agents
158
+ : [];
159
+ for (const a of allAgents) {
160
+ out.push({
161
+ slug: pickSlug(a.name, a.uuid, used),
162
+ name: a.name,
163
+ description: trimOrUndefined(a.description),
164
+ uuid: a.uuid,
165
+ updatedAt: dateToIso(a.updatedAt),
166
+ });
167
+ }
168
+ out.sort((a, b) => a.slug.localeCompare(b.slug));
169
+ return out;
170
+ }
171
+ function isAgentsResult(value) {
172
+ if (value === null || typeof value !== "object")
173
+ return false;
174
+ const v = value;
175
+ return Array.isArray(v.agents);
176
+ }
177
+ function collectNative(actions) {
178
+ const out = [];
179
+ for (const [slug, meta] of Object.entries(actions)) {
180
+ if (COMPOSITE_NATIVE.has(slug))
181
+ continue;
182
+ if (BUNDLED_NATIVE.has(slug))
183
+ continue;
184
+ const name = meta.name.length > 0 ? meta.name : slug;
185
+ out.push({
186
+ slug,
187
+ name,
188
+ description: trimOrUndefined(meta.description),
189
+ });
190
+ }
191
+ out.sort((a, b) => a.slug.localeCompare(b.slug));
192
+ return out;
193
+ }
194
+ function trimOrUndefined(value) {
195
+ if (typeof value !== "string")
196
+ return undefined;
197
+ const trimmed = value.trim();
198
+ return trimmed.length > 0 ? trimmed : undefined;
199
+ }
200
+ function dateToIso(value) {
201
+ if (value === null || value === undefined)
202
+ return undefined;
203
+ if (value instanceof Date) {
204
+ return Number.isNaN(value.getTime()) ? undefined : value.toISOString();
205
+ }
206
+ if (typeof value === "string") {
207
+ const date = new Date(value);
208
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
209
+ }
210
+ return undefined;
211
+ }
212
+ // Slugify `name` to a JS identifier; append a UUID-based suffix on collision
213
+ // (or when the name slugifies to nothing). `used` is mutated.
214
+ function pickSlug(name, uuid, used) {
215
+ const base = slugify(name);
216
+ if (base.length > 0 && !used.has(base)) {
217
+ used.add(base);
218
+ return base;
219
+ }
220
+ const suffix = uuid.replace(/-/g, "_");
221
+ const fallback = base.length > 0 ? `${base}_${suffix}` : `_${suffix}`;
222
+ used.add(fallback);
223
+ return fallback;
224
+ }
225
+ function slugify(name) {
226
+ const normalized = name
227
+ .normalize("NFKD")
228
+ .replace(/[^\w]+/g, "_")
229
+ .replace(/^_+|_+$/g, "")
230
+ .toLowerCase();
231
+ if (normalized.length === 0)
232
+ return "";
233
+ // JS identifiers can't start with a digit.
234
+ return /^\d/.test(normalized) ? `_${normalized}` : normalized;
235
+ }
236
+ const GENERATED_AT = new Date().toISOString();
237
+ const HEADER = `// THIS FILE IS GENERATED by \`cargo-ai workflow sync\`. Do not edit by hand.
238
+ // Re-run the command to refresh after adding/removing workspace integrations,
239
+ // tools, or agents.
240
+ // Last regenerated: ${GENERATED_AT}
241
+
242
+ `;
243
+ function renderTypes(payload) {
244
+ const lines = [];
245
+ lines.push(`declare module "@cargo-ai/workflow-sdk" {`);
246
+ if (payload.integrations.length > 0) {
247
+ lines.push(` interface Integrations {`);
248
+ for (const c of payload.integrations) {
249
+ const intDoc = formatJsDoc({
250
+ name: c.name ?? c.slug,
251
+ description: c.description,
252
+ updatedAt: GENERATED_AT,
253
+ }, " ");
254
+ if (intDoc !== "")
255
+ lines.push(intDoc.trimEnd());
256
+ lines.push(` ${jsonKey(c.slug)}: {`);
257
+ for (const action of c.actions) {
258
+ const actionDoc = formatJsDoc({
259
+ name: action.name,
260
+ description: action.description,
261
+ category: c.slug,
262
+ updatedAt: GENERATED_AT,
263
+ }, " ");
264
+ if (actionDoc !== "")
265
+ lines.push(actionDoc.trimEnd());
266
+ lines.push(` ${jsonKey(action.slug)}: IntegrationAction<${action.inputTypeSrc}, Record<string, unknown>>;`);
267
+ }
268
+ lines.push(` };`);
269
+ }
270
+ lines.push(` }`);
271
+ }
272
+ if (payload.tools.length > 0) {
273
+ lines.push(` interface Tools {`);
274
+ for (const t of payload.tools) {
275
+ const doc = formatJsDoc({
276
+ name: t.name,
277
+ description: t.description,
278
+ updatedAt: t.updatedAt ?? GENERATED_AT,
279
+ }, " ");
280
+ if (doc !== "")
281
+ lines.push(doc.trimEnd());
282
+ lines.push(` ${jsonKey(t.slug)}: ToolFn<${t.inputTypeSrc}, Record<string, unknown>>;`);
283
+ }
284
+ lines.push(` }`);
285
+ }
286
+ if (payload.agents.length > 0) {
287
+ lines.push(` interface Agents {`);
288
+ for (const a of payload.agents) {
289
+ const doc = formatJsDoc({
290
+ name: a.name,
291
+ description: a.description,
292
+ updatedAt: a.updatedAt ?? GENERATED_AT,
293
+ }, " ");
294
+ if (doc !== "")
295
+ lines.push(doc.trimEnd());
296
+ lines.push(` ${jsonKey(a.slug)}: AgentFn<${AGENT_INPUT_TYPE_SRC}, AgentDefaultOutput>;`);
297
+ }
298
+ lines.push(` }`);
299
+ }
300
+ if (payload.native.length > 0) {
301
+ lines.push(` interface Native {`);
302
+ for (const n of payload.native) {
303
+ const doc = formatJsDoc({
304
+ name: n.name,
305
+ description: n.description,
306
+ updatedAt: GENERATED_AT,
307
+ }, " ");
308
+ if (doc !== "")
309
+ lines.push(doc.trimEnd());
310
+ lines.push(` ${jsonKey(n.slug)}: NativeAction<Record<string, unknown>, Record<string, unknown>>;`);
311
+ }
312
+ lines.push(` }`);
313
+ }
314
+ lines.push(`}`);
315
+ lines.push(``);
316
+ const body = lines.join("\n");
317
+ // `Ref` only appears when at least one entry has a typed input, so import
318
+ // it conditionally to keep the generated .d.ts free of unused imports.
319
+ const imports = [
320
+ "AgentDefaultOutput",
321
+ "AgentFn",
322
+ "IntegrationAction",
323
+ "NativeAction",
324
+ ...(body.includes("Ref<") ? ["Ref"] : []),
325
+ "ToolFn",
326
+ ];
327
+ const header = [
328
+ HEADER.trim(),
329
+ `import type {`,
330
+ ...imports.map((name) => ` ${name},`),
331
+ `} from "@cargo-ai/workflow-sdk";`,
332
+ ``,
333
+ ].join("\n");
334
+ return `${header}\n${body}`;
335
+ }
336
+ function renderEagerRegistration(payload) {
337
+ const lines = [];
338
+ lines.push(HEADER.trim());
339
+ const imports = [];
340
+ if (payload.integrations.length > 0)
341
+ imports.push("registerIntegration");
342
+ if (payload.tools.length > 0)
343
+ imports.push("registerTool");
344
+ if (payload.agents.length > 0)
345
+ imports.push("registerAgent");
346
+ if (payload.native.length > 0)
347
+ imports.push("registerNative");
348
+ if (imports.length === 0) {
349
+ lines.push(``);
350
+ lines.push(`// Workspace surface is empty (no custom integrations / tools / agents / natives).`);
351
+ lines.push(``);
352
+ return lines.join("\n");
353
+ }
354
+ lines.push(`import { ${imports.join(", ")} } from "@cargo-ai/workflow-sdk";`);
355
+ lines.push(``);
356
+ for (const c of payload.integrations) {
357
+ lines.push(`registerIntegration(${JSON.stringify(c.slug)}, {`);
358
+ for (const action of c.actions) {
359
+ const displayName = action.name !== undefined && action.name.length > 0
360
+ ? action.name
361
+ : `${capitalize(c.slug)} ${humanize(action.slug)}`;
362
+ lines.push(` ${jsonKey(action.slug)}: { name: ${JSON.stringify(displayName)} },`);
363
+ }
364
+ lines.push(`});`);
365
+ }
366
+ for (const t of payload.tools) {
367
+ lines.push(`registerTool(${JSON.stringify(t.slug)}, { toolUuid: ${JSON.stringify(t.uuid)} }, ${JSON.stringify(t.name)});`);
368
+ }
369
+ for (const a of payload.agents) {
370
+ lines.push(`registerAgent(${JSON.stringify(a.slug)}, { agentUuid: ${JSON.stringify(a.uuid)} }, ${JSON.stringify(a.name)});`);
371
+ }
372
+ for (const n of payload.native) {
373
+ lines.push(`registerNative(${JSON.stringify(n.slug)}, ${JSON.stringify(n.name)});`);
374
+ }
375
+ lines.push(``);
376
+ return lines.join("\n");
377
+ }
378
+ function jsonKey(key) {
379
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
380
+ }
381
+ // Render a JSDoc block above a generated declaration so editors surface the
382
+ // entry's metadata on hover. Returns "" when there's nothing to document.
383
+ function formatJsDoc(doc, indent) {
384
+ const body = [];
385
+ if (doc.name !== undefined && doc.name.trim().length > 0) {
386
+ body.push(doc.name.trim());
387
+ }
388
+ if (doc.description !== undefined && doc.description.trim().length > 0) {
389
+ if (body.length > 0)
390
+ body.push("");
391
+ for (const line of doc.description.split("\n")) {
392
+ body.push(line.trimEnd());
393
+ }
394
+ }
395
+ const tags = [];
396
+ if (doc.category !== undefined) {
397
+ const cats = Array.isArray(doc.category)
398
+ ? doc.category.join(", ")
399
+ : String(doc.category);
400
+ if (cats.trim().length > 0) {
401
+ tags.push(`@category ${cats}`);
402
+ }
403
+ }
404
+ if (doc.author !== undefined && doc.author.trim().length > 0) {
405
+ tags.push(`@author ${doc.author.trim()}`);
406
+ }
407
+ if (doc.version !== undefined && String(doc.version).length > 0) {
408
+ tags.push(`@version ${String(doc.version)}`);
409
+ }
410
+ if (doc.updatedAt !== undefined && doc.updatedAt !== null) {
411
+ const date = doc.updatedAt instanceof Date ? doc.updatedAt : new Date(doc.updatedAt);
412
+ if (!Number.isNaN(date.getTime())) {
413
+ tags.push(`@updated ${date.toISOString().slice(0, 10)}`);
414
+ }
415
+ }
416
+ if (doc.see !== undefined && doc.see.trim().length > 0) {
417
+ tags.push(`@see ${doc.see.trim()}`);
418
+ }
419
+ if (body.length > 0 && tags.length > 0) {
420
+ body.push("");
421
+ }
422
+ body.push(...tags);
423
+ if (body.length === 0)
424
+ return "";
425
+ const out = [`${indent}/**`];
426
+ for (const line of body) {
427
+ const safe = line.replace(/\*\//g, "*\\/");
428
+ out.push(safe.length > 0 ? `${indent} * ${safe}` : `${indent} *`);
429
+ }
430
+ out.push(`${indent} */`);
431
+ return `${out.join("\n")}\n`;
432
+ }
433
+ function capitalize(s) {
434
+ if (s.length === 0)
435
+ return s;
436
+ const first = s[0];
437
+ if (first === undefined)
438
+ return s;
439
+ return first.toUpperCase() + s.slice(1);
440
+ }
441
+ function humanize(slug) {
442
+ return slug
443
+ .replace(/([A-Z])/g, " $1")
444
+ .replace(/[_-]+/g, " ")
445
+ .trim()
446
+ .toLowerCase();
447
+ }
448
+ function relativeToBase(base, path) {
449
+ if (path.startsWith(base)) {
450
+ const rel = path.slice(base.length);
451
+ return rel.startsWith("/") ? rel.slice(1) : rel;
452
+ }
453
+ return path;
454
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cargo-ai/cli",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "private": false,
5
5
  "description": "Command-line interface for the Cargo API",
6
6
  "engines": {
@@ -31,7 +31,8 @@
31
31
  "@cargo-ai/api": "^1.0.27",
32
32
  "@cargo-ai/app-sdk": "^1.0.0",
33
33
  "@cargo-ai/worker-sdk": "^1.0.2",
34
- "commander": "^12.1.0"
34
+ "commander": "^12.1.0",
35
+ "tsx": "^4.19.2"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@cargo-ai/eslint-config": "*",
@@ -40,7 +41,6 @@
40
41
  "@types/node": "^20.10.8",
41
42
  "eslint": "9.26.0",
42
43
  "prettier": "3.3.3",
43
- "tsx": "^4.19.2",
44
44
  "typescript": "5.3.2"
45
45
  }
46
46
  }
@@ -1,4 +0,0 @@
1
- import type { Command } from "commander";
2
- import type { Api } from "../../api.js";
3
- export declare function registerDraftReleaseCommands(parent: Command, getApi: () => Api): void;
4
- //# sourceMappingURL=draftRelease.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"draftRelease.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/draftRelease.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA8GN"}
@@ -1,73 +0,0 @@
1
- import { handleApiCall, outputJson, parseJson } from "../runHandler.js";
2
- export function registerDraftReleaseCommands(parent, getApi) {
3
- const draftRelease = parent
4
- .command("draft-release")
5
- .description("Draft release operations");
6
- draftRelease
7
- .command("get")
8
- .description("Get draft release for a workflow")
9
- .requiredOption("--workflow-uuid <uuid>", "Workflow UUID")
10
- .action(async (opts) => {
11
- const api = getApi();
12
- const result = await handleApiCall(() => api.orchestration.draftRelease.get({
13
- workflowUuid: opts.workflowUuid,
14
- }));
15
- outputJson(result);
16
- });
17
- draftRelease
18
- .command("deploy")
19
- .description("Deploy draft release")
20
- .requiredOption("--workflow-uuid <uuid>", "Workflow UUID")
21
- .option("--version <version>", "Release version (e.g. '1.0.0', '1.1.0', '1.0.1', etc.)")
22
- .requiredOption("--nodes <json>", "Nodes (JSON array of node definitions)")
23
- .requiredOption("--form-fields <json>", "Form fields (JSON array, or 'null')")
24
- .option("--description <text>", "Release description")
25
- .option("--options <json>", "Options (JSON object)")
26
- .action(async (opts) => {
27
- const api = getApi();
28
- const result = await handleApiCall(() => api.orchestration.draftRelease.deploy({
29
- workflowUuid: opts.workflowUuid,
30
- version: opts.version,
31
- nodes: parseJson(opts.nodes, "--nodes"),
32
- formFields: opts.formFields === "null"
33
- ? null
34
- : parseJson(opts.formFields, "--form-fields"),
35
- description: opts.description,
36
- options: opts.options !== undefined
37
- ? parseJson(opts.options, "--options")
38
- : undefined,
39
- }));
40
- outputJson(result);
41
- });
42
- draftRelease
43
- .command("update")
44
- .description("Update draft release")
45
- .requiredOption("--workflow-uuid <uuid>", "Workflow UUID")
46
- .option("--parent-uuid <uuid>", "Parent release UUID")
47
- .option("--nodes <json>", "Nodes (JSON array of node definitions)")
48
- .option("--test-records <json>", "Test records (JSON array)")
49
- .option("--form-fields <json>", "Form fields (JSON array, or 'null')")
50
- .option("--options <json>", "Options (JSON object)")
51
- .action(async (opts) => {
52
- const api = getApi();
53
- const result = await handleApiCall(() => api.orchestration.draftRelease.update({
54
- workflowUuid: opts.workflowUuid,
55
- parentUuid: opts.parentUuid,
56
- nodes: opts.nodes !== undefined
57
- ? parseJson(opts.nodes, "--nodes")
58
- : undefined,
59
- testRecords: opts.testRecords !== undefined
60
- ? parseJson(opts.testRecords, "--test-records")
61
- : undefined,
62
- formFields: opts.formFields === "null"
63
- ? null
64
- : opts.formFields !== undefined
65
- ? parseJson(opts.formFields, "--form-fields")
66
- : undefined,
67
- options: opts.options !== undefined
68
- ? parseJson(opts.options, "--options")
69
- : undefined,
70
- }));
71
- outputJson(result);
72
- });
73
- }