claude-multi-session 1.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -9,27 +9,52 @@
9
9
  * or: npx claude-multi-session <command> [options]
10
10
  *
11
11
  * Commands:
12
- * spawn Start a new streaming session
13
- * send Send a message to an existing session
14
- * resume Restore and resume a stopped/paused session
15
- * pause Pause a session (keeps process alive)
16
- * fork Branch off a session into a new one
17
- * stop Gracefully stop a session
18
- * kill Force kill a session
19
- * status Show detailed session info
20
- * output Get last response text
21
- * list List all sessions
22
- * history Show full interaction history
23
- * delete Permanently remove a session
24
- * batch Spawn multiple sessions from JSON file
25
- * cleanup Remove old sessions
26
- * delegate Smart task delegation with safety + control loop
27
- * continue Send follow-up to a delegated session
28
- * setup Register MCP server with Claude Code
29
- * help Show this help
12
+ * spawn Start a new streaming session
13
+ * send Send a message to an existing session
14
+ * resume Restore and resume a stopped/paused session
15
+ * pause Pause a session (keeps process alive)
16
+ * fork Branch off a session into a new one
17
+ * stop Gracefully stop a session
18
+ * kill Force kill a session
19
+ * status Show detailed session info
20
+ * output Get last response text
21
+ * list List all sessions
22
+ * history Show full interaction history
23
+ * delete Permanently remove a session
24
+ * batch Spawn multiple sessions from JSON file
25
+ * cleanup Remove old sessions
26
+ * delegate Smart task delegation with safety + control loop
27
+ * continue Send follow-up to a delegated session
28
+ * team-roster Show team members
29
+ * team-send Send message to teammate
30
+ * team-broadcast Send to all teammates
31
+ * team-inbox Check inbox
32
+ * artifact-publish Publish artifact
33
+ * artifact-get Read artifact
34
+ * artifact-list List artifacts
35
+ * contract-create Create contract
36
+ * contract-list List contracts
37
+ * contract-start Start contract
38
+ * pipeline-create Create pipeline
39
+ * pipeline-list List pipelines
40
+ * snapshot-create Create snapshot
41
+ * snapshot-list List snapshots
42
+ * snapshot-rollback Rollback to snapshot
43
+ * setup Register MCP server with Claude Code
44
+ * help Show this help
30
45
  */
31
46
 
32
47
  const { SessionManager, Delegate } = require('../src/index');
48
+ const TeamHub = require('../src/team-hub');
49
+ const ArtifactStore = require('../src/artifact-store');
50
+ const ContractStore = require('../src/contract-store');
51
+ const PipelineEngine = require('../src/pipeline-engine');
52
+ const SnapshotEngine = require('../src/snapshot-engine');
53
+ const SessionSnapshot = require('../src/session-snapshot');
54
+ const BriefingGenerator = require('../src/briefing-generator');
55
+ const StaleDetector = require('../src/stale-detector');
56
+ const DecisionJournal = require('../src/decision-journal');
57
+ const PatternRegistry = require('../src/pattern-registry');
33
58
  const fs = require('fs');
34
59
  const path = require('path');
35
60
 
@@ -488,6 +513,922 @@ function cmdCleanup(mgr, flags) {
488
513
  }
489
514
  }
490
515
 
516
+ // ============================================================================
517
+ // Team/Artifact/Contract/Pipeline/Snapshot Commands
518
+ // ============================================================================
519
+
520
+ /**
521
+ * TEAM-ROSTER — Show team members
522
+ */
523
+ function cmdTeamRoster(flags) {
524
+ const team = flags.team || 'default';
525
+ const hub = new TeamHub(team);
526
+ const roster = hub.getRoster();
527
+
528
+ console.log(`\n=== Team Roster: ${team} ===\n`);
529
+ console.log(`${'Name'.padEnd(25)} ${'Role'.padEnd(20)} ${'Status'.padEnd(12)} ${'Task'.padEnd(30)} ${'Last Seen'}`);
530
+ console.log('-'.repeat(120));
531
+
532
+ for (const member of roster) {
533
+ console.log(
534
+ `${truncate(member.name, 24).padEnd(25)} ` +
535
+ `${truncate(member.role || '-', 19).padEnd(20)} ` +
536
+ `${(member.status || 'offline').padEnd(12)} ` +
537
+ `${truncate(member.task || '-', 29).padEnd(30)} ` +
538
+ `${timeAgo(member.lastSeen)}`
539
+ );
540
+ }
541
+
542
+ console.log(`\nTotal: ${roster.length}\n`);
543
+ }
544
+
545
+ /**
546
+ * TEAM-SEND — Send message to teammate
547
+ */
548
+ function cmdTeamSend(flags) {
549
+ if (!flags.from) { console.error('Error: --from required'); process.exit(1); }
550
+ if (!flags.to) { console.error('Error: --to required'); process.exit(1); }
551
+ if (!flags.message) { console.error('Error: --message required'); process.exit(1); }
552
+
553
+ const team = flags.team || 'default';
554
+ const hub = new TeamHub(team);
555
+
556
+ hub.sendDirect(flags.from, flags.to, flags.message, flags.priority || 'normal');
557
+ console.log(`Message sent from "${flags.from}" to "${flags.to}".`);
558
+ }
559
+
560
+ /**
561
+ * TEAM-BROADCAST — Send to all teammates
562
+ */
563
+ function cmdTeamBroadcast(flags) {
564
+ if (!flags.from) { console.error('Error: --from required'); process.exit(1); }
565
+ if (!flags.message) { console.error('Error: --message required'); process.exit(1); }
566
+
567
+ const team = flags.team || 'default';
568
+ const hub = new TeamHub(team);
569
+
570
+ hub.sendBroadcast(flags.from, flags.message, flags.priority || 'normal');
571
+ console.log(`Broadcast sent from "${flags.from}" to all team members.`);
572
+ }
573
+
574
+ /**
575
+ * TEAM-INBOX — Check inbox
576
+ */
577
+ function cmdTeamInbox(flags) {
578
+ if (!flags.name) { console.error('Error: --name required'); process.exit(1); }
579
+
580
+ const team = flags.team || 'default';
581
+ const hub = new TeamHub(team);
582
+ const markRead = flags['mark-read'] !== undefined ? flags['mark-read'] : true;
583
+ const limit = flags.limit ? parseInt(flags.limit) : 20;
584
+
585
+ const messages = hub.getInbox(flags.name, markRead, limit);
586
+
587
+ console.log(`\n=== Inbox: ${flags.name} (${messages.length} messages) ===\n`);
588
+
589
+ if (messages.length === 0) {
590
+ console.log('No messages.\n');
591
+ return;
592
+ }
593
+
594
+ for (const msg of messages) {
595
+ console.log(`[${msg.priority || 'normal'}] From: ${msg.from} | ${timeAgo(msg.timestamp)}`);
596
+ console.log(` ${msg.content}`);
597
+ if (msg.messageId) console.log(` ID: ${msg.messageId}`);
598
+ console.log('');
599
+ }
600
+ }
601
+
602
+ /**
603
+ * ARTIFACT-PUBLISH — Publish artifact
604
+ */
605
+ function cmdArtifactPublish(flags) {
606
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
607
+ if (!flags.type) { console.error('Error: --type required'); process.exit(1); }
608
+ if (!flags.name) { console.error('Error: --name required'); process.exit(1); }
609
+ if (!flags.data) { console.error('Error: --data required (JSON string)'); process.exit(1); }
610
+
611
+ const team = flags.team || 'default';
612
+ const store = new ArtifactStore(team);
613
+
614
+ let data;
615
+ try {
616
+ data = JSON.parse(flags.data);
617
+ } catch (e) {
618
+ console.error(`Invalid JSON in --data: ${e.message}`);
619
+ process.exit(1);
620
+ }
621
+
622
+ const artifact = store.publish(flags.id, {
623
+ type: flags.type,
624
+ name: flags.name,
625
+ data: data,
626
+ publisher: flags.publisher,
627
+ summary: flags.summary,
628
+ tags: flags.tags ? flags.tags.split(',') : [],
629
+ derivedFrom: flags['derived-from'] ? flags['derived-from'].split(',') : [],
630
+ });
631
+
632
+ console.log(`Artifact published: ${flags.id} (version ${artifact.version})`);
633
+ }
634
+
635
+ /**
636
+ * ARTIFACT-GET — Read artifact
637
+ */
638
+ function cmdArtifactGet(flags) {
639
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
640
+
641
+ const team = flags.team || 'default';
642
+ const store = new ArtifactStore(team);
643
+
644
+ const artifact = store.get(flags.id, flags.version ? parseInt(flags.version) : undefined);
645
+
646
+ if (!artifact) {
647
+ console.log(`Artifact "${flags.id}" not found.`);
648
+ return;
649
+ }
650
+
651
+ console.log(`\n=== Artifact: ${artifact.artifactId} (v${artifact.version}) ===\n`);
652
+ console.log(`Type: ${artifact.type}`);
653
+ console.log(`Name: ${artifact.name}`);
654
+ console.log(`Publisher: ${artifact.publisher || 'unknown'}`);
655
+ console.log(`Published: ${artifact.timestamp}`);
656
+ if (artifact.summary) console.log(`Summary: ${artifact.summary}`);
657
+ if (artifact.tags && artifact.tags.length > 0) console.log(`Tags: ${artifact.tags.join(', ')}`);
658
+ console.log('\nData:');
659
+ console.log(JSON.stringify(artifact.data, null, 2));
660
+ console.log('');
661
+ }
662
+
663
+ /**
664
+ * ARTIFACT-LIST — List artifacts
665
+ */
666
+ function cmdArtifactList(flags) {
667
+ const team = flags.team || 'default';
668
+ const store = new ArtifactStore(team);
669
+
670
+ const artifacts = store.list(flags.type, flags.publisher, flags.tag);
671
+
672
+ console.log(`\n=== Artifacts (${artifacts.length}) ===\n`);
673
+
674
+ if (artifacts.length === 0) {
675
+ console.log('No artifacts.\n');
676
+ return;
677
+ }
678
+
679
+ console.log(`${'ID'.padEnd(30)} ${'Type'.padEnd(20)} ${'Version'.padEnd(8)} ${'Publisher'.padEnd(15)} ${'Published'}`);
680
+ console.log('-'.repeat(100));
681
+
682
+ for (const a of artifacts) {
683
+ console.log(
684
+ `${truncate(a.artifactId, 29).padEnd(30)} ` +
685
+ `${truncate(a.type, 19).padEnd(20)} ` +
686
+ `${String(a.version).padEnd(8)} ` +
687
+ `${truncate(a.publisher || '-', 14).padEnd(15)} ` +
688
+ `${timeAgo(a.timestamp)}`
689
+ );
690
+ }
691
+
692
+ console.log('');
693
+ }
694
+
695
+ /**
696
+ * CONTRACT-CREATE — Create contract
697
+ */
698
+ function cmdContractCreate(flags) {
699
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
700
+ if (!flags.title) { console.error('Error: --title required'); process.exit(1); }
701
+ if (!flags.assignee) { console.error('Error: --assignee required'); process.exit(1); }
702
+ if (!flags.assigner) { console.error('Error: --assigner required'); process.exit(1); }
703
+
704
+ const team = flags.team || 'default';
705
+ const store = new ContractStore(team);
706
+
707
+ const contract = store.create(flags.id, {
708
+ title: flags.title,
709
+ assignee: flags.assignee,
710
+ assigner: flags.assigner,
711
+ description: flags.description,
712
+ inputs: flags.inputs ? JSON.parse(flags.inputs) : {},
713
+ expectedOutputs: flags['expected-outputs'] ? JSON.parse(flags['expected-outputs']) : [],
714
+ acceptanceCriteria: flags['acceptance-criteria'] ? JSON.parse(flags['acceptance-criteria']) : [],
715
+ dependencies: flags.dependencies ? flags.dependencies.split(',') : [],
716
+ priority: flags.priority || 'normal',
717
+ timeoutMs: flags['timeout-ms'] ? parseInt(flags['timeout-ms']) : null,
718
+ });
719
+
720
+ console.log(`Contract created: ${flags.id} (status: ${contract.status})`);
721
+ }
722
+
723
+ /**
724
+ * CONTRACT-LIST — List contracts
725
+ */
726
+ function cmdContractList(flags) {
727
+ const team = flags.team || 'default';
728
+ const store = new ContractStore(team);
729
+
730
+ const contracts = store.list(flags.status, flags.assignee, flags.assigner);
731
+
732
+ console.log(`\n=== Contracts (${contracts.length}) ===\n`);
733
+
734
+ if (contracts.length === 0) {
735
+ console.log('No contracts.\n');
736
+ return;
737
+ }
738
+
739
+ console.log(`${'ID'.padEnd(25)} ${'Title'.padEnd(30)} ${'Status'.padEnd(15)} ${'Assignee'.padEnd(15)} ${'Created'}`);
740
+ console.log('-'.repeat(110));
741
+
742
+ for (const c of contracts) {
743
+ console.log(
744
+ `${truncate(c.contractId, 24).padEnd(25)} ` +
745
+ `${truncate(c.title, 29).padEnd(30)} ` +
746
+ `${(c.status || 'pending').padEnd(15)} ` +
747
+ `${truncate(c.assignee, 14).padEnd(15)} ` +
748
+ `${timeAgo(c.created)}`
749
+ );
750
+ }
751
+
752
+ console.log('');
753
+ }
754
+
755
+ /**
756
+ * CONTRACT-START — Start contract
757
+ */
758
+ function cmdContractStart(flags) {
759
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
760
+
761
+ const team = flags.team || 'default';
762
+ const store = new ContractStore(team);
763
+
764
+ const contract = store.start(flags.id);
765
+ console.log(`Contract "${flags.id}" started (status: ${contract.status})`);
766
+ }
767
+
768
+ /**
769
+ * PIPELINE-CREATE — Create pipeline
770
+ */
771
+ function cmdPipelineCreate(flags) {
772
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
773
+ if (!flags.rules) { console.error('Error: --rules required (JSON string)'); process.exit(1); }
774
+
775
+ const team = flags.team || 'default';
776
+ const engine = new PipelineEngine(team);
777
+
778
+ let rules;
779
+ try {
780
+ rules = JSON.parse(flags.rules);
781
+ } catch (e) {
782
+ console.error(`Invalid JSON in --rules: ${e.message}`);
783
+ process.exit(1);
784
+ }
785
+
786
+ const pipeline = engine.create(flags.id, rules, flags.owner);
787
+ console.log(`Pipeline created: ${flags.id} (enabled: ${pipeline.enabled})`);
788
+ }
789
+
790
+ /**
791
+ * PIPELINE-LIST — List pipelines
792
+ */
793
+ function cmdPipelineList(flags) {
794
+ const team = flags.team || 'default';
795
+ const engine = new PipelineEngine(team);
796
+
797
+ const pipelines = engine.list(flags.owner);
798
+
799
+ console.log(`\n=== Pipelines (${pipelines.length}) ===\n`);
800
+
801
+ if (pipelines.length === 0) {
802
+ console.log('No pipelines.\n');
803
+ return;
804
+ }
805
+
806
+ console.log(`${'ID'.padEnd(25)} ${'Enabled'.padEnd(10)} ${'Rules'.padEnd(8)} ${'Executed'.padEnd(10)} ${'Owner'}`);
807
+ console.log('-'.repeat(80));
808
+
809
+ for (const p of pipelines) {
810
+ console.log(
811
+ `${truncate(p.pipelineId, 24).padEnd(25)} ` +
812
+ `${(p.enabled ? 'yes' : 'no').padEnd(10)} ` +
813
+ `${String(p.rules.length).padEnd(8)} ` +
814
+ `${String(p.executionCount || 0).padEnd(10)} ` +
815
+ `${p.owner || '-'}`
816
+ );
817
+ }
818
+
819
+ console.log('');
820
+ }
821
+
822
+ /**
823
+ * SNAPSHOT-CREATE — Create snapshot
824
+ */
825
+ function cmdSnapshotCreate(flags) {
826
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
827
+
828
+ const team = flags.team || 'default';
829
+ const engine = new SnapshotEngine(team);
830
+
831
+ const snapshot = engine.createSnapshot(flags.id, flags.label, flags.description);
832
+ console.log(`Snapshot created: ${flags.id} at ${snapshot.timestamp}`);
833
+ }
834
+
835
+ /**
836
+ * SNAPSHOT-LIST — List snapshots
837
+ */
838
+ function cmdSnapshotList(flags) {
839
+ const team = flags.team || 'default';
840
+ const engine = new SnapshotEngine(team);
841
+
842
+ const snapshots = engine.listSnapshots();
843
+
844
+ console.log(`\n=== Snapshots (${snapshots.length}) ===\n`);
845
+
846
+ if (snapshots.length === 0) {
847
+ console.log('No snapshots.\n');
848
+ return;
849
+ }
850
+
851
+ console.log(`${'ID'.padEnd(25)} ${'Label'.padEnd(30)} ${'Created'}`);
852
+ console.log('-'.repeat(70));
853
+
854
+ for (const s of snapshots) {
855
+ console.log(
856
+ `${truncate(s.snapshotId, 24).padEnd(25)} ` +
857
+ `${truncate(s.label || '-', 29).padEnd(30)} ` +
858
+ `${timeAgo(s.timestamp)}`
859
+ );
860
+ }
861
+
862
+ console.log('');
863
+ }
864
+
865
+ /**
866
+ * SNAPSHOT-ROLLBACK — Rollback to snapshot
867
+ */
868
+ function cmdSnapshotRollback(flags) {
869
+ if (!flags.id) { console.error('Error: --id required'); process.exit(1); }
870
+
871
+ const team = flags.team || 'default';
872
+ const engine = new SnapshotEngine(team);
873
+
874
+ engine.rollback(flags.id, flags['preserve-artifacts'] !== false);
875
+ console.log(`Rolled back to snapshot: ${flags.id}`);
876
+ }
877
+
878
+ // ============================================================================
879
+ // Session Continuity Commands (Layer 0)
880
+ // ============================================================================
881
+
882
+ /**
883
+ * SNAPSHOT — Capture a session snapshot
884
+ */
885
+ function cmdSnapshot(flags) {
886
+ if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
887
+
888
+ const snap = new SessionSnapshot(flags['project-path']);
889
+ const sessionName = flags.session || 'cli-session';
890
+ const taskSummary = flags.task || 'Work in progress';
891
+
892
+ const result = snap.capture(sessionName, {
893
+ taskSummary: taskSummary,
894
+ activeFiles: flags.files ? flags.files.split(',') : [],
895
+ openQuestions: flags.questions ? flags.questions.split(',') : []
896
+ });
897
+
898
+ console.log(`Snapshot captured: ${result.snapshotId}`);
899
+ console.log(` Session: ${sessionName}`);
900
+ console.log(` Captured at: ${result.capturedAt}`);
901
+ console.log(` Files tracked: ${result.workingContext?.activeFiles?.length || 0}`);
902
+ }
903
+
904
+ /**
905
+ * BRIEFING — Generate session briefing
906
+ */
907
+ function cmdBriefing(flags) {
908
+ if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
909
+
910
+ const gen = new BriefingGenerator(flags['project-path']);
911
+ const maxTokens = flags['max-tokens'] ? parseInt(flags['max-tokens']) : 4000;
912
+ const includeDecisions = flags['include-decisions'] !== false;
913
+ const includePatterns = flags['include-patterns'] !== false;
914
+
915
+ const result = gen.generate({
916
+ maxTokens: maxTokens,
917
+ includeDecisions: includeDecisions,
918
+ includePatterns: includePatterns
919
+ });
920
+
921
+ console.log('\n' + result.markdown);
922
+ }
923
+
924
+ /**
925
+ * STALE-CHECK — Check if context is stale
926
+ */
927
+ function cmdStaleCheck(flags) {
928
+ if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
929
+
930
+ const detector = new StaleDetector(flags['project-path']);
931
+ const result = detector.check();
932
+
933
+ console.log(`\n=== Stale Context Check ===\n`);
934
+ console.log(`Status: ${result.isStale ? 'STALE' : 'FRESH'}`);
935
+
936
+ if (result.warnings && result.warnings.length > 0) {
937
+ console.log(`\nWarnings:`);
938
+ result.warnings.forEach(w => console.log(` - ${w}`));
939
+ }
940
+
941
+ if (result.changedFiles && result.changedFiles.length > 0) {
942
+ console.log(`\nChanged files since last snapshot: ${result.changedFiles.length}`);
943
+ }
944
+
945
+ console.log('');
946
+ }
947
+
948
+ /**
949
+ * DECISIONS — List decisions
950
+ */
951
+ function cmdDecisions(flags) {
952
+ if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
953
+
954
+ const journal = new DecisionJournal(flags['project-path']);
955
+ const limit = flags.limit ? parseInt(flags.limit) : 50;
956
+ const tag = flags.tag;
957
+
958
+ const decisions = journal.list({ limit: limit, tag: tag });
959
+
960
+ console.log(`\n=== Decisions (${decisions.length}) ===\n`);
961
+
962
+ if (decisions.length === 0) {
963
+ console.log('No decisions recorded.\n');
964
+ return;
965
+ }
966
+
967
+ for (const d of decisions) {
968
+ console.log(`[${d.createdAt || 'unknown'}]`);
969
+ console.log(` Decision: ${d.decision}`);
970
+ if (d.reason) console.log(` Reason: ${d.reason}`);
971
+ if (d.tags && d.tags.length > 0) console.log(` Tags: ${d.tags.join(', ')}`);
972
+ console.log('');
973
+ }
974
+ }
975
+
976
+ /**
977
+ * PATTERNS — List patterns
978
+ */
979
+ function cmdPatterns(flags) {
980
+ if (!flags['project-path']) { console.error('Error: --project-path required'); process.exit(1); }
981
+
982
+ const registry = new PatternRegistry(flags['project-path']);
983
+ const tag = flags.tag;
984
+
985
+ const patterns = registry.list({ tag: tag });
986
+
987
+ console.log(`\n=== Patterns (${patterns.length}) ===\n`);
988
+
989
+ if (patterns.length === 0) {
990
+ console.log('No patterns recorded.\n');
991
+ return;
992
+ }
993
+
994
+ for (const p of patterns) {
995
+ console.log(`[${p.id}] ${p.rule}`);
996
+ if (p.context) console.log(` Context: ${p.context}`);
997
+ if (p.example) console.log(` Example: ${p.example}`);
998
+ if (p.tags && p.tags.length > 0) console.log(` Tags: ${p.tags.join(', ')}`);
999
+ console.log('');
1000
+ }
1001
+ }
1002
+
1003
+ // ============================================================================
1004
+ // Session Continuity Automation Commands
1005
+ // ============================================================================
1006
+
1007
+ /**
1008
+ * CONTINUITY-SETUP — Install Claude Code hooks for automatic session continuity.
1009
+ *
1010
+ * Writes 4 hook entries to ~/.claude/settings.json so that:
1011
+ * - SessionStart automatically injects a diff-aware briefing
1012
+ * - Stop quietly checkpoints in the background
1013
+ * - PreCompact saves context before compaction
1014
+ * - SessionEnd captures a final snapshot on exit
1015
+ */
1016
+ function cmdContinuitySetup() {
1017
+ const os = require('os');
1018
+ const crypto = require('crypto');
1019
+
1020
+ // Resolve the absolute path to our hook entry point script
1021
+ const hookScript = path.resolve(__dirname, 'continuity-hook.js');
1022
+
1023
+ // Path to Claude Code's settings file
1024
+ const claudeDir = path.join(os.homedir(), '.claude');
1025
+ const settingsPath = path.join(claudeDir, 'settings.json');
1026
+
1027
+ // Create ~/.claude/ directory if it doesn't exist
1028
+ if (!fs.existsSync(claudeDir)) {
1029
+ fs.mkdirSync(claudeDir, { recursive: true });
1030
+ }
1031
+
1032
+ // Read existing settings (or start fresh)
1033
+ let settings = {};
1034
+ if (fs.existsSync(settingsPath)) {
1035
+ try {
1036
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
1037
+ } catch (e) {
1038
+ // If settings file is corrupted, start fresh
1039
+ settings = {};
1040
+ }
1041
+ }
1042
+
1043
+ // Make sure hooks object exists
1044
+ if (!settings.hooks) {
1045
+ settings.hooks = {};
1046
+ }
1047
+
1048
+ // The hook command uses the absolute path to our entry point
1049
+ // On Windows, we need forward slashes in the JSON string for safety
1050
+ const hookScriptEscaped = hookScript.replace(/\\/g, '\\\\');
1051
+ const hookCmd = `node "${hookScriptEscaped}"`;
1052
+
1053
+ // Define the 4 hook configurations we need to install
1054
+ const hookConfigs = {
1055
+ SessionStart: {
1056
+ matcher: '',
1057
+ hooks: [{
1058
+ type: 'command',
1059
+ command: `${hookCmd} SessionStart`,
1060
+ timeout: 5
1061
+ }]
1062
+ },
1063
+ Stop: {
1064
+ matcher: '',
1065
+ hooks: [{
1066
+ type: 'command',
1067
+ command: `${hookCmd} Stop`,
1068
+ async: true,
1069
+ timeout: 10
1070
+ }]
1071
+ },
1072
+ PreCompact: {
1073
+ matcher: '',
1074
+ hooks: [{
1075
+ type: 'command',
1076
+ command: `${hookCmd} PreCompact`,
1077
+ timeout: 10
1078
+ }]
1079
+ },
1080
+ SessionEnd: {
1081
+ matcher: '',
1082
+ hooks: [{
1083
+ type: 'command',
1084
+ command: `${hookCmd} SessionEnd`,
1085
+ async: true,
1086
+ timeout: 15
1087
+ }]
1088
+ }
1089
+ };
1090
+
1091
+ // Check if we're updating existing hooks (contains continuity-hook.js)
1092
+ let isUpdate = false;
1093
+
1094
+ // Merge hooks into settings — don't overwrite existing hooks from other tools
1095
+ for (const [eventName, newHookEntry] of Object.entries(hookConfigs)) {
1096
+ if (!settings.hooks[eventName]) {
1097
+ // No existing hooks for this event — create the array
1098
+ settings.hooks[eventName] = [newHookEntry];
1099
+ } else {
1100
+ // Existing hooks — check if our hook is already installed
1101
+ const existingArray = settings.hooks[eventName];
1102
+ let found = false;
1103
+
1104
+ for (let i = 0; i < existingArray.length; i++) {
1105
+ const entry = existingArray[i];
1106
+ // Check if any hook command in this entry contains our script
1107
+ if (entry.hooks && Array.isArray(entry.hooks)) {
1108
+ for (let j = 0; j < entry.hooks.length; j++) {
1109
+ if (entry.hooks[j].command && entry.hooks[j].command.includes('continuity-hook')) {
1110
+ // Update the existing hook with new path
1111
+ existingArray[i] = newHookEntry;
1112
+ found = true;
1113
+ isUpdate = true;
1114
+ break;
1115
+ }
1116
+ }
1117
+ }
1118
+ if (found) break;
1119
+ }
1120
+
1121
+ if (!found) {
1122
+ // Our hook isn't installed yet — add it to the array
1123
+ existingArray.push(newHookEntry);
1124
+ }
1125
+ }
1126
+ }
1127
+
1128
+ // Write the settings back using atomic write (write to temp, then rename)
1129
+ const tmpPath = settingsPath + '.tmp.' + Date.now();
1130
+ fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), 'utf-8');
1131
+ fs.renameSync(tmpPath, settingsPath);
1132
+
1133
+ // Print success output
1134
+ if (isUpdate) {
1135
+ console.log(`\n Session Continuity hooks updated in ~/.claude/settings.json`);
1136
+ console.log(` (Hook script path updated to: ${hookScript})\n`);
1137
+ } else {
1138
+ console.log(`\n Session Continuity hooks installed to ~/.claude/settings.json\n`);
1139
+ console.log(` Hooks configured:`);
1140
+ console.log(` SessionStart -> Auto-inject session briefing (timeout: 5s)`);
1141
+ console.log(` Stop -> Background checkpoint (async, timeout: 10s)`);
1142
+ console.log(` PreCompact -> Critical save before compaction (timeout: 10s)`);
1143
+ console.log(` SessionEnd -> Final snapshot on exit (async, timeout: 15s)`);
1144
+ console.log(`\n Hook script: ${hookScript}`);
1145
+ console.log(`\n Continuity is now active for ALL projects.`);
1146
+ console.log(` Start a Claude Code session, do some work, exit, and start again.`);
1147
+ console.log(` The next session will automatically receive a briefing about what changed.\n`);
1148
+ }
1149
+ }
1150
+
1151
+ /**
1152
+ * CONTINUITY-UNSETUP — Remove continuity hooks from ~/.claude/settings.json.
1153
+ */
1154
+ function cmdContinuityUnsetup() {
1155
+ const os = require('os');
1156
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
1157
+
1158
+ // If settings file doesn't exist, nothing to remove
1159
+ if (!fs.existsSync(settingsPath)) {
1160
+ console.log('No settings file found at ~/.claude/settings.json — nothing to remove.');
1161
+ return;
1162
+ }
1163
+
1164
+ // Read existing settings
1165
+ let settings;
1166
+ try {
1167
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
1168
+ } catch (e) {
1169
+ console.error('Error reading settings file:', e.message);
1170
+ return;
1171
+ }
1172
+
1173
+ if (!settings.hooks) {
1174
+ console.log('No hooks found in settings — nothing to remove.');
1175
+ return;
1176
+ }
1177
+
1178
+ let removedCount = 0;
1179
+
1180
+ // Remove any hook entries that contain 'continuity-hook' in their command
1181
+ for (const eventName of Object.keys(settings.hooks)) {
1182
+ const entries = settings.hooks[eventName];
1183
+ if (!Array.isArray(entries)) continue;
1184
+
1185
+ // Filter out entries that have continuity-hook commands
1186
+ settings.hooks[eventName] = entries.filter(entry => {
1187
+ if (entry.hooks && Array.isArray(entry.hooks)) {
1188
+ const hasContinuity = entry.hooks.some(h =>
1189
+ h.command && h.command.includes('continuity-hook')
1190
+ );
1191
+ if (hasContinuity) {
1192
+ removedCount++;
1193
+ return false; // Remove this entry
1194
+ }
1195
+ }
1196
+ return true; // Keep this entry
1197
+ });
1198
+
1199
+ // Clean up empty arrays
1200
+ if (settings.hooks[eventName].length === 0) {
1201
+ delete settings.hooks[eventName];
1202
+ }
1203
+ }
1204
+
1205
+ // Clean up empty hooks object
1206
+ if (Object.keys(settings.hooks).length === 0) {
1207
+ delete settings.hooks;
1208
+ }
1209
+
1210
+ // Write back
1211
+ const tmpPath = settingsPath + '.tmp.' + Date.now();
1212
+ fs.writeFileSync(tmpPath, JSON.stringify(settings, null, 2), 'utf-8');
1213
+ fs.renameSync(tmpPath, settingsPath);
1214
+
1215
+ if (removedCount > 0) {
1216
+ console.log(`\n Removed ${removedCount} continuity hook(s) from ~/.claude/settings.json`);
1217
+ console.log(` Session Continuity automation is now disabled.\n`);
1218
+ } else {
1219
+ console.log('No continuity hooks found in settings — nothing to remove.');
1220
+ }
1221
+ }
1222
+
1223
+ /**
1224
+ * CONTINUITY-RESET — Delete ALL continuity data for a project.
1225
+ */
1226
+ function cmdContinuityReset(flags) {
1227
+ if (!flags['project-path']) {
1228
+ console.error('Error: --project-path required');
1229
+ process.exit(1);
1230
+ }
1231
+
1232
+ const os = require('os');
1233
+ const crypto = require('crypto');
1234
+
1235
+ const projectPath = flags['project-path'];
1236
+
1237
+ // Compute the same project hash used by all continuity modules
1238
+ const projectHash = crypto
1239
+ .createHash('sha256')
1240
+ .update(projectPath)
1241
+ .digest('hex')
1242
+ .slice(0, 16);
1243
+
1244
+ // Primary storage location: ~/.claude-multi-session/continuity/{hash}/
1245
+ const continuityDir = path.join(
1246
+ os.homedir(),
1247
+ '.claude-multi-session',
1248
+ 'continuity',
1249
+ projectHash
1250
+ );
1251
+
1252
+ let deleted = false;
1253
+
1254
+ // Delete the primary continuity directory recursively
1255
+ if (fs.existsSync(continuityDir)) {
1256
+ fs.rmSync(continuityDir, { recursive: true, force: true });
1257
+ deleted = true;
1258
+ }
1259
+
1260
+ // Also check the old BriefingGenerator path (in case data was stored there before the fix)
1261
+ const oldBriefingsDir = path.join(
1262
+ os.homedir(),
1263
+ '.claude',
1264
+ 'multi-session',
1265
+ projectHash,
1266
+ 'briefings'
1267
+ );
1268
+ if (fs.existsSync(oldBriefingsDir)) {
1269
+ fs.rmSync(oldBriefingsDir, { recursive: true, force: true });
1270
+ deleted = true;
1271
+
1272
+ // Clean up the parent directory if it's now empty
1273
+ const oldParent = path.join(os.homedir(), '.claude', 'multi-session', projectHash);
1274
+ try {
1275
+ const remaining = fs.readdirSync(oldParent);
1276
+ if (remaining.length === 0) {
1277
+ fs.rmdirSync(oldParent);
1278
+ }
1279
+ } catch (e) {
1280
+ // Ignore — parent might not exist
1281
+ }
1282
+ }
1283
+
1284
+ if (deleted) {
1285
+ console.log(`\n Continuity data cleared for ${projectPath}`);
1286
+ console.log(` Project hash: ${projectHash}\n`);
1287
+ } else {
1288
+ console.log(`\n No continuity data found for ${projectPath}`);
1289
+ console.log(` Project hash: ${projectHash}\n`);
1290
+ }
1291
+ }
1292
+
1293
+ /**
1294
+ * CONTINUITY-PRUNE — Keep only the N most recent snapshots for a project.
1295
+ */
1296
+ function cmdContinuityPrune(flags) {
1297
+ if (!flags['project-path']) {
1298
+ console.error('Error: --project-path required');
1299
+ process.exit(1);
1300
+ }
1301
+
1302
+ const projectPath = flags['project-path'];
1303
+ const keep = flags.keep ? parseInt(flags.keep) : 10;
1304
+
1305
+ // Instantiate SessionSnapshot to get the snapshots directory
1306
+ const snap = new SessionSnapshot(projectPath);
1307
+ const snapshotsDir = path.join(snap.getProjectDir(), 'snapshots');
1308
+
1309
+ if (!fs.existsSync(snapshotsDir)) {
1310
+ console.log('No snapshots directory found — nothing to prune.');
1311
+ return;
1312
+ }
1313
+
1314
+ // Read all snapshot files
1315
+ const files = fs.readdirSync(snapshotsDir)
1316
+ .filter(f => f.startsWith('snap_') && f.endsWith('.json'));
1317
+
1318
+ if (files.length <= keep) {
1319
+ console.log(`\n Only ${files.length} snapshot(s) found — nothing to prune (keeping ${keep}).\n`);
1320
+ return;
1321
+ }
1322
+
1323
+ // Sort ascending by filename (contains timestamp) — oldest first
1324
+ files.sort();
1325
+
1326
+ // Delete the oldest files, keep the most recent
1327
+ const toDelete = files.slice(0, files.length - keep);
1328
+ let deletedCount = 0;
1329
+
1330
+ for (const file of toDelete) {
1331
+ try {
1332
+ fs.unlinkSync(path.join(snapshotsDir, file));
1333
+ deletedCount++;
1334
+ } catch (e) {
1335
+ console.error(` Warning: could not delete ${file}: ${e.message}`);
1336
+ }
1337
+ }
1338
+
1339
+ console.log(`\n Pruned ${deletedCount} snapshot(s), kept ${keep} most recent.\n`);
1340
+ }
1341
+
1342
+ /**
1343
+ * CONTINUITY-STATUS — Show continuity status at a glance.
1344
+ */
1345
+ function cmdContinuityStatus(flags) {
1346
+ if (!flags['project-path']) {
1347
+ console.error('Error: --project-path required');
1348
+ process.exit(1);
1349
+ }
1350
+
1351
+ const os = require('os');
1352
+
1353
+ const projectPath = flags['project-path'];
1354
+
1355
+ // Get snapshot info
1356
+ const snap = new SessionSnapshot(projectPath);
1357
+ const latest = snap.getLatest();
1358
+ const allSnapshots = snap.list(100);
1359
+ const projectHash = snap.getProjectHash();
1360
+
1361
+ // Get decision count
1362
+ const journal = new DecisionJournal(projectPath);
1363
+ const decisions = journal.list({});
1364
+
1365
+ // Get pattern count
1366
+ const registry = new PatternRegistry(projectPath);
1367
+ const patterns = registry.list({});
1368
+
1369
+ // Check staleness
1370
+ let staleStatus = 'N/A';
1371
+ let changedCount = 0;
1372
+ if (latest) {
1373
+ try {
1374
+ const detector = new StaleDetector(projectPath);
1375
+ const staleResult = detector.check();
1376
+ if (staleResult.isStale) {
1377
+ changedCount = staleResult.totalFileChanges || 0;
1378
+ staleStatus = `STALE -- ${staleResult.warnings.length} warning(s), ${changedCount} file change(s)`;
1379
+ } else {
1380
+ staleStatus = 'FRESH';
1381
+ }
1382
+ } catch (e) {
1383
+ staleStatus = 'Error checking: ' + e.message;
1384
+ }
1385
+ }
1386
+
1387
+ // Check if hooks are installed
1388
+ let hooksInstalled = false;
1389
+ const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
1390
+ if (fs.existsSync(settingsPath)) {
1391
+ try {
1392
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
1393
+ if (settings.hooks) {
1394
+ // Check if any hook command contains continuity-hook
1395
+ const hookStr = JSON.stringify(settings.hooks);
1396
+ hooksInstalled = hookStr.includes('continuity-hook');
1397
+ }
1398
+ } catch (e) {
1399
+ // Ignore
1400
+ }
1401
+ }
1402
+
1403
+ // Format last snapshot time
1404
+ let lastSnapshotStr = 'None';
1405
+ let lastTrigger = 'N/A';
1406
+ let oldestStr = 'N/A';
1407
+ if (latest) {
1408
+ const capturedAt = new Date(latest.capturedAt);
1409
+ const ago = timeAgo(latest.capturedAt);
1410
+ lastSnapshotStr = `${capturedAt.toISOString()} (${ago})`;
1411
+ lastTrigger = latest.capturedBy || 'unknown';
1412
+ }
1413
+ if (allSnapshots.length > 0) {
1414
+ const oldest = allSnapshots[allSnapshots.length - 1];
1415
+ oldestStr = timeAgo(oldest.capturedAt);
1416
+ }
1417
+
1418
+ // Print the status
1419
+ console.log(`\n=== Session Continuity Status ===\n`);
1420
+ console.log(`Project: ${projectPath}`);
1421
+ console.log(`Project hash: ${projectHash}`);
1422
+ console.log(`Last snapshot: ${lastSnapshotStr}`);
1423
+ console.log(`Trigger: ${lastTrigger}`);
1424
+ console.log(`Snapshots stored: ${allSnapshots.length} (oldest: ${oldestStr})`);
1425
+ console.log(`Decisions logged: ${decisions.length}`);
1426
+ console.log(`Patterns saved: ${patterns.length}`);
1427
+ console.log(`Context status: ${staleStatus}`);
1428
+ console.log(`Hooks installed: ${hooksInstalled ? 'Yes (in ~/.claude/settings.json)' : 'No'}`);
1429
+ console.log('');
1430
+ }
1431
+
491
1432
  function cmdHelp() {
492
1433
  console.log(`
493
1434
  ╔══════════════════════════════════════════════════════════════╗
@@ -558,6 +1499,103 @@ COMMANDS:
558
1499
  [--global] [--local] [--uninstall]
559
1500
  [--guide] [--no-guide]
560
1501
 
1502
+ TEAM COMMANDS:
1503
+
1504
+ team-roster Show team members
1505
+ [--team <name>]
1506
+
1507
+ team-send Send message to teammate
1508
+ --from <name> --to <name> --message <text>
1509
+ [--priority low|normal|high|urgent] [--team <name>]
1510
+
1511
+ team-broadcast Send to all teammates
1512
+ --from <name> --message <text>
1513
+ [--priority low|normal|high|urgent] [--team <name>]
1514
+
1515
+ team-inbox Check inbox
1516
+ --name <name> [--mark-read] [--limit <n>] [--team <name>]
1517
+
1518
+ ARTIFACT COMMANDS:
1519
+
1520
+ artifact-publish Publish artifact
1521
+ --id <id> --type <type> --name <name> --data <json>
1522
+ [--publisher <name>] [--summary <text>] [--tags <t1,t2>]
1523
+ [--derived-from <id1,id2>] [--team <name>]
1524
+
1525
+ artifact-get Read artifact
1526
+ --id <id> [--version <n>] [--team <name>]
1527
+
1528
+ artifact-list List artifacts
1529
+ [--type <type>] [--publisher <name>] [--tag <tag>] [--team <name>]
1530
+
1531
+ CONTRACT COMMANDS:
1532
+
1533
+ contract-create Create contract
1534
+ --id <id> --title <text> --assignee <name> --assigner <name>
1535
+ [--description <text>] [--priority low|normal|high|urgent]
1536
+ [--team <name>]
1537
+
1538
+ contract-list List contracts
1539
+ [--status pending|ready|in_progress|completed|failed]
1540
+ [--assignee <name>] [--assigner <name>] [--team <name>]
1541
+
1542
+ contract-start Start contract
1543
+ --id <id> [--team <name>]
1544
+
1545
+ PIPELINE COMMANDS:
1546
+
1547
+ pipeline-create Create pipeline
1548
+ --id <id> --rules <json> [--owner <name>] [--team <name>]
1549
+
1550
+ pipeline-list List pipelines
1551
+ [--owner <name>] [--team <name>]
1552
+
1553
+ SNAPSHOT COMMANDS:
1554
+
1555
+ snapshot-create Create snapshot
1556
+ --id <id> [--label <text>] [--description <text>] [--team <name>]
1557
+
1558
+ snapshot-list List snapshots
1559
+ [--team <name>]
1560
+
1561
+ snapshot-rollback Rollback to snapshot
1562
+ --id <id> [--preserve-artifacts] [--team <name>]
1563
+
1564
+ SESSION CONTINUITY COMMANDS (Layer 0):
1565
+
1566
+ snapshot Capture session snapshot
1567
+ --project-path <path> [--session <name>] [--task <summary>]
1568
+ [--files <file1,file2>] [--questions <q1,q2>]
1569
+
1570
+ briefing Generate session briefing
1571
+ --project-path <path> [--max-tokens <n>]
1572
+ [--include-decisions] [--include-patterns]
1573
+
1574
+ stale-check Check if context is stale
1575
+ --project-path <path>
1576
+
1577
+ decisions List decisions
1578
+ --project-path <path> [--limit <n>] [--tag <tag>]
1579
+
1580
+ patterns List patterns
1581
+ --project-path <path> [--tag <tag>]
1582
+
1583
+ CONTINUITY AUTOMATION COMMANDS:
1584
+
1585
+ continuity-setup Install hooks for automatic session continuity
1586
+ (writes to ~/.claude/settings.json)
1587
+
1588
+ continuity-unsetup Remove continuity hooks from settings
1589
+
1590
+ continuity-reset Delete ALL continuity data for a project
1591
+ --project-path <path>
1592
+
1593
+ continuity-prune Keep only N most recent snapshots
1594
+ --project-path <path> [--keep <n>]
1595
+
1596
+ continuity-status Show continuity status at a glance
1597
+ --project-path <path>
1598
+
561
1599
  HOW IT WORKS:
562
1600
 
563
1601
  Unlike the resume approach (new process per message), this system
@@ -659,6 +1697,31 @@ async function main() {
659
1697
  cleanup: () => cmdCleanup(mgr, flags),
660
1698
  delegate: () => cmdDelegate(mgr, flags),
661
1699
  continue: () => cmdContinue(mgr, flags),
1700
+ 'team-roster': () => cmdTeamRoster(flags),
1701
+ 'team-send': () => cmdTeamSend(flags),
1702
+ 'team-broadcast': () => cmdTeamBroadcast(flags),
1703
+ 'team-inbox': () => cmdTeamInbox(flags),
1704
+ 'artifact-publish': () => cmdArtifactPublish(flags),
1705
+ 'artifact-get': () => cmdArtifactGet(flags),
1706
+ 'artifact-list': () => cmdArtifactList(flags),
1707
+ 'contract-create': () => cmdContractCreate(flags),
1708
+ 'contract-list': () => cmdContractList(flags),
1709
+ 'contract-start': () => cmdContractStart(flags),
1710
+ 'pipeline-create': () => cmdPipelineCreate(flags),
1711
+ 'pipeline-list': () => cmdPipelineList(flags),
1712
+ 'snapshot-create': () => cmdSnapshotCreate(flags),
1713
+ 'snapshot-list': () => cmdSnapshotList(flags),
1714
+ 'snapshot-rollback': () => cmdSnapshotRollback(flags),
1715
+ snapshot: () => cmdSnapshot(flags),
1716
+ briefing: () => cmdBriefing(flags),
1717
+ 'stale-check': () => cmdStaleCheck(flags),
1718
+ decisions: () => cmdDecisions(flags),
1719
+ patterns: () => cmdPatterns(flags),
1720
+ 'continuity-setup': () => cmdContinuitySetup(),
1721
+ 'continuity-unsetup': () => cmdContinuityUnsetup(),
1722
+ 'continuity-reset': () => cmdContinuityReset(flags),
1723
+ 'continuity-prune': () => cmdContinuityPrune(flags),
1724
+ 'continuity-status': () => cmdContinuityStatus(flags),
662
1725
  setup: () => cmdSetup(flags),
663
1726
  help: () => cmdHelp(),
664
1727
  };