@obov/mext 0.0.0-stage → 0.1.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.
Files changed (155) hide show
  1. package/dist/build-cli.d.ts +39 -0
  2. package/dist/build-cli.js +160 -0
  3. package/dist/build-cli.js.map +1 -0
  4. package/dist/build.d.ts +65 -0
  5. package/dist/build.js +252 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/cli/args.d.ts +107 -0
  8. package/dist/cli/args.js +621 -0
  9. package/dist/cli/args.js.map +1 -0
  10. package/dist/cli/bin.d.ts +2 -0
  11. package/dist/cli/bin.js +13 -0
  12. package/dist/cli/bin.js.map +1 -0
  13. package/dist/cli/build.d.ts +11 -0
  14. package/dist/cli/build.js +87 -0
  15. package/dist/cli/build.js.map +1 -0
  16. package/dist/cli/dev-lock.d.ts +23 -0
  17. package/dist/cli/dev-lock.js +68 -0
  18. package/dist/cli/dev-lock.js.map +1 -0
  19. package/dist/cli/dev-server.d.ts +20 -0
  20. package/dist/cli/dev-server.js +74 -0
  21. package/dist/cli/dev-server.js.map +1 -0
  22. package/dist/cli/dev.d.ts +21 -0
  23. package/dist/cli/dev.js +564 -0
  24. package/dist/cli/dev.js.map +1 -0
  25. package/dist/cli/help.d.ts +31 -0
  26. package/dist/cli/help.js +238 -0
  27. package/dist/cli/help.js.map +1 -0
  28. package/dist/cli/io.d.ts +26 -0
  29. package/dist/cli/io.js +74 -0
  30. package/dist/cli/io.js.map +1 -0
  31. package/dist/cli/login.d.ts +32 -0
  32. package/dist/cli/login.js +284 -0
  33. package/dist/cli/login.js.map +1 -0
  34. package/dist/cli/logout.d.ts +9 -0
  35. package/dist/cli/logout.js +105 -0
  36. package/dist/cli/logout.js.map +1 -0
  37. package/dist/cli/main.d.ts +19 -0
  38. package/dist/cli/main.js +77 -0
  39. package/dist/cli/main.js.map +1 -0
  40. package/dist/cli/mcp.d.ts +16 -0
  41. package/dist/cli/mcp.js +92 -0
  42. package/dist/cli/mcp.js.map +1 -0
  43. package/dist/cli/output.d.ts +34 -0
  44. package/dist/cli/output.js +172 -0
  45. package/dist/cli/output.js.map +1 -0
  46. package/dist/cli/remote.d.ts +17 -0
  47. package/dist/cli/remote.js +35 -0
  48. package/dist/cli/remote.js.map +1 -0
  49. package/dist/cli/run.d.ts +14 -0
  50. package/dist/cli/run.js +218 -0
  51. package/dist/cli/run.js.map +1 -0
  52. package/dist/cli/start.d.ts +8 -0
  53. package/dist/cli/start.js +37 -0
  54. package/dist/cli/start.js.map +1 -0
  55. package/dist/client/cache.d.ts +104 -0
  56. package/dist/client/cache.js +208 -0
  57. package/dist/client/cache.js.map +1 -0
  58. package/dist/client/main.d.ts +25 -0
  59. package/dist/client/main.js +100 -0
  60. package/dist/client/main.js.map +1 -0
  61. package/dist/client/manifest.d.ts +19 -0
  62. package/dist/client/manifest.js +68 -0
  63. package/dist/client/manifest.js.map +1 -0
  64. package/dist/client/runner.d.ts +49 -0
  65. package/dist/client/runner.js +562 -0
  66. package/dist/client/runner.js.map +1 -0
  67. package/dist/command.d.ts +75 -0
  68. package/dist/command.js +44 -0
  69. package/dist/command.js.map +1 -0
  70. package/dist/config.d.ts +53 -0
  71. package/dist/config.js +122 -0
  72. package/dist/config.js.map +1 -0
  73. package/dist/errors.d.ts +74 -0
  74. package/dist/errors.js +38 -0
  75. package/dist/errors.js.map +1 -0
  76. package/dist/ids.d.ts +6 -0
  77. package/dist/ids.js +12 -0
  78. package/dist/ids.js.map +1 -0
  79. package/dist/index.d.ts +45 -0
  80. package/dist/index.js +27 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/instrumentation.d.ts +97 -0
  83. package/dist/instrumentation.js +165 -0
  84. package/dist/instrumentation.js.map +1 -0
  85. package/dist/invoke.d.ts +64 -0
  86. package/dist/invoke.js +152 -0
  87. package/dist/invoke.js.map +1 -0
  88. package/dist/lookup.d.ts +9 -0
  89. package/dist/lookup.js +27 -0
  90. package/dist/lookup.js.map +1 -0
  91. package/dist/manifest.d.ts +68 -0
  92. package/dist/manifest.js +77 -0
  93. package/dist/manifest.js.map +1 -0
  94. package/dist/mcp/constants.d.ts +9 -0
  95. package/dist/mcp/constants.js +10 -0
  96. package/dist/mcp/constants.js.map +1 -0
  97. package/dist/mcp/dispatch.d.ts +66 -0
  98. package/dist/mcp/dispatch.js +249 -0
  99. package/dist/mcp/dispatch.js.map +1 -0
  100. package/dist/mcp/help.d.ts +57 -0
  101. package/dist/mcp/help.js +136 -0
  102. package/dist/mcp/help.js.map +1 -0
  103. package/dist/mcp/http.d.ts +92 -0
  104. package/dist/mcp/http.js +366 -0
  105. package/dist/mcp/http.js.map +1 -0
  106. package/dist/mcp/index.d.ts +15 -0
  107. package/dist/mcp/index.js +9 -0
  108. package/dist/mcp/index.js.map +1 -0
  109. package/dist/mcp/install.d.ts +17 -0
  110. package/dist/mcp/install.js +36 -0
  111. package/dist/mcp/install.js.map +1 -0
  112. package/dist/mcp/mrtr.d.ts +82 -0
  113. package/dist/mcp/mrtr.js +130 -0
  114. package/dist/mcp/mrtr.js.map +1 -0
  115. package/dist/mcp/server.d.ts +58 -0
  116. package/dist/mcp/server.js +204 -0
  117. package/dist/mcp/server.js.map +1 -0
  118. package/dist/mcp/stdio.d.ts +57 -0
  119. package/dist/mcp/stdio.js +79 -0
  120. package/dist/mcp/stdio.js.map +1 -0
  121. package/dist/principal.d.ts +16 -0
  122. package/dist/principal.js +24 -0
  123. package/dist/principal.js.map +1 -0
  124. package/dist/registry.d.ts +90 -0
  125. package/dist/registry.js +169 -0
  126. package/dist/registry.js.map +1 -0
  127. package/dist/remote/browser.d.ts +15 -0
  128. package/dist/remote/browser.js +47 -0
  129. package/dist/remote/browser.js.map +1 -0
  130. package/dist/remote/client.d.ts +113 -0
  131. package/dist/remote/client.js +312 -0
  132. package/dist/remote/client.js.map +1 -0
  133. package/dist/remote/credentials.d.ts +48 -0
  134. package/dist/remote/credentials.js +196 -0
  135. package/dist/remote/credentials.js.map +1 -0
  136. package/dist/remote/index.d.ts +8 -0
  137. package/dist/remote/index.js +5 -0
  138. package/dist/remote/index.js.map +1 -0
  139. package/dist/remote/oauth.d.ts +186 -0
  140. package/dist/remote/oauth.js +507 -0
  141. package/dist/remote/oauth.js.map +1 -0
  142. package/dist/scan.d.ts +34 -0
  143. package/dist/scan.js +155 -0
  144. package/dist/scan.js.map +1 -0
  145. package/dist/start.d.ts +63 -0
  146. package/dist/start.js +267 -0
  147. package/dist/start.js.map +1 -0
  148. package/dist/suggest.d.ts +7 -0
  149. package/dist/suggest.js +45 -0
  150. package/dist/suggest.js.map +1 -0
  151. package/dist/version.d.ts +4 -0
  152. package/dist/version.js +13 -0
  153. package/dist/version.js.map +1 -0
  154. package/package.json +49 -4
  155. package/README.md +0 -3
@@ -0,0 +1,562 @@
1
+ import { COMMAND } from "../command.js";
2
+ import { MextError } from "../errors.js";
3
+ import { isSegment } from "../ids.js";
4
+ import { createRemoteClient, isRecord, RemoteInputRequiredError, RemoteRpcError, RemoteTransportError, RemoteUnauthorizedError, } from "../remote/client.js";
5
+ import { credentialStore, envPrefix } from "../remote/credentials.js";
6
+ import { isExpired, OAuthFlowError, refreshCredentials } from "../remote/oauth.js";
7
+ import { cliSpecFromJsonSchema, coerceValue, defineCliSpec, parseArgs } from "../cli/args.js";
8
+ import { cliPath, renderHelp } from "../cli/help.js";
9
+ import { createLineReader, readAll } from "../cli/io.js";
10
+ import { CliInterrupted, EXIT, errorJson, exitCodeOf, formatError, formatJson, formatProgress, formatResult, } from "../cli/output.js";
11
+ import { MANIFEST_HASH_META_KEY } from "../mcp/constants.js";
12
+ import { VERSION } from "../version.js";
13
+ import { openSchema, schemaCacheDir, schemaCacheFile } from "./cache.js";
14
+ import { manifestHelp } from "./manifest.js";
15
+ export async function runRemote(options, argv, io) {
16
+ const { url, prog } = options;
17
+ const tokenEnv = `${envPrefix(options.bin)}_TOKEN`;
18
+ const ctx = { login: options.login, tokenEnv, auth: undefined, footer: options.rootHelpFooter };
19
+ const lead = { json: false, yes: false, help: false };
20
+ let i = 0;
21
+ for (; i < argv.length; i++) {
22
+ const tok = argv[i];
23
+ if (!tok.startsWith("-") || tok === "-")
24
+ break;
25
+ if (tok === "--json")
26
+ lead.json = true;
27
+ else if (tok === "--yes")
28
+ lead.yes = true;
29
+ else if (tok === "--help" || tok === "-h")
30
+ lead.help = true;
31
+ else
32
+ return report(usageError(`unknown flag ${tok} before the command`), io, prog, argv.includes("--json"), ctx);
33
+ }
34
+ const rest = argv.slice(i);
35
+ const beforeDashDash = rest.indexOf("--") < 0 ? rest : rest.slice(0, rest.indexOf("--"));
36
+ const json = lead.json || beforeDashDash.includes("--json");
37
+ const controller = new AbortController();
38
+ const onAbort = () => controller.abort(io.signal?.reason);
39
+ if (io.signal?.aborted)
40
+ onAbort();
41
+ io.signal?.addEventListener("abort", onAbort, { once: true });
42
+ const lines = createLineReader(io);
43
+ let session;
44
+ let schema;
45
+ try {
46
+ if (!/^https?:\/\//i.test(url))
47
+ throw usageError(`--url must be an http(s) URL (got "${url}")`);
48
+ session = openSession(options, tokenEnv, io, controller.signal);
49
+ const s = session;
50
+ schema = await openRunSchema(options, s, ctx, io);
51
+ const source = schema;
52
+ const wantsHelp = lead.help || beforeDashDash.includes("--help") || beforeDashDash.includes("-h");
53
+ let stdin;
54
+ const readStdin = () => (stdin ??= readAll(io.stdin));
55
+ // 경로 해석 · 플래그 파싱 (로컬). 실패하면 서버 help 로 1회 재시도
56
+ const plan = async () => {
57
+ // <prog> help [target...]
58
+ if (rest[0] === "help") {
59
+ const tokens = rest.slice(1).filter((t) => !t.startsWith("-"));
60
+ const resolved = await resolveRemotePath(source, tokens);
61
+ if (resolved.consumed < tokens.length)
62
+ throw await unknownAt(source, resolved, tokens.slice(resolved.consumed));
63
+ return { kind: "help", help: resolved.help };
64
+ }
65
+ const resolved = await resolveRemotePath(source, rest);
66
+ const { help, consumed } = resolved;
67
+ const command = help.kind === "command" ? help : help.kind === "namespace" ? help.command : undefined;
68
+ const next = rest[consumed];
69
+ const acceptsPositional = command !== undefined && command.positional.length > 0;
70
+ if (help.kind !== "command" && next !== undefined && !next.startsWith("-") && !acceptsPositional) {
71
+ throw await unknownAt(source, resolved, rest.slice(consumed));
72
+ }
73
+ if (wantsHelp)
74
+ return { kind: "help", help };
75
+ if (!command)
76
+ return { kind: "namespace", help };
77
+ const entry = entryFromHelp(command);
78
+ const parsed = await parseArgs(entry, rest.slice(consumed), { readStdin });
79
+ return { kind: "run", command, entry, input: parsed.input, yes: parsed.yes };
80
+ };
81
+ let planned;
82
+ try {
83
+ planned = await plan();
84
+ }
85
+ catch (error) {
86
+ if (!isLocalParseFailure(error) || !schema.refetch())
87
+ throw error;
88
+ try {
89
+ planned = await plan();
90
+ }
91
+ catch (retryError) {
92
+ // 재조회 실패 (오프라인 등) = 원래 로컬 오류
93
+ if (retryError instanceof RemoteTransportError || retryError instanceof RemoteRpcError)
94
+ throw error;
95
+ throw retryError;
96
+ }
97
+ }
98
+ if (planned.kind === "help")
99
+ return printHelp(planned.help, io, prog, json, ctx);
100
+ if (planned.kind === "namespace") {
101
+ const { help } = planned;
102
+ const id = help.kind === "namespace" ? help.id : "";
103
+ const error = new MextError("UNKNOWN_COMMAND", id === "" ? "No command given." : `"${cliPath(id)}" is a namespace. Specify a command.`, { command: id, suggested: [], usage: id === "" ? "help" : `help ${id}` });
104
+ if (json)
105
+ return report(error, io, prog, true, ctx);
106
+ io.stderr.write(formatError(error, prog));
107
+ io.stderr.write(`\n${helpText(help, prog, ctx)}`);
108
+ return EXIT.usage;
109
+ }
110
+ const { command, entry } = planned;
111
+ const yes = lead.yes || planned.yes;
112
+ const tool = commandTool(dispatcherOf(url, await schema.tools()), command);
113
+ const args = { command: entry.id, args: planned.input };
114
+ const result = await s.call((o) => s.client.callTool(tool, args, {
115
+ ...o,
116
+ progressToken: "mext-cli",
117
+ onNotification: (method, params) => {
118
+ if (method !== "notifications/progress" || typeof params.progress !== "number")
119
+ return;
120
+ const update = { progress: params.progress };
121
+ if (typeof params.total === "number")
122
+ update.total = params.total;
123
+ if (typeof params.message === "string")
124
+ update.message = params.message;
125
+ io.stderr.write(formatProgress(update));
126
+ },
127
+ onInputRequired: inputHandler(io, lines, yes, entry.id, controller.signal),
128
+ }));
129
+ // drift: 이번 결과는 그대로, 다르면 캐시 stale (다음 실행에서 서버 help)
130
+ schema.observe(metaHash(result));
131
+ if (result.isError === true)
132
+ throw toolError(result, ctx);
133
+ const value = result.structuredContent === undefined ? textValue(result) : result.structuredContent;
134
+ io.stdout.write(json ? formatJson(value) : formatResult(value));
135
+ return EXIT.ok;
136
+ }
137
+ catch (error) {
138
+ if (session)
139
+ ctx.auth = session.auth;
140
+ if (controller.signal.aborted)
141
+ return report(new CliInterrupted(), io, prog, json, ctx);
142
+ return report(error, io, prog, json, ctx);
143
+ }
144
+ finally {
145
+ io.signal?.removeEventListener("abort", onAbort);
146
+ lines.close();
147
+ await schema?.save();
148
+ }
149
+ }
150
+ /** 서버 help 재조회 대상 로컬 오류 */
151
+ function isLocalParseFailure(error) {
152
+ return error instanceof MextError && (error.code === "UNKNOWN_COMMAND" || error.code === "INVALID_ARGUMENT");
153
+ }
154
+ /** 결과 _meta["dev.mext/manifestHash"] */
155
+ function metaHash(result) {
156
+ const meta = result._meta;
157
+ const value = isRecord(meta) ? meta[MANIFEST_HASH_META_KEY] : undefined;
158
+ return typeof value === "string" ? value : undefined;
159
+ }
160
+ /** 이번 실행의 스키마 상태: 캐시 파일 (<BIN>_CACHE_DIR · XDG · HOME) · 내장 manifest · 서버 help */
161
+ function openRunSchema(options, s, ctx, io) {
162
+ const dir = schemaCacheDir(options.bin, io.env);
163
+ return openSchema({
164
+ file: dir === undefined ? undefined : schemaCacheFile(dir, options.url),
165
+ url: options.url,
166
+ client: { bin: options.bin, version: options.version, mext: VERSION },
167
+ ...(options.manifest && options.manifestHash !== undefined
168
+ ? { embedded: { manifest: options.manifest, hash: options.manifestHash } }
169
+ : {}),
170
+ fetchHelp: serverHelpFetcher(s, ctx),
171
+ fetchTools: async () => {
172
+ const r = await s.call((o) => s.client.listToolsResult(o));
173
+ dispatcherOf(s.url, r.tools);
174
+ return r.ttlMs === undefined ? { tools: r.tools } : { tools: r.tools, ttlMs: r.ttlMs };
175
+ },
176
+ warn: (message) => io.stderr.write(message),
177
+ });
178
+ }
179
+ /** 네트워크 · 키체인 접근은 첫 요청까지 미룬다 (번들 형태의 help 는 둘 다 없음) */
180
+ function openSession(options, tokenEnv, io, signal) {
181
+ const { url } = options;
182
+ const envToken = io.env[tokenEnv] !== undefined && io.env[tokenEnv] !== "" ? io.env[tokenEnv] : undefined;
183
+ const store = envToken === undefined ? credentialStore({ env: io.env, bin: options.bin }) : undefined;
184
+ let stored;
185
+ let auth = envToken !== undefined ? "env" : undefined;
186
+ let refreshed = false;
187
+ const refresh = async () => {
188
+ if (!store || !stored?.refresh_token || refreshed)
189
+ return false;
190
+ refreshed = true;
191
+ try {
192
+ stored = await refreshCredentials(stored);
193
+ await store.set(url, stored).catch(() => { });
194
+ return true;
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ };
200
+ let loading;
201
+ const load = () => (loading ??= (async () => {
202
+ if (!store)
203
+ return;
204
+ stored = await store.get(url).catch(() => undefined);
205
+ if (stored && isExpired(stored))
206
+ await refresh();
207
+ auth = stored ? "stored" : "none";
208
+ })());
209
+ const client = createRemoteClient({
210
+ url,
211
+ clientInfo: { name: options.bin, version: options.version },
212
+ token: async () => {
213
+ await load();
214
+ return envToken ?? stored?.access_token;
215
+ },
216
+ });
217
+ const call = async (fn) => {
218
+ try {
219
+ return await fn({ signal });
220
+ }
221
+ catch (error) {
222
+ if (error instanceof RemoteUnauthorizedError && (await refresh()))
223
+ return fn({ signal });
224
+ throw error;
225
+ }
226
+ };
227
+ return {
228
+ url,
229
+ client,
230
+ get auth() {
231
+ return auth;
232
+ },
233
+ call,
234
+ };
235
+ }
236
+ /** tools/list 결과 → dispatcher 도구 (execute 또는 query · mutate). 없으면 RemoteTransportError */
237
+ function dispatcherOf(url, list) {
238
+ const execute = list.find((t) => t.name === "execute");
239
+ const query = list.find((t) => t.name === "query");
240
+ const helpTool = execute ?? query;
241
+ if (!helpTool || (!execute && !list.some((t) => t.name === "mutate"))) {
242
+ throw new RemoteTransportError(`${url} does not expose mext dispatcher tools (execute, or query and mutate).`);
243
+ }
244
+ return { list, helpTool };
245
+ }
246
+ /** 명령어를 호출할 도구: help 의 tool (split) → execute → readOnly 면 query, 아니면 mutate */
247
+ function commandTool(tools, command) {
248
+ const byName = (name) => (name === undefined ? undefined : tools.list.find((t) => t.name === name));
249
+ return byName(command.tool) ?? byName("execute") ?? byName(command.readOnly ? "query" : "mutate") ?? tools.helpTool;
250
+ }
251
+ /** 서버의 dispatcher help (tools/call). UNKNOWN_COMMAND = 캐시할 결과, 그 외 오류 = throw */
252
+ function serverHelpFetcher(s, ctx) {
253
+ return async (target, tools) => {
254
+ const { helpTool } = dispatcherOf(s.url, await tools());
255
+ const args = target === "" ? { command: "help" } : { command: "help", args: { target } };
256
+ const result = await s.call((o) => s.client.callTool(helpTool, args, o));
257
+ const hash = metaHash(result);
258
+ if (result.isError === true) {
259
+ const error = toolError(result, ctx);
260
+ if (error instanceof MextError && error.code === "UNKNOWN_COMMAND")
261
+ return { value: { error: error.toJSON() }, hash };
262
+ throw error;
263
+ }
264
+ const value = result.structuredContent;
265
+ if (!isRecord(value) || (value.kind !== "root" && value.kind !== "namespace" && value.kind !== "command")) {
266
+ throw new RemoteTransportError(`${s.url} returned an unexpected help result.`);
267
+ }
268
+ return { value: { help: value }, hash };
269
+ };
270
+ }
271
+ /** 번들 형태: 내장 manifest (네트워크 없음) */
272
+ export function manifestHelpSource(manifest) {
273
+ return { help: async (target) => manifestHelp(manifest, target) };
274
+ }
275
+ async function resolveRemotePath(source, tokens) {
276
+ const segs = [];
277
+ for (const t of tokens) {
278
+ if (!isSegment(t))
279
+ break;
280
+ segs.push(t);
281
+ }
282
+ let unknown;
283
+ for (let k = segs.length; k >= 0; k--) {
284
+ try {
285
+ return { help: await source.help(segs.slice(0, k).join(".")), consumed: k, unknown };
286
+ }
287
+ catch (error) {
288
+ if (k > 0 && error instanceof MextError && error.code === "UNKNOWN_COMMAND") {
289
+ unknown ??= error;
290
+ continue;
291
+ }
292
+ throw error;
293
+ }
294
+ }
295
+ throw new Error("unreachable");
296
+ }
297
+ /** 해석되지 않은 경로 → UNKNOWN_COMMAND (suggested · usage) */
298
+ async function unknownAt(source, resolved, tail) {
299
+ if (resolved.unknown)
300
+ return resolved.unknown;
301
+ const id = resolved.help.kind === "root" ? "" : resolved.help.id;
302
+ const first = tail.find((t) => !t.startsWith("-"));
303
+ const candidate = [id, first ?? ""].filter((x) => x !== "").join(".");
304
+ try {
305
+ await source.help(candidate);
306
+ }
307
+ catch (error) {
308
+ if (error instanceof MextError)
309
+ return error;
310
+ throw error;
311
+ }
312
+ return new MextError("UNKNOWN_COMMAND", `Unknown command "${candidate}".`, { command: candidate, suggested: [], usage: "help" });
313
+ }
314
+ /* help → 로컬 렌더러용 Registry (같은 help 텍스트) */
315
+ /** 원격 명령어의 input 자리. CLI 필드는 defineCliSpec (JSON Schema 분석) 이 정하므로 Zod 스키마가 필요 없다 */
316
+ const REMOTE_INPUT = { _zod: { def: { shape: {} } } };
317
+ function commandOf(meta) {
318
+ return {
319
+ [COMMAND]: true,
320
+ description: meta.description,
321
+ input: REMOTE_INPUT,
322
+ output: undefined,
323
+ readOnly: meta.readOnly,
324
+ destructive: meta.destructive,
325
+ requiresConfirmation: meta.requiresConfirmation,
326
+ scopes: [...(meta.scopes ?? [])],
327
+ examples: [...(meta.examples ?? [])],
328
+ positional: [...(meta.positional ?? [])],
329
+ aliases: [],
330
+ execute() {
331
+ throw new Error("remote command");
332
+ },
333
+ };
334
+ }
335
+ /** 원격 명령어 help → RegisteredCommand (CLI 필드 = inputSchema 분석) */
336
+ export function entryFromHelp(help) {
337
+ const entry = {
338
+ id: help.id,
339
+ command: commandOf(help),
340
+ aliases: [...help.aliases],
341
+ file: undefined,
342
+ inputSchema: help.inputSchema,
343
+ outputSchema: help.outputSchema,
344
+ };
345
+ defineCliSpec(entry, cliSpecFromJsonSchema(help.inputSchema, help.positional));
346
+ return entry;
347
+ }
348
+ function registryFromHelp(help) {
349
+ const commands = new Map();
350
+ const aliases = new Map();
351
+ const namespaces = new Map();
352
+ if (help.kind === "command") {
353
+ const entry = entryFromHelp(help);
354
+ commands.set(entry.id, entry);
355
+ for (const a of entry.aliases)
356
+ aliases.set(a, entry.id);
357
+ namespaces.set("", { id: "", description: undefined, commands: [], namespaces: [] });
358
+ return { commands, aliases, namespaces };
359
+ }
360
+ const id = help.kind === "root" ? "" : help.id;
361
+ namespaces.set(id, {
362
+ id,
363
+ description: help.description,
364
+ commands: help.commands.map((c) => c.id),
365
+ namespaces: help.namespaces.map((n) => n.id),
366
+ });
367
+ for (const n of help.namespaces)
368
+ namespaces.set(n.id, { id: n.id, description: n.description, commands: [], namespaces: [] });
369
+ for (const c of help.commands) {
370
+ commands.set(c.id, {
371
+ id: c.id,
372
+ command: commandOf(c),
373
+ aliases: [],
374
+ file: undefined,
375
+ inputSchema: {},
376
+ outputSchema: undefined,
377
+ });
378
+ }
379
+ if (help.kind === "namespace" && help.command)
380
+ commands.set(help.command.id, entryFromHelp(help.command));
381
+ return { commands, aliases, namespaces };
382
+ }
383
+ /** 사람용 help 텍스트. 루트면 footer (고객 CLI 내장 명령) 를 붙인다 */
384
+ function helpText(help, prog, ctx) {
385
+ const target = help.kind === "root" ? "" : help.id;
386
+ const text = renderHelp(registryFromHelp(help), target, { prog });
387
+ return help.kind === "root" && ctx.footer ? `${text}\n${ctx.footer}` : text;
388
+ }
389
+ /** --help --json = help 결과 그대로 (서버 help 의 structuredContent · 내장 manifest help) */
390
+ function printHelp(help, io, prog, json, ctx) {
391
+ io.stdout.write(json ? formatJson(help) : helpText(help, prog, ctx));
392
+ return EXIT.ok;
393
+ }
394
+ /* 결과 · 오류 */
395
+ const MEXT_CODES = [
396
+ "UNKNOWN_COMMAND",
397
+ "INVALID_ARGUMENT",
398
+ "UNAUTHENTICATED",
399
+ "PERMISSION_DENIED",
400
+ "INPUT_UNAVAILABLE",
401
+ ];
402
+ /** 서버 핸들러 예외 등 mext 구조화 오류 외의 도구 오류. --json = 서버 오류 본문 그대로, exit 1 */
403
+ export class RemoteExecutionError extends Error {
404
+ body;
405
+ constructor(body) {
406
+ super(typeof body.message === "string" ? body.message : "Remote command failed.");
407
+ this.name = "RemoteExecutionError";
408
+ this.body = body;
409
+ }
410
+ }
411
+ /** isError 결과 → MextError (code 가 mext 구조화 오류면) 또는 RemoteExecutionError */
412
+ function toolError(result, ctx) {
413
+ const sc = result.structuredContent;
414
+ const body = isRecord(sc) && isRecord(sc.error) ? sc.error : undefined;
415
+ if (!body)
416
+ return new RemoteExecutionError({ code: "EXECUTION_FAILED", message: textValue(result) ?? "Remote command failed." });
417
+ const { code, message, ...details } = body;
418
+ if (typeof code === "string" && MEXT_CODES.includes(code)) {
419
+ const text = typeof message === "string" ? message : code;
420
+ if (code === "UNAUTHENTICATED") {
421
+ return new MextError("UNAUTHENTICATED", text, { ...details, login: ctx.login });
422
+ }
423
+ return new MextError(code, text, details);
424
+ }
425
+ return new RemoteExecutionError(body);
426
+ }
427
+ function textValue(result) {
428
+ const texts = (result.content ?? []).flatMap((c) => isRecord(c) && c.type === "text" && typeof c.text === "string" ? [c.text] : []);
429
+ return texts.length > 0 ? texts.join("\n") : undefined;
430
+ }
431
+ /** 401 → UNAUTHENTICATED (login 안내 포함) */
432
+ function unauthenticatedFrom(error, ctx) {
433
+ const data = isRecord(error.data) ? error.data : {};
434
+ const reason = data.reason === "invalid_token" || data.reason === "missing_token"
435
+ ? data.reason
436
+ : error.challenge.error === "invalid_token"
437
+ ? "invalid_token"
438
+ : "missing_token";
439
+ let message = typeof data.message === "string" ? data.message : error.message;
440
+ if (ctx.auth === "env")
441
+ message += ` ${ctx.tokenEnv} was rejected.`;
442
+ return new MextError("UNAUTHENTICATED", message, { reason, login: ctx.login });
443
+ }
444
+ function report(error, io, prog, json, ctx) {
445
+ let e = error;
446
+ if (e instanceof RemoteUnauthorizedError)
447
+ e = unauthenticatedFrom(e, ctx);
448
+ if (json) {
449
+ io.stderr.write(formatJson(errorBody(e)));
450
+ return exitCodeOf(e);
451
+ }
452
+ io.stderr.write(formatError(e, prog));
453
+ if (e instanceof MextError && e.code === "PERMISSION_DENIED" && ctx.auth === "none") {
454
+ const reason = e.details.reason;
455
+ if (reason === "anonymous")
456
+ io.stderr.write(`Run "${ctx.login}" to log in.\n`);
457
+ }
458
+ return exitCodeOf(e);
459
+ }
460
+ function errorBody(error) {
461
+ if (error instanceof RemoteExecutionError)
462
+ return error.body;
463
+ if (error instanceof RemoteTransportError ||
464
+ error instanceof RemoteRpcError ||
465
+ error instanceof RemoteInputRequiredError ||
466
+ error instanceof OAuthFlowError) {
467
+ const body = { code: "REMOTE_ERROR", message: error.message };
468
+ if (error instanceof RemoteRpcError)
469
+ body.rpc = { code: error.code, status: error.status, data: error.data };
470
+ return body;
471
+ }
472
+ return errorJson(error);
473
+ }
474
+ function usageError(message) {
475
+ return new MextError("INVALID_ARGUMENT", message, {
476
+ command: "",
477
+ missing_fields: [],
478
+ issues: [{ path: "", code: "unknown_flag", message }],
479
+ expected_schema: {},
480
+ examples: [],
481
+ });
482
+ }
483
+ /* MRTR: InputRequiredResult → 프롬프트 → inputResponses */
484
+ /**
485
+ * elicitation/create (form) 만 처리 (clientCapabilities = elicitation.form).
486
+ * requestedSchema 가 boolean 속성 하나 = 확인: --yes → 승인, TTY → [y/N], 그 외 INPUT_UNAVAILABLE.
487
+ * 그 외 form = TTY 에서 필드별 입력 (스키마 모양으로 변환), TTY 없음 = INPUT_UNAVAILABLE.
488
+ * 응답 = { action: "accept", content }. 이번 라운드 요청에 대한 응답만 보낸다
489
+ */
490
+ function inputHandler(io, lines, yes, command, signal) {
491
+ const unavailable = (message, hint) => new MextError("INPUT_UNAVAILABLE", `${command} asked "${message}", but stdin is not a terminal.${hint}`, { command });
492
+ const ask = async (prompt) => {
493
+ const answer = await lines.question(prompt, signal);
494
+ if (signal.aborted)
495
+ throw new CliInterrupted();
496
+ return answer ?? "";
497
+ };
498
+ return async (required) => {
499
+ const out = {};
500
+ for (const [key, request] of Object.entries(required.inputRequests ?? {})) {
501
+ const params = isRecord(request.params) ? request.params : {};
502
+ const message = typeof params.message === "string" ? params.message : "Input required";
503
+ if (request.method !== "elicitation/create") {
504
+ throw new RemoteTransportError(`${command}: unsupported input request ${request.method}.`);
505
+ }
506
+ if (params.mode === "url") {
507
+ if (!io.isTTY)
508
+ throw unavailable(message, " Run it in an interactive terminal.");
509
+ await ask(`${message}\n ${String(params.url)}\nPress Enter when done. `);
510
+ out[key] = { action: "accept" };
511
+ continue;
512
+ }
513
+ const schema = isRecord(params.requestedSchema) ? params.requestedSchema : { type: "object", properties: {} };
514
+ const confirmKey = confirmField(schema);
515
+ if (confirmKey !== undefined) {
516
+ if (yes) {
517
+ out[key] = { action: "accept", content: { [confirmKey]: true } };
518
+ continue;
519
+ }
520
+ if (!io.isTTY)
521
+ throw unavailable(message, " Pass --yes to approve.");
522
+ const answer = await ask(`${message} [y/N] `);
523
+ out[key] = { action: "accept", content: { [confirmKey]: /^y(es)?$/i.test(answer.trim()) } };
524
+ continue;
525
+ }
526
+ if (!io.isTTY)
527
+ throw unavailable(message, " Run it in an interactive terminal.");
528
+ io.stderr.write(`${message}\n`);
529
+ const spec = cliSpecFromJsonSchema(schema);
530
+ const content = {};
531
+ for (const f of spec.fields) {
532
+ const answer = await ask(` ${f.key}${f.required ? "" : " (optional)"}: `);
533
+ if (answer === "")
534
+ continue;
535
+ if (f.kind.type === "array") {
536
+ const kind = f.kind;
537
+ content[f.key] = answer.split(",").map((s) => {
538
+ const r = coerceValue(kind, s.trim());
539
+ return r.ok ? r.value : s;
540
+ });
541
+ continue;
542
+ }
543
+ const r = coerceValue(f.kind, answer);
544
+ content[f.key] = r.ok ? r.value : answer;
545
+ }
546
+ out[key] = { action: "accept", content };
547
+ }
548
+ return out;
549
+ };
550
+ }
551
+ /** requestedSchema 의 속성이 boolean 하나면 그 키 (확인 요청) */
552
+ function confirmField(schema) {
553
+ const props = schema.properties;
554
+ if (!isRecord(props))
555
+ return undefined;
556
+ const keys = Object.keys(props);
557
+ if (keys.length !== 1)
558
+ return undefined;
559
+ const p = props[keys[0]];
560
+ return isRecord(p) && p.type === "boolean" ? keys[0] : undefined;
561
+ }
562
+ //# sourceMappingURL=runner.js.map