@misaka-net/misakanet-setup 0.4.0 → 0.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
@@ -1,7 +1,7 @@
1
1
  # @misaka-net/misakanet-setup
2
2
 
3
- One command to teach your **Claude Code**, **Codex**, **Hermes** or **OpenClaw** to check
4
- MisakaNet's failure lessons before repeating a mistake — and to distil the session's reusable
3
+ One command to teach your **Claude Code**, **Codex**, **Hermes**, **OpenClaw** or **codewhale** to
4
+ check MisakaNet's failure lessons before repeating a mistake — and to distil the session's reusable
5
5
  lessons at a checkpoint.
6
6
 
7
7
  ```bash
@@ -9,7 +9,7 @@ npx @misaka-net/misakanet-setup
9
9
  ```
10
10
 
11
11
  Then **close and reopen the assistant window** (the new MCP server loads on restart) and ask
12
- something with an error in it, e.g. "docker exit code 137 是什么原因" — it should search the
12
+ something with an error in it, e.g. "pip install timeout 是什么原因" — it should search the
13
13
  knowledge base on its own instead of guessing.
14
14
 
15
15
  ## What it installs, and why all three
@@ -31,6 +31,8 @@ npx @misaka-net/misakanet-setup --verify # READY / NOT READY, installed ver
31
31
  npx @misaka-net/misakanet-setup --only claude # one agent only
32
32
  npx @misaka-net/misakanet-setup --no-register # read-only, no anonymous token
33
33
  npx @misaka-net/misakanet-setup --upgrade # same as installing the latest (the command is idempotent)
34
+ # re-running also *refreshes* an older hook (the previous copy
35
+ # is kept as ~/.misakanet-agent/hook.mjs.misakanet.bak)
34
36
  npx @misaka-net/misakanet-setup --uninstall # remove exactly what it added
35
37
  ```
36
38
 
@@ -69,7 +69,7 @@ const valueOf = (flag, dflt) => {
69
69
  const only = valueOf('--only', '').split(',').map((s) => s.trim()).filter(Boolean);
70
70
  const DRY = has('--dry-run');
71
71
  const HOME = resolve(valueOf('--home', homedir()));
72
- const AGENTS = ['claude', 'codex', 'hermes', 'openclaw'];
72
+ const AGENTS = ['claude', 'codex', 'hermes', 'openclaw', 'codewhale'];
73
73
 
74
74
  const done = [];
75
75
  const manual = [];
@@ -103,7 +103,7 @@ function readText(path) {
103
103
  * meant to prevent is now caught in CI instead, by a test that binds this literal to the
104
104
  * manifest — a failing test is a better place for that than a request header.
105
105
  */
106
- const VERSION = '0.4.0';
106
+ const VERSION = '0.4.2';
107
107
 
108
108
  function backup(path) {
109
109
  if (DRY || !readText(path)) return;
@@ -252,23 +252,44 @@ function locateHook() {
252
252
 
253
253
  async function installHook() {
254
254
  const hookPath = join(stateDir(), 'hook.mjs');
255
+ const bundled = locateHook();
256
+ let existing = null;
255
257
  try {
256
- if (readFileSync(hookPath, 'utf8').includes('MisakaNet')) {
258
+ existing = readFileSync(hookPath, 'utf8');
259
+ } catch { /* not installed yet */ }
260
+ const isOurs = existing !== null && existing.includes('MisakaNet');
261
+
262
+ if (!bundled) {
263
+ if (isOurs) {
257
264
  ok(`自动沉淀的钩子已存在:${hookPath}`);
258
265
  return hookPath;
259
266
  }
260
- } catch { /* not installed yet */ }
261
- const bundled = locateHook();
262
- if (!bundled) {
263
267
  need('自动沉淀那部分装不上:这个 npm 包里没有带钩子文件(安装不完整)→ '
264
268
  + '重新执行 npx 安装即可;其它功能不受影响');
265
269
  return null;
266
270
  }
271
+
272
+ if (isOurs && existing.trim() === bundled.trim()) {
273
+ ok(`自动沉淀的钩子已是最新:${hookPath}`);
274
+ return hookPath;
275
+ }
276
+
277
+ // Refresh a hook that is *ours* (marker) but older than the bundled copy, keeping the
278
+ // previous file beside it.
279
+ //
280
+ // This used to return early for any hook containing "MisakaNet", which made a hook fix
281
+ // unable to reach an existing install: the 14-day upgrade nudge (#1712) could not
282
+ // arrive, and re-running the installer — the very thing the nudge asks the user to do —
283
+ // changed nothing (found 2026-09-15: the machine's installed hook predated the nudge
284
+ // while `npx …@latest` reported success).
267
285
  if (!DRY) {
268
286
  mkdirSync(stateDir(), { recursive: true });
287
+ if (existing !== null) backup(hookPath);
269
288
  writeFileSync(hookPath, bundled);
270
289
  }
271
- ok(`安装自动沉淀钩子 → ${hookPath}`);
290
+ ok(isOurs
291
+ ? `自动沉淀的钩子已更新(旧版备份为 ${hookPath}.misakanet.bak)→ ${hookPath}`
292
+ : `安装自动沉淀钩子 → ${hookPath}`);
272
293
  return hookPath;
273
294
  }
274
295
 
@@ -331,6 +352,7 @@ function detect(agent) {
331
352
  codex: ['.codex'],
332
353
  hermes: ['.hermes'],
333
354
  openclaw: ['.openclaw'],
355
+ codewhale: ['.codewhale'],
334
356
  }[agent] || [];
335
357
  return paths.some((p) => existsSync(join(HOME, p)));
336
358
  }
@@ -447,11 +469,30 @@ async function installCodex(hookPath, bearer) {
447
469
 
448
470
  const rules = join(HOME, '.codex', 'AGENTS.md');
449
471
  ok(`Codex:规则块 ${injectBlock(rules, PROMPT_BLOCK)} → ${rules}`);
450
- need('Codex:用户级钩子的写法我没能确证 → "第 20 轮自动沉淀"靠规则自律;'
451
- + '要硬保证就用 --verify 看状态,或把本会话放在 CC 里跑');
472
+ // Verified on codex-cli 0.154.0 (2026-09-15), so this is no longer a "could not
473
+ // confirm" note: `codex mcp list` shows misakanet enabled with the Bearer token,
474
+ // `codex doctor` reports config.toml parse ok + 1 streamable_http server + 0
475
+ // disabled, and `codex debug prompt-input` renders a `# AGENTS.md instructions`
476
+ // item carrying this rule block. What stays open is the *hook*: 0.154.0's lifecycle
477
+ // hooks are admin-managed (requirements.toml), so the round-20 distillation is
478
+ // rule-driven rather than hook-driven.
479
+ ok('Codex:注册已核对(`codex mcp list` / `codex doctor` / `codex debug prompt-input`)');
480
+ need('Codex:没有用户级 lifecycle hook → "第 20 轮自动沉淀"靠规则块自律;'
481
+ + '要硬保证就配外层 wrapper 每轮后跑 checkpoint_reminder');
452
482
  }
453
483
 
454
484
  const STATE_VERSION_FILE = 'version';
485
+
486
+ /**
487
+ * The question the onboarding text tells a user to ask — and the reason it is a constant.
488
+ *
489
+ * It used to be "docker exit code 137 是什么原因", which the corpus answers with three only
490
+ * loosely-related lessons (top hit: kubernetes-crashloopbackoff-debugging) — so a new user's
491
+ * first question, the one we chose for them, landed on a near-miss. It is now a query whose top
492
+ * hit is a lesson about that very failure, and tests/test_onboarding_example.py keeps it that
493
+ * way offline (plus a check that this literal is not duplicated back into the messages).
494
+ */
495
+ const ONBOARDING_QUERY = 'pip install timeout';
455
496
  const REGISTRY_LATEST = process.env.MISAKANET_REGISTRY_URL
456
497
  || 'https://registry.npmjs.org/@misaka-net%2fmisakanet-setup/latest';
457
498
 
@@ -633,6 +674,75 @@ async function installHermes(hookPath, bearer) {
633
674
  * The manual `openclaw mcp add …` line is still printed for the user to run themselves — as
634
675
  * text, never executed.
635
676
  */
677
+ /**
678
+ * codewhale keeps MCP servers in its own JSON file, and reads workspace rules from a
679
+ * plain `AGENTS.md` — but only in a *trusted* project (`config.toml`'s
680
+ * `[projects."<dir>"] trust_level = "trusted"`). So the block goes into the trusted
681
+ * project directories, which are also where the user actually works, rather than into a
682
+ * guessed user-level path (checked on 0.9.7: no user-level AGENTS.md is read).
683
+ *
684
+ * The token is the one thing codewhale will not take inline: `bearer_token_env_var` names
685
+ * an environment variable, so "install once" for this agent ends with the user exporting
686
+ * `MISAKANET_TOKEN` once. That is stated rather than silently half-installed.
687
+ */
688
+ function codewhaleProjects() {
689
+ const text = readText(join(HOME, '.codewhale', 'config.toml'));
690
+ if (!text) return [];
691
+ const out = [];
692
+ let current = null;
693
+ for (const line of text.split('\n')) {
694
+ const header = line.match(/^\s*\[projects\.(.+?)\]\s*$/);
695
+ if (header) { current = header[1].trim().replace(/^"|"$/g, ''); continue; }
696
+ if (/^\s*\[/.test(line)) { current = null; continue; }
697
+ if (current && /^\s*trust_level\s*=\s*"trusted"/.test(line)) out.push(current);
698
+ }
699
+ return out;
700
+ }
701
+
702
+ async function installCodewhale(bearer) {
703
+ const cfg = join(HOME, '.codewhale', 'mcp.json');
704
+ const data = readJson(cfg, null) || {
705
+ timeouts: { connect_timeout: 10, execute_timeout: 60, read_timeout: 120 },
706
+ servers: {},
707
+ };
708
+ const servers = { ...(data.servers || {}) };
709
+ const entry = {
710
+ command: null,
711
+ args: [],
712
+ env: {},
713
+ url: ENDPOINT,
714
+ connect_timeout: null,
715
+ execute_timeout: null,
716
+ read_timeout: null,
717
+ disabled: false,
718
+ enabled: true,
719
+ required: false,
720
+ enabled_tools: [],
721
+ disabled_tools: [],
722
+ bearer_token_env_var: 'MISAKANET_TOKEN',
723
+ };
724
+ if (JSON.stringify(servers.misakanet || null) !== JSON.stringify(entry)) {
725
+ servers.misakanet = entry;
726
+ backup(cfg);
727
+ writeText(cfg, `${JSON.stringify({ ...data, servers }, null, 2)}\n`);
728
+ ok(`codewhale:注册 MCP(streamable-http)→ ${cfg}`);
729
+ } else {
730
+ ok('codewhale:MCP 已注册(无改动)');
731
+ }
732
+
733
+ const projects = codewhaleProjects();
734
+ for (const project of projects) {
735
+ if (!existsSync(project)) continue;
736
+ ok(`codewhale:规则块 ${injectBlock(join(project, 'AGENTS.md'), PROMPT_BLOCK)} → ${join(project, 'AGENTS.md')}`);
737
+ }
738
+ if (!projects.length) {
739
+ need('codewhale:没有受信任的项目目录(config.toml 里没有 trust_level = "trusted")→ '
740
+ + '先在 codewhale 里打开一次你的项目并信任它,再运行本命令,规则块才会写进去');
741
+ }
742
+ need('codewhale:token 只能通过环境变量给 → 在你的 shell 配置里加 `export MISAKANET_TOKEN=<你的 token>`'
743
+ + '(本机状态:npx @misaka-net/misakanet-setup --verify 会告诉你 token 在哪)');
744
+ }
745
+
636
746
  async function installOpenclaw(bearer) {
637
747
  const workspace = openclawWorkspaces()[0];
638
748
  const rules = join(workspace, 'AGENTS.md');
@@ -810,6 +920,20 @@ function uninstall() {
810
920
  const rules = join(workspace, 'AGENTS.md');
811
921
  if (stripBlock(rules)) ok(`移除规则块 → ${rules}`);
812
922
  }
923
+
924
+ // codewhale: same two surfaces as the install (its own mcp.json + trusted projects).
925
+ for (const project of codewhaleProjects()) {
926
+ const rules = join(project, 'AGENTS.md');
927
+ if (stripBlock(rules)) ok(`移除规则块 → ${rules}`);
928
+ }
929
+ const whaleCfg = join(HOME, '.codewhale', 'mcp.json');
930
+ const whale = readJson(whaleCfg, null);
931
+ if (whale?.servers?.misakanet) {
932
+ delete whale.servers.misakanet;
933
+ backup(whaleCfg);
934
+ writeText(whaleCfg, `${JSON.stringify(whale, null, 2)}\n`);
935
+ ok(`移除 MCP 注册 → ${whaleCfg}`);
936
+ }
813
937
  const cfg = join(HOME, '.claude.json');
814
938
  const data = readJson(cfg, null);
815
939
  if (data && data.mcpServers?.misakanet) {
@@ -913,7 +1037,7 @@ if (mode === 'uninstall') {
913
1037
  if (mode === 'verify') {
914
1038
  const allOk = await verify();
915
1039
  console.log(render());
916
- console.log(`\n结论:${allOk ? 'READY —— 打开一个新会话,问它「docker exit code 137 是什么原因」' : 'NOT READY —— 上面每条 ! 都给了修复动作'}`);
1040
+ console.log(`\n结论:${allOk ? `READY —— 打开一个新会话,问它「${ONBOARDING_QUERY} 是什么原因」` : 'NOT READY —— 上面每条 ! 都给了修复动作'}`);
917
1041
  process.exit(allOk ? 0 : 1);
918
1042
  }
919
1043
 
@@ -934,6 +1058,7 @@ if (!targets.length) {
934
1058
  else if (agent === 'codex') await installCodex(hookPath, bearer);
935
1059
  else if (agent === 'hermes') await installHermes(hookPath, bearer);
936
1060
  else if (agent === 'openclaw') await installOpenclaw(bearer);
1061
+ else if (agent === 'codewhale') await installCodewhale(bearer);
937
1062
  }
938
1063
  }
939
1064
 
@@ -941,6 +1066,6 @@ console.log(render());
941
1066
  console.log(`
942
1067
  接下来:
943
1068
  1) **把这个助手窗口关掉再打开一次**(新功能要重开会话才生效)
944
- 2) 随便问一句带报错的:「docker exit code 137 是什么原因」——它应该先去查经验库
1069
+ 2) 随便问一句带报错的:「${ONBOARDING_QUERY} 是什么原因」——它应该先去查经验库
945
1070
  3) 想确认状态:npx @misaka-net/misakanet-setup --verify
946
1071
  4) 想关掉:npx @misaka-net/misakanet-setup --uninstall`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@misaka-net/misakanet-setup",
3
- "version": "0.4.0",
4
- "description": "One-command setup: teach your Claude Code / Codex / Hermes / OpenClaw to search MisakaNet failure lessons before repeating a mistake, and to distil the session at a checkpoint.",
3
+ "version": "0.4.2",
4
+ "description": "One-command setup: teach your Claude Code / Codex / Hermes / OpenClaw / codewhale to search MisakaNet failure lessons before repeating a mistake, and to distil the session at a checkpoint.",
5
5
  "bin": {
6
6
  "misakanet-setup": "bin/misakanet-setup.mjs"
7
7
  },