@cargo-ai/cli 1.0.12 → 1.0.14

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.
Files changed (75) hide show
  1. package/README.md +23 -2
  2. package/build/api.d.ts +5 -1
  3. package/build/api.d.ts.map +1 -1
  4. package/build/api.js +6 -7
  5. package/build/commands/{auth.d.ts → auth/index.d.ts} +2 -2
  6. package/build/commands/auth/index.d.ts.map +1 -0
  7. package/build/commands/auth/index.js +131 -0
  8. package/build/commands/auth/oauth.d.ts +7 -0
  9. package/build/commands/auth/oauth.d.ts.map +1 -0
  10. package/build/commands/auth/oauth.js +140 -0
  11. package/build/commands/auth/workspace.d.ts +10 -0
  12. package/build/commands/auth/workspace.d.ts.map +1 -0
  13. package/build/commands/auth/workspace.js +120 -0
  14. package/build/commands/billing/subscription.d.ts.map +1 -1
  15. package/build/commands/billing/subscription.js +55 -17
  16. package/build/commands/orchestration/index.d.ts.map +1 -1
  17. package/build/commands/orchestration/index.js +2 -0
  18. package/build/commands/orchestration/query.d.ts +4 -0
  19. package/build/commands/orchestration/query.d.ts.map +1 -0
  20. package/build/commands/orchestration/query.js +23 -0
  21. package/build/commands/orchestration/run.d.ts.map +1 -1
  22. package/build/commands/orchestration/run.js +6 -1
  23. package/build/commands/runHandler.d.ts +32 -1
  24. package/build/commands/runHandler.d.ts.map +1 -1
  25. package/build/commands/runHandler.js +191 -24
  26. package/build/commands/storage/column.d.ts.map +1 -1
  27. package/build/commands/storage/column.js +11 -0
  28. package/build/commands/storage/index.d.ts.map +1 -1
  29. package/build/commands/storage/index.js +2 -0
  30. package/build/commands/storage/query.d.ts +4 -0
  31. package/build/commands/storage/query.d.ts.map +1 -0
  32. package/build/commands/storage/query.js +23 -0
  33. package/build/commands/workspaceManagement/file.d.ts.map +1 -0
  34. package/build/commands/workspaceManagement/folder.d.ts.map +1 -0
  35. package/build/commands/workspaceManagement/index.d.ts +4 -0
  36. package/build/commands/workspaceManagement/index.d.ts.map +1 -0
  37. package/build/commands/workspaceManagement/index.js +19 -0
  38. package/build/commands/workspaceManagement/report.d.ts +4 -0
  39. package/build/commands/workspaceManagement/report.d.ts.map +1 -0
  40. package/build/commands/workspaceManagement/report.js +17 -0
  41. package/build/commands/workspaceManagement/role.d.ts.map +1 -0
  42. package/build/commands/workspaceManagement/token.d.ts.map +1 -0
  43. package/build/commands/{workspace → workspaceManagement}/token.js +3 -2
  44. package/build/commands/workspaceManagement/user.d.ts.map +1 -0
  45. package/build/commands/{workspace → workspaceManagement}/user.js +30 -22
  46. package/build/commands/workspaceManagement/workspaces.d.ts.map +1 -0
  47. package/build/commands/{workspace → workspaceManagement}/workspaces.js +36 -7
  48. package/build/config.d.ts +3 -1
  49. package/build/config.d.ts.map +1 -1
  50. package/build/config.js +19 -17
  51. package/build/index.js +24 -10
  52. package/build/oauthConfig.d.ts +13 -0
  53. package/build/oauthConfig.d.ts.map +1 -0
  54. package/build/oauthConfig.js +17 -0
  55. package/package.json +2 -2
  56. package/build/commands/auth.d.ts.map +0 -1
  57. package/build/commands/auth.js +0 -104
  58. package/build/commands/workspace/file.d.ts.map +0 -1
  59. package/build/commands/workspace/folder.d.ts.map +0 -1
  60. package/build/commands/workspace/index.d.ts +0 -4
  61. package/build/commands/workspace/index.d.ts.map +0 -1
  62. package/build/commands/workspace/index.js +0 -17
  63. package/build/commands/workspace/role.d.ts.map +0 -1
  64. package/build/commands/workspace/token.d.ts.map +0 -1
  65. package/build/commands/workspace/user.d.ts.map +0 -1
  66. package/build/commands/workspace/workspaces.d.ts.map +0 -1
  67. /package/build/commands/{workspace → workspaceManagement}/file.d.ts +0 -0
  68. /package/build/commands/{workspace → workspaceManagement}/file.js +0 -0
  69. /package/build/commands/{workspace → workspaceManagement}/folder.d.ts +0 -0
  70. /package/build/commands/{workspace → workspaceManagement}/folder.js +0 -0
  71. /package/build/commands/{workspace → workspaceManagement}/role.d.ts +0 -0
  72. /package/build/commands/{workspace → workspaceManagement}/role.js +0 -0
  73. /package/build/commands/{workspace → workspaceManagement}/token.d.ts +0 -0
  74. /package/build/commands/{workspace → workspaceManagement}/user.d.ts +0 -0
  75. /package/build/commands/{workspace → workspaceManagement}/workspaces.d.ts +0 -0
package/README.md CHANGED
@@ -23,10 +23,14 @@ npx @cargo-ai/cli orchestration workflow list
23
23
 
24
24
  ## Authentication
25
25
 
26
- ### Login (recommended)
26
+ A single `cargo-ai login` command supports two modes — pass exactly one of `--token` or `--oauth`:
27
27
 
28
28
  ```bash
29
+ # 1. Sign in with an existing workspace-scoped API token
29
30
  cargo-ai login --token <your-api-token>
31
+
32
+ # 2. Sign in via your browser using the OAuth 2.0 Device Authorization Flow
33
+ cargo-ai login --oauth
30
34
  ```
31
35
 
32
36
  Credentials are stored in `~/.config/cargo-ai/credentials.json` (file mode `0600`).
@@ -39,6 +43,23 @@ cargo-ai whoami
39
43
  cargo-ai logout
40
44
  ```
41
45
 
46
+ ### Browser sign-in (`--oauth`)
47
+
48
+ `cargo-ai login --oauth` runs the standard [OAuth 2.0 Device Authorization Flow](https://datatracker.ietf.org/doc/html/rfc8628) (similar UX to `gh auth login`) against the Cargo OAuth provider, prints a verification URL and user code, opens your default browser, and polls until you complete sign-in. On success the access token is written to `~/.config/cargo-ai/credentials.json`.
49
+
50
+ The CLI ships with a built-in OAuth client, so no setup is required. Advanced users can override the provider via `--client-id`, `--domain`, and `--audience`.
51
+
52
+ ### Workspace selection
53
+
54
+ API tokens (`--token`) are workspace-scoped, so the workspace is implicit and nothing is persisted alongside the token.
55
+
56
+ OAuth sessions (`--oauth`) are user-scoped, so after authentication the CLI picks a default workspace and saves it next to the token:
57
+
58
+ - **One workspace**: it is selected automatically.
59
+ - **Multiple workspaces**: you are prompted to pick one (in a TTY); in non-interactive shells the CLI prints the list and asks you to re-run with `--workspace-uuid <uuid>`.
60
+
61
+ Pass `cargo-ai login --oauth --workspace-uuid <uuid>` to skip the prompt. `CARGO_WORKSPACE_UUID` (or per-command `--workspace-uuid` flags where supported) overrides the saved default at runtime.
62
+
42
63
  ### Environment variables (override)
43
64
 
44
65
  Environment variables take precedence over saved credentials, useful for CI or temporary overrides.
@@ -81,7 +102,7 @@ cargo-ai orchestration workflow --help
81
102
  | ------------------------ | ---------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
82
103
  | **init** | Workspace initialisation (user, workspace, datasets, etc.) | `cargo-ai init` |
83
104
  | **orchestration** | Workflows, plays, runs, batches, tools, templates | `cargo-ai orchestration workflow list`, `cargo-ai orchestration run list --workflow-uuid <uuid>` |
84
- | **workspace** | Workspaces, users, tokens, roles, folders | `cargo-ai workspace workspaces list`, `cargo-ai workspace token list` |
105
+ | **workspaceManagement** | Workspaces, users, tokens, roles, folders | `cargo-ai workspaceManagement workspaces list`, `cargo-ai workspaceManagement token list` |
85
106
  | **storage** | Datasets, models, relationships, runs, records | `cargo-ai storage dataset list`, `cargo-ai storage model list` |
86
107
  | **connection** | Connectors and integrations | `cargo-ai connection connector list`, `cargo-ai connection integration list` |
87
108
  | **billing** | Usage and subscription | `cargo-ai billing subscription get`, `cargo-ai billing usage get-metrics --payload '{"from":"2025-01-01","to":"2025-01-31"}'` |
package/build/api.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { type Api } from "@cargo-ai/api";
2
2
  export type { Api };
3
- export declare function createApi(): Api;
3
+ export declare function createApi(opts: {
4
+ baseUrl: string;
5
+ accessToken: string;
6
+ workspaceUuid: string | undefined;
7
+ }): Api;
4
8
  //# sourceMappingURL=api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,eAAe,CAAC;AAInD,YAAY,EAAE,GAAG,EAAE,CAAC;AAMpB,wBAAgB,SAAS,IAAI,GAAG,CAQ/B"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,GAAG,EAAY,MAAM,eAAe,CAAC;AAEnD,YAAY,EAAE,GAAG,EAAE,CAAC;AAQpB,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC,GAAG,GAAG,CAON"}
package/build/api.js CHANGED
@@ -1,14 +1,13 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { buildApi } from "@cargo-ai/api";
3
- import { getConfig } from "./config.js";
4
3
  const require = createRequire(import.meta.url);
5
4
  const { version } = require("../package.json");
6
- export function createApi() {
7
- const { baseUrl, accessToken, workspaceUuid } = getConfig();
5
+ const ORIGIN = { name: "cli", version };
6
+ export function createApi(opts) {
8
7
  return buildApi({
9
- baseUrl,
10
- workspaceUuid,
11
- getAccessToken: async () => accessToken,
12
- origin: { name: "cli", version },
8
+ baseUrl: opts.baseUrl,
9
+ workspaceUuid: opts.workspaceUuid,
10
+ accessToken: opts.accessToken,
11
+ origin: ORIGIN,
13
12
  });
14
13
  }
@@ -1,4 +1,4 @@
1
1
  import type { Command } from "commander";
2
- import type { Api } from "../api.js";
2
+ import type { Api } from "../../api.js";
3
3
  export declare function registerAuthCommands(program: Command, getApi: () => Api): void;
4
- //# sourceMappingURL=auth.d.ts.map
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AA8BxC,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAiFN"}
@@ -0,0 +1,131 @@
1
+ import { getConfig } from "../../config.js";
2
+ import { clearCredentials, getCredentialsPath, saveCredentials, } from "../../credentials.js";
3
+ import { ExitCodes, failWith, handleApiCall, info, outputJson, success, } from "../runHandler.js";
4
+ import { runOAuthDeviceFlow } from "./oauth.js";
5
+ import { resolveWorkspace } from "./workspace.js";
6
+ const DEFAULT_BASE_URL = "https://api.getcargo.io";
7
+ export function registerAuthCommands(program, getApi) {
8
+ program
9
+ .command("login")
10
+ .description("Sign in to Cargo with an API token (--token) or via browser (--oauth)")
11
+ .option("--token <token>", "Cargo API token; mutually exclusive with --oauth")
12
+ .option("--oauth", "Sign in via browser using the OAuth 2.0 Device Authorization Flow")
13
+ .option("--base-url <url>", `Cargo API base URL (string, default: ${DEFAULT_BASE_URL})`)
14
+ .option("--workspace-uuid <uuid>", "Default workspace UUID to save (--oauth only; skips the workspace prompt)")
15
+ .option("--client-id <id>", "OAuth native application client ID (--oauth only; defaults to the built-in Cargo CLI client)")
16
+ .option("--domain <domain>", "OAuth provider domain (--oauth only; default: auth.getcargo.io)")
17
+ .option("--audience <audience>", "OAuth API audience (--oauth only; default: https://api.getcargo.io)")
18
+ .addHelpText("after", `
19
+ Examples:
20
+ $ cargo-ai login --token sk_live_abc123
21
+ $ cargo-ai login --token sk_live_abc123 --base-url https://api.custom.io
22
+ $ cargo-ai login --oauth
23
+ $ cargo-ai login --oauth --workspace-uuid 550e8400-e29b-41d4-a716-446655440000
24
+
25
+ Credentials are stored in ~/.config/cargo-ai/credentials.json.
26
+ Environment variables (CARGO_API_TOKEN, CARGO_WORKSPACE_UUID, CARGO_BASE_URL) take precedence over saved credentials.`)
27
+ .action(async (opts) => {
28
+ await runLogin(opts);
29
+ });
30
+ program
31
+ .command("logout")
32
+ .description("Remove saved API credentials")
33
+ .action(() => {
34
+ const removed = clearCredentials();
35
+ if (removed === true) {
36
+ success("Signed out.");
37
+ }
38
+ else {
39
+ info("No saved credentials to remove.");
40
+ }
41
+ outputJson({
42
+ ok: true,
43
+ removed,
44
+ credentialsFile: getCredentialsPath(),
45
+ });
46
+ });
47
+ program
48
+ .command("whoami")
49
+ .description("Show current user and workspace")
50
+ .addHelpText("after", `
51
+ Returns JSON with: user (uuid, email, name), workspace (uuid, name),
52
+ authentication source ("environment" or "credentials-file"), and base URL.`)
53
+ .action(async () => {
54
+ await runWhoami(getApi);
55
+ });
56
+ }
57
+ async function runLogin(opts) {
58
+ const hasToken = opts.token !== undefined;
59
+ const wantsOAuth = opts.oauth === true;
60
+ if (hasToken === true && wantsOAuth === true) {
61
+ failWith("Cannot combine --token and --oauth; pass only one.", {
62
+ code: ExitCodes.InvalidUsage,
63
+ });
64
+ }
65
+ if (hasToken === false && wantsOAuth === false) {
66
+ failWith("Must pass --token <token> (API token) or --oauth (browser sign-in).", { code: ExitCodes.InvalidUsage });
67
+ }
68
+ const baseUrl = opts.baseUrl !== undefined && opts.baseUrl.length > 0
69
+ ? opts.baseUrl
70
+ : DEFAULT_BASE_URL;
71
+ if (hasToken === true) {
72
+ const token = opts.token;
73
+ saveCredentials({
74
+ accessToken: token,
75
+ baseUrl: opts.baseUrl,
76
+ });
77
+ success("Signed in with API token.");
78
+ outputJson({
79
+ ok: true,
80
+ credentialsFile: getCredentialsPath(),
81
+ baseUrl,
82
+ tokenType: "api-token",
83
+ });
84
+ return;
85
+ }
86
+ const result = await runOAuthDeviceFlow({
87
+ clientId: opts.clientId,
88
+ domain: opts.domain,
89
+ audience: opts.audience,
90
+ });
91
+ const workspace = await resolveWorkspace({
92
+ baseUrl,
93
+ accessToken: result.accessToken,
94
+ explicitUuid: opts.workspaceUuid,
95
+ });
96
+ saveCredentials({
97
+ accessToken: result.accessToken,
98
+ workspaceUuid: workspace.uuid,
99
+ baseUrl: opts.baseUrl,
100
+ });
101
+ success(`Signed in. Default workspace: ${workspace.name}.`);
102
+ outputJson({
103
+ ok: true,
104
+ credentialsFile: getCredentialsPath(),
105
+ baseUrl,
106
+ workspace,
107
+ tokenType: "oauth-jwt",
108
+ expiresIn: result.expiresIn,
109
+ });
110
+ }
111
+ async function runWhoami(getApi) {
112
+ const { accessToken, baseUrl, source } = getConfig();
113
+ if (accessToken === undefined) {
114
+ failWith('Not authenticated. Run "cargo-ai login --token <token>" or "cargo-ai login --oauth".', { code: ExitCodes.NotAuthenticated });
115
+ }
116
+ const api = getApi();
117
+ const { user } = await handleApiCall(() => api.userManagement.user.getCurrent());
118
+ const { workspace } = await handleApiCall(() => api.workspaceManagement.workspace.getCurrent());
119
+ outputJson({
120
+ user: {
121
+ uuid: user.uuid,
122
+ email: user.email,
123
+ firstName: user.firstName,
124
+ lastName: user.lastName,
125
+ },
126
+ workspace: { uuid: workspace.uuid, name: workspace.name },
127
+ source,
128
+ baseUrl,
129
+ credentialsFile: getCredentialsPath(),
130
+ });
131
+ }
@@ -0,0 +1,7 @@
1
+ import { type OAuthConfigOverrides } from "../../oauthConfig.js";
2
+ export type OAuthLoginResult = {
3
+ accessToken: string;
4
+ expiresIn: number;
5
+ };
6
+ export declare function runOAuthDeviceFlow(overrides: OAuthConfigOverrides): Promise<OAuthLoginResult>;
7
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/oauth.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,sBAAsB,CAAC;AA8B9B,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,oBAAoB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CA0B3B"}
@@ -0,0 +1,140 @@
1
+ import { spawn } from "node:child_process";
2
+ import { platform } from "node:process";
3
+ import { getOAuthConfig, } from "../../oauthConfig.js";
4
+ import { failWith, info } from "../runHandler.js";
5
+ const DEFAULT_POLL_INTERVAL_SECONDS = 5;
6
+ const SLOW_DOWN_BACKOFF_SECONDS = 5;
7
+ const DEVICE_CODE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
8
+ export async function runOAuthDeviceFlow(overrides) {
9
+ const oauthConfig = getOAuthConfig(overrides);
10
+ const deviceCode = await requestDeviceCode(oauthConfig);
11
+ info([
12
+ "",
13
+ "To complete sign-in, open this URL in a browser:",
14
+ ` ${deviceCode.verification_uri_complete}`,
15
+ "",
16
+ `If your browser does not show the code prefilled, enter it manually: ${deviceCode.user_code}`,
17
+ "",
18
+ "Waiting for you to finish in the browser...",
19
+ "",
20
+ ].join("\n"));
21
+ openBrowser(deviceCode.verification_uri_complete);
22
+ const token = await pollForToken(oauthConfig, deviceCode);
23
+ return {
24
+ accessToken: token.access_token,
25
+ expiresIn: token.expires_in,
26
+ };
27
+ }
28
+ async function requestDeviceCode(config) {
29
+ const body = new URLSearchParams({
30
+ client_id: config.clientId,
31
+ scope: config.scope,
32
+ audience: config.audience,
33
+ });
34
+ body.set("screen_hint", "signup");
35
+ const response = await fetch(`https://${config.domain}/oauth/device/code`, {
36
+ method: "POST",
37
+ headers: { "content-type": "application/x-www-form-urlencoded" },
38
+ body: body.toString(),
39
+ });
40
+ if (response.ok === false) {
41
+ const text = await safeReadText(response);
42
+ failWith(`Failed to start device authorization (HTTP ${String(response.status)}): ${text}`);
43
+ }
44
+ const json = (await response.json());
45
+ if (typeof json.device_code !== "string" ||
46
+ typeof json.user_code !== "string" ||
47
+ typeof json.verification_uri_complete !== "string" ||
48
+ typeof json.expires_in !== "number" ||
49
+ typeof json.interval !== "number") {
50
+ failWith(`Unexpected device authorization response from OAuth provider: ${JSON.stringify(json)}`);
51
+ }
52
+ return json;
53
+ }
54
+ async function pollForToken(config, deviceCode) {
55
+ const startedAtMs = Date.now();
56
+ const expiresAtMs = startedAtMs + deviceCode.expires_in * 1000;
57
+ let intervalSeconds = deviceCode.interval > 0
58
+ ? deviceCode.interval
59
+ : DEFAULT_POLL_INTERVAL_SECONDS;
60
+ while (Date.now() < expiresAtMs) {
61
+ await sleep(intervalSeconds * 1000);
62
+ const response = await fetch(`https://${config.domain}/oauth/token`, {
63
+ method: "POST",
64
+ headers: { "content-type": "application/x-www-form-urlencoded" },
65
+ body: new URLSearchParams({
66
+ grant_type: DEVICE_CODE_GRANT,
67
+ device_code: deviceCode.device_code,
68
+ client_id: config.clientId,
69
+ }).toString(),
70
+ });
71
+ if (response.ok === true) {
72
+ const token = (await response.json());
73
+ if (typeof token.access_token !== "string") {
74
+ failWith(`Unexpected token response from OAuth provider: ${JSON.stringify(token)}`);
75
+ }
76
+ return token;
77
+ }
78
+ const errorBody = (await safeReadJson(response));
79
+ const errorCode = errorBody !== undefined ? errorBody.error : undefined;
80
+ if (errorCode === "authorization_pending") {
81
+ continue;
82
+ }
83
+ if (errorCode === "slow_down") {
84
+ intervalSeconds = intervalSeconds + SLOW_DOWN_BACKOFF_SECONDS;
85
+ continue;
86
+ }
87
+ if (errorCode === "expired_token" ||
88
+ errorCode === "access_denied" ||
89
+ errorCode === "invalid_grant") {
90
+ failWith(`Authorization failed: ${errorCode}${errorBody !== undefined && errorBody.error_description !== undefined
91
+ ? ` - ${errorBody.error_description}`
92
+ : ""}`, { extra: { oauthError: errorBody } });
93
+ }
94
+ failWith(`Token polling failed (HTTP ${String(response.status)}): ${errorBody !== undefined ? JSON.stringify(errorBody) : "no body"}`, { extra: { oauthError: errorBody } });
95
+ }
96
+ failWith("Authorization timed out before the user completed the flow.");
97
+ }
98
+ function openBrowser(url) {
99
+ const command = pickOpenCommand();
100
+ const args = platform === "win32" ? ["/c", "start", "", url] : [url];
101
+ try {
102
+ const child = spawn(command, args, {
103
+ detached: true,
104
+ stdio: "ignore",
105
+ });
106
+ child.on("error", () => {
107
+ // Browser launch failed silently; user can still use the printed URL.
108
+ });
109
+ child.unref();
110
+ }
111
+ catch {
112
+ // Ignore; user can still use the printed URL.
113
+ }
114
+ }
115
+ function pickOpenCommand() {
116
+ if (platform === "darwin")
117
+ return "open";
118
+ if (platform === "win32")
119
+ return "cmd";
120
+ return "xdg-open";
121
+ }
122
+ function sleep(ms) {
123
+ return new Promise((resolve) => setTimeout(resolve, ms));
124
+ }
125
+ async function safeReadText(response) {
126
+ try {
127
+ return await response.text();
128
+ }
129
+ catch {
130
+ return "<unreadable body>";
131
+ }
132
+ }
133
+ async function safeReadJson(response) {
134
+ try {
135
+ return await response.json();
136
+ }
137
+ catch {
138
+ return undefined;
139
+ }
140
+ }
@@ -0,0 +1,10 @@
1
+ export type ResolvedWorkspace = {
2
+ uuid: string;
3
+ name: string;
4
+ };
5
+ export declare function resolveWorkspace(opts: {
6
+ baseUrl: string;
7
+ accessToken: string;
8
+ explicitUuid: string | undefined;
9
+ }): Promise<ResolvedWorkspace>;
10
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../../src/commands/auth/workspace.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;CAClC,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiG7B"}
@@ -0,0 +1,120 @@
1
+ import * as readline from "node:readline/promises";
2
+ import { createApi } from "../../api.js";
3
+ import { ExitCodes, failWith, info, startSpinner, success, } from "../runHandler.js";
4
+ export async function resolveWorkspace(opts) {
5
+ const api = createApi({
6
+ baseUrl: opts.baseUrl,
7
+ accessToken: opts.accessToken,
8
+ workspaceUuid: undefined,
9
+ });
10
+ let workspaces;
11
+ try {
12
+ const result = await api.workspaceManagement.workspace.all({
13
+ refresh: false,
14
+ });
15
+ workspaces = result.workspaces;
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ failWith(`Failed to list workspaces: ${message}`);
20
+ }
21
+ if (opts.explicitUuid !== undefined) {
22
+ const matchedWorkspace = workspaces.find((workspace) => {
23
+ return workspace.uuid === opts.explicitUuid;
24
+ });
25
+ if (matchedWorkspace === undefined) {
26
+ failWith(`Workspace ${opts.explicitUuid} is not in your accessible workspaces.`, { code: ExitCodes.NotFound });
27
+ }
28
+ return { uuid: matchedWorkspace.uuid, name: matchedWorkspace.name };
29
+ }
30
+ if (workspaces.length === 0) {
31
+ if (process.stdin.isTTY !== true) {
32
+ failWith("No workspaces found.", { code: ExitCodes.NotFound });
33
+ }
34
+ const name = await promptWorkspaceName();
35
+ if (name === undefined) {
36
+ failWith("No workspace name provided.", {
37
+ code: ExitCodes.InvalidUsage,
38
+ });
39
+ }
40
+ const spinner = startSpinner("Creating workspace...");
41
+ let createdWorkspace;
42
+ try {
43
+ createdWorkspace = await api.workspaceManagement.workspace.create({
44
+ name,
45
+ });
46
+ }
47
+ catch (err) {
48
+ spinner.stop();
49
+ const message = err instanceof Error ? err.message : String(err);
50
+ failWith(`Failed to create workspace: ${message}`);
51
+ }
52
+ spinner.stop();
53
+ success(`Created workspace: ${createdWorkspace.workspace.name} (${createdWorkspace.workspace.uuid})`);
54
+ return {
55
+ uuid: createdWorkspace.workspace.uuid,
56
+ name: createdWorkspace.workspace.name,
57
+ };
58
+ }
59
+ if (workspaces.length === 1) {
60
+ const [firstWorkspace] = workspaces;
61
+ if (firstWorkspace === undefined) {
62
+ failWith("Unexpected empty workspace list after length check.");
63
+ }
64
+ return { uuid: firstWorkspace.uuid, name: firstWorkspace.name };
65
+ }
66
+ if (process.stdin.isTTY !== true) {
67
+ failWith("Multiple workspaces available; pass --workspace-uuid <uuid> to select one.", { code: ExitCodes.InvalidUsage });
68
+ }
69
+ const selectedWorkspace = await promptWorkspaceSelection(workspaces);
70
+ if (selectedWorkspace === undefined) {
71
+ failWith("No workspace selected.", { code: ExitCodes.InvalidUsage });
72
+ }
73
+ return selectedWorkspace;
74
+ }
75
+ async function promptWorkspaceName() {
76
+ const rl = readline.createInterface({
77
+ input: process.stdin,
78
+ output: process.stderr,
79
+ });
80
+ try {
81
+ info("You don't have any workspaces yet. Let's create one.");
82
+ const answer = await rl.question("Workspace name (or press Enter to skip): ");
83
+ const trimmed = answer.trim();
84
+ if (trimmed === "") {
85
+ return undefined;
86
+ }
87
+ return trimmed;
88
+ }
89
+ finally {
90
+ rl.close();
91
+ }
92
+ }
93
+ async function promptWorkspaceSelection(workspaces) {
94
+ const rl = readline.createInterface({
95
+ input: process.stdin,
96
+ output: process.stderr,
97
+ });
98
+ try {
99
+ info("You have multiple workspaces. Pick one to set as the default for this CLI:");
100
+ workspaces.forEach((workspace, idx) => {
101
+ info(` ${String(idx + 1)}) ${workspace.name} (${workspace.uuid})`);
102
+ });
103
+ info("");
104
+ const maxAttempts = 3;
105
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
106
+ const answer = await rl.question(`Enter a number [1-${String(workspaces.length)}]: `);
107
+ const trimmed = answer.trim();
108
+ const index = Number.parseInt(trimmed, 10);
109
+ const selectedWorkspace = workspaces[index - 1];
110
+ if (selectedWorkspace !== undefined) {
111
+ return { uuid: selectedWorkspace.uuid, name: selectedWorkspace.name };
112
+ }
113
+ info(`Invalid selection: ${trimmed}`);
114
+ }
115
+ return undefined;
116
+ }
117
+ finally {
118
+ rl.close();
119
+ }
120
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../src/commands/billing/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAkKN"}
1
+ {"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../src/commands/billing/subscription.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAGxC,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GAAG,GAChB,IAAI,CAsPN"}
@@ -5,7 +5,11 @@ export function registerSubscriptionCommands(parent, getApi) {
5
5
  .description("Subscription operations");
6
6
  subscription
7
7
  .command("get")
8
- .description("Get current subscription")
8
+ .description("Get the current subscription for this workspace")
9
+ .addHelpText("after", `
10
+ Examples:
11
+ $ cargo-ai billing subscription get
12
+ $ cargo-ai billing subscription get | jq '.subscription.plan'`)
9
13
  .action(async () => {
10
14
  const api = getApi();
11
15
  const result = await handleApiCall(() => api.billing.subscription.get());
@@ -13,7 +17,11 @@ export function registerSubscriptionCommands(parent, getApi) {
13
17
  });
14
18
  subscription
15
19
  .command("get-invoices")
16
- .description("Get invoices")
20
+ .description("List invoices for the current workspace")
21
+ .addHelpText("after", `
22
+ Examples:
23
+ $ cargo-ai billing subscription get-invoices
24
+ $ cargo-ai billing subscription get-invoices | jq '.invoices | length'`)
17
25
  .action(async () => {
18
26
  const api = getApi();
19
27
  const result = await handleApiCall(() => api.billing.subscription.getInvoices());
@@ -21,7 +29,10 @@ export function registerSubscriptionCommands(parent, getApi) {
21
29
  });
22
30
  subscription
23
31
  .command("get-credit-card")
24
- .description("Get credit card")
32
+ .description("Get the credit card on file for the current workspace")
33
+ .addHelpText("after", `
34
+ Example:
35
+ $ cargo-ai billing subscription get-credit-card`)
25
36
  .action(async () => {
26
37
  const api = getApi();
27
38
  const result = await handleApiCall(() => api.billing.subscription.getCreditCard());
@@ -29,7 +40,10 @@ export function registerSubscriptionCommands(parent, getApi) {
29
40
  });
30
41
  subscription
31
42
  .command("create-setup-intent")
32
- .description("Create a setup intent")
43
+ .description("Create a Stripe setup intent for adding/updating a payment method")
44
+ .addHelpText("after", `
45
+ Example:
46
+ $ cargo-ai billing subscription create-setup-intent | jq -r .clientSecret`)
33
47
  .action(async () => {
34
48
  const api = getApi();
35
49
  const result = await handleApiCall(() => api.billing.subscription.createSetupIntent());
@@ -37,8 +51,11 @@ export function registerSubscriptionCommands(parent, getApi) {
37
51
  });
38
52
  subscription
39
53
  .command("update-payment-method")
40
- .description("Update payment method")
41
- .requiredOption("--payment-method-id <id>", "Stripe payment method ID")
54
+ .description("Replace the workspace's payment method")
55
+ .requiredOption("--payment-method-id <id>", "Stripe payment method ID (e.g. pm_1OabcXYZ...)")
56
+ .addHelpText("after", `
57
+ Example:
58
+ $ cargo-ai billing subscription update-payment-method --payment-method-id pm_1OabcXYZ`)
42
59
  .action(async (opts) => {
43
60
  const api = getApi();
44
61
  const result = await handleApiCall(() => api.billing.subscription.updatePaymentMethod({
@@ -48,7 +65,10 @@ export function registerSubscriptionCommands(parent, getApi) {
48
65
  });
49
66
  subscription
50
67
  .command("create-portal-session")
51
- .description("Create a portal session")
68
+ .description("Create a Stripe customer portal session URL")
69
+ .addHelpText("after", `
70
+ Example:
71
+ $ cargo-ai billing subscription create-portal-session | jq -r .url`)
52
72
  .action(async () => {
53
73
  const api = getApi();
54
74
  const result = await handleApiCall(() => api.billing.subscription.createPortalSession());
@@ -56,7 +76,10 @@ export function registerSubscriptionCommands(parent, getApi) {
56
76
  });
57
77
  subscription
58
78
  .command("create-stripe-credits-checkout-session")
59
- .description("Create a Stripe credits checkout session")
79
+ .description("Create a Stripe checkout session for purchasing credits")
80
+ .addHelpText("after", `
81
+ Example:
82
+ $ cargo-ai billing subscription create-stripe-credits-checkout-session | jq -r .url`)
60
83
  .action(async () => {
61
84
  const api = getApi();
62
85
  const result = await handleApiCall(() => api.billing.subscription.createStripeCreditsCheckoutSession());
@@ -64,9 +87,13 @@ export function registerSubscriptionCommands(parent, getApi) {
64
87
  });
65
88
  subscription
66
89
  .command("update-plan")
67
- .description("Update subscription plan")
68
- .requiredOption("--credits-count <n>", "Number of credits")
69
- .requiredOption("--cadence <cadence>", "Subscription cadence")
90
+ .description("Change the credit allotment and billing cadence")
91
+ .requiredOption("--credits-count <n>", "Number of credits per period")
92
+ .requiredOption("--cadence <cadence>", "Billing cadence. Allowed values: monthly, yearly")
93
+ .addHelpText("after", `
94
+ Examples:
95
+ $ cargo-ai billing subscription update-plan --credits-count 10000 --cadence monthly
96
+ $ cargo-ai billing subscription update-plan --credits-count 50000 --cadence yearly`)
70
97
  .action(async (opts) => {
71
98
  const api = getApi();
72
99
  const result = await handleApiCall(() => api.billing.subscription.updatePlan({
@@ -77,10 +104,14 @@ export function registerSubscriptionCommands(parent, getApi) {
77
104
  });
78
105
  subscription
79
106
  .command("update-topup")
80
- .description("Update topup settings")
81
- .option("--enabled", "Enable topup")
82
- .requiredOption("--credits-count <n>", "Number of credits")
83
- .requiredOption("--threshold <n>", "Threshold")
107
+ .description("Configure automatic credit top-ups")
108
+ .option("--enabled", "Enable automatic top-ups (omit to disable)")
109
+ .requiredOption("--credits-count <n>", "Number of credits to add per top-up")
110
+ .requiredOption("--threshold <n>", "Trigger top-up when remaining credits drop below this number")
111
+ .addHelpText("after", `
112
+ Examples:
113
+ $ cargo-ai billing subscription update-topup --enabled --credits-count 5000 --threshold 1000
114
+ $ cargo-ai billing subscription update-topup --credits-count 5000 --threshold 1000 # disabled`)
84
115
  .action(async (opts) => {
85
116
  const api = getApi();
86
117
  const result = await handleApiCall(() => api.billing.subscription.updateTopup({
@@ -92,11 +123,18 @@ export function registerSubscriptionCommands(parent, getApi) {
92
123
  });
93
124
  subscription
94
125
  .command("update-credits-overage-behaviors")
95
- .description("Update credits overage behaviors")
96
- .requiredOption("--behavior <unit:action>", "Behavior as unit:action (e.g. billing.credits:deny), repeatable", (val, acc) => {
126
+ .description("Set how the workspace behaves when a credit unit runs out (allow vs deny)")
127
+ .requiredOption("--behavior <unit:action>", "Behavior as unit:action (e.g. billing.credits:deny). Repeat to set multiple units.", (val, acc) => {
97
128
  acc.push(val);
98
129
  return acc;
99
130
  }, [])
131
+ .addHelpText("after", `
132
+ Examples:
133
+ $ cargo-ai billing subscription update-credits-overage-behaviors \\
134
+ --behavior billing.credits:deny
135
+ $ cargo-ai billing subscription update-credits-overage-behaviors \\
136
+ --behavior billing.credits:allow \\
137
+ --behavior data.records:deny`)
100
138
  .action(async (opts) => {
101
139
  const creditsOverageBehaviors = opts.behavior.map((b) => {
102
140
  const parts = b.split(":");