@cargo-ai/cli 1.0.30 → 1.0.32
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 +1 -2
- package/build/commands/ai/message.d.ts.map +1 -1
- package/build/commands/ai/message.js +95 -29
- package/build/commands/content/file.js +3 -3
- package/build/commands/mcp.d.ts +4 -0
- package/build/commands/mcp.d.ts.map +1 -0
- package/build/commands/mcp.js +192 -0
- package/build/commands/orchestration/batch.d.ts.map +1 -1
- package/build/commands/orchestration/batch.js +25 -7
- package/build/commands/orchestration/release.d.ts.map +1 -1
- package/build/commands/orchestration/release.js +3 -53
- package/build/commands/orchestration/run.d.ts.map +1 -1
- package/build/commands/orchestration/run.js +25 -7
- package/build/commands/workspaceManagement/file.js +3 -3
- package/build/index.js +6 -1
- package/build/proxy.d.ts +12 -0
- package/build/proxy.d.ts.map +1 -1
- package/build/proxy.js +39 -7
- package/build/utils/cdkState.d.ts +26 -0
- package/build/utils/cdkState.d.ts.map +1 -0
- package/build/utils/cdkState.js +101 -0
- package/build/utils/loadCompiledAgent.d.ts +5 -0
- package/build/utils/loadCompiledAgent.d.ts.map +1 -0
- package/build/utils/loadCompiledAgent.js +75 -0
- package/build/utils/loadCompiledWorkflow.d.ts +8 -0
- package/build/utils/loadCompiledWorkflow.d.ts.map +1 -0
- package/build/utils/loadCompiledWorkflow.js +38 -0
- package/package.json +6 -4
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
|
|
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;
|
|
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 {
|
|
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
|
|
9
|
-
.
|
|
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
|
-
|
|
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
|
|
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.
|
|
52
|
-
const name = basename(opts.
|
|
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) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAerC,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAqD3E"}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { getConfig } from "../config.js";
|
|
5
|
+
import { getProxyFetch } from "../proxy.js";
|
|
6
|
+
import { ExitCodes, failWith, handleApiCall, info } from "./runHandler.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const { version } = require("../../package.json");
|
|
9
|
+
// The prod REST API and the prod MCP server are the same backend image behind
|
|
10
|
+
// two hostnames; the MCP one (mcp.getcargo.io) is tuned for long-lived streaming.
|
|
11
|
+
// Point the bridge there when the CLI is talking to prod, but leave any other
|
|
12
|
+
// base URL (staging, localhost) untouched — that same host serves the MCP routes.
|
|
13
|
+
const PROD_API_HOST = "api.getcargo.io";
|
|
14
|
+
const PROD_MCP_HOST = "mcp.getcargo.io";
|
|
15
|
+
export function registerMcpCommand(parent, getApi) {
|
|
16
|
+
parent
|
|
17
|
+
.command("mcp")
|
|
18
|
+
.description("Run a local MCP server that bridges to a Cargo MCP server over stdio")
|
|
19
|
+
.option("--server <uuid>", "MCP server UUID to expose (default: CARGO_MCP_SERVER_UUID, or the workspace's only MCP server)")
|
|
20
|
+
.addHelpText("after", `
|
|
21
|
+
Exposes a Cargo-hosted MCP server as a local stdio MCP server, so any stdio MCP
|
|
22
|
+
client (Claude Code, Claude Desktop, Cursor, ...) can use it with the CLI's own
|
|
23
|
+
credentials — no token to copy into client config.
|
|
24
|
+
|
|
25
|
+
Register it with a client, e.g. Claude Code:
|
|
26
|
+
$ claude mcp add cargo -- cargo-ai mcp --server <uuid>
|
|
27
|
+
|
|
28
|
+
Environment:
|
|
29
|
+
CARGO_MCP_SERVER_UUID default --server value
|
|
30
|
+
CARGO_MCP_BASE_URL override the MCP endpoint origin (advanced/local dev)
|
|
31
|
+
|
|
32
|
+
Reads auth from the same place as every other command (CARGO_API_TOKEN or the
|
|
33
|
+
credentials file). stdout carries the MCP protocol; all logs go to stderr.`)
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const { accessToken, baseUrl, workspaceUuid } = getConfig();
|
|
36
|
+
if (accessToken === undefined) {
|
|
37
|
+
failWith('Not authenticated. Run "cargo-ai login --token <token>" or "cargo-ai login --oauth".', { code: ExitCodes.NotAuthenticated });
|
|
38
|
+
}
|
|
39
|
+
const serverUuid = await resolveServerUuid(opts.server, getApi);
|
|
40
|
+
const endpoint = resolveMcpEndpoint(baseUrl, serverUuid);
|
|
41
|
+
const headers = {
|
|
42
|
+
authorization: `Bearer ${accessToken}`,
|
|
43
|
+
"x-cargo-origin": "cli",
|
|
44
|
+
"x-cargo-origin-version": version,
|
|
45
|
+
};
|
|
46
|
+
if (workspaceUuid !== undefined) {
|
|
47
|
+
headers["selected-workspace-uuid"] = workspaceUuid;
|
|
48
|
+
}
|
|
49
|
+
info(`Cargo MCP bridge -> ${endpoint}`);
|
|
50
|
+
await runBridge(endpoint, headers);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const resolveServerUuid = async (explicit, getApi) => {
|
|
54
|
+
if (explicit !== undefined)
|
|
55
|
+
return explicit;
|
|
56
|
+
const fromEnv = process.env["CARGO_MCP_SERVER_UUID"];
|
|
57
|
+
if (fromEnv !== undefined && fromEnv.length > 0)
|
|
58
|
+
return fromEnv;
|
|
59
|
+
const api = getApi();
|
|
60
|
+
const { mcpServers } = await handleApiCall(() => api.ai.mcpServer.all(), {
|
|
61
|
+
spinner: false,
|
|
62
|
+
});
|
|
63
|
+
if (mcpServers.length === 0) {
|
|
64
|
+
failWith("No MCP server in this workspace. Create one with 'cargo-ai ai mcp-server create --name ...', then pass --server <uuid>.", { code: ExitCodes.InvalidUsage });
|
|
65
|
+
}
|
|
66
|
+
const only = mcpServers[0];
|
|
67
|
+
if (mcpServers.length > 1 || only === undefined) {
|
|
68
|
+
failWith("Multiple MCP servers found; pass --server <uuid>.", {
|
|
69
|
+
code: ExitCodes.InvalidUsage,
|
|
70
|
+
extra: {
|
|
71
|
+
servers: mcpServers.map((server) => ({
|
|
72
|
+
uuid: server.uuid,
|
|
73
|
+
name: server.name,
|
|
74
|
+
})),
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return only.uuid;
|
|
79
|
+
};
|
|
80
|
+
const resolveMcpEndpoint = (baseUrl, uuid) => {
|
|
81
|
+
const override = process.env["CARGO_MCP_BASE_URL"];
|
|
82
|
+
const origin = override !== undefined && override.length > 0
|
|
83
|
+
? override
|
|
84
|
+
: mapApiHostToMcp(baseUrl);
|
|
85
|
+
return `${origin.replace(/\/+$/, "")}/v1/ai/mcpServers/${uuid}/mcp`;
|
|
86
|
+
};
|
|
87
|
+
const mapApiHostToMcp = (baseUrl) => {
|
|
88
|
+
try {
|
|
89
|
+
const url = new URL(baseUrl);
|
|
90
|
+
if (url.hostname === PROD_API_HOST) {
|
|
91
|
+
url.hostname = PROD_MCP_HOST;
|
|
92
|
+
return url.origin;
|
|
93
|
+
}
|
|
94
|
+
return baseUrl;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return baseUrl;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
// Pipe two MCP transports message-for-message: the local stdio server (facing the
|
|
101
|
+
// MCP client) and the remote streamable-HTTP client (facing Cargo). Forwarding raw
|
|
102
|
+
// JSON-RPC keeps the bridge transparent — tools, resources, and long-running-tool
|
|
103
|
+
// progress notifications all pass through without enumerating capabilities here.
|
|
104
|
+
const runBridge = async (endpoint, headers) => {
|
|
105
|
+
// Prefer a proxied fetch when HTTPS_PROXY/HTTP_PROXY applies — Node's
|
|
106
|
+
// built-in fetch ignores those env vars, so without this the bridge would
|
|
107
|
+
// fail in proxied environments even though server discovery (Axios) works.
|
|
108
|
+
const remote = new StreamableHTTPClientTransport(new URL(endpoint), {
|
|
109
|
+
requestInit: { headers },
|
|
110
|
+
fetch: getProxyFetch(endpoint),
|
|
111
|
+
});
|
|
112
|
+
const local = new StdioServerTransport();
|
|
113
|
+
await new Promise((resolve, reject) => {
|
|
114
|
+
const state = { closed: false };
|
|
115
|
+
// Shared handlers so finish/fail can remove whichever listeners are still
|
|
116
|
+
// registered. process.once / stdin.once only drop the event that fired; the
|
|
117
|
+
// others would otherwise keep the event loop alive and hang the process
|
|
118
|
+
// after resolve()/reject().
|
|
119
|
+
const onSignal = () => {
|
|
120
|
+
finish("Cargo MCP: shutting down.");
|
|
121
|
+
};
|
|
122
|
+
const onStdinGone = () => {
|
|
123
|
+
finish("Cargo MCP: client disconnected.");
|
|
124
|
+
};
|
|
125
|
+
const teardown = () => {
|
|
126
|
+
process.removeListener("SIGINT", onSignal);
|
|
127
|
+
process.removeListener("SIGTERM", onSignal);
|
|
128
|
+
process.stdin.removeListener("end", onStdinGone);
|
|
129
|
+
process.stdin.removeListener("close", onStdinGone);
|
|
130
|
+
void remote.close().catch(() => { });
|
|
131
|
+
void local.close().catch(() => { });
|
|
132
|
+
};
|
|
133
|
+
// Client disconnect and process signals are normal ends — resolve so the
|
|
134
|
+
// command exits 0. Upstream/startup/stdio failures must reject so callers
|
|
135
|
+
// and MCP clients see a non-zero exit (CLI contract: 0 means success).
|
|
136
|
+
const finish = (reason) => {
|
|
137
|
+
if (state.closed)
|
|
138
|
+
return;
|
|
139
|
+
state.closed = true;
|
|
140
|
+
if (reason !== undefined)
|
|
141
|
+
info(reason);
|
|
142
|
+
teardown();
|
|
143
|
+
resolve();
|
|
144
|
+
};
|
|
145
|
+
const fail = (message) => {
|
|
146
|
+
if (state.closed)
|
|
147
|
+
return;
|
|
148
|
+
state.closed = true;
|
|
149
|
+
teardown();
|
|
150
|
+
reject(new Error(message));
|
|
151
|
+
};
|
|
152
|
+
// Forward raw JSON-RPC both ways. A failing transport reports the reason via
|
|
153
|
+
// onerror (which fires before the send rejection), so the reason is logged
|
|
154
|
+
// there once; these catches only need to guarantee teardown.
|
|
155
|
+
remote.onmessage = (message) => {
|
|
156
|
+
void local.send(message).catch(() => finish());
|
|
157
|
+
};
|
|
158
|
+
local.onmessage = (message) => {
|
|
159
|
+
void remote.send(message).catch(() => finish());
|
|
160
|
+
};
|
|
161
|
+
// Reject with the failure reason once. Stay silent once we're already
|
|
162
|
+
// closing: the in-flight SSE stream aborting during our own shutdown surfaces
|
|
163
|
+
// here as expected noise.
|
|
164
|
+
remote.onerror = (error) => {
|
|
165
|
+
fail(`Cargo MCP: upstream error: ${error.message}`);
|
|
166
|
+
};
|
|
167
|
+
local.onerror = (error) => {
|
|
168
|
+
fail(`Cargo MCP: stdio error: ${error.message}`);
|
|
169
|
+
};
|
|
170
|
+
remote.onclose = () => fail("Cargo MCP: upstream closed.");
|
|
171
|
+
local.onclose = () => finish("Cargo MCP: client disconnected.");
|
|
172
|
+
process.once("SIGINT", onSignal);
|
|
173
|
+
process.once("SIGTERM", onSignal);
|
|
174
|
+
// @modelcontextprotocol/sdk 1.29.0 StdioServerTransport only listens for
|
|
175
|
+
// stdin `data`/`error` — it never wires `end`/`close`, so local.onclose
|
|
176
|
+
// above does not fire when the MCP client closes the pipe without a
|
|
177
|
+
// signal. Detect stdin EOF ourselves so we tear down the upstream
|
|
178
|
+
// streamable-HTTP session instead of leaving a zombie bridge.
|
|
179
|
+
// See https://github.com/modelcontextprotocol/typescript-sdk/issues/2002
|
|
180
|
+
if (process.stdin.readableEnded === true ||
|
|
181
|
+
process.stdin.destroyed === true) {
|
|
182
|
+
onStdinGone();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
process.stdin.once("end", onStdinGone);
|
|
186
|
+
process.stdin.once("close", onStdinGone);
|
|
187
|
+
Promise.all([remote.start(), local.start()]).catch((error) => {
|
|
188
|
+
fail(`Cargo MCP: failed to start bridge: ${describe(error)}`);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const describe = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -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;
|
|
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 {
|
|
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("--
|
|
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
|
|
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":"
|
|
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 {
|
|
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
|
|
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;
|
|
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 {
|
|
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("--
|
|
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
|
|
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
|
|
9
|
+
.requiredOption("--file <path>", "Path to the file to upload")
|
|
10
10
|
.action(async (opts) => {
|
|
11
|
-
const buffer = await readFile(opts.
|
|
12
|
-
const name = basename(opts.
|
|
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) => {
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { registerCommands as registerCdkCommands } from "@cargo-ai/cdk/cli";
|
|
3
|
+
import { registerCommands as registerCdkCommands, registerManifestCommands, } from "@cargo-ai/cdk/cli";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { createApi } from "./api.js";
|
|
6
6
|
import { registerAiCommands } from "./commands/ai/index.js";
|
|
@@ -12,6 +12,7 @@ import { registerContextCommands } from "./commands/context/index.js";
|
|
|
12
12
|
import { registerExpressionCommands } from "./commands/expression/index.js";
|
|
13
13
|
import { registerHostingCommands } from "./commands/hosting/index.js";
|
|
14
14
|
import { registerInitCommand } from "./commands/init.js";
|
|
15
|
+
import { registerMcpCommand } from "./commands/mcp.js";
|
|
15
16
|
import { registerOrchestrationCommands } from "./commands/orchestration/index.js";
|
|
16
17
|
import { registerRevenueOrganizationCommands } from "./commands/revenueOrganization/index.js";
|
|
17
18
|
import { ExitCodes, failWith } from "./commands/runHandler.js";
|
|
@@ -73,9 +74,13 @@ registerSystemOfRecordIntegrationCommands(program, getApi);
|
|
|
73
74
|
registerUserManagementCommands(program, getApi);
|
|
74
75
|
registerAiCommands(program, getApi);
|
|
75
76
|
registerHostingCommands(program, getApi);
|
|
77
|
+
registerMcpCommand(program, getApi);
|
|
76
78
|
registerCdkCommands(program
|
|
77
79
|
.command("cdk")
|
|
78
80
|
.description("Cargo CDK — define resources in code and deploy them (plan/deploy)"), getApi);
|
|
81
|
+
registerManifestCommands(program
|
|
82
|
+
.command("manifest")
|
|
83
|
+
.description("Manifest — scaffold a GTM repo (context, infra, skills, evals, outputs) and install cookbook modules into it"));
|
|
79
84
|
program
|
|
80
85
|
.parseAsync()
|
|
81
86
|
.then(() => maybeNotifyUpdate(version))
|
package/build/proxy.d.ts
CHANGED
|
@@ -14,4 +14,16 @@ import type { ClientTransport } from "@cargo-ai/api";
|
|
|
14
14
|
* default direct-egress behavior.
|
|
15
15
|
*/
|
|
16
16
|
export declare const getProxyTransport: (targetUrl: string) => ClientTransport | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Build a `fetch` implementation that tunnels through an HTTP/HTTPS proxy
|
|
19
|
+
* when one is configured via the same env vars as {@link getProxyTransport}.
|
|
20
|
+
*
|
|
21
|
+
* Node's built-in `fetch` (and therefore MCP's `StreamableHTTPClientTransport`)
|
|
22
|
+
* does not honor `HTTPS_PROXY` / `HTTP_PROXY` on its own. Callers that need
|
|
23
|
+
* proxied egress should pass the result as the transport's `fetch` option.
|
|
24
|
+
*
|
|
25
|
+
* Returns `undefined` when no proxy applies, so the caller can keep the
|
|
26
|
+
* default global `fetch`.
|
|
27
|
+
*/
|
|
28
|
+
export declare const getProxyFetch: (targetUrl: string) => ((url: string | URL, init?: RequestInit) => Promise<Response>) | undefined;
|
|
17
29
|
//# sourceMappingURL=proxy.d.ts.map
|
package/build/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAmErD;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,cACjB,MAAM,KAChB,eAAe,GAAG,SAWpB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,cACb,MAAM,YAER,MAAM,GAAG,GAAG,SAAS,WAAW,KAAK,QAAQ,QAAQ,CAAC,aAchE,CAAC"}
|
package/build/proxy.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HttpProxyAgent } from "http-proxy-agent";
|
|
2
2
|
import { HttpsProxyAgent } from "https-proxy-agent";
|
|
3
|
+
import { fetch as undiciFetch, ProxyAgent, } from "undici";
|
|
3
4
|
const getEnv = (...names) => {
|
|
4
5
|
for (const name of names) {
|
|
5
6
|
const value = process.env[name];
|
|
@@ -32,6 +33,21 @@ const shouldBypassProxy = (targetUrl, noProxy) => {
|
|
|
32
33
|
return hostname === normalized || hostname.endsWith(`.${normalized}`);
|
|
33
34
|
});
|
|
34
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the proxy URL for a target from the standard proxy env vars
|
|
38
|
+
* (`HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`, honoring `NO_PROXY`).
|
|
39
|
+
*
|
|
40
|
+
* Returns `undefined` when no proxy applies.
|
|
41
|
+
*/
|
|
42
|
+
const resolveProxyUrl = (targetUrl) => {
|
|
43
|
+
if (shouldBypassProxy(targetUrl, getEnv("NO_PROXY", "no_proxy"))) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
const isHttps = targetUrl.startsWith("https:");
|
|
47
|
+
return isHttps
|
|
48
|
+
? getEnv("HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy")
|
|
49
|
+
: getEnv("HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy");
|
|
50
|
+
};
|
|
35
51
|
/**
|
|
36
52
|
* Build Axios transport overrides that tunnel requests through an
|
|
37
53
|
* HTTP/HTTPS proxy when one is configured via the standard proxy env
|
|
@@ -47,13 +63,7 @@ const shouldBypassProxy = (targetUrl, noProxy) => {
|
|
|
47
63
|
* default direct-egress behavior.
|
|
48
64
|
*/
|
|
49
65
|
export const getProxyTransport = (targetUrl) => {
|
|
50
|
-
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
const isHttps = targetUrl.startsWith("https:");
|
|
54
|
-
const proxyUrl = isHttps
|
|
55
|
-
? getEnv("HTTPS_PROXY", "https_proxy", "ALL_PROXY", "all_proxy")
|
|
56
|
-
: getEnv("HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy");
|
|
66
|
+
const proxyUrl = resolveProxyUrl(targetUrl);
|
|
57
67
|
if (proxyUrl === undefined) {
|
|
58
68
|
return undefined;
|
|
59
69
|
}
|
|
@@ -63,3 +73,25 @@ export const getProxyTransport = (targetUrl) => {
|
|
|
63
73
|
proxy: false,
|
|
64
74
|
};
|
|
65
75
|
};
|
|
76
|
+
/**
|
|
77
|
+
* Build a `fetch` implementation that tunnels through an HTTP/HTTPS proxy
|
|
78
|
+
* when one is configured via the same env vars as {@link getProxyTransport}.
|
|
79
|
+
*
|
|
80
|
+
* Node's built-in `fetch` (and therefore MCP's `StreamableHTTPClientTransport`)
|
|
81
|
+
* does not honor `HTTPS_PROXY` / `HTTP_PROXY` on its own. Callers that need
|
|
82
|
+
* proxied egress should pass the result as the transport's `fetch` option.
|
|
83
|
+
*
|
|
84
|
+
* Returns `undefined` when no proxy applies, so the caller can keep the
|
|
85
|
+
* default global `fetch`.
|
|
86
|
+
*/
|
|
87
|
+
export const getProxyFetch = (targetUrl) => {
|
|
88
|
+
const proxyUrl = resolveProxyUrl(targetUrl);
|
|
89
|
+
if (proxyUrl === undefined) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
const agent = new ProxyAgent(proxyUrl);
|
|
93
|
+
return (url, init) => undiciFetch(url, {
|
|
94
|
+
...init,
|
|
95
|
+
dispatcher: agent,
|
|
96
|
+
});
|
|
97
|
+
};
|
|
@@ -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.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"private": false,
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"description": "Command-line interface for the Cargo API",
|
|
@@ -31,14 +31,16 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@cargo-ai/api": "^1.0.40",
|
|
34
|
-
"@cargo-ai/
|
|
35
|
-
"@cargo-ai/app-sdk": "^1.0.3",
|
|
34
|
+
"@cargo-ai/app-sdk": "^1.0.5",
|
|
36
35
|
"@cargo-ai/cdk": "*",
|
|
36
|
+
"@cargo-ai/types": "*",
|
|
37
37
|
"@cargo-ai/worker-sdk": "^1.0.6",
|
|
38
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
38
39
|
"commander": "^12.1.0",
|
|
39
40
|
"http-proxy-agent": "^9.1.0",
|
|
40
41
|
"https-proxy-agent": "^9.1.0",
|
|
41
|
-
"tsx": "^4.19.2"
|
|
42
|
+
"tsx": "^4.19.2",
|
|
43
|
+
"undici": "^7.24.8"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@cargo-ai/eslint-config": "*",
|