@wowok/skills 3.0.4 → 3.1.1

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 +5 -4
  27. package/scripts/install.js +21 -859
  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 +10 -30
  32. package/wowok-machine/SKILL.md +5 -18
  33. package/wowok-market/SKILL.md +5 -21
  34. package/wowok-messenger/SKILL.md +32 -50
  35. package/wowok-onboard/SKILL.md +5 -22
  36. package/wowok-order/SKILL.md +6 -19
  37. package/wowok-output/SKILL.md +5 -10
  38. package/wowok-planner/SKILL.md +6 -20
  39. package/wowok-provider/SKILL.md +6 -18
  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 -2880
  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
@@ -1,871 +1,33 @@
1
1
  /**
2
- * WoWok Skills installer
2
+ * npm lifecycle entry point.
3
3
  *
4
- * npm lifecycle integration:
5
- * postinstall → copy SKILL.md folders to ALL client skill dirs by default
6
- * (no need to pick a client — skills fit everywhere)
7
- * preuninstall → remove SKILL.md folders from all installed client dirs
4
+ * Deliberately tiny: ALL logic lives in src/installer.ts so the npm hook and
5
+ * the `wowok-skills` CLI share one implementation (the two used to be
6
+ * duplicated and drifted apart).
8
7
  *
9
- * Environment variables:
10
- * WOWOK_SKILLS_TARGETS Comma-separated client targets to override the default (all).
11
- * Rarely needed — the default installs to every supported client.
12
- * WOWOK_SKILLS_NO_MCP Set to "1" or "true" to skip MCP server management (default: auto-install MCP)
13
- * WOWOK_REFERRER Airdrop referrer (address or name) to record during global install —
14
- * saved once so the MCP auto-injects it on first on-chain interaction.
15
- */
16
-
17
- const fs = require('fs');
18
- const path = require('path');
19
- const os = require('os');
20
- const { execSync } = require('child_process');
21
-
22
- /**
23
- * Retained skills (post GLM5-31 sink refactor) — only these are installed.
24
- * The 4 rule-reference skills (wowok-safety/tools/scenario/guard) were sunk
25
- * into the MCP knowledge layer and are served by the MCP server directly:
26
- * - wowok-safety → schema_query action='get_safety_rules'
27
- * - wowok-tools → schema_query action='get_tool_reference'
28
- * - wowok-scenario → industry_pack_operation recommend_industry / list_modes
29
- * - wowok-guard → schema_query action='get_guard_design_patterns'
30
- */
31
- const SKILL_DIRS = [
32
- 'wowok-provider',
33
- 'wowok-arbitrator',
34
- 'wowok-order',
35
- 'wowok-messenger',
36
- 'wowok-machine',
37
- 'wowok-output',
38
- 'wowok-onboard',
39
- 'wowok-planner',
40
- 'wowok-auditor',
41
- 'wowok-supplier',
42
- 'wowok-collaborator',
43
- 'wowok-market',
44
- 'wowok-governance',
45
- ];
46
-
47
- /**
48
- * Deprecated skills removed in the GLM5-31 sink refactor. They are NEVER
49
- * installed, but preuninstall/init still clean them up from legacy installs
50
- * and warn the user where the content now lives.
51
- */
52
- const LEGACY_SKILL_DIRS = [
53
- 'wowok-guard',
54
- 'wowok-tools',
55
- 'wowok-safety',
56
- 'wowok-scenario',
57
- ];
58
-
59
- /** Where each deprecated skill's content now lives (migration message). */
60
- const SKILL_MIGRATION_MAP = {
61
- 'wowok-safety': "MCP schema_query action='get_safety_rules'",
62
- 'wowok-tools': "MCP schema_query action='get_tool_reference'",
63
- 'wowok-scenario': "MCP industry_pack_operation action='recommend_industry' / 'list_modes'",
64
- 'wowok-guard': "MCP schema_query action='get_guard_design_patterns'",
65
- };
66
-
67
- const CLIENT_DIRS = {
68
- claude: path.join(os.homedir(), '.claude', 'skills'),
69
- cursor: path.join(os.homedir(), '.cursor', 'rules'),
70
- windsurf: path.join(os.homedir(), '.windsurf', 'skills'),
71
- codebuddy: path.join(os.homedir(), '.codebuddy', 'skills'),
72
- codex: path.join(os.homedir(), '.codex', 'skills'),
73
- trae: path.join(os.homedir(), '.agents', 'skills'),
74
- qoder: path.join(os.homedir(), '.qoder', 'skills'),
75
- roo: path.join(os.homedir(), '.roo', 'skills'),
76
- cline: path.join(os.homedir(), '.cline', 'skills'),
77
- kilo: path.join(os.homedir(), '.kilo', 'skills'),
78
- agents: path.join(os.homedir(), '.agents', 'skills'), // deprecated alias
79
- copilot: path.join(os.homedir(), '.github', 'prompts'),
80
- };
81
-
82
- const ALL_CLIENT_TARGETS = [
83
- 'claude', 'cursor', 'windsurf', 'codebuddy', 'codex', 'trae', 'qoder', 'roo', 'cline', 'kilo', 'copilot',
84
- ];
85
-
86
- function getPackageRoot() {
87
- return path.resolve(__dirname, '..');
88
- }
89
-
90
- /**
91
- * Wow MCP data dir — mirrors @wowok/wowok getWowMcpDir(): dirname(wowDir)/mcp.
92
- * Windows: %USERPROFILE%\.wow\mcp · macOS: ~/Library/Application Support/.wow/mcp
93
- * Linux: ~/.config/.wow/mcp (or XDG fallbacks) · WOWOK_DATA_DIR override.
94
- */
95
- function wowMcpDir() {
96
- const home = os.homedir();
97
- let wowDir;
98
- if (process.env.WOWOK_DATA_DIR) {
99
- wowDir = process.env.WOWOK_DATA_DIR;
100
- } else if (process.platform === 'win32') {
101
- wowDir = path.join(home, '.wow', 'V1');
102
- } else if (process.platform === 'darwin') {
103
- wowDir = path.join(home, 'Library', 'Application Support', '.wow', 'V1');
104
- } else {
105
- const xdgConfig = process.env.XDG_CONFIG_HOME;
106
- if (xdgConfig) {
107
- wowDir = path.join(xdgConfig, '.wow', 'V1');
108
- } else {
109
- const xdgData = process.env.XDG_DATA_HOME;
110
- wowDir = xdgData ? path.join(xdgData, '.wow', 'V1') : path.join(home, '.wow', 'V1');
111
- }
112
- }
113
- return path.join(path.dirname(wowDir), 'mcp');
114
- }
115
-
116
- /**
117
- * Persist the airdrop referrer so the MCP auto-injects it on every call.
118
- * Also used by `wowok-skills referrer <addr>` and `init --referrer`.
119
- */
120
- function saveReferrer(referrer) {
121
- const dir = wowMcpDir();
122
- fs.mkdirSync(dir, { recursive: true });
123
- fs.writeFileSync(path.join(dir, 'referrer'), String(referrer).trim() + '\n', 'utf-8');
124
- console.log(`[wowok-skills] airdrop referrer saved: ${String(referrer).trim()}`);
125
- console.log('[wowok-skills] it is auto-recorded on your first on-chain interaction.');
126
- }
127
-
128
- function copyDir(src, dest) {
129
- if (!fs.existsSync(src)) return false;
130
- fs.mkdirSync(dest, { recursive: true });
131
- const entries = fs.readdirSync(src, { withFileTypes: true });
132
- for (const entry of entries) {
133
- const srcPath = path.join(src, entry.name);
134
- const destPath = path.join(dest, entry.name);
135
- if (entry.isDirectory()) {
136
- copyDir(srcPath, destPath);
137
- } else {
138
- fs.copyFileSync(srcPath, destPath);
139
- }
140
- }
141
- return true;
142
- }
143
-
144
- function removeDir(dir) {
145
- if (!fs.existsSync(dir)) return;
146
- const entries = fs.readdirSync(dir, { withFileTypes: true });
147
- for (const entry of entries) {
148
- const fullPath = path.join(dir, entry.name);
149
- if (entry.isDirectory()) {
150
- removeDir(fullPath);
151
- } else {
152
- fs.unlinkSync(fullPath);
153
- }
154
- }
155
- fs.rmdirSync(dir);
156
- }
157
-
158
- function getFileExt(target) {
159
- const exts = { claude: '.md', codex: '.md', agents: '.md', codebuddy: '.md', cursor: '.mdc', copilot: '.prompt.md' };
160
- return exts[target] || '.md';
161
- }
162
-
163
- function parseFrontmatter(content) {
164
- const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
165
- if (!match) return null;
166
- const frontmatterStr = match[1];
167
- const body = match[2];
168
- const frontmatter = {};
169
- let currentKey = null;
170
- let currentValue = '';
171
- for (const line of frontmatterStr.split('\n')) {
172
- const kvMatch = line.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
173
- if (kvMatch) {
174
- if (currentKey) {
175
- frontmatter[currentKey] = currentValue.trim();
176
- }
177
- currentKey = kvMatch[1];
178
- currentValue = kvMatch[2];
179
- } else if (currentKey) {
180
- currentValue += '\n' + line;
181
- }
182
- }
183
- if (currentKey) {
184
- frontmatter[currentKey] = currentValue.trim();
185
- }
186
- return { frontmatter, body };
187
- }
188
-
189
- function convertSkill(content, target, skillDir) {
190
- if (target === 'cursor') {
191
- const parsed = parseFrontmatter(content);
192
- if (!parsed) return content;
193
- const { frontmatter, body } = parsed;
194
- let description = (frontmatter.description || frontmatter.name || skillDir);
195
- if (typeof description === 'string') {
196
- description = description.replace(/\n/g, ' ');
197
- }
198
- const isAlways = frontmatter.loading === 'always' || frontmatter.always === true;
199
- const alwaysApply = isAlways ? 'true' : 'false';
200
- const newFrontmatter = [
201
- '---',
202
- `description: "${description}"`,
203
- `alwaysApply: ${alwaysApply}`,
204
- '---',
205
- ].join('\n');
206
- return newFrontmatter + '\n' + body;
207
- }
208
- if (target === 'copilot') {
209
- const parsed = parseFrontmatter(content);
210
- if (!parsed) return content;
211
- return parsed.body;
212
- }
213
- return content;
214
- }
215
-
216
- function installSkills(targetDir, target) {
217
- const pkgRoot = getPackageRoot();
218
- const ext = getFileExt(target);
219
- let count = 0;
220
-
221
- fs.mkdirSync(targetDir, { recursive: true });
222
-
223
- for (const dir of SKILL_DIRS) {
224
- const src = path.join(pkgRoot, dir, 'SKILL.md');
225
-
226
- if (!fs.existsSync(src)) {
227
- console.warn(`[wowok-skills] WARN: SKILL.md not found for ${dir}`);
228
- continue;
229
- }
230
-
231
- const content = fs.readFileSync(src, 'utf-8');
232
- const converted = convertSkill(content, target, dir);
233
- const basename = (target === 'cursor' || target === 'copilot')
234
- ? `wowok-${dir.replace('wowok-', '')}${ext}`
235
- : 'SKILL.md';
236
- const destDir = path.join(targetDir, dir);
237
- const dest = path.join(destDir, basename);
238
-
239
- fs.mkdirSync(destDir, { recursive: true });
240
- fs.writeFileSync(dest, converted, 'utf-8');
241
- count++;
242
- console.log(`[wowok-skills] installed: ${dir} → ${dest}`);
243
- }
244
-
245
- return count;
246
- }
247
-
248
- function uninstallSkills(targetDir) {
249
- let count = 0;
250
-
251
- // Remove both retained and legacy (deprecated) skills — legacy dirs may
252
- // still exist from pre-refactor installs and must be cleaned up.
253
- for (const dir of [...SKILL_DIRS, ...LEGACY_SKILL_DIRS]) {
254
- const dirPath = path.join(targetDir, dir);
255
- if (fs.existsSync(dirPath)) {
256
- removeDir(dirPath);
257
- count++;
258
- console.log(`[wowok-skills] removed: ${dirPath}`);
259
- }
260
- }
261
-
262
- return count;
263
- }
264
-
265
- /**
266
- * Detect deprecated skills still present in a target dir and print a
267
- * migration hint (GLM5-31 §3.2). Returns the list of legacy dirs found.
268
- */
269
- function warnLegacySkills(targetDir) {
270
- const found = LEGACY_SKILL_DIRS.filter((dir) => fs.existsSync(path.join(targetDir, dir)));
271
- if (found.length > 0) {
272
- console.warn(`[wowok-skills] ⚠ MIGRATION: deprecated skills detected in ${targetDir}:`);
273
- for (const dir of found) {
274
- console.warn(`[wowok-skills] - ${dir} → content now served by ${SKILL_MIGRATION_MAP[dir]}`);
275
- }
276
- console.warn('[wowok-skills] These skills are stale (MCP knowledge layer is the source of truth).');
277
- console.warn('[wowok-skills] Remove them with: wowok-skills uninit (or delete the folders above)');
278
- }
279
- return found;
280
- }
281
-
282
- function getTargets() {
283
- const envTargets = process.env.WOWOK_SKILLS_TARGETS;
284
- if (!envTargets) {
285
- return [...ALL_CLIENT_TARGETS];
286
- }
287
- return envTargets.split(',').map(t => t.trim()).filter(t => CLIENT_DIRS[t]);
288
- }
289
-
290
- function main() {
291
- const event = process.env.npm_lifecycle_event || '';
292
- const skipMcp = process.env.WOWOK_SKILLS_NO_MCP === '1' || process.env.WOWOK_SKILLS_NO_MCP === 'true';
293
-
294
- if (event === 'postinstall') {
295
- const targets = getTargets();
296
- console.log(`[wowok-skills] Installing skills to ${targets.length} client(s)...`);
297
-
298
- let total = 0;
299
- for (const target of targets) {
300
- const dir = CLIENT_DIRS[target];
301
- console.log(`[wowok-skills] → ${dir}`);
302
- const count = installSkills(dir, target);
303
- total += count;
304
- // GLM5-31 §3.2: warn about stale pre-refactor skills still present.
305
- warnLegacySkills(dir);
306
- }
307
-
308
- console.log(`[wowok-skills] Done — ${total} skills installed across ${targets.length} client(s).`);
309
-
310
- // ─── MCP Server Management ───────────────────────────────────────
311
- console.log('');
312
-
313
- if (skipMcp) {
314
- console.log('[wowok-skills] WOWOK_SKILLS_NO_MCP=1 — skipping MCP server management.');
315
- } else {
316
- console.log('[wowok-skills] Checking MCP server...');
317
-
318
- // Step 1: Install or upgrade the MCP server
319
- const mcpChanged = ensureMcpServer();
320
-
321
- // Step 2: Write MCP config for each target client
322
- console.log('[wowok-skills] Registering MCP server in client config...');
323
- for (const target of targets) {
324
- writeMcpConfig(target);
325
- }
326
-
327
- // Step 3: Restart MCP server so the new version takes effect
328
- if (mcpChanged) {
329
- console.log('[wowok-skills] Restarting MCP server (so IDE picks up the new version)...');
330
- restartMcpServer();
331
- } else {
332
- console.log('[wowok-skills] MCP server unchanged — no restart needed.');
333
- }
334
-
335
- console.log('[wowok-skills] MCP server setup complete.');
336
- }
337
-
338
- // ─── Airdrop referrer from WOWOK_REFERRER (single-command global install) ──
339
- const referrer = process.env.WOWOK_REFERRER;
340
- if (referrer && referrer.trim()) {
341
- console.log('');
342
- saveReferrer(referrer);
343
- }
344
-
345
- // ─── Project-install reminder ──────────────────────────────────────────
346
- console.log('');
347
- console.log('[wowok-skills] Skills installed GLOBALLY + MCP server registered (all clients).');
348
- console.log('[wowok-skills] To ALSO install skills into a project, cd into it and run `wowok-skills init`.');
349
- } else if (event === 'preuninstall') {
350
- // Use the same target list as postinstall (ALL_CLIENT_TARGETS) so the
351
- // set of dirs we remove from is symmetric with the set we installed to.
352
- // Object.keys(CLIENT_DIRS) would additionally include the 'agents' alias
353
- // which is identical to 'trae' → redundant.
354
- const targets = [...ALL_CLIENT_TARGETS];
355
- console.log('[wowok-skills] Removing skills from all client dirs...');
356
-
357
- let total = 0;
358
- for (const target of targets) {
359
- const dir = CLIENT_DIRS[target];
360
- if (countExisting(dir) > 0) {
361
- console.log(`[wowok-skills] → ${dir}`);
362
- total += uninstallSkills(dir);
363
- }
364
- }
365
-
366
- console.log(`[wowok-skills] Done — ${total} skills removed.`);
367
- }
368
- }
369
-
370
- function countExisting(targetDir) {
371
- let count = 0;
372
- for (const dir of [...SKILL_DIRS, ...LEGACY_SKILL_DIRS]) {
373
- if (fs.existsSync(path.join(targetDir, dir))) {
374
- count++;
375
- }
376
- }
377
- return count;
378
- }
379
-
380
- // =========================================================================
381
- // MCP Server Management — auto-install, upgrade, config, and restart
382
- // =========================================================================
383
-
384
- /** MCP server npm package name */
385
- const MCP_PACKAGE = '@wowok/agent-mcp';
386
-
387
- /**
388
- * Resolve CodeBuddy MCP config file according to its official priority rules.
389
- * Reference: https://www.codebuddy.ai/docs/cli/mcp#configuration-file-locations
8
+ * postinstall → copy SKILL.md to every client + register the MCP server
390
9
  *
391
- * USER scope priority (highest → lowest):
392
- * 1. ~/.codebuddy/.mcp.json (recommended)
393
- * 2. ~/.codebuddy/mcp.json (deprecated)
394
- * 3. ~/.codebuddy.json (legacy)
10
+ * There is NO uninstall hook: npm v7+ never runs `preuninstall`/`uninstall`
11
+ * scripts, so removal is an explicit command — `wowok-skills uninit`.
395
12
  *
396
- * Read rule : pick the FIRST existing candidate.
397
- * Write rule: if any file exists, merge into that first existing file;
398
- * if none exist, create the highest-priority one (.mcp.json).
399
- */
400
- function resolveCodeBuddyMcpFile() {
401
- const home = os.homedir();
402
- const candidates = [
403
- path.join(home, '.codebuddy', '.mcp.json'), // 1. recommended
404
- path.join(home, '.codebuddy', 'mcp.json'), // 2. deprecated
405
- path.join(home, '.codebuddy.json'), // 3. legacy
406
- ];
407
- const existing = candidates.find((p) => fs.existsSync(p));
408
- return existing || candidates[0];
409
- }
410
-
411
- /**
412
- * Resolve VS Code globalStorage MCP settings files for a given extension ID.
413
- *
414
- * VS Code extensions store their settings under:
415
- * <user-data>/User/globalStorage/<extensionId>/settings/<fileName>
416
- *
417
- * Where <user-data> depends on the platform and the installed VS Code
418
- * *variant* (Code, Code - Insiders, VSCodium, Cursor). Only variants whose
419
- * User directory actually exists on disk are returned.
420
- *
421
- * macOS: ~/Library/Application Support/{Code,Code - Insiders,VSCodium,Cursor}
422
- * Windows: %APPDATA%\{Code,Code - Insiders,VSCodium,Cursor}
423
- * Linux: ~/.config/{Code,Code - Insiders,VSCodium,Cursor}
13
+ * Environment variables:
14
+ * WOWOK_SKILLS_TARGETS comma-separated target ids (default: all)
15
+ * WOWOK_SKILLS_NO_MCP "1" to skip MCP install/registration
16
+ * WOWOK_REFERRER airdrop referrer (address or name) to persist
424
17
  */
425
- function resolveVscodeGlobalStorageFiles(extensionId, fileName) {
426
- const home = os.homedir();
427
- const vsBaseDirs = (() => {
428
- switch (process.platform) {
429
- case 'darwin':
430
- return [path.join(home, 'Library', 'Application Support')];
431
- case 'win32':
432
- return [process.env.APPDATA || path.join(home, 'AppData', 'Roaming')];
433
- default:
434
- return [process.env.XDG_CONFIG_HOME || path.join(home, '.config')];
435
- }
436
- })();
437
18
 
438
- const results = [];
439
- const vsVariants = ['Code', 'Code - Insiders', 'VSCodium', 'Cursor'];
440
- for (const base of vsBaseDirs) {
441
- for (const variant of vsVariants) {
442
- const vsUserDir = path.join(base, variant, 'User');
443
- if (fs.existsSync(vsUserDir)) {
444
- results.push(path.join(
445
- vsUserDir,
446
- 'globalStorage',
447
- extensionId,
448
- 'settings',
449
- fileName,
450
- ));
451
- }
452
- }
453
- }
454
- return results;
455
- }
19
+ const path = require('path');
20
+ const fs = require('fs');
456
21
 
457
- /**
458
- * Resolve ALL Roo Code MCP config files that should receive the wowok entry.
459
- * Roo Code ships in two forms and we should cover BOTH:
460
- *
461
- * (A) Standalone CLI config → ~/.roo/mcp_settings.json
462
- * Reference: https://mcp.harishgarg.com/use/apple-script/mcp-server/with/43
463
- *
464
- * (B) VS Code extension (rooveterinaryinc.roo-cline) config. The extension
465
- * stores MCP settings inside VS Code's globalStorage under its
466
- * extension ID.
467
- * References:
468
- * - Path convention : https://www.skillmd.ai/skills/mcp-installer-1/
469
- * - macOS/Windows : https://crosstrade.io/learn/connect-roo-code-to-ninjatrader-8-mcp
470
- */
471
- function resolveRooMcpFiles() {
472
- const results = [];
473
- // (A) Standalone CLI config — always include this target
474
- results.push(path.join(os.homedir(), '.roo', 'mcp_settings.json'));
475
- // (B) VS Code extension globalStorage entries
476
- results.push(...resolveVscodeGlobalStorageFiles(
477
- 'rooveterinaryinc.roo-cline',
478
- 'cline_mcp_settings.json',
479
- ));
480
- return results;
481
- }
22
+ const dist = path.join(__dirname, '..', 'dist', 'installer.js');
482
23
 
483
- /**
484
- * Resolve ALL Cline (VS Code) MCP config files.
485
- * Cline is a VS Code extension that stores MCP settings in globalStorage
486
- * under its extension ID (saoudrizwan.claude-dev), filename cline_mcp_settings.json.
487
- * Reference: https://www.skillmd.ai/skills/mcp-installer-1/
488
- */
489
- function resolveClineMcpFiles() {
490
- return resolveVscodeGlobalStorageFiles(
491
- 'saoudrizwan.claude-dev',
492
- 'cline_mcp_settings.json',
24
+ if (!fs.existsSync(dist)) {
25
+ console.warn(
26
+ '[wowok-skills] dist/installer.js is missing — skills were NOT installed.\n' +
27
+ '[wowok-skills] Build the package first (npm run build) or reinstall from the npm registry.',
493
28
  );
29
+ process.exit(0);
494
30
  }
495
31
 
496
- /**
497
- * Resolve ALL Kilo Code MCP config files.
498
- * Kilo Code reads MCP settings from two separate places (documented by Kilo):
499
- * (A) VS Code extension → globalStorage/kilocode.kilo-code/settings/mcp_settings.json
500
- * (B) CLI → ~/.kilocode/cli/global/settings/mcp_settings.json
501
- * References:
502
- * - https://kilo.ai/docs/features/mcp/using-mcp-in-cli
503
- * - https://github.com/Kilo-Org/kilocode/blob/main/docs/file-locations.md
504
- */
505
- function resolveKiloMcpFiles() {
506
- const results = [];
507
- results.push(...resolveVscodeGlobalStorageFiles(
508
- 'kilocode.kilo-code',
509
- 'mcp_settings.json',
510
- ));
511
- results.push(path.join(os.homedir(), '.kilocode', 'cli', 'global', 'settings', 'mcp_settings.json'));
512
- return results;
513
- }
514
-
515
- /**
516
- * MCP config entries per client target.
517
- * null means the client does not support MCP or manages it internally.
518
- * Each entry may either provide a static `configPath` string, or a
519
- * `resolveConfigPaths()` function that returns an array of concrete paths
520
- * (for clients like CodeBuddy with priority-resolution rules, or Roo with
521
- * multiple simultaneous install targets).
522
- */
523
- const MCP_TARGET_CONFIGS = {
524
- claude: {
525
- configPath: path.join(os.homedir(), '.claude', 'settings.json'),
526
- merge: (config) => {
527
- config.mcpServers = config.mcpServers || {};
528
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
529
- return config;
530
- }
531
- },
532
- codebuddy: {
533
- resolveConfigPaths: () => [resolveCodeBuddyMcpFile()],
534
- merge: (config) => {
535
- config.mcpServers = config.mcpServers || {};
536
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
537
- return config;
538
- }
539
- },
540
- windsurf: {
541
- configPath: path.join(os.homedir(), '.codeium', 'windsurf', 'mcp_config.json'),
542
- merge: (config) => {
543
- config.mcpServers = config.mcpServers || {};
544
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
545
- return config;
546
- }
547
- },
548
- qoder: {
549
- configPath: (() => {
550
- // Qoder MCP config paths — confirmed by community guides:
551
- // Windows : %APPDATA%\Qoder\mcp-settings.json
552
- // macOS : ~/Library/Application Support/Qoder/mcp-settings.json
553
- // Linux : ~/.config/qoder/mcp-settings.json
554
- // Reference: github.com/oscar-wang-xin/mcp-session-saver/docs/MULTI_IDE_SETUP.md
555
- if (process.platform === 'win32') {
556
- return path.join(
557
- process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
558
- 'Qoder', 'mcp-settings.json',
559
- );
560
- }
561
- if (process.platform === 'darwin') {
562
- return path.join(os.homedir(), 'Library', 'Application Support', 'Qoder', 'mcp-settings.json');
563
- }
564
- return path.join(
565
- process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'),
566
- 'qoder', 'mcp-settings.json',
567
- );
568
- })(),
569
- merge: (config) => {
570
- config.mcpServers = config.mcpServers || {};
571
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
572
- return config;
573
- }
574
- },
575
- roo: {
576
- resolveConfigPaths: resolveRooMcpFiles,
577
- merge: (config) => {
578
- config.mcpServers = config.mcpServers || {};
579
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
580
- return config;
581
- }
582
- },
583
- cline: {
584
- resolveConfigPaths: resolveClineMcpFiles,
585
- merge: (config) => {
586
- config.mcpServers = config.mcpServers || {};
587
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
588
- return config;
589
- }
590
- },
591
- kilo: {
592
- resolveConfigPaths: resolveKiloMcpFiles,
593
- merge: (config) => {
594
- config.mcpServers = config.mcpServers || {};
595
- config.mcpServers.wowok = { command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
596
- return config;
597
- }
598
- },
599
- // trae / agents: IDE-managed MCP via ~/.trae-cn/mcps/ directory structure — skip global config
600
- trae: null,
601
- agents: null,
602
- // cursor: MCP config is project-level, handled by `wowok-skills init` in project dir
603
- cursor: null,
604
- // copilot: user-level MCP config (Copilot CLI)
605
- copilot: {
606
- configPath: path.join(os.homedir(), '.copilot', 'mcp-config.json'),
607
- merge: (config) => {
608
- config.mcpServers = config.mcpServers || {};
609
- config.mcpServers.wowok = { type: 'local', command: 'npx', args: ['-y', '@wowok/agent-mcp'] };
610
- return config;
611
- }
612
- },
613
- // codex: global TOML config (~/.codex/config.toml)
614
- codex: {
615
- configPath: path.join(os.homedir(), '.codex', 'config.toml'),
616
- format: 'toml',
617
- },
618
- };
619
-
620
- /**
621
- * Parse a semver string to its major version number.
622
- * Returns 0 for invalid/missing versions.
623
- */
624
- function semverMajor(version) {
625
- if (!version) return 0;
626
- const parts = version.split('.');
627
- return parseInt(parts[0], 10) || 0;
628
- }
629
-
630
- /**
631
- * Get the currently installed global version of the MCP server.
632
- * Uses `npm ls -g <package>` (targeted, no tree scan) for speed.
633
- * Returns null if not installed.
634
- */
635
- function getInstalledMcpVersion() {
636
- try {
637
- // npm ls -g <pkg> is faster than npm list -g because it skips
638
- // scanning unrelated packages in the global node_modules.
639
- const output = execSync(`npm ls -g ${MCP_PACKAGE} --depth=0 --json`, {
640
- encoding: 'utf-8',
641
- stdio: 'pipe',
642
- timeout: 15000,
643
- });
644
- const parsed = JSON.parse(output);
645
- // npm ls output has the package as a top-level key, not under dependencies
646
- const pkg = parsed.dependencies?.[MCP_PACKAGE] || parsed[MCP_PACKAGE];
647
- return pkg?.version || null;
648
- } catch {
649
- return null;
650
- }
651
- }
652
-
653
- /**
654
- * Get the latest available version from the npm registry.
655
- * Returns null on failure (offline, registry unreachable, etc.).
656
- */
657
- function getLatestMcpVersion() {
658
- try {
659
- // --prefer-online forces npm to bypass local cache and hit the registry
660
- // directly. Without this flag, stale cache can return outdated version
661
- // numbers (see project memory: components.mjs fix).
662
- const output = execSync(`npm view --prefer-online ${MCP_PACKAGE} version`, {
663
- encoding: 'utf-8',
664
- stdio: 'pipe',
665
- timeout: 15000,
666
- });
667
- return output.trim();
668
- } catch {
669
- return null;
670
- }
671
- }
672
-
673
- /**
674
- * Install an npm package globally with retry logic.
675
- * Retries up to 2 times on failure (covers transient network errors).
676
- */
677
- function npmInstallGlobal(pkg, retries = 2) {
678
- for (let attempt = 1; attempt <= retries; attempt++) {
679
- try {
680
- execSync(`npm install -g ${pkg}`, { stdio: 'inherit', timeout: 120000 });
681
- return true;
682
- } catch (err) {
683
- if (attempt < retries) {
684
- console.log(`[wowok-skills] npm install attempt ${attempt} failed, retrying...`);
685
- // Wait 2s before retry to let network recover
686
- execSync('ping -n 3 127.0.0.1 >nul', { stdio: 'pipe', timeout: 5000 });
687
- } else {
688
- console.error(`[wowok-skills] npm install failed after ${retries} attempts: ${err.message}`);
689
- return false;
690
- }
691
- }
692
- }
693
- return false;
694
- }
695
-
696
- /**
697
- * Ensure the MCP server is installed and up to date.
698
- *
699
- * - Not installed → install latest
700
- * - Outdated (same major) → upgrade to latest
701
- * - Major version bump → warn user, skip auto-upgrade
702
- * - Up to date → skip
703
- *
704
- * Returns true if the package was installed or upgraded (changed).
705
- */
706
- function ensureMcpServer() {
707
- const currentVersion = getInstalledMcpVersion();
708
- const latestVersion = getLatestMcpVersion();
709
-
710
- if (currentVersion) {
711
- console.log(`[wowok-skills] MCP server ${MCP_PACKAGE} v${currentVersion} found.`);
712
-
713
- if (!latestVersion) {
714
- console.log(`[wowok-skills] Cannot check latest version (offline or registry unreachable). Keeping v${currentVersion}.`);
715
- return false;
716
- }
717
-
718
- if (currentVersion === latestVersion) {
719
- console.log(`[wowok-skills] MCP server is up to date (v${currentVersion}).`);
720
- return false;
721
- }
722
-
723
- // Major version guard: warn if the latest version has a different major
724
- const currentMajor = semverMajor(currentVersion);
725
- const latestMajor = semverMajor(latestVersion);
726
-
727
- if (latestMajor > currentMajor) {
728
- console.warn(`[wowok-skills] ⚠ WARNING: Major version bump detected v${currentVersion} → v${latestVersion}.`);
729
- console.warn(`[wowok-skills] Skipping auto-upgrade to avoid breaking changes.`);
730
- console.warn(`[wowok-skills] To upgrade manually: npm install -g ${MCP_PACKAGE}@latest`);
731
- return false;
732
- }
733
-
734
- console.log(`[wowok-skills] Upgrading MCP server v${currentVersion} → v${latestVersion}...`);
735
- if (npmInstallGlobal(`${MCP_PACKAGE}@latest`)) {
736
- console.log(`[wowok-skills] MCP server upgraded to v${latestVersion}.`);
737
- return true;
738
- }
739
- console.error(`[wowok-skills] MCP server upgrade FAILED. Keeping v${currentVersion}.`);
740
- return false;
741
- }
742
-
743
- console.log(`[wowok-skills] Installing MCP server ${MCP_PACKAGE}...`);
744
- if (npmInstallGlobal(MCP_PACKAGE)) {
745
- console.log(`[wowok-skills] MCP server installed.`);
746
- return true;
747
- }
748
- console.error(`[wowok-skills] MCP server installation FAILED.`);
749
- return false;
750
- }
751
-
752
- /**
753
- * Write MCP server config entry for a given client target.
754
- * Safely merges with any existing MCP configuration.
755
- * Supports JSON format (most clients) and TOML format (Codex).
756
- *
757
- * When the target's MCP_TARGET_CONFIGS entry has `resolveConfigPaths()`
758
- * (instead of a single `configPath` string) the function will iterate the
759
- * returned array so multi-target clients (Roo, CodeBuddy with priority
760
- * resolution) get every applicable location written to.
761
- */
762
- function writeMcpConfig(target) {
763
- const cfg = MCP_TARGET_CONFIGS[target];
764
- if (!cfg) {
765
- console.log(`[wowok-skills] MCP config: ${target} does not support external MCP registration (skipped).`);
766
- return false;
767
- }
768
-
769
- // ── TOML format (Codex CLI uses ~/.codex/config.toml) ──────────────
770
- if (cfg.format === 'toml') {
771
- try {
772
- const tomlEntry = '\n[mcp_servers.wowok]\ncommand = "npx"\nargs = ["-y", "@wowok/agent-mcp"]\n';
773
- let content = '';
774
- if (fs.existsSync(cfg.configPath)) {
775
- content = fs.readFileSync(cfg.configPath, 'utf-8');
776
- }
777
- // Line-level regex detection. Avoids false positives when the header
778
- // appears inside a comment (e.g. `; [mcp_servers.wowok]`) or a
779
- // multi-line string literal.
780
- if (/^\s*\[mcp_servers\.wowok\]\s*$/m.test(content)) {
781
- console.log(`[wowok-skills] MCP config already up to date: ${cfg.configPath}`);
782
- return false;
783
- }
784
- fs.mkdirSync(path.dirname(cfg.configPath), { recursive: true });
785
- fs.writeFileSync(cfg.configPath, content + tomlEntry, 'utf-8');
786
- console.log(`[wowok-skills] MCP config written: ${cfg.configPath}`);
787
- return true;
788
- } catch (err) {
789
- console.error(`[wowok-skills] ERROR writing MCP config for ${target}: ${err.message}`);
790
- return false;
791
- }
792
- }
793
-
794
- // ── JSON format (all other clients) ────────────────────────────────
795
- // Expand to an array of concrete paths so a single target can cover
796
- // multiple physical files (priority resolution for CodeBuddy, CLI +
797
- // VS Code variants for Roo/Cline/Kilo).
798
- const configPaths = cfg.resolveConfigPaths ? cfg.resolveConfigPaths() : [cfg.configPath];
799
- let anyWritten = false;
800
-
801
- if (configPaths.length === 0) {
802
- console.log(`[wowok-skills] MCP config: no config file detected for ${target} (client not installed) — skipped.`);
803
- return false;
804
- }
805
-
806
- for (const configPath of configPaths) {
807
- try {
808
- let config = {};
809
- const dir = path.dirname(configPath);
810
- if (fs.existsSync(configPath)) {
811
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
812
- }
813
-
814
- const before = JSON.stringify(config);
815
- config = cfg.merge(config);
816
- const after = JSON.stringify(config);
817
-
818
- if (before !== after) {
819
- fs.mkdirSync(dir, { recursive: true });
820
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
821
- console.log(`[wowok-skills] MCP config written: ${configPath}`);
822
- anyWritten = true;
823
- } else {
824
- console.log(`[wowok-skills] MCP config already up to date: ${configPath}`);
825
- }
826
- } catch (err) {
827
- console.error(`[wowok-skills] ERROR writing MCP config for ${target} at ${configPath}: ${err.message}`);
828
- }
829
- }
830
-
831
- return anyWritten;
832
- }
833
-
834
- /**
835
- * Attempt to restart the running MCP server process.
836
- *
837
- * The MCP server is a child process of the IDE (Claude Code, Trae, etc.).
838
- * We cannot directly "start" it — only kill the existing process so the IDE
839
- * detects the exit and auto-restarts it with the new version.
840
- *
841
- * Best-effort: if the kill fails (no process running), it's harmless.
842
- */
843
- function restartMcpServer() {
844
- try {
845
- if (process.platform === 'win32') {
846
- // Windows: use PowerShell (via -EncodedCommand to avoid quoting hell)
847
- const psScript =
848
- 'Get-CimInstance Win32_Process -Filter "Name=\'node.exe\' AND CommandLine LIKE \'%wowok%agent-mcp%\'" | ' +
849
- 'ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }';
850
- const encoded = Buffer.from(psScript, 'utf16le').toString('base64');
851
- execSync(`powershell -NoProfile -EncodedCommand ${encoded}`, {
852
- stdio: 'pipe',
853
- timeout: 10000,
854
- });
855
- } else {
856
- // Unix: pkill by command-line pattern
857
- execSync('pkill -f "wowok.*agent-mcp" 2>/dev/null || true', {
858
- stdio: 'pipe',
859
- timeout: 5000,
860
- shell: true,
861
- });
862
- }
863
- console.log('[wowok-skills] MCP server process terminated (IDE will auto-restart).');
864
- return true;
865
- } catch (e) {
866
- console.log('[wowok-skills] No running MCP server process found. Start your IDE to launch it.');
867
- return false;
868
- }
869
- }
870
-
871
- main();
32
+ const { runLifecycle } = require(dist);
33
+ runLifecycle(process.env.npm_lifecycle_event || '');