focalapi-cli 0.2.0 → 0.2.2

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/cli.js CHANGED
@@ -8,11 +8,11 @@ var ApiError = class extends Error {
8
8
  code;
9
9
  status;
10
10
  hint;
11
- /** 上游返回的稳定错误代码或类型;不包含密钥等敏感请求信息。 */
11
+ /** Stable upstream error code or type, without sensitive request data such as keys. */
12
12
  upstreamCode;
13
- /** 可用于向服务方关联日志的请求 ID */
13
+ /** Request ID that the service operator can use to correlate logs. */
14
14
  requestId;
15
- /** 上游原始响应体(已截断),仅调试用途,打印前需脱敏。 */
15
+ /** Truncated raw upstream response for debugging only; redact it before printing. */
16
16
  body;
17
17
  constructor(code, message, opts) {
18
18
  super(message);
@@ -155,7 +155,7 @@ function displayWidth(s) {
155
155
  }
156
156
 
157
157
  // src/lib/version.ts
158
- var VERSION = true ? "0.2.0" : "0.0.0-dev";
158
+ var VERSION = true ? "0.2.2" : "0.0.0-dev";
159
159
 
160
160
  // src/commands/auth.ts
161
161
  import { createInterface } from "readline/promises";
@@ -471,15 +471,22 @@ var RECOMMENDED_MODELS = {
471
471
  "seedream-5-0-260128",
472
472
  "gpt-image-2",
473
473
  "gemini-3.1-flash-image",
474
- "grok-imagine-image-quality",
474
+ "grok-imagine-image-2.0",
475
+ "kling-v3",
476
+ "qwen-image-3.0-pro",
477
+ "krea-2-large",
475
478
  "seedream-4-5-251128"
476
479
  ],
477
480
  video: [
478
481
  "dreamina-seedance-2-5-260628",
479
- "veo-3.1-generate-preview",
482
+ "kling-3.0",
483
+ "viduq3-pro",
484
+ "gemini-omni-flash-preview",
480
485
  "grok-imagine-video-1.5",
481
- "dreamina-seedance-2-0-260128",
482
- "veo-3.1-fast-generate-preview"
486
+ "ltx-2-5-fast",
487
+ "flux-3",
488
+ "MiniMax-H3",
489
+ "dreamina-seedance-2-0-260128"
483
490
  ]
484
491
  };
485
492
  function parseCreativeCapability(value) {
@@ -906,6 +913,24 @@ var GROK_IMAGE_ASPECT_RATIOS = [
906
913
  "1:3",
907
914
  "3:1"
908
915
  ];
916
+ var GROK_IMAGE_20_ASPECT_RATIOS = [
917
+ "auto",
918
+ "1:1",
919
+ "2:3",
920
+ "3:2",
921
+ "3:4",
922
+ "4:3",
923
+ "9:16",
924
+ "16:9",
925
+ "9:19.5",
926
+ "19.5:9",
927
+ "9:20",
928
+ "20:9",
929
+ "1:2",
930
+ "2:1"
931
+ ];
932
+ var KLING_IMAGE_ASPECT_RATIOS = ["16:9", "9:16", "1:1", "4:3", "3:4", "3:2", "2:3", "21:9"];
933
+ var KREA_IMAGE_ASPECT_RATIOS = ["1:1", "4:3", "3:2", "16:9", "2.35:1", "4:5", "2:3", "9:16"];
909
934
  var IMAGE_CONSTRAINTS = {
910
935
  "gpt-image-2": {
911
936
  defaultSize: "1024x1024",
@@ -968,52 +993,103 @@ var IMAGE_CONSTRAINTS = {
968
993
  optimizePromptModes: SEEDREAM_OPTIMIZE_PROMPT_MODES
969
994
  },
970
995
  "grok-imagine-image-quality": {
971
- defaultSize: "1024x1024",
972
996
  maxN: 10,
973
997
  maxReferenceImages: 3,
974
998
  aspectRatios: GROK_IMAGE_ASPECT_RATIOS,
975
999
  resolutions: ["1k", "2k"],
976
- supportsSeed: true
1000
+ supportsSeed: true,
1001
+ maxSeed: 2147483647
977
1002
  },
978
1003
  "grok-imagine-image": {
979
- defaultSize: "1024x1024",
980
1004
  maxN: 10,
981
1005
  maxReferenceImages: 3,
982
1006
  aspectRatios: GROK_IMAGE_ASPECT_RATIOS,
983
1007
  resolutions: ["1k", "2k"],
984
- supportsSeed: true
1008
+ supportsSeed: true,
1009
+ maxSeed: 2147483647
1010
+ },
1011
+ "grok-imagine-image-2.0": {
1012
+ maxN: 10,
1013
+ maxReferenceImages: 3,
1014
+ aspectRatios: GROK_IMAGE_20_ASPECT_RATIOS,
1015
+ resolutions: ["1k", "2k"],
1016
+ qualities: ["low", "medium"],
1017
+ supportsSeed: true,
1018
+ maxSeed: 2147483647
1019
+ },
1020
+ "kling-v3": {
1021
+ maxN: 9,
1022
+ maxReferenceImages: 1,
1023
+ aspectRatios: KLING_IMAGE_ASPECT_RATIOS,
1024
+ supportsNegativePrompt: true
1025
+ },
1026
+ "krea-2-medium": {
1027
+ maxN: 1,
1028
+ maxReferenceImages: 0,
1029
+ aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1030
+ resolutions: ["1k"],
1031
+ supportsSeed: true,
1032
+ maxSeed: 2147483647,
1033
+ creativityModes: ["raw", "low", "medium", "high"],
1034
+ maxStyleReferences: 10,
1035
+ maxMoodboards: 1
1036
+ },
1037
+ "krea-2-medium-turbo": {
1038
+ maxN: 1,
1039
+ maxReferenceImages: 0,
1040
+ aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1041
+ resolutions: ["1k"],
1042
+ supportsSeed: true,
1043
+ maxSeed: 2147483647,
1044
+ creativityModes: ["raw", "low", "medium", "high"],
1045
+ maxStyleReferences: 10,
1046
+ maxMoodboards: 1
1047
+ },
1048
+ "krea-2-large": {
1049
+ maxN: 1,
1050
+ maxReferenceImages: 0,
1051
+ aspectRatios: KREA_IMAGE_ASPECT_RATIOS,
1052
+ resolutions: ["1k"],
1053
+ supportsSeed: true,
1054
+ maxSeed: 2147483647,
1055
+ creativityModes: ["raw", "low", "medium", "high"],
1056
+ maxStyleReferences: 10,
1057
+ maxMoodboards: 1
1058
+ },
1059
+ "qwen-image-3.0": {
1060
+ defaultSize: "1024x1024",
1061
+ maxN: 6,
1062
+ maxReferenceImages: 3,
1063
+ minMegapixels: 0.262144,
1064
+ maxMegapixels: 4.194304,
1065
+ maxAspectRatio: 8,
1066
+ supportsSeed: true,
1067
+ maxSeed: 2147483647,
1068
+ supportsNegativePrompt: true,
1069
+ supportsPromptExtend: true,
1070
+ supportsWatermark: true
1071
+ },
1072
+ "qwen-image-3.0-pro": {
1073
+ defaultSize: "1024x1024",
1074
+ maxN: 6,
1075
+ maxReferenceImages: 3,
1076
+ minMegapixels: 0.262144,
1077
+ maxMegapixels: 4.194304,
1078
+ maxAspectRatio: 8,
1079
+ supportsSeed: true,
1080
+ maxSeed: 2147483647,
1081
+ supportsNegativePrompt: true,
1082
+ supportsPromptExtend: true,
1083
+ supportsWatermark: true
985
1084
  }
986
1085
  };
987
1086
  var SEEDANCE_RATIOS = ["adaptive", "16:9", "4:3", "1:1", "3:4", "9:16", "21:9"];
988
1087
  var GROK_VIDEO_ASPECT_RATIOS = ["auto", "16:9", "4:3", "3:2", "1:1", "2:3", "3:4", "9:16"];
1088
+ var KLING_VIDEO_ASPECT_RATIOS = ["16:9", "9:16", "1:1"];
1089
+ var VIDU_VIDEO_ASPECT_RATIOS = ["16:9", "9:16", "3:4", "4:3", "1:1"];
1090
+ var MINIMAX_VIDEO_RATIOS = ["16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"];
1091
+ var FLUX_VIDEO_RATIOS = ["auto", "21:9", "2:1", "16:9", "4:3", "1:1", "3:4", "9:16"];
989
1092
  var VIDEO_CONSTRAINTS = {
990
- "veo-3.1-generate-preview": {
991
- resolutions: ["720p", "1080p", "4k"],
992
- ratios: ["16:9", "9:16"],
993
- minSeconds: 4,
994
- maxSeconds: 8,
995
- allowedSeconds: [4, 6, 8],
996
- requiredSecondsByResolution: { "1080p": 8, "4k": 8 },
997
- supportsSeed: true
998
- },
999
- "veo-3.1-fast-generate-preview": {
1000
- resolutions: ["720p", "1080p", "4k"],
1001
- ratios: ["16:9", "9:16"],
1002
- minSeconds: 4,
1003
- maxSeconds: 8,
1004
- allowedSeconds: [4, 6, 8],
1005
- requiredSecondsByResolution: { "1080p": 8, "4k": 8 },
1006
- supportsSeed: true
1007
- },
1008
- "veo-3.1-lite-generate-preview": {
1009
- resolutions: ["720p", "1080p"],
1010
- ratios: ["16:9", "9:16"],
1011
- minSeconds: 4,
1012
- maxSeconds: 8,
1013
- allowedSeconds: [4, 6, 8],
1014
- requiredSecondsByResolution: { "1080p": 8 },
1015
- supportsSeed: true
1016
- },
1017
1093
  "dreamina-seedance-2-0-260128": {
1018
1094
  resolutions: ["480p", "720p", "1080p", "4k"],
1019
1095
  ratios: SEEDANCE_RATIOS,
@@ -1041,19 +1117,80 @@ var VIDEO_CONSTRAINTS = {
1041
1117
  minSeconds: 4,
1042
1118
  maxSeconds: 30
1043
1119
  },
1120
+ "gemini-omni-flash-preview": {
1121
+ resolutions: [],
1122
+ ratios: ["16:9", "9:16"],
1123
+ minSeconds: 3,
1124
+ maxSeconds: 10
1125
+ },
1044
1126
  "grok-imagine-video": {
1045
1127
  resolutions: ["480p", "720p", "1080p"],
1046
1128
  aspectRatios: GROK_VIDEO_ASPECT_RATIOS,
1047
1129
  minSeconds: 1,
1048
1130
  maxSeconds: 15,
1049
- supportsSeed: true
1131
+ supportsSeed: true,
1132
+ maxSeed: 2147483647
1050
1133
  },
1051
1134
  "grok-imagine-video-1.5": {
1052
1135
  resolutions: ["480p", "720p", "1080p"],
1053
1136
  aspectRatios: GROK_VIDEO_ASPECT_RATIOS,
1054
1137
  minSeconds: 1,
1055
1138
  maxSeconds: 15,
1056
- supportsSeed: true
1139
+ supportsSeed: true,
1140
+ maxSeed: 2147483647
1141
+ },
1142
+ "kling-3.0": {
1143
+ resolutions: ["720p", "1080p", "4k"],
1144
+ aspectRatios: KLING_VIDEO_ASPECT_RATIOS,
1145
+ minSeconds: 3,
1146
+ maxSeconds: 15
1147
+ },
1148
+ "viduq3-pro": {
1149
+ resolutions: ["720p", "1080p"],
1150
+ aspectRatios: VIDU_VIDEO_ASPECT_RATIOS,
1151
+ minSeconds: 1,
1152
+ maxSeconds: 16,
1153
+ supportsSeed: true,
1154
+ maxSeed: 2147483647
1155
+ },
1156
+ "viduq3-turbo": {
1157
+ resolutions: ["720p", "1080p"],
1158
+ aspectRatios: VIDU_VIDEO_ASPECT_RATIOS,
1159
+ minSeconds: 1,
1160
+ maxSeconds: 16,
1161
+ supportsSeed: true,
1162
+ maxSeed: 2147483647
1163
+ },
1164
+ "MiniMax-H3": {
1165
+ resolutions: ["768p", "2k"],
1166
+ ratios: MINIMAX_VIDEO_RATIOS,
1167
+ minSeconds: 5,
1168
+ maxSeconds: 15
1169
+ },
1170
+ "ltx-2-5-fast": {
1171
+ resolutions: ["1280x720", "720x1280", "1920x1080", "1080x1920", "2560x1440", "1440x2560", "3840x2160", "2160x3840"],
1172
+ minSeconds: 2,
1173
+ maxSeconds: 20,
1174
+ allowedSeconds: [2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20],
1175
+ supportsSeed: true,
1176
+ maxSeed: 4294967295,
1177
+ allowedFps: [24, 25, 48, 50]
1178
+ },
1179
+ "ltx-2-5-pro": {
1180
+ resolutions: ["1280x720", "720x1280", "1920x1080", "1080x1920"],
1181
+ minSeconds: 2,
1182
+ maxSeconds: 10,
1183
+ allowedSeconds: [2, 3, 4, 5, 6, 8, 10],
1184
+ supportsSeed: true,
1185
+ maxSeed: 4294967295,
1186
+ allowedFps: [24, 25, 50]
1187
+ },
1188
+ "flux-3": {
1189
+ resolutions: ["hd", "fhd"],
1190
+ ratios: FLUX_VIDEO_RATIOS,
1191
+ minSeconds: 5,
1192
+ maxSeconds: 20,
1193
+ safetyTolerance: { minimum: 0, maximum: 4 }
1057
1194
  }
1058
1195
  };
1059
1196
  var COMMON_GEMINI_RATIOS = ["auto", "1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9"];
@@ -1113,17 +1250,40 @@ function validateImageGeneration(model, input) {
1113
1250
  validateOptionalChoice(model, "resolution", input.resolution, constraint.resolutions);
1114
1251
  validateOptionalChoice(model, "output_format", input.outputFormat, constraint.outputFormats);
1115
1252
  validateOptionalChoice(model, "optimize_prompt", input.optimizePrompt, constraint.optimizePromptModes);
1253
+ validateOptionalChoice(model, "creativity", input.creativity, constraint.creativityModes);
1254
+ if (input.negativePrompt !== void 0 && !constraint.supportsNegativePrompt) {
1255
+ throw new ApiError("invalid_request", `${model} does not support negative_prompt`);
1256
+ }
1257
+ if (input.negativePrompt !== void 0 && model === "kling-v3" && (input.imageCount ?? 0) > 0) {
1258
+ throw new ApiError("invalid_request", "kling-v3 negative_prompt cannot be combined with an image input");
1259
+ }
1260
+ if (input.promptExtend !== void 0 && !constraint.supportsPromptExtend) {
1261
+ throw new ApiError("invalid_request", `${model} does not support prompt_extend`);
1262
+ }
1263
+ if (input.styleReferenceCount !== void 0 && (constraint.maxStyleReferences === void 0 || input.styleReferenceCount > constraint.maxStyleReferences)) {
1264
+ throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxStyleReferences ?? 0} image style references`);
1265
+ }
1266
+ if (input.moodboardCount !== void 0 && (constraint.maxMoodboards === void 0 || input.moodboardCount > constraint.maxMoodboards)) {
1267
+ throw new ApiError("invalid_request", `${model} supports at most ${constraint.maxMoodboards ?? 0} moodboards`);
1268
+ }
1116
1269
  if (input.seed !== void 0) {
1117
1270
  if (!constraint.supportsSeed) {
1118
1271
  throw new ApiError("invalid_request", `${model} does not support seed`);
1119
1272
  }
1120
- if (!Number.isInteger(input.seed) || input.seed < 0) {
1121
- throw new ApiError("invalid_request", "seed must be a non-negative integer");
1273
+ if (!Number.isInteger(input.seed) || input.seed < 0 || constraint.maxSeed !== void 0 && input.seed > constraint.maxSeed) {
1274
+ const maximum = constraint.maxSeed === void 0 ? "" : ` no greater than ${constraint.maxSeed}`;
1275
+ throw new ApiError("invalid_request", `seed must be a non-negative integer${maximum}`);
1122
1276
  }
1123
1277
  }
1124
1278
  if (input.watermark !== void 0 && !constraint.supportsWatermark) {
1125
1279
  throw new ApiError("invalid_request", `${model} does not support watermark`);
1126
1280
  }
1281
+ if (!constraint.defaultSize) {
1282
+ if (input.size) {
1283
+ throw new ApiError("invalid_request", `${model} does not support size; use its aspect_ratio or resolution parameter`);
1284
+ }
1285
+ return;
1286
+ }
1127
1287
  const suppliedSize = input.size ?? constraint.defaultSize;
1128
1288
  if (constraint.sizeTiers?.includes(suppliedSize.toLowerCase())) return;
1129
1289
  if (constraint.sizeTiers && !/^\d+x\d+$/i.test(suppliedSize)) {
@@ -1210,10 +1370,18 @@ function validateVideoGeneration(model, input) {
1210
1370
  if (!constraint.supportsSeed) {
1211
1371
  throw new ApiError("invalid_request", `${model} does not support seed`);
1212
1372
  }
1213
- if (!Number.isInteger(input.seed) || input.seed < 0) {
1214
- throw new ApiError("invalid_request", "seed must be a non-negative integer");
1373
+ if (!Number.isInteger(input.seed) || input.seed < 0 || constraint.maxSeed !== void 0 && input.seed > constraint.maxSeed) {
1374
+ const maximum = constraint.maxSeed === void 0 ? "" : ` no greater than ${constraint.maxSeed}`;
1375
+ throw new ApiError("invalid_request", `seed must be a non-negative integer${maximum}`);
1215
1376
  }
1216
1377
  }
1378
+ if (input.fps !== void 0 && (!constraint.allowedFps || !constraint.allowedFps.includes(input.fps))) {
1379
+ throw new ApiError("invalid_request", `${model} fps must be one of ${constraint.allowedFps?.join(", ") ?? "none"} (received: ${input.fps})`);
1380
+ }
1381
+ if (input.safetyTolerance !== void 0 && (!constraint.safetyTolerance || !Number.isInteger(input.safetyTolerance) || input.safetyTolerance < constraint.safetyTolerance.minimum || input.safetyTolerance > constraint.safetyTolerance.maximum)) {
1382
+ const range = constraint.safetyTolerance ? `${constraint.safetyTolerance.minimum}-${constraint.safetyTolerance.maximum}` : "unsupported";
1383
+ throw new ApiError("invalid_request", `${model} safety_tolerance must be ${range} (received: ${input.safetyTolerance})`);
1384
+ }
1217
1385
  if (input.priority !== void 0 && !constraint.supportsPriority) {
1218
1386
  throw new ApiError("invalid_request", `${model} does not support priority`);
1219
1387
  }
@@ -1470,12 +1638,14 @@ function extractGeminiImageItems(response) {
1470
1638
  }
1471
1639
  function registerGen(program) {
1472
1640
  const gen = program.command("gen").description("\u56FE\u50CF / \u89C6\u9891\u751F\u6210");
1473
- gen.command("image").description("\u751F\u6210\u56FE\u50CF\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u56FE\u50CF\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--size <size>", "\u5C3A\u5BF8\uFF0C\u5982 1024x1024").option("--aspect-ratio <ratio>", "\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF08\u4EC5 Grok \u56FE\u50CF\u6A21\u578B\uFF09\uFF0C\u5982 16:9").option("--resolution <resolution>", "\u539F\u751F\u8F93\u51FA\u6863\u4F4D\uFF08\u4EC5 Grok \u56FE\u50CF\u6A21\u578B\uFF09\uFF0C\u5982 1k\u30012k").option("--seed <n>", "\u968F\u673A\u79CD\u5B50\uFF08\u4EC5 Grok \u56FE\u50CF\u6A21\u578B\uFF0C\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--quality <quality>", "\u56FE\u50CF\u8D28\u91CF\u6863\u4F4D\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09").option("--background <background>", "\u80CC\u666F\u6A21\u5F0F\uFF08\u4EC5 gpt-image-2 \u652F\u6301 auto/opaque\uFF09").option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u4EC5 Seedream\uFF0Ctrue \u6216 false\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--output-format <format>", "\u8F93\u51FA\u683C\u5F0F\uFF08\u4EC5 Seedream\uFF1Apng \u6216 jpeg\uFF09").option("--optimize-prompt <mode>", "\u63D0\u793A\u8BCD\u4F18\u5316\uFF08\u4EC5 Seedream\uFF1Aauto\u3001enabled \u6216 disabled\uFF09").option("--image <url...>", "\u53C2\u8003\u56FE\u6216\u7F16\u8F91\u56FE URL\uFF0C\u53EF\u591A\u4E2A").option("--mask <url>", "\u7F16\u8F91 mask URL\uFF08gpt-image-2 \u9700\u8981\u5355\u5F20\u53C2\u8003\u56FE\uFF09").option("--response-format <format>", "\u56FE\u50CF\u54CD\u5E94\u683C\u5F0F\uFF1Aurl \u6216 b64_json").option("--n <count>", "\u5F20\u6570\uFF081\u2013128\uFF09", (v) => Number.parseInt(v, 10), 1).option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u56FE\u50CF\u751F\u6210\u5B8C\u6210").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).action(async (promptParts, opts, cmd) => {
1641
+ gen.command("image").description("\u751F\u6210\u56FE\u50CF\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u56FE\u50CF\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--size <size>", "\u5C3A\u5BF8\uFF0C\u5982 1024x1024").option("--aspect-ratio <ratio>", "\u6A21\u578B\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF0C\u5982 16:9").option("--resolution <resolution>", "\u6A21\u578B\u539F\u751F\u8F93\u51FA\u6863\u4F4D\uFF0C\u5982 1k\u30012k").option("--seed <n>", "\u6A21\u578B\u968F\u673A\u79CD\u5B50\uFF08\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--quality <quality>", "\u56FE\u50CF\u8D28\u91CF\u6863\u4F4D\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09").option("--background <background>", "\u80CC\u666F\u6A21\u5F0F\uFF08\u4EC5 gpt-image-2 \u652F\u6301 auto/opaque\uFF09").option("--negative-prompt <text>", "\u8D1F\u9762\u63D0\u793A\u8BCD\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09").option("--creativity <level>", "\u63D0\u793A\u8BCD\u6269\u5C55\u5F3A\u5EA6\uFF0C\u5982 raw\u3001low\u3001medium\u3001high").option("--prompt-extend <boolean>", "\u662F\u5426\u6269\u5C55\u63D0\u793A\u8BCD\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "prompt-extend")).option("--style-references <json>", "Krea image_style_references JSON \u6570\u7EC4").option("--moodboards <json>", "Krea moodboards JSON \u6570\u7EC4").option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--output-format <format>", "\u8F93\u51FA\u683C\u5F0F\uFF08\u4EC5 Seedream\uFF1Apng \u6216 jpeg\uFF09").option("--optimize-prompt <mode>", "\u63D0\u793A\u8BCD\u4F18\u5316\uFF08\u4EC5 Seedream\uFF1Aauto\u3001enabled \u6216 disabled\uFF09").option("--image <url...>", "\u53C2\u8003\u56FE\u6216\u7F16\u8F91\u56FE URL\uFF0C\u53EF\u591A\u4E2A").option("--mask <url>", "\u7F16\u8F91 mask URL\uFF08gpt-image-2 \u9700\u8981\u5355\u5F20\u53C2\u8003\u56FE\uFF09").option("--response-format <format>", "\u56FE\u50CF\u54CD\u5E94\u683C\u5F0F\uFF1Aurl \u6216 b64_json").option("--n <count>", "\u5F20\u6570\uFF081\u2013128\uFF09", (v) => Number.parseInt(v, 10), 1).option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u56FE\u50CF\u751F\u6210\u5B8C\u6210").option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).action(async (promptParts, opts, cmd) => {
1474
1642
  const g = cmd.optsWithGlobals();
1475
1643
  const auth = resolveAuth(g);
1476
1644
  const model = opts.model ?? (await resolveCreativeModel(auth, "image")).model.id;
1477
1645
  if (!opts.model && !g.json) info(`\u5DF2\u81EA\u52A8\u9009\u62E9\u56FE\u50CF\u6A21\u578B\uFF1A${model}`);
1478
1646
  const n = clampInt(opts.n, 1, MAX_IMAGE_N, "n");
1647
+ const styleReferences = opts.styleReferences ? parseJsonArray(opts.styleReferences, "style-references") : void 0;
1648
+ const moodboards = opts.moodboards ? parseJsonArray(opts.moodboards, "moodboards") : void 0;
1479
1649
  validateImageGeneration(model, {
1480
1650
  n,
1481
1651
  size: opts.size,
@@ -1487,6 +1657,11 @@ function registerGen(program) {
1487
1657
  watermark: opts.watermark,
1488
1658
  outputFormat: opts.outputFormat,
1489
1659
  optimizePrompt: opts.optimizePrompt,
1660
+ negativePrompt: opts.negativePrompt,
1661
+ creativity: opts.creativity,
1662
+ promptExtend: opts.promptExtend,
1663
+ styleReferenceCount: styleReferences?.length,
1664
+ moodboardCount: moodboards?.length,
1490
1665
  responseFormat: opts.responseFormat,
1491
1666
  imageCount: opts.image?.length,
1492
1667
  hasMask: Boolean(opts.mask)
@@ -1501,6 +1676,11 @@ function registerGen(program) {
1501
1676
  if (opts.seed !== void 0) body.seed = opts.seed;
1502
1677
  if (opts.quality) body.quality = opts.quality;
1503
1678
  if (opts.background) body.background = opts.background;
1679
+ if (opts.negativePrompt) body.negative_prompt = opts.negativePrompt;
1680
+ if (opts.creativity) body.creativity = opts.creativity.toLowerCase();
1681
+ if (opts.promptExtend !== void 0) body.prompt_extend = opts.promptExtend;
1682
+ if (styleReferences) body.image_style_references = styleReferences;
1683
+ if (moodboards) body.moodboards = moodboards;
1504
1684
  if (opts.watermark !== void 0) body.watermark = opts.watermark;
1505
1685
  if (opts.outputFormat) body.output_format = opts.outputFormat.toLowerCase();
1506
1686
  if (opts.optimizePrompt) body.optimize_prompt_options = { thinking: opts.optimizePrompt.toLowerCase() };
@@ -1644,7 +1824,7 @@ function registerGen(program) {
1644
1824
  if (res.id) info(`\u4EA4\u4E92 ID\uFF1A${res.id}`);
1645
1825
  }
1646
1826
  });
1647
- gen.command("video").description("\u751F\u6210\u89C6\u9891\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u89C6\u9891\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--seconds <n>", "\u65F6\u957F\u79D2\u6570\uFF1B\u7CBE\u786E\u8303\u56F4\u8FD0\u884C focalapi models get <model> \u67E5\u770B", (v) => Number.parseInt(v, 10)).option("--size <size>", "\u5206\u8FA8\u7387\uFF0C\u5982 1280x720").option("--resolution <resolution>", "\u539F\u751F\u8F93\u51FA\u5206\u8FA8\u7387\uFF0C\u5982 480p\u3001720p\u30011080p\u30014k").option("--ratio <ratio>", "\u539F\u751F\u5BBD\u9AD8\u6BD4\uFF0C\u5982 16:9\u30019:16\u3001adaptive").option("--aspect-ratio <ratio>", "Grok \u89C6\u9891\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF0C\u5982 16:9\u30019:16\u3001auto").option("--seed <n>", "Grok \u89C6\u9891\u968F\u673A\u79CD\u5B50\uFF08\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--image <url...>", "\u56FE\u751F\u89C6\u9891\u7684\u6E90\u56FE\u50CF URL\uFF0C\u53EF\u591A\u4E2A").option("--generate-audio <boolean>", "\u662F\u5426\u751F\u6210\u97F3\u9891\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "generate-audio")).option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--service-tier <tier>", "\u670D\u52A1\u5C42\u7EA7\uFF08Seedance 2.0 \u9ED8\u8BA4 default\uFF09").option("--priority <n>", "\u4EFB\u52A1\u4F18\u5148\u7EA7\uFF08\u4EC5 Seedance 2.0 \u7CFB\u5217\uFF09", (v) => Number.parseInt(v, 10)).option("--callback-url <url>", "\u4EFB\u52A1\u5B8C\u6210\u56DE\u8C03 URL").option("--return-last-frame <boolean>", "\u662F\u5426\u8FD4\u56DE\u6700\u540E\u4E00\u5E27\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "return-last-frame")).option("--execution-expires-after <seconds>", "\u4EFB\u52A1\u8FC7\u671F\u79D2\u6570\uFF083600\u2013259200\uFF09", (v) => Number.parseInt(v, 10)).option("--safety-identifier <identifier>", "Seedance \u5B89\u5168\u6807\u8BC6\u7B26\uFF081\u201364 \u4E2A\u53EF\u6253\u5370 ASCII \u5B57\u7B26\uFF09").option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u5B8C\u6210").option("--poll-interval <ms>", "\u8F6E\u8BE2\u95F4\u9694\u6BEB\u79D2", (v) => Number.parseInt(v, 10), 5e3).option("--timeout <minutes>", "\u6700\u957F\u7B49\u5F85\u5206\u949F", (v) => Number.parseInt(v, 10), 30).option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).option("--content <json>", "Ark-compatible content JSON array; overrides prompt/image facade fields").action(
1827
+ gen.command("video").description("\u751F\u6210\u89C6\u9891\uFF08\u7701\u7565 --model \u65F6\u81EA\u52A8\u9009\u62E9\u5F53\u524D\u53EF\u7528\u9ED8\u8BA4\u6A21\u578B\uFF09").argument("<prompt...>", "\u63D0\u793A\u8BCD").option("-m, --model <model>", "\u89C6\u9891\u6A21\u578B ID\uFF1B\u7701\u7565\u65F6\u7531 focalapi \u81EA\u52A8\u9009\u62E9").option("--seconds <n>", "\u65F6\u957F\u79D2\u6570\uFF1B\u7CBE\u786E\u8303\u56F4\u8FD0\u884C focalapi models get <model> \u67E5\u770B", (v) => Number.parseInt(v, 10)).option("--size <size>", "\u5206\u8FA8\u7387\uFF0C\u5982 1280x720").option("--resolution <resolution>", "\u539F\u751F\u8F93\u51FA\u5206\u8FA8\u7387\uFF0C\u5982 480p\u3001720p\u30011080p\u30014k").option("--ratio <ratio>", "\u539F\u751F\u5BBD\u9AD8\u6BD4\uFF0C\u5982 16:9\u30019:16\u3001adaptive").option("--aspect-ratio <ratio>", "\u6A21\u578B\u539F\u751F\u753B\u9762\u6BD4\u4F8B\uFF0C\u5982 16:9\u30019:16\u3001auto").option("--seed <n>", "\u6A21\u578B\u968F\u673A\u79CD\u5B50\uFF08\u975E\u8D1F\u6574\u6570\uFF09", (v) => Number.parseInt(v, 10)).option("--fps <n>", "\u8F93\u51FA\u5E27\u7387\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09", (v) => Number.parseInt(v, 10)).option("--safety-tolerance <n>", "\u5B89\u5168\u5BB9\u5FCD\u5EA6\uFF08\u4EC5\u652F\u6301\u8BE5\u53C2\u6570\u7684\u6A21\u578B\u751F\u6548\uFF09", (v) => Number.parseInt(v, 10)).option("--image <url...>", "\u56FE\u751F\u89C6\u9891\u7684\u6E90\u56FE\u50CF URL\uFF0C\u53EF\u591A\u4E2A").option("--generate-audio <boolean>", "\u662F\u5426\u751F\u6210\u97F3\u9891\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "generate-audio")).option("--watermark <boolean>", "\u662F\u5426\u6DFB\u52A0\u6C34\u5370\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "watermark")).option("--service-tier <tier>", "\u670D\u52A1\u5C42\u7EA7\uFF08Seedance 2.0 \u9ED8\u8BA4 default\uFF09").option("--priority <n>", "\u4EFB\u52A1\u4F18\u5148\u7EA7\uFF08\u4EC5 Seedance 2.0 \u7CFB\u5217\uFF09", (v) => Number.parseInt(v, 10)).option("--callback-url <url>", "\u4EFB\u52A1\u5B8C\u6210\u56DE\u8C03 URL").option("--return-last-frame <boolean>", "\u662F\u5426\u8FD4\u56DE\u6700\u540E\u4E00\u5E27\uFF08\u53EA\u63A5\u53D7 true \u6216 false\uFF09", (v) => parseBooleanOption(v, "return-last-frame")).option("--execution-expires-after <seconds>", "\u4EFB\u52A1\u8FC7\u671F\u79D2\u6570\uFF083600\u2013259200\uFF09", (v) => Number.parseInt(v, 10)).option("--safety-identifier <identifier>", "Seedance \u5B89\u5168\u6807\u8BC6\u7B26\uFF081\u201364 \u4E2A\u53EF\u6253\u5370 ASCII \u5B57\u7B26\uFF09").option("--no-wait", "\u63D0\u4EA4\u540E\u7ACB\u5373\u8FD4\u56DE task_id\uFF0C\u4E0D\u7B49\u5F85\u5B8C\u6210").option("--poll-interval <ms>", "\u8F6E\u8BE2\u95F4\u9694\u6BEB\u79D2", (v) => Number.parseInt(v, 10), 5e3).option("--timeout <minutes>", "\u6700\u957F\u7B49\u5F85\u5206\u949F", (v) => Number.parseInt(v, 10), 30).option("-o, --out <dir>", "\u8F93\u51FA\u76EE\u5F55", DEFAULT_OUT_DIR).option("--content <json>", "Ark-compatible content JSON array; overrides prompt/image facade fields").action(
1648
1828
  async (promptParts, opts, cmd) => {
1649
1829
  const g = cmd.optsWithGlobals();
1650
1830
  const auth = resolveAuth(g);
@@ -1662,6 +1842,8 @@ function registerGen(program) {
1662
1842
  if (opts.ratio) metadata.ratio = opts.ratio;
1663
1843
  if (opts.aspectRatio) metadata.ratio = opts.aspectRatio;
1664
1844
  if (opts.seed !== void 0) metadata.seed = opts.seed;
1845
+ if (opts.fps !== void 0) metadata.fps = opts.fps;
1846
+ if (opts.safetyTolerance !== void 0) metadata.safety_tolerance = opts.safetyTolerance;
1665
1847
  if (opts.generateAudio !== void 0) metadata.generate_audio = opts.generateAudio;
1666
1848
  if (opts.watermark !== void 0) metadata.watermark = opts.watermark;
1667
1849
  if (opts.serviceTier) metadata.service_tier = opts.serviceTier;
@@ -1677,6 +1859,8 @@ function registerGen(program) {
1677
1859
  ratio: opts.ratio,
1678
1860
  aspectRatio: opts.aspectRatio,
1679
1861
  seed: opts.seed,
1862
+ fps: opts.fps,
1863
+ safetyTolerance: opts.safetyTolerance,
1680
1864
  serviceTier: opts.serviceTier,
1681
1865
  priority: opts.priority,
1682
1866
  executionExpiresAfter: opts.executionExpiresAfter,
@@ -1762,8 +1946,8 @@ function registerTask(program) {
1762
1946
  import { writeFile as writeFile2 } from "fs/promises";
1763
1947
  import { resolve as resolve3 } from "path";
1764
1948
  function registerAudio(program) {
1765
- const audio = program.command("audio").description("\u97F3\u9891\uFF1A\u8F6C\u5199\u4E0E\u5408\u6210");
1766
- audio.command("transcribe").description("\u8BED\u97F3\u8F6C\u6587\u5B57").argument("<file>", "\u97F3\u9891\u6587\u4EF6\u8DEF\u5F84").requiredOption("-m, --model <model>", "\u8F6C\u5199\u6A21\u578B ID").option("--language <lang>", "\u8BED\u8A00\u4EE3\u7801\uFF08\u5982 zh\u3001en\uFF09").action(async (file, opts, cmd) => {
1949
+ const audio = program.command("audio").description("\u97F3\u9891\uFF1A\u8F6C\u5199\u4E0E\u5408\u6210\uFF08\u6CA1\u6709\u9ED8\u8BA4\u6A21\u578B\uFF1B\u5148\u8FD0\u884C focalapi models list \u67E5\u770B\u5F53\u524D Key \u53EF\u7528\u7684\u97F3\u9891\u6A21\u578B\uFF09");
1950
+ audio.command("transcribe").description("\u8BED\u97F3\u8F6C\u6587\u5B57").argument("<file>", "\u97F3\u9891\u6587\u4EF6\u8DEF\u5F84").requiredOption("-m, --model <model>", "\u8F6C\u5199\u6A21\u578B ID\uFF08\u5FC5\u586B\u3001\u65E0\u9ED8\u8BA4\u503C\uFF1B\u8FD0\u884C focalapi models list \u67E5\u770B\uFF09").option("--language <lang>", "\u8BED\u8A00\u4EE3\u7801\uFF08\u5982 zh\u3001en\uFF09").action(async (file, opts, cmd) => {
1767
1951
  const g = cmd.optsWithGlobals();
1768
1952
  const auth = resolveAuth(g);
1769
1953
  const input = readInputFile(file);
@@ -1784,7 +1968,7 @@ function registerAudio(program) {
1784
1968
  process.stdout.write((res.text ?? JSON.stringify(res)) + "\n");
1785
1969
  }
1786
1970
  });
1787
- audio.command("speech").description("\u6587\u5B57\u8F6C\u8BED\u97F3\uFF0C\u4EA7\u7269\u4FDD\u5B58\u4E3A\u97F3\u9891\u6587\u4EF6").argument("<text...>", "\u8981\u5408\u6210\u7684\u6587\u672C").requiredOption("-m, --model <model>", "TTS \u6A21\u578B ID").option("--voice <voice>", "\u97F3\u8272", "alloy").option("--format <fmt>", "\u97F3\u9891\u683C\u5F0F\uFF08mp3/wav/...\uFF09", "mp3").option("-o, --out <file>", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").action(async (textParts, opts, cmd) => {
1971
+ audio.command("speech").description("\u6587\u5B57\u8F6C\u8BED\u97F3\uFF0C\u4EA7\u7269\u4FDD\u5B58\u4E3A\u97F3\u9891\u6587\u4EF6").argument("<text...>", "\u8981\u5408\u6210\u7684\u6587\u672C").requiredOption("-m, --model <model>", "TTS \u6A21\u578B ID\uFF08\u5FC5\u586B\u3001\u65E0\u9ED8\u8BA4\u503C\uFF1B\u8FD0\u884C focalapi models list \u67E5\u770B\uFF09").option("--voice <voice>", "\u97F3\u8272", "alloy").option("--format <fmt>", "\u97F3\u9891\u683C\u5F0F\uFF08mp3/wav/...\uFF09", "mp3").option("-o, --out <file>", "\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84").action(async (textParts, opts, cmd) => {
1788
1972
  const g = cmd.optsWithGlobals();
1789
1973
  const auth = resolveAuth(g);
1790
1974
  const res = await rawRequest({
package/package.json CHANGED
@@ -1,53 +1,57 @@
1
- {
2
- "name": "focalapi-cli",
3
- "version": "0.2.0",
4
- "description": "让任意 AI Agent 直接调用 focalapi 创作模型的命令行工具",
5
- "type": "module",
6
- "bin": {
7
- "focalapi": "dist/cli.js"
8
- },
9
- "files": [
10
- "dist",
11
- "scripts",
12
- "skills",
13
- "README.md",
14
- "CHANGELOG.md",
15
- "LICENSE"
16
- ],
17
- "engines": {
18
- "node": ">=18"
19
- },
20
- "scripts": {
21
- "dev": "tsx src/cli.ts",
22
- "build": "tsup",
23
- "test": "vitest run",
24
- "test:watch": "vitest",
25
- "typecheck": "tsc --noEmit",
26
- "postinstall": "node scripts/postinstall.cjs",
27
- "prepublishOnly": "npm run build && npm test"
28
- },
29
- "keywords": [
30
- "focalapi",
31
- "ai",
32
- "agent",
33
- "cli",
34
- "llm",
35
- "image-generation",
36
- "video-generation"
37
- ],
38
- "license": "Apache-2.0",
39
- "repository": {
40
- "type": "git",
41
- "url": "https://gitee.com/xnn-ai/focalapi-cli.git"
42
- },
43
- "dependencies": {
44
- "commander": "^12.1.0"
45
- },
46
- "devDependencies": {
47
- "@types/node": "^20.11.0",
48
- "tsup": "^8.2.4",
49
- "tsx": "^4.19.0",
50
- "typescript": "^5.5.4",
51
- "vitest": "^2.1.9"
52
- }
53
- }
1
+ {
2
+ "name": "focalapi-cli",
3
+ "version": "0.2.2",
4
+ "description": "让任意 AI Agent 直接调用 focalapi 创作模型的命令行工具",
5
+ "type": "module",
6
+ "bin": {
7
+ "focalapi": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "scripts",
12
+ "skills",
13
+ "README.md",
14
+ "CHANGELOG.md",
15
+ "LICENSE"
16
+ ],
17
+ "engines": {
18
+ "node": ">=18"
19
+ },
20
+ "scripts": {
21
+ "dev": "tsx src/cli.ts",
22
+ "build": "tsup",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest",
25
+ "typecheck": "tsc --noEmit",
26
+ "postinstall": "node scripts/postinstall.cjs",
27
+ "prepublishOnly": "npm run build && npm test"
28
+ },
29
+ "keywords": [
30
+ "focalapi",
31
+ "ai",
32
+ "agent",
33
+ "cli",
34
+ "llm",
35
+ "image-generation",
36
+ "video-generation"
37
+ ],
38
+ "license": "Apache-2.0",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/focalapi/focalapi-cli.git"
42
+ },
43
+ "homepage": "https://github.com/focalapi/focalapi-cli#readme",
44
+ "bugs": {
45
+ "url": "https://github.com/focalapi/focalapi-cli/issues"
46
+ },
47
+ "dependencies": {
48
+ "commander": "^12.1.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^20.11.0",
52
+ "tsup": "^8.2.4",
53
+ "tsx": "^4.19.0",
54
+ "typescript": "^5.5.4",
55
+ "vitest": "^2.1.9"
56
+ }
57
+ }
@@ -1,41 +1,41 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * npm 全局安装后的 best-effort Agent 接入。
5
- *
6
- * 只运行本地 connect:不联网、不读取 API Key、不改 Agent provider。没有检测到
7
- * Agent 或接入失败都不影响 CLI 安装;用户之后可运行 focalapi connect 修复。
8
- */
9
-
10
- const { existsSync } = require('node:fs');
11
- const { join } = require('node:path');
12
- const { spawnSync } = require('node:child_process');
13
-
14
- if (process.env.FOCALAPI_SKIP_POSTINSTALL === '1' || process.env.CI === '1' || process.env.CI === 'true') {
15
- process.exit(0);
16
- }
17
-
18
- const cli = join(__dirname, '..', 'dist', 'cli.js');
19
- if (!existsSync(cli)) process.exit(0);
20
-
21
- const result = spawnSync(process.execPath, [cli, 'connect', 'install', '--json'], {
22
- encoding: 'utf8',
23
- env: process.env,
24
- windowsHide: true,
25
- });
26
-
27
- if (result.status === 0) {
28
- try {
29
- const output = JSON.parse(result.stdout || '{}');
30
- const count = Array.isArray(output.installed) ? output.installed.length : 0;
31
- if (count > 0) console.log(`focalapi-cli: 已自动接入 ${count} 个 Agent Skills 目录;重启 Agent 后生效。`);
32
- } catch {
33
- // connect 成功但输出不可解析不影响安装。
34
- }
35
- process.exit(0);
36
- }
37
-
38
- if (!String(result.stderr).includes('未检测到本机 Agent')) {
39
- console.warn('focalapi-cli: Agent Skills 自动接入未完成;请稍后运行 `focalapi connect`。');
40
- }
41
- process.exit(0);
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Best-effort Agent integration after global npm installation.
5
+ *
6
+ * Run local connect only: do not access the network, read an API key, or change an Agent provider.
7
+ * Missing Agents or integration failures never fail CLI installation; users can run focalapi connect later.
8
+ */
9
+
10
+ const { existsSync } = require('node:fs');
11
+ const { join } = require('node:path');
12
+ const { spawnSync } = require('node:child_process');
13
+
14
+ if (process.env.FOCALAPI_SKIP_POSTINSTALL === '1' || process.env.CI === '1' || process.env.CI === 'true') {
15
+ process.exit(0);
16
+ }
17
+
18
+ const cli = join(__dirname, '..', 'dist', 'cli.js');
19
+ if (!existsSync(cli)) process.exit(0);
20
+
21
+ const result = spawnSync(process.execPath, [cli, 'connect', 'install', '--json'], {
22
+ encoding: 'utf8',
23
+ env: process.env,
24
+ windowsHide: true,
25
+ });
26
+
27
+ if (result.status === 0) {
28
+ try {
29
+ const output = JSON.parse(result.stdout || '{}');
30
+ const count = Array.isArray(output.installed) ? output.installed.length : 0;
31
+ if (count > 0) console.log(`focalapi-cli: 已自动接入 ${count} 个 Agent Skills 目录;重启 Agent 后生效。`);
32
+ } catch {
33
+ // Unparseable output after a successful connect must not fail installation.
34
+ }
35
+ process.exit(0);
36
+ }
37
+
38
+ if (!String(result.stderr).includes('未检测到本机 Agent')) {
39
+ console.warn('focalapi-cli: Agent Skills 自动接入未完成;请稍后运行 `focalapi connect`。');
40
+ }
41
+ process.exit(0);