@bctrl/cli 0.1.2 → 0.1.4

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 (42) hide show
  1. package/README.md +95 -97
  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/commands/ai/index.js +59 -102
  8. package/dist/commands/{browser → api-key}/index.d.ts +1 -1
  9. package/dist/commands/api-key/index.js +47 -0
  10. package/dist/commands/auth/login.js +2 -1
  11. package/dist/commands/auth/status.js +5 -0
  12. package/dist/commands/browser-extension/index.d.ts +3 -0
  13. package/dist/commands/browser-extension/index.js +86 -0
  14. package/dist/commands/file/index.js +9 -3
  15. package/dist/commands/{invocation → help}/index.d.ts +1 -1
  16. package/dist/commands/help/index.js +17 -0
  17. package/dist/commands/proxy/index.d.ts +3 -0
  18. package/dist/commands/proxy/index.js +66 -0
  19. package/dist/commands/run/index.js +205 -155
  20. package/dist/commands/runtime/index.js +265 -116
  21. package/dist/commands/shared/help.js +1 -1
  22. package/dist/commands/shared/rest.d.ts +1 -0
  23. package/dist/commands/shared/rest.js +14 -6
  24. package/dist/commands/space/index.js +17 -32
  25. package/dist/commands/space/list.d.ts +1 -11
  26. package/dist/commands/space/list.js +14 -23
  27. package/dist/commands/subaccount/index.js +29 -80
  28. package/dist/commands/tool/index.js +15 -7
  29. package/dist/commands/tool-call/index.js +6 -1
  30. package/dist/commands/toolset/index.js +6 -3
  31. package/dist/commands/usage/index.d.ts +3 -0
  32. package/dist/commands/usage/index.js +13 -0
  33. package/dist/commands/vault/index.js +104 -25
  34. package/dist/config/auth-store.d.ts +10 -12
  35. package/dist/config/auth-store.js +4 -5
  36. package/dist/factory.js +1 -1
  37. package/dist/generated/help.d.ts +125 -110
  38. package/dist/generated/help.js +175 -141
  39. package/dist/root.js +10 -4
  40. package/package.json +47 -47
  41. package/dist/commands/browser/index.js +0 -53
  42. package/dist/commands/invocation/index.js +0 -36
package/README.md CHANGED
@@ -1,97 +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 connection create rt_123
42
- bctrl runtime stop rt_123
43
- ```
44
-
45
- ## JSON Input
46
-
47
- Use `--input` for full request bodies:
48
-
49
- ```bash
50
- bctrl runtime create --input runtime.json
51
- ```
52
-
53
- Use `-` to read from stdin:
54
-
55
- ```bash
56
- echo '{"type":"browser","spaceId":"sp_123","name":"agent-runtime"}' \
57
- | bctrl runtime create --input -
58
- ```
59
-
60
- ## Output
61
-
62
- Print full JSON:
63
-
64
- ```bash
65
- bctrl runtime list --space sp_123 --json
66
- ```
67
-
68
- Print selected JSON fields:
69
-
70
- ```bash
71
- bctrl runtime list --space sp_123 --json id,status,name
72
- ```
73
-
74
- Filter with `jq`:
75
-
76
- ```bash
77
- bctrl runtime list --space sp_123 --json --jq '.data[] | select(.status == "active")'
78
- ```
79
-
80
- Render with a template:
81
-
82
- ```bash
83
- bctrl runtime list \
84
- --space sp_123 \
85
- --json \
86
- --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
87
- ```
88
-
89
- ## Help
90
-
91
- Use command help for exact input and output fields:
92
-
93
- ```bash
94
- bctrl runtime create --help
95
- bctrl runtime start --help
96
- bctrl runtime connection create --help
97
- ```
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':
@@ -3,16 +3,35 @@ import { readJsonFile } from '../shared/io.js';
3
3
  import { addOutputFlags } from '../shared/output.js';
4
4
  import { createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, requestAndPrint, } from '../shared/rest.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');
11
+ function createAiModelsCommand(factory) {
12
+ const command = new Command('models').description('Discover AI models');
13
13
  command.addCommand(createListCommand(factory, {
14
- description: 'List AI providers',
15
- path: '/ai-providers',
14
+ description: 'List AI models',
15
+ path: '/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' ? 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(createListCommand(factory, {
33
+ description: 'List AI credentials',
34
+ path: '/ai/credentials',
16
35
  configure: (cmd) => cmd
17
36
  .option('--provider <provider>', 'Filter by provider')
18
37
  .option('--name <name>', 'Filter by name')
@@ -24,22 +43,40 @@ function createAiProviderCommand(factory) {
24
43
  }),
25
44
  }));
26
45
  command.addCommand(createViewCommand(factory, {
27
- description: 'View an AI provider',
28
- path: '/ai-providers/{id}',
46
+ name: 'get',
47
+ description: 'Get an AI credential',
48
+ path: '/ai/credentials/{credentialId}',
49
+ argName: 'credentialId',
50
+ }));
51
+ command.addCommand(createAiCredentialWriteCommand(factory, 'create', 'post', '/ai/credentials'));
52
+ command.addCommand(createAiCredentialWriteCommand(factory, 'patch', 'patch', '/ai/credentials/{credentialId}', [
53
+ 'credentialId',
54
+ ]));
55
+ command.addCommand(addOutputFlags(new Command('test').description('Test an AI credential').argument('<credentialId>')).action(async (credentialId, options) => {
56
+ await requestAndPrint(factory, 'post', `/ai/credentials/${encodeURIComponent(credentialId)}/test`, { output: options });
29
57
  }));
30
- command.addCommand(createJsonBodyCommand(factory, {
31
- name: 'create',
32
- description: 'Create an AI provider',
33
- method: 'post',
34
- path: '/ai-providers',
58
+ command.addCommand(createDeleteCommand(factory, {
59
+ description: 'Delete an AI credential',
60
+ path: '/ai/credentials/{credentialId}',
61
+ argNames: ['credentialId'],
62
+ }));
63
+ return command;
64
+ }
65
+ function createAiCredentialWriteCommand(factory, name, method, path, argNames = []) {
66
+ return createJsonBodyCommand(factory, {
67
+ name,
68
+ description: name === 'create' ? 'Create an AI credential' : 'Update an AI credential',
69
+ method,
70
+ path,
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
82
  if (typeof options.input === 'string')
@@ -49,90 +86,10 @@ function createAiProviderCommand(factory) {
49
86
  provider: options.provider,
50
87
  apiKey: options.apiKey,
51
88
  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,
89
+ subaccountId: options.subaccountId,
90
+ defaultModel: options.defaultModel,
91
+ baseUrl: options.baseUrl,
61
92
  };
62
93
  },
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;
103
- }
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;
94
+ });
138
95
  }
@@ -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 { createDeleteCommand, createJsonBodyCommand, createListCommand, } from '../shared/rest.js';
4
+ export function createApiKeyCommand(factory) {
5
+ const command = new Command('api-key').description('Manage API keys');
6
+ command.addCommand(createListCommand(factory, {
7
+ description: 'List API keys',
8
+ path: '/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(createJsonBodyCommand(factory, {
22
+ name: 'create',
23
+ description: 'Create an API key',
24
+ method: 'post',
25
+ path: '/api-keys',
26
+ configure: (cmd) => cmd
27
+ .option('--name <name>', 'API key name')
28
+ .option('--subaccount-id <id>', 'Create a subaccount-scoped key')
29
+ .option('--expires-at <iso>', 'Expiration timestamp')
30
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
31
+ body: async (_args, options) => {
32
+ if (typeof options.input === 'string')
33
+ return readJsonFile(options.input);
34
+ return {
35
+ name: options.name,
36
+ subaccountId: options.subaccountId,
37
+ expiresAt: options.expiresAt,
38
+ };
39
+ },
40
+ }));
41
+ command.addCommand(createDeleteCommand(factory, {
42
+ description: 'Delete an API key',
43
+ path: '/api-keys/{keyId}',
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,86 @@
1
+ import { Command } from 'commander';
2
+ import { readBlob, readJsonFile } from '../shared/io.js';
3
+ import { addOutputFlags, outputData } from '../shared/output.js';
4
+ import { createDeleteCommand, createJsonBodyCommand, createListCommand, createViewCommand, } from '../shared/rest.js';
5
+ export function createBrowserExtensionCommand(factory) {
6
+ const command = new Command('browser-extension').description('Manage browser extensions');
7
+ command.addCommand(createListCommand(factory, {
8
+ description: 'List browser extensions',
9
+ path: '/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(createViewCommand(factory, {
23
+ name: 'get',
24
+ description: 'Get a browser extension',
25
+ path: '/browser-extensions/{extensionId}',
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 client = await factory.apiClient();
34
+ const file = await readBlob(path);
35
+ const result = await client.uploadFile('/browser-extensions/upload', {
36
+ file: file.blob,
37
+ fileName: file.fileName,
38
+ fields: {
39
+ ...(options.name ? { name: options.name } : {}),
40
+ ...(options.subaccountId ? { subaccountId: options.subaccountId } : {}),
41
+ },
42
+ });
43
+ await outputData(factory.io, result, options);
44
+ }));
45
+ command.addCommand(createJsonBodyCommand(factory, {
46
+ name: 'import',
47
+ description: 'Import a browser extension from a URL',
48
+ method: 'post',
49
+ path: '/browser-extensions/import',
50
+ configure: (cmd) => cmd
51
+ .option('--url <url>', 'Extension URL')
52
+ .option('--name <name>', 'Display name')
53
+ .option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key')
54
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
55
+ body: async (_args, options) => {
56
+ if (typeof options.input === 'string')
57
+ return readJsonFile(options.input);
58
+ return {
59
+ url: options.url,
60
+ name: options.name,
61
+ subaccountId: options.subaccountId,
62
+ };
63
+ },
64
+ }));
65
+ command.addCommand(createJsonBodyCommand(factory, {
66
+ name: 'patch',
67
+ description: 'Update a browser extension',
68
+ method: 'patch',
69
+ path: '/browser-extensions/{extensionId}',
70
+ argNames: ['extensionId'],
71
+ configure: (cmd) => cmd
72
+ .option('--name <name>', 'Display name')
73
+ .option('--input <path>', 'Read full JSON request body from file, or - for stdin'),
74
+ body: async (_args, options) => {
75
+ if (typeof options.input === 'string')
76
+ return readJsonFile(options.input);
77
+ return { name: options.name };
78
+ },
79
+ }));
80
+ command.addCommand(createDeleteCommand(factory, {
81
+ description: 'Delete a browser extension',
82
+ path: '/browser-extensions/{extensionId}',
83
+ argNames: ['extensionId'],
84
+ }));
85
+ return command;
86
+ }