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 +2 -2
- package/src/commands/build/exec/cli-runtime/cli-bundler.js +2 -0
- package/src/commands/build/exec/cli-runtime/cli-bundler.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +122 -24
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +12 -0
- package/src/commands/build/exec/cli-runtime/schema-extractor.js +17 -1
- package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
- package/src/commands/build/exec/index.js +42 -1
- package/src/commands/build/exec/index.js.map +1 -1
- package/src/commands/build/index.js +9 -5
- package/src/commands/build/index.js.map +1 -1
- package/src/commands/skills/catalog.d.ts +13 -2
- package/src/commands/skills/catalog.js +8 -5
- package/src/commands/skills/catalog.js.map +1 -1
- package/src/core/cli.js +1 -0
- package/src/core/cli.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontmcp",
|
|
3
|
-
"version": "1.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.
|
|
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,
|
|
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 ||
|
|
460
|
-
if (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
console.log(
|
|
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 ||
|
|
509
|
-
if (
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
console.log(
|
|
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
|
-
|
|
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).
|
|
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
|
}
|