@wowok/skills 3.0.3 → 3.1.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.
Files changed (47) hide show
  1. package/README.md +146 -122
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +223 -837
  5. package/dist/cli.js.map +1 -1
  6. package/dist/index.d.ts +4 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +24 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/installer.d.ts +121 -0
  11. package/dist/installer.d.ts.map +1 -0
  12. package/dist/installer.js +802 -0
  13. package/dist/installer.js.map +1 -0
  14. package/dist/skills.d.ts +5 -2
  15. package/dist/skills.d.ts.map +1 -1
  16. package/dist/skills.js +86 -62
  17. package/dist/skills.js.map +1 -1
  18. package/dist/targets.d.ts +94 -0
  19. package/dist/targets.d.ts.map +1 -0
  20. package/dist/targets.js +421 -0
  21. package/dist/targets.js.map +1 -0
  22. package/dist/types.d.ts +5 -4
  23. package/dist/types.d.ts.map +1 -1
  24. package/dist/types.js +0 -32
  25. package/dist/types.js.map +1 -1
  26. package/package.json +7 -4
  27. package/scripts/install.js +21 -858
  28. package/wowok-arbitrator/SKILL.md +5 -12
  29. package/wowok-auditor/SKILL.md +5 -17
  30. package/wowok-collaborator/SKILL.md +5 -17
  31. package/wowok-governance/SKILL.md +95 -0
  32. package/wowok-machine/SKILL.md +5 -18
  33. package/wowok-market/SKILL.md +82 -0
  34. package/wowok-messenger/SKILL.md +5 -18
  35. package/wowok-onboard/SKILL.md +5 -22
  36. package/wowok-order/SKILL.md +5 -18
  37. package/wowok-output/SKILL.md +5 -10
  38. package/wowok-planner/SKILL.md +5 -19
  39. package/wowok-provider/SKILL.md +5 -17
  40. package/wowok-supplier/SKILL.md +5 -16
  41. package/examples/Insurance/Insurance.md +0 -1245
  42. package/examples/MyShop/MyShop.md +0 -2003
  43. package/examples/MyShop/myshop_machine_nodes.json +0 -93
  44. package/examples/MyShop_Advanced/MyShop_Advanced.md +0 -2874
  45. package/examples/ThreeBody_Signature/ThreeBody_Signature.md +0 -1831
  46. package/examples/Travel/Travel.md +0 -1849
  47. package/examples/Travel/calc-weather-timestamps.js +0 -12
@@ -0,0 +1,802 @@
1
+ "use strict";
2
+ /**
3
+ * The ONE installer implementation, shared by:
4
+ * - the npm lifecycle hook (scripts/install.js → dist/installer.js)
5
+ * - the CLI (`wowok-skills init|uninit|doctor` → src/cli.ts)
6
+ *
7
+ * Responsibilities
8
+ * - copy every SKILL.md to every resolved target root (idempotent, hash-based)
9
+ * - prune skills that this tool installed but no longer ships
10
+ * - clean up deprecated skills + pre-3.1 artifacts (cursor `.mdc` rules, …)
11
+ * - register the MCP server in each client's DOCUMENTED config file, using a
12
+ * Windows-safe launcher (`node <abs entry>` instead of bare `npx`)
13
+ * - report per-client state (`doctor`)
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ Object.defineProperty(exports, "__esModule", { value: true });
49
+ exports.packageRoot = packageRoot;
50
+ exports.packageVersion = packageVersion;
51
+ exports.installSkillsInto = installSkillsInto;
52
+ exports.uninstallSkillsFrom = uninstallSkillsFrom;
53
+ exports.cleanupLegacyArtifacts = cleanupLegacyArtifacts;
54
+ exports.installSkillsForTargets = installSkillsForTargets;
55
+ exports.uninstallSkillsForTargets = uninstallSkillsForTargets;
56
+ exports.resolveInstalledMcpEntry = resolveInstalledMcpEntry;
57
+ exports.resolveMcpLaunch = resolveMcpLaunch;
58
+ exports.cleanupLegacyMcpEntries = cleanupLegacyMcpEntries;
59
+ exports.registerMcpForTargets = registerMcpForTargets;
60
+ exports.getInstalledMcpVersion = getInstalledMcpVersion;
61
+ exports.ensureMcpServer = ensureMcpServer;
62
+ exports.restartMcpServer = restartMcpServer;
63
+ exports.wowMcpDir = wowMcpDir;
64
+ exports.saveReferrer = saveReferrer;
65
+ exports.resolveTargets = resolveTargets;
66
+ exports.supportedTargetList = supportedTargetList;
67
+ exports.statusForTargets = statusForTargets;
68
+ exports.runLifecycle = runLifecycle;
69
+ const fs = __importStar(require("fs"));
70
+ const os = __importStar(require("os"));
71
+ const path = __importStar(require("path"));
72
+ const child_process_1 = require("child_process");
73
+ const crypto = __importStar(require("crypto"));
74
+ const targets_1 = require("./targets");
75
+ // =========================================================================
76
+ // Small helpers
77
+ // =========================================================================
78
+ function packageRoot() {
79
+ return path.resolve(__dirname, '..');
80
+ }
81
+ function packageVersion() {
82
+ try {
83
+ const raw = fs.readFileSync(path.join(packageRoot(), 'package.json'), 'utf-8');
84
+ return JSON.parse(raw).version || '0.0.0';
85
+ }
86
+ catch {
87
+ return '0.0.0';
88
+ }
89
+ }
90
+ function hashContent(content) {
91
+ return crypto.createHash('sha1').update(content).digest('hex').slice(0, 16);
92
+ }
93
+ function readSkillSource(name) {
94
+ const file = path.join(packageRoot(), name, 'SKILL.md');
95
+ try {
96
+ return fs.readFileSync(file, 'utf-8');
97
+ }
98
+ catch {
99
+ return null;
100
+ }
101
+ }
102
+ function removeDir(dir) {
103
+ fs.rmSync(dir, { recursive: true, force: true });
104
+ }
105
+ function readJson(file) {
106
+ try {
107
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ /** Is `dir/<name>/SKILL.md` one of our artifacts (or a deprecated one)? */
114
+ function isOurSkillDir(dir, name) {
115
+ const file = path.join(dir, name, 'SKILL.md');
116
+ if (!fs.existsSync(file))
117
+ return false;
118
+ try {
119
+ const raw = fs.readFileSync(file, 'utf-8');
120
+ const m = raw.match(/^---\r?\n[\s\S]*?\bname:\s*([\w-]+)/);
121
+ return !m || m[1] === name;
122
+ }
123
+ catch {
124
+ return false;
125
+ }
126
+ }
127
+ function readManifest(root) {
128
+ const m = readJson(path.join(root, targets_1.MANIFEST_FILE));
129
+ return m && typeof m === 'object' && m.files ? m : null;
130
+ }
131
+ function writeManifest(root, files) {
132
+ const manifest = {
133
+ tool: '@wowok/skills',
134
+ version: packageVersion(),
135
+ updated_at: new Date().toISOString(),
136
+ files,
137
+ };
138
+ fs.writeFileSync(path.join(root, targets_1.MANIFEST_FILE), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
139
+ }
140
+ /** Install (or refresh) every shipped skill into one root directory. */
141
+ function installSkillsInto(root, opts = {}) {
142
+ const result = {
143
+ root,
144
+ written: 0,
145
+ unchanged: 0,
146
+ pruned: 0,
147
+ legacyRemoved: 0,
148
+ errors: [],
149
+ };
150
+ try {
151
+ fs.mkdirSync(root, { recursive: true });
152
+ }
153
+ catch (err) {
154
+ result.errors.push(`cannot create ${root}: ${err.message}`);
155
+ return result;
156
+ }
157
+ // 1. Remove deprecated skills (v2.0 sink refactor) — only our own artifacts.
158
+ for (const legacy of targets_1.LEGACY_SKILL_NAMES) {
159
+ const dir = path.join(root, legacy);
160
+ if (fs.existsSync(dir) && isOurSkillDir(root, legacy)) {
161
+ removeDir(dir);
162
+ result.legacyRemoved++;
163
+ console.log(`[wowok-skills] removed deprecated skill: ${dir} (now served by ${targets_1.SKILL_MIGRATION_TARGETS[legacy]})`);
164
+ }
165
+ }
166
+ // 2. Write every shipped skill when its content differs.
167
+ const previous = readManifest(root);
168
+ const files = {};
169
+ for (const name of targets_1.SKILL_NAMES) {
170
+ const content = readSkillSource(name);
171
+ if (content === null) {
172
+ result.errors.push(`SKILL.md not found in package for ${name}`);
173
+ continue;
174
+ }
175
+ const hash = hashContent(content);
176
+ files[name] = hash;
177
+ const destDir = path.join(root, name);
178
+ const dest = path.join(destDir, 'SKILL.md');
179
+ const current = fs.existsSync(dest) ? fs.readFileSync(dest, 'utf-8') : null;
180
+ if (!opts.force && current !== null && hashContent(current) === hash) {
181
+ result.unchanged++;
182
+ continue;
183
+ }
184
+ fs.mkdirSync(destDir, { recursive: true });
185
+ fs.writeFileSync(dest, content, 'utf-8');
186
+ result.written++;
187
+ }
188
+ // 3. Prune skills a previous version installed but this version no longer ships.
189
+ if (previous) {
190
+ for (const name of Object.keys(previous.files)) {
191
+ if (targets_1.SKILL_NAMES.includes(name))
192
+ continue;
193
+ const dir = path.join(root, name);
194
+ if (fs.existsSync(dir) && isOurSkillDir(root, name)) {
195
+ removeDir(dir);
196
+ result.pruned++;
197
+ console.log(`[wowok-skills] pruned obsolete skill: ${dir}`);
198
+ }
199
+ }
200
+ }
201
+ // 4. Remove pre-3.1 artifacts: dirs that only hold an old cursor `.mdc` rule.
202
+ for (const name of [...targets_1.SKILL_NAMES, ...targets_1.LEGACY_SKILL_NAMES]) {
203
+ const dir = path.join(root, name);
204
+ if (!fs.existsSync(dir))
205
+ continue;
206
+ const entries = fs.readdirSync(dir);
207
+ const onlyLegacyMdc = entries.length > 0 &&
208
+ entries.every((e) => e === `${name}.mdc` || e === 'SKILL.md') &&
209
+ entries.some((e) => e.endsWith('.mdc'));
210
+ if (onlyLegacyMdc) {
211
+ removeDir(dir);
212
+ result.pruned++;
213
+ console.log(`[wowok-skills] removed legacy rule artifact: ${dir}`);
214
+ }
215
+ }
216
+ writeManifest(root, files);
217
+ return result;
218
+ }
219
+ /** Remove every skill this tool owns from one root directory. */
220
+ function uninstallSkillsFrom(root) {
221
+ let removed = 0;
222
+ for (const name of [...targets_1.SKILL_NAMES, ...targets_1.LEGACY_SKILL_NAMES]) {
223
+ const dir = path.join(root, name);
224
+ if (fs.existsSync(dir) && isOurSkillDir(root, name)) {
225
+ removeDir(dir);
226
+ removed++;
227
+ console.log(`[wowok-skills] removed: ${dir}`);
228
+ }
229
+ }
230
+ const manifest = path.join(root, targets_1.MANIFEST_FILE);
231
+ if (fs.existsSync(manifest))
232
+ fs.unlinkSync(manifest);
233
+ return removed;
234
+ }
235
+ /**
236
+ * Remove stale artifacts written by installers older than v3.1:
237
+ * cursor `.mdc` rules, `.prompt.md` files, and skills placed in directories the
238
+ * client never reads (`~/.codex/skills`, `~/.windsurf/skills`, …).
239
+ *
240
+ * Only entries named `wowok-*` that carry one of OUR files are touched, and a
241
+ * directory that is also a current target root is never touched (so a legacy
242
+ * path that became correct keeps working).
243
+ */
244
+ function cleanupLegacyArtifacts(targetIds, scope, cwd) {
245
+ let removed = 0;
246
+ const currentRoots = (0, targets_1.resolveSkillRoots)(targetIds, scope, cwd);
247
+ for (const id of targetIds) {
248
+ const target = (0, targets_1.getClientTarget)(id);
249
+ if (!target)
250
+ continue;
251
+ const dirs = scope === 'user'
252
+ ? (target.legacyUserDirs ?? [])
253
+ : (target.legacyProjectDirs ?? []);
254
+ for (const raw of dirs) {
255
+ const top = scope === 'user' ? (0, targets_1.expandHome)(raw) : path.resolve(cwd, raw);
256
+ if (currentRoots.includes(top))
257
+ continue;
258
+ if (!fs.existsSync(top))
259
+ continue;
260
+ for (const entry of fs.readdirSync(top, { withFileTypes: true })) {
261
+ if (!entry.name.startsWith('wowok-'))
262
+ continue;
263
+ const dir = path.join(top, entry.name);
264
+ const artifacts = [
265
+ 'SKILL.md',
266
+ `${entry.name}.mdc`,
267
+ `${entry.name}.prompt.md`,
268
+ ];
269
+ let isOurs;
270
+ if (entry.isDirectory()) {
271
+ isOurs = fs.readdirSync(dir).some((f) => artifacts.includes(f));
272
+ }
273
+ else {
274
+ isOurs = entry.name.endsWith('.mdc') || entry.name.endsWith('.prompt.md');
275
+ }
276
+ if (isOurs) {
277
+ removeDir(dir);
278
+ removed++;
279
+ console.log(`[wowok-skills] removed pre-3.1 artifact: ${dir}`);
280
+ }
281
+ }
282
+ // Drop the container directory when we emptied one we created ourselves.
283
+ if (fs.existsSync(top) && fs.readdirSync(top).length === 0) {
284
+ fs.rmdirSync(top);
285
+ console.log(`[wowok-skills] removed empty directory: ${top}`);
286
+ }
287
+ }
288
+ }
289
+ return removed;
290
+ }
291
+ /** Install skills for the given targets into the requested scope(s). */
292
+ function installSkillsForTargets(targetIds, scopes, cwd, opts = {}) {
293
+ const results = [];
294
+ for (const scope of scopes) {
295
+ cleanupLegacyArtifacts(targetIds, scope, cwd);
296
+ for (const root of (0, targets_1.resolveSkillRoots)(targetIds, scope, cwd)) {
297
+ results.push(installSkillsInto(root, opts));
298
+ }
299
+ }
300
+ return results;
301
+ }
302
+ /** Uninstall skills for the given targets (both scopes). */
303
+ function uninstallSkillsForTargets(targetIds, scopes, cwd) {
304
+ let total = 0;
305
+ for (const scope of scopes) {
306
+ for (const root of (0, targets_1.resolveSkillRoots)(targetIds, scope, cwd)) {
307
+ if (!fs.existsSync(root))
308
+ continue;
309
+ total += uninstallSkillsFrom(root);
310
+ }
311
+ cleanupLegacyArtifacts(targetIds, scope, cwd);
312
+ }
313
+ return total;
314
+ }
315
+ // =========================================================================
316
+ // MCP server: install / upgrade / register
317
+ // =========================================================================
318
+ function globalNpmRoot() {
319
+ try {
320
+ const out = (0, child_process_1.execSync)('npm root -g', { encoding: 'utf-8', stdio: 'pipe', timeout: 15000 });
321
+ const dir = out.trim();
322
+ return dir && fs.existsSync(dir) ? dir : null;
323
+ }
324
+ catch {
325
+ return null;
326
+ }
327
+ }
328
+ /** Absolute path of the globally installed MCP entry file, if present. */
329
+ function resolveInstalledMcpEntry() {
330
+ const root = globalNpmRoot();
331
+ if (!root)
332
+ return null;
333
+ const pkgDir = path.join(root, targets_1.MCP_PACKAGE);
334
+ const pkgJson = readJson(path.join(pkgDir, 'package.json'));
335
+ if (!pkgJson)
336
+ return null;
337
+ const main = pkgJson.main || 'dist/index.js';
338
+ const entry = path.resolve(pkgDir, main);
339
+ return fs.existsSync(entry) ? entry : null;
340
+ }
341
+ /**
342
+ * How a client should launch the MCP server.
343
+ *
344
+ * Preferred: `node <absolute entry>` — version-pinned to the package that
345
+ * `ensureMcpServer()` verified, and works on native Windows (node.exe is a real
346
+ * executable, unlike the `npx.cmd` shim).
347
+ * Fallback: `cmd /c npx -y …` on Windows / `npx -y …` elsewhere — required
348
+ * because bare `npx` cannot be spawned on native Windows (spawn ENOENT).
349
+ */
350
+ function resolveMcpLaunch() {
351
+ const entry = resolveInstalledMcpEntry();
352
+ if (entry)
353
+ return { command: 'node', args: [entry] };
354
+ if (process.platform === 'win32') {
355
+ return { command: 'cmd', args: ['/c', 'npx', '-y', targets_1.MCP_PACKAGE] };
356
+ }
357
+ return { command: 'npx', args: ['-y', targets_1.MCP_PACKAGE] };
358
+ }
359
+ function tomlString(value) {
360
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
361
+ }
362
+ function writeMcpSpec(spec, launch) {
363
+ const { path: file, format } = spec;
364
+ try {
365
+ // ── TOML (Codex: ~/.codex/config.toml) ────────────────────────────
366
+ if (format === 'toml') {
367
+ const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : '';
368
+ if (/^\s*\[mcp_servers\.wowok\]\s*$/m.test(existing)) {
369
+ return { path: file, status: 'unchanged' };
370
+ }
371
+ const block = `\n[mcp_servers.wowok]\n` +
372
+ `command = ${tomlString(launch.command)}\n` +
373
+ `args = [${launch.args.map(tomlString).join(', ')}]\n`;
374
+ fs.mkdirSync(path.dirname(file), { recursive: true });
375
+ fs.writeFileSync(file, existing + block, 'utf-8');
376
+ return { path: file, status: 'written' };
377
+ }
378
+ // ── Kilo Code new platform: { "mcp": { "wowok": { type:"local", command:[…] } } }
379
+ if (format === 'kilo-json') {
380
+ let config = {};
381
+ if (fs.existsSync(file)) {
382
+ try {
383
+ config = JSON.parse(fs.readFileSync(file, 'utf-8'));
384
+ }
385
+ catch (err) {
386
+ return { path: file, status: 'error', detail: `not valid JSON — left untouched (${err.message})` };
387
+ }
388
+ }
389
+ const before = JSON.stringify(config);
390
+ config.mcp = config.mcp || {};
391
+ config.mcp.wowok = {
392
+ type: 'local',
393
+ command: [launch.command, ...launch.args],
394
+ enabled: true,
395
+ };
396
+ if (JSON.stringify(config) === before)
397
+ return { path: file, status: 'unchanged' };
398
+ fs.mkdirSync(path.dirname(file), { recursive: true });
399
+ fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n', 'utf-8');
400
+ return { path: file, status: 'written' };
401
+ }
402
+ // ── JSON with an `mcpServers` map (everyone else) ────────────────
403
+ let config = {};
404
+ if (fs.existsSync(file)) {
405
+ const raw = fs.readFileSync(file, 'utf-8');
406
+ try {
407
+ config = raw.trim() ? JSON.parse(raw) : {};
408
+ }
409
+ catch (err) {
410
+ return { path: file, status: 'error', detail: `not valid JSON — left untouched (${err.message})` };
411
+ }
412
+ if (config === null || typeof config !== 'object' || Array.isArray(config)) {
413
+ return { path: file, status: 'error', detail: 'unexpected JSON shape — left untouched' };
414
+ }
415
+ }
416
+ const before = JSON.stringify(config);
417
+ config.mcpServers = config.mcpServers || {};
418
+ const previousEntry = config.mcpServers.wowok;
419
+ const entry = {
420
+ ...(spec.extraEntry || {}),
421
+ command: launch.command,
422
+ args: launch.args,
423
+ };
424
+ // Never silently re-enable a server the user disabled on purpose.
425
+ if (previousEntry && typeof previousEntry === 'object' && 'disabled' in previousEntry) {
426
+ if (previousEntry.disabled)
427
+ entry.disabled = true;
428
+ }
429
+ config.mcpServers.wowok = entry;
430
+ if (JSON.stringify(config) === before)
431
+ return { path: file, status: 'unchanged' };
432
+ fs.mkdirSync(path.dirname(file), { recursive: true });
433
+ fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n', 'utf-8');
434
+ return { path: file, status: 'written' };
435
+ }
436
+ catch (err) {
437
+ return { path: file, status: 'error', detail: err.message };
438
+ }
439
+ }
440
+ /**
441
+ * Remove stale MCP entries that a pre-3.1 installer wrote into files the client
442
+ * never reads (`~/.claude/settings.json`, `~/.roo/mcp_settings.json`, Qoder's
443
+ * `mcp-settings.json`). Only the `wowok` entry is touched; a file that becomes
444
+ * empty is deleted, anything else in it is preserved.
445
+ */
446
+ function cleanupLegacyMcpEntries(targetIds) {
447
+ let cleaned = 0;
448
+ for (const id of targetIds) {
449
+ const target = (0, targets_1.getClientTarget)(id);
450
+ if (!target?.legacyMcpFiles)
451
+ continue;
452
+ for (const file of target.legacyMcpFiles) {
453
+ if (!fs.existsSync(file))
454
+ continue;
455
+ const config = readJson(file);
456
+ if (!config || typeof config !== 'object')
457
+ continue;
458
+ let touched = false;
459
+ for (const container of ['mcpServers', 'mcp']) {
460
+ if (config[container] && typeof config[container] === 'object' && 'wowok' in config[container]) {
461
+ delete config[container].wowok;
462
+ touched = true;
463
+ }
464
+ }
465
+ if (!touched)
466
+ continue;
467
+ const meaningful = Object.entries(config).some(([, value]) => value !== null &&
468
+ typeof value === 'object' &&
469
+ Object.keys(value).length > 0);
470
+ if (meaningful) {
471
+ fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n', 'utf-8');
472
+ console.log(`[wowok-skills] removed stale MCP entry from ${file}`);
473
+ }
474
+ else {
475
+ fs.unlinkSync(file);
476
+ console.log(`[wowok-skills] removed stale MCP file (created by a pre-3.1 installer): ${file}`);
477
+ }
478
+ cleaned++;
479
+ }
480
+ }
481
+ return cleaned;
482
+ }
483
+ /** Register the MCP server for the given targets (defaults to the given scope). */
484
+ function registerMcpForTargets(targetIds, scopes, cwd) {
485
+ const launch = resolveMcpLaunch();
486
+ const results = [];
487
+ const notes = new Set();
488
+ cleanupLegacyMcpEntries(targetIds);
489
+ for (const scope of scopes) {
490
+ for (const spec of (0, targets_1.resolveMcpSpecs)(targetIds, scope, cwd)) {
491
+ if (spec.note)
492
+ notes.add(spec.note);
493
+ results.push(writeMcpSpec(spec, launch));
494
+ }
495
+ }
496
+ for (const id of targetIds) {
497
+ for (const note of (0, targets_1.getClientTarget)(id)?.notes ?? [])
498
+ notes.add(note);
499
+ }
500
+ for (const note of notes)
501
+ console.log(`[wowok-skills] note: ${note}`);
502
+ return results;
503
+ }
504
+ function semverMajor(version) {
505
+ if (!version)
506
+ return 0;
507
+ return parseInt(version.split('.')[0], 10) || 0;
508
+ }
509
+ function getInstalledMcpVersion() {
510
+ try {
511
+ const out = (0, child_process_1.execSync)(`npm ls -g ${targets_1.MCP_PACKAGE} --depth=0 --json`, {
512
+ encoding: 'utf-8',
513
+ stdio: 'pipe',
514
+ timeout: 15000,
515
+ });
516
+ const parsed = JSON.parse(out);
517
+ const pkg = parsed.dependencies?.[targets_1.MCP_PACKAGE] || parsed[targets_1.MCP_PACKAGE];
518
+ return pkg?.version || null;
519
+ }
520
+ catch {
521
+ return null;
522
+ }
523
+ }
524
+ function getLatestMcpVersion() {
525
+ try {
526
+ const out = (0, child_process_1.execSync)(`npm view --prefer-online ${targets_1.MCP_PACKAGE} version`, {
527
+ encoding: 'utf-8',
528
+ stdio: 'pipe',
529
+ timeout: 15000,
530
+ });
531
+ return out.trim() || null;
532
+ }
533
+ catch {
534
+ return null;
535
+ }
536
+ }
537
+ function npmInstallGlobal(pkg, retries = 2) {
538
+ for (let attempt = 1; attempt <= retries; attempt++) {
539
+ try {
540
+ (0, child_process_1.execSync)(`npm install -g ${pkg}`, { stdio: 'inherit', timeout: 180000 });
541
+ return true;
542
+ }
543
+ catch (err) {
544
+ if (attempt >= retries) {
545
+ console.error(`[wowok-skills] npm install failed after ${retries} attempts: ${err.message}`);
546
+ return false;
547
+ }
548
+ console.log(`[wowok-skills] npm install attempt ${attempt} failed, retrying...`);
549
+ (0, child_process_1.execSync)(process.platform === 'win32' ? 'ping -n 3 127.0.0.1 >nul' : 'sleep 2', {
550
+ stdio: 'pipe',
551
+ timeout: 8000,
552
+ });
553
+ }
554
+ }
555
+ return false;
556
+ }
557
+ /** Ensure the global MCP server exists and is current. Returns true if changed. */
558
+ function ensureMcpServer() {
559
+ const current = getInstalledMcpVersion();
560
+ const latest = getLatestMcpVersion();
561
+ if (current) {
562
+ console.log(`[wowok-skills] MCP server ${targets_1.MCP_PACKAGE} v${current} found.`);
563
+ if (!latest) {
564
+ console.log(' cannot reach the registry — keeping the installed version.');
565
+ return false;
566
+ }
567
+ if (current === latest) {
568
+ console.log(` up to date (v${current}).`);
569
+ return false;
570
+ }
571
+ if (semverMajor(latest) > semverMajor(current)) {
572
+ console.warn(` ⚠ major version bump v${current} → v${latest}; skipping auto-upgrade.`);
573
+ console.warn(` upgrade manually: npm install -g ${targets_1.MCP_PACKAGE}@latest`);
574
+ return false;
575
+ }
576
+ console.log(` upgrading v${current} → v${latest}...`);
577
+ if (npmInstallGlobal(`${targets_1.MCP_PACKAGE}@latest`))
578
+ return true;
579
+ console.error(` upgrade failed — keeping v${current}.`);
580
+ return false;
581
+ }
582
+ console.log(`[wowok-skills] Installing MCP server ${targets_1.MCP_PACKAGE}...`);
583
+ if (npmInstallGlobal(targets_1.MCP_PACKAGE)) {
584
+ console.log(' installed.');
585
+ return true;
586
+ }
587
+ console.error(' installation failed.');
588
+ return false;
589
+ }
590
+ /**
591
+ * Kill the running MCP server process so clients relaunch it with the new code.
592
+ * Matches ONLY our launcher (`node <entry>` / the npx cache invocation) so an
593
+ * unrelated node process is never touched.
594
+ */
595
+ function restartMcpServer() {
596
+ try {
597
+ if (process.platform === 'win32') {
598
+ const entry = resolveInstalledMcpEntry();
599
+ const patterns = [`'%${targets_1.MCP_PACKAGE}%'`, `'%agent-mcp%'`];
600
+ const entryClause = entry
601
+ ? `OR (${patterns.map((p) => `CommandLine LIKE ${p}`).join(' OR ')})`
602
+ : '';
603
+ const ps = 'Get-CimInstance Win32_Process -Filter "Name=\'node.exe\'" | ' +
604
+ `Where-Object { $_.CommandLine -like '*agent-mcp*' ${entryClause} } | ` +
605
+ 'ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }';
606
+ const encoded = Buffer.from(ps, 'utf16le').toString('base64');
607
+ (0, child_process_1.execSync)(`powershell -NoProfile -EncodedCommand ${encoded}`, { stdio: 'pipe', timeout: 15000 });
608
+ }
609
+ else {
610
+ (0, child_process_1.execSync)(`pkill -f "node_modules/${targets_1.MCP_PACKAGE}/" || pkill -f "npx.*${targets_1.MCP_PACKAGE}" || true`, {
611
+ stdio: 'pipe',
612
+ timeout: 8000,
613
+ });
614
+ }
615
+ console.log('[wowok-skills] MCP server process terminated (clients auto-restart it).');
616
+ }
617
+ catch {
618
+ console.log('[wowok-skills] No running MCP server process found.');
619
+ }
620
+ }
621
+ // =========================================================================
622
+ // Airdrop referrer
623
+ // =========================================================================
624
+ /** Wow MCP data dir — mirrors @wowok/wowok getWowMcpDir(). */
625
+ function wowMcpDir() {
626
+ const h = os.homedir();
627
+ let wowDir;
628
+ if (process.env.WOWOK_DATA_DIR) {
629
+ wowDir = process.env.WOWOK_DATA_DIR;
630
+ }
631
+ else if (process.platform === 'win32') {
632
+ wowDir = path.join(h, '.wow', 'V1');
633
+ }
634
+ else if (process.platform === 'darwin') {
635
+ wowDir = path.join(h, 'Library', 'Application Support', '.wow', 'V1');
636
+ }
637
+ else {
638
+ const xdgConfig = process.env.XDG_CONFIG_HOME;
639
+ const xdgData = process.env.XDG_DATA_HOME;
640
+ if (xdgConfig)
641
+ wowDir = path.join(xdgConfig, '.wow', 'V1');
642
+ else
643
+ wowDir = xdgData ? path.join(xdgData, '.wow', 'V1') : path.join(h, '.wow', 'V1');
644
+ }
645
+ return path.join(path.dirname(wowDir), 'mcp');
646
+ }
647
+ function saveReferrer(referrer) {
648
+ const dir = wowMcpDir();
649
+ fs.mkdirSync(dir, { recursive: true });
650
+ fs.writeFileSync(path.join(dir, 'referrer'), referrer.trim() + '\n', 'utf-8');
651
+ console.log(`[wowok-skills] airdrop referrer saved: ${referrer.trim()}`);
652
+ console.log('[wowok-skills] it is auto-recorded on your first on-chain interaction.');
653
+ }
654
+ // =========================================================================
655
+ // Target selection
656
+ // =========================================================================
657
+ /** Parse `--target` / positional / env overrides into a validated target list. */
658
+ function resolveTargets(requested, env = process.env.WOWOK_SKILLS_TARGETS) {
659
+ let ids = [];
660
+ const unknown = [];
661
+ if (requested && requested.length > 0) {
662
+ ids = [...requested];
663
+ }
664
+ else if (env) {
665
+ ids = env.split(',').map((t) => t.trim()).filter(Boolean);
666
+ }
667
+ else {
668
+ return { targets: [...targets_1.DEFAULT_TARGET_IDS], unknown };
669
+ }
670
+ if (ids.includes('all'))
671
+ return { targets: [...targets_1.DEFAULT_TARGET_IDS], unknown };
672
+ const targets = [];
673
+ for (const id of ids) {
674
+ const target = (0, targets_1.getClientTarget)(id);
675
+ if (!target) {
676
+ unknown.push(id);
677
+ continue;
678
+ }
679
+ if (!targets.includes(target.id))
680
+ targets.push(target.id);
681
+ }
682
+ return { targets, unknown };
683
+ }
684
+ function supportedTargetList() {
685
+ return targets_1.CLIENT_TARGETS.map((t) => `${t.id.padEnd(10)} ${t.label}`).join('\n');
686
+ }
687
+ function rootStatus(root) {
688
+ const status = {
689
+ root,
690
+ present: fs.existsSync(root),
691
+ fresh: 0,
692
+ stale: 0,
693
+ missing: 0,
694
+ legacy: 0,
695
+ manifestVersion: readManifest(root)?.version ?? null,
696
+ };
697
+ for (const name of targets_1.SKILL_NAMES) {
698
+ const dest = path.join(root, name, 'SKILL.md');
699
+ const src = readSkillSource(name);
700
+ if (!fs.existsSync(dest)) {
701
+ status.missing++;
702
+ }
703
+ else if (src !== null && hashContent(fs.readFileSync(dest, 'utf-8')) === hashContent(src)) {
704
+ status.fresh++;
705
+ }
706
+ else {
707
+ status.stale++;
708
+ }
709
+ }
710
+ status.legacy = targets_1.LEGACY_SKILL_NAMES.filter((n) => fs.existsSync(path.join(root, n))).length;
711
+ return status;
712
+ }
713
+ function statusForTargets(targetIds, cwd) {
714
+ return targetIds.map((id) => {
715
+ const target = (0, targets_1.getClientTarget)(id);
716
+ const mcp = [
717
+ ...(0, targets_1.resolveMcpSpecs)([id], 'user', cwd),
718
+ ...(0, targets_1.resolveMcpSpecs)([id], 'project', cwd),
719
+ ].map((spec) => {
720
+ const present = fs.existsSync(spec.path);
721
+ let registered = false;
722
+ let detail;
723
+ if (present) {
724
+ const raw = fs.readFileSync(spec.path, 'utf-8');
725
+ registered =
726
+ spec.format === 'toml'
727
+ ? /^\s*\[mcp_servers\.wowok\]\s*$/m.test(raw)
728
+ : /"wowok"\s*:/.test(raw);
729
+ }
730
+ if (spec.note)
731
+ detail = spec.note;
732
+ if (id === 'trae' && present && registered) {
733
+ const parsed = readJson(spec.path);
734
+ if (parsed?.mcpServers?.wowok?.disabled)
735
+ detail = 'entry exists but is DISABLED in Trae — enable it in the MCP panel';
736
+ }
737
+ return { path: spec.path, present, registered, detail };
738
+ });
739
+ return {
740
+ id,
741
+ label: target.label,
742
+ userRoots: (0, targets_1.resolveSkillRoots)([id], 'user', cwd).map(rootStatus),
743
+ projectRoots: (0, targets_1.resolveSkillRoots)([id], 'project', cwd).map(rootStatus),
744
+ mcp,
745
+ notes: target.notes ?? [],
746
+ };
747
+ });
748
+ }
749
+ // =========================================================================
750
+ // Entry points
751
+ // =========================================================================
752
+ /**
753
+ * npm lifecycle hook. `postinstall` installs to every client (this is the
754
+ * one-command onboarding path). There is intentionally NO uninstall hook —
755
+ * npm v7+ never runs `preuninstall`, so removal is an explicit CLI command.
756
+ */
757
+ function runLifecycle(event) {
758
+ if (event !== 'postinstall')
759
+ return;
760
+ const { targets, unknown } = resolveTargets(undefined);
761
+ if (unknown.length > 0)
762
+ console.warn(`[wowok-skills] ignoring unknown targets: ${unknown.join(', ')}`);
763
+ console.log(`[wowok-skills] Installing skills for ${targets.length} client target(s)...`);
764
+ const results = installSkillsForTargets(targets, ['user'], process.cwd());
765
+ let written = 0;
766
+ let unchanged = 0;
767
+ for (const r of results) {
768
+ written += r.written;
769
+ unchanged += r.unchanged;
770
+ console.log(`[wowok-skills] → ${r.root} (${r.written} written, ${r.unchanged} up to date, ${r.legacyRemoved} deprecated removed)`);
771
+ for (const err of r.errors)
772
+ console.warn(`[wowok-skills] WARN: ${err}`);
773
+ }
774
+ console.log(`[wowok-skills] Done — ${written} files written, ${unchanged} already current.`);
775
+ const skipMcp = process.env.WOWOK_SKILLS_NO_MCP === '1' || process.env.WOWOK_SKILLS_NO_MCP === 'true';
776
+ if (skipMcp) {
777
+ console.log('[wowok-skills] WOWOK_SKILLS_NO_MCP=1 — skipping MCP server management.');
778
+ }
779
+ else {
780
+ console.log('');
781
+ console.log('[wowok-skills] Checking MCP server...');
782
+ const changed = ensureMcpServer();
783
+ console.log('[wowok-skills] Registering MCP server in client config files...');
784
+ const launch = resolveMcpLaunch();
785
+ console.log(`[wowok-skills] launcher: ${launch.command} ${launch.args.join(' ')}` +
786
+ (launch.command === 'node' ? ' (version-pinned, Windows-safe)' : ' (npx fallback)'));
787
+ for (const r of registerMcpForTargets(targets, ['user'], process.cwd())) {
788
+ console.log(`[wowok-skills] ${r.status.padEnd(9)} ${r.path}${r.detail ? ` (${r.detail})` : ''}`);
789
+ }
790
+ if (changed)
791
+ restartMcpServer();
792
+ }
793
+ const referrer = process.env.WOWOK_REFERRER;
794
+ if (referrer && referrer.trim()) {
795
+ console.log('');
796
+ saveReferrer(referrer);
797
+ }
798
+ console.log('');
799
+ console.log('[wowok-skills] Global install complete. Verify with: wowok-skills doctor');
800
+ console.log('[wowok-skills] To also commit skills into a project: cd <project> && wowok-skills init');
801
+ }
802
+ //# sourceMappingURL=installer.js.map