@asteroidai/cli 1.9.27 → 1.9.30

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/dist/asteroid.js CHANGED
@@ -2637,7 +2637,7 @@ var COMMANDS = [
2637
2637
  command: "secret-request create",
2638
2638
  tool: "secret_requests_create",
2639
2639
  title: "Create request",
2640
- description: "Create a secret request. The item key is derived from name unless itemKey is given. The share token is returned once and never listed again. The recipient submits values on the public page; that creates the secret.",
2640
+ description: "Create a secret request. The item key is derived from name unless itemKey is given. The recipient URL and its token are returned once and never listed again. Send it to the person who holds the credentials. They submit values on that page, which creates the secret.",
2641
2641
  method: "POST",
2642
2642
  path: "/agents/v2/secret-requests",
2643
2643
  args: [],
@@ -5550,7 +5550,7 @@ var ApiError = class extends Error {
5550
5550
  status;
5551
5551
  body;
5552
5552
  };
5553
- var VERSION = "1.9.27";
5553
+ var VERSION = "1.9.30";
5554
5554
  function problem(status, body, fallback) {
5555
5555
  if (body && typeof body === "object") {
5556
5556
  const record2 = body;
package/dist/core.js CHANGED
@@ -2658,7 +2658,7 @@ var COMMANDS = [
2658
2658
  command: "secret-request create",
2659
2659
  tool: "secret_requests_create",
2660
2660
  title: "Create request",
2661
- description: "Create a secret request. The item key is derived from name unless itemKey is given. The share token is returned once and never listed again. The recipient submits values on the public page; that creates the secret.",
2661
+ description: "Create a secret request. The item key is derived from name unless itemKey is given. The recipient URL and its token are returned once and never listed again. Send it to the person who holds the credentials. They submit values on that page, which creates the secret.",
2662
2662
  method: "POST",
2663
2663
  path: "/agents/v2/secret-requests",
2664
2664
  args: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asteroidai/cli",
3
- "version": "1.9.27",
3
+ "version": "1.9.30",
4
4
  "description": "The asteroid command-line interface for the Asteroid platform.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -8,15 +8,10 @@
8
8
  "asteroid": "./dist/asteroid.js"
9
9
  },
10
10
  "exports": {
11
- "./core": {
12
- "types": "./src/core.ts",
13
- "development": "./src/core.ts",
14
- "default": "./dist/core.js"
15
- }
11
+ "./core": "./dist/core.js"
16
12
  },
17
13
  "files": [
18
- "dist",
19
- "src"
14
+ "dist"
20
15
  ],
21
16
  "engines": {
22
17
  "node": ">=22"
package/src/account.ts DELETED
@@ -1,501 +0,0 @@
1
- import { UsageError } from "./args";
2
- import {
3
- API_KEY_OVERRIDES,
4
- type Auth,
5
- AuthError,
6
- isStaffLogin,
7
- NotMemberError,
8
- orgFromToken,
9
- rememberOrg,
10
- resolveAuth,
11
- staffHint,
12
- UNSET_API_KEY,
13
- } from "./auth/credentials";
14
- import { CredentialStore, type StaffToken } from "./auth/store";
15
- import {
16
- authorizeDevice,
17
- pollDevice,
18
- tokenClaims,
19
- type WorkosOrg,
20
- } from "./auth/workos";
21
- import {
22
- type Config,
23
- findOrg,
24
- loadConfig,
25
- mergeOrgs,
26
- type Org,
27
- orgLabel,
28
- requestedOrg,
29
- saveConfig,
30
- } from "./config";
31
- import { ApiError, send } from "./http";
32
- import { clean, render } from "./output";
33
- import type { Deps } from "./run";
34
-
35
- async function chooseOrg(deps: Deps, orgs: WorkosOrg[]): Promise<WorkosOrg> {
36
- if (orgs.length === 0)
37
- throw new AuthError("your account belongs to no organization");
38
- if (orgs.length === 1 || !deps.ui) return orgs[0];
39
- const id = await deps.ui.select(
40
- "Choose an organization",
41
- orgs.map((org) => ({ value: org.id, label: clean(org.name) })),
42
- );
43
- const chosen = orgs.find((org) => org.id === id);
44
- if (!chosen) throw new AuthError("sign-in cancelled");
45
- return chosen;
46
- }
47
-
48
- export async function login(deps: Deps): Promise<number> {
49
- if (deps.env.apiKey)
50
- deps.stderr(
51
- `Warning: ${API_KEY_OVERRIDES} Every command will use the key, not this sign-in. ${UNSET_API_KEY}\n`,
52
- );
53
- const client = {
54
- clientId: deps.env.workosClientId,
55
- apiUrl: deps.env.workosApiUrl,
56
- fetch: deps.fetch,
57
- };
58
- const device = await authorizeDevice(client);
59
- const url = device.verification_uri_complete ?? device.verification_uri;
60
- deps.stderr(
61
- `Open ${clean(url)}\nand confirm the code ${clean(device.user_code)}\n`,
62
- );
63
- deps.openUrl?.(url);
64
-
65
- let offered: WorkosOrg[] = [];
66
- const polling = pollDevice(client, device, (orgs) => {
67
- offered = orgs;
68
- return chooseOrg(deps, orgs);
69
- });
70
- const session = deps.ui
71
- ? await deps.ui.wait(
72
- "Waiting for you to confirm in the browser",
73
- "Confirmed",
74
- polling,
75
- )
76
- : await polling;
77
-
78
- const store = new CredentialStore(deps.env);
79
- const where = await store.writeLogin({
80
- refreshToken: session.refreshToken,
81
- email: session.email,
82
- });
83
- const org = orgFromToken(session.accessToken);
84
- const expiresAt = (tokenClaims(session.accessToken).exp ?? 0) * 1000;
85
- if (org && expiresAt)
86
- store.cacheToken(org.workosId, {
87
- accessToken: session.accessToken,
88
- expiresAt,
89
- });
90
-
91
- const config = loadConfig(deps.env);
92
- config.orgs = mergeOrgs(
93
- config.orgs,
94
- offered.map((o) => ({ workosId: o.id, name: o.name })),
95
- );
96
- config.org = undefined;
97
- saveConfig(deps.env, config);
98
- rememberOrg(deps.env, config, org);
99
-
100
- deps.stderr(
101
- `Signed in as ${clean(session.email ?? "you")}${config.org ? ` in ${orgLabel(config.org)}` : ""}. ` +
102
- `Token saved to the ${where === "keychain" ? "OS keychain" : "config directory"}.\n`,
103
- );
104
- return 0;
105
- }
106
-
107
- export async function logout(deps: Deps): Promise<number> {
108
- const cleared = await new CredentialStore(deps.env).clear();
109
- saveConfig(deps.env, { ...loadConfig(deps.env), org: undefined });
110
- if (!cleared) {
111
- deps.stderr(
112
- "Could not remove the sign-in from the OS keychain. Delete the asteroid-cli entry by hand.\n",
113
- );
114
- return 1;
115
- }
116
- deps.stderr("Signed out.\n");
117
- return 0;
118
- }
119
-
120
- export async function orgs(
121
- deps: Deps,
122
- flagOrg: string | undefined,
123
- json: boolean,
124
- ): Promise<number> {
125
- if (deps.env.apiKey)
126
- throw new UsageError(
127
- `${API_KEY_OVERRIDES} The key acts in one organization; \`asteroid whoami\` shows it. ${UNSET_API_KEY}`,
128
- );
129
- const config = loadConfig(deps.env);
130
- const auth = await resolveAuth(
131
- deps.env,
132
- config,
133
- requestedOrg(flagOrg, deps.env, deps.cwd, config),
134
- deps.fetch,
135
- );
136
- const list = loadConfig(deps.env).orgs ?? [];
137
- const staff = (await new CredentialStore(deps.env).staffTokens()).filter(
138
- (held) => held.expiresAt > Date.now(),
139
- );
140
- const active = auth.org?.workosId;
141
- if (json) {
142
- deps.stdout(
143
- `${JSON.stringify(
144
- [
145
- ...list.map((org) => ({ ...org, active: org.workosId === active })),
146
- ...staff.map((held) => ({
147
- ...held.org,
148
- active: held.org.workosId === active,
149
- staff: { reason: held.reason, expiresAt: held.expiresAt },
150
- })),
151
- ],
152
- null,
153
- 2,
154
- )}\n`,
155
- );
156
- return 0;
157
- }
158
- for (const org of list)
159
- deps.stdout(`${org.workosId === active ? "*" : " "} ${orgLabel(org)}\n`);
160
- for (const held of staff)
161
- deps.stdout(
162
- `${held.org.workosId === active ? "*" : " "} ${orgLabel(held.org)} staff access, ${timeLeft(held.expiresAt)} left\n`,
163
- );
164
- return 0;
165
- }
166
-
167
- function timeLeft(expiresAt: number): string {
168
- const minutes = Math.max(0, Math.round((expiresAt - Date.now()) / 60_000));
169
- return minutes >= 60
170
- ? `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}m`
171
- : `${minutes}m`;
172
- }
173
-
174
- function isMember(config: Config, org: Org): boolean {
175
- return (config.orgs ?? []).some((o) => o.workosId === org.workosId);
176
- }
177
-
178
- /** The org whose sign-in calls the mint: the default, or else any of yours. */
179
- function homeOrg(config: Config): Org {
180
- const org =
181
- config.org && isMember(config, config.org) ? config.org : config.orgs?.[0];
182
- if (!org)
183
- throw new AuthError(
184
- "staff access needs a sign-in to one of your own organizations. Run `asteroid login`.",
185
- );
186
- return org;
187
- }
188
-
189
- const STAFF_TOKEN_PATH = "/users/v2/staff-tokens";
190
-
191
- interface MintedStaffToken {
192
- token: string;
193
- grantId: string;
194
- organizationId: string;
195
- organizationName: string;
196
- workosOrganizationId?: string;
197
- reason: string;
198
- expiresAt: string;
199
- }
200
-
201
- async function mintStaffToken(
202
- deps: Deps,
203
- config: Config,
204
- ref: string,
205
- reason: string,
206
- ): Promise<StaffToken> {
207
- const home = await resolveAuth(
208
- deps.env,
209
- config,
210
- homeOrg(config).workosId,
211
- deps.fetch,
212
- );
213
- let minted: MintedStaffToken;
214
- try {
215
- const response = await send(
216
- deps.env.apiUrl,
217
- home,
218
- {
219
- method: "POST",
220
- path: STAFF_TOKEN_PATH,
221
- query: new URLSearchParams(),
222
- json: { organization: ref, reason },
223
- },
224
- false,
225
- deps.fetch,
226
- );
227
- minted = response.json as MintedStaffToken;
228
- } catch (error) {
229
- if (error instanceof ApiError && error.status === 403)
230
- throw new AuthError(
231
- `you are not a member of ${clean(ref)}, and staff access is for Asteroid staff only.`,
232
- );
233
- if (error instanceof ApiError && error.status === 404)
234
- throw new UsageError(
235
- `unknown organization ${clean(ref)}. Pass its id or its WorkOS id (org_...).`,
236
- );
237
- throw error;
238
- }
239
- return {
240
- token: minted.token,
241
- grantId: minted.grantId,
242
- org: {
243
- workosId: minted.workosOrganizationId ?? minted.organizationId,
244
- id: minted.organizationId,
245
- name: minted.organizationName,
246
- },
247
- reason: minted.reason,
248
- expiresAt: Date.parse(minted.expiresAt),
249
- };
250
- }
251
-
252
- async function revokeStaffToken(
253
- deps: Deps,
254
- home: Auth,
255
- held: StaffToken,
256
- ): Promise<boolean> {
257
- try {
258
- await send(
259
- deps.env.apiUrl,
260
- home,
261
- {
262
- method: "DELETE",
263
- path: `${STAFF_TOKEN_PATH}/${encodeURIComponent(held.grantId)}`,
264
- query: new URLSearchParams(),
265
- },
266
- false,
267
- deps.fetch,
268
- );
269
- return true;
270
- } catch (error) {
271
- // Gone already: revoked, or never recorded. Either way it no longer works.
272
- if (error instanceof ApiError && error.status === 404) return true;
273
- deps.stderr(
274
- `Could not revoke staff access to ${orgLabel(held.org)}: ${clean(error instanceof Error ? error.message : String(error))}. It ends at ${new Date(held.expiresAt).toISOString()}.\n`,
275
- );
276
- return false;
277
- }
278
- }
279
-
280
- /** `use --exit`: revoke every staff token now and go back to your own org. */
281
- async function exitStaff(deps: Deps): Promise<number> {
282
- const store = new CredentialStore(deps.env);
283
- const held = await store.staffTokens();
284
- const config = loadConfig(deps.env);
285
- if (held.length === 0) {
286
- deps.stderr("No staff access to leave.\n");
287
- return 0;
288
- }
289
- const home = await resolveAuth(
290
- deps.env,
291
- config,
292
- homeOrg(config).workosId,
293
- deps.fetch,
294
- );
295
- const left: StaffToken[] = [];
296
- const kept: StaffToken[] = [];
297
- for (const token of held) {
298
- // Keep a token whose grant is still live, so a retry can revoke it.
299
- const ended =
300
- token.expiresAt <= Date.now() ||
301
- (await revokeStaffToken(deps, home, token));
302
- if (!ended) {
303
- kept.push(token);
304
- continue;
305
- }
306
- await store.dropStaffToken(token.grantId);
307
- left.push(token);
308
- }
309
- const back = held.find((t) => t.from)?.from ?? homeOrg(config);
310
- saveConfig(deps.env, { ...loadConfig(deps.env), org: back });
311
- if (left.length > 0)
312
- deps.stderr(
313
- `Left staff access to ${left.map((t) => orgLabel(t.org)).join(", ")}.\n`,
314
- );
315
- if (kept.length > 0)
316
- deps.stderr(
317
- `Still holding staff access to ${kept.map((t) => orgLabel(t.org)).join(", ")}. Run \`asteroid use --exit\` again to retry.\n`,
318
- );
319
- deps.stderr(`Now using ${orgLabel(back)}.\n`);
320
- return kept.length === 0 ? 0 : 1;
321
- }
322
-
323
- async function askReason(deps: Deps, org: string): Promise<string | undefined> {
324
- if (!deps.ui) return undefined;
325
- const reason = await deps.ui.text(
326
- `You are not a member of ${clean(org)}. Why do you need staff access? This is audited.`,
327
- );
328
- return reason?.trim() || undefined;
329
- }
330
-
331
- /** The staff path of `use`: mint a token for an org you are not a member of. */
332
- async function useAsStaff(
333
- deps: Deps,
334
- config: Config,
335
- ref: string,
336
- reason: string | undefined,
337
- staff: boolean,
338
- ): Promise<number> {
339
- const store = new CredentialStore(deps.env);
340
- const held = await store.staffToken(ref);
341
- if (held && held.expiresAt - 30_000 > Date.now() && !reason) {
342
- saveConfig(deps.env, { ...config, org: held.org });
343
- deps.stderr(
344
- `Now using ${orgLabel(held.org)} with staff access, ${timeLeft(held.expiresAt)} left.\n`,
345
- );
346
- return 0;
347
- }
348
- if (!reason && staff) reason = await askReason(deps, ref);
349
- if (!reason)
350
- throw new UsageError(
351
- `you are not a member of ${clean(ref)}.${staffHint(ref, staff)}${staff ? " The reason is audited." : ""}`,
352
- );
353
- // The mint takes an id, not a name. A held token knows the id.
354
- const minted = await mintStaffToken(
355
- deps,
356
- config,
357
- held?.org.id ?? ref,
358
- reason,
359
- );
360
- const previous = config.org
361
- ? isMember(config, config.org)
362
- ? config.org
363
- : (await store.staffToken(config.org.workosId))?.from
364
- : undefined;
365
- try {
366
- await store.saveStaffToken(
367
- { ...minted, from: previous ?? held?.from },
368
- held?.grantId,
369
- );
370
- } catch (error) {
371
- // Nothing holds the token, so nothing could revoke it later. End it now.
372
- const home = await resolveAuth(
373
- deps.env,
374
- config,
375
- homeOrg(config).workosId,
376
- deps.fetch,
377
- );
378
- await revokeStaffToken(deps, home, minted);
379
- throw error;
380
- }
381
- saveConfig(deps.env, { ...loadConfig(deps.env), org: minted.org });
382
- deps.stderr(
383
- `Staff access to ${orgLabel(minted.org)} for ${timeLeft(minted.expiresAt)}, until ${new Date(minted.expiresAt).toLocaleTimeString()}. ` +
384
- `Reason: ${clean(minted.reason)}. This is audited. Run \`asteroid use --exit\` to leave.\n`,
385
- );
386
- return 0;
387
- }
388
-
389
- export interface UseOptions {
390
- reason?: string;
391
- exit?: boolean;
392
- }
393
-
394
- export async function use(
395
- deps: Deps,
396
- ref: string | undefined,
397
- options: UseOptions = {},
398
- ): Promise<number> {
399
- if (deps.env.apiKey)
400
- throw new UsageError(
401
- `${API_KEY_OVERRIDES} \`use\` only switches the login's organization. ${UNSET_API_KEY}`,
402
- );
403
- if (options.exit) {
404
- if (ref || options.reason)
405
- throw new UsageError("usage: asteroid use --exit");
406
- return exitStaff(deps);
407
- }
408
- // `--reason --exit` must not mint a grant whose reason is "--exit".
409
- if (options.reason?.startsWith("-"))
410
- throw new UsageError(
411
- `--reason needs a value, not ${clean(options.reason)}. Give the reason without a leading -.`,
412
- );
413
- const config = loadConfig(deps.env);
414
- if (!ref && deps.ui && (config.orgs?.length ?? 0) > 0) {
415
- ref = await deps.ui.select(
416
- "Use which organization?",
417
- (config.orgs ?? []).map((o) => ({
418
- value: o.workosId,
419
- label: orgLabel(o),
420
- })),
421
- );
422
- if (!ref) return 1;
423
- }
424
- if (!ref) throw new UsageError("usage: asteroid use <org>");
425
- const login = await new CredentialStore(deps.env).readLogin();
426
- if (!login) throw new AuthError("not signed in. Run `asteroid login`.");
427
- const staff = isStaffLogin(login);
428
- const org = findOrg(ref, config.orgs ?? []);
429
- // Not one of yours: an id or name the sign-in does not list. A bare WorkOS id
430
- // may still be yours, so the refresh decides.
431
- if (!org || (!isMember(config, org) && options.reason))
432
- return useAsStaff(deps, config, ref, options.reason, staff);
433
- let auth: Auth;
434
- try {
435
- auth = await resolveAuth(deps.env, config, org.workosId, deps.fetch);
436
- } catch (error) {
437
- if (error instanceof NotMemberError)
438
- return useAsStaff(deps, config, ref, options.reason, staff);
439
- throw error;
440
- }
441
- const updated = loadConfig(deps.env);
442
- updated.org =
443
- updated.orgs?.find((o) => o.workosId === org.workosId) ?? auth.org ?? org;
444
- saveConfig(deps.env, updated);
445
- deps.stderr(`Now using ${orgLabel(updated.org)}.\n`);
446
- return 0;
447
- }
448
-
449
- export async function whoami(
450
- deps: Deps,
451
- flagOrg: string | undefined,
452
- json: boolean,
453
- ): Promise<number> {
454
- const config = loadConfig(deps.env);
455
- const auth = await resolveAuth(
456
- deps.env,
457
- config,
458
- requestedOrg(flagOrg, deps.env, deps.cwd, config),
459
- deps.fetch,
460
- );
461
- const response = await send(
462
- deps.env.apiUrl,
463
- auth,
464
- { method: "GET", path: "/users/v2/whoami", query: new URLSearchParams() },
465
- false,
466
- deps.fetch,
467
- );
468
- const body = (response.json ?? {}) as Record<string, unknown>;
469
- const minutes = auth.expiresAt
470
- ? Math.max(0, Math.round((auth.expiresAt - Date.now()) / 60_000))
471
- : undefined;
472
- if (json) {
473
- deps.stdout(
474
- `${JSON.stringify({ ...body, organizationName: auth.org?.name, tokenExpiresInMinutes: minutes }, null, 2)}\n`,
475
- );
476
- return 0;
477
- }
478
- const grant = body.staffGrant as { reason?: string } | undefined;
479
- const person = body.person as { email?: string } | undefined;
480
- const orgId = body.organizationId as string | undefined;
481
- const organization = auth.org ? orgLabel(auth.org) : (orgId ?? "none");
482
- deps.stdout(
483
- `${render({
484
- as: person?.email ?? body.id,
485
- kind: body.kind,
486
- credential: body.credential,
487
- source:
488
- auth.kind === "api-key"
489
- ? "ASTEROID_API_KEY"
490
- : auth.staff
491
- ? "asteroid use (staff access)"
492
- : "asteroid login",
493
- organization,
494
- ...(grant ? { staffReason: grant.reason } : {}),
495
- ...(auth.expiresAt === undefined
496
- ? {}
497
- : { tokenExpiresIn: timeLeft(auth.expiresAt) }),
498
- })}\n`,
499
- );
500
- return 0;
501
- }
package/src/args.ts DELETED
@@ -1,39 +0,0 @@
1
- import type { CliCommand } from "./types";
2
-
3
- export class UsageError extends Error {}
4
-
5
- export interface Parsed {
6
- positionals: string[];
7
- flags: Map<string, string[]>;
8
- json: boolean;
9
- org?: string;
10
- body?: string;
11
- output?: string;
12
- }
13
-
14
- export function leadingWords(argv: string[]): string[] {
15
- const words: string[] = [];
16
- for (const token of argv) {
17
- if (token.startsWith("-")) break;
18
- words.push(token);
19
- }
20
- return words;
21
- }
22
-
23
- export function matchCommand(
24
- words: string[],
25
- commands: CliCommand[],
26
- ): { command?: CliCommand; prefix: string[] } {
27
- for (let length = Math.min(words.length, 4); length > 0; length--) {
28
- const candidate = words.slice(0, length).join(" ");
29
- const command = commands.find((c) => c.command === candidate);
30
- if (command) return { command, prefix: words.slice(0, length) };
31
- }
32
- for (let length = Math.min(words.length, 3); length > 0; length--) {
33
- const prefix = words.slice(0, length);
34
- if (commands.some((c) => c.command.startsWith(`${prefix.join(" ")} `))) {
35
- return { prefix };
36
- }
37
- }
38
- return { prefix: [] };
39
- }