@obov/mext 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-cli.d.ts +2 -1
- package/dist/build-cli.js +24 -14
- package/dist/build-cli.js.map +1 -1
- package/dist/build.d.ts +23 -3
- package/dist/build.js +58 -5
- package/dist/build.js.map +1 -1
- package/dist/cli/build.js +5 -0
- package/dist/cli/build.js.map +1 -1
- package/dist/cli/dev-server.js +2 -1
- package/dist/cli/dev-server.js.map +1 -1
- package/dist/cli/dev.js +2 -1
- package/dist/cli/dev.js.map +1 -1
- package/dist/cli/output.d.ts +45 -1
- package/dist/cli/output.js +60 -3
- package/dist/cli/output.js.map +1 -1
- package/dist/cli/run.d.ts +26 -0
- package/dist/cli/run.js +41 -20
- package/dist/cli/run.js.map +1 -1
- package/dist/client/main.d.ts +8 -2
- package/dist/client/main.js +4 -3
- package/dist/client/main.js.map +1 -1
- package/dist/client/runner.d.ts +3 -0
- package/dist/client/runner.js +13 -12
- package/dist/client/runner.js.map +1 -1
- package/dist/config.d.ts +67 -2
- package/dist/config.js +89 -4
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +19 -1
- package/dist/errors.js +47 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/instrumentation.d.ts +2 -2
- package/dist/instrumentation.js +5 -5
- package/dist/instrumentation.js.map +1 -1
- package/dist/local-bin.d.ts +20 -0
- package/dist/local-bin.js +102 -0
- package/dist/local-bin.js.map +1 -0
- package/dist/mcp/dispatch.d.ts +1 -1
- package/dist/mcp/dispatch.js +3 -3
- package/dist/mcp/dispatch.js.map +1 -1
- package/dist/mcp/http.d.ts +4 -0
- package/dist/mcp/http.js +2 -0
- package/dist/mcp/http.js.map +1 -1
- package/dist/mcp/install.d.ts +2 -2
- package/dist/mcp/install.js +18 -0
- package/dist/mcp/install.js.map +1 -1
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.js +7 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/stdio.d.ts +18 -0
- package/dist/mcp/stdio.js +155 -12
- package/dist/mcp/stdio.js.map +1 -1
- package/dist/start.d.ts +2 -1
- package/dist/start.js +13 -1
- package/dist/start.js.map +1 -1
- package/package.json +1 -1
package/dist/config.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CliErrorFormatter } from "./cli/output.ts";
|
|
1
2
|
import type { MextApp } from "./command.ts";
|
|
2
3
|
import type { OAuthResourceOptions } from "./mcp/http.ts";
|
|
3
4
|
import { type Principal } from "./principal.ts";
|
|
@@ -24,23 +25,72 @@ export interface MextConfig {
|
|
|
24
25
|
localPrincipal?: Principal | (() => Principal | Promise<Principal>);
|
|
25
26
|
/** 고객 CLI (mext build → .mext/cli/). 없으면 고객 CLI 를 만들지 않는다 */
|
|
26
27
|
cli?: CliConfig;
|
|
28
|
+
/** MCP 서버 선언 (serverInfo.name · instructions · capabilities) · stdio 서버 발신 알림 (§6) */
|
|
29
|
+
mcp?: McpConfig;
|
|
27
30
|
}
|
|
31
|
+
/** mext.config.ts 의 mcp (§6 서버 선언 · 서버 발신 알림) */
|
|
32
|
+
export interface McpConfig {
|
|
33
|
+
/** serverInfo.name. 기본 "mext" */
|
|
34
|
+
name?: string;
|
|
35
|
+
/** initialize · server/discover 결과 instructions */
|
|
36
|
+
instructions?: string;
|
|
37
|
+
/** 서버 capabilities 에 병합. experimental 만 (예: { experimental: { "claude/channel": {} } }) */
|
|
38
|
+
capabilities?: McpCapabilities;
|
|
39
|
+
/**
|
|
40
|
+
* stdio 전용. 연결 하나에 1회 (2025 = notifications/initialized 후, 2026-07-28 = 연결 직후).
|
|
41
|
+
* 반환 함수 = 연결 종료 시 cleanup. HTTP 는 stateless 라 호출하지 않는다
|
|
42
|
+
*/
|
|
43
|
+
onConnect?: McpConnectHandler;
|
|
44
|
+
/**
|
|
45
|
+
* stdio 프로토콜. auto = 2026-07-28 · 2025 모두, legacy = 2025 만 (server/discover 거부).
|
|
46
|
+
* Claude Code channel 은 2025 연결에서만 알림을 전달. 기본 = experimental["claude/channel"] 선언 시 legacy, 그 외 auto
|
|
47
|
+
*/
|
|
48
|
+
stdioEra?: "auto" | "legacy";
|
|
49
|
+
}
|
|
50
|
+
export interface McpCapabilities {
|
|
51
|
+
experimental?: Record<string, object>;
|
|
52
|
+
}
|
|
53
|
+
/** stdio 연결. notify = 요청과 무관한 서버 발신 알림 */
|
|
54
|
+
export interface McpConnection {
|
|
55
|
+
/** 협상된 프로토콜 era */
|
|
56
|
+
readonly era: "legacy" | "modern";
|
|
57
|
+
/** 연결 종료 후 호출 = reject */
|
|
58
|
+
notify(method: string, params?: Record<string, unknown>): Promise<void>;
|
|
59
|
+
/**
|
|
60
|
+
* 클라이언트 발신 알림 수신 (예: notifications/claude/channel/permission_request).
|
|
61
|
+
* SDK 가 처리하는 사양 알림 (initialized · cancelled 등) 은 대상 아님. 같은 method 재등록 = 교체. 반환 = 해제
|
|
62
|
+
*/
|
|
63
|
+
onNotification(method: string, handler: (params: Record<string, unknown>) => void | Promise<void>): () => void;
|
|
64
|
+
}
|
|
65
|
+
export type McpConnectHandler = (conn: McpConnection) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>;
|
|
28
66
|
/** mext.config.ts 의 cli (§5 고객 CLI 설정) */
|
|
29
67
|
export interface CliConfig {
|
|
30
68
|
/** 실행 파일 이름 (kebab-case). 환경변수 접두 (MYAPP_TOKEN · MYAPP_URL) · 설정 폴더 (~/.config/<bin>) */
|
|
31
69
|
bin: string;
|
|
32
|
-
/** 서버 MCP 엔드포인트 (http(s)). 실행 시 <BIN>_URL 환경변수가 있으면 그
|
|
33
|
-
url
|
|
70
|
+
/** 서버 MCP 엔드포인트 (http(s)). 실행 시 <BIN>_URL 환경변수가 있으면 그 값. local 이면 없음 */
|
|
71
|
+
url?: string;
|
|
34
72
|
/** npm 패키지 이름. 기본 = bin */
|
|
35
73
|
packageName?: string;
|
|
36
74
|
/** 고객 CLI 버전 (--version · clientInfo). 기본 = 도입 프로젝트 package.json version */
|
|
37
75
|
version?: string;
|
|
38
76
|
/** true = 번들 형태 (내장 manifest, 기본), false = 범용 형태 (help · 스키마를 서버에서 조회) */
|
|
39
77
|
manifest?: boolean;
|
|
78
|
+
/**
|
|
79
|
+
* 오류 출력 · exit code 커스텀 모듈 (도입 프로젝트 루트 기준 경로). default export = CliErrorFormatter.
|
|
80
|
+
* 고객 CLI bin.js 에 번들, mext run (로컬) 이 import. @obov/mext 는 타입만 import (값 import = 번들 포함)
|
|
81
|
+
*/
|
|
82
|
+
errors?: string;
|
|
83
|
+
/**
|
|
84
|
+
* true = 로컬 bin (§5). mext build → .mext/bin.js (핸들러를 사용자 PC 에서 실행, .mext/cli/ 없음).
|
|
85
|
+
* 도입 프로젝트 package.json bin { "<bin>": ".mext/bin.js" }. url · packageName · manifest 와 함께 쓸 수 없음
|
|
86
|
+
*/
|
|
87
|
+
local?: boolean;
|
|
40
88
|
}
|
|
41
89
|
export declare const CONFIG_FILE = "mext.config.ts";
|
|
42
90
|
/** 고객 CLI 의 내장 명령 (루트 세그먼트로 쓸 수 없음) */
|
|
43
91
|
export declare const CLI_BUILTIN_COMMANDS: readonly string[];
|
|
92
|
+
/** 로컬 bin (cli.local) 의 내장 명령 */
|
|
93
|
+
export declare const LOCAL_BIN_BUILTIN_COMMANDS: readonly string[];
|
|
44
94
|
/** 타입 도우미. 값은 그대로 반환 */
|
|
45
95
|
export declare function defineConfig(config: MextConfig): MextConfig;
|
|
46
96
|
/** cwd 의 mext.config.ts 를 import. 없으면 {}. 형식이 틀리면 TypeError (모르는 키 포함) */
|
|
@@ -49,5 +99,20 @@ export declare function loadConfig(cwd?: string): Promise<MextConfig>;
|
|
|
49
99
|
export declare function resolveCommandsDir(cwd: string, config: MextConfig): string;
|
|
50
100
|
/** 로컬 실행 주체: config.localPrincipal (값 · 함수) 또는 localPrincipal() */
|
|
51
101
|
export declare function resolveLocalPrincipal(config: MextConfig): Promise<Principal>;
|
|
102
|
+
/** config.mcp 의 서버 선언 (모든 transport). onConnect 는 stdio 만 따로 전달 */
|
|
103
|
+
export declare function mcpDeclarations(config: MextConfig): {
|
|
104
|
+
name?: string;
|
|
105
|
+
instructions?: string;
|
|
106
|
+
capabilities?: McpCapabilities;
|
|
107
|
+
};
|
|
108
|
+
/** config.mcp 의 stdio 옵션 = 선언 + onConnect */
|
|
109
|
+
export declare function mcpStdioDeclarations(config: MextConfig): ReturnType<typeof mcpDeclarations> & {
|
|
110
|
+
onConnect?: McpConnectHandler;
|
|
111
|
+
era?: "auto" | "legacy";
|
|
112
|
+
};
|
|
113
|
+
/** cli.errors 절대 경로 (없으면 undefined) */
|
|
114
|
+
export declare function resolveCliErrorsPath(cwd: string, config: MextConfig): string | undefined;
|
|
115
|
+
/** cli.errors 모듈의 default export (함수). 파일 없음 · default 가 함수 아님 = TypeError */
|
|
116
|
+
export declare function loadCliErrorFormatter(cwd: string, config: MextConfig): Promise<CliErrorFormatter | undefined>;
|
|
52
117
|
/** ctx.app: config.app() 또는 {} */
|
|
53
118
|
export declare function createApp(config: MextConfig): Promise<MextApp>;
|
package/dist/config.js
CHANGED
|
@@ -12,10 +12,13 @@ import { pathToFileURL } from "node:url";
|
|
|
12
12
|
import { isSegment } from "./ids.js";
|
|
13
13
|
import { localPrincipal } from "./principal.js";
|
|
14
14
|
export const CONFIG_FILE = "mext.config.ts";
|
|
15
|
-
const CONFIG_KEYS = ["commandsDir", "app", "auth", "tools", "server", "oauth", "localPrincipal", "cli"];
|
|
16
|
-
const
|
|
15
|
+
const CONFIG_KEYS = ["commandsDir", "app", "auth", "tools", "server", "oauth", "localPrincipal", "cli", "mcp"];
|
|
16
|
+
const MCP_KEYS = ["name", "instructions", "capabilities", "onConnect", "stdioEra"];
|
|
17
|
+
const CLI_KEYS = ["bin", "url", "packageName", "version", "manifest", "errors", "local"];
|
|
17
18
|
/** 고객 CLI 의 내장 명령 (루트 세그먼트로 쓸 수 없음) */
|
|
18
19
|
export const CLI_BUILTIN_COMMANDS = ["help", "login", "logout", "mcp"];
|
|
20
|
+
/** 로컬 bin (cli.local) 의 내장 명령 */
|
|
21
|
+
export const LOCAL_BIN_BUILTIN_COMMANDS = ["help", "mcp"];
|
|
19
22
|
/** 타입 도우미. 값은 그대로 반환 */
|
|
20
23
|
export function defineConfig(config) {
|
|
21
24
|
return config;
|
|
@@ -39,6 +42,39 @@ export async function resolveLocalPrincipal(config) {
|
|
|
39
42
|
return localPrincipal();
|
|
40
43
|
return typeof p === "function" ? await p() : p;
|
|
41
44
|
}
|
|
45
|
+
/** config.mcp 의 서버 선언 (모든 transport). onConnect 는 stdio 만 따로 전달 */
|
|
46
|
+
export function mcpDeclarations(config) {
|
|
47
|
+
const mcp = config.mcp;
|
|
48
|
+
return {
|
|
49
|
+
...(mcp?.name !== undefined ? { name: mcp.name } : {}),
|
|
50
|
+
...(mcp?.instructions !== undefined ? { instructions: mcp.instructions } : {}),
|
|
51
|
+
...(mcp?.capabilities !== undefined ? { capabilities: mcp.capabilities } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** config.mcp 의 stdio 옵션 = 선언 + onConnect */
|
|
55
|
+
export function mcpStdioDeclarations(config) {
|
|
56
|
+
return {
|
|
57
|
+
...mcpDeclarations(config),
|
|
58
|
+
...(config.mcp?.onConnect ? { onConnect: config.mcp.onConnect } : {}),
|
|
59
|
+
...(config.mcp?.stdioEra ? { era: config.mcp.stdioEra } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** cli.errors 절대 경로 (없으면 undefined) */
|
|
63
|
+
export function resolveCliErrorsPath(cwd, config) {
|
|
64
|
+
return config.cli?.errors === undefined ? undefined : resolve(cwd, config.cli.errors);
|
|
65
|
+
}
|
|
66
|
+
/** cli.errors 모듈의 default export (함수). 파일 없음 · default 가 함수 아님 = TypeError */
|
|
67
|
+
export async function loadCliErrorFormatter(cwd, config) {
|
|
68
|
+
const file = resolveCliErrorsPath(cwd, config);
|
|
69
|
+
if (file === undefined)
|
|
70
|
+
return undefined;
|
|
71
|
+
if (!existsSync(file))
|
|
72
|
+
throw new TypeError(`${CONFIG_FILE}: cli.errors file not found: ${config.cli.errors}`);
|
|
73
|
+
const mod = (await import(__rewriteRelativeImportExtension(pathToFileURL(file).href)));
|
|
74
|
+
if (typeof mod.default !== "function")
|
|
75
|
+
throw new TypeError(`${config.cli.errors}: default export must be a function (CliErrorFormatter)`);
|
|
76
|
+
return mod.default;
|
|
77
|
+
}
|
|
42
78
|
/** ctx.app: config.app() 또는 {} */
|
|
43
79
|
export async function createApp(config) {
|
|
44
80
|
return config.app ? await config.app() : {};
|
|
@@ -76,8 +112,46 @@ function validateConfig(value) {
|
|
|
76
112
|
}
|
|
77
113
|
if (c.cli !== undefined)
|
|
78
114
|
validateCli(c.cli, fail);
|
|
115
|
+
if (c.mcp !== undefined)
|
|
116
|
+
validateMcp(c.mcp, fail);
|
|
79
117
|
return c;
|
|
80
118
|
}
|
|
119
|
+
function validateMcp(value, fail) {
|
|
120
|
+
if (!isPlainObject(value))
|
|
121
|
+
fail("mcp must be an object");
|
|
122
|
+
const mcp = value;
|
|
123
|
+
const unknown = Object.keys(mcp).filter((k) => !MCP_KEYS.includes(k));
|
|
124
|
+
if (unknown.length > 0)
|
|
125
|
+
fail(`unknown mcp key${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}`);
|
|
126
|
+
if (mcp.name !== undefined && (typeof mcp.name !== "string" || mcp.name.trim() === ""))
|
|
127
|
+
fail("mcp.name must be a non-empty string");
|
|
128
|
+
if (mcp.instructions !== undefined && typeof mcp.instructions !== "string")
|
|
129
|
+
fail("mcp.instructions must be a string");
|
|
130
|
+
if (mcp.onConnect !== undefined && typeof mcp.onConnect !== "function")
|
|
131
|
+
fail("mcp.onConnect must be a function");
|
|
132
|
+
if (mcp.stdioEra !== undefined && mcp.stdioEra !== "auto" && mcp.stdioEra !== "legacy")
|
|
133
|
+
fail('mcp.stdioEra must be "auto" or "legacy"');
|
|
134
|
+
if (mcp.capabilities !== undefined) {
|
|
135
|
+
if (!isPlainObject(mcp.capabilities))
|
|
136
|
+
fail("mcp.capabilities must be an object ({ experimental })");
|
|
137
|
+
const caps = mcp.capabilities;
|
|
138
|
+
const other = Object.keys(caps).filter((k) => k !== "experimental");
|
|
139
|
+
if (other.length > 0)
|
|
140
|
+
fail(`mcp.capabilities supports only experimental (got ${other.join(", ")})`);
|
|
141
|
+
const exp = caps.experimental;
|
|
142
|
+
if (exp !== undefined) {
|
|
143
|
+
if (!isPlainObject(exp))
|
|
144
|
+
fail("mcp.capabilities.experimental must be an object");
|
|
145
|
+
for (const [key, v] of Object.entries(exp)) {
|
|
146
|
+
if (!isPlainObject(v))
|
|
147
|
+
fail(`mcp.capabilities.experimental["${key}"] must be an object`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function isPlainObject(value) {
|
|
153
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
154
|
+
}
|
|
81
155
|
function validateCli(value, fail) {
|
|
82
156
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
83
157
|
fail("cli must be an object ({ bin, url })");
|
|
@@ -87,8 +161,16 @@ function validateCli(value, fail) {
|
|
|
87
161
|
fail(`unknown cli key${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}`);
|
|
88
162
|
if (typeof cli.bin !== "string" || !isSegment(cli.bin))
|
|
89
163
|
fail('cli.bin must be a kebab-case name (e.g. "myapp")');
|
|
90
|
-
if (typeof cli.
|
|
91
|
-
fail("cli.
|
|
164
|
+
if (cli.local !== undefined && typeof cli.local !== "boolean")
|
|
165
|
+
fail("cli.local must be a boolean");
|
|
166
|
+
if (cli.local === true) {
|
|
167
|
+
const remoteOnly = ["url", "packageName", "manifest"].filter((k) => cli[k] !== undefined);
|
|
168
|
+
if (remoteOnly.length > 0)
|
|
169
|
+
fail(`cli.local cannot be combined with ${remoteOnly.map((k) => `cli.${k}`).join(", ")} (local bin runs handlers, no server)`);
|
|
170
|
+
}
|
|
171
|
+
else if (typeof cli.url !== "string" || !isHttpUrl(cli.url)) {
|
|
172
|
+
fail("cli.url must be an http(s) URL of the MCP endpoint (or set cli.local: true for a local bin)");
|
|
173
|
+
}
|
|
92
174
|
if (cli.packageName !== undefined && (typeof cli.packageName !== "string" || !NPM_NAME.test(cli.packageName))) {
|
|
93
175
|
fail('cli.packageName must be an npm package name (e.g. "@example/myapp-cli")');
|
|
94
176
|
}
|
|
@@ -97,6 +179,9 @@ function validateCli(value, fail) {
|
|
|
97
179
|
}
|
|
98
180
|
if (cli.manifest !== undefined && typeof cli.manifest !== "boolean")
|
|
99
181
|
fail("cli.manifest must be a boolean");
|
|
182
|
+
if (cli.errors !== undefined && (typeof cli.errors !== "string" || cli.errors.trim() === "")) {
|
|
183
|
+
fail('cli.errors must be a module path (e.g. "./cli-errors.ts")');
|
|
184
|
+
}
|
|
100
185
|
}
|
|
101
186
|
/** npm 패키지 이름 (소문자 · scope 선택) */
|
|
102
187
|
const NPM_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/;
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\"\nimport { join, resolve } from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\nimport type { MextApp } from \"./command.ts\"\nimport { isSegment } from \"./ids.ts\"\nimport type { OAuthResourceOptions } from \"./mcp/http.ts\"\nimport { localPrincipal, type Principal } from \"./principal.ts\"\n\n/** mext.config.ts 의 default export */\nexport interface MextConfig {\n /** 명령어 폴더. 설정 파일 위치 (cwd) 기준. 기본 \"commands\" */\n commandsDir?: string\n /** ctx.app 팩토리. 실행 프로세스당 1회 */\n app?: () => MextApp | Promise<MextApp>\n /** HTTP 전용. 요청마다 Authorization 헤더로 주체 생성. 잘못된 토큰 = unauthenticated() throw */\n auth?: (req: { authorization: string | undefined }) => Principal | Promise<Principal>\n /** MCP 노출 방식. 기본 \"execute\" */\n tools?: \"execute\" | \"split\"\n server?: { host?: string; port?: number; allowedOrigins?: string[] }\n /** HTTP 전용. OAuth 보호 자원 메타데이터 (인가 서버 위치). mext login · MCP 클라이언트의 discovery */\n oauth?: OAuthResourceOptions\n /** 로컬 실행 (mext run · stdio) 주체. 기본 localPrincipal() */\n localPrincipal?: Principal | (() => Principal | Promise<Principal>)\n /** 고객 CLI (mext build → .mext/cli/). 없으면 고객 CLI 를 만들지 않는다 */\n cli?: CliConfig\n}\n\n/** mext.config.ts 의 cli (§5 고객 CLI 설정) */\nexport interface CliConfig {\n /** 실행 파일 이름 (kebab-case). 환경변수 접두 (MYAPP_TOKEN · MYAPP_URL) · 설정 폴더 (~/.config/<bin>) */\n bin: string\n /** 서버 MCP 엔드포인트 (http(s)). 실행 시 <BIN>_URL 환경변수가 있으면 그 값 */\n url: string\n /** npm 패키지 이름. 기본 = bin */\n packageName?: string\n /** 고객 CLI 버전 (--version · clientInfo). 기본 = 도입 프로젝트 package.json version */\n version?: string\n /** true = 번들 형태 (내장 manifest, 기본), false = 범용 형태 (help · 스키마를 서버에서 조회) */\n manifest?: boolean\n}\n\nexport const CONFIG_FILE = \"mext.config.ts\"\n\nconst CONFIG_KEYS: readonly string[] = [\"commandsDir\", \"app\", \"auth\", \"tools\", \"server\", \"oauth\", \"localPrincipal\", \"cli\"]\nconst CLI_KEYS: readonly string[] = [\"bin\", \"url\", \"packageName\", \"version\", \"manifest\"]\n/** 고객 CLI 의 내장 명령 (루트 세그먼트로 쓸 수 없음) */\nexport const CLI_BUILTIN_COMMANDS: readonly string[] = [\"help\", \"login\", \"logout\", \"mcp\"]\n\n/** 타입 도우미. 값은 그대로 반환 */\nexport function defineConfig(config: MextConfig): MextConfig {\n return config\n}\n\n/** cwd 의 mext.config.ts 를 import. 없으면 {}. 형식이 틀리면 TypeError (모르는 키 포함) */\nexport async function loadConfig(cwd: string = process.cwd()): Promise<MextConfig> {\n const file = join(resolve(cwd), CONFIG_FILE)\n if (!existsSync(file)) return {}\n const mod = (await import(pathToFileURL(file).href)) as { default?: unknown }\n return validateConfig(mod.default)\n}\n\n/** commands/ 절대 경로 */\nexport function resolveCommandsDir(cwd: string, config: MextConfig): string {\n return resolve(cwd, config.commandsDir ?? \"commands\")\n}\n\n/** 로컬 실행 주체: config.localPrincipal (값 · 함수) 또는 localPrincipal() */\nexport async function resolveLocalPrincipal(config: MextConfig): Promise<Principal> {\n const p = config.localPrincipal\n if (p === undefined) return localPrincipal()\n return typeof p === \"function\" ? await p() : p\n}\n\n/** ctx.app: config.app() 또는 {} */\nexport async function createApp(config: MextConfig): Promise<MextApp> {\n return config.app ? await config.app() : ({} as MextApp)\n}\n\nfunction validateConfig(value: unknown): MextConfig {\n const fail = (message: string): never => {\n throw new TypeError(`${CONFIG_FILE}: ${message}`)\n }\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return fail(\"default export must be an object (export default defineConfig({ ... }))\")\n }\n const c = value as Record<string, unknown>\n const unknown = Object.keys(c).filter((k) => !CONFIG_KEYS.includes(k))\n if (unknown.length > 0) fail(`unknown key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`)\n if (c.commandsDir !== undefined && typeof c.commandsDir !== \"string\") fail(\"commandsDir must be a string\")\n if (c.app !== undefined && typeof c.app !== \"function\") fail(\"app must be a function\")\n if (c.auth !== undefined && typeof c.auth !== \"function\") fail(\"auth must be a function\")\n if (c.tools !== undefined && c.tools !== \"execute\" && c.tools !== \"split\") fail('tools must be \"execute\" or \"split\"')\n if (c.server !== undefined && (typeof c.server !== \"object\" || c.server === null)) fail(\"server must be an object\")\n if (c.oauth !== undefined) {\n const o = c.oauth as { authorizationServers?: unknown } | null\n if (typeof o !== \"object\" || o === null || !Array.isArray(o.authorizationServers) || o.authorizationServers.length === 0) {\n fail(\"oauth.authorizationServers must be a non-empty array of URLs\")\n }\n }\n const lp = c.localPrincipal\n if (lp !== undefined && typeof lp !== \"function\" && !isPrincipal(lp)) {\n fail(\"localPrincipal must be a Principal ({ id, kind, scopes }) or a function\")\n }\n if (c.cli !== undefined) validateCli(c.cli, fail)\n return c as MextConfig\n}\n\nfunction validateCli(value: unknown, fail: (message: string) => never): void {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) fail(\"cli must be an object ({ bin, url })\")\n const cli = value as Record<string, unknown>\n const unknown = Object.keys(cli).filter((k) => !CLI_KEYS.includes(k))\n if (unknown.length > 0) fail(`unknown cli key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`)\n if (typeof cli.bin !== \"string\" || !isSegment(cli.bin)) fail('cli.bin must be a kebab-case name (e.g. \"myapp\")')\n if (typeof cli.url !== \"string\" || !isHttpUrl(cli.url)) fail(\"cli.url must be an http(s) URL of the MCP endpoint\")\n if (cli.packageName !== undefined && (typeof cli.packageName !== \"string\" || !NPM_NAME.test(cli.packageName))) {\n fail('cli.packageName must be an npm package name (e.g. \"@example/myapp-cli\")')\n }\n if (cli.version !== undefined && (typeof cli.version !== \"string\" || cli.version.trim() === \"\")) {\n fail(\"cli.version must be a non-empty string\")\n }\n if (cli.manifest !== undefined && typeof cli.manifest !== \"boolean\") fail(\"cli.manifest must be a boolean\")\n}\n\n/** npm 패키지 이름 (소문자 · scope 선택) */\nconst NPM_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\\/)?[a-z0-9][a-z0-9._~-]*$/\n\nfunction isHttpUrl(value: string): boolean {\n if (!/^https?:\\/\\//i.test(value)) return false\n try {\n new URL(value)\n return true\n } catch {\n return false\n }\n}\n\nfunction isPrincipal(value: unknown): value is Principal {\n const p = value as Partial<Principal> | null\n return (\n typeof p === \"object\" &&\n p !== null &&\n typeof p.id === \"string\" &&\n (p.kind === \"local\" || p.kind === \"token\" || p.kind === \"anonymous\") &&\n Array.isArray(p.scopes)\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\"\nimport { join, resolve } from \"node:path\"\nimport { pathToFileURL } from \"node:url\"\nimport type { CliErrorFormatter } from \"./cli/output.ts\"\nimport type { MextApp } from \"./command.ts\"\nimport { isSegment } from \"./ids.ts\"\nimport type { OAuthResourceOptions } from \"./mcp/http.ts\"\nimport { localPrincipal, type Principal } from \"./principal.ts\"\n\n/** mext.config.ts 의 default export */\nexport interface MextConfig {\n /** 명령어 폴더. 설정 파일 위치 (cwd) 기준. 기본 \"commands\" */\n commandsDir?: string\n /** ctx.app 팩토리. 실행 프로세스당 1회 */\n app?: () => MextApp | Promise<MextApp>\n /** HTTP 전용. 요청마다 Authorization 헤더로 주체 생성. 잘못된 토큰 = unauthenticated() throw */\n auth?: (req: { authorization: string | undefined }) => Principal | Promise<Principal>\n /** MCP 노출 방식. 기본 \"execute\" */\n tools?: \"execute\" | \"split\"\n server?: { host?: string; port?: number; allowedOrigins?: string[] }\n /** HTTP 전용. OAuth 보호 자원 메타데이터 (인가 서버 위치). mext login · MCP 클라이언트의 discovery */\n oauth?: OAuthResourceOptions\n /** 로컬 실행 (mext run · stdio) 주체. 기본 localPrincipal() */\n localPrincipal?: Principal | (() => Principal | Promise<Principal>)\n /** 고객 CLI (mext build → .mext/cli/). 없으면 고객 CLI 를 만들지 않는다 */\n cli?: CliConfig\n /** MCP 서버 선언 (serverInfo.name · instructions · capabilities) · stdio 서버 발신 알림 (§6) */\n mcp?: McpConfig\n}\n\n/** mext.config.ts 의 mcp (§6 서버 선언 · 서버 발신 알림) */\nexport interface McpConfig {\n /** serverInfo.name. 기본 \"mext\" */\n name?: string\n /** initialize · server/discover 결과 instructions */\n instructions?: string\n /** 서버 capabilities 에 병합. experimental 만 (예: { experimental: { \"claude/channel\": {} } }) */\n capabilities?: McpCapabilities\n /**\n * stdio 전용. 연결 하나에 1회 (2025 = notifications/initialized 후, 2026-07-28 = 연결 직후).\n * 반환 함수 = 연결 종료 시 cleanup. HTTP 는 stateless 라 호출하지 않는다\n */\n onConnect?: McpConnectHandler\n /**\n * stdio 프로토콜. auto = 2026-07-28 · 2025 모두, legacy = 2025 만 (server/discover 거부).\n * Claude Code channel 은 2025 연결에서만 알림을 전달. 기본 = experimental[\"claude/channel\"] 선언 시 legacy, 그 외 auto\n */\n stdioEra?: \"auto\" | \"legacy\"\n}\n\nexport interface McpCapabilities {\n experimental?: Record<string, object>\n}\n\n/** stdio 연결. notify = 요청과 무관한 서버 발신 알림 */\nexport interface McpConnection {\n /** 협상된 프로토콜 era */\n readonly era: \"legacy\" | \"modern\"\n /** 연결 종료 후 호출 = reject */\n notify(method: string, params?: Record<string, unknown>): Promise<void>\n /**\n * 클라이언트 발신 알림 수신 (예: notifications/claude/channel/permission_request).\n * SDK 가 처리하는 사양 알림 (initialized · cancelled 등) 은 대상 아님. 같은 method 재등록 = 교체. 반환 = 해제\n */\n onNotification(method: string, handler: (params: Record<string, unknown>) => void | Promise<void>): () => void\n}\n\nexport type McpConnectHandler = (\n conn: McpConnection,\n) => void | (() => void | Promise<void>) | Promise<void | (() => void | Promise<void>)>\n\n/** mext.config.ts 의 cli (§5 고객 CLI 설정) */\nexport interface CliConfig {\n /** 실행 파일 이름 (kebab-case). 환경변수 접두 (MYAPP_TOKEN · MYAPP_URL) · 설정 폴더 (~/.config/<bin>) */\n bin: string\n /** 서버 MCP 엔드포인트 (http(s)). 실행 시 <BIN>_URL 환경변수가 있으면 그 값. local 이면 없음 */\n url?: string\n /** npm 패키지 이름. 기본 = bin */\n packageName?: string\n /** 고객 CLI 버전 (--version · clientInfo). 기본 = 도입 프로젝트 package.json version */\n version?: string\n /** true = 번들 형태 (내장 manifest, 기본), false = 범용 형태 (help · 스키마를 서버에서 조회) */\n manifest?: boolean\n /**\n * 오류 출력 · exit code 커스텀 모듈 (도입 프로젝트 루트 기준 경로). default export = CliErrorFormatter.\n * 고객 CLI bin.js 에 번들, mext run (로컬) 이 import. @obov/mext 는 타입만 import (값 import = 번들 포함)\n */\n errors?: string\n /**\n * true = 로컬 bin (§5). mext build → .mext/bin.js (핸들러를 사용자 PC 에서 실행, .mext/cli/ 없음).\n * 도입 프로젝트 package.json bin { \"<bin>\": \".mext/bin.js\" }. url · packageName · manifest 와 함께 쓸 수 없음\n */\n local?: boolean\n}\n\nexport const CONFIG_FILE = \"mext.config.ts\"\n\nconst CONFIG_KEYS: readonly string[] = [\"commandsDir\", \"app\", \"auth\", \"tools\", \"server\", \"oauth\", \"localPrincipal\", \"cli\", \"mcp\"]\nconst MCP_KEYS: readonly string[] = [\"name\", \"instructions\", \"capabilities\", \"onConnect\", \"stdioEra\"]\nconst CLI_KEYS: readonly string[] = [\"bin\", \"url\", \"packageName\", \"version\", \"manifest\", \"errors\", \"local\"]\n/** 고객 CLI 의 내장 명령 (루트 세그먼트로 쓸 수 없음) */\nexport const CLI_BUILTIN_COMMANDS: readonly string[] = [\"help\", \"login\", \"logout\", \"mcp\"]\n/** 로컬 bin (cli.local) 의 내장 명령 */\nexport const LOCAL_BIN_BUILTIN_COMMANDS: readonly string[] = [\"help\", \"mcp\"]\n\n/** 타입 도우미. 값은 그대로 반환 */\nexport function defineConfig(config: MextConfig): MextConfig {\n return config\n}\n\n/** cwd 의 mext.config.ts 를 import. 없으면 {}. 형식이 틀리면 TypeError (모르는 키 포함) */\nexport async function loadConfig(cwd: string = process.cwd()): Promise<MextConfig> {\n const file = join(resolve(cwd), CONFIG_FILE)\n if (!existsSync(file)) return {}\n const mod = (await import(pathToFileURL(file).href)) as { default?: unknown }\n return validateConfig(mod.default)\n}\n\n/** commands/ 절대 경로 */\nexport function resolveCommandsDir(cwd: string, config: MextConfig): string {\n return resolve(cwd, config.commandsDir ?? \"commands\")\n}\n\n/** 로컬 실행 주체: config.localPrincipal (값 · 함수) 또는 localPrincipal() */\nexport async function resolveLocalPrincipal(config: MextConfig): Promise<Principal> {\n const p = config.localPrincipal\n if (p === undefined) return localPrincipal()\n return typeof p === \"function\" ? await p() : p\n}\n\n/** config.mcp 의 서버 선언 (모든 transport). onConnect 는 stdio 만 따로 전달 */\nexport function mcpDeclarations(config: MextConfig): { name?: string; instructions?: string; capabilities?: McpCapabilities } {\n const mcp = config.mcp\n return {\n ...(mcp?.name !== undefined ? { name: mcp.name } : {}),\n ...(mcp?.instructions !== undefined ? { instructions: mcp.instructions } : {}),\n ...(mcp?.capabilities !== undefined ? { capabilities: mcp.capabilities } : {}),\n }\n}\n\n/** config.mcp 의 stdio 옵션 = 선언 + onConnect */\nexport function mcpStdioDeclarations(\n config: MextConfig,\n): ReturnType<typeof mcpDeclarations> & { onConnect?: McpConnectHandler; era?: \"auto\" | \"legacy\" } {\n return {\n ...mcpDeclarations(config),\n ...(config.mcp?.onConnect ? { onConnect: config.mcp.onConnect } : {}),\n ...(config.mcp?.stdioEra ? { era: config.mcp.stdioEra } : {}),\n }\n}\n\n/** cli.errors 절대 경로 (없으면 undefined) */\nexport function resolveCliErrorsPath(cwd: string, config: MextConfig): string | undefined {\n return config.cli?.errors === undefined ? undefined : resolve(cwd, config.cli.errors)\n}\n\n/** cli.errors 모듈의 default export (함수). 파일 없음 · default 가 함수 아님 = TypeError */\nexport async function loadCliErrorFormatter(cwd: string, config: MextConfig): Promise<CliErrorFormatter | undefined> {\n const file = resolveCliErrorsPath(cwd, config)\n if (file === undefined) return undefined\n if (!existsSync(file)) throw new TypeError(`${CONFIG_FILE}: cli.errors file not found: ${config.cli!.errors}`)\n const mod = (await import(pathToFileURL(file).href)) as { default?: unknown }\n if (typeof mod.default !== \"function\") throw new TypeError(`${config.cli!.errors}: default export must be a function (CliErrorFormatter)`)\n return mod.default as CliErrorFormatter\n}\n\n/** ctx.app: config.app() 또는 {} */\nexport async function createApp(config: MextConfig): Promise<MextApp> {\n return config.app ? await config.app() : ({} as MextApp)\n}\n\nfunction validateConfig(value: unknown): MextConfig {\n const fail = (message: string): never => {\n throw new TypeError(`${CONFIG_FILE}: ${message}`)\n }\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n return fail(\"default export must be an object (export default defineConfig({ ... }))\")\n }\n const c = value as Record<string, unknown>\n const unknown = Object.keys(c).filter((k) => !CONFIG_KEYS.includes(k))\n if (unknown.length > 0) fail(`unknown key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`)\n if (c.commandsDir !== undefined && typeof c.commandsDir !== \"string\") fail(\"commandsDir must be a string\")\n if (c.app !== undefined && typeof c.app !== \"function\") fail(\"app must be a function\")\n if (c.auth !== undefined && typeof c.auth !== \"function\") fail(\"auth must be a function\")\n if (c.tools !== undefined && c.tools !== \"execute\" && c.tools !== \"split\") fail('tools must be \"execute\" or \"split\"')\n if (c.server !== undefined && (typeof c.server !== \"object\" || c.server === null)) fail(\"server must be an object\")\n if (c.oauth !== undefined) {\n const o = c.oauth as { authorizationServers?: unknown } | null\n if (typeof o !== \"object\" || o === null || !Array.isArray(o.authorizationServers) || o.authorizationServers.length === 0) {\n fail(\"oauth.authorizationServers must be a non-empty array of URLs\")\n }\n }\n const lp = c.localPrincipal\n if (lp !== undefined && typeof lp !== \"function\" && !isPrincipal(lp)) {\n fail(\"localPrincipal must be a Principal ({ id, kind, scopes }) or a function\")\n }\n if (c.cli !== undefined) validateCli(c.cli, fail)\n if (c.mcp !== undefined) validateMcp(c.mcp, fail)\n return c as MextConfig\n}\n\nfunction validateMcp(value: unknown, fail: (message: string) => never): void {\n if (!isPlainObject(value)) fail(\"mcp must be an object\")\n const mcp = value as Record<string, unknown>\n const unknown = Object.keys(mcp).filter((k) => !MCP_KEYS.includes(k))\n if (unknown.length > 0) fail(`unknown mcp key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`)\n if (mcp.name !== undefined && (typeof mcp.name !== \"string\" || mcp.name.trim() === \"\")) fail(\"mcp.name must be a non-empty string\")\n if (mcp.instructions !== undefined && typeof mcp.instructions !== \"string\") fail(\"mcp.instructions must be a string\")\n if (mcp.onConnect !== undefined && typeof mcp.onConnect !== \"function\") fail(\"mcp.onConnect must be a function\")\n if (mcp.stdioEra !== undefined && mcp.stdioEra !== \"auto\" && mcp.stdioEra !== \"legacy\") fail('mcp.stdioEra must be \"auto\" or \"legacy\"')\n if (mcp.capabilities !== undefined) {\n if (!isPlainObject(mcp.capabilities)) fail(\"mcp.capabilities must be an object ({ experimental })\")\n const caps = mcp.capabilities as Record<string, unknown>\n const other = Object.keys(caps).filter((k) => k !== \"experimental\")\n if (other.length > 0) fail(`mcp.capabilities supports only experimental (got ${other.join(\", \")})`)\n const exp = caps.experimental\n if (exp !== undefined) {\n if (!isPlainObject(exp)) fail(\"mcp.capabilities.experimental must be an object\")\n for (const [key, v] of Object.entries(exp as Record<string, unknown>)) {\n if (!isPlainObject(v)) fail(`mcp.capabilities.experimental[\"${key}\"] must be an object`)\n }\n }\n }\n}\n\nfunction isPlainObject(value: unknown): boolean {\n return typeof value === \"object\" && value !== null && !Array.isArray(value)\n}\n\nfunction validateCli(value: unknown, fail: (message: string) => never): void {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) fail(\"cli must be an object ({ bin, url })\")\n const cli = value as Record<string, unknown>\n const unknown = Object.keys(cli).filter((k) => !CLI_KEYS.includes(k))\n if (unknown.length > 0) fail(`unknown cli key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`)\n if (typeof cli.bin !== \"string\" || !isSegment(cli.bin)) fail('cli.bin must be a kebab-case name (e.g. \"myapp\")')\n if (cli.local !== undefined && typeof cli.local !== \"boolean\") fail(\"cli.local must be a boolean\")\n if (cli.local === true) {\n const remoteOnly = [\"url\", \"packageName\", \"manifest\"].filter((k) => cli[k] !== undefined)\n if (remoteOnly.length > 0) fail(`cli.local cannot be combined with ${remoteOnly.map((k) => `cli.${k}`).join(\", \")} (local bin runs handlers, no server)`)\n } else if (typeof cli.url !== \"string\" || !isHttpUrl(cli.url)) {\n fail(\"cli.url must be an http(s) URL of the MCP endpoint (or set cli.local: true for a local bin)\")\n }\n if (cli.packageName !== undefined && (typeof cli.packageName !== \"string\" || !NPM_NAME.test(cli.packageName))) {\n fail('cli.packageName must be an npm package name (e.g. \"@example/myapp-cli\")')\n }\n if (cli.version !== undefined && (typeof cli.version !== \"string\" || cli.version.trim() === \"\")) {\n fail(\"cli.version must be a non-empty string\")\n }\n if (cli.manifest !== undefined && typeof cli.manifest !== \"boolean\") fail(\"cli.manifest must be a boolean\")\n if (cli.errors !== undefined && (typeof cli.errors !== \"string\" || cli.errors.trim() === \"\")) {\n fail('cli.errors must be a module path (e.g. \"./cli-errors.ts\")')\n }\n}\n\n/** npm 패키지 이름 (소문자 · scope 선택) */\nconst NPM_NAME = /^(?:@[a-z0-9][a-z0-9._~-]*\\/)?[a-z0-9][a-z0-9._~-]*$/\n\nfunction isHttpUrl(value: string): boolean {\n if (!/^https?:\\/\\//i.test(value)) return false\n try {\n new URL(value)\n return true\n } catch {\n return false\n }\n}\n\nfunction isPrincipal(value: unknown): value is Principal {\n const p = value as Partial<Principal> | null\n return (\n typeof p === \"object\" &&\n p !== null &&\n typeof p.id === \"string\" &&\n (p.kind === \"local\" || p.kind === \"token\" || p.kind === \"anonymous\") &&\n Array.isArray(p.scopes)\n )\n}\n"]}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type BuildIssueCode = "COMMANDS_DIR_MISSING" | "PATH_COLLISION" | "DUPLICATE_ID" | "INVALID_SEGMENT" | "INVALID_ALIAS" | "RESERVED_ID" | "ROOT_INDEX" | "INVALID_COMMAND" | "LOAD_FAILED" | "SCHEMA_UNREPRESENTABLE" | "CLI_UNCONVERTIBLE" | "BUNDLE_FAILED";
|
|
1
|
+
export type BuildIssueCode = "COMMANDS_DIR_MISSING" | "PATH_COLLISION" | "DUPLICATE_ID" | "INVALID_SEGMENT" | "INVALID_ALIAS" | "RESERVED_ID" | "ROOT_INDEX" | "INVALID_COMMAND" | "LOAD_FAILED" | "SCHEMA_UNREPRESENTABLE" | "CLI_UNCONVERTIBLE" | "BUNDLE_FAILED" | "INVALID_CONFIG";
|
|
2
2
|
export interface BuildIssue {
|
|
3
3
|
code: BuildIssueCode;
|
|
4
4
|
message: string;
|
|
@@ -72,3 +72,21 @@ export declare function isMextError<C extends MextErrorCode>(error: unknown, cod
|
|
|
72
72
|
export declare function unauthenticated(reason: MextErrorDetails["UNAUTHENTICATED"]["reason"]): MextError<"UNAUTHENTICATED">;
|
|
73
73
|
/** ctx.ask 거절. AskHandler 가 input 요청 거절 · 취소에 던진다. invoke 는 requiresConfirmation 확인 거절에 던진다 */
|
|
74
74
|
export declare function inputDeclined(command: string, kind: "confirm" | "input"): MextError<"INPUT_DECLINED">;
|
|
75
|
+
/** mext 가 쓰는 오류 code. CommandError 로 만들 수 없다 */
|
|
76
|
+
export declare const RESERVED_ERROR_CODES: readonly string[];
|
|
77
|
+
/**
|
|
78
|
+
* 핸들러 정의 오류 (§4). 응답 본문 = { code, message, ...details } (MCP structuredContent.error · CLI --json).
|
|
79
|
+
* code = 대문자 스네이크, mext 예약 code 불가 (TypeError). onCommandError 에 보고하지 않는다 (의도된 오류)
|
|
80
|
+
*/
|
|
81
|
+
export declare class CommandError extends Error {
|
|
82
|
+
readonly code: string;
|
|
83
|
+
readonly details: Record<string, unknown>;
|
|
84
|
+
constructor(code: string, message: string, details?: Record<string, unknown>);
|
|
85
|
+
toJSON(): {
|
|
86
|
+
code: string;
|
|
87
|
+
message: string;
|
|
88
|
+
} & Record<string, unknown>;
|
|
89
|
+
}
|
|
90
|
+
export declare function isCommandError(error: unknown): error is CommandError;
|
|
91
|
+
/** 응답 본문으로 그대로 보내는 오류 (MextError · CommandError) */
|
|
92
|
+
export declare function isDeclaredError(error: unknown): error is MextError | CommandError;
|
package/dist/errors.js
CHANGED
|
@@ -35,4 +35,51 @@ export function inputDeclined(command, kind) {
|
|
|
35
35
|
const message = kind === "confirm" ? `${command} was not confirmed and did not run.` : `${command} requested input, but it was declined.`;
|
|
36
36
|
return new MextError("INPUT_DECLINED", message, { command, kind });
|
|
37
37
|
}
|
|
38
|
+
/** mext 가 쓰는 오류 code. CommandError 로 만들 수 없다 */
|
|
39
|
+
export const RESERVED_ERROR_CODES = [
|
|
40
|
+
"UNKNOWN_COMMAND",
|
|
41
|
+
"INVALID_ARGUMENT",
|
|
42
|
+
"UNAUTHENTICATED",
|
|
43
|
+
"PERMISSION_DENIED",
|
|
44
|
+
"INPUT_UNAVAILABLE",
|
|
45
|
+
"INPUT_DECLINED",
|
|
46
|
+
"EXECUTION_ERROR",
|
|
47
|
+
"EXECUTION_FAILED",
|
|
48
|
+
"INTERRUPTED",
|
|
49
|
+
"BUILD_ERROR",
|
|
50
|
+
"REMOTE_ERROR",
|
|
51
|
+
];
|
|
52
|
+
const ERROR_CODE = /^[A-Z][A-Z0-9_]*$/;
|
|
53
|
+
/**
|
|
54
|
+
* 핸들러 정의 오류 (§4). 응답 본문 = { code, message, ...details } (MCP structuredContent.error · CLI --json).
|
|
55
|
+
* code = 대문자 스네이크, mext 예약 code 불가 (TypeError). onCommandError 에 보고하지 않는다 (의도된 오류)
|
|
56
|
+
*/
|
|
57
|
+
export class CommandError extends Error {
|
|
58
|
+
code;
|
|
59
|
+
details;
|
|
60
|
+
constructor(code, message, details = {}) {
|
|
61
|
+
if (typeof code !== "string" || !ERROR_CODE.test(code)) {
|
|
62
|
+
throw new TypeError(`CommandError code must be UPPER_SNAKE_CASE (got ${JSON.stringify(code)})`);
|
|
63
|
+
}
|
|
64
|
+
if (RESERVED_ERROR_CODES.includes(code))
|
|
65
|
+
throw new TypeError(`CommandError code "${code}" is reserved by mext`);
|
|
66
|
+
if (typeof details !== "object" || details === null || Array.isArray(details)) {
|
|
67
|
+
throw new TypeError("CommandError details must be an object");
|
|
68
|
+
}
|
|
69
|
+
super(message);
|
|
70
|
+
this.name = "CommandError";
|
|
71
|
+
this.code = code;
|
|
72
|
+
this.details = details;
|
|
73
|
+
}
|
|
74
|
+
toJSON() {
|
|
75
|
+
return { ...this.details, code: this.code, message: this.message };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export function isCommandError(error) {
|
|
79
|
+
return error instanceof CommandError;
|
|
80
|
+
}
|
|
81
|
+
/** 응답 본문으로 그대로 보내는 오류 (MextError · CommandError) */
|
|
82
|
+
export function isDeclaredError(error) {
|
|
83
|
+
return error instanceof MextError || error instanceof CommandError;
|
|
84
|
+
}
|
|
38
85
|
//# sourceMappingURL=errors.js.map
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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","sourcesContent":["export type BuildIssueCode =\n | \"COMMANDS_DIR_MISSING\"\n | \"PATH_COLLISION\"\n | \"DUPLICATE_ID\"\n | \"INVALID_SEGMENT\"\n | \"INVALID_ALIAS\"\n | \"RESERVED_ID\"\n | \"ROOT_INDEX\"\n | \"INVALID_COMMAND\"\n | \"LOAD_FAILED\"\n | \"SCHEMA_UNREPRESENTABLE\"\n | \"CLI_UNCONVERTIBLE\"\n | \"BUNDLE_FAILED\"\n | \"INVALID_CONFIG\"\n\nexport interface BuildIssue {\n code: BuildIssueCode\n message: string\n /** commands/ 기준 상대 경로 (posix) */\n file?: string\n id?: string\n}\n\n/** 스캔 · Registry 생성의 빌드 오류. 발견한 문제를 모두 모아 한 번에 던진다 */\nexport class MextBuildError extends Error {\n readonly issues: readonly BuildIssue[]\n\n constructor(issues: readonly BuildIssue[]) {\n super(\n `mext build failed (${issues.length} issue${issues.length === 1 ? \"\" : \"s\"})\\n` +\n issues.map((i) => ` ${i.code}${i.file ? ` ${i.file}` : \"\"}: ${i.message}`).join(\"\\n\"),\n )\n this.name = \"MextBuildError\"\n this.issues = issues\n }\n}\n\nexport type MextErrorCode =\n | \"UNKNOWN_COMMAND\"\n | \"INVALID_ARGUMENT\"\n | \"UNAUTHENTICATED\"\n | \"PERMISSION_DENIED\"\n | \"INPUT_UNAVAILABLE\"\n | \"INPUT_DECLINED\"\n\nexport interface ArgumentIssue {\n /** 점 구분 경로. \"\" = 입력 전체 */\n path: string\n code: string\n message: string\n}\n\nexport interface MextErrorDetails {\n UNKNOWN_COMMAND: {\n command: string\n /** 유사 정식 ID (가까운 순) */\n suggested: string[]\n /** 다음에 볼 help 대상. \"help\" (루트) · \"help users\" (네임스페이스) */\n usage: string\n }\n INVALID_ARGUMENT: {\n command: string\n /** 원본 입력에 없는 필수 필드 경로 */\n missing_fields: string[]\n issues: ArgumentIssue[]\n /** input JSON Schema 2020-12 */\n expected_schema: Record<string, unknown>\n examples: unknown[]\n }\n UNAUTHENTICATED: {\n reason: \"missing_token\" | \"invalid_token\"\n /** CLI 원격 실행: 토큰 발급 명령 (mext login --url <url>) */\n login?: string\n }\n PERMISSION_DENIED: {\n command: string\n /** anonymous = 인증 없는 주체의 readOnly 외 명령어 · read_only = readOnly 전용 호출 (split query) · scope = scopes 부족 */\n reason: \"anonymous\" | \"read_only\" | \"scope\"\n required_scopes: string[]\n held_scopes: string[]\n }\n /** ctx.ask 를 처리할 수 없는 실행 환경 (TTY 없는 CLI 등) */\n INPUT_UNAVAILABLE: {\n command: string\n }\n /** ctx.ask 거절 · 취소. confirm = requiresConfirmation 확인 거절 (실행 안 함), input = 입력 요청 거절 */\n INPUT_DECLINED: {\n command: string\n kind: \"confirm\" | \"input\"\n }\n}\n\n/** 구조화 오류. 어댑터는 code 로 분기하고 toJSON() 을 응답 본문으로 쓴다 */\nexport class MextError<C extends MextErrorCode = MextErrorCode> extends Error {\n readonly code: C\n readonly details: MextErrorDetails[C]\n\n constructor(code: C, message: string, details: MextErrorDetails[C]) {\n super(message)\n this.name = \"MextError\"\n this.code = code\n this.details = details\n }\n\n toJSON(): { code: C; message: string } & MextErrorDetails[C] {\n return { code: this.code, message: this.message, ...this.details }\n }\n}\n\nexport function isMextError<C extends MextErrorCode>(error: unknown, code?: C): error is MextError<C> {\n return error instanceof MextError && (code === undefined || error.code === code)\n}\n\n/** auth 단계에서 어댑터가 던지는 오류. anonymous 로 낮추지 않는다 */\nexport function unauthenticated(reason: MextErrorDetails[\"UNAUTHENTICATED\"][\"reason\"]): MextError<\"UNAUTHENTICATED\"> {\n const message = reason === \"missing_token\" ? \"Authentication required.\" : \"Invalid authentication token.\"\n return new MextError(\"UNAUTHENTICATED\", message, { reason })\n}\n\n/** ctx.ask 거절. AskHandler 가 input 요청 거절 · 취소에 던진다. invoke 는 requiresConfirmation 확인 거절에 던진다 */\nexport function inputDeclined(command: string, kind: \"confirm\" | \"input\"): MextError<\"INPUT_DECLINED\"> {\n const message =\n kind === \"confirm\" ? `${command} was not confirmed and did not run.` : `${command} requested input, but it was declined.`\n return new MextError(\"INPUT_DECLINED\", message, { command, kind })\n}\n\n/** mext 가 쓰는 오류 code. CommandError 로 만들 수 없다 */\nexport const RESERVED_ERROR_CODES: readonly string[] = [\n \"UNKNOWN_COMMAND\",\n \"INVALID_ARGUMENT\",\n \"UNAUTHENTICATED\",\n \"PERMISSION_DENIED\",\n \"INPUT_UNAVAILABLE\",\n \"INPUT_DECLINED\",\n \"EXECUTION_ERROR\",\n \"EXECUTION_FAILED\",\n \"INTERRUPTED\",\n \"BUILD_ERROR\",\n \"REMOTE_ERROR\",\n]\n\nconst ERROR_CODE = /^[A-Z][A-Z0-9_]*$/\n\n/**\n * 핸들러 정의 오류 (§4). 응답 본문 = { code, message, ...details } (MCP structuredContent.error · CLI --json).\n * code = 대문자 스네이크, mext 예약 code 불가 (TypeError). onCommandError 에 보고하지 않는다 (의도된 오류)\n */\nexport class CommandError extends Error {\n readonly code: string\n readonly details: Record<string, unknown>\n\n constructor(code: string, message: string, details: Record<string, unknown> = {}) {\n if (typeof code !== \"string\" || !ERROR_CODE.test(code)) {\n throw new TypeError(`CommandError code must be UPPER_SNAKE_CASE (got ${JSON.stringify(code)})`)\n }\n if (RESERVED_ERROR_CODES.includes(code)) throw new TypeError(`CommandError code \"${code}\" is reserved by mext`)\n if (typeof details !== \"object\" || details === null || Array.isArray(details)) {\n throw new TypeError(\"CommandError details must be an object\")\n }\n super(message)\n this.name = \"CommandError\"\n this.code = code\n this.details = details\n }\n\n toJSON(): { code: string; message: string } & Record<string, unknown> {\n return { ...this.details, code: this.code, message: this.message }\n }\n}\n\nexport function isCommandError(error: unknown): error is CommandError {\n return error instanceof CommandError\n}\n\n/** 응답 본문으로 그대로 보내는 오류 (MextError · CommandError) */\nexport function isDeclaredError(error: unknown): error is MextError | CommandError {\n return error instanceof MextError || error instanceof CommandError\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { defineCommand, isCommand } from "./command.ts";
|
|
|
3
3
|
export type { Ask, AskRequest, Command, CommandContext, CommandDefinition, ConfirmRequest, InputRequest, InputSchema, MextApp, ProgressUpdate, } from "./command.ts";
|
|
4
4
|
export { ANONYMOUS, hasScope, localPrincipal } from "./principal.ts";
|
|
5
5
|
export type { Principal } from "./principal.ts";
|
|
6
|
-
export { inputDeclined, isMextError, MextBuildError, MextError, unauthenticated } from "./errors.ts";
|
|
6
|
+
export { CommandError, inputDeclined, isCommandError, isMextError, MextBuildError, MextError, RESERVED_ERROR_CODES, unauthenticated, } from "./errors.ts";
|
|
7
7
|
export type { ArgumentIssue, BuildIssue, BuildIssueCode, MextErrorCode, MextErrorDetails } from "./errors.ts";
|
|
8
8
|
export { authorize, confirmationMessage, createContext, findCommand, invoke, parseInput } from "./invoke.ts";
|
|
9
9
|
export type { AskHandler, AskHandlerRequest, InvokeOptions, InvokeResult } from "./invoke.ts";
|
|
@@ -21,7 +21,7 @@ export { formatBuildSummary, runBuild } from "./cli/build.ts";
|
|
|
21
21
|
export { mextVersion } from "./version.ts";
|
|
22
22
|
export * from "./mcp/index.ts";
|
|
23
23
|
export { CLI_BUILTIN_COMMANDS, CONFIG_FILE, createApp, defineConfig, loadConfig, resolveCommandsDir, resolveLocalPrincipal } from "./config.ts";
|
|
24
|
-
export type { CliConfig, MextConfig } from "./config.ts";
|
|
24
|
+
export type { CliConfig, McpCapabilities, McpConfig, McpConnectHandler, McpConnection, MextConfig } from "./config.ts";
|
|
25
25
|
export { buildCustomerCli, CLI_BIN_FILE, CLI_DIR, customerCliIssues } from "./build-cli.ts";
|
|
26
26
|
export type { BuildCustomerCliOptions, CustomerCliBuild } from "./build-cli.ts";
|
|
27
27
|
export { runCustomerCli } from "./client/main.ts";
|
|
@@ -38,6 +38,7 @@ export type { CliField, CliSpec, FieldKind, ParseArgsOptions, ParsedArgs, Primit
|
|
|
38
38
|
export { cliPath, formatInvocation, renderCommandHelp, renderHelp, renderNamespaceHelp, resolveHelpTarget, } from "./cli/help.ts";
|
|
39
39
|
export type { HelpOptions, HelpTarget } from "./cli/help.ts";
|
|
40
40
|
export { errorJson, exitCodeOf, formatError, formatJson, formatResult } from "./cli/output.ts";
|
|
41
|
+
export type { CliErrorFormatter, CliErrorInfo, CliErrorOutput } from "./cli/output.ts";
|
|
41
42
|
export { loginCommand, resolveServerUrl } from "./cli/login.ts";
|
|
42
43
|
export type { AuthCommandOptions } from "./cli/login.ts";
|
|
43
44
|
export { logoutCommand } from "./cli/logout.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { VERSION } from "./version.js";
|
|
2
2
|
export { defineCommand, isCommand } from "./command.js";
|
|
3
3
|
export { ANONYMOUS, hasScope, localPrincipal } from "./principal.js";
|
|
4
|
-
export { inputDeclined, isMextError, MextBuildError, MextError, unauthenticated } from "./errors.js";
|
|
4
|
+
export { CommandError, inputDeclined, isCommandError, isMextError, MextBuildError, MextError, RESERVED_ERROR_CODES, unauthenticated, } from "./errors.js";
|
|
5
5
|
export { authorize, confirmationMessage, createContext, findCommand, invoke, parseInput } from "./invoke.js";
|
|
6
6
|
export { findInstrumentation, INSTRUMENTATION_FILE, loadInstrumentation, loadProjectInstrumentation, } from "./instrumentation.js";
|
|
7
7
|
export { collectRegistry, createLazyRegistry, createRegistry, loadCommand, resolveCommand } from "./registry.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAavD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpE,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAavD,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpE,OAAO,EACL,YAAY,EACZ,aAAa,EACb,cAAc,EACd,WAAW,EACX,cAAc,EACd,SAAS,EACT,oBAAoB,EACpB,eAAe,GAChB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE5G,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,sBAAsB,CAAA;AAS7B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAWhH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAErF,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAEvH,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,YAAY,EAAE,UAAU,EAAE,wBAAwB,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEvI,OAAO,EAAE,kBAAkB,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,cAAc,gBAAgB,CAAA;AAC9B,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAE/I,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE3F,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAEjD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AAEnE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAGnD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,cAAc,EAAE,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AAE/H,OAAO,EACL,OAAO,EACP,gBAAgB,EAChB,iBAAiB,EACjB,UAAU,EACV,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAE9F,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAE/D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAC/C,cAAc,mBAAmB,CAAA;AACjC,OAAO,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA","sourcesContent":["export { VERSION } from \"./version.ts\"\n\nexport { defineCommand, isCommand } from \"./command.ts\"\nexport type {\n Ask,\n AskRequest,\n Command,\n CommandContext,\n CommandDefinition,\n ConfirmRequest,\n InputRequest,\n InputSchema,\n MextApp,\n ProgressUpdate,\n} from \"./command.ts\"\nexport { ANONYMOUS, hasScope, localPrincipal } from \"./principal.ts\"\nexport type { Principal } from \"./principal.ts\"\nexport {\n CommandError,\n inputDeclined,\n isCommandError,\n isMextError,\n MextBuildError,\n MextError,\n RESERVED_ERROR_CODES,\n unauthenticated,\n} from \"./errors.ts\"\nexport type { ArgumentIssue, BuildIssue, BuildIssueCode, MextErrorCode, MextErrorDetails } from \"./errors.ts\"\nexport { authorize, confirmationMessage, createContext, findCommand, invoke, parseInput } from \"./invoke.ts\"\nexport type { AskHandler, AskHandlerRequest, InvokeOptions, InvokeResult } from \"./invoke.ts\"\nexport {\n findInstrumentation,\n INSTRUMENTATION_FILE,\n loadInstrumentation,\n loadProjectInstrumentation,\n} from \"./instrumentation.ts\"\nexport type {\n CommandErrorInfo,\n Instrumentation,\n InstrumentationModule,\n LoadInstrumentationOptions,\n OtelApi,\n Transport,\n} from \"./instrumentation.ts\"\nexport { collectRegistry, createLazyRegistry, createRegistry, loadCommand, resolveCommand } from \"./registry.ts\"\nexport type {\n JsonSchema,\n LazyCommandInput,\n NamespaceInfo,\n RegisteredCommand,\n Registry,\n RegistryCommandInput,\n RegistryInput,\n RegistryNamespaceInput,\n} from \"./registry.ts\"\nexport { collectCommands, loadRegistry, scanCommands, walkCommands } from \"./scan.ts\"\nexport type { ScannedFile, ScanResult } from \"./scan.ts\"\nexport { buildManifest, describeCommand, generateManifest, MANIFEST_SCHEMA_VERSION, manifestHash } from \"./manifest.ts\"\nexport type { BuildManifest, BuildManifestCommand, Manifest, ManifestCommand, ManifestNamespace } from \"./manifest.ts\"\nexport { BUILD_DIR, BUILD_MANIFEST_FILE, buildProject, HANDLER_JS, RolldownUnavailableError, SERVER_JS, START_ENTRY } from \"./build.ts\"\nexport type { BuildOptions, BuildResult, BuildWarning, RolldownModule, RolldownOutputItem } from \"./build.ts\"\nexport { formatBuildSummary, runBuild } from \"./cli/build.ts\"\nexport { mextVersion } from \"./version.ts\"\nexport * from \"./mcp/index.ts\"\nexport { CLI_BUILTIN_COMMANDS, CONFIG_FILE, createApp, defineConfig, loadConfig, resolveCommandsDir, resolveLocalPrincipal } from \"./config.ts\"\nexport type { CliConfig, McpCapabilities, McpConfig, McpConnectHandler, McpConnection, MextConfig } from \"./config.ts\"\nexport { buildCustomerCli, CLI_BIN_FILE, CLI_DIR, customerCliIssues } from \"./build-cli.ts\"\nexport type { BuildCustomerCliOptions, CustomerCliBuild } from \"./build-cli.ts\"\nexport { runCustomerCli } from \"./client/main.ts\"\nexport type { CustomerCliConfig } from \"./client/main.ts\"\nexport { clientManifest, manifestHelp } from \"./client/manifest.ts\"\nexport type { ClientManifest, ClientManifestCommand } from \"./client/manifest.ts\"\nexport { runRemote } from \"./client/runner.ts\"\nexport type { HelpSource, RemoteRunOptions } from \"./client/runner.ts\"\nexport { runCli, SUBCOMMANDS } from \"./cli/main.ts\"\nexport type { CliSubcommand } from \"./cli/main.ts\"\nexport type { CliIo, CliWriter } from \"./cli/io.ts\"\nexport { analyzeInput, assertCliConvertible, cliBuildIssues, cliSpec, parseArgs, RESERVED_FLAGS, toKebab } from \"./cli/args.ts\"\nexport type { CliField, CliSpec, FieldKind, ParseArgsOptions, ParsedArgs, Primitive, ScalarKind } from \"./cli/args.ts\"\nexport {\n cliPath,\n formatInvocation,\n renderCommandHelp,\n renderHelp,\n renderNamespaceHelp,\n resolveHelpTarget,\n} from \"./cli/help.ts\"\nexport type { HelpOptions, HelpTarget } from \"./cli/help.ts\"\nexport { errorJson, exitCodeOf, formatError, formatJson, formatResult } from \"./cli/output.ts\"\nexport type { CliErrorFormatter, CliErrorInfo, CliErrorOutput } from \"./cli/output.ts\"\nexport { loginCommand, resolveServerUrl } from \"./cli/login.ts\"\nexport type { AuthCommandOptions } from \"./cli/login.ts\"\nexport { logoutCommand } from \"./cli/logout.ts\"\nexport * from \"./remote/index.ts\"\nexport { cliSpecFromJsonSchema, defineCliSpec } from \"./cli/args.ts\"\n"]}
|
|
@@ -85,12 +85,12 @@ export interface ObserveOptions {
|
|
|
85
85
|
/**
|
|
86
86
|
* 명령어 실행 1회를 `mext.command <id>` span 으로 감싼다 (otel 있을 때만, 없으면 fn 그대로).
|
|
87
87
|
* 속성 = mext.command.id · mext.transport · mext.principal.kind. 인자 · 결과 · 토큰은 기록하지 않는다.
|
|
88
|
-
* 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError 메시지는 입력 일부를 담을 수 있어 제외).
|
|
88
|
+
* 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError · CommandError 메시지는 입력 일부를 담을 수 있어 제외).
|
|
89
89
|
* 취소 (signal abort · MRTR 대기) = status 없음.
|
|
90
90
|
*/
|
|
91
91
|
export declare function observeCommand<T>(command: string, options: ObserveOptions, signal: () => AbortSignal | undefined, fn: () => Promise<T>): Promise<T>;
|
|
92
92
|
/**
|
|
93
|
-
* 핸들러 예외 → onCommandError (await). MextError · 취소 · MRTR 대기는 보고하지 않는다.
|
|
93
|
+
* 핸들러 예외 → onCommandError (await). MextError · CommandError · 취소 · MRTR 대기는 보고하지 않는다.
|
|
94
94
|
* onCommandError 자체의 예외 = onerror (기본 stderr), 호출자에게 전파하지 않는다.
|
|
95
95
|
*/
|
|
96
96
|
export declare function reportCommandError(error: unknown, command: string, options: ObserveOptions, signal: AbortSignal | undefined): Promise<void>;
|
package/dist/instrumentation.js
CHANGED
|
@@ -9,7 +9,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
10
|
import { isAbsolute, join, resolve } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
|
-
import {
|
|
12
|
+
import { isDeclaredError } from "./errors.js";
|
|
13
13
|
/**
|
|
14
14
|
* instrumentation.ts (프로젝트 루트, 선택) · OpenTelemetry span (§11).
|
|
15
15
|
* register() = 프로세스당 1회, 첫 명령어 실행 전. onCommandError = 핸들러 예외 보고.
|
|
@@ -95,7 +95,7 @@ const TRACER_NAME = "@obov/mext";
|
|
|
95
95
|
/**
|
|
96
96
|
* 명령어 실행 1회를 `mext.command <id>` span 으로 감싼다 (otel 있을 때만, 없으면 fn 그대로).
|
|
97
97
|
* 속성 = mext.command.id · mext.transport · mext.principal.kind. 인자 · 결과 · 토큰은 기록하지 않는다.
|
|
98
|
-
* 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError 메시지는 입력 일부를 담을 수 있어 제외).
|
|
98
|
+
* 실패: status ERROR + mext.error.code. 핸들러 예외만 recordException (MextError · CommandError 메시지는 입력 일부를 담을 수 있어 제외).
|
|
99
99
|
* 취소 (signal abort · MRTR 대기) = status 없음.
|
|
100
100
|
*/
|
|
101
101
|
export function observeCommand(command, options, signal, fn) {
|
|
@@ -115,7 +115,7 @@ export function observeCommand(command, options, signal, fn) {
|
|
|
115
115
|
}
|
|
116
116
|
catch (error) {
|
|
117
117
|
if (!signal()?.aborted) {
|
|
118
|
-
if (
|
|
118
|
+
if (isDeclaredError(error)) {
|
|
119
119
|
span.setAttribute("mext.error.code", error.code);
|
|
120
120
|
span.setStatus({ code: otel.SpanStatusCode.ERROR, message: error.code });
|
|
121
121
|
}
|
|
@@ -133,12 +133,12 @@ export function observeCommand(command, options, signal, fn) {
|
|
|
133
133
|
});
|
|
134
134
|
}
|
|
135
135
|
/**
|
|
136
|
-
* 핸들러 예외 → onCommandError (await). MextError · 취소 · MRTR 대기는 보고하지 않는다.
|
|
136
|
+
* 핸들러 예외 → onCommandError (await). MextError · CommandError · 취소 · MRTR 대기는 보고하지 않는다.
|
|
137
137
|
* onCommandError 자체의 예외 = onerror (기본 stderr), 호출자에게 전파하지 않는다.
|
|
138
138
|
*/
|
|
139
139
|
export async function reportCommandError(error, command, options, signal) {
|
|
140
140
|
const hook = options.instrumentation?.onCommandError;
|
|
141
|
-
if (!hook ||
|
|
141
|
+
if (!hook || isDeclaredError(error) || signal?.aborted)
|
|
142
142
|
return;
|
|
143
143
|
const info = {
|
|
144
144
|
command,
|