@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 +1 -1
- package/bin/misakanet-setup.mjs +132 -9
- package/package.json +1 -1
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.
|
|
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
|
|
package/bin/misakanet-setup.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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"
|