@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 +5 -3
- package/bin/misakanet-setup.mjs +136 -11
- package/package.json +2 -2
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 **
|
|
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. "
|
|
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
|
|
package/bin/misakanet-setup.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
451
|
-
|
|
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 ?
|
|
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)
|
|
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.
|
|
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
|
},
|