cognitive-enzyme-hook 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.
Files changed (3) hide show
  1. package/README.md +15 -4
  2. package/cli.mjs +209 -0
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -13,14 +13,25 @@ returns an error, the hook silently exits without injecting anything.
13
13
 
14
14
  ## Install
15
15
 
16
- From a clone of this repository (the package is not published to the npm
17
- registry yet):
16
+ One-liner wires `~/.claude/settings.json` and installs the global bins if missing:
18
17
 
19
18
  ```bash
20
- npm i -g ./hooks/npm
19
+ npx -y cognitive-enzyme-hook install
21
20
  ```
22
21
 
23
- Once published: `npm i -g cognitive-enzyme-hook`.
22
+ Options: `--project` (write `./.claude/settings.json` instead), `--url <CE_API_URL>`
23
+ and `--token <CE_API_TOKEN>` (written into `env`), `--no-npm` (skip the global bin
24
+ install). It is idempotent — existing equivalent hook entries (including
25
+ absolute-path setups) are detected and skipped, and the previous settings file is
26
+ backed up as `settings.json.bak`. Also available: `uninstall` and `status`.
27
+
28
+ Manual alternative:
29
+
30
+ ```bash
31
+ npm i -g cognitive-enzyme-hook # or from a clone: npm i -g ./hooks/npm
32
+ ```
33
+
34
+ then add the configuration below yourself.
24
35
 
25
36
  ## Configure
26
37
 
package/cli.mjs ADDED
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cognitive-enzyme-hook CLI — 一键安装/卸载 Claude Code hooks。
4
+ *
5
+ * npx -y cognitive-enzyme-hook install [--project] [--url <CE_API_URL>]
6
+ * [--token <CE_API_TOKEN>] [--no-npm]
7
+ * npx -y cognitive-enzyme-hook uninstall [--project]
8
+ * npx -y cognitive-enzyme-hook status
9
+ *
10
+ * install:往 ~/.claude/settings.json(默认)或 ./.claude/settings.json
11
+ * (--project)合并三条 hook:UserPromptSubmit → ce-prime-hook,
12
+ * SessionEnd / PreCompact → ce-capture-hook。幂等——已有等效条目
13
+ * (含仓库内绝对路径写法、Python 版 hook)则跳过。修改前备份 .bak。
14
+ * ce-prime-hook 不在 PATH 时自动 `npm i -g`(--no-npm 关闭)。
15
+ *
16
+ * 零依赖:仅用 Node 18+ 内置模块。
17
+ */
18
+
19
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
20
+ import { spawnSync } from "node:child_process";
21
+ import { homedir } from "node:os";
22
+ import { dirname, join } from "node:path";
23
+
24
+ const PKG = "cognitive-enzyme-hook";
25
+
26
+ // pattern 同时识别 registry bin、仓库内 mjs 绝对路径、Python 版 hook,
27
+ // 避免与已手工配置的等效条目重复注入
28
+ const HOOK_EVENTS = [
29
+ { event: "UserPromptSubmit", command: "ce-prime-hook", timeout: 10,
30
+ pattern: /ce[-_]prime[-_]hook|hooks[\\/]npm[\\/]index\.mjs/ },
31
+ { event: "SessionEnd", command: "ce-capture-hook", timeout: 30,
32
+ pattern: /ce[-_]capture[-_]hook|hooks[\\/]npm[\\/]capture\.mjs/ },
33
+ { event: "PreCompact", command: "ce-capture-hook", timeout: 30,
34
+ pattern: /ce[-_]capture[-_]hook|hooks[\\/]npm[\\/]capture\.mjs/ },
35
+ ];
36
+
37
+ function fail(msg) {
38
+ console.error(`[${PKG}] ${msg}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ function settingsPath(project) {
43
+ const base = project ? process.cwd() : homedir();
44
+ return join(base, ".claude", "settings.json");
45
+ }
46
+
47
+ function readSettings(path) {
48
+ if (!existsSync(path)) return {};
49
+ const raw = readFileSync(path, "utf8").replace(/^/, "");
50
+ if (!raw.trim()) return {};
51
+ try {
52
+ return JSON.parse(raw);
53
+ } catch (e) {
54
+ fail(`${path} 不是合法 JSON(${e.message})——不做修改,请先手工修复。`);
55
+ }
56
+ }
57
+
58
+ function writeSettings(path, settings) {
59
+ mkdirSync(dirname(path), { recursive: true });
60
+ if (existsSync(path)) copyFileSync(path, `${path}.bak`);
61
+ writeFileSync(path, JSON.stringify(settings, null, 2) + "\n");
62
+ }
63
+
64
+ function matchedHooks(settings, spec) {
65
+ const groups = (settings.hooks || {})[spec.event] || [];
66
+ return groups.flatMap((g) => (g.hooks || []))
67
+ .filter((h) => spec.pattern.test(String(h.command || "")));
68
+ }
69
+
70
+ function binOnPath(name) {
71
+ const probe = process.platform === "win32" ? `where ${name}` : `command -v ${name}`;
72
+ return spawnSync(probe, { shell: true, stdio: "ignore" }).status === 0;
73
+ }
74
+
75
+ // ── install ────────────────────────────────────────────────────
76
+
77
+ function install(flags) {
78
+ const path = settingsPath(flags.project);
79
+ const settings = readSettings(path);
80
+
81
+ const added = [];
82
+ const skipped = [];
83
+ for (const spec of HOOK_EVENTS) {
84
+ const existing = matchedHooks(settings, spec);
85
+ if (existing.length) {
86
+ skipped.push(`${spec.event}(已有: ${existing[0].command})`);
87
+ continue;
88
+ }
89
+ settings.hooks ||= {};
90
+ settings.hooks[spec.event] ||= [];
91
+ settings.hooks[spec.event].push({
92
+ hooks: [{ type: "command", command: spec.command, timeout: spec.timeout }],
93
+ });
94
+ added.push(spec.event);
95
+ }
96
+
97
+ if (flags.url || flags.token) {
98
+ settings.env ||= {};
99
+ if (flags.url) settings.env.CE_API_URL = flags.url;
100
+ if (flags.token) settings.env.CE_API_TOKEN = flags.token;
101
+ }
102
+
103
+ if (added.length || flags.url || flags.token) {
104
+ writeSettings(path, settings);
105
+ console.log(`[${PKG}] 已写入 ${path}${existsSync(`${path}.bak`) ? "(原文件备份为 .bak)" : ""}`);
106
+ }
107
+ for (const e of added) console.log(` + ${e}`);
108
+ for (const s of skipped) console.log(` = 跳过 ${s}`);
109
+
110
+ // hook 命令依赖全局 bin;不在 PATH 时代为安装
111
+ if (!flags.noNpm && !binOnPath("ce-prime-hook")) {
112
+ console.log(`[${PKG}] 未检测到全局 bin,执行 npm i -g ${PKG} ...`);
113
+ const r = spawnSync(`npm i -g ${PKG}`, { shell: true, stdio: "inherit" });
114
+ if (r.status !== 0) {
115
+ console.error(`[${PKG}] 全局安装失败——请手动执行: npm i -g ${PKG}`);
116
+ }
117
+ }
118
+
119
+ console.log(`[${PKG}] 完成。重启 Claude Code(或执行一次 /hooks)后生效。`);
120
+ if (!flags.url) {
121
+ console.log(` 提示: 服务不在 http://localhost:8420 时,用 --url 指定,或在 settings.json env 里配 CE_API_URL。`);
122
+ }
123
+ if (!flags.token) {
124
+ console.log(` 提示: 服务端开了鉴权的话,用 --token 写入 CE_API_TOKEN。`);
125
+ }
126
+ }
127
+
128
+ // ── uninstall ──────────────────────────────────────────────────
129
+
130
+ function uninstall(flags) {
131
+ const path = settingsPath(flags.project);
132
+ if (!existsSync(path)) fail(`${path} 不存在,无需卸载。`);
133
+ const settings = readSettings(path);
134
+
135
+ let removed = 0;
136
+ for (const spec of HOOK_EVENTS) {
137
+ const groups = (settings.hooks || {})[spec.event];
138
+ if (!groups) continue;
139
+ for (const g of groups) {
140
+ const before = (g.hooks || []).length;
141
+ g.hooks = (g.hooks || []).filter((h) => !spec.pattern.test(String(h.command || "")));
142
+ removed += before - g.hooks.length;
143
+ }
144
+ const kept = groups.filter((g) => (g.hooks || []).length);
145
+ if (kept.length) settings.hooks[spec.event] = kept;
146
+ else delete settings.hooks[spec.event];
147
+ }
148
+ if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
149
+
150
+ if (!removed) {
151
+ console.log(`[${PKG}] ${path} 中没有 CE hooks,无需卸载。`);
152
+ return;
153
+ }
154
+ writeSettings(path, settings);
155
+ console.log(`[${PKG}] 已从 ${path} 移除 ${removed} 条 hook(原文件备份为 .bak)。`);
156
+ if (settings.env?.CE_API_TOKEN || settings.env?.CE_API_URL) {
157
+ console.log(` 提示: env 中的 CE_API_URL / CE_API_TOKEN 未动,不需要请手工删除。`);
158
+ }
159
+ console.log(` 全局 bin 如需一并移除: npm rm -g ${PKG}`);
160
+ }
161
+
162
+ // ── status ─────────────────────────────────────────────────────
163
+
164
+ function status() {
165
+ for (const [label, project] of [["全局", false], ["项目", true]]) {
166
+ const path = settingsPath(project);
167
+ console.log(`${label} ${path}${existsSync(path) ? "" : "(不存在)"}`);
168
+ if (!existsSync(path)) continue;
169
+ const settings = readSettings(path);
170
+ for (const spec of HOOK_EVENTS) {
171
+ const hit = matchedHooks(settings, spec);
172
+ console.log(` ${hit.length ? "✔" : "✘"} ${spec.event}${hit.length ? ` → ${hit[0].command}` : ""}`);
173
+ }
174
+ if (settings.env?.CE_API_URL) console.log(` env CE_API_URL = ${settings.env.CE_API_URL}`);
175
+ if (settings.env?.CE_API_TOKEN) console.log(` env CE_API_TOKEN = (已设置)`);
176
+ }
177
+ console.log(`bin ce-prime-hook ${binOnPath("ce-prime-hook") ? "✔ 在 PATH" : "✘ 不在 PATH(npm i -g " + PKG + ")"}`);
178
+ }
179
+
180
+ // ── main ───────────────────────────────────────────────────────
181
+
182
+ const argv = process.argv.slice(2);
183
+ const cmd = argv[0];
184
+ const flags = { project: false, noNpm: false, url: "", token: "" };
185
+ for (let i = 1; i < argv.length; i++) {
186
+ const a = argv[i];
187
+ if (a === "--project") flags.project = true;
188
+ else if (a === "--no-npm") flags.noNpm = true;
189
+ else if (a === "--url") flags.url = argv[++i] || fail("--url 需要一个值");
190
+ else if (a === "--token") flags.token = argv[++i] || fail("--token 需要一个值");
191
+ else fail(`未知参数: ${a}`);
192
+ }
193
+
194
+ if (cmd === "install") install(flags);
195
+ else if (cmd === "uninstall") uninstall(flags);
196
+ else if (cmd === "status") status();
197
+ else {
198
+ console.log(`用法:
199
+ npx -y ${PKG} install [--project] [--url <CE_API_URL>] [--token <CE_API_TOKEN>] [--no-npm]
200
+ npx -y ${PKG} uninstall [--project]
201
+ npx -y ${PKG} status
202
+
203
+ install 往 ~/.claude/settings.json(--project 则为 ./.claude/settings.json)
204
+ 合并 prime/capture 三条 hook,幂等,修改前备份 .bak;
205
+ 全局 bin 缺失时自动 npm i -g(--no-npm 关闭)
206
+ uninstall 移除 CE 相关 hook 条目
207
+ status 查看全局/项目 hook 安装状态`);
208
+ process.exit(cmd ? 1 : 0);
209
+ }
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "cognitive-enzyme-hook",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Claude Code hooks for Cognitive Enzyme: inject System-1 reflexes on prompt (prime) and distill memories on session end (capture).",
5
5
  "type": "module",
6
6
  "bin": {
7
+ "cognitive-enzyme-hook": "cli.mjs",
7
8
  "ce-prime-hook": "index.mjs",
8
9
  "ce-capture-hook": "capture.mjs"
9
10
  },
@@ -17,6 +18,7 @@
17
18
  },
18
19
  "homepage": "https://github.com/7-e1even/Cognitive-Enzyme#readme",
19
20
  "files": [
21
+ "cli.mjs",
20
22
  "index.mjs",
21
23
  "capture.mjs",
22
24
  "README.md"