@bctrl/cli 0.1.3 → 0.1.5

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 (50) hide show
  1. package/README.md +95 -95
  2. package/dist/api/auth.d.ts +5 -6
  3. package/dist/api/auth.js +4 -5
  4. package/dist/api/client.d.ts +1 -0
  5. package/dist/api/client.js +3 -0
  6. package/dist/api/errors.js +2 -2
  7. package/dist/bin/bctrl.js +0 -0
  8. package/dist/commands/ai/index.js +71 -106
  9. package/dist/commands/{browser → api-key}/index.d.ts +1 -1
  10. package/dist/commands/api-key/index.js +47 -0
  11. package/dist/commands/auth/login.js +2 -1
  12. package/dist/commands/auth/status.js +5 -0
  13. package/dist/commands/browser-extension/index.d.ts +3 -0
  14. package/dist/commands/browser-extension/index.js +85 -0
  15. package/dist/commands/file/index.js +26 -21
  16. package/dist/commands/{invocation → help}/index.d.ts +1 -1
  17. package/dist/commands/help/index.js +17 -0
  18. package/dist/commands/proxy/index.d.ts +3 -0
  19. package/dist/commands/proxy/index.js +72 -0
  20. package/dist/commands/run/index.js +227 -160
  21. package/dist/commands/runtime/index.js +343 -118
  22. package/dist/commands/shared/help.d.ts +1 -0
  23. package/dist/commands/shared/help.js +10 -6
  24. package/dist/commands/shared/operation.d.ts +83 -0
  25. package/dist/commands/shared/{rest.js → operation.js} +81 -32
  26. package/dist/commands/space/index.js +62 -68
  27. package/dist/commands/space/list.d.ts +1 -11
  28. package/dist/commands/space/list.js +12 -20
  29. package/dist/commands/subaccount/index.js +56 -125
  30. package/dist/commands/tool/index.js +28 -22
  31. package/dist/commands/tool-call/index.js +11 -6
  32. package/dist/commands/toolset/index.js +16 -15
  33. package/dist/commands/usage/index.d.ts +3 -0
  34. package/dist/commands/usage/index.js +13 -0
  35. package/dist/commands/vault/index.js +115 -34
  36. package/dist/config/auth-store.d.ts +10 -12
  37. package/dist/config/auth-store.js +4 -5
  38. package/dist/generated/help.d.ts +5992 -71
  39. package/dist/generated/help.js +7918 -123
  40. package/dist/generated/openapi-routes.d.ts +391 -0
  41. package/dist/generated/openapi-routes.js +100 -0
  42. package/dist/generated/openapi-types.d.ts +13755 -0
  43. package/dist/generated/openapi-types.js +5 -0
  44. package/dist/openapi.d.ts +20 -0
  45. package/dist/openapi.js +1 -0
  46. package/dist/root.js +10 -4
  47. package/package.json +47 -45
  48. package/dist/commands/browser/index.js +0 -53
  49. package/dist/commands/invocation/index.js +0 -36
  50. package/dist/commands/shared/rest.d.ts +0 -54
package/README.md CHANGED
@@ -1,95 +1,95 @@
1
- # BCTRL CLI
2
-
3
- CLI for controlling BCTRL runtimes, files, runs, vault secrets, tools, and account resources from scripts or AI agents.
4
-
5
- ## Install
6
-
7
- ```bash
8
- npm install -g @bctrl/cli
9
- ```
10
-
11
- ## Auth
12
-
13
- The CLI authenticates with a BCTRL API key.
14
-
15
- Use an API key for the current shell:
16
-
17
- ```bash
18
- export BCTRL_API_KEY="bctrl_..."
19
- ```
20
-
21
- Or save it locally:
22
-
23
- ```bash
24
- bctrl auth login
25
- ```
26
-
27
- If `BCTRL_API_KEY` is already set, `auth login` saves that key. Otherwise, it prompts you to paste an API key.
28
-
29
- Check the active auth:
30
-
31
- ```bash
32
- bctrl auth status
33
- ```
34
-
35
- ## Browser Runtime Flow
36
-
37
- ```bash
38
- bctrl space list
39
- bctrl runtime create --space sp_123 --name checkout-test
40
- bctrl runtime start rt_123
41
- bctrl runtime stop rt_123
42
- ```
43
-
44
- ## JSON Input
45
-
46
- Use `--input` for full request bodies:
47
-
48
- ```bash
49
- bctrl runtime create --input runtime.json
50
- ```
51
-
52
- Use `-` to read from stdin:
53
-
54
- ```bash
55
- echo '{"type":"browser","spaceId":"sp_123","name":"agent-runtime"}' \
56
- | bctrl runtime create --input -
57
- ```
58
-
59
- ## Output
60
-
61
- Print full JSON:
62
-
63
- ```bash
64
- bctrl runtime list --space sp_123 --json
65
- ```
66
-
67
- Print selected JSON fields:
68
-
69
- ```bash
70
- bctrl runtime list --space sp_123 --json id,status,name
71
- ```
72
-
73
- Filter with `jq`:
74
-
75
- ```bash
76
- bctrl runtime list --space sp_123 --json --jq '.data[] | select(.status == "active")'
77
- ```
78
-
79
- Render with a template:
80
-
81
- ```bash
82
- bctrl runtime list \
83
- --space sp_123 \
84
- --json \
85
- --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
86
- ```
87
-
88
- ## Help
89
-
90
- Use command help for exact input and output fields:
91
-
92
- ```bash
93
- bctrl runtime create --help
94
- bctrl runtime start --help
95
- ```
1
+ # BCTRL CLI
2
+
3
+ CLI for controlling BCTRL runtimes, files, runs, vault secrets, tools, and account resources from scripts or AI agents.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @bctrl/cli
9
+ ```
10
+
11
+ ## Auth
12
+
13
+ The CLI authenticates with a BCTRL API key.
14
+
15
+ Use an API key for the current shell:
16
+
17
+ ```bash
18
+ export BCTRL_API_KEY="bctrl_..."
19
+ ```
20
+
21
+ Or save it locally:
22
+
23
+ ```bash
24
+ bctrl auth login
25
+ ```
26
+
27
+ If `BCTRL_API_KEY` is already set, `auth login` saves that key. Otherwise, it prompts you to paste an API key.
28
+
29
+ Check the active auth:
30
+
31
+ ```bash
32
+ bctrl auth status
33
+ ```
34
+
35
+ ## Browser Runtime Flow
36
+
37
+ ```bash
38
+ bctrl space list
39
+ bctrl runtime create --space sp_123 --name checkout-test
40
+ bctrl runtime start rt_123
41
+ bctrl runtime stop rt_123
42
+ ```
43
+
44
+ ## JSON Input
45
+
46
+ Use `--input` for full request bodies:
47
+
48
+ ```bash
49
+ bctrl runtime create --input runtime.json
50
+ ```
51
+
52
+ Use `-` to read from stdin:
53
+
54
+ ```bash
55
+ echo '{"type":"browser","spaceId":"sp_123","name":"agent-runtime"}' \
56
+ | bctrl runtime create --input -
57
+ ```
58
+
59
+ ## Output
60
+
61
+ Print full JSON:
62
+
63
+ ```bash
64
+ bctrl runtime list --space sp_123 --json
65
+ ```
66
+
67
+ Print selected JSON fields:
68
+
69
+ ```bash
70
+ bctrl runtime list --space sp_123 --json id,status,name
71
+ ```
72
+
73
+ Filter with `jq`:
74
+
75
+ ```bash
76
+ bctrl runtime list --space sp_123 --json --jq '.data[] | select(.status == "active")'
77
+ ```
78
+
79
+ Render with a template:
80
+
81
+ ```bash
82
+ bctrl runtime list \
83
+ --space sp_123 \
84
+ --json \
85
+ --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
86
+ ```
87
+
88
+ ## Help
89
+
90
+ Use command help for exact input and output fields:
91
+
92
+ ```bash
93
+ bctrl runtime create --help
94
+ bctrl runtime start --help
95
+ ```
@@ -1,15 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  export declare const AuthWhoamiSchema: z.ZodObject<{
3
- authenticated: z.ZodLiteral<true>;
4
3
  email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
5
- keyKind: z.ZodEnum<{
6
- user: "user";
4
+ scope: z.ZodEnum<{
5
+ organization: "organization";
7
6
  subaccount: "subaccount";
8
7
  }>;
9
8
  organizationId: z.ZodString;
10
- subaccountId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
11
- userId: z.ZodString;
12
- apiKeyId: z.ZodString;
9
+ subaccountId: z.ZodNullable<z.ZodString>;
10
+ defaultSpaceId: z.ZodString;
11
+ keyId: z.ZodString;
13
12
  plan: z.ZodString;
14
13
  }, z.core.$strict>;
15
14
  export type AuthWhoami = z.infer<typeof AuthWhoamiSchema>;
package/dist/api/auth.js CHANGED
@@ -3,13 +3,12 @@ import { CliError } from '../runtime/errors.js';
3
3
  import { apiErrorFromResponse } from './errors.js';
4
4
  export const AuthWhoamiSchema = z
5
5
  .object({
6
- authenticated: z.literal(true),
7
6
  email: z.string().email().nullable().optional(),
8
- keyKind: z.enum(['user', 'subaccount']),
7
+ scope: z.enum(['organization', 'subaccount']),
9
8
  organizationId: z.string().min(1),
10
- subaccountId: z.string().min(1).nullable().optional(),
11
- userId: z.string().min(1),
12
- apiKeyId: z.string().min(1),
9
+ subaccountId: z.string().min(1).nullable(),
10
+ defaultSpaceId: z.string().min(1),
11
+ keyId: z.string().min(1),
13
12
  plan: z.string().min(1),
14
13
  })
15
14
  .strict();
@@ -15,6 +15,7 @@ export type BctrlApiClient = {
15
15
  };
16
16
  export type RequestOptions = {
17
17
  query?: Record<string, string | number | boolean | undefined>;
18
+ idempotencyKey?: string;
18
19
  };
19
20
  export type JsonRequestOptions = RequestOptions & {
20
21
  body?: unknown;
@@ -21,6 +21,9 @@ async function requestJson(config, method, path, options) {
21
21
  authorization: `Bearer ${config.activeToken.token}`,
22
22
  'user-agent': 'BCTRL CLI',
23
23
  };
24
+ if (options?.idempotencyKey) {
25
+ headers['idempotency-key'] = options.idempotencyKey;
26
+ }
24
27
  let body;
25
28
  if (options && 'body' in options && options.body !== undefined) {
26
29
  headers['content-type'] = 'application/json';
@@ -73,8 +73,8 @@ function suggestionForApiError(error) {
73
73
  return 'bctrl vault list';
74
74
  case 'browser_extension.not_found':
75
75
  return 'bctrl browser extension list';
76
- case 'ai_provider.not_found':
77
- return 'bctrl ai provider list';
76
+ case 'ai_credential.not_found':
77
+ return 'bctrl ai credentials list';
78
78
  case 'proxy.not_found':
79
79
  return 'bctrl ai proxy list';
80
80
  case 'subaccount.not_found':
package/dist/bin/bctrl.js CHANGED
File without changes
@@ -1,18 +1,37 @@
1
1
  import { Command } from 'commander';
2
2
  import { readJsonFile } from '../shared/io.js';
3
3
  import { addOutputFlags } from '../shared/output.js';
4
- import { createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.js';
4
+ import { createOperationDeleteCommand, createOperationJsonBodyCommand, createOperationListCommand, createOperationViewCommand, outputFlags, requestOperationAndPrint, } from '../shared/operation.js';
5
5
  export function createAiCommand(factory) {
6
- const command = new Command('ai').description('Manage AI providers and proxies');
7
- command.addCommand(createAiProviderCommand(factory));
8
- command.addCommand(createAiProxyCommand(factory));
6
+ const command = new Command('ai').description('Manage AI models and credentials');
7
+ command.addCommand(createAiModelsCommand(factory));
8
+ command.addCommand(createAiCredentialsCommand(factory));
9
9
  return command;
10
10
  }
11
- function createAiProviderCommand(factory) {
12
- const command = new Command('provider').description('Manage AI providers');
13
- command.addCommand(createListCommand(factory, {
14
- description: 'List AI providers',
15
- path: '/ai-providers',
11
+ function createAiModelsCommand(factory) {
12
+ const command = new Command('models').description('Discover AI models');
13
+ command.addCommand(createOperationListCommand(factory, {
14
+ operationId: 'ai.models.list',
15
+ description: 'List AI models',
16
+ configure: (cmd) => cmd
17
+ .option('--provider <provider>', 'Filter by provider')
18
+ .option('--status <status>', 'Filter by support status')
19
+ .option('--managed <value>', 'Filter by managed availability')
20
+ .option('--engine <engine>', 'Filter by engine: stagehand or browserUse'),
21
+ query: (options) => ({
22
+ provider: typeof options.provider === 'string' ? options.provider : undefined,
23
+ status: typeof options.status === 'string' ? options.status : undefined,
24
+ managed: typeof options.managed === 'string' ? parseBooleanFlag(options.managed) : undefined,
25
+ engine: typeof options.engine === 'string' ? options.engine : undefined,
26
+ }),
27
+ }));
28
+ return command;
29
+ }
30
+ function createAiCredentialsCommand(factory) {
31
+ const command = new Command('credentials').description('Manage BYOK AI credentials');
32
+ command.addCommand(createOperationListCommand(factory, {
33
+ operationId: 'ai.credentials.list',
34
+ description: 'List AI credentials',
16
35
  configure: (cmd) => cmd
17
36
  .option('--provider <provider>', 'Filter by provider')
18
37
  .option('--name <name>', 'Filter by name')
@@ -23,116 +42,62 @@ function createAiProviderCommand(factory) {
23
42
  status: typeof options.status === 'string' ? options.status : undefined,
24
43
  }),
25
44
  }));
26
- command.addCommand(createViewCommand(factory, {
27
- description: 'View an AI provider',
28
- path: '/ai-providers/{id}',
45
+ command.addCommand(createOperationViewCommand(factory, {
46
+ operationId: 'ai.credentials.get',
47
+ name: 'get',
48
+ description: 'Get an AI credential',
49
+ argName: 'credentialId',
50
+ }));
51
+ command.addCommand(createAiCredentialWriteCommand(factory, 'create', 'ai.credentials.create'));
52
+ command.addCommand(createAiCredentialWriteCommand(factory, 'patch', 'ai.credentials.update', ['credentialId']));
53
+ command.addCommand(addOutputFlags(new Command('test').description('Test an AI credential').argument('<credentialId>')).action(async (credentialId, options) => {
54
+ await requestOperationAndPrint(factory, 'ai.credentials.test', {
55
+ pathParams: { credentialId },
56
+ output: outputFlags(options),
57
+ });
58
+ }));
59
+ command.addCommand(createOperationDeleteCommand(factory, {
60
+ operationId: 'ai.credentials.delete',
61
+ description: 'Delete an AI credential',
62
+ argNames: ['credentialId'],
29
63
  }));
30
- command.addCommand(createJsonBodyCommand(factory, {
31
- name: 'create',
32
- description: 'Create an AI provider',
33
- method: 'post',
34
- path: '/ai-providers',
64
+ return command;
65
+ }
66
+ function createAiCredentialWriteCommand(factory, name, operationId, argNames = []) {
67
+ return createOperationJsonBodyCommand(factory, {
68
+ operationId,
69
+ name,
70
+ description: name === 'create' ? 'Create an AI credential' : 'Update an AI credential',
71
+ argNames,
35
72
  configure: (cmd) => cmd
36
- .option('--name <name>', 'Provider name')
73
+ .option('--name <name>', 'Credential name')
37
74
  .option('--provider <provider>', 'Provider key')
38
75
  .option('--api-key <key>', 'Provider API key')
39
- .option('--status <status>', 'Provider status')
76
+ .option('--status <status>', 'Credential status')
40
77
  .option('--default-model <model>', 'Default model')
41
- .option('--base-url <url>', 'OpenAI-compatible provider base URL')
42
- .option('--subaccount <id>', 'Subaccount scope')
78
+ .option('--base-url <url>', 'Custom provider base URL')
79
+ .option('--subaccount-id <id>', 'Subaccount scope when using a parent/org key')
43
80
  .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
44
81
  body: async (_args, options) => {
45
- if (typeof options.input === 'string')
46
- return readJsonFile(options.input);
82
+ if (typeof options.input === 'string') {
83
+ return (await readJsonFile(options.input));
84
+ }
47
85
  return {
48
86
  name: options.name,
49
87
  provider: options.provider,
50
88
  apiKey: options.apiKey,
51
89
  status: options.status,
52
- scope: typeof options.subaccount === 'string'
53
- ? { subaccountId: options.subaccount }
54
- : undefined,
55
- config: typeof options.defaultModel === 'string' || typeof options.baseUrl === 'string'
56
- ? {
57
- defaultModel: typeof options.defaultModel === 'string' ? options.defaultModel : undefined,
58
- baseUrl: typeof options.baseUrl === 'string' ? options.baseUrl : undefined,
59
- }
60
- : undefined,
90
+ subaccountId: options.subaccountId,
91
+ defaultModel: options.defaultModel,
92
+ baseUrl: options.baseUrl,
61
93
  };
62
94
  },
63
- }));
64
- command.addCommand(createJsonBodyCommand(factory, {
65
- name: 'edit',
66
- description: 'Edit an AI provider',
67
- method: 'patch',
68
- path: '/ai-providers/{id}',
69
- argNames: ['id'],
70
- configure: (cmd) => cmd
71
- .option('--name <name>', 'Provider name')
72
- .option('--api-key <key>', 'Provider API key')
73
- .option('--status <status>', 'Provider status')
74
- .option('--default-model <model>', 'Default model')
75
- .option('--base-url <url>', 'OpenAI-compatible provider base URL')
76
- .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
77
- body: async (_args, options) => {
78
- if (typeof options.input === 'string')
79
- return readJsonFile(options.input);
80
- return {
81
- name: options.name,
82
- apiKey: options.apiKey,
83
- status: options.status,
84
- config: typeof options.defaultModel === 'string' || typeof options.baseUrl === 'string'
85
- ? {
86
- defaultModel: typeof options.defaultModel === 'string' ? options.defaultModel : undefined,
87
- baseUrl: typeof options.baseUrl === 'string' ? options.baseUrl : undefined,
88
- }
89
- : undefined,
90
- };
91
- },
92
- }));
93
- command.addCommand(addOutputFlags(new Command('test').description('Test an AI provider').argument('<id>')).action(async (id, options) => {
94
- await requestAndPrint(factory, 'post', `/ai-providers/${encodeURIComponent(id)}/test`, {
95
- output: options,
96
- });
97
- }));
98
- command.addCommand(createDeleteCommand(factory, {
99
- description: 'Delete an AI provider',
100
- path: '/ai-providers/{id}',
101
- }));
102
- return command;
95
+ });
103
96
  }
104
- function createAiProxyCommand(factory) {
105
- const command = new Command('proxy').description('Manage AI/browser proxies');
106
- command.addCommand(createListCommand(factory, {
107
- description: 'List proxies',
108
- path: '/proxies',
109
- configure: (cmd) => cmd,
110
- }));
111
- command.addCommand(createViewCommand(factory, {
112
- description: 'View a proxy',
113
- path: '/proxies/{id}',
114
- }));
115
- command.addCommand(createJsonBodyCommand(factory, {
116
- name: 'create',
117
- description: 'Create a proxy',
118
- method: 'post',
119
- path: '/proxies',
120
- }));
121
- command.addCommand(createJsonBodyCommand(factory, {
122
- name: 'edit',
123
- description: 'Edit a proxy',
124
- method: 'patch',
125
- path: '/proxies/{id}',
126
- argNames: ['id'],
127
- }));
128
- command.addCommand(addOutputFlags(new Command('test').description('Test a proxy').argument('<id>')).action(async (id, options) => {
129
- await requestAndPrint(factory, 'post', `/proxies/${encodeURIComponent(id)}/test`, {
130
- output: options,
131
- });
132
- }));
133
- command.addCommand(createDeleteCommand(factory, {
134
- description: 'Delete a proxy',
135
- path: '/proxies/{id}',
136
- }));
137
- return command;
97
+ function parseBooleanFlag(value) {
98
+ if (value === 'true')
99
+ return true;
100
+ if (value === 'false')
101
+ return false;
102
+ return undefined;
138
103
  }
@@ -1,3 +1,3 @@
1
1
  import { Command } from 'commander';
2
2
  import type { Factory } from '../../factory.js';
3
- export declare function createBrowserCommand(factory: Factory): Command;
3
+ export declare function createApiKeyCommand(factory: Factory): Command;
@@ -0,0 +1,47 @@
1
+ import { Command } from 'commander';
2
+ import { readJsonFile } from '../shared/io.js';
3
+ import { createOperationDeleteCommand, createOperationJsonBodyCommand, createOperationListCommand, } from '../shared/operation.js';
4
+ export function createApiKeyCommand(factory) {
5
+ const command = new Command('api-key').description('Manage API keys');
6
+ command.addCommand(createOperationListCommand(factory, {
7
+ operationId: 'api-keys.list',
8
+ description: 'List API keys',
9
+ configure: (cmd) => cmd
10
+ .option('--subaccount-id <id>', 'Filter by subaccount id')
11
+ .option('--kind <kind>', 'Filter by key kind: organization or subaccount')
12
+ .option('-L, --limit <number>', 'Maximum number of results to return')
13
+ .option('--cursor <cursor>', 'Pagination cursor'),
14
+ query: (options) => ({
15
+ subaccountId: typeof options.subaccountId === 'string' ? options.subaccountId : undefined,
16
+ keyKind: typeof options.kind === 'string' ? options.kind : undefined,
17
+ limit: typeof options.limit === 'string' ? Number(options.limit) : undefined,
18
+ cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
19
+ }),
20
+ }));
21
+ command.addCommand(createOperationJsonBodyCommand(factory, {
22
+ operationId: 'api-keys.create',
23
+ name: 'create',
24
+ description: 'Create an API key',
25
+ configure: (cmd) => cmd
26
+ .option('--name <name>', 'API key name')
27
+ .option('--subaccount-id <id>', 'Create a subaccount-scoped key')
28
+ .option('--expires-at <iso>', 'Expiration timestamp')
29
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
30
+ body: async (_args, options) => {
31
+ if (typeof options.input === 'string') {
32
+ return (await readJsonFile(options.input));
33
+ }
34
+ return {
35
+ name: options.name,
36
+ subaccountId: options.subaccountId,
37
+ expiresAt: options.expiresAt,
38
+ };
39
+ },
40
+ }));
41
+ command.addCommand(createOperationDeleteCommand(factory, {
42
+ operationId: 'api-keys.delete',
43
+ description: 'Delete an API key',
44
+ argNames: ['keyId'],
45
+ }));
46
+ return command;
47
+ }
@@ -36,11 +36,12 @@ export async function authLoginRun(options) {
36
36
  }
37
37
  options.io.writeErr(`Logged in to ${config.apiBaseUrl}\n`);
38
38
  options.io.writeErr(`Credentials saved to ${path}\n`);
39
- options.io.writeErr(`Key kind: ${whoami.keyKind}\n`);
39
+ options.io.writeErr(`Scope: ${whoami.scope}\n`);
40
40
  options.io.writeErr(`Organization: ${whoami.organizationId}\n`);
41
41
  if (whoami.subaccountId) {
42
42
  options.io.writeErr(`Subaccount: ${whoami.subaccountId}\n`);
43
43
  }
44
+ options.io.writeErr(`Default space: ${whoami.defaultSpaceId}\n`);
44
45
  if (config.activeToken?.source === 'BCTRL_API_KEY') {
45
46
  options.io.writeErr('BCTRL_API_KEY is set and will take precedence over stored credentials.\n');
46
47
  }
@@ -31,6 +31,7 @@ export async function authStatusRun(options) {
31
31
  const validateToken = options.validateToken ?? validateAuthToken;
32
32
  const whoami = await validateToken(config.apiBaseUrl, config.activeToken.token);
33
33
  const status = {
34
+ authenticated: true,
34
35
  apiBaseUrl: config.apiBaseUrl,
35
36
  tokenSource: config.activeToken.source,
36
37
  ...whoami,
@@ -60,6 +61,10 @@ function writeHumanAuthStatus(io, status) {
60
61
  io.writeOut([
61
62
  'Authenticated: yes',
62
63
  `Email: ${status.email ?? '-'}`,
64
+ `Scope: ${status.scope}`,
65
+ `Organization: ${status.organizationId}`,
66
+ ...(status.subaccountId ? [`Subaccount: ${status.subaccountId}`] : []),
67
+ `Default space: ${status.defaultSpaceId}`,
63
68
  `Plan: ${status.plan}`,
64
69
  `Auth: ${formatTokenSource(status.tokenSource)}`,
65
70
  `API: ${status.apiBaseUrl}`,
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import type { Factory } from '../../factory.js';
3
+ export declare function createBrowserExtensionCommand(factory: Factory): Command;
@@ -0,0 +1,85 @@
1
+ import { Command } from 'commander';
2
+ import { readBlob, readJsonFile } from '../shared/io.js';
3
+ import { addOutputFlags, outputData } from '../shared/output.js';
4
+ import { createOperationDeleteCommand, createOperationJsonBodyCommand, createOperationListCommand, createOperationViewCommand, uploadOperationFile, } from '../shared/operation.js';
5
+ export function createBrowserExtensionCommand(factory) {
6
+ const command = new Command('browser-extension').description('Manage browser extensions');
7
+ command.addCommand(createOperationListCommand(factory, {
8
+ operationId: 'browser-extensions.list',
9
+ description: 'List browser extensions',
10
+ configure: (cmd) => cmd
11
+ .option('--subaccount-id <id>', 'Filter by subaccount id')
12
+ .option('--q <text>', 'Search query')
13
+ .option('--format <format>', 'Filter by format')
14
+ .option('--source <source>', 'Filter by source'),
15
+ query: (options) => ({
16
+ subaccountId: typeof options.subaccountId === 'string' ? options.subaccountId : undefined,
17
+ q: typeof options.q === 'string' ? options.q : undefined,
18
+ format: typeof options.format === 'string' ? options.format : undefined,
19
+ source: typeof options.source === 'string' ? options.source : undefined,
20
+ }),
21
+ }));
22
+ command.addCommand(createOperationViewCommand(factory, {
23
+ operationId: 'browser-extensions.get',
24
+ name: 'get',
25
+ description: 'Get a browser extension',
26
+ argName: 'extensionId',
27
+ }));
28
+ command.addCommand(addOutputFlags(new Command('upload')
29
+ .description('Upload a browser extension package')
30
+ .argument('<path>')
31
+ .option('--name <name>', 'Display name')
32
+ .option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key')).action(async (path, options) => {
33
+ const file = await readBlob(path);
34
+ const result = await uploadOperationFile(factory, 'browser-extensions.upload', {
35
+ file: file.blob,
36
+ fileName: file.fileName,
37
+ fields: {
38
+ ...(options.name ? { name: options.name } : {}),
39
+ ...(options.subaccountId ? { subaccountId: options.subaccountId } : {}),
40
+ },
41
+ });
42
+ await outputData(factory.io, result, options);
43
+ }));
44
+ command.addCommand(createOperationJsonBodyCommand(factory, {
45
+ operationId: 'browser-extensions.import',
46
+ name: 'import',
47
+ description: 'Import a browser extension from a URL',
48
+ configure: (cmd) => cmd
49
+ .option('--url <url>', 'Extension URL')
50
+ .option('--name <name>', 'Display name')
51
+ .option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key')
52
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
53
+ body: async (_args, options) => {
54
+ if (typeof options.input === 'string') {
55
+ return (await readJsonFile(options.input));
56
+ }
57
+ return {
58
+ url: options.url,
59
+ name: options.name,
60
+ subaccountId: options.subaccountId,
61
+ };
62
+ },
63
+ }));
64
+ command.addCommand(createOperationJsonBodyCommand(factory, {
65
+ operationId: 'browser-extensions.update',
66
+ name: 'patch',
67
+ description: 'Update a browser extension',
68
+ argNames: ['extensionId'],
69
+ configure: (cmd) => cmd
70
+ .option('--name <name>', 'Display name')
71
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
72
+ body: async (_args, options) => {
73
+ if (typeof options.input === 'string') {
74
+ return (await readJsonFile(options.input));
75
+ }
76
+ return { name: options.name };
77
+ },
78
+ }));
79
+ command.addCommand(createOperationDeleteCommand(factory, {
80
+ operationId: 'browser-extensions.delete',
81
+ description: 'Delete a browser extension',
82
+ argNames: ['extensionId'],
83
+ }));
84
+ return command;
85
+ }