@leeguoo/yapi-mcp 0.3.16 → 0.3.21

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;
@@ -462,10 +544,6 @@ function parseDocsSyncArgs(argv) {
462
544
  options.mermaidLook = "handDrawn";
463
545
  continue;
464
546
  }
465
- if (arg === "--d2-sketch") {
466
- options.d2Sketch = true;
467
- continue;
468
- }
469
547
  if (arg === "--force") {
470
548
  options.force = true;
471
549
  continue;
@@ -555,6 +633,10 @@ function usage() {
555
633
  return [
556
634
  "Usage:",
557
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]",
558
640
  " yapi docs-sync [options] [dir...]",
559
641
  " yapi docs-sync bind <action> [options]",
560
642
  " yapi login [options]",
@@ -565,7 +647,7 @@ function usage() {
565
647
  " --config <path> config file path (default: ~/.yapi/config.toml)",
566
648
  " --base-url <url> YApi base URL",
567
649
  " --token <token> project token (supports projectId:token)",
568
- " --project-id <id> select token for project",
650
+ " --project-id <id> select token for project (filters search results)",
569
651
  " --auth-mode <mode> token or global",
570
652
  " --email <email> login email for global mode",
571
653
  " --password <pwd> login password for global mode",
@@ -579,6 +661,16 @@ function usage() {
579
661
  " --data <payload> request body (JSON or text)",
580
662
  " --data-file <file> request body file",
581
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",
582
674
  " --no-pretty print raw response",
583
675
  "Docs-sync options:",
584
676
  " --dir <path> docs directory (repeatable; default: docs/release-notes)",
@@ -588,7 +680,6 @@ function usage() {
588
680
  " --mermaid-hand-drawn force mermaid hand-drawn look (default)",
589
681
  " --mermaid-classic render mermaid with classic look",
590
682
  " --mermaid-hand-drawn-seed <n> hand-drawn seed (implies hand-drawn look)",
591
- " --d2-sketch render D2 diagrams in sketch style",
592
683
  " --force sync all files even if unchanged",
593
684
  "Docs-sync bind actions:",
594
685
  " list, get, add, update, remove",
@@ -619,7 +710,6 @@ function docsSyncUsage() {
619
710
  " --mermaid-hand-drawn force mermaid hand-drawn look (default)",
620
711
  " --mermaid-classic render mermaid with classic look",
621
712
  " --mermaid-hand-drawn-seed <n> hand-drawn seed (implies hand-drawn look)",
622
- " --d2-sketch render D2 diagrams in sketch style",
623
713
  " --force sync all files even if unchanged",
624
714
  " -h, --help show help",
625
715
  ].join("\n");
@@ -695,6 +785,105 @@ function searchUsage() {
695
785
  " -h, --help show help",
696
786
  ].join("\n");
697
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
+ }
698
887
  function escapeTomlValue(value) {
699
888
  return String(value || "")
700
889
  .replace(/\\/g, "\\\\")
@@ -732,8 +921,12 @@ function promptHidden(question) {
732
921
  output: process.stdout,
733
922
  terminal: true,
734
923
  });
924
+ const output = rl.output || process.stdout;
735
925
  const originalWrite = rl
736
926
  ._writeToOutput;
927
+ if (question) {
928
+ output.write(question);
929
+ }
737
930
  rl.stdoutMuted = true;
738
931
  rl._writeToOutput =
739
932
  function writeToOutput(value) {
@@ -743,12 +936,15 @@ function promptHidden(question) {
743
936
  originalWrite.call(this, value);
744
937
  }
745
938
  else {
746
- rl.output.write(value);
939
+ output.write(value);
747
940
  }
748
941
  };
749
942
  return new Promise((resolve) => {
750
- rl.question(question, (answer) => {
943
+ rl.question("", (answer) => {
751
944
  rl.stdoutMuted = false;
945
+ if (question) {
946
+ output.write("\n");
947
+ }
752
948
  rl.close();
753
949
  resolve(answer);
754
950
  });
@@ -795,7 +991,7 @@ function readVersion() {
795
991
  return "unknown";
796
992
  }
797
993
  }
798
- async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buildQueryItems) {
994
+ async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buildQueryItems, transform) {
799
995
  const options = parseArgs(rawArgs);
800
996
  if (options.help) {
801
997
  console.log(usageFn());
@@ -865,17 +1061,32 @@ async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buil
865
1061
  }
866
1062
  }
867
1063
  let queryItems = [];
1064
+ let method = "GET";
1065
+ let data = undefined;
868
1066
  if (buildQueryItems) {
869
1067
  const result = buildQueryItems(options);
870
1068
  if (!result.ok)
871
1069
  return 2;
872
1070
  queryItems = result.queryItems || [];
1071
+ if (result.method) {
1072
+ method = result.method;
1073
+ }
1074
+ if (result.data !== undefined) {
1075
+ data = result.data;
1076
+ }
873
1077
  }
874
1078
  const url = buildUrl(baseUrl, endpoint, queryItems, authMode === "token" ? token : "", options.tokenParam || "token");
875
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
+ }
876
1086
  const response = await fetchWithTimeout(url, {
877
- method: "GET",
878
- headers,
1087
+ method,
1088
+ headers: requestHeaders,
1089
+ body,
879
1090
  }, options.timeout || 30000);
880
1091
  const text = await response.text();
881
1092
  return { response, text, json: parseJsonMaybe(text) };
@@ -905,7 +1116,8 @@ async function runSimpleRequest(rawArgs, usageFn, endpoint, requireBaseUrl, buil
905
1116
  }
906
1117
  try {
907
1118
  const payload = result.json ?? JSON.parse(text);
908
- console.log(JSON.stringify(payload, null, 2));
1119
+ const nextPayload = transform ? transform(payload, options) : payload;
1120
+ console.log(JSON.stringify(nextPayload ?? payload, null, 2));
909
1121
  }
910
1122
  catch {
911
1123
  console.log(text);
@@ -990,8 +1202,257 @@ async function runSearch(rawArgs) {
990
1202
  return { ok: false };
991
1203
  }
992
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 };
993
1229
  });
994
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
+ }
995
1456
  function findDocsSyncHome(startDir) {
996
1457
  let current = path_1.default.resolve(startDir);
997
1458
  while (true) {
@@ -1130,6 +1591,17 @@ function normalizeBindingDir(rootDir, bindingDir) {
1130
1591
  return resolved;
1131
1592
  return relative;
1132
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
+ }
1133
1605
  function loadMapping(dirPath) {
1134
1606
  const mappingPath = path_1.default.join(dirPath, ".yapi.json");
1135
1607
  if (!fs_1.default.existsSync(mappingPath)) {
@@ -1153,9 +1625,6 @@ function buildDocsSyncHash(markdown, options) {
1153
1625
  hash.update(`mermaid-seed:${options.mermaidHandDrawnSeed}\n`);
1154
1626
  }
1155
1627
  }
1156
- if (options.d2Sketch) {
1157
- hash.update("d2-sketch\n");
1158
- }
1159
1628
  hash.update(markdown);
1160
1629
  return hash.digest("hex");
1161
1630
  }
@@ -1226,6 +1695,111 @@ function normalizeProjectEnvs(raw) {
1226
1695
  });
1227
1696
  return result;
1228
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
+ }
1229
1803
  async function fetchProjectInfo(projectId, baseUrl, request) {
1230
1804
  if (!projectId)
1231
1805
  return null;
@@ -1346,7 +1920,7 @@ async function syncDocsDir(dirPath, mapping, options, request) {
1346
1920
  if (!mapping.template_id && envTemplateId)
1347
1921
  mapping.template_id = Number(envTemplateId);
1348
1922
  if (!mapping.project_id || !mapping.catid) {
1349
- throw new Error("project_id/catid missing; set in binding/.yapi.json or env");
1923
+ 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。");
1350
1924
  }
1351
1925
  const { byPath, byTitle, byId } = await listExistingInterfaces(Number(mapping.catid), request);
1352
1926
  let updated = 0;
@@ -1402,7 +1976,6 @@ async function syncDocsDir(dirPath, mapping, options, request) {
1402
1976
  logMermaid: true,
1403
1977
  mermaidLook: options.mermaidLook,
1404
1978
  mermaidHandDrawnSeed: options.mermaidHandDrawnSeed,
1405
- d2Sketch: options.d2Sketch,
1406
1979
  logger: (message) => console.log(`${logPrefix} ${message}`),
1407
1980
  onMermaidError: () => {
1408
1981
  mermaidFailed = true;
@@ -1675,7 +2248,29 @@ async function runDocsSync(rawArgs) {
1675
2248
  console.error("no docs-sync bindings found (run docs-sync bind add or use --dir)");
1676
2249
  return 2;
1677
2250
  }
2251
+ const usingDefaultDir = !useBindings && !options.dirs.length;
1678
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
+ }
1679
2274
  let config = {};
1680
2275
  let configPath = options.config || "";
1681
2276
  if (options.config) {
@@ -1880,6 +2475,13 @@ async function runDocsSync(rawArgs) {
1880
2475
  }
1881
2476
  async function main() {
1882
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 });
1883
2485
  if (rawArgs[0] === "install-skill") {
1884
2486
  await (0, install_1.runInstallSkill)(rawArgs.slice(1));
1885
2487
  return 0;
@@ -1893,6 +2495,18 @@ async function main() {
1893
2495
  if (rawArgs[0] === "search") {
1894
2496
  return await runSearch(rawArgs.slice(1));
1895
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
+ }
1896
2510
  if (rawArgs[0] === "docs-sync") {
1897
2511
  return await runDocsSync(rawArgs.slice(1));
1898
2512
  }