clawvault 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,13 +55,16 @@ clawvault search "decision" # BM25 keyword search
55
55
  clawvault vsearch "what did I decide" # Semantic search
56
56
 
57
57
  # Session management
58
- clawvault handoff --working-on "task1" --next "task2" # Before context death
59
- clawvault recap # On session start
58
+ clawvault wake
59
+ clawvault sleep "build wake/sleep commands" --next "run doctor"
60
+ clawvault handoff --working-on "task1" --next "task2" # Manual handoff (advanced)
61
+ clawvault recap # Manual recap (advanced)
60
62
  ```
61
63
 
62
- **Tip:** Set `CLAWVAULT_PATH` environment variable to skip directory walk:
64
+ **Tip:** Set `CLAWVAULT_PATH` to skip directory walk (or use `shell-init`):
63
65
  ```bash
64
66
  echo 'export CLAWVAULT_PATH="$HOME/memory"' >> ~/.bashrc
67
+ eval "$(clawvault shell-init)"
65
68
  ```
66
69
 
67
70
  ## Search: qmd vs memory_search
@@ -135,18 +138,19 @@ clawvault stats # Vault overview
135
138
  ### Session Continuity
136
139
 
137
140
  ```bash
138
- # Before context death (long pause, session end, hitting limits)
139
- clawvault handoff \
140
- --working-on "building CRM, fixing webhook" \
141
+ # Start a session (recover + recap + summary)
142
+ clawvault wake
143
+
144
+ # End a session with a handoff
145
+ clawvault sleep "building CRM, fixing webhook" \
141
146
  --blocked "waiting for API key" \
142
147
  --next "deploy to production" \
143
148
  --decisions "chose Supabase over Firebase" \
144
149
  --feeling "focused"
145
150
 
146
- # On session start
147
- clawvault recap # Full markdown recap
148
- clawvault recap --brief # Token-efficient version
149
- clawvault recap --json # For programmatic use
151
+ # Manual tools (advanced)
152
+ clawvault handoff --working-on "task1" --next "task2"
153
+ clawvault recap --brief # Token-efficient recap
150
154
 
151
155
  # Health check
152
156
  clawvault doctor
@@ -173,15 +177,19 @@ clawvault remember decision "Title" --content "..."
173
177
  clawvault remember lesson "Title" --content "..."
174
178
  \`\`\`
175
179
 
176
- ### Session Handoff
177
- Before context death:
180
+ ### Session Start
181
+ \`\`\`bash
182
+ clawvault wake
183
+ \`\`\`
184
+
185
+ ### Session End
178
186
  \`\`\`bash
179
- clawvault handoff --working-on "..." --next "..."
187
+ clawvault sleep "..." --next "..."
180
188
  \`\`\`
181
189
 
182
- On wake:
190
+ ### Checkpoint (during heavy work)
183
191
  \`\`\`bash
184
- clawvault recap
192
+ clawvault checkpoint --working-on "..." --focus "..." --blocked "..."
185
193
  \`\`\`
186
194
 
187
195
  ### Why qmd over memory_search?
package/bin/clawvault.js CHANGED
@@ -14,7 +14,6 @@ import {
14
14
  ClawVault,
15
15
  createVault,
16
16
  findVault,
17
- hasQmd,
18
17
  QmdUnavailableError,
19
18
  QMD_INSTALL_COMMAND
20
19
  } from '../dist/index.js';
@@ -59,6 +58,28 @@ async function getVault(vaultPath) {
59
58
  return vault;
60
59
  }
61
60
 
61
+ function resolveVaultPath(vaultPath) {
62
+ if (vaultPath) {
63
+ return path.resolve(vaultPath);
64
+ }
65
+ if (process.env.CLAWVAULT_PATH) {
66
+ return path.resolve(process.env.CLAWVAULT_PATH);
67
+ }
68
+ let current = process.cwd();
69
+ while (true) {
70
+ if (fs.existsSync(path.join(current, '.clawvault.json'))) {
71
+ return current;
72
+ }
73
+ const parent = path.dirname(current);
74
+ if (parent === current) {
75
+ console.error(chalk.red('Error: No ClawVault found. Run `clawvault init` first.'));
76
+ console.log(chalk.dim('Tip: Set CLAWVAULT_PATH environment variable to your vault path'));
77
+ process.exit(1);
78
+ }
79
+ current = parent;
80
+ }
81
+ }
82
+
62
83
  async function runQmd(args) {
63
84
  return new Promise((resolve, reject) => {
64
85
  const proc = spawn('qmd', args, { stdio: 'inherit' });
@@ -571,6 +592,117 @@ program
571
592
  }
572
593
  });
573
594
 
595
+ // === WAKE (session start) ===
596
+ program
597
+ .command('wake')
598
+ .description('Start a session (recover + recap + summary)')
599
+ .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include', '3')
600
+ .option('--full', 'Show full recap (default: brief)')
601
+ .option('-v, --vault <path>', 'Vault path')
602
+ .action(async (options) => {
603
+ try {
604
+ const vaultPath = resolveVaultPath(options.vault);
605
+ const { wake } = await import('../dist/commands/wake.js');
606
+ const { formatRecoveryInfo } = await import('../dist/commands/recover.js');
607
+ const result = await wake({
608
+ vaultPath,
609
+ handoffLimit: parseInt(options.handoffLimit),
610
+ brief: !options.full
611
+ });
612
+
613
+ console.log(chalk.cyan('\n🌅 ClawVault Wake\n'));
614
+ console.log(formatRecoveryInfo(result.recovery));
615
+ console.log();
616
+ console.log(chalk.cyan('Recap'));
617
+ console.log(result.recapMarkdown.trim());
618
+ console.log();
619
+ console.log(chalk.green(`You were working on: ${result.summary}`));
620
+
621
+ process.exitCode = result.recovery.died ? 1 : 0;
622
+ } catch (err) {
623
+ if (err instanceof QmdUnavailableError) {
624
+ printQmdMissing();
625
+ process.exit(1);
626
+ }
627
+ console.error(chalk.red(`Error: ${err.message}`));
628
+ process.exit(1);
629
+ }
630
+ });
631
+
632
+ // === SLEEP (session end) ===
633
+ program
634
+ .command('sleep <summary>')
635
+ .description('End a session with a handoff (and optional git commit)')
636
+ .option('-n, --next <items>', 'Next steps (comma-separated)')
637
+ .option('-b, --blocked <items>', 'Blocked items (comma-separated)')
638
+ .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
639
+ .option('-q, --questions <items>', 'Open questions (comma-separated)')
640
+ .option('-f, --feeling <state>', 'Emotional/energy state')
641
+ .option('-s, --session <key>', 'Session key')
642
+ .option('--index', 'Update qmd index after handoff')
643
+ .option('--no-git', 'Skip git commit prompt')
644
+ .option('-v, --vault <path>', 'Vault path')
645
+ .action(async (summary, options) => {
646
+ try {
647
+ const vaultPath = resolveVaultPath(options.vault);
648
+ const { sleep } = await import('../dist/commands/sleep.js');
649
+ const result = await sleep({
650
+ workingOn: summary,
651
+ next: options.next,
652
+ blocked: options.blocked,
653
+ decisions: options.decisions,
654
+ questions: options.questions,
655
+ feeling: options.feeling,
656
+ sessionKey: options.session,
657
+ vaultPath,
658
+ index: options.index,
659
+ git: options.git
660
+ });
661
+
662
+ console.log(chalk.green(`✓ Handoff saved: ${result.document.id}`));
663
+ console.log(chalk.dim(` Path: ${result.document.path}`));
664
+ console.log(chalk.dim(` Working on: ${result.handoff.workingOn.join(', ')}`));
665
+ if (result.handoff.nextSteps.length > 0) {
666
+ console.log(chalk.dim(` Next: ${result.handoff.nextSteps.join(', ')}`));
667
+ } else {
668
+ console.log(chalk.dim(' Next: (none)'));
669
+ }
670
+ if (result.handoff.blocked.length > 0) {
671
+ console.log(chalk.dim(` Blocked: ${result.handoff.blocked.join(', ')}`));
672
+ } else {
673
+ console.log(chalk.dim(' Blocked: (none)'));
674
+ }
675
+ if (result.handoff.decisions?.length) {
676
+ console.log(chalk.dim(` Decisions: ${result.handoff.decisions.join(', ')}`));
677
+ }
678
+ if (result.handoff.openQuestions?.length) {
679
+ console.log(chalk.dim(` Questions: ${result.handoff.openQuestions.join(', ')}`));
680
+ }
681
+ if (result.handoff.feeling) {
682
+ console.log(chalk.dim(` Feeling: ${result.handoff.feeling}`));
683
+ }
684
+ if (options.index) {
685
+ console.log(chalk.dim(' qmd: index updated'));
686
+ }
687
+ if (result.git) {
688
+ if (result.git.committed) {
689
+ console.log(chalk.green(`✓ Git commit created${result.git.message ? `: ${result.git.message}` : ''}`));
690
+ } else if (result.git.skippedReason === 'clean') {
691
+ console.log(chalk.dim(' Git: clean'));
692
+ } else if (result.git.skippedReason === 'declined') {
693
+ console.log(chalk.dim(' Git: commit skipped'));
694
+ }
695
+ }
696
+ } catch (err) {
697
+ if (err instanceof QmdUnavailableError) {
698
+ printQmdMissing();
699
+ process.exit(1);
700
+ }
701
+ console.error(chalk.red(`Error: ${err.message}`));
702
+ process.exit(1);
703
+ }
704
+ });
705
+
574
706
  // === HANDOFF (session bridge) ===
575
707
  program
576
708
  .command('handoff')
@@ -651,6 +783,20 @@ program
651
783
  }
652
784
  });
653
785
 
786
+ // === SHELL INIT ===
787
+ program
788
+ .command('shell-init')
789
+ .description('Output shell integration for ClawVault')
790
+ .action(async () => {
791
+ try {
792
+ const { shellInit } = await import('../dist/commands/shell-init.js');
793
+ console.log(shellInit());
794
+ } catch (err) {
795
+ console.error(chalk.red(`Error: ${err.message}`));
796
+ process.exit(1);
797
+ }
798
+ });
799
+
654
800
  // === TEMPLATE ===
655
801
  const template = program
656
802
  .command('template')
@@ -725,59 +871,39 @@ program
725
871
  .description('Check ClawVault setup health')
726
872
  .option('-v, --vault <path>', 'Vault path')
727
873
  .action(async (options) => {
728
- console.log(chalk.cyan('\n🩺 ClawVault Health Check\n'));
729
-
730
- let issues = 0;
731
-
732
- // Check qmd
733
- if (hasQmd()) {
734
- console.log(chalk.green('✓ qmd installed'));
735
- } else {
736
- console.log(chalk.red('✗ qmd not installed'));
737
- console.log(chalk.dim(` Install: ${QMD_INSTALL_COMMAND}`));
738
- issues++;
739
- }
740
-
741
- // Check vault
742
874
  try {
743
- const vault = await getVault(options.vault);
744
- console.log(chalk.green(`✓ Vault found: ${vault.getPath()}`));
745
-
746
- const stats = await vault.stats();
747
- console.log(chalk.dim(` ${stats.documents} documents, ${stats.links} links`));
748
-
749
- // Check qmd collection
750
- const collection = vault.getQmdCollection();
751
- if (collection) {
752
- console.log(chalk.green(`✓ qmd collection: ${collection}`));
753
- } else {
754
- console.log(chalk.yellow('⚠ No qmd collection configured'));
755
- issues++;
875
+ const { doctor } = await import('../dist/commands/doctor.js');
876
+ const report = await doctor(options.vault);
877
+
878
+ console.log(chalk.cyan('\n🩺 ClawVault Health Check\n'));
879
+ if (report.vaultPath) {
880
+ console.log(chalk.dim(`Vault: ${report.vaultPath}`));
881
+ console.log();
756
882
  }
757
-
758
- // Check for handoffs
759
- const handoffs = await vault.list('handoffs');
760
- if (handoffs.length > 0) {
761
- console.log(chalk.green(`✓ ${handoffs.length} handoff(s) stored`));
762
- } else {
763
- console.log(chalk.yellow('⚠ No handoffs yet — run `clawvault handoff` before context death'));
883
+
884
+ for (const check of report.checks) {
885
+ const prefix = check.status === 'ok'
886
+ ? chalk.green('✓')
887
+ : check.status === 'warn'
888
+ ? chalk.yellow('⚠')
889
+ : chalk.red('');
890
+ const detail = check.detail ? ` — ${check.detail}` : '';
891
+ console.log(`${prefix} ${check.label}${detail}`);
892
+ if (check.hint) {
893
+ console.log(chalk.dim(` ${check.hint}`));
894
+ }
764
895
  }
765
-
766
- // Check for content
767
- if (stats.documents < 5) {
768
- console.log(chalk.yellow('⚠ Vault is sparse — start storing memories!'));
896
+
897
+ const issues = report.warnings + report.errors;
898
+ console.log();
899
+ if (issues === 0) {
900
+ console.log(chalk.green('✅ ClawVault is healthy!\n'));
901
+ } else {
902
+ console.log(chalk.yellow(`⚠ ${issues} issue(s) found\n`));
769
903
  }
770
-
771
904
  } catch (err) {
772
- console.log(chalk.red(`✗ Vault error: ${err.message}`));
773
- issues++;
774
- }
775
-
776
- console.log();
777
- if (issues === 0) {
778
- console.log(chalk.green('✅ ClawVault is healthy!\n'));
779
- } else {
780
- console.log(chalk.yellow(`⚠ ${issues} issue(s) found\n`));
905
+ console.error(chalk.red(`Error: ${err.message}`));
906
+ process.exit(1);
781
907
  }
782
908
  });
783
909
 
@@ -0,0 +1,146 @@
1
+ import {
2
+ checkDirtyDeath,
3
+ clearDirtyFlag
4
+ } from "./chunk-MZZJLQNQ.js";
5
+ import {
6
+ formatAge
7
+ } from "./chunk-7ZRP733D.js";
8
+
9
+ // src/commands/recover.ts
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ async function recover(vaultPath, options = {}) {
13
+ const { clearFlag = false } = options;
14
+ const { died, checkpoint, deathTime } = await checkDirtyDeath(vaultPath);
15
+ if (!died) {
16
+ return {
17
+ died: false,
18
+ deathTime: null,
19
+ checkpoint: null,
20
+ handoffPath: null,
21
+ handoffContent: null,
22
+ recoveryMessage: "No context death detected. Clean startup."
23
+ };
24
+ }
25
+ const handoffsDir = path.join(vaultPath, "handoffs");
26
+ let handoffPath = null;
27
+ let handoffContent = null;
28
+ if (fs.existsSync(handoffsDir)) {
29
+ const files = fs.readdirSync(handoffsDir).filter((f) => f.startsWith("handoff-") && f.endsWith(".md")).sort().reverse();
30
+ if (files.length > 0) {
31
+ handoffPath = path.join(handoffsDir, files[0]);
32
+ handoffContent = fs.readFileSync(handoffPath, "utf-8");
33
+ }
34
+ }
35
+ let message = "\u26A0\uFE0F **CONTEXT DEATH DETECTED**\n\n";
36
+ message += `Your previous session died at ${deathTime}.
37
+
38
+ `;
39
+ if (checkpoint) {
40
+ message += "**Last known state:**\n";
41
+ if (checkpoint.workingOn) {
42
+ message += `- Working on: ${checkpoint.workingOn}
43
+ `;
44
+ }
45
+ if (checkpoint.focus) {
46
+ message += `- Focus: ${checkpoint.focus}
47
+ `;
48
+ }
49
+ if (checkpoint.blocked) {
50
+ message += `- Blocked: ${checkpoint.blocked}
51
+ `;
52
+ }
53
+ message += "\n";
54
+ }
55
+ if (handoffPath) {
56
+ message += `**Last handoff:** ${path.basename(handoffPath)}
57
+ `;
58
+ message += "Review and resume from where you left off.\n";
59
+ } else {
60
+ message += "**No handoff found.** You may have lost context.\n";
61
+ }
62
+ if (clearFlag) {
63
+ await clearDirtyFlag(vaultPath);
64
+ }
65
+ return {
66
+ died: true,
67
+ deathTime,
68
+ checkpoint,
69
+ handoffPath,
70
+ handoffContent,
71
+ recoveryMessage: message
72
+ };
73
+ }
74
+ function formatRecoveryInfo(info, options = {}) {
75
+ const { verbose = false } = options;
76
+ if (!info.died) {
77
+ return "\u2713 Clean startup - no context death detected.";
78
+ }
79
+ let output = "\n\u26A0\uFE0F CONTEXT DEATH DETECTED\n";
80
+ output += "\u2550".repeat(40) + "\n\n";
81
+ output += `Death time: ${info.deathTime}
82
+ `;
83
+ if (info.checkpoint?.timestamp) {
84
+ const age = formatAge(Date.now() - new Date(info.checkpoint.timestamp).getTime());
85
+ output += `Checkpoint: ${info.checkpoint.timestamp} (${age} ago)
86
+ `;
87
+ }
88
+ output += "\n";
89
+ if (info.checkpoint) {
90
+ output += "Last checkpoint:\n";
91
+ if (info.checkpoint.workingOn) {
92
+ output += ` \u2022 Working on: ${info.checkpoint.workingOn}
93
+ `;
94
+ }
95
+ if (info.checkpoint.focus) {
96
+ output += ` \u2022 Focus: ${info.checkpoint.focus}
97
+ `;
98
+ }
99
+ if (info.checkpoint.blocked) {
100
+ output += ` \u2022 Blocked: ${info.checkpoint.blocked}
101
+ `;
102
+ }
103
+ if (info.checkpoint.sessionKey || info.checkpoint.model || info.checkpoint.tokenEstimate !== void 0) {
104
+ output += " \u2022 Session:\n";
105
+ if (info.checkpoint.sessionKey) {
106
+ output += ` - Key: ${info.checkpoint.sessionKey}
107
+ `;
108
+ }
109
+ if (info.checkpoint.model) {
110
+ output += ` - Model: ${info.checkpoint.model}
111
+ `;
112
+ }
113
+ if (info.checkpoint.tokenEstimate !== void 0) {
114
+ output += ` - Token estimate: ${info.checkpoint.tokenEstimate}
115
+ `;
116
+ }
117
+ }
118
+ output += "\n";
119
+ } else {
120
+ output += "No checkpoint data found.\n\n";
121
+ }
122
+ if (info.handoffPath) {
123
+ output += `Last handoff: ${path.basename(info.handoffPath)}
124
+ `;
125
+ } else {
126
+ output += "No handoff found - context may be lost.\n";
127
+ }
128
+ if (verbose) {
129
+ if (info.checkpoint) {
130
+ output += "\nCheckpoint JSON:\n";
131
+ output += JSON.stringify(info.checkpoint, null, 2) + "\n";
132
+ }
133
+ if (info.handoffContent) {
134
+ output += "\nHandoff content:\n";
135
+ output += info.handoffContent.trim() + "\n";
136
+ }
137
+ }
138
+ output += "\n" + "\u2550".repeat(40) + "\n";
139
+ output += "Run `clawvault recap` to see full context.\n";
140
+ return output;
141
+ }
142
+
143
+ export {
144
+ recover,
145
+ formatRecoveryInfo
146
+ };
@@ -0,0 +1,16 @@
1
+ type DoctorStatus = 'ok' | 'warn' | 'error';
2
+ interface DoctorCheck {
3
+ label: string;
4
+ status: DoctorStatus;
5
+ detail?: string;
6
+ hint?: string;
7
+ }
8
+ interface DoctorReport {
9
+ vaultPath?: string;
10
+ checks: DoctorCheck[];
11
+ warnings: number;
12
+ errors: number;
13
+ }
14
+ declare function doctor(vaultPath?: string): Promise<DoctorReport>;
15
+
16
+ export { type DoctorCheck, type DoctorReport, type DoctorStatus, doctor };