@misaka-net/misakanet-setup 0.5.0 → 0.5.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 +4 -1
- package/bin/misakanet-setup.mjs +127 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
-
|
|
12
|
+
a distinctive fragment of an error, e.g. "switch vision model" or "tool call permission denied" — 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
|
|
@@ -34,6 +34,9 @@ npx @misaka-net/misakanet-setup --no-register # read-only, no anonymous token
|
|
|
34
34
|
npx @misaka-net/misakanet-setup --upgrade # same as installing the latest (the command is idempotent)
|
|
35
35
|
# re-running also *refreshes* an older hook (the previous copy
|
|
36
36
|
# is kept as ~/.misakanet-agent/hook.mjs.misakanet.bak)
|
|
37
|
+
npx @misaka-net/misakanet-setup --report # this machine's state as YAML, safe to paste in public
|
|
38
|
+
# (token value never printed, home paths written as ~;
|
|
39
|
+
# two blank fields are yours to fill — see the bounty)
|
|
37
40
|
npx @misaka-net/misakanet-setup --voice # opt-in: a cue when a search hits, another when it misses
|
|
38
41
|
# (Claude Code: a PostToolUse hook; mute later with MISAKANET_VOICE=0)
|
|
39
42
|
npx @misaka-net/misakanet-setup --uninstall # remove exactly what it added
|
package/bin/misakanet-setup.mjs
CHANGED
|
@@ -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.5.
|
|
106
|
+
const VERSION = '0.5.2';
|
|
107
107
|
|
|
108
108
|
function backup(path) {
|
|
109
109
|
if (DRY || !readText(path)) return;
|
|
@@ -466,7 +466,17 @@ async function installClaude(hookPath, bearer) {
|
|
|
466
466
|
need('语音钩子:找到了播放器但没有找到音频文件 → 重装一次 npx 包即可(其它功能不受影响)');
|
|
467
467
|
}
|
|
468
468
|
}
|
|
469
|
-
|
|
469
|
+
let post = settings.hooks.PostToolUse || [];
|
|
470
|
+
// Upgrade path: 0.4.2 shipped this entry *without* a matcher, which never fires. Treat a
|
|
471
|
+
// matcher-less entry of ours as stale rather than "already installed", or re-running the
|
|
472
|
+
// installer would leave a hook that stays silent forever (the exact failure this release
|
|
473
|
+
// is about).
|
|
474
|
+
const stale = post.filter((e) => JSON.stringify(e).includes('voice-hook') && e.matcher !== '*');
|
|
475
|
+
if (stale.length) {
|
|
476
|
+
post = post.filter((e) => !stale.includes(e));
|
|
477
|
+
settings.hooks.PostToolUse = post;
|
|
478
|
+
changed = true;
|
|
479
|
+
}
|
|
470
480
|
if (!JSON.stringify(post).includes('voice-hook')) {
|
|
471
481
|
// `matcher: '*'` is load-bearing, not cosmetic: measured on 2026-09-16, a PostToolUse
|
|
472
482
|
// entry **without** a matcher never fired in this host, while the same command with
|
|
@@ -583,7 +593,16 @@ const STATE_VERSION_FILE = 'version';
|
|
|
583
593
|
* hit is a lesson about that very failure, and tests/test_onboarding_example.py keeps it that
|
|
584
594
|
* way offline (plus a check that this literal is not duplicated back into the messages).
|
|
585
595
|
*/
|
|
586
|
-
|
|
596
|
+
// Three examples, not one, and all three are *distinctive fragments* rather than sentences.
|
|
597
|
+
//
|
|
598
|
+
// Two lessons are baked in here. A single narrow example (the old "pip install timeout") made the
|
|
599
|
+
// corpus look one topic deep, so a new user's first impression undersold it. And the natural-language
|
|
600
|
+
// form of a question ("如何切换识图模型") returns nothing at all, because the corpus is indexed by
|
|
601
|
+
// error text and keywords — the same thing the rules block tells the agent to send. So these teach
|
|
602
|
+
// the usage while they demonstrate the value, and every one of them is checked offline by
|
|
603
|
+
// tests/test_onboarding_example.py: it fails if a new user's first hit is not a lesson about the
|
|
604
|
+
// question we told them to ask.
|
|
605
|
+
const ONBOARDING_QUERIES = ["switch vision model", "context window exceeded", "tool call permission denied"];
|
|
587
606
|
const REGISTRY_LATEST = process.env.MISAKANET_REGISTRY_URL
|
|
588
607
|
|| 'https://registry.npmjs.org/@misaka-net%2fmisakanet-setup/latest';
|
|
589
608
|
|
|
@@ -865,6 +884,9 @@ async function installOpenclaw(bearer) {
|
|
|
865
884
|
ok(`OpenClaw:注册 MCP(streamable-http)→ ${cfg}`);
|
|
866
885
|
}
|
|
867
886
|
|
|
887
|
+
// The last endpoint probe, recorded so `--report` can print it without probing twice.
|
|
888
|
+
let lastProbe = { reachable: false, tools: 0 };
|
|
889
|
+
|
|
868
890
|
async function verify() {
|
|
869
891
|
let allOk = true;
|
|
870
892
|
// Handshake, not a search. `tools/list` proves the endpoint speaks MCP and answers, and it
|
|
@@ -875,6 +897,7 @@ async function verify() {
|
|
|
875
897
|
// network was broken (found 2026-09-15 by running the agent chain test on this machine).
|
|
876
898
|
const probe = await mcpRequest('tools/list', {}, 6000, probeEndpoint());
|
|
877
899
|
const tools = Array.isArray(probe?.tools) ? probe.tools : [];
|
|
900
|
+
lastProbe = { reachable: tools.length > 0, tools: tools.length };
|
|
878
901
|
if (tools.length) {
|
|
879
902
|
ok(`端点可达:${ENDPOINT}(MCP 握手成功,${tools.length} 个工具)`);
|
|
880
903
|
} else if (probe && (probe.error || probe.protocolVersion || probe.serverInfo)) {
|
|
@@ -1113,6 +1136,88 @@ function uninstall() {
|
|
|
1113
1136
|
}
|
|
1114
1137
|
}
|
|
1115
1138
|
|
|
1139
|
+
/**
|
|
1140
|
+
* `--report`: this machine's state as YAML, safe to paste into a public issue.
|
|
1141
|
+
*
|
|
1142
|
+
* Why it exists: the bounty that asks strangers' machines to run the installer also asks them to
|
|
1143
|
+
* report back — and a report that a human has to assemble by hand is (a) rarely sent and (b)
|
|
1144
|
+
* usually containing their token. So the tool prints its own evidence, redacted by construction,
|
|
1145
|
+
* and leaves only the two things it cannot know (which tools the *agent* sees, and a line of
|
|
1146
|
+
* live-call evidence) as empty fields for the reporter to fill.
|
|
1147
|
+
*
|
|
1148
|
+
* Invariants: the token value never appears (only present/absent); every home path is written as
|
|
1149
|
+
* `~`; no hostname, no IP, no user name. `redact()` is deliberately blunt and applied to every
|
|
1150
|
+
* string, including the messages assembled by the checks.
|
|
1151
|
+
*/
|
|
1152
|
+
function redact(value) {
|
|
1153
|
+
return String(value)
|
|
1154
|
+
.split(HOME).join('~')
|
|
1155
|
+
.replace(/mcp_[A-Za-z0-9_-]{8,}/g, '<REDACTED>')
|
|
1156
|
+
.replace(/sk-[A-Za-z0-9_-]{8,}/g, '<REDACTED>')
|
|
1157
|
+
.replace(/Bearer [A-Za-z0-9_.-]{8,}/gi, 'Bearer <REDACTED>');
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
function voiceStatus() {
|
|
1161
|
+
try {
|
|
1162
|
+
const settings = readJson(join(HOME, '.claude', 'settings.json'), null);
|
|
1163
|
+
const buckets = settings?.hooks?.PostToolUse;
|
|
1164
|
+
if (!Array.isArray(buckets)) return 'absent';
|
|
1165
|
+
const ours = buckets.filter((entry) => JSON.stringify(entry).includes('voice-hook'));
|
|
1166
|
+
if (!ours.length) return 'absent';
|
|
1167
|
+
return ours.some((entry) => entry.matcher === '*') ? 'on' : 'stale';
|
|
1168
|
+
} catch {
|
|
1169
|
+
return 'unknown';
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function platformName() {
|
|
1174
|
+
if (process.platform === 'win32') return 'windows';
|
|
1175
|
+
if (process.platform === 'darwin') return 'macos';
|
|
1176
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return 'wsl2';
|
|
1177
|
+
return process.platform; // linux
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function distroName() {
|
|
1181
|
+
try {
|
|
1182
|
+
const release = readFileSync('/etc/os-release', 'utf8');
|
|
1183
|
+
const id = release.match(/^ID="?([a-z0-9._-]+)"?$/m);
|
|
1184
|
+
return id ? id[1] : 'unknown';
|
|
1185
|
+
} catch {
|
|
1186
|
+
return 'n/a';
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
async function report() {
|
|
1191
|
+
const allOk = await verify();
|
|
1192
|
+
const agents = AGENTS.filter((agent) => detect(agent));
|
|
1193
|
+
const token = existsSync(join(stateDir(), 'token'));
|
|
1194
|
+
const lines = [
|
|
1195
|
+
'# MisakaNet setup report — safe to paste in public; the two empty fields at the end are',
|
|
1196
|
+
'# for you to fill (they are the only parts this tool cannot know).',
|
|
1197
|
+
'schema: misakanet-setup-report/1',
|
|
1198
|
+
`setup-version: ${VERSION}`,
|
|
1199
|
+
`os: ${platformName()}`,
|
|
1200
|
+
`distro: ${distroName()}`,
|
|
1201
|
+
`arch: ${process.arch}`,
|
|
1202
|
+
`node: ${process.version}`,
|
|
1203
|
+
`detected-agents: [${agents.join(', ')}]`,
|
|
1204
|
+
`verify: ${allOk ? 'READY' : 'NOT READY'}`,
|
|
1205
|
+
`endpoint-reachable: ${lastProbe.reachable}`,
|
|
1206
|
+
`endpoint-tools: ${lastProbe.tools}`,
|
|
1207
|
+
`token: ${token ? 'present' : 'absent'}`,
|
|
1208
|
+
`hook: ${existsSync(join(stateDir(), 'hook.mjs')) ? 'present' : 'absent'}`,
|
|
1209
|
+
`voice: ${voiceStatus()}`,
|
|
1210
|
+
`open-items: ${manual.length}`,
|
|
1211
|
+
...(manual.length
|
|
1212
|
+
? ['open-items-detail:', ...manual.map((m) => ` - ${redact(m)}`)]
|
|
1213
|
+
: []),
|
|
1214
|
+
'# Fill these two in (see the bounty for how):',
|
|
1215
|
+
'tools-visible: {} # e.g. {codex: 7} — from `codex mcp list` / `codewhale mcp tools`',
|
|
1216
|
+
'live-call-evidence: "" # one line your agent printed when it called misakanet_search',
|
|
1217
|
+
];
|
|
1218
|
+
console.log(lines.join('\n'));
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1116
1221
|
// ── main ─────────────────────────────────────────────────────────────
|
|
1117
1222
|
function render() {
|
|
1118
1223
|
const out = [];
|
|
@@ -1122,9 +1227,15 @@ function render() {
|
|
|
1122
1227
|
return out.join('\n');
|
|
1123
1228
|
}
|
|
1124
1229
|
|
|
1125
|
-
const mode = has('--uninstall') ? 'uninstall'
|
|
1126
|
-
|
|
1127
|
-
|
|
1230
|
+
const mode = has('--uninstall') ? 'uninstall'
|
|
1231
|
+
: (has('--report') ? 'report'
|
|
1232
|
+
: (has('--verify') ? 'verify' : 'install'));
|
|
1233
|
+
if (mode !== 'report') {
|
|
1234
|
+
// The banner names the home directory, which is exactly the kind of thing that gets pasted
|
|
1235
|
+
// along with a report — so `--report` prints only the report.
|
|
1236
|
+
console.log(`MisakaNet 安装程序(npx 版)${DRY ? '(--dry-run,不会写任何文件)' : ''}${has('--upgrade') ? '(--upgrade:与安装等价,覆盖安装即升级)' : ''}`);
|
|
1237
|
+
console.log(`家目录:${HOME}\n`);
|
|
1238
|
+
}
|
|
1128
1239
|
|
|
1129
1240
|
if (mode === 'uninstall') {
|
|
1130
1241
|
uninstall();
|
|
@@ -1133,10 +1244,18 @@ if (mode === 'uninstall') {
|
|
|
1133
1244
|
process.exit(0);
|
|
1134
1245
|
}
|
|
1135
1246
|
|
|
1247
|
+
if (mode === 'report') {
|
|
1248
|
+
// Reporting must work before an install too: a bot's first useful data point is often "this
|
|
1249
|
+
// machine has no agent config at all". Exit 0 even when NOT READY — the report is evidence,
|
|
1250
|
+
// not a gate.
|
|
1251
|
+
await report();
|
|
1252
|
+
process.exit(0);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1136
1255
|
if (mode === 'verify') {
|
|
1137
1256
|
const allOk = await verify();
|
|
1138
1257
|
console.log(render());
|
|
1139
|
-
console.log(`\n结论:${allOk ? `READY —— 打开一个新会话,问它「${
|
|
1258
|
+
console.log(`\n结论:${allOk ? `READY —— 打开一个新会话,问它「${ONBOARDING_QUERIES[0]}」这类带报错原文的片段` : 'NOT READY —— 上面每条 ! 都给了修复动作'}`);
|
|
1140
1259
|
process.exit(allOk ? 0 : 1);
|
|
1141
1260
|
}
|
|
1142
1261
|
|
|
@@ -1165,6 +1284,6 @@ console.log(render());
|
|
|
1165
1284
|
console.log(`
|
|
1166
1285
|
接下来:
|
|
1167
1286
|
1) **把这个助手窗口关掉再打开一次**(新功能要重开会话才生效)
|
|
1168
|
-
2)
|
|
1287
|
+
2) 随便挑一句带报错原文的片段问它(例如「${ONBOARDING_QUERIES[0]}」「${ONBOARDING_QUERIES[1]}」「${ONBOARDING_QUERIES[2]}」)——它应该先去查经验库
|
|
1169
1288
|
3) 想确认状态:npx @misaka-net/misakanet-setup --verify
|
|
1170
1289
|
4) 想关掉:npx @misaka-net/misakanet-setup --uninstall`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@misaka-net/misakanet-setup",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
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"
|