@misaka-net/misakanet-setup 0.2.1 → 0.3.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
@@ -16,7 +16,7 @@ knowledge base on its own instead of guessing.
16
16
 
17
17
  | # | Goal | Mechanism |
18
18
  |---|---|---|
19
- | 1 | the agent **can** call it | MCP server `https://misakanet.org/mcp` (streamable HTTP) in `~/.claude.json`, `~/.codex/config.toml` and `~/.openclaw/openclaw.json` (`mcp.servers.misakanet`), with the Bearer token so reads are not metered by the anonymous 5/day/IP limit. Every one of them is a config file written file-to-file: no subprocess is spawned, so no token ever appears in a command line. Hermes keeps its own registry and is the one target left to `hermes mcp add …`, which the installer prints for you |
19
+ | 1 | the agent **can** call it | MCP server `https://misakanet.org/mcp` (streamable HTTP) in `~/.claude.json`, `~/.codex/config.toml` and `~/.openclaw/openclaw.json` (`mcp.servers.misakanet`), with the Bearer token so reads are not metered by the anonymous 5/day/IP limit. Hermes is the same thing in its own files: `mcp_servers.misakanet` in `~/.hermes/config.yaml` plus the token in `~/.hermes/.env` under `MCP_MISAKANET_API_KEY`. All four are config files written file-to-file — no subprocess is spawned, so **no token ever appears in a command line** |
20
20
  | 2 | the agent **knows when** | rules block appended to `~/.claude/CLAUDE.md` / `~/.codex/AGENTS.md` / `~/.hermes/SOUL.md` / `~/.openclaw/workspace/AGENTS.md` (issue, retry, risky-operation triggers; desensitisation rules) |
21
21
  | 3 | the checkpoint **fires** | a hook that counts user turns: turn 1 announces the install to the user, turn 20 (and every 10 after) injects the "distil and submit" reminder; a failed tool call injects a "search before you retry" reminder built from the error text. Claude Code only today — Codex's user-level hook shape is unconfirmed and OpenClaw's events are unverified, so those two work from the rules block, and `--verify` says so per target instead of implying otherwise |
22
22
 
@@ -46,6 +46,19 @@ const END = 'misakanet:end';
46
46
  const TOP_START = 'misakanet-top:start';
47
47
  const TOP_END = 'misakanet-top:end';
48
48
 
49
+ // Hermes keeps its MCP registry in a YAML file, so its block is delimited with YAML comments
50
+ // rather than the HTML comments the markdown rules files use.
51
+ const YAML_START = `# ${START}`;
52
+ const YAML_END = `# ${END}`;
53
+ // Derivation copied from hermes_cli/mcp_config.py: f"MCP_{name.upper().replace('-', '_')}_API_KEY".
54
+ const HERMES_ENV_KEY = 'MCP_MISAKANET_API_KEY';
55
+ /** The entry this installer wrote, delimited by its markers. */
56
+ const HERMES_MARKED = new RegExp(
57
+ `[ \\t]*misakanet:[ \\t]*#\\s*${START}\\s*\\n[\\s\\S]*?^[ \\t]*#\\s*${END}\\s*$\\n?`, 'm');
58
+ /** An unmarked entry (added by `hermes mcp add`) together with its indented children. */
59
+ const HERMES_BARE = new RegExp(
60
+ `^[ \\t]*misakanet:[ \\t]*\\n(?:[ \\t]{4,}.*\\n|[ \\t]*\\n)*`, 'm');
61
+
49
62
  // ── small helpers ────────────────────────────────────────────────────
50
63
  const args = process.argv.slice(2);
51
64
  const has = (flag) => args.includes(flag);
@@ -90,7 +103,7 @@ function readText(path) {
90
103
  * meant to prevent is now caught in CI instead, by a test that binds this literal to the
91
104
  * manifest — a failing test is a better place for that than a request header.
92
105
  */
93
- const VERSION = '0.2.1';
106
+ const VERSION = '0.3.0';
94
107
 
95
108
  function backup(path) {
96
109
  if (DRY || !readText(path)) return;
@@ -421,14 +434,75 @@ async function installCodex(hookPath, bearer) {
421
434
  + '要硬保证就用 --verify 看状态,或把本会话放在 CC 里跑');
422
435
  }
423
436
 
424
- async function installHermes(hookPath) {
437
+ /**
438
+ * Hermes MCP: `mcp_servers.<name>` in ~/.hermes/config.yaml, with the token in
439
+ * ~/.hermes/.env under `MCP_<NAME>_API_KEY`.
440
+ *
441
+ * This is exactly what `hermes mcp add <name> --url <url> --auth header` writes (its own code
442
+ * derives the key with `f"MCP_{name.upper().replace('-', '_')}_API_KEY"` and stores the value
443
+ * in the dotenv file, leaving `Authorization: Bearer ${…}` as the config template) — minus the
444
+ * subprocess, which is what keeps the token out of argv (alert #269) and lets the install work
445
+ * where that CLI cannot start.
446
+ *
447
+ * The block is marker-delimited: re-running replaces it, and --uninstall takes back exactly
448
+ * the entry this installer added.
449
+ */
450
+ async function installHermes(hookPath, bearer) {
451
+ const cfg = join(HOME, '.hermes', 'config.yaml');
452
+ const text = readText(cfg);
425
453
  const rules = join(HOME, '.hermes', 'SOUL.md');
426
- if (readText(join(HOME, '.hermes', 'config.yaml'))) {
427
- ok(`Hermes:规则块 ${injectBlock(rules, PROMPT_BLOCK)} → ${rules}`);
428
- need(`Hermes:MCP 由它自己管 → 执行 hermes mcp add misakanet --url ${ENDPOINT};`
429
- + '钩子要过它的 allowlist(hermes hooks doctor 查看)');
430
- } else {
454
+ if (!text) {
431
455
  need('Hermes:找不到 ~/.hermes/config.yaml → 先运行一次 hermes 再回来装');
456
+ return;
457
+ }
458
+ ok(`Hermes:规则块 ${injectBlock(rules, PROMPT_BLOCK)} → ${rules}`);
459
+
460
+ const envKey = HERMES_ENV_KEY;
461
+ const lines = [` misakanet: ${YAML_START}`, ` url: ${ENDPOINT}`];
462
+ if (bearer) lines.push(' headers:', ` Authorization: Bearer \${${envKey}}`);
463
+ lines.push(` ${YAML_END}`);
464
+ // No trailing newline: the three call sites below each own the one they need, and the
465
+ // "insert after mcp_servers:" case must NOT add one (the line it replaces already ends with
466
+ // the newline that separates it from the next key — adding another wrote a blank line into
467
+ // the user's YAML).
468
+ const block = lines.join('\n');
469
+
470
+ let updated;
471
+ if (HERMES_MARKED.test(text)) {
472
+ updated = text.replace(HERMES_MARKED, () => `${block}\n`);
473
+ } else if (HERMES_BARE.test(text)) {
474
+ // An entry the user added through the CLI: replace it rather than write a duplicate key.
475
+ updated = text.replace(HERMES_BARE, () => `${block}\n`);
476
+ } else if (/^mcp_servers:[ \t]*$/m.test(text)) {
477
+ updated = text.replace(/^(mcp_servers:[ \t]*)$/m, (line) => `${line}\n${block}`);
478
+ } else {
479
+ updated = `${text}${text.endsWith('\n') ? '' : '\n'}mcp_servers:\n${block}\n`;
480
+ }
481
+ if (updated === text) {
482
+ ok('Hermes:MCP 已注册(无改动)');
483
+ } else {
484
+ backup(cfg);
485
+ writeText(cfg, updated);
486
+ ok(`Hermes:注册 MCP(streamable-http)→ ${cfg}`);
487
+ }
488
+
489
+ if (!bearer) {
490
+ skip('Hermes:这次没有 token → 走匿名通道(5/天/IP),写入类工具不可用');
491
+ return;
492
+ }
493
+ const envPath = join(HOME, '.hermes', '.env');
494
+ const envText = readText(envPath);
495
+ const assignment = `${envKey}=${bearer}`;
496
+ const current = new RegExp(`^${envKey}=.*$`, 'm');
497
+ const next = current.test(envText)
498
+ ? envText.replace(current, () => assignment)
499
+ : `${envText}${envText && !envText.endsWith('\n') ? '\n' : ''}${assignment}\n`;
500
+ if (next !== envText) {
501
+ backup(envPath);
502
+ writeText(envPath, next);
503
+ ok(`Hermes:token 写入 ${envPath}(${envKey},配置里只留 \${${envKey}} 模板)`);
504
+ } else {
505
+ ok('Hermes:token 已就绪(无改动)');
432
506
  }
433
507
  }
434
508
 
@@ -557,6 +631,31 @@ async function verify() {
557
631
  ok(`OpenClaw:MCP 已注册(${entry.url || '?'})`);
558
632
  }
559
633
  }
634
+ // Hermes: same rule — reported only when the user has it. Its config file *is* the registry,
635
+ // so the state is readable without running anything. What cannot be confirmed from here is
636
+ // whether Hermes has loaded that file, and the output says so rather than implying it.
637
+ if (readText(join(HOME, '.hermes', 'config.yaml'))) {
638
+ const rules = join(HOME, '.hermes', 'SOUL.md');
639
+ if (!readText(rules).includes(`<!-- ${START} -->`)) {
640
+ allOk = false;
641
+ need(`Hermes:规则块没装(${rules})→ 重跑安装命令`);
642
+ } else {
643
+ ok('Hermes:规则块已装');
644
+ }
645
+ const cfg = join(HOME, '.hermes', 'config.yaml');
646
+ const text = readText(cfg);
647
+ if (!HERMES_MARKED.test(text) && !HERMES_BARE.test(text)) {
648
+ allOk = false;
649
+ need(`Hermes:MCP 未注册(${cfg})→ 重跑安装命令,或执行 `
650
+ + `hermes mcp add misakanet --url ${ENDPOINT} --auth header`);
651
+ } else if (!new RegExp(`^${HERMES_ENV_KEY}=`, 'm').test(readText(join(HOME, '.hermes', '.env')))) {
652
+ ok('Hermes:MCP 条目已写入配置(无 token → 匿名 5 次/天/IP)');
653
+ skip(`Hermes:想让检索不计匿名额度,重跑安装命令即可写入 ${HERMES_ENV_KEY}`);
654
+ } else {
655
+ ok(`Hermes:MCP 条目与 token 都在(${HERMES_ENV_KEY})`);
656
+ skip('Hermes:本进程只能确认"配置已写",无法确认 Hermes 是否已加载 → 可用 hermes mcp list 复核');
657
+ }
658
+ }
560
659
  return allOk;
561
660
  }
562
661
 
@@ -607,7 +706,31 @@ function uninstall() {
607
706
  if (!DRY) rmSync(stateDir(), { recursive: true, force: true });
608
707
  ok(`删除状态目录 → ${stateDir()}`);
609
708
  }
610
- need('Hermes 的 MCP 条目由它自己管 → 需要时执行 hermes mcp remove misakanet');
709
+ // Hermes' entry is one we wrote ourselves (config block + the token line it reads from .env),
710
+ // so we can take both back — same rule as OpenClaw and Claude Code.
711
+ const hermesCfg = join(HOME, '.hermes', 'config.yaml');
712
+ {
713
+ const text = readText(hermesCfg);
714
+ // The bare form is included so an entry added by `hermes mcp add` on our behalf goes too.
715
+ const stripped = text.replace(HERMES_MARKED, '').replace(HERMES_BARE, '');
716
+ if (stripped !== text) {
717
+ backup(hermesCfg);
718
+ writeText(hermesCfg, stripped);
719
+ ok(`移除 MCP 条目 → ${hermesCfg}`);
720
+ }
721
+ }
722
+ const hermesEnv = join(HOME, '.hermes', '.env');
723
+ {
724
+ const text = readText(hermesEnv);
725
+ const stripped = text.replace(new RegExp(`^${HERMES_ENV_KEY}=.*\\n?`, 'm'), '');
726
+ if (stripped !== text) {
727
+ backup(hermesEnv);
728
+ writeText(hermesEnv, stripped);
729
+ ok(`移除 token 行 → ${hermesEnv}(${HERMES_ENV_KEY})`);
730
+ }
731
+ }
732
+ need('Hermes 的钩子不归本安装器管 → 需要时看 hermes hooks doctor;'
733
+ + `规则块已在 ~/.hermes/SOUL.md 移除`);
611
734
 
612
735
  // OpenClaw's MCP entry is one we wrote ourselves, so we can take it back ourselves.
613
736
  const ocCfg = join(HOME, '.openclaw', 'openclaw.json');
@@ -661,7 +784,7 @@ if (!targets.length) {
661
784
  for (const agent of targets) {
662
785
  if (agent === 'claude') await installClaude(hookPath, bearer);
663
786
  else if (agent === 'codex') await installCodex(hookPath, bearer);
664
- else if (agent === 'hermes') await installHermes(hookPath);
787
+ else if (agent === 'hermes') await installHermes(hookPath, bearer);
665
788
  else if (agent === 'openclaw') await installOpenclaw(bearer);
666
789
  }
667
790
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@misaka-net/misakanet-setup",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
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.",
5
5
  "bin": {
6
6
  "misakanet-setup": "bin/misakanet-setup.mjs"