@leeguoo/yapi-mcp 0.3.26 → 0.4.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 (81) hide show
  1. package/dist/cli/commands/col.d.ts +2 -0
  2. package/dist/cli/commands/col.js +80 -0
  3. package/dist/cli/commands/col.js.map +1 -0
  4. package/dist/cli/commands/docs-sync.d.ts +3 -0
  5. package/dist/cli/commands/docs-sync.js +1011 -0
  6. package/dist/cli/commands/docs-sync.js.map +1 -0
  7. package/dist/cli/commands/env.d.ts +2 -0
  8. package/dist/cli/commands/env.js +13 -0
  9. package/dist/cli/commands/env.js.map +1 -0
  10. package/dist/cli/commands/export.d.ts +2 -0
  11. package/dist/cli/commands/export.js +86 -0
  12. package/dist/cli/commands/export.js.map +1 -0
  13. package/dist/cli/commands/follow.d.ts +2 -0
  14. package/dist/cli/commands/follow.js +8 -0
  15. package/dist/cli/commands/follow.js.map +1 -0
  16. package/dist/cli/commands/group.d.ts +2 -0
  17. package/dist/cli/commands/group.js +22 -0
  18. package/dist/cli/commands/group.js.map +1 -0
  19. package/dist/cli/commands/install-skill.d.ts +1 -0
  20. package/dist/cli/commands/install-skill.js +9 -0
  21. package/dist/cli/commands/install-skill.js.map +1 -0
  22. package/dist/cli/commands/interface.d.ts +2 -0
  23. package/dist/cli/commands/interface.js +99 -0
  24. package/dist/cli/commands/interface.js.map +1 -0
  25. package/dist/cli/commands/log.d.ts +2 -0
  26. package/dist/cli/commands/log.js +31 -0
  27. package/dist/cli/commands/log.js.map +1 -0
  28. package/dist/cli/commands/login.d.ts +2 -0
  29. package/dist/cli/commands/login.js +90 -0
  30. package/dist/cli/commands/login.js.map +1 -0
  31. package/dist/cli/commands/logout.d.ts +2 -0
  32. package/dist/cli/commands/logout.js +25 -0
  33. package/dist/cli/commands/logout.js.map +1 -0
  34. package/dist/cli/commands/member.d.ts +2 -0
  35. package/dist/cli/commands/member.js +25 -0
  36. package/dist/cli/commands/member.js.map +1 -0
  37. package/dist/cli/commands/project.d.ts +2 -0
  38. package/dist/cli/commands/project.js +49 -0
  39. package/dist/cli/commands/project.js.map +1 -0
  40. package/dist/cli/commands/request.d.ts +2 -0
  41. package/dist/cli/commands/request.js +189 -0
  42. package/dist/cli/commands/request.js.map +1 -0
  43. package/dist/cli/commands/search.d.ts +2 -0
  44. package/dist/cli/commands/search.js +39 -0
  45. package/dist/cli/commands/search.js.map +1 -0
  46. package/dist/cli/commands/self-update.d.ts +1 -0
  47. package/dist/cli/commands/self-update.js +19 -0
  48. package/dist/cli/commands/self-update.js.map +1 -0
  49. package/dist/cli/commands/user.d.ts +2 -0
  50. package/dist/cli/commands/user.js +27 -0
  51. package/dist/cli/commands/user.js.map +1 -0
  52. package/dist/cli/commands/whoami.d.ts +2 -0
  53. package/dist/cli/commands/whoami.js +8 -0
  54. package/dist/cli/commands/whoami.js.map +1 -0
  55. package/dist/cli/http.d.ts +9 -0
  56. package/dist/cli/http.js +44 -0
  57. package/dist/cli/http.js.map +1 -0
  58. package/dist/cli/simple-request.d.ts +2 -0
  59. package/dist/cli/simple-request.js +135 -0
  60. package/dist/cli/simple-request.js.map +1 -0
  61. package/dist/cli/types.d.ts +161 -0
  62. package/dist/cli/types.js +3 -0
  63. package/dist/cli/types.js.map +1 -0
  64. package/dist/cli/utils.d.ts +53 -0
  65. package/dist/cli/utils.js +610 -0
  66. package/dist/cli/utils.js.map +1 -0
  67. package/dist/yapi-cli.js +441 -3134
  68. package/dist/yapi-cli.js.map +1 -1
  69. package/package.json +12 -25
  70. package/dist/cli.d.ts +0 -2
  71. package/dist/cli.js +0 -32
  72. package/dist/cli.js.map +0 -1
  73. package/dist/config.d.ts +0 -28
  74. package/dist/config.js +0 -240
  75. package/dist/config.js.map +0 -1
  76. package/dist/index.d.ts +0 -5
  77. package/dist/index.js +0 -66
  78. package/dist/index.js.map +0 -1
  79. package/dist/server.d.ts +0 -28
  80. package/dist/server.js +0 -1004
  81. package/dist/server.js.map +0 -1
package/dist/yapi-cli.js CHANGED
@@ -4,2120 +4,107 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const crypto_1 = __importDefault(require("crypto"));
8
- const child_process_1 = require("child_process");
9
- const fs_1 = __importDefault(require("fs"));
10
- const os_1 = __importDefault(require("os"));
11
- const path_1 = __importDefault(require("path"));
12
- const readline_1 = __importDefault(require("readline"));
13
- const markdown_1 = require("./docs/markdown");
14
- const install_1 = require("./skill/install");
7
+ const yargs_1 = __importDefault(require("yargs"));
8
+ const helpers_1 = require("yargs/helpers");
9
+ const utils_1 = require("./cli/utils");
10
+ const http_1 = require("./cli/http");
15
11
  const metadata_1 = require("./skill/metadata");
16
- const auth_1 = require("./services/yapi/auth");
17
- const authCache_1 = require("./services/yapi/authCache");
18
- class HttpStatusError extends Error {
19
- status;
20
- statusText;
21
- body;
22
- endpoint;
23
- constructor(endpoint, status, statusText, body) {
24
- super(`request failed: ${status} ${statusText} ${body}`);
25
- this.name = "HttpStatusError";
26
- this.status = status;
27
- this.statusText = statusText;
28
- this.body = body;
29
- this.endpoint = endpoint;
30
- }
31
- }
32
- function parseKeyValue(raw) {
33
- if (!raw || !raw.includes("="))
34
- throw new Error("expected key=value");
35
- const idx = raw.indexOf("=");
36
- return [raw.slice(0, idx), raw.slice(idx + 1)];
37
- }
38
- function parseQueryArg(raw) {
39
- const trimmed = String(raw || "").trim().replace(/^\?/, "");
40
- if (!trimmed)
41
- throw new Error("expected key=value");
42
- if (!trimmed.includes("&"))
43
- return [parseKeyValue(trimmed)];
44
- const items = Array.from(new URLSearchParams(trimmed).entries()).filter(([key]) => Boolean(key));
45
- if (items.length)
46
- return items;
47
- return [parseKeyValue(trimmed)];
48
- }
49
- function parseHeader(raw) {
50
- if (!raw || !raw.includes(":"))
51
- throw new Error("expected Header:Value");
52
- const idx = raw.indexOf(":");
53
- return [raw.slice(0, idx).trim(), raw.slice(idx + 1).trim()];
54
- }
55
- function parseJsonMaybe(text) {
56
- try {
57
- return JSON.parse(text);
58
- }
59
- catch {
60
- return null;
61
- }
62
- }
63
- function formatBytes(bytes) {
64
- if (!Number.isFinite(bytes) || bytes <= 0)
65
- return "0 B";
66
- const units = ["B", "KiB", "MiB", "GiB"];
67
- let value = bytes;
68
- let index = 0;
69
- while (value >= 1024 && index < units.length - 1) {
70
- value /= 1024;
71
- index += 1;
72
- }
73
- return `${value >= 10 || index === 0 ? value.toFixed(0) : value.toFixed(2)} ${units[index]}`;
74
- }
75
- function parseByteSize(raw) {
76
- const text = String(raw || "");
77
- const bytesMatch = text.match(/(\d+)\s*bytes?/i);
78
- if (bytesMatch)
79
- return Number(bytesMatch[1]);
80
- const unitMatch = text.match(/(\d+(?:\.\d+)?)\s*(kib|kb|mib|mb|gib|gb)\b/i);
81
- if (!unitMatch)
82
- return null;
83
- const value = Number(unitMatch[1]);
84
- if (!Number.isFinite(value))
85
- return null;
86
- const unit = unitMatch[2].toLowerCase();
87
- const factors = {
88
- kb: 1000,
89
- kib: 1024,
90
- mb: 1000 * 1000,
91
- mib: 1024 * 1024,
92
- gb: 1000 * 1000 * 1000,
93
- gib: 1024 * 1024 * 1024,
94
- };
95
- return Math.round(value * (factors[unit] || 1));
96
- }
97
- function parsePayloadLimit(text) {
98
- const match = String(text || "").match(/(?:limit|max(?:imum)?(?:\s+body)?(?:\s+size)?)[^0-9]{0,12}(\d+(?:\.\d+)?\s*(?:bytes?|kib|kb|mib|mb|gib|gb))/i);
99
- if (match)
100
- return parseByteSize(match[1]);
101
- return parseByteSize(text);
102
- }
103
- function findGitRoot(startDir) {
104
- let current = path_1.default.resolve(startDir);
105
- while (true) {
106
- const candidate = path_1.default.join(current, ".git");
107
- if (fs_1.default.existsSync(candidate))
108
- return current;
109
- const parent = path_1.default.dirname(current);
110
- if (parent === current)
111
- return null;
112
- current = parent;
113
- }
114
- }
115
- function getPayloadMessage(payload) {
116
- if (!payload || typeof payload !== "object")
117
- return "";
118
- const record = payload;
119
- const message = record.errmsg ?? record.message ?? record.msg;
120
- return typeof message === "string" ? message : "";
121
- }
122
- function getPayloadErrcode(payload) {
123
- if (!payload || typeof payload !== "object")
124
- return null;
125
- const record = payload;
126
- const code = Number(record.errcode ?? record.code ?? record.errCode);
127
- return Number.isFinite(code) ? code : null;
128
- }
129
- function looksLikeAuthError(status, payload) {
130
- if (status === 401 || status === 403)
131
- return true;
132
- const code = getPayloadErrcode(payload);
133
- if (code !== null && [40011, 40012, 40013, 401, 403].includes(code))
134
- return true;
135
- const message = getPayloadMessage(payload);
136
- if (!message)
137
- return false;
138
- return /登录|login|权限|unauthori|forbidden|not\s*login|no\s*permission/i.test(message);
139
- }
140
- function joinUrl(baseUrl, endpoint) {
141
- if (!baseUrl)
142
- return endpoint;
143
- if (baseUrl.endsWith("/") && endpoint.startsWith("/"))
144
- return baseUrl.slice(0, -1) + endpoint;
145
- if (!baseUrl.endsWith("/") && !endpoint.startsWith("/"))
146
- return baseUrl + "/" + endpoint;
147
- return baseUrl + endpoint;
148
- }
149
- function globalConfigPath() {
150
- const yapiHome = process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi");
151
- return path_1.default.join(yapiHome, "config.toml");
152
- }
153
- function parseSimpleToml(text) {
154
- const data = {};
155
- const lines = String(text || "").split(/\r?\n/);
156
- for (const rawLine of lines) {
157
- const line = rawLine.split("#", 1)[0].split(";", 1)[0].trim();
158
- if (!line || line.startsWith("["))
159
- continue;
160
- const idx = line.indexOf("=");
161
- if (idx === -1)
162
- continue;
163
- const key = line.slice(0, idx).trim();
164
- let value = line.slice(idx + 1).trim();
165
- if (!key)
166
- continue;
167
- if ((value.startsWith('"') && value.endsWith('"')) ||
168
- (value.startsWith("'") && value.endsWith("'"))) {
169
- value = value.slice(1, -1);
170
- }
171
- data[key] = value;
172
- }
173
- return data;
174
- }
175
- function resolveToken(tokenValue, projectId) {
176
- if (!tokenValue)
177
- return "";
178
- if (tokenValue.includes(",") || tokenValue.includes(":")) {
179
- let defaultToken = "";
180
- const mapping = {};
181
- tokenValue.split(",").forEach((rawPair) => {
182
- const pair = rawPair.trim();
183
- if (!pair)
184
- return;
185
- const idx = pair.indexOf(":");
186
- if (idx === -1) {
187
- defaultToken = pair;
188
- return;
189
- }
190
- const pid = pair.slice(0, idx).trim();
191
- const token = pair.slice(idx + 1).trim();
192
- if (pid && token)
193
- mapping[pid] = token;
194
- });
195
- if (projectId && mapping[projectId])
196
- return mapping[projectId];
197
- if (defaultToken)
198
- return defaultToken;
199
- const keys = Object.keys(mapping);
200
- if (keys.length)
201
- return mapping[keys[0]];
202
- }
203
- return tokenValue;
204
- }
205
- function resolveLocalBin(binName) {
206
- const baseName = process.platform === "win32" ? `${binName}.cmd` : binName;
207
- const localBin = path_1.default.resolve(__dirname, "..", "node_modules", ".bin", baseName);
208
- if (fs_1.default.existsSync(localBin))
209
- return localBin;
210
- return binName;
211
- }
212
- function parseJsonLoose(text) {
213
- const raw = String(text || "").trim();
214
- if (!raw)
215
- return null;
216
- const direct = parseJsonMaybe(raw);
217
- if (direct !== null)
218
- return direct;
219
- const lines = raw
220
- .split(/\r?\n/)
221
- .map((line) => line.trim())
222
- .filter(Boolean);
223
- for (let i = lines.length - 1; i >= 0; i -= 1) {
224
- const parsed = parseJsonMaybe(lines[i]);
225
- if (parsed !== null)
226
- return parsed;
227
- }
228
- const firstBrace = raw.indexOf("{");
229
- const firstBracket = raw.indexOf("[");
230
- const starts = [firstBrace, firstBracket].filter((n) => n >= 0);
231
- if (!starts.length)
232
- return null;
233
- const start = Math.min(...starts);
234
- const lastBrace = raw.lastIndexOf("}");
235
- const lastBracket = raw.lastIndexOf("]");
236
- const end = Math.max(lastBrace, lastBracket);
237
- if (end <= start)
238
- return null;
239
- const sliced = raw.slice(start, end + 1);
240
- return parseJsonMaybe(sliced);
241
- }
242
- function extractCookiesFromPayload(payload) {
243
- const queue = [payload];
244
- const visited = new Set();
245
- while (queue.length) {
246
- const current = queue.shift();
247
- if (current === null || current === undefined)
248
- continue;
249
- if (visited.has(current))
250
- continue;
251
- visited.add(current);
252
- if (Array.isArray(current)) {
253
- if (current.every((item) => item && typeof item === "object")) {
254
- return current;
255
- }
256
- continue;
257
- }
258
- if (typeof current !== "object")
259
- continue;
260
- const obj = current;
261
- if (Array.isArray(obj.cookies))
262
- return obj.cookies;
263
- if (Array.isArray(obj.data))
264
- return obj.data;
265
- if (obj.data && typeof obj.data === "object")
266
- queue.push(obj.data);
267
- if (obj.result && typeof obj.result === "object")
268
- queue.push(obj.result);
269
- if (obj.payload && typeof obj.payload === "object")
270
- queue.push(obj.payload);
271
- }
272
- return [];
273
- }
274
- function normalizeCookieExpiresMs(raw) {
275
- const n = Number(raw);
276
- if (!Number.isFinite(n) || n <= 0)
277
- return undefined;
278
- return n > 1_000_000_000_000 ? n : n * 1000;
279
- }
280
- function runAgentBrowser(args, options = {}) {
281
- const bins = [
282
- resolveLocalBin("agent-browser-stealth"),
283
- resolveLocalBin("agent-browser"),
284
- "agent-browser-stealth",
285
- "agent-browser",
286
- ];
287
- const uniq = Array.from(new Set(bins));
288
- let lastError = null;
289
- for (const bin of uniq) {
290
- try {
291
- if (options.captureOutput) {
292
- const output = (0, child_process_1.execFileSync)(bin, args, {
293
- encoding: "utf8",
294
- stdio: ["pipe", "pipe", "pipe"],
295
- });
296
- return String(output || "");
297
- }
298
- (0, child_process_1.execFileSync)(bin, args, { stdio: "inherit" });
299
- return "";
300
- }
301
- catch (error) {
302
- const code = error.code;
303
- if (code === "ENOENT") {
304
- lastError = error;
305
- continue;
306
- }
307
- if (options.ignoreError)
308
- return "";
309
- throw error;
310
- }
311
- }
312
- if (lastError?.code === "ENOENT") {
313
- throw new Error("未找到 agent-browser-stealth。请先执行: pnpm -C packages/yapi-mcp add agent-browser-stealth && pnpm -C packages/yapi-mcp exec agent-browser-stealth install");
314
- }
315
- throw new Error("启动 agent-browser-stealth 失败");
316
- }
317
- async function loginByBrowserAndReadCookie(baseUrl, loginUrlOption) {
318
- const normalizedBase = String(baseUrl || "").replace(/\/+$/, "");
319
- const defaultLoginUrl = normalizedBase;
320
- let loginUrl = String(loginUrlOption || "").trim();
321
- if (!loginUrl) {
322
- const answer = await promptText(`YApi page URL [${defaultLoginUrl}]: `);
323
- loginUrl = answer.trim() || defaultLoginUrl;
324
- }
325
- try {
326
- const parsed = new URL(loginUrl);
327
- loginUrl = parsed.toString();
328
- }
329
- catch {
330
- throw new Error(`invalid login url: ${loginUrl}`);
331
- }
332
- const sessionName = `yapi-login-${Date.now()}-${crypto_1.default.randomBytes(4).toString("hex")}`;
333
- console.log(`opening browser for login: ${loginUrl}`);
334
- console.log(`browser session name: ${sessionName}`);
335
- runAgentBrowser(["open", loginUrl, "--headed", "--session-name", sessionName]);
336
- await promptText("请在浏览器完成登录,然后按回车继续...");
337
- let cookies = [];
338
- try {
339
- const output = runAgentBrowser(["cookies", "--json", "--session-name", sessionName], {
340
- captureOutput: true,
341
- });
342
- const payload = parseJsonLoose(output);
343
- cookies = extractCookiesFromPayload(payload);
344
- }
345
- finally {
346
- runAgentBrowser(["close", "--session-name", sessionName], { ignoreError: true });
347
- }
348
- if (!cookies.length) {
349
- throw new Error(`未读取到浏览器 cookie。请确认登录完成后再回车;如果仍失败,请先自检:\nagent-browser-stealth cookies --json --session-name ${sessionName}`);
350
- }
351
- const tokenCookie = cookies.find((item) => String(item?.name || "") === "_yapi_token");
352
- const uidCookie = cookies.find((item) => String(item?.name || "") === "_yapi_uid");
353
- const yapiToken = String(tokenCookie?.value || "").trim();
354
- if (!yapiToken) {
355
- throw new Error("未找到 _yapi_token,请确认登录站点是目标 YApi 域名");
356
- }
357
- const yapiUid = String(uidCookie?.value || "").trim() || undefined;
358
- const expiresAt = normalizeCookieExpiresMs(tokenCookie?.expiresAt) ?? normalizeCookieExpiresMs(tokenCookie?.expires);
359
- return { yapiToken, yapiUid, expiresAt };
360
- }
361
- async function fetchWithTimeout(url, options, timeoutMs) {
362
- const controller = new AbortController();
363
- const timer = setTimeout(() => controller.abort(), timeoutMs);
364
- try {
365
- return await fetch(url, { ...options, signal: controller.signal });
366
- }
367
- finally {
368
- clearTimeout(timer);
369
- }
370
- }
371
- function buildUrl(baseUrl, endpoint, queryItems, token, tokenParam) {
372
- const url = baseUrl ? joinUrl(baseUrl, endpoint) : endpoint;
373
- const parsed = new URL(url);
374
- for (const [key, value] of queryItems) {
375
- if (key)
376
- parsed.searchParams.append(key, value ?? "");
377
- }
378
- if (token && !parsed.searchParams.has(tokenParam)) {
379
- parsed.searchParams.append(tokenParam, token);
380
- }
381
- return parsed.toString();
382
- }
383
- function parseArgs(argv) {
384
- const options = {
385
- query: [],
386
- header: [],
387
- method: "GET",
388
- tokenParam: "token",
389
- timeout: 30000,
390
- };
391
- for (let i = 0; i < argv.length; i += 1) {
392
- const arg = argv[i];
393
- if (!arg)
394
- continue;
395
- if (arg === "-h" || arg === "--help") {
396
- options.help = true;
397
- continue;
398
- }
399
- if (arg === "-V" || arg === "--version") {
400
- options.version = true;
401
- continue;
402
- }
403
- if (arg === "--no-update" || arg === "--no-update-check") {
404
- options.noUpdate = true;
405
- continue;
406
- }
407
- if (arg === "--config") {
408
- options.config = argv[++i];
409
- continue;
410
- }
411
- if (arg.startsWith("--config=")) {
412
- options.config = arg.slice(9);
413
- continue;
414
- }
415
- if (arg === "--base-url") {
416
- options.baseUrl = argv[++i];
417
- continue;
418
- }
419
- if (arg.startsWith("--base-url=")) {
420
- options.baseUrl = arg.slice(11);
421
- continue;
422
- }
423
- if (arg === "--login-url") {
424
- options.loginUrl = argv[++i];
425
- continue;
426
- }
427
- if (arg.startsWith("--login-url=")) {
428
- options.loginUrl = arg.slice(12);
429
- continue;
430
- }
431
- if (arg === "--token") {
432
- options.token = argv[++i];
433
- continue;
434
- }
435
- if (arg.startsWith("--token=")) {
436
- options.token = arg.slice(8);
437
- continue;
438
- }
439
- if (arg === "--project-id") {
440
- options.projectId = argv[++i];
441
- continue;
442
- }
443
- if (arg.startsWith("--project-id=")) {
444
- options.projectId = arg.slice(13);
445
- continue;
446
- }
447
- if (arg === "--auth-mode") {
448
- options.authMode = argv[++i];
449
- continue;
450
- }
451
- if (arg.startsWith("--auth-mode=")) {
452
- options.authMode = arg.slice(12);
453
- continue;
454
- }
455
- if (arg === "--browser") {
456
- options.browser = true;
457
- continue;
458
- }
459
- if (arg === "--email") {
460
- options.email = argv[++i];
461
- continue;
462
- }
463
- if (arg.startsWith("--email=")) {
464
- options.email = arg.slice(8);
465
- continue;
466
- }
467
- if (arg === "--password") {
468
- options.password = argv[++i];
469
- continue;
470
- }
471
- if (arg.startsWith("--password=")) {
472
- options.password = arg.slice(11);
473
- continue;
474
- }
475
- if (arg === "--cookie") {
476
- options.cookie = argv[++i];
477
- continue;
478
- }
479
- if (arg.startsWith("--cookie=")) {
480
- options.cookie = arg.slice(9);
481
- continue;
482
- }
483
- if (arg === "--token-param") {
484
- options.tokenParam = argv[++i];
485
- continue;
486
- }
487
- if (arg.startsWith("--token-param=")) {
488
- options.tokenParam = arg.slice(14);
489
- continue;
490
- }
491
- if (arg === "--method") {
492
- options.method = argv[++i];
493
- continue;
494
- }
495
- if (arg.startsWith("--method=")) {
496
- options.method = arg.slice(9);
497
- continue;
498
- }
499
- if (arg === "--path") {
500
- options.path = argv[++i];
501
- continue;
502
- }
503
- if (arg.startsWith("--path=")) {
504
- options.path = arg.slice(7);
505
- continue;
506
- }
507
- if (arg === "--url") {
508
- options.url = argv[++i];
509
- continue;
510
- }
511
- if (arg.startsWith("--url=")) {
512
- options.url = arg.slice(6);
513
- continue;
514
- }
515
- if (arg === "--query") {
516
- options.query?.push(argv[++i]);
517
- continue;
518
- }
519
- if (arg.startsWith("--query=")) {
520
- options.query?.push(arg.slice(8));
521
- continue;
522
- }
523
- if (arg === "--q" || arg === "-q") {
524
- options.q = argv[++i];
525
- continue;
526
- }
527
- if (arg.startsWith("--q=")) {
528
- options.q = arg.slice(4);
529
- continue;
530
- }
531
- if (arg === "--header") {
532
- options.header?.push(argv[++i]);
533
- continue;
534
- }
535
- if (arg.startsWith("--header=")) {
536
- options.header?.push(arg.slice(9));
537
- continue;
538
- }
539
- if (arg === "--data") {
540
- options.data = argv[++i];
541
- continue;
542
- }
543
- if (arg.startsWith("--data=")) {
544
- options.data = arg.slice(7);
545
- continue;
546
- }
547
- if (arg === "--data-file") {
548
- options.dataFile = argv[++i];
549
- continue;
550
- }
551
- if (arg.startsWith("--data-file=")) {
552
- options.dataFile = arg.slice(12);
553
- continue;
554
- }
555
- if (arg === "--timeout") {
556
- options.timeout = Number(argv[++i]);
557
- continue;
558
- }
559
- if (arg.startsWith("--timeout=")) {
560
- options.timeout = Number(arg.slice(10));
561
- continue;
562
- }
563
- if (arg === "--name") {
564
- options.name = argv[++i];
565
- continue;
566
- }
567
- if (arg.startsWith("--name=")) {
568
- options.name = arg.slice(7);
569
- continue;
570
- }
571
- if (arg === "--desc") {
572
- options.desc = argv[++i];
573
- continue;
574
- }
575
- if (arg.startsWith("--desc=")) {
576
- options.desc = arg.slice(7);
577
- continue;
578
- }
579
- if (arg === "--cat-id" || arg === "--catid") {
580
- options.catId = argv[++i];
581
- continue;
582
- }
583
- if (arg.startsWith("--cat-id=")) {
584
- options.catId = arg.slice(9);
585
- continue;
586
- }
587
- if (arg.startsWith("--catid=")) {
588
- options.catId = arg.slice(8);
589
- continue;
590
- }
591
- if (arg === "--id") {
592
- options.id = argv[++i];
593
- continue;
594
- }
595
- if (arg.startsWith("--id=")) {
596
- options.id = arg.slice(5);
597
- continue;
598
- }
599
- if (arg === "--group-id") {
600
- options.groupId = argv[++i];
601
- continue;
602
- }
603
- if (arg.startsWith("--group-id=")) {
604
- options.groupId = arg.slice(11);
605
- continue;
606
- }
607
- if (arg === "--type") {
608
- options.type = argv[++i];
609
- continue;
610
- }
611
- if (arg.startsWith("--type=")) {
612
- options.type = arg.slice(7);
613
- continue;
614
- }
615
- if (arg === "--type-id") {
616
- options.typeId = argv[++i];
617
- continue;
618
- }
619
- if (arg.startsWith("--type-id=")) {
620
- options.typeId = arg.slice(10);
621
- continue;
622
- }
623
- if (arg === "--page") {
624
- options.page = Number(argv[++i]);
625
- continue;
626
- }
627
- if (arg.startsWith("--page=")) {
628
- options.page = Number(arg.slice(7));
629
- continue;
630
- }
631
- if (arg === "--limit") {
632
- const raw = String(argv[++i] ?? "").trim();
633
- options.limit = raw.toLowerCase() === "all" ? "all" : Number(raw);
634
- continue;
635
- }
636
- if (arg.startsWith("--limit=")) {
637
- const raw = String(arg.slice(8)).trim();
638
- options.limit = raw.toLowerCase() === "all" ? "all" : Number(raw);
639
- continue;
640
- }
641
- if (arg === "--no-pretty") {
642
- options.noPretty = true;
643
- continue;
644
- }
645
- }
646
- return options;
647
- }
648
- function parseDocsSyncArgs(argv) {
649
- const options = {
650
- dirs: [],
651
- bindings: [],
652
- tokenParam: "token",
653
- timeout: 30000,
654
- mermaidLook: "handDrawn",
655
- };
656
- for (let i = 0; i < argv.length; i += 1) {
657
- const arg = argv[i];
658
- if (!arg)
659
- continue;
660
- if (arg === "-h" || arg === "--help") {
661
- options.help = true;
662
- continue;
663
- }
664
- if (arg === "--config") {
665
- options.config = argv[++i];
666
- continue;
667
- }
668
- if (arg.startsWith("--config=")) {
669
- options.config = arg.slice(9);
670
- continue;
671
- }
672
- if (arg === "--base-url") {
673
- options.baseUrl = argv[++i];
674
- continue;
675
- }
676
- if (arg.startsWith("--base-url=")) {
677
- options.baseUrl = arg.slice(11);
678
- continue;
679
- }
680
- if (arg === "--token") {
681
- options.token = argv[++i];
682
- continue;
683
- }
684
- if (arg.startsWith("--token=")) {
685
- options.token = arg.slice(8);
686
- continue;
687
- }
688
- if (arg === "--project-id") {
689
- options.projectId = argv[++i];
690
- continue;
691
- }
692
- if (arg.startsWith("--project-id=")) {
693
- options.projectId = arg.slice(13);
694
- continue;
695
- }
696
- if (arg === "--auth-mode") {
697
- options.authMode = argv[++i];
698
- continue;
699
- }
700
- if (arg.startsWith("--auth-mode=")) {
701
- options.authMode = arg.slice(12);
702
- continue;
703
- }
704
- if (arg === "--email") {
705
- options.email = argv[++i];
706
- continue;
707
- }
708
- if (arg.startsWith("--email=")) {
709
- options.email = arg.slice(8);
710
- continue;
711
- }
712
- if (arg === "--password") {
713
- options.password = argv[++i];
714
- continue;
715
- }
716
- if (arg.startsWith("--password=")) {
717
- options.password = arg.slice(11);
718
- continue;
719
- }
720
- if (arg === "--cookie") {
721
- options.cookie = argv[++i];
722
- continue;
723
- }
724
- if (arg.startsWith("--cookie=")) {
725
- options.cookie = arg.slice(9);
726
- continue;
727
- }
728
- if (arg === "--token-param") {
729
- options.tokenParam = argv[++i];
730
- continue;
731
- }
732
- if (arg.startsWith("--token-param=")) {
733
- options.tokenParam = arg.slice(14);
734
- continue;
735
- }
736
- if (arg === "--timeout") {
737
- options.timeout = Number(argv[++i]);
738
- continue;
739
- }
740
- if (arg.startsWith("--timeout=")) {
741
- options.timeout = Number(arg.slice(10));
742
- continue;
743
- }
744
- if (arg === "--dir") {
745
- options.dirs.push(argv[++i]);
746
- continue;
747
- }
748
- if (arg.startsWith("--dir=")) {
749
- options.dirs.push(arg.slice(6));
750
- continue;
751
- }
752
- if (arg === "--binding") {
753
- options.bindings.push(argv[++i]);
754
- continue;
755
- }
756
- if (arg.startsWith("--binding=")) {
757
- options.bindings.push(arg.slice(10));
758
- continue;
759
- }
760
- if (arg === "--dry-run") {
761
- options.dryRun = true;
762
- continue;
763
- }
764
- if (arg === "--no-mermaid") {
765
- options.noMermaid = true;
766
- continue;
767
- }
768
- if (arg === "--mermaid-hand-drawn") {
769
- options.mermaidLook = "handDrawn";
770
- continue;
771
- }
772
- if (arg === "--mermaid-classic") {
773
- options.mermaidLook = "classic";
774
- options.mermaidHandDrawnSeed = undefined;
775
- continue;
776
- }
777
- if (arg === "--mermaid-hand-drawn-seed") {
778
- const seed = Number(argv[++i]);
779
- if (Number.isFinite(seed)) {
780
- options.mermaidHandDrawnSeed = seed;
781
- }
782
- if (!options.mermaidLook)
783
- options.mermaidLook = "handDrawn";
784
- continue;
785
- }
786
- if (arg.startsWith("--mermaid-hand-drawn-seed=")) {
787
- const seed = Number(arg.slice(27));
788
- if (Number.isFinite(seed)) {
789
- options.mermaidHandDrawnSeed = seed;
790
- }
791
- if (!options.mermaidLook)
792
- options.mermaidLook = "handDrawn";
793
- continue;
794
- }
795
- if (arg === "--force") {
796
- options.force = true;
797
- continue;
798
- }
799
- if (arg.startsWith("-"))
800
- continue;
801
- options.dirs.push(arg);
802
- }
803
- return options;
804
- }
805
- function parseDocsSyncBindArgs(argv) {
806
- const options = {
807
- sourceFiles: [],
808
- };
809
- for (let i = 0; i < argv.length; i += 1) {
810
- const arg = argv[i];
811
- if (!arg)
812
- continue;
813
- if (arg === "-h" || arg === "--help") {
814
- options.help = true;
815
- continue;
816
- }
817
- if (arg === "--name" || arg === "--binding") {
818
- options.name = argv[++i];
819
- continue;
820
- }
821
- if (arg.startsWith("--name=")) {
822
- options.name = arg.slice(7);
823
- continue;
824
- }
825
- if (arg.startsWith("--binding=")) {
826
- options.name = arg.slice(10);
827
- continue;
828
- }
829
- if (arg === "--dir") {
830
- options.dir = argv[++i];
831
- continue;
832
- }
833
- if (arg.startsWith("--dir=")) {
834
- options.dir = arg.slice(6);
835
- continue;
836
- }
837
- if (arg === "--project-id") {
838
- options.projectId = Number(argv[++i]);
839
- continue;
840
- }
841
- if (arg.startsWith("--project-id=")) {
842
- options.projectId = Number(arg.slice(13));
843
- continue;
844
- }
845
- if (arg === "--catid" || arg === "--cat-id") {
846
- options.catId = Number(argv[++i]);
847
- continue;
848
- }
849
- if (arg.startsWith("--catid=")) {
850
- options.catId = Number(arg.slice(8));
851
- continue;
852
- }
853
- if (arg.startsWith("--cat-id=")) {
854
- options.catId = Number(arg.slice(9));
855
- continue;
856
- }
857
- if (arg === "--template-id") {
858
- options.templateId = Number(argv[++i]);
859
- continue;
860
- }
861
- if (arg.startsWith("--template-id=")) {
862
- options.templateId = Number(arg.slice(14));
863
- continue;
864
- }
865
- if (arg === "--source-file") {
866
- options.sourceFiles?.push(argv[++i]);
867
- continue;
868
- }
869
- if (arg.startsWith("--source-file=")) {
870
- options.sourceFiles?.push(arg.slice(14));
871
- continue;
872
- }
873
- if (arg === "--clear-source-files") {
874
- options.clearSourceFiles = true;
875
- continue;
876
- }
877
- }
878
- return options;
879
- }
880
- function usage() {
881
- return [
882
- "Usage:",
883
- " yapi --path /api/interface/get --query id=123",
884
- " yapi group [options]",
885
- " yapi project [options]",
886
- " yapi interface [options]",
887
- " yapi log [options]",
888
- " yapi docs-sync [options] [dir...]",
889
- " yapi docs-sync bind <action> [options]",
890
- " yapi login [options]",
891
- " yapi logout [options]",
892
- " yapi whoami [options]",
893
- " yapi search [options]",
894
- " yapi install-skill [options]",
895
- " yapi self-update",
896
- "Options:",
897
- " --config <path> config file path (default: ~/.yapi/config.toml)",
898
- " --base-url <url> YApi base URL",
899
- " --token <token> project token (supports projectId:token)",
900
- " --project-id <id> select token for project (filters search results)",
901
- " --auth-mode <mode> token or global",
902
- " --email <email> login email for global mode",
903
- " --password <pwd> login password for global mode",
904
- " --cookie <cookie> cookie for global mode",
905
- " --q <keyword> search keyword (for yapi search)",
906
- " --path <path> API path (e.g., /api/interface/get)",
907
- " --url <url> full URL (overrides base-url/path)",
908
- " --query key=value query param (repeatable)",
909
- " --header Header:Value request header (repeatable)",
910
- " --method <method> HTTP method",
911
- " --data <payload> request body (JSON or text)",
912
- " --data-file <file> request body file",
913
- " --timeout <ms> request timeout in ms",
914
- " --id <id> resource id (for group/project/interface)",
915
- " --name <name> category name (for interface cat)",
916
- " --desc <desc> category description (for interface cat)",
917
- " --cat-id <id> category id (for interface cat)",
918
- " --group-id <id> group id (for project list)",
919
- " --type <type> log type (for log list)",
920
- " --type-id <id> log type id (for log list)",
921
- " --page <n> page number (for list)",
922
- " --limit <n|all> page size (for list)",
923
- " --no-update disable update check",
924
- " --no-pretty print raw response",
925
- "Docs-sync options:",
926
- " --dir <path> docs directory (repeatable; default: docs/release-notes)",
927
- " --binding <name> use binding from .yapi/docs-sync.json (repeatable)",
928
- " --dry-run compute but do not update YApi or mapping files",
929
- " --no-mermaid do not render mermaid code blocks",
930
- " --mermaid-hand-drawn force mermaid hand-drawn look (default)",
931
- " --mermaid-classic render mermaid with classic look",
932
- " --mermaid-hand-drawn-seed <n> hand-drawn seed (implies hand-drawn look)",
933
- " --force sync all files even if unchanged",
934
- "Docs-sync bind actions:",
935
- " list, get, add, update, remove",
936
- " -V, --version print version",
937
- " -h, --help show help",
938
- ].join("\n");
939
- }
940
- function selfUpdateUsage() {
941
- return ["Usage:", " yapi self-update", "", "Install the latest @leeguoo/yapi-mcp globally with npm."].join("\n");
942
- }
943
- function docsSyncUsage() {
944
- return [
945
- "Usage:",
946
- " yapi docs-sync [options] [dir...]",
947
- " yapi docs-sync bind <action> [options]",
948
- "Options:",
949
- " --config <path> config file path (default: ~/.yapi/config.toml)",
950
- " --base-url <url> YApi base URL",
951
- " --token <token> project token (supports projectId:token)",
952
- " --project-id <id> select token for project",
953
- " --auth-mode <mode> token or global",
954
- " --email <email> login email for global mode",
955
- " --password <pwd> login password for global mode",
956
- " --cookie <cookie> cookie for global mode",
957
- " --token-param <name> token query param name (default: token)",
958
- " --timeout <ms> request timeout in ms",
959
- " --dir <path> docs directory (repeatable; default: docs/release-notes)",
960
- " --binding <name> use binding from .yapi/docs-sync.json (repeatable)",
961
- " --dry-run compute but do not update YApi or mapping files",
962
- " --no-mermaid do not render mermaid code blocks",
963
- " --mermaid-hand-drawn force mermaid hand-drawn look (default)",
964
- " --mermaid-classic render mermaid with classic look",
965
- " --mermaid-hand-drawn-seed <n> hand-drawn seed (implies hand-drawn look)",
966
- " --force sync all files even if unchanged",
967
- " -h, --help show help",
968
- ].join("\n");
969
- }
970
- function docsSyncBindUsage() {
971
- return [
972
- "Usage:",
973
- " yapi docs-sync bind list",
974
- " yapi docs-sync bind get --name <binding>",
975
- " yapi docs-sync bind add --name <binding> --dir <path> --project-id <id> --catid <id> [options]",
976
- " yapi docs-sync bind update --name <binding> [options]",
977
- " yapi docs-sync bind remove --name <binding>",
978
- "Options:",
979
- " --name <binding> binding name (alias: --binding)",
980
- " --dir <path> docs directory path",
981
- " --project-id <id> YApi project id",
982
- " --catid <id> YApi category id",
983
- " --template-id <id> template interface id",
984
- " --source-file <path> sync specific file(s) (repeatable)",
985
- " --clear-source-files clear source_files list",
986
- " -h, --help show help",
987
- ].join("\n");
988
- }
989
- function loginUsage() {
990
- return [
991
- "Usage:",
992
- " yapi login [options]",
993
- "Options:",
994
- " --config <path> config file path (default: ~/.yapi/config.toml)",
995
- " --base-url <url> YApi base URL",
996
- " --login-url <url> page URL for browser login (default: <base-url>)",
997
- " --browser force browser login and cookie sync",
998
- " --email <email> login email for global mode",
999
- " --password <pwd> login password for global mode",
1000
- " --timeout <ms> request timeout in ms",
1001
- " -h, --help show help",
1002
- ].join("\n");
1003
- }
1004
- function logoutUsage() {
1005
- return [
1006
- "Usage:",
1007
- " yapi logout [options]",
1008
- "Options:",
1009
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1010
- " --base-url <url> YApi base URL",
1011
- " -h, --help show help",
1012
- ].join("\n");
1013
- }
1014
- function whoamiUsage() {
1015
- return [
1016
- "Usage:",
1017
- " yapi whoami [options]",
1018
- "Options:",
1019
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1020
- " --base-url <url> YApi base URL",
1021
- " --token <token> project token (supports projectId:token)",
1022
- " --project-id <id> select token for project",
1023
- " --auth-mode <mode> token or global",
1024
- " --email <email> login email for global mode",
1025
- " --password <pwd> login password for global mode",
1026
- " --cookie <cookie> cookie for global mode",
1027
- " --token-param <name> token query param name (default: token)",
1028
- " --timeout <ms> request timeout in ms",
1029
- " --no-pretty print raw response",
1030
- " -h, --help show help",
1031
- ].join("\n");
1032
- }
1033
- function searchUsage() {
1034
- return [
1035
- "Usage:",
1036
- " yapi search --q <keyword>",
1037
- "Options:",
1038
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1039
- " --base-url <url> YApi base URL",
1040
- " --token <token> project token (supports projectId:token)",
1041
- " --project-id <id> select token for project",
1042
- " --auth-mode <mode> token or global",
1043
- " --email <email> login email for global mode",
1044
- " --password <pwd> login password for global mode",
1045
- " --cookie <cookie> cookie for global mode",
1046
- " --token-param <name> token query param name (default: token)",
1047
- " --q <keyword> search keyword",
1048
- " --timeout <ms> request timeout in ms",
1049
- " --no-pretty print raw response",
1050
- " -h, --help show help",
1051
- ].join("\n");
1052
- }
1053
- function groupUsage() {
1054
- return [
1055
- "Usage:",
1056
- " yapi group list",
1057
- " yapi group get --id <group_id>",
1058
- "Options:",
1059
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1060
- " --base-url <url> YApi base URL",
1061
- " --token <token> project token (supports projectId:token)",
1062
- " --project-id <id> select token for project",
1063
- " --auth-mode <mode> token or global",
1064
- " --email <email> login email for global mode",
1065
- " --password <pwd> login password for global mode",
1066
- " --cookie <cookie> cookie for global mode",
1067
- " --token-param <name> token query param name (default: token)",
1068
- " --timeout <ms> request timeout in ms",
1069
- " --id <id> group id (for get)",
1070
- " --no-pretty print raw response",
1071
- " -h, --help show help",
1072
- ].join("\n");
1073
- }
1074
- function projectUsage() {
1075
- return [
1076
- "Usage:",
1077
- " yapi project list --group-id <group_id> [--page <n>] [--limit <n>]",
1078
- " yapi project get --id <project_id>",
1079
- " yapi project token --project-id <project_id>",
1080
- "Options:",
1081
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1082
- " --base-url <url> YApi base URL",
1083
- " --token <token> project token (supports projectId:token)",
1084
- " --project-id <id> select token for project",
1085
- " --auth-mode <mode> token or global",
1086
- " --email <email> login email for global mode",
1087
- " --password <pwd> login password for global mode",
1088
- " --cookie <cookie> cookie for global mode",
1089
- " --token-param <name> token query param name (default: token)",
1090
- " --timeout <ms> request timeout in ms",
1091
- " --group-id <id> group id (for list)",
1092
- " --id <id> project id (for get)",
1093
- " --page <n> page number (default: 1)",
1094
- " --limit <n> page size (default: 10)",
1095
- " --no-pretty print raw response",
1096
- " -h, --help show help",
1097
- ].join("\n");
1098
- }
1099
- function interfaceUsage() {
1100
- return [
1101
- "Usage:",
1102
- " yapi interface list --project-id <project_id> [--page <n>] [--limit <n>]",
1103
- " yapi interface list-menu --project-id <project_id>",
1104
- " yapi interface get --id <api_id>",
1105
- " yapi interface cat add --project-id <project_id> --name <name> [--desc <desc>]",
1106
- " yapi interface cat update --cat-id <cat_id> --name <name> [--desc <desc>]",
1107
- " yapi interface cat delete --cat-id <cat_id>",
1108
- "Options:",
1109
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1110
- " --base-url <url> YApi base URL",
1111
- " --token <token> project token (supports projectId:token)",
1112
- " --project-id <id> select token for project",
1113
- " --auth-mode <mode> token or global",
1114
- " --email <email> login email for global mode",
1115
- " --password <pwd> login password for global mode",
1116
- " --cookie <cookie> cookie for global mode",
1117
- " --token-param <name> token query param name (default: token)",
1118
- " --timeout <ms> request timeout in ms",
1119
- " --id <id> interface id (for get)",
1120
- " --cat-id <id> category id (for cat update/delete)",
1121
- " --name <name> category name (for cat add/update)",
1122
- " --desc <desc> category description (for cat add/update)",
1123
- " --page <n> page number (default: 1)",
1124
- " --limit <n|all> page size (default: 20)",
1125
- " --no-pretty print raw response",
1126
- " -h, --help show help",
1127
- ].join("\n");
1128
- }
1129
- function logUsage() {
1130
- return [
1131
- "Usage:",
1132
- " yapi log list --type <type> --type-id <id> [--page <n>] [--limit <n>]",
1133
- "Options:",
1134
- " --config <path> config file path (default: ~/.yapi/config.toml)",
1135
- " --base-url <url> YApi base URL",
1136
- " --token <token> project token (supports projectId:token)",
1137
- " --project-id <id> select token for project",
1138
- " --auth-mode <mode> token or global",
1139
- " --email <email> login email for global mode",
1140
- " --password <pwd> login password for global mode",
1141
- " --cookie <cookie> cookie for global mode",
1142
- " --token-param <name> token query param name (default: token)",
1143
- " --timeout <ms> request timeout in ms",
1144
- " --type <type> log type (e.g., group/project)",
1145
- " --type-id <id> log type id",
1146
- " --page <n> page number (default: 1)",
1147
- " --limit <n> page size (default: 10)",
1148
- " --no-pretty print raw response",
1149
- " -h, --help show help",
1150
- ].join("\n");
1151
- }
1152
- function escapeTomlValue(value) {
1153
- return String(value || "")
1154
- .replace(/\\/g, "\\\\")
1155
- .replace(/"/g, '\\"');
1156
- }
1157
- function formatToml(config) {
1158
- const orderedKeys = ["base_url", "auth_mode", "email", "password", "token", "project_id"];
1159
- const lines = ["# YApi CLI config"];
1160
- for (const key of orderedKeys) {
1161
- const value = config[key] || "";
1162
- lines.push(`${key} = "${escapeTomlValue(value)}"`);
1163
- }
1164
- return `${lines.join("\n")}\n`;
1165
- }
1166
- function writeConfig(filePath, config) {
1167
- fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
1168
- fs_1.default.writeFileSync(filePath, formatToml(config), "utf8");
1169
- }
1170
- function promptText(question) {
1171
- const rl = readline_1.default.createInterface({
1172
- input: process.stdin,
1173
- output: process.stdout,
1174
- terminal: true,
1175
- });
1176
- return new Promise((resolve) => {
1177
- rl.question(question, (answer) => {
1178
- rl.close();
1179
- resolve(answer);
1180
- });
1181
- });
1182
- }
1183
- function promptHidden(question) {
1184
- const rl = readline_1.default.createInterface({
1185
- input: process.stdin,
1186
- output: process.stdout,
1187
- terminal: true,
1188
- });
1189
- const output = rl.output || process.stdout;
1190
- const originalWrite = rl
1191
- ._writeToOutput;
1192
- if (question) {
1193
- output.write(question);
1194
- }
1195
- rl.stdoutMuted = true;
1196
- rl._writeToOutput =
1197
- function writeToOutput(value) {
1198
- if (rl.stdoutMuted)
1199
- return;
1200
- if (typeof originalWrite === "function") {
1201
- originalWrite.call(this, value);
1202
- }
1203
- else {
1204
- output.write(value);
1205
- }
1206
- };
1207
- return new Promise((resolve) => {
1208
- rl.question("", (answer) => {
1209
- rl.stdoutMuted = false;
1210
- if (question) {
1211
- output.write("\n");
1212
- }
1213
- rl.close();
1214
- resolve(answer);
1215
- });
1216
- });
1217
- }
1218
- async function promptRequired(question, hidden) {
1219
- while (true) {
1220
- const answer = hidden ? await promptHidden(question) : await promptText(question);
1221
- const trimmed = String(answer || "").trim();
1222
- if (trimmed)
1223
- return trimmed;
1224
- }
1225
- }
1226
- async function initConfigIfMissing(options) {
1227
- const hasBaseUrl = Boolean(options.baseUrl);
1228
- const hasEmail = Boolean(options.email);
1229
- const hasPassword = Boolean(options.password);
1230
- if (!hasBaseUrl || !hasEmail || !hasPassword) {
1231
- if (!process.stdin.isTTY || !process.stdout.isTTY)
1232
- return null;
1233
- }
1234
- const baseUrl = hasBaseUrl ? options.baseUrl : await promptRequired("YApi base URL: ", false);
1235
- const email = hasEmail ? options.email : await promptRequired("YApi email: ", false);
1236
- const password = hasPassword ? options.password : await promptRequired("YApi password: ", true);
1237
- const config = {
1238
- base_url: baseUrl || "",
1239
- auth_mode: "global",
1240
- email: email || "",
1241
- password: password || "",
1242
- token: options.token || "",
1243
- project_id: options.projectId || "",
1244
- };
1245
- const configPath = globalConfigPath();
1246
- writeConfig(configPath, config);
1247
- return { configPath, config };
1248
- }
1249
- function readVersion() {
1250
- try {
1251
- const pkgPath = path_1.default.resolve(__dirname, "../package.json");
1252
- const pkg = JSON.parse(fs_1.default.readFileSync(pkgPath, "utf8"));
1253
- return pkg.version || "unknown";
1254
- }
1255
- catch {
1256
- return "unknown";
1257
- }
1258
- }
1259
- async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buildQueryItems, transform) {
1260
- const options = parseArgs(rawArgs);
1261
- if (options.help) {
1262
- console.log(usageFn());
1263
- return 0;
1264
- }
1265
- if (options.version) {
1266
- console.log(readVersion());
1267
- return 0;
1268
- }
1269
- const configPath = options.config || globalConfigPath();
1270
- const config = fs_1.default.existsSync(configPath)
1271
- ? parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"))
1272
- : {};
1273
- const baseUrl = options.baseUrl || config.base_url || "";
1274
- if (requireBaseUrl && !baseUrl) {
1275
- console.error("missing --base-url or config base_url");
1276
- return 2;
1277
- }
1278
- const projectId = options.projectId || config.project_id || "";
1279
- const rawToken = options.token || config.token || "";
1280
- const token = resolveToken(rawToken, projectId);
1281
- let authMode = (options.authMode || config.auth_mode || "").trim().toLowerCase();
1282
- if (!authMode) {
1283
- authMode = token
1284
- ? "token"
1285
- : options.email || options.password || config.email || config.password
1286
- ? "global"
1287
- : "token";
1288
- }
1289
- if (authMode !== "token" && authMode !== "global") {
1290
- console.error("invalid --auth-mode (use token or global)");
1291
- return 2;
1292
- }
1293
- const headers = {};
1294
- if (options.cookie) {
1295
- headers.Cookie = options.cookie;
1296
- }
1297
- const email = options.email || config.email || "";
1298
- const password = options.password || config.password || "";
1299
- const authService = authMode === "global"
1300
- ? new auth_1.YApiAuthService(baseUrl, email || "", password || "", "warn", {
1301
- timeoutMs: options.timeout || 30000,
1302
- })
1303
- : null;
1304
- const canRelogin = authMode === "global" &&
1305
- Boolean(authService) &&
1306
- Boolean(email) &&
1307
- Boolean(password) &&
1308
- !options.cookie;
1309
- if (!headers.Cookie && authMode === "global") {
1310
- const cachedCookie = authService?.getCachedCookieHeader();
1311
- if (cachedCookie) {
1312
- headers.Cookie = cachedCookie;
1313
- }
1314
- else if (email && password && authService) {
1315
- try {
1316
- headers.Cookie = await authService.getCookieHeaderWithLogin();
1317
- }
1318
- catch (error) {
1319
- console.error(error instanceof Error ? error.message : String(error));
1320
- return 2;
1321
- }
1322
- }
1323
- else {
1324
- console.error("missing email/password for global auth");
1325
- return 2;
1326
- }
1327
- }
1328
- let queryItems = [];
1329
- let method = "GET";
1330
- let data = undefined;
1331
- if (buildQueryItems) {
1332
- const result = buildQueryItems(options);
1333
- if (!result.ok)
1334
- return 2;
1335
- queryItems = result.queryItems || [];
1336
- if (result.method) {
1337
- method = result.method;
1338
- }
1339
- if (result.data !== undefined) {
1340
- data = result.data;
1341
- }
1342
- }
1343
- const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
1344
- const sendOnce = async () => {
1345
- let body;
1346
- const requestHeaders = { ...headers };
1347
- if (data !== undefined) {
1348
- body = JSON.stringify(data);
1349
- requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
1350
- }
1351
- const response = await fetchWithTimeout(url, {
1352
- method,
1353
- headers: requestHeaders,
1354
- body,
1355
- }, options.timeout || 30000);
1356
- const text = await response.text();
1357
- return { response, text, json: parseJsonMaybe(text) };
1358
- };
1359
- let result;
1360
- try {
1361
- result = await sendOnce();
1362
- }
1363
- catch (error) {
1364
- console.error("request failed: " + (error instanceof Error ? error.message : String(error)));
1365
- return 2;
1366
- }
1367
- if (canRelogin && looksLikeAuthError(result.response.status, result.json)) {
1368
- try {
1369
- headers.Cookie = await authService.getCookieHeaderWithLogin({ forceLogin: true });
1370
- result = await sendOnce();
1371
- }
1372
- catch (error) {
1373
- console.error(error instanceof Error ? error.message : String(error));
1374
- return 2;
1375
- }
1376
- }
1377
- const { text } = result;
1378
- if (options.noPretty) {
1379
- console.log(text);
1380
- return 0;
1381
- }
1382
- try {
1383
- const payload = result.json ?? JSON.parse(text);
1384
- const nextPayload = transform ? transform(payload, options) : payload;
1385
- console.log(JSON.stringify(nextPayload ?? payload, null, 2));
1386
- }
1387
- catch {
1388
- console.log(text);
1389
- }
1390
- return 0;
1391
- }
1392
- async function runLogin(rawArgs) {
1393
- const options = parseArgs(rawArgs);
1394
- if (options.help) {
1395
- console.log(loginUsage());
1396
- return 0;
1397
- }
1398
- if (options.version) {
1399
- console.log(readVersion());
1400
- return 0;
1401
- }
1402
- const configPath = options.config || globalConfigPath();
1403
- const config = fs_1.default.existsSync(configPath)
1404
- ? parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"))
1405
- : {};
1406
- let baseUrl = options.baseUrl || config.base_url || "";
1407
- let email = options.email || config.email || "";
1408
- let password = options.password || config.password || "";
1409
- const projectId = options.projectId || config.project_id || "";
1410
- const token = options.token || config.token || "";
1411
- let updated = false;
1412
- if (!baseUrl) {
1413
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
1414
- console.error("missing --base-url or config base_url");
1415
- return 2;
1416
- }
1417
- baseUrl = await promptRequired("YApi base URL: ", false);
1418
- updated = true;
1419
- }
1420
- const useBrowserLogin = Boolean(options.browser) || !email || !password;
1421
- if (useBrowserLogin) {
1422
- if (!process.stdin.isTTY || !process.stdout.isTTY) {
1423
- console.error("browser login requires interactive terminal");
1424
- return 2;
1425
- }
1426
- try {
1427
- const session = await loginByBrowserAndReadCookie(baseUrl, options.loginUrl);
1428
- const cache = new authCache_1.YApiAuthCache(baseUrl, "warn");
1429
- cache.saveSession({
1430
- yapiToken: session.yapiToken,
1431
- yapiUid: session.yapiUid,
1432
- expiresAt: session.expiresAt,
1433
- updatedAt: Date.now(),
1434
- });
1435
- }
1436
- catch (error) {
1437
- console.error(error instanceof Error ? error.message : String(error));
1438
- return 2;
1439
- }
1440
- const shouldWriteConfig = updated || !fs_1.default.existsSync(configPath) || config.auth_mode !== "global";
1441
- if (shouldWriteConfig) {
1442
- const mergedConfig = {
1443
- base_url: baseUrl,
1444
- auth_mode: "global",
1445
- email,
1446
- password,
1447
- token,
1448
- project_id: projectId,
1449
- };
1450
- writeConfig(configPath, mergedConfig);
1451
- }
1452
- console.log("login success (cookie synced from browser to ~/.yapi-mcp/auth-*.json)");
1453
- return 0;
1454
- }
1455
- try {
1456
- const authService = new auth_1.YApiAuthService(baseUrl, email, password, "warn", {
1457
- timeoutMs: options.timeout || 30000,
1458
- });
1459
- await authService.getCookieHeaderWithLogin({ forceLogin: true });
1460
- }
1461
- catch (error) {
1462
- console.error(error instanceof Error ? error.message : String(error));
1463
- return 2;
1464
- }
1465
- const shouldWriteConfig = updated || !fs_1.default.existsSync(configPath) || config.auth_mode !== "global";
1466
- if (shouldWriteConfig) {
1467
- const mergedConfig = {
1468
- base_url: baseUrl,
1469
- auth_mode: "global",
1470
- email,
1471
- password,
1472
- token,
1473
- project_id: projectId,
1474
- };
1475
- writeConfig(configPath, mergedConfig);
1476
- }
1477
- console.log("login success (cookie cached in ~/.yapi-mcp/auth-*.json)");
1478
- return 0;
1479
- }
1480
- async function runLogout(rawArgs) {
1481
- const options = parseArgs(rawArgs);
1482
- if (options.help) {
1483
- console.log(logoutUsage());
1484
- return 0;
1485
- }
1486
- if (options.version) {
1487
- console.log(readVersion());
1488
- return 0;
1489
- }
1490
- const configPath = options.config || globalConfigPath();
1491
- const config = fs_1.default.existsSync(configPath)
1492
- ? parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"))
1493
- : {};
1494
- const baseUrl = options.baseUrl || config.base_url || "";
1495
- if (!baseUrl) {
1496
- console.error("missing --base-url or config base_url");
1497
- return 2;
1498
- }
1499
- const cache = new authCache_1.YApiAuthCache(baseUrl, "warn");
1500
- cache.clearSession();
1501
- console.log("logout success (session cleared from ~/.yapi-mcp/auth-*.json)");
1502
- return 0;
1503
- }
1504
- async function runWhoami(rawArgs) {
1505
- return await runSimpleRequest(rawArgs, whoamiUsage, "/api/user/status", true);
1506
- }
1507
- async function runSearch(rawArgs) {
1508
- return await runSimpleRequest(rawArgs, searchUsage, "/api/project/search", true, (options) => {
1509
- const keyword = String(options.q || "").trim();
1510
- if (!keyword) {
1511
- console.error("missing --q for search");
1512
- return { ok: false };
1513
- }
1514
- return { ok: true, queryItems: [["q", keyword]] };
1515
- }, (payload, options) => {
1516
- const filterProjectId = String(options.projectId || "").trim();
1517
- if (!filterProjectId)
1518
- return payload;
1519
- if (!payload || typeof payload !== "object")
1520
- return payload;
1521
- const record = payload;
1522
- const data = record.data;
1523
- if (!data || typeof data !== "object")
1524
- return payload;
1525
- const nextData = { ...data };
1526
- if (Array.isArray(data.interface)) {
1527
- nextData.interface = data.interface.filter((item) => {
1528
- const projectId = item?.projectId ?? item?.project_id ?? item?.projectID ?? "";
1529
- return String(projectId) === filterProjectId;
1530
- });
1531
- }
1532
- if (Array.isArray(data.project)) {
1533
- nextData.project = data.project.filter((item) => {
1534
- const projectId = item?._id ?? item?.id ?? item?.project_id ?? item?.projectId ?? "";
1535
- return String(projectId) === filterProjectId;
1536
- });
1537
- }
1538
- return { ...record, data: nextData };
1539
- });
1540
- }
1541
- async function runGroup(rawArgs) {
1542
- const action = (rawArgs[0] || "list").toLowerCase();
1543
- const options = parseArgs(rawArgs.slice(1));
1544
- if (options.help) {
1545
- console.log(groupUsage());
1546
- return 0;
1547
- }
1548
- if (options.version) {
1549
- console.log(readVersion());
1550
- return 0;
1551
- }
1552
- if (action === "list") {
1553
- return await runSimpleRequest(rawArgs.slice(1), groupUsage, "/api/group/list", true);
1554
- }
1555
- if (action === "get") {
1556
- return await runSimpleRequest(rawArgs.slice(1), groupUsage, "/api/group/get", true, (opts) => {
1557
- const id = String(opts.id || "").trim();
1558
- if (!id) {
1559
- console.error("missing --id for group get");
1560
- return { ok: false };
1561
- }
1562
- return { ok: true, queryItems: [["id", id]] };
1563
- });
1564
- }
1565
- console.error(`unknown group action: ${action}`);
1566
- console.error(groupUsage());
1567
- return 2;
1568
- }
1569
- async function runProject(rawArgs) {
1570
- const action = (rawArgs[0] || "list").toLowerCase();
1571
- const options = parseArgs(rawArgs.slice(1));
1572
- if (options.help) {
1573
- console.log(projectUsage());
1574
- return 0;
1575
- }
1576
- if (options.version) {
1577
- console.log(readVersion());
1578
- return 0;
1579
- }
1580
- if (action === "list") {
1581
- return await runSimpleRequest(rawArgs.slice(1), projectUsage, "/api/project/list", true, (opts) => {
1582
- const groupId = String(opts.groupId || "").trim();
1583
- if (!groupId) {
1584
- console.error("missing --group-id for project list");
1585
- return { ok: false };
1586
- }
1587
- const page = Number.isFinite(opts.page ?? NaN) ? String(opts.page) : "1";
1588
- const limit = resolveLimit(opts.limit, "10");
1589
- return {
1590
- ok: true,
1591
- queryItems: [
1592
- ["group_id", groupId],
1593
- ["page", page],
1594
- ["limit", limit],
1595
- ],
1596
- };
1597
- });
1598
- }
1599
- if (action === "get") {
1600
- return await runSimpleRequest(rawArgs.slice(1), projectUsage, "/api/project/get", true, (opts) => {
1601
- const id = String(opts.id || "").trim();
1602
- if (!id) {
1603
- console.error("missing --id for project get");
1604
- return { ok: false };
1605
- }
1606
- return { ok: true, queryItems: [["id", id]] };
1607
- });
1608
- }
1609
- if (action === "token") {
1610
- return await runSimpleRequest(rawArgs.slice(1), projectUsage, "/api/project/token", true, (opts) => {
1611
- const projectId = String(opts.projectId || opts.id || "").trim();
1612
- if (!projectId) {
1613
- console.error("missing --project-id for project token");
1614
- return { ok: false };
1615
- }
1616
- return { ok: true, queryItems: [["project_id", projectId]] };
1617
- });
1618
- }
1619
- console.error(`unknown project action: ${action}`);
1620
- console.error(projectUsage());
1621
- return 2;
1622
- }
1623
- async function runInterface(rawArgs) {
1624
- const action = (rawArgs[0] || "").toLowerCase();
1625
- const options = parseArgs(rawArgs.slice(1));
1626
- if (options.help) {
1627
- console.log(interfaceUsage());
1628
- return 0;
1629
- }
1630
- if (options.version) {
1631
- console.log(readVersion());
1632
- return 0;
1633
- }
1634
- if (action === "list") {
1635
- return await runSimpleRequest(rawArgs.slice(1), interfaceUsage, "/api/interface/list", true, (opts) => {
1636
- const projectId = String(opts.projectId || "").trim();
1637
- if (!projectId) {
1638
- console.error("missing --project-id for interface list");
1639
- return { ok: false };
1640
- }
1641
- const page = Number.isFinite(opts.page ?? NaN) ? String(opts.page) : "1";
1642
- const limit = resolveLimit(opts.limit, "20");
1643
- return {
1644
- ok: true,
1645
- queryItems: [
1646
- ["project_id", projectId],
1647
- ["page", page],
1648
- ["limit", limit],
1649
- ],
1650
- };
1651
- });
1652
- }
1653
- if (action === "list-menu" || action === "menu" || action === "list_menu") {
1654
- return await runSimpleRequest(rawArgs.slice(1), interfaceUsage, "/api/interface/list_menu", true, (opts) => {
1655
- const projectId = String(opts.projectId || "").trim();
1656
- if (!projectId) {
1657
- console.error("missing --project-id for interface list-menu");
1658
- return { ok: false };
1659
- }
1660
- return { ok: true, queryItems: [["project_id", projectId]] };
1661
- });
1662
- }
1663
- if (action === "get") {
1664
- return await runSimpleRequest(rawArgs.slice(1), interfaceUsage, "/api/interface/get", true, (opts) => {
1665
- const id = String(opts.id || "").trim();
1666
- if (!id) {
1667
- console.error("missing --id for interface get");
1668
- return { ok: false };
1669
- }
1670
- return { ok: true, queryItems: [["id", id]] };
1671
- });
1672
- }
1673
- if (action === "cat" || action === "category") {
1674
- const subAction = (rawArgs[1] || "").toLowerCase();
1675
- const subArgs = rawArgs.slice(2);
1676
- if (subAction === "add") {
1677
- return await runSimpleRequest(subArgs, interfaceUsage, "/api/interface/add_cat", true, (opts) => {
1678
- const projectId = String(opts.projectId || "").trim();
1679
- const name = String(opts.name || "").trim();
1680
- if (!projectId || !name) {
1681
- console.error("missing --project-id/--name for interface cat add");
1682
- return { ok: false };
1683
- }
1684
- const payload = {
1685
- project_id: projectId,
1686
- name,
1687
- };
1688
- if (opts.desc !== undefined) {
1689
- payload.desc = opts.desc;
1690
- }
1691
- return { ok: true, method: "POST", data: payload };
1692
- });
1693
- }
1694
- if (subAction === "update" || subAction === "up") {
1695
- return await runSimpleRequest(subArgs, interfaceUsage, "/api/interface/up_cat", true, (opts) => {
1696
- const catId = String(opts.catId || "").trim();
1697
- const name = String(opts.name || "").trim();
1698
- if (!catId || !name) {
1699
- console.error("missing --cat-id/--name for interface cat update");
1700
- return { ok: false };
1701
- }
1702
- const payload = {
1703
- catid: catId,
1704
- name,
1705
- };
1706
- if (opts.desc !== undefined) {
1707
- payload.desc = opts.desc;
1708
- }
1709
- return { ok: true, method: "POST", data: payload };
1710
- });
1711
- }
1712
- if (subAction === "delete" || subAction === "del" || subAction === "remove") {
1713
- return await runSimpleRequest(subArgs, interfaceUsage, "/api/interface/del_cat", true, (opts) => {
1714
- const catId = String(opts.catId || "").trim();
1715
- if (!catId) {
1716
- console.error("missing --cat-id for interface cat delete");
1717
- return { ok: false };
1718
- }
1719
- return { ok: true, method: "POST", data: { catid: catId } };
1720
- });
1721
- }
1722
- console.error(`unknown interface cat action: ${subAction || "(missing)"}`);
1723
- console.error(interfaceUsage());
1724
- return 2;
1725
- }
1726
- console.error(`unknown interface action: ${action || "(missing)"}`);
1727
- console.error(interfaceUsage());
1728
- return 2;
1729
- }
1730
- async function runLog(rawArgs) {
1731
- const action = (rawArgs[0] || "list").toLowerCase();
1732
- const options = parseArgs(rawArgs.slice(1));
1733
- if (options.help) {
1734
- console.log(logUsage());
1735
- return 0;
1736
- }
1737
- if (options.version) {
1738
- console.log(readVersion());
1739
- return 0;
1740
- }
1741
- if (action === "list") {
1742
- return await runSimpleRequest(rawArgs.slice(1), logUsage, "/api/log/list", true, (opts) => {
1743
- const type = String(opts.type || "").trim();
1744
- const typeId = String(opts.typeId || "").trim();
1745
- if (!type || !typeId) {
1746
- console.error("missing --type/--type-id for log list");
1747
- return { ok: false };
1748
- }
1749
- const page = Number.isFinite(opts.page ?? NaN) ? String(opts.page) : "1";
1750
- const limit = resolveLimit(opts.limit, "10");
1751
- return {
1752
- ok: true,
1753
- queryItems: [
1754
- ["type", type],
1755
- ["typeid", typeId],
1756
- ["page", page],
1757
- ["limit", limit],
1758
- ],
1759
- };
1760
- });
1761
- }
1762
- console.error(`unknown log action: ${action}`);
1763
- console.error(logUsage());
1764
- return 2;
1765
- }
1766
- function findDocsSyncHome(startDir) {
1767
- let current = path_1.default.resolve(startDir);
1768
- while (true) {
1769
- const candidate = path_1.default.join(current, ".yapi");
1770
- if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isDirectory()) {
1771
- return candidate;
1772
- }
1773
- const parent = path_1.default.dirname(current);
1774
- if (parent === current)
1775
- return null;
1776
- current = parent;
1777
- }
1778
- }
1779
- function resolveDocsSyncHome(startDir, ensure) {
1780
- const found = findDocsSyncHome(startDir);
1781
- if (found)
1782
- return found;
1783
- if (!ensure)
1784
- return null;
1785
- const home = path_1.default.join(path_1.default.resolve(startDir), ".yapi");
1786
- fs_1.default.mkdirSync(home, { recursive: true });
1787
- ensureDocsSyncReadme(home);
1788
- return home;
1789
- }
1790
- function globalYapiHomeDir() {
1791
- return path_1.default.resolve(process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi"));
1792
- }
1793
- function normalizeComparablePath(targetPath) {
1794
- try {
1795
- return fs_1.default.realpathSync.native(targetPath);
1796
- }
1797
- catch {
1798
- return path_1.default.resolve(targetPath);
1799
- }
1800
- }
1801
- function isGlobalDocsSyncHome(homeDir) {
1802
- return normalizeComparablePath(homeDir) === normalizeComparablePath(globalYapiHomeDir());
1803
- }
1804
- function docsSyncConfigPath(homeDir) {
1805
- return path_1.default.join(homeDir, "docs-sync.json");
1806
- }
1807
- function docsSyncLinksPath(homeDir) {
1808
- return path_1.default.join(homeDir, "docs-sync.links.json");
1809
- }
1810
- function docsSyncProjectsPath(homeDir) {
1811
- return path_1.default.join(homeDir, "docs-sync.projects.json");
1812
- }
1813
- function docsSyncDeploymentsPath(homeDir) {
1814
- return path_1.default.join(homeDir, "docs-sync.deployments.json");
1815
- }
1816
- const DOCS_SYNC_README = [
1817
- "# YApi Docs Sync",
1818
- "",
1819
- "This folder is generated by the yapi docs-sync tool.",
1820
- "Extra caches are written when running docs-sync with bindings.",
1821
- "",
1822
- "Project: https://github.com/leeguooooo/cross-request-master",
1823
- "",
1824
- "Files:",
1825
- "- docs-sync.json: bindings and file-to-doc ID mapping.",
1826
- "- docs-sync.links.json: local docs to YApi doc URLs.",
1827
- "- docs-sync.projects.json: cached project metadata/envs.",
1828
- "- docs-sync.deployments.json: local docs to deployed URLs.",
1829
- ].join("\n");
1830
- function ensureDocsSyncReadme(homeDir) {
1831
- const readmePath = path_1.default.join(homeDir, "README.md");
1832
- if (fs_1.default.existsSync(readmePath))
1833
- return;
1834
- fs_1.default.writeFileSync(readmePath, `${DOCS_SYNC_README}\n`, "utf8");
1835
- }
1836
- function loadDocsSyncConfig(homeDir) {
1837
- const configPath = docsSyncConfigPath(homeDir);
1838
- if (!fs_1.default.existsSync(configPath)) {
1839
- return { version: 1, bindings: {} };
1840
- }
1841
- const raw = fs_1.default.readFileSync(configPath, "utf8");
1842
- const parsed = JSON.parse(raw);
1843
- const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
1844
- return {
1845
- version: typeof parsed.version === "number" ? parsed.version : 1,
1846
- bindings: bindings,
1847
- };
1848
- }
1849
- function saveDocsSyncConfig(homeDir, config) {
1850
- const configPath = docsSyncConfigPath(homeDir);
1851
- fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1852
- }
1853
- function loadDocsSyncLinks(homeDir) {
1854
- const configPath = docsSyncLinksPath(homeDir);
1855
- if (!fs_1.default.existsSync(configPath)) {
1856
- return { version: 1, bindings: {} };
1857
- }
1858
- const raw = fs_1.default.readFileSync(configPath, "utf8");
1859
- const parsed = JSON.parse(raw);
1860
- const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
12
+ const login_1 = require("./cli/commands/login");
13
+ const logout_1 = require("./cli/commands/logout");
14
+ const whoami_1 = require("./cli/commands/whoami");
15
+ const search_1 = require("./cli/commands/search");
16
+ const group_1 = require("./cli/commands/group");
17
+ const project_1 = require("./cli/commands/project");
18
+ const interface_1 = require("./cli/commands/interface");
19
+ const log_1 = require("./cli/commands/log");
20
+ const docs_sync_1 = require("./cli/commands/docs-sync");
21
+ const self_update_1 = require("./cli/commands/self-update");
22
+ const install_skill_1 = require("./cli/commands/install-skill");
23
+ const request_1 = require("./cli/commands/request");
24
+ const col_1 = require("./cli/commands/col");
25
+ const export_1 = require("./cli/commands/export");
26
+ const env_1 = require("./cli/commands/env");
27
+ const member_1 = require("./cli/commands/member");
28
+ const follow_1 = require("./cli/commands/follow");
29
+ const user_1 = require("./cli/commands/user");
30
+ function toOptions(argv) {
1861
31
  return {
1862
- version: typeof parsed.version === "number" ? parsed.version : 1,
1863
- bindings: bindings,
32
+ config: argv.config,
33
+ baseUrl: (argv.baseUrl || argv["base-url"]),
34
+ loginUrl: (argv.loginUrl || argv["login-url"]),
35
+ token: argv.token,
36
+ projectId: (argv.projectId || argv["project-id"]),
37
+ authMode: (argv.authMode || argv["auth-mode"]),
38
+ browser: argv.browser,
39
+ email: argv.email,
40
+ password: argv.password,
41
+ cookie: argv.cookie,
42
+ tokenParam: (argv.tokenParam || argv["token-param"]),
43
+ method: argv.method,
44
+ path: argv.path,
45
+ url: argv.url,
46
+ query: argv.query ? (Array.isArray(argv.query) ? argv.query : [argv.query]) : [],
47
+ header: argv.header ? (Array.isArray(argv.header) ? argv.header : [argv.header]) : [],
48
+ data: argv.data,
49
+ dataFile: (argv.dataFile || argv["data-file"]),
50
+ timeout: argv.timeout,
51
+ id: argv.id,
52
+ name: argv.name,
53
+ desc: argv.desc,
54
+ catId: (argv.catId || argv["cat-id"] || argv.catid),
55
+ groupId: (argv.groupId || argv["group-id"]),
56
+ type: argv.type,
57
+ typeId: (argv.typeId || argv["type-id"]),
58
+ page: argv.page,
59
+ limit: argv.limit,
60
+ noUpdate: (argv.noUpdate || argv["no-update"]),
61
+ q: argv.q,
62
+ noPretty: (argv.noPretty || argv["no-pretty"]),
1864
63
  };
1865
64
  }
1866
- function saveDocsSyncLinks(homeDir, config) {
1867
- const configPath = docsSyncLinksPath(homeDir);
1868
- fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1869
- }
1870
- function loadDocsSyncProjects(homeDir) {
1871
- const configPath = docsSyncProjectsPath(homeDir);
1872
- if (!fs_1.default.existsSync(configPath)) {
1873
- return { version: 1, projects: {} };
1874
- }
1875
- const raw = fs_1.default.readFileSync(configPath, "utf8");
1876
- const parsed = JSON.parse(raw);
1877
- const projects = parsed.projects && typeof parsed.projects === "object" ? parsed.projects : {};
65
+ function toDocsSyncOptions(argv) {
66
+ const rawDirs = argv.dir
67
+ ? (Array.isArray(argv.dir) ? argv.dir : [argv.dir])
68
+ : [];
69
+ const positionalDirs = Array.isArray(argv._)
70
+ ? argv._.map(String).filter((s) => s && s !== "docs-sync")
71
+ : [];
1878
72
  return {
1879
- version: typeof parsed.version === "number" ? parsed.version : 1,
1880
- projects: projects,
73
+ config: argv.config,
74
+ baseUrl: (argv.baseUrl || argv["base-url"]),
75
+ token: argv.token,
76
+ projectId: (argv.projectId || argv["project-id"]),
77
+ authMode: (argv.authMode || argv["auth-mode"]),
78
+ email: argv.email,
79
+ password: argv.password,
80
+ cookie: argv.cookie,
81
+ tokenParam: (argv.tokenParam || argv["token-param"]),
82
+ timeout: argv.timeout,
83
+ dirs: rawDirs.length ? rawDirs : positionalDirs,
84
+ bindings: argv.binding ? (Array.isArray(argv.binding) ? argv.binding : [argv.binding]) : [],
85
+ dryRun: (argv.dryRun || argv["dry-run"]),
86
+ noMermaid: (argv.noMermaid || argv["no-mermaid"]),
87
+ mermaidLook: argv["mermaid-classic"] ? "classic" : "handDrawn",
88
+ mermaidHandDrawnSeed: argv["mermaid-hand-drawn-seed"],
89
+ force: argv.force,
1881
90
  };
1882
91
  }
1883
- function saveDocsSyncProjects(homeDir, config) {
1884
- const configPath = docsSyncProjectsPath(homeDir);
1885
- fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1886
- }
1887
- function loadDocsSyncDeployments(homeDir) {
1888
- const configPath = docsSyncDeploymentsPath(homeDir);
1889
- if (!fs_1.default.existsSync(configPath)) {
1890
- return { version: 1, bindings: {} };
1891
- }
1892
- const raw = fs_1.default.readFileSync(configPath, "utf8");
1893
- const parsed = JSON.parse(raw);
1894
- const bindings = parsed.bindings && typeof parsed.bindings === "object" ? parsed.bindings : {};
92
+ function toDocsSyncBindArgs(argv) {
1895
93
  return {
1896
- version: typeof parsed.version === "number" ? parsed.version : 1,
1897
- bindings: bindings,
94
+ name: (argv.name || argv.binding),
95
+ dir: argv.dir,
96
+ projectId: argv["project-id"] !== undefined ? Number(argv["project-id"]) : undefined,
97
+ catId: argv.catid !== undefined ? Number(argv.catid) : (argv["cat-id"] !== undefined ? Number(argv["cat-id"]) : undefined),
98
+ templateId: argv["template-id"] !== undefined ? Number(argv["template-id"]) : undefined,
99
+ sourceFiles: argv["source-file"] ? (Array.isArray(argv["source-file"]) ? argv["source-file"] : [argv["source-file"]]) : [],
100
+ clearSourceFiles: argv["clear-source-files"],
1898
101
  };
1899
102
  }
1900
- function saveDocsSyncDeployments(homeDir, config) {
1901
- const configPath = docsSyncDeploymentsPath(homeDir);
1902
- fs_1.default.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
1903
- }
1904
- function resolveBindingDir(rootDir, bindingDir) {
1905
- if (!bindingDir)
1906
- return rootDir;
1907
- return path_1.default.isAbsolute(bindingDir) ? bindingDir : path_1.default.resolve(rootDir, bindingDir);
1908
- }
1909
- function normalizeBindingDir(rootDir, bindingDir) {
1910
- const resolved = resolveBindingDir(rootDir, bindingDir);
1911
- const relative = path_1.default.relative(rootDir, resolved);
1912
- if (!relative || relative === ".")
1913
- return ".";
1914
- if (relative.startsWith("..") || path_1.default.isAbsolute(relative))
1915
- return resolved;
1916
- return relative;
1917
- }
1918
- function getBindingBaseDir(homeDir, rootDir, cwd) {
1919
- if (!isGlobalDocsSyncHome(homeDir)) {
1920
- return { baseDir: rootDir, gitRoot: findGitRoot(cwd), usedGitRoot: false };
1921
- }
1922
- const gitRoot = findGitRoot(cwd);
1923
- if (gitRoot) {
1924
- return { baseDir: gitRoot, gitRoot, usedGitRoot: true };
1925
- }
1926
- return { baseDir: path_1.default.resolve(cwd), gitRoot: null, usedGitRoot: false };
1927
- }
1928
- function normalizeBindingDirForContext(homeDir, rootDir, cwd, bindingDir) {
1929
- const context = getBindingBaseDir(homeDir, rootDir, cwd);
1930
- const resolved = path_1.default.isAbsolute(bindingDir)
1931
- ? bindingDir
1932
- : path_1.default.resolve(context.baseDir, bindingDir);
1933
- const relative = path_1.default.relative(rootDir, resolved);
1934
- if (!relative || relative === ".")
1935
- return ".";
1936
- if (relative.startsWith("..") || path_1.default.isAbsolute(relative))
1937
- return resolved;
1938
- return relative;
1939
- }
1940
- function resolveBindingDirForContext(homeDir, rootDir, cwd, bindingDir) {
1941
- if (!bindingDir)
1942
- return rootDir;
1943
- if (path_1.default.isAbsolute(bindingDir))
1944
- return bindingDir;
1945
- const direct = path_1.default.resolve(rootDir, bindingDir);
1946
- if (!isGlobalDocsSyncHome(homeDir))
1947
- return direct;
1948
- if (fs_1.default.existsSync(direct))
1949
- return direct;
1950
- const { baseDir } = getBindingBaseDir(homeDir, rootDir, cwd);
1951
- const contextual = path_1.default.resolve(baseDir, bindingDir);
1952
- if (fs_1.default.existsSync(contextual))
1953
- return contextual;
1954
- return direct;
1955
- }
1956
- function suggestDocsSyncDir(startDir) {
1957
- const candidates = ["docs", "doc", "documentation", "release-notes"];
1958
- for (const candidate of candidates) {
1959
- const candidatePath = path_1.default.resolve(startDir, candidate);
1960
- if (fs_1.default.existsSync(candidatePath) && fs_1.default.statSync(candidatePath).isDirectory()) {
1961
- const relative = path_1.default.relative(startDir, candidatePath);
1962
- return relative && relative !== "." ? relative : candidate;
1963
- }
1964
- }
1965
- return null;
1966
- }
1967
- function loadMapping(dirPath) {
1968
- const mappingPath = path_1.default.join(dirPath, ".yapi.json");
1969
- if (!fs_1.default.existsSync(mappingPath)) {
1970
- return { mapping: {}, mappingPath };
1971
- }
1972
- const raw = fs_1.default.readFileSync(mappingPath, "utf8");
1973
- const mapping = JSON.parse(raw);
1974
- return { mapping, mappingPath };
1975
- }
1976
- function saveMapping(mapping, mappingPath) {
1977
- fs_1.default.writeFileSync(mappingPath, `${JSON.stringify(mapping, null, 2)}\n`, "utf8");
1978
- }
1979
- function buildDocsSyncHash(markdown, options) {
1980
- const hash = crypto_1.default.createHash("sha1");
1981
- hash.update(options.noMermaid ? "no-mermaid\n" : "mermaid\n");
1982
- if (!options.noMermaid) {
1983
- if (options.mermaidLook) {
1984
- hash.update(`mermaid-look:${options.mermaidLook}\n`);
1985
- }
1986
- if (Number.isFinite(options.mermaidHandDrawnSeed ?? NaN)) {
1987
- hash.update(`mermaid-seed:${options.mermaidHandDrawnSeed}\n`);
1988
- }
1989
- }
1990
- hash.update(markdown);
1991
- return hash.digest("hex");
1992
- }
1993
- function resolveSourceFiles(dirPath, mapping) {
1994
- const sources = Array.isArray(mapping.source_files) ? mapping.source_files : [];
1995
- if (!sources.length) {
1996
- return fs_1.default
1997
- .readdirSync(dirPath)
1998
- .filter((name) => name.endsWith(".md") && name !== "README.md")
1999
- .map((name) => path_1.default.join(dirPath, name))
2000
- .sort((a, b) => a.localeCompare(b));
2001
- }
2002
- return sources.map((source) => {
2003
- const resolved = path_1.default.isAbsolute(source) ? source : path_1.default.resolve(dirPath, source);
2004
- if (!fs_1.default.existsSync(resolved) || !fs_1.default.statSync(resolved).isFile()) {
2005
- throw new Error(`source file not found: ${resolved}`);
2006
- }
2007
- return resolved;
2008
- });
2009
- }
2010
- function normalizeUiBaseUrl(baseUrl) {
2011
- const trimmed = String(baseUrl || "")
2012
- .trim()
2013
- .replace(/\/+$/, "");
2014
- if (!trimmed)
2015
- return "";
2016
- return trimmed.endsWith("/api") ? trimmed.slice(0, -4) : trimmed;
2017
- }
2018
- function buildInterfaceWebUrl(baseUrl, projectId, apiId) {
2019
- const base = normalizeUiBaseUrl(baseUrl);
2020
- const pid = encodeURIComponent(String(projectId ?? ""));
2021
- const aid = encodeURIComponent(String(apiId ?? ""));
2022
- if (!base || !pid || !aid)
2023
- return "";
2024
- return `${base}/project/${pid}/interface/api/${aid}`;
2025
- }
2026
- function normalizePathSegment(value) {
2027
- const trimmed = String(value || "").trim();
2028
- if (!trimmed || trimmed === "/")
2029
- return "";
2030
- const noTrailing = trimmed.replace(/\/+$/, "");
2031
- return noTrailing.startsWith("/") ? noTrailing : `/${noTrailing}`;
2032
- }
2033
- function buildEnvUrl(domain, basepath, apiPath) {
2034
- const base = String(domain || "")
2035
- .trim()
2036
- .replace(/\/+$/, "");
2037
- if (!base)
2038
- return "";
2039
- const normalizedBasepath = normalizePathSegment(basepath);
2040
- const normalizedPath = normalizePathSegment(apiPath);
2041
- return `${base}${normalizedBasepath}${normalizedPath}`;
2042
- }
2043
- function normalizeProjectEnvs(raw) {
2044
- if (!Array.isArray(raw))
2045
- return [];
2046
- const result = [];
2047
- raw.forEach((item) => {
2048
- const name = item && typeof item === "object" && "name" in item
2049
- ? String(item.name || "").trim()
2050
- : "";
2051
- const domain = item && typeof item === "object" && "domain" in item
2052
- ? String(item.domain || "").trim()
2053
- : "";
2054
- if (!domain)
2055
- return;
2056
- result.push({ name: name || domain, domain });
2057
- });
2058
- return result;
2059
- }
2060
- function resolveLimit(value, fallback) {
2061
- if (typeof value === "string") {
2062
- const trimmed = value.trim();
2063
- if (trimmed)
2064
- return trimmed;
2065
- }
2066
- if (Number.isFinite(value ?? NaN)) {
2067
- return String(value);
2068
- }
2069
- return fallback;
2070
- }
2071
- const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
2072
- function updateCachePath() {
2073
- const yapiHome = process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi");
2074
- return path_1.default.join(yapiHome, "update.json");
2075
- }
2076
- function readUpdateCache() {
2077
- try {
2078
- const cachePath = updateCachePath();
2079
- if (!fs_1.default.existsSync(cachePath))
2080
- return {};
2081
- const raw = fs_1.default.readFileSync(cachePath, "utf8");
2082
- const parsed = JSON.parse(raw);
2083
- return parsed && typeof parsed === "object" ? parsed : {};
2084
- }
2085
- catch {
2086
- return {};
2087
- }
2088
- }
2089
- function writeUpdateCache(cache) {
2090
- try {
2091
- const cachePath = updateCachePath();
2092
- fs_1.default.mkdirSync(path_1.default.dirname(cachePath), { recursive: true });
2093
- fs_1.default.writeFileSync(cachePath, `${JSON.stringify(cache, null, 2)}\n`, "utf8");
2094
- }
2095
- catch {
2096
- }
2097
- }
2098
- function compareSemver(a, b) {
2099
- const toParts = (value) => String(value || "")
2100
- .trim()
2101
- .split(".")
2102
- .map((part) => Number(part.replace(/\D/g, "")) || 0);
2103
- const aParts = toParts(a);
2104
- const bParts = toParts(b);
2105
- const len = Math.max(aParts.length, bParts.length);
2106
- for (let i = 0; i < len; i += 1) {
2107
- const diff = (aParts[i] || 0) - (bParts[i] || 0);
2108
- if (diff !== 0)
2109
- return diff;
2110
- }
2111
- return 0;
2112
- }
2113
- function isNewerVersion(latest, current) {
2114
- return compareSemver(latest, current) > 0;
2115
- }
2116
103
  async function fetchLatestVersion(timeoutMs) {
2117
104
  try {
2118
105
  const encoded = encodeURIComponent("@leeguoo/yapi-mcp");
2119
106
  const url = `https://registry.npmjs.org/${encoded}/latest`;
2120
- const response = await fetchWithTimeout(url, { method: "GET" }, timeoutMs);
107
+ const response = await (0, http_1.fetchWithTimeout)(url, { method: "GET" }, timeoutMs);
2121
108
  if (!response.ok)
2122
109
  return null;
2123
110
  const payload = (await response.json());
@@ -2134,13 +121,13 @@ async function checkForUpdates(options) {
2134
121
  return;
2135
122
  if (process.env.CI === "1")
2136
123
  return;
2137
- const currentVersion = readVersion();
124
+ const currentVersion = (0, utils_1.readVersion)();
2138
125
  if (!currentVersion || currentVersion === "unknown")
2139
126
  return;
2140
- const cache = readUpdateCache();
127
+ const cache = (0, utils_1.readUpdateCache)();
2141
128
  const now = Date.now();
2142
129
  let latest = cache.latest;
2143
- const shouldCheck = !cache.lastChecked || now - cache.lastChecked > UPDATE_CHECK_TTL_MS || !latest;
130
+ const shouldCheck = !cache.lastChecked || now - cache.lastChecked > utils_1.UPDATE_CHECK_TTL_MS || !latest;
2144
131
  if (shouldCheck) {
2145
132
  const fetched = await fetchLatestVersion(2000);
2146
133
  if (fetched) {
@@ -2149,23 +136,23 @@ async function checkForUpdates(options) {
2149
136
  }
2150
137
  cache.lastChecked = now;
2151
138
  }
2152
- if (!latest || !isNewerVersion(latest, currentVersion)) {
2153
- writeUpdateCache(cache);
139
+ if (!latest || !(0, utils_1.isNewerVersion)(latest, currentVersion)) {
140
+ (0, utils_1.writeUpdateCache)(cache);
2154
141
  return;
2155
142
  }
2156
- const shouldNotify = cache.lastNotified !== latest || !cache.lastNotifiedAt || now - cache.lastNotifiedAt > UPDATE_CHECK_TTL_MS;
143
+ const shouldNotify = cache.lastNotified !== latest || !cache.lastNotifiedAt || now - cache.lastNotifiedAt > utils_1.UPDATE_CHECK_TTL_MS;
2157
144
  if (shouldNotify) {
2158
145
  console.warn(`update available: ${currentVersion} -> ${latest}. Run: npm install -g @leeguoo/yapi-mcp@latest`);
2159
146
  console.warn("or: pnpm add -g @leeguoo/yapi-mcp@latest");
2160
147
  cache.lastNotified = latest;
2161
148
  cache.lastNotifiedAt = now;
2162
149
  }
2163
- writeUpdateCache(cache);
150
+ (0, utils_1.writeUpdateCache)(cache);
2164
151
  }
2165
152
  function warnIfInstalledSkillsOutdated(options) {
2166
153
  if (options.skip)
2167
154
  return;
2168
- const currentVersion = readVersion();
155
+ const currentVersion = (0, utils_1.readVersion)();
2169
156
  if (!currentVersion || currentVersion === "unknown")
2170
157
  return;
2171
158
  const outdated = (0, metadata_1.findOutdatedSkillInstalls)(currentVersion);
@@ -2176,1043 +163,363 @@ function warnIfInstalledSkillsOutdated(options) {
2176
163
  .join(", ");
2177
164
  console.warn(`skill update available: installed ${summary}, current ${currentVersion}. Run: yapi install-skill --force`);
2178
165
  }
2179
- function runSelfUpdate(rawArgs) {
2180
- if (rawArgs.includes("-h") || rawArgs.includes("--help")) {
2181
- console.log(selfUpdateUsage());
2182
- return 0;
2183
- }
2184
- try {
2185
- (0, child_process_1.execFileSync)(resolveLocalBin("npm"), ["install", "-g", "@leeguoo/yapi-mcp@latest"], {
2186
- stdio: "inherit",
2187
- });
2188
- console.log("Updated yapi CLI to latest.");
2189
- return 0;
2190
- }
2191
- catch (error) {
2192
- console.error(error instanceof Error ? error.message : String(error));
2193
- return 2;
2194
- }
2195
- }
2196
- async function fetchProjectInfo(projectId, baseUrl, request) {
2197
- if (!projectId)
2198
- return null;
2199
- const resp = await request("/api/project/get", "GET", { id: projectId });
2200
- if (resp?.errcode !== 0) {
2201
- throw new Error(`project get failed: ${resp?.errmsg || "unknown error"}`);
2202
- }
2203
- const data = resp?.data || {};
2204
- const name = typeof data.name === "string" ? data.name : "";
2205
- const basepath = typeof data.basepath === "string" ? data.basepath : "";
2206
- const envs = normalizeProjectEnvs(data.env || data.envs);
2207
- return {
2208
- project_id: Number(projectId),
2209
- name,
2210
- basepath,
2211
- envs,
2212
- base_url: baseUrl,
2213
- };
2214
- }
2215
- async function listExistingInterfaces(catId, request) {
2216
- const resp = await request("/api/interface/list_cat", "GET", {
2217
- catid: catId,
2218
- page: 1,
2219
- limit: 10000,
2220
- });
2221
- if (resp?.errcode !== 0) {
2222
- throw new Error(`list_cat failed: ${resp?.errmsg || "unknown error"}`);
2223
- }
2224
- const items = resp?.data?.list || [];
2225
- const byPath = {};
2226
- const byTitle = {};
2227
- const byId = {};
2228
- for (const item of items) {
2229
- const apiPath = item?.path;
2230
- const title = item?.title;
2231
- const itemId = item?._id;
2232
- if (apiPath && itemId)
2233
- byPath[apiPath] = Number(itemId);
2234
- if (title && itemId)
2235
- byTitle[title] = Number(itemId);
2236
- if (itemId) {
2237
- byId[String(itemId)] = { path: apiPath || "", title: title || "" };
2238
- }
2239
- }
2240
- return { byPath, byTitle, byId };
2241
- }
2242
- function buildAddPayload(template, title, apiPath, catId, projectId) {
2243
- return {
2244
- title,
2245
- path: apiPath,
2246
- method: template.method || "GET",
2247
- catid: catId,
2248
- project_id: projectId,
2249
- status: template.status || "undone",
2250
- type: template.type || "static",
2251
- api_opened: template.api_opened || false,
2252
- req_query: template.req_query || [],
2253
- req_headers: template.req_headers || [],
2254
- req_params: template.req_params || [],
2255
- req_body_type: template.req_body_type || "json",
2256
- req_body_form: template.req_body_form || [],
2257
- req_body_is_json_schema: template.req_body_is_json_schema ?? true,
2258
- res_body_type: template.res_body_type || "json",
2259
- res_body: template.res_body || '{"type":"object","title":"title","properties":{}}',
2260
- res_body_is_json_schema: template.res_body_is_json_schema ?? true,
2261
- tag: template.tag || [],
2262
- };
2263
- }
2264
- function buildUpdatePayload(docId, title, markdown, html) {
2265
- const payload = { id: docId, markdown, desc: html };
2266
- if (title) {
2267
- payload.title = title;
2268
- }
2269
- return payload;
2270
- }
2271
- function pickLargestMermaid(metrics) {
2272
- return metrics
2273
- .filter((item) => item.renderer === "mermaid")
2274
- .sort((a, b) => b.renderedBytes - a.renderedBytes)[0];
2275
- }
2276
- function buildDocsSyncPreviewLine(item) {
2277
- const parts = [
2278
- `file=${item.fileName}`,
2279
- `action=${item.action}`,
2280
- `markdown=${formatBytes(item.markdownBytes)}`,
2281
- `html=${formatBytes(item.htmlBytes)}`,
2282
- `payload=${formatBytes(item.payloadBytes)}`,
2283
- `path=${item.apiPath}`,
2284
- ];
2285
- if (item.docId) {
2286
- parts.push(`doc_id=${item.docId}`);
2287
- }
2288
- if (item.largestMermaid) {
2289
- parts.push(`largest_mermaid=#${item.largestMermaid.index}`, `largest_mermaid_svg=${formatBytes(item.largestMermaid.renderedBytes)}`);
2290
- }
2291
- return `preview ${parts.join(" ")}`;
2292
- }
2293
- function buildDocsSyncPayloadTooLargeMessage(fileName, preview, error) {
2294
- const lines = [
2295
- `413 Payload Too Large while syncing ${fileName}`,
2296
- `- request payload: ${formatBytes(preview.payloadBytes)}`,
2297
- `- markdown size: ${formatBytes(preview.markdownBytes)}`,
2298
- `- rendered html size: ${formatBytes(preview.htmlBytes)}`,
2299
- ];
2300
- const limitBytes = parsePayloadLimit(error.body || error.message);
2301
- if (limitBytes) {
2302
- lines.push(`- server limit: ${formatBytes(limitBytes)}`);
2303
- }
2304
- else {
2305
- lines.push("- server limit: unknown (response did not expose an exact value)");
2306
- }
2307
- if (preview.largestMermaid) {
2308
- lines.push(`- largest Mermaid block: #${preview.largestMermaid.index} -> ${formatBytes(preview.largestMermaid.renderedBytes)}`);
2309
- }
2310
- else {
2311
- lines.push("- largest Mermaid block: none");
2312
- }
2313
- lines.push("- suggestion: run `yapi docs-sync --dry-run ...` to preview all files before upload");
2314
- lines.push("- suggestion: split oversized Mermaid diagrams or move them into separate docs");
2315
- return lines.join("\n");
2316
- }
2317
- async function addInterface(title, apiPath, mapping, request) {
2318
- const projectId = Number(mapping.project_id || 0);
2319
- const catId = Number(mapping.catid || 0);
2320
- if (!projectId || !catId) {
2321
- throw new Error("project_id and catid are required to create new docs");
2322
- }
2323
- let template = {};
2324
- if (mapping.template_id) {
2325
- const resp = await request("/api/interface/get", "GET", { id: mapping.template_id });
2326
- if (resp?.errcode !== 0) {
2327
- throw new Error(`interface get failed: ${resp?.errmsg || "unknown error"}`);
2328
- }
2329
- template = resp?.data || {};
2330
- }
2331
- const payload = buildAddPayload(template, title, apiPath, catId, projectId);
2332
- const resp = await request("/api/interface/add", "POST", {}, payload);
2333
- if (resp?.errcode !== 0) {
2334
- throw new Error(`interface add failed: ${resp?.errmsg || "unknown error"}`);
2335
- }
2336
- const newId = resp?.data?._id;
2337
- if (!newId) {
2338
- throw new Error("interface add succeeded but missing id");
2339
- }
2340
- return Number(newId);
2341
- }
2342
- async function updateInterface(docId, title, markdown, html, request) {
2343
- const payload = buildUpdatePayload(docId, title, markdown, html);
2344
- const resp = await request("/api/interface/up", "POST", {}, payload);
2345
- if (resp?.errcode !== 0) {
2346
- throw new Error(`interface up failed: ${resp?.errmsg || "unknown error"}`);
2347
- }
2348
- }
2349
- async function syncDocsDir(dirPath, mapping, options, request) {
2350
- if (!mapping.files || typeof mapping.files !== "object") {
2351
- mapping.files = {};
2352
- }
2353
- if (!mapping.file_hashes || typeof mapping.file_hashes !== "object") {
2354
- mapping.file_hashes = {};
2355
- }
2356
- const envProjectId = process.env.YAPI_PROJECT_ID;
2357
- const envCatId = process.env.YAPI_CATID;
2358
- const envTemplateId = process.env.YAPI_TEMPLATE_ID;
2359
- if (!mapping.project_id && envProjectId)
2360
- mapping.project_id = Number(envProjectId);
2361
- if (!mapping.catid && envCatId)
2362
- mapping.catid = Number(envCatId);
2363
- if (!mapping.template_id && envTemplateId)
2364
- mapping.template_id = Number(envTemplateId);
2365
- const hasTarget = Boolean(mapping.project_id && mapping.catid);
2366
- if (!hasTarget && !options.dryRun) {
2367
- throw new Error("缺少 project_id/catid。请先绑定或配置:yapi docs-sync bind add --name <binding> --dir <path> --project-id <id> --catid <id>,或在目录下添加 .yapi.json,或设置环境变量 YAPI_PROJECT_ID/YAPI_CATID。");
2368
- }
2369
- const { byPath, byTitle, byId } = hasTarget
2370
- ? await listExistingInterfaces(Number(mapping.catid), request)
2371
- : { byPath: {}, byTitle: {}, byId: {} };
2372
- let updated = 0;
2373
- let created = 0;
2374
- let skipped = 0;
2375
- let previewOnly = 0;
2376
- const fileInfos = {};
2377
- const previews = [];
2378
- const files = resolveSourceFiles(dirPath, mapping);
2379
- for (const mdPath of files) {
2380
- const stem = path_1.default.parse(mdPath).name;
2381
- const relName = path_1.default.basename(mdPath);
2382
- const apiPath = `/${stem}`;
2383
- const markdown = fs_1.default.readFileSync(mdPath, "utf8");
2384
- const desiredTitle = (0, markdown_1.extractFirstMarkdownH1Title)(markdown).trim() || stem;
2385
- let docId = mapping.files[relName];
2386
- if (!docId) {
2387
- docId = byPath[apiPath] || byTitle[desiredTitle] || byTitle[stem];
2388
- if (docId)
2389
- mapping.files[relName] = docId;
2390
- }
2391
- let action = docId ? "update" : hasTarget ? "create" : "preview-only";
2392
- if (!docId && hasTarget) {
2393
- created += 1;
2394
- if (!options.dryRun) {
2395
- docId = await addInterface(desiredTitle, apiPath, mapping, request);
2396
- mapping.files[relName] = docId;
2397
- }
2398
- }
2399
- if (!docId && !hasTarget) {
2400
- previewOnly += 1;
2401
- }
2402
- if (docId) {
2403
- const resolvedPath = byId[String(docId)]?.path || apiPath;
2404
- fileInfos[relName] = { docId: Number(docId), apiPath: resolvedPath };
2405
- }
2406
- const contentHash = buildDocsSyncHash(markdown, options);
2407
- const previousHash = mapping.file_hashes[relName];
2408
- const currentTitle = docId ? byId[String(docId)]?.title : "";
2409
- const titleToUpdate = !docId
2410
- ? undefined
2411
- : !currentTitle || currentTitle !== desiredTitle
2412
- ? desiredTitle
2413
- : undefined;
2414
- const shouldSyncTitle = Boolean(titleToUpdate);
2415
- const shouldSkipUnchanged = !options.force &&
2416
- docId &&
2417
- previousHash &&
2418
- previousHash === contentHash &&
2419
- !shouldSyncTitle;
2420
- if (shouldSkipUnchanged && !options.dryRun) {
2421
- skipped += 1;
2422
- continue;
2423
- }
2424
- const logPrefix = `[docs-sync:${relName}]`;
2425
- let mermaidFailed = false;
2426
- let diagramFailed = false;
2427
- const diagramMetrics = [];
2428
- const html = (0, markdown_1.renderMarkdownToHtml)(markdown, {
2429
- noMermaid: options.noMermaid,
2430
- logMermaid: true,
2431
- mermaidLook: options.mermaidLook,
2432
- mermaidHandDrawnSeed: options.mermaidHandDrawnSeed,
2433
- logger: (message) => console.log(`${logPrefix} ${message}`),
2434
- onDiagramRendered: (metric) => {
2435
- diagramMetrics.push(metric);
2436
- },
2437
- onMermaidError: () => {
2438
- mermaidFailed = true;
2439
- },
2440
- onDiagramError: () => {
2441
- diagramFailed = true;
2442
- },
2443
- });
2444
- if (shouldSkipUnchanged) {
2445
- action = "skip";
2446
- skipped += 1;
2447
- }
2448
- const payloadObject = docId && action !== "create"
2449
- ? buildUpdatePayload(docId, titleToUpdate, markdown, html)
2450
- : buildAddPayload({}, desiredTitle, apiPath, Number(mapping.catid || 0), Number(mapping.project_id || 0));
2451
- const preview = {
2452
- fileName: relName,
2453
- action,
2454
- markdownBytes: Buffer.byteLength(markdown, "utf8"),
2455
- htmlBytes: Buffer.byteLength(html, "utf8"),
2456
- payloadBytes: Buffer.byteLength(JSON.stringify(payloadObject), "utf8"),
2457
- apiPath: docId ? fileInfos[relName]?.apiPath || apiPath : apiPath,
2458
- docId: docId ? Number(docId) : undefined,
2459
- largestMermaid: pickLargestMermaid(diagramMetrics),
2460
- };
2461
- previews.push(preview);
2462
- if (!options.dryRun && docId && action !== "skip") {
2463
- try {
2464
- await updateInterface(docId, titleToUpdate, markdown, html, request);
2465
- }
2466
- catch (error) {
2467
- if (error instanceof HttpStatusError && error.status === 413) {
2468
- throw new Error(buildDocsSyncPayloadTooLargeMessage(relName, preview, error));
2469
- }
2470
- throw error;
2471
- }
2472
- }
2473
- if (docId && !mermaidFailed && !diagramFailed) {
2474
- mapping.file_hashes[relName] = contentHash;
2475
- }
2476
- if (action !== "skip") {
2477
- updated += 1;
2478
- }
2479
- }
2480
- return { updated, created, skipped, previewOnly, files: fileInfos, previews };
2481
- }
2482
- function buildEnvUrls(projectInfo, apiPath) {
2483
- const urls = {};
2484
- if (!projectInfo || !projectInfo.envs || !projectInfo.envs.length)
2485
- return urls;
2486
- const basepath = projectInfo.basepath || "";
2487
- projectInfo.envs.forEach((env, index) => {
2488
- const rawKey = env.name || env.domain || `env${index + 1}`;
2489
- const url = buildEnvUrl(env.domain, basepath, apiPath);
2490
- if (!url)
2491
- return;
2492
- let key = rawKey;
2493
- let suffix = 1;
2494
- while (urls[key]) {
2495
- suffix += 1;
2496
- key = `${rawKey}-${suffix}`;
2497
- }
2498
- urls[key] = url;
2499
- });
2500
- return urls;
2501
- }
2502
- async function updateDocsSyncCaches(homeDir, baseUrl, bindingResults, request) {
2503
- const linksConfig = loadDocsSyncLinks(homeDir);
2504
- const projectsConfig = loadDocsSyncProjects(homeDir);
2505
- const deploymentsConfig = loadDocsSyncDeployments(homeDir);
2506
- const projectCache = new Map();
2507
- const resolveProjectInfo = async (projectId) => {
2508
- if (!projectId)
2509
- return null;
2510
- if (projectCache.has(projectId))
2511
- return projectCache.get(projectId) || null;
2512
- const existing = projectsConfig.projects[String(projectId)] || null;
2513
- try {
2514
- const fresh = await fetchProjectInfo(projectId, baseUrl, request);
2515
- if (fresh) {
2516
- projectsConfig.projects[String(projectId)] = fresh;
2517
- projectCache.set(projectId, fresh);
2518
- return fresh;
2519
- }
2520
- }
2521
- catch (error) {
2522
- const message = error instanceof Error ? error.message : String(error);
2523
- console.warn(`project get failed (project_id=${projectId}): ${message}`);
2524
- }
2525
- projectCache.set(projectId, existing);
2526
- return existing;
2527
- };
2528
- for (const [name, result] of Object.entries(bindingResults)) {
2529
- const binding = result.binding;
2530
- const projectId = Number(binding.project_id || 0);
2531
- const linkFiles = {};
2532
- const deploymentFiles = {};
2533
- const projectInfo = await resolveProjectInfo(projectId);
2534
- for (const [fileName, fileInfo] of Object.entries(result.files)) {
2535
- const docUrl = projectId ? buildInterfaceWebUrl(baseUrl, projectId, fileInfo.docId) : "";
2536
- linkFiles[fileName] = {
2537
- doc_id: fileInfo.docId,
2538
- api_path: fileInfo.apiPath,
2539
- url: docUrl,
2540
- };
2541
- deploymentFiles[fileName] = {
2542
- api_path: fileInfo.apiPath,
2543
- env_urls: buildEnvUrls(projectInfo, fileInfo.apiPath),
2544
- };
2545
- }
2546
- linksConfig.bindings[name] = {
2547
- dir: binding.dir,
2548
- project_id: projectId || undefined,
2549
- catid: binding.catid,
2550
- files: linkFiles,
2551
- };
2552
- deploymentsConfig.bindings[name] = {
2553
- dir: binding.dir,
2554
- project_id: projectId || undefined,
2555
- files: deploymentFiles,
2556
- };
2557
- }
2558
- saveDocsSyncLinks(homeDir, linksConfig);
2559
- saveDocsSyncProjects(homeDir, projectsConfig);
2560
- saveDocsSyncDeployments(homeDir, deploymentsConfig);
2561
- }
2562
- async function runDocsSyncBindings(rawArgs) {
2563
- const action = (rawArgs[0] || "list").toLowerCase();
2564
- const args = rawArgs[0] ? rawArgs.slice(1) : rawArgs;
2565
- const options = parseDocsSyncBindArgs(args);
2566
- if (options.help) {
2567
- console.log(docsSyncBindUsage());
2568
- return 0;
2569
- }
2570
- const writeActions = new Set(["add", "update", "remove", "rm", "delete", "del"]);
2571
- const readActions = new Set(["list", "get"]);
2572
- if (!writeActions.has(action) && !readActions.has(action)) {
2573
- console.error(`unknown docs-sync bind action: ${action}`);
2574
- console.error(docsSyncBindUsage());
2575
- return 2;
2576
- }
2577
- const homeDir = resolveDocsSyncHome(process.cwd(), writeActions.has(action));
2578
- if (!homeDir) {
2579
- if (action === "list") {
2580
- console.log("no docs-sync bindings (missing .yapi/docs-sync.json)");
2581
- return 0;
2582
- }
2583
- console.error("missing .yapi/docs-sync.json (run in project root or create one with docs-sync bind add)");
2584
- return 2;
2585
- }
2586
- const rootDir = path_1.default.dirname(homeDir);
2587
- const config = loadDocsSyncConfig(homeDir);
2588
- if (action === "list") {
2589
- const entries = Object.entries(config.bindings).sort(([a], [b]) => a.localeCompare(b));
2590
- if (!entries.length) {
2591
- console.log("no docs-sync bindings");
2592
- return 0;
2593
- }
2594
- for (const [name, binding] of entries) {
2595
- const filesCount = binding.files ? Object.keys(binding.files).length : 0;
2596
- console.log(`${name} dir=${binding.dir} project_id=${binding.project_id ?? ""} catid=${binding.catid ?? ""}` +
2597
- (binding.template_id ? ` template_id=${binding.template_id}` : "") +
2598
- ` files=${filesCount}`);
2599
- }
2600
- return 0;
2601
- }
2602
- if (!options.name) {
2603
- console.error("missing --name for docs-sync bind action");
2604
- console.error(docsSyncBindUsage());
2605
- return 2;
2606
- }
2607
- if (action === "get") {
2608
- const binding = config.bindings[options.name];
2609
- if (!binding) {
2610
- console.error(`binding not found: ${options.name}`);
2611
- return 2;
2612
- }
2613
- console.log(JSON.stringify(binding, null, 2));
2614
- return 0;
2615
- }
2616
- if (action === "remove" || action === "rm" || action === "delete" || action === "del") {
2617
- if (!config.bindings[options.name]) {
2618
- console.error(`binding not found: ${options.name}`);
2619
- return 2;
2620
- }
2621
- delete config.bindings[options.name];
2622
- saveDocsSyncConfig(homeDir, config);
2623
- console.log(`binding removed: ${options.name}`);
2624
- return 0;
2625
- }
2626
- const hasExisting = Boolean(config.bindings[options.name]);
2627
- if (action === "add" && hasExisting) {
2628
- console.error(`binding already exists: ${options.name}`);
2629
- return 2;
2630
- }
2631
- if (action === "update" && !hasExisting) {
2632
- console.error(`binding not found: ${options.name}`);
2633
- return 2;
2634
- }
2635
- if (action === "add") {
2636
- if (!options.dir ||
2637
- !Number.isFinite(options.projectId ?? NaN) ||
2638
- !Number.isFinite(options.catId ?? NaN)) {
2639
- console.error("add requires --dir, --project-id, and --catid");
2640
- console.error(docsSyncBindUsage());
2641
- return 2;
2642
- }
2643
- }
2644
- const existing = config.bindings[options.name] || {};
2645
- const next = {
2646
- dir: existing.dir || "",
2647
- project_id: existing.project_id,
2648
- catid: existing.catid,
2649
- template_id: existing.template_id,
2650
- source_files: existing.source_files ? [...existing.source_files] : undefined,
2651
- files: existing.files ? { ...existing.files } : {},
2652
- file_hashes: existing.file_hashes ? { ...existing.file_hashes } : {},
2653
- };
2654
- if (options.dir) {
2655
- next.dir = normalizeBindingDirForContext(homeDir, rootDir, process.cwd(), options.dir);
2656
- }
2657
- if (options.projectId !== undefined && Number.isFinite(options.projectId)) {
2658
- next.project_id = Number(options.projectId);
2659
- }
2660
- if (options.catId !== undefined && Number.isFinite(options.catId)) {
2661
- next.catid = Number(options.catId);
2662
- }
2663
- if (options.templateId !== undefined && Number.isFinite(options.templateId)) {
2664
- next.template_id = Number(options.templateId);
2665
- }
2666
- if (options.clearSourceFiles) {
2667
- next.source_files = [];
2668
- }
2669
- else if (options.sourceFiles && options.sourceFiles.length) {
2670
- next.source_files = options.sourceFiles;
2671
- }
2672
- if (!next.dir || !next.project_id || !next.catid) {
2673
- console.error("binding requires dir/project_id/catid");
2674
- return 2;
2675
- }
2676
- config.bindings[options.name] = next;
2677
- saveDocsSyncConfig(homeDir, config);
2678
- const resolvedDir = resolveBindingDirForContext(homeDir, rootDir, process.cwd(), next.dir);
2679
- console.log(`${action === "add" ? "binding added" : "binding updated"}: ${options.name}`);
2680
- console.log(`stored_dir=${next.dir}`);
2681
- console.log(`resolved_dir=${resolvedDir}`);
2682
- const gitRoot = findGitRoot(process.cwd());
2683
- if (gitRoot) {
2684
- console.log(`git_root=${gitRoot}`);
2685
- }
2686
- else if (isGlobalDocsSyncHome(homeDir)) {
2687
- console.warn("warning: no git root detected, relative --dir was resolved from current working directory");
2688
- }
2689
- return 0;
2690
- }
2691
- async function runDocsSync(rawArgs) {
2692
- const firstArg = rawArgs[0];
2693
- if (firstArg === "bind" || firstArg === "bindings") {
2694
- return await runDocsSyncBindings(rawArgs.slice(1));
2695
- }
2696
- const options = parseDocsSyncArgs(rawArgs);
2697
- if (options.help) {
2698
- console.log(docsSyncUsage());
2699
- return 0;
2700
- }
2701
- try {
2702
- if (!(0, markdown_1.isPandocAvailable)()) {
2703
- console.warn("pandoc not found, fallback to markdown-it renderer.");
2704
- console.warn("Install pandoc (macOS): brew install pandoc");
2705
- console.warn("More info: https://pandoc.org/installing.html");
2706
- }
2707
- if (!options.noMermaid && !(0, markdown_1.isMmdcAvailable)()) {
2708
- console.warn("mmdc not found, Mermaid blocks will stay as code.");
2709
- console.warn("Install mermaid-cli: npm i -g @mermaid-js/mermaid-cli");
2710
- }
2711
- if (!(0, markdown_1.isPlantUmlAvailable)()) {
2712
- console.warn("plantuml not found, PlantUML blocks will be removed from HTML.");
2713
- console.warn("Install PlantUML (macOS): brew install plantuml");
2714
- }
2715
- if (!(0, markdown_1.isGraphvizAvailable)()) {
2716
- console.warn("graphviz (dot) not found, Graphviz blocks will be removed from HTML.");
2717
- console.warn("Install Graphviz (macOS): brew install graphviz");
2718
- }
2719
- if (!(0, markdown_1.isD2Available)()) {
2720
- console.warn("d2 not found, D2 blocks will be removed from HTML.");
2721
- console.warn("Install D2 (macOS): brew install d2");
2722
- }
2723
- if (options.bindings.length && options.dirs.length) {
2724
- console.error("use --binding or --dir, not both");
2725
- return 2;
2726
- }
2727
- const docsSyncHome = resolveDocsSyncHome(process.cwd(), false);
2728
- const docsSyncConfig = docsSyncHome ? loadDocsSyncConfig(docsSyncHome) : null;
2729
- let bindingNames = options.bindings;
2730
- let useBindings = bindingNames.length > 0;
2731
- if (!useBindings &&
2732
- !options.dirs.length &&
2733
- docsSyncConfig &&
2734
- Object.keys(docsSyncConfig.bindings).length) {
2735
- useBindings = true;
2736
- bindingNames = Object.keys(docsSyncConfig.bindings);
2737
- }
2738
- if (useBindings && (!docsSyncHome || !docsSyncConfig)) {
2739
- console.error("missing .yapi/docs-sync.json (run docs-sync bind add or use --dir)");
2740
- return 2;
2741
- }
2742
- if (useBindings && !bindingNames.length) {
2743
- console.error("no docs-sync bindings found (run docs-sync bind add or use --dir)");
2744
- return 2;
2745
- }
2746
- const usingDefaultDir = !useBindings && !options.dirs.length;
2747
- const dirs = useBindings ? [] : options.dirs.length ? options.dirs : ["docs/release-notes"];
2748
- if (!useBindings) {
2749
- const missingDirs = dirs.filter((dir) => {
2750
- const dirPath = path_1.default.resolve(dir);
2751
- return !fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory();
2752
- });
2753
- if (missingDirs.length) {
2754
- const firstMissing = path_1.default.resolve(missingDirs[0]);
2755
- console.error(`dir not found: ${firstMissing}`);
2756
- if (usingDefaultDir) {
2757
- const suggestion = suggestDocsSyncDir(process.cwd());
2758
- if (suggestion) {
2759
- console.error(`hint: pass --dir ${suggestion}`);
2760
- }
2761
- console.error("hint: or bind a directory with yapi docs-sync bind add");
2762
- }
2763
- else {
2764
- console.error("hint: pass --dir <existing_dir> or bind a directory");
2765
- }
2766
- return 2;
2767
- }
2768
- }
2769
- let config = {};
2770
- let configPath = options.config || "";
2771
- if (options.config) {
2772
- if (fs_1.default.existsSync(configPath)) {
2773
- config = parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"));
2774
- }
2775
- else {
2776
- const init = await initConfigIfMissing(options);
2777
- if (init) {
2778
- config = init.config;
2779
- configPath = init.configPath;
2780
- }
2781
- else {
2782
- console.error(`missing config file: ${configPath}`);
2783
- return 2;
2784
- }
2785
- }
2786
- }
2787
- else {
2788
- const globalPath = globalConfigPath();
2789
- if (fs_1.default.existsSync(globalPath)) {
2790
- configPath = globalPath;
2791
- config = parseSimpleToml(fs_1.default.readFileSync(globalPath, "utf8"));
2792
- }
2793
- else {
2794
- const init = await initConfigIfMissing(options);
2795
- if (init) {
2796
- config = init.config;
2797
- configPath = init.configPath;
2798
- }
2799
- else {
2800
- console.error("missing config: create ~/.yapi/config.toml or pass --config");
2801
- return 2;
2802
- }
2803
- }
2804
- }
2805
- const baseUrl = options.baseUrl || config.base_url || "";
2806
- if (!baseUrl) {
2807
- console.error("missing --base-url or config base_url");
2808
- return 2;
2809
- }
2810
- const projectId = options.projectId || config.project_id || "";
2811
- const rawToken = options.token || config.token || "";
2812
- const token = resolveToken(rawToken, projectId);
2813
- let authMode = (options.authMode || config.auth_mode || "").trim().toLowerCase();
2814
- if (!authMode) {
2815
- authMode = token
2816
- ? "token"
2817
- : options.email || options.password || config.email || config.password
2818
- ? "global"
2819
- : "token";
2820
- }
2821
- if (authMode !== "token" && authMode !== "global") {
2822
- console.error("invalid --auth-mode (use token or global)");
2823
- return 2;
2824
- }
2825
- const headers = {};
2826
- const email = options.email || config.email || "";
2827
- const password = options.password || config.password || "";
2828
- const authService = authMode === "global"
2829
- ? new auth_1.YApiAuthService(baseUrl, email || "", password || "", "warn", {
2830
- timeoutMs: options.timeout || 30000,
2831
- })
2832
- : null;
2833
- const canRelogin = authMode === "global" &&
2834
- Boolean(authService) &&
2835
- Boolean(email) &&
2836
- Boolean(password) &&
2837
- !options.cookie;
2838
- if (options.cookie) {
2839
- headers.Cookie = options.cookie;
2840
- }
2841
- else if (authMode === "global") {
2842
- const cachedCookie = authService?.getCachedCookieHeader();
2843
- if (cachedCookie) {
2844
- headers.Cookie = cachedCookie;
2845
- }
2846
- else if (email && password && authService) {
2847
- try {
2848
- headers.Cookie = await authService.getCookieHeaderWithLogin();
2849
- }
2850
- catch (error) {
2851
- console.error(error instanceof Error ? error.message : String(error));
2852
- return 2;
2853
- }
2854
- }
2855
- else {
2856
- console.error("missing email/password for global auth");
2857
- return 2;
2858
- }
2859
- }
2860
- const request = async (endpoint, method, query = {}, data) => {
2861
- const queryItems = [];
2862
- for (const [key, value] of Object.entries(query)) {
2863
- if (value !== undefined && value !== null) {
2864
- queryItems.push([key, String(value)]);
2865
- }
2866
- }
2867
- const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
2868
- let body;
2869
- if (data !== undefined) {
2870
- body = JSON.stringify(data);
2871
- }
2872
- const sendOnce = async () => {
2873
- const requestHeaders = { ...headers };
2874
- if (body !== undefined) {
2875
- requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
2876
- }
2877
- const response = await fetchWithTimeout(url, {
2878
- method,
2879
- headers: requestHeaders,
2880
- body,
2881
- }, options.timeout || 30000);
2882
- const text = await response.text();
2883
- return { response, text, json: parseJsonMaybe(text) };
2884
- };
2885
- let result = await sendOnce();
2886
- if (canRelogin && looksLikeAuthError(result.response.status, result.json)) {
2887
- try {
2888
- headers.Cookie = await authService.getCookieHeaderWithLogin({ forceLogin: true });
2889
- }
2890
- catch (error) {
2891
- throw new Error(error instanceof Error ? error.message : String(error));
2892
- }
2893
- result = await sendOnce();
2894
- }
2895
- if (!result.response.ok) {
2896
- throw new HttpStatusError(endpoint, result.response.status, result.response.statusText, result.text);
2897
- }
2898
- if (!result.json) {
2899
- throw new Error(`invalid JSON response from ${endpoint}`);
2900
- }
2901
- return result.json;
2902
- };
2903
- if (useBindings) {
2904
- const rootDir = path_1.default.dirname(docsSyncHome);
2905
- const configForBindings = docsSyncConfig;
2906
- const dirToBindings = new Map();
2907
- for (const name of bindingNames) {
2908
- const binding = configForBindings.bindings[name];
2909
- if (!binding) {
2910
- throw new Error(`binding not found: ${name}`);
2911
- }
2912
- const dirPath = resolveBindingDirForContext(docsSyncHome, rootDir, process.cwd(), binding.dir);
2913
- const existing = dirToBindings.get(dirPath) || [];
2914
- existing.push(name);
2915
- dirToBindings.set(dirPath, existing);
2916
- }
2917
- const duplicates = Array.from(dirToBindings.entries()).filter(([, names]) => names.length > 1);
2918
- if (duplicates.length) {
2919
- const lines = [];
2920
- lines.push("invalid docs-sync bindings: multiple bindings share the same dir");
2921
- duplicates.forEach(([dirPath, names]) => {
2922
- lines.push(`- dir=${dirPath} bindings=${names.join(", ")}`);
2923
- });
2924
- lines.push("");
2925
- lines.push("Fix: split docs into separate directories (recommended).");
2926
- lines.push("Example:");
2927
- lines.push(" yapi docs-sync bind update --name <bindingA> --dir docs/yapi-sync/<bindingA>");
2928
- lines.push(" yapi docs-sync bind update --name <bindingB> --dir docs/yapi-sync/<bindingB>");
2929
- throw new Error(lines.join("\n"));
2930
- }
2931
- const bindingResults = {};
2932
- for (const name of bindingNames) {
2933
- const binding = configForBindings.bindings[name];
2934
- if (!binding) {
2935
- throw new Error(`binding not found: ${name}`);
2936
- }
2937
- const dirPath = resolveBindingDirForContext(docsSyncHome, rootDir, process.cwd(), binding.dir);
2938
- if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
2939
- throw new Error(`dir not found for binding ${name}: ${dirPath}`);
2940
- }
2941
- const result = await syncDocsDir(dirPath, binding, options, request);
2942
- if (options.dryRun) {
2943
- console.log(`dry-run preview binding=${name}`);
2944
- result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
2945
- }
2946
- console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} binding=${name} dir=${dirPath}`);
2947
- bindingResults[name] = { binding, files: result.files };
2948
- }
2949
- if (!options.dryRun) {
2950
- saveDocsSyncConfig(docsSyncHome, configForBindings);
2951
- await updateDocsSyncCaches(docsSyncHome, baseUrl, bindingResults, request);
2952
- }
2953
- }
2954
- else {
2955
- for (const dir of dirs) {
2956
- const dirPath = path_1.default.resolve(dir);
2957
- if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) {
2958
- throw new Error(`dir not found: ${dirPath}`);
2959
- }
2960
- const { mapping, mappingPath } = loadMapping(dirPath);
2961
- const result = await syncDocsDir(dirPath, mapping, options, request);
2962
- if (options.dryRun) {
2963
- console.log(`dry-run preview dir=${dirPath}`);
2964
- result.previews.forEach((item) => console.log(buildDocsSyncPreviewLine(item)));
2965
- }
2966
- if (!options.dryRun) {
2967
- saveMapping(mapping, mappingPath);
2968
- }
2969
- console.log(`synced=${result.updated} created=${result.created} skipped=${result.skipped} preview_only=${result.previewOnly} dir=${dirPath}`);
2970
- }
2971
- }
2972
- return 0;
2973
- }
2974
- catch (error) {
2975
- console.error(error instanceof Error ? error.message : String(error));
2976
- return 2;
2977
- }
2978
- }
2979
166
  async function main() {
2980
- const rawArgs = process.argv.slice(2);
2981
- const parsedForUpdate = parseArgs(rawArgs);
167
+ const rawArgs = (0, helpers_1.hideBin)(process.argv);
168
+ const hasHelp = rawArgs.includes("-h") || rawArgs.includes("--help");
169
+ const hasVersion = rawArgs.includes("-V") || rawArgs.includes("--version");
170
+ const hasNoUpdate = rawArgs.includes("--no-update") || rawArgs.includes("--no-update-check");
2982
171
  const subcommand = rawArgs[0] || "";
2983
- const skipUpdateCheck = parsedForUpdate.version ||
2984
- parsedForUpdate.help ||
2985
- parsedForUpdate.noUpdate ||
2986
- rawArgs.includes("-h") ||
2987
- rawArgs.includes("--help");
2988
- await checkForUpdates({ noUpdate: parsedForUpdate.noUpdate, skip: skipUpdateCheck });
172
+ const skipUpdateCheck = hasVersion || hasHelp || hasNoUpdate;
173
+ await checkForUpdates({ noUpdate: hasNoUpdate, skip: skipUpdateCheck });
2989
174
  const skipSkillWarning = skipUpdateCheck ||
2990
175
  subcommand === "install-skill" ||
2991
176
  subcommand === "self-update" ||
2992
177
  process.env.YAPI_NO_SKILL_UPDATE_CHECK === "1";
2993
178
  warnIfInstalledSkillsOutdated({ skip: skipSkillWarning });
2994
- if (subcommand === "self-update") {
2995
- return runSelfUpdate(rawArgs.slice(1));
2996
- }
2997
- if (rawArgs[0] === "install-skill") {
2998
- await (0, install_1.runInstallSkill)(rawArgs.slice(1));
2999
- return 0;
3000
- }
3001
- if (rawArgs[0] === "login") {
3002
- return await runLogin(rawArgs.slice(1));
3003
- }
3004
- if (rawArgs[0] === "logout") {
3005
- return await runLogout(rawArgs.slice(1));
3006
- }
3007
- if (rawArgs[0] === "whoami") {
3008
- return await runWhoami(rawArgs.slice(1));
3009
- }
3010
- if (rawArgs[0] === "search") {
3011
- return await runSearch(rawArgs.slice(1));
3012
- }
3013
- if (rawArgs[0] === "group") {
3014
- return await runGroup(rawArgs.slice(1));
3015
- }
3016
- if (rawArgs[0] === "project") {
3017
- return await runProject(rawArgs.slice(1));
3018
- }
3019
- if (rawArgs[0] === "interface") {
3020
- return await runInterface(rawArgs.slice(1));
3021
- }
3022
- if (rawArgs[0] === "log") {
3023
- return await runLog(rawArgs.slice(1));
3024
- }
3025
- if (rawArgs[0] === "docs-sync") {
3026
- return await runDocsSync(rawArgs.slice(1));
3027
- }
3028
- const options = parseArgs(rawArgs);
3029
- if (options.version) {
3030
- console.log(readVersion());
3031
- return 0;
3032
- }
3033
- if (options.help) {
3034
- console.log(usage());
3035
- return 0;
3036
- }
3037
- if (options.url && options.path) {
3038
- console.error("use --url or --path, not both");
3039
- return 2;
3040
- }
3041
- if (!options.url && !options.path) {
3042
- console.error("missing --path or --url");
3043
- console.error(usage());
3044
- return 2;
3045
- }
3046
- let config = {};
3047
- let configPath = options.config || "";
3048
- if (options.config) {
3049
- if (fs_1.default.existsSync(configPath)) {
3050
- config = parseSimpleToml(fs_1.default.readFileSync(configPath, "utf8"));
3051
- }
3052
- else {
3053
- const init = await initConfigIfMissing(options);
3054
- if (init) {
3055
- config = init.config;
3056
- configPath = init.configPath;
3057
- }
3058
- else {
3059
- console.error(`missing config file: ${configPath}`);
3060
- return 2;
3061
- }
3062
- }
3063
- }
3064
- else {
3065
- const globalPath = globalConfigPath();
3066
- if (fs_1.default.existsSync(globalPath)) {
3067
- configPath = globalPath;
3068
- config = parseSimpleToml(fs_1.default.readFileSync(globalPath, "utf8"));
179
+ let commandHandled = false;
180
+ const parser = (0, yargs_1.default)(rawArgs)
181
+ .scriptName("yapi")
182
+ .version((0, utils_1.readVersion)())
183
+ .alias("V", "version")
184
+ .help("help")
185
+ .alias("h", "help")
186
+ .strict(false)
187
+ .command("login", "Login to YApi", (y) => y
188
+ .option("config", { type: "string", describe: "config file path" })
189
+ .option("base-url", { type: "string", describe: "YApi base URL" })
190
+ .option("login-url", { type: "string", describe: "page URL for browser login" })
191
+ .option("browser", { type: "boolean", describe: "force browser login" })
192
+ .option("email", { type: "string", describe: "login email" })
193
+ .option("password", { type: "string", describe: "login password" })
194
+ .option("timeout", { type: "number", describe: "request timeout in ms", default: 30000 })
195
+ .option("token", { type: "string", describe: "project token" })
196
+ .option("project-id", { type: "string", describe: "project id" }), async (argv) => {
197
+ commandHandled = true;
198
+ process.exitCode = await (0, login_1.runLogin)(toOptions(argv));
199
+ })
200
+ .command("logout", "Logout from YApi", (y) => y
201
+ .option("config", { type: "string", describe: "config file path" })
202
+ .option("base-url", { type: "string", describe: "YApi base URL" }), async (argv) => {
203
+ commandHandled = true;
204
+ process.exitCode = await (0, logout_1.runLogout)(toOptions(argv));
205
+ })
206
+ .command("whoami", "Show current user status", (y) => y
207
+ .option("config", { type: "string" })
208
+ .option("base-url", { type: "string" })
209
+ .option("token", { type: "string" })
210
+ .option("project-id", { type: "string" })
211
+ .option("auth-mode", { type: "string" })
212
+ .option("email", { type: "string" })
213
+ .option("password", { type: "string" })
214
+ .option("cookie", { type: "string" })
215
+ .option("token-param", { type: "string", default: "token" })
216
+ .option("timeout", { type: "number", default: 30000 })
217
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
218
+ commandHandled = true;
219
+ process.exitCode = await (0, whoami_1.runWhoami)(toOptions(argv));
220
+ })
221
+ .command("search", "Search YApi projects and interfaces", (y) => y
222
+ .option("config", { type: "string" })
223
+ .option("base-url", { type: "string" })
224
+ .option("token", { type: "string" })
225
+ .option("project-id", { type: "string" })
226
+ .option("auth-mode", { type: "string" })
227
+ .option("email", { type: "string" })
228
+ .option("password", { type: "string" })
229
+ .option("cookie", { type: "string" })
230
+ .option("token-param", { type: "string", default: "token" })
231
+ .option("q", { type: "string", describe: "search keyword" })
232
+ .option("timeout", { type: "number", default: 30000 })
233
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
234
+ commandHandled = true;
235
+ process.exitCode = await (0, search_1.runSearch)(toOptions(argv));
236
+ })
237
+ .command("group [action]", "Group operations (list, get)", (y) => y
238
+ .positional("action", { type: "string", default: "list" })
239
+ .option("config", { type: "string" })
240
+ .option("base-url", { type: "string" })
241
+ .option("token", { type: "string" })
242
+ .option("project-id", { type: "string" })
243
+ .option("auth-mode", { type: "string" })
244
+ .option("email", { type: "string" })
245
+ .option("password", { type: "string" })
246
+ .option("cookie", { type: "string" })
247
+ .option("token-param", { type: "string", default: "token" })
248
+ .option("timeout", { type: "number", default: 30000 })
249
+ .option("id", { type: "string", describe: "group id" })
250
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
251
+ commandHandled = true;
252
+ const action = String(argv.action || "list").toLowerCase();
253
+ process.exitCode = await (0, group_1.runGroup)(action, toOptions(argv));
254
+ })
255
+ .command("project [action]", "Project operations (list, get, token)", (y) => y
256
+ .positional("action", { type: "string", default: "list" })
257
+ .option("config", { type: "string" })
258
+ .option("base-url", { type: "string" })
259
+ .option("token", { type: "string" })
260
+ .option("project-id", { type: "string" })
261
+ .option("auth-mode", { type: "string" })
262
+ .option("email", { type: "string" })
263
+ .option("password", { type: "string" })
264
+ .option("cookie", { type: "string" })
265
+ .option("token-param", { type: "string", default: "token" })
266
+ .option("timeout", { type: "number", default: 30000 })
267
+ .option("group-id", { type: "string", describe: "group id for list" })
268
+ .option("id", { type: "string", describe: "project id for get" })
269
+ .option("page", { type: "number" })
270
+ .option("limit", { type: "string" })
271
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
272
+ commandHandled = true;
273
+ const action = String(argv.action || "list").toLowerCase();
274
+ process.exitCode = await (0, project_1.runProject)(action, toOptions(argv));
275
+ })
276
+ .command("interface [action] [subaction]", "Interface operations (list, list-menu, get, cat add/update/delete)", (y) => y
277
+ .positional("action", { type: "string", default: "" })
278
+ .positional("subaction", { type: "string", default: "" })
279
+ .option("config", { type: "string" })
280
+ .option("base-url", { type: "string" })
281
+ .option("token", { type: "string" })
282
+ .option("project-id", { type: "string" })
283
+ .option("auth-mode", { type: "string" })
284
+ .option("email", { type: "string" })
285
+ .option("password", { type: "string" })
286
+ .option("cookie", { type: "string" })
287
+ .option("token-param", { type: "string", default: "token" })
288
+ .option("timeout", { type: "number", default: 30000 })
289
+ .option("id", { type: "string" })
290
+ .option("cat-id", { type: "string" })
291
+ .option("catid", { type: "string" })
292
+ .option("name", { type: "string" })
293
+ .option("desc", { type: "string" })
294
+ .option("page", { type: "number" })
295
+ .option("limit", { type: "string" })
296
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
297
+ commandHandled = true;
298
+ const action = String(argv.action || "").toLowerCase();
299
+ const subAction = String(argv.subaction || "").toLowerCase();
300
+ const opts = toOptions(argv);
301
+ process.exitCode = await (0, interface_1.runInterface)(action, subAction, opts, opts);
302
+ })
303
+ .command("col [action]", "Test collection operations (list, cases, run)", (y) => y
304
+ .positional("action", { type: "string", default: "list" })
305
+ .option("config", { type: "string" })
306
+ .option("base-url", { type: "string" })
307
+ .option("token", { type: "string" })
308
+ .option("project-id", { type: "string" })
309
+ .option("auth-mode", { type: "string" })
310
+ .option("email", { type: "string" })
311
+ .option("password", { type: "string" })
312
+ .option("cookie", { type: "string" })
313
+ .option("token-param", { type: "string", default: "token" })
314
+ .option("timeout", { type: "number", default: 30000 })
315
+ .option("id", { type: "string", describe: "collection id" })
316
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
317
+ commandHandled = true;
318
+ const action = String(argv.action || "list").toLowerCase();
319
+ process.exitCode = await (0, col_1.runCol)(action, toOptions(argv));
320
+ })
321
+ .command("export", "Export project data (json/swagger/html)", (y) => y
322
+ .option("config", { type: "string" })
323
+ .option("base-url", { type: "string" })
324
+ .option("token", { type: "string" })
325
+ .option("project-id", { type: "string", describe: "project id (required)" })
326
+ .option("auth-mode", { type: "string" })
327
+ .option("email", { type: "string" })
328
+ .option("password", { type: "string" })
329
+ .option("cookie", { type: "string" })
330
+ .option("token-param", { type: "string", default: "token" })
331
+ .option("timeout", { type: "number", default: 30000 })
332
+ .option("type", { type: "string", describe: "export format: json, swagger, html", default: "json" })
333
+ .option("q", { type: "string", describe: "status filter: all, done, undone", default: "all" })
334
+ .option("name", { type: "string", describe: "output file path" })
335
+ .option("id", { type: "string", describe: "project id (alias)" })
336
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
337
+ commandHandled = true;
338
+ process.exitCode = await (0, export_1.runExport)(toOptions(argv));
339
+ })
340
+ .command("env", "Show project environments", (y) => y
341
+ .option("config", { type: "string" })
342
+ .option("base-url", { type: "string" })
343
+ .option("token", { type: "string" })
344
+ .option("project-id", { type: "string", describe: "project id (required)" })
345
+ .option("auth-mode", { type: "string" })
346
+ .option("email", { type: "string" })
347
+ .option("password", { type: "string" })
348
+ .option("cookie", { type: "string" })
349
+ .option("token-param", { type: "string", default: "token" })
350
+ .option("timeout", { type: "number", default: 30000 })
351
+ .option("id", { type: "string", describe: "project id (alias)" })
352
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
353
+ commandHandled = true;
354
+ process.exitCode = await (0, env_1.runEnv)(toOptions(argv));
355
+ })
356
+ .command("member [action]", "Member management (list, group)", (y) => y
357
+ .positional("action", { type: "string", default: "list" })
358
+ .option("config", { type: "string" })
359
+ .option("base-url", { type: "string" })
360
+ .option("token", { type: "string" })
361
+ .option("project-id", { type: "string", describe: "project id" })
362
+ .option("group-id", { type: "string", describe: "group id" })
363
+ .option("auth-mode", { type: "string" })
364
+ .option("email", { type: "string" })
365
+ .option("password", { type: "string" })
366
+ .option("cookie", { type: "string" })
367
+ .option("token-param", { type: "string", default: "token" })
368
+ .option("timeout", { type: "number", default: 30000 })
369
+ .option("id", { type: "string", describe: "resource id" })
370
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
371
+ commandHandled = true;
372
+ const action = String(argv.action || "list").toLowerCase();
373
+ process.exitCode = await (0, member_1.runMember)(action, toOptions(argv));
374
+ })
375
+ .command("follow", "List followed projects", (y) => y
376
+ .option("config", { type: "string" })
377
+ .option("base-url", { type: "string" })
378
+ .option("token", { type: "string" })
379
+ .option("auth-mode", { type: "string" })
380
+ .option("email", { type: "string" })
381
+ .option("password", { type: "string" })
382
+ .option("cookie", { type: "string" })
383
+ .option("token-param", { type: "string", default: "token" })
384
+ .option("timeout", { type: "number", default: 30000 })
385
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
386
+ commandHandled = true;
387
+ process.exitCode = await (0, follow_1.runFollow)(toOptions(argv));
388
+ })
389
+ .command("user [action]", "User operations (list, search)", (y) => y
390
+ .positional("action", { type: "string", default: "list" })
391
+ .option("config", { type: "string" })
392
+ .option("base-url", { type: "string" })
393
+ .option("token", { type: "string" })
394
+ .option("auth-mode", { type: "string" })
395
+ .option("email", { type: "string" })
396
+ .option("password", { type: "string" })
397
+ .option("cookie", { type: "string" })
398
+ .option("token-param", { type: "string", default: "token" })
399
+ .option("timeout", { type: "number", default: 30000 })
400
+ .option("q", { type: "string", describe: "search keyword" })
401
+ .option("name", { type: "string", describe: "search keyword (alias)" })
402
+ .option("page", { type: "number" })
403
+ .option("limit", { type: "string" })
404
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
405
+ commandHandled = true;
406
+ const action = String(argv.action || "list").toLowerCase();
407
+ process.exitCode = await (0, user_1.runUser)(action, toOptions(argv));
408
+ })
409
+ .command("log [action]", "Log operations (list)", (y) => y
410
+ .positional("action", { type: "string", default: "list" })
411
+ .option("config", { type: "string" })
412
+ .option("base-url", { type: "string" })
413
+ .option("token", { type: "string" })
414
+ .option("project-id", { type: "string" })
415
+ .option("auth-mode", { type: "string" })
416
+ .option("email", { type: "string" })
417
+ .option("password", { type: "string" })
418
+ .option("cookie", { type: "string" })
419
+ .option("token-param", { type: "string", default: "token" })
420
+ .option("timeout", { type: "number", default: 30000 })
421
+ .option("type", { type: "string", describe: "log type" })
422
+ .option("type-id", { type: "string", describe: "log type id" })
423
+ .option("page", { type: "number" })
424
+ .option("limit", { type: "string" })
425
+ .option("no-pretty", { type: "boolean" }), async (argv) => {
426
+ commandHandled = true;
427
+ const action = String(argv.action || "list").toLowerCase();
428
+ process.exitCode = await (0, log_1.runLog)(action, toOptions(argv));
429
+ })
430
+ .command("docs-sync [subcmd] [bindaction]", "Sync markdown docs to YApi", (y) => y
431
+ .positional("subcmd", { type: "string", default: "" })
432
+ .positional("bindaction", { type: "string", default: "" })
433
+ .option("config", { type: "string" })
434
+ .option("base-url", { type: "string" })
435
+ .option("token", { type: "string" })
436
+ .option("project-id", { type: "string" })
437
+ .option("auth-mode", { type: "string" })
438
+ .option("email", { type: "string" })
439
+ .option("password", { type: "string" })
440
+ .option("cookie", { type: "string" })
441
+ .option("token-param", { type: "string", default: "token" })
442
+ .option("timeout", { type: "number", default: 30000 })
443
+ .option("dir", { type: "string", array: true, describe: "docs directory (repeatable)" })
444
+ .option("binding", { type: "string", array: true, describe: "binding name (repeatable)" })
445
+ .option("dry-run", { type: "boolean", describe: "compute but do not update" })
446
+ .option("no-mermaid", { type: "boolean", describe: "do not render mermaid" })
447
+ .option("mermaid-hand-drawn", { type: "boolean", describe: "force hand-drawn look" })
448
+ .option("mermaid-classic", { type: "boolean", describe: "render with classic look" })
449
+ .option("mermaid-hand-drawn-seed", { type: "number", describe: "hand-drawn seed" })
450
+ .option("force", { type: "boolean", describe: "sync all files even if unchanged" })
451
+ .option("name", { type: "string", describe: "binding name (for bind actions)" })
452
+ .option("catid", { type: "string", describe: "YApi category id" })
453
+ .option("cat-id", { type: "string", describe: "YApi category id" })
454
+ .option("template-id", { type: "number", describe: "template interface id" })
455
+ .option("source-file", { type: "string", array: true, describe: "sync specific file(s)" })
456
+ .option("clear-source-files", { type: "boolean", describe: "clear source_files list" }), async (argv) => {
457
+ commandHandled = true;
458
+ const subcmd = String(argv.subcmd || "").toLowerCase();
459
+ if (subcmd === "bind" || subcmd === "bindings") {
460
+ const bindAction = String(argv.bindaction || "list").toLowerCase();
461
+ process.exitCode = await (0, docs_sync_1.runDocsSyncBindings)(bindAction, toDocsSyncBindArgs(argv));
3069
462
  }
3070
463
  else {
3071
- const init = await initConfigIfMissing(options);
3072
- if (init) {
3073
- config = init.config;
3074
- configPath = init.configPath;
3075
- }
3076
- else {
3077
- console.error("missing config: create ~/.yapi/config.toml or pass --config");
3078
- return 2;
3079
- }
3080
- }
3081
- }
3082
- const authBaseUrl = options.baseUrl || config.base_url || "";
3083
- const baseUrl = options.url ? null : authBaseUrl;
3084
- const endpoint = options.url || options.path || "";
3085
- if (!options.url && !baseUrl) {
3086
- console.error("missing --base-url or config base_url");
3087
- return 2;
3088
- }
3089
- const projectId = options.projectId || config.project_id || "";
3090
- const rawToken = options.token || config.token || "";
3091
- const token = resolveToken(rawToken, projectId);
3092
- let authMode = (options.authMode || config.auth_mode || "").trim().toLowerCase();
3093
- if (!authMode) {
3094
- authMode = token
3095
- ? "token"
3096
- : options.email || options.password || config.email || config.password
3097
- ? "global"
3098
- : "token";
3099
- }
3100
- if (authMode !== "token" && authMode !== "global") {
3101
- console.error("invalid --auth-mode (use token or global)");
3102
- return 2;
3103
- }
3104
- if (authMode === "global" && !authBaseUrl) {
3105
- console.error("missing --base-url or config base_url for global auth");
3106
- return 2;
3107
- }
3108
- const headers = {};
3109
- for (const header of options.header || []) {
3110
- const [key, value] = parseHeader(header);
3111
- headers[key] = value;
3112
- }
3113
- const email = options.email || config.email || "";
3114
- const password = options.password || config.password || "";
3115
- const authService = authMode === "global"
3116
- ? new auth_1.YApiAuthService(authBaseUrl, email || "", password || "", "warn", {
3117
- timeoutMs: options.timeout || 30000,
3118
- })
3119
- : null;
3120
- const canRelogin = authMode === "global" &&
3121
- Boolean(authService) &&
3122
- Boolean(email) &&
3123
- Boolean(password) &&
3124
- !options.cookie;
3125
- if (options.cookie) {
3126
- headers.Cookie = options.cookie;
3127
- }
3128
- else if (authMode === "global") {
3129
- const cachedCookie = authService?.getCachedCookieHeader();
3130
- if (cachedCookie) {
3131
- headers.Cookie = cachedCookie;
3132
- }
3133
- else if (email && password && authService) {
3134
- try {
3135
- headers.Cookie = await authService.getCookieHeaderWithLogin();
3136
- }
3137
- catch (error) {
3138
- console.error(error instanceof Error ? error.message : String(error));
3139
- return 2;
3140
- }
3141
- }
3142
- else {
3143
- console.error("missing email/password for global auth");
3144
- return 2;
3145
- }
3146
- }
3147
- const queryItems = [];
3148
- for (const query of options.query || []) {
3149
- queryItems.push(...parseQueryArg(query));
3150
- }
3151
- const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
3152
- let dataRaw = null;
3153
- if (options.dataFile) {
3154
- dataRaw = fs_1.default.readFileSync(options.dataFile, "utf8");
3155
- }
3156
- else if (options.data !== undefined) {
3157
- dataRaw = options.data;
3158
- }
3159
- let body;
3160
- const method = (options.method || "GET").toUpperCase();
3161
- if (dataRaw !== null && method !== "GET" && method !== "HEAD") {
3162
- try {
3163
- const parsed = JSON.parse(dataRaw);
3164
- body = JSON.stringify(parsed);
3165
- if (!headers["Content-Type"])
3166
- headers["Content-Type"] = "application/json";
3167
- }
3168
- catch {
3169
- body = String(dataRaw);
3170
- if (!headers["Content-Type"])
3171
- headers["Content-Type"] = "text/plain";
3172
- }
3173
- }
3174
- const sendOnce = async () => {
3175
- const response = await fetchWithTimeout(url, {
3176
- method,
3177
- headers,
3178
- body,
3179
- }, options.timeout || 30000);
3180
- const text = await response.text();
3181
- return { response, text, json: parseJsonMaybe(text) };
3182
- };
3183
- let result;
3184
- try {
3185
- result = await sendOnce();
3186
- }
3187
- catch (error) {
3188
- console.error("request failed: " + (error instanceof Error ? error.message : String(error)));
3189
- return 2;
3190
- }
3191
- if (canRelogin && looksLikeAuthError(result.response.status, result.json)) {
3192
- try {
3193
- headers.Cookie = await authService.getCookieHeaderWithLogin({ forceLogin: true });
3194
- result = await sendOnce();
3195
- }
3196
- catch (error) {
3197
- console.error(error instanceof Error ? error.message : String(error));
3198
- return 2;
3199
- }
3200
- }
3201
- const { text } = result;
3202
- if (options.noPretty) {
3203
- console.log(text);
3204
- return 0;
3205
- }
3206
- try {
3207
- const payload = result.json ?? JSON.parse(text);
3208
- console.log(JSON.stringify(payload, null, 2));
3209
- }
3210
- catch {
3211
- console.log(text);
3212
- }
464
+ process.exitCode = await (0, docs_sync_1.runDocsSync)(toDocsSyncOptions(argv));
465
+ }
466
+ })
467
+ .command("install-skill", "Install a skill", (y) => y, async () => {
468
+ commandHandled = true;
469
+ const idx = process.argv.indexOf("install-skill");
470
+ const remaining = idx >= 0 ? process.argv.slice(idx + 1) : [];
471
+ process.exitCode = await (0, install_skill_1.runInstallSkillCommand)(remaining);
472
+ })
473
+ .command("self-update", "Update yapi CLI to latest version", (y) => y, () => {
474
+ commandHandled = true;
475
+ process.exitCode = (0, self_update_1.runSelfUpdate)();
476
+ })
477
+ .option("path", { type: "string", describe: "API path (e.g., /api/interface/get)" })
478
+ .option("url", { type: "string", describe: "full URL (overrides base-url/path)" })
479
+ .option("method", { type: "string", describe: "HTTP method", default: "GET" })
480
+ .option("query", { type: "string", array: true, describe: "query param key=value (repeatable)" })
481
+ .option("header", { type: "string", array: true, describe: "request header Header:Value (repeatable)" })
482
+ .option("data", { type: "string", describe: "request body (JSON or text)" })
483
+ .option("data-file", { type: "string", describe: "request body file" })
484
+ .option("config", { type: "string", describe: "config file path (default: ~/.yapi/config.toml)" })
485
+ .option("base-url", { type: "string", describe: "YApi base URL" })
486
+ .option("token", { type: "string", describe: "project token (supports projectId:token)" })
487
+ .option("project-id", { type: "string", describe: "select token for project" })
488
+ .option("auth-mode", { type: "string", describe: "token or global" })
489
+ .option("email", { type: "string", describe: "login email for global mode" })
490
+ .option("password", { type: "string", describe: "login password for global mode" })
491
+ .option("cookie", { type: "string", describe: "cookie for global mode" })
492
+ .option("token-param", { type: "string", describe: "token query param name", default: "token" })
493
+ .option("timeout", { type: "number", describe: "request timeout in ms", default: 30000 })
494
+ .option("no-update", { type: "boolean", describe: "disable update check", default: false })
495
+ .option("no-pretty", { type: "boolean", describe: "print raw response", default: false })
496
+ .option("id", { type: "string", describe: "resource id" })
497
+ .option("name", { type: "string", describe: "category name" })
498
+ .option("desc", { type: "string", describe: "category description" })
499
+ .option("cat-id", { type: "string", describe: "category id" })
500
+ .option("catid", { type: "string", describe: "category id" })
501
+ .option("group-id", { type: "string", describe: "group id" })
502
+ .option("type", { type: "string", describe: "log type" })
503
+ .option("type-id", { type: "string", describe: "log type id" })
504
+ .option("page", { type: "number", describe: "page number" })
505
+ .option("limit", { type: "string", describe: "page size (number or 'all')" })
506
+ .option("q", { type: "string", describe: "search keyword" })
507
+ .option("login-url", { type: "string", describe: "page URL for browser login" })
508
+ .option("browser", { type: "boolean", describe: "force browser login" });
509
+ const argv = await parser.parse();
510
+ if (commandHandled) {
511
+ return process.exitCode ?? 0;
512
+ }
513
+ const opts = toOptions(argv);
514
+ if (opts.path || opts.url) {
515
+ return await (0, request_1.runRequest)(opts);
516
+ }
517
+ parser.showHelp();
3213
518
  return 0;
3214
519
  }
3215
520
  main().then((code) => {
3216
- process.exitCode = code;
521
+ if (process.exitCode === undefined || process.exitCode === null) {
522
+ process.exitCode = code;
523
+ }
3217
524
  });
3218
525
  //# sourceMappingURL=yapi-cli.js.map