@leeguoo/yapi-mcp 0.3.20 → 0.3.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/yapi-cli.js CHANGED
@@ -164,6 +164,10 @@ function parseArgs(argv) {
164
164
  options.version = true;
165
165
  continue;
166
166
  }
167
+ if (arg === "--no-update" || arg === "--no-update-check") {
168
+ options.noUpdate = true;
169
+ continue;
170
+ }
167
171
  if (arg === "--config") {
168
172
  options.config = argv[++i];
169
173
  continue;
@@ -308,6 +312,84 @@ function parseArgs(argv) {
308
312
  options.timeout = Number(arg.slice(10));
309
313
  continue;
310
314
  }
315
+ if (arg === "--name") {
316
+ options.name = argv[++i];
317
+ continue;
318
+ }
319
+ if (arg.startsWith("--name=")) {
320
+ options.name = arg.slice(7);
321
+ continue;
322
+ }
323
+ if (arg === "--desc") {
324
+ options.desc = argv[++i];
325
+ continue;
326
+ }
327
+ if (arg.startsWith("--desc=")) {
328
+ options.desc = arg.slice(7);
329
+ continue;
330
+ }
331
+ if (arg === "--cat-id" || arg === "--catid") {
332
+ options.catId = argv[++i];
333
+ continue;
334
+ }
335
+ if (arg.startsWith("--cat-id=")) {
336
+ options.catId = arg.slice(9);
337
+ continue;
338
+ }
339
+ if (arg.startsWith("--catid=")) {
340
+ options.catId = arg.slice(8);
341
+ continue;
342
+ }
343
+ if (arg === "--id") {
344
+ options.id = argv[++i];
345
+ continue;
346
+ }
347
+ if (arg.startsWith("--id=")) {
348
+ options.id = arg.slice(5);
349
+ continue;
350
+ }
351
+ if (arg === "--group-id") {
352
+ options.groupId = argv[++i];
353
+ continue;
354
+ }
355
+ if (arg.startsWith("--group-id=")) {
356
+ options.groupId = arg.slice(11);
357
+ continue;
358
+ }
359
+ if (arg === "--type") {
360
+ options.type = argv[++i];
361
+ continue;
362
+ }
363
+ if (arg.startsWith("--type=")) {
364
+ options.type = arg.slice(7);
365
+ continue;
366
+ }
367
+ if (arg === "--type-id") {
368
+ options.typeId = argv[++i];
369
+ continue;
370
+ }
371
+ if (arg.startsWith("--type-id=")) {
372
+ options.typeId = arg.slice(10);
373
+ continue;
374
+ }
375
+ if (arg === "--page") {
376
+ options.page = Number(argv[++i]);
377
+ continue;
378
+ }
379
+ if (arg.startsWith("--page=")) {
380
+ options.page = Number(arg.slice(7));
381
+ continue;
382
+ }
383
+ if (arg === "--limit") {
384
+ const raw = String(argv[++i] ?? "").trim();
385
+ options.limit = raw.toLowerCase() === "all" ? "all" : Number(raw);
386
+ continue;
387
+ }
388
+ if (arg.startsWith("--limit=")) {
389
+ const raw = String(arg.slice(8)).trim();
390
+ options.limit = raw.toLowerCase() === "all" ? "all" : Number(raw);
391
+ continue;
392
+ }
311
393
  if (arg === "--no-pretty") {
312
394
  options.noPretty = true;
313
395
  continue;
@@ -551,6 +633,10 @@ function usage() {
551
633
  return [
552
634
  "Usage:",
553
635
  " yapi --path /api/interface/get --query id=123",
636
+ " yapi group [options]",
637
+ " yapi project [options]",
638
+ " yapi interface [options]",
639
+ " yapi log [options]",
554
640
  " yapi docs-sync [options] [dir...]",
555
641
  " yapi docs-sync bind <action> [options]",
556
642
  " yapi login [options]",
@@ -561,7 +647,7 @@ function usage() {
561
647
  " --config <path> config file path (default: ~/.yapi/config.toml)",
562
648
  " --base-url <url> YApi base URL",
563
649
  " --token <token> project token (supports projectId:token)",
564
- " --project-id <id> select token for project",
650
+ " --project-id <id> select token for project (filters search results)",
565
651
  " --auth-mode <mode> token or global",
566
652
  " --email <email> login email for global mode",
567
653
  " --password <pwd> login password for global mode",
@@ -575,6 +661,16 @@ function usage() {
575
661
  " --data <payload> request body (JSON or text)",
576
662
  " --data-file <file> request body file",
577
663
  " --timeout <ms> request timeout in ms",
664
+ " --id <id> resource id (for group/project/interface)",
665
+ " --name <name> category name (for interface cat)",
666
+ " --desc <desc> category description (for interface cat)",
667
+ " --cat-id <id> category id (for interface cat)",
668
+ " --group-id <id> group id (for project list)",
669
+ " --type <type> log type (for log list)",
670
+ " --type-id <id> log type id (for log list)",
671
+ " --page <n> page number (for list)",
672
+ " --limit <n|all> page size (for list)",
673
+ " --no-update disable update check",
578
674
  " --no-pretty print raw response",
579
675
  "Docs-sync options:",
580
676
  " --dir <path> docs directory (repeatable; default: docs/release-notes)",
@@ -689,6 +785,105 @@ function searchUsage() {
689
785
  " -h, --help show help",
690
786
  ].join("\n");
691
787
  }
788
+ function groupUsage() {
789
+ return [
790
+ "Usage:",
791
+ " yapi group list",
792
+ " yapi group get --id <group_id>",
793
+ "Options:",
794
+ " --config <path> config file path (default: ~/.yapi/config.toml)",
795
+ " --base-url <url> YApi base URL",
796
+ " --token <token> project token (supports projectId:token)",
797
+ " --project-id <id> select token for project",
798
+ " --auth-mode <mode> token or global",
799
+ " --email <email> login email for global mode",
800
+ " --password <pwd> login password for global mode",
801
+ " --cookie <cookie> cookie for global mode",
802
+ " --token-param <name> token query param name (default: token)",
803
+ " --timeout <ms> request timeout in ms",
804
+ " --id <id> group id (for get)",
805
+ " --no-pretty print raw response",
806
+ " -h, --help show help",
807
+ ].join("\n");
808
+ }
809
+ function projectUsage() {
810
+ return [
811
+ "Usage:",
812
+ " yapi project list --group-id <group_id> [--page <n>] [--limit <n>]",
813
+ " yapi project get --id <project_id>",
814
+ " yapi project token --project-id <project_id>",
815
+ "Options:",
816
+ " --config <path> config file path (default: ~/.yapi/config.toml)",
817
+ " --base-url <url> YApi base URL",
818
+ " --token <token> project token (supports projectId:token)",
819
+ " --project-id <id> select token for project",
820
+ " --auth-mode <mode> token or global",
821
+ " --email <email> login email for global mode",
822
+ " --password <pwd> login password for global mode",
823
+ " --cookie <cookie> cookie for global mode",
824
+ " --token-param <name> token query param name (default: token)",
825
+ " --timeout <ms> request timeout in ms",
826
+ " --group-id <id> group id (for list)",
827
+ " --id <id> project id (for get)",
828
+ " --page <n> page number (default: 1)",
829
+ " --limit <n> page size (default: 10)",
830
+ " --no-pretty print raw response",
831
+ " -h, --help show help",
832
+ ].join("\n");
833
+ }
834
+ function interfaceUsage() {
835
+ return [
836
+ "Usage:",
837
+ " yapi interface list --project-id <project_id> [--page <n>] [--limit <n>]",
838
+ " yapi interface list-menu --project-id <project_id>",
839
+ " yapi interface get --id <api_id>",
840
+ " yapi interface cat add --project-id <project_id> --name <name> [--desc <desc>]",
841
+ " yapi interface cat update --cat-id <cat_id> --name <name> [--desc <desc>]",
842
+ " yapi interface cat delete --cat-id <cat_id>",
843
+ "Options:",
844
+ " --config <path> config file path (default: ~/.yapi/config.toml)",
845
+ " --base-url <url> YApi base URL",
846
+ " --token <token> project token (supports projectId:token)",
847
+ " --project-id <id> select token for project",
848
+ " --auth-mode <mode> token or global",
849
+ " --email <email> login email for global mode",
850
+ " --password <pwd> login password for global mode",
851
+ " --cookie <cookie> cookie for global mode",
852
+ " --token-param <name> token query param name (default: token)",
853
+ " --timeout <ms> request timeout in ms",
854
+ " --id <id> interface id (for get)",
855
+ " --cat-id <id> category id (for cat update/delete)",
856
+ " --name <name> category name (for cat add/update)",
857
+ " --desc <desc> category description (for cat add/update)",
858
+ " --page <n> page number (default: 1)",
859
+ " --limit <n|all> page size (default: 20)",
860
+ " --no-pretty print raw response",
861
+ " -h, --help show help",
862
+ ].join("\n");
863
+ }
864
+ function logUsage() {
865
+ return [
866
+ "Usage:",
867
+ " yapi log list --type <type> --type-id <id> [--page <n>] [--limit <n>]",
868
+ "Options:",
869
+ " --config <path> config file path (default: ~/.yapi/config.toml)",
870
+ " --base-url <url> YApi base URL",
871
+ " --token <token> project token (supports projectId:token)",
872
+ " --project-id <id> select token for project",
873
+ " --auth-mode <mode> token or global",
874
+ " --email <email> login email for global mode",
875
+ " --password <pwd> login password for global mode",
876
+ " --cookie <cookie> cookie for global mode",
877
+ " --token-param <name> token query param name (default: token)",
878
+ " --timeout <ms> request timeout in ms",
879
+ " --type <type> log type (e.g., group/project)",
880
+ " --type-id <id> log type id",
881
+ " --page <n> page number (default: 1)",
882
+ " --limit <n> page size (default: 10)",
883
+ " --no-pretty print raw response",
884
+ " -h, --help show help",
885
+ ].join("\n");
886
+ }
692
887
  function escapeTomlValue(value) {
693
888
  return String(value || "")
694
889
  .replace(/\\/g, "\\\\")
@@ -726,8 +921,12 @@ function promptHidden(question) {
726
921
  output: process.stdout,
727
922
  terminal: true,
728
923
  });
924
+ const output = rl.output || process.stdout;
729
925
  const originalWrite = rl
730
926
  ._writeToOutput;
927
+ if (question) {
928
+ output.write(question);
929
+ }
731
930
  rl.stdoutMuted = true;
732
931
  rl._writeToOutput =
733
932
  function writeToOutput(value) {
@@ -737,12 +936,15 @@ function promptHidden(question) {
737
936
  originalWrite.call(this, value);
738
937
  }
739
938
  else {
740
- rl.output.write(value);
939
+ output.write(value);
741
940
  }
742
941
  };
743
942
  return new Promise((resolve) => {
744
- rl.question(question, (answer) => {
943
+ rl.question("", (answer) => {
745
944
  rl.stdoutMuted = false;
945
+ if (question) {
946
+ output.write("\n");
947
+ }
746
948
  rl.close();
747
949
  resolve(answer);
748
950
  });
@@ -789,7 +991,7 @@ function readVersion() {
789
991
  return "unknown";
790
992
  }
791
993
  }
792
- async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buildQueryItems) {
994
+ async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buildQueryItems, transform) {
793
995
  const options = parseArgs(rawArgs);
794
996
  if (options.help) {
795
997
  console.log(usageFn());
@@ -859,17 +1061,32 @@ async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buil
859
1061
  }
860
1062
  }
861
1063
  let queryItems = [];
1064
+ let method = "GET";
1065
+ let data = undefined;
862
1066
  if (buildQueryItems) {
863
1067
  const result = buildQueryItems(options);
864
1068
  if (!result.ok)
865
1069
  return 2;
866
1070
  queryItems = result.queryItems || [];
1071
+ if (result.method) {
1072
+ method = result.method;
1073
+ }
1074
+ if (result.data !== undefined) {
1075
+ data = result.data;
1076
+ }
867
1077
  }
868
1078
  const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
869
1079
  const sendOnce = async () => {
1080
+ let body;
1081
+ const requestHeaders = { ...headers };
1082
+ if (data !== undefined) {
1083
+ body = JSON.stringify(data);
1084
+ requestHeaders["Content-Type"] = "application/json;charset=UTF-8";
1085
+ }
870
1086
  const response = await fetchWithTimeout(url, {
871
- method: "GET",
872
- headers,
1087
+ method,
1088
+ headers: requestHeaders,
1089
+ body,
873
1090
  }, options.timeout || 30000);
874
1091
  const text = await response.text();
875
1092
  return { response, text, json: parseJsonMaybe(text) };
@@ -899,7 +1116,8 @@ async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buil
899
1116
  }
900
1117
  try {
901
1118
  const payload = result.json ?? JSON.parse(text);
902
- console.log(JSON.stringify(payload, null, 2));
1119
+ const nextPayload = transform ? transform(payload, options) : payload;
1120
+ console.log(JSON.stringify(nextPayload ?? payload, null, 2));
903
1121
  }
904
1122
  catch {
905
1123
  console.log(text);
@@ -984,8 +1202,257 @@ async function runSearch(rawArgs) {
984
1202
  return { ok: false };
985
1203
  }
986
1204
  return { ok: true, queryItems: [["q", keyword]] };
1205
+ }, (payload, options) => {
1206
+ const filterProjectId = String(options.projectId || "").trim();
1207
+ if (!filterProjectId)
1208
+ return payload;
1209
+ if (!payload || typeof payload !== "object")
1210
+ return payload;
1211
+ const record = payload;
1212
+ const data = record.data;
1213
+ if (!data || typeof data !== "object")
1214
+ return payload;
1215
+ const nextData = { ...data };
1216
+ if (Array.isArray(data.interface)) {
1217
+ nextData.interface = data.interface.filter((item) => {
1218
+ const projectId = item?.projectId ?? item?.project_id ?? item?.projectID ?? "";
1219
+ return String(projectId) === filterProjectId;
1220
+ });
1221
+ }
1222
+ if (Array.isArray(data.project)) {
1223
+ nextData.project = data.project.filter((item) => {
1224
+ const projectId = item?._id ?? item?.id ?? item?.project_id ?? item?.projectId ?? "";
1225
+ return String(projectId) === filterProjectId;
1226
+ });
1227
+ }
1228
+ return { ...record, data: nextData };
987
1229
  });
988
1230
  }
1231
+ async function runGroup(rawArgs) {
1232
+ const action = (rawArgs[0] || "list").toLowerCase();
1233
+ const options = parseArgs(rawArgs.slice(1));
1234
+ if (options.help) {
1235
+ console.log(groupUsage());
1236
+ return 0;
1237
+ }
1238
+ if (options.version) {
1239
+ console.log(readVersion());
1240
+ return 0;
1241
+ }
1242
+ if (action === "list") {
1243
+ return await runSimpleRequest(rawArgs.slice(1), groupUsage, "/api/group/list", true);
1244
+ }
1245
+ if (action === "get") {
1246
+ return await runSimpleRequest(rawArgs.slice(1), groupUsage, "/api/group/get", true, (opts) => {
1247
+ const id = String(opts.id || "").trim();
1248
+ if (!id) {
1249
+ console.error("missing --id for group get");
1250
+ return { ok: false };
1251
+ }
1252
+ return { ok: true, queryItems: [["id", id]] };
1253
+ });
1254
+ }
1255
+ console.error(`unknown group action: ${action}`);
1256
+ console.error(groupUsage());
1257
+ return 2;
1258
+ }
1259
+ async function runProject(rawArgs) {
1260
+ const action = (rawArgs[0] || "list").toLowerCase();
1261
+ const options = parseArgs(rawArgs.slice(1));
1262
+ if (options.help) {
1263
+ console.log(projectUsage());
1264
+ return 0;
1265
+ }
1266
+ if (options.version) {
1267
+ console.log(readVersion());
1268
+ return 0;
1269
+ }
1270
+ if (action === "list") {
1271
+ return await runSimpleRequest(rawArgs.slice(1), projectUsage, "/api/project/list", true, (opts) => {
1272
+ const groupId = String(opts.groupId || "").trim();
1273
+ if (!groupId) {
1274
+ console.error("missing --group-id for project list");
1275
+ return { ok: false };
1276
+ }
1277
+ const page = Number.isFinite(opts.page ?? NaN) ? String(opts.page) : "1";
1278
+ const limit = resolveLimit(opts.limit, "10");
1279
+ return {
1280
+ ok: true,
1281
+ queryItems: [
1282
+ ["group_id", groupId],
1283
+ ["page", page],
1284
+ ["limit", limit],
1285
+ ],
1286
+ };
1287
+ });
1288
+ }
1289
+ if (action === "get") {
1290
+ return await runSimpleRequest(rawArgs.slice(1), projectUsage, "/api/project/get", true, (opts) => {
1291
+ const id = String(opts.id || "").trim();
1292
+ if (!id) {
1293
+ console.error("missing --id for project get");
1294
+ return { ok: false };
1295
+ }
1296
+ return { ok: true, queryItems: [["id", id]] };
1297
+ });
1298
+ }
1299
+ if (action === "token") {
1300
+ return await runSimpleRequest(rawArgs.slice(1), projectUsage, "/api/project/token", true, (opts) => {
1301
+ const projectId = String(opts.projectId || opts.id || "").trim();
1302
+ if (!projectId) {
1303
+ console.error("missing --project-id for project token");
1304
+ return { ok: false };
1305
+ }
1306
+ return { ok: true, queryItems: [["project_id", projectId]] };
1307
+ });
1308
+ }
1309
+ console.error(`unknown project action: ${action}`);
1310
+ console.error(projectUsage());
1311
+ return 2;
1312
+ }
1313
+ async function runInterface(rawArgs) {
1314
+ const action = (rawArgs[0] || "").toLowerCase();
1315
+ const options = parseArgs(rawArgs.slice(1));
1316
+ if (options.help) {
1317
+ console.log(interfaceUsage());
1318
+ return 0;
1319
+ }
1320
+ if (options.version) {
1321
+ console.log(readVersion());
1322
+ return 0;
1323
+ }
1324
+ if (action === "list") {
1325
+ return await runSimpleRequest(rawArgs.slice(1), interfaceUsage, "/api/interface/list", true, (opts) => {
1326
+ const projectId = String(opts.projectId || "").trim();
1327
+ if (!projectId) {
1328
+ console.error("missing --project-id for interface list");
1329
+ return { ok: false };
1330
+ }
1331
+ const page = Number.isFinite(opts.page ?? NaN) ? String(opts.page) : "1";
1332
+ const limit = resolveLimit(opts.limit, "20");
1333
+ return {
1334
+ ok: true,
1335
+ queryItems: [
1336
+ ["project_id", projectId],
1337
+ ["page", page],
1338
+ ["limit", limit],
1339
+ ],
1340
+ };
1341
+ });
1342
+ }
1343
+ if (action === "list-menu" || action === "menu" || action === "list_menu") {
1344
+ return await runSimpleRequest(rawArgs.slice(1), interfaceUsage, "/api/interface/list_menu", true, (opts) => {
1345
+ const projectId = String(opts.projectId || "").trim();
1346
+ if (!projectId) {
1347
+ console.error("missing --project-id for interface list-menu");
1348
+ return { ok: false };
1349
+ }
1350
+ return { ok: true, queryItems: [["project_id", projectId]] };
1351
+ });
1352
+ }
1353
+ if (action === "get") {
1354
+ return await runSimpleRequest(rawArgs.slice(1), interfaceUsage, "/api/interface/get", true, (opts) => {
1355
+ const id = String(opts.id || "").trim();
1356
+ if (!id) {
1357
+ console.error("missing --id for interface get");
1358
+ return { ok: false };
1359
+ }
1360
+ return { ok: true, queryItems: [["id", id]] };
1361
+ });
1362
+ }
1363
+ if (action === "cat" || action === "category") {
1364
+ const subAction = (rawArgs[1] || "").toLowerCase();
1365
+ const subArgs = rawArgs.slice(2);
1366
+ if (subAction === "add") {
1367
+ return await runSimpleRequest(subArgs, interfaceUsage, "/api/interface/add_cat", true, (opts) => {
1368
+ const projectId = String(opts.projectId || "").trim();
1369
+ const name = String(opts.name || "").trim();
1370
+ if (!projectId || !name) {
1371
+ console.error("missing --project-id/--name for interface cat add");
1372
+ return { ok: false };
1373
+ }
1374
+ const payload = {
1375
+ project_id: projectId,
1376
+ name,
1377
+ };
1378
+ if (opts.desc !== undefined) {
1379
+ payload.desc = opts.desc;
1380
+ }
1381
+ return { ok: true, method: "POST", data: payload };
1382
+ });
1383
+ }
1384
+ if (subAction === "update" || subAction === "up") {
1385
+ return await runSimpleRequest(subArgs, interfaceUsage, "/api/interface/up_cat", true, (opts) => {
1386
+ const catId = String(opts.catId || "").trim();
1387
+ const name = String(opts.name || "").trim();
1388
+ if (!catId || !name) {
1389
+ console.error("missing --cat-id/--name for interface cat update");
1390
+ return { ok: false };
1391
+ }
1392
+ const payload = {
1393
+ catid: catId,
1394
+ name,
1395
+ };
1396
+ if (opts.desc !== undefined) {
1397
+ payload.desc = opts.desc;
1398
+ }
1399
+ return { ok: true, method: "POST", data: payload };
1400
+ });
1401
+ }
1402
+ if (subAction === "delete" || subAction === "del" || subAction === "remove") {
1403
+ return await runSimpleRequest(subArgs, interfaceUsage, "/api/interface/del_cat", true, (opts) => {
1404
+ const catId = String(opts.catId || "").trim();
1405
+ if (!catId) {
1406
+ console.error("missing --cat-id for interface cat delete");
1407
+ return { ok: false };
1408
+ }
1409
+ return { ok: true, method: "POST", data: { catid: catId } };
1410
+ });
1411
+ }
1412
+ console.error(`unknown interface cat action: ${subAction || "(missing)"}`);
1413
+ console.error(interfaceUsage());
1414
+ return 2;
1415
+ }
1416
+ console.error(`unknown interface action: ${action || "(missing)"}`);
1417
+ console.error(interfaceUsage());
1418
+ return 2;
1419
+ }
1420
+ async function runLog(rawArgs) {
1421
+ const action = (rawArgs[0] || "list").toLowerCase();
1422
+ const options = parseArgs(rawArgs.slice(1));
1423
+ if (options.help) {
1424
+ console.log(logUsage());
1425
+ return 0;
1426
+ }
1427
+ if (options.version) {
1428
+ console.log(readVersion());
1429
+ return 0;
1430
+ }
1431
+ if (action === "list") {
1432
+ return await runSimpleRequest(rawArgs.slice(1), logUsage, "/api/log/list", true, (opts) => {
1433
+ const type = String(opts.type || "").trim();
1434
+ const typeId = String(opts.typeId || "").trim();
1435
+ if (!type || !typeId) {
1436
+ console.error("missing --type/--type-id for log list");
1437
+ return { ok: false };
1438
+ }
1439
+ const page = Number.isFinite(opts.page ?? NaN) ? String(opts.page) : "1";
1440
+ const limit = resolveLimit(opts.limit, "10");
1441
+ return {
1442
+ ok: true,
1443
+ queryItems: [
1444
+ ["type", type],
1445
+ ["typeid", typeId],
1446
+ ["page", page],
1447
+ ["limit", limit],
1448
+ ],
1449
+ };
1450
+ });
1451
+ }
1452
+ console.error(`unknown log action: ${action}`);
1453
+ console.error(logUsage());
1454
+ return 2;
1455
+ }
989
1456
  function findDocsSyncHome(startDir) {
990
1457
  let current = path_1.default.resolve(startDir);
991
1458
  while (true) {
@@ -1124,6 +1591,17 @@ function normalizeBindingDir(rootDir, bindingDir) {
1124
1591
  return resolved;
1125
1592
  return relative;
1126
1593
  }
1594
+ function suggestDocsSyncDir(startDir) {
1595
+ const candidates = ["docs", "doc", "documentation", "release-notes"];
1596
+ for (const candidate of candidates) {
1597
+ const candidatePath = path_1.default.resolve(startDir, candidate);
1598
+ if (fs_1.default.existsSync(candidatePath) && fs_1.default.statSync(candidatePath).isDirectory()) {
1599
+ const relative = path_1.default.relative(startDir, candidatePath);
1600
+ return relative && relative !== "." ? relative : candidate;
1601
+ }
1602
+ }
1603
+ return null;
1604
+ }
1127
1605
  function loadMapping(dirPath) {
1128
1606
  const mappingPath = path_1.default.join(dirPath, ".yapi.json");
1129
1607
  if (!fs_1.default.existsSync(mappingPath)) {
@@ -1217,6 +1695,111 @@ function normalizeProjectEnvs(raw) {
1217
1695
  });
1218
1696
  return result;
1219
1697
  }
1698
+ function resolveLimit(value, fallback) {
1699
+ if (typeof value === "string") {
1700
+ const trimmed = value.trim();
1701
+ if (trimmed)
1702
+ return trimmed;
1703
+ }
1704
+ if (Number.isFinite(value ?? NaN)) {
1705
+ return String(value);
1706
+ }
1707
+ return fallback;
1708
+ }
1709
+ const UPDATE_CHECK_TTL_MS = 12 * 60 * 60 * 1000;
1710
+ function updateCachePath() {
1711
+ const yapiHome = process.env.YAPI_HOME || path_1.default.join(os_1.default.homedir(), ".yapi");
1712
+ return path_1.default.join(yapiHome, "update.json");
1713
+ }
1714
+ function readUpdateCache() {
1715
+ try {
1716
+ const cachePath = updateCachePath();
1717
+ if (!fs_1.default.existsSync(cachePath))
1718
+ return {};
1719
+ const raw = fs_1.default.readFileSync(cachePath, "utf8");
1720
+ const parsed = JSON.parse(raw);
1721
+ return parsed && typeof parsed === "object" ? parsed : {};
1722
+ }
1723
+ catch {
1724
+ return {};
1725
+ }
1726
+ }
1727
+ function writeUpdateCache(cache) {
1728
+ try {
1729
+ const cachePath = updateCachePath();
1730
+ fs_1.default.mkdirSync(path_1.default.dirname(cachePath), { recursive: true });
1731
+ fs_1.default.writeFileSync(cachePath, `${JSON.stringify(cache, null, 2)}\n`, "utf8");
1732
+ }
1733
+ catch {
1734
+ }
1735
+ }
1736
+ function compareSemver(a, b) {
1737
+ const toParts = (value) => String(value || "")
1738
+ .trim()
1739
+ .split(".")
1740
+ .map((part) => Number(part.replace(/\D/g, "")) || 0);
1741
+ const aParts = toParts(a);
1742
+ const bParts = toParts(b);
1743
+ const len = Math.max(aParts.length, bParts.length);
1744
+ for (let i = 0; i < len; i += 1) {
1745
+ const diff = (aParts[i] || 0) - (bParts[i] || 0);
1746
+ if (diff !== 0)
1747
+ return diff;
1748
+ }
1749
+ return 0;
1750
+ }
1751
+ function isNewerVersion(latest, current) {
1752
+ return compareSemver(latest, current) > 0;
1753
+ }
1754
+ async function fetchLatestVersion(timeoutMs) {
1755
+ try {
1756
+ const encoded = encodeURIComponent("@leeguoo/yapi-mcp");
1757
+ const url = `https://registry.npmjs.org/${encoded}/latest`;
1758
+ const response = await fetchWithTimeout(url, { method: "GET" }, timeoutMs);
1759
+ if (!response.ok)
1760
+ return null;
1761
+ const payload = (await response.json());
1762
+ return typeof payload?.version === "string" ? payload.version : null;
1763
+ }
1764
+ catch {
1765
+ return null;
1766
+ }
1767
+ }
1768
+ async function checkForUpdates(options) {
1769
+ if (options.skip || options.noUpdate)
1770
+ return;
1771
+ if (process.env.YAPI_NO_UPDATE_CHECK === "1")
1772
+ return;
1773
+ if (process.env.CI === "1")
1774
+ return;
1775
+ const currentVersion = readVersion();
1776
+ if (!currentVersion || currentVersion === "unknown")
1777
+ return;
1778
+ const cache = readUpdateCache();
1779
+ const now = Date.now();
1780
+ let latest = cache.latest;
1781
+ const shouldCheck = !cache.lastChecked || now - cache.lastChecked > UPDATE_CHECK_TTL_MS || !latest;
1782
+ if (shouldCheck) {
1783
+ const fetched = await fetchLatestVersion(2000);
1784
+ if (fetched) {
1785
+ latest = fetched;
1786
+ cache.latest = fetched;
1787
+ }
1788
+ cache.lastChecked = now;
1789
+ }
1790
+ if (!latest || !isNewerVersion(latest, currentVersion)) {
1791
+ writeUpdateCache(cache);
1792
+ return;
1793
+ }
1794
+ const shouldNotify = cache.lastNotified !== latest || !cache.lastNotifiedAt || now - cache.lastNotifiedAt > UPDATE_CHECK_TTL_MS;
1795
+ if (shouldNotify) {
1796
+ console.warn(`update available: ${currentVersion} -> ${latest}. Run: npm install -g @leeguoo/yapi-mcp@latest`);
1797
+ console.warn("or: pnpm add -g @leeguoo/yapi-mcp@latest");
1798
+ cache.lastNotified = latest;
1799
+ cache.lastNotifiedAt = now;
1800
+ }
1801
+ writeUpdateCache(cache);
1802
+ }
1220
1803
  async function fetchProjectInfo(projectId, baseUrl, request) {
1221
1804
  if (!projectId)
1222
1805
  return null;
@@ -1665,7 +2248,29 @@ async function runDocsSync(rawArgs) {
1665
2248
  console.error("no docs-sync bindings found (run docs-sync bind add or use --dir)");
1666
2249
  return 2;
1667
2250
  }
2251
+ const usingDefaultDir = !useBindings && !options.dirs.length;
1668
2252
  const dirs = useBindings ? [] : options.dirs.length ? options.dirs : ["docs/release-notes"];
2253
+ if (!useBindings) {
2254
+ const missingDirs = dirs.filter((dir) => {
2255
+ const dirPath = path_1.default.resolve(dir);
2256
+ return !fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory();
2257
+ });
2258
+ if (missingDirs.length) {
2259
+ const firstMissing = path_1.default.resolve(missingDirs[0]);
2260
+ console.error(`dir not found: ${firstMissing}`);
2261
+ if (usingDefaultDir) {
2262
+ const suggestion = suggestDocsSyncDir(process.cwd());
2263
+ if (suggestion) {
2264
+ console.error(`hint: pass --dir ${suggestion}`);
2265
+ }
2266
+ console.error("hint: or bind a directory with yapi docs-sync bind add");
2267
+ }
2268
+ else {
2269
+ console.error("hint: pass --dir <existing_dir> or bind a directory");
2270
+ }
2271
+ return 2;
2272
+ }
2273
+ }
1669
2274
  let config = {};
1670
2275
  let configPath = options.config || "";
1671
2276
  if (options.config) {
@@ -1870,6 +2475,13 @@ async function runDocsSync(rawArgs) {
1870
2475
  }
1871
2476
  async function main() {
1872
2477
  const rawArgs = process.argv.slice(2);
2478
+ const parsedForUpdate = parseArgs(rawArgs);
2479
+ const skipUpdateCheck = parsedForUpdate.version ||
2480
+ parsedForUpdate.help ||
2481
+ parsedForUpdate.noUpdate ||
2482
+ rawArgs.includes("-h") ||
2483
+ rawArgs.includes("--help");
2484
+ await checkForUpdates({ noUpdate: parsedForUpdate.noUpdate, skip: skipUpdateCheck });
1873
2485
  if (rawArgs[0] === "install-skill") {
1874
2486
  await (0, install_1.runInstallSkill)(rawArgs.slice(1));
1875
2487
  return 0;
@@ -1883,6 +2495,18 @@ async function main() {
1883
2495
  if (rawArgs[0] === "search") {
1884
2496
  return await runSearch(rawArgs.slice(1));
1885
2497
  }
2498
+ if (rawArgs[0] === "group") {
2499
+ return await runGroup(rawArgs.slice(1));
2500
+ }
2501
+ if (rawArgs[0] === "project") {
2502
+ return await runProject(rawArgs.slice(1));
2503
+ }
2504
+ if (rawArgs[0] === "interface") {
2505
+ return await runInterface(rawArgs.slice(1));
2506
+ }
2507
+ if (rawArgs[0] === "log") {
2508
+ return await runLog(rawArgs.slice(1));
2509
+ }
1886
2510
  if (rawArgs[0] === "docs-sync") {
1887
2511
  return await runDocsSync(rawArgs.slice(1));
1888
2512
  }