mnotes-cli 1.11.0 → 1.12.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.
@@ -68,14 +68,16 @@ const DEFAULT_OPTS = {
68
68
  // T-1: Template generation — hooks
69
69
  // =============================================================
70
70
  (0, vitest_1.describe)("hooks template", () => {
71
- (0, vitest_1.it)("generates SessionStart hook referencing bash script", () => {
71
+ (0, vitest_1.it)("generates SessionStart hook referencing global bash script path", () => {
72
72
  const hooks = (0, hooks_1.generateHooksTemplate)(DEFAULT_OPTS);
73
73
  (0, vitest_1.expect)(hooks.SessionStart).toBeDefined();
74
74
  (0, vitest_1.expect)(hooks.SessionStart).toHaveLength(1);
75
75
  (0, vitest_1.expect)(hooks.SessionStart[0].matcher).toBe("");
76
76
  (0, vitest_1.expect)(hooks.SessionStart[0].hooks).toHaveLength(1);
77
77
  (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].type).toBe("command");
78
- (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).toBe(".claude/hooks/mnotes-session-start.sh");
78
+ // Global path — resolves at generation time via os.homedir()
79
+ (0, vitest_1.expect)(hooks.SessionStart[0].hooks[0].command).toContain(path.join(".claude", "hooks", "mnotes", "scripts", "mnotes-session-start.sh"));
80
+ (0, vitest_1.expect)(path.isAbsolute(hooks.SessionStart[0].hooks[0].command)).toBe(true);
79
81
  });
80
82
  (0, vitest_1.it)("generates hook scripts with correct URL, workspaceId, and Accept headers", () => {
81
83
  const scripts = (0, hooks_1.generateHookScripts)(DEFAULT_OPTS);
@@ -178,29 +180,45 @@ const DEFAULT_OPTS = {
178
180
  // =============================================================
179
181
  (0, vitest_1.describe)("scaffoldItems: hooks", () => {
180
182
  let tmpDir;
183
+ let fakeHome;
184
+ let origHome;
181
185
  (0, vitest_1.beforeEach)(() => {
182
186
  tmpDir = makeTmpDir();
187
+ fakeHome = makeTmpDir();
188
+ origHome = process.env.HOME;
189
+ process.env.HOME = fakeHome;
183
190
  });
184
191
  (0, vitest_1.afterEach)(() => {
185
192
  cleanTmpDir(tmpDir);
193
+ cleanTmpDir(fakeHome);
194
+ if (origHome === undefined)
195
+ delete process.env.HOME;
196
+ else
197
+ process.env.HOME = origHome;
186
198
  });
187
- (0, vitest_1.it)("creates .claude/settings.json with hooks and bash scripts (AC-4)", () => {
199
+ (0, vitest_1.it)("writes scripts to global ~/.claude/hooks/mnotes/scripts/ and settings.json to project (AC-4)", () => {
188
200
  const results = (0, wizard_1.scaffoldItems)(tmpDir, ["hooks"], DEFAULT_OPTS);
189
201
  (0, vitest_1.expect)(results).toHaveLength(1);
190
202
  (0, vitest_1.expect)(results[0].item).toBe("hooks");
191
203
  // 2 bash scripts + settings.json
192
204
  (0, vitest_1.expect)(results[0].filesWritten).toHaveLength(3);
205
+ // settings.json is project-local
193
206
  const settingsPath = path.join(tmpDir, ".claude", "settings.json");
194
207
  (0, vitest_1.expect)(fs.existsSync(settingsPath)).toBe(true);
195
208
  const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
196
209
  (0, vitest_1.expect)(settings.hooks).toBeDefined();
197
210
  (0, vitest_1.expect)(settings.hooks.SessionStart).toBeDefined();
198
211
  (0, vitest_1.expect)(settings.hooks.SessionStart).toHaveLength(1);
199
- // Bash scripts exist and are executable
200
- const startScript = path.join(tmpDir, ".claude", "hooks", "mnotes-session-start.sh");
201
- const stopScript = path.join(tmpDir, ".claude", "hooks", "mnotes-session-stop.sh");
212
+ // Bash scripts live under fake HOME at ~/.claude/hooks/mnotes/scripts/
213
+ const globalScriptsDir = path.join(fakeHome, ".claude", "hooks", "mnotes", "scripts");
214
+ const startScript = path.join(globalScriptsDir, "mnotes-session-start.sh");
215
+ const stopScript = path.join(globalScriptsDir, "mnotes-session-stop.sh");
202
216
  (0, vitest_1.expect)(fs.existsSync(startScript)).toBe(true);
203
217
  (0, vitest_1.expect)(fs.existsSync(stopScript)).toBe(true);
218
+ // Project directory must NOT contain scripts
219
+ (0, vitest_1.expect)(fs.existsSync(path.join(tmpDir, ".claude", "hooks"))).toBe(false);
220
+ // settings.json references the global absolute path
221
+ (0, vitest_1.expect)(settings.hooks.SessionStart[0].hooks[0].command).toBe(startScript);
204
222
  const startContent = fs.readFileSync(startScript, "utf-8");
205
223
  (0, vitest_1.expect)(startContent).toContain("Accept: text/event-stream");
206
224
  (0, vitest_1.expect)(startContent).toContain("Accept: application/json");
@@ -348,11 +366,16 @@ const DEFAULT_OPTS = {
348
366
  // =============================================================
349
367
  (0, vitest_1.describe)("handleClaudeCode with wizard flags", () => {
350
368
  let tmpDir;
369
+ let fakeHome;
370
+ let origHome;
351
371
  let origCwd;
352
372
  let origExit;
353
373
  let exitCode;
354
374
  (0, vitest_1.beforeEach)(() => {
355
375
  tmpDir = makeTmpDir();
376
+ fakeHome = makeTmpDir();
377
+ origHome = process.env.HOME;
378
+ process.env.HOME = fakeHome;
356
379
  origCwd = process.cwd;
357
380
  process.cwd = () => tmpDir;
358
381
  exitCode = undefined;
@@ -366,6 +389,11 @@ const DEFAULT_OPTS = {
366
389
  process.cwd = origCwd;
367
390
  process.exit = origExit;
368
391
  cleanTmpDir(tmpDir);
392
+ cleanTmpDir(fakeHome);
393
+ if (origHome === undefined)
394
+ delete process.env.HOME;
395
+ else
396
+ process.env.HOME = origHome;
369
397
  vitest_1.vi.restoreAllMocks();
370
398
  });
371
399
  (0, vitest_1.it)("--no-wizard does core setup only, no extras (AC-6)", async () => {
@@ -430,10 +458,15 @@ const DEFAULT_OPTS = {
430
458
  // =============================================================
431
459
  (0, vitest_1.describe)("CLI flag integration", () => {
432
460
  let tmpDir;
461
+ let fakeHome;
462
+ let origHome;
433
463
  let origCwd;
434
464
  let origExit;
435
465
  (0, vitest_1.beforeEach)(() => {
436
466
  tmpDir = makeTmpDir();
467
+ fakeHome = makeTmpDir();
468
+ origHome = process.env.HOME;
469
+ process.env.HOME = fakeHome;
437
470
  origCwd = process.cwd;
438
471
  process.cwd = () => tmpDir;
439
472
  origExit = process.exit;
@@ -445,6 +478,11 @@ const DEFAULT_OPTS = {
445
478
  process.cwd = origCwd;
446
479
  process.exit = origExit;
447
480
  cleanTmpDir(tmpDir);
481
+ cleanTmpDir(fakeHome);
482
+ if (origHome === undefined)
483
+ delete process.env.HOME;
484
+ else
485
+ process.env.HOME = origHome;
448
486
  vitest_1.vi.restoreAllMocks();
449
487
  });
450
488
  (0, vitest_1.it)("--no-wizard flag is recognized by commander", async () => {
@@ -102,19 +102,23 @@ function scaffoldItems(dir, items, opts) {
102
102
  return results;
103
103
  }
104
104
  /**
105
- * Merges hooks into `.claude/settings.json` and writes bash scripts to `.claude/hooks/`.
105
+ * Merges hooks into `<project>/.claude/settings.json` and writes bash scripts to
106
+ * `~/.claude/hooks/mnotes/scripts/` (user-global, namespaced under `mnotes`).
107
+ * Scripts live globally so they're shared across projects; settings.json stays
108
+ * project-local so each project opts in independently.
106
109
  * Preserves all existing settings and hooks.
107
110
  */
108
111
  function scaffoldHooks(dir, opts) {
109
112
  const settingsPath = path.join(dir, ".claude", "settings.json");
110
113
  const claudeDir = path.join(dir, ".claude");
111
- const hooksDir = path.join(claudeDir, "hooks");
112
- fs.mkdirSync(hooksDir, { recursive: true });
114
+ const hookScriptsDir = (0, index_1.getHookScriptsDir)();
115
+ fs.mkdirSync(claudeDir, { recursive: true });
116
+ fs.mkdirSync(hookScriptsDir, { recursive: true });
113
117
  const filesWritten = [];
114
- // 1. Write bash scripts to .claude/hooks/
118
+ // 1. Write bash scripts to ~/.claude/hooks/mnotes/scripts/
115
119
  const scripts = (0, index_1.generateHookScripts)(opts);
116
120
  for (const script of scripts) {
117
- const scriptPath = path.join(hooksDir, script.filename);
121
+ const scriptPath = path.join(hookScriptsDir, script.filename);
118
122
  fs.writeFileSync(scriptPath, script.content, { mode: 0o755 });
119
123
  filesWritten.push(scriptPath);
120
124
  }
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * Template for Claude Code session hooks.
3
- * Generates bash scripts in `.claude/hooks/` and hook entries for `.claude/settings.json`.
4
- * Generated by m-notes CLI do not edit manually.
3
+ * Generates bash scripts in `~/.claude/hooks/mnotes/scripts/` (user-global,
4
+ * namespaced under `mnotes`) and hook entries for `.claude/settings.json`
5
+ * (project-local). Generated by m-notes CLI — do not edit manually.
5
6
  */
7
+ /**
8
+ * Absolute path where m-notes hook scripts live (user-global, namespaced).
9
+ * Resolved via function so tests can override `HOME` to point to a tmp dir.
10
+ */
11
+ export declare function getHookScriptsDir(): string;
12
+ /** @deprecated kept for backward compatibility — use `getHookScriptsDir()` so HOME overrides work. */
13
+ export declare const HOOK_SCRIPTS_DIR: string;
6
14
  export interface HooksTemplateOpts {
7
15
  url: string;
8
16
  workspaceId: string;
@@ -29,8 +37,9 @@ export interface HookScript {
29
37
  export declare function generateHookScripts(opts: HooksTemplateOpts): HookScript[];
30
38
  /**
31
39
  * Generates the hooks object to merge into `.claude/settings.json`.
32
- * References bash scripts in `.claude/hooks/` instead of inline commands.
40
+ * References bash scripts in `~/.claude/hooks/mnotes/scripts/` (absolute path
41
+ * so hooks work across projects without per-project script copies).
33
42
  */
34
- export declare function generateHooksTemplate(opts: HooksTemplateOpts): ClaudeCodeHooks;
43
+ export declare function generateHooksTemplate(_opts: HooksTemplateOpts): ClaudeCodeHooks;
35
44
  /** Header comment for the hooks section, used for identification. */
36
45
  export declare const HOOKS_HEADER = "m-notes: auto-generated hooks";
@@ -1,13 +1,59 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Template for Claude Code session hooks.
4
- * Generates bash scripts in `.claude/hooks/` and hook entries for `.claude/settings.json`.
5
- * Generated by m-notes CLI do not edit manually.
4
+ * Generates bash scripts in `~/.claude/hooks/mnotes/scripts/` (user-global,
5
+ * namespaced under `mnotes`) and hook entries for `.claude/settings.json`
6
+ * (project-local). Generated by m-notes CLI — do not edit manually.
6
7
  */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
7
41
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.HOOKS_HEADER = void 0;
42
+ exports.HOOKS_HEADER = exports.HOOK_SCRIPTS_DIR = void 0;
43
+ exports.getHookScriptsDir = getHookScriptsDir;
9
44
  exports.generateHookScripts = generateHookScripts;
10
45
  exports.generateHooksTemplate = generateHooksTemplate;
46
+ const os = __importStar(require("os"));
47
+ const path = __importStar(require("path"));
48
+ /**
49
+ * Absolute path where m-notes hook scripts live (user-global, namespaced).
50
+ * Resolved via function so tests can override `HOME` to point to a tmp dir.
51
+ */
52
+ function getHookScriptsDir() {
53
+ return path.join(os.homedir(), ".claude", "hooks", "mnotes", "scripts");
54
+ }
55
+ /** @deprecated kept for backward compatibility — use `getHookScriptsDir()` so HOME overrides work. */
56
+ exports.HOOK_SCRIPTS_DIR = getHookScriptsDir();
11
57
  /**
12
58
  * Generates bash scripts to be written to `.claude/hooks/`.
13
59
  */
@@ -61,9 +107,13 @@ curl -s \\
61
107
  }
62
108
  /**
63
109
  * Generates the hooks object to merge into `.claude/settings.json`.
64
- * References bash scripts in `.claude/hooks/` instead of inline commands.
110
+ * References bash scripts in `~/.claude/hooks/mnotes/scripts/` (absolute path
111
+ * so hooks work across projects without per-project script copies).
65
112
  */
66
- function generateHooksTemplate(opts) {
113
+ function generateHooksTemplate(_opts) {
114
+ const dir = getHookScriptsDir();
115
+ const startScript = path.join(dir, "mnotes-session-start.sh");
116
+ const stopScript = path.join(dir, "mnotes-session-stop.sh");
67
117
  return {
68
118
  SessionStart: [
69
119
  {
@@ -71,7 +121,7 @@ function generateHooksTemplate(opts) {
71
121
  hooks: [
72
122
  {
73
123
  type: "command",
74
- command: ".claude/hooks/mnotes-session-start.sh",
124
+ command: startScript,
75
125
  },
76
126
  ],
77
127
  },
@@ -82,7 +132,7 @@ function generateHooksTemplate(opts) {
82
132
  hooks: [
83
133
  {
84
134
  type: "command",
85
- command: ".claude/hooks/mnotes-session-stop.sh",
135
+ command: stopScript,
86
136
  },
87
137
  ],
88
138
  },
@@ -1,4 +1,4 @@
1
- export { generateHooksTemplate, generateHookScripts, HOOKS_HEADER } from "./hooks";
1
+ export { generateHooksTemplate, generateHookScripts, HOOKS_HEADER, HOOK_SCRIPTS_DIR, getHookScriptsDir } from "./hooks";
2
2
  export type { HooksTemplateOpts, ClaudeCodeHooks, ClaudeCodeHook, HookScript } from "./hooks";
3
3
  export { generateSkillTemplates } from "./skills";
4
4
  export type { SkillTemplateOpts, SkillFile } from "./skills";
@@ -1,10 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.generateAgentTemplates = exports.generateSkillTemplates = exports.HOOKS_HEADER = exports.generateHookScripts = exports.generateHooksTemplate = void 0;
3
+ exports.generateAgentTemplates = exports.generateSkillTemplates = exports.getHookScriptsDir = exports.HOOK_SCRIPTS_DIR = exports.HOOKS_HEADER = exports.generateHookScripts = exports.generateHooksTemplate = void 0;
4
4
  var hooks_1 = require("./hooks");
5
5
  Object.defineProperty(exports, "generateHooksTemplate", { enumerable: true, get: function () { return hooks_1.generateHooksTemplate; } });
6
6
  Object.defineProperty(exports, "generateHookScripts", { enumerable: true, get: function () { return hooks_1.generateHookScripts; } });
7
7
  Object.defineProperty(exports, "HOOKS_HEADER", { enumerable: true, get: function () { return hooks_1.HOOKS_HEADER; } });
8
+ Object.defineProperty(exports, "HOOK_SCRIPTS_DIR", { enumerable: true, get: function () { return hooks_1.HOOK_SCRIPTS_DIR; } });
9
+ Object.defineProperty(exports, "getHookScriptsDir", { enumerable: true, get: function () { return hooks_1.getHookScriptsDir; } });
8
10
  var skills_1 = require("./skills");
9
11
  Object.defineProperty(exports, "generateSkillTemplates", { enumerable: true, get: function () { return skills_1.generateSkillTemplates; } });
10
12
  var agents_1 = require("./agents");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnotes-cli",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "CLI for m-notes AI knowledge base — manage notes, search, and CRUD from the terminal",
5
5
  "bin": {
6
6
  "mnotes": "./dist/index.js"