@cargo-ai/cli 1.0.44 → 1.0.46
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 +50 -10
- package/build/api.d.ts +2 -1
- package/build/api.d.ts.map +1 -1
- package/build/api.js +5 -1
- package/build/commands/auth/auth0.d.ts +33 -0
- package/build/commands/auth/auth0.d.ts.map +1 -0
- package/build/commands/auth/auth0.js +90 -0
- package/build/commands/auth/email.d.ts +16 -0
- package/build/commands/auth/email.d.ts.map +1 -0
- package/build/commands/auth/email.js +93 -0
- package/build/commands/auth/index.d.ts +19 -1
- package/build/commands/auth/index.d.ts.map +1 -1
- package/build/commands/auth/index.js +121 -59
- package/build/commands/auth/loginChannel.d.ts +21 -0
- package/build/commands/auth/loginChannel.d.ts.map +1 -0
- package/build/commands/auth/loginChannel.js +42 -0
- package/build/commands/auth/oauth.d.ts +2 -6
- package/build/commands/auth/oauth.d.ts.map +1 -1
- package/build/commands/auth/oauth.js +54 -91
- package/build/commands/auth/passwordless.d.ts +22 -0
- package/build/commands/auth/passwordless.d.ts.map +1 -0
- package/build/commands/auth/passwordless.js +70 -0
- package/build/commands/auth/session.d.ts +21 -0
- package/build/commands/auth/session.d.ts.map +1 -0
- package/build/commands/auth/session.js +55 -0
- package/build/commands/auth/workspace.d.ts +9 -1
- package/build/commands/auth/workspace.d.ts.map +1 -1
- package/build/commands/auth/workspace.js +42 -69
- package/build/commands/billing/addPaymentMethod.d.ts +26 -0
- package/build/commands/billing/addPaymentMethod.d.ts.map +1 -0
- package/build/commands/billing/addPaymentMethod.js +57 -0
- package/build/commands/billing/subscription.d.ts.map +1 -1
- package/build/commands/billing/subscription.js +28 -1
- package/build/commands/doctor.js +3 -3
- package/build/commands/mcp.d.ts.map +1 -1
- package/build/commands/mcp.js +35 -8
- package/build/commands/orchestration/action.d.ts.map +1 -1
- package/build/commands/orchestration/action.js +11 -1
- package/build/commands/orchestration/index.d.ts.map +1 -1
- package/build/commands/orchestration/index.js +6 -1
- package/build/commands/orchestration/node.d.ts.map +1 -1
- package/build/commands/orchestration/node.js +41 -1
- package/build/commands/runHandler.d.ts +6 -0
- package/build/commands/runHandler.d.ts.map +1 -1
- package/build/commands/runHandler.js +34 -4
- package/build/config.d.ts +22 -1
- package/build/config.d.ts.map +1 -1
- package/build/config.js +44 -3
- package/build/credentials.d.ts +1 -10
- package/build/credentials.d.ts.map +1 -1
- package/build/credentials.js +4 -34
- package/build/index.js +14 -7
- package/build/oauthConfig.d.ts +1 -6
- package/build/oauthConfig.d.ts.map +1 -1
- package/build/oauthConfig.js +9 -14
- package/build/utils/openBrowser.d.ts +7 -0
- package/build/utils/openBrowser.d.ts.map +1 -0
- package/build/utils/openBrowser.js +29 -0
- package/build/utils/prompt.d.ts +12 -0
- package/build/utils/prompt.d.ts.map +1 -0
- package/build/utils/prompt.js +38 -0
- package/package.json +4 -2
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { openBrowser } from "../../utils/openBrowser.js";
|
|
2
|
+
import { failWith, handleApiCall, info, outputJson, sleep, success, } from "../runHandler.js";
|
|
3
|
+
export const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
4
|
+
export const DEFAULT_POLL_INTERVAL_SECONDS = 3;
|
|
5
|
+
/**
|
|
6
|
+
* Hands card entry off to Stripe's hosted billing portal and waits for the card
|
|
7
|
+
* on file to change.
|
|
8
|
+
*
|
|
9
|
+
* The card never touches this process: the CLI only ever sees the last four
|
|
10
|
+
* digits that the API already exposes. Polling is what makes the handoff usable
|
|
11
|
+
* from a script — the caller gets a definite answer instead of having to guess
|
|
12
|
+
* whether the human finished.
|
|
13
|
+
*/
|
|
14
|
+
export async function runAddPaymentMethod(api, opts) {
|
|
15
|
+
const before = await handleApiCall(() => api.billing.subscription.getCreditCard());
|
|
16
|
+
const { portalSession } = await handleApiCall(() => api.billing.subscription.createPortalSession());
|
|
17
|
+
info("");
|
|
18
|
+
info("To add or replace your card, open this URL in a browser:");
|
|
19
|
+
info(` ${portalSession.url}`);
|
|
20
|
+
info("");
|
|
21
|
+
info("Waiting for the card on file to change...");
|
|
22
|
+
info("");
|
|
23
|
+
if (opts.open === true) {
|
|
24
|
+
openBrowser(portalSession.url);
|
|
25
|
+
}
|
|
26
|
+
const deadline = Date.now() + opts.timeout * 1000;
|
|
27
|
+
const beforeFingerprint = fingerprintCard(before.creditCard);
|
|
28
|
+
while (Date.now() < deadline) {
|
|
29
|
+
await sleep(opts.pollInterval * 1000);
|
|
30
|
+
const current = await handleApiCall(() => api.billing.subscription.getCreditCard(), { spinner: false });
|
|
31
|
+
if (fingerprintCard(current.creditCard) !== beforeFingerprint) {
|
|
32
|
+
const { creditCard } = current;
|
|
33
|
+
success(creditCard !== undefined
|
|
34
|
+
? `Payment method updated: ${creditCard.brand} ending ${creditCard.last4}.`
|
|
35
|
+
: "Payment method removed.");
|
|
36
|
+
outputJson({ ok: true, status: "updated", creditCard });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
failWith(`Timed out after ${String(opts.timeout)}s waiting for the card to change. If you finished in the browser, check with: cargo-ai billing subscription get-credit-card`);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stripe never exposes a raw card number here, so a change is detected from the
|
|
44
|
+
* fields the API does return. Replacing a card with the very same one is
|
|
45
|
+
* therefore indistinguishable from doing nothing, and the command times out.
|
|
46
|
+
*/
|
|
47
|
+
export function fingerprintCard(card) {
|
|
48
|
+
if (card === undefined) {
|
|
49
|
+
return "none";
|
|
50
|
+
}
|
|
51
|
+
return [
|
|
52
|
+
card.brand,
|
|
53
|
+
card.last4,
|
|
54
|
+
String(card.expMonth),
|
|
55
|
+
String(card.expYear),
|
|
56
|
+
].join("|");
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../src/commands/billing/subscription.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":"subscription.d.ts","sourceRoot":"","sources":["../../../src/commands/billing/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAQxC,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAqSN"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { handleApiCall, outputJson } from "../runHandler.js";
|
|
1
|
+
import { handleApiCall, outputJson, parsePositiveInt } from "../runHandler.js";
|
|
2
|
+
import { DEFAULT_POLL_INTERVAL_SECONDS, DEFAULT_TIMEOUT_SECONDS, runAddPaymentMethod, } from "./addPaymentMethod.js";
|
|
2
3
|
export function registerSubscriptionCommands(parent, getApi) {
|
|
3
4
|
const subscription = parent
|
|
4
5
|
.command("subscription")
|
|
@@ -49,6 +50,32 @@ Example:
|
|
|
49
50
|
const result = await handleApiCall(() => api.billing.subscription.createSetupIntent());
|
|
50
51
|
outputJson(result);
|
|
51
52
|
});
|
|
53
|
+
subscription
|
|
54
|
+
.command("add-payment-method")
|
|
55
|
+
.description("Add or replace the card on file via Stripe's hosted portal, then wait for it to land")
|
|
56
|
+
.option("--timeout <seconds>", `How long to wait for the card to change (default: ${String(DEFAULT_TIMEOUT_SECONDS)})`)
|
|
57
|
+
.option("--poll-interval <seconds>", `How often to check for the new card (default: ${String(DEFAULT_POLL_INTERVAL_SECONDS)})`)
|
|
58
|
+
.option("--no-open", "Print the URL without launching a browser")
|
|
59
|
+
.addHelpText("after", `
|
|
60
|
+
Examples:
|
|
61
|
+
$ cargo-ai billing subscription add-payment-method
|
|
62
|
+
$ cargo-ai billing subscription add-payment-method --no-open --timeout 600
|
|
63
|
+
|
|
64
|
+
Card details are entered on Stripe's hosted portal and never pass through this
|
|
65
|
+
CLI. The URL is printed as well as opened, so this works over SSH and in
|
|
66
|
+
sandboxes — relay the link and the command will detect the new card itself.`)
|
|
67
|
+
.action(async (opts) => {
|
|
68
|
+
const api = getApi();
|
|
69
|
+
await runAddPaymentMethod(api, {
|
|
70
|
+
timeout: opts.timeout !== undefined
|
|
71
|
+
? parsePositiveInt(opts.timeout, "--timeout")
|
|
72
|
+
: DEFAULT_TIMEOUT_SECONDS,
|
|
73
|
+
pollInterval: opts.pollInterval !== undefined
|
|
74
|
+
? parsePositiveInt(opts.pollInterval, "--poll-interval")
|
|
75
|
+
: DEFAULT_POLL_INTERVAL_SECONDS,
|
|
76
|
+
open: opts.open,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
52
79
|
subscription
|
|
53
80
|
.command("update-payment-method")
|
|
54
81
|
.description("Replace the workspace's payment method")
|
package/build/commands/doctor.js
CHANGED
|
@@ -55,13 +55,13 @@ function buildSkillsPinCheck(currentVersion) {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
async function checkApi(config) {
|
|
58
|
-
if (config.
|
|
58
|
+
if (config.getAccessToken === undefined) {
|
|
59
59
|
return {
|
|
60
60
|
check: {
|
|
61
61
|
ok: false,
|
|
62
62
|
error: {
|
|
63
63
|
status: "no-credentials",
|
|
64
|
-
message: 'Not authenticated. Run "cargo-ai login --
|
|
64
|
+
message: 'Not authenticated. Run "cargo-ai login --email <email>" (no browser needed) or "cargo-ai login --oauth".',
|
|
65
65
|
},
|
|
66
66
|
},
|
|
67
67
|
exitCode: ExitCodes.NotAuthenticated,
|
|
@@ -69,7 +69,7 @@ async function checkApi(config) {
|
|
|
69
69
|
}
|
|
70
70
|
const client = createApi({
|
|
71
71
|
baseUrl: config.baseUrl,
|
|
72
|
-
|
|
72
|
+
getAccessToken: config.getAccessToken,
|
|
73
73
|
workspaceUuid: config.workspaceUuid,
|
|
74
74
|
});
|
|
75
75
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAOA,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,CAoD3E"}
|
package/build/commands/mcp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
+
import { SessionExpiredError } from "@cargo-ai/cdk/cli";
|
|
2
3
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
5
|
import { getConfig } from "../config.js";
|
|
@@ -32,14 +33,13 @@ Environment:
|
|
|
32
33
|
Reads auth from the same place as every other command (CARGO_API_TOKEN or the
|
|
33
34
|
credentials file). stdout carries the MCP protocol; all logs go to stderr.`)
|
|
34
35
|
.action(async (opts) => {
|
|
35
|
-
const {
|
|
36
|
-
if (
|
|
37
|
-
failWith('Not authenticated. Run "cargo-ai login --
|
|
36
|
+
const { getAccessToken, baseUrl, workspaceUuid } = getConfig();
|
|
37
|
+
if (getAccessToken === undefined) {
|
|
38
|
+
failWith('Not authenticated. Run "cargo-ai login --email <email>" (no browser needed) or "cargo-ai login --oauth".', { code: ExitCodes.NotAuthenticated });
|
|
38
39
|
}
|
|
39
40
|
const serverUuid = await resolveServerUuid(opts.server, getApi);
|
|
40
41
|
const endpoint = resolveMcpEndpoint(baseUrl, serverUuid);
|
|
41
42
|
const headers = {
|
|
42
|
-
authorization: `Bearer ${accessToken}`,
|
|
43
43
|
"x-cargo-origin": "cli",
|
|
44
44
|
"x-cargo-origin-version": version,
|
|
45
45
|
};
|
|
@@ -47,7 +47,7 @@ credentials file). stdout carries the MCP protocol; all logs go to stderr.`)
|
|
|
47
47
|
headers["selected-workspace-uuid"] = workspaceUuid;
|
|
48
48
|
}
|
|
49
49
|
info(`Cargo MCP bridge -> ${endpoint}`);
|
|
50
|
-
await runBridge(endpoint, headers);
|
|
50
|
+
await runBridge(endpoint, headers, getAccessToken);
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
const resolveServerUuid = async (explicit, getApi) => {
|
|
@@ -101,13 +101,40 @@ const mapApiHostToMcp = (baseUrl) => {
|
|
|
101
101
|
// MCP client) and the remote streamable-HTTP client (facing Cargo). Forwarding raw
|
|
102
102
|
// JSON-RPC keeps the bridge transparent — tools, resources, and long-running-tool
|
|
103
103
|
// progress notifications all pass through without enumerating capabilities here.
|
|
104
|
-
const runBridge = async (endpoint, headers) => {
|
|
104
|
+
const runBridge = async (endpoint, headers, getAccessToken) => {
|
|
105
105
|
// Prefer a proxied fetch when HTTPS_PROXY/HTTP_PROXY applies — Node's
|
|
106
106
|
// built-in fetch ignores those env vars, so without this the bridge would
|
|
107
107
|
// fail in proxied environments even though server discovery (Axios) works.
|
|
108
|
+
const proxyFetch = getProxyFetch(endpoint);
|
|
109
|
+
// The transport reports every failure as an error of its own making, so the
|
|
110
|
+
// reason a request could not be sent is lost by the time the bridge tears
|
|
111
|
+
// down. Hold on to an expired session here: it is the one failure the user
|
|
112
|
+
// can act on, and it has to keep its exit code.
|
|
113
|
+
let sessionExpired;
|
|
114
|
+
// A bridge stays open far longer than an access token lives, so the
|
|
115
|
+
// Authorization header is rebuilt per request instead of being captured once
|
|
116
|
+
// in `requestInit` — that is what lets the session renew mid-session.
|
|
117
|
+
const authenticatedFetch = async (url, init) => {
|
|
118
|
+
let accessToken;
|
|
119
|
+
try {
|
|
120
|
+
accessToken = await getAccessToken();
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof SessionExpiredError) {
|
|
124
|
+
sessionExpired = error;
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
const merged = new Headers(init !== undefined ? init.headers : undefined);
|
|
129
|
+
merged.set("authorization", `Bearer ${accessToken}`);
|
|
130
|
+
const request = { ...init, headers: merged };
|
|
131
|
+
return proxyFetch !== undefined
|
|
132
|
+
? proxyFetch(url, request)
|
|
133
|
+
: fetch(url, request);
|
|
134
|
+
};
|
|
108
135
|
const remote = new StreamableHTTPClientTransport(new URL(endpoint), {
|
|
109
136
|
requestInit: { headers },
|
|
110
|
-
fetch:
|
|
137
|
+
fetch: authenticatedFetch,
|
|
111
138
|
});
|
|
112
139
|
const local = new StdioServerTransport();
|
|
113
140
|
await new Promise((resolve, reject) => {
|
|
@@ -147,7 +174,7 @@ const runBridge = async (endpoint, headers) => {
|
|
|
147
174
|
return;
|
|
148
175
|
state.closed = true;
|
|
149
176
|
teardown();
|
|
150
|
-
reject(new Error(message));
|
|
177
|
+
reject(sessionExpired !== undefined ? sessionExpired : new Error(message));
|
|
151
178
|
};
|
|
152
179
|
// Forward raw JSON-RPC both ways. A failing transport reports the reason via
|
|
153
180
|
// onerror (which fires before the send rejection), so the reason is logged
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAaxC,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,
|
|
1
|
+
{"version":3,"file":"action.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/action.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAaxC,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA4LN"}
|
|
@@ -2,7 +2,17 @@ import { applyRecordsLimit, ExitCodes, failWith, handleApiCall, outputJson, pars
|
|
|
2
2
|
export function registerActionCommands(parent, getApi) {
|
|
3
3
|
const action = parent
|
|
4
4
|
.command("action")
|
|
5
|
-
.description("Execute actions (single run or batch)")
|
|
5
|
+
.description("Execute actions (single run or batch)")
|
|
6
|
+
.addHelpText("after", `
|
|
7
|
+
The default way to run a tool or connector action: no workflow, release, or node
|
|
8
|
+
graph needed. Creates a run and consumes credits.
|
|
9
|
+
|
|
10
|
+
- 'config' inside --action must be empty ({}); inputs go in --data.
|
|
11
|
+
- The run is asynchronous: pass --wait-until-finished, or poll it with
|
|
12
|
+
'cargo-ai orchestration run get <uuid>'.
|
|
13
|
+
|
|
14
|
+
To debug a node inside an existing workflow instead, use
|
|
15
|
+
'cargo-ai orchestration node'.`);
|
|
6
16
|
action
|
|
7
17
|
.command("execute")
|
|
8
18
|
.description("Execute a single action")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAexC,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAexC,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA0BN"}
|
|
@@ -14,7 +14,12 @@ import { registerWorkflowCommands } from "./workflow.js";
|
|
|
14
14
|
export function registerOrchestrationCommands(parent, getApi) {
|
|
15
15
|
const orchestration = parent
|
|
16
16
|
.command("orchestration")
|
|
17
|
-
.description("Workflows, plays, runs, batches, tools, templates")
|
|
17
|
+
.description("Workflows, plays, runs, batches, tools, templates")
|
|
18
|
+
.addHelpText("after", `
|
|
19
|
+
Running vs. debugging:
|
|
20
|
+
action Run a tool or connector action — no workflow needed. The usual choice.
|
|
21
|
+
node Debug one node of an existing workflow ('node compute' is free,
|
|
22
|
+
'node execute' really runs it and consumes credits).`);
|
|
18
23
|
registerActionCommands(orchestration, getApi);
|
|
19
24
|
registerWorkflowCommands(orchestration, getApi);
|
|
20
25
|
registerPlayCommands(orchestration, getApi);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/node.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CA8J7E"}
|
|
@@ -2,13 +2,33 @@ import { handleApiCall, outputJson, parseJson } from "../runHandler.js";
|
|
|
2
2
|
export function registerNodeCommands(parent, getApi) {
|
|
3
3
|
const node = parent
|
|
4
4
|
.command("node")
|
|
5
|
-
.description("
|
|
5
|
+
.description("Debug a workflow one node at a time: compute a node's config, validate node definitions, execute a single node")
|
|
6
|
+
.addHelpText("after", `
|
|
7
|
+
Use these commands to debug one node of an existing workflow release. To just
|
|
8
|
+
run a tool or connector action, no workflow needed, use instead:
|
|
9
|
+
$ cargo-ai orchestration action execute --action '<json>' --data '<json>'
|
|
10
|
+
|
|
11
|
+
'compute' and 'validate' are free and run nothing. 'execute' is not a dry run:
|
|
12
|
+
it performs the node's real action and consumes credits.
|
|
13
|
+
|
|
14
|
+
Debugging loop:
|
|
15
|
+
$ cargo-ai orchestration span list --run-uuid <uuid> --execution-started-after <iso-date>
|
|
16
|
+
$ cargo-ai orchestration release get-deployed --workflow-uuid <uuid>
|
|
17
|
+
$ cargo-ai orchestration node compute --node '<json>' --context '<json>'
|
|
18
|
+
$ cargo-ai orchestration node execute --workflow-uuid <uuid> --release-uuid <uuid> --node '<json>' --computed-config '<json>' --context '<json>'`);
|
|
6
19
|
node
|
|
7
20
|
.command("compute")
|
|
8
21
|
.description("Compute a node's configuration from its definition and context")
|
|
9
22
|
.requiredOption("--node <json>", "Node definition (JSON object, required)")
|
|
10
23
|
.requiredOption("--context <json>", "Execution context (JSON object, required)")
|
|
11
24
|
.option("--group-context <json>", "Group context for grouped nodes (JSON object)")
|
|
25
|
+
.addHelpText("after", `
|
|
26
|
+
Free, runs nothing: resolves the node's template expressions against --context,
|
|
27
|
+
so you can see a node's inputs before spending credits on it. Returns
|
|
28
|
+
{"computedConfig":{...}} — pass that inner object to 'node execute'.
|
|
29
|
+
|
|
30
|
+
Examples:
|
|
31
|
+
$ cargo-ai orchestration node compute --node '{"uuid":"...","slug":"enrich","kind":"connector","integrationSlug":"clearbit","actionSlug":"company_enrich","connectorUuid":"...","config":{"domain":{"kind":"templateExpression","expression":"{{nodes.start.domain}}","instructTo":"none","fromRecipe":false}}}' --context '{"nodes":{"start":{"domain":"acme.com"}}}'`)
|
|
12
32
|
.action(async (opts) => {
|
|
13
33
|
const api = getApi();
|
|
14
34
|
const result = await handleApiCall(() => api.orchestration.node.compute({
|
|
@@ -24,6 +44,13 @@ export function registerNodeCommands(parent, getApi) {
|
|
|
24
44
|
.command("validate")
|
|
25
45
|
.description("Validate node definitions for correctness")
|
|
26
46
|
.requiredOption("--nodes <json>", "Node definitions to validate (JSON array, required)")
|
|
47
|
+
.addHelpText("after", `
|
|
48
|
+
Free, runs nothing: checks that a node graph is well-formed. Takes the whole
|
|
49
|
+
graph as a JSON array, not a single node. Returns {"outcome":"valid"} or
|
|
50
|
+
{"outcome":"notValid","invalidNodes":[...]}.
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
$ cargo-ai orchestration node validate --nodes '[{"uuid":"00000000-0001-0000-0000-000000000000","slug":"start","kind":"native","actionSlug":"start","config":{},"childrenUuids":[],"fallbackOnFailure":false,"position":{"x":0,"y":0}}]'`)
|
|
27
54
|
.action(async (opts) => {
|
|
28
55
|
const api = getApi();
|
|
29
56
|
const result = await handleApiCall(() => api.orchestration.node.validate({
|
|
@@ -40,6 +67,19 @@ export function registerNodeCommands(parent, getApi) {
|
|
|
40
67
|
.requiredOption("--computed-config <json>", "Computed configuration from 'node compute' (JSON object, required)")
|
|
41
68
|
.requiredOption("--context <json>", "Execution context (JSON object, required)")
|
|
42
69
|
.option("--group-context <json>", "Group context for grouped nodes (JSON object)")
|
|
70
|
+
.addHelpText("after", `
|
|
71
|
+
Not a dry run: performs the node's real action and consumes credits. The node
|
|
72
|
+
must belong to the given release — to run an arbitrary action with no workflow,
|
|
73
|
+
use 'cargo-ai orchestration action execute' instead.
|
|
74
|
+
|
|
75
|
+
Get --workflow-uuid, --release-uuid and --node from 'release get-deployed', and
|
|
76
|
+
--computed-config from the 'computedConfig' of 'node compute'.
|
|
77
|
+
|
|
78
|
+
Returns {"status":"success","data":{...},"creditsUsedCount":N}, or
|
|
79
|
+
{"status":"pending","runUuid":"..."} — poll that with 'orchestration run get'.
|
|
80
|
+
|
|
81
|
+
Examples:
|
|
82
|
+
$ cargo-ai orchestration node execute --workflow-uuid <uuid> --release-uuid <uuid> --node '<json>' --computed-config '{"domain":"acme.com"}' --context '{"nodes":{"start":{"domain":"acme.com"}}}'`)
|
|
43
83
|
.action(async (opts) => {
|
|
44
84
|
const api = getApi();
|
|
45
85
|
const result = await handleApiCall(() => api.orchestration.node.execute({
|
|
@@ -39,8 +39,14 @@ export declare function applyRecordsLimit<T>(records: T[], limit: number): {
|
|
|
39
39
|
};
|
|
40
40
|
type HandleApiCallOpts = {
|
|
41
41
|
spinner?: string | false;
|
|
42
|
+
/**
|
|
43
|
+
* Replaces the default "run cargo-ai login" advice on a 401 — which reads as
|
|
44
|
+
* a loop when the failing call is itself part of signing in.
|
|
45
|
+
*/
|
|
46
|
+
notAuthenticatedHint?: string;
|
|
42
47
|
};
|
|
43
48
|
export declare function handleApiCall<T>(fn: () => Promise<T>, opts?: HandleApiCallOpts): Promise<T>;
|
|
49
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
44
50
|
export declare function pollRunUntilFinished<T>(getResult: () => Promise<T>, getStatus: (result: T) => string, intervalMs: number): Promise<T>;
|
|
45
51
|
export declare function pollBatchUntilFinished<T>(getResult: () => Promise<T>, getStatus: (result: T) => string, intervalMs: number): Promise<T>;
|
|
46
52
|
export declare function pollMessageUntilFinished<T>(getResult: () => Promise<T>, getFinishedAt: (result: T) => string | null | undefined, intervalMs: number): Promise<T>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runHandler.d.ts","sourceRoot":"","sources":["../../src/commands/runHandler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,SAAS;;;;;;;CAOZ,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAclE,eAAO,MAAM,MAAM;aACR,MAAM,KAAG,MAAM;eACb,MAAM,KAAG,MAAM;gBACd,MAAM,KAAG,MAAM;cACjB,MAAM,KAAG,MAAM;aAChB,MAAM,KAAG,MAAM;cACd,MAAM,KAAG,MAAM;CAC1B,CAAC;AAEF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAM/C;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAID,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUhE;AAED,KAAK,YAAY,GAAG;IAClB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY,GAAG,KAAK,
|
|
1
|
+
{"version":3,"file":"runHandler.d.ts","sourceRoot":"","sources":["../../src/commands/runHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -57,6 +57,12 @@ export function failWith(message, opts) {
|
|
|
57
57
|
? opts.code
|
|
58
58
|
: ExitCodes.GenericError;
|
|
59
59
|
const extra = opts !== undefined ? opts.extra : undefined;
|
|
60
|
+
// This exits the process, so a `finally { spinner.stop() }` around the failing
|
|
61
|
+
// call never runs. Stop it here or the error prints onto a spinning line and
|
|
62
|
+
// the terminal is left with a hidden cursor.
|
|
63
|
+
if (activeSpinner !== undefined) {
|
|
64
|
+
activeSpinner.stop();
|
|
65
|
+
}
|
|
60
66
|
if (process.stderr.isTTY === true) {
|
|
61
67
|
process.stderr.write(`${colors.red("✗")} ${message}\n`);
|
|
62
68
|
if (extra !== undefined) {
|
|
@@ -77,7 +83,28 @@ export function failWith(message, opts) {
|
|
|
77
83
|
}
|
|
78
84
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
79
85
|
const SPINNER_INTERVAL_MS = 80;
|
|
86
|
+
let activeSpinner;
|
|
80
87
|
export function startSpinner(message) {
|
|
88
|
+
// Only one spinner can own the line, so retire any predecessor: otherwise its
|
|
89
|
+
// interval keeps redrawing underneath this one and never gets cleared.
|
|
90
|
+
if (activeSpinner !== undefined) {
|
|
91
|
+
activeSpinner.stop();
|
|
92
|
+
}
|
|
93
|
+
const spinner = createSpinner(message);
|
|
94
|
+
const tracked = {
|
|
95
|
+
update: spinner.update,
|
|
96
|
+
log: spinner.log,
|
|
97
|
+
stop: () => {
|
|
98
|
+
if (activeSpinner === tracked) {
|
|
99
|
+
activeSpinner = undefined;
|
|
100
|
+
}
|
|
101
|
+
spinner.stop();
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
activeSpinner = tracked;
|
|
105
|
+
return tracked;
|
|
106
|
+
}
|
|
107
|
+
function createSpinner(message) {
|
|
81
108
|
if (process.stderr.isTTY !== true) {
|
|
82
109
|
// Non-TTY (CI, piped): no animation — just emit each line so progress is
|
|
83
110
|
// still visible in logs.
|
|
@@ -182,7 +209,7 @@ export async function handleApiCall(fn, opts) {
|
|
|
182
209
|
const parsedError = error;
|
|
183
210
|
const status = parsedError.status;
|
|
184
211
|
const body = parsedError.body !== undefined ? parsedError.body : String(error);
|
|
185
|
-
const { message, code } = describeApiError(status, body);
|
|
212
|
+
const { message, code } = describeApiError(status, body, opts !== undefined ? opts.notAuthenticatedHint : undefined);
|
|
186
213
|
failWith(message, {
|
|
187
214
|
code,
|
|
188
215
|
extra: { status: status !== undefined ? status : "unknown", body },
|
|
@@ -191,12 +218,15 @@ export async function handleApiCall(fn, opts) {
|
|
|
191
218
|
throw error;
|
|
192
219
|
}
|
|
193
220
|
}
|
|
194
|
-
function describeApiError(status, body) {
|
|
221
|
+
function describeApiError(status, body, notAuthenticatedHint) {
|
|
195
222
|
const detail = extractApiErrorDetail(body);
|
|
196
223
|
const detailSuffix = detail !== undefined ? `: ${detail}` : "";
|
|
197
224
|
if (status === 401) {
|
|
225
|
+
const hint = notAuthenticatedHint !== undefined
|
|
226
|
+
? notAuthenticatedHint
|
|
227
|
+
: "Run `cargo-ai login` to refresh credentials.";
|
|
198
228
|
return {
|
|
199
|
-
message: `Not authenticated${detailSuffix}.
|
|
229
|
+
message: `Not authenticated${detailSuffix}. ${hint}`,
|
|
200
230
|
code: ExitCodes.NotAuthenticated,
|
|
201
231
|
};
|
|
202
232
|
}
|
|
@@ -247,7 +277,7 @@ function extractApiErrorDetail(body) {
|
|
|
247
277
|
}
|
|
248
278
|
return undefined;
|
|
249
279
|
}
|
|
250
|
-
function sleep(ms) {
|
|
280
|
+
export function sleep(ms) {
|
|
251
281
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
252
282
|
}
|
|
253
283
|
const TERMINAL_RUN_STATUSES = new Set([
|
package/build/config.d.ts
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
import type { AccessTokenProvider } from "@cargo-ai/cdk/cli";
|
|
1
2
|
export type ConfigSource = "environment" | "credentials-file" | "none";
|
|
2
3
|
export type Config = {
|
|
3
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Resolves the token for each request, renewing the session first when it is
|
|
6
|
+
* about to expire. `undefined` means no credential at all.
|
|
7
|
+
*/
|
|
8
|
+
getAccessToken: AccessTokenProvider | undefined;
|
|
4
9
|
baseUrl: string;
|
|
5
10
|
workspaceUuid: string | undefined;
|
|
6
11
|
source: ConfigSource;
|
|
7
12
|
};
|
|
8
13
|
export declare function getConfig(): Config;
|
|
14
|
+
/**
|
|
15
|
+
* An API token is bound to one workspace by the server, which derives the
|
|
16
|
+
* workspace from the token and ignores the one we send alongside it. Pointing
|
|
17
|
+
* CARGO_WORKSPACE_UUID somewhere else would therefore run against the token's
|
|
18
|
+
* workspace while looking like it worked, so refuse instead of silently
|
|
19
|
+
* touching the wrong data. A signed-in session carries no such binding: the
|
|
20
|
+
* server honours the workspace header, so the override just works.
|
|
21
|
+
*
|
|
22
|
+
* Exported for tests; `getConfig` is the only production caller.
|
|
23
|
+
*/
|
|
24
|
+
export declare function describeWorkspaceOverrideConflict(opts: {
|
|
25
|
+
isWorkspaceBoundToken: boolean;
|
|
26
|
+
tokenSource: ConfigSource;
|
|
27
|
+
credentialsWorkspaceUuid: string | undefined;
|
|
28
|
+
requestedWorkspaceUuid: string | undefined;
|
|
29
|
+
}): string | undefined;
|
|
9
30
|
//# sourceMappingURL=config.d.ts.map
|
package/build/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAO7D,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAEvE,MAAM,MAAM,MAAM,GAAG;IACnB;;;OAGG;IACH,cAAc,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,wBAAgB,SAAS,IAAI,MAAM,CA0BlC;AAED;;;;;;;;;GASG;AACH,wBAAgB,iCAAiC,CAAC,IAAI,EAAE;IACtD,qBAAqB,EAAE,OAAO,CAAC;IAC/B,WAAW,EAAE,YAAY,CAAC;IAC1B,wBAAwB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C,GAAG,MAAM,GAAG,SAAS,CA0BrB"}
|
package/build/config.js
CHANGED
|
@@ -1,15 +1,56 @@
|
|
|
1
|
-
import { loadProjectEnv,
|
|
1
|
+
import { loadProjectEnv, resolveAuth } from "@cargo-ai/cdk/cli";
|
|
2
|
+
import { ExitCodes, failWith } from "./commands/runHandler.js";
|
|
2
3
|
import { loadCredentials } from "./credentials.js";
|
|
3
4
|
export function getConfig() {
|
|
4
5
|
// Fold project `.env` into the environment first (never overriding explicit
|
|
5
6
|
// exports): a repo pinned to one workspace must win over a personal login.
|
|
6
7
|
loadProjectEnv();
|
|
7
8
|
const credentials = loadCredentials();
|
|
8
|
-
const resolved =
|
|
9
|
+
const resolved = resolveAuth(process.env, credentials);
|
|
10
|
+
const conflict = describeWorkspaceOverrideConflict({
|
|
11
|
+
isWorkspaceBoundToken: resolved.isWorkspaceBoundToken,
|
|
12
|
+
tokenSource: resolved.tokenSource,
|
|
13
|
+
credentialsWorkspaceUuid: credentials !== undefined ? credentials.workspaceUuid : undefined,
|
|
14
|
+
requestedWorkspaceUuid: resolved.workspaceUuid,
|
|
15
|
+
});
|
|
16
|
+
if (conflict !== undefined) {
|
|
17
|
+
failWith(conflict, { code: ExitCodes.InvalidUsage });
|
|
18
|
+
}
|
|
9
19
|
return {
|
|
10
|
-
|
|
20
|
+
getAccessToken: resolved.getAccessToken,
|
|
11
21
|
baseUrl: resolved.baseUrl,
|
|
12
22
|
workspaceUuid: resolved.workspaceUuid,
|
|
13
23
|
source: resolved.tokenSource,
|
|
14
24
|
};
|
|
15
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* An API token is bound to one workspace by the server, which derives the
|
|
28
|
+
* workspace from the token and ignores the one we send alongside it. Pointing
|
|
29
|
+
* CARGO_WORKSPACE_UUID somewhere else would therefore run against the token's
|
|
30
|
+
* workspace while looking like it worked, so refuse instead of silently
|
|
31
|
+
* touching the wrong data. A signed-in session carries no such binding: the
|
|
32
|
+
* server honours the workspace header, so the override just works.
|
|
33
|
+
*
|
|
34
|
+
* Exported for tests; `getConfig` is the only production caller.
|
|
35
|
+
*/
|
|
36
|
+
export function describeWorkspaceOverrideConflict(opts) {
|
|
37
|
+
if (opts.isWorkspaceBoundToken === false) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (opts.tokenSource !== "credentials-file") {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
if (opts.credentialsWorkspaceUuid === undefined ||
|
|
44
|
+
opts.requestedWorkspaceUuid === undefined) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (opts.requestedWorkspaceUuid === opts.credentialsWorkspaceUuid) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
return [
|
|
51
|
+
`Workspace ${opts.requestedWorkspaceUuid} was requested, but the saved credential is an API token scoped to workspace ${opts.credentialsWorkspaceUuid}.`,
|
|
52
|
+
"The server takes the workspace from the token, so the request would silently run against the token's workspace.",
|
|
53
|
+
`Sign in for that workspace instead: cargo-ai login --email <email> --workspace-uuid ${opts.requestedWorkspaceUuid}`,
|
|
54
|
+
"Or unset CARGO_WORKSPACE_UUID to use the workspace the saved token belongs to.",
|
|
55
|
+
].join(" ");
|
|
56
|
+
}
|
package/build/credentials.d.ts
CHANGED
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
export type Credentials
|
|
2
|
-
accessToken: string;
|
|
3
|
-
workspaceUuid?: string;
|
|
4
|
-
baseUrl?: string;
|
|
5
|
-
};
|
|
6
|
-
export declare function loadCredentials(): Credentials | undefined;
|
|
7
|
-
export declare function saveCredentials(creds: Credentials): void;
|
|
8
|
-
export declare function clearCredentials(): boolean;
|
|
9
|
-
export declare function getCredentialsPath(): string;
|
|
10
|
-
export declare function getConfigDir(): string;
|
|
1
|
+
export { clearCredentials, type StoredCredentials as Credentials, getConfigDir, getCredentialsPath, loadCredentials, type RefreshableSession, saveCredentials, } from "@cargo-ai/cdk/cli";
|
|
11
2
|
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,gBAAgB,EAChB,KAAK,iBAAiB,IAAI,WAAW,EACrC,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,KAAK,kBAAkB,EACvB,eAAe,GAChB,MAAM,mBAAmB,CAAC"}
|
package/build/credentials.js
CHANGED
|
@@ -1,34 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
|
|
6
|
-
export function loadCredentials() {
|
|
7
|
-
if (existsSync(CREDENTIALS_FILE) === false)
|
|
8
|
-
return undefined;
|
|
9
|
-
try {
|
|
10
|
-
const raw = readFileSync(CREDENTIALS_FILE, "utf-8");
|
|
11
|
-
return JSON.parse(raw);
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return undefined;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
export function saveCredentials(creds) {
|
|
18
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
19
|
-
writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2) + "\n", {
|
|
20
|
-
mode: 0o600,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
export function clearCredentials() {
|
|
24
|
-
if (existsSync(CREDENTIALS_FILE) === false)
|
|
25
|
-
return false;
|
|
26
|
-
unlinkSync(CREDENTIALS_FILE);
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
export function getCredentialsPath() {
|
|
30
|
-
return CREDENTIALS_FILE;
|
|
31
|
-
}
|
|
32
|
-
export function getConfigDir() {
|
|
33
|
-
return CONFIG_DIR;
|
|
34
|
-
}
|
|
1
|
+
// The credentials file is shared with `cargo-cdk`, so its format and access
|
|
2
|
+
// live in @cargo-ai/cdk/cli — one login serves both binaries and neither can
|
|
3
|
+
// drift from the other's idea of what is on disk.
|
|
4
|
+
export { clearCredentials, getConfigDir, getCredentialsPath, loadCredentials, saveCredentials, } from "@cargo-ai/cdk/cli";
|