frontmcp 1.0.0 → 1.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontmcp",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "FrontMCP command line interface",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@clack/prompts": "^0.10.0",
33
- "@frontmcp/utils": "1.0.0",
33
+ "@frontmcp/utils": "1.0.2",
34
34
  "commander": "^13.0.0",
35
35
  "tslib": "^2.3.0",
36
36
  "vectoriadb": "^2.1.3",
@@ -31,6 +31,7 @@ async function bundleCliWithEsbuild(cliEntryPath, outDir, config, options) {
31
31
  // Only true native addons and optional peer deps stay external in self-contained mode
32
32
  'better-sqlite3',
33
33
  'fsevents',
34
+ 'esbuild',
34
35
  '@vercel/kv',
35
36
  '@frontmcp/storage-sqlite',
36
37
  '@enclave-vm/core',
@@ -41,6 +42,7 @@ async function bundleCliWithEsbuild(cliEntryPath, outDir, config, options) {
41
42
  '@frontmcp/sdk',
42
43
  'better-sqlite3',
43
44
  'fsevents',
45
+ 'esbuild',
44
46
  '@vercel/kv',
45
47
  '@frontmcp/storage-sqlite',
46
48
  '@enclave-vm/core',
@@ -1 +1 @@
1
- {"version":3,"file":"cli-bundler.js","sourceRoot":"","sources":["../../../../../../src/commands/build/exec/cli-runtime/cli-bundler.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAeH,oDAuEC;;AApFD,mDAA6B;AAQ7B;;;;GAIG;AACI,KAAK,UAAU,oBAAoB,CACxC,YAAoB,EACpB,MAAc,EACd,MAA0B,EAC1B,OAAqC;IAErC,IAAI,OAAiC,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,GAAG,MAAM,CAAC,IAAI,gBAAgB,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;IAEtD,uEAAuE;IACvE,gEAAgE;IAChE,MAAM,gBAAgB,GAAG,GAAG,MAAM,CAAC,IAAI,YAAY,CAAC;IAEpD,MAAM,QAAQ,GAAG,aAAa;QAC5B,CAAC,CAAC;YACE,sFAAsF;YACtF,gBAAgB;YAChB,UAAU;YACV,YAAY;YACZ,0BAA0B;YAC1B,kBAAkB;YAClB,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,IAAI,EAAE,CAAC;SAC7C;QACH,CAAC,CAAC;YACE,KAAK,gBAAgB,EAAE;YACvB,eAAe;YACf,gBAAgB;YAChB,UAAU;YACV,YAAY;YACZ,0BAA0B;YAC1B,kBAAkB;YAClB,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,IAAI,EAAE,CAAC;YAC5C,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC;SACpC,CAAC;IAEN,MAAM,OAAO,CAAC,KAAK,CAAC;QAClB,WAAW,EAAE,CAAC,YAAY,CAAC;QAC3B,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,QAAQ;QAC1C,OAAO,EAAE,UAAU;QACnB,QAAQ;QACR,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK;QACvC,MAAM,EAAE;YACN,EAAE,EAAE,qBAAqB;SAC1B;QACD,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM;QAC9B,SAAS,EAAE,KAAK;QAChB,QAAQ,EAAE,SAAS;KACpB,CAAC,CAAC;IAEH,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAErC,OAAO;QACL,UAAU;QACV,UAAU,EAAE,IAAI,CAAC,IAAI;KACtB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * esbuild configuration for the CLI bundle.\n * Bundles the generated CLI entry point with commander.js inlined.\n */\n\nimport * as path from 'path';\nimport { FrontmcpExecConfig } from '../config';\n\nexport interface CliBundleResult {\n bundlePath: string;\n bundleSize: number;\n}\n\n/**\n * Bundle the generated CLI entry with esbuild.\n * Commander.js and runtime modules are inlined; the server bundle is external\n * unless selfContained mode is enabled (for SEA builds).\n */\nexport async function bundleCliWithEsbuild(\n cliEntryPath: string,\n outDir: string,\n config: FrontmcpExecConfig,\n options?: { selfContained?: boolean },\n): Promise<CliBundleResult> {\n let esbuild: typeof import('esbuild');\n try {\n esbuild = require('esbuild');\n } catch {\n throw new Error(\n 'esbuild is required for CLI builds. Install it: npm install -D esbuild',\n );\n }\n\n const cliBundleName = `${config.name}-cli.bundle.js`;\n const bundlePath = path.join(outDir, cliBundleName);\n const selfContained = options?.selfContained ?? false;\n\n // The server bundle is loaded via require() at runtime — keep external\n // unless selfContained mode where everything is inlined for SEA\n const serverBundleName = `${config.name}.bundle.js`;\n\n const external = selfContained\n ? [\n // Only true native addons and optional peer deps stay external in self-contained mode\n 'better-sqlite3',\n 'fsevents',\n '@vercel/kv',\n '@frontmcp/storage-sqlite',\n '@enclave-vm/core',\n ...(config.dependencies?.nativeAddons || []),\n ]\n : [\n `./${serverBundleName}`,\n '@frontmcp/sdk',\n 'better-sqlite3',\n 'fsevents',\n '@vercel/kv',\n '@frontmcp/storage-sqlite',\n '@enclave-vm/core',\n ...(config.dependencies?.nativeAddons || []),\n ...(config.esbuild?.external || []),\n ];\n\n await esbuild.build({\n entryPoints: [cliEntryPath],\n bundle: true,\n platform: 'node',\n format: 'cjs',\n target: config.esbuild?.target || 'node22',\n outfile: bundlePath,\n external,\n keepNames: true,\n treeShaking: true,\n minify: config.esbuild?.minify ?? false,\n banner: {\n js: '#!/usr/bin/env node',\n },\n define: config.esbuild?.define,\n sourcemap: false,\n logLevel: 'warning',\n });\n\n const fs = require('fs');\n const stat = fs.statSync(bundlePath);\n\n return {\n bundlePath,\n bundleSize: stat.size,\n };\n}\n"]}
1
+ {"version":3,"file":"cli-bundler.js","sourceRoot":"","sources":["../../../../../../src/commands/build/exec/cli-runtime/cli-bundler.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAeH,oDAyEC;;AAtFD,mDAA6B;AAQ7B;;;;GAIG;AACI,KAAK,UAAU,oBAAoB,CACxC,YAAoB,EACpB,MAAc,EACd,MAA0B,EAC1B,OAAqC;IAErC,IAAI,OAAiC,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CACb,wEAAwE,CACzE,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,GAAG,MAAM,CAAC,IAAI,gBAAgB,CAAC;IACrD,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;IACpD,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,KAAK,CAAC;IAEtD,uEAAuE;IACvE,gEAAgE;IAChE,MAAM,gBAAgB,GAAG,GAAG,MAAM,CAAC,IAAI,YAAY,CAAC;IAEpD,MAAM,QAAQ,GAAG,aAAa;QAC5B,CAAC,CAAC;YACE,sFAAsF;YACtF,gBAAgB;YAChB,UAAU;YACV,SAAS;YACT,YAAY;YACZ,0BAA0B;YAC1B,kBAAkB;YAClB,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,IAAI,EAAE,CAAC;SAC7C;QACH,CAAC,CAAC;YACE,KAAK,gBAAgB,EAAE;YACvB,eAAe;YACf,gBAAgB;YAChB,UAAU;YACV,SAAS;YACT,YAAY;YACZ,0BAA0B;YAC1B,kBAAkB;YAClB,GAAG,CAAC,MAAM,CAAC,YAAY,EAAE,YAAY,IAAI,EAAE,CAAC;YAC5C,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,IAAI,EAAE,CAAC;SACpC,CAAC;IAEN,MAAM,OAAO,CAAC,KAAK,CAAC;QAClB,WAAW,EAAE,CAAC,YAAY,CAAC;QAC3B,MAAM,EAAE,IAAI;QACZ,QAAQ,EAAE,MAAM;QAChB,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,QAAQ;QAC1C,OAAO,EAAE,UAAU;QACnB,QAAQ;QACR,SAAS,EAAE,IAAI;QACf,WAAW,EAAE,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,KAAK;QACvC,MAAM,EAAE;YACN,EAAE,EAAE,qBAAqB;SAC1B;QACD,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM;QAC9B,SAAS,EAAE,KAAK;QAChB,QAAQ,EAAE,SAAS;KACpB,CAAC,CAAC;IAEH,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IAErC,OAAO;QACL,UAAU;QACV,UAAU,EAAE,IAAI,CAAC,IAAI;KACtB,CAAC;AACJ,CAAC","sourcesContent":["/**\n * esbuild configuration for the CLI bundle.\n * Bundles the generated CLI entry point with commander.js inlined.\n */\n\nimport * as path from 'path';\nimport { FrontmcpExecConfig } from '../config';\n\nexport interface CliBundleResult {\n bundlePath: string;\n bundleSize: number;\n}\n\n/**\n * Bundle the generated CLI entry with esbuild.\n * Commander.js and runtime modules are inlined; the server bundle is external\n * unless selfContained mode is enabled (for SEA builds).\n */\nexport async function bundleCliWithEsbuild(\n cliEntryPath: string,\n outDir: string,\n config: FrontmcpExecConfig,\n options?: { selfContained?: boolean },\n): Promise<CliBundleResult> {\n let esbuild: typeof import('esbuild');\n try {\n esbuild = require('esbuild');\n } catch {\n throw new Error(\n 'esbuild is required for CLI builds. Install it: npm install -D esbuild',\n );\n }\n\n const cliBundleName = `${config.name}-cli.bundle.js`;\n const bundlePath = path.join(outDir, cliBundleName);\n const selfContained = options?.selfContained ?? false;\n\n // The server bundle is loaded via require() at runtime — keep external\n // unless selfContained mode where everything is inlined for SEA\n const serverBundleName = `${config.name}.bundle.js`;\n\n const external = selfContained\n ? [\n // Only true native addons and optional peer deps stay external in self-contained mode\n 'better-sqlite3',\n 'fsevents',\n 'esbuild',\n '@vercel/kv',\n '@frontmcp/storage-sqlite',\n '@enclave-vm/core',\n ...(config.dependencies?.nativeAddons || []),\n ]\n : [\n `./${serverBundleName}`,\n '@frontmcp/sdk',\n 'better-sqlite3',\n 'fsevents',\n 'esbuild',\n '@vercel/kv',\n '@frontmcp/storage-sqlite',\n '@enclave-vm/core',\n ...(config.dependencies?.nativeAddons || []),\n ...(config.esbuild?.external || []),\n ];\n\n await esbuild.build({\n entryPoints: [cliEntryPath],\n bundle: true,\n platform: 'node',\n format: 'cjs',\n target: config.esbuild?.target || 'node22',\n outfile: bundlePath,\n external,\n keepNames: true,\n treeShaking: true,\n minify: config.esbuild?.minify ?? false,\n banner: {\n js: '#!/usr/bin/env node',\n },\n define: config.esbuild?.define,\n sourcemap: false,\n logLevel: 'warning',\n });\n\n const fs = require('fs');\n const stat = fs.statSync(bundlePath);\n\n return {\n bundlePath,\n bundleSize: stat.size,\n };\n}\n"]}
@@ -110,6 +110,8 @@ ${hasOAuth ? "var oauthHelper = require('./oauth-helper');" : ''}
110
110
  var APP_NAME = ${JSON.stringify(appName)};
111
111
  var SCRIPT_DIR = __dirname;
112
112
  var FRONTMCP_HOME = process.env.FRONTMCP_HOME || path.join(os.homedir(), '.frontmcp');
113
+ // Set app name for file logger (writes to ~/.frontmcp/logs/{appName}-{timestamp}.log)
114
+ process.env.FRONTMCP_APP_NAME = process.env.FRONTMCP_APP_NAME || APP_NAME;
113
115
  ${selfContained
114
116
  ? `// Self-contained: server bundle and SDK are inlined by esbuild
115
117
  var SERVER_BUNDLE = '../${serverBundleFilename}';`
@@ -132,7 +134,10 @@ async function getClient() {
132
134
  }
133
135
 
134
136
  // Fallback: in-process connect (with CLI mode for faster init)
137
+ // Suppress @FrontMcp decorator bootstrap — we only need config metadata, not a running server.
138
+ process.env.FRONTMCP_SCHEMA_EXTRACT = '1';
135
139
  var mod = require(${selfContained ? `'../${serverBundleFilename}'` : 'SERVER_BUNDLE'});
140
+ delete process.env.FRONTMCP_SCHEMA_EXTRACT;
136
141
  var configOrClass = mod.default || mod;
137
142
  var sdk = require('@frontmcp/sdk');
138
143
  var connect = sdk.connect || sdk.direct.connect;${authRequired ? `
@@ -145,12 +150,37 @@ async function getClient() {
145
150
  return _client;
146
151
  }
147
152
 
153
+ async function closeClient() {
154
+ if (_client && typeof _client.close === 'function') {
155
+ try { await _client.close(); } catch (_) {}
156
+ }
157
+ _client = null;
158
+ }
159
+
160
+ // Flag set by long-running commands (serve, daemon) to prevent the footer from calling process.exit().
161
+ var _isLongRunning = false;
162
+
148
163
  var program = new Command();
149
164
  program
150
165
  .name(${JSON.stringify(appName)})
151
166
  .version(${JSON.stringify(appVersion)})
152
167
  .description(${JSON.stringify(description || `${appName} CLI`)})
153
- .option('--output <mode>', 'Output format: text or json', ${JSON.stringify(outputDefault)});
168
+ .option('--output <mode>', 'Output format: text or json', ${JSON.stringify(outputDefault)})
169
+ .option('--verbose', 'Enable verbose console logging (logs always go to ~/.frontmcp/logs/)')
170
+ .option('--log-dir <path>', 'Directory for log files (default: ~/.frontmcp/logs/)');
171
+
172
+ // Wire --verbose and --log-dir to env vars early (before any command action runs).
173
+ // Parse argv directly since commander hooks may not be available in all versions.
174
+ (function() {
175
+ var argv = process.argv;
176
+ if (argv.indexOf('--verbose') !== -1) {
177
+ process.env.FRONTMCP_CLI_VERBOSE = '1';
178
+ }
179
+ var logDirIdx = argv.indexOf('--log-dir');
180
+ if (logDirIdx !== -1 && argv[logDirIdx + 1]) {
181
+ process.env.FRONTMCP_LOG_DIR = argv[logDirIdx + 1];
182
+ }
183
+ })();
154
184
 
155
185
  program.configureHelp({
156
186
  sortSubcommands: false,
@@ -456,15 +486,20 @@ skillsCmd
456
486
  if (mode === 'json') {
457
487
  console.log(JSON.stringify(result, null, 2));
458
488
  } else {
459
- var skills = result.skills || result || [];
460
- if (Array.isArray(skills) && skills.length === 0) { console.log('No skills found.'); return; }
461
- if (Array.isArray(skills)) {
462
- skills.forEach(function(s) {
463
- console.log(' ' + (s.name || s.id || JSON.stringify(s)));
464
- });
465
- } else {
466
- console.log(JSON.stringify(result, null, 2));
467
- }
489
+ var skills = result.skills || [];
490
+ if (skills.length === 0) { console.log('No skills found.'); return; }
491
+ console.log('\\n Skills matching "' + (query || '') + '":\\n');
492
+ skills.forEach(function(s) {
493
+ var tags = (s.tags || []).slice(0, 3).join(', ');
494
+ var score = s.score != null ? ' [score: ' + Number(s.score).toFixed(2) + ']' : '';
495
+ console.log(' ' + (s.name || s.id) + score);
496
+ if (s.description) console.log(' ' + s.description.split('. Use when')[0]);
497
+ if (tags) console.log(' tags: ' + tags);
498
+ console.log('');
499
+ });
500
+ console.log(' ' + skills.length + ' result(s).');
501
+ console.log(" Use '" + program.name() + " skills read <name>' for full details.");
502
+ console.log(" Use '" + program.name() + " skills load <name>' to load a skill.\\n");
468
503
  }
469
504
  } catch (err) {
470
505
  console.error('Error:', err.message || err);
@@ -494,6 +529,43 @@ skillsCmd
494
529
  }
495
530
  });
496
531
 
532
+ skillsCmd
533
+ .command('read <name>')
534
+ .description('Read full details for a skill')
535
+ .action(async function(name) {
536
+ try {
537
+ var client = await getClient();
538
+ var result = await client.loadSkills([name]);
539
+ var mode = program.opts().output || 'text';
540
+ if (mode === 'json') {
541
+ console.log(JSON.stringify(result, null, 2));
542
+ } else {
543
+ var skills = result.skills || [];
544
+ if (skills.length === 0) { console.log('Skill "' + name + '" not found.'); return; }
545
+ var sk = skills[0];
546
+ console.log('\\n ' + sk.name);
547
+ if (sk.description) console.log(' ' + sk.description);
548
+ console.log('');
549
+ if (sk.instructions) {
550
+ console.log(sk.instructions);
551
+ console.log('');
552
+ }
553
+ if (sk.tools && sk.tools.length > 0) {
554
+ console.log(' Tools (' + sk.tools.length + '):');
555
+ sk.tools.forEach(function(t) {
556
+ console.log(' ' + t.name + (t.available ? '' : ' (unavailable)'));
557
+ });
558
+ console.log('');
559
+ }
560
+ if (result.nextSteps) console.log(' ' + result.nextSteps);
561
+ console.log(" Load: " + program.name() + " skills load " + name + '\\n');
562
+ }
563
+ } catch (err) {
564
+ console.error('Error:', err.message || err);
565
+ process.exitCode = 1;
566
+ }
567
+ });
568
+
497
569
  skillsCmd
498
570
  .command('list')
499
571
  .description('List available skills')
@@ -505,15 +577,17 @@ skillsCmd
505
577
  if (mode === 'json') {
506
578
  console.log(JSON.stringify(result, null, 2));
507
579
  } else {
508
- var skills = result.skills || result || [];
509
- if (Array.isArray(skills) && skills.length === 0) { console.log('No skills available.'); return; }
510
- if (Array.isArray(skills)) {
511
- skills.forEach(function(s) {
512
- console.log(' ' + (s.name || s.id || JSON.stringify(s)));
513
- });
514
- } else {
515
- console.log(JSON.stringify(result, null, 2));
516
- }
580
+ var skills = result.skills || [];
581
+ if (skills.length === 0) { console.log('No skills available.'); return; }
582
+ console.log('\\n Available Skills (' + skills.length + '):\\n');
583
+ skills.forEach(function(s) {
584
+ var desc = s.description ? s.description.split('. Use when')[0] : '';
585
+ console.log(' ' + (s.name || s.id));
586
+ if (desc) console.log(' ' + desc);
587
+ console.log('');
588
+ });
589
+ console.log(" Use '" + program.name() + " skills search <query>' for semantic search.");
590
+ console.log(" Use '" + program.name() + " skills read <name>' for full details.\\n");
517
591
  }
518
592
  } catch (err) {
519
593
  console.error('Error:', err.message || err);
@@ -787,7 +861,9 @@ async function getSubscribeClient() {
787
861
  // If connected via daemon, the onNotification/onResourceUpdated are no-ops.
788
862
  // Reconnect via in-process for push support.
789
863
  if (client._isDaemon) {
790
- _client = null; // clear cached daemon client
864
+ // Close the daemon client before replacing with in-process client
865
+ if (typeof client.close === 'function') { try { await client.close(); } catch (_) {} }
866
+ _client = null;
791
867
  var mod = require(SERVER_BUNDLE);
792
868
  var configOrClass = mod.default || mod;
793
869
  var sdk = require('@frontmcp/sdk');
@@ -816,6 +892,7 @@ subscribeCmd
816
892
  process.on('SIGINT', async function() {
817
893
  console.log('\\nUnsubscribing...');
818
894
  try { await client.unsubscribeResource(uri); } catch (_) { /* ok */ }
895
+ await closeClient();
819
896
  process.exit(0);
820
897
  });
821
898
  // Keep process alive — setInterval creates an active event loop handle
@@ -842,8 +919,9 @@ subscribeCmd
842
919
  console.log(fmt.formatSubscriptionEvent({ type: 'notification', method: notification.method, params: notification.params, timestamp: new Date().toISOString() }, mode));
843
920
  }
844
921
  });
845
- process.on('SIGINT', function() {
922
+ process.on('SIGINT', async function() {
846
923
  console.log('\\nStopping...');
924
+ await closeClient();
847
925
  process.exit(0);
848
926
  });
849
927
  // Keep process alive — setInterval creates an active event loop handle
@@ -984,6 +1062,7 @@ function generateServeCommand(serverBundleFilename, selfContained) {
984
1062
  .description('Start the HTTP/SSE server')
985
1063
  .option('-p, --port <port>', 'Port number', function(v) { return parseInt(v, 10); })
986
1064
  .action(async function(opts) {
1065
+ _isLongRunning = true;
987
1066
  var mod = ${requireExpr};
988
1067
  if (opts.port) process.env.PORT = String(opts.port);
989
1068
  // If the bundle exports a start() function (@FrontMcp-decorated class auto-bootstraps), use it
@@ -1110,13 +1189,20 @@ function generateInstallCommand(appName, nativeDeps, selfContained) {
1110
1189
  console.log('Installing ${appName}...');
1111
1190
  dirs.forEach(function(d) { fs.mkdirSync(d, { recursive: true }); });
1112
1191
 
1113
- // Copy bundle files
1192
+ // Copy bundle files and skill content
1114
1193
  var files = fs.readdirSync(SCRIPT_DIR).filter(function(f) {
1115
- return f.endsWith('.js') || f.endsWith('.json')${selfContained ? " || f.endsWith('-bin')" : ''};
1194
+ return f.endsWith('.js') || f.endsWith('.json') || f.endsWith('.md')${selfContained ? " || f.endsWith('-bin')" : ''};
1116
1195
  });
1117
1196
  files.forEach(function(f) {
1118
1197
  fs.copyFileSync(pathMod.join(SCRIPT_DIR, f), pathMod.join(appDir, f));
1119
1198
  });
1199
+ // Copy skill content directories (only those that exist in the build output)
1200
+ var entries = fs.readdirSync(SCRIPT_DIR, { withFileTypes: true });
1201
+ entries.forEach(function(ent) {
1202
+ if (ent.isDirectory()) {
1203
+ fs.cpSync(pathMod.join(SCRIPT_DIR, ent.name), pathMod.join(appDir, ent.name), { recursive: true });
1204
+ }
1205
+ });
1120
1206
  console.log(' Copied ' + files.length + ' files to ' + appDir);
1121
1207
 
1122
1208
  // Install native deps
@@ -1253,6 +1339,10 @@ ${selfContained ? ` // SEA mode: spawn the binary itself in daemon mode — a
1253
1339
  env: env
1254
1340
  });`}
1255
1341
 
1342
+ // Close inherited file descriptors in the parent — the child already has its own copy.
1343
+ fs.closeSync(out);
1344
+ fs.closeSync(err);
1345
+
1256
1346
  fs.writeFileSync(pidPath, JSON.stringify({
1257
1347
  pid: child.pid,
1258
1348
  socketPath: socketPath,
@@ -1335,8 +1425,16 @@ function generateFooter() {
1335
1425
  console.error('Unknown command: ' + args[0]);
1336
1426
  process.exitCode = 1;
1337
1427
  });
1338
- program.parseAsync(process.argv).catch(function(err) {
1428
+ program.parseAsync(process.argv).then(async function() {
1429
+ // Long-running commands (serve) set _isLongRunning to keep the event loop alive.
1430
+ // Short-lived commands close the client and exit explicitly to avoid hanging
1431
+ // on unclosed handles (file loggers, in-memory transport, etc.).
1432
+ if (_isLongRunning) return;
1433
+ await closeClient();
1434
+ process.exit(process.exitCode || 0);
1435
+ }).catch(async function(err) {
1339
1436
  console.error('Fatal:', err.message || err);
1437
+ await closeClient();
1340
1438
  process.exit(1);
1341
1439
  });`;
1342
1440
  }