@parall/cli 1.22.0 → 1.24.0

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.
@@ -1,3 +1,48 @@
1
1
  import { Command } from 'commander';
2
+ import type { AgentPresence, CreateAgentResponse, Machine, ParallClient } from '@parall/sdk';
3
+ export declare function validateMachineOptions(opts: {
4
+ machineType?: string;
5
+ machineLabel?: string;
6
+ cpuCores?: number;
7
+ memoryMB?: number;
8
+ storageGB?: number;
9
+ }): void;
10
+ export declare function validateApiKeyOutputOptions(opts: {
11
+ apiKeyFile?: string;
12
+ showApiKey?: boolean;
13
+ discardApiKey?: boolean;
14
+ machineType?: string;
15
+ }): void;
16
+ export declare function validateWaitOptions(opts: {
17
+ wait?: boolean;
18
+ waitOnline?: boolean;
19
+ machineType?: string;
20
+ }): void;
21
+ export declare function redactCreateAgentResponse(result: CreateAgentResponse, opts: {
22
+ apiKeyFile?: string;
23
+ discardApiKey?: boolean;
24
+ }): Partial<{
25
+ api_key_redacted: boolean;
26
+ api_key_file: string | undefined;
27
+ api_key_discarded: boolean | undefined;
28
+ user: import("@parall/sdk").User;
29
+ machine?: Machine;
30
+ }>;
31
+ export declare class ProvisioningWaitError extends Error {
32
+ readonly phase: 'machine' | 'agent_online';
33
+ readonly state?: string;
34
+ constructor(message: string, phase: 'machine' | 'agent_online', state?: string);
35
+ }
36
+ export declare function waitForMachineRunning(client: ParallClient, orgId: string, machineId: string, opts: {
37
+ deadline: number;
38
+ intervalMs: number;
39
+ }): Promise<{
40
+ state: string;
41
+ machine: Machine;
42
+ }>;
43
+ export declare function waitForAgentOnline(client: ParallClient, orgId: string, agentId: string, opts: {
44
+ deadline: number;
45
+ intervalMs: number;
46
+ }): Promise<AgentPresence>;
2
47
  export declare function registerAgentCommands(program: Command): void;
3
48
  //# sourceMappingURL=agents.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,QAerD"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/commands/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,KAAK,EAAE,aAAa,EAAsB,mBAAmB,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAgCjH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,QAgBA;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE;IAChD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,QAWA;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,QAIvG;AAuBD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,mBAAmB,EAAE,IAAI,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE;;;;;;GAQ5H;AAMD,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc,EAAE,KAAK,CAAC,EAAE,MAAM;CAM/E;AAQD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAc9C;AAED,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GAC7C,OAAO,CAAC,aAAa,CAAC,CASxB;AA4CD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,QAoIrD"}
@@ -1,5 +1,172 @@
1
+ import { closeSync, openSync, unlinkSync, writeFileSync } from 'node:fs';
1
2
  import { resolveCredentials } from '../lib/client.js';
2
- import { printJson, printError } from '../lib/output.js';
3
+ import { printJson, printError, printRefHint } from '../lib/output.js';
4
+ function stripUndefined(obj) {
5
+ return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
6
+ }
7
+ function parseOptionalInt(value, field) {
8
+ if (value === undefined)
9
+ return undefined;
10
+ const parsed = Number(value);
11
+ if (!Number.isInteger(parsed) || parsed <= 0) {
12
+ throw new Error(`${field} must be a positive integer`);
13
+ }
14
+ return parsed;
15
+ }
16
+ function parsePermissions(value) {
17
+ if (!value)
18
+ return undefined;
19
+ const permissions = value.split(',').map((v) => v.trim()).filter(Boolean);
20
+ return permissions.length > 0 ? permissions : undefined;
21
+ }
22
+ function parseOptionalSeconds(value, field, fallback) {
23
+ if (value === undefined)
24
+ return fallback;
25
+ const parsed = Number(value);
26
+ if (!Number.isInteger(parsed) || parsed <= 0) {
27
+ throw new Error(`${field} must be a positive integer`);
28
+ }
29
+ return parsed;
30
+ }
31
+ export function validateMachineOptions(opts) {
32
+ const hasCustomSpec = opts.cpuCores !== undefined || opts.memoryMB !== undefined || opts.storageGB !== undefined;
33
+ if (opts.machineLabel !== undefined && opts.machineType !== 'cloud') {
34
+ throw new Error('--machine-label requires --machine-type cloud');
35
+ }
36
+ if (hasCustomSpec && opts.machineType !== 'cloud') {
37
+ throw new Error('cpu-cores/memory-mb/storage-gb require --machine-type cloud');
38
+ }
39
+ if (hasCustomSpec && opts.machineLabel !== 'custom') {
40
+ throw new Error('cpu-cores/memory-mb/storage-gb require --machine-label custom');
41
+ }
42
+ if (opts.machineType === 'cloud' && opts.machineLabel === 'custom') {
43
+ if (opts.cpuCores === undefined || opts.memoryMB === undefined || opts.storageGB === undefined) {
44
+ throw new Error('custom machine requires --cpu-cores --memory-mb --storage-gb');
45
+ }
46
+ }
47
+ }
48
+ export function validateApiKeyOutputOptions(opts) {
49
+ const selected = [opts.apiKeyFile !== undefined, opts.showApiKey === true, opts.discardApiKey === true].filter(Boolean).length;
50
+ if (selected > 1) {
51
+ throw new Error('--api-key-file, --show-api-key, and --discard-api-key are mutually exclusive');
52
+ }
53
+ if (selected === 0) {
54
+ throw new Error('choose --api-key-file <path>, --show-api-key, or --discard-api-key for the one-time api_key');
55
+ }
56
+ if (opts.discardApiKey && opts.machineType !== 'cloud') {
57
+ throw new Error('--discard-api-key requires --machine-type cloud because self-hosted agents need the one-time api_key');
58
+ }
59
+ }
60
+ export function validateWaitOptions(opts) {
61
+ if ((opts.wait || opts.waitOnline) && opts.machineType !== 'cloud') {
62
+ throw new Error('--wait and --wait-online require --machine-type cloud');
63
+ }
64
+ }
65
+ function writeApiKeyFile(fd, apiKey) {
66
+ writeFileSync(fd, `${apiKey}\n`, { encoding: 'utf8' });
67
+ closeSync(fd);
68
+ }
69
+ function cleanupApiKeyFile(fd, path) {
70
+ if (fd === undefined)
71
+ return;
72
+ try {
73
+ closeSync(fd);
74
+ }
75
+ catch {
76
+ // best-effort cleanup
77
+ }
78
+ if (path) {
79
+ try {
80
+ unlinkSync(path);
81
+ }
82
+ catch {
83
+ // best-effort cleanup
84
+ }
85
+ }
86
+ }
87
+ export function redactCreateAgentResponse(result, opts) {
88
+ const { api_key: _apiKey, ...safeResult } = result;
89
+ return stripUndefined({
90
+ ...safeResult,
91
+ api_key_redacted: true,
92
+ api_key_file: opts.apiKeyFile,
93
+ api_key_discarded: opts.discardApiKey === true ? true : undefined,
94
+ });
95
+ }
96
+ function sleep(ms) {
97
+ return new Promise((resolve) => setTimeout(resolve, ms));
98
+ }
99
+ export class ProvisioningWaitError extends Error {
100
+ phase;
101
+ state;
102
+ constructor(message, phase, state) {
103
+ super(message);
104
+ this.name = 'ProvisioningWaitError';
105
+ this.phase = phase;
106
+ this.state = state;
107
+ }
108
+ }
109
+ function ensureTimeRemaining(deadline, label, phase, state) {
110
+ if (Date.now() >= deadline) {
111
+ throw new ProvisioningWaitError(`${label} timed out`, phase, state);
112
+ }
113
+ }
114
+ export async function waitForMachineRunning(client, orgId, machineId, opts) {
115
+ let lastState = 'unknown';
116
+ for (;;) {
117
+ ensureTimeRemaining(opts.deadline, 'machine provisioning', 'machine', lastState === 'unknown' ? undefined : lastState);
118
+ const status = await client.getMachineStatus(orgId, machineId);
119
+ lastState = status.state;
120
+ if (lastState === 'running') {
121
+ return { state: lastState, machine: await client.getMachine(orgId, machineId) };
122
+ }
123
+ if (lastState === 'error' || lastState === 'terminated' || lastState === 'stopped') {
124
+ throw new ProvisioningWaitError(`machine provisioning ended with state ${lastState}`, 'machine', lastState);
125
+ }
126
+ await sleep(Math.min(opts.intervalMs, Math.max(0, opts.deadline - Date.now())));
127
+ }
128
+ }
129
+ export async function waitForAgentOnline(client, orgId, agentId, opts) {
130
+ for (;;) {
131
+ ensureTimeRemaining(opts.deadline, 'agent online wait', 'agent_online');
132
+ const presence = parseAgentMonitor(await client.getAgentMonitor(orgId, agentId));
133
+ if (presence.online) {
134
+ return presence;
135
+ }
136
+ await sleep(Math.min(opts.intervalMs, Math.max(0, opts.deadline - Date.now())));
137
+ }
138
+ }
139
+ function parseAgentMonitor(raw) {
140
+ if (raw === null || typeof raw !== 'object') {
141
+ return { online: false, status: 'offline' };
142
+ }
143
+ const data = raw;
144
+ const online = data.online === true;
145
+ return stripUndefined({
146
+ online,
147
+ status: typeof data.status === 'string' ? data.status : online ? 'online' : 'offline',
148
+ session_id: typeof data.session_id === 'string' ? data.session_id : undefined,
149
+ telemetry: data.telemetry !== null && typeof data.telemetry === 'object'
150
+ ? data.telemetry
151
+ : undefined,
152
+ });
153
+ }
154
+ function createAgentOutput(result, opts) {
155
+ return opts.showApiKey
156
+ ? result
157
+ : redactCreateAgentResponse(result, {
158
+ apiKeyFile: opts.apiKeyFile,
159
+ discardApiKey: opts.discardApiKey,
160
+ });
161
+ }
162
+ function printCreateAgentResult(result, opts, provisioning) {
163
+ const output = createAgentOutput(result, opts);
164
+ printJson(Object.keys(provisioning).length > 0 ? { ...output, provisioning } : output);
165
+ printRefHint(result.user.id);
166
+ }
167
+ function errorMessage(err) {
168
+ return err instanceof Error ? err.message : String(err);
169
+ }
3
170
  export function registerAgentCommands(program) {
4
171
  const agents = program.command('agents').description('Manage agents');
5
172
  agents
@@ -15,4 +182,122 @@ export function registerAgentCommands(program) {
15
182
  printError(err);
16
183
  }
17
184
  });
185
+ agents
186
+ .command('create')
187
+ .description('Create an agent in the organization')
188
+ .requiredOption('--name <name>', 'Agent display name')
189
+ .option('--runtime-type <type>', 'Runtime type: openclaw, claude-code, codex, hermes')
190
+ .option('--machine-type <type>', 'Machine type. Use "cloud" to provision hosted compute')
191
+ .option('--machine-label <label>', 'Hosted preset: lite, standard, pro, or custom')
192
+ .option('--cpu-cores <n>', 'CPU cores for machine-label=custom')
193
+ .option('--memory-mb <n>', 'Memory in MiB for machine-label=custom')
194
+ .option('--storage-gb <n>', 'Storage in GiB for machine-label=custom')
195
+ .option('--model-management <mode>', 'Model management mode: platform or self')
196
+ .option('--agent-model <model>', 'Platform-assigned model')
197
+ .option('--agent-provider <provider>', 'Agent provider metadata')
198
+ .option('--agent-endpoint <endpoint>', 'Agent endpoint metadata')
199
+ .option('--agent-permissions <permissions>', 'Comma-separated agent permissions')
200
+ .option('--openai-api-key <key>', 'Hosted Codex OpenAI API key override')
201
+ .option('--openai-base-url <url>', 'Hosted Codex OpenAI-compatible base URL override')
202
+ .option('--api-key-file <path>', 'Write the one-time API key to a new 0600 file')
203
+ .option('--show-api-key', 'Print the one-time API key to stdout; unsafe in logged agent runtimes')
204
+ .option('--discard-api-key', 'Discard the one-time API key; use for cloud agents where the server injects it')
205
+ .option('--wait', 'For cloud agents, wait until the machine reaches running')
206
+ .option('--wait-online', 'Wait until the created agent reports online presence; implies --wait for cloud agents')
207
+ .option('--wait-timeout-sec <n>', 'Maximum seconds to wait for provisioning/online status', '600')
208
+ .option('--wait-interval-sec <n>', 'Seconds between status polls', '5')
209
+ .action(async (opts) => {
210
+ let apiKeyFileFd;
211
+ try {
212
+ const { client, orgId } = resolveCredentials();
213
+ const cpuCores = parseOptionalInt(opts.cpuCores, 'cpu-cores');
214
+ const memoryMB = parseOptionalInt(opts.memoryMb, 'memory-mb');
215
+ const storageGB = parseOptionalInt(opts.storageGb, 'storage-gb');
216
+ validateApiKeyOutputOptions({
217
+ apiKeyFile: opts.apiKeyFile,
218
+ showApiKey: opts.showApiKey,
219
+ discardApiKey: opts.discardApiKey,
220
+ machineType: opts.machineType,
221
+ });
222
+ validateMachineOptions({
223
+ machineType: opts.machineType,
224
+ machineLabel: opts.machineLabel,
225
+ cpuCores,
226
+ memoryMB,
227
+ storageGB,
228
+ });
229
+ validateWaitOptions({
230
+ wait: opts.wait,
231
+ waitOnline: opts.waitOnline,
232
+ machineType: opts.machineType,
233
+ });
234
+ const waitTimeoutSec = parseOptionalSeconds(opts.waitTimeoutSec, 'wait-timeout-sec', 600);
235
+ const waitIntervalSec = parseOptionalSeconds(opts.waitIntervalSec, 'wait-interval-sec', 5);
236
+ if (opts.apiKeyFile) {
237
+ apiKeyFileFd = openSync(opts.apiKeyFile, 'wx', 0o600);
238
+ }
239
+ const providerConfig = stripUndefined({
240
+ openai_api_key: opts.openaiApiKey,
241
+ openai_base_url: opts.openaiBaseUrl,
242
+ });
243
+ const req = stripUndefined({
244
+ display_name: opts.name,
245
+ runtime_type: opts.runtimeType,
246
+ machine_type: opts.machineType,
247
+ machine_label: opts.machineLabel,
248
+ cpu_cores: cpuCores,
249
+ memory_mb: memoryMB,
250
+ storage_gb: storageGB,
251
+ model_management: opts.modelManagement,
252
+ agent_model: opts.agentModel,
253
+ agent_provider: opts.agentProvider,
254
+ agent_endpoint: opts.agentEndpoint,
255
+ agent_permissions: parsePermissions(opts.agentPermissions),
256
+ provider_config: Object.keys(providerConfig).length > 0 ? providerConfig : undefined,
257
+ });
258
+ const result = await client.createAgent(orgId, req);
259
+ if (apiKeyFileFd !== undefined) {
260
+ writeApiKeyFile(apiKeyFileFd, result.api_key);
261
+ apiKeyFileFd = undefined;
262
+ }
263
+ const waitDeadline = Date.now() + waitTimeoutSec * 1000;
264
+ const provisioning = {};
265
+ try {
266
+ const shouldWaitMachine = opts.wait || opts.waitOnline;
267
+ if (shouldWaitMachine && result.machine) {
268
+ const waited = await waitForMachineRunning(client, orgId, result.machine.id, {
269
+ deadline: waitDeadline,
270
+ intervalMs: waitIntervalSec * 1000,
271
+ });
272
+ result.machine = waited.machine;
273
+ provisioning.machine_state = waited.state;
274
+ }
275
+ if (opts.waitOnline) {
276
+ const presence = await waitForAgentOnline(client, orgId, result.user.id, {
277
+ deadline: waitDeadline,
278
+ intervalMs: waitIntervalSec * 1000,
279
+ });
280
+ provisioning.agent_online = true;
281
+ provisioning.presence = presence;
282
+ }
283
+ }
284
+ catch (waitErr) {
285
+ provisioning.wait_failed = true;
286
+ provisioning.wait_error = errorMessage(waitErr);
287
+ if (waitErr instanceof ProvisioningWaitError) {
288
+ provisioning.wait_phase = waitErr.phase;
289
+ if (waitErr.state) {
290
+ provisioning.last_state = waitErr.state;
291
+ }
292
+ }
293
+ printCreateAgentResult(result, opts, provisioning);
294
+ printError(waitErr);
295
+ }
296
+ printCreateAgentResult(result, opts, provisioning);
297
+ }
298
+ catch (err) {
299
+ cleanupApiKeyFile(apiKeyFileFd, opts.apiKeyFile);
300
+ printError(err);
301
+ }
302
+ });
18
303
  }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerApprovalCommands(program: Command): void;
3
+ //# sourceMappingURL=approvals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approvals.d.ts","sourceRoot":"","sources":["../../src/commands/approvals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,QAgExD"}
@@ -0,0 +1,67 @@
1
+ import { resolveCredentials } from '../lib/client.js';
2
+ import { printJson, printError, printRefHint, stripPrllScheme } from '../lib/output.js';
3
+ export function registerApprovalCommands(program) {
4
+ const approvals = program.command('approvals').description('Manage approval requests');
5
+ approvals
6
+ .command('request')
7
+ .description('Request approval for a permission-denied action')
8
+ .requiredOption('--action <type>', 'Action type (e.g. chat.archive)')
9
+ .requiredOption('--resource <uri>', 'Resource URI (e.g. prll://cht_123)')
10
+ .requiredOption('--chat <chatId>', 'Chat to post the approval card in (e.g. prll://cht_456)')
11
+ .requiredOption('--title <title>', 'Human-readable title for the request')
12
+ .option('--reason <text>', 'Why this action is needed')
13
+ .option('--payload <json>', 'Frozen action payload as a JSON object')
14
+ .action(async (opts) => {
15
+ try {
16
+ const { client, orgId } = resolveCredentials();
17
+ let actionPayload;
18
+ if (opts.payload) {
19
+ const parsed = JSON.parse(opts.payload);
20
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
21
+ throw new Error('--payload must be a JSON object');
22
+ }
23
+ actionPayload = parsed;
24
+ }
25
+ const result = await client.requestApproval(orgId, {
26
+ action_type: opts.action,
27
+ resource_uri: opts.resource,
28
+ action_payload: actionPayload,
29
+ chat_id: stripPrllScheme(opts.chat),
30
+ title: opts.title,
31
+ reason: opts.reason,
32
+ });
33
+ printJson(result);
34
+ printRefHint(result.id);
35
+ }
36
+ catch (err) {
37
+ printError(err);
38
+ }
39
+ });
40
+ approvals
41
+ .command('cancel')
42
+ .description('Cancel a pending approval request')
43
+ .argument('<approvalId>', 'Approval request ID')
44
+ .action(async (approvalId) => {
45
+ try {
46
+ const { client } = resolveCredentials();
47
+ await client.cancelApproval(stripPrllScheme(approvalId));
48
+ printJson({ ok: true, id: stripPrllScheme(approvalId), status: 'cancelled' });
49
+ }
50
+ catch (err) {
51
+ printError(err);
52
+ }
53
+ });
54
+ approvals
55
+ .command('list')
56
+ .description('List pending approval requests')
57
+ .action(async () => {
58
+ try {
59
+ const { client } = resolveCredentials();
60
+ const result = await client.getPendingApprovals();
61
+ printJson(result);
62
+ }
63
+ catch (err) {
64
+ printError(err);
65
+ }
66
+ });
67
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMachineCommands(program: Command): void;
3
+ //# sourceMappingURL=machines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machines.d.ts","sourceRoot":"","sources":["../../src/commands/machines.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA0DvD"}
@@ -0,0 +1,70 @@
1
+ import { resolveCredentials } from '../lib/client.js';
2
+ import { printJson, printError, stripPrllScheme } from '../lib/output.js';
3
+ function parseLines(value) {
4
+ if (value === undefined)
5
+ return 100;
6
+ const parsed = Number(value);
7
+ if (!Number.isInteger(parsed) || parsed <= 0) {
8
+ throw new Error('lines must be a positive integer');
9
+ }
10
+ return parsed;
11
+ }
12
+ export function registerMachineCommands(program) {
13
+ const machines = program.command('machines').description('Inspect hosted agent machines');
14
+ machines
15
+ .command('list')
16
+ .description('List machines in the organization')
17
+ .action(async () => {
18
+ try {
19
+ const { client, orgId } = resolveCredentials();
20
+ const result = await client.getMachines(orgId);
21
+ printJson(result);
22
+ }
23
+ catch (err) {
24
+ printError(err);
25
+ }
26
+ });
27
+ machines
28
+ .command('get')
29
+ .description('Get a machine by ID')
30
+ .argument('<machineId>', 'Machine ID')
31
+ .action(async (machineId) => {
32
+ try {
33
+ const { client, orgId } = resolveCredentials();
34
+ const result = await client.getMachine(orgId, stripPrllScheme(machineId));
35
+ printJson(result);
36
+ }
37
+ catch (err) {
38
+ printError(err);
39
+ }
40
+ });
41
+ machines
42
+ .command('status')
43
+ .description('Get current machine status')
44
+ .argument('<machineId>', 'Machine ID')
45
+ .action(async (machineId) => {
46
+ try {
47
+ const { client, orgId } = resolveCredentials();
48
+ const result = await client.getMachineStatus(orgId, stripPrllScheme(machineId));
49
+ printJson(result);
50
+ }
51
+ catch (err) {
52
+ printError(err);
53
+ }
54
+ });
55
+ machines
56
+ .command('logs')
57
+ .description('Get recent machine logs')
58
+ .argument('<machineId>', 'Machine ID')
59
+ .option('--lines <n>', 'Number of log lines', '100')
60
+ .action(async (machineId, opts) => {
61
+ try {
62
+ const { client, orgId } = resolveCredentials();
63
+ const result = await client.getMachineLogs(orgId, stripPrllScheme(machineId), parseLines(opts.lines));
64
+ printJson(result);
65
+ }
66
+ catch (err) {
67
+ printError(err);
68
+ }
69
+ });
70
+ }
@@ -11,7 +11,7 @@ export function registerScheduleCommands(program) {
11
11
  .command('list')
12
12
  .description('List schedules in the organization')
13
13
  .option('--attached-to <uri>', 'Filter by attached prll:// URI (exact match)')
14
- .option('--target-id <id>', 'Filter by target user ID')
14
+ .option('--attendee-id <id>', 'Filter by attendee user ID')
15
15
  .option('--creator-id <id>', 'Filter by creator user ID')
16
16
  .option('--status <statuses>', 'Comma-separated statuses (e.g. active,paused)')
17
17
  .option('--limit <n>', 'Maximum number of schedules to return', '50')
@@ -21,7 +21,7 @@ export function registerScheduleCommands(program) {
21
21
  const { client, orgId } = resolveCredentials();
22
22
  const params = stripUndefined({
23
23
  attached_to: opts.attachedTo,
24
- target_id: opts.targetId ? stripPrllScheme(opts.targetId) : undefined,
24
+ attendee_id: opts.attendeeId ? stripPrllScheme(opts.attendeeId) : undefined,
25
25
  creator_id: opts.creatorId ? stripPrllScheme(opts.creatorId) : undefined,
26
26
  status: opts.status,
27
27
  limit: Number(opts.limit),
@@ -39,7 +39,7 @@ export function registerScheduleCommands(program) {
39
39
  .description('Create a schedule. Provide exactly one of --cron-expr, --interval-seconds, or --run-at.')
40
40
  .requiredOption('--name <name>', 'Schedule name')
41
41
  .requiredOption('--description <text>', 'Message delivered to the target on fire')
42
- .requiredOption('--target-id <id>', 'Target user ID (agent or human)')
42
+ .requiredOption('--target-ids <ids>', 'Comma-separated target user IDs (agents or humans)')
43
43
  .option('--spec-type <type>', 'cron | interval | one_shot (inferred from spec flags if omitted)')
44
44
  .option('--cron-expr <expr>', '5-field cron expression')
45
45
  .option('--interval-seconds <n>', 'Interval in seconds (>= 60)')
@@ -67,7 +67,7 @@ export function registerScheduleCommands(program) {
67
67
  const data = {
68
68
  name: opts.name,
69
69
  description: opts.description,
70
- target_id: stripPrllScheme(opts.targetId),
70
+ target_ids: opts.targetIds.split(',').map((s) => stripPrllScheme(s.trim())).filter(Boolean),
71
71
  spec_type: specType,
72
72
  ...stripUndefined({
73
73
  attached_to_uri: opts.attachedToUri,
@@ -109,7 +109,7 @@ export function registerScheduleCommands(program) {
109
109
  .argument('<scheduleId>', 'Schedule ID')
110
110
  .option('--name <name>', 'Schedule name')
111
111
  .option('--description <text>', 'Message delivered to the target on fire')
112
- .option('--target-id <id>', 'Target user ID')
112
+ .option('--target-ids <ids>', 'Comma-separated target user IDs')
113
113
  .option('--attached-to-uri <uri>', 'Attach to a prll:// resource URI')
114
114
  .option('--clear-attached-to', 'Explicitly clear attached_to_uri')
115
115
  .option('--cron-expr <expr>', '5-field cron expression')
@@ -147,7 +147,7 @@ export function registerScheduleCommands(program) {
147
147
  const data = stripUndefined({
148
148
  name: opts.name,
149
149
  description: opts.description,
150
- target_id: opts.targetId ? stripPrllScheme(opts.targetId) : undefined,
150
+ target_ids: opts.targetIds ? opts.targetIds.split(',').map((s) => stripPrllScheme(s.trim())).filter(Boolean) : undefined,
151
151
  attached_to_uri: opts.attachedToUri,
152
152
  attached_to_uri_clear: opts.clearAttachedTo || undefined,
153
153
  cron_expr: opts.cronExpr,
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { createRequire } from 'module';
3
3
  import { Command } from 'commander';
4
4
  import { registerAgentCommands } from './commands/agents.js';
5
+ import { registerApprovalCommands } from './commands/approvals.js';
5
6
  import { registerChatCommands } from './commands/chats.js';
6
7
  import { registerDMCommands } from './commands/dm.js';
7
8
  import { registerMessageCommands } from './commands/messages.js';
@@ -14,6 +15,7 @@ import { registerMcpCommands } from './commands/mcp.js';
14
15
  import { registerNoReplyCommands } from './commands/no-reply.js';
15
16
  import { registerRefCommands } from './commands/refs.js';
16
17
  import { registerFileCommands } from './commands/files.js';
18
+ import { registerMachineCommands } from './commands/machines.js';
17
19
  const require = createRequire(import.meta.url);
18
20
  const pkg = require('../package.json');
19
21
  const program = new Command();
@@ -22,6 +24,7 @@ program
22
24
  .description('CLI client for Parall — universal agent & human access to Parall API')
23
25
  .version(pkg.version);
24
26
  registerAgentCommands(program);
27
+ registerApprovalCommands(program);
25
28
  registerChatCommands(program);
26
29
  registerDMCommands(program);
27
30
  registerMessageCommands(program);
@@ -34,4 +37,5 @@ registerMcpCommands(program);
34
37
  registerNoReplyCommands(program);
35
38
  registerRefCommands(program);
36
39
  registerFileCommands(program);
40
+ registerMachineCommands(program);
37
41
  program.parse();
@@ -19,7 +19,7 @@ export function stripPrllScheme(idOrUri) {
19
19
  export function printError(err) {
20
20
  let msg;
21
21
  if (err instanceof ApiError) {
22
- msg = { error: err.message, status: err.status };
22
+ msg = { error: err.message, status: err.status, ...(err.code ? { code: err.code } : {}) };
23
23
  }
24
24
  else if (err instanceof Error) {
25
25
  msg = { error: err.message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/cli",
3
- "version": "1.22.0",
3
+ "version": "1.24.0",
4
4
  "description": "CLI client for Parall — universal agent & human access to Parall API",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -30,7 +30,7 @@
30
30
  "@modelcontextprotocol/sdk": "1.27.1",
31
31
  "commander": "^13.0.0",
32
32
  "zod": "^4.3.6",
33
- "@parall/sdk": "1.22.0"
33
+ "@parall/sdk": "1.24.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^22.0.0",