chainlesschain 0.45.81 → 0.46.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.
@@ -290,6 +290,689 @@ 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("--limit <n>", "Max results", "20")
304
+ .option("--json", "Output as JSON")
305
+ .action(async (query, options) => {
306
+ const [{ searchTemplates, _deps: mpDeps }, { EvoMapClient }] =
307
+ await Promise.all([
308
+ import("../lib/cowork-template-marketplace.js"),
309
+ import("../lib/evomap-client.js"),
310
+ ]);
311
+ mpDeps.evomapClient = new EvoMapClient();
312
+ try {
313
+ const results = await searchTemplates(query || "", {
314
+ limit: parseInt(options.limit, 10) || 20,
315
+ });
316
+ if (options.json) {
317
+ console.log(JSON.stringify(results, null, 2));
318
+ return;
319
+ }
320
+ if (results.length === 0) {
321
+ logger.info("No templates found.");
322
+ return;
323
+ }
324
+ logger.log(chalk.bold(`\n${results.length} template(s) found:\n`));
325
+ for (const g of results) {
326
+ logger.log(
327
+ ` ${chalk.cyan(g.id)} ${chalk.gray("v" + (g.version || "?"))} by ${g.author || "unknown"}`,
328
+ );
329
+ if (g.description) {
330
+ logger.log(chalk.gray(` ${g.description.slice(0, 100)}`));
331
+ }
332
+ logger.log(
333
+ chalk.gray(
334
+ ` downloads: ${g.downloads || 0} rating: ${g.rating || "N/A"}`,
335
+ ),
336
+ );
337
+ }
338
+ logger.log("");
339
+ } catch (err) {
340
+ logger.error(`Search failed: ${err.message}`);
341
+ process.exit(1);
342
+ }
343
+ });
344
+
345
+ tpl
346
+ .command("install <geneId>")
347
+ .description("Install a Cowork template from the EvoMap hub")
348
+ .action(async (geneId) => {
349
+ const [{ installTemplate, _deps: mpDeps }, { EvoMapClient }] =
350
+ await Promise.all([
351
+ import("../lib/cowork-template-marketplace.js"),
352
+ import("../lib/evomap-client.js"),
353
+ ]);
354
+ mpDeps.evomapClient = new EvoMapClient();
355
+ try {
356
+ const template = await installTemplate(process.cwd(), geneId);
357
+ logger.log(
358
+ chalk.green(
359
+ `✓ Installed template '${template.id}' (${template.name})`,
360
+ ),
361
+ );
362
+ } catch (err) {
363
+ logger.error(`Install failed: ${err.message}`);
364
+ process.exit(1);
365
+ }
366
+ });
367
+
368
+ tpl
369
+ .command("list")
370
+ .description("List locally installed Cowork templates")
371
+ .option("--json", "Output as JSON")
372
+ .action(async (options) => {
373
+ const { listUserTemplates } =
374
+ await import("../lib/cowork-template-marketplace.js");
375
+ const templates = listUserTemplates(process.cwd());
376
+ if (options.json) {
377
+ console.log(JSON.stringify(templates, null, 2));
378
+ return;
379
+ }
380
+ if (templates.length === 0) {
381
+ logger.info("No user templates installed.");
382
+ return;
383
+ }
384
+ logger.log(chalk.bold(`\n${templates.length} installed template(s):\n`));
385
+ for (const t of templates) {
386
+ logger.log(
387
+ ` ${chalk.cyan(t.id)} — ${t.name} ${chalk.gray("[" + t.category + "]")}`,
388
+ );
389
+ }
390
+ logger.log("");
391
+ });
392
+
393
+ tpl
394
+ .command("remove <id>")
395
+ .description("Remove an installed Cowork template")
396
+ .action(async (id) => {
397
+ const { removeUserTemplate } =
398
+ await import("../lib/cowork-template-marketplace.js");
399
+ if (removeUserTemplate(process.cwd(), id)) {
400
+ logger.log(chalk.green(`✓ Removed '${id}'`));
401
+ } else {
402
+ logger.error(`Template not installed: ${id}`);
403
+ process.exit(1);
404
+ }
405
+ });
406
+
407
+ tpl
408
+ .command("publish <templateId>")
409
+ .description("Publish a built-in or installed Cowork template to EvoMap")
410
+ .requiredOption("--author <name>", "Author name for the published gene")
411
+ .option("--version <v>", "Gene version", "1.0.0")
412
+ .option("--description <text>", "Gene description")
413
+ .option("--tags <list>", "Comma-separated tags")
414
+ .action(async (templateId, options) => {
415
+ const [
416
+ { publishTemplate, toShareableTemplate, _deps: mpDeps },
417
+ { getTemplate },
418
+ { EvoMapClient },
419
+ ] = await Promise.all([
420
+ import("../lib/cowork-template-marketplace.js"),
421
+ import("../lib/cowork-task-templates.js"),
422
+ import("../lib/evomap-client.js"),
423
+ ]);
424
+ mpDeps.evomapClient = new EvoMapClient();
425
+
426
+ const template = getTemplate(templateId);
427
+ if (template.id === "free") {
428
+ logger.error(`Unknown template: ${templateId}`);
429
+ process.exit(1);
430
+ }
431
+ const shareable = toShareableTemplate(template);
432
+ try {
433
+ const result = await publishTemplate(shareable, {
434
+ author: options.author,
435
+ version: options.version,
436
+ description: options.description,
437
+ tags: options.tags
438
+ ? options.tags.split(",").map((t) => t.trim())
439
+ : [],
440
+ });
441
+ logger.log(chalk.green(`✓ Published ${result?.id || shareable.id}`));
442
+ } catch (err) {
443
+ logger.error(`Publish failed: ${err.message}`);
444
+ process.exit(1);
445
+ }
446
+ });
447
+
448
+ // cowork cron — schedule daily tasks
449
+ const cron = cowork
450
+ .command("cron")
451
+ .description("Schedule Cowork tasks on a cron expression");
452
+
453
+ cron
454
+ .command("list")
455
+ .description("List all scheduled Cowork tasks")
456
+ .option("--json", "Output as JSON")
457
+ .action(async (options) => {
458
+ const { loadSchedules } = await import("../lib/cowork-cron.js");
459
+ const schedules = loadSchedules(process.cwd());
460
+ if (options.json) {
461
+ console.log(JSON.stringify(schedules, null, 2));
462
+ return;
463
+ }
464
+ if (schedules.length === 0) {
465
+ logger.log(chalk.gray("No scheduled tasks."));
466
+ return;
467
+ }
468
+ logger.log(chalk.bold(`\n${schedules.length} scheduled task(s):\n`));
469
+ for (const s of schedules) {
470
+ const flag = s.enabled ? chalk.green("✓") : chalk.gray("✗");
471
+ logger.log(
472
+ ` ${flag} ${chalk.cyan(s.id)} ${chalk.yellow(s.cron)} [${s.templateId || "free"}]`,
473
+ );
474
+ logger.log(chalk.gray(` ${s.userMessage.slice(0, 80)}`));
475
+ if (s.lastRunAt) {
476
+ logger.log(
477
+ chalk.gray(` last run: ${s.lastRunAt} (${s.lastStatus})`),
478
+ );
479
+ }
480
+ }
481
+ logger.log("");
482
+ });
483
+
484
+ cron
485
+ .command("add")
486
+ .description("Add a scheduled Cowork task")
487
+ .requiredOption(
488
+ "--cron <expr>",
489
+ "5-field cron expression (e.g. '0 9 * * 1-5')",
490
+ )
491
+ .requiredOption("--message <text>", "Task prompt / user message")
492
+ .option(
493
+ "--template <id>",
494
+ "Template id (e.g. doc-convert); omit for free mode",
495
+ )
496
+ .option("--files <list>", "Comma-separated absolute file paths", "")
497
+ .action(async (options) => {
498
+ const { addSchedule } = await import("../lib/cowork-cron.js");
499
+ try {
500
+ const entry = addSchedule(process.cwd(), {
501
+ cron: options.cron,
502
+ templateId: options.template || null,
503
+ userMessage: options.message,
504
+ files: options.files
505
+ ? options.files
506
+ .split(",")
507
+ .map((f) => f.trim())
508
+ .filter(Boolean)
509
+ : [],
510
+ });
511
+ logger.log(chalk.green(`✓ Added schedule ${entry.id}`));
512
+ logger.log(chalk.gray(` cron: ${entry.cron}`));
513
+ logger.log(chalk.gray(` template: ${entry.templateId || "free"}`));
514
+ } catch (err) {
515
+ logger.error(err.message);
516
+ process.exit(1);
517
+ }
518
+ });
519
+
520
+ cron
521
+ .command("remove <id>")
522
+ .description("Remove a scheduled task by id")
523
+ .action(async (id) => {
524
+ const { removeSchedule } = await import("../lib/cowork-cron.js");
525
+ if (removeSchedule(process.cwd(), id)) {
526
+ logger.log(chalk.green(`✓ Removed ${id}`));
527
+ } else {
528
+ logger.error(`Schedule not found: ${id}`);
529
+ process.exit(1);
530
+ }
531
+ });
532
+
533
+ cron
534
+ .command("enable <id>")
535
+ .description("Enable a scheduled task")
536
+ .action(async (id) => {
537
+ const { setScheduleEnabled } = await import("../lib/cowork-cron.js");
538
+ if (setScheduleEnabled(process.cwd(), id, true)) {
539
+ logger.log(chalk.green(`✓ Enabled ${id}`));
540
+ } else {
541
+ logger.error(`Schedule not found: ${id}`);
542
+ process.exit(1);
543
+ }
544
+ });
545
+
546
+ cron
547
+ .command("disable <id>")
548
+ .description("Disable a scheduled task (keeps the record)")
549
+ .action(async (id) => {
550
+ const { setScheduleEnabled } = await import("../lib/cowork-cron.js");
551
+ if (setScheduleEnabled(process.cwd(), id, false)) {
552
+ logger.log(chalk.yellow(`✓ Disabled ${id}`));
553
+ } else {
554
+ logger.error(`Schedule not found: ${id}`);
555
+ process.exit(1);
556
+ }
557
+ });
558
+
559
+ cron
560
+ .command("run")
561
+ .description("Start the cron scheduler in the foreground (Ctrl-C to stop)")
562
+ .option("--interval <ms>", "Tick interval in ms (default 60000)", "60000")
563
+ .action(async (options) => {
564
+ const [{ CoworkCronScheduler, _deps: cronDeps }, { runCoworkTask }] =
565
+ await Promise.all([
566
+ import("../lib/cowork-cron.js"),
567
+ import("../lib/cowork-task-runner.js"),
568
+ ]);
569
+ // Inject the runner so the scheduler doesn't require a circular import
570
+ cronDeps.runTask = runCoworkTask;
571
+
572
+ const scheduler = new CoworkCronScheduler({
573
+ cwd: process.cwd(),
574
+ intervalMs: parseInt(options.interval, 10) || 60_000,
575
+ onEvent: (e) => {
576
+ const ts = new Date().toISOString();
577
+ logger.log(chalk.gray(`[${ts}] ${JSON.stringify(e)}`));
578
+ },
579
+ });
580
+ scheduler.start();
581
+ logger.log(
582
+ chalk.green("Cowork cron scheduler running. Press Ctrl-C to stop."),
583
+ );
584
+ process.on("SIGINT", () => {
585
+ scheduler.stop();
586
+ process.exit(0);
587
+ });
588
+ });
589
+
590
+ // cowork share — export/import signed packets for P2P transfer
591
+ const share = cowork
592
+ .command("share")
593
+ .description(
594
+ "Export/import signed Cowork packets (templates or task results)",
595
+ );
596
+
597
+ share
598
+ .command("export-template <id>")
599
+ .description("Export an installed template as a signed packet")
600
+ .requiredOption("--out <file>", "Output packet file (.json)")
601
+ .option("--author <name>", "Author name", "anonymous")
602
+ .action(async (id, options) => {
603
+ const [{ listUserTemplates }, { exportTemplatePacket, writePacket }] =
604
+ await Promise.all([
605
+ import("../lib/cowork-template-marketplace.js"),
606
+ import("../lib/cowork-share.js"),
607
+ ]);
608
+ const templates = listUserTemplates(process.cwd());
609
+ const tpl = templates.find((t) => t.id === id);
610
+ if (!tpl) {
611
+ logger.error(`Template not installed: ${id}`);
612
+ process.exit(1);
613
+ }
614
+ const packet = exportTemplatePacket(tpl, { author: options.author });
615
+ writePacket(options.out, packet);
616
+ logger.log(chalk.green(`✓ Wrote template packet to ${options.out}`));
617
+ });
618
+
619
+ share
620
+ .command("export-result <taskId>")
621
+ .description("Export a historical Cowork task result as a signed packet")
622
+ .requiredOption("--out <file>", "Output packet file (.json)")
623
+ .option("--author <name>", "Author name", "anonymous")
624
+ .action(async (taskId, options) => {
625
+ const { findHistoryRecord, exportResultPacket, writePacket } =
626
+ await import("../lib/cowork-share.js");
627
+ const rec = findHistoryRecord(process.cwd(), taskId);
628
+ if (!rec) {
629
+ logger.error(`Task not found in history: ${taskId}`);
630
+ process.exit(1);
631
+ }
632
+ const packet = exportResultPacket(rec, { author: options.author });
633
+ writePacket(options.out, packet);
634
+ logger.log(chalk.green(`✓ Wrote result packet to ${options.out}`));
635
+ });
636
+
637
+ share
638
+ .command("import <file>")
639
+ .description(
640
+ "Import a signed packet (auto-detects template vs result by kind)",
641
+ )
642
+ .action(async (file) => {
643
+ const { readPacket, importTemplatePacket, importResultPacket } =
644
+ await import("../lib/cowork-share.js");
645
+ try {
646
+ const packet = readPacket(file);
647
+ if (packet.kind === "template") {
648
+ const tpl = importTemplatePacket(process.cwd(), packet);
649
+ logger.log(chalk.green(`✓ Imported template '${tpl.id}'`));
650
+ } else if (packet.kind === "result") {
651
+ const { file: outPath, taskId } = importResultPacket(
652
+ process.cwd(),
653
+ packet,
654
+ );
655
+ logger.log(chalk.green(`✓ Imported result ${taskId} → ${outPath}`));
656
+ } else {
657
+ logger.error(`Unknown packet kind: ${packet.kind}`);
658
+ process.exit(1);
659
+ }
660
+ } catch (err) {
661
+ logger.error(err.message);
662
+ process.exit(1);
663
+ }
664
+ });
665
+
666
+ share
667
+ .command("verify <file>")
668
+ .description("Verify a packet's checksum without importing")
669
+ .action(async (file) => {
670
+ const { readPacket } = await import("../lib/cowork-share.js");
671
+ try {
672
+ const pkt = readPacket(file);
673
+ logger.log(chalk.green(`✓ Valid ${pkt.kind} packet`));
674
+ logger.log(chalk.gray(` author: ${pkt.meta.author}`));
675
+ logger.log(chalk.gray(` createdAt: ${pkt.meta.createdAt}`));
676
+ logger.log(chalk.gray(` checksum: ${pkt.checksum.slice(0, 16)}…`));
677
+ } catch (err) {
678
+ logger.error(err.message);
679
+ process.exit(1);
680
+ }
681
+ });
682
+
683
+ // cowork workflow — chain multiple Cowork tasks into a DAG
684
+ const workflow = cowork
685
+ .command("workflow")
686
+ .description("Define and execute multi-step Cowork workflows (DAG)");
687
+
688
+ workflow
689
+ .command("list")
690
+ .description("List saved workflows")
691
+ .option("--json", "Output as JSON")
692
+ .action(async (options) => {
693
+ const { listWorkflows } = await import("../lib/cowork-workflow.js");
694
+ const wfs = listWorkflows(process.cwd());
695
+ if (options.json) {
696
+ console.log(JSON.stringify(wfs, null, 2));
697
+ return;
698
+ }
699
+ if (wfs.length === 0) {
700
+ logger.log(chalk.gray("No workflows saved."));
701
+ return;
702
+ }
703
+ logger.log(chalk.bold(`\n${wfs.length} workflow(s):\n`));
704
+ for (const wf of wfs) {
705
+ logger.log(
706
+ ` ${chalk.cyan(wf.id)} ${wf.name} (${wf.steps.length} steps)`,
707
+ );
708
+ }
709
+ logger.log("");
710
+ });
711
+
712
+ workflow
713
+ .command("show <id>")
714
+ .description("Show a workflow definition")
715
+ .action(async (id) => {
716
+ const { getWorkflow } = await import("../lib/cowork-workflow.js");
717
+ const wf = getWorkflow(process.cwd(), id);
718
+ if (!wf) {
719
+ logger.error(`Workflow not found: ${id}`);
720
+ process.exit(1);
721
+ }
722
+ console.log(JSON.stringify(wf, null, 2));
723
+ });
724
+
725
+ workflow
726
+ .command("add <file>")
727
+ .description("Add a workflow from a JSON definition file")
728
+ .action(async (file) => {
729
+ const fs = await import("node:fs");
730
+ const { saveWorkflow } = await import("../lib/cowork-workflow.js");
731
+ try {
732
+ const body = fs.readFileSync(file, "utf-8");
733
+ const wf = JSON.parse(body);
734
+ saveWorkflow(process.cwd(), wf);
735
+ logger.log(chalk.green(`✓ Saved workflow '${wf.id}'`));
736
+ } catch (err) {
737
+ logger.error(err.message);
738
+ process.exit(1);
739
+ }
740
+ });
741
+
742
+ workflow
743
+ .command("remove <id>")
744
+ .description("Remove a saved workflow")
745
+ .action(async (id) => {
746
+ const { removeWorkflow } = await import("../lib/cowork-workflow.js");
747
+ if (removeWorkflow(process.cwd(), id)) {
748
+ logger.log(chalk.green(`✓ Removed '${id}'`));
749
+ } else {
750
+ logger.error(`Workflow not found: ${id}`);
751
+ process.exit(1);
752
+ }
753
+ });
754
+
755
+ workflow
756
+ .command("run <id>")
757
+ .description("Execute a saved workflow end-to-end")
758
+ .option("--continue-on-error", "Keep running after a step fails", false)
759
+ .option("--max-parallel <n>", "Max parallel steps per batch", "4")
760
+ .action(async (id, options) => {
761
+ const [
762
+ { getWorkflow, executeWorkflow, _deps: wfDeps },
763
+ { runCoworkTask },
764
+ ] = await Promise.all([
765
+ import("../lib/cowork-workflow.js"),
766
+ import("../lib/cowork-task-runner.js"),
767
+ ]);
768
+ const wf = getWorkflow(process.cwd(), id);
769
+ if (!wf) {
770
+ logger.error(`Workflow not found: ${id}`);
771
+ process.exit(1);
772
+ }
773
+ wfDeps.runTask = runCoworkTask;
774
+
775
+ logger.log(
776
+ chalk.bold(
777
+ `\nExecuting workflow '${wf.name}' (${wf.steps.length} steps)...`,
778
+ ),
779
+ );
780
+ try {
781
+ const result = await executeWorkflow({
782
+ workflow: wf,
783
+ cwd: process.cwd(),
784
+ maxParallel: parseInt(options.maxParallel, 10) || 4,
785
+ continueOnError: !!options.continueOnError,
786
+ onStepStart: ({ stepId }) =>
787
+ logger.log(chalk.gray(` → step ${stepId} ...`)),
788
+ onStepComplete: (out) => {
789
+ const flag =
790
+ out.status === "completed"
791
+ ? chalk.green("✓")
792
+ : out.status === "skipped"
793
+ ? chalk.gray("—")
794
+ : chalk.red("✗");
795
+ logger.log(` ${flag} ${out.id} (${out.status})`);
796
+ },
797
+ });
798
+ const color =
799
+ result.status === "completed"
800
+ ? chalk.green
801
+ : result.status === "partial"
802
+ ? chalk.yellow
803
+ : chalk.red;
804
+ logger.log(color(`\nWorkflow ${result.status}.\n`));
805
+ } catch (err) {
806
+ logger.error(err.message);
807
+ process.exit(1);
808
+ }
809
+ });
810
+
811
+ // cowork learning — analyze historical runs
812
+ const learning = cowork
813
+ .command("learning")
814
+ .description("Analyze Cowork history for stats, recommendations, failures");
815
+
816
+ learning
817
+ .command("stats")
818
+ .description("Per-template aggregate stats across all runs")
819
+ .option("--json", "Output as JSON")
820
+ .action(async (options) => {
821
+ const { loadHistory, computeTemplateStats } =
822
+ await import("../lib/cowork-learning.js");
823
+ const stats = computeTemplateStats(loadHistory(process.cwd()));
824
+ if (options.json) {
825
+ console.log(JSON.stringify(stats, null, 2));
826
+ return;
827
+ }
828
+ if (stats.length === 0) {
829
+ logger.log(chalk.gray("No history yet. Run some tasks first."));
830
+ return;
831
+ }
832
+ logger.log(chalk.bold(`\nTemplate stats (${stats.length}):\n`));
833
+ for (const s of stats) {
834
+ const pct = Math.round(s.successRate * 100);
835
+ logger.log(
836
+ ` ${chalk.cyan(s.templateId)} runs=${s.runs} ok=${s.successes} fail=${s.failures} ${pct}% avgTok=${s.avgTokens} avgIter=${s.avgIterations}`,
837
+ );
838
+ if (s.topTools.length) {
839
+ logger.log(
840
+ chalk.gray(
841
+ ` tools: ${s.topTools.map((t) => `${t.tool}(${t.count})`).join(", ")}`,
842
+ ),
843
+ );
844
+ }
845
+ }
846
+ logger.log("");
847
+ });
848
+
849
+ learning
850
+ .command("recommend <message...>")
851
+ .description("Recommend the best template for a new user message")
852
+ .option("--min-runs <n>", "Only consider templates with ≥N past runs", "1")
853
+ .option("--json", "Output as JSON")
854
+ .action(async (messageParts, options) => {
855
+ const { loadHistory, recommendTemplate } =
856
+ await import("../lib/cowork-learning.js");
857
+ const message = messageParts.join(" ");
858
+ const rec = recommendTemplate(message, loadHistory(process.cwd()), {
859
+ minRuns: parseInt(options.minRuns, 10) || 1,
860
+ });
861
+ if (options.json) {
862
+ console.log(JSON.stringify(rec, null, 2));
863
+ return;
864
+ }
865
+ if (!rec) {
866
+ logger.log(
867
+ chalk.gray("No recommendation — no overlapping history found."),
868
+ );
869
+ return;
870
+ }
871
+ logger.log(chalk.bold(`\nRecommended: ${chalk.cyan(rec.templateId)}`));
872
+ logger.log(
873
+ chalk.gray(` score: ${rec.score} confidence: ${rec.confidence}`),
874
+ );
875
+ for (const r of rec.reasons) logger.log(chalk.gray(` - ${r}`));
876
+ logger.log("");
877
+ });
878
+
879
+ learning
880
+ .command("failures")
881
+ .description("Group failures by template with common summaries")
882
+ .option("--limit <n>", "Max examples per template", "3")
883
+ .option("--json", "Output as JSON")
884
+ .action(async (options) => {
885
+ const { loadHistory, summarizeFailures } =
886
+ await import("../lib/cowork-learning.js");
887
+ const out = summarizeFailures(loadHistory(process.cwd()), {
888
+ limit: parseInt(options.limit, 10) || 3,
889
+ });
890
+ if (options.json) {
891
+ console.log(JSON.stringify(out, null, 2));
892
+ return;
893
+ }
894
+ if (out.length === 0) {
895
+ logger.log(chalk.green("✓ No failures in history."));
896
+ return;
897
+ }
898
+ logger.log(chalk.bold(`\nFailures by template:\n`));
899
+ for (const g of out) {
900
+ logger.log(` ${chalk.red(g.templateId)} failures=${g.failureCount}`);
901
+ for (const cs of g.commonSummaries) {
902
+ logger.log(chalk.gray(` × ${cs.count} ${cs.summary}`));
903
+ }
904
+ }
905
+ logger.log("");
906
+ });
907
+
908
+ learning
909
+ .command("suggest")
910
+ .description("Suggest systemPromptExtension patches from failure history")
911
+ .option("--json", "Output as JSON")
912
+ .action(async (options) => {
913
+ const { loadHistory, suggestPromptPatch } =
914
+ await import("../lib/cowork-learning.js");
915
+ const patches = suggestPromptPatch(loadHistory(process.cwd()));
916
+ if (options.json) {
917
+ console.log(JSON.stringify(patches, null, 2));
918
+ return;
919
+ }
920
+ if (patches.length === 0) {
921
+ logger.log(
922
+ chalk.gray(
923
+ "No patch suggestions — not enough history (need ≥10 runs and ≥3 failures per template).",
924
+ ),
925
+ );
926
+ return;
927
+ }
928
+ logger.log(chalk.bold(`\nSuggested patches (${patches.length}):\n`));
929
+ for (const p of patches) {
930
+ const color =
931
+ p.confidence === "high"
932
+ ? chalk.red
933
+ : p.confidence === "medium"
934
+ ? chalk.yellow
935
+ : chalk.gray;
936
+ logger.log(
937
+ ` ${chalk.cyan(p.templateId)} ${color(p.confidence)} runs=${p.runs} failures=${p.failures} (${Math.round(p.failureRate * 100)}%)`,
938
+ );
939
+ logger.log(chalk.gray(` ${p.patch}`));
940
+ }
941
+ logger.log(
942
+ chalk.dim(
943
+ "\n Apply with: cc cowork learning apply <templateId> (writes to user-templates/)",
944
+ ),
945
+ );
946
+ logger.log("");
947
+ });
948
+
949
+ learning
950
+ .command("apply <templateId>")
951
+ .description("Apply a suggested patch to the user-templates layer")
952
+ .option("--json", "Output as JSON")
953
+ .action(async (templateId, options) => {
954
+ const { loadHistory, suggestPromptPatch, applyPromptPatch } =
955
+ await import("../lib/cowork-learning.js");
956
+ const patches = suggestPromptPatch(loadHistory(process.cwd()));
957
+ const match = patches.find((p) => p.templateId === templateId);
958
+ if (!match) {
959
+ logger.error(
960
+ `No qualifying patch for template '${templateId}'. Run 'cowork learning suggest' to see available patches.`,
961
+ );
962
+ process.exit(1);
963
+ }
964
+ const result = applyPromptPatch(process.cwd(), match);
965
+ if (options.json) {
966
+ console.log(JSON.stringify(result, null, 2));
967
+ return;
968
+ }
969
+ logger.log(
970
+ chalk.green(
971
+ `✓ Applied patch to ${chalk.cyan(result.templateId)} (${result.file}).`,
972
+ ),
973
+ );
974
+ });
975
+
293
976
  // cowork status — show collaboration state
294
977
  cowork
295
978
  .command("status")
@@ -306,6 +989,18 @@ export function registerCoworkCommand(program) {
306
989
  logger.log(
307
990
  ` ${chalk.cyan("cowork analyze <path>")} Code analysis (style/knowledge-graph/decisions)`,
308
991
  );
992
+ logger.log(
993
+ ` ${chalk.cyan("cowork cron list|add|remove|run")} Schedule recurring Cowork tasks`,
994
+ );
995
+ logger.log(
996
+ ` ${chalk.cyan("cowork learning stats|recommend|failures")} Analyze run history`,
997
+ );
998
+ logger.log(
999
+ ` ${chalk.cyan("cowork workflow list|show|add|remove|run")} DAG of chained Cowork tasks`,
1000
+ );
1001
+ logger.log(
1002
+ ` ${chalk.cyan("cowork share export-template|export-result|import|verify")} Signed packet exchange`,
1003
+ );
309
1004
  logger.log("");
310
1005
  logger.log(
311
1006
  chalk.gray(