@bctrl/cli 0.1.8 → 0.1.9

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/README.md CHANGED
@@ -1,113 +1,37 @@
1
1
  # BCTRL CLI
2
2
 
3
- Command-line tools for BCTRL cloud browser automation. Use it to create browser runtimes, start live sessions, submit hosted invocations, inspect runs and files, and manage account resources from a terminal or script.
4
-
5
- ## Install
6
-
7
3
  ```bash
8
4
  npm install -g @bctrl/cli
5
+ bctrl auth login
9
6
  ```
10
7
 
11
- Requires Node.js 22.14 or newer.
12
-
13
- ## Authenticate
14
-
15
- For an interactive terminal, start the browser approval flow and wait for completion:
16
-
17
- ```bash
18
- bctrl auth login --url --wait
19
- ```
20
-
21
- For CI, agents, or one-off shells, use an API key:
22
-
23
- ```bash
24
- export BCTRL_API_KEY="bctrl_..."
25
- bctrl auth status
26
- ```
27
-
28
- `BCTRL_API_KEY` takes precedence over credentials stored by `bctrl auth login`.
29
-
30
- ## Quick Start
31
-
32
- Create a browser runtime, start it, run an extraction task, then stop it:
33
-
34
- ```bash
35
- bctrl runtime create --name research-browser --json
36
- bctrl runtime start <runtime-id> --json
37
-
38
- bctrl runtime invocation create <runtime-id> \
39
- --action extract \
40
- --instruction "Extract the page title." \
41
- --json
42
-
43
- bctrl runtime invocation wait <runtime-id> <invocation-id> --json
44
- bctrl runtime stop <runtime-id>
45
- ```
46
-
47
- For full request bodies, pass JSON with `--body`. Inline JSON, `@file`, and `-` for stdin are supported:
48
-
49
- ```bash
50
- bctrl runtime invocation create <runtime-id> \
51
- --body '{"action":"observe","instruction":"Summarize the current page."}' \
52
- --json
53
-
54
- cat invocation.json | bctrl runtime invocation create <runtime-id> --body - --json
55
- ```
56
-
57
- Use `--params` for path and query overrides when you need the exact API surface:
8
+ Create and start runtimes with `bctrl runtime`. Automation is exposed through a
9
+ small generic Tool interface:
58
10
 
59
11
  ```bash
60
- bctrl run list --params '{"limit":25}' --json
61
- ```
62
-
63
- ## Output
12
+ bctrl tools call stagehand.act \
13
+ --body '{"runtimeId":"rt_...","instruction":"Click Continue"}'
64
14
 
65
- Print full JSON:
66
-
67
- ```bash
68
- bctrl runtime list --json
15
+ bctrl tools start captcha.solve --body '{"runtimeId":"rt_..."}'
16
+ bctrl tool-calls result call_... --params '{"waitSeconds":60}'
69
17
  ```
70
18
 
71
- Print selected fields:
19
+ Use persistent conversations for long-running agents:
72
20
 
73
21
  ```bash
74
- bctrl runtime list --json id,status,name
22
+ bctrl conversations create \
23
+ --body '{"agent":"browser-use","runtimeId":"rt_..."}'
24
+ bctrl conversations message conv_... --body '{"text":"Complete checkout"}'
25
+ bctrl conversations stream conv_...
75
26
  ```
76
27
 
77
- Filter with jq syntax:
28
+ Every automation path contributes to one Run:
78
29
 
79
30
  ```bash
80
- bctrl runtime list --json --jq '.data[] | select(.status == "active")'
31
+ bctrl runs trace run_...
32
+ bctrl runs events run_...
33
+ bctrl runs stream run_...
81
34
  ```
82
35
 
83
- Render with a template:
84
-
85
- ```bash
86
- bctrl runtime list \
87
- --json \
88
- --template '{{#each data}}{{id}} {{status}}{{newline}}{{/each}}'
89
- ```
90
-
91
- ## Common Commands
92
-
93
- ```bash
94
- bctrl space list
95
- bctrl runtime create --name browser-task
96
- bctrl runtime start <runtime-id>
97
- bctrl runtime target create <runtime-id> --uri https://example.com --activate
98
- bctrl runtime invocation create <runtime-id> --action act --instruction "Click the sign in button"
99
- bctrl run list --json
100
- bctrl runtime stop <runtime-id>
101
- ```
102
-
103
- Run any command with `--help` for the exact arguments and flags:
104
-
105
- ```bash
106
- bctrl runtime invocation create --help
107
- ```
108
-
109
- ## Documentation
110
-
111
- - CLI guide: https://platform.bctrl.ai/cli
112
- - Command reference: https://platform.bctrl.ai/cli/reference
113
- - API reference: https://platform.bctrl.ai/api-reference
36
+ All JSON-body commands accept inline JSON, `@file.json`, or `-` for stdin through
37
+ `--body`. Use `--json`, `--jq`, or `--template` to control output.
@@ -17,6 +17,7 @@ export type RequestOptions = {
17
17
  query?: Record<string, string | number | boolean | undefined>;
18
18
  idempotencyKey?: string;
19
19
  actingSubaccountId?: string;
20
+ runtimeId?: string;
20
21
  };
21
22
  export type JsonRequestOptions = RequestOptions & {
22
23
  body?: unknown;
@@ -120,6 +120,7 @@ function requestHeaders(token, options, accept) {
120
120
  authorization: `Bearer ${token}`,
121
121
  'user-agent': 'BCTRL CLI',
122
122
  ...(options?.actingSubaccountId ? { 'BCTRL-Subaccount-Id': options.actingSubaccountId } : {}),
123
+ ...(options?.runtimeId ? { 'BCTRL-Runtime-Id': options.runtimeId } : {}),
123
124
  };
124
125
  }
125
126
  function buildUrl(baseUrl, path, query) {
@@ -77,8 +77,8 @@ function suggestionForApiError(error) {
77
77
  return commandWithOptionalSpace('bctrl toolset list', error.details);
78
78
  case 'tool_call.not_found':
79
79
  return 'bctrl tool-call list';
80
- case 'vault_secret.not_found':
81
- return 'bctrl vault list';
80
+ case 'tool.not_found':
81
+ return 'bctrl tools list';
82
82
  case 'browser_extension.not_found':
83
83
  return 'bctrl browser extension list';
84
84
  case 'ai_credential.not_found':
@@ -1,3 +1,3 @@
1
1
  import { Command } from 'commander';
2
2
  import type { Factory } from '../../factory.js';
3
- export declare function createVaultCommand(factory: Factory): Command;
3
+ export declare function createAccountCommand(factory: Factory): Command;
@@ -0,0 +1,21 @@
1
+ import { Command } from 'commander';
2
+ import { addOutputFlags } from '../shared/output.js';
3
+ import { createOperationJsonBodyCommand, outputFlags, requestOperationAndPrint, } from '../shared/operation.js';
4
+ export function createAccountCommand(factory) {
5
+ const command = new Command('account').description('Manage organization settings');
6
+ command.addCommand(addOutputFlags(new Command('get').description('Get organization settings')).action(async (options) => {
7
+ await requestOperationAndPrint(factory, 'account.get', {
8
+ output: outputFlags(options),
9
+ });
10
+ }));
11
+ command.addCommand(createOperationJsonBodyCommand(factory, {
12
+ operationId: 'account.update',
13
+ name: 'patch',
14
+ description: 'Update organization settings (use --body for branding merge-patch JSON)',
15
+ configure: (cmd) => cmd.option('--dry-run', 'Validate and resolve without persisting'),
16
+ query: (_args, options) => ({
17
+ dryRun: options.dryRun === true,
18
+ }),
19
+ }));
20
+ return command;
21
+ }
@@ -70,14 +70,19 @@ function createAiCredentialWriteCommand(factory, name, operationId, argNames = [
70
70
  name,
71
71
  description: name === 'create' ? 'Create an AI credential' : 'Update an AI credential',
72
72
  argNames,
73
- configure: (cmd) => cmd
74
- .option('--name <name>', 'Credential name')
75
- .option('--provider <provider>', 'Provider key')
76
- .option('--api-key <key>', 'Provider API key')
77
- .option('--status <status>', 'Credential status')
78
- .option('--default-model <model>', 'Default model')
79
- .option('--base-url <url>', 'Custom provider base URL')
80
- .option('--subaccount-id <id>', 'Subaccount scope when using a parent/org key'),
73
+ configure: (cmd) => {
74
+ const configured = cmd
75
+ .option('--name <name>', 'Credential name')
76
+ .option('--provider <provider>', 'Provider key')
77
+ .option('--api-key <key>', 'Provider API key')
78
+ .option('--status <status>', 'Credential status')
79
+ .option('--default-model <model>', 'Default model')
80
+ .option('--base-url <url>', 'Custom provider base URL');
81
+ if (name === 'create') {
82
+ configured.option('--subaccount-id <id>', 'Use this Subaccount account context with an Organization key');
83
+ }
84
+ return configured;
85
+ },
81
86
  body: async (_args, options) => {
82
87
  return {
83
88
  name: options.name,
@@ -23,7 +23,7 @@ export function createApiKeyCommand(factory) {
23
23
  description: 'Create an API key',
24
24
  configure: (cmd) => cmd
25
25
  .option('--name <name>', 'API key name')
26
- .option('--subaccount-id <id>', 'Create a subaccount-scoped key')
26
+ .option('--subaccount-id <id>', 'Create a key confined to this Subaccount')
27
27
  .option('--expires-at <iso>', 'Expiration timestamp'),
28
28
  body: async (_args, options) => {
29
29
  return {
@@ -5,7 +5,7 @@ import { actingSubaccountOption, createOperationDeleteCommand, createOperationJs
5
5
  export function createBrowserExtensionCommand(factory) {
6
6
  const command = new Command('browser-extension').description('Manage browser extensions');
7
7
  command.addCommand(createOperationListCommand(factory, {
8
- operationId: 'browser-extensions.list',
8
+ operationId: 'browser.extensions.list',
9
9
  description: 'List browser extensions',
10
10
  configure: (cmd) => cmd
11
11
  .option('--subaccount-id <id>', 'Filter by subaccount id')
@@ -20,7 +20,7 @@ export function createBrowserExtensionCommand(factory) {
20
20
  actingSubaccountId: actingSubaccountOption(),
21
21
  }));
22
22
  command.addCommand(createOperationViewCommand(factory, {
23
- operationId: 'browser-extensions.get',
23
+ operationId: 'browser.extensions.get',
24
24
  name: 'get',
25
25
  description: 'Get a browser extension',
26
26
  argName: 'extensionId',
@@ -29,9 +29,9 @@ export function createBrowserExtensionCommand(factory) {
29
29
  .description('Upload a browser extension package')
30
30
  .argument('<path>')
31
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) => {
32
+ .option('--subaccount-id <id>', 'Create in this Subaccount account context with an Organization key')).action(async (path, options) => {
33
33
  const file = await readBlob(path);
34
- const result = await uploadOperationFile(factory, 'browser-extensions.upload', {
34
+ const result = await uploadOperationFile(factory, 'browser.extensions.create', {
35
35
  file: file.blob,
36
36
  fileName: file.fileName,
37
37
  fields: {
@@ -42,13 +42,13 @@ export function createBrowserExtensionCommand(factory) {
42
42
  await outputData(factory.io, result, options);
43
43
  }));
44
44
  command.addCommand(createOperationJsonBodyCommand(factory, {
45
- operationId: 'browser-extensions.import',
45
+ operationId: 'browser.extensions.create',
46
46
  name: 'import',
47
47
  description: 'Import a browser extension from a URL',
48
48
  configure: (cmd) => cmd
49
49
  .option('--url <url>', 'Extension URL')
50
50
  .option('--name <name>', 'Display name')
51
- .option('--subaccount-id <id>', 'Create under a subaccount when using a parent/org key'),
51
+ .option('--subaccount-id <id>', 'Create in this Subaccount account context with an Organization key'),
52
52
  body: async (_args, options) => {
53
53
  return {
54
54
  url: options.url,
@@ -58,7 +58,7 @@ export function createBrowserExtensionCommand(factory) {
58
58
  actingSubaccountId: (_args, options) => actingSubaccountOption()(options),
59
59
  }));
60
60
  command.addCommand(createOperationJsonBodyCommand(factory, {
61
- operationId: 'browser-extensions.update',
61
+ operationId: 'browser.extensions.update',
62
62
  name: 'patch',
63
63
  description: 'Update a browser extension',
64
64
  argNames: ['extensionId'],
@@ -69,7 +69,7 @@ export function createBrowserExtensionCommand(factory) {
69
69
  },
70
70
  }));
71
71
  command.addCommand(createOperationDeleteCommand(factory, {
72
- operationId: 'browser-extensions.delete',
72
+ operationId: 'browser.extensions.delete',
73
73
  description: 'Delete a browser extension',
74
74
  argNames: ['extensionId'],
75
75
  }));
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ import type { Factory } from '../../factory.js';
3
+ export declare function createConversationCommand(factory: Factory): Command;
@@ -0,0 +1,51 @@
1
+ import { Command } from 'commander';
2
+ import { createOperationJsonBodyCommand, createOperationListCommand, createOperationViewCommand, streamOperationText, } from '../shared/operation.js';
3
+ export function createConversationCommand(factory) {
4
+ const command = new Command('conversations').description('Converse with runtime agents');
5
+ command.addCommand(createOperationListCommand(factory, {
6
+ operationId: 'conversations.list',
7
+ description: 'List conversations',
8
+ }));
9
+ command.addCommand(createOperationJsonBodyCommand(factory, {
10
+ operationId: 'conversations.create',
11
+ name: 'create',
12
+ description: 'Create a conversation',
13
+ }));
14
+ command.addCommand(createOperationViewCommand(factory, {
15
+ operationId: 'conversations.get',
16
+ name: 'get',
17
+ description: 'Get a conversation and its messages',
18
+ argName: 'conversationId',
19
+ }));
20
+ command.addCommand(createOperationJsonBodyCommand(factory, {
21
+ operationId: 'conversations.update',
22
+ name: 'patch',
23
+ description: 'Update the default agent, model, toolset, or title',
24
+ argNames: ['conversationId'],
25
+ }));
26
+ command.addCommand(createOperationJsonBodyCommand(factory, {
27
+ operationId: 'conversations.messages.create',
28
+ name: 'message',
29
+ description: 'Send a message and start an agent turn',
30
+ argNames: ['conversationId'],
31
+ }));
32
+ command.addCommand(createOperationJsonBodyCommand(factory, {
33
+ operationId: 'conversations.cancel',
34
+ name: 'cancel',
35
+ description: 'Cancel the active agent turn',
36
+ argNames: ['conversationId'],
37
+ }));
38
+ command.addCommand(new Command('stream')
39
+ .description('Stream normalized conversation events')
40
+ .argument('<conversationId>')
41
+ .option('--after <cursor>', 'Resume after a stream cursor')
42
+ .action(async (conversationId, options) => {
43
+ const stream = await streamOperationText(factory, 'conversations.stream', {
44
+ pathParams: { conversationId },
45
+ query: { after: options.after },
46
+ });
47
+ for await (const chunk of stream)
48
+ factory.io.writeOut(chunk);
49
+ }));
50
+ return command;
51
+ }
@@ -10,21 +10,26 @@ export function createFileCommand(factory) {
10
10
  description: 'List files',
11
11
  configure: (cmd) => cmd
12
12
  .option('--space <id>', 'Filter by space id')
13
+ .option('--run <id>', 'Filter by run id')
14
+ .option('--runtime <id>', 'Filter by runtime id')
15
+ .option('--type <type...>', 'Filter by file artifact type')
13
16
  .option('--source <source>', 'Filter by file source')
14
17
  .option('--prefix <path>', 'Filter by path prefix')
15
18
  .option('--folders', 'Directory view: direct files plus subfolder rollups')
16
19
  .option('--created-after <iso>', 'Only files created after this timestamp')
17
- .option('--query <text>', 'Search query')
20
+ .option('--q <text>', 'Search query')
18
21
  .option('-L, --limit <number>', 'Maximum number of results to return', parsePositiveInteger)
19
22
  .option('--cursor <cursor>', 'Pagination cursor'),
20
23
  query: (options) => ({
21
24
  spaceId: typeof options.space === 'string' ? options.space : undefined,
25
+ runId: typeof options.run === 'string' ? options.run : undefined,
26
+ runtimeId: typeof options.runtime === 'string' ? options.runtime : undefined,
27
+ type: Array.isArray(options.type) ? options.type : undefined,
22
28
  source: typeof options.source === 'string' ? options.source : undefined,
23
29
  prefix: typeof options.prefix === 'string' ? options.prefix : undefined,
24
30
  include: options.folders === true ? 'folders' : undefined,
25
31
  createdAfter: typeof options.createdAfter === 'string' ? options.createdAfter : undefined,
26
- // The public query param is `q` (was sent as `query`, which the API ignores).
27
- q: typeof options.query === 'string' ? options.query : undefined,
32
+ q: typeof options.q === 'string' ? options.q : undefined,
28
33
  limit: typeof options.limit === 'number' ? options.limit : undefined,
29
34
  cursor: typeof options.cursor === 'string' ? options.cursor : undefined,
30
35
  }),