@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,621 @@
1
+ import { MextBuildError, MextError } from "../errors.js";
2
+ import { closest } from "../suggest.js";
3
+ /** mext run 이 명령어 플래그 위치에서도 가로채는 플래그. input 키와 겹치면 빌드 오류 */
4
+ export const RESERVED_FLAGS = ["help", "json", "yes", "input-json"];
5
+ const WRAPPERS = new Set(["optional", "nullable", "default", "prefault", "catch", "readonly", "nonoptional"]);
6
+ const UNCONVERTIBLE = new Set([
7
+ "any",
8
+ "unknown",
9
+ "function",
10
+ "promise",
11
+ "symbol",
12
+ "custom",
13
+ "transform",
14
+ "never",
15
+ "void",
16
+ "undefined",
17
+ "bigint",
18
+ "date",
19
+ "map",
20
+ "set",
21
+ "file",
22
+ ]);
23
+ function defOf(schema) {
24
+ return schema._zod.def;
25
+ }
26
+ /** optional · nullable · default · pipe (입력 쪽) · lazy 등을 벗긴 실제 타입 */
27
+ function unwrap(schema) {
28
+ let cur = schema;
29
+ for (let depth = 0; depth < 32; depth++) {
30
+ const def = defOf(cur);
31
+ if (WRAPPERS.has(def.type) && def.innerType)
32
+ cur = def.innerType;
33
+ else if (def.type === "pipe" && def.in && def.out)
34
+ cur = defOf(def.in).type === "transform" ? def.out : def.in;
35
+ else if (def.type === "lazy" && def.getter)
36
+ cur = def.getter();
37
+ else
38
+ return cur;
39
+ }
40
+ return cur;
41
+ }
42
+ function scalarOf(schema) {
43
+ const base = unwrap(schema);
44
+ const def = defOf(base);
45
+ switch (def.type) {
46
+ case "string":
47
+ case "template_literal":
48
+ return { type: "string" };
49
+ case "number":
50
+ return { type: "number" };
51
+ case "boolean":
52
+ return { type: "boolean" };
53
+ case "enum":
54
+ case "literal":
55
+ case "union": {
56
+ const values = literalValues(base);
57
+ return values ? { type: "enum", values } : undefined;
58
+ }
59
+ }
60
+ if (UNCONVERTIBLE.has(def.type))
61
+ return { type: "unconvertible", zodType: def.type };
62
+ return undefined;
63
+ }
64
+ /** enum · 리터럴 · 리터럴만의 union → 값 목록. 그 외 undefined */
65
+ function literalValues(schema) {
66
+ const def = defOf(unwrap(schema));
67
+ let raw;
68
+ if (def.type === "enum")
69
+ raw = Object.values(def.entries ?? {});
70
+ else if (def.type === "literal")
71
+ raw = [...(def.values ?? [])];
72
+ else if (def.type === "union") {
73
+ raw = [];
74
+ for (const option of def.options ?? []) {
75
+ const values = literalValues(option);
76
+ if (!values)
77
+ return undefined;
78
+ raw.push(...values);
79
+ }
80
+ }
81
+ else
82
+ return undefined;
83
+ if (!raw.every((v) => typeof v === "string" || typeof v === "number" || typeof v === "boolean"))
84
+ return undefined;
85
+ return [...new Set(raw)];
86
+ }
87
+ function analyze(schema, topLevel) {
88
+ const scalar = scalarOf(schema);
89
+ if (scalar)
90
+ return scalar;
91
+ const base = unwrap(schema);
92
+ const def = defOf(base);
93
+ if (def.type === "array" && def.element) {
94
+ const item = scalarOf(def.element);
95
+ return item && item.type !== "unconvertible" ? { type: "array", item } : { type: "json" };
96
+ }
97
+ if (def.type === "object" && topLevel) {
98
+ const fields = [];
99
+ for (const [key, sub] of Object.entries(def.shape ?? {})) {
100
+ const kind = analyze(sub, false);
101
+ if (kind.type === "unconvertible" || kind.type === "json" || kind.type === "object")
102
+ return { type: "json" };
103
+ fields.push(field(key, kind, undefined));
104
+ }
105
+ return { type: "object", fields };
106
+ }
107
+ return { type: "json" };
108
+ }
109
+ function field(key, kind, json, required = false) {
110
+ const has = json !== undefined && Object.hasOwn(json, "default");
111
+ return {
112
+ key,
113
+ flag: toKebab(key),
114
+ kind,
115
+ description: typeof json?.description === "string" ? json.description : undefined,
116
+ required,
117
+ hasDefault: has,
118
+ default: has ? json.default : undefined,
119
+ };
120
+ }
121
+ /** camelCase → kebab-case (userId → user-id · URLPath → url-path) */
122
+ export function toKebab(key) {
123
+ return key
124
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
125
+ .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2")
126
+ .toLowerCase();
127
+ }
128
+ const FLAG_PATTERN = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
129
+ /** JSON Schema 의 객체 (nullable 이면 anyOf 안) */
130
+ function objectSchema(json) {
131
+ if (!json)
132
+ return undefined;
133
+ if (typeof json.properties === "object" && json.properties !== null)
134
+ return json;
135
+ for (const key of ["anyOf", "oneOf"]) {
136
+ const list = json[key];
137
+ if (Array.isArray(list)) {
138
+ const found = list.find((s) => typeof s === "object" && s !== null && "properties" in s);
139
+ if (found)
140
+ return found;
141
+ }
142
+ }
143
+ return undefined;
144
+ }
145
+ function propertiesOf(json) {
146
+ const obj = objectSchema(json);
147
+ return {
148
+ props: obj?.properties ?? {},
149
+ required: Array.isArray(obj?.required) ? obj.required : [],
150
+ };
151
+ }
152
+ /**
153
+ * input 스키마 → CLI 필드. problems = 변환 불가 (any · 함수 등) · 플래그로 쓸 수 없는 키 · 플래그 충돌 · 예약 플래그.
154
+ * jsonSchema = input JSON Schema (io input). 설명 · 기본값 · 필수 여부의 원본
155
+ */
156
+ export function analyzeInput(schema, jsonSchema, positional = []) {
157
+ const problems = [];
158
+ const fields = [];
159
+ const { props, required } = propertiesOf(jsonSchema);
160
+ const shape = schema._zod.def.shape ?? {};
161
+ const flags = new Map();
162
+ for (const [key, sub] of Object.entries(shape)) {
163
+ const kind = analyze(sub, true);
164
+ if (kind.type === "unconvertible") {
165
+ problems.push(`input "${key}": z.${kind.zodType}() is not convertible to CLI flags`);
166
+ continue;
167
+ }
168
+ const f = field(key, kind, props[key], required.includes(key));
169
+ if (!FLAG_PATTERN.test(f.flag)) {
170
+ problems.push(`input "${key}": key cannot be a CLI flag (use camelCase letters and digits)`);
171
+ continue;
172
+ }
173
+ if (RESERVED_FLAGS.includes(f.flag)) {
174
+ problems.push(`input "${key}": --${f.flag} is reserved by the CLI`);
175
+ continue;
176
+ }
177
+ const other = flags.get(f.flag);
178
+ if (other !== undefined) {
179
+ problems.push(`input "${key}": --${f.flag} collides with input "${other}"`);
180
+ continue;
181
+ }
182
+ if (kind.type === "object") {
183
+ const sub = propertiesOf(props[key]);
184
+ const subFlags = new Set();
185
+ const subs = [];
186
+ for (const s of kind.fields) {
187
+ const sf = field(s.key, s.kind, sub.props[s.key], sub.required.includes(s.key));
188
+ if (!FLAG_PATTERN.test(sf.flag)) {
189
+ problems.push(`input "${key}.${s.key}": key cannot be a CLI flag (use camelCase letters and digits)`);
190
+ continue;
191
+ }
192
+ if (subFlags.has(sf.flag)) {
193
+ problems.push(`input "${key}.${s.key}": --${f.flag}.${sf.flag} collides with another key`);
194
+ continue;
195
+ }
196
+ subFlags.add(sf.flag);
197
+ subs.push(sf);
198
+ }
199
+ f.kind = { type: "object", fields: subs };
200
+ }
201
+ flags.set(f.flag, key);
202
+ fields.push(f);
203
+ }
204
+ // --no-x 와 같은 이름의 플래그가 있으면 모호
205
+ for (const f of fields) {
206
+ if (f.flag.startsWith("no-") && isBooleanKind(fields.find((g) => g.flag === f.flag.slice(3))?.kind)) {
207
+ problems.push(`input "${f.key}": --${f.flag} collides with the negation of --${f.flag.slice(3)}`);
208
+ }
209
+ }
210
+ return { spec: { fields, positional: [...positional] }, problems };
211
+ }
212
+ function isBooleanKind(kind) {
213
+ return kind?.type === "boolean" || (kind?.type === "array" && kind.item.type === "boolean");
214
+ }
215
+ const specCache = new WeakMap();
216
+ function analyzed(entry) {
217
+ let result = specCache.get(entry);
218
+ if (!result) {
219
+ result = analyzeInput(entry.command.input, entry.inputSchema, entry.command.positional);
220
+ specCache.set(entry, result);
221
+ }
222
+ return result;
223
+ }
224
+ /**
225
+ * Registry 밖 명령어 (원격 help 로 만든 항목) 의 CLI 필드 지정. 이후 cliSpec · parseArgs · help 가 이 값을 쓴다.
226
+ * 로컬 명령어는 Zod 스키마로 분석하므로 쓰지 않는다
227
+ */
228
+ export function defineCliSpec(entry, spec) {
229
+ specCache.set(entry, { spec, problems: [] });
230
+ }
231
+ // ---------------------------------------------------------------- JSON Schema 분석 (원격)
232
+ /**
233
+ * input JSON Schema (원격 help 의 inputSchema) → CLI 필드. 로컬 (Zod) 분석과 같은 규칙을 JSON Schema 모양으로:
234
+ * type string · number · integer · boolean · enum · const · 리터럴 anyOf = 원시, nullable (anyOf null) 은 벗김,
235
+ * 원시 배열 = 반복, 최상위 1단 객체 (하위 전부 원시 · 원시 배열) = 점 플래그, 그 외 = JSON 문자열.
236
+ * 플래그로 쓸 수 없는 키 · 예약 플래그 · 충돌 키는 건너뛴다 (--input-json 으로 전달)
237
+ */
238
+ export function cliSpecFromJsonSchema(jsonSchema, positional = []) {
239
+ const { props, required } = propertiesOf(jsonSchema);
240
+ const fields = [];
241
+ const flags = new Set();
242
+ for (const [key, sub] of Object.entries(props)) {
243
+ const kind = jsonKind(sub, true);
244
+ const f = field(key, kind, sub, required.includes(key));
245
+ if (!FLAG_PATTERN.test(f.flag) || RESERVED_FLAGS.includes(f.flag) || flags.has(f.flag))
246
+ continue;
247
+ if (kind.type === "object") {
248
+ const subProps = propertiesOf(stripNullable(sub));
249
+ const subs = [];
250
+ const subFlags = new Set();
251
+ for (const s of kind.fields) {
252
+ const sf = field(s.key, s.kind, subProps.props[s.key], subProps.required.includes(s.key));
253
+ if (!FLAG_PATTERN.test(sf.flag) || subFlags.has(sf.flag))
254
+ continue;
255
+ subFlags.add(sf.flag);
256
+ subs.push(sf);
257
+ }
258
+ f.kind = { type: "object", fields: subs };
259
+ }
260
+ flags.add(f.flag);
261
+ fields.push(f);
262
+ }
263
+ return { fields, positional: positional.filter((k) => fields.some((f) => f.key === k)) };
264
+ }
265
+ function isJsonObject(value) {
266
+ return typeof value === "object" && value !== null && !Array.isArray(value);
267
+ }
268
+ function isPrimitive(value) {
269
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
270
+ }
271
+ /** anyOf · oneOf 에서 { type: "null" } 을 뺀 나머지가 하나면 그것, type 배열의 "null" 제거 */
272
+ function stripNullable(schema) {
273
+ for (const key of ["anyOf", "oneOf"]) {
274
+ const list = schema[key];
275
+ if (!Array.isArray(list))
276
+ continue;
277
+ const rest = list.filter((s) => !(isJsonObject(s) && s.type === "null"));
278
+ if (rest.length === 1 && rest.length < list.length && isJsonObject(rest[0]))
279
+ return rest[0];
280
+ }
281
+ if (Array.isArray(schema.type)) {
282
+ const types = schema.type.filter((t) => t !== "null");
283
+ if (types.length === 1)
284
+ return { ...schema, type: types[0] };
285
+ }
286
+ return schema;
287
+ }
288
+ function jsonLiterals(schema) {
289
+ if (Array.isArray(schema.enum))
290
+ return schema.enum.every(isPrimitive) ? [...new Set(schema.enum)] : undefined;
291
+ if ("const" in schema)
292
+ return isPrimitive(schema.const) ? [schema.const] : undefined;
293
+ for (const key of ["anyOf", "oneOf"]) {
294
+ const list = schema[key];
295
+ if (!Array.isArray(list))
296
+ continue;
297
+ const out = [];
298
+ for (const option of list) {
299
+ const values = isJsonObject(option) ? jsonLiterals(option) : undefined;
300
+ if (!values)
301
+ return undefined;
302
+ out.push(...values);
303
+ }
304
+ return [...new Set(out)];
305
+ }
306
+ return undefined;
307
+ }
308
+ function jsonScalar(raw) {
309
+ if (!isJsonObject(raw))
310
+ return undefined;
311
+ const schema = stripNullable(raw);
312
+ const values = jsonLiterals(schema);
313
+ if (values)
314
+ return { type: "enum", values };
315
+ switch (schema.type) {
316
+ case "string":
317
+ return { type: "string" };
318
+ case "number":
319
+ case "integer":
320
+ return { type: "number" };
321
+ case "boolean":
322
+ return { type: "boolean" };
323
+ }
324
+ return undefined;
325
+ }
326
+ function jsonKind(raw, topLevel) {
327
+ const scalar = jsonScalar(raw);
328
+ if (scalar)
329
+ return scalar;
330
+ if (!isJsonObject(raw))
331
+ return { type: "json" };
332
+ const schema = stripNullable(raw);
333
+ if (schema.type === "array" && isJsonObject(schema.items)) {
334
+ const item = jsonScalar(schema.items);
335
+ return item ? { type: "array", item } : { type: "json" };
336
+ }
337
+ if (topLevel && schema.type === "object" && isJsonObject(schema.properties)) {
338
+ const fields = [];
339
+ for (const [key, sub] of Object.entries(schema.properties)) {
340
+ const kind = jsonKind(sub, false);
341
+ if (kind.type === "json" || kind.type === "object")
342
+ return { type: "json" };
343
+ fields.push(field(key, kind, undefined));
344
+ }
345
+ return { type: "object", fields };
346
+ }
347
+ return { type: "json" };
348
+ }
349
+ /** 명령어의 CLI 필드 (캐시) */
350
+ export function cliSpec(entry) {
351
+ return analyzed(entry).spec;
352
+ }
353
+ /** Registry 전체의 CLI 변환 불가 문제 (빌드 오류 issues) */
354
+ export function cliBuildIssues(registry) {
355
+ const issues = [];
356
+ for (const entry of registry.commands.values()) {
357
+ for (const message of analyzed(entry).problems) {
358
+ issues.push({ code: "CLI_UNCONVERTIBLE", message, id: entry.id, file: entry.file });
359
+ }
360
+ }
361
+ return issues;
362
+ }
363
+ /** 변환 불가 타입이 있으면 MextBuildError */
364
+ export function assertCliConvertible(registry) {
365
+ const issues = cliBuildIssues(registry);
366
+ if (issues.length > 0)
367
+ throw new MextBuildError(issues);
368
+ }
369
+ function resolveFlag(spec, name) {
370
+ const top = spec.fields.find((f) => f.flag === name);
371
+ if (top)
372
+ return { field: top, negated: false };
373
+ const dot = name.indexOf(".");
374
+ if (dot > 0) {
375
+ const parent = spec.fields.find((f) => f.flag === name.slice(0, dot));
376
+ if (parent?.kind.type === "object") {
377
+ const sub = parent.kind.fields.find((f) => f.flag === name.slice(dot + 1));
378
+ if (sub)
379
+ return { field: parent, sub, negated: false };
380
+ }
381
+ }
382
+ if (name.startsWith("no-")) {
383
+ const r = resolveFlag(spec, name.slice(3));
384
+ if (r && !r.negated && isBooleanKind((r.sub ?? r.field).kind))
385
+ return { ...r, negated: true };
386
+ }
387
+ return undefined;
388
+ }
389
+ /** 사용 가능한 플래그 이름 전체 (제안용) */
390
+ function allFlags(spec) {
391
+ const out = [];
392
+ for (const f of spec.fields) {
393
+ out.push(f.flag);
394
+ if (isBooleanKind(f.kind))
395
+ out.push(`no-${f.flag}`);
396
+ if (f.kind.type === "object") {
397
+ for (const s of f.kind.fields) {
398
+ out.push(`${f.flag}.${s.flag}`);
399
+ if (isBooleanKind(s.kind))
400
+ out.push(`no-${f.flag}.${s.flag}`);
401
+ }
402
+ }
403
+ }
404
+ return [...out, ...RESERVED_FLAGS];
405
+ }
406
+ function coerceScalar(kind, raw) {
407
+ switch (kind.type) {
408
+ case "string":
409
+ return raw;
410
+ case "number": {
411
+ const n = Number(raw);
412
+ return raw.trim() !== "" && Number.isFinite(n) ? n : raw;
413
+ }
414
+ case "boolean":
415
+ return raw === "true" ? true : raw === "false" ? false : raw;
416
+ case "enum":
417
+ return kind.values.find((v) => String(v) === raw) ?? raw;
418
+ }
419
+ }
420
+ /** 원시 · 배열 · 객체 · JSON 필드 종류에 맞춘 문자열 변환. JSON 파싱 실패 = undefined (호출자가 오류) */
421
+ export function coerceValue(kind, raw) {
422
+ if (kind.type === "array")
423
+ return { ok: true, value: coerceScalar(kind.item, raw) };
424
+ if (kind.type === "object" || kind.type === "json") {
425
+ try {
426
+ return { ok: true, value: JSON.parse(raw) };
427
+ }
428
+ catch {
429
+ return { ok: false };
430
+ }
431
+ }
432
+ return { ok: true, value: coerceScalar(kind, raw) };
433
+ }
434
+ function isPlainObject(value) {
435
+ return typeof value === "object" && value !== null && !Array.isArray(value);
436
+ }
437
+ /** 파서 단계 INVALID_ARGUMENT */
438
+ export function argumentError(entry, issues) {
439
+ return new MextError("INVALID_ARGUMENT", `Invalid arguments for ${entry.id}: ${issues.map((i) => i.message).join("; ")}`, {
440
+ command: entry.id,
441
+ missing_fields: [],
442
+ issues,
443
+ expected_schema: entry.inputSchema,
444
+ examples: [...entry.command.examples],
445
+ });
446
+ }
447
+ /**
448
+ * 명령어 경로 뒤의 argv → 원본 입력.
449
+ * --flag v · --flag=v · boolean --x · --x=true|false · --no-x · 반복 (배열) · --obj.key v · JSON 문자열 ·
450
+ * --input-json <json|-> · - (stdin) · positional · -- 이후 = positional. 모르는 플래그는 항상 오류.
451
+ * 개별 플래그는 --input-json · stdin 입력의 같은 키를 덮어쓴다 (점 플래그는 객체 안에 병합).
452
+ */
453
+ export async function parseArgs(entry, argv, options = {}) {
454
+ const spec = cliSpec(entry);
455
+ const fail = (path, code, message) => {
456
+ throw argumentError(entry, [{ path, code, message }]);
457
+ };
458
+ const values = {};
459
+ const given = new Set(); // 지정된 경로 (배열 제외). "filter" = JSON 전체 · "filter.status" = 점 플래그
460
+ const flags = { json: false, yes: false, help: false };
461
+ const positionals = [];
462
+ let source;
463
+ let rest = false;
464
+ const setInput = (next, label) => {
465
+ if (source)
466
+ fail("", "duplicate_input", `${label}: whole input given more than once (--input-json · -)`);
467
+ source = next;
468
+ };
469
+ const assign = (r, value, label) => {
470
+ const target = r.sub ?? r.field;
471
+ const path = r.sub ? `${r.field.key}.${r.sub.key}` : r.field.key;
472
+ if (r.sub) {
473
+ if (given.has(r.field.key))
474
+ fail(path, "conflict", `${label} cannot be combined with --${r.field.flag} <json>`);
475
+ }
476
+ else if (r.field.kind.type === "object" && [...given].some((p) => p.startsWith(`${r.field.key}.`))) {
477
+ fail(path, "conflict", `${label} <json> cannot be combined with --${r.field.flag}.<key>`);
478
+ }
479
+ let container = values;
480
+ if (r.sub) {
481
+ const existing = values[r.field.key];
482
+ if (!isPlainObject(existing))
483
+ values[r.field.key] = {};
484
+ container = values[r.field.key];
485
+ }
486
+ if (target.kind.type === "array") {
487
+ const list = container[target.key];
488
+ if (Array.isArray(list))
489
+ list.push(value);
490
+ else
491
+ container[target.key] = [value];
492
+ return;
493
+ }
494
+ if (given.has(path))
495
+ fail(path, "duplicate_flag", `${label} given more than once`);
496
+ given.add(path);
497
+ container[target.key] = value;
498
+ };
499
+ const coerce = (r, raw, label) => {
500
+ const kind = r.sub ? r.sub.kind : r.field.kind;
501
+ const result = coerceValue(kind, raw);
502
+ if (!result.ok) {
503
+ return fail(r.sub ? `${r.field.key}.${r.sub.key}` : r.field.key, "invalid_json", `${label}: value is not valid JSON`);
504
+ }
505
+ return result.value;
506
+ };
507
+ for (let i = 0; i < argv.length; i++) {
508
+ const tok = argv[i];
509
+ if (rest) {
510
+ positionals.push(tok);
511
+ continue;
512
+ }
513
+ if (tok === "--") {
514
+ rest = true;
515
+ continue;
516
+ }
517
+ if (tok === "-") {
518
+ setInput({ kind: "stdin" }, "-");
519
+ continue;
520
+ }
521
+ if (tok === "-h") {
522
+ flags.help = true;
523
+ continue;
524
+ }
525
+ if (tok.startsWith("--")) {
526
+ const body = tok.slice(2);
527
+ const eq = body.indexOf("=");
528
+ const name = eq < 0 ? body : body.slice(0, eq);
529
+ const inline = eq < 0 ? undefined : body.slice(eq + 1);
530
+ const label = `--${name}`;
531
+ const takeValue = () => {
532
+ if (inline !== undefined)
533
+ return inline;
534
+ const next = argv[i + 1];
535
+ if (next === undefined || (next.startsWith("--") && next.length > 2)) {
536
+ return fail("", "missing_value", `${label} requires a value`);
537
+ }
538
+ i++;
539
+ return next;
540
+ };
541
+ if (name === "help" || name === "json" || name === "yes") {
542
+ if (inline !== undefined)
543
+ fail("", "unexpected_value", `${label} does not take a value`);
544
+ flags[name] = true;
545
+ continue;
546
+ }
547
+ if (name === "input-json") {
548
+ const text = takeValue();
549
+ setInput(text === "-" ? { kind: "stdin" } : { kind: "json", text }, label);
550
+ continue;
551
+ }
552
+ const r = resolveFlag(spec, name);
553
+ if (!r) {
554
+ const suggested = closest(name, allFlags(spec).map((f) => [f, f]));
555
+ const hint = suggested.length > 0 ? ` Did you mean --${suggested[0]}?` : "";
556
+ return fail("", "unknown_flag", `unknown flag ${label}.${hint}`);
557
+ }
558
+ const kind = (r.sub ?? r.field).kind;
559
+ if (isBooleanKind(kind)) {
560
+ if (r.negated) {
561
+ if (inline !== undefined)
562
+ fail("", "unexpected_value", `${label} does not take a value`);
563
+ assign(r, false, label);
564
+ }
565
+ else {
566
+ assign(r, inline === undefined ? true : coerceScalar({ type: "boolean" }, inline), label);
567
+ }
568
+ continue;
569
+ }
570
+ assign(r, coerce(r, takeValue(), label), label);
571
+ continue;
572
+ }
573
+ if (tok.startsWith("-") && !/^-[\d.]/.test(tok)) {
574
+ fail("", "unknown_flag", `unknown flag ${tok} (short flags are not supported)`);
575
+ }
576
+ positionals.push(tok);
577
+ }
578
+ // positional
579
+ let p = 0;
580
+ for (let k = 0; k < spec.positional.length && p < positionals.length; k++) {
581
+ const f = spec.fields.find((x) => x.key === spec.positional[k]);
582
+ if (!f)
583
+ continue;
584
+ const r = { field: f, negated: false };
585
+ const label = `<${f.flag}>`;
586
+ const variadic = f.kind.type === "array" && k === spec.positional.length - 1;
587
+ do {
588
+ assign(r, coerce(r, positionals[p], label), label);
589
+ p++;
590
+ } while (variadic && p < positionals.length);
591
+ }
592
+ if (p < positionals.length)
593
+ fail("", "unexpected_argument", `unexpected argument "${positionals[p]}"`);
594
+ let input = values;
595
+ if (source) {
596
+ const text = source.kind === "stdin" ? await (options.readStdin ?? (async () => ""))() : source.text;
597
+ let base;
598
+ try {
599
+ base = JSON.parse(text);
600
+ }
601
+ catch {
602
+ return fail("", "invalid_json", `${source.kind === "stdin" ? "stdin" : "--input-json"} is not valid JSON`);
603
+ }
604
+ if (Object.keys(values).length === 0)
605
+ input = base;
606
+ else if (!isPlainObject(base)) {
607
+ fail("", "invalid_json", "whole input must be a JSON object to combine with flags");
608
+ }
609
+ else {
610
+ const merged = { ...base };
611
+ for (const [key, value] of Object.entries(values)) {
612
+ // 점 플래그로 모은 객체는 병합, 그 외 (JSON 문자열 포함) 는 덮어쓰기
613
+ const dotted = !given.has(key) && spec.fields.find((x) => x.key === key)?.kind.type === "object";
614
+ merged[key] = dotted && isPlainObject(base[key]) && isPlainObject(value) ? { ...base[key], ...value } : value;
615
+ }
616
+ input = merged;
617
+ }
618
+ }
619
+ return { input, ...flags };
620
+ }
621
+ //# sourceMappingURL=args.js.map