@cargo-ai/cli 1.0.145 → 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.
@@ -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;AAsHD;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ7D"}
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("Multiple workspaces available and no interactive terminal; pass --workspace-uuid <uuid> (or set CARGO_WORKSPACE_UUID) to select one.", { code: ExitCodes.InvalidUsage });
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
- const workspace = await createWorkspace(api, name);
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
- const result = await handleApiCall(() => api.workspaceManagement.workspace.create({ name }), { spinner: "Creating workspace..." });
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
  /**
@@ -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,CAc7C"}
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 = pickOpenCommand();
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
- function pickOpenCommand() {
24
- if (platform === "darwin")
25
- return "open";
26
- if (platform === "win32")
27
- return "cmd";
28
- return "xdg-open";
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cargo-ai/cli",
3
- "version": "1.0.145",
3
+ "version": "1.0.146",
4
4
  "mcpName": "io.github.getcargohq/cargo",
5
5
  "private": false,
6
6
  "license": "UNLICENSED",
@@ -77,7 +77,7 @@
77
77
  "@cargo-ai/api": "^1.0.107",
78
78
  "@cargo-ai/app-sdk": "^1.0.12",
79
79
  "@cargo-ai/cdk": "^1.0.140",
80
- "@cargo-ai/types": "^1.0.107",
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",