ticlawk 0.1.16-dev.1 → 0.1.16-dev.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -0
- package/bin/ticlawk.mjs +121 -0
- package/package.json +1 -1
- package/src/adapters/ticlawk/api.mjs +216 -6
- package/src/adapters/ticlawk/index.mjs +221 -71
- package/src/cli/agent-commands.mjs +545 -6
- package/src/core/agent-cli-handlers.mjs +416 -3
- package/src/core/agent-home.mjs +89 -0
- package/src/core/http.mjs +114 -0
- package/src/core/reminder-ticker.mjs +70 -0
- package/src/core/runtime-contract.mjs +1 -1
- package/src/core/runtime-support.mjs +31 -29
- package/src/core/ticlawk-control.mjs +3 -3
- package/src/migrate/write-initial-memory.mjs +101 -0
- package/src/runtimes/_shared/standing-prompt.mjs +277 -76
- package/src/runtimes/claude-code/index.mjs +8 -27
- package/src/runtimes/codex/index.mjs +15 -32
- package/src/runtimes/openclaw/index.mjs +34 -13
- package/src/runtimes/openclaw/target.mjs +0 -30
- package/src/runtimes/opencode/index.mjs +13 -31
- package/src/runtimes/pi/index.mjs +13 -32
- package/ticlawk.mjs +31 -6
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
* work cleanly (matching the Slock convention).
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
import { readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
24
|
+
import { basename, extname } from 'node:path';
|
|
23
25
|
import { request as httpRequest } from 'node:http';
|
|
24
26
|
import { request as httpsRequest } from 'node:https';
|
|
25
27
|
|
|
@@ -177,11 +179,50 @@ export async function runMessageReadCommand(args) {
|
|
|
177
179
|
return exitFromStatus(res.statusCode);
|
|
178
180
|
}
|
|
179
181
|
|
|
182
|
+
export async function runTaskCreateCommand(args) {
|
|
183
|
+
const env = requireAgentEnv();
|
|
184
|
+
const target = getArg(args, 'target');
|
|
185
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
186
|
+
if (!target && !conversationId) {
|
|
187
|
+
console.error('--target or --conversation-id is required');
|
|
188
|
+
return 2;
|
|
189
|
+
}
|
|
190
|
+
const text = await readStdin();
|
|
191
|
+
if (!text || !text.trim()) {
|
|
192
|
+
console.error('task body is required on stdin');
|
|
193
|
+
console.error('Example:');
|
|
194
|
+
console.error(" ticlawk task create --target \"#frontend\" --title \"fix login\" <<'EOF'");
|
|
195
|
+
console.error(' Detailed description goes here.');
|
|
196
|
+
console.error(' EOF');
|
|
197
|
+
return 2;
|
|
198
|
+
}
|
|
199
|
+
const res = await daemonRequest({
|
|
200
|
+
method: 'POST',
|
|
201
|
+
path: '/agent/task/create',
|
|
202
|
+
headers: commonHeaders(env),
|
|
203
|
+
body: {
|
|
204
|
+
target,
|
|
205
|
+
conversation_id: conversationId,
|
|
206
|
+
text: text.replace(/\n+$/, ''),
|
|
207
|
+
title: getArg(args, 'title'),
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
printJson(res.body);
|
|
211
|
+
return exitFromStatus(res.statusCode);
|
|
212
|
+
}
|
|
213
|
+
|
|
180
214
|
export async function runTaskClaimCommand(args) {
|
|
181
215
|
const env = requireAgentEnv();
|
|
182
216
|
const messageId = getArg(args, 'message-id');
|
|
183
|
-
|
|
184
|
-
|
|
217
|
+
const number = getNumberArg(args, 'number');
|
|
218
|
+
const target = getArg(args, 'target');
|
|
219
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
220
|
+
if (!messageId && number == null) {
|
|
221
|
+
console.error('--message-id or --number is required');
|
|
222
|
+
return 2;
|
|
223
|
+
}
|
|
224
|
+
if (number != null && !target && !conversationId) {
|
|
225
|
+
console.error('--target or --conversation-id is required when claiming by --number');
|
|
185
226
|
return 2;
|
|
186
227
|
}
|
|
187
228
|
const res = await daemonRequest({
|
|
@@ -190,6 +231,9 @@ export async function runTaskClaimCommand(args) {
|
|
|
190
231
|
headers: commonHeaders(env),
|
|
191
232
|
body: {
|
|
192
233
|
source_message_id: messageId,
|
|
234
|
+
number,
|
|
235
|
+
target,
|
|
236
|
+
conversation_id: conversationId,
|
|
193
237
|
lease_seconds: getNumberArg(args, 'lease-seconds'),
|
|
194
238
|
},
|
|
195
239
|
});
|
|
@@ -201,6 +245,23 @@ export async function runTaskClaimCommand(args) {
|
|
|
201
245
|
return exitFromStatus(res.statusCode);
|
|
202
246
|
}
|
|
203
247
|
|
|
248
|
+
export async function runTaskUnclaimCommand(args) {
|
|
249
|
+
const env = requireAgentEnv();
|
|
250
|
+
const taskId = getArg(args, 'task-id');
|
|
251
|
+
if (!taskId) {
|
|
252
|
+
console.error('--task-id is required');
|
|
253
|
+
return 2;
|
|
254
|
+
}
|
|
255
|
+
const res = await daemonRequest({
|
|
256
|
+
method: 'POST',
|
|
257
|
+
path: '/agent/task/unclaim',
|
|
258
|
+
headers: commonHeaders(env),
|
|
259
|
+
body: { task_id: taskId },
|
|
260
|
+
});
|
|
261
|
+
printJson(res.body);
|
|
262
|
+
return exitFromStatus(res.statusCode);
|
|
263
|
+
}
|
|
264
|
+
|
|
204
265
|
export async function runTaskUpdateCommand(args) {
|
|
205
266
|
const env = requireAgentEnv();
|
|
206
267
|
const taskId = getArg(args, 'task-id');
|
|
@@ -209,6 +270,10 @@ export async function runTaskUpdateCommand(args) {
|
|
|
209
270
|
console.error('--task-id and --status are required');
|
|
210
271
|
return 2;
|
|
211
272
|
}
|
|
273
|
+
if (!['todo', 'in_progress', 'in_review', 'done', 'canceled'].includes(status)) {
|
|
274
|
+
console.error(`--status must be one of: todo, in_progress, in_review, done, canceled`);
|
|
275
|
+
return 2;
|
|
276
|
+
}
|
|
212
277
|
const res = await daemonRequest({
|
|
213
278
|
method: 'POST',
|
|
214
279
|
path: '/agent/task/update',
|
|
@@ -219,6 +284,37 @@ export async function runTaskUpdateCommand(args) {
|
|
|
219
284
|
return exitFromStatus(res.statusCode);
|
|
220
285
|
}
|
|
221
286
|
|
|
287
|
+
export async function runMessageReactCommand(args) {
|
|
288
|
+
const env = requireAgentEnv();
|
|
289
|
+
const messageId = getArg(args, 'message-id');
|
|
290
|
+
const emoji = getArg(args, 'emoji');
|
|
291
|
+
const remove = !!args.remove;
|
|
292
|
+
if (!messageId) {
|
|
293
|
+
console.error('--message-id is required');
|
|
294
|
+
return 2;
|
|
295
|
+
}
|
|
296
|
+
if (!emoji) {
|
|
297
|
+
console.error('--emoji is required');
|
|
298
|
+
return 2;
|
|
299
|
+
}
|
|
300
|
+
if (emoji.length > 16 || /\s/.test(emoji)) {
|
|
301
|
+
console.error('--emoji must be a single emoji, ≤16 chars, no whitespace');
|
|
302
|
+
return 2;
|
|
303
|
+
}
|
|
304
|
+
const res = await daemonRequest({
|
|
305
|
+
method: 'POST',
|
|
306
|
+
path: '/agent/message/react',
|
|
307
|
+
headers: commonHeaders(env),
|
|
308
|
+
body: { message_id: messageId, emoji, remove },
|
|
309
|
+
});
|
|
310
|
+
printJson(res.body);
|
|
311
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
312
|
+
const verb = remove ? 'removed from' : 'added to';
|
|
313
|
+
console.error(`Reaction ${emoji} ${verb} message ${messageId.slice(0, 8)}.`);
|
|
314
|
+
}
|
|
315
|
+
return exitFromStatus(res.statusCode);
|
|
316
|
+
}
|
|
317
|
+
|
|
222
318
|
export async function runTaskListCommand(args) {
|
|
223
319
|
const env = requireAgentEnv();
|
|
224
320
|
const params = new URLSearchParams();
|
|
@@ -235,6 +331,410 @@ export async function runTaskListCommand(args) {
|
|
|
235
331
|
return exitFromStatus(res.statusCode);
|
|
236
332
|
}
|
|
237
333
|
|
|
334
|
+
// ── Reminders ──
|
|
335
|
+
|
|
336
|
+
function resolveFireAt(args) {
|
|
337
|
+
const explicit = getArg(args, 'fire-at');
|
|
338
|
+
if (explicit) return explicit;
|
|
339
|
+
const inSeconds = getNumberArg(args, 'in-seconds');
|
|
340
|
+
if (inSeconds != null) {
|
|
341
|
+
return new Date(Date.now() + inSeconds * 1000).toISOString();
|
|
342
|
+
}
|
|
343
|
+
const inMinutes = getNumberArg(args, 'in-minutes');
|
|
344
|
+
if (inMinutes != null) {
|
|
345
|
+
return new Date(Date.now() + inMinutes * 60 * 1000).toISOString();
|
|
346
|
+
}
|
|
347
|
+
return null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function runReminderScheduleCommand(args) {
|
|
351
|
+
const env = requireAgentEnv();
|
|
352
|
+
const title = getArg(args, 'title');
|
|
353
|
+
if (!title) {
|
|
354
|
+
console.error('--title is required');
|
|
355
|
+
return 2;
|
|
356
|
+
}
|
|
357
|
+
const fireAt = resolveFireAt(args);
|
|
358
|
+
if (!fireAt) {
|
|
359
|
+
console.error('one of --fire-at <ISO>, --in-seconds N, --in-minutes N is required');
|
|
360
|
+
return 2;
|
|
361
|
+
}
|
|
362
|
+
const target = getArg(args, 'target');
|
|
363
|
+
const anchorConversationId = getArg(args, 'anchor-conversation-id');
|
|
364
|
+
const anchorMessageId = getArg(args, 'anchor-message-id');
|
|
365
|
+
if (!target && !anchorConversationId) {
|
|
366
|
+
console.error('--target or --anchor-conversation-id is required');
|
|
367
|
+
return 2;
|
|
368
|
+
}
|
|
369
|
+
const res = await daemonRequest({
|
|
370
|
+
method: 'POST',
|
|
371
|
+
path: '/agent/reminder/schedule',
|
|
372
|
+
headers: commonHeaders(env),
|
|
373
|
+
body: {
|
|
374
|
+
title,
|
|
375
|
+
fire_at: fireAt,
|
|
376
|
+
target,
|
|
377
|
+
anchor_conversation_id: anchorConversationId,
|
|
378
|
+
anchor_message_id: anchorMessageId,
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
printJson(res.body);
|
|
382
|
+
return exitFromStatus(res.statusCode);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export async function runReminderListCommand(args) {
|
|
386
|
+
const env = requireAgentEnv();
|
|
387
|
+
const params = new URLSearchParams();
|
|
388
|
+
const status = getArg(args, 'status');
|
|
389
|
+
if (status) params.set('status', status);
|
|
390
|
+
const res = await daemonRequest({
|
|
391
|
+
method: 'GET',
|
|
392
|
+
path: `/agent/reminder/list?${params}`,
|
|
393
|
+
headers: commonHeaders(env),
|
|
394
|
+
});
|
|
395
|
+
printJson(res.body);
|
|
396
|
+
return exitFromStatus(res.statusCode);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export async function runReminderSnoozeCommand(args) {
|
|
400
|
+
const env = requireAgentEnv();
|
|
401
|
+
const reminderId = getArg(args, 'reminder-id') || args._?.[2];
|
|
402
|
+
const fireAt = resolveFireAt(args);
|
|
403
|
+
if (!reminderId) {
|
|
404
|
+
console.error('--reminder-id (or positional) is required');
|
|
405
|
+
return 2;
|
|
406
|
+
}
|
|
407
|
+
if (!fireAt) {
|
|
408
|
+
console.error('one of --fire-at <ISO>, --in-seconds N, --in-minutes N is required');
|
|
409
|
+
return 2;
|
|
410
|
+
}
|
|
411
|
+
const res = await daemonRequest({
|
|
412
|
+
method: 'POST',
|
|
413
|
+
path: '/agent/reminder/snooze',
|
|
414
|
+
headers: commonHeaders(env),
|
|
415
|
+
body: { reminder_id: reminderId, fire_at: fireAt },
|
|
416
|
+
});
|
|
417
|
+
printJson(res.body);
|
|
418
|
+
return exitFromStatus(res.statusCode);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export async function runReminderUpdateCommand(args) {
|
|
422
|
+
const env = requireAgentEnv();
|
|
423
|
+
const reminderId = getArg(args, 'reminder-id') || args._?.[2];
|
|
424
|
+
const title = getArg(args, 'title');
|
|
425
|
+
const fireAt = resolveFireAt(args);
|
|
426
|
+
if (!reminderId) {
|
|
427
|
+
console.error('--reminder-id (or positional) is required');
|
|
428
|
+
return 2;
|
|
429
|
+
}
|
|
430
|
+
if (!title && !fireAt) {
|
|
431
|
+
console.error('at least one of --title or --fire-at / --in-seconds / --in-minutes is required');
|
|
432
|
+
return 2;
|
|
433
|
+
}
|
|
434
|
+
const res = await daemonRequest({
|
|
435
|
+
method: 'POST',
|
|
436
|
+
path: '/agent/reminder/update',
|
|
437
|
+
headers: commonHeaders(env),
|
|
438
|
+
body: { reminder_id: reminderId, title, fire_at: fireAt },
|
|
439
|
+
});
|
|
440
|
+
printJson(res.body);
|
|
441
|
+
return exitFromStatus(res.statusCode);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export async function runReminderCancelCommand(args) {
|
|
445
|
+
const env = requireAgentEnv();
|
|
446
|
+
const reminderId = getArg(args, 'reminder-id') || args._?.[2];
|
|
447
|
+
if (!reminderId) {
|
|
448
|
+
console.error('--reminder-id (or positional) is required');
|
|
449
|
+
return 2;
|
|
450
|
+
}
|
|
451
|
+
const res = await daemonRequest({
|
|
452
|
+
method: 'POST',
|
|
453
|
+
path: '/agent/reminder/cancel',
|
|
454
|
+
headers: commonHeaders(env),
|
|
455
|
+
body: { reminder_id: reminderId },
|
|
456
|
+
});
|
|
457
|
+
printJson(res.body);
|
|
458
|
+
return exitFromStatus(res.statusCode);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export async function runReminderLogCommand(args) {
|
|
462
|
+
const env = requireAgentEnv();
|
|
463
|
+
const reminderId = getArg(args, 'reminder-id') || args._?.[2];
|
|
464
|
+
if (!reminderId) {
|
|
465
|
+
console.error('--reminder-id (or positional) is required');
|
|
466
|
+
return 2;
|
|
467
|
+
}
|
|
468
|
+
const params = new URLSearchParams();
|
|
469
|
+
params.set('reminder_id', reminderId);
|
|
470
|
+
const res = await daemonRequest({
|
|
471
|
+
method: 'GET',
|
|
472
|
+
path: `/agent/reminder/log?${params}`,
|
|
473
|
+
headers: commonHeaders(env),
|
|
474
|
+
});
|
|
475
|
+
printJson(res.body);
|
|
476
|
+
return exitFromStatus(res.statusCode);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export async function runMessageCheckCommand(args) {
|
|
480
|
+
const env = requireAgentEnv();
|
|
481
|
+
const params = new URLSearchParams();
|
|
482
|
+
const target = getArg(args, 'target');
|
|
483
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
484
|
+
if (target) params.set('target', target);
|
|
485
|
+
if (conversationId) params.set('conversation_id', conversationId);
|
|
486
|
+
const res = await daemonRequest({
|
|
487
|
+
method: 'GET',
|
|
488
|
+
path: `/agent/message/check?${params}`,
|
|
489
|
+
headers: commonHeaders(env),
|
|
490
|
+
});
|
|
491
|
+
printJson(res.body);
|
|
492
|
+
return exitFromStatus(res.statusCode);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
export async function runMessageSearchCommand(args) {
|
|
496
|
+
const env = requireAgentEnv();
|
|
497
|
+
const query = getArg(args, 'query') || getArg(args, 'q');
|
|
498
|
+
if (!query) {
|
|
499
|
+
console.error('--query (or --q) is required');
|
|
500
|
+
return 2;
|
|
501
|
+
}
|
|
502
|
+
const params = new URLSearchParams();
|
|
503
|
+
params.set('q', query);
|
|
504
|
+
const target = getArg(args, 'target');
|
|
505
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
506
|
+
const limit = getArg(args, 'limit');
|
|
507
|
+
if (target) params.set('target', target);
|
|
508
|
+
if (conversationId) params.set('conversation_id', conversationId);
|
|
509
|
+
if (limit) params.set('limit', limit);
|
|
510
|
+
const res = await daemonRequest({
|
|
511
|
+
method: 'GET',
|
|
512
|
+
path: `/agent/message/search?${params}`,
|
|
513
|
+
headers: commonHeaders(env),
|
|
514
|
+
});
|
|
515
|
+
printJson(res.body);
|
|
516
|
+
return exitFromStatus(res.statusCode);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export async function runProfileShowCommand(args) {
|
|
520
|
+
const env = requireAgentEnv();
|
|
521
|
+
const positional = args._?.[2]; // ticlawk profile show @handle
|
|
522
|
+
const explicit = getArg(args, 'id') || getArg(args, 'handle');
|
|
523
|
+
let target = explicit || positional || env.agentId;
|
|
524
|
+
if (typeof target === 'string' && target.startsWith('@')) target = target.slice(1);
|
|
525
|
+
const params = new URLSearchParams();
|
|
526
|
+
if (target) params.set('id', target);
|
|
527
|
+
const res = await daemonRequest({
|
|
528
|
+
method: 'GET',
|
|
529
|
+
path: `/agent/profile/show?${params}`,
|
|
530
|
+
headers: commonHeaders(env),
|
|
531
|
+
});
|
|
532
|
+
printJson(res.body);
|
|
533
|
+
return exitFromStatus(res.statusCode);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
export async function runProfileUpdateCommand(args) {
|
|
537
|
+
const env = requireAgentEnv();
|
|
538
|
+
const displayName = getArg(args, 'display-name');
|
|
539
|
+
const description = getArg(args, 'description');
|
|
540
|
+
const avatarFile = getArg(args, 'avatar-file');
|
|
541
|
+
if (displayName == null && description == null && avatarFile == null) {
|
|
542
|
+
console.error('at least one of --display-name, --description, --avatar-file is required');
|
|
543
|
+
return 2;
|
|
544
|
+
}
|
|
545
|
+
let avatarUrl = null;
|
|
546
|
+
if (avatarFile) {
|
|
547
|
+
// Re-use the attachment upload path then set the public_url as the avatar.
|
|
548
|
+
const upload = await uploadFileViaDaemon(env, avatarFile);
|
|
549
|
+
if (!upload.ok) {
|
|
550
|
+
console.error(`avatar upload failed: ${upload.error}`);
|
|
551
|
+
return 1;
|
|
552
|
+
}
|
|
553
|
+
avatarUrl = upload.publicUrl;
|
|
554
|
+
}
|
|
555
|
+
const res = await daemonRequest({
|
|
556
|
+
method: 'POST',
|
|
557
|
+
path: '/agent/profile/update',
|
|
558
|
+
headers: commonHeaders(env),
|
|
559
|
+
body: {
|
|
560
|
+
display_name: displayName,
|
|
561
|
+
description,
|
|
562
|
+
avatar_url: avatarUrl,
|
|
563
|
+
},
|
|
564
|
+
});
|
|
565
|
+
printJson(res.body);
|
|
566
|
+
return exitFromStatus(res.statusCode);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function uploadFileViaDaemon(env, filePath) {
|
|
570
|
+
let stat;
|
|
571
|
+
try { stat = statSync(filePath); } catch (err) {
|
|
572
|
+
return { ok: false, error: `cannot stat ${filePath}: ${err.message}` };
|
|
573
|
+
}
|
|
574
|
+
if (!stat.isFile()) return { ok: false, error: `${filePath} is not a regular file` };
|
|
575
|
+
const data = readFileSync(filePath);
|
|
576
|
+
const contentType = inferContentType(filePath);
|
|
577
|
+
const res = await daemonRequest({
|
|
578
|
+
method: 'POST',
|
|
579
|
+
path: '/agent/attachment/upload',
|
|
580
|
+
headers: commonHeaders(env),
|
|
581
|
+
body: {
|
|
582
|
+
filename: basename(filePath),
|
|
583
|
+
content_type: contentType,
|
|
584
|
+
data_base64: data.toString('base64'),
|
|
585
|
+
},
|
|
586
|
+
});
|
|
587
|
+
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
588
|
+
return { ok: false, error: res.body?.error || `HTTP ${res.statusCode}` };
|
|
589
|
+
}
|
|
590
|
+
return { ok: true, assetId: res.body?.asset?.asset_id, publicUrl: res.body?.public_url };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function inferContentType(filePath) {
|
|
594
|
+
const ext = extname(filePath).toLowerCase();
|
|
595
|
+
switch (ext) {
|
|
596
|
+
case '.png': return 'image/png';
|
|
597
|
+
case '.jpg': case '.jpeg': return 'image/jpeg';
|
|
598
|
+
case '.gif': return 'image/gif';
|
|
599
|
+
case '.webp': return 'image/webp';
|
|
600
|
+
case '.pdf': return 'application/pdf';
|
|
601
|
+
case '.txt': return 'text/plain';
|
|
602
|
+
case '.md': return 'text/markdown';
|
|
603
|
+
case '.json': return 'application/json';
|
|
604
|
+
default: return 'application/octet-stream';
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
export async function runAttachmentUploadCommand(args) {
|
|
609
|
+
const env = requireAgentEnv();
|
|
610
|
+
const file = args._?.[2];
|
|
611
|
+
if (!file) {
|
|
612
|
+
console.error('usage: ticlawk attachment upload <file>');
|
|
613
|
+
return 2;
|
|
614
|
+
}
|
|
615
|
+
const upload = await uploadFileViaDaemon(env, file);
|
|
616
|
+
printJson(upload);
|
|
617
|
+
return upload.ok ? 0 : 1;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
export async function runAttachmentViewCommand(args) {
|
|
621
|
+
const env = requireAgentEnv();
|
|
622
|
+
const assetId = args._?.[2];
|
|
623
|
+
if (!assetId) {
|
|
624
|
+
console.error('usage: ticlawk attachment view <asset_id> [--out <path>]');
|
|
625
|
+
return 2;
|
|
626
|
+
}
|
|
627
|
+
const params = new URLSearchParams();
|
|
628
|
+
params.set('asset_id', assetId);
|
|
629
|
+
const res = await daemonRequest({
|
|
630
|
+
method: 'GET',
|
|
631
|
+
path: `/agent/attachment/view?${params}`,
|
|
632
|
+
headers: commonHeaders(env),
|
|
633
|
+
});
|
|
634
|
+
printJson(res.body);
|
|
635
|
+
const out = getArg(args, 'out');
|
|
636
|
+
if (out && res.body?.public_url) {
|
|
637
|
+
// Download via fetch and write to disk.
|
|
638
|
+
const fetched = await fetch(res.body.public_url);
|
|
639
|
+
if (fetched.ok) {
|
|
640
|
+
const buf = Buffer.from(await fetched.arrayBuffer());
|
|
641
|
+
writeFileSync(out, buf);
|
|
642
|
+
console.error(`wrote ${buf.length} bytes to ${out}`);
|
|
643
|
+
} else {
|
|
644
|
+
console.error(`download failed: HTTP ${fetched.status}`);
|
|
645
|
+
return 1;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
return exitFromStatus(res.statusCode);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
export async function runGroupCreateCommand(args) {
|
|
652
|
+
const env = requireAgentEnv();
|
|
653
|
+
const name = getArg(args, 'name');
|
|
654
|
+
if (!name) {
|
|
655
|
+
console.error('--name is required');
|
|
656
|
+
return 2;
|
|
657
|
+
}
|
|
658
|
+
const description = getArg(args, 'description');
|
|
659
|
+
const memberArgs = args.member;
|
|
660
|
+
const memberAgentIds = Array.isArray(memberArgs)
|
|
661
|
+
? memberArgs
|
|
662
|
+
: memberArgs
|
|
663
|
+
? [memberArgs]
|
|
664
|
+
: [];
|
|
665
|
+
const res = await daemonRequest({
|
|
666
|
+
method: 'POST',
|
|
667
|
+
path: '/agent/group/create',
|
|
668
|
+
headers: commonHeaders(env),
|
|
669
|
+
body: {
|
|
670
|
+
name,
|
|
671
|
+
description,
|
|
672
|
+
member_agent_ids: memberAgentIds.map((s) => String(s).trim()).filter(Boolean),
|
|
673
|
+
},
|
|
674
|
+
});
|
|
675
|
+
printJson(res.body);
|
|
676
|
+
return exitFromStatus(res.statusCode);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function runGroupMembersAddCommand(args) {
|
|
680
|
+
const env = requireAgentEnv();
|
|
681
|
+
const target = getArg(args, 'target');
|
|
682
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
683
|
+
if (!target && !conversationId) {
|
|
684
|
+
console.error('--target or --conversation-id is required');
|
|
685
|
+
return 2;
|
|
686
|
+
}
|
|
687
|
+
const memberArgs = args.add;
|
|
688
|
+
const agentIds = Array.isArray(memberArgs)
|
|
689
|
+
? memberArgs
|
|
690
|
+
: memberArgs
|
|
691
|
+
? [memberArgs]
|
|
692
|
+
: [];
|
|
693
|
+
if (agentIds.length === 0) {
|
|
694
|
+
console.error('--add <agent-id> required (may repeat)');
|
|
695
|
+
return 2;
|
|
696
|
+
}
|
|
697
|
+
const res = await daemonRequest({
|
|
698
|
+
method: 'POST',
|
|
699
|
+
path: '/agent/group/members/add',
|
|
700
|
+
headers: commonHeaders(env),
|
|
701
|
+
body: {
|
|
702
|
+
target,
|
|
703
|
+
conversation_id: conversationId,
|
|
704
|
+
agent_ids: agentIds.map((s) => String(s).trim()).filter(Boolean),
|
|
705
|
+
},
|
|
706
|
+
});
|
|
707
|
+
printJson(res.body);
|
|
708
|
+
return exitFromStatus(res.statusCode);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
export async function runGroupMembersRemoveCommand(args) {
|
|
712
|
+
const env = requireAgentEnv();
|
|
713
|
+
const target = getArg(args, 'target');
|
|
714
|
+
const conversationId = getArg(args, 'conversation-id');
|
|
715
|
+
const removeArg = getArg(args, 'remove');
|
|
716
|
+
if (!target && !conversationId) {
|
|
717
|
+
console.error('--target or --conversation-id is required');
|
|
718
|
+
return 2;
|
|
719
|
+
}
|
|
720
|
+
if (!removeArg) {
|
|
721
|
+
console.error('--remove <agent-id> required');
|
|
722
|
+
return 2;
|
|
723
|
+
}
|
|
724
|
+
const res = await daemonRequest({
|
|
725
|
+
method: 'POST',
|
|
726
|
+
path: '/agent/group/members/remove',
|
|
727
|
+
headers: commonHeaders(env),
|
|
728
|
+
body: {
|
|
729
|
+
target,
|
|
730
|
+
conversation_id: conversationId,
|
|
731
|
+
agent_id: removeArg,
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
printJson(res.body);
|
|
735
|
+
return exitFromStatus(res.statusCode);
|
|
736
|
+
}
|
|
737
|
+
|
|
238
738
|
export async function runGroupMembersCommand(args) {
|
|
239
739
|
const env = requireAgentEnv();
|
|
240
740
|
const target = getArg(args, 'target');
|
|
@@ -269,7 +769,7 @@ export async function runServerInfoCommand(args) {
|
|
|
269
769
|
}
|
|
270
770
|
|
|
271
771
|
export const AGENT_COMMAND_HELP = {
|
|
272
|
-
message: `ticlawk message <send|read>
|
|
772
|
+
message: `ticlawk message <send|read|check|search|react>
|
|
273
773
|
ticlawk message send --target "<target>" [--seen-up-to-seq N] [--reply-to <msg-id>]
|
|
274
774
|
Body is read from stdin (use <<'EOF' ... EOF for multiline).
|
|
275
775
|
Targets:
|
|
@@ -277,13 +777,52 @@ export const AGENT_COMMAND_HELP = {
|
|
|
277
777
|
#<group> group conversation
|
|
278
778
|
#<group>:<msgid> thread under a top-level message in that group
|
|
279
779
|
ticlawk message read --target "<target>" [--around <msg-id>] [--before-seq N] [--limit N]
|
|
780
|
+
ticlawk message check [--target "<target>"]
|
|
781
|
+
Non-blocking poll for new/unprocessed messages.
|
|
782
|
+
ticlawk message search --query <q> [--target "<target>"] [--limit N]
|
|
783
|
+
Text search (ilike) across messages visible to you.
|
|
784
|
+
ticlawk message react --message-id <id> --emoji <e> [--remove]
|
|
785
|
+
Use sparingly: prefer acknowledgement/follow-up signals like 👀. Do not
|
|
786
|
+
auto-react to every merge, deploy, or task completion with celebratory emoji.
|
|
787
|
+
`,
|
|
788
|
+
profile: `ticlawk profile <show|update>
|
|
789
|
+
ticlawk profile show [@handle | --id <agent-id>]
|
|
790
|
+
ticlawk profile update [--display-name X] [--description Y] [--avatar-file path]
|
|
791
|
+
`,
|
|
792
|
+
attachment: `ticlawk attachment <upload|view>
|
|
793
|
+
ticlawk attachment upload <file>
|
|
794
|
+
ticlawk attachment view <asset-id> [--out <path>]
|
|
795
|
+
`,
|
|
796
|
+
reminder: `ticlawk reminder <schedule|list|snooze|update|cancel|log>
|
|
797
|
+
ticlawk reminder schedule --title <t> (--fire-at <iso> | --in-seconds N | --in-minutes N) (--target "<target>" | --anchor-conversation-id <id>) [--anchor-message-id <id>]
|
|
798
|
+
ticlawk reminder list [--status active|fired|canceled]
|
|
799
|
+
ticlawk reminder snooze <reminder-id> (--fire-at <iso> | --in-seconds N | --in-minutes N)
|
|
800
|
+
ticlawk reminder update <reminder-id> [--title <t>] [--fire-at <iso>]
|
|
801
|
+
ticlawk reminder cancel <reminder-id>
|
|
802
|
+
ticlawk reminder log <reminder-id>
|
|
803
|
+
|
|
804
|
+
Use reminders for follow-up that depends on future state you cannot resolve
|
|
805
|
+
now. A reminder fires by posting a system message into the anchor
|
|
806
|
+
conversation and waking the owner agent via an explicit delivery.
|
|
280
807
|
`,
|
|
281
|
-
task: `ticlawk task <claim|update|list>
|
|
808
|
+
task: `ticlawk task <create|claim|unclaim|update|list>
|
|
809
|
+
ticlawk task create --target "<target>" [--title <t>]
|
|
810
|
+
Body is read from stdin. Creates a brand-new message published as a todo
|
|
811
|
+
task. To own it, follow up with \`ticlawk task claim --message-id <id>\`.
|
|
282
812
|
ticlawk task claim --message-id <id> [--lease-seconds N]
|
|
283
|
-
ticlawk task
|
|
813
|
+
ticlawk task claim --number <N> --target "<target>" [--lease-seconds N]
|
|
814
|
+
ticlawk task unclaim --task-id <id>
|
|
815
|
+
ticlawk task update --task-id <id> --status <todo|in_progress|in_review|done|canceled>
|
|
284
816
|
ticlawk task list [--target <target>]
|
|
285
817
|
`,
|
|
286
|
-
group: `ticlawk group members
|
|
818
|
+
group: `ticlawk group <create|members>
|
|
819
|
+
ticlawk group create --name <n> [--description <d>] [--member <agent-id> ...]
|
|
820
|
+
Agent self-creates a new group. The agent is added as a member; the
|
|
821
|
+
conversation owner is set to the user that owns this agent. Other
|
|
822
|
+
member agents must belong to the same user (RLS enforces).
|
|
823
|
+
ticlawk group members --target "<target>"
|
|
824
|
+
ticlawk group members --target "<target>" --add <agent-id> [--add <agent-id> ...]
|
|
825
|
+
ticlawk group members --target "<target>" --remove <agent-id>
|
|
287
826
|
`,
|
|
288
827
|
server: `ticlawk server info [--refresh]
|
|
289
828
|
`,
|