@cargo-ai/cli 1.0.143 → 1.0.146
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 -1
- package/build/agentMode.d.ts +30 -0
- package/build/agentMode.d.ts.map +1 -0
- package/build/agentMode.js +45 -0
- package/build/commands/auth/email.js +1 -1
- package/build/commands/auth/workspace.d.ts +17 -0
- package/build/commands/auth/workspace.d.ts.map +1 -1
- package/build/commands/auth/workspace.js +74 -7
- package/build/commands/authHints.d.ts +8 -0
- package/build/commands/authHints.d.ts.map +1 -0
- package/build/commands/authHints.js +9 -0
- package/build/commands/connection/connector.d.ts.map +1 -1
- package/build/commands/connection/connector.js +7 -3
- package/build/commands/doctor.d.ts +13 -0
- package/build/commands/doctor.d.ts.map +1 -1
- package/build/commands/doctor.js +32 -5
- package/build/commands/insufficientCredits.d.ts +31 -0
- package/build/commands/insufficientCredits.d.ts.map +1 -0
- package/build/commands/insufficientCredits.js +137 -0
- package/build/commands/runHandler.d.ts +16 -0
- package/build/commands/runHandler.d.ts.map +1 -1
- package/build/commands/runHandler.js +69 -13
- package/build/commands/signedOutNotice.d.ts.map +1 -1
- package/build/commands/signedOutNotice.js +3 -2
- package/build/index.js +24 -4
- package/build/updateNotifier.d.ts +23 -3
- package/build/updateNotifier.d.ts.map +1 -1
- package/build/updateNotifier.js +57 -5
- package/build/utils/openBrowser.d.ts +14 -0
- package/build/utils/openBrowser.d.ts.map +1 -1
- package/build/utils/openBrowser.js +20 -8
- package/build/utils/prompt.d.ts.map +1 -1
- package/build/utils/prompt.js +9 -0
- package/build/version.d.ts +5 -0
- package/build/version.d.ts.map +1 -1
- package/build/version.js +3 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -246,7 +246,7 @@ Other release verbs (`list`, `get <uuid>`, `get-deployed`, `get-draft`, `update-
|
|
|
246
246
|
|
|
247
247
|
## Agent Skills
|
|
248
248
|
|
|
249
|
-
[Cargo Skills](https://github.com/getcargohq/cargo-skills) teaches AI coding agents (Claude Code, Codex, Cursor, Windsurf, GitHub Copilot, Hermes Agent, etc.) how to use this CLI
|
|
249
|
+
[Cargo Skills](https://github.com/getcargohq/cargo-skills) teaches AI coding agents (Claude Code, Codex, Cursor, Windsurf, GitHub Copilot, Hermes Agent, etc.) how to use this CLI: the Cargo skills bundle covers lead sourcing, enrichment, email verification, lead scoring, CRM sync, buying-signal monitoring, orchestration, storage, and projects (GTM as Code).
|
|
250
250
|
|
|
251
251
|
**Let your agent install everything.** Paste this into Claude Code, Codex, or Cursor and it handles the CLI, sign-in, and skills for its own platform:
|
|
252
252
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
export type AgentContext = {
|
|
3
|
+
/** The harness that launched this process, e.g. `claude-code`. */
|
|
4
|
+
codingAgent: string | undefined;
|
|
5
|
+
ci: boolean;
|
|
6
|
+
stdoutIsTty: boolean;
|
|
7
|
+
stderrIsTty: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* True whenever nobody can be expected to answer or watch: a coding agent
|
|
10
|
+
* (whose PTY can make every stream look like a terminal), CI, or a stdout
|
|
11
|
+
* that is not a terminal.
|
|
12
|
+
*/
|
|
13
|
+
nonInteractive: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function readAgentContext(env?: NodeJS.ProcessEnv, streams?: {
|
|
16
|
+
stdoutIsTty: boolean;
|
|
17
|
+
stderrIsTty: boolean;
|
|
18
|
+
}): AgentContext;
|
|
19
|
+
/**
|
|
20
|
+
* Whether stderr may carry terminal UI (colour, spinners, multi-line human
|
|
21
|
+
* errors). A coding agent's PTY reports a TTY, so the TTY alone cannot decide.
|
|
22
|
+
*/
|
|
23
|
+
export declare function canDrawOnStderr(context?: AgentContext): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether this run may block on a person typing into the terminal. Unlike the
|
|
26
|
+
* pipe rule above, a piped stdout alone does not refuse: `cargo-ai login | jq`
|
|
27
|
+
* by hand still has someone at /dev/tty.
|
|
28
|
+
*/
|
|
29
|
+
export declare function mayWaitForPerson(context?: AgentContext): boolean;
|
|
30
|
+
//# sourceMappingURL=agentMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agentMode.d.ts","sourceRoot":"","sources":["../src/agentMode.ts"],"names":[],"mappings":";AAgBA,MAAM,MAAM,YAAY,GAAG;IACzB,kEAAkE;IAClE,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,EAAE,OAAO,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB,CAAC;AAEF,wBAAgB,gBAAgB,CAC9B,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAGpD,GACA,YAAY,CAYd;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,OAAO,GAAE,YAAiC,GACzC,OAAO,CAMT;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,YAAiC,GACzC,OAAO,CAET"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Who is on the other end of this process: a person at a terminal, or a coding
|
|
2
|
+
// agent, CI job or pipe that can read output but never answer a question.
|
|
3
|
+
//
|
|
4
|
+
// Agent detection itself lives in `@cargo-ai/cdk/cli` (`detectCodingAgent`),
|
|
5
|
+
// which already gates every clack prompt and names the harness to the backend;
|
|
6
|
+
// this module only adds what the `cargo-ai` bin needs on top: the TTY facts,
|
|
7
|
+
// and one answer to "may this run behave like a terminal UI" for the places
|
|
8
|
+
// that do their own drawing (spinners, colour, the error format, /dev/tty
|
|
9
|
+
// prompts, opening a browser).
|
|
10
|
+
//
|
|
11
|
+
// The rule, after trigger.dev's CLI: when an agent is detected, never prompt
|
|
12
|
+
// (fail with the flag that answers the question), never animate, never open a
|
|
13
|
+
// browser nobody asked for, and keep stderr to lines a model can parse.
|
|
14
|
+
import { detectCodingAgent, isCiEnvironment } from "@cargo-ai/cdk/cli";
|
|
15
|
+
export function readAgentContext(env = process.env, streams = {
|
|
16
|
+
stdoutIsTty: process.stdout.isTTY === true,
|
|
17
|
+
stderrIsTty: process.stderr.isTTY === true,
|
|
18
|
+
}) {
|
|
19
|
+
const codingAgent = detectCodingAgent(env);
|
|
20
|
+
const ci = isCiEnvironment(env);
|
|
21
|
+
return {
|
|
22
|
+
codingAgent,
|
|
23
|
+
ci,
|
|
24
|
+
stdoutIsTty: streams.stdoutIsTty,
|
|
25
|
+
stderrIsTty: streams.stderrIsTty,
|
|
26
|
+
nonInteractive: codingAgent !== undefined || ci === true || streams.stdoutIsTty === false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Whether stderr may carry terminal UI (colour, spinners, multi-line human
|
|
31
|
+
* errors). A coding agent's PTY reports a TTY, so the TTY alone cannot decide.
|
|
32
|
+
*/
|
|
33
|
+
export function canDrawOnStderr(context = readAgentContext()) {
|
|
34
|
+
return (context.stderrIsTty === true &&
|
|
35
|
+
context.codingAgent === undefined &&
|
|
36
|
+
context.ci === false);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Whether this run may block on a person typing into the terminal. Unlike the
|
|
40
|
+
* pipe rule above, a piped stdout alone does not refuse: `cargo-ai login | jq`
|
|
41
|
+
* by hand still has someone at /dev/tty.
|
|
42
|
+
*/
|
|
43
|
+
export function mayWaitForPerson(context = readAgentContext()) {
|
|
44
|
+
return context.codingAgent === undefined && context.ci === false;
|
|
45
|
+
}
|
|
@@ -81,7 +81,7 @@ async function resolveCode(opts) {
|
|
|
81
81
|
success(`Verification code sent to ${opts.email}.`);
|
|
82
82
|
const promptInput = openPromptInput();
|
|
83
83
|
if (promptInput === undefined) {
|
|
84
|
-
info(`No interactive terminal
|
|
84
|
+
info(`No interactive terminal to type the code into. Get the code from ${opts.email}'s inbox (ask the user if you are an agent), then run: ${opts.resumeCommand}`);
|
|
85
85
|
return undefined;
|
|
86
86
|
}
|
|
87
87
|
try {
|
|
@@ -15,6 +15,23 @@ export declare function resolveWorkspace(opts: {
|
|
|
15
15
|
explicitUuid: string | undefined;
|
|
16
16
|
explicitName: string | undefined;
|
|
17
17
|
}): Promise<WorkspaceResolution>;
|
|
18
|
+
/**
|
|
19
|
+
* Without a terminal nothing can be picked, so the error itself has to carry
|
|
20
|
+
* the choice: every workspace with its uuid, and the flag that selects one.
|
|
21
|
+
* The sign-in is not saved, and an emailed code works once, so the command is
|
|
22
|
+
* re-run from the start.
|
|
23
|
+
*
|
|
24
|
+
* Exported for tests; `resolveWorkspace` is the only production caller.
|
|
25
|
+
*/
|
|
26
|
+
export declare function describeWorkspaceChoice(workspaces: readonly Pick<WorkspaceManagement.Workspace, "uuid" | "name">[]): string;
|
|
27
|
+
/**
|
|
28
|
+
* The free plan caps how many workspaces one person owns, so naming a new one
|
|
29
|
+
* can be refused. The fix is to use a workspace that already exists, which the
|
|
30
|
+
* message lists with the flag that picks it.
|
|
31
|
+
*
|
|
32
|
+
* Exported for tests; `createWorkspace` is the only production caller.
|
|
33
|
+
*/
|
|
34
|
+
export declare function describeFreeWorkspaceLimit(name: string, workspaces: readonly Pick<WorkspaceManagement.Workspace, "uuid" | "name">[]): string;
|
|
18
35
|
export declare function findWorkspaceByName(workspaces: readonly WorkspaceManagement.Workspace[], name: string): ResolvedWorkspace | undefined;
|
|
19
36
|
/**
|
|
20
37
|
* The workspace the sign-in Action just created, when it is the account's only
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/workspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGzD,OAAO,EAAE,KAAK,GAAG,EAAa,MAAM,cAAc,CAAC;AAUnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAuBF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwH/B;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS,mBAAmB,CAAC,SAAS,EAAE,EACpD,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,SAAS,CAY/B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS,IAAI,CACvB,mBAAmB,CAAC,SAAS,EAC7B,MAAM,GAAG,MAAM,GAAG,WAAW,CAC9B,EAAE,EACH,GAAG,EAAE,IAAI,GACR,iBAAiB,GAAG,SAAS,CAkB/B;
|
|
1
|
+
{"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/workspace.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAGzD,OAAO,EAAE,KAAK,GAAG,EAAa,MAAM,cAAc,CAAC;AAUnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mFAAmF;IACnF,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAuBF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwH/B;AAUD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,SAAS,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,GAC1E,MAAM,CASR;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,SAAS,IAAI,CAAC,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC,EAAE,GAC1E,MAAM,CAsBR;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS,mBAAmB,CAAC,SAAS,EAAE,EACpD,IAAI,EAAE,MAAM,GACX,iBAAiB,GAAG,SAAS,CAY/B;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,SAAS,IAAI,CACvB,mBAAmB,CAAC,SAAS,EAC7B,MAAM,GAAG,MAAM,GAAG,WAAW,CAC9B,EAAE,EACH,GAAG,EAAE,IAAI,GACR,iBAAiB,GAAG,SAAS,CAkB/B;AAqJD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ7D"}
|
|
@@ -55,7 +55,7 @@ export async function resolveWorkspace(opts) {
|
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
57
|
return {
|
|
58
|
-
workspace: await createWorkspace(api, opts.explicitName),
|
|
58
|
+
workspace: await createWorkspace(api, opts.explicitName, workspaces),
|
|
59
59
|
wasCreated: true,
|
|
60
60
|
};
|
|
61
61
|
}
|
|
@@ -92,7 +92,10 @@ export async function resolveWorkspace(opts) {
|
|
|
92
92
|
}
|
|
93
93
|
const promptInput = openPromptInput();
|
|
94
94
|
if (promptInput === undefined) {
|
|
95
|
-
failWith(
|
|
95
|
+
failWith(describeWorkspaceChoice(workspaces), {
|
|
96
|
+
code: ExitCodes.InvalidUsage,
|
|
97
|
+
extra: { workspaces: listWorkspaceChoices(workspaces) },
|
|
98
|
+
});
|
|
96
99
|
}
|
|
97
100
|
let selectedWorkspace;
|
|
98
101
|
try {
|
|
@@ -106,6 +109,49 @@ export async function resolveWorkspace(opts) {
|
|
|
106
109
|
}
|
|
107
110
|
return { workspace: selectedWorkspace, wasCreated: false };
|
|
108
111
|
}
|
|
112
|
+
function listWorkspaceChoices(workspaces) {
|
|
113
|
+
return workspaces.map((workspace) => {
|
|
114
|
+
return { uuid: workspace.uuid, name: workspace.name };
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Without a terminal nothing can be picked, so the error itself has to carry
|
|
119
|
+
* the choice: every workspace with its uuid, and the flag that selects one.
|
|
120
|
+
* The sign-in is not saved, and an emailed code works once, so the command is
|
|
121
|
+
* re-run from the start.
|
|
122
|
+
*
|
|
123
|
+
* Exported for tests; `resolveWorkspace` is the only production caller.
|
|
124
|
+
*/
|
|
125
|
+
export function describeWorkspaceChoice(workspaces) {
|
|
126
|
+
return [
|
|
127
|
+
`You have ${String(workspaces.length)} workspaces and there is no interactive terminal to pick one, so this sign-in was not saved.`,
|
|
128
|
+
"Run the same login command again with --workspace-uuid <uuid> (or set CARGO_WORKSPACE_UUID). With --email, request a new code first: a code works only once.",
|
|
129
|
+
"Your workspaces:",
|
|
130
|
+
...workspaces.map((workspace) => {
|
|
131
|
+
return ` --workspace-uuid ${workspace.uuid} (${workspace.name})`;
|
|
132
|
+
}),
|
|
133
|
+
].join("\n");
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The free plan caps how many workspaces one person owns, so naming a new one
|
|
137
|
+
* can be refused. The fix is to use a workspace that already exists, which the
|
|
138
|
+
* message lists with the flag that picks it.
|
|
139
|
+
*
|
|
140
|
+
* Exported for tests; `createWorkspace` is the only production caller.
|
|
141
|
+
*/
|
|
142
|
+
export function describeFreeWorkspaceLimit(name, workspaces) {
|
|
143
|
+
const lines = [
|
|
144
|
+
`Cannot create workspace "${name}": you have reached the free plan's workspace limit.`,
|
|
145
|
+
];
|
|
146
|
+
if (workspaces.length === 0) {
|
|
147
|
+
lines.push("Upgrade a workspace, or ask a teammate to invite you to theirs.");
|
|
148
|
+
return lines.join("\n");
|
|
149
|
+
}
|
|
150
|
+
lines.push("Use an existing workspace instead: re-run the login command with --workspace-uuid <uuid> in place of --workspace-name. Or upgrade one of them to create more.", "Your workspaces:", ...workspaces.map((workspace) => {
|
|
151
|
+
return ` --workspace-uuid ${workspace.uuid} (${workspace.name})`;
|
|
152
|
+
}));
|
|
153
|
+
return lines.join("\n");
|
|
154
|
+
}
|
|
109
155
|
export function findWorkspaceByName(workspaces, name) {
|
|
110
156
|
const wanted = name.trim().toLowerCase();
|
|
111
157
|
const matched = workspaces.find((workspace) => {
|
|
@@ -142,7 +188,7 @@ async function chooseWorkspace(api, workspaces) {
|
|
|
142
188
|
if (workspaces.length === 0) {
|
|
143
189
|
info("You don't have any workspaces yet. Let's create one.");
|
|
144
190
|
return {
|
|
145
|
-
workspace: await createWorkspace(api, await askWorkspaceName()),
|
|
191
|
+
workspace: await createWorkspace(api, await askWorkspaceName(), workspaces),
|
|
146
192
|
wasCreated: true,
|
|
147
193
|
};
|
|
148
194
|
}
|
|
@@ -169,7 +215,7 @@ async function chooseWorkspace(api, workspaces) {
|
|
|
169
215
|
});
|
|
170
216
|
if (chosen === CREATE) {
|
|
171
217
|
return {
|
|
172
|
-
workspace: await createWorkspace(api, await askWorkspaceName()),
|
|
218
|
+
workspace: await createWorkspace(api, await askWorkspaceName(), workspaces),
|
|
173
219
|
wasCreated: true,
|
|
174
220
|
};
|
|
175
221
|
}
|
|
@@ -212,12 +258,33 @@ async function createWorkspaceByAsking(api) {
|
|
|
212
258
|
code: ExitCodes.InvalidUsage,
|
|
213
259
|
});
|
|
214
260
|
}
|
|
215
|
-
|
|
261
|
+
// Only reached with no workspaces, so the limit message has none to list.
|
|
262
|
+
const workspace = await createWorkspace(api, name, []);
|
|
216
263
|
success(`Created workspace: ${workspace.name} (${workspace.uuid})`);
|
|
217
264
|
return { workspace, wasCreated: true };
|
|
218
265
|
}
|
|
219
|
-
async function createWorkspace(api, name
|
|
220
|
-
|
|
266
|
+
async function createWorkspace(api, name,
|
|
267
|
+
// Required, not defaulted: the free-plan-limit message lists these as the
|
|
268
|
+
// way out, and a caller that forgot them printed "you have none" to someone
|
|
269
|
+
// who has several.
|
|
270
|
+
existingWorkspaces) {
|
|
271
|
+
const result = await handleApiCall(async () => {
|
|
272
|
+
try {
|
|
273
|
+
return await api.workspaceManagement.workspace.create({ name });
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
if (error.reason === "freeWorkspaceLimitReached") {
|
|
277
|
+
failWith(describeFreeWorkspaceLimit(name, existingWorkspaces), {
|
|
278
|
+
code: ExitCodes.InvalidUsage,
|
|
279
|
+
extra: {
|
|
280
|
+
reason: "freeWorkspaceLimitReached",
|
|
281
|
+
workspaces: listWorkspaceChoices(existingWorkspaces),
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
}, { spinner: "Creating workspace..." });
|
|
221
288
|
return { uuid: result.workspace.uuid, name: result.workspace.name };
|
|
222
289
|
}
|
|
223
290
|
/**
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Sends a code to the address; creates the account on first use. */
|
|
2
|
+
export declare const LOGIN_EMAIL_COMMAND = "cargo-ai login --email you@company.com";
|
|
3
|
+
/** What a new account starts with (#6543): stated once, here. */
|
|
4
|
+
export declare const SIGNUP_OFFER = "100 free credits for your first month, no card";
|
|
5
|
+
/** What works with no account, so a signed-out agent can keep going. */
|
|
6
|
+
export declare const SIGNED_OUT_WORKS = "Browsing the catalog (`cargo-ai orchestration action list <query>`, `cargo-ai connection integration list`) and `--dry-run` work signed out.";
|
|
7
|
+
export declare const NOT_SIGNED_IN_MESSAGE = "Not signed in. Run `cargo-ai login --email you@company.com`: it emails a code (no browser) and creates the account if it is new, with 100 free credits for your first month, no card. Browsing the catalog (`cargo-ai orchestration action list <query>`, `cargo-ai connection integration list`) and `--dry-run` work signed out.";
|
|
8
|
+
//# sourceMappingURL=authHints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authHints.d.ts","sourceRoot":"","sources":["../../src/commands/authHints.ts"],"names":[],"mappings":"AAGA,qEAAqE;AACrE,eAAO,MAAM,mBAAmB,2CAA2C,CAAC;AAE5E,iEAAiE;AACjE,eAAO,MAAM,YAAY,mDAAmD,CAAC;AAE7E,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,iJACmH,CAAC;AAEjJ,eAAO,MAAM,qBAAqB,uUAAiK,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The commands a signed-out caller is pointed at. Kept free of imports so the
|
|
2
|
+
// error path (`runHandler.ts`) and the notices can share them without a cycle.
|
|
3
|
+
/** Sends a code to the address; creates the account on first use. */
|
|
4
|
+
export const LOGIN_EMAIL_COMMAND = "cargo-ai login --email you@company.com";
|
|
5
|
+
/** What a new account starts with (#6543): stated once, here. */
|
|
6
|
+
export const SIGNUP_OFFER = "100 free credits for your first month, no card";
|
|
7
|
+
/** What works with no account, so a signed-out agent can keep going. */
|
|
8
|
+
export const SIGNED_OUT_WORKS = "Browsing the catalog (`cargo-ai orchestration action list <query>`, `cargo-ai connection integration list`) and `--dry-run` work signed out.";
|
|
9
|
+
export const NOT_SIGNED_IN_MESSAGE = `Not signed in. Run \`${LOGIN_EMAIL_COMMAND}\`: it emails a code (no browser) and creates the account if it is new, with ${SIGNUP_OFFER}. ${SIGNED_OUT_WORKS}`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../src/commands/connection/connector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"connector.d.ts","sourceRoot":"","sources":["../../../src/commands/connection/connector.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAcxC,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAgUN"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { awaitBrowserStep, buildCliConnectorCreateUrl, buildCliConnectorEditUrl, isCodingAgent, openBrowser, resolveUserUuid, shouldWaitForPerson, } from "@cargo-ai/cdk/cli";
|
|
1
|
+
import { awaitBrowserStep, buildCliConnectorCreateUrl, buildCliConnectorEditUrl, isCiEnvironment, isCodingAgent, openBrowser, resolveUserUuid, shouldWaitForPerson, } from "@cargo-ai/cdk/cli";
|
|
2
2
|
import { getConfig } from "../../config.js";
|
|
3
3
|
import { parseBooleanOption } from "../../utils/booleanOption.js";
|
|
4
4
|
import { openPromptInput } from "../../utils/prompt.js";
|
|
@@ -212,7 +212,7 @@ async function runCreateHandoff(input) {
|
|
|
212
212
|
// refuses coding agents, whose stdin often looks like a TTY.
|
|
213
213
|
if (shouldWaitForPerson({
|
|
214
214
|
isCodingAgent: isCodingAgent(),
|
|
215
|
-
isCi:
|
|
215
|
+
isCi: isCiEnvironment(),
|
|
216
216
|
hasPromptInput: openPromptInput() !== undefined,
|
|
217
217
|
}) === false) {
|
|
218
218
|
outputJson({
|
|
@@ -249,7 +249,11 @@ function openConnectorEditForm(connectorUuid) {
|
|
|
249
249
|
connectorUuid,
|
|
250
250
|
});
|
|
251
251
|
info(["", "Re-authorize the connector here:", ` ${colors.cyan(url)}`, ""].join("\n"));
|
|
252
|
-
|
|
252
|
+
// Nobody asked for a browser here: an agent gets the URL above and in the
|
|
253
|
+
// JSON, and opening a tab on the person's screen mid-session is a surprise.
|
|
254
|
+
if (isCodingAgent() === false) {
|
|
255
|
+
openBrowser(url);
|
|
256
|
+
}
|
|
253
257
|
outputJson({
|
|
254
258
|
ok: true,
|
|
255
259
|
status: "actionRequired",
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import type { Command } from "commander";
|
|
2
|
+
import { type ExitCode } from "./runHandler.js";
|
|
2
3
|
export declare function registerDoctorCommand(program: Command, currentVersion: string): void;
|
|
4
|
+
/**
|
|
5
|
+
* The API result decides first; `--strict` only turns a healthy run with an
|
|
6
|
+
* outdated CLI into exit 7. An unknown latest version (offline registry) is
|
|
7
|
+
* not "outdated": the check could not tell.
|
|
8
|
+
*
|
|
9
|
+
* Exported for tests.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveDoctorExitCode(input: {
|
|
12
|
+
apiExitCode: ExitCode;
|
|
13
|
+
cliUpToDate: boolean | null;
|
|
14
|
+
strict: boolean;
|
|
15
|
+
}): ExitCode;
|
|
3
16
|
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAazC,OAAO,EAAU,KAAK,QAAQ,EAAyB,MAAM,iBAAiB,CAAC;AAsK/E,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,GACrB,IAAI,CA6FN;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,WAAW,EAAE,QAAQ,CAAC;IACtB,WAAW,EAAE,OAAO,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,QAAQ,CAUX"}
|
package/build/commands/doctor.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { determineIfIsFetcherError } from "@cargo-ai/api";
|
|
2
|
-
import { formatSetupIssues } from "@cargo-ai/cdk/cli";
|
|
2
|
+
import { formatSetupIssues, isCodingAgent, } from "@cargo-ai/cdk/cli";
|
|
3
3
|
import { createApi } from "../api.js";
|
|
4
4
|
import { getConfig } from "../config.js";
|
|
5
5
|
import { getCredentialsPath } from "../credentials.js";
|
|
6
6
|
import { collectSetupIssues, readSkillsPin } from "../setupIssues.js";
|
|
7
7
|
import { fetchLatestSkillsPin, fetchLatestVersion, isOutdated, UPDATE_COMMAND, } from "../version.js";
|
|
8
|
+
import { LOGIN_EMAIL_COMMAND } from "./authHints.js";
|
|
8
9
|
import { colors, ExitCodes, outputJson } from "./runHandler.js";
|
|
9
|
-
|
|
10
|
+
// An agent cannot click through a browser sign-in, but can relay an emailed
|
|
11
|
+
// code: point it at the channel it can finish.
|
|
12
|
+
const LOGIN_COMMAND = isCodingAgent() === true ? LOGIN_EMAIL_COMMAND : "cargo-ai login --oauth";
|
|
10
13
|
const STATUS_PAGE = "https://status.getcargo.io";
|
|
11
14
|
function buildCliCheck(currentVersion, latestVersion) {
|
|
12
15
|
if (latestVersion === undefined) {
|
|
@@ -130,13 +133,16 @@ most severe failure:
|
|
|
130
133
|
1 API unreachable or returned an unexpected error
|
|
131
134
|
3 not authenticated (no credentials, or the API rejected them)
|
|
132
135
|
4 permission denied for the configured workspace
|
|
136
|
+
7 with --strict only: the CLI is behind the latest npm release (checked
|
|
137
|
+
after the failures above, so a more severe one still wins)
|
|
133
138
|
|
|
134
139
|
Scripts and agents should branch on the exit code and read the JSON for details.
|
|
135
140
|
|
|
136
141
|
Examples:
|
|
137
142
|
$ cargo-ai doctor
|
|
138
|
-
$ cargo-ai doctor || echo "exit: $?"`)
|
|
139
|
-
.
|
|
143
|
+
$ cargo-ai doctor --strict || echo "exit: $?"`)
|
|
144
|
+
.option("--strict", "Also fail (exit 7) when the CLI is behind the latest release")
|
|
145
|
+
.action(async (opts) => {
|
|
140
146
|
let latestVersion;
|
|
141
147
|
try {
|
|
142
148
|
latestVersion = await fetchLatestVersion();
|
|
@@ -160,7 +166,12 @@ Examples:
|
|
|
160
166
|
workspaceUuid: config.workspaceUuid !== undefined ? config.workspaceUuid : null,
|
|
161
167
|
credentialsFile: getCredentialsPath(),
|
|
162
168
|
};
|
|
163
|
-
const { check: api, exitCode } = await checkApi(config);
|
|
169
|
+
const { check: api, exitCode: apiExitCode } = await checkApi(config);
|
|
170
|
+
const exitCode = resolveDoctorExitCode({
|
|
171
|
+
apiExitCode,
|
|
172
|
+
cliUpToDate: cli.upToDate,
|
|
173
|
+
strict: opts.strict === true,
|
|
174
|
+
});
|
|
164
175
|
// One list, most severe first, each entry carrying its own fix — the same
|
|
165
176
|
// shape the pre-command header renders, so a reader who has seen the
|
|
166
177
|
// amber line recognises it here rather than learning a second format.
|
|
@@ -188,6 +199,22 @@ Examples:
|
|
|
188
199
|
process.exit(exitCode);
|
|
189
200
|
});
|
|
190
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* The API result decides first; `--strict` only turns a healthy run with an
|
|
204
|
+
* outdated CLI into exit 7. An unknown latest version (offline registry) is
|
|
205
|
+
* not "outdated": the check could not tell.
|
|
206
|
+
*
|
|
207
|
+
* Exported for tests.
|
|
208
|
+
*/
|
|
209
|
+
export function resolveDoctorExitCode(input) {
|
|
210
|
+
if (input.apiExitCode !== ExitCodes.Success) {
|
|
211
|
+
return input.apiExitCode;
|
|
212
|
+
}
|
|
213
|
+
if (input.strict === true && input.cliUpToDate === false) {
|
|
214
|
+
return ExitCodes.Outdated;
|
|
215
|
+
}
|
|
216
|
+
return ExitCodes.Success;
|
|
217
|
+
}
|
|
191
218
|
function describeApiIssue(api) {
|
|
192
219
|
if (api.ok === true) {
|
|
193
220
|
return undefined;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
declare const CODES: {
|
|
2
|
+
readonly notEnoughCredits: "insufficient_credits";
|
|
3
|
+
readonly userCreditsLimitReached: "user_credits_limit_reached";
|
|
4
|
+
};
|
|
5
|
+
export type InsufficientCreditsCode = (typeof CODES)[keyof typeof CODES];
|
|
6
|
+
export type InsufficientCreditsFix = {
|
|
7
|
+
kind: string;
|
|
8
|
+
description: string;
|
|
9
|
+
url?: string;
|
|
10
|
+
command?: string;
|
|
11
|
+
};
|
|
12
|
+
export type InsufficientCredits = {
|
|
13
|
+
code: InsufficientCreditsCode;
|
|
14
|
+
errorMessage: string | undefined;
|
|
15
|
+
creditsBalance: number | null;
|
|
16
|
+
estimatedCreditsCost: number | null;
|
|
17
|
+
billingUrl: string | null;
|
|
18
|
+
fixes: InsufficientCreditsFix[];
|
|
19
|
+
};
|
|
20
|
+
/** The error, when `body` is one; undefined for every other API error. */
|
|
21
|
+
export declare function readInsufficientCredits(body: unknown): InsufficientCredits | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* One line for the message, the fixes as fields. The JSON form (non-TTY or a
|
|
24
|
+
* coding agent) carries every field verbatim beside `body`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function describeInsufficientCredits(error: InsufficientCredits): {
|
|
27
|
+
message: string;
|
|
28
|
+
extra: Record<string, unknown>;
|
|
29
|
+
};
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=insufficientCredits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insufficientCredits.d.ts","sourceRoot":"","sources":["../../src/commands/insufficientCredits.ts"],"names":[],"mappings":"AAQA,QAAA,MAAM,KAAK;;;CAGD,CAAC;AAEX,MAAM,MAAM,uBAAuB,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AAEzE,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,uBAAuB,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,sBAAsB,EAAE,CAAC;CACjC,CAAC;AA8BF,0EAA0E;AAC1E,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,OAAO,GACZ,mBAAmB,GAAG,SAAS,CA8BjC;AAgED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,mBAAmB,GAAG;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAwCA"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// The API's out-of-credits error, read back and said in one line a person or
|
|
2
|
+
// an agent can act on.
|
|
3
|
+
//
|
|
4
|
+
// The backend answers `{ reason: "notEnoughCredits" | "userCreditsLimitReached" }`
|
|
5
|
+
// and, since the structured error, also `code`, `errorMessage`,
|
|
6
|
+
// `creditsBalance`, `estimatedCreditsCost`, `billingUrl` and `fixes`. Both
|
|
7
|
+
// shapes are read, so a CLI talking to an older backend still names the fix.
|
|
8
|
+
const CODES = {
|
|
9
|
+
notEnoughCredits: "insufficient_credits",
|
|
10
|
+
userCreditsLimitReached: "user_credits_limit_reached",
|
|
11
|
+
};
|
|
12
|
+
// Named when the backend sent no fixes of its own (an older backend).
|
|
13
|
+
const FALLBACK_FIXES = {
|
|
14
|
+
insufficient_credits: [
|
|
15
|
+
{
|
|
16
|
+
kind: "upgradePlan",
|
|
17
|
+
description: "Buy more credits by raising the plan's allotment",
|
|
18
|
+
command: "cargo-ai billing subscription update-plan --credits-count <n> --cadence monthly",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
kind: "enableTopup",
|
|
22
|
+
description: "Top up automatically when credits run low",
|
|
23
|
+
command: "cargo-ai billing subscription update-topup --enabled --credits-count <n> --threshold <n>",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
user_credits_limit_reached: [
|
|
27
|
+
{
|
|
28
|
+
kind: "askAdmin",
|
|
29
|
+
description: "Ask a workspace admin to raise or remove your monthly credits limit",
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
/** The error, when `body` is one; undefined for every other API error. */
|
|
34
|
+
export function readInsufficientCredits(body) {
|
|
35
|
+
if (body === null || typeof body !== "object") {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
const record = body;
|
|
39
|
+
const code = resolveCode(record);
|
|
40
|
+
if (code === undefined) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
code,
|
|
45
|
+
errorMessage: typeof record["errorMessage"] === "string"
|
|
46
|
+
? record["errorMessage"]
|
|
47
|
+
: undefined,
|
|
48
|
+
creditsBalance: typeof record["creditsBalance"] === "number"
|
|
49
|
+
? record["creditsBalance"]
|
|
50
|
+
: null,
|
|
51
|
+
estimatedCreditsCost: typeof record["estimatedCreditsCost"] === "number"
|
|
52
|
+
? record["estimatedCreditsCost"]
|
|
53
|
+
: null,
|
|
54
|
+
billingUrl: typeof record["billingUrl"] === "string" ? record["billingUrl"] : null,
|
|
55
|
+
fixes: readFixes(record["fixes"], code),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function resolveCode(record) {
|
|
59
|
+
const code = record["code"];
|
|
60
|
+
if (code === CODES.notEnoughCredits ||
|
|
61
|
+
code === CODES.userCreditsLimitReached) {
|
|
62
|
+
return code;
|
|
63
|
+
}
|
|
64
|
+
const reason = record["reason"];
|
|
65
|
+
if (reason === "notEnoughCredits") {
|
|
66
|
+
return CODES.notEnoughCredits;
|
|
67
|
+
}
|
|
68
|
+
if (reason === "userCreditsLimitReached") {
|
|
69
|
+
return CODES.userCreditsLimitReached;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
function readFixes(value, code) {
|
|
74
|
+
if (Array.isArray(value) === false) {
|
|
75
|
+
return FALLBACK_FIXES[code];
|
|
76
|
+
}
|
|
77
|
+
const fixes = value.flatMap((entry) => {
|
|
78
|
+
if (entry === null || typeof entry !== "object") {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const fix = entry;
|
|
82
|
+
if (typeof fix["kind"] !== "string" ||
|
|
83
|
+
typeof fix["description"] !== "string") {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
return [
|
|
87
|
+
{
|
|
88
|
+
kind: fix["kind"],
|
|
89
|
+
description: fix["description"],
|
|
90
|
+
...(typeof fix["url"] === "string" ? { url: fix["url"] } : {}),
|
|
91
|
+
...(typeof fix["command"] === "string"
|
|
92
|
+
? { command: fix["command"] }
|
|
93
|
+
: {}),
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
});
|
|
97
|
+
return fixes.length > 0 ? fixes : FALLBACK_FIXES[code];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* One line for the message, the fixes as fields. The JSON form (non-TTY or a
|
|
101
|
+
* coding agent) carries every field verbatim beside `body`.
|
|
102
|
+
*/
|
|
103
|
+
export function describeInsufficientCredits(error) {
|
|
104
|
+
const headline = error.code === CODES.userCreditsLimitReached
|
|
105
|
+
? "You have reached your monthly credits limit in this workspace"
|
|
106
|
+
: "This workspace is out of credits";
|
|
107
|
+
const balance = error.creditsBalance !== null
|
|
108
|
+
? ` (balance: ${formatCredits(error.creditsBalance)}`
|
|
109
|
+
: "";
|
|
110
|
+
const cost = error.estimatedCreditsCost !== null
|
|
111
|
+
? `${balance === "" ? " (" : ", "}estimated cost: ${formatCredits(error.estimatedCreditsCost)}`
|
|
112
|
+
: "";
|
|
113
|
+
const closing = balance !== "" || cost !== "" ? ")" : "";
|
|
114
|
+
const firstFix = error.fixes[0];
|
|
115
|
+
const fixHint = firstFix === undefined
|
|
116
|
+
? ""
|
|
117
|
+
: ` ${firstFix.description}${firstFix.command !== undefined
|
|
118
|
+
? `: ${firstFix.command}`
|
|
119
|
+
: firstFix.url !== undefined
|
|
120
|
+
? `: ${firstFix.url}`
|
|
121
|
+
: error.billingUrl !== null
|
|
122
|
+
? `: ${error.billingUrl}`
|
|
123
|
+
: ""}.`;
|
|
124
|
+
return {
|
|
125
|
+
message: `${headline}${balance}${cost}${closing}.${fixHint}`,
|
|
126
|
+
extra: {
|
|
127
|
+
code: error.code,
|
|
128
|
+
creditsBalance: error.creditsBalance,
|
|
129
|
+
estimatedCreditsCost: error.estimatedCreditsCost,
|
|
130
|
+
billingUrl: error.billingUrl,
|
|
131
|
+
fixes: error.fixes,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function formatCredits(count) {
|
|
136
|
+
return count.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
137
|
+
}
|
|
@@ -5,6 +5,10 @@ export declare const ExitCodes: {
|
|
|
5
5
|
readonly NotAuthenticated: 3;
|
|
6
6
|
readonly PermissionDenied: 4;
|
|
7
7
|
readonly NotFound: 5;
|
|
8
|
+
/** The workspace is out of credits, or the member is over their limit. */
|
|
9
|
+
readonly InsufficientCredits: 6;
|
|
10
|
+
/** Only `doctor --strict`: the CLI is behind the latest npm release. */
|
|
11
|
+
readonly Outdated: 7;
|
|
8
12
|
};
|
|
9
13
|
export type ExitCode = (typeof ExitCodes)[keyof typeof ExitCodes];
|
|
10
14
|
export declare const colorize: (text: string, code: string) => string;
|
|
@@ -54,6 +58,18 @@ type HandleApiCallOpts = {
|
|
|
54
58
|
notAuthenticatedHint?: string;
|
|
55
59
|
};
|
|
56
60
|
export declare function handleApiCall<T>(fn: () => Promise<T>, opts?: HandleApiCallOpts): Promise<T>;
|
|
61
|
+
/**
|
|
62
|
+
* The message, exit code and machine-readable fields for a failed API call.
|
|
63
|
+
* `extra` is lifted to the top of the JSON error so an agent can branch on
|
|
64
|
+
* `code` without digging into `body`.
|
|
65
|
+
*
|
|
66
|
+
* Exported for tests.
|
|
67
|
+
*/
|
|
68
|
+
export declare function describeApiError(status: number | undefined, body: unknown, notAuthenticatedHint: string | undefined): {
|
|
69
|
+
message: string;
|
|
70
|
+
code: ExitCode;
|
|
71
|
+
extra?: Record<string, unknown>;
|
|
72
|
+
};
|
|
57
73
|
export declare function sleep(ms: number): Promise<void>;
|
|
58
74
|
/**
|
|
59
75
|
* Poll until the thing reports a `finishedAt`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runHandler.d.ts","sourceRoot":"","sources":["../../src/commands/runHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runHandler.d.ts","sourceRoot":"","sources":["../../src/commands/runHandler.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS;;;;;;;IAOpB,0EAA0E;;IAE1E,wEAAwE;;CAEhE,CAAC;AAEX,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAMlE,eAAO,MAAM,QAAQ,SAAU,MAAM,QAAQ,MAAM,KAAG,MAKrD,CAAC;AAEF,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;AAKD,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYhE;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,CA2CpE;AAsBD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAmBlE;AAED,MAAM,MAAM,OAAO,GAAG;IAEpB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAGlC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAU5B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAIF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAwBrD;AAiED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,GAAG,CAQhE;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ1E;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,OAAO,EAAE,CAAC,EAAE,EACZ,KAAK,EAAE,MAAM,GACZ;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,YAAY,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CASxD;AAKD,KAAK,iBAAiB,GAAG;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACzB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,wBAAsB,aAAa,CAAC,CAAC,EACnC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,CAAC,EAAE,iBAAiB,GACvB,OAAO,CAAC,CAAC,CAAC,CAoEZ;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,IAAI,EAAE,OAAO,EACb,oBAAoB,EAAE,MAAM,GAAG,SAAS,GACvC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CA8DtE;AAwBD,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAC3B,aAAa,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9D,UAAU,EAAE,MAAM,EAClB,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,CAAC,CAAC,CAyBZ"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { determineIfIsFetcherError } from "@cargo-ai/api";
|
|
2
|
+
import { canDrawOnStderr, mayWaitForPerson } from "../agentMode.js";
|
|
2
3
|
import { clipToCells } from "../utils/textWidth.js";
|
|
4
|
+
import { LOGIN_EMAIL_COMMAND } from "./authHints.js";
|
|
5
|
+
import { describeInsufficientCredits, readInsufficientCredits, } from "./insufficientCredits.js";
|
|
3
6
|
export const ExitCodes = {
|
|
4
7
|
Success: 0,
|
|
5
8
|
GenericError: 1,
|
|
@@ -7,10 +10,14 @@ export const ExitCodes = {
|
|
|
7
10
|
NotAuthenticated: 3,
|
|
8
11
|
PermissionDenied: 4,
|
|
9
12
|
NotFound: 5,
|
|
13
|
+
/** The workspace is out of credits, or the member is over their limit. */
|
|
14
|
+
InsufficientCredits: 6,
|
|
15
|
+
/** Only `doctor --strict`: the CLI is behind the latest npm release. */
|
|
16
|
+
Outdated: 7,
|
|
10
17
|
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
// A coding agent's PTY reports a TTY, and escape codes in what it reads back
|
|
19
|
+
// are noise it has to strip, so colour follows the same rule as the spinner.
|
|
20
|
+
const useColor = canDrawOnStderr() && process.env["NO_COLOR"] === undefined;
|
|
14
21
|
export const colorize = (text, code) => {
|
|
15
22
|
if (useColor !== true) {
|
|
16
23
|
return text;
|
|
@@ -39,10 +46,12 @@ export function success(message) {
|
|
|
39
46
|
console.error(`${colors.green("✓")} ${message}`);
|
|
40
47
|
}
|
|
41
48
|
// Ask a yes/no question on stderr (stdout stays reserved for JSON). Resolves
|
|
42
|
-
// false on a non-TTY stdin
|
|
49
|
+
// false on a non-TTY stdin, and for a coding agent or CI whatever stdin looks
|
|
50
|
+
// like (callers should require an explicit --yes there).
|
|
43
51
|
export async function confirm(question) {
|
|
44
|
-
if (process.stdin.isTTY !== true)
|
|
52
|
+
if (process.stdin.isTTY !== true || mayWaitForPerson() === false) {
|
|
45
53
|
return false;
|
|
54
|
+
}
|
|
46
55
|
const { createInterface } = await import("node:readline/promises");
|
|
47
56
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
48
57
|
try {
|
|
@@ -64,10 +73,21 @@ export function failWith(message, opts) {
|
|
|
64
73
|
if (activeSpinner !== undefined) {
|
|
65
74
|
activeSpinner.stop();
|
|
66
75
|
}
|
|
67
|
-
|
|
76
|
+
// A person gets the coloured line; anything else (a pipe, CI, a coding
|
|
77
|
+
// agent even on a PTY) gets one JSON object it can parse.
|
|
78
|
+
if (canDrawOnStderr() === true) {
|
|
68
79
|
process.stderr.write(`${colors.red("✗")} ${message}\n`);
|
|
69
80
|
if (extra !== undefined) {
|
|
70
81
|
for (const [key, value] of Object.entries(extra)) {
|
|
82
|
+
if (value === null || value === undefined) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (key === "fixes" && Array.isArray(value)) {
|
|
86
|
+
for (const fix of value) {
|
|
87
|
+
process.stderr.write(` ${colors.dim("fix:")} ${formatFix(fix)}\n`);
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
71
91
|
const valueStr = typeof value === "string" ? value : JSON.stringify(value);
|
|
72
92
|
process.stderr.write(` ${colors.dim(`${key}:`)} ${valueStr}\n`);
|
|
73
93
|
}
|
|
@@ -82,6 +102,16 @@ export function failWith(message, opts) {
|
|
|
82
102
|
}
|
|
83
103
|
process.exit(code);
|
|
84
104
|
}
|
|
105
|
+
/** `Top up automatically: cargo-ai billing …`, for the human error format. */
|
|
106
|
+
function formatFix(fix) {
|
|
107
|
+
const description = typeof fix["description"] === "string" ? fix["description"] : "";
|
|
108
|
+
const target = typeof fix["command"] === "string"
|
|
109
|
+
? colors.cyan(fix["command"])
|
|
110
|
+
: typeof fix["url"] === "string"
|
|
111
|
+
? colors.cyan(fix["url"])
|
|
112
|
+
: undefined;
|
|
113
|
+
return target === undefined ? description : `${description}: ${target}`;
|
|
114
|
+
}
|
|
85
115
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
86
116
|
const SPINNER_INTERVAL_MS = 80;
|
|
87
117
|
/** Assumed width when the terminal will not say — the conventional default. */
|
|
@@ -130,9 +160,10 @@ export function startSpinner(message) {
|
|
|
130
160
|
return tracked;
|
|
131
161
|
}
|
|
132
162
|
function createSpinner(message) {
|
|
133
|
-
if (
|
|
134
|
-
// Non-TTY (CI, piped)
|
|
135
|
-
//
|
|
163
|
+
if (canDrawOnStderr() === false) {
|
|
164
|
+
// Non-TTY (CI, piped) or a coding agent: no animation, whose redraws an
|
|
165
|
+
// agent reads back as a wall of escape codes. Just emit each line so
|
|
166
|
+
// progress is still visible in logs.
|
|
136
167
|
process.stderr.write(`${message}\n`);
|
|
137
168
|
return {
|
|
138
169
|
update: (m) => process.stderr.write(`${m}\n`),
|
|
@@ -254,25 +285,50 @@ export async function handleApiCall(fn, opts) {
|
|
|
254
285
|
: parsedError.body !== undefined
|
|
255
286
|
? parsedError.body
|
|
256
287
|
: String(error);
|
|
257
|
-
const { message, code } = describeApiError(status, body, opts !== undefined ? opts.notAuthenticatedHint : undefined);
|
|
288
|
+
const { message, code, extra } = describeApiError(status, body, opts !== undefined ? opts.notAuthenticatedHint : undefined);
|
|
258
289
|
failWith(message, {
|
|
259
290
|
code,
|
|
260
|
-
extra: {
|
|
291
|
+
extra: {
|
|
292
|
+
...extra,
|
|
293
|
+
status: status !== undefined ? status : "unknown",
|
|
294
|
+
body,
|
|
295
|
+
},
|
|
261
296
|
});
|
|
262
297
|
}
|
|
263
298
|
throw error;
|
|
264
299
|
}
|
|
265
300
|
}
|
|
266
|
-
|
|
301
|
+
/**
|
|
302
|
+
* The message, exit code and machine-readable fields for a failed API call.
|
|
303
|
+
* `extra` is lifted to the top of the JSON error so an agent can branch on
|
|
304
|
+
* `code` without digging into `body`.
|
|
305
|
+
*
|
|
306
|
+
* Exported for tests.
|
|
307
|
+
*/
|
|
308
|
+
export function describeApiError(status, body, notAuthenticatedHint) {
|
|
267
309
|
const detail = extractApiErrorDetail(body);
|
|
268
310
|
const detailSuffix = detail !== undefined ? `: ${detail}` : "";
|
|
311
|
+
// Checked before the status: the orchestration routes answer it as a 400
|
|
312
|
+
// and the newer ones as a 402, and either way the fix is the same.
|
|
313
|
+
const insufficientCredits = readInsufficientCredits(body);
|
|
314
|
+
if (insufficientCredits !== undefined) {
|
|
315
|
+
const described = describeInsufficientCredits(insufficientCredits);
|
|
316
|
+
return {
|
|
317
|
+
message: described.message,
|
|
318
|
+
code: ExitCodes.InsufficientCredits,
|
|
319
|
+
extra: described.extra,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
269
322
|
if (status === 401) {
|
|
270
323
|
const hint = notAuthenticatedHint !== undefined
|
|
271
324
|
? notAuthenticatedHint
|
|
272
|
-
:
|
|
325
|
+
: `Sign in again with \`${LOGIN_EMAIL_COMMAND}\` (emails a code, no browser needed).`;
|
|
273
326
|
return {
|
|
274
327
|
message: `Not authenticated${detailSuffix}. ${hint}`,
|
|
275
328
|
code: ExitCodes.NotAuthenticated,
|
|
329
|
+
extra: notAuthenticatedHint !== undefined
|
|
330
|
+
? undefined
|
|
331
|
+
: { code: "not_authenticated", fix: LOGIN_EMAIL_COMMAND },
|
|
276
332
|
};
|
|
277
333
|
}
|
|
278
334
|
if (status === 403) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signedOutNotice.d.ts","sourceRoot":"","sources":["../../src/commands/signedOutNotice.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"signedOutNotice.d.ts","sourceRoot":"","sources":["../../src/commands/signedOutNotice.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,eAAe,2CAAsB,CAAC;AAEnD,eAAO,MAAM,iBAAiB,gMAA+I,CAAC;AAE9K,wBAAgB,aAAa,IAAI,IAAI,CAIpC"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// What a signed-out caller of a command that reads the public routes is told,
|
|
2
2
|
// once, on stderr: stdout stays the JSON the command was asked for.
|
|
3
3
|
import { isSignedIn } from "@cargo-ai/cdk/cli";
|
|
4
|
+
import { LOGIN_EMAIL_COMMAND, SIGNUP_OFFER } from "./authHints.js";
|
|
4
5
|
import { info } from "./runHandler.js";
|
|
5
|
-
export const SIGN_UP_COMMAND =
|
|
6
|
-
export const SIGNED_OUT_NOTICE = `Not signed in: reading the official integrations and their actions only. Sign up with \`${SIGN_UP_COMMAND}\` (
|
|
6
|
+
export const SIGN_UP_COMMAND = LOGIN_EMAIL_COMMAND;
|
|
7
|
+
export const SIGNED_OUT_NOTICE = `Not signed in: reading the official integrations and their actions only. Sign up with \`${SIGN_UP_COMMAND}\` (${SIGNUP_OFFER}) to run one.`;
|
|
7
8
|
export function noteSignedOut() {
|
|
8
9
|
if (isSignedIn() === false) {
|
|
9
10
|
info(SIGNED_OUT_NOTICE);
|
package/build/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { availableCodingAgents, BRAND, canRunPalette, creditsLeftOf, declareSources, fetchStateConnected, fetchSubscription, fetchWorkspaceName, findProjectDir, formatWorkspaceSuffix, isInteractive, isUnpaidPlan, printSetupIssues, registerCommands as registerCdkCommands, runPalette, SessionExpiredError, skillsInstalledFor, } from "@cargo-ai/cdk/cli";
|
|
3
|
+
import { availableCodingAgents, BRAND, canRunPalette, creditsLeftOf, declareSources, fetchStateConnected, fetchSubscription, fetchWorkspaceName, findProjectDir, formatWorkspaceSuffix, isCodingAgent, isInteractive, isUnpaidPlan, printSetupIssues, registerCommands as registerCdkCommands, runPalette, SessionExpiredError, skillsInstalledFor, } from "@cargo-ai/cdk/cli";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { AGENT_SKILLS_HINT } from "./agentSkills.js";
|
|
6
6
|
import { createApi } from "./api.js";
|
|
@@ -9,6 +9,7 @@ import { pinAskBooleanFlags } from "./commands/ask/argvFlags.js";
|
|
|
9
9
|
import { registerAskCommand } from "./commands/ask/index.js";
|
|
10
10
|
import { rewriteChatFlag } from "./commands/ask/rewriteChatFlag.js";
|
|
11
11
|
import { registerAuthCommands } from "./commands/auth/index.js";
|
|
12
|
+
import { LOGIN_EMAIL_COMMAND, NOT_SIGNED_IN_MESSAGE, } from "./commands/authHints.js";
|
|
12
13
|
import { registerBillingCommands } from "./commands/billing/index.js";
|
|
13
14
|
import { registerBookDemoCommand } from "./commands/bookDemo.js";
|
|
14
15
|
import { registerConnectionCommands } from "./commands/connection/index.js";
|
|
@@ -37,7 +38,7 @@ import { getConfig } from "./config.js";
|
|
|
37
38
|
import { removeLegacySessionHooks } from "./legacySessionHooks.js";
|
|
38
39
|
import { buildPaletteLayout } from "./paletteLayout.js";
|
|
39
40
|
import { collectSetupIssues, isSignedIn } from "./setupIssues.js";
|
|
40
|
-
import { cachedLatestSkillsPin, cachedLatestVersion, maybeRefreshUpdateCache, } from "./updateNotifier.js";
|
|
41
|
+
import { cachedLatestSkillsPin, cachedLatestVersion, maybeNoticeOutdatedForAgent, maybeRefreshUpdateCache, } from "./updateNotifier.js";
|
|
41
42
|
const require = createRequire(import.meta.url);
|
|
42
43
|
const { version } = require("../package.json");
|
|
43
44
|
const program = new Command();
|
|
@@ -90,9 +91,15 @@ Exit codes:
|
|
|
90
91
|
0 Success
|
|
91
92
|
1 Generic error
|
|
92
93
|
2 Invalid usage (missing/conflicting flags)
|
|
93
|
-
3 Not authenticated (run \`cargo-ai login\`)
|
|
94
|
+
3 Not authenticated (run \`cargo-ai login --email you@company.com\`)
|
|
94
95
|
4 Permission denied
|
|
95
96
|
5 Not found
|
|
97
|
+
6 Out of credits, or over your monthly credits limit (the error names the fix)
|
|
98
|
+
7 CLI behind the latest release (\`cargo-ai doctor --strict\` only)
|
|
99
|
+
|
|
100
|
+
Coding agents (Claude Code, Cursor, Codex, Gemini CLI, Copilot, OpenCode, or
|
|
101
|
+
any harness setting AI_AGENT) and CI get no prompts, spinners or colour, errors
|
|
102
|
+
as one JSON line on stderr, and the exact flag to pass instead of a question.
|
|
96
103
|
|
|
97
104
|
Run "cargo-ai <command> --help" for details on a specific command group.
|
|
98
105
|
Run "cargo-ai <command> <subcommand> --help" for details on a subcommand.`);
|
|
@@ -130,6 +137,13 @@ program.hook("preAction", (_program, action) => {
|
|
|
130
137
|
if (SETUP_HEADER_SKIP.includes(rootCommandName(action))) {
|
|
131
138
|
return;
|
|
132
139
|
}
|
|
140
|
+
// A coding agent can run on a PTY, so the TTY check behind the header does
|
|
141
|
+
// not keep it out. Agents get the one line they can act on instead, and
|
|
142
|
+
// stderr stays a single parseable JSON error when a command fails.
|
|
143
|
+
if (isCodingAgent() === true) {
|
|
144
|
+
maybeNoticeOutdatedForAgent(version);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
133
147
|
printSetupIssues(collectSetupIssues({
|
|
134
148
|
currentVersion: version,
|
|
135
149
|
latestVersion: cachedLatestVersion(),
|
|
@@ -147,7 +161,13 @@ function rootCommandName(command) {
|
|
|
147
161
|
const getApi = () => {
|
|
148
162
|
const { baseUrl, getAccessToken, workspaceUuid } = getConfig();
|
|
149
163
|
if (getAccessToken === undefined) {
|
|
150
|
-
failWith(
|
|
164
|
+
failWith(NOT_SIGNED_IN_MESSAGE, {
|
|
165
|
+
code: ExitCodes.NotAuthenticated,
|
|
166
|
+
extra: {
|
|
167
|
+
code: "not_authenticated",
|
|
168
|
+
fix: LOGIN_EMAIL_COMMAND,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
151
171
|
}
|
|
152
172
|
return createApi({ baseUrl, getAccessToken, workspaceUuid });
|
|
153
173
|
};
|
|
@@ -16,9 +16,29 @@ export declare function cachedLatestSkillsPin(): string | undefined;
|
|
|
16
16
|
* palette) can put an outdated CLI in that list instead of printing a footer
|
|
17
17
|
* after the fact.
|
|
18
18
|
*
|
|
19
|
-
*
|
|
20
|
-
* `CARGO_NO_UPDATE_NOTIFIER`, and on any network/parse failure — it
|
|
21
|
-
* never block, delay, or break the command that triggered it.
|
|
19
|
+
* Skipped on non-TTY stderr (scripts/CI), when disabled via
|
|
20
|
+
* `CARGO_NO_UPDATE_NOTIFIER`, and silent on any network/parse failure — it
|
|
21
|
+
* must never block, delay, or break the command that triggered it. A coding
|
|
22
|
+
* agent is the exception to the TTY rule: it runs without one, and an agent
|
|
23
|
+
* whose CLI never refreshes this cache would never learn it is behind. The
|
|
24
|
+
* cost is the same as a person's: one 1.5s-capped fetch a day, after the
|
|
25
|
+
* command has finished.
|
|
22
26
|
*/
|
|
23
27
|
export declare function maybeRefreshUpdateCache(): Promise<string | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* The one line a coding agent is told when its CLI is behind, or undefined.
|
|
30
|
+
* Pure, for tests; `maybeNoticeOutdatedForAgent` is the caller.
|
|
31
|
+
*/
|
|
32
|
+
export declare function formatAgentUpdateNotice(input: {
|
|
33
|
+
currentVersion: string;
|
|
34
|
+
latestVersion: string | undefined;
|
|
35
|
+
lastNoticeAt: number | undefined;
|
|
36
|
+
now: number;
|
|
37
|
+
}): string | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Agents never see the amber setup header (index.ts skips it for them), so the outdated
|
|
40
|
+
* CLI is said to them as one stderr line instead, at most hourly, from the
|
|
41
|
+
* cache alone: no network on the command's path.
|
|
42
|
+
*/
|
|
43
|
+
export declare function maybeNoticeOutdatedForAgent(currentVersion: string): void;
|
|
24
44
|
//# sourceMappingURL=updateNotifier.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateNotifier.d.ts","sourceRoot":"","sources":["../src/updateNotifier.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"updateNotifier.d.ts","sourceRoot":"","sources":["../src/updateNotifier.ts"],"names":[],"mappings":"AAqBA;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAYxD;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,SAAS,CAY1D;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA2C3E;AAID;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE;IAC7C,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,GAAG,SAAS,CAiBrB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAyBxE"}
|
package/build/updateNotifier.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isCodingAgent } from "@cargo-ai/cdk/cli";
|
|
2
|
+
import { fetchLatestSkillsPin, fetchLatestVersion, isOutdated, readUpdateCache, UPDATE_COMMAND, writeUpdateCache, } from "./version.js";
|
|
2
3
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // once per day
|
|
3
4
|
const NOTIFY_FETCH_TIMEOUT_MS = 1500;
|
|
4
5
|
const isDisabled = () => {
|
|
@@ -41,12 +42,16 @@ export function cachedLatestSkillsPin() {
|
|
|
41
42
|
* palette) can put an outdated CLI in that list instead of printing a footer
|
|
42
43
|
* after the fact.
|
|
43
44
|
*
|
|
44
|
-
*
|
|
45
|
-
* `CARGO_NO_UPDATE_NOTIFIER`, and on any network/parse failure — it
|
|
46
|
-
* never block, delay, or break the command that triggered it.
|
|
45
|
+
* Skipped on non-TTY stderr (scripts/CI), when disabled via
|
|
46
|
+
* `CARGO_NO_UPDATE_NOTIFIER`, and silent on any network/parse failure — it
|
|
47
|
+
* must never block, delay, or break the command that triggered it. A coding
|
|
48
|
+
* agent is the exception to the TTY rule: it runs without one, and an agent
|
|
49
|
+
* whose CLI never refreshes this cache would never learn it is behind. The
|
|
50
|
+
* cost is the same as a person's: one 1.5s-capped fetch a day, after the
|
|
51
|
+
* command has finished.
|
|
47
52
|
*/
|
|
48
53
|
export async function maybeRefreshUpdateCache() {
|
|
49
|
-
if (process.stderr.isTTY !== true) {
|
|
54
|
+
if (process.stderr.isTTY !== true && isCodingAgent() === false) {
|
|
50
55
|
return cachedLatestVersion();
|
|
51
56
|
}
|
|
52
57
|
if (isDisabled() === true) {
|
|
@@ -72,6 +77,9 @@ export async function maybeRefreshUpdateCache() {
|
|
|
72
77
|
lastCheck: now,
|
|
73
78
|
latestVersion,
|
|
74
79
|
...(latestSkillsPin !== undefined ? { latestSkillsPin } : {}),
|
|
80
|
+
...(cache !== undefined && cache.agentNoticeAt !== undefined
|
|
81
|
+
? { agentNoticeAt: cache.agentNoticeAt }
|
|
82
|
+
: {}),
|
|
75
83
|
};
|
|
76
84
|
writeUpdateCache(cache);
|
|
77
85
|
}
|
|
@@ -82,3 +90,47 @@ export async function maybeRefreshUpdateCache() {
|
|
|
82
90
|
return cachedLatestVersion();
|
|
83
91
|
}
|
|
84
92
|
}
|
|
93
|
+
const AGENT_NOTICE_INTERVAL_MS = 60 * 60 * 1000; // once per hour
|
|
94
|
+
/**
|
|
95
|
+
* The one line a coding agent is told when its CLI is behind, or undefined.
|
|
96
|
+
* Pure, for tests; `maybeNoticeOutdatedForAgent` is the caller.
|
|
97
|
+
*/
|
|
98
|
+
export function formatAgentUpdateNotice(input) {
|
|
99
|
+
if (input.latestVersion === undefined) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
if (isOutdated(input.currentVersion, input.latestVersion) === false) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
if (input.lastNoticeAt !== undefined &&
|
|
106
|
+
input.now - input.lastNoticeAt < AGENT_NOTICE_INTERVAL_MS) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return `cargo-ai ${input.currentVersion} is behind the latest release ${input.latestVersion}. Update with: ${UPDATE_COMMAND}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Agents never see the amber setup header (index.ts skips it for them), so the outdated
|
|
113
|
+
* CLI is said to them as one stderr line instead, at most hourly, from the
|
|
114
|
+
* cache alone: no network on the command's path.
|
|
115
|
+
*/
|
|
116
|
+
export function maybeNoticeOutdatedForAgent(currentVersion) {
|
|
117
|
+
if (isDisabled() === true || isCodingAgent() === false) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const cache = readUpdateCache();
|
|
121
|
+
if (cache === undefined) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
const line = formatAgentUpdateNotice({
|
|
126
|
+
currentVersion,
|
|
127
|
+
latestVersion: cache.latestVersion,
|
|
128
|
+
lastNoticeAt: cache.agentNoticeAt,
|
|
129
|
+
now,
|
|
130
|
+
});
|
|
131
|
+
if (line === undefined) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
process.stderr.write(`${line}\n`);
|
|
135
|
+
writeUpdateCache({ ...cache, agentNoticeAt: now });
|
|
136
|
+
}
|
|
@@ -1,7 +1,21 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
1
2
|
/**
|
|
2
3
|
* Best-effort browser launch. Every caller also prints the URL, so a failure
|
|
3
4
|
* here is never fatal: headless boxes and sandboxes simply fall back to the
|
|
4
5
|
* user opening the link themselves.
|
|
5
6
|
*/
|
|
6
7
|
export declare function openBrowser(url: string): void;
|
|
8
|
+
/**
|
|
9
|
+
* The command that hands `url` to the default browser.
|
|
10
|
+
*
|
|
11
|
+
* Windows goes through rundll32 rather than `cmd /c start`: cmd parses its
|
|
12
|
+
* command line, so every `&` in a query string ends the command there and the
|
|
13
|
+
* browser opens a truncated URL. rundll32 takes the URL as a plain argument.
|
|
14
|
+
*
|
|
15
|
+
* Exported for tests.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildOpenCommand(os: NodeJS.Platform, url: string): {
|
|
18
|
+
command: string;
|
|
19
|
+
args: string[];
|
|
20
|
+
};
|
|
7
21
|
//# sourceMappingURL=openBrowser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openBrowser.d.ts","sourceRoot":"","sources":["../../src/utils/openBrowser.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"openBrowser.d.ts","sourceRoot":"","sources":["../../src/utils/openBrowser.ts"],"names":[],"mappings":";AAGA;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAa7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,MAAM,CAAC,QAAQ,EACnB,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CASrC"}
|
|
@@ -6,8 +6,7 @@ import { platform } from "node:process";
|
|
|
6
6
|
* user opening the link themselves.
|
|
7
7
|
*/
|
|
8
8
|
export function openBrowser(url) {
|
|
9
|
-
const command =
|
|
10
|
-
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
9
|
+
const { command, args } = buildOpenCommand(platform, url);
|
|
11
10
|
try {
|
|
12
11
|
const child = spawn(command, args, {
|
|
13
12
|
detached: true,
|
|
@@ -20,10 +19,23 @@ export function openBrowser(url) {
|
|
|
20
19
|
// Intentionally ignored — see above.
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The command that hands `url` to the default browser.
|
|
24
|
+
*
|
|
25
|
+
* Windows goes through rundll32 rather than `cmd /c start`: cmd parses its
|
|
26
|
+
* command line, so every `&` in a query string ends the command there and the
|
|
27
|
+
* browser opens a truncated URL. rundll32 takes the URL as a plain argument.
|
|
28
|
+
*
|
|
29
|
+
* Exported for tests.
|
|
30
|
+
*/
|
|
31
|
+
export function buildOpenCommand(os, url) {
|
|
32
|
+
if (os === "darwin")
|
|
33
|
+
return { command: "open", args: [url] };
|
|
34
|
+
if (os === "win32") {
|
|
35
|
+
return {
|
|
36
|
+
command: "rundll32",
|
|
37
|
+
args: ["url.dll,FileProtocolHandler", url],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { command: "xdg-open", args: [url] };
|
|
29
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/utils/prompt.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/utils/prompt.ts"],"names":[],"mappings":";AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC,cAAc,CAAC;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAWF,wBAAgB,eAAe,IAAI,WAAW,GAAG,SAAS,CAgBzD;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,CAAC,cAAc,EAC5B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAkB7B"}
|
package/build/utils/prompt.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as readline from "node:readline/promises";
|
|
3
3
|
import * as tty from "node:tty";
|
|
4
|
+
import { mayWaitForPerson } from "../agentMode.js";
|
|
4
5
|
// Interactive prompts must not assume stdin is a terminal: `curl | sh`
|
|
5
6
|
// installers and agent-driven shells pipe stdin while the controlling
|
|
6
7
|
// terminal stays reachable via /dev/tty. Prefer stdin when it is a TTY,
|
|
7
8
|
// otherwise fall back to opening /dev/tty directly.
|
|
9
|
+
//
|
|
10
|
+
// Never for a coding agent or CI: their shells often still reach /dev/tty (the
|
|
11
|
+
// person's terminal, behind the agent), so a prompt there waits for an answer
|
|
12
|
+
// the agent cannot type and the command hangs until the harness kills it.
|
|
13
|
+
// Every caller already has a non-interactive path that names the flag to pass.
|
|
8
14
|
export function openPromptInput() {
|
|
15
|
+
if (mayWaitForPerson() === false) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
9
18
|
if (process.stdin.isTTY === true) {
|
|
10
19
|
return { stream: process.stdin, close: () => undefined };
|
|
11
20
|
}
|
package/build/version.d.ts
CHANGED
|
@@ -9,6 +9,11 @@ export type UpdateCache = {
|
|
|
9
9
|
* caches written before it existed, and when that fetch failed.
|
|
10
10
|
*/
|
|
11
11
|
latestSkillsPin?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Epoch milliseconds of the last time a coding agent was told the CLI is
|
|
14
|
+
* behind, so the line is repeated hourly rather than on every command.
|
|
15
|
+
*/
|
|
16
|
+
agentNoticeAt?: number;
|
|
12
17
|
};
|
|
13
18
|
/**
|
|
14
19
|
* Fetch the latest published version of the CLI from the npm registry.
|
package/build/version.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,cAAc,wCAAwC,CAAC;AAEpE,MAAM,MAAM,WAAW,GAAG;IACxB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAaA,eAAO,MAAM,cAAc,wCAAwC,CAAC;AAEpE,MAAM,MAAM,WAAW,GAAG;IACxB,gEAAgE;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,GAAE,MAAiC,GAC3C,OAAO,CAAC,MAAM,CAAC,CAiBjB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,GAAE,MAAiC,GAC3C,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA2B1D;AAED,2DAA2D;AAC3D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAEnE;AAED,wBAAgB,eAAe,IAAI,WAAW,GAAG,SAAS,CA4BzD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,IAAI,CAOzD"}
|
package/build/version.js
CHANGED
|
@@ -95,6 +95,9 @@ export function readUpdateCache() {
|
|
|
95
95
|
...(typeof parsed.latestSkillsPin === "string"
|
|
96
96
|
? { latestSkillsPin: parsed.latestSkillsPin }
|
|
97
97
|
: {}),
|
|
98
|
+
...(typeof parsed.agentNoticeAt === "number"
|
|
99
|
+
? { agentNoticeAt: parsed.agentNoticeAt }
|
|
100
|
+
: {}),
|
|
98
101
|
};
|
|
99
102
|
}
|
|
100
103
|
return undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cargo-ai/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.146",
|
|
4
4
|
"mcpName": "io.github.getcargohq/cargo",
|
|
5
5
|
"private": false,
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"@ai-sdk/harness": "1.0.144",
|
|
76
76
|
"@ai-sdk/harness-claude-code": "1.0.148",
|
|
77
77
|
"@cargo-ai/api": "^1.0.107",
|
|
78
|
-
"@cargo-ai/app-sdk": "^1.0.
|
|
79
|
-
"@cargo-ai/cdk": "^1.0.
|
|
80
|
-
"@cargo-ai/types": "^1.0.
|
|
78
|
+
"@cargo-ai/app-sdk": "^1.0.12",
|
|
79
|
+
"@cargo-ai/cdk": "^1.0.140",
|
|
80
|
+
"@cargo-ai/types": "^1.0.108",
|
|
81
81
|
"@cargo-ai/utils": "^1.0.37",
|
|
82
82
|
"@cargo-ai/worker-sdk": "^1.0.21",
|
|
83
83
|
"@modelcontextprotocol/sdk": "^1.32.0",
|