@suwujs/codex-vault 0.1.0 → 0.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/README.md CHANGED
@@ -25,14 +25,22 @@ Agent: "You're working on the API redesign. Last session you decided
25
25
 
26
26
  ```bash
27
27
  # From your project directory:
28
- git clone https://github.com/sukbearai/codex-vault.git /tmp/codex-vault
29
- bash /tmp/codex-vault/plugin/install.sh # integrated mode — installs vault + hooks into your project
28
+ npx @suwujs/codex-vault init
30
29
  claude # or: codex
31
30
  ```
32
31
 
33
32
  Fill in `vault/brain/North Star.md` with your goals, then start talking.
34
33
 
34
+ <details>
35
+ <summary>Alternative: install from source</summary>
36
+
37
+ ```bash
38
+ git clone https://github.com/sukbearai/codex-vault.git /tmp/codex-vault
39
+ bash /tmp/codex-vault/plugin/install.sh
40
+ ```
41
+
35
42
  > **Standalone mode**: run `install.sh` from inside the codex-vault repo itself to use `vault/` as the working directory.
43
+ </details>
36
44
 
37
45
  ## How It Works
38
46
 
@@ -167,7 +175,6 @@ See [docs/usage.md](docs/usage.md) — 7 real scenarios from first session to pr
167
175
  ## Inspired By
168
176
 
169
177
  - [llm-wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) by Karpathy — the pattern
170
- - [obsidian-mind](https://github.com/sukbearai/obsidian-mind) — full-featured implementation for engineer performance tracking
171
178
 
172
179
  ## License
173
180
 
package/README.zh-CN.md CHANGED
@@ -62,14 +62,22 @@ LLM agent 每次对话都从零开始。上次做的决策忘了,讨论过的
62
62
 
63
63
  ```bash
64
64
  # 在你的项目目录下:
65
- git clone https://github.com/sukbearai/codex-vault.git /tmp/codex-vault
66
- bash /tmp/codex-vault/plugin/install.sh # 集成模式 — 自动安装 vault + hooks
65
+ npx @suwujs/codex-vault init
67
66
  claude # 或: codex
68
67
  ```
69
68
 
70
69
  填好 `vault/brain/North Star.md`,开始对话。
71
70
 
71
+ <details>
72
+ <summary>其他安装方式:从源码安装</summary>
73
+
74
+ ```bash
75
+ git clone https://github.com/sukbearai/codex-vault.git /tmp/codex-vault
76
+ bash /tmp/codex-vault/plugin/install.sh
77
+ ```
78
+
72
79
  > **独立模式**:在 codex-vault 仓库内运行 `install.sh`,以 `vault/` 为工作目录。
80
+ </details>
73
81
 
74
82
  ## 工作原理
75
83
 
@@ -148,7 +156,6 @@ vault/
148
156
  ## 灵感来源
149
157
 
150
158
  - [llm-wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) by Karpathy — 这个模式的起源
151
- - [obsidian-mind](https://github.com/sukbearai/obsidian-mind) — 面向工程师效能追踪的完整实现
152
159
 
153
160
  ## License
154
161
 
package/bin/cli.js CHANGED
@@ -28,8 +28,11 @@ switch (cmd) {
28
28
  break;
29
29
 
30
30
  case 'upgrade':
31
+ cmdUpgrade();
32
+ break;
33
+
31
34
  case 'uninstall':
32
- console.log(`"${cmd}" is coming in v0.2.0.`);
35
+ cmdUninstall();
33
36
  break;
34
37
 
35
38
  default:
@@ -43,20 +46,23 @@ function printHelp() {
43
46
 
44
47
  Usage:
45
48
  codex-vault init Install vault into current directory (default)
46
- codex-vault upgrade Upgrade vault (coming in v0.2.0)
47
- codex-vault uninstall Remove vault (coming in v0.2.0)
49
+ codex-vault upgrade Upgrade existing vault to latest version
50
+ codex-vault uninstall Remove vault integration (preserves vault data)
48
51
  codex-vault -v, --version Print version
49
52
  codex-vault -h, --help Print this help`);
50
53
  }
51
54
 
52
- function runInit() {
53
- // Check bash availability
55
+ function assertBash() {
54
56
  const bashCheck = spawnSync('bash', ['--version'], { stdio: 'ignore' });
55
57
  if (bashCheck.error) {
56
58
  console.error('Error: bash is not available.');
57
59
  console.error('On Windows, please install Git Bash or WSL.');
58
60
  process.exit(1);
59
61
  }
62
+ }
63
+
64
+ function runInit() {
65
+ assertBash();
60
66
 
61
67
  // Check if already installed
62
68
  const versionFile = path.join(process.cwd(), 'vault', '.codex-vault', 'version');
@@ -88,3 +94,344 @@ function runInit() {
88
94
  fs.writeFileSync(versionFile, VERSION + '\n');
89
95
  console.log(`\ncodex-vault v${VERSION} installed successfully.`);
90
96
  }
97
+
98
+ function cmdUpgrade() {
99
+ assertBash();
100
+
101
+ // Check if installed
102
+ const versionFile = path.join(process.cwd(), 'vault', '.codex-vault', 'version');
103
+ if (!fs.existsSync(versionFile)) {
104
+ console.error('codex-vault is not installed in this directory.');
105
+ console.error('Run "codex-vault init" first.');
106
+ process.exit(1);
107
+ }
108
+
109
+ const installedVersion = fs.readFileSync(versionFile, 'utf8').trim();
110
+
111
+ // Already up to date?
112
+ if (installedVersion === VERSION) {
113
+ console.log(`Already at v${VERSION}.`);
114
+ return;
115
+ }
116
+
117
+ console.log(`Upgrading: v${installedVersion} → v${VERSION}`);
118
+
119
+ // Backup hooks directory
120
+ const hooksDir = path.join(process.cwd(), 'vault', '.codex-vault', 'hooks');
121
+ const backupDir = path.join(process.cwd(), 'vault', '.codex-vault', `backup-${installedVersion}`, 'hooks');
122
+ const backupRoot = path.join(process.cwd(), 'vault', '.codex-vault', `backup-${installedVersion}`);
123
+
124
+ if (fs.existsSync(hooksDir)) {
125
+ fs.mkdirSync(backupDir, { recursive: true });
126
+ fs.cpSync(hooksDir, backupDir, { recursive: true });
127
+ console.log(`Hooks backed up to vault/.codex-vault/backup-${installedVersion}/hooks/`);
128
+ }
129
+
130
+ // Run install.sh
131
+ const result = spawnSync('bash', [INSTALL_SH], {
132
+ cwd: process.cwd(),
133
+ stdio: 'inherit',
134
+ });
135
+
136
+ if (result.error) {
137
+ console.error('Failed to run install.sh:', result.error.message);
138
+ if (fs.existsSync(backupRoot)) {
139
+ console.error(`Backup available at: vault/.codex-vault/backup-${installedVersion}/`);
140
+ }
141
+ process.exit(1);
142
+ }
143
+
144
+ if (result.status !== 0) {
145
+ console.error('install.sh exited with errors.');
146
+ if (fs.existsSync(backupRoot)) {
147
+ console.error(`Backup available at: vault/.codex-vault/backup-${installedVersion}/`);
148
+ }
149
+ process.exit(result.status);
150
+ }
151
+
152
+ // Update version file
153
+ fs.writeFileSync(versionFile, VERSION + '\n');
154
+
155
+ console.log(`\nUpgraded to v${VERSION} successfully.`);
156
+ if (fs.existsSync(backupRoot)) {
157
+ console.log(`Backup at: vault/.codex-vault/backup-${installedVersion}/`);
158
+ }
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // uninstall
163
+ // ---------------------------------------------------------------------------
164
+
165
+ function cmdUninstall() {
166
+ const cwd = process.cwd();
167
+ const versionFile = path.join(cwd, 'vault', '.codex-vault', 'version');
168
+
169
+ // 1. Check installation
170
+ if (!fs.existsSync(versionFile)) {
171
+ console.error('codex-vault is not installed in this directory.');
172
+ console.error('Nothing to uninstall.');
173
+ process.exit(1);
174
+ }
175
+
176
+ const installedVersion = fs.readFileSync(versionFile, 'utf8').trim();
177
+ console.log(`Uninstalling codex-vault v${installedVersion}...`);
178
+ console.log('NOTE: vault/ data (brain/, work/, sources/) is preserved.\n');
179
+
180
+ // 2. Remove vault/.codex-vault/ (hooks + version + backups)
181
+ const codexVaultDir = path.join(cwd, 'vault', '.codex-vault');
182
+ if (fs.existsSync(codexVaultDir)) {
183
+ fs.rmSync(codexVaultDir, { recursive: true, force: true });
184
+ console.log(' [x] Removed vault/.codex-vault/');
185
+ }
186
+
187
+ // 3. Clean .claude/settings.json
188
+ cleanHooksJson(path.join(cwd, '.claude', 'settings.json'), '.claude/settings.json');
189
+
190
+ // 4. Clean .codex/hooks.json
191
+ cleanHooksJson(path.join(cwd, '.codex', 'hooks.json'), '.codex/hooks.json');
192
+
193
+ // 5. Clean .codex/config.toml
194
+ cleanCodexConfigToml(cwd);
195
+
196
+ // 6. Remove skills
197
+ const SKILL_NAMES = ['dump', 'ingest', 'recall', 'wrap-up'];
198
+ removeSkills(path.join(cwd, '.claude'), '.claude', SKILL_NAMES);
199
+ removeSkills(path.join(cwd, '.codex'), '.codex', SKILL_NAMES);
200
+
201
+ // 7. Clean CLAUDE.md
202
+ cleanInstructionFile(path.join(cwd, 'CLAUDE.md'), 'CLAUDE.md');
203
+
204
+ // 8. Clean AGENTS.md
205
+ cleanAgentsMd(cwd);
206
+
207
+ // Summary
208
+ console.log('\ncodex-vault has been uninstalled.');
209
+ console.log('Vault data preserved at vault/');
210
+ }
211
+
212
+ // ---------------------------------------------------------------------------
213
+ // helpers
214
+ // ---------------------------------------------------------------------------
215
+
216
+ /**
217
+ * Remove codex-vault hook entries from a JSON hooks file
218
+ * (.claude/settings.json or .codex/hooks.json).
219
+ */
220
+ function cleanHooksJson(filePath, label) {
221
+ if (!fs.existsSync(filePath)) return;
222
+
223
+ let data;
224
+ try {
225
+ data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
226
+ } catch (e) {
227
+ console.log(` [!] Could not parse ${label} — skipping (${e.message})`);
228
+ return;
229
+ }
230
+
231
+ if (!data.hooks || typeof data.hooks !== 'object') return;
232
+
233
+ let changed = false;
234
+ for (const event of Object.keys(data.hooks)) {
235
+ const entries = data.hooks[event];
236
+ if (!Array.isArray(entries)) continue;
237
+
238
+ const filtered = entries.filter((entry) => {
239
+ const hooks = entry.hooks || [];
240
+ // Keep the entry only if none of its hook commands belong to codex-vault
241
+ const isVaultEntry = hooks.some(
242
+ (h) => typeof h.command === 'string' && h.command.includes('codex-vault/hooks/')
243
+ );
244
+ return !isVaultEntry;
245
+ });
246
+
247
+ if (filtered.length !== entries.length) {
248
+ changed = true;
249
+ if (filtered.length === 0) {
250
+ delete data.hooks[event];
251
+ } else {
252
+ data.hooks[event] = filtered;
253
+ }
254
+ }
255
+ }
256
+
257
+ if (!changed) return;
258
+
259
+ // If hooks object is now empty, remove it
260
+ if (Object.keys(data.hooks).length === 0) {
261
+ delete data.hooks;
262
+ }
263
+
264
+ // If entire JSON is now empty, delete the file
265
+ if (Object.keys(data).length === 0) {
266
+ fs.unlinkSync(filePath);
267
+ console.log(` [x] Removed ${label} (empty after cleanup)`);
268
+ cleanEmptyParents(path.dirname(filePath));
269
+ } else {
270
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
271
+ console.log(` [x] Cleaned codex-vault hooks from ${label}`);
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Remove codex_hooks feature flag from .codex/config.toml.
277
+ */
278
+ function cleanCodexConfigToml(cwd) {
279
+ const filePath = path.join(cwd, '.codex', 'config.toml');
280
+ if (!fs.existsSync(filePath)) return;
281
+
282
+ let content = fs.readFileSync(filePath, 'utf8');
283
+ const original = content;
284
+
285
+ // Remove lines containing codex_hooks
286
+ content = content.replace(/^.*codex_hooks.*\n?/gm, '');
287
+
288
+ // Remove empty [features] section (only whitespace/empty lines after header until next section or EOF)
289
+ content = content.replace(/^\[features\]\s*\n(?=\[|$)/gm, '');
290
+ // Also handle [features] at very end of file with nothing after it
291
+ content = content.replace(/^\[features\]\s*$/gm, '');
292
+
293
+ // Trim trailing whitespace
294
+ content = content.replace(/\n+$/, content.trim() === '' ? '' : '\n');
295
+
296
+ if (content === original) return;
297
+
298
+ if (content.trim() === '') {
299
+ fs.unlinkSync(filePath);
300
+ console.log(' [x] Removed .codex/config.toml (empty after cleanup)');
301
+ cleanEmptyParents(path.dirname(filePath));
302
+ } else {
303
+ fs.writeFileSync(filePath, content);
304
+ console.log(' [x] Cleaned codex_hooks from .codex/config.toml');
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Remove codex-vault skill directories from an agent config dir.
310
+ */
311
+ function removeSkills(agentDir, label, skillNames) {
312
+ const skillsDir = path.join(agentDir, 'skills');
313
+ if (!fs.existsSync(skillsDir)) return;
314
+
315
+ let removed = 0;
316
+ for (const name of skillNames) {
317
+ const skillDir = path.join(skillsDir, name);
318
+ if (fs.existsSync(skillDir)) {
319
+ fs.rmSync(skillDir, { recursive: true, force: true });
320
+ removed++;
321
+ }
322
+ }
323
+
324
+ if (removed === 0) return;
325
+
326
+ console.log(` [x] Removed ${removed} skills from ${label}/skills/`);
327
+
328
+ // Clean up empty directories
329
+ if (isDirEmpty(skillsDir)) {
330
+ fs.rmdirSync(skillsDir);
331
+ if (isDirEmpty(agentDir)) {
332
+ fs.rmdirSync(agentDir);
333
+ }
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Remove the # Codex-Vault section from CLAUDE.md.
339
+ * The section may be preceded by a --- separator (appended by install.sh).
340
+ * Removes from the separator (or heading) through EOF or the next --- + # heading.
341
+ */
342
+ function cleanInstructionFile(filePath, label) {
343
+ if (!fs.existsSync(filePath)) return;
344
+
345
+ const content = fs.readFileSync(filePath, 'utf8');
346
+ const lines = content.split('\n');
347
+
348
+ // Find the "# Codex-Vault" heading
349
+ let sectionStart = -1;
350
+ for (let i = 0; i < lines.length; i++) {
351
+ if (/^# Codex-Vault/.test(lines[i])) {
352
+ sectionStart = i;
353
+ break;
354
+ }
355
+ }
356
+
357
+ if (sectionStart === -1) return;
358
+
359
+ // Check if preceded by --- separator (possibly with blank lines between)
360
+ let cutStart = sectionStart;
361
+ for (let i = sectionStart - 1; i >= 0; i--) {
362
+ if (lines[i].trim() === '') continue;
363
+ if (lines[i].trim() === '---') {
364
+ cutStart = i;
365
+ }
366
+ break;
367
+ }
368
+
369
+ // Find the end: next --- followed by # heading, or EOF
370
+ let cutEnd = lines.length;
371
+ for (let i = sectionStart + 1; i < lines.length; i++) {
372
+ if (lines[i].trim() === '---' && i + 1 < lines.length && /^# /.test(lines[i + 1])) {
373
+ cutEnd = i;
374
+ break;
375
+ }
376
+ }
377
+
378
+ // Build remaining content
379
+ const before = lines.slice(0, cutStart);
380
+ const after = lines.slice(cutEnd);
381
+ let remaining = before.concat(after).join('\n');
382
+
383
+ // Trim trailing whitespace/newlines
384
+ remaining = remaining.replace(/\s+$/, '');
385
+
386
+ if (remaining === '') {
387
+ fs.unlinkSync(filePath);
388
+ console.log(` [x] Removed ${label} (empty after cleanup)`);
389
+ } else {
390
+ fs.writeFileSync(filePath, remaining + '\n');
391
+ console.log(` [x] Cleaned codex-vault section from ${label}`);
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Handle AGENTS.md: if it's just "@CLAUDE.md", delete it.
397
+ * Otherwise apply the same section-removal logic as CLAUDE.md.
398
+ */
399
+ function cleanAgentsMd(cwd) {
400
+ const filePath = path.join(cwd, 'AGENTS.md');
401
+ if (!fs.existsSync(filePath)) return;
402
+
403
+ const content = fs.readFileSync(filePath, 'utf8');
404
+
405
+ // If content is just @CLAUDE.md (possibly with whitespace), delete the file
406
+ if (content.trim() === '@CLAUDE.md') {
407
+ fs.unlinkSync(filePath);
408
+ console.log(' [x] Removed AGENTS.md (@CLAUDE.md reference)');
409
+ return;
410
+ }
411
+
412
+ // Otherwise, apply the same section-removal logic
413
+ cleanInstructionFile(filePath, 'AGENTS.md');
414
+ }
415
+
416
+ /**
417
+ * Check if a directory is empty.
418
+ */
419
+ function isDirEmpty(dirPath) {
420
+ try {
421
+ const entries = fs.readdirSync(dirPath);
422
+ return entries.length === 0;
423
+ } catch (e) {
424
+ return false;
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Remove empty parent directories up to (but not including) cwd.
430
+ */
431
+ function cleanEmptyParents(dirPath) {
432
+ const cwd = process.cwd();
433
+ if (dirPath === cwd || !dirPath.startsWith(cwd)) return;
434
+ if (isDirEmpty(dirPath)) {
435
+ fs.rmdirSync(dirPath);
436
+ }
437
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suwujs/codex-vault",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Persistent knowledge vault for LLM agents (Claude Code, Codex CLI)",
5
5
  "license": "MIT",
6
6
  "repository": {
package/plugin/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0