@parall/cli 1.36.0 → 1.37.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 +1 @@
1
- {"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/commands/dm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiCpC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,QAwDlD"}
1
+ {"version":3,"file":"dm.d.ts","sourceRoot":"","sources":["../../src/commands/dm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCpC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,QA+DlD"}
@@ -1,5 +1,6 @@
1
1
  import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
2
2
  import { printJson, printError, printRefHint, stripPrllScheme } from '../lib/output.js';
3
+ import { NO_BODY_ERROR, resolveMessageText, TEXT_FILE_OPTION_DESC, TEXT_OPTION_DESC, } from '../lib/text-input.js';
3
4
  import { uploadFile } from '../lib/upload.js';
4
5
  /**
5
6
  * Resolve a name-or-id argument to a user ID.
@@ -25,7 +26,8 @@ export function registerDMCommands(program) {
25
26
  .command('dm')
26
27
  .description('Send a direct message to a user by name or ID (auto-creates chat if needed)')
27
28
  .argument('<nameOrId>', 'Target user display name or ID (usr_...)')
28
- .option('--text <text>', 'Message text')
29
+ .option('--text <text>', TEXT_OPTION_DESC)
30
+ .option('--text-file <path>', TEXT_FILE_OPTION_DESC)
29
31
  .option('--file <path>', 'Upload and attach a local file')
30
32
  .option('--attachment <id>', 'Attach an existing attachment (att_xxx or prll://att_xxx)')
31
33
  .option('--no-reply', 'Hint that the recipient should not reply')
@@ -38,7 +40,7 @@ export function registerDMCommands(program) {
38
40
  printError(new Error('--file and --attachment are mutually exclusive'));
39
41
  return;
40
42
  }
41
- const text = opts.text || '';
43
+ const text = resolveMessageText(opts) || '';
42
44
  let attachmentIds;
43
45
  if (opts.file) {
44
46
  const result = await uploadFile(client, orgId, opts.file);
@@ -48,7 +50,7 @@ export function registerDMCommands(program) {
48
50
  attachmentIds = [stripPrllScheme(opts.attachment)];
49
51
  }
50
52
  if (!text && !attachmentIds) {
51
- printError(new Error('Provide --text, --file, or --attachment'));
53
+ printError(new Error(NO_BODY_ERROR));
52
54
  return;
53
55
  }
54
56
  const req = {
@@ -1 +1 @@
1
- {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QAqJvD"}
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/commands/messages.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWzC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QA0JvD"}
@@ -1,6 +1,7 @@
1
1
  import { ApiError } from '@parall/sdk';
2
2
  import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
3
3
  import { printError, printJson, printRefHint, stripPrllScheme } from '../lib/output.js';
4
+ import { NO_BODY_ERROR, resolveMessageText, TEXT_FILE_OPTION_DESC, TEXT_OPTION_DESC, } from '../lib/text-input.js';
4
5
  import { uploadFile } from '../lib/upload.js';
5
6
  export function registerMessageCommands(program) {
6
7
  const messages = program.command('messages').description('Manage messages');
@@ -11,6 +12,7 @@ export function registerMessageCommands(program) {
11
12
  .option('--limit <n>', 'Maximum number of messages to return', '20')
12
13
  .option('--before <cursor>', 'Cursor for messages before')
13
14
  .option('--after <cursor>', 'Cursor for messages after')
15
+ .option('--since <date>', 'Only messages at/after this time (RFC3339 or YYYY-MM-DD)')
14
16
  .option('--thread-root-id <id>', 'Filter by thread root message ID')
15
17
  .option('--top-level', 'Only return top-level messages')
16
18
  .action(async (chatId, opts) => {
@@ -21,6 +23,8 @@ export function registerMessageCommands(program) {
21
23
  params.before = stripPrllScheme(opts.before);
22
24
  if (opts.after !== undefined)
23
25
  params.after = stripPrllScheme(opts.after);
26
+ if (opts.since !== undefined)
27
+ params.since = opts.since;
24
28
  if (opts.threadRootId !== undefined)
25
29
  params.thread_root_id = stripPrllScheme(opts.threadRootId);
26
30
  if (opts.topLevel)
@@ -50,7 +54,8 @@ export function registerMessageCommands(program) {
50
54
  .command('send')
51
55
  .description('Send a message to a chat (text, file, or both)')
52
56
  .argument('[chatId]', 'Chat ID (defaults to PRLL_CHAT_ID if set)')
53
- .option('--text <text>', 'Message text')
57
+ .option('--text <text>', TEXT_OPTION_DESC)
58
+ .option('--text-file <path>', TEXT_FILE_OPTION_DESC)
54
59
  .option('--file <path>', 'Upload and attach a local file')
55
60
  .option('--attachment <id>', 'Attach an existing attachment (att_xxx or prll://att_xxx)')
56
61
  .option('--thread-root-id <id>', 'Reply to a thread')
@@ -67,14 +72,14 @@ export function registerMessageCommands(program) {
67
72
  // `messages send` targets a chat. A user id here is an addressing
68
73
  // mistake — point at `dm` instead of letting the server reject it.
69
74
  if (chatId.startsWith('usr_')) {
70
- printError(new ApiError(400, `prll://${chatId} is a user, not a chat. To message a user use: parall dm prll://${chatId} --text "..."`, 'INVALID_TARGET'));
75
+ printError(new ApiError(400, `prll://${chatId} is a user, not a chat. To message a user use: parall dm prll://${chatId} --text-file -`, 'INVALID_TARGET'));
71
76
  return;
72
77
  }
73
78
  if (opts.file && opts.attachment) {
74
79
  printError(new Error('--file and --attachment are mutually exclusive'));
75
80
  return;
76
81
  }
77
- const text = opts.text || '';
82
+ const text = resolveMessageText(opts) || '';
78
83
  let attachmentIds;
79
84
  if (opts.file) {
80
85
  const result = await uploadFile(client, orgId, opts.file);
@@ -84,7 +89,7 @@ export function registerMessageCommands(program) {
84
89
  attachmentIds = [stripPrllScheme(opts.attachment)];
85
90
  }
86
91
  if (!text && !attachmentIds) {
87
- printError(new Error('Provide --text, --file, or --attachment'));
92
+ printError(new Error(NO_BODY_ERROR));
88
93
  return;
89
94
  }
90
95
  const req = {
@@ -1,3 +1,10 @@
1
1
  import { Command } from 'commander';
2
+ /** Build the SDK ref-graph params from the positional URI + CLI flags. */
3
+ export declare function buildRefGraphParams(uri: string, opts: {
4
+ depth?: string;
5
+ }): {
6
+ uri: string;
7
+ depth?: number;
8
+ };
2
9
  export declare function registerRefCommands(program: Command): void;
3
10
  //# sourceMappingURL=refs.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/commands/refs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QA+CnD"}
1
+ {"version":3,"file":"refs.d.ts","sourceRoot":"","sources":["../../src/commands/refs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,0EAA0E;AAC1E,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAOjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,QA+DnD"}
@@ -1,5 +1,15 @@
1
1
  import { resolveCredentials } from '../lib/client.js';
2
- import { printJson, printError } from '../lib/output.js';
2
+ import { ensurePrllScheme, parsePositiveInt, printError, printJson } from '../lib/output.js';
3
+ /** Build the SDK ref-graph params from the positional URI + CLI flags. */
4
+ export function buildRefGraphParams(uri, opts) {
5
+ const params = { uri: ensurePrllScheme(uri) };
6
+ // Drop a non-positive-integer --depth (NaN / 1.5 / -2) so the server applies
7
+ // its default; the server clamps the valid range (1–4) regardless.
8
+ const depth = parsePositiveInt(opts.depth);
9
+ if (depth !== undefined)
10
+ params.depth = depth;
11
+ return params;
12
+ }
3
13
  export function registerRefCommands(program) {
4
14
  const refs = program.command('refs').description('Reference resolution and backlinks');
5
15
  refs
@@ -34,6 +44,20 @@ export function registerRefCommands(program) {
34
44
  printError(err);
35
45
  }
36
46
  });
47
+ refs
48
+ .command('graph <uri>')
49
+ .description('Multi-hop traversal of the prll:// reference graph around an entity-level URI (no path/anchor)')
50
+ .option('--depth <n>', 'Hops to traverse outward (server clamps to 1–4)', '2')
51
+ .action(async (uri, opts) => {
52
+ try {
53
+ const { client, orgId } = resolveCredentials();
54
+ const result = await client.getRefsGraph(orgId, buildRefGraphParams(uri, opts));
55
+ printJson(result);
56
+ }
57
+ catch (err) {
58
+ printError(err);
59
+ }
60
+ });
37
61
  refs
38
62
  .command('check')
39
63
  .description('Check for broken references in the organization (admin only)')
@@ -0,0 +1,20 @@
1
+ import type { SearchParams } from '@parall/sdk';
2
+ import { Command } from 'commander';
3
+ /**
4
+ * Normalize a `--types m,t,w` flag into the server's CSV of canonical entity
5
+ * names (`message,task,wiki`). Unknown tokens pass through unchanged so the
6
+ * server can reject/ignore them. Returns undefined for an empty/absent flag so
7
+ * the server applies its default (search all three).
8
+ */
9
+ export declare function normalizeSearchTypes(raw?: string): string | undefined;
10
+ export interface SearchOpts {
11
+ types?: string;
12
+ channel?: string;
13
+ limit?: string;
14
+ wikiType?: string;
15
+ since?: string;
16
+ }
17
+ /** Build the SDK search params from the positional query + CLI flags. */
18
+ export declare function buildSearchParams(query: string, opts: SearchOpts): SearchParams;
19
+ export declare function registerSearchCommands(program: Command): void;
20
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASrE;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,yEAAyE;AACzE,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,YAAY,CAY/E;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,QA4BtD"}
@@ -0,0 +1,70 @@
1
+ import { resolveCredentials } from '../lib/client.js';
2
+ import { parsePositiveInt, printError, printJson, stripPrllScheme } from '../lib/output.js';
3
+ // Short aliases agents pass via --types map to the server's entity names.
4
+ const TYPE_ALIASES = {
5
+ m: 'message',
6
+ message: 'message',
7
+ messages: 'message',
8
+ t: 'task',
9
+ task: 'task',
10
+ tasks: 'task',
11
+ w: 'wiki',
12
+ wiki: 'wiki',
13
+ };
14
+ /**
15
+ * Normalize a `--types m,t,w` flag into the server's CSV of canonical entity
16
+ * names (`message,task,wiki`). Unknown tokens pass through unchanged so the
17
+ * server can reject/ignore them. Returns undefined for an empty/absent flag so
18
+ * the server applies its default (search all three).
19
+ */
20
+ export function normalizeSearchTypes(raw) {
21
+ if (!raw)
22
+ return undefined;
23
+ const mapped = raw
24
+ .split(',')
25
+ .map((s) => s.trim().toLowerCase())
26
+ .filter(Boolean)
27
+ .map((s) => TYPE_ALIASES[s] ?? s);
28
+ const unique = [...new Set(mapped)];
29
+ return unique.length > 0 ? unique.join(',') : undefined;
30
+ }
31
+ /** Build the SDK search params from the positional query + CLI flags. */
32
+ export function buildSearchParams(query, opts) {
33
+ const params = { q: query };
34
+ const types = normalizeSearchTypes(opts.types);
35
+ if (types)
36
+ params.types = types;
37
+ if (opts.channel !== undefined)
38
+ params.chat_id = stripPrllScheme(opts.channel);
39
+ // Only forward a clean positive integer; a bad --limit falls through to the
40
+ // server default rather than sending NaN/1.5/-2.
41
+ const limit = parsePositiveInt(opts.limit);
42
+ if (limit !== undefined)
43
+ params.limit = limit;
44
+ if (opts.wikiType !== undefined)
45
+ params.wiki_type = opts.wikiType;
46
+ if (opts.since !== undefined)
47
+ params.since = opts.since;
48
+ return params;
49
+ }
50
+ export function registerSearchCommands(program) {
51
+ program
52
+ .command('search')
53
+ .description('Unified semantic search across messages, tasks, and wiki (org-scoped)')
54
+ .argument('<query>', 'Search query')
55
+ .option('--types <list>', 'Comma-separated entity types: m[essage], t[ask], w[iki] (default: all)')
56
+ .option('--channel <chatId>', 'Narrow MESSAGE results to one chat — tasks/wiki unaffected (prll://cht_… or cht_…)')
57
+ .option('--limit <n>', 'Max results per entity type (server caps at 50)')
58
+ .option('--wiki-type <type>', 'Narrow wiki results to a frontmatter document type')
59
+ .option('--since <date>', 'Only messages/tasks at or after this time (RFC3339 or YYYY-MM-DD); not applied to wiki')
60
+ .action(async (query, opts) => {
61
+ try {
62
+ const { client, orgId } = resolveCredentials();
63
+ const result = await client.search(orgId, buildSearchParams(query, opts));
64
+ printJson(result);
65
+ }
66
+ catch (err) {
67
+ printError(err);
68
+ }
69
+ });
70
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/commands/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QAqTpD"}
1
+ {"version":3,"file":"tasks.d.ts","sourceRoot":"","sources":["../../src/commands/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,QA6UpD"}
@@ -268,10 +268,20 @@ export function registerTaskCommands(program) {
268
268
  .command('watch')
269
269
  .description('Watch a task (subscribe to comment notifications)')
270
270
  .argument('<taskId>', 'Task ID')
271
- .action(async (taskId) => {
271
+ .option('--user-id <userId>', 'Subscribe another user instead of yourself (requires you to be the task creator, assignee, project lead, or an org owner/admin)')
272
+ .action(async (taskId, opts) => {
272
273
  try {
273
274
  const { client, orgId } = resolveCredentials();
274
- await client.watchTask(orgId, stripPrllScheme(taskId));
275
+ const normalizedTaskId = stripPrllScheme(taskId);
276
+ const targetUserId = opts.userId ? stripPrllScheme(opts.userId) : undefined;
277
+ // A self-targeted --user-id is equivalent to plain self-watch; route it
278
+ // back to /watch so the behaviour matches `parall tasks watch` exactly.
279
+ if (targetUserId && targetUserId !== (await client.getMe()).id) {
280
+ await client.subscribeTaskMember(orgId, normalizedTaskId, targetUserId);
281
+ }
282
+ else {
283
+ await client.watchTask(orgId, normalizedTaskId);
284
+ }
275
285
  printJson({ ok: true });
276
286
  }
277
287
  catch (err) {
@@ -282,10 +292,20 @@ export function registerTaskCommands(program) {
282
292
  .command('unwatch')
283
293
  .description('Unwatch a task (stop receiving comment notifications)')
284
294
  .argument('<taskId>', 'Task ID')
285
- .action(async (taskId) => {
295
+ .option('--user-id <userId>', 'Unsubscribe another user instead of yourself (requires you to be the task creator, assignee, project lead, or an org owner/admin)')
296
+ .action(async (taskId, opts) => {
286
297
  try {
287
298
  const { client, orgId } = resolveCredentials();
288
- await client.unwatchTask(orgId, stripPrllScheme(taskId));
299
+ const normalizedTaskId = stripPrllScheme(taskId);
300
+ const targetUserId = opts.userId ? stripPrllScheme(opts.userId) : undefined;
301
+ // A self-targeted --user-id is equivalent to plain self-unwatch; route it
302
+ // back to /watch so the behaviour matches `parall tasks unwatch` exactly.
303
+ if (targetUserId && targetUserId !== (await client.getMe()).id) {
304
+ await client.unsubscribeTaskMember(orgId, normalizedTaskId, targetUserId);
305
+ }
306
+ else {
307
+ await client.unwatchTask(orgId, normalizedTaskId);
308
+ }
289
309
  printJson({ ok: true });
290
310
  }
291
311
  catch (err) {
package/dist/index.js CHANGED
@@ -16,6 +16,7 @@ import { registerWikiCommands } from './commands/wiki.js';
16
16
  import { registerMcpCommands } from './commands/mcp.js';
17
17
  import { registerNoReplyCommands } from './commands/no-reply.js';
18
18
  import { registerRefCommands } from './commands/refs.js';
19
+ import { registerSearchCommands } from './commands/search.js';
19
20
  import { registerFileCommands } from './commands/files.js';
20
21
  import { registerMachineCommands } from './commands/machines.js';
21
22
  import { registerClipCommands } from './commands/clip.js';
@@ -41,6 +42,7 @@ registerWikiCommands(program);
41
42
  registerMcpCommands(program);
42
43
  registerNoReplyCommands(program);
43
44
  registerRefCommands(program);
45
+ registerSearchCommands(program);
44
46
  registerFileCommands(program);
45
47
  registerMachineCommands(program);
46
48
  registerClipCommands(program);
@@ -9,5 +9,17 @@ export declare function printRefHint(entityId: string, action?: string): void;
9
9
  * Allows CLI commands to accept both raw IDs (`tsk_abc`) and URIs (`prll://tsk_abc`).
10
10
  */
11
11
  export declare function stripPrllScheme(idOrUri: string): string;
12
+ /**
13
+ * Ensure a value carries the `prll://` scheme — the inverse of stripPrllScheme.
14
+ * Lets URI-consuming commands accept both `tsk_abc` and `prll://tsk_abc`.
15
+ */
16
+ export declare function ensurePrllScheme(idOrUri: string): string;
17
+ /**
18
+ * Parse a CLI numeric flag (string) into a positive integer, or undefined when
19
+ * absent or not a clean positive integer (NaN / fractional / <= 0). Callers omit
20
+ * the param on undefined so the server applies its own default + bounds rather
21
+ * than receiving a malformed value.
22
+ */
23
+ export declare function parsePositiveInt(raw: string | undefined): number | undefined;
12
24
  export declare function printError(err: unknown): never;
13
25
  //# sourceMappingURL=output.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE7C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAY,GAAG,IAAI,CAEvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAwC9C"}
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAEA,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAE7C;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,SAAY,GAAG,IAAI,CAEvE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAW5E;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,OAAO,GAAG,KAAK,CAwC9C"}
@@ -16,6 +16,33 @@ export function printRefHint(entityId, action = 'Created') {
16
16
  export function stripPrllScheme(idOrUri) {
17
17
  return idOrUri.startsWith('prll://') ? idOrUri.slice(7) : idOrUri;
18
18
  }
19
+ /**
20
+ * Ensure a value carries the `prll://` scheme — the inverse of stripPrllScheme.
21
+ * Lets URI-consuming commands accept both `tsk_abc` and `prll://tsk_abc`.
22
+ */
23
+ export function ensurePrllScheme(idOrUri) {
24
+ return idOrUri.startsWith('prll://') ? idOrUri : `prll://${idOrUri}`;
25
+ }
26
+ /**
27
+ * Parse a CLI numeric flag (string) into a positive integer, or undefined when
28
+ * absent or not a clean positive integer (NaN / fractional / <= 0). Callers omit
29
+ * the param on undefined so the server applies its own default + bounds rather
30
+ * than receiving a malformed value.
31
+ */
32
+ export function parsePositiveInt(raw) {
33
+ if (raw === undefined)
34
+ return undefined;
35
+ // Plain decimal digits only — reject the scientific/hex/float forms Number()
36
+ // would otherwise coerce (1e2, 0x10, 1.5) so "clean positive integer" holds.
37
+ const t = raw.trim();
38
+ if (!/^\d+$/.test(t))
39
+ return undefined;
40
+ // The regex alone is NOT enough: a digit-only string of hundreds of digits
41
+ // overflows Number() to Infinity, which Number.isInteger rejects — so this
42
+ // guard still does real work (keeps `limit=Infinity` off the wire).
43
+ const n = Number(t);
44
+ return Number.isInteger(n) && n > 0 ? n : undefined;
45
+ }
19
46
  export function printError(err) {
20
47
  if (err instanceof ApiError) {
21
48
  // Faithful, parseable line: the server's real message + machine anchors.
@@ -0,0 +1,30 @@
1
+ export declare const TEXT_OPTION_DESC = "Message text (shell-safe only for short literals with no $, backtick, or quote)";
2
+ export declare const TEXT_FILE_OPTION_DESC = "Read message text from a file ('-' = stdin) \u2014 shell-safe channel for content with $, backticks, or quotes";
3
+ export declare const NO_BODY_ERROR = "Provide --text, --text-file, --file, or --attachment";
4
+ /**
5
+ * Resolve message body text from the mutually-exclusive `--text` / `--text-file`
6
+ * options shared by `messages send` and `dm`.
7
+ *
8
+ * Agents build these commands with an LLM and run them through a shell, so any
9
+ * body passed as a double-quoted `--text "..."` argument is mangled by shell
10
+ * expansion *before* the CLI ever sees it: `$1,000` becomes `,000`, `$USER`
11
+ * expands, and `` `cmd` `` / `$(cmd)` execute. Single quotes are no better —
12
+ * they break on the apostrophes that fill natural-language replies. `--text-file`
13
+ * sidesteps the shell entirely: the body comes from a file the agent wrote (raw
14
+ * bytes, no shell) or from stdin via a quoted heredoc (`<<'EOF'`, which disables
15
+ * all expansion). `-` means stdin.
16
+ *
17
+ * A single trailing newline — the structural newline a heredoc or editor appends
18
+ * — is stripped so a one-line reply doesn't arrive with a dangling blank line;
19
+ * this mirrors how `$(...)` command substitution trims trailing newlines, and
20
+ * leading/interior whitespace is preserved untouched.
21
+ *
22
+ * Returns `undefined` when neither option is set (preserving the old
23
+ * `opts.text`-or-nothing contract). Throws on conflicting options or an
24
+ * unreadable file; callers already funnel that into `printError`.
25
+ */
26
+ export declare function resolveMessageText(opts: {
27
+ text?: string;
28
+ textFile?: string;
29
+ }): string | undefined;
30
+ //# sourceMappingURL=text-input.d.ts.map
@@ -0,0 +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,CA8BjG"}
@@ -0,0 +1,61 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ // Shared option help + validation copy for the message-body flags, so
4
+ // `messages send` and `dm` stay in lockstep. commander's --help prints these
5
+ // verbatim (no markdown), so keep them backtick-free.
6
+ export const TEXT_OPTION_DESC = 'Message text (shell-safe only for short literals with no $, backtick, or quote)';
7
+ export const TEXT_FILE_OPTION_DESC = "Read message text from a file ('-' = stdin) — shell-safe channel for content with $, backticks, or quotes";
8
+ export const NO_BODY_ERROR = 'Provide --text, --text-file, --file, or --attachment';
9
+ /**
10
+ * Resolve message body text from the mutually-exclusive `--text` / `--text-file`
11
+ * options shared by `messages send` and `dm`.
12
+ *
13
+ * Agents build these commands with an LLM and run them through a shell, so any
14
+ * body passed as a double-quoted `--text "..."` argument is mangled by shell
15
+ * expansion *before* the CLI ever sees it: `$1,000` becomes `,000`, `$USER`
16
+ * expands, and `` `cmd` `` / `$(cmd)` execute. Single quotes are no better —
17
+ * they break on the apostrophes that fill natural-language replies. `--text-file`
18
+ * sidesteps the shell entirely: the body comes from a file the agent wrote (raw
19
+ * bytes, no shell) or from stdin via a quoted heredoc (`<<'EOF'`, which disables
20
+ * all expansion). `-` means stdin.
21
+ *
22
+ * A single trailing newline — the structural newline a heredoc or editor appends
23
+ * — is stripped so a one-line reply doesn't arrive with a dangling blank line;
24
+ * this mirrors how `$(...)` command substitution trims trailing newlines, and
25
+ * leading/interior whitespace is preserved untouched.
26
+ *
27
+ * Returns `undefined` when neither option is set (preserving the old
28
+ * `opts.text`-or-nothing contract). Throws on conflicting options or an
29
+ * unreadable file; callers already funnel that into `printError`.
30
+ */
31
+ export function resolveMessageText(opts) {
32
+ if (opts.text !== undefined && opts.textFile !== undefined) {
33
+ throw new Error('--text and --text-file are mutually exclusive');
34
+ }
35
+ if (opts.textFile === undefined)
36
+ return opts.text;
37
+ let raw;
38
+ if (opts.textFile === '-') {
39
+ // fd 0 = stdin; readFileSync drains a heredoc / pipe synchronously. Guard
40
+ // the interactive case — reading a TTY would block until EOF (which an
41
+ // agent never sends), hanging the dispatch until its deadline.
42
+ if (process.stdin.isTTY) {
43
+ throw new Error("--text-file - reads the body from stdin; pipe it in (e.g. a quoted heredoc `<<'EOF'`), don't run it interactively");
44
+ }
45
+ raw = fs.readFileSync(0, 'utf-8');
46
+ }
47
+ else {
48
+ const resolved = path.resolve(opts.textFile);
49
+ try {
50
+ raw = fs.readFileSync(resolved, 'utf-8');
51
+ }
52
+ catch (err) {
53
+ // Surface the underlying reason (missing / no permission / is-a-directory)
54
+ // so the agent can self-correct — printError renders only `.message` — and
55
+ // keep the original error as `cause` for anything that inspects it.
56
+ const reason = err instanceof Error ? err.message : String(err);
57
+ throw new Error(`Cannot read --text-file ${opts.textFile}: ${reason}`, { cause: err });
58
+ }
59
+ }
60
+ return raw.replace(/\r?\n$/, '');
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/cli",
3
- "version": "1.36.0",
3
+ "version": "1.37.0",
4
4
  "description": "CLI client for Parall — universal agent & human access to Parall API",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,13 +36,13 @@
36
36
  "diff": "^8.0.3",
37
37
  "js-yaml": "^4.1.0",
38
38
  "zod": "^4.3.6",
39
- "@parall/sdk": "1.36.0"
39
+ "@parall/sdk": "1.37.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/js-yaml": "^4.0.9",
43
43
  "@types/node": "^22.0.0",
44
44
  "typescript": "^5.7.0",
45
- "@parall/agent-core": "1.36.0"
45
+ "@parall/agent-core": "1.37.0"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "tsc",