frontmcp 1.1.0 → 1.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.
- package/package.json +5 -5
- package/src/commands/build/adapters/cloudflare.js +29 -1
- package/src/commands/build/adapters/cloudflare.js.map +1 -1
- package/src/commands/build/adapters/distributed.js +4 -1
- package/src/commands/build/adapters/distributed.js.map +1 -1
- package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.d.ts +18 -0
- package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.js +58 -0
- package/src/commands/build/exec/cli-runtime/extract-public-message.snippet.js.map +1 -0
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.d.ts +2 -2
- package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +165 -47
- 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 +7 -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.d.ts +11 -0
- package/src/commands/build/exec/index.js +68 -20
- package/src/commands/build/exec/index.js.map +1 -1
- package/src/commands/build/exec/installer-script.d.ts +8 -2
- package/src/commands/build/exec/installer-script.js +34 -15
- package/src/commands/build/exec/installer-script.js.map +1 -1
- package/src/commands/build/exec/manifest.d.ts +16 -3
- package/src/commands/build/exec/manifest.js +17 -5
- package/src/commands/build/exec/manifest.js.map +1 -1
- package/src/commands/build/exec/runner-script.d.ts +9 -1
- package/src/commands/build/exec/runner-script.js +60 -2
- package/src/commands/build/exec/runner-script.js.map +1 -1
- package/src/commands/build/index.js +80 -18
- package/src/commands/build/index.js.map +1 -1
- package/src/commands/build/load-entry-config.d.ts +5 -0
- package/src/commands/build/load-entry-config.js +96 -0
- package/src/commands/build/load-entry-config.js.map +1 -0
- package/src/commands/build/mcpb/manifest.d.ts +14 -0
- package/src/commands/build/mcpb/manifest.js +29 -0
- package/src/commands/build/mcpb/manifest.js.map +1 -1
- package/src/commands/build/types.d.ts +19 -0
- package/src/commands/build/types.js.map +1 -1
- package/src/commands/dev/doctor.js +7 -3
- package/src/commands/dev/doctor.js.map +1 -1
- package/src/commands/package/install.d.ts +1 -1
- package/src/commands/package/install.js +10 -8
- package/src/commands/package/install.js.map +1 -1
- package/src/commands/package/types.d.ts +2 -1
- package/src/commands/package/types.js.map +1 -1
- package/src/config/frontmcp-config.loader.d.ts +20 -0
- package/src/config/frontmcp-config.loader.js +124 -5
- package/src/config/frontmcp-config.loader.js.map +1 -1
- package/src/config/index.d.ts +1 -1
- package/src/config/index.js +2 -1
- package/src/config/index.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frontmcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "FrontMCP command line interface",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"url": "https://github.com/agentfront/frontmcp/issues"
|
|
23
23
|
},
|
|
24
24
|
"main": "./src/index.js",
|
|
25
|
+
"types": "./index.d.js",
|
|
25
26
|
"bin": {
|
|
26
27
|
"frontmcp": "./src/core/cli.js"
|
|
27
28
|
},
|
|
@@ -30,9 +31,9 @@
|
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
33
|
"@clack/prompts": "^0.10.0",
|
|
33
|
-
"@frontmcp/lazy-zod": "1.1.
|
|
34
|
-
"@frontmcp/utils": "1.1.
|
|
35
|
-
"@frontmcp/skills": "1.1.
|
|
34
|
+
"@frontmcp/lazy-zod": "1.1.1",
|
|
35
|
+
"@frontmcp/utils": "1.1.1",
|
|
36
|
+
"@frontmcp/skills": "1.1.1",
|
|
36
37
|
"commander": "^13.0.0",
|
|
37
38
|
"tslib": "^2.3.0",
|
|
38
39
|
"vectoriadb": "^2.2.0",
|
|
@@ -48,6 +49,5 @@
|
|
|
48
49
|
"@types/yauzl": "^2.10.3",
|
|
49
50
|
"@types/yazl": "^2.4.5"
|
|
50
51
|
},
|
|
51
|
-
"types": "./index.d.js",
|
|
52
52
|
"type": "commonjs"
|
|
53
53
|
}
|
|
@@ -72,8 +72,36 @@ module.exports = {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
`,
|
|
75
|
+
// #375 — fail the build when the user's @FrontMcp config references
|
|
76
|
+
// Node-only storage providers that can't run on Workers. Conditional
|
|
77
|
+
// (env-gated) usage still passes; only unconditional declarations throw.
|
|
78
|
+
validate: (decoratorConfig) => {
|
|
79
|
+
if (!decoratorConfig)
|
|
80
|
+
return;
|
|
81
|
+
const sqlite = decoratorConfig['sqlite'];
|
|
82
|
+
const redis = decoratorConfig['redis'];
|
|
83
|
+
const errors = [];
|
|
84
|
+
if (sqlite && typeof sqlite === 'object') {
|
|
85
|
+
errors.push('sqlite storage is not supported on --target cloudflare (no fs / native modules on Workers). ' +
|
|
86
|
+
'Use Cloudflare KV / Durable Objects, or gate the sqlite branch behind a build-time `define` ' +
|
|
87
|
+
'so the bundler can dead-code-eliminate it.');
|
|
88
|
+
}
|
|
89
|
+
if (redis && typeof redis === 'object') {
|
|
90
|
+
errors.push('ioredis-style `redis` storage is not supported on --target cloudflare (no Node net). ' +
|
|
91
|
+
'Use Vercel KV / Upstash Redis (HTTP), or move the redis config to a runtime branch ' +
|
|
92
|
+
'gated on `globalThis.process?.env`.');
|
|
93
|
+
}
|
|
94
|
+
if (errors.length) {
|
|
95
|
+
throw new Error(`[--target cloudflare] config incompatible with Cloudflare Workers:\n - ${errors.join('\n - ')}`);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
// #374 — always write wrangler.toml from the build output. Skipping when
|
|
99
|
+
// the file already exists left users with a wrangler.toml that pointed at
|
|
100
|
+
// dist/index.js while the build emitted dist/cloudflare/index.js, and
|
|
101
|
+
// wrangler deploy silently failed.
|
|
102
|
+
alwaysWriteConfig: true,
|
|
75
103
|
getConfig: (_cwd) => `name = "frontmcp-worker"
|
|
76
|
-
main = "dist/index.js"
|
|
104
|
+
main = "dist/cloudflare/index.js"
|
|
77
105
|
compatibility_date = "2024-01-01"
|
|
78
106
|
`,
|
|
79
107
|
configFileName: 'wrangler.toml',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/cloudflare.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACU,QAAA,iBAAiB,GAAoB;IAChD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;WAKrC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDxB;IAEC,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC;;;CAG9B;IAEC,cAAc,EAAE,eAAe;CAChC,CAAC","sourcesContent":["import type { AdapterTemplate } from '../types';\n\n/**\n * Cloudflare Workers adapter - edge deployment on Cloudflare.\n * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.\n *\n * @see https://developers.cloudflare.com/workers/\n */\nexport const cloudflareAdapter: AdapterTemplate = {\n moduleFormat: 'commonjs',\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Cloudflare Workers entry point\n// Generated by: frontmcp build --target cloudflare\nprocess.env.FRONTMCP_SERVERLESS = '1';\nprocess.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';\n\nrequire('${mainModulePath}');\nconst { getServerlessHandlerAsync } = require('@frontmcp/sdk');\n\nlet app = null;\n\nasync function handleRequest(request) {\n if (!app) {\n app = await getServerlessHandlerAsync();\n }\n\n // Convert Cloudflare Request to Node.js format\n const url = new URL(request.url);\n const req = {\n method: request.method,\n url: url.pathname + url.search,\n headers: Object.fromEntries(request.headers),\n body: request.body,\n };\n\n return new Promise((resolve) => {\n const res = {\n statusCode: 200,\n headers: {},\n body: '',\n status(code) { this.statusCode = code; return this; },\n setHeader(key, value) { this.headers[key] = value; },\n json(data) {\n this.headers['Content-Type'] = 'application/json';\n this.body = JSON.stringify(data);\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n send(data) {\n this.body = data;\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n end() {\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n };\n app(req, res);\n });\n}\n\nmodule.exports = {\n async fetch(request, env, ctx) {\n return handleRequest(request);\n }\n};\n`,\n\n getConfig: (_cwd: string) => `name = \"frontmcp-worker\"\nmain = \"dist/index.js\"\ncompatibility_date = \"2024-01-01\"\n`,\n\n configFileName: 'wrangler.toml',\n};\n"]}
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/cloudflare.ts"],"names":[],"mappings":";;;AAEA;;;;;GAKG;AACU,QAAA,iBAAiB,GAAoB;IAChD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;WAKrC,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyDxB;IAEC,oEAAoE;IACpE,qEAAqE;IACrE,yEAAyE;IACzE,QAAQ,EAAE,CAAC,eAAe,EAAE,EAAE;QAC5B,IAAI,CAAC,eAAe;YAAE,OAAO;QAC7B,MAAM,MAAM,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,KAAK,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QACvC,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,CAAC,IAAI,CACT,8FAA8F;gBAC5F,8FAA8F;gBAC9F,4CAA4C,CAC/C,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,CAAC,IAAI,CACT,uFAAuF;gBACrF,qFAAqF;gBACrF,qCAAqC,CACxC,CAAC;QACJ,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,2EAA2E,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CACnG,CAAC;QACJ,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,0EAA0E;IAC1E,sEAAsE;IACtE,mCAAmC;IACnC,iBAAiB,EAAE,IAAI;IAEvB,SAAS,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC;;;CAG9B;IAEC,cAAc,EAAE,eAAe;CAChC,CAAC","sourcesContent":["import type { AdapterTemplate } from '../types';\n\n/**\n * Cloudflare Workers adapter - edge deployment on Cloudflare.\n * Compiles to CommonJS and adapts the Express app to Cloudflare's fetch API.\n *\n * @see https://developers.cloudflare.com/workers/\n */\nexport const cloudflareAdapter: AdapterTemplate = {\n moduleFormat: 'commonjs',\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated Cloudflare Workers entry point\n// Generated by: frontmcp build --target cloudflare\nprocess.env.FRONTMCP_SERVERLESS = '1';\nprocess.env.FRONTMCP_DEPLOYMENT_MODE = 'serverless';\n\nrequire('${mainModulePath}');\nconst { getServerlessHandlerAsync } = require('@frontmcp/sdk');\n\nlet app = null;\n\nasync function handleRequest(request) {\n if (!app) {\n app = await getServerlessHandlerAsync();\n }\n\n // Convert Cloudflare Request to Node.js format\n const url = new URL(request.url);\n const req = {\n method: request.method,\n url: url.pathname + url.search,\n headers: Object.fromEntries(request.headers),\n body: request.body,\n };\n\n return new Promise((resolve) => {\n const res = {\n statusCode: 200,\n headers: {},\n body: '',\n status(code) { this.statusCode = code; return this; },\n setHeader(key, value) { this.headers[key] = value; },\n json(data) {\n this.headers['Content-Type'] = 'application/json';\n this.body = JSON.stringify(data);\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n send(data) {\n this.body = data;\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n end() {\n resolve(new Response(this.body, {\n status: this.statusCode,\n headers: this.headers,\n }));\n },\n };\n app(req, res);\n });\n}\n\nmodule.exports = {\n async fetch(request, env, ctx) {\n return handleRequest(request);\n }\n};\n`,\n\n // #375 — fail the build when the user's @FrontMcp config references\n // Node-only storage providers that can't run on Workers. Conditional\n // (env-gated) usage still passes; only unconditional declarations throw.\n validate: (decoratorConfig) => {\n if (!decoratorConfig) return;\n const sqlite = decoratorConfig['sqlite'];\n const redis = decoratorConfig['redis'];\n const errors: string[] = [];\n if (sqlite && typeof sqlite === 'object') {\n errors.push(\n 'sqlite storage is not supported on --target cloudflare (no fs / native modules on Workers). ' +\n 'Use Cloudflare KV / Durable Objects, or gate the sqlite branch behind a build-time `define` ' +\n 'so the bundler can dead-code-eliminate it.',\n );\n }\n if (redis && typeof redis === 'object') {\n errors.push(\n 'ioredis-style `redis` storage is not supported on --target cloudflare (no Node net). ' +\n 'Use Vercel KV / Upstash Redis (HTTP), or move the redis config to a runtime branch ' +\n 'gated on `globalThis.process?.env`.',\n );\n }\n if (errors.length) {\n throw new Error(\n `[--target cloudflare] config incompatible with Cloudflare Workers:\\n - ${errors.join('\\n - ')}`,\n );\n }\n },\n\n // #374 — always write wrangler.toml from the build output. Skipping when\n // the file already exists left users with a wrangler.toml that pointed at\n // dist/index.js while the build emitted dist/cloudflare/index.js, and\n // wrangler deploy silently failed.\n alwaysWriteConfig: true,\n\n getConfig: (_cwd: string) => `name = \"frontmcp-worker\"\nmain = \"dist/cloudflare/index.js\"\ncompatibility_date = \"2024-01-01\"\n`,\n\n configFileName: 'wrangler.toml',\n};\n"]}
|
|
@@ -23,7 +23,10 @@ process.env.FRONTMCP_DEPLOYMENT_MODE = 'distributed';
|
|
|
23
23
|
`,
|
|
24
24
|
getEntryTemplate: (mainModulePath) => `// Auto-generated distributed entry point
|
|
25
25
|
// Generated by: frontmcp build --target distributed
|
|
26
|
-
|
|
26
|
+
// The build emits the HA bootstrap as serverless-setup.js (shared name across
|
|
27
|
+
// all serverless/distributed adapters); requiring ha-setup.js used to crash at
|
|
28
|
+
// runtime because that filename was never written.
|
|
29
|
+
require('./serverless-setup.js');
|
|
27
30
|
require('${mainModulePath}');
|
|
28
31
|
`,
|
|
29
32
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distributed.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/distributed.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACU,QAAA,kBAAkB,GAAoB;IACjD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,GAAG,EAAE,CAAC;;;;CAIzB;IAEC,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC
|
|
1
|
+
{"version":3,"file":"distributed.js","sourceRoot":"","sources":["../../../../../src/commands/build/adapters/distributed.ts"],"names":[],"mappings":";;;AAEA;;;;;;;;;;;;GAYG;AACU,QAAA,kBAAkB,GAAoB;IACjD,YAAY,EAAE,UAAU;IAExB,gBAAgB,EAAE,GAAG,EAAE,CAAC;;;;CAIzB;IAEC,gBAAgB,EAAE,CAAC,cAAsB,EAAE,EAAE,CAAC;;;;;;WAMrC,cAAc;CACxB;CACA,CAAC","sourcesContent":["import type { AdapterTemplate } from '../types';\n\n/**\n * Distributed adapter - multi-pod Node.js deployment with HA.\n * Compiles to CommonJS with an HA setup file that sets FRONTMCP_DEPLOYMENT_MODE.\n *\n * The setup file runs before any imports to ensure the deployment mode\n * is detected before @FrontMcp decorators execute. This enables:\n * - Session heartbeat and takeover\n * - Cross-pod notification relay via Redis pub/sub\n * - LB affinity via __frontmcp_node cookie\n * - Machine ID from K8s HOSTNAME or os.hostname()\n *\n * Requires Redis configuration in @FrontMcp() for session persistence.\n */\nexport const distributedAdapter: AdapterTemplate = {\n moduleFormat: 'commonjs',\n\n getSetupTemplate: () => `// Distributed HA setup - MUST be imported first\n// Sets FRONTMCP_DEPLOYMENT_MODE before any decorators run\n// Enables heartbeat, session takeover, and notification relay\nprocess.env.FRONTMCP_DEPLOYMENT_MODE = 'distributed';\n`,\n\n getEntryTemplate: (mainModulePath: string) => `// Auto-generated distributed entry point\n// Generated by: frontmcp build --target distributed\n// The build emits the HA bootstrap as serverless-setup.js (shared name across\n// all serverless/distributed adapters); requiring ha-setup.js used to crash at\n// runtime because that filename was never written.\nrequire('./serverless-setup.js');\nrequire('${mainModulePath}');\n`,\n};\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI-bundle source for `_extractPublicMessage` / `_exitWithError`.
|
|
3
|
+
*
|
|
4
|
+
* The SEA CLI bundle has to be self-contained — it can't reach into
|
|
5
|
+
* `@frontmcp/sdk` at runtime — so we duplicate the SDK's
|
|
6
|
+
* `extractPublicMessage` walker into the generated source. To prevent the
|
|
7
|
+
* two implementations from drifting, the duplicated source lives in this
|
|
8
|
+
* file as a string constant and is shared by:
|
|
9
|
+
*
|
|
10
|
+
* - `generateCliEntry.ts` (embeds it in the generated CLI source)
|
|
11
|
+
* - `extract-public-message-parity.spec.ts` (parity-tests it against
|
|
12
|
+
* the SDK's TS implementation using shared fixtures)
|
|
13
|
+
*
|
|
14
|
+
* Keep the function bodies free of TS / ESM-only syntax — the resulting
|
|
15
|
+
* string is concatenated into a CommonJS bundle and `Function('return …')`
|
|
16
|
+
* compiles it as plain ES2020.
|
|
17
|
+
*/
|
|
18
|
+
export declare const EXTRACT_PUBLIC_MESSAGE_SNIPPET: string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EXTRACT_PUBLIC_MESSAGE_SNIPPET = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* CLI-bundle source for `_extractPublicMessage` / `_exitWithError`.
|
|
6
|
+
*
|
|
7
|
+
* The SEA CLI bundle has to be self-contained — it can't reach into
|
|
8
|
+
* `@frontmcp/sdk` at runtime — so we duplicate the SDK's
|
|
9
|
+
* `extractPublicMessage` walker into the generated source. To prevent the
|
|
10
|
+
* two implementations from drifting, the duplicated source lives in this
|
|
11
|
+
* file as a string constant and is shared by:
|
|
12
|
+
*
|
|
13
|
+
* - `generateCliEntry.ts` (embeds it in the generated CLI source)
|
|
14
|
+
* - `extract-public-message-parity.spec.ts` (parity-tests it against
|
|
15
|
+
* the SDK's TS implementation using shared fixtures)
|
|
16
|
+
*
|
|
17
|
+
* Keep the function bodies free of TS / ESM-only syntax — the resulting
|
|
18
|
+
* string is concatenated into a CommonJS bundle and `Function('return …')`
|
|
19
|
+
* compiles it as plain ES2020.
|
|
20
|
+
*/
|
|
21
|
+
exports.EXTRACT_PUBLIC_MESSAGE_SNIPPET = `
|
|
22
|
+
// Walk an error chain (cause / originalError) and return the most user-friendly
|
|
23
|
+
// message — prefers PublicMcpError.getPublicMessage() over wrapped wrappers like
|
|
24
|
+
// "Tool 'X' execution failed: Unknown error". Mirrors @frontmcp/sdk's
|
|
25
|
+
// extractPublicMessage so the SEA bundle stays self-contained. Cycles in the
|
|
26
|
+
// chain are short-circuited via a WeakSet of already-visited error objects.
|
|
27
|
+
function _extractPublicMessage(err) {
|
|
28
|
+
return _extractPublicMessageImpl(err, new WeakSet());
|
|
29
|
+
}
|
|
30
|
+
function _extractPublicMessageImpl(err, visited) {
|
|
31
|
+
if (err && typeof err === 'object') {
|
|
32
|
+
if (visited.has(err)) return 'Unknown error';
|
|
33
|
+
visited.add(err);
|
|
34
|
+
}
|
|
35
|
+
if (err == null) return 'Unknown error';
|
|
36
|
+
if (typeof err === 'string') return err;
|
|
37
|
+
// Direct PublicMcpError (has isPublic === true and a non-default message)
|
|
38
|
+
if (err && err.isPublic === true && err.message) return err.message;
|
|
39
|
+
// Wrapped: try originalError, then cause.
|
|
40
|
+
var inner = err && (err.originalError || err.cause);
|
|
41
|
+
if (inner) {
|
|
42
|
+
var innerMsg = _extractPublicMessageImpl(inner, visited);
|
|
43
|
+
if (innerMsg && innerMsg !== 'Unknown error') return innerMsg;
|
|
44
|
+
}
|
|
45
|
+
// Fallback: own .message, but skip generic wrappers when we have nothing better.
|
|
46
|
+
if (err.message) return err.message;
|
|
47
|
+
return String(err);
|
|
48
|
+
}
|
|
49
|
+
// Print an error with the best available public message and set the appropriate
|
|
50
|
+
// exit code (1 = runtime error, 2 = usage error). Centralized so every action
|
|
51
|
+
// handler reports the same shape.
|
|
52
|
+
function _exitWithError(err, code) {
|
|
53
|
+
var msg = _extractPublicMessage(err);
|
|
54
|
+
console.error('Error: ' + msg);
|
|
55
|
+
process.exitCode = code || 1;
|
|
56
|
+
}
|
|
57
|
+
`.trim();
|
|
58
|
+
//# sourceMappingURL=extract-public-message.snippet.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-public-message.snippet.js","sourceRoot":"","sources":["../../../../../../src/commands/build/exec/cli-runtime/extract-public-message.snippet.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;;;;;;GAgBG;AACU,QAAA,8BAA8B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC7C,CAAC,IAAI,EAAE,CAAC","sourcesContent":["/**\n * CLI-bundle source for `_extractPublicMessage` / `_exitWithError`.\n *\n * The SEA CLI bundle has to be self-contained — it can't reach into\n * `@frontmcp/sdk` at runtime — so we duplicate the SDK's\n * `extractPublicMessage` walker into the generated source. To prevent the\n * two implementations from drifting, the duplicated source lives in this\n * file as a string constant and is shared by:\n *\n * - `generateCliEntry.ts` (embeds it in the generated CLI source)\n * - `extract-public-message-parity.spec.ts` (parity-tests it against\n * the SDK's TS implementation using shared fixtures)\n *\n * Keep the function bodies free of TS / ESM-only syntax — the resulting\n * string is concatenated into a CommonJS bundle and `Function('return …')`\n * compiles it as plain ES2020.\n */\nexport const EXTRACT_PUBLIC_MESSAGE_SNIPPET = `\n// Walk an error chain (cause / originalError) and return the most user-friendly\n// message — prefers PublicMcpError.getPublicMessage() over wrapped wrappers like\n// \"Tool 'X' execution failed: Unknown error\". Mirrors @frontmcp/sdk's\n// extractPublicMessage so the SEA bundle stays self-contained. Cycles in the\n// chain are short-circuited via a WeakSet of already-visited error objects.\nfunction _extractPublicMessage(err) {\n return _extractPublicMessageImpl(err, new WeakSet());\n}\nfunction _extractPublicMessageImpl(err, visited) {\n if (err && typeof err === 'object') {\n if (visited.has(err)) return 'Unknown error';\n visited.add(err);\n }\n if (err == null) return 'Unknown error';\n if (typeof err === 'string') return err;\n // Direct PublicMcpError (has isPublic === true and a non-default message)\n if (err && err.isPublic === true && err.message) return err.message;\n // Wrapped: try originalError, then cause.\n var inner = err && (err.originalError || err.cause);\n if (inner) {\n var innerMsg = _extractPublicMessageImpl(inner, visited);\n if (innerMsg && innerMsg !== 'Unknown error') return innerMsg;\n }\n // Fallback: own .message, but skip generic wrappers when we have nothing better.\n if (err.message) return err.message;\n return String(err);\n}\n// Print an error with the best available public message and set the appropriate\n// exit code (1 = runtime error, 2 = usage error). Centralized so every action\n// handler reports the same shape.\nfunction _exitWithError(err, code) {\n var msg = _extractPublicMessage(err);\n console.error('Error: ' + msg);\n process.exitCode = code || 1;\n}\n`.trim();\n"]}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Generates the CLI entry point TypeScript/JavaScript source code.
|
|
3
3
|
* This creates a commander.js-based CLI where each MCP tool is a subcommand.
|
|
4
4
|
*/
|
|
5
|
-
import { CliConfig, OAuthConfig } from '../config';
|
|
6
|
-
import { ExtractedSchema } from './schema-extractor';
|
|
5
|
+
import { type CliConfig, type OAuthConfig } from '../config';
|
|
6
|
+
import { type ExtractedSchema } from './schema-extractor';
|
|
7
7
|
export declare const RESERVED_COMMANDS: Set<string>;
|
|
8
8
|
export interface CliEntryOptions {
|
|
9
9
|
appName: string;
|
|
@@ -8,6 +8,7 @@ exports.RESERVED_COMMANDS = void 0;
|
|
|
8
8
|
exports.resolveToolCommandName = resolveToolCommandName;
|
|
9
9
|
exports.generateCliEntry = generateCliEntry;
|
|
10
10
|
exports.extractTemplateParams = extractTemplateParams;
|
|
11
|
+
const extract_public_message_snippet_1 = require("./extract-public-message.snippet");
|
|
11
12
|
const schema_extractor_1 = require("./schema-extractor");
|
|
12
13
|
const schema_to_commander_1 = require("./schema-to-commander");
|
|
13
14
|
exports.RESERVED_COMMANDS = new Set([
|
|
@@ -15,6 +16,9 @@ exports.RESERVED_COMMANDS = new Set([
|
|
|
15
16
|
'login', 'logout', 'connect', 'serve', 'daemon',
|
|
16
17
|
'doctor', 'install', 'uninstall', 'sessions', 'help', 'version',
|
|
17
18
|
'skills', 'job', 'workflow',
|
|
19
|
+
// Reserved by the symmetric `prompt get <name>` / `resource read <uri>`
|
|
20
|
+
// sub-API; included so a user-defined entry can never shadow them.
|
|
21
|
+
'get', 'list', 'read',
|
|
18
22
|
]);
|
|
19
23
|
/**
|
|
20
24
|
* Resolve tool command name, appending '-tool' suffix if it conflicts with a built-in command.
|
|
@@ -193,7 +197,31 @@ async function closeClient() {
|
|
|
193
197
|
// Flag set by long-running commands (serve, daemon) to prevent the footer from calling process.exit().
|
|
194
198
|
var _isLongRunning = false;
|
|
195
199
|
|
|
200
|
+
${extract_public_message_snippet_1.EXTRACT_PUBLIC_MESSAGE_SNIPPET}
|
|
201
|
+
|
|
196
202
|
var program = new Command();
|
|
203
|
+
// Make Commander's own usage errors (unknown subcommand, missing required option,
|
|
204
|
+
// invalid value) exit with code 2 instead of the default 0 — matches POSIX
|
|
205
|
+
// convention and lets shell scripts/CI distinguish runtime failures from usage.
|
|
206
|
+
//
|
|
207
|
+
// Sets process.exitCode and re-throws so the parseAsync() footer can run
|
|
208
|
+
// closeClient() / native-addon teardown before the actual exit. Calling
|
|
209
|
+
// process.exit() directly here would skip that and could leave better-sqlite3
|
|
210
|
+
// / ONNX / file handles in a corrupt state for short-lived runs.
|
|
211
|
+
program.exitOverride(function(err) {
|
|
212
|
+
if (err.code === 'commander.helpDisplayed' || err.code === 'commander.help' || err.code === 'commander.version') {
|
|
213
|
+
process.exitCode = 0;
|
|
214
|
+
} else if (err.code === 'commander.unknownCommand' || err.code === 'commander.unknownOption' ||
|
|
215
|
+
err.code === 'commander.missingArgument' || err.code === 'commander.missingMandatoryOptionValue' ||
|
|
216
|
+
err.code === 'commander.invalidArgument' || err.code === 'commander.optionMissingArgument' ||
|
|
217
|
+
err.code === 'commander.invalidOptionArgument' || err.code === 'commander.excessArguments') {
|
|
218
|
+
process.exitCode = 2;
|
|
219
|
+
} else {
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
}
|
|
222
|
+
// Re-throw so parseAsync().catch in the footer runs cleanup before exit.
|
|
223
|
+
throw err;
|
|
224
|
+
});
|
|
197
225
|
program
|
|
198
226
|
.name(${JSON.stringify(appName)})
|
|
199
227
|
.version(${JSON.stringify(appVersion)})
|
|
@@ -280,16 +308,28 @@ ${optionLines}
|
|
|
280
308
|
var result = await client.callTool(${JSON.stringify(tool.name)}, args);
|
|
281
309
|
var mode = program.opts().output || ${JSON.stringify('text')};
|
|
282
310
|
console.log(fmt.formatToolResult(result, mode));
|
|
311
|
+
// The SDK converts thrown errors into a CallToolResult with isError:true
|
|
312
|
+
// (so HTTP/JSON-RPC clients still get a structured response). Detect
|
|
313
|
+
// that here and map to a non-zero exit code so shell scripts can gate
|
|
314
|
+
// on success — Zod input validation errors → exit 2 (usage), all
|
|
315
|
+
// other tool errors → exit 1 (runtime).
|
|
316
|
+
if (result && result.isError === true) {
|
|
317
|
+
var rmeta = (result && result._meta) || {};
|
|
318
|
+
process.exitCode = (rmeta.code === 'INVALID_INPUT') ? 2 : 1;
|
|
319
|
+
}
|
|
283
320
|
} catch (err) {
|
|
284
321
|
var meta = err && err._meta ? err._meta : (err && err.data && err.data._meta ? err.data._meta : null);
|
|
285
322
|
if (meta && meta.authorization_required) {
|
|
286
323
|
console.error('Authorization required' + (meta.app ? ' for ' + meta.app : ''));
|
|
287
324
|
if (meta.auth_url) console.error('Authorize at: ' + meta.auth_url);
|
|
288
325
|
console.error('Or run: ' + ${JSON.stringify(appName)} + ' login');
|
|
326
|
+
process.exitCode = 1;
|
|
289
327
|
} else {
|
|
290
|
-
|
|
328
|
+
// Thrown error path (transport / DI / pre-flow failure) — same
|
|
329
|
+
// mapping as the isError result path above.
|
|
330
|
+
var isUsage = err && err.code === 'INVALID_INPUT';
|
|
331
|
+
_exitWithError(err, isUsage ? 2 : 1);
|
|
291
332
|
}
|
|
292
|
-
process.exitCode = 1;
|
|
293
333
|
}
|
|
294
334
|
});`;
|
|
295
335
|
});
|
|
@@ -362,8 +402,7 @@ resourceCmd
|
|
|
362
402
|
});
|
|
363
403
|
}
|
|
364
404
|
} catch (err) {
|
|
365
|
-
|
|
366
|
-
process.exitCode = 1;
|
|
405
|
+
_exitWithError(err, 1);
|
|
367
406
|
}
|
|
368
407
|
});
|
|
369
408
|
|
|
@@ -377,8 +416,7 @@ resourceCmd
|
|
|
377
416
|
var mode = program.opts().output || 'text';
|
|
378
417
|
console.log(fmt.formatResourceResult(result, mode));
|
|
379
418
|
} catch (err) {
|
|
380
|
-
|
|
381
|
-
process.exitCode = 1;
|
|
419
|
+
_exitWithError(err, 1);
|
|
382
420
|
}
|
|
383
421
|
});`;
|
|
384
422
|
}
|
|
@@ -412,8 +450,7 @@ ${optionLines}
|
|
|
412
450
|
var mode = program.opts().output || 'text';
|
|
413
451
|
console.log(fmt.formatResourceResult(result, mode));
|
|
414
452
|
} catch (err) {
|
|
415
|
-
|
|
416
|
-
process.exitCode = 1;
|
|
453
|
+
_exitWithError(err, 1);
|
|
417
454
|
}
|
|
418
455
|
});`;
|
|
419
456
|
});
|
|
@@ -437,14 +474,25 @@ templateCmd
|
|
|
437
474
|
});
|
|
438
475
|
}
|
|
439
476
|
} catch (err) {
|
|
440
|
-
|
|
441
|
-
process.exitCode = 1;
|
|
477
|
+
_exitWithError(err, 1);
|
|
442
478
|
}
|
|
443
479
|
});
|
|
444
480
|
|
|
445
481
|
${subcommands.join('\n\n')}`;
|
|
446
482
|
}
|
|
447
483
|
function generatePromptCommands(prompts) {
|
|
484
|
+
// Map known prompts → option specs so `prompt get <name>` knows which flags
|
|
485
|
+
// to accept per prompt. Unknown prompt names still call getPrompt() and
|
|
486
|
+
// surface the server's error to the user.
|
|
487
|
+
const promptArgsMap = prompts.map((p) => {
|
|
488
|
+
const args = (p.arguments || []).map((a) => ({
|
|
489
|
+
name: a.name,
|
|
490
|
+
kebab: (0, schema_to_commander_1.camelToKebab)(a.name),
|
|
491
|
+
camel: kebabToCamel((0, schema_to_commander_1.camelToKebab)(a.name)),
|
|
492
|
+
required: !!a.required,
|
|
493
|
+
}));
|
|
494
|
+
return `${JSON.stringify(p.name)}: ${JSON.stringify(args)}`;
|
|
495
|
+
}).join(',\n ');
|
|
448
496
|
const subcommands = prompts.map((prompt) => {
|
|
449
497
|
const cmdName = (0, schema_to_commander_1.camelToKebab)(prompt.name).replace(/_/g, '-');
|
|
450
498
|
const argOptions = (prompt.arguments || [])
|
|
@@ -458,7 +506,7 @@ function generatePromptCommands(prompts) {
|
|
|
458
506
|
.join('\n');
|
|
459
507
|
return `promptCmd
|
|
460
508
|
.command(${JSON.stringify(cmdName)})
|
|
461
|
-
.description(${JSON.stringify(prompt.description || '')})
|
|
509
|
+
.description(${JSON.stringify((prompt.description || '') + ' (deprecated alias — use `prompt get ' + cmdName + '`)')})
|
|
462
510
|
${argOptions}
|
|
463
511
|
.action(async function(opts) {
|
|
464
512
|
try {
|
|
@@ -473,8 +521,7 @@ ${argOptions}
|
|
|
473
521
|
var mode = program.opts().output || 'text';
|
|
474
522
|
console.log(fmt.formatPromptResult(result, mode));
|
|
475
523
|
} catch (err) {
|
|
476
|
-
|
|
477
|
-
process.exitCode = 1;
|
|
524
|
+
_exitWithError(err, 1);
|
|
478
525
|
}
|
|
479
526
|
});`;
|
|
480
527
|
});
|
|
@@ -498,8 +545,83 @@ promptCmd
|
|
|
498
545
|
});
|
|
499
546
|
}
|
|
500
547
|
} catch (err) {
|
|
501
|
-
|
|
502
|
-
|
|
548
|
+
_exitWithError(err, 1);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// Symmetric \`prompt get <name>\` to mirror \`resource read <uri>\`. Looks up
|
|
553
|
+
// per-prompt argument metadata from \`promptArgs\` and forwards as MCP
|
|
554
|
+
// \`prompts/get\` request arguments. Unknown / missing required flags exit 2.
|
|
555
|
+
//
|
|
556
|
+
// Robust flag parser supports:
|
|
557
|
+
// --key value (canonical form)
|
|
558
|
+
// --key=value (single-token form)
|
|
559
|
+
// --bool (boolean — value defaults to "true")
|
|
560
|
+
// -- (end-of-options marker; everything after is ignored)
|
|
561
|
+
// Unknown flags fail-fast with exit 2 instead of being silently dropped.
|
|
562
|
+
var promptArgs = {
|
|
563
|
+
${promptArgsMap}
|
|
564
|
+
};
|
|
565
|
+
var _getCmd = promptCmd
|
|
566
|
+
.command('get <name>')
|
|
567
|
+
.description('Render a prompt by name')
|
|
568
|
+
.allowUnknownOption(true)
|
|
569
|
+
.action(async function(name) {
|
|
570
|
+
try {
|
|
571
|
+
var spec = promptArgs[name];
|
|
572
|
+
if (!spec) {
|
|
573
|
+
console.error('Error: Unknown prompt: ' + name);
|
|
574
|
+
process.exitCode = 1;
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
var rawTokens = this.args.slice(1);
|
|
578
|
+
var args = {};
|
|
579
|
+
var unknown = [];
|
|
580
|
+
var byKebab = {};
|
|
581
|
+
for (var s = 0; s < spec.length; s++) byKebab[spec[s].kebab] = spec[s];
|
|
582
|
+
for (var i = 0; i < rawTokens.length; i++) {
|
|
583
|
+
var tok = rawTokens[i];
|
|
584
|
+
if (tok === '--') break;
|
|
585
|
+
if (typeof tok !== 'string' || tok.indexOf('--') !== 0) continue;
|
|
586
|
+
var keyAndVal = tok.slice(2);
|
|
587
|
+
var key, val;
|
|
588
|
+
var eq = keyAndVal.indexOf('=');
|
|
589
|
+
if (eq >= 0) {
|
|
590
|
+
key = keyAndVal.slice(0, eq);
|
|
591
|
+
val = keyAndVal.slice(eq + 1);
|
|
592
|
+
} else {
|
|
593
|
+
key = keyAndVal;
|
|
594
|
+
var next = (i + 1 < rawTokens.length) ? rawTokens[i + 1] : undefined;
|
|
595
|
+
if (typeof next === 'string' && next.indexOf('--') !== 0) {
|
|
596
|
+
val = next;
|
|
597
|
+
i++;
|
|
598
|
+
} else {
|
|
599
|
+
val = 'true';
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
var match = byKebab[key];
|
|
603
|
+
if (!match) { unknown.push('--' + key); continue; }
|
|
604
|
+
args[match.name] = val;
|
|
605
|
+
}
|
|
606
|
+
if (unknown.length > 0) {
|
|
607
|
+
console.error('Error: unknown option(s) for prompt "' + name + '": ' + unknown.join(', '));
|
|
608
|
+
process.exitCode = 2;
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
// Validate required args
|
|
612
|
+
for (var r = 0; r < spec.length; r++) {
|
|
613
|
+
if (spec[r].required && args[spec[r].name] === undefined) {
|
|
614
|
+
console.error('Error: missing required option --' + spec[r].kebab);
|
|
615
|
+
process.exitCode = 2;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
var client = await getClient();
|
|
620
|
+
var result = await client.getPrompt(name, args);
|
|
621
|
+
var mode = program.opts().output || 'text';
|
|
622
|
+
console.log(fmt.formatPromptResult(result, mode));
|
|
623
|
+
} catch (err) {
|
|
624
|
+
_exitWithError(err, 1);
|
|
503
625
|
}
|
|
504
626
|
});
|
|
505
627
|
|
|
@@ -535,8 +657,7 @@ skillsCmd
|
|
|
535
657
|
console.log(" Use '" + program.name() + " skills load <name>' to load a skill.\\n");
|
|
536
658
|
}
|
|
537
659
|
} catch (err) {
|
|
538
|
-
|
|
539
|
-
process.exitCode = 1;
|
|
660
|
+
_exitWithError(err, 1);
|
|
540
661
|
}
|
|
541
662
|
});
|
|
542
663
|
|
|
@@ -557,8 +678,7 @@ skillsCmd
|
|
|
557
678
|
}
|
|
558
679
|
}
|
|
559
680
|
} catch (err) {
|
|
560
|
-
|
|
561
|
-
process.exitCode = 1;
|
|
681
|
+
_exitWithError(err, 1);
|
|
562
682
|
}
|
|
563
683
|
});
|
|
564
684
|
|
|
@@ -594,8 +714,7 @@ skillsCmd
|
|
|
594
714
|
console.log(" Load: " + program.name() + " skills load " + name + '\\n');
|
|
595
715
|
}
|
|
596
716
|
} catch (err) {
|
|
597
|
-
|
|
598
|
-
process.exitCode = 1;
|
|
717
|
+
_exitWithError(err, 1);
|
|
599
718
|
}
|
|
600
719
|
});
|
|
601
720
|
|
|
@@ -623,8 +742,7 @@ skillsCmd
|
|
|
623
742
|
console.log(" Use '" + program.name() + " skills read <name>' for full details.\\n");
|
|
624
743
|
}
|
|
625
744
|
} catch (err) {
|
|
626
|
-
|
|
627
|
-
process.exitCode = 1;
|
|
745
|
+
_exitWithError(err, 1);
|
|
628
746
|
}
|
|
629
747
|
});`;
|
|
630
748
|
}
|
|
@@ -659,8 +777,7 @@ ${optionLines}
|
|
|
659
777
|
}
|
|
660
778
|
}
|
|
661
779
|
} catch (err) {
|
|
662
|
-
|
|
663
|
-
process.exitCode = 1;
|
|
780
|
+
_exitWithError(err, 1);
|
|
664
781
|
}
|
|
665
782
|
});`;
|
|
666
783
|
}
|
|
@@ -690,8 +807,7 @@ ${optionLines}
|
|
|
690
807
|
}
|
|
691
808
|
}
|
|
692
809
|
} catch (err) {
|
|
693
|
-
|
|
694
|
-
process.exitCode = 1;
|
|
810
|
+
_exitWithError(err, 1);
|
|
695
811
|
}
|
|
696
812
|
});`;
|
|
697
813
|
});
|
|
@@ -721,8 +837,7 @@ ${optionLines}
|
|
|
721
837
|
}
|
|
722
838
|
}
|
|
723
839
|
} catch (err) {
|
|
724
|
-
|
|
725
|
-
process.exitCode = 1;
|
|
840
|
+
_exitWithError(err, 1);
|
|
726
841
|
}
|
|
727
842
|
});`;
|
|
728
843
|
return `var jobCmd = program.command('job').description('Job operations');
|
|
@@ -749,8 +864,7 @@ jobCmd
|
|
|
749
864
|
}
|
|
750
865
|
}
|
|
751
866
|
} catch (err) {
|
|
752
|
-
|
|
753
|
-
process.exitCode = 1;
|
|
867
|
+
_exitWithError(err, 1);
|
|
754
868
|
}
|
|
755
869
|
});
|
|
756
870
|
|
|
@@ -782,8 +896,7 @@ ${jobs.length > 0 ? genericRun : `jobRunCmd
|
|
|
782
896
|
}
|
|
783
897
|
}
|
|
784
898
|
} catch (err) {
|
|
785
|
-
|
|
786
|
-
process.exitCode = 1;
|
|
899
|
+
_exitWithError(err, 1);
|
|
787
900
|
}
|
|
788
901
|
});`}
|
|
789
902
|
|
|
@@ -801,8 +914,7 @@ jobCmd
|
|
|
801
914
|
console.log('Status: ' + (result.status || JSON.stringify(result)));
|
|
802
915
|
}
|
|
803
916
|
} catch (err) {
|
|
804
|
-
|
|
805
|
-
process.exitCode = 1;
|
|
917
|
+
_exitWithError(err, 1);
|
|
806
918
|
}
|
|
807
919
|
});`;
|
|
808
920
|
}
|
|
@@ -831,8 +943,7 @@ workflowCmd
|
|
|
831
943
|
}
|
|
832
944
|
}
|
|
833
945
|
} catch (err) {
|
|
834
|
-
|
|
835
|
-
process.exitCode = 1;
|
|
946
|
+
_exitWithError(err, 1);
|
|
836
947
|
}
|
|
837
948
|
});
|
|
838
949
|
|
|
@@ -861,8 +972,7 @@ workflowCmd
|
|
|
861
972
|
}
|
|
862
973
|
}
|
|
863
974
|
} catch (err) {
|
|
864
|
-
|
|
865
|
-
process.exitCode = 1;
|
|
975
|
+
_exitWithError(err, 1);
|
|
866
976
|
}
|
|
867
977
|
});
|
|
868
978
|
|
|
@@ -880,8 +990,7 @@ workflowCmd
|
|
|
880
990
|
console.log('Status: ' + (result.status || JSON.stringify(result)));
|
|
881
991
|
}
|
|
882
992
|
} catch (err) {
|
|
883
|
-
|
|
884
|
-
process.exitCode = 1;
|
|
993
|
+
_exitWithError(err, 1);
|
|
885
994
|
}
|
|
886
995
|
});`;
|
|
887
996
|
}
|
|
@@ -933,8 +1042,7 @@ subscribeCmd
|
|
|
933
1042
|
setInterval(function() {}, 2147483647);
|
|
934
1043
|
await new Promise(function() {});
|
|
935
1044
|
} catch (err) {
|
|
936
|
-
|
|
937
|
-
process.exitCode = 1;
|
|
1045
|
+
_exitWithError(err, 1);
|
|
938
1046
|
}
|
|
939
1047
|
});
|
|
940
1048
|
|
|
@@ -962,8 +1070,7 @@ subscribeCmd
|
|
|
962
1070
|
setInterval(function() {}, 2147483647);
|
|
963
1071
|
await new Promise(function() {});
|
|
964
1072
|
} catch (err) {
|
|
965
|
-
|
|
966
|
-
process.exitCode = 1;
|
|
1073
|
+
_exitWithError(err, 1);
|
|
967
1074
|
}
|
|
968
1075
|
});`;
|
|
969
1076
|
}
|
|
@@ -1521,9 +1628,20 @@ program.parseAsync(process.argv).then(async function() {
|
|
|
1521
1628
|
// (ONNX runtime, etc.) can release mutexes before V8 tears down.
|
|
1522
1629
|
setImmediate(function() { process.exit(process.exitCode || 0); });
|
|
1523
1630
|
}).catch(async function(err) {
|
|
1524
|
-
|
|
1631
|
+
// Commander errors come through exitOverride with the code already set on
|
|
1632
|
+
// process.exitCode. They are user-facing usage errors, not fatals — don't
|
|
1633
|
+
// re-print "Fatal:" / "Unknown error" for them.
|
|
1634
|
+
var isCommanderErr = err && typeof err.code === 'string' && err.code.indexOf('commander.') === 0;
|
|
1635
|
+
if (!isCommanderErr) {
|
|
1636
|
+
console.error('Fatal:', err.message || err);
|
|
1637
|
+
}
|
|
1525
1638
|
await closeClient();
|
|
1526
|
-
|
|
1639
|
+
// Use the exit code set by exitOverride (which can legitimately be 0 for
|
|
1640
|
+
// --help / --version). Only fall back to 1 when no code was set.
|
|
1641
|
+
setImmediate(function() {
|
|
1642
|
+
var code = (typeof process.exitCode === 'number') ? process.exitCode : 1;
|
|
1643
|
+
process.exit(code);
|
|
1644
|
+
});
|
|
1527
1645
|
});`;
|
|
1528
1646
|
}
|
|
1529
1647
|
/**
|