@cargo-ai/cli 1.0.29 → 1.0.31

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
@@ -136,11 +136,10 @@ cargo-ai orchestration release deploy-draft --file ./my-workflow.ts --workflow-u
136
136
  cargo-ai orchestration release deploy-draft --workflow-uuid <uuid> --nodes '[…]' --form-fields '[…]'
137
137
  ```
138
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`:
139
+ `--file <path>` loads a `@cargo-ai/workflow-sdk` module (its `export default defineWorkflow(...)`) and sources `nodes` / `formFields` from the compiled output. With `--file`:
140
140
 
141
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
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
143
  - `--nodes` / `--form-fields` are rejected (they're sourced from the module).
145
144
 
146
145
  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.
@@ -1 +1 @@
1
- {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/commands/ai/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAQxC,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAgMN"}
1
+ {"version":3,"file":"message.d.ts","sourceRoot":"","sources":["../../../src/commands/ai/message.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAiCxC,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA2RN"}
@@ -1,13 +1,20 @@
1
- import { handleApiCall, outputJson, parseJson, pollMessageUntilFinished, } from "../runHandler.js";
1
+ import { loadCompiledAgent, resolveCompiledAgentFromState, } from "../../utils/loadCompiledAgent.js";
2
+ import { ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, pollMessageUntilFinished, } from "../runHandler.js";
2
3
  export function registerMessageCommands(parent, getApi) {
3
4
  const message = parent
4
5
  .command("message")
5
6
  .description("Manage chat messages (create, get, list, cancel, remove)");
6
7
  message
7
8
  .command("create")
8
- .description("Create a message in a chat. Returns the user message and a pending assistant message.")
9
- .requiredOption("--chat-uuid <uuid>", "Chat UUID (string, required)")
9
+ .description("Create a message in a chat. Returns the user message and a pending " +
10
+ "assistant message. Pass --file to test-message a defineAgent " +
11
+ "module inline (its @cargo-ai/cdk handles resolved from " +
12
+ "cargo.state.json — no deploy).")
13
+ .option("--chat-uuid <uuid>", "Chat UUID (string). When omitted, a new draft chat is created automatically.")
14
+ .option("--agent-uuid <uuid>", "Agent UUID for the auto-created chat (string, only used when --chat-uuid is omitted)")
15
+ .option("--release-uuid <uuid>", "Release UUID for the auto-created chat (string, only used when --chat-uuid is omitted)")
10
16
  .requiredOption("--parts <json>", "Message parts (JSON array of part objects, required)")
17
+ .option("--file <path>", "Path to a @cargo-ai/cdk module whose default export is a defineAgent() handle. Compiles it, resolves its resource handles to real uuids from cargo.state.json, and sends the agent's prompt/tools/models/capabilities inline — no deploy. Rejected with the inline flags (and --agent-uuid / --release-uuid).")
11
18
  .option("--actions <json>", "Actions available to the assistant (JSON array of action objects)")
12
19
  .option("--resources <json>", "Resources available to the assistant (JSON array of resource objects)")
13
20
  .option("--capabilities <json>", "Capabilities to enable (JSON array of capability objects)")
@@ -25,6 +32,9 @@ export function registerMessageCommands(parent, getApi) {
25
32
  .addHelpText("after", `
26
33
  Examples:
27
34
  $ cargo-ai ai message create --chat-uuid 550e8400-... --parts '[{"type":"text","text":"Hello"}]'
35
+ $ cargo-ai ai message create --parts '[{"type":"text","text":"Hello"}]'
36
+ $ cargo-ai ai message create --agent-uuid 550e8400-... --parts '[{"type":"text","text":"Hello"}]'
37
+ $ cargo-ai ai message create --file ./agents/researcher.ts --parts '[{"type":"text","text":"Research acme.com"}]' --wait-until-finished
28
38
  $ cargo-ai ai message create --chat-uuid 550e8400-... --parts '[{"type":"text","text":"Find leads"}]' \\
29
39
  --actions '[{"slug":"search","kind":"tool","config":{}}]' --wait-until-finished
30
40
 
@@ -34,35 +44,91 @@ Resource object shape: { "slug": string, "kind": "model", "modelUuid": string }
34
44
  MCP client shape: { "name": string, "url": string }`)
35
45
  .action(async (opts) => {
36
46
  const api = getApi();
47
+ // --file test-messages a defineAgent module: it supplies the whole
48
+ // LLM / tools / models / capabilities inline (no deploy), so the flags
49
+ // that would otherwise set those — plus the deployed-agent bindings —
50
+ // are rejected in its favour.
51
+ let params;
52
+ if (opts.file !== undefined) {
53
+ const conflicts = [
54
+ ["--agent-uuid", opts.agentUuid],
55
+ ["--release-uuid", opts.releaseUuid],
56
+ ["--actions", opts.actions],
57
+ ["--resources", opts.resources],
58
+ ["--capabilities", opts.capabilities],
59
+ ["--mcp-clients", opts.mcpClients],
60
+ ["--system-prompt", opts.systemPrompt],
61
+ ["--with-reasoning", opts.withReasoning],
62
+ ["--temperature", opts.temperature],
63
+ ["--max-steps", opts.maxSteps],
64
+ ["--integration-slug", opts.integrationSlug],
65
+ ["--connector-uuid", opts.connectorUuid],
66
+ ["--language-model-slug", opts.languageModelSlug],
67
+ ["--output", opts.output],
68
+ ]
69
+ .filter(([, value]) => value !== undefined)
70
+ .map(([flag]) => flag);
71
+ if (conflicts.length > 0) {
72
+ failWith(`${conflicts.join(", ")} not allowed with --file (sourced from the agent).`, { code: ExitCodes.InvalidUsage });
73
+ }
74
+ const compiled = await loadCompiledAgent(opts.file);
75
+ const resolved = resolveCompiledAgentFromState(compiled, opts.file);
76
+ params = {
77
+ actions: resolved.actions,
78
+ resources: resolved.resources,
79
+ capabilities: resolved.capabilities,
80
+ mcpClients: resolved.mcpClients,
81
+ systemPrompt: resolved.systemPrompt,
82
+ withReasoning: resolved.withReasoning,
83
+ temperature: resolved.temperature,
84
+ maxSteps: resolved.maxSteps,
85
+ connectorUuid: resolved.connectorUuid,
86
+ languageModelSlug: resolved.languageModelSlug,
87
+ output: resolved.output,
88
+ };
89
+ if (resolved.connectorUuid !== undefined) {
90
+ const uuid = resolved.connectorUuid;
91
+ const connectorResult = await handleApiCall(() => api.connection.connector.get({ uuid }));
92
+ params.integrationSlug = connectorResult.connector.integrationSlug;
93
+ }
94
+ info(`Test-messaging agent "${compiled.slug}" — no deploy.`);
95
+ }
96
+ else {
97
+ params = {
98
+ actions: opts.actions !== undefined
99
+ ? parseJson(opts.actions, "--actions")
100
+ : undefined,
101
+ resources: opts.resources !== undefined
102
+ ? parseJson(opts.resources, "--resources")
103
+ : undefined,
104
+ capabilities: opts.capabilities !== undefined
105
+ ? parseJson(opts.capabilities, "--capabilities")
106
+ : undefined,
107
+ mcpClients: opts.mcpClients !== undefined
108
+ ? parseJson(opts.mcpClients, "--mcp-clients")
109
+ : undefined,
110
+ systemPrompt: opts.systemPrompt,
111
+ withReasoning: opts.withReasoning,
112
+ temperature: opts.temperature !== undefined
113
+ ? parseFloat(opts.temperature)
114
+ : undefined,
115
+ maxSteps: opts.maxSteps !== undefined
116
+ ? parseInt(opts.maxSteps, 10)
117
+ : undefined,
118
+ integrationSlug: opts.integrationSlug,
119
+ connectorUuid: opts.connectorUuid,
120
+ languageModelSlug: opts.languageModelSlug,
121
+ output: opts.output !== undefined
122
+ ? parseJson(opts.output, "--output")
123
+ : undefined,
124
+ };
125
+ }
37
126
  const result = await handleApiCall(() => api.ai.message.create({
38
127
  chatUuid: opts.chatUuid,
128
+ agentUuid: opts.agentUuid,
129
+ releaseUuid: opts.releaseUuid,
39
130
  parts: parseJson(opts.parts, "--parts"),
40
- actions: opts.actions !== undefined
41
- ? parseJson(opts.actions, "--actions")
42
- : undefined,
43
- resources: opts.resources !== undefined
44
- ? parseJson(opts.resources, "--resources")
45
- : undefined,
46
- capabilities: opts.capabilities !== undefined
47
- ? parseJson(opts.capabilities, "--capabilities")
48
- : undefined,
49
- mcpClients: opts.mcpClients !== undefined
50
- ? parseJson(opts.mcpClients, "--mcp-clients")
51
- : undefined,
52
- systemPrompt: opts.systemPrompt,
53
- withReasoning: opts.withReasoning,
54
- temperature: opts.temperature !== undefined
55
- ? parseFloat(opts.temperature)
56
- : undefined,
57
- maxSteps: opts.maxSteps !== undefined
58
- ? parseInt(opts.maxSteps, 10)
59
- : undefined,
60
- integrationSlug: opts.integrationSlug,
61
- connectorUuid: opts.connectorUuid,
62
- languageModelSlug: opts.languageModelSlug,
63
- output: opts.output !== undefined
64
- ? parseJson(opts.output, "--output")
65
- : undefined,
131
+ ...params,
66
132
  }));
67
133
  if (opts.waitUntilFinished === true) {
68
134
  const intervalMs = parseInt(opts.pollingInterval, 10);
@@ -45,11 +45,11 @@ export function registerFileCommands(parent, getApi) {
45
45
  file
46
46
  .command("upload")
47
47
  .description("Upload a file")
48
- .requiredOption("--file-path <path>", "Path to the file to upload")
48
+ .requiredOption("--file <path>", "Path to the file to upload")
49
49
  .option("--folder-uuid <uuid>", "Folder UUID")
50
50
  .action(async (opts) => {
51
- const buffer = await readFile(opts.filePath);
52
- const name = basename(opts.filePath);
51
+ const buffer = await readFile(opts.file);
52
+ const name = basename(opts.file);
53
53
  const fileObj = new File([buffer], name);
54
54
  const api = getApi();
55
55
  await handleApiCall(() => new Promise((resolve, reject) => {
@@ -1 +1 @@
1
- {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAQxC,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAyON"}
1
+ {"version":3,"file":"batch.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAexC,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA2QN"}
@@ -1,4 +1,5 @@
1
- import { handleApiCall, outputJson, parseJson, pollBatchUntilFinished, } from "../runHandler.js";
1
+ import { loadCompiledWorkflow, resolveCompiledWorkflowFromState, } from "../../utils/loadCompiledWorkflow.js";
2
+ import { ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, pollBatchUntilFinished, } from "../runHandler.js";
2
3
  export function registerBatchCommands(parent, getApi) {
3
4
  const batch = parent
4
5
  .command("batch")
@@ -60,25 +61,42 @@ export function registerBatchCommands(parent, getApi) {
60
61
  });
61
62
  batch
62
63
  .command("create")
63
- .description("Create a batch")
64
+ .description("Create a batch. Pass --file to test-run a Workflow SDK module (compiled and " +
65
+ "run as custom nodes, with @cargo-ai/cdk handles resolved from cargo.state.json — no deploy).")
64
66
  .option("--workflow-uuid <uuid>", "Workflow UUID (string)")
65
67
  .requiredOption("--data <json>", 'Batch data source (JSON object, required). Supported kinds: {"kind":"segment","segmentUuid":"..."} or {"kind":"file","s3Filename":"..."}')
66
68
  .option("--release-uuid <uuid>", "Release UUID to pin the batch to (string)")
67
- .option("--nodes <json>", "Custom nodes to override the workflow definition (JSON array).")
69
+ .option("--file <path>", "Path to a Workflow SDK module (its default export must be the compiled workflow returned by defineWorkflow()). Compiles it, resolves any @cargo-ai/cdk resource handles to real uuids from cargo.state.json, and runs the resulting nodes. Rejected with --nodes.")
70
+ .option("--nodes <json>", "Custom nodes to override the workflow definition (JSON array). Rejected with --file.")
68
71
  .option("--wait-until-finished", "Poll the batch until it reaches a terminal status before returning (boolean flag)")
69
72
  .option("--polling-interval <ms>", "Polling interval in milliseconds, used with --wait-until-finished (integer, default: 5000)", "5000")
70
73
  .addHelpText("after", `
71
74
  Examples:
72
75
  $ cargo-ai orchestration batch create --data '{"kind":"segment","segmentUuid":"550e8400-..."}'
73
- $ cargo-ai orchestration batch create --workflow-uuid 550e8400-... --data '{"kind":"file","s3Filename":"input.csv"}' --wait-until-finished`)
76
+ $ cargo-ai orchestration batch create --workflow-uuid 550e8400-... --data '{"kind":"file","s3Filename":"input.csv"}' --wait-until-finished
77
+ $ cargo-ai orchestration batch create --file ./plays/onboarding.ts --data '{"kind":"segment","segmentUuid":"550e8400-..."}' --wait-until-finished`)
74
78
  .action(async (opts) => {
79
+ // `--file` compiles a Workflow SDK module and runs its (state-resolved)
80
+ // nodes ad-hoc; `--nodes` supplies a raw node graph. They're mutually
81
+ // exclusive sources for the same payload field.
82
+ let nodes;
83
+ if (opts.file !== undefined) {
84
+ if (opts.nodes !== undefined) {
85
+ failWith("--nodes is not allowed with --file (sourced from the module)", { code: ExitCodes.InvalidUsage });
86
+ }
87
+ const compiled = await loadCompiledWorkflow(opts.file);
88
+ const resolved = resolveCompiledWorkflowFromState(compiled, opts.file);
89
+ info(`Test-running "${resolved.slug}" (${String(resolved.nodes.length)} node(s)) — no deploy.`);
90
+ nodes = resolved.nodes;
91
+ }
92
+ else if (opts.nodes !== undefined) {
93
+ nodes = parseJson(opts.nodes, "--nodes");
94
+ }
75
95
  const api = getApi();
76
96
  const result = await handleApiCall(() => api.orchestration.batch.create({
77
97
  workflowUuid: opts.workflowUuid,
78
98
  releaseUuid: opts.releaseUuid,
79
- nodes: opts.nodes !== undefined
80
- ? parseJson(opts.nodes, "--nodes")
81
- : undefined,
99
+ nodes,
82
100
  data: parseJson(opts.data, "--data"),
83
101
  }));
84
102
  if (opts.waitUntilFinished === true) {
@@ -1 +1 @@
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
+ {"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;AAmCxC,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA+KN"}
@@ -1,6 +1,4 @@
1
- import { existsSync } from "node:fs";
2
- import { isAbsolute, resolve } from "node:path";
3
- import { pathToFileURL } from "node:url";
1
+ import { loadCompiledWorkflow, } from "../../utils/loadCompiledWorkflow.js";
4
2
  import { ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, success, } from "../runHandler.js";
5
3
  export function registerReleaseCommands(parent, getApi) {
6
4
  const release = parent
@@ -90,7 +88,7 @@ export function registerReleaseCommands(parent, getApi) {
90
88
  .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
89
  .option("--nodes <json>", "Nodes (JSON array of node definitions). Required without --file; rejected with --file.")
92
90
  .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).")
91
+ .option("--description <text>", "Release description.")
94
92
  .option("--options <json>", "Options (JSON object)")
95
93
  .option("--dry-run", "With --file: compile and print the nodes + form fields without deploying. Ignored without --file.")
96
94
  .action(async (opts) => {
@@ -157,7 +155,6 @@ async function runFileDeploy(getApi, opts) {
157
155
  if (opts.dryRun === true) {
158
156
  outputJson({
159
157
  slug: compiled.slug,
160
- description: compiled.description,
161
158
  nodes: compiled.nodes,
162
159
  formFields: compiled.formFields,
163
160
  });
@@ -168,14 +165,13 @@ async function runFileDeploy(getApi, opts) {
168
165
  const version = opts.version !== undefined
169
166
  ? opts.version
170
167
  : await computeNextVersion(api, workflowUuid);
171
- const description = opts.description !== undefined ? opts.description : compiled.description;
172
168
  info(`Deploying "${compiled.slug}" → workflow ${workflowUuid} as v${version} (${String(compiled.nodes.length)} node(s))…`);
173
169
  const payload = {
174
170
  workflowUuid,
175
171
  version,
176
172
  nodes: compiled.nodes,
177
173
  formFields: compiled.formFields,
178
- description,
174
+ description: opts.description,
179
175
  options: opts.options !== undefined
180
176
  ? parseJson(opts.options, "--options")
181
177
  : undefined,
@@ -184,52 +180,6 @@ async function runFileDeploy(getApi, opts) {
184
180
  success(`Deployed v${version}.`);
185
181
  outputJson(result);
186
182
  }
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
183
  // Resolve the target workflow UUID. The platform `Workflow` has no free-form
234
184
  // slug field — the closest stable identifier is `template.slug` — and there
235
185
  // is no `workflow.create` endpoint, so an explicit `--workflow-uuid` always
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAQxC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAmlB5E"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAexC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAsnB5E"}
@@ -1,4 +1,5 @@
1
- import { handleApiCall, outputJson, parseJson, pollRunUntilFinished, } from "../runHandler.js";
1
+ import { loadCompiledWorkflow, resolveCompiledWorkflowFromState, } from "../../utils/loadCompiledWorkflow.js";
2
+ import { ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, pollRunUntilFinished, } from "../runHandler.js";
2
3
  export function registerRunCommands(parent, getApi) {
3
4
  const run = parent
4
5
  .command("run")
@@ -69,27 +70,44 @@ Examples:
69
70
  });
70
71
  run
71
72
  .command("create")
72
- .description("Create a run")
73
+ .description("Create a run. Pass --file to test-run a Workflow SDK module (compiled and " +
74
+ "run as custom nodes, with @cargo-ai/cdk handles resolved from cargo.state.json — no deploy).")
73
75
  .option("--workflow-uuid <uuid>", "Workflow UUID (string)")
74
76
  .requiredOption("--data <json>", "Run input data (JSON object, required). The shape depends on your workflow's start node configuration")
75
77
  .option("--release-uuid <uuid>", "Release UUID to pin the run to (string)")
76
- .option("--nodes <json>", "Custom nodes to override the workflow definition (JSON array).")
78
+ .option("--file <path>", "Path to a Workflow SDK module (its default export must be the compiled workflow returned by defineWorkflow()). Compiles it, resolves any @cargo-ai/cdk resource handles to real uuids from cargo.state.json, and runs the resulting nodes. Rejected with --nodes.")
79
+ .option("--nodes <json>", "Custom nodes to override the workflow definition (JSON array). Rejected with --file.")
77
80
  .option("--wait-until-finished", "Poll the run until it reaches a terminal status before returning (boolean flag)")
78
81
  .option("--polling-interval <ms>", "Polling interval in milliseconds, used with --wait-until-finished (integer, default: 5000)", "5000")
79
82
  .addHelpText("after", `
80
83
  Examples:
81
84
  $ cargo-ai orchestration run create --data '{"email":"user@example.com"}'
82
85
  $ cargo-ai orchestration run create --workflow-uuid 550e8400-... --data '{"id":"123"}' --wait-until-finished
83
- $ cargo-ai orchestration run create --data '{"id":"123"}' --wait-until-finished --polling-interval 10000`)
86
+ $ cargo-ai orchestration run create --data '{"id":"123"}' --wait-until-finished --polling-interval 10000
87
+ $ cargo-ai orchestration run create --file ./plays/onboarding.ts --data '{"domain":"acme.com"}' --wait-until-finished`)
84
88
  .action(async (opts) => {
85
89
  const data = parseJson(opts.data, "--data");
90
+ // `--file` compiles a Workflow SDK module and runs its (state-resolved)
91
+ // nodes ad-hoc; `--nodes` supplies a raw node graph. They're mutually
92
+ // exclusive sources for the same payload field.
93
+ let nodes;
94
+ if (opts.file !== undefined) {
95
+ if (opts.nodes !== undefined) {
96
+ failWith("--nodes is not allowed with --file (sourced from the module)", { code: ExitCodes.InvalidUsage });
97
+ }
98
+ const compiled = await loadCompiledWorkflow(opts.file);
99
+ const resolved = resolveCompiledWorkflowFromState(compiled, opts.file);
100
+ info(`Test-running "${resolved.slug}" (${String(resolved.nodes.length)} node(s)) — no deploy.`);
101
+ nodes = resolved.nodes;
102
+ }
103
+ else if (opts.nodes !== undefined) {
104
+ nodes = parseJson(opts.nodes, "--nodes");
105
+ }
86
106
  const api = getApi();
87
107
  const result = await handleApiCall(() => api.orchestration.run.create({
88
108
  workflowUuid: opts.workflowUuid,
89
109
  releaseUuid: opts.releaseUuid,
90
- nodes: opts.nodes !== undefined
91
- ? parseJson(opts.nodes, "--nodes")
92
- : undefined,
110
+ nodes,
93
111
  data,
94
112
  }));
95
113
  if (opts.waitUntilFinished === true) {
@@ -6,10 +6,10 @@ export function registerFileCommands(parent, getApi) {
6
6
  file
7
7
  .command("upload")
8
8
  .description("Upload a file")
9
- .requiredOption("--file-path <path>", "Path to the file to upload")
9
+ .requiredOption("--file <path>", "Path to the file to upload")
10
10
  .action(async (opts) => {
11
- const buffer = await readFile(opts.filePath);
12
- const name = basename(opts.filePath);
11
+ const buffer = await readFile(opts.file);
12
+ const name = basename(opts.file);
13
13
  const fileObj = new File([buffer], name);
14
14
  const api = getApi();
15
15
  await handleApiCall(() => new Promise((resolve, reject) => {
@@ -0,0 +1,26 @@
1
+ export declare function resolveDefaultExport(mod: {
2
+ default?: unknown;
3
+ }): unknown;
4
+ /**
5
+ * Load a `.ts`/`.js` module through the tsx ESM loader (so `.ts` sources and
6
+ * their imports of `@cargo-ai/*` / `zod` are transpiled on the fly) and return
7
+ * its resolved default export. Shared by both `--file` loaders: the workflow
8
+ * loader validates the result as a compiled workflow; the agent loader reads the
9
+ * agent back out of the CDK registry that loading populated (so it must
10
+ * `resetRegistry()` BEFORE calling this). `subject` labels the file in errors
11
+ * (e.g. "Workflow", "Agent").
12
+ */
13
+ export declare function importModuleDefault(file: string, subject: string): Promise<{
14
+ absPath: string;
15
+ defaultExport: unknown;
16
+ }>;
17
+ /**
18
+ * Deep-resolve every `@cargo-ai/cdk` handle token in `value` to its real uuid
19
+ * using the nearest cargo.state.json (searched from `file`'s directory upward,
20
+ * then the cwd). `subject` names the thing under test in error messages (e.g. a
21
+ * workflow or agent slug). A value with no tokens (literal-uuid refs, or a pure
22
+ * body) is returned unchanged — no state needed. Fails with an actionable error
23
+ * when state is missing or a referenced resource isn't deployed yet.
24
+ */
25
+ export declare function resolveTokensFromState(value: unknown, file: string, subject: string): unknown;
26
+ //# sourceMappingURL=cdkState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdkState.d.ts","sourceRoot":"","sources":["../../src/utils/cdkState.ts"],"names":[],"mappings":"AAmBA,wBAAgB,oBAAoB,CAAC,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CASxE;AAED;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,OAAO,CAAA;CAAE,CAAC,CAyBtD;AAaD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,OAAO,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,OAAO,CAuCT"}
@@ -0,0 +1,101 @@
1
+ // Shared helpers for the CLI `--file` paths that compile a
2
+ // `@cargo-ai/cdk`-aware module (a Workflow SDK workflow, or a `defineAgent`) and
3
+ // test-run it against ALREADY-DEPLOYED resources. Both need the same two things:
4
+ // peel the tsx interop layer off a module's default export, and resolve any
5
+ // `@cargo-ai/cdk` handle tokens the module references to their real uuids from
6
+ // the committed cargo.state.json — without deploying the module itself.
7
+ import { existsSync } from "node:fs";
8
+ import { dirname, isAbsolute, join, resolve } from "node:path";
9
+ import { pathToFileURL } from "node:url";
10
+ import { dependenciesOf, readState, resolveTokens } from "@cargo-ai/cdk";
11
+ import { ExitCodes, failWith } from "../commands/runHandler.js";
12
+ // Peel the extra interop layer tsx adds when a TS source is transpiled to CJS:
13
+ // the ESM namespace's `default` is then the CJS `module.exports`
14
+ // (`{ __esModule: true, default: … }`), so the real value lives one level
15
+ // deeper. True-ESM modules expose it directly.
16
+ export function resolveDefaultExport(mod) {
17
+ const top = mod.default;
18
+ if (top !== null && typeof top === "object") {
19
+ const inner = top;
20
+ if (inner["__esModule"] === true && "default" in inner) {
21
+ return inner["default"];
22
+ }
23
+ }
24
+ return top;
25
+ }
26
+ /**
27
+ * Load a `.ts`/`.js` module through the tsx ESM loader (so `.ts` sources and
28
+ * their imports of `@cargo-ai/*` / `zod` are transpiled on the fly) and return
29
+ * its resolved default export. Shared by both `--file` loaders: the workflow
30
+ * loader validates the result as a compiled workflow; the agent loader reads the
31
+ * agent back out of the CDK registry that loading populated (so it must
32
+ * `resetRegistry()` BEFORE calling this). `subject` labels the file in errors
33
+ * (e.g. "Workflow", "Agent").
34
+ */
35
+ export async function importModuleDefault(file, subject) {
36
+ const absPath = isAbsolute(file) ? file : resolve(process.cwd(), file);
37
+ if (!existsSync(absPath)) {
38
+ failWith(`${subject} file not found: ${absPath}`, {
39
+ code: ExitCodes.InvalidUsage,
40
+ });
41
+ }
42
+ let mod;
43
+ try {
44
+ const { tsImport } = await import("tsx/esm/api");
45
+ mod = (await tsImport(pathToFileURL(absPath).href, import.meta.url));
46
+ }
47
+ catch (error) {
48
+ failWith(`Failed to load ${subject.toLowerCase()} module: ${error instanceof Error ? error.message : String(error)}`, { code: ExitCodes.GenericError });
49
+ }
50
+ return { absPath, defaultExport: resolveDefaultExport(mod) };
51
+ }
52
+ // Walk up from `startDir` looking for a directory that holds cargo.state.json.
53
+ function findStateRoot(startDir) {
54
+ let dir = startDir;
55
+ for (;;) {
56
+ if (existsSync(join(dir, "cargo.state.json")))
57
+ return dir;
58
+ const parent = dirname(dir);
59
+ if (parent === dir)
60
+ return undefined;
61
+ dir = parent;
62
+ }
63
+ }
64
+ /**
65
+ * Deep-resolve every `@cargo-ai/cdk` handle token in `value` to its real uuid
66
+ * using the nearest cargo.state.json (searched from `file`'s directory upward,
67
+ * then the cwd). `subject` names the thing under test in error messages (e.g. a
68
+ * workflow or agent slug). A value with no tokens (literal-uuid refs, or a pure
69
+ * body) is returned unchanged — no state needed. Fails with an actionable error
70
+ * when state is missing or a referenced resource isn't deployed yet.
71
+ */
72
+ export function resolveTokensFromState(value, file, subject) {
73
+ const deps = dependenciesOf(value);
74
+ if (deps.size === 0)
75
+ return value;
76
+ const absPath = isAbsolute(file) ? file : resolve(process.cwd(), file);
77
+ const stateRoot = findStateRoot(dirname(absPath)) ?? findStateRoot(process.cwd());
78
+ if (stateRoot === undefined) {
79
+ failWith(`${subject} references ${String(deps.size)} deployed resource(s) ` +
80
+ `(${[...deps].join(", ")}), but no cargo.state.json was found near ` +
81
+ `${absPath} or the current directory. Deploy the resources first with ` +
82
+ "`cargo-ai cdk deploy`, or reference them by literal uuid " +
83
+ "(toolRef / agentRef / connectorRef).", { code: ExitCodes.NotFound });
84
+ }
85
+ const state = readState(stateRoot);
86
+ const outputs = new Map();
87
+ if (state !== undefined) {
88
+ for (const [id, entry] of Object.entries(state.resources)) {
89
+ outputs.set(id, entry.outputs);
90
+ }
91
+ }
92
+ const missing = [...deps].filter((id) => !outputs.has(id));
93
+ if (missing.length > 0) {
94
+ failWith(`${String(missing.length)} resource(s) referenced by ${subject} ` +
95
+ `aren't in cargo.state.json yet: ${missing.join(", ")}. A test run ` +
96
+ "resolves existing (already-deployed) resources from state — it won't " +
97
+ "create new ones. Deploy them first with `cargo-ai cdk deploy`, or " +
98
+ "reference them by literal uuid (toolRef / agentRef / connectorRef).", { code: ExitCodes.NotFound });
99
+ }
100
+ return resolveTokens(value, outputs);
101
+ }
@@ -0,0 +1,5 @@
1
+ import { type CompiledAgent } from "@cargo-ai/cdk";
2
+ export type { CompiledAgent } from "@cargo-ai/cdk";
3
+ export declare function loadCompiledAgent(file: string): Promise<CompiledAgent>;
4
+ export declare function resolveCompiledAgentFromState(compiled: CompiledAgent, file: string): CompiledAgent;
5
+ //# sourceMappingURL=loadCompiledAgent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadCompiledAgent.d.ts","sourceRoot":"","sources":["../../src/utils/loadCompiledAgent.ts"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,aAAa,EAInB,MAAM,eAAe,CAAC;AAKvB,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAqCnD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA6C5E;AAQD,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,MAAM,GACX,aAAa,CAQf"}
@@ -0,0 +1,75 @@
1
+ // The `ai message create --file` path: compile a `@cargo-ai/cdk` `defineAgent`
2
+ // module and lower it to the params an `ai message create` accepts, so you can
3
+ // chat against an agent's real prompt / tools / models / capabilities WITHOUT
4
+ // deploying it. Two steps mirror the workflow `--file` flow: `loadCompiledAgent`
5
+ // runs the module's `define*` calls into the shared CDK registry, reads the
6
+ // agent back, and maps its buckets with the SAME transform `cdk deploy` uses
7
+ // (`compileAgent`); `resolveCompiledAgentFromState` then resolves the
8
+ // `@cargo-ai/cdk` handle tokens it still carries to real uuids from
9
+ // cargo.state.json — a faithful test of the agent's core chat behaviour.
10
+ import { compileAgent, resetRegistry, resources, } from "@cargo-ai/cdk";
11
+ import { ExitCodes, failWith } from "../commands/runHandler.js";
12
+ import { importModuleDefault, resolveTokensFromState } from "./cdkState.js";
13
+ function isAgentHandle(value) {
14
+ if (value === null || typeof value !== "object")
15
+ return false;
16
+ const v = value;
17
+ return v["resource"] === "agent" && typeof v["slug"] === "string";
18
+ }
19
+ // Agent config an inline message can't express — rejected up front (rather than
20
+ // silently dropped) so a test run is never quietly less than the deployed agent.
21
+ // Each entry: the spec field, and whether the stored value means "set".
22
+ const UNSUPPORTED = [
23
+ { field: "evaluator", label: "evaluator", isSet: (v) => v !== null },
24
+ { field: "heartbeat", label: "heartbeat", isSet: (v) => v !== null },
25
+ {
26
+ field: "triggers",
27
+ label: "triggers",
28
+ isSet: (v) => Array.isArray(v) && v.length > 0,
29
+ },
30
+ {
31
+ field: "suggestedActions",
32
+ label: "suggestedActions",
33
+ isSet: (v) => Array.isArray(v) && v.length > 0,
34
+ },
35
+ ];
36
+ // Load a `defineAgent` module and lower it to a `CompiledAgent` (tokens still
37
+ // embedded — resolve them with `resolveCompiledAgentFromState`). `resetRegistry`
38
+ // first so a stale registry (e.g. a prior load in the same process) can't leak
39
+ // resources into this one.
40
+ export async function loadCompiledAgent(file) {
41
+ // Reset BEFORE loading: the import runs the module's `define*` calls, which
42
+ // register into the shared CDK registry we read back below. A stale registry
43
+ // (e.g. a prior load in the same process) would otherwise leak in.
44
+ resetRegistry();
45
+ const { absPath, defaultExport: handle } = await importModuleDefault(file, "Agent");
46
+ if (!isAgentHandle(handle)) {
47
+ failWith("Agent module must `export default` an agent handle (the value returned by defineAgent()).", { code: ExitCodes.InvalidUsage });
48
+ }
49
+ // A file can register several agents (its sub-agents), so pick THE one it
50
+ // exports by slug rather than assuming a single registration.
51
+ const node = resources().find((n) => n.kind === "agent" && n.slug === handle.slug);
52
+ if (node === undefined) {
53
+ failWith(`Agent "${handle.slug}" was not registered by ${absPath}. Ensure the ` +
54
+ "module's default export is the handle returned by defineAgent().", { code: ExitCodes.GenericError });
55
+ }
56
+ const setUnsupported = UNSUPPORTED.filter((u) => u.isSet(node.spec[u.field])).map((u) => u.label);
57
+ if (setUnsupported.length > 0) {
58
+ failWith(`Agent "${handle.slug}" sets ${setUnsupported.join(", ")}, which a test ` +
59
+ "message can't apply (they only take effect on a deployed release). " +
60
+ "Remove them for a test run, or deploy the agent with `cargo-ai cdk " +
61
+ "deploy` and chat against it by uuid.", { code: ExitCodes.InvalidUsage });
62
+ }
63
+ return compileAgent(node.spec);
64
+ }
65
+ // Resolve the agent params' `@cargo-ai/cdk` resource handles (tool / agent /
66
+ // connector / model) to their real uuids using the committed cargo.state.json,
67
+ // so a test message runs against the ALREADY-DEPLOYED resources without
68
+ // deploying the agent itself. Returns the same compiled agent with engine-ready
69
+ // params. An agent with no handle tokens (literal-uuid refs) is returned
70
+ // unchanged — no state needed.
71
+ export function resolveCompiledAgentFromState(compiled, file) {
72
+ // The slug is a plain string (no token), so resolving the whole object is
73
+ // safe and keeps it flat.
74
+ return resolveTokensFromState(compiled, file, `"${compiled.slug}"`);
75
+ }
@@ -0,0 +1,8 @@
1
+ export type CompiledWorkflow = {
2
+ slug: string;
3
+ nodes: unknown[];
4
+ formFields: unknown[];
5
+ };
6
+ export declare function loadCompiledWorkflow(file: string): Promise<CompiledWorkflow>;
7
+ export declare function resolveCompiledWorkflowFromState(compiled: CompiledWorkflow, file: string): CompiledWorkflow;
8
+ //# sourceMappingURL=loadCompiledWorkflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadCompiledWorkflow.d.ts","sourceRoot":"","sources":["../../src/utils/loadCompiledWorkflow.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,UAAU,EAAE,OAAO,EAAE,CAAC;CACvB,CAAC;AAKF,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,gBAAgB,CAAC,CAW3B;AAUD,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,MAAM,GACX,gBAAgB,CAOlB"}
@@ -0,0 +1,38 @@
1
+ // Shared helpers for the `--file` paths that compile a `@cargo-ai/workflow-sdk`
2
+ // module: `release deploy-draft --file` (deploy it) and `run create --file` /
3
+ // `batch create --file` (test-run it). Loading is identical; `run create` and
4
+ // `batch create` additionally resolve any `@cargo-ai/cdk` resource handles the
5
+ // workflow references to their real uuids from cargo.state.json (see
6
+ // `resolveCompiledWorkflowFromState`).
7
+ import { ExitCodes, failWith } from "../commands/runHandler.js";
8
+ import { importModuleDefault, resolveTokensFromState } from "./cdkState.js";
9
+ // Load a workflow module via the tsx ESM loader so `.ts` sources (and their
10
+ // imports of `@cargo-ai/workflow-sdk` / `zod`) are transpiled on the fly,
11
+ // returning its default export validated as a compiled workflow.
12
+ export async function loadCompiledWorkflow(file) {
13
+ const { defaultExport } = await importModuleDefault(file, "Workflow");
14
+ if (!isCompiledWorkflow(defaultExport)) {
15
+ failWith("Workflow module must `export default` a compiled workflow (the value returned by defineWorkflow()).", { code: ExitCodes.InvalidUsage });
16
+ }
17
+ return defaultExport;
18
+ }
19
+ // Resolve the workflow's `@cargo-ai/cdk` resource handles (tool / agent /
20
+ // connector / model / …) to their real uuids using the committed
21
+ // cargo.state.json, so a test-run executes against the ALREADY-DEPLOYED
22
+ // resources without deploying the workflow itself. Returns the same compiled
23
+ // workflow with an engine-ready node graph (`formFields` carry no tokens, so
24
+ // they pass through). A workflow with no handle tokens (literal-uuid refs via
25
+ // `toolRef`/`connectorRef`, or a pure-native body) is returned unchanged — no
26
+ // state needed.
27
+ export function resolveCompiledWorkflowFromState(compiled, file) {
28
+ const nodes = resolveTokensFromState(compiled.nodes, file, `"${compiled.slug}"`);
29
+ return { ...compiled, nodes };
30
+ }
31
+ function isCompiledWorkflow(value) {
32
+ if (value === null || typeof value !== "object")
33
+ return false;
34
+ const v = value;
35
+ return (typeof v["slug"] === "string" &&
36
+ Array.isArray(v["nodes"]) &&
37
+ Array.isArray(v["formFields"]));
38
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cargo-ai/cli",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "description": "Command-line interface for the Cargo API",
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "type": "module",
14
14
  "bin": {
15
- "cargo-ai": "./build/index.js"
15
+ "cargo-ai": "./build/index.js",
16
+ "cargo": "./build/index.js"
16
17
  },
17
18
  "files": [
18
19
  "build",