@misaka-net/misakanet-setup 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.
- package/README.md +6 -5
- package/bin/misakanet-setup.mjs +126 -6
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @misaka-net/misakanet-setup
|
|
2
2
|
|
|
3
|
-
One command to teach your **Claude Code** or **
|
|
4
|
-
before repeating a mistake — and to distil the session's reusable
|
|
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
|
|
5
|
+
lessons at a checkpoint.
|
|
5
6
|
|
|
6
7
|
```bash
|
|
7
8
|
npx @misaka-net/misakanet-setup
|
|
@@ -15,9 +16,9 @@ knowledge base on its own instead of guessing.
|
|
|
15
16
|
|
|
16
17
|
| # | Goal | Mechanism |
|
|
17
18
|
|---|---|---|
|
|
18
|
-
| 1 | the agent **can** call it | MCP server `https://misakanet.org/mcp` (streamable HTTP) in `~/.claude.json` / `~/.codex/config.toml`, with the Bearer token so reads are not metered by the anonymous 5/day/IP limit |
|
|
19
|
-
| 2 | the agent **knows when** | rules block appended to `~/.claude/CLAUDE.md` / `~/.codex/AGENTS.md` (issue, retry, risky-operation triggers; desensitisation rules) |
|
|
20
|
-
| 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 |
|
|
19
|
+
| 1 | the agent **can** call it | MCP server `https://misakanet.org/mcp` (streamable HTTP) in `~/.claude.json` / `~/.codex/config.toml`, with the Bearer token so reads are not metered by the anonymous 5/day/IP limit. Hermes and OpenClaw own their MCP registries, so theirs is registered through their own CLIs (`hermes mcp add …`, `openclaw mcp add …`) instead of by editing a file we do not own |
|
|
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
|
+
| 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 |
|
|
21
22
|
|
|
22
23
|
Without (3) a rule saying "summarise every 20 turns" never fires — agents do not keep
|
|
23
24
|
counters. Without (1)/(2) the hook has nothing to call.
|
package/bin/misakanet-setup.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* misakanet-setup — one command, no Python, for people who do not read docs.
|
|
4
4
|
*
|
|
5
|
-
* npx @misaka-net/misakanet-setup install (
|
|
5
|
+
* npx @misaka-net/misakanet-setup install (Claude Code / Codex / Hermes / OpenClaw)
|
|
6
6
|
* npx @misaka-net/misakanet-setup --dry-run show what would change, write nothing
|
|
7
7
|
* npx @misaka-net/misakanet-setup --verify is it actually working?
|
|
8
8
|
* npx @misaka-net/misakanet-setup --uninstall
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* Everything is idempotent, every rewritten file is backed up to *.misakanet.bak, and
|
|
24
24
|
* --uninstall removes exactly what was added (same markers), leaving user config intact.
|
|
25
25
|
*/
|
|
26
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, rmSync, chmodSync } from 'node:fs';
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, rmSync, chmodSync, accessSync, constants } from 'node:fs';
|
|
27
|
+
import { spawnSync } from 'node:child_process';
|
|
27
28
|
import { homedir } from 'node:os';
|
|
28
29
|
import { join, dirname, resolve } from 'node:path';
|
|
29
30
|
import { fileURLToPath } from 'node:url';
|
|
@@ -52,7 +53,7 @@ const valueOf = (flag, dflt) => {
|
|
|
52
53
|
const only = valueOf('--only', '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
53
54
|
const DRY = has('--dry-run');
|
|
54
55
|
const HOME = resolve(valueOf('--home', homedir()));
|
|
55
|
-
const AGENTS = ['claude', 'codex', 'hermes'];
|
|
56
|
+
const AGENTS = ['claude', 'codex', 'hermes', 'openclaw'];
|
|
56
57
|
|
|
57
58
|
const done = [];
|
|
58
59
|
const manual = [];
|
|
@@ -77,6 +78,16 @@ function readText(path) {
|
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Own version, read from the manifest rather than typed twice.
|
|
83
|
+
*
|
|
84
|
+
* It rode along as a literal `misakanet-setup/0.1.0` while the package moved on - the same
|
|
85
|
+
* hardcoded-version drift this repository has had to fix on the site badge and in JOIN.md.
|
|
86
|
+
* The tarball always carries package.json (npm cannot publish without it), so the fallback
|
|
87
|
+
* only covers a hand-copied file.
|
|
88
|
+
*/
|
|
89
|
+
const PKG_VERSION = readJson(join(PKG_ROOT, 'package.json'), {}).version || '0.0.0';
|
|
90
|
+
|
|
80
91
|
function backup(path) {
|
|
81
92
|
if (DRY || !readText(path)) return;
|
|
82
93
|
try {
|
|
@@ -145,7 +156,7 @@ async function mcpCall(tool, toolArgs, bearer = '', timeoutMs = 6000, urlOverrid
|
|
|
145
156
|
Accept: 'application/json',
|
|
146
157
|
'MCP-Protocol-Version': '2025-06-18',
|
|
147
158
|
Origin: 'https://misakanet.org',
|
|
148
|
-
'User-Agent':
|
|
159
|
+
'User-Agent': `misakanet-setup/${PKG_VERSION}`,
|
|
149
160
|
};
|
|
150
161
|
if (bearer) headers.Authorization = `Bearer ${bearer}`;
|
|
151
162
|
const controller = new AbortController();
|
|
@@ -268,10 +279,39 @@ async function ensureIdentity() {
|
|
|
268
279
|
|
|
269
280
|
// ── per-agent install ────────────────────────────────────────────────
|
|
270
281
|
function detect(agent) {
|
|
271
|
-
const paths = {
|
|
282
|
+
const paths = {
|
|
283
|
+
claude: ['.claude.json', '.claude'],
|
|
284
|
+
codex: ['.codex'],
|
|
285
|
+
hermes: ['.hermes'],
|
|
286
|
+
openclaw: ['.openclaw'],
|
|
287
|
+
}[agent] || [];
|
|
272
288
|
return paths.some((p) => existsSync(join(HOME, p)));
|
|
273
289
|
}
|
|
274
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Locate an executable on PATH.
|
|
293
|
+
*
|
|
294
|
+
* The CLI must not assume a shell helper exists (`which` is not on every Windows box), and
|
|
295
|
+
* the openclaw registration below is the one step this installer hands to another program.
|
|
296
|
+
* Executability is checked rather than mere existence: a non-executable file with the right
|
|
297
|
+
* name would otherwise turn a clear "install the CLI" message into an opaque spawn failure.
|
|
298
|
+
*/
|
|
299
|
+
function findOnPath(bin) {
|
|
300
|
+
const sep = process.platform === 'win32' ? ';' : ':';
|
|
301
|
+
const exts = process.platform === 'win32' ? ['.cmd', '.exe', '.bat', ''] : [''];
|
|
302
|
+
for (const dir of (process.env.PATH || '').split(sep)) {
|
|
303
|
+
if (!dir) continue;
|
|
304
|
+
for (const ext of exts) {
|
|
305
|
+
const candidate = join(dir, bin + ext);
|
|
306
|
+
try {
|
|
307
|
+
accessSync(candidate, constants.X_OK);
|
|
308
|
+
return candidate;
|
|
309
|
+
} catch { /* not here (or not executable): keep looking */ }
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return '';
|
|
313
|
+
}
|
|
314
|
+
|
|
275
315
|
async function installClaude(hookPath, bearer) {
|
|
276
316
|
const cfg = join(HOME, '.claude.json');
|
|
277
317
|
const data = readJson(cfg, null);
|
|
@@ -399,6 +439,51 @@ async function installHermes(hookPath) {
|
|
|
399
439
|
}
|
|
400
440
|
}
|
|
401
441
|
|
|
442
|
+
/**
|
|
443
|
+
* OpenClaw: rules live in ~/.openclaw/workspace, and the agent has its own MCP client
|
|
444
|
+
* (`openclaw mcp add`), so this target registers through that CLI instead of editing a
|
|
445
|
+
* config file we do not own. Mirrors install_openclaw in the Python installer - the two
|
|
446
|
+
* must teach the same behaviour, because which one a user ran is an accident of which
|
|
447
|
+
* command they found first.
|
|
448
|
+
*
|
|
449
|
+
* The CLI keeps its cache under $XDG_CACHE_HOME, which may be read-only in a sandbox; that
|
|
450
|
+
* surfaces as "Could not start the CLI", hence the explicit hint rather than a raw stderr.
|
|
451
|
+
*/
|
|
452
|
+
async function installOpenclaw(bearer) {
|
|
453
|
+
const rules = join(HOME, '.openclaw', 'workspace', 'AGENTS.md');
|
|
454
|
+
const manual = `openclaw mcp add misakanet --url ${ENDPOINT} --transport streamable-http`;
|
|
455
|
+
if (!existsSync(dirname(rules))) {
|
|
456
|
+
need(`OpenClaw:找不到 ${dirname(rules)} → 先运行一次 openclaw 生成 workspace`);
|
|
457
|
+
} else {
|
|
458
|
+
ok(`OpenClaw:规则块 ${injectBlock(rules, PROMPT_BLOCK)} → ${rules}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const cli = findOnPath('openclaw');
|
|
462
|
+
if (!cli) {
|
|
463
|
+
need(`OpenClaw:没找到 openclaw CLI → 手动执行 \`${manual}\``);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (DRY) {
|
|
467
|
+
ok(`OpenClaw:会执行 \`${manual} --no-probe\``);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
const cmd = [cli, 'mcp', 'add', 'misakanet', '--url', ENDPOINT,
|
|
471
|
+
'--transport', 'streamable-http', '--no-probe'];
|
|
472
|
+
if (bearer) cmd.push('--header', `Authorization=Bearer ${bearer}`);
|
|
473
|
+
const result = spawnSync(cmd[0], cmd.slice(1), { encoding: 'utf8', timeout: 20000 });
|
|
474
|
+
const out = `${result.stdout || ''}${result.stderr || ''}`;
|
|
475
|
+
if (result.status === 0) {
|
|
476
|
+
ok('OpenClaw:已注册 MCP `misakanet`(openclaw mcp add)');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (out.includes('Could not start the CLI')) {
|
|
480
|
+
need('OpenClaw:CLI 起不动(缓存目录不可写)→ 设 XDG_CACHE_HOME 到可写目录后重跑,'
|
|
481
|
+
+ `或手动执行:${manual}`);
|
|
482
|
+
} else {
|
|
483
|
+
need(`OpenClaw:\`openclaw mcp add\` 失败:${out.trim().slice(0, 200) || `退出码 ${result.status}`}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
402
487
|
async function verify() {
|
|
403
488
|
let allOk = true;
|
|
404
489
|
const probe = await mcpCall('misakanet_search', { query: 'docker exit code 137', top: 1 }, '', 6000, probeEndpoint());
|
|
@@ -454,11 +539,44 @@ async function verify() {
|
|
|
454
539
|
if (!entry) { allOk = false; need(`Claude Code:MCP 未注册(${cfg})`); }
|
|
455
540
|
else ok(`Claude Code:MCP 已注册(${entry.url})`);
|
|
456
541
|
}
|
|
542
|
+
// OpenClaw is only reported when the user actually has it: telling a machine without
|
|
543
|
+
// OpenClaw that it is "not ready" would be a lie about a target that was never selected.
|
|
544
|
+
// The MCP state is asked of the CLI that owns it rather than guessed from its config.
|
|
545
|
+
if (detect('openclaw')) {
|
|
546
|
+
const rules = join(HOME, '.openclaw', 'workspace', 'AGENTS.md');
|
|
547
|
+
if (!readText(rules).includes(`<!-- ${START} -->`)) {
|
|
548
|
+
allOk = false;
|
|
549
|
+
need(`OpenClaw:规则块没装(${rules})→ 重跑安装命令`);
|
|
550
|
+
} else {
|
|
551
|
+
ok('OpenClaw:规则块已装');
|
|
552
|
+
}
|
|
553
|
+
const cli = findOnPath('openclaw');
|
|
554
|
+
if (!cli) {
|
|
555
|
+
allOk = false;
|
|
556
|
+
need(`OpenClaw:没找到 openclaw CLI → 无法确证 MCP 是否注册;手动执行 `
|
|
557
|
+
+ `openclaw mcp add misakanet --url ${ENDPOINT} --transport streamable-http`);
|
|
558
|
+
} else {
|
|
559
|
+
const listed = spawnSync(cli, ['mcp', 'list'], { encoding: 'utf8', timeout: 15000 });
|
|
560
|
+
const out = `${listed.stdout || ''}${listed.stderr || ''}`;
|
|
561
|
+
if (listed.status === 0 && /misakanet/.test(out)) {
|
|
562
|
+
ok('OpenClaw:MCP 已注册(openclaw mcp list)');
|
|
563
|
+
} else if (listed.status === 0) {
|
|
564
|
+
allOk = false;
|
|
565
|
+
need(`OpenClaw:MCP 未注册 → 重跑安装命令,或手动执行 `
|
|
566
|
+
+ `openclaw mcp add misakanet --url ${ENDPOINT} --transport streamable-http`);
|
|
567
|
+
} else {
|
|
568
|
+
allOk = false;
|
|
569
|
+
need(`OpenClaw:\`openclaw mcp list\` 没跑通 → 无法确证 MCP 状态`
|
|
570
|
+
+ `${out.trim() ? `(${out.trim().slice(0, 120)})` : ''};可先手工确认一次`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
457
574
|
return allOk;
|
|
458
575
|
}
|
|
459
576
|
|
|
460
577
|
function uninstall() {
|
|
461
|
-
for (const rel of ['.claude/CLAUDE.md', '.codex/AGENTS.md', '.hermes/SOUL.md'
|
|
578
|
+
for (const rel of ['.claude/CLAUDE.md', '.codex/AGENTS.md', '.hermes/SOUL.md',
|
|
579
|
+
'.openclaw/workspace/AGENTS.md']) {
|
|
462
580
|
if (stripBlock(join(HOME, rel))) ok(`移除规则块 → ${rel}`);
|
|
463
581
|
}
|
|
464
582
|
const cfg = join(HOME, '.claude.json');
|
|
@@ -504,6 +622,7 @@ function uninstall() {
|
|
|
504
622
|
ok(`删除状态目录 → ${stateDir()}`);
|
|
505
623
|
}
|
|
506
624
|
need('Hermes 的 MCP 条目由它自己管 → 需要时执行 hermes mcp remove misakanet');
|
|
625
|
+
need('OpenClaw 的 MCP 条目由它自己管 → 需要时执行 openclaw mcp remove misakanet');
|
|
507
626
|
}
|
|
508
627
|
|
|
509
628
|
// ── main ─────────────────────────────────────────────────────────────
|
|
@@ -548,6 +667,7 @@ if (!targets.length) {
|
|
|
548
667
|
if (agent === 'claude') await installClaude(hookPath, bearer);
|
|
549
668
|
else if (agent === 'codex') await installCodex(hookPath, bearer);
|
|
550
669
|
else if (agent === 'hermes') await installHermes(hookPath);
|
|
670
|
+
else if (agent === 'openclaw') await installOpenclaw(bearer);
|
|
551
671
|
}
|
|
552
672
|
}
|
|
553
673
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misaka-net/misakanet-setup",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "One-command setup: teach your Claude Code / Codex to search MisakaNet failure lessons before repeating a mistake, and to distil the session at a checkpoint.",
|
|
3
|
+
"version": "0.2.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.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"misakanet-setup": "
|
|
6
|
+
"misakanet-setup": "bin/misakanet-setup.mjs"
|
|
7
7
|
},
|
|
8
8
|
"type": "module",
|
|
9
9
|
"files": [
|