@parall/cli 1.36.1 → 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.
- package/dist/commands/dm.d.ts.map +1 -1
- package/dist/commands/dm.js +5 -3
- package/dist/commands/messages.d.ts.map +1 -1
- package/dist/commands/messages.js +6 -4
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +24 -4
- package/dist/lib/text-input.d.ts +30 -0
- package/dist/lib/text-input.d.ts.map +1 -0
- package/dist/lib/text-input.js +61 -0
- package/package.json +3 -3
|
@@ -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;
|
|
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"}
|
package/dist/commands/dm.js
CHANGED
|
@@ -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>',
|
|
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
|
|
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(
|
|
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;
|
|
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');
|
|
@@ -53,7 +54,8 @@ export function registerMessageCommands(program) {
|
|
|
53
54
|
.command('send')
|
|
54
55
|
.description('Send a message to a chat (text, file, or both)')
|
|
55
56
|
.argument('[chatId]', 'Chat ID (defaults to PRLL_CHAT_ID if set)')
|
|
56
|
-
.option('--text <text>',
|
|
57
|
+
.option('--text <text>', TEXT_OPTION_DESC)
|
|
58
|
+
.option('--text-file <path>', TEXT_FILE_OPTION_DESC)
|
|
57
59
|
.option('--file <path>', 'Upload and attach a local file')
|
|
58
60
|
.option('--attachment <id>', 'Attach an existing attachment (att_xxx or prll://att_xxx)')
|
|
59
61
|
.option('--thread-root-id <id>', 'Reply to a thread')
|
|
@@ -70,14 +72,14 @@ export function registerMessageCommands(program) {
|
|
|
70
72
|
// `messages send` targets a chat. A user id here is an addressing
|
|
71
73
|
// mistake — point at `dm` instead of letting the server reject it.
|
|
72
74
|
if (chatId.startsWith('usr_')) {
|
|
73
|
-
printError(new ApiError(400, `prll://${chatId} is a user, not a chat. To message a user use: parall dm prll://${chatId} --text
|
|
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'));
|
|
74
76
|
return;
|
|
75
77
|
}
|
|
76
78
|
if (opts.file && opts.attachment) {
|
|
77
79
|
printError(new Error('--file and --attachment are mutually exclusive'));
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
80
|
-
const text = opts
|
|
82
|
+
const text = resolveMessageText(opts) || '';
|
|
81
83
|
let attachmentIds;
|
|
82
84
|
if (opts.file) {
|
|
83
85
|
const result = await uploadFile(client, orgId, opts.file);
|
|
@@ -87,7 +89,7 @@ export function registerMessageCommands(program) {
|
|
|
87
89
|
attachmentIds = [stripPrllScheme(opts.attachment)];
|
|
88
90
|
}
|
|
89
91
|
if (!text && !attachmentIds) {
|
|
90
|
-
printError(new Error(
|
|
92
|
+
printError(new Error(NO_BODY_ERROR));
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
const req = {
|
|
@@ -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,
|
|
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"}
|
package/dist/commands/tasks.js
CHANGED
|
@@ -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
|
-
.
|
|
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
|
-
|
|
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
|
-
.
|
|
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
|
-
|
|
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) {
|
|
@@ -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.
|
|
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.
|
|
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.
|
|
45
|
+
"@parall/agent-core": "1.37.0"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc",
|