@masslessai/push-todo 4.1.8 → 4.1.9

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.
Files changed (3) hide show
  1. package/lib/api.js +140 -0
  2. package/lib/cli.js +100 -0
  3. package/package.json +1 -1
package/lib/api.js CHANGED
@@ -381,4 +381,144 @@ export async function learnVocabulary(todoId, keywords) {
381
381
  return response.json();
382
382
  }
383
383
 
384
+ // ==================== Phase 3: Extended Skill CLI ====================
385
+
386
+ /**
387
+ * Report structured progress for a running task.
388
+ * Called by the agent during execution to push activity updates to Supabase.
389
+ *
390
+ * @param {string} todoId - UUID of the task
391
+ * @param {Object} options - Progress details
392
+ * @param {string} options.phase - Current phase (e.g., "testing", "implementing")
393
+ * @param {string} [options.detail] - Human-readable detail
394
+ * @returns {Promise<Object>}
395
+ */
396
+ export async function reportProgress(todoId, { phase, detail }) {
397
+ const response = await apiRequest('update-task-execution', {
398
+ method: 'PATCH',
399
+ body: JSON.stringify({
400
+ todoId,
401
+ event: {
402
+ type: 'progress',
403
+ timestamp: new Date().toISOString(),
404
+ summary: detail ? `${phase}: ${detail}` : phase,
405
+ phase
406
+ }
407
+ })
408
+ });
409
+
410
+ if (!response.ok) {
411
+ const text = await response.text();
412
+ throw new Error(`Failed to report progress: ${text}`);
413
+ }
414
+
415
+ return response.json();
416
+ }
417
+
418
+ /**
419
+ * Update execution status/phase for a running task.
420
+ *
421
+ * @param {string} todoId - UUID of the task
422
+ * @param {string} status - New phase label (e.g., "implementing", "testing", "reviewing")
423
+ * @returns {Promise<Object>}
424
+ */
425
+ export async function updateStatus(todoId, status) {
426
+ const response = await apiRequest('update-task-execution', {
427
+ method: 'PATCH',
428
+ body: JSON.stringify({
429
+ todoId,
430
+ event: {
431
+ type: 'progress',
432
+ timestamp: new Date().toISOString(),
433
+ summary: `Phase: ${status}`
434
+ }
435
+ })
436
+ });
437
+
438
+ if (!response.ok) {
439
+ const text = await response.text();
440
+ throw new Error(`Failed to update status: ${text}`);
441
+ }
442
+
443
+ return response.json();
444
+ }
445
+
446
+ /**
447
+ * Check for messages from the human for a running task.
448
+ * Returns pending messages or empty array.
449
+ *
450
+ * @param {string} todoId - UUID of the task
451
+ * @returns {Promise<Object[]>} Array of pending messages
452
+ */
453
+ export async function checkMessages(todoId) {
454
+ const params = new URLSearchParams({ todo_id: todoId, direction: 'to_agent' });
455
+ const response = await apiRequest(`task-messages?${params}`, {
456
+ method: 'GET'
457
+ });
458
+
459
+ if (!response.ok) {
460
+ // 404 = no task_messages table/function yet — return empty gracefully
461
+ if (response.status === 404) return [];
462
+ const text = await response.text();
463
+ throw new Error(`Failed to check messages: ${text}`);
464
+ }
465
+
466
+ const result = await response.json();
467
+ return result.messages || [];
468
+ }
469
+
470
+ /**
471
+ * Request input from the human for a running task.
472
+ * Posts a question and polls until the human responds or timeout.
473
+ *
474
+ * @param {string} todoId - UUID of the task
475
+ * @param {string} question - Question to ask the human
476
+ * @param {number} [timeoutMs=300000] - Timeout in ms (default 5 min)
477
+ * @returns {Promise<string|null>} Human's response or null if timeout
478
+ */
479
+ export async function requestInput(todoId, question, timeoutMs = 300000) {
480
+ // Post the question
481
+ const postResponse = await apiRequest('task-messages', {
482
+ method: 'POST',
483
+ body: JSON.stringify({
484
+ todo_id: todoId,
485
+ direction: 'to_human',
486
+ message: question,
487
+ type: 'input_request'
488
+ })
489
+ });
490
+
491
+ if (!postResponse.ok) {
492
+ // 404 = task-messages endpoint not deployed yet
493
+ if (postResponse.status === 404) {
494
+ console.error('Message system not available yet. Use [push-confirm] pattern instead.');
495
+ return null;
496
+ }
497
+ const text = await postResponse.text();
498
+ throw new Error(`Failed to request input: ${text}`);
499
+ }
500
+
501
+ const { message_id } = await postResponse.json();
502
+
503
+ // Poll for response
504
+ const startTime = Date.now();
505
+ const pollInterval = 5000; // 5s
506
+
507
+ while (Date.now() - startTime < timeoutMs) {
508
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
509
+
510
+ const checkResponse = await apiRequest(`task-messages?message_id=${message_id}&direction=to_agent`, {
511
+ method: 'GET'
512
+ });
513
+
514
+ if (checkResponse.ok) {
515
+ const result = await checkResponse.json();
516
+ const reply = (result.messages || []).find(m => m.in_reply_to === message_id);
517
+ if (reply) return reply.message;
518
+ }
519
+ }
520
+
521
+ return null; // Timeout
522
+ }
523
+
384
524
  export { API_BASE };
package/lib/cli.js CHANGED
@@ -68,6 +68,13 @@ ${bold('OPTIONS:')}
68
68
  --view-screenshot <idx> Open screenshot for viewing (index or filename)
69
69
  --learn-vocabulary <uuid> Contribute vocabulary for a task
70
70
  --keywords <terms> Comma-separated vocabulary terms (with --learn-vocabulary)
71
+ --report-progress <uuid> Report progress for a running task
72
+ --phase <name> Phase name (with --report-progress)
73
+ --detail <text> Detail text (with --report-progress)
74
+ --update-status <uuid> Update execution phase for a running task
75
+ --check-messages <uuid> Check for messages from the human
76
+ --request-input <uuid> Request input from the human (blocking)
77
+ --question <text> Question to ask (with --request-input)
71
78
  --set-batch-size <N> Set max tasks for batch queue (1-20)
72
79
  --daemon-status Show daemon status
73
80
  --daemon-start Start daemon manually
@@ -175,6 +182,14 @@ const options = {
175
182
  'create-todo': { type: 'string' },
176
183
  'notify': { type: 'string' },
177
184
  'queue-execution': { type: 'string' },
185
+ // Skill CLI options (Phase 3)
186
+ 'report-progress': { type: 'string' },
187
+ 'phase': { type: 'string' },
188
+ 'detail': { type: 'string' },
189
+ 'update-status': { type: 'string' },
190
+ 'check-messages': { type: 'string' },
191
+ 'request-input': { type: 'string' },
192
+ 'question': { type: 'string' },
178
193
  };
179
194
 
180
195
  /**
@@ -512,6 +527,91 @@ export async function run(argv) {
512
527
  return;
513
528
  }
514
529
 
530
+ // Handle --report-progress (report structured progress for a running task)
531
+ if (values['report-progress']) {
532
+ try {
533
+ const result = await api.reportProgress(values['report-progress'], {
534
+ phase: values.phase || 'progress',
535
+ detail: values.detail || undefined,
536
+ });
537
+ if (values.json) {
538
+ console.log(JSON.stringify(result, null, 2));
539
+ } else {
540
+ console.log(green('Progress reported.'));
541
+ }
542
+ } catch (error) {
543
+ console.error(red(`Failed to report progress: ${error.message}`));
544
+ process.exit(1);
545
+ }
546
+ return;
547
+ }
548
+
549
+ // Handle --update-status (update execution phase for a running task)
550
+ if (values['update-status']) {
551
+ const status = positionals[0] || values.phase;
552
+ if (!status) {
553
+ console.error(red('Status required. Usage: --update-status <uuid> <status>'));
554
+ process.exit(1);
555
+ }
556
+ try {
557
+ const result = await api.updateStatus(values['update-status'], status);
558
+ if (values.json) {
559
+ console.log(JSON.stringify(result, null, 2));
560
+ } else {
561
+ console.log(green(`Status updated to: ${status}`));
562
+ }
563
+ } catch (error) {
564
+ console.error(red(`Failed to update status: ${error.message}`));
565
+ process.exit(1);
566
+ }
567
+ return;
568
+ }
569
+
570
+ // Handle --check-messages (check for messages from the human)
571
+ if (values['check-messages']) {
572
+ try {
573
+ const messages = await api.checkMessages(values['check-messages']);
574
+ if (values.json) {
575
+ console.log(JSON.stringify(messages, null, 2));
576
+ } else if (messages.length === 0) {
577
+ console.log(dim('No pending messages.'));
578
+ } else {
579
+ for (const msg of messages) {
580
+ console.log(`[${msg.created_at || 'unknown'}] ${msg.message}`);
581
+ }
582
+ }
583
+ } catch (error) {
584
+ console.error(red(`Failed to check messages: ${error.message}`));
585
+ process.exit(1);
586
+ }
587
+ return;
588
+ }
589
+
590
+ // Handle --request-input (ask the human a question, poll for response)
591
+ if (values['request-input']) {
592
+ const question = values.question || positionals.slice(0).join(' ');
593
+ if (!question) {
594
+ console.error(red('--question required with --request-input'));
595
+ console.error('Usage: --request-input <uuid> --question "What should I do?"');
596
+ process.exit(1);
597
+ }
598
+ try {
599
+ console.log(dim('Waiting for human response (timeout: 5 min)...'));
600
+ const reply = await api.requestInput(values['request-input'], question);
601
+ if (values.json) {
602
+ console.log(JSON.stringify({ reply }, null, 2));
603
+ } else if (reply) {
604
+ console.log(green('Response:'), reply);
605
+ } else {
606
+ console.log(dim('No response received (timeout).'));
607
+ }
608
+ } catch (error) {
609
+ console.error(red(`Failed to request input: ${error.message}`));
610
+ process.exit(1);
611
+ }
612
+ return;
613
+ }
614
+
515
615
  // Auto-start daemon on every command (self-healing behavior)
516
616
  ensureDaemonRunning();
517
617
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@masslessai/push-todo",
3
- "version": "4.1.8",
3
+ "version": "4.1.9",
4
4
  "description": "Voice tasks from Push iOS app for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {