chainlesschain 0.45.81 → 0.47.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.
Files changed (71) hide show
  1. package/README.md +10 -0
  2. package/bin/chainlesschain.js +0 -0
  3. package/package.json +1 -1
  4. package/src/assets/web-panel/.build-hash +1 -1
  5. package/src/assets/web-panel/assets/{Analytics-C1AnPdMx.js → Analytics-DgypYeUB.js} +2 -2
  6. package/src/assets/web-panel/assets/AppLayout-Bzf3mSZI.js +1 -0
  7. package/src/assets/web-panel/assets/AppLayout-DQyDwGut.css +1 -0
  8. package/src/assets/web-panel/assets/{Backup-D31iZX3l.js → Backup-Ba9UybpT.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DiXJ3TuK.js → Chat-BwXskT21.js} +1 -1
  10. package/src/assets/web-panel/assets/Cowork-CXuhlHew.css +1 -0
  11. package/src/assets/web-panel/assets/Cowork-UmOe7qvE.js +7 -0
  12. package/src/assets/web-panel/assets/{Cron-DBt1ueXh.js → Cron-JHS-rc-4.js} +2 -2
  13. package/src/assets/web-panel/assets/{Dashboard-HPh9FcPt.js → Dashboard-B95cMCO7.js} +2 -2
  14. package/src/assets/web-panel/assets/Dashboard-CKeMmCoT.css +1 -0
  15. package/src/assets/web-panel/assets/{Git-hwQ1oZHj.js → Git-CSYO0_zk.js} +2 -2
  16. package/src/assets/web-panel/assets/{Logs-4D9p6PRM.js → Logs-Hxw_K0km.js} +2 -2
  17. package/src/assets/web-panel/assets/{McpTools-CyAUjbbs.js → McpTools-DIE75TrB.js} +2 -2
  18. package/src/assets/web-panel/assets/{Memory-BMqOR7S-.js → Memory-C4KVnLlp.js} +2 -2
  19. package/src/assets/web-panel/assets/{Notes-Cmas8i4E.js → Notes-DuzrHMAk.js} +2 -2
  20. package/src/assets/web-panel/assets/{Organization-DnSa58Tl.js → Organization-DTq6uF82.js} +4 -4
  21. package/src/assets/web-panel/assets/{P2P-BxksIBWs.js → P2P-C0hjlhsR.js} +2 -2
  22. package/src/assets/web-panel/assets/{Permissions-Bq5Qn2s3.js → Permissions-Ec0NH-xC.js} +4 -4
  23. package/src/assets/web-panel/assets/{Projects-B7EM0uPg.js → Projects-U8D0asCS.js} +2 -2
  24. package/src/assets/web-panel/assets/{Providers-DAwgG5KV.js → Providers-BngtTLvJ.js} +2 -2
  25. package/src/assets/web-panel/assets/{RssFeed-HSZoRXvS.js → RssFeed-B9NbwCKM.js} +3 -3
  26. package/src/assets/web-panel/assets/{Security-Cz17qBny.js → Security-BL5Rkr1T.js} +3 -3
  27. package/src/assets/web-panel/assets/{Services-D2EsLq-v.js → Services-D4MJzLld.js} +2 -2
  28. package/src/assets/web-panel/assets/{Skills-C9v-f3vZ.js → Skills-CQTOMDwF.js} +1 -1
  29. package/src/assets/web-panel/assets/{Tasks-yMEcU0n7.js → Tasks-DepbJMnL.js} +1 -1
  30. package/src/assets/web-panel/assets/{Templates-l7SvlKuB.js → Templates-C24PVZPu.js} +1 -1
  31. package/src/assets/web-panel/assets/{Wallet-BHWhLWn9.js → Wallet-PQoSpN_P.js} +3 -3
  32. package/src/assets/web-panel/assets/{WebAuthn-kWhFYaUK.js → WebAuthn-BcuyQ4Lr.js} +4 -4
  33. package/src/assets/web-panel/assets/WorkflowEditor-C-SvXbHW.js +1 -0
  34. package/src/assets/web-panel/assets/WorkflowEditor-D5bX6woe.css +1 -0
  35. package/src/assets/web-panel/assets/{antd-D6h4fDFf.js → antd-DEjZPGMj.js} +82 -82
  36. package/src/assets/web-panel/assets/index-CwvzTTw_.js +2 -0
  37. package/src/assets/web-panel/assets/{markdown-BZsB-Dsv.js → markdown-CusdXFxb.js} +1 -1
  38. package/src/assets/web-panel/index.html +2 -2
  39. package/src/commands/cowork.js +867 -0
  40. package/src/gateways/ws/action-protocol.js +182 -2
  41. package/src/gateways/ws/message-dispatcher.js +5 -0
  42. package/src/gateways/ws/ws-server.js +21 -0
  43. package/src/lib/cowork-cron.js +474 -0
  44. package/src/lib/cowork-evomap-adapter.js +121 -0
  45. package/src/lib/cowork-learning.js +438 -0
  46. package/src/lib/cowork-mcp-tools.js +182 -0
  47. package/src/lib/cowork-observe-html.js +108 -0
  48. package/src/lib/cowork-observe.js +160 -0
  49. package/src/lib/cowork-share.js +322 -0
  50. package/src/lib/cowork-task-runner.js +317 -3
  51. package/src/lib/cowork-task-templates.js +101 -13
  52. package/src/lib/cowork-template-marketplace.js +205 -0
  53. package/src/lib/cowork-workflow.js +571 -0
  54. package/src/lib/provider-options.js +133 -0
  55. package/src/lib/skill-loader.js +65 -0
  56. package/src/lib/sub-agent-context.js +54 -2
  57. package/src/lib/sub-agent-profiles.js +164 -0
  58. package/src/lib/todo-manager.js +108 -0
  59. package/src/lib/turn-context.js +95 -0
  60. package/src/lib/web-fetch.js +224 -0
  61. package/src/lib/workflow-expr.js +318 -0
  62. package/src/repl/agent-repl.js +4 -0
  63. package/src/runtime/agent-core.js +135 -3
  64. package/src/runtime/coding-agent-contract-shared.cjs +131 -0
  65. package/src/runtime/coding-agent-policy.cjs +30 -0
  66. package/src/assets/web-panel/assets/AppLayout-YdvJBMHH.js +0 -1
  67. package/src/assets/web-panel/assets/AppLayout-cxfKLu-m.css +0 -1
  68. package/src/assets/web-panel/assets/Cowork-BnrHWwZw.js +0 -7
  69. package/src/assets/web-panel/assets/Cowork-CcSoS3eX.css +0 -1
  70. package/src/assets/web-panel/assets/Dashboard-BS-tzGNj.css +0 -1
  71. package/src/assets/web-panel/assets/index-ByUk2Wmr.js +0 -2
@@ -290,6 +290,861 @@ export function registerCoworkCommand(program) {
290
290
  }
291
291
  });
292
292
 
293
+ // cowork template — marketplace subcommands
294
+ const tpl = cowork
295
+ .command("template")
296
+ .description(
297
+ "Cowork template marketplace (search/install/publish via EvoMap)",
298
+ );
299
+
300
+ tpl
301
+ .command("search [query]")
302
+ .description("Search for Cowork templates on the EvoMap hub")
303
+ .option("--hub <url>", "EvoMap hub URL")
304
+ .option("--limit <n>", "Max results", "20")
305
+ .option("--json", "Output as JSON")
306
+ .action(async (query, options) => {
307
+ const { searchTemplatesInHub } =
308
+ await import("../lib/cowork-evomap-adapter.js");
309
+ try {
310
+ const results = await searchTemplatesInHub(query || "", {
311
+ hubUrl: options.hub,
312
+ limit: parseInt(options.limit, 10) || 20,
313
+ });
314
+ if (options.json) {
315
+ console.log(JSON.stringify(results, null, 2));
316
+ return;
317
+ }
318
+ if (results.length === 0) {
319
+ logger.info("No templates found.");
320
+ return;
321
+ }
322
+ logger.log(chalk.bold(`\n${results.length} template(s) found:\n`));
323
+ for (const g of results) {
324
+ logger.log(
325
+ ` ${chalk.cyan(g.id)} ${chalk.gray("v" + (g.version || "?"))} by ${g.author || "unknown"}`,
326
+ );
327
+ if (g.description) {
328
+ logger.log(chalk.gray(` ${g.description.slice(0, 100)}`));
329
+ }
330
+ logger.log(
331
+ chalk.gray(
332
+ ` downloads: ${g.downloads || 0} rating: ${g.rating || "N/A"}`,
333
+ ),
334
+ );
335
+ }
336
+ logger.log("");
337
+ } catch (err) {
338
+ logger.error(`Search failed: ${err.message}`);
339
+ process.exit(1);
340
+ }
341
+ });
342
+
343
+ tpl
344
+ .command("install <geneId>")
345
+ .description("Install a Cowork template from the EvoMap hub")
346
+ .option("--hub <url>", "EvoMap hub URL")
347
+ .option("--require-signed", "Reject genes without an Ed25519 signature")
348
+ .option(
349
+ "--trust <did>",
350
+ "Only accept genes signed by this DID (repeatable)",
351
+ (value, prev) => (prev ? [...prev, value] : [value]),
352
+ )
353
+ .action(async (geneId, options) => {
354
+ const { installTemplateFromHub } =
355
+ await import("../lib/cowork-evomap-adapter.js");
356
+ try {
357
+ const template = await installTemplateFromHub(process.cwd(), geneId, {
358
+ hubUrl: options.hub,
359
+ requireSigned: !!options.requireSigned,
360
+ trustedDids: options.trust || null,
361
+ });
362
+ logger.log(
363
+ chalk.green(
364
+ `✓ Installed template '${template.id}' (${template.name})`,
365
+ ),
366
+ );
367
+ } catch (err) {
368
+ logger.error(`Install failed: ${err.message}`);
369
+ process.exit(1);
370
+ }
371
+ });
372
+
373
+ tpl
374
+ .command("list")
375
+ .description("List locally installed Cowork templates")
376
+ .option("--json", "Output as JSON")
377
+ .action(async (options) => {
378
+ const { listUserTemplates } =
379
+ await import("../lib/cowork-template-marketplace.js");
380
+ const templates = listUserTemplates(process.cwd());
381
+ if (options.json) {
382
+ console.log(JSON.stringify(templates, null, 2));
383
+ return;
384
+ }
385
+ if (templates.length === 0) {
386
+ logger.info("No user templates installed.");
387
+ return;
388
+ }
389
+ logger.log(chalk.bold(`\n${templates.length} installed template(s):\n`));
390
+ for (const t of templates) {
391
+ logger.log(
392
+ ` ${chalk.cyan(t.id)} — ${t.name} ${chalk.gray("[" + t.category + "]")}`,
393
+ );
394
+ }
395
+ logger.log("");
396
+ });
397
+
398
+ tpl
399
+ .command("remove <id>")
400
+ .description("Remove an installed Cowork template")
401
+ .action(async (id) => {
402
+ const { removeUserTemplate } =
403
+ await import("../lib/cowork-template-marketplace.js");
404
+ if (removeUserTemplate(process.cwd(), id)) {
405
+ logger.log(chalk.green(`✓ Removed '${id}'`));
406
+ } else {
407
+ logger.error(`Template not installed: ${id}`);
408
+ process.exit(1);
409
+ }
410
+ });
411
+
412
+ tpl
413
+ .command("publish <templateId>")
414
+ .description("Publish a built-in or installed Cowork template to EvoMap")
415
+ .option("--hub <url>", "EvoMap hub URL")
416
+ .option("--api-key <key>", "EvoMap API key (or set EVOMAP_API_KEY)")
417
+ .option(
418
+ "--sign <did>",
419
+ "Sign the gene with this DID from the local identity store",
420
+ )
421
+ .action(async (templateId, options) => {
422
+ const [{ publishTemplateToHub }, { getTemplate }] = await Promise.all([
423
+ import("../lib/cowork-evomap-adapter.js"),
424
+ import("../lib/cowork-task-templates.js"),
425
+ ]);
426
+
427
+ const template = getTemplate(templateId);
428
+ if (template.id === "free" && templateId !== "free") {
429
+ logger.error(`Unknown template: ${templateId}`);
430
+ process.exit(1);
431
+ }
432
+
433
+ let signer;
434
+ if (options.sign) {
435
+ signer = await _resolveSigner(options.sign);
436
+ }
437
+
438
+ try {
439
+ const result = await publishTemplateToHub(template, {
440
+ hubUrl: options.hub,
441
+ apiKey: options.apiKey,
442
+ signer,
443
+ });
444
+ logger.log(
445
+ chalk.green(`✓ Published ${result?.id || template.id}`) +
446
+ (signer ? chalk.gray(` (signed by ${signer.did})`) : ""),
447
+ );
448
+ } catch (err) {
449
+ logger.error(`Publish failed: ${err.message}`);
450
+ process.exit(1);
451
+ }
452
+ });
453
+
454
+ // cowork cron — schedule daily tasks
455
+ const cron = cowork
456
+ .command("cron")
457
+ .description("Schedule Cowork tasks on a cron expression");
458
+
459
+ cron
460
+ .command("list")
461
+ .description("List all scheduled Cowork tasks")
462
+ .option("--json", "Output as JSON")
463
+ .action(async (options) => {
464
+ const { loadSchedules } = await import("../lib/cowork-cron.js");
465
+ const schedules = loadSchedules(process.cwd());
466
+ if (options.json) {
467
+ console.log(JSON.stringify(schedules, null, 2));
468
+ return;
469
+ }
470
+ if (schedules.length === 0) {
471
+ logger.log(chalk.gray("No scheduled tasks."));
472
+ return;
473
+ }
474
+ logger.log(chalk.bold(`\n${schedules.length} scheduled task(s):\n`));
475
+ for (const s of schedules) {
476
+ const flag = s.enabled ? chalk.green("✓") : chalk.gray("✗");
477
+ logger.log(
478
+ ` ${flag} ${chalk.cyan(s.id)} ${chalk.yellow(s.cron)} [${s.templateId || "free"}]`,
479
+ );
480
+ logger.log(chalk.gray(` ${s.userMessage.slice(0, 80)}`));
481
+ if (s.lastRunAt) {
482
+ logger.log(
483
+ chalk.gray(` last run: ${s.lastRunAt} (${s.lastStatus})`),
484
+ );
485
+ }
486
+ }
487
+ logger.log("");
488
+ });
489
+
490
+ cron
491
+ .command("add")
492
+ .description("Add a scheduled Cowork task")
493
+ .requiredOption(
494
+ "--cron <expr>",
495
+ "5-field cron expression (e.g. '0 9 * * 1-5')",
496
+ )
497
+ .requiredOption("--message <text>", "Task prompt / user message")
498
+ .option(
499
+ "--template <id>",
500
+ "Template id (e.g. doc-convert); omit for free mode",
501
+ )
502
+ .option("--files <list>", "Comma-separated absolute file paths", "")
503
+ .action(async (options) => {
504
+ const { addSchedule } = await import("../lib/cowork-cron.js");
505
+ try {
506
+ const entry = addSchedule(process.cwd(), {
507
+ cron: options.cron,
508
+ templateId: options.template || null,
509
+ userMessage: options.message,
510
+ files: options.files
511
+ ? options.files
512
+ .split(",")
513
+ .map((f) => f.trim())
514
+ .filter(Boolean)
515
+ : [],
516
+ });
517
+ logger.log(chalk.green(`✓ Added schedule ${entry.id}`));
518
+ logger.log(chalk.gray(` cron: ${entry.cron}`));
519
+ logger.log(chalk.gray(` template: ${entry.templateId || "free"}`));
520
+ } catch (err) {
521
+ logger.error(err.message);
522
+ process.exit(1);
523
+ }
524
+ });
525
+
526
+ cron
527
+ .command("remove <id>")
528
+ .description("Remove a scheduled task by id")
529
+ .action(async (id) => {
530
+ const { removeSchedule } = await import("../lib/cowork-cron.js");
531
+ if (removeSchedule(process.cwd(), id)) {
532
+ logger.log(chalk.green(`✓ Removed ${id}`));
533
+ } else {
534
+ logger.error(`Schedule not found: ${id}`);
535
+ process.exit(1);
536
+ }
537
+ });
538
+
539
+ cron
540
+ .command("enable <id>")
541
+ .description("Enable a scheduled task")
542
+ .action(async (id) => {
543
+ const { setScheduleEnabled } = await import("../lib/cowork-cron.js");
544
+ if (setScheduleEnabled(process.cwd(), id, true)) {
545
+ logger.log(chalk.green(`✓ Enabled ${id}`));
546
+ } else {
547
+ logger.error(`Schedule not found: ${id}`);
548
+ process.exit(1);
549
+ }
550
+ });
551
+
552
+ cron
553
+ .command("disable <id>")
554
+ .description("Disable a scheduled task (keeps the record)")
555
+ .action(async (id) => {
556
+ const { setScheduleEnabled } = await import("../lib/cowork-cron.js");
557
+ if (setScheduleEnabled(process.cwd(), id, false)) {
558
+ logger.log(chalk.yellow(`✓ Disabled ${id}`));
559
+ } else {
560
+ logger.error(`Schedule not found: ${id}`);
561
+ process.exit(1);
562
+ }
563
+ });
564
+
565
+ cron
566
+ .command("run")
567
+ .description("Start the cron scheduler in the foreground (Ctrl-C to stop)")
568
+ .option("--interval <ms>", "Tick interval in ms (default 60000)", "60000")
569
+ .action(async (options) => {
570
+ const [{ CoworkCronScheduler, _deps: cronDeps }, { runCoworkTask }] =
571
+ await Promise.all([
572
+ import("../lib/cowork-cron.js"),
573
+ import("../lib/cowork-task-runner.js"),
574
+ ]);
575
+ // Inject the runner so the scheduler doesn't require a circular import
576
+ cronDeps.runTask = runCoworkTask;
577
+
578
+ const scheduler = new CoworkCronScheduler({
579
+ cwd: process.cwd(),
580
+ intervalMs: parseInt(options.interval, 10) || 60_000,
581
+ onEvent: (e) => {
582
+ const ts = new Date().toISOString();
583
+ logger.log(chalk.gray(`[${ts}] ${JSON.stringify(e)}`));
584
+ },
585
+ });
586
+ scheduler.start();
587
+ logger.log(
588
+ chalk.green("Cowork cron scheduler running. Press Ctrl-C to stop."),
589
+ );
590
+ process.on("SIGINT", () => {
591
+ scheduler.stop();
592
+ process.exit(0);
593
+ });
594
+ });
595
+
596
+ // cowork share — export/import signed packets for P2P transfer
597
+ const share = cowork
598
+ .command("share")
599
+ .description(
600
+ "Export/import signed Cowork packets (templates or task results)",
601
+ );
602
+
603
+ async function _resolveSigner(didQuery) {
604
+ if (!didQuery) return null;
605
+ const { getConnection } = await import("../lib/db-connection.js");
606
+ const { getIdentity } = await import("../lib/did-manager.js");
607
+ const db = await getConnection();
608
+ const identity = getIdentity(db, didQuery);
609
+ if (!identity) {
610
+ throw new Error(`DID identity not found: ${didQuery}`);
611
+ }
612
+ return {
613
+ did: identity.did,
614
+ publicKey: identity.public_key,
615
+ privateKey: identity.secret_key,
616
+ };
617
+ }
618
+
619
+ share
620
+ .command("export-template <id>")
621
+ .description("Export an installed template as a signed packet")
622
+ .requiredOption("--out <file>", "Output packet file (.json)")
623
+ .option("--author <name>", "Author name", "anonymous")
624
+ .option("--sign <did>", "Sign packet with a local DID identity")
625
+ .action(async (id, options) => {
626
+ const [{ listUserTemplates }, { exportTemplatePacket, writePacket }] =
627
+ await Promise.all([
628
+ import("../lib/cowork-template-marketplace.js"),
629
+ import("../lib/cowork-share.js"),
630
+ ]);
631
+ const templates = listUserTemplates(process.cwd());
632
+ const tpl = templates.find((t) => t.id === id);
633
+ if (!tpl) {
634
+ logger.error(`Template not installed: ${id}`);
635
+ process.exit(1);
636
+ }
637
+ let signer = null;
638
+ try {
639
+ signer = await _resolveSigner(options.sign);
640
+ } catch (err) {
641
+ logger.error(err.message);
642
+ process.exit(1);
643
+ }
644
+ const packet = exportTemplatePacket(tpl, {
645
+ author: options.author,
646
+ signer,
647
+ });
648
+ writePacket(options.out, packet);
649
+ logger.log(chalk.green(`✓ Wrote template packet to ${options.out}`));
650
+ if (signer) logger.log(chalk.gray(` signed by: ${signer.did}`));
651
+ });
652
+
653
+ share
654
+ .command("export-result <taskId>")
655
+ .description("Export a historical Cowork task result as a signed packet")
656
+ .requiredOption("--out <file>", "Output packet file (.json)")
657
+ .option("--author <name>", "Author name", "anonymous")
658
+ .option("--sign <did>", "Sign packet with a local DID identity")
659
+ .action(async (taskId, options) => {
660
+ const { findHistoryRecord, exportResultPacket, writePacket } =
661
+ await import("../lib/cowork-share.js");
662
+ const rec = findHistoryRecord(process.cwd(), taskId);
663
+ if (!rec) {
664
+ logger.error(`Task not found in history: ${taskId}`);
665
+ process.exit(1);
666
+ }
667
+ let signer = null;
668
+ try {
669
+ signer = await _resolveSigner(options.sign);
670
+ } catch (err) {
671
+ logger.error(err.message);
672
+ process.exit(1);
673
+ }
674
+ const packet = exportResultPacket(rec, {
675
+ author: options.author,
676
+ signer,
677
+ });
678
+ writePacket(options.out, packet);
679
+ logger.log(chalk.green(`✓ Wrote result packet to ${options.out}`));
680
+ if (signer) logger.log(chalk.gray(` signed by: ${signer.did}`));
681
+ });
682
+
683
+ share
684
+ .command("import <file>")
685
+ .description(
686
+ "Import a signed packet (auto-detects template vs result by kind)",
687
+ )
688
+ .option("--require-signed", "Reject unsigned packets")
689
+ .option(
690
+ "--trust <did>",
691
+ "Accept only packets signed by one of these DIDs (repeatable)",
692
+ (value, prev) => (prev ? [...prev, value] : [value]),
693
+ )
694
+ .action(async (file, options) => {
695
+ const { readPacket, importTemplatePacket, importResultPacket } =
696
+ await import("../lib/cowork-share.js");
697
+ try {
698
+ const packet = readPacket(file, {
699
+ requireSigned: !!options.requireSigned,
700
+ trustedDids: options.trust || null,
701
+ });
702
+ if (packet.kind === "template") {
703
+ const tpl = importTemplatePacket(process.cwd(), packet);
704
+ logger.log(chalk.green(`✓ Imported template '${tpl.id}'`));
705
+ } else if (packet.kind === "result") {
706
+ const { file: outPath, taskId } = importResultPacket(
707
+ process.cwd(),
708
+ packet,
709
+ );
710
+ logger.log(chalk.green(`✓ Imported result ${taskId} → ${outPath}`));
711
+ } else {
712
+ logger.error(`Unknown packet kind: ${packet.kind}`);
713
+ process.exit(1);
714
+ }
715
+ } catch (err) {
716
+ logger.error(err.message);
717
+ process.exit(1);
718
+ }
719
+ });
720
+
721
+ share
722
+ .command("verify <file>")
723
+ .description("Verify a packet's checksum without importing")
724
+ .action(async (file) => {
725
+ const { readPacket } = await import("../lib/cowork-share.js");
726
+ try {
727
+ const pkt = readPacket(file);
728
+ logger.log(chalk.green(`✓ Valid ${pkt.kind} packet`));
729
+ logger.log(chalk.gray(` author: ${pkt.meta.author}`));
730
+ logger.log(chalk.gray(` createdAt: ${pkt.meta.createdAt}`));
731
+ logger.log(chalk.gray(` checksum: ${pkt.checksum.slice(0, 16)}…`));
732
+ if (pkt.signature) {
733
+ logger.log(chalk.gray(` signed by: ${pkt.signature.did}`));
734
+ } else {
735
+ logger.log(chalk.gray(` signed: no`));
736
+ }
737
+ } catch (err) {
738
+ logger.error(err.message);
739
+ process.exit(1);
740
+ }
741
+ });
742
+
743
+ // cowork workflow — chain multiple Cowork tasks into a DAG
744
+ const workflow = cowork
745
+ .command("workflow")
746
+ .description("Define and execute multi-step Cowork workflows (DAG)");
747
+
748
+ workflow
749
+ .command("list")
750
+ .description("List saved workflows")
751
+ .option("--json", "Output as JSON")
752
+ .action(async (options) => {
753
+ const { listWorkflows } = await import("../lib/cowork-workflow.js");
754
+ const wfs = listWorkflows(process.cwd());
755
+ if (options.json) {
756
+ console.log(JSON.stringify(wfs, null, 2));
757
+ return;
758
+ }
759
+ if (wfs.length === 0) {
760
+ logger.log(chalk.gray("No workflows saved."));
761
+ return;
762
+ }
763
+ logger.log(chalk.bold(`\n${wfs.length} workflow(s):\n`));
764
+ for (const wf of wfs) {
765
+ logger.log(
766
+ ` ${chalk.cyan(wf.id)} ${wf.name} (${wf.steps.length} steps)`,
767
+ );
768
+ }
769
+ logger.log("");
770
+ });
771
+
772
+ workflow
773
+ .command("show <id>")
774
+ .description("Show a workflow definition")
775
+ .action(async (id) => {
776
+ const { getWorkflow } = await import("../lib/cowork-workflow.js");
777
+ const wf = getWorkflow(process.cwd(), id);
778
+ if (!wf) {
779
+ logger.error(`Workflow not found: ${id}`);
780
+ process.exit(1);
781
+ }
782
+ console.log(JSON.stringify(wf, null, 2));
783
+ });
784
+
785
+ workflow
786
+ .command("add <file>")
787
+ .description("Add a workflow from a JSON definition file")
788
+ .action(async (file) => {
789
+ const fs = await import("node:fs");
790
+ const { saveWorkflow } = await import("../lib/cowork-workflow.js");
791
+ try {
792
+ const body = fs.readFileSync(file, "utf-8");
793
+ const wf = JSON.parse(body);
794
+ saveWorkflow(process.cwd(), wf);
795
+ logger.log(chalk.green(`✓ Saved workflow '${wf.id}'`));
796
+ } catch (err) {
797
+ logger.error(err.message);
798
+ process.exit(1);
799
+ }
800
+ });
801
+
802
+ workflow
803
+ .command("remove <id>")
804
+ .description("Remove a saved workflow")
805
+ .action(async (id) => {
806
+ const { removeWorkflow } = await import("../lib/cowork-workflow.js");
807
+ if (removeWorkflow(process.cwd(), id)) {
808
+ logger.log(chalk.green(`✓ Removed '${id}'`));
809
+ } else {
810
+ logger.error(`Workflow not found: ${id}`);
811
+ process.exit(1);
812
+ }
813
+ });
814
+
815
+ workflow
816
+ .command("run <id>")
817
+ .description("Execute a saved workflow end-to-end")
818
+ .option("--continue-on-error", "Keep running after a step fails", false)
819
+ .option("--max-parallel <n>", "Max parallel steps per batch", "4")
820
+ .action(async (id, options) => {
821
+ const [
822
+ { getWorkflow, executeWorkflow, _deps: wfDeps },
823
+ { runCoworkTask },
824
+ ] = await Promise.all([
825
+ import("../lib/cowork-workflow.js"),
826
+ import("../lib/cowork-task-runner.js"),
827
+ ]);
828
+ const wf = getWorkflow(process.cwd(), id);
829
+ if (!wf) {
830
+ logger.error(`Workflow not found: ${id}`);
831
+ process.exit(1);
832
+ }
833
+ wfDeps.runTask = runCoworkTask;
834
+
835
+ logger.log(
836
+ chalk.bold(
837
+ `\nExecuting workflow '${wf.name}' (${wf.steps.length} steps)...`,
838
+ ),
839
+ );
840
+ try {
841
+ const result = await executeWorkflow({
842
+ workflow: wf,
843
+ cwd: process.cwd(),
844
+ maxParallel: parseInt(options.maxParallel, 10) || 4,
845
+ continueOnError: !!options.continueOnError,
846
+ onStepStart: ({ stepId }) =>
847
+ logger.log(chalk.gray(` → step ${stepId} ...`)),
848
+ onStepComplete: (out) => {
849
+ const flag =
850
+ out.status === "completed"
851
+ ? chalk.green("✓")
852
+ : out.status === "skipped"
853
+ ? chalk.gray("—")
854
+ : chalk.red("✗");
855
+ logger.log(` ${flag} ${out.id} (${out.status})`);
856
+ },
857
+ });
858
+ const color =
859
+ result.status === "completed"
860
+ ? chalk.green
861
+ : result.status === "partial"
862
+ ? chalk.yellow
863
+ : chalk.red;
864
+ logger.log(color(`\nWorkflow ${result.status}.\n`));
865
+ } catch (err) {
866
+ logger.error(err.message);
867
+ process.exit(1);
868
+ }
869
+ });
870
+
871
+ // cowork observe — unified dashboard over tasks/workflows/schedules
872
+ const observe = cowork
873
+ .command("observe")
874
+ .description(
875
+ "Aggregate view over Cowork history, workflows, and schedules",
876
+ );
877
+
878
+ observe
879
+ .command("report", { isDefault: true })
880
+ .description("Print the aggregate report (default)")
881
+ .option("--days <n>", "Window size in days", "7")
882
+ .option("--json", "Output as JSON")
883
+ .action(async (options) => {
884
+ const { aggregate } = await import("../lib/cowork-observe.js");
885
+ const windowDays = parseInt(options.days, 10) || 7;
886
+ const data = aggregate(process.cwd(), { windowDays });
887
+ if (options.json) {
888
+ console.log(JSON.stringify(data, null, 2));
889
+ return;
890
+ }
891
+ const pct = (x) => `${Math.round((x || 0) * 100)}%`;
892
+ logger.log(chalk.bold(`Cowork Observe — last ${data.window.days}d`));
893
+ logger.log(chalk.gray(` ${data.window.from} → ${data.window.to}`));
894
+ logger.log("");
895
+ logger.log(chalk.cyan("Tasks"));
896
+ logger.log(` total: ${data.tasks.total}`);
897
+ logger.log(` completed: ${data.tasks.completed}`);
898
+ logger.log(` failed: ${data.tasks.failed}`);
899
+ logger.log(` success rate: ${pct(data.tasks.successRate)}`);
900
+ logger.log(` avg tokens: ${data.tasks.avgTokens}`);
901
+ logger.log("");
902
+ logger.log(chalk.cyan(`Templates (${data.templates.length})`));
903
+ for (const t of data.templates.slice(0, 5)) {
904
+ logger.log(
905
+ ` ${t.templateName || t.templateId} runs=${t.runs} success=${pct(t.successRate)}`,
906
+ );
907
+ }
908
+ logger.log("");
909
+ logger.log(chalk.cyan("Schedules"));
910
+ logger.log(` active: ${data.schedules.active}`);
911
+ for (const n of data.schedules.nextTriggers) {
912
+ logger.log(
913
+ chalk.gray(` next: ${n.at} ${n.cron} (${n.scheduleId || "-"})`),
914
+ );
915
+ }
916
+ if (data.failures.length > 0) {
917
+ logger.log("");
918
+ logger.log(chalk.yellow(`Failures (${data.failures.length})`));
919
+ for (const f of data.failures.slice(0, 3)) {
920
+ const top = f.commonSummaries?.[0]?.summary || "—";
921
+ logger.log(
922
+ ` ${f.templateName || f.templateId} ×${f.failureCount} ${top.slice(0, 60)}`,
923
+ );
924
+ }
925
+ }
926
+ });
927
+
928
+ observe
929
+ .command("serve")
930
+ .description("Start a read-only HTTP dashboard")
931
+ .option("--port <n>", "HTTP port (0 for random)", "18820")
932
+ .option("--host <addr>", "Bind address", "127.0.0.1")
933
+ .option("--days <n>", "Window size in days", "7")
934
+ .action(async (options) => {
935
+ const http = await import("node:http");
936
+ const { aggregate } = await import("../lib/cowork-observe.js");
937
+ const { buildHtml } = await import("../lib/cowork-observe-html.js");
938
+ const windowDays = parseInt(options.days, 10) || 7;
939
+
940
+ const server = http.createServer((req, res) => {
941
+ const urlPath = (req.url || "/").split("?")[0];
942
+ try {
943
+ if (urlPath === "/" || urlPath === "/index.html") {
944
+ const data = aggregate(process.cwd(), { windowDays });
945
+ const html = buildHtml(data);
946
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
947
+ res.end(html);
948
+ return;
949
+ }
950
+ if (urlPath === "/api/observe") {
951
+ const data = aggregate(process.cwd(), { windowDays });
952
+ res.writeHead(200, {
953
+ "content-type": "application/json; charset=utf-8",
954
+ });
955
+ res.end(JSON.stringify(data));
956
+ return;
957
+ }
958
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
959
+ res.end("Not found");
960
+ } catch (err) {
961
+ res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
962
+ res.end(`Error: ${err.message}`);
963
+ }
964
+ });
965
+
966
+ const port = parseInt(options.port, 10);
967
+ server.listen(Number.isFinite(port) ? port : 18820, options.host, () => {
968
+ const addr = server.address();
969
+ const actualPort = typeof addr === "object" && addr ? addr.port : port;
970
+ logger.log(
971
+ chalk.green(
972
+ `✓ Cowork Observe dashboard at http://${options.host}:${actualPort}/`,
973
+ ),
974
+ );
975
+ logger.log(chalk.gray(" Press Ctrl+C to stop."));
976
+ });
977
+
978
+ process.on("SIGINT", () => {
979
+ server.close(() => process.exit(0));
980
+ });
981
+ });
982
+
983
+ // cowork learning — analyze historical runs
984
+ const learning = cowork
985
+ .command("learning")
986
+ .description("Analyze Cowork history for stats, recommendations, failures");
987
+
988
+ learning
989
+ .command("stats")
990
+ .description("Per-template aggregate stats across all runs")
991
+ .option("--json", "Output as JSON")
992
+ .action(async (options) => {
993
+ const { loadHistory, computeTemplateStats } =
994
+ await import("../lib/cowork-learning.js");
995
+ const stats = computeTemplateStats(loadHistory(process.cwd()));
996
+ if (options.json) {
997
+ console.log(JSON.stringify(stats, null, 2));
998
+ return;
999
+ }
1000
+ if (stats.length === 0) {
1001
+ logger.log(chalk.gray("No history yet. Run some tasks first."));
1002
+ return;
1003
+ }
1004
+ logger.log(chalk.bold(`\nTemplate stats (${stats.length}):\n`));
1005
+ for (const s of stats) {
1006
+ const pct = Math.round(s.successRate * 100);
1007
+ logger.log(
1008
+ ` ${chalk.cyan(s.templateId)} runs=${s.runs} ok=${s.successes} fail=${s.failures} ${pct}% avgTok=${s.avgTokens} avgIter=${s.avgIterations}`,
1009
+ );
1010
+ if (s.topTools.length) {
1011
+ logger.log(
1012
+ chalk.gray(
1013
+ ` tools: ${s.topTools.map((t) => `${t.tool}(${t.count})`).join(", ")}`,
1014
+ ),
1015
+ );
1016
+ }
1017
+ }
1018
+ logger.log("");
1019
+ });
1020
+
1021
+ learning
1022
+ .command("recommend <message...>")
1023
+ .description("Recommend the best template for a new user message")
1024
+ .option("--min-runs <n>", "Only consider templates with ≥N past runs", "1")
1025
+ .option("--json", "Output as JSON")
1026
+ .action(async (messageParts, options) => {
1027
+ const { loadHistory, recommendTemplate } =
1028
+ await import("../lib/cowork-learning.js");
1029
+ const message = messageParts.join(" ");
1030
+ const rec = recommendTemplate(message, loadHistory(process.cwd()), {
1031
+ minRuns: parseInt(options.minRuns, 10) || 1,
1032
+ });
1033
+ if (options.json) {
1034
+ console.log(JSON.stringify(rec, null, 2));
1035
+ return;
1036
+ }
1037
+ if (!rec) {
1038
+ logger.log(
1039
+ chalk.gray("No recommendation — no overlapping history found."),
1040
+ );
1041
+ return;
1042
+ }
1043
+ logger.log(chalk.bold(`\nRecommended: ${chalk.cyan(rec.templateId)}`));
1044
+ logger.log(
1045
+ chalk.gray(` score: ${rec.score} confidence: ${rec.confidence}`),
1046
+ );
1047
+ for (const r of rec.reasons) logger.log(chalk.gray(` - ${r}`));
1048
+ logger.log("");
1049
+ });
1050
+
1051
+ learning
1052
+ .command("failures")
1053
+ .description("Group failures by template with common summaries")
1054
+ .option("--limit <n>", "Max examples per template", "3")
1055
+ .option("--json", "Output as JSON")
1056
+ .action(async (options) => {
1057
+ const { loadHistory, summarizeFailures } =
1058
+ await import("../lib/cowork-learning.js");
1059
+ const out = summarizeFailures(loadHistory(process.cwd()), {
1060
+ limit: parseInt(options.limit, 10) || 3,
1061
+ });
1062
+ if (options.json) {
1063
+ console.log(JSON.stringify(out, null, 2));
1064
+ return;
1065
+ }
1066
+ if (out.length === 0) {
1067
+ logger.log(chalk.green("✓ No failures in history."));
1068
+ return;
1069
+ }
1070
+ logger.log(chalk.bold(`\nFailures by template:\n`));
1071
+ for (const g of out) {
1072
+ logger.log(` ${chalk.red(g.templateId)} failures=${g.failureCount}`);
1073
+ for (const cs of g.commonSummaries) {
1074
+ logger.log(chalk.gray(` × ${cs.count} ${cs.summary}`));
1075
+ }
1076
+ }
1077
+ logger.log("");
1078
+ });
1079
+
1080
+ learning
1081
+ .command("suggest")
1082
+ .description("Suggest systemPromptExtension patches from failure history")
1083
+ .option("--json", "Output as JSON")
1084
+ .action(async (options) => {
1085
+ const { loadHistory, suggestPromptPatch } =
1086
+ await import("../lib/cowork-learning.js");
1087
+ const patches = suggestPromptPatch(loadHistory(process.cwd()));
1088
+ if (options.json) {
1089
+ console.log(JSON.stringify(patches, null, 2));
1090
+ return;
1091
+ }
1092
+ if (patches.length === 0) {
1093
+ logger.log(
1094
+ chalk.gray(
1095
+ "No patch suggestions — not enough history (need ≥10 runs and ≥3 failures per template).",
1096
+ ),
1097
+ );
1098
+ return;
1099
+ }
1100
+ logger.log(chalk.bold(`\nSuggested patches (${patches.length}):\n`));
1101
+ for (const p of patches) {
1102
+ const color =
1103
+ p.confidence === "high"
1104
+ ? chalk.red
1105
+ : p.confidence === "medium"
1106
+ ? chalk.yellow
1107
+ : chalk.gray;
1108
+ logger.log(
1109
+ ` ${chalk.cyan(p.templateId)} ${color(p.confidence)} runs=${p.runs} failures=${p.failures} (${Math.round(p.failureRate * 100)}%)`,
1110
+ );
1111
+ logger.log(chalk.gray(` ${p.patch}`));
1112
+ }
1113
+ logger.log(
1114
+ chalk.dim(
1115
+ "\n Apply with: cc cowork learning apply <templateId> (writes to user-templates/)",
1116
+ ),
1117
+ );
1118
+ logger.log("");
1119
+ });
1120
+
1121
+ learning
1122
+ .command("apply <templateId>")
1123
+ .description("Apply a suggested patch to the user-templates layer")
1124
+ .option("--json", "Output as JSON")
1125
+ .action(async (templateId, options) => {
1126
+ const { loadHistory, suggestPromptPatch, applyPromptPatch } =
1127
+ await import("../lib/cowork-learning.js");
1128
+ const patches = suggestPromptPatch(loadHistory(process.cwd()));
1129
+ const match = patches.find((p) => p.templateId === templateId);
1130
+ if (!match) {
1131
+ logger.error(
1132
+ `No qualifying patch for template '${templateId}'. Run 'cowork learning suggest' to see available patches.`,
1133
+ );
1134
+ process.exit(1);
1135
+ }
1136
+ const result = applyPromptPatch(process.cwd(), match);
1137
+ if (options.json) {
1138
+ console.log(JSON.stringify(result, null, 2));
1139
+ return;
1140
+ }
1141
+ logger.log(
1142
+ chalk.green(
1143
+ `✓ Applied patch to ${chalk.cyan(result.templateId)} (${result.file}).`,
1144
+ ),
1145
+ );
1146
+ });
1147
+
293
1148
  // cowork status — show collaboration state
294
1149
  cowork
295
1150
  .command("status")
@@ -306,6 +1161,18 @@ export function registerCoworkCommand(program) {
306
1161
  logger.log(
307
1162
  ` ${chalk.cyan("cowork analyze <path>")} Code analysis (style/knowledge-graph/decisions)`,
308
1163
  );
1164
+ logger.log(
1165
+ ` ${chalk.cyan("cowork cron list|add|remove|run")} Schedule recurring Cowork tasks`,
1166
+ );
1167
+ logger.log(
1168
+ ` ${chalk.cyan("cowork learning stats|recommend|failures")} Analyze run history`,
1169
+ );
1170
+ logger.log(
1171
+ ` ${chalk.cyan("cowork workflow list|show|add|remove|run")} DAG of chained Cowork tasks`,
1172
+ );
1173
+ logger.log(
1174
+ ` ${chalk.cyan("cowork share export-template|export-result|import|verify")} Signed packet exchange`,
1175
+ );
309
1176
  logger.log("");
310
1177
  logger.log(
311
1178
  chalk.gray(