@fastgpt-plugin/cli 0.2.0-alpha.2 → 0.2.0-alpha.3

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/README.en.md CHANGED
@@ -46,6 +46,8 @@ fastgpt-plugin dev --no-interactive \
46
46
  --connect "fpg_dbg_..."
47
47
  ```
48
48
 
49
+ After `--connect` starts and connects successfully, it overwrites the persisted connection key in local `config.json`, so later `fastgpt-plugin dev` runs can reuse it. In the TUI, press `c` to enter and save a new connection key. Stop the session with `Ctrl+C`; press `Ctrl+C` again to force exit.
50
+
49
51
  When passing a raw connection key, set `FASTGPT_PLUGIN_DEBUG_CONNECT_URL`, or set `FASTGPT_PLUGIN_SERVER_URL` so the CLI can request `/api/plugin/debug-sessions/connection-key:exchange`. Full connect links remain supported for compatibility. The exchange result returns the gateway WSS endpoint, `debug:tmbId:{tmbId}` source, and scoped connect token. The CLI does not need `CONNECTION_GATEWAY_AUTH_TOKEN` or `JWT_SECRET`.
50
52
 
51
53
  When no plugin directories are passed, `dev` auto-discovers plugins from the current directory. If the current directory has `index.ts`, it is used as the plugin entry; otherwise, the CLI scans one level of child directories for `index.ts`. You can still pass multiple plugin directories explicitly.
package/README.md CHANGED
@@ -46,6 +46,8 @@ fastgpt-plugin dev --no-interactive \
46
46
  --connect "fpg_dbg_..."
47
47
  ```
48
48
 
49
+ `--connect` 启动并成功连接后会覆盖本地持久配置 `config.json` 里的 connection key,后续 `fastgpt-plugin dev` 可直接复用该配置。TUI 运行中按 `c` 可以重新输入并保存 connection key;停止会话使用 `Ctrl+C`,再次按 `Ctrl+C` 强制退出。
50
+
49
51
  使用裸 connection key 时,需要设置 `FASTGPT_PLUGIN_DEBUG_CONNECT_URL`,或设置 `FASTGPT_PLUGIN_SERVER_URL` 让 CLI 默认请求 `/api/plugin/debug-sessions/connection-key:exchange`。兼容场景仍可传入完整 connect link。exchange 结果会返回 gateway WSS 地址、`debug:tmbId:{tmbId}` source 和 scoped connect token。CLI 不需要 `CONNECTION_GATEWAY_AUTH_TOKEN` 或 `JWT_SECRET`。
50
52
 
51
53
  `dev` 未传插件目录时会自动探测当前目录:如果当前目录有 `index.ts`,则把当前目录作为插件;否则扫描当前目录下一层子目录中的 `index.ts`。也可以手动传入多个插件目录。
package/dist/index.js CHANGED
@@ -526,7 +526,7 @@ var CheckCommand = class extends BaseCommand {
526
526
  //#endregion
527
527
  //#region package.json
528
528
  var name = "@fastgpt-plugin/cli";
529
- var version = "0.2.0-alpha.2";
529
+ var version = "0.2.0-alpha.3";
530
530
 
531
531
  //#endregion
532
532
  //#region src/constants.ts
@@ -811,6 +811,12 @@ const ConnectionGatewayWsHeartbeatMessageSchema = z.object({
811
811
  type: z.literal("heartbeat"),
812
812
  ts: z.number().int().positive()
813
813
  });
814
+ const ConnectionGatewayWsMetadataMessageSchema = z.object({
815
+ protocol: ConnectionGatewayWsProtocolSchema,
816
+ type: z.literal("metadata"),
817
+ requestId: z.string().min(1).optional(),
818
+ metadata: z.record(z.string(), z.unknown())
819
+ });
814
820
  const ConnectionGatewayWsErrorMessageSchema = z.object({
815
821
  protocol: ConnectionGatewayWsProtocolSchema,
816
822
  type: z.literal("error"),
@@ -827,7 +833,8 @@ const ConnectionGatewayWsBoundMessageSchema = z.object({
827
833
  const ConnectionGatewayWsClientMessageSchema = z.discriminatedUnion("type", [
828
834
  ConnectionGatewayWsBindMessageSchema,
829
835
  ConnectionGatewayWsEnvelopeMessageSchema,
830
- ConnectionGatewayWsHeartbeatMessageSchema
836
+ ConnectionGatewayWsHeartbeatMessageSchema,
837
+ ConnectionGatewayWsMetadataMessageSchema
831
838
  ]);
832
839
  const ConnectionGatewayWsServerMessageSchema = z.discriminatedUnion("type", [
833
840
  ConnectionGatewayWsBoundMessageSchema,
@@ -2238,6 +2245,7 @@ async function assertPathExists(targetPath, message) {
2238
2245
  //#endregion
2239
2246
  //#region src/debug/gateway.ts
2240
2247
  const GATEWAY_DUPLICATE_CONNECTION_CODE = "connection_gateway.session_already_bound";
2248
+ const GATEWAY_HEARTBEAT_INTERVAL_MS = 5e3;
2241
2249
  async function connectDebugGateway({ targets, options, onLog }) {
2242
2250
  if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
2243
2251
  if (options.reconnect) return connectReconnectingDebugGateway({
@@ -2300,6 +2308,9 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
2300
2308
  latestTargets = targets;
2301
2309
  current?.updateTargets(targets);
2302
2310
  },
2311
+ reconnect() {
2312
+ current?.close();
2313
+ },
2303
2314
  close() {
2304
2315
  closed = true;
2305
2316
  current?.close();
@@ -2312,6 +2323,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
2312
2323
  let targetsByPluginId = createTargetsByPluginId(targets);
2313
2324
  let closed = false;
2314
2325
  let session = null;
2326
+ let stopHeartbeat = () => void 0;
2315
2327
  let boundResolve;
2316
2328
  let boundReject;
2317
2329
  const bound = new Promise((resolve, reject) => {
@@ -2321,6 +2333,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
2321
2333
  const closedPromise = new Promise((resolve) => {
2322
2334
  socket.addEventListener("close", () => {
2323
2335
  closed = true;
2336
+ stopHeartbeat();
2324
2337
  resolve();
2325
2338
  });
2326
2339
  socket.addEventListener("error", () => {
@@ -2369,19 +2382,56 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
2369
2382
  throw error;
2370
2383
  });
2371
2384
  const boundSession = await bound;
2385
+ stopHeartbeat = startGatewayHeartbeat(socket);
2372
2386
  onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
2373
2387
  return {
2374
2388
  session: boundSession,
2375
2389
  updateTargets(targets) {
2376
2390
  targetsByPluginId = createTargetsByPluginId(targets);
2391
+ updateGatewayMetadata({
2392
+ socket,
2393
+ targets,
2394
+ options,
2395
+ onLog
2396
+ });
2377
2397
  onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
2378
2398
  },
2379
2399
  close() {
2380
- if (!closed) socket.close();
2400
+ if (!closed) {
2401
+ stopHeartbeat();
2402
+ socket.close();
2403
+ }
2381
2404
  },
2382
2405
  closed: closedPromise
2383
2406
  };
2384
2407
  }
2408
+ async function updateGatewayMetadata({ socket, targets, options, onLog }) {
2409
+ try {
2410
+ await sendMessage(socket, {
2411
+ protocol: "connection-gateway.ws.v1",
2412
+ type: "metadata",
2413
+ requestId: randomUUID(),
2414
+ metadata: makePluginDebugMetadata(targets, options.source)
2415
+ });
2416
+ } catch (error) {
2417
+ onLog?.(`Connection Gateway metadata 更新失败: ${formatErrorMessage(error)}`);
2418
+ }
2419
+ }
2420
+ function startGatewayHeartbeat(socket) {
2421
+ const timer = setInterval(() => {
2422
+ sendMessage(socket, {
2423
+ protocol: "connection-gateway.ws.v1",
2424
+ type: "heartbeat",
2425
+ ts: Date.now()
2426
+ }).catch(() => {
2427
+ socket.close();
2428
+ });
2429
+ }, GATEWAY_HEARTBEAT_INTERVAL_MS);
2430
+ if (typeof timer === "object" && timer && "unref" in timer) timer.unref();
2431
+ return () => {
2432
+ clearInterval(timer);
2433
+ };
2434
+ }
2385
2435
  function createTargetsByPluginId(targets) {
2386
2436
  return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
2387
2437
  }
@@ -2713,8 +2763,9 @@ var InvokeManager = class {
2713
2763
  //#region src/debug/remote-invoke.ts
2714
2764
  var RemoteDebugInvokeBridge = class {
2715
2765
  sessions = /* @__PURE__ */ new Map();
2766
+ getFastgptBaseUrl;
2716
2767
  constructor(fastgptBaseUrl) {
2717
- this.fastgptBaseUrl = fastgptBaseUrl;
2768
+ this.getFastgptBaseUrl = typeof fastgptBaseUrl === "function" ? fastgptBaseUrl : () => fastgptBaseUrl;
2718
2769
  }
2719
2770
  attach(runtime) {
2720
2771
  runtime.setHostRequestHandler((request) => this.handleHostRequest(request));
@@ -2731,7 +2782,7 @@ var RemoteDebugInvokeBridge = class {
2731
2782
  if (!session) throw new Error("远程调试反向调用会话不存在或已结束。");
2732
2783
  const invoke = new InvokeManager({
2733
2784
  token: session.invokeToken,
2734
- fastgptBaseUrl: this.fastgptBaseUrl
2785
+ fastgptBaseUrl: this.getFastgptBaseUrl()
2735
2786
  });
2736
2787
  switch (method) {
2737
2788
  case InvokeMethodEnum.uploadFile: return invoke.uploadFile({
@@ -2793,7 +2844,11 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
2793
2844
  commandName,
2794
2845
  targets
2795
2846
  });
2796
- attachRemoteInvokeBridges(targets, gatewayOptions.fastgptBaseUrl);
2847
+ let currentGatewayOptions = gatewayOptions;
2848
+ gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
2849
+ currentGatewayOptions = nextOptions;
2850
+ };
2851
+ attachRemoteInvokeBridges(targets, () => currentGatewayOptions.fastgptBaseUrl);
2797
2852
  const reporter = createRemoteDebugReporter({
2798
2853
  commandName,
2799
2854
  options,
@@ -2811,6 +2866,20 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
2811
2866
  gateway.close();
2812
2867
  if (force) process.exit(130);
2813
2868
  });
2869
+ reporter.attachConfigureConnectionKey(async () => {
2870
+ await configureConnectionKey({
2871
+ options,
2872
+ commandName,
2873
+ targets,
2874
+ reconnect: () => {
2875
+ if (gateway.reconnect) {
2876
+ gateway.reconnect();
2877
+ return;
2878
+ }
2879
+ gateway.close();
2880
+ }
2881
+ });
2882
+ });
2814
2883
  const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
2815
2884
  reporter.ready(source);
2816
2885
  await gateway.closed;
@@ -2831,7 +2900,11 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2831
2900
  commandName,
2832
2901
  targets: initialTargets
2833
2902
  });
2834
- attachRemoteInvokeBridges(initialTargets, gatewayOptions.fastgptBaseUrl);
2903
+ let currentGatewayOptions = gatewayOptions;
2904
+ gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
2905
+ currentGatewayOptions = nextOptions;
2906
+ };
2907
+ attachRemoteInvokeBridges(initialTargets, () => currentGatewayOptions.fastgptBaseUrl);
2835
2908
  const reporter = createRemoteDebugReporter({
2836
2909
  commandName,
2837
2910
  options,
@@ -2842,6 +2915,7 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2842
2915
  let reloadPending = false;
2843
2916
  let closed = false;
2844
2917
  let gateway;
2918
+ let activeTargets = initialTargets;
2845
2919
  const reloadTargets = async () => {
2846
2920
  if (reloadRunning) {
2847
2921
  reloadPending = true;
@@ -2853,7 +2927,8 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2853
2927
  reloadPending = false;
2854
2928
  try {
2855
2929
  const nextTargets = await loadTargets(entries, options);
2856
- attachRemoteInvokeBridges(nextTargets, gatewayOptions.fastgptBaseUrl);
2930
+ attachRemoteInvokeBridges(nextTargets, () => currentGatewayOptions.fastgptBaseUrl);
2931
+ activeTargets = nextTargets;
2857
2932
  gateway?.updateTargets(nextTargets);
2858
2933
  reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
2859
2934
  } catch (error) {
@@ -2881,6 +2956,20 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2881
2956
  connectedGateway.close();
2882
2957
  if (force) process.exit(130);
2883
2958
  });
2959
+ reporter.attachConfigureConnectionKey(async () => {
2960
+ await configureConnectionKey({
2961
+ options,
2962
+ commandName,
2963
+ targets: activeTargets,
2964
+ reconnect: () => {
2965
+ if (connectedGateway.reconnect) {
2966
+ connectedGateway.reconnect();
2967
+ return;
2968
+ }
2969
+ connectedGateway.close();
2970
+ }
2971
+ });
2972
+ });
2884
2973
  const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
2885
2974
  reporter.ready(source);
2886
2975
  await connectedGateway.closed;
@@ -2918,9 +3007,9 @@ async function loadTargets(entries, options) {
2918
3007
  if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
2919
3008
  return targets;
2920
3009
  }
2921
- function attachRemoteInvokeBridges(targets, fastgptBaseUrl) {
3010
+ function attachRemoteInvokeBridges(targets, getFastgptBaseUrl) {
2922
3011
  targets.forEach((target) => {
2923
- const invokeBridge = new RemoteDebugInvokeBridge(fastgptBaseUrl);
3012
+ const invokeBridge = new RemoteDebugInvokeBridge(getFastgptBaseUrl);
2924
3013
  invokeBridge.attach(target.runtime);
2925
3014
  target.invokeBridge = invokeBridge;
2926
3015
  });
@@ -2956,7 +3045,10 @@ function createRemoteDebugReporter({ commandName, options, gatewayOptions, targe
2956
3045
  return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
2957
3046
  }
2958
3047
  async function ensureConnectLink({ options, commandName, targets }) {
2959
- if (options.connect) return;
3048
+ if (options.connect) {
3049
+ options.connectPersistOnSuccess = true;
3050
+ return;
3051
+ }
2960
3052
  const savedConnect = await readSavedConnectionKey();
2961
3053
  if (savedConnect) {
2962
3054
  options.connect = savedConnect;
@@ -3019,6 +3111,16 @@ async function saveConnectionKey(connectionKey) {
3019
3111
  mode: 384
3020
3112
  });
3021
3113
  }
3114
+ async function configureConnectionKey({ options, commandName, targets, reconnect }) {
3115
+ options.connect = await promptConnectLink({
3116
+ commandName,
3117
+ targets,
3118
+ defaultValue: options.connect
3119
+ });
3120
+ options.connectPersistOnSuccess = true;
3121
+ logger.info("已更新 FastGPT connection key,重连成功后会保存到本地配置。");
3122
+ reconnect();
3123
+ }
3022
3124
  async function readCliConfig() {
3023
3125
  const configPath = getCliConfigPath();
3024
3126
  let raw;
@@ -3050,10 +3152,20 @@ const CLI_CONFIG_FILE_NAME = "config.json";
3050
3152
  const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
3051
3153
  const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
3052
3154
  var PlainRemoteDebugReporter = class {
3155
+ closeSession;
3156
+ interruptCount = 0;
3157
+ onSigint = () => {
3158
+ this.requestClose("SIGINT");
3159
+ };
3160
+ onSigterm = () => {
3161
+ this.requestClose("SIGTERM");
3162
+ };
3053
3163
  constructor(summary) {
3054
3164
  this.summary = summary;
3055
3165
  }
3056
3166
  start() {
3167
+ process.on("SIGINT", this.onSigint);
3168
+ process.on("SIGTERM", this.onSigterm);
3057
3169
  logger.info([
3058
3170
  "FastGPT 插件开发会话",
3059
3171
  ` command: fastgpt-plugin ${this.summary.commandName}`,
@@ -3065,6 +3177,13 @@ var PlainRemoteDebugReporter = class {
3065
3177
  ...this.summary.targets.map(formatTargetLine)
3066
3178
  ].join("\n"));
3067
3179
  }
3180
+ requestClose(signal) {
3181
+ this.interruptCount += 1;
3182
+ const force = this.interruptCount >= 2;
3183
+ this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次发送 ${signal} 强制退出。`);
3184
+ this.closeSession?.({ force });
3185
+ if (force && !this.closeSession) process.exit(130);
3186
+ }
3068
3187
  log(message) {
3069
3188
  logger.info(message);
3070
3189
  }
@@ -3074,8 +3193,13 @@ var PlainRemoteDebugReporter = class {
3074
3193
  });
3075
3194
  logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
3076
3195
  }
3077
- attachClose() {}
3196
+ attachClose(close) {
3197
+ this.closeSession = close;
3198
+ }
3199
+ attachConfigureConnectionKey() {}
3078
3200
  end() {
3201
+ process.off("SIGINT", this.onSigint);
3202
+ process.off("SIGTERM", this.onSigterm);
3079
3203
  logger.info("远程调试会话已结束。");
3080
3204
  }
3081
3205
  };
@@ -3085,36 +3209,34 @@ var TuiRemoteDebugReporter = class {
3085
3209
  logs = [];
3086
3210
  instance;
3087
3211
  closeSession;
3212
+ configureConnectionKey;
3088
3213
  interruptCount = 0;
3214
+ configuring = false;
3089
3215
  onSigint = () => {
3090
- this.requestClose("ctrl-c");
3216
+ this.requestClose("SIGINT");
3091
3217
  };
3092
- requestClose = (reason) => {
3093
- if (reason === "q") {
3094
- this.log("收到退出指令,正在关闭远程调试会话。");
3095
- this.closeSession?.({ force: false });
3096
- return;
3097
- }
3218
+ requestClose = (signal) => {
3098
3219
  this.interruptCount += 1;
3099
3220
  const force = this.interruptCount >= 2;
3100
3221
  this.status = force ? "closed" : "closing";
3101
- this.log(force ? "再次收到 Ctrl+C,正在强制退出。" : "收到 Ctrl+C,正在关闭远程调试会话。再次按 Ctrl+C 强制退出。");
3222
+ this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次按 Ctrl+C 强制退出。`);
3102
3223
  this.closeSession?.({ force });
3103
3224
  if (force && !this.closeSession) process.exit(130);
3104
3225
  };
3226
+ requestConfigureConnectionKey = () => {
3227
+ if (this.configuring) return;
3228
+ if (!this.configureConnectionKey) {
3229
+ this.log("connection key 配置入口尚未就绪。");
3230
+ return;
3231
+ }
3232
+ this.runConfigureConnectionKey();
3233
+ };
3105
3234
  constructor(summary) {
3106
3235
  this.summary = summary;
3107
3236
  }
3108
3237
  start() {
3109
3238
  process.on("SIGINT", this.onSigint);
3110
- this.instance = render(this.createApp(), {
3111
- stdin: process.stdin,
3112
- stdout: process.stdout,
3113
- stderr: process.stderr,
3114
- exitOnCtrlC: false,
3115
- interactive: true,
3116
- patchConsole: false
3117
- });
3239
+ this.mount();
3118
3240
  }
3119
3241
  log(message) {
3120
3242
  this.logs.push({
@@ -3132,39 +3254,78 @@ var TuiRemoteDebugReporter = class {
3132
3254
  attachClose(close) {
3133
3255
  this.closeSession = close;
3134
3256
  }
3257
+ attachConfigureConnectionKey(handler) {
3258
+ this.configureConnectionKey = handler;
3259
+ }
3135
3260
  async end() {
3136
3261
  process.off("SIGINT", this.onSigint);
3137
3262
  this.status = "closed";
3138
3263
  this.rerender();
3139
- const instance = this.instance;
3140
- this.instance = void 0;
3141
- if (instance) {
3142
- await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
3143
- instance.unmount();
3144
- await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
3145
- }
3264
+ await this.unmount();
3146
3265
  logger.info("远程调试会话已结束。");
3147
3266
  }
3148
3267
  rerender() {
3149
3268
  this.instance?.rerender(this.createApp());
3150
3269
  }
3270
+ mount() {
3271
+ if (this.instance) return;
3272
+ this.instance = render(this.createApp(), {
3273
+ stdin: process.stdin,
3274
+ stdout: process.stdout,
3275
+ stderr: process.stderr,
3276
+ exitOnCtrlC: false,
3277
+ interactive: true,
3278
+ patchConsole: false
3279
+ });
3280
+ }
3281
+ async unmount() {
3282
+ const instance = this.instance;
3283
+ this.instance = void 0;
3284
+ if (!instance) return;
3285
+ await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
3286
+ instance.unmount();
3287
+ await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
3288
+ }
3289
+ async runConfigureConnectionKey() {
3290
+ if (!this.configureConnectionKey) return;
3291
+ const previousStatus = this.status;
3292
+ this.configuring = true;
3293
+ this.status = "configuring";
3294
+ this.rerender();
3295
+ try {
3296
+ await this.unmount();
3297
+ await this.configureConnectionKey();
3298
+ this.status = "connecting";
3299
+ this.log("已更新 connection key,正在重新连接 Connection Gateway。");
3300
+ } catch (error) {
3301
+ this.status = previousStatus;
3302
+ this.log(`connection key 配置失败: ${formatConnectionKeyExchangeError(error)}`);
3303
+ } finally {
3304
+ this.configuring = false;
3305
+ if (this.status !== "closed") {
3306
+ this.mount();
3307
+ this.rerender();
3308
+ }
3309
+ }
3310
+ }
3151
3311
  createApp() {
3152
3312
  return React.createElement(RemoteDebugInkApp, {
3153
3313
  summary: this.summary,
3154
3314
  status: this.status,
3155
3315
  source: this.source,
3156
3316
  logs: this.logs,
3157
- onQuit: this.requestClose
3317
+ onClose: () => this.requestClose("SIGINT"),
3318
+ onConfigureConnectionKey: this.requestConfigureConnectionKey
3158
3319
  });
3159
3320
  }
3160
3321
  };
3161
- function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
3322
+ function RemoteDebugInkApp({ summary, status, source, logs, onClose, onConfigureConnectionKey }) {
3162
3323
  useInput((inputValue, key) => {
3163
3324
  if (key.ctrl && inputValue === "c") {
3164
- onQuit("ctrl-c");
3325
+ onClose();
3165
3326
  return;
3166
3327
  }
3167
- if (inputValue === "q") onQuit("q");
3328
+ if (inputValue === "c") onConfigureConnectionKey();
3168
3329
  });
3169
3330
  const statusMeta = getTuiStatusMeta(status);
3170
3331
  const sourceLabel = source === "-" ? "waiting for bind" : source;
@@ -3256,7 +3417,7 @@ function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
3256
3417
  }, ...activityItems)), React.createElement(Box, {
3257
3418
  marginTop: 1,
3258
3419
  justifyContent: "space-between"
3259
- }, React.createElement(Text, { dimColor: true }, "q stop"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
3420
+ }, React.createElement(Text, { dimColor: true }, "c configure connection key"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
3260
3421
  }
3261
3422
  function getTuiStatusMeta(status) {
3262
3423
  if (status === "connected") return {
@@ -3273,6 +3434,13 @@ function getTuiStatusMeta(status) {
3273
3434
  color: "gray",
3274
3435
  borderColor: "gray"
3275
3436
  };
3437
+ if (status === "configuring") return {
3438
+ label: "CONFIGURE",
3439
+ marker: "◆",
3440
+ description: "Waiting for a new FastGPT connection key.",
3441
+ color: "cyanBright",
3442
+ borderColor: "cyan"
3443
+ };
3276
3444
  if (status === "closing") return {
3277
3445
  label: "CLOSING",
3278
3446
  marker: "◆",
@@ -3329,7 +3497,7 @@ function formatConnectionKeyExchangeError(error) {
3329
3497
  }
3330
3498
  async function resolveConnectGatewayOptions(options) {
3331
3499
  const info = await exchangeConnectLink(options.connect);
3332
- return {
3500
+ const resolvedOptions = {
3333
3501
  gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
3334
3502
  connectToken: info.connectToken,
3335
3503
  userId: info.tmbId,
@@ -3337,9 +3505,20 @@ async function resolveConnectGatewayOptions(options) {
3337
3505
  fastgptBaseUrl: info.fastgptBaseUrl,
3338
3506
  tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
3339
3507
  reconnect: options.noReconnect ? false : options.reconnect ?? true,
3340
- reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms"),
3341
- resolveReconnectOptions: () => resolveConnectGatewayOptions(options)
3508
+ reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms")
3509
+ };
3510
+ resolvedOptions.resolveReconnectOptions = async () => {
3511
+ const nextOptions = await resolveConnectGatewayOptions(options);
3512
+ if (options.connectPersistOnSuccess === true) {
3513
+ await saveConnectionKey(options.connect);
3514
+ logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
3515
+ options.connectPersistOnSuccess = false;
3516
+ }
3517
+ resolvedOptions.onResolvedReconnectOptions?.(nextOptions);
3518
+ nextOptions.onResolvedReconnectOptions = resolvedOptions.onResolvedReconnectOptions;
3519
+ return nextOptions;
3342
3520
  };
3521
+ return resolvedOptions;
3343
3522
  }
3344
3523
  function resolveUploadDir({ entryDir, index, total, uploadDir }) {
3345
3524
  if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
@@ -3719,9 +3898,6 @@ async function run(argv) {
3719
3898
 
3720
3899
  //#endregion
3721
3900
  //#region src/index.ts
3722
- process.on("SIGINT", () => {
3723
- process.exit(130);
3724
- });
3725
3901
  const main = async () => {
3726
3902
  try {
3727
3903
  await run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastgpt-plugin/cli",
3
- "version": "0.2.0-alpha.2",
3
+ "version": "0.2.0-alpha.3",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {