@cargo-ai/cli 1.0.67 → 1.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -142,7 +142,6 @@ cargo-ai orchestration workflow --help
142
142
 
143
143
  | Domain | Description | Example commands |
144
144
  | ------------------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
145
- | **init** | Workspace initialisation (user, workspace, datasets, etc.) | `cargo-ai init` |
146
145
  | **orchestration** | Workflows, plays, runs, batches, tools, templates | `cargo-ai orchestration workflow list`, `cargo-ai orchestration run list --workflow-uuid <uuid>` |
147
146
  | **workspaceManagement** | Workspaces, users, tokens, roles, folders | `cargo-ai workspaceManagement workspaces list`, `cargo-ai workspaceManagement token list` |
148
147
  | **storage** | Datasets, models, relationships, runs, records | `cargo-ai storage dataset list`, `cargo-ai storage model list` |
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,WAAW,CAAC;AAGjD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AA0BxD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CA2GN;AAiGD,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,IAAI,EAAE,aAAa,GAClB,cAAc,CAchB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,OAAO,EAAU,MAAM,WAAW,CAAC;AAGjD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AA0BxD,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAsIN;AAiGD,MAAM,MAAM,cAAc,GAAG;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,WAAW,GAAG,SAAS,EACpC,IAAI,EAAE,aAAa,GAClB,cAAc,CAchB"}
@@ -1,4 +1,4 @@
1
- import { revokeSession } from "@cargo-ai/cdk/cli";
1
+ import { declarePrompt, revokeSession } from "@cargo-ai/cdk/cli";
2
2
  import { Option } from "commander";
3
3
  import { AGENT_SKILLS_NEXT_STEP } from "../../agentSkills.js";
4
4
  import { getConfig } from "../../config.js";
@@ -10,7 +10,7 @@ import { selectLoginChannel } from "./loginChannel.js";
10
10
  import { runOAuthDeviceFlow } from "./oauth.js";
11
11
  import { DEFAULT_BASE_URL, establishSession, resolveBaseUrl, } from "./session.js";
12
12
  export function registerAuthCommands(program, getApi) {
13
- program
13
+ const login = program
14
14
  .command("login")
15
15
  .description("Sign in to Cargo with an emailed code (--email), via browser (--oauth), or with an existing API token (--token)")
16
16
  .option("--email <email>", "Sign in with a code emailed to this address; creates the account on first use")
@@ -53,6 +53,32 @@ Environment variables (CARGO_API_TOKEN, CARGO_WORKSPACE_UUID, CARGO_BASE_URL) ta
53
53
  .action(async (opts) => {
54
54
  await runLogin(opts);
55
55
  });
56
+ // Commander can say an option is required but not that exactly one of three
57
+ // must be present, so the palette saw a command with no inputs, ran it bare,
58
+ // and got the handler's own usage error back. The choice is declared here,
59
+ // beside the options it chooses between.
60
+ declarePrompt(login, {
61
+ message: "how do you want to sign in?",
62
+ choices: [
63
+ {
64
+ label: "Browser sign-in",
65
+ hint: "opens a browser; no code to copy",
66
+ flag: "--oauth",
67
+ },
68
+ {
69
+ label: "Emailed code",
70
+ hint: "no browser needed; signs you up on first use",
71
+ flag: "--email",
72
+ value: { name: "email", hint: "you@company.com" },
73
+ },
74
+ {
75
+ label: "Existing API token",
76
+ hint: "for CI, or a token you already hold",
77
+ flag: "--token",
78
+ value: { name: "token", hint: "", secret: true },
79
+ },
80
+ ],
81
+ });
56
82
  program
57
83
  .command("logout")
58
84
  .description("Sign out: revoke the saved credential and remove it locally")
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgKzC,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,GACrB,IAAI,CAoDN"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiKzC,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,cAAc,EAAE,MAAM,GACrB,IAAI,CAyEN"}
@@ -1,45 +1,29 @@
1
- import * as fs from "node:fs";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
1
  import { determineIfIsFetcherError } from "@cargo-ai/api";
2
+ import { formatSetupIssues } from "@cargo-ai/cdk/cli";
5
3
  import { createApi } from "../api.js";
6
4
  import { getConfig } from "../config.js";
7
5
  import { getCredentialsPath } from "../credentials.js";
8
- import { fetchLatestVersion, isOutdated } from "../version.js";
9
- import { ExitCodes, outputJson } from "./runHandler.js";
10
- const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
11
- function getSkillsPinCandidates() {
12
- const home = os.homedir();
13
- const envDir = process.env["CARGO_SKILLS_DIR"];
14
- const candidates = [];
15
- if (envDir !== undefined && envDir.length > 0) {
16
- candidates.push(path.join(envDir, "cargo", "cli-version"));
17
- }
18
- candidates.push(path.join(home, ".claude", "skills", "cargo", "cli-version"), path.join(home, ".claude", "plugins", "marketplaces", "cargo", "cargo", "cli-version"), path.join(home, ".openclaw", "skills", "cargo", "cli-version"));
19
- return candidates;
20
- }
21
- function readSkillsPin() {
22
- for (const candidate of getSkillsPinCandidates()) {
23
- try {
24
- const raw = fs.readFileSync(candidate, "utf8").trim();
25
- if (SEMVER_PATTERN.test(raw)) {
26
- return { version: raw, path: candidate };
27
- }
28
- }
29
- catch {
30
- // Missing or unreadable — try the next candidate.
31
- }
32
- }
33
- return undefined;
34
- }
6
+ import { collectSetupIssues, readSkillsPin } from "../setupIssues.js";
7
+ import { fetchLatestVersion, isOutdated, UPDATE_COMMAND } from "../version.js";
8
+ import { colors, ExitCodes, outputJson } from "./runHandler.js";
9
+ const LOGIN_COMMAND = "cargo-ai login --oauth";
10
+ const STATUS_PAGE = "https://status.getcargo.io";
35
11
  function buildCliCheck(currentVersion, latestVersion) {
36
12
  if (latestVersion === undefined) {
37
13
  return { current: currentVersion, latest: null, upToDate: null };
38
14
  }
15
+ if (isOutdated(currentVersion, latestVersion)) {
16
+ return {
17
+ current: currentVersion,
18
+ latest: latestVersion,
19
+ upToDate: false,
20
+ fix: UPDATE_COMMAND,
21
+ };
22
+ }
39
23
  return {
40
24
  current: currentVersion,
41
25
  latest: latestVersion,
42
- upToDate: !isOutdated(currentVersion, latestVersion),
26
+ upToDate: true,
43
27
  };
44
28
  }
45
29
  function buildSkillsPinCheck(currentVersion) {
@@ -47,11 +31,25 @@ function buildSkillsPinCheck(currentVersion) {
47
31
  if (pin === undefined) {
48
32
  return { found: false };
49
33
  }
34
+ if (pin.version === currentVersion) {
35
+ return {
36
+ found: true,
37
+ pinned: pin.version,
38
+ path: pin.path,
39
+ matchesCli: true,
40
+ };
41
+ }
50
42
  return {
51
43
  found: true,
52
44
  pinned: pin.version,
53
45
  path: pin.path,
54
- matchesCli: pin.version === currentVersion,
46
+ matchesCli: false,
47
+ // Whichever side is behind is the side to update: an older CLI cannot run
48
+ // what the skills ask of it, and older skills describe commands this CLI
49
+ // has moved on from.
50
+ fix: isOutdated(currentVersion, pin.version)
51
+ ? UPDATE_COMMAND
52
+ : "npx skills add getcargohq/cargo-skills",
55
53
  };
56
54
  }
57
55
  async function checkApi(config) {
@@ -63,6 +61,7 @@ async function checkApi(config) {
63
61
  status: "no-credentials",
64
62
  message: 'Not authenticated. Run "cargo-ai login --email <email>" (no browser needed) or "cargo-ai login --oauth".',
65
63
  },
64
+ fix: LOGIN_COMMAND,
66
65
  },
67
66
  exitCode: ExitCodes.NotAuthenticated,
68
67
  };
@@ -105,6 +104,9 @@ async function checkApi(config) {
105
104
  status,
106
105
  message: error instanceof Error ? error.message : String(error),
107
106
  },
107
+ // A rejected or mis-scoped credential is fixed by signing in again;
108
+ // anything else is the service, not this machine, so point at status.
109
+ fix: status === 401 || status === 403 ? LOGIN_COMMAND : STATUS_PAGE,
108
110
  },
109
111
  exitCode,
110
112
  };
@@ -146,10 +148,67 @@ Examples:
146
148
  credentialsFile: getCredentialsPath(),
147
149
  };
148
150
  const { check: api, exitCode } = await checkApi(config);
151
+ // One list, most severe first, each entry carrying its own fix — the same
152
+ // shape the pre-command header renders, so a reader who has seen the
153
+ // amber line recognises it here rather than learning a second format.
154
+ const offline = collectSetupIssues({
155
+ currentVersion,
156
+ latestVersion,
157
+ });
158
+ const apiIssue = describeApiIssue(api);
159
+ const issues = apiIssue === undefined
160
+ ? offline
161
+ : [
162
+ apiIssue,
163
+ // The live call is authoritative about the credential, so it
164
+ // replaces the offline "is there a file" guess.
165
+ ...offline.filter((issue) => issue.key !== "not-signed-in"),
166
+ ];
149
167
  outputJson({
150
168
  ok: exitCode === ExitCodes.Success,
169
+ issues,
151
170
  checks: { cli, skillsPin, credentials, api },
152
171
  });
172
+ printReport(issues, api);
153
173
  process.exit(exitCode);
154
174
  });
155
175
  }
176
+ function describeApiIssue(api) {
177
+ if (api.ok === true) {
178
+ return undefined;
179
+ }
180
+ if (api.error.status === "no-credentials") {
181
+ return { key: "not-signed-in", label: "not signed in", fix: api.fix };
182
+ }
183
+ if (api.error.status === 401) {
184
+ return {
185
+ key: "credential-rejected",
186
+ label: "credential rejected",
187
+ fix: api.fix,
188
+ };
189
+ }
190
+ if (api.error.status === 403) {
191
+ return {
192
+ key: "workspace-denied",
193
+ label: "no access to this workspace",
194
+ fix: api.fix,
195
+ };
196
+ }
197
+ return { key: "api-unreachable", label: "API unreachable", fix: api.fix };
198
+ }
199
+ /**
200
+ * The human half. stdout stays the JSON contract scripts and agents parse, so
201
+ * this goes to stderr and only when someone is watching.
202
+ */
203
+ function printReport(issues, api) {
204
+ if (process.stderr.isTTY !== true) {
205
+ return;
206
+ }
207
+ const issueLine = formatSetupIssues(issues);
208
+ if (issueLine !== undefined) {
209
+ process.stderr.write(`${issueLine}\n`);
210
+ return;
211
+ }
212
+ const where = api.ok === true ? ` ${colors.dim("·")} ${api.workspace.name}` : "";
213
+ process.stderr.write(`${colors.green("✓")} Setup looks healthy${where}\n`);
214
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAgBxC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAsqB5E"}
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../../src/commands/orchestration/run.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAgBxC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CA+rB5E"}
@@ -1,3 +1,4 @@
1
+ import { declarePrompt } from "@cargo-ai/cdk/cli";
1
2
  import { parseBooleanOption } from "../../utils/booleanOption.js";
2
3
  import { loadCompiledWorkflow, resolveCompiledWorkflowFromState, } from "../../utils/loadCompiledWorkflow.js";
3
4
  import { ExitCodes, failWith, handleApiCall, info, outputJson, parseJson, pollRunUntilFinished, } from "../runHandler.js";
@@ -5,10 +6,10 @@ export function registerRunCommands(parent, getApi) {
5
6
  const run = parent
6
7
  .command("run")
7
8
  .description("Manage workflow runs (list, get, create, cancel, count, download)");
8
- run
9
+ const listRuns = run
9
10
  .command("list")
10
11
  .description("List runs")
11
- .requiredOption("--workflow-uuid <uuid>", "Workflow UUID (string, required)")
12
+ .option("--workflow-uuid <uuid>", "Workflow UUID (string). Omit to list the runs of every play and tool, which the API allows only alongside --created-after inside a 30-day window")
12
13
  .option("--batch-uuid <uuid>", "Filter by batch UUID (string)")
13
14
  .option("--release-uuid <uuid>", "Filter by release UUID (string)")
14
15
  .option("--statuses <list>", "Filter by statuses (comma-separated: idle,pending,running,success,error,cancelling,cancelled,skipped)")
@@ -32,7 +33,8 @@ export function registerRunCommands(parent, getApi) {
32
33
  Examples:
33
34
  $ cargo-ai orchestration run list --workflow-uuid 550e8400-...
34
35
  $ cargo-ai orchestration run list --workflow-uuid 550e8400-... --statuses running,pending --limit 50
35
- $ cargo-ai orchestration run list --workflow-uuid 550e8400-... --created-after 2025-01-01T00:00:00Z`)
36
+ $ cargo-ai orchestration run list --workflow-uuid 550e8400-... --created-after 2025-01-01T00:00:00Z
37
+ $ cargo-ai orchestration run list --created-after 2025-01-01T00:00:00Z # every play and tool`)
36
38
  .action(async (opts) => {
37
39
  const api = getApi();
38
40
  const result = await handleApiCall(() => api.orchestration.run.list({
@@ -141,10 +143,10 @@ Examples:
141
143
  await handleApiCall(() => api.orchestration.run.cancel(payload));
142
144
  outputJson({ ok: true });
143
145
  });
144
- run
146
+ const countRuns = run
145
147
  .command("count")
146
148
  .description("Count runs matching the given filters")
147
- .requiredOption("--workflow-uuid <uuid>", "Workflow UUID (string, required)")
149
+ .option("--workflow-uuid <uuid>", "Workflow UUID (string). Omit to count the runs of every play and tool, which the API allows only alongside --created-after inside a 30-day window")
148
150
  .option("--batch-uuid <uuid>", "Filter by batch UUID (string)")
149
151
  .option("--release-uuid <uuid>", "Filter by release UUID (string)")
150
152
  .option("--statuses <list>", "Filter by statuses (comma-separated: idle,pending,running,success,error,cancelling,cancelled,skipped)")
@@ -188,6 +190,29 @@ Examples:
188
190
  }));
189
191
  outputJson(result);
190
192
  });
193
+ // Both of these take the option or go without it, and going without it is
194
+ // only legal alongside --created-after. Commander cannot express that, so the
195
+ // palette would offer a command that fails on the server's own message. The
196
+ // choice is declared here instead, beside the option it chooses between.
197
+ for (const command of [listRuns, countRuns]) {
198
+ declarePrompt(command, {
199
+ message: "which runs?",
200
+ choices: [
201
+ {
202
+ label: "One workflow",
203
+ hint: "a single play or tool",
204
+ flag: "--workflow-uuid",
205
+ value: { name: "workflow-uuid", hint: "550e8400-e29b-41d4-a716-…" },
206
+ },
207
+ {
208
+ label: "Every play and tool",
209
+ hint: "needs a start date; the API allows at most a 30-day window",
210
+ flag: "--created-after",
211
+ value: { name: "created-after", hint: "2026-08-01T00:00:00Z" },
212
+ },
213
+ ],
214
+ });
215
+ }
191
216
  run
192
217
  .command("download")
193
218
  .description("Download run results as JSON")
package/build/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
- import { registerCommands as registerCdkCommands, registerManifestCommands, SessionExpiredError, } from "@cargo-ai/cdk/cli";
3
+ import { BRAND, canRunPalette, fetchCreditsLeft, fetchWorkspaceName, formatWorkspaceSuffix, printSetupIssues, registerCommands as registerCdkCommands, runPalette, SessionExpiredError, } 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";
@@ -13,7 +13,6 @@ import { registerContextCommands } from "./commands/context/index.js";
13
13
  import { registerDoctorCommand } from "./commands/doctor.js";
14
14
  import { registerExpressionCommands } from "./commands/expression/index.js";
15
15
  import { registerHostingCommands } from "./commands/hosting/index.js";
16
- import { registerInitCommand } from "./commands/init.js";
17
16
  import { registerMailboxManagementCommands } from "./commands/mailboxManagement/index.js";
18
17
  import { registerMcpCommand } from "./commands/mcp.js";
19
18
  import { registerObservabilityCommands } from "./commands/observability/index.js";
@@ -27,7 +26,8 @@ import { registerUserManagementCommands } from "./commands/userManagement/index.
27
26
  import { registerVersionCommand } from "./commands/version.js";
28
27
  import { registerWorkspaceManagementCommands } from "./commands/workspaceManagement/index.js";
29
28
  import { getConfig } from "./config.js";
30
- import { maybeNotifyUpdate } from "./updateNotifier.js";
29
+ import { collectSetupIssues, isSignedIn } from "./setupIssues.js";
30
+ import { cachedLatestVersion, maybeRefreshUpdateCache, } from "./updateNotifier.js";
31
31
  const require = createRequire(import.meta.url);
32
32
  const { version } = require("../package.json");
33
33
  const program = new Command();
@@ -71,6 +71,40 @@ Exit codes:
71
71
 
72
72
  Run "cargo-ai <command> --help" for details on a specific command group.
73
73
  Run "cargo-ai <command> <subcommand> --help" for details on a subcommand.`);
74
+ // Commands that are themselves the fix, or that already say all of this
75
+ // better: the header would either duplicate their output or nag someone who is
76
+ // mid-repair.
77
+ const SETUP_HEADER_SKIP = ["login", "logout", "doctor", "version", "mcp"];
78
+ /**
79
+ * Say what is wrong before the command runs rather than after it fails, and
80
+ * name the fix. Every check behind this is a file read (see `setupIssues.ts`)
81
+ * and the line only renders for a TTY, so scripts and agents pay nothing.
82
+ *
83
+ * A one-shot CLI has no session to show this once per, so someone who has
84
+ * decided to live with a mismatch would otherwise see it before every command
85
+ * forever. Give them the same opt-out the update notifier has.
86
+ */
87
+ program.hook("preAction", (_program, action) => {
88
+ const suppressed = process.env["CARGO_NO_SETUP_HEADER"];
89
+ if (suppressed === "1" || suppressed === "true") {
90
+ return;
91
+ }
92
+ if (SETUP_HEADER_SKIP.includes(rootCommandName(action))) {
93
+ return;
94
+ }
95
+ printSetupIssues(collectSetupIssues({
96
+ currentVersion: version,
97
+ latestVersion: cachedLatestVersion(),
98
+ }));
99
+ });
100
+ /** The top-level group a subcommand belongs to (`connection`, not `list`). */
101
+ function rootCommandName(command) {
102
+ const parent = command.parent;
103
+ if (parent === null || parent.parent === null) {
104
+ return command.name();
105
+ }
106
+ return rootCommandName(parent);
107
+ }
74
108
  const getApi = () => {
75
109
  const { baseUrl, getAccessToken, workspaceUuid } = getConfig();
76
110
  if (getAccessToken === undefined) {
@@ -81,7 +115,6 @@ const getApi = () => {
81
115
  registerAuthCommands(program, getApi);
82
116
  registerVersionCommand(program, version);
83
117
  registerDoctorCommand(program, version);
84
- registerInitCommand(program, getApi);
85
118
  registerObservabilityCommands(program, getApi);
86
119
  registerOrchestrationCommands(program, getApi);
87
120
  registerWorkspaceManagementCommands(program, getApi);
@@ -102,12 +135,43 @@ registerMcpCommand(program, getApi);
102
135
  registerCdkCommands(program
103
136
  .command("cdk")
104
137
  .description("Cargo CDK — define resources in code and deploy them (plan/deploy)"), getApi);
105
- registerManifestCommands(program
106
- .command("manifest")
107
- .description("Manifest scaffold a GTM repo (context, infra, skills, evals, outputs)"));
108
- program
109
- .parseAsync()
110
- .then(() => maybeNotifyUpdate(version))
138
+ // Bare `cargo-ai` in a terminal opens the palette instead of printing the help
139
+ // wall: twenty command groups is a list to navigate, not one to read. Anything
140
+ // non-interactive (a pipe, CI, an agent) keeps the help it has always had.
141
+ const bare = process.argv.slice(2).length === 0 && canRunPalette();
142
+ const start = bare ? openPalette() : program.parseAsync();
143
+ async function openPalette() {
144
+ const [title, latestVersion] = await Promise.all([
145
+ paletteTitle(),
146
+ maybeRefreshUpdateCache(),
147
+ ]);
148
+ await runPalette({
149
+ program,
150
+ binPath: process.argv[1] === undefined ? "cargo-ai" : process.argv[1],
151
+ title,
152
+ issues: collectSetupIssues({ currentVersion: version, latestVersion }),
153
+ });
154
+ }
155
+ /**
156
+ * `🧱 cargo-ai 1.0.66 · Acme · 9,657.48 credits`. The two lookups behind the
157
+ * second half are the same best-effort, time-boxed ones `cargo-cdk` uses, so a
158
+ * bare invocation still opens promptly when the network does not answer — and
159
+ * they are skipped entirely when there is no credential to make them with.
160
+ */
161
+ async function paletteTitle() {
162
+ const base = `${BRAND} cargo-ai ${version}`;
163
+ if (isSignedIn() === false) {
164
+ return base;
165
+ }
166
+ const api = getApi();
167
+ const [name, creditsLeft] = await Promise.all([
168
+ fetchWorkspaceName(api),
169
+ fetchCreditsLeft(api),
170
+ ]);
171
+ return `${base}${formatWorkspaceSuffix(name, creditsLeft)}`;
172
+ }
173
+ start
174
+ .then(() => maybeRefreshUpdateCache())
111
175
  .catch((err) => {
112
176
  // An expired session is the ordinary way to lose authentication, so it has
113
177
  // to carry the documented exit code rather than the generic one.
@@ -0,0 +1,37 @@
1
+ import type { SetupIssue } from "@cargo-ai/cdk/cli";
2
+ export type SkillsPin = {
3
+ version: string;
4
+ path: string;
5
+ };
6
+ export type SetupIssueInput = {
7
+ currentVersion: string;
8
+ /**
9
+ * A version from the update cache (or a live fetch). Passing one opts this
10
+ * caller in to reporting an outdated CLI, so the version sits with the
11
+ * other setup issues rather than as a footer after the command. The
12
+ * palette and the pre-command header pass the cache; `doctor` fetches live.
13
+ */
14
+ latestVersion?: string;
15
+ };
16
+ /**
17
+ * The two facts this reads off the machine. Injected so the rules — which side
18
+ * of a pin mismatch to update, when an outdated CLI is this line's business at
19
+ * all — can be tested without a home directory.
20
+ */
21
+ export type SetupIssueDeps = {
22
+ isSignedIn: () => boolean;
23
+ readSkillsPin: () => SkillsPin | undefined;
24
+ };
25
+ export declare function collectSetupIssues(input: SetupIssueInput, deps?: SetupIssueDeps): SetupIssue[];
26
+ /**
27
+ * The CLI version the installed skills bundle was written against, from
28
+ * whichever agent's skills directory holds one.
29
+ */
30
+ export declare function readSkillsPin(): SkillsPin | undefined;
31
+ /**
32
+ * Deliberately not `getConfig()`: that resolves the whole auth config and exits
33
+ * the process on a workspace conflict, which a header printed before every
34
+ * command must never do. Presence of a credential is all this line needs.
35
+ */
36
+ export declare function isSignedIn(): boolean;
37
+ //# sourceMappingURL=setupIssues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setupIssues.d.ts","sourceRoot":"","sources":["../src/setupIssues.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAWpD,MAAM,MAAM,SAAS,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,MAAM,MAAM,eAAe,GAAG;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,UAAU,EAAE,MAAM,OAAO,CAAC;IAC1B,aAAa,EAAE,MAAM,SAAS,GAAG,SAAS,CAAC;CAC5C,CAAC;AAIF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,GAAE,cAA4B,GACjC,UAAU,EAAE,CAyCd;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,SAAS,GAAG,SAAS,CAcrD;AAED;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAYpC"}
@@ -0,0 +1,93 @@
1
+ // What is wrong with this machine's Cargo setup, and the command that fixes
2
+ // each one.
3
+ //
4
+ // This runs before every command, so every check here is a small file read or
5
+ // cheaper — no network. `cargo-ai doctor` is the same list with the online
6
+ // checks added.
7
+ import * as fs from "node:fs";
8
+ import * as os from "node:os";
9
+ import * as path from "node:path";
10
+ import { loadCredentials } from "./credentials.js";
11
+ import { isOutdated, UPDATE_COMMAND } from "./version.js";
12
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
13
+ const SKILLS_UPDATE_COMMAND = "npx skills add getcargohq/cargo-skills";
14
+ const LOGIN_COMMAND = "cargo-ai login --oauth";
15
+ const machineDeps = { isSignedIn, readSkillsPin };
16
+ export function collectSetupIssues(input, deps = machineDeps) {
17
+ const issues = [];
18
+ if (deps.isSignedIn() === false) {
19
+ issues.push({
20
+ key: "not-signed-in",
21
+ label: "not signed in",
22
+ fix: LOGIN_COMMAND,
23
+ });
24
+ }
25
+ const latest = input.latestVersion;
26
+ if (latest !== undefined &&
27
+ isOutdated(input.currentVersion, latest) === true) {
28
+ issues.push({
29
+ key: "cli-outdated",
30
+ label: `CLI ${input.currentVersion} → ${latest}`,
31
+ fix: UPDATE_COMMAND,
32
+ });
33
+ }
34
+ const pin = deps.readSkillsPin();
35
+ if (pin !== undefined && pin.version !== input.currentVersion) {
36
+ // The pin records the CLI the installed skills were written against. Which
37
+ // side is behind decides the fix: an older CLI cannot run what the skills
38
+ // ask of it, and older skills describe commands this CLI has moved on from.
39
+ issues.push({
40
+ key: "skills-pin-mismatch",
41
+ label: `skills expect CLI ${pin.version}`,
42
+ fix: isOutdated(input.currentVersion, pin.version) === true
43
+ ? UPDATE_COMMAND
44
+ : SKILLS_UPDATE_COMMAND,
45
+ });
46
+ }
47
+ return issues;
48
+ }
49
+ /**
50
+ * The CLI version the installed skills bundle was written against, from
51
+ * whichever agent's skills directory holds one.
52
+ */
53
+ export function readSkillsPin() {
54
+ for (const candidate of skillsPinCandidates()) {
55
+ try {
56
+ const raw = fs.readFileSync(candidate, "utf8").trim();
57
+ if (SEMVER_PATTERN.test(raw)) {
58
+ return { version: raw, path: candidate };
59
+ }
60
+ }
61
+ catch {
62
+ // Missing or unreadable — try the next candidate.
63
+ }
64
+ }
65
+ return undefined;
66
+ }
67
+ /**
68
+ * Deliberately not `getConfig()`: that resolves the whole auth config and exits
69
+ * the process on a workspace conflict, which a header printed before every
70
+ * command must never do. Presence of a credential is all this line needs.
71
+ */
72
+ export function isSignedIn() {
73
+ const token = process.env["CARGO_API_TOKEN"];
74
+ if (token !== undefined && token.trim() !== "") {
75
+ return true;
76
+ }
77
+ try {
78
+ return loadCredentials() !== undefined;
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }
84
+ function skillsPinCandidates() {
85
+ const home = os.homedir();
86
+ const envDir = process.env["CARGO_SKILLS_DIR"];
87
+ const candidates = [];
88
+ if (envDir !== undefined && envDir.length > 0) {
89
+ candidates.push(path.join(envDir, "cargo", "cli-version"));
90
+ }
91
+ candidates.push(path.join(home, ".claude", "skills", "cargo", "cli-version"), path.join(home, ".claude", "plugins", "marketplaces", "cargo", "cargo", "cli-version"), path.join(home, ".openclaw", "skills", "cargo", "cli-version"));
92
+ return candidates;
93
+ }
@@ -1,9 +1,18 @@
1
1
  /**
2
- * Best-effort "update available" nudge, printed to stderr after a command's
3
- * own output. The registry is checked at most once per 24h (result cached in
4
- * the config dir). Stays silent on non-TTY stderr (scripts/CI), when disabled
5
- * via CARGO_NO_UPDATE_NOTIFIER, and on any network/parse failure — it must
2
+ * Latest version already sitting in the on-disk cache. No network. Undefined
3
+ * when the check is disabled or nothing has been fetched yet the header
4
+ * then simply omits the outdated-CLI issue rather than guessing.
5
+ */
6
+ export declare function cachedLatestVersion(): string | undefined;
7
+ /**
8
+ * Refresh the registry cache at most once per 24h. Returns the latest version
9
+ * we now know about, so a caller that is about to render setup issues (the
10
+ * palette) can put an outdated CLI in that list instead of printing a footer
11
+ * after the fact.
12
+ *
13
+ * Stays silent on non-TTY stderr (scripts/CI), when disabled via
14
+ * `CARGO_NO_UPDATE_NOTIFIER`, and on any network/parse failure — it must
6
15
  * never block, delay, or break the command that triggered it.
7
16
  */
8
- export declare function maybeNotifyUpdate(currentVersion: string): Promise<void>;
17
+ export declare function maybeRefreshUpdateCache(): Promise<string | undefined>;
9
18
  //# sourceMappingURL=updateNotifier.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"updateNotifier.d.ts","sourceRoot":"","sources":["../src/updateNotifier.ts"],"names":[],"mappings":"AAmBA;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B7E"}
1
+ {"version":3,"file":"updateNotifier.d.ts","sourceRoot":"","sources":["../src/updateNotifier.ts"],"names":[],"mappings":"AAgBA;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,SAAS,CAYxD;AAED;;;;;;;;;GASG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAwB3E"}
@@ -1,5 +1,4 @@
1
- import { colors, info } from "./commands/runHandler.js";
2
- import { fetchLatestVersion, isOutdated, readUpdateCache, UPDATE_COMMAND, writeUpdateCache, } from "./version.js";
1
+ import { fetchLatestVersion, readUpdateCache, writeUpdateCache, } from "./version.js";
3
2
  const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // once per day
4
3
  const NOTIFY_FETCH_TIMEOUT_MS = 1500;
5
4
  const isDisabled = () => {
@@ -7,18 +6,37 @@ const isDisabled = () => {
7
6
  return (flag === "1" || flag === "true" || process.env["NO_UPDATE_NOTIFIER"] === "1");
8
7
  };
9
8
  /**
10
- * Best-effort "update available" nudge, printed to stderr after a command's
11
- * own output. The registry is checked at most once per 24h (result cached in
12
- * the config dir). Stays silent on non-TTY stderr (scripts/CI), when disabled
13
- * via CARGO_NO_UPDATE_NOTIFIER, and on any network/parse failure — it must
9
+ * Latest version already sitting in the on-disk cache. No network. Undefined
10
+ * when the check is disabled or nothing has been fetched yet the header
11
+ * then simply omits the outdated-CLI issue rather than guessing.
12
+ */
13
+ export function cachedLatestVersion() {
14
+ if (isDisabled() === true) {
15
+ return undefined;
16
+ }
17
+ const cache = readUpdateCache();
18
+ if (cache === undefined) {
19
+ return undefined;
20
+ }
21
+ return cache.latestVersion;
22
+ }
23
+ /**
24
+ * Refresh the registry cache at most once per 24h. Returns the latest version
25
+ * we now know about, so a caller that is about to render setup issues (the
26
+ * palette) can put an outdated CLI in that list instead of printing a footer
27
+ * after the fact.
28
+ *
29
+ * Stays silent on non-TTY stderr (scripts/CI), when disabled via
30
+ * `CARGO_NO_UPDATE_NOTIFIER`, and on any network/parse failure — it must
14
31
  * never block, delay, or break the command that triggered it.
15
32
  */
16
- export async function maybeNotifyUpdate(currentVersion) {
17
- // Only nudge interactive users; never pollute scripted/CI stderr.
18
- if (process.stderr.isTTY !== true)
19
- return;
20
- if (isDisabled())
21
- return;
33
+ export async function maybeRefreshUpdateCache() {
34
+ if (process.stderr.isTTY !== true) {
35
+ return cachedLatestVersion();
36
+ }
37
+ if (isDisabled() === true) {
38
+ return undefined;
39
+ }
22
40
  try {
23
41
  let cache = readUpdateCache();
24
42
  const now = Date.now();
@@ -27,12 +45,10 @@ export async function maybeNotifyUpdate(currentVersion) {
27
45
  cache = { lastCheck: now, latestVersion };
28
46
  writeUpdateCache(cache);
29
47
  }
30
- if (isOutdated(currentVersion, cache.latestVersion)) {
31
- info(`${colors.yellow("⚠")} A new version of cargo-ai is available: ${colors.dim(currentVersion)} → ${colors.green(cache.latestVersion)}`);
32
- info(` Update with: ${colors.cyan(UPDATE_COMMAND)}`);
33
- }
48
+ return cache.latestVersion;
34
49
  }
35
50
  catch {
36
51
  // Fail open: an update check must never get in the way of the command.
52
+ return cachedLatestVersion();
37
53
  }
38
54
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cargo-ai/cli",
3
- "version": "1.0.67",
3
+ "version": "1.0.70",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "homepage": "https://getcargo.ai",
@@ -66,7 +66,7 @@
66
66
  "dependencies": {
67
67
  "@cargo-ai/api": "^1.0.69",
68
68
  "@cargo-ai/app-sdk": "^1.0.9",
69
- "@cargo-ai/cdk": "^1.0.53",
69
+ "@cargo-ai/cdk": "^1.0.56",
70
70
  "@cargo-ai/types": "^1.0.66",
71
71
  "@cargo-ai/worker-sdk": "^1.0.17",
72
72
  "@modelcontextprotocol/sdk": "1.29.0",
@@ -1,4 +0,0 @@
1
- import type { Command } from "commander";
2
- import type { Api } from "../api.js";
3
- export declare function registerInitCommand(parent: Command, getApi: () => Api): void;
4
- //# sourceMappingURL=init.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGrC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,IAAI,CAW5E"}
@@ -1,11 +0,0 @@
1
- import { handleApiCall, outputJson } from "./runHandler.js";
2
- export function registerInitCommand(parent, getApi) {
3
- parent
4
- .command("init")
5
- .description("Fetch workspace initialisation (user, workspace, datasets, etc.)")
6
- .action(async () => {
7
- const api = getApi();
8
- const result = await handleApiCall(() => api.getInitialisation());
9
- outputJson(result);
10
- });
11
- }