@parall/cli 1.53.0 → 1.54.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.
- package/dist/commands/clip.d.ts.map +1 -1
- package/dist/commands/clip.js +118 -18
- package/dist/lib/mcp-connections.d.ts +11 -0
- package/dist/lib/mcp-connections.d.ts.map +1 -0
- package/dist/lib/mcp-connections.js +68 -0
- package/dist/lib/text-input.d.ts +8 -0
- package/dist/lib/text-input.d.ts.map +1 -1
- package/dist/lib/text-input.js +23 -17
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/commands/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"clip.d.ts","sourceRoot":"","sources":["../../src/commands/clip.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmFpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QA+fpD"}
|
package/dist/commands/clip.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { resolveCredentials } from '../lib/client.js';
|
|
2
2
|
import { execEdgeClipWithBoundedWait } from '../lib/edge-exec.js';
|
|
3
|
+
import { getClipMCPConfigById, listClipMCPConnectionSummaries } from '../lib/mcp-connections.js';
|
|
3
4
|
import { printJson, printError, parsePositiveInt } from '../lib/output.js';
|
|
4
5
|
import { buildPublishedManifest, collectPublishSource } from '../lib/publish-source.js';
|
|
6
|
+
import { readTextFileArg } from '../lib/text-input.js';
|
|
5
7
|
const NO_ALIAS_HINT = 'no alias — reference this connection by its ccn_ id, or ask a human to name it in the Clip Console so callers can tell the accounts apart';
|
|
6
8
|
function connectionAnnotations(conn, deviceById) {
|
|
7
9
|
const out = { target: 'orphaned' };
|
|
@@ -218,7 +220,32 @@ export function registerClipCommands(program) {
|
|
|
218
220
|
const deviceById = conns.some((c) => c.device_id)
|
|
219
221
|
? await loadDeviceIndex(client, orgId)
|
|
220
222
|
: new Map();
|
|
221
|
-
|
|
223
|
+
// Multi-connection MCP model: an MCP clip may hold several
|
|
224
|
+
// connections — one credential slot (account) each. The raw rows
|
|
225
|
+
// cannot tell an agent WHICH account is usable, so join the per-config
|
|
226
|
+
// summaries (auth mode, oauth status, tool count) when any MCP row
|
|
227
|
+
// exists; null = surface unavailable, degrade to the plain rows.
|
|
228
|
+
const mcpByConnection = conns.some((c) => c.mcp_config_id)
|
|
229
|
+
? new Map(((await listClipMCPConnectionSummaries(orgId, found.id)) ?? []).map((s) => [
|
|
230
|
+
s.connection_id,
|
|
231
|
+
s,
|
|
232
|
+
]))
|
|
233
|
+
: new Map();
|
|
234
|
+
printJson(conns.map((c) => {
|
|
235
|
+
const mcp = mcpByConnection.get(c.id);
|
|
236
|
+
return {
|
|
237
|
+
...c,
|
|
238
|
+
...connectionAnnotations(c, deviceById),
|
|
239
|
+
...(mcp
|
|
240
|
+
? {
|
|
241
|
+
auth_type: mcp.auth_type,
|
|
242
|
+
credential_set: mcp.credential_set,
|
|
243
|
+
...(mcp.oauth_status ? { oauth_status: mcp.oauth_status } : {}),
|
|
244
|
+
...(typeof mcp.tool_count === 'number' ? { tool_count: mcp.tool_count } : {}),
|
|
245
|
+
}
|
|
246
|
+
: {}),
|
|
247
|
+
};
|
|
248
|
+
}));
|
|
222
249
|
}
|
|
223
250
|
catch (err) {
|
|
224
251
|
printError(err);
|
|
@@ -226,9 +253,10 @@ export function registerClipCommands(program) {
|
|
|
226
253
|
});
|
|
227
254
|
clip
|
|
228
255
|
.command('tools')
|
|
229
|
-
.description('List the MCP tool schemas (name, description, inputSchema) of an MCP clip — the discovery step before `clip exec <clip> <tool
|
|
256
|
+
.description('List the MCP tool schemas (name, description, inputSchema) of an MCP clip — the discovery step before `clip exec <clip> <tool>`. With several connections (accounts), pass --connection to pick one; without it the DEFAULT connection answers')
|
|
230
257
|
.argument('<clip>', 'Registry clip id (crg_...) or name')
|
|
231
|
-
.
|
|
258
|
+
.option('--connection <ref>', 'Connection id (ccn_…) or alias — tools are per connection under the multi-account model; discover refs with `parall clip connections <clip>`')
|
|
259
|
+
.action(async (clip, opts) => {
|
|
232
260
|
try {
|
|
233
261
|
const { client, orgId } = resolveCredentials();
|
|
234
262
|
// Same lookup `clip connections`/`info` use.
|
|
@@ -239,38 +267,110 @@ export function registerClipCommands(program) {
|
|
|
239
267
|
return;
|
|
240
268
|
}
|
|
241
269
|
// MCP tools are NOT frozen in the clip manifest, so `clip info` cannot
|
|
242
|
-
// show them; the live
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
270
|
+
// show them; the live per-config snapshot is the only source.
|
|
271
|
+
// name/description/inputSchema ride verbatim in each tool object —
|
|
272
|
+
// print as-is (untrusted external data, not instructions).
|
|
273
|
+
if (opts?.connection) {
|
|
274
|
+
const resp = await client.listClipConnections(orgId, found.id);
|
|
275
|
+
const conns = resp.data ?? [];
|
|
276
|
+
const conn = conns.find((c) => c.id === opts.connection || c.alias === opts.connection);
|
|
277
|
+
if (!conn) {
|
|
278
|
+
printError(new Error(`No connection matching "${opts.connection}" (see \`parall clip connections ${found.name}\`)`));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (!conn.mcp_config_id) {
|
|
282
|
+
printError(new Error(`Connection "${opts.connection}" is not an MCP connection`));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const config = await getClipMCPConfigById(orgId, found.id, conn.mcp_config_id);
|
|
286
|
+
printJson(config.tools ?? []);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// No --connection: the DEFAULT connection's snapshot (the singular
|
|
290
|
+
// endpoint). A 404 with SEVERAL connections means "no default — pick
|
|
291
|
+
// one"; surface that hint instead of the server's bare not-found.
|
|
292
|
+
try {
|
|
293
|
+
const config = await client.getClipMCPConfig(orgId, found.id);
|
|
294
|
+
printJson(config.tools ?? []);
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
const status = err.status;
|
|
298
|
+
if (status === 404) {
|
|
299
|
+
const summaries = await listClipMCPConnectionSummaries(orgId, found.id);
|
|
300
|
+
if (summaries && summaries.length > 0) {
|
|
301
|
+
printError(new Error(`This clip has ${summaries.length} MCP connections and no default — pass --connection <ccn_|alias> (see \`parall clip connections ${found.name}\`)`));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
throw err;
|
|
306
|
+
}
|
|
249
307
|
}
|
|
250
308
|
catch (err) {
|
|
251
309
|
printError(err);
|
|
252
310
|
}
|
|
253
311
|
});
|
|
254
|
-
// `clip invoke` (the v2 clip-service path) is
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
258
|
-
//
|
|
312
|
+
// `clip invoke` (the v2 clip-service path) is retired — but the verb stays
|
|
313
|
+
// as a hidden tombstone rather than being absent. An absent verb hands the
|
|
314
|
+
// caller to commander's edit-distance guess, which suggested `info` — a
|
|
315
|
+
// discovery verb — when the migration target is `exec`; agents coming from
|
|
316
|
+
// the v2 habit followed it into the wrong command. The stub never touches
|
|
317
|
+
// any endpoint (the v2 path resolved a different clip namespace and only
|
|
318
|
+
// answered CLIP_NOT_FOUND); it redirects on the JSON error contract and
|
|
319
|
+
// stays out of --help. The v2 server endpoint retires separately.
|
|
320
|
+
clip
|
|
321
|
+
.command('invoke', { hidden: true })
|
|
322
|
+
.allowUnknownOption(true)
|
|
323
|
+
.allowExcessArguments(true)
|
|
324
|
+
.argument('[legacy...]')
|
|
325
|
+
.action(() => {
|
|
326
|
+
printError(new Error('`clip invoke` was removed with the v2 clip service. Execute with `parall clip exec <clip> <command> [json-args] --connection <ccn_|alias>` (or --edge <edgeId> for a desktop device you own); discover clips and connections with `parall clip list`.'));
|
|
327
|
+
});
|
|
259
328
|
clip
|
|
260
329
|
.command('exec')
|
|
261
|
-
.description('Execute a registry (Edge) clip command
|
|
330
|
+
.description('Execute a registry (Edge) clip command against an EXPLICIT target: --connection <ccn_|alias> (the only route to a cloud/hosted profile or MCP server) or --edge <edgeId> (a desktop device you own). Omitting both is an error')
|
|
262
331
|
.argument('<clip>', 'Clip name in the org clip registry')
|
|
263
332
|
.argument('<command>', 'Command name to execute')
|
|
264
333
|
.argument('[args]', 'Command arguments (JSON string or plain text)')
|
|
265
|
-
.option('--connection <ref>', 'Clip connection id (ccn_…) or alias — discover them with `parall clip connections <clip>`. REQUIRED to reach a cloud (hosted) profile: the binding
|
|
334
|
+
.option('--connection <ref>', 'Clip connection id (ccn_…) or alias — discover them with `parall clip connections <clip>`. REQUIRED to reach a cloud (hosted) profile OR an MCP connection: the binding is the authorization, and with several MCP accounts it names WHICH one you act as. The only other target form is --edge (a desktop device you own) — MCP is never reachable without --connection')
|
|
266
335
|
.option('--edge <edgeId>', 'A desktop (BYOC) device you own. Mutually exclusive with --connection')
|
|
267
336
|
.option('--profile <name>', 'Browser profile (with --connection it may only restate the granted one)')
|
|
268
337
|
.option('--timeout <ms>', 'Execution timeout in milliseconds', '30000')
|
|
338
|
+
.option('--args-file <path>', "Read command args as JSON from a file ('-' = stdin) — shell-safe channel for quote-heavy JSON. Mutually exclusive with the [args] positional")
|
|
269
339
|
.action(async (clipName, command, args, opts) => {
|
|
270
340
|
try {
|
|
271
341
|
const { client, orgId } = resolveCredentials();
|
|
342
|
+
// Explicit target only, refused locally before any request. The
|
|
343
|
+
// server still resolves a bare request to the caller's own online
|
|
344
|
+
// desktop device (legacy BYOC fallback) so older pinned CLIs keep
|
|
345
|
+
// working — but that resolution depends on whichever device happens
|
|
346
|
+
// to be online, and for the common caller (a hosted agent, which
|
|
347
|
+
// owns no desktop device) it can only fail after a round-trip.
|
|
348
|
+
if (!opts?.connection && !opts?.edge) {
|
|
349
|
+
printError(new Error('clip exec needs an explicit target: --connection <ccn_|alias> (cloud/MCP profiles — discover with `parall clip connections <clip>`) or --edge <edgeId> (a desktop device you own). The legacy no-flag form resolved to whichever of your own desktop devices was online; name the target instead.'));
|
|
350
|
+
}
|
|
351
|
+
if (opts?.connection && opts?.edge) {
|
|
352
|
+
printError(new Error('--connection and --edge are mutually exclusive — pass exactly one target'));
|
|
353
|
+
}
|
|
272
354
|
let parsedArgs;
|
|
273
|
-
if (
|
|
355
|
+
if (opts?.argsFile !== undefined) {
|
|
356
|
+
if (args !== undefined) {
|
|
357
|
+
printError(new Error('[args] and --args-file are mutually exclusive — pass the args one way'));
|
|
358
|
+
}
|
|
359
|
+
const raw = readTextFileArg('--args-file', opts.argsFile);
|
|
360
|
+
try {
|
|
361
|
+
parsedArgs = JSON.parse(raw);
|
|
362
|
+
}
|
|
363
|
+
catch (err) {
|
|
364
|
+
// Strict by design: the positional's parse-or-string fallback
|
|
365
|
+
// exists for plain-text single arguments; a FILE is only ever
|
|
366
|
+
// the JSON channel, and silently sending malformed JSON as one
|
|
367
|
+
// big string would hand the clip a payload the caller never
|
|
368
|
+
// meant to build.
|
|
369
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
370
|
+
printError(new Error(`--args-file must contain valid JSON: ${reason}`));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
else if (args !== undefined) {
|
|
274
374
|
try {
|
|
275
375
|
parsedArgs = JSON.parse(args);
|
|
276
376
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type MCPConfigResponse, type MCPConnectionSummary } from '@parall/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* List a clip's MCP connections (default first). Resolves null when the
|
|
4
|
+
* surface is unavailable — older server without the plural family (404 route
|
|
5
|
+
* miss) or cap:clip-mcp off — so callers degrade to the plain connection rows
|
|
6
|
+
* instead of failing discovery.
|
|
7
|
+
*/
|
|
8
|
+
export declare function listClipMCPConnectionSummaries(orgId: string, clipId: string): Promise<MCPConnectionSummary[] | null>;
|
|
9
|
+
/** Per-config read — the only surface carrying the full tools snapshot. */
|
|
10
|
+
export declare function getClipMCPConfigById(orgId: string, clipId: string, configId: string): Promise<MCPConfigResponse>;
|
|
11
|
+
//# sourceMappingURL=mcp-connections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-connections.d.ts","sourceRoot":"","sources":["../../src/lib/mcp-connections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,iBAAiB,EAAE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAwC3F;;;;;GAKG;AACH,wBAAsB,8BAA8B,CAClD,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,oBAAoB,EAAE,GAAG,IAAI,CAAC,CAsBxC;AAED,2EAA2E;AAC3E,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,iBAAiB,CAAC,CAE5B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ENDPOINTS } from '@parall/sdk';
|
|
2
|
+
// Raw reads of the multi-connection MCP config surface. The SDK deliberately
|
|
3
|
+
// exposes only contract types + endpoint builders for this family (the #2281
|
|
4
|
+
// layering — no ParallClient methods), so the CLI carries its own thin fetch:
|
|
5
|
+
// X-Api-Key auth from the same env resolveCredentials() uses, swimlane header
|
|
6
|
+
// included so lane testing behaves like every other CLI call.
|
|
7
|
+
function headers() {
|
|
8
|
+
const h = {
|
|
9
|
+
'X-Api-Key': process.env.PRLL_API_KEY ?? '',
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
};
|
|
12
|
+
const lane = process.env.PRLL_SWIMLANE_NAME?.trim();
|
|
13
|
+
if (lane)
|
|
14
|
+
h['X-Prll-Swimlane'] = lane;
|
|
15
|
+
return h;
|
|
16
|
+
}
|
|
17
|
+
async function get(path) {
|
|
18
|
+
const base = (process.env.PRLL_API_URL ?? '').replace(/\/$/, '');
|
|
19
|
+
const res = await fetch(`${base}${path}`, { headers: headers() });
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
const text = await res.text();
|
|
22
|
+
let message = `API ${res.status}: ${text}`;
|
|
23
|
+
let code;
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(text);
|
|
26
|
+
if (typeof parsed.error?.message === 'string')
|
|
27
|
+
message = parsed.error.message;
|
|
28
|
+
if (typeof parsed.error?.code === 'string')
|
|
29
|
+
code = parsed.error.code;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// non-JSON error body — keep the raw text
|
|
33
|
+
}
|
|
34
|
+
const err = new Error(message);
|
|
35
|
+
err.status = res.status;
|
|
36
|
+
err.code = code;
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
return res.json();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* List a clip's MCP connections (default first). Resolves null when the
|
|
43
|
+
* surface is unavailable — older server without the plural family (404 route
|
|
44
|
+
* miss) or cap:clip-mcp off — so callers degrade to the plain connection rows
|
|
45
|
+
* instead of failing discovery.
|
|
46
|
+
*/
|
|
47
|
+
export async function listClipMCPConnectionSummaries(orgId, clipId) {
|
|
48
|
+
try {
|
|
49
|
+
const resp = await get(ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId));
|
|
50
|
+
return resp.data ?? [];
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
// Degrade to plain connection rows — but leave a diagnosable trace for
|
|
54
|
+
// anything that is NOT the expected "surface unavailable" shape (older
|
|
55
|
+
// server → 404 route miss), so an expired key or network fault doesn't
|
|
56
|
+
// silently read as "no MCP annotations". stderr only: stdout is the JSON
|
|
57
|
+
// contract.
|
|
58
|
+
const status = err.status;
|
|
59
|
+
if (status !== 404) {
|
|
60
|
+
console.error(`warning: MCP connection details unavailable (${err instanceof Error ? err.message : String(err)}) — showing plain connection rows`);
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Per-config read — the only surface carrying the full tools snapshot. */
|
|
66
|
+
export function getClipMCPConfigById(orgId, clipId, configId) {
|
|
67
|
+
return get(ENDPOINTS.ORG_CLIP_MCP_CONFIG_BY_ID(orgId, clipId, configId));
|
|
68
|
+
}
|
package/dist/lib/text-input.d.ts
CHANGED
|
@@ -27,4 +27,12 @@ export declare function resolveMessageText(opts: {
|
|
|
27
27
|
text?: string;
|
|
28
28
|
textFile?: string;
|
|
29
29
|
}): string | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Read the payload of a file-input flag (`--text-file`, `--args-file`) from a
|
|
32
|
+
* path or stdin (`-`). Shared so every such flag keeps the same three
|
|
33
|
+
* guarantees: stdin drains a pipe/heredoc synchronously, an interactive TTY is
|
|
34
|
+
* refused instead of hanging the dispatch, and a read failure surfaces its
|
|
35
|
+
* underlying reason under the flag's own name.
|
|
36
|
+
*/
|
|
37
|
+
export declare function readTextFileArg(flag: string, value: string): string;
|
|
30
38
|
//# sourceMappingURL=text-input.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text-input.d.ts","sourceRoot":"","sources":["../../src/lib/text-input.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,oFACsD,CAAC;AACpF,eAAO,MAAM,qBAAqB,mHAC2E,CAAC;AAC9G,eAAO,MAAM,aAAa,yDAAyD,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"text-input.d.ts","sourceRoot":"","sources":["../../src/lib/text-input.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,gBAAgB,oFACsD,CAAC;AACpF,eAAO,MAAM,qBAAqB,mHAC2E,CAAC;AAC9G,eAAO,MAAM,aAAa,yDAAyD,CAAC;AAEpF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,CAMjG;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAsBnE"}
|
package/dist/lib/text-input.js
CHANGED
|
@@ -34,28 +34,34 @@ export function resolveMessageText(opts) {
|
|
|
34
34
|
}
|
|
35
35
|
if (opts.textFile === undefined)
|
|
36
36
|
return opts.text;
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
return readTextFileArg('--text-file', opts.textFile).replace(/\r?\n$/, '');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Read the payload of a file-input flag (`--text-file`, `--args-file`) from a
|
|
41
|
+
* path or stdin (`-`). Shared so every such flag keeps the same three
|
|
42
|
+
* guarantees: stdin drains a pipe/heredoc synchronously, an interactive TTY is
|
|
43
|
+
* refused instead of hanging the dispatch, and a read failure surfaces its
|
|
44
|
+
* underlying reason under the flag's own name.
|
|
45
|
+
*/
|
|
46
|
+
export function readTextFileArg(flag, value) {
|
|
47
|
+
if (value === '-') {
|
|
39
48
|
// fd 0 = stdin; readFileSync drains a heredoc / pipe synchronously. Guard
|
|
40
49
|
// the interactive case — reading a TTY would block until EOF (which an
|
|
41
50
|
// agent never sends), hanging the dispatch until its deadline.
|
|
42
51
|
if (process.stdin.isTTY) {
|
|
43
|
-
throw new Error(
|
|
52
|
+
throw new Error(`${flag} - reads from stdin; pipe it in (e.g. a quoted heredoc \`<<'EOF'\`), don't run it interactively`);
|
|
44
53
|
}
|
|
45
|
-
|
|
54
|
+
return fs.readFileSync(0, 'utf-8');
|
|
46
55
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
throw new Error(`Cannot read --text-file ${opts.textFile}: ${reason}`, { cause: err });
|
|
58
|
-
}
|
|
56
|
+
const resolved = path.resolve(value);
|
|
57
|
+
try {
|
|
58
|
+
return fs.readFileSync(resolved, 'utf-8');
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
// Surface the underlying reason (missing / no permission / is-a-directory)
|
|
62
|
+
// so the agent can self-correct — printError renders only `.message` — and
|
|
63
|
+
// keep the original error as `cause` for anything that inspects it.
|
|
64
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
65
|
+
throw new Error(`Cannot read ${flag} ${value}: ${reason}`, { cause: err });
|
|
59
66
|
}
|
|
60
|
-
return raw.replace(/\r?\n$/, '');
|
|
61
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.54.0",
|
|
4
4
|
"description": "CLI client for Parall — universal agent & human access to Parall API",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,14 +36,14 @@
|
|
|
36
36
|
"diff": "^8.0.3",
|
|
37
37
|
"js-yaml": "^4.1.0",
|
|
38
38
|
"zod": "^4.3.6",
|
|
39
|
-
"@parall/
|
|
40
|
-
"@parall/
|
|
39
|
+
"@parall/sdk": "1.54.0",
|
|
40
|
+
"@parall/agent-core": "1.54.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/js-yaml": "^4.0.9",
|
|
44
44
|
"@types/node": "^22.0.0",
|
|
45
45
|
"typescript": "^5.7.0",
|
|
46
|
-
"@parall/agent-core": "1.
|
|
46
|
+
"@parall/agent-core": "1.54.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc",
|