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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -11,7 +11,7 @@ import { kebabCase } from "es-toolkit";
11
11
  import fs$1, { createWriteStream } from "node:fs";
12
12
  import os from "node:os";
13
13
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
14
- import { Box, Text, render, useInput } from "ink";
14
+ import { Box, Text, render, useApp, useInput } from "ink";
15
15
  import React from "react";
16
16
  import { createHash, randomUUID } from "node:crypto";
17
17
  import { Readable } from "stream";
@@ -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";
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,12 +2245,14 @@ 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";
2241
- async function connectDebugGateway({ targets, options, onLog }) {
2248
+ const GATEWAY_HEARTBEAT_INTERVAL_MS = 5e3;
2249
+ async function connectDebugGateway({ targets, options, onLog, onReconnect }) {
2242
2250
  if (targets.length === 0) throw new Error("Gateway debug targets cannot be empty");
2243
2251
  if (options.reconnect) return connectReconnectingDebugGateway({
2244
2252
  targets,
2245
2253
  options,
2246
- onLog
2254
+ onLog,
2255
+ onReconnect
2247
2256
  });
2248
2257
  return connectSingleDebugGateway({
2249
2258
  targets,
@@ -2251,7 +2260,7 @@ async function connectDebugGateway({ targets, options, onLog }) {
2251
2260
  onLog
2252
2261
  });
2253
2262
  }
2254
- async function connectReconnectingDebugGateway({ targets, options, onLog }) {
2263
+ async function connectReconnectingDebugGateway({ targets, options, onLog, onReconnect }) {
2255
2264
  let closed = false;
2256
2265
  let current = null;
2257
2266
  let currentSession = null;
@@ -2274,6 +2283,7 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
2274
2283
  options: currentOptions,
2275
2284
  onLog
2276
2285
  });
2286
+ if (currentSession) onReconnect?.(current.session);
2277
2287
  currentSession = current.session;
2278
2288
  await current.closed;
2279
2289
  } catch (error) {
@@ -2300,6 +2310,9 @@ async function connectReconnectingDebugGateway({ targets, options, onLog }) {
2300
2310
  latestTargets = targets;
2301
2311
  current?.updateTargets(targets);
2302
2312
  },
2313
+ reconnect() {
2314
+ current?.close();
2315
+ },
2303
2316
  close() {
2304
2317
  closed = true;
2305
2318
  current?.close();
@@ -2312,6 +2325,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
2312
2325
  let targetsByPluginId = createTargetsByPluginId(targets);
2313
2326
  let closed = false;
2314
2327
  let session = null;
2328
+ let stopHeartbeat = () => void 0;
2315
2329
  let boundResolve;
2316
2330
  let boundReject;
2317
2331
  const bound = new Promise((resolve, reject) => {
@@ -2321,6 +2335,7 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
2321
2335
  const closedPromise = new Promise((resolve) => {
2322
2336
  socket.addEventListener("close", () => {
2323
2337
  closed = true;
2338
+ stopHeartbeat();
2324
2339
  resolve();
2325
2340
  });
2326
2341
  socket.addEventListener("error", () => {
@@ -2369,19 +2384,56 @@ async function connectSingleDebugGateway({ targets, options, onLog }) {
2369
2384
  throw error;
2370
2385
  });
2371
2386
  const boundSession = await bound;
2387
+ stopHeartbeat = startGatewayHeartbeat(socket);
2372
2388
  onLog?.(`已连接 Connection Gateway session: ${boundSession.id}`);
2373
2389
  return {
2374
2390
  session: boundSession,
2375
2391
  updateTargets(targets) {
2376
2392
  targetsByPluginId = createTargetsByPluginId(targets);
2393
+ updateGatewayMetadata({
2394
+ socket,
2395
+ targets,
2396
+ options,
2397
+ onLog
2398
+ });
2377
2399
  onLog?.(`已热更新本地调试插件: ${targets.map((target) => target.snapshot.pluginId).join(", ")}`);
2378
2400
  },
2379
2401
  close() {
2380
- if (!closed) socket.close();
2402
+ if (!closed) {
2403
+ stopHeartbeat();
2404
+ socket.close();
2405
+ }
2381
2406
  },
2382
2407
  closed: closedPromise
2383
2408
  };
2384
2409
  }
2410
+ async function updateGatewayMetadata({ socket, targets, options, onLog }) {
2411
+ try {
2412
+ await sendMessage(socket, {
2413
+ protocol: "connection-gateway.ws.v1",
2414
+ type: "metadata",
2415
+ requestId: randomUUID(),
2416
+ metadata: makePluginDebugMetadata(targets, options.source)
2417
+ });
2418
+ } catch (error) {
2419
+ onLog?.(`Connection Gateway metadata 更新失败: ${formatErrorMessage(error)}`);
2420
+ }
2421
+ }
2422
+ function startGatewayHeartbeat(socket) {
2423
+ const timer = setInterval(() => {
2424
+ sendMessage(socket, {
2425
+ protocol: "connection-gateway.ws.v1",
2426
+ type: "heartbeat",
2427
+ ts: Date.now()
2428
+ }).catch(() => {
2429
+ socket.close();
2430
+ });
2431
+ }, GATEWAY_HEARTBEAT_INTERVAL_MS);
2432
+ if (typeof timer === "object" && timer && "unref" in timer) timer.unref();
2433
+ return () => {
2434
+ clearInterval(timer);
2435
+ };
2436
+ }
2385
2437
  function createTargetsByPluginId(targets) {
2386
2438
  return new Map(targets.map((target) => [target.snapshot.pluginId, target]));
2387
2439
  }
@@ -2713,8 +2765,9 @@ var InvokeManager = class {
2713
2765
  //#region src/debug/remote-invoke.ts
2714
2766
  var RemoteDebugInvokeBridge = class {
2715
2767
  sessions = /* @__PURE__ */ new Map();
2768
+ getFastgptBaseUrl;
2716
2769
  constructor(fastgptBaseUrl) {
2717
- this.fastgptBaseUrl = fastgptBaseUrl;
2770
+ this.getFastgptBaseUrl = typeof fastgptBaseUrl === "function" ? fastgptBaseUrl : () => fastgptBaseUrl;
2718
2771
  }
2719
2772
  attach(runtime) {
2720
2773
  runtime.setHostRequestHandler((request) => this.handleHostRequest(request));
@@ -2731,7 +2784,7 @@ var RemoteDebugInvokeBridge = class {
2731
2784
  if (!session) throw new Error("远程调试反向调用会话不存在或已结束。");
2732
2785
  const invoke = new InvokeManager({
2733
2786
  token: session.invokeToken,
2734
- fastgptBaseUrl: this.fastgptBaseUrl
2787
+ fastgptBaseUrl: this.getFastgptBaseUrl()
2735
2788
  });
2736
2789
  switch (method) {
2737
2790
  case InvokeMethodEnum.uploadFile: return invoke.uploadFile({
@@ -2793,7 +2846,11 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
2793
2846
  commandName,
2794
2847
  targets
2795
2848
  });
2796
- attachRemoteInvokeBridges(targets, gatewayOptions.fastgptBaseUrl);
2849
+ let currentGatewayOptions = gatewayOptions;
2850
+ gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
2851
+ currentGatewayOptions = nextOptions;
2852
+ };
2853
+ attachRemoteInvokeBridges(targets, () => currentGatewayOptions.fastgptBaseUrl);
2797
2854
  const reporter = createRemoteDebugReporter({
2798
2855
  commandName,
2799
2856
  options,
@@ -2805,12 +2862,29 @@ async function runRemoteDebugSessionOnce({ entriesInput, options, commandName })
2805
2862
  const gateway = await connectDebugGateway({
2806
2863
  targets,
2807
2864
  options: gatewayOptions,
2808
- onLog: (message) => reporter.log(`[gateway] ${message}`)
2865
+ onLog: (message) => reporter.log(`[gateway] ${message}`),
2866
+ onReconnect: (session) => {
2867
+ reporter.ready(session.sessionScope.source ?? currentGatewayOptions.source ?? "-");
2868
+ }
2809
2869
  });
2810
2870
  reporter.attachClose(({ force }) => {
2811
2871
  gateway.close();
2812
2872
  if (force) process.exit(130);
2813
2873
  });
2874
+ reporter.attachConfigureConnectionKey(async () => {
2875
+ await configureConnectionKey({
2876
+ options,
2877
+ commandName,
2878
+ targets,
2879
+ reconnect: () => {
2880
+ if (gateway.reconnect) {
2881
+ gateway.reconnect();
2882
+ return;
2883
+ }
2884
+ gateway.close();
2885
+ }
2886
+ });
2887
+ });
2814
2888
  const source = gateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
2815
2889
  reporter.ready(source);
2816
2890
  await gateway.closed;
@@ -2831,7 +2905,11 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2831
2905
  commandName,
2832
2906
  targets: initialTargets
2833
2907
  });
2834
- attachRemoteInvokeBridges(initialTargets, gatewayOptions.fastgptBaseUrl);
2908
+ let currentGatewayOptions = gatewayOptions;
2909
+ gatewayOptions.onResolvedReconnectOptions = (nextOptions) => {
2910
+ currentGatewayOptions = nextOptions;
2911
+ };
2912
+ attachRemoteInvokeBridges(initialTargets, () => currentGatewayOptions.fastgptBaseUrl);
2835
2913
  const reporter = createRemoteDebugReporter({
2836
2914
  commandName,
2837
2915
  options,
@@ -2842,6 +2920,7 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2842
2920
  let reloadPending = false;
2843
2921
  let closed = false;
2844
2922
  let gateway;
2923
+ let activeTargets = initialTargets;
2845
2924
  const reloadTargets = async () => {
2846
2925
  if (reloadRunning) {
2847
2926
  reloadPending = true;
@@ -2853,7 +2932,8 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2853
2932
  reloadPending = false;
2854
2933
  try {
2855
2934
  const nextTargets = await loadTargets(entries, options);
2856
- attachRemoteInvokeBridges(nextTargets, gatewayOptions.fastgptBaseUrl);
2935
+ attachRemoteInvokeBridges(nextTargets, () => currentGatewayOptions.fastgptBaseUrl);
2936
+ activeTargets = nextTargets;
2857
2937
  gateway?.updateTargets(nextTargets);
2858
2938
  reporter.log(`检测到插件文件变化,已热更新 ${nextTargets.length} 个本地插件。`);
2859
2939
  } catch (error) {
@@ -2872,7 +2952,10 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2872
2952
  const connectedGateway = await connectDebugGateway({
2873
2953
  targets: initialTargets,
2874
2954
  options: gatewayOptions,
2875
- onLog: (message) => reporter.log(`[gateway] ${message}`)
2955
+ onLog: (message) => reporter.log(`[gateway] ${message}`),
2956
+ onReconnect: (session) => {
2957
+ reporter.ready(session.sessionScope.source ?? currentGatewayOptions.source ?? "-");
2958
+ }
2876
2959
  });
2877
2960
  gateway = connectedGateway;
2878
2961
  reporter.attachClose(({ force }) => {
@@ -2881,6 +2964,20 @@ async function runWatchRemoteDebugSession({ entriesInput, options, commandName }
2881
2964
  connectedGateway.close();
2882
2965
  if (force) process.exit(130);
2883
2966
  });
2967
+ reporter.attachConfigureConnectionKey(async () => {
2968
+ await configureConnectionKey({
2969
+ options,
2970
+ commandName,
2971
+ targets: activeTargets,
2972
+ reconnect: () => {
2973
+ if (connectedGateway.reconnect) {
2974
+ connectedGateway.reconnect();
2975
+ return;
2976
+ }
2977
+ connectedGateway.close();
2978
+ }
2979
+ });
2980
+ });
2884
2981
  const source = connectedGateway.session.sessionScope.source ?? gatewayOptions.source ?? "-";
2885
2982
  reporter.ready(source);
2886
2983
  await connectedGateway.closed;
@@ -2918,9 +3015,9 @@ async function loadTargets(entries, options) {
2918
3015
  if (duplicatePluginIds.length > 0) throw new Error(`gateway pluginId 重复: ${duplicatePluginIds.join(", ")}`);
2919
3016
  return targets;
2920
3017
  }
2921
- function attachRemoteInvokeBridges(targets, fastgptBaseUrl) {
3018
+ function attachRemoteInvokeBridges(targets, getFastgptBaseUrl) {
2922
3019
  targets.forEach((target) => {
2923
- const invokeBridge = new RemoteDebugInvokeBridge(fastgptBaseUrl);
3020
+ const invokeBridge = new RemoteDebugInvokeBridge(getFastgptBaseUrl);
2924
3021
  invokeBridge.attach(target.runtime);
2925
3022
  target.invokeBridge = invokeBridge;
2926
3023
  });
@@ -2956,7 +3053,10 @@ function createRemoteDebugReporter({ commandName, options, gatewayOptions, targe
2956
3053
  return options.interactive !== false && Boolean(process.stdout.isTTY) && Boolean(process.stdin.isTTY) ? new TuiRemoteDebugReporter(summary) : new PlainRemoteDebugReporter(summary);
2957
3054
  }
2958
3055
  async function ensureConnectLink({ options, commandName, targets }) {
2959
- if (options.connect) return;
3056
+ if (options.connect) {
3057
+ options.connectPersistOnSuccess = true;
3058
+ return;
3059
+ }
2960
3060
  const savedConnect = await readSavedConnectionKey();
2961
3061
  if (savedConnect) {
2962
3062
  options.connect = savedConnect;
@@ -3019,6 +3119,16 @@ async function saveConnectionKey(connectionKey) {
3019
3119
  mode: 384
3020
3120
  });
3021
3121
  }
3122
+ async function configureConnectionKey({ options, commandName, targets, reconnect }) {
3123
+ options.connect = await promptConnectLink({
3124
+ commandName,
3125
+ targets,
3126
+ defaultValue: options.connect
3127
+ });
3128
+ options.connectPersistOnSuccess = true;
3129
+ logger.info("已更新 FastGPT connection key,重连成功后会保存到本地配置。");
3130
+ reconnect();
3131
+ }
3022
3132
  async function readCliConfig() {
3023
3133
  const configPath = getCliConfigPath();
3024
3134
  let raw;
@@ -3050,10 +3160,20 @@ const CLI_CONFIG_FILE_NAME = "config.json";
3050
3160
  const CLI_CONFIG_DIR_NAME = "fastgpt-plugin";
3051
3161
  const CliConfigSchema = z.object({ debug: z.object({ connectionKey: z.string().min(1).optional() }).optional() }).passthrough();
3052
3162
  var PlainRemoteDebugReporter = class {
3163
+ closeSession;
3164
+ interruptCount = 0;
3165
+ onSigint = () => {
3166
+ this.requestClose("SIGINT");
3167
+ };
3168
+ onSigterm = () => {
3169
+ this.requestClose("SIGTERM");
3170
+ };
3053
3171
  constructor(summary) {
3054
3172
  this.summary = summary;
3055
3173
  }
3056
3174
  start() {
3175
+ process.on("SIGINT", this.onSigint);
3176
+ process.on("SIGTERM", this.onSigterm);
3057
3177
  logger.info([
3058
3178
  "FastGPT 插件开发会话",
3059
3179
  ` command: fastgpt-plugin ${this.summary.commandName}`,
@@ -3065,6 +3185,13 @@ var PlainRemoteDebugReporter = class {
3065
3185
  ...this.summary.targets.map(formatTargetLine)
3066
3186
  ].join("\n"));
3067
3187
  }
3188
+ requestClose(signal) {
3189
+ this.interruptCount += 1;
3190
+ const force = this.interruptCount >= 2;
3191
+ this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次发送 ${signal} 强制退出。`);
3192
+ this.closeSession?.({ force });
3193
+ if (force && !this.closeSession) process.exit(130);
3194
+ }
3068
3195
  log(message) {
3069
3196
  logger.info(message);
3070
3197
  }
@@ -3074,8 +3201,13 @@ var PlainRemoteDebugReporter = class {
3074
3201
  });
3075
3202
  logger.info(`已建立 1 个远程调试通道,挂载 ${this.summary.targets.length} 个插件,按 Ctrl+C 停止。`);
3076
3203
  }
3077
- attachClose() {}
3204
+ attachClose(close) {
3205
+ this.closeSession = close;
3206
+ }
3207
+ attachConfigureConnectionKey() {}
3078
3208
  end() {
3209
+ process.off("SIGINT", this.onSigint);
3210
+ process.off("SIGTERM", this.onSigterm);
3079
3211
  logger.info("远程调试会话已结束。");
3080
3212
  }
3081
3213
  };
@@ -3085,36 +3217,34 @@ var TuiRemoteDebugReporter = class {
3085
3217
  logs = [];
3086
3218
  instance;
3087
3219
  closeSession;
3220
+ configureConnectionKey;
3088
3221
  interruptCount = 0;
3222
+ configuring = false;
3089
3223
  onSigint = () => {
3090
- this.requestClose("ctrl-c");
3224
+ this.requestClose("SIGINT");
3091
3225
  };
3092
- requestClose = (reason) => {
3093
- if (reason === "q") {
3094
- this.log("收到退出指令,正在关闭远程调试会话。");
3095
- this.closeSession?.({ force: false });
3096
- return;
3097
- }
3226
+ requestClose = (signal) => {
3098
3227
  this.interruptCount += 1;
3099
3228
  const force = this.interruptCount >= 2;
3100
3229
  this.status = force ? "closed" : "closing";
3101
- this.log(force ? "再次收到 Ctrl+C,正在强制退出。" : "收到 Ctrl+C,正在关闭远程调试会话。再次按 Ctrl+C 强制退出。");
3230
+ this.log(force ? `再次收到 ${signal},正在强制退出。` : `收到 ${signal},正在关闭远程调试会话。再次按 Ctrl+C 强制退出。`);
3102
3231
  this.closeSession?.({ force });
3103
3232
  if (force && !this.closeSession) process.exit(130);
3104
3233
  };
3234
+ requestConfigureConnectionKey = (suspendTerminal) => {
3235
+ if (this.configuring) return;
3236
+ if (!this.configureConnectionKey) {
3237
+ this.log("connection key 配置入口尚未就绪。");
3238
+ return;
3239
+ }
3240
+ this.runConfigureConnectionKey(suspendTerminal);
3241
+ };
3105
3242
  constructor(summary) {
3106
3243
  this.summary = summary;
3107
3244
  }
3108
3245
  start() {
3109
3246
  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
- });
3247
+ this.mount();
3118
3248
  }
3119
3249
  log(message) {
3120
3250
  this.logs.push({
@@ -3132,39 +3262,80 @@ var TuiRemoteDebugReporter = class {
3132
3262
  attachClose(close) {
3133
3263
  this.closeSession = close;
3134
3264
  }
3265
+ attachConfigureConnectionKey(handler) {
3266
+ this.configureConnectionKey = handler;
3267
+ }
3135
3268
  async end() {
3136
3269
  process.off("SIGINT", this.onSigint);
3137
3270
  this.status = "closed";
3138
3271
  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
- }
3272
+ await this.unmount();
3146
3273
  logger.info("远程调试会话已结束。");
3147
3274
  }
3148
3275
  rerender() {
3149
3276
  this.instance?.rerender(this.createApp());
3150
3277
  }
3278
+ mount() {
3279
+ if (this.instance) return;
3280
+ this.instance = render(this.createApp(), {
3281
+ stdin: process.stdin,
3282
+ stdout: process.stdout,
3283
+ stderr: process.stderr,
3284
+ exitOnCtrlC: false,
3285
+ interactive: true,
3286
+ patchConsole: false
3287
+ });
3288
+ }
3289
+ async unmount() {
3290
+ const instance = this.instance;
3291
+ this.instance = void 0;
3292
+ if (!instance) return;
3293
+ await Promise.race([instance.waitUntilRenderFlush(), setTimeout$1(50)]).catch(() => void 0);
3294
+ instance.unmount();
3295
+ await Promise.race([instance.waitUntilExit(), setTimeout$1(50)]).catch(() => void 0);
3296
+ }
3297
+ async runConfigureConnectionKey(suspendTerminal) {
3298
+ if (!this.configureConnectionKey) return;
3299
+ const previousStatus = this.status;
3300
+ this.configuring = true;
3301
+ this.status = "configuring";
3302
+ this.rerender();
3303
+ try {
3304
+ await suspendTerminal(async () => {
3305
+ await this.configureConnectionKey?.();
3306
+ });
3307
+ this.status = "connecting";
3308
+ this.log("已更新 connection key,正在重新连接 Connection Gateway。");
3309
+ } catch (error) {
3310
+ this.status = previousStatus;
3311
+ this.log(`connection key 配置失败: ${formatConnectionKeyExchangeError(error)}`);
3312
+ } finally {
3313
+ this.configuring = false;
3314
+ if (this.status !== "closed") {
3315
+ this.mount();
3316
+ this.rerender();
3317
+ }
3318
+ }
3319
+ }
3151
3320
  createApp() {
3152
3321
  return React.createElement(RemoteDebugInkApp, {
3153
3322
  summary: this.summary,
3154
3323
  status: this.status,
3155
3324
  source: this.source,
3156
3325
  logs: this.logs,
3157
- onQuit: this.requestClose
3326
+ onClose: () => this.requestClose("SIGINT"),
3327
+ onConfigureConnectionKey: this.requestConfigureConnectionKey
3158
3328
  });
3159
3329
  }
3160
3330
  };
3161
- function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
3331
+ function RemoteDebugInkApp({ summary, status, source, logs, onClose, onConfigureConnectionKey }) {
3332
+ const { suspendTerminal } = useApp();
3162
3333
  useInput((inputValue, key) => {
3163
3334
  if (key.ctrl && inputValue === "c") {
3164
- onQuit("ctrl-c");
3335
+ onClose();
3165
3336
  return;
3166
3337
  }
3167
- if (inputValue === "q") onQuit("q");
3338
+ if (inputValue === "c") onConfigureConnectionKey(suspendTerminal);
3168
3339
  });
3169
3340
  const statusMeta = getTuiStatusMeta(status);
3170
3341
  const sourceLabel = source === "-" ? "waiting for bind" : source;
@@ -3256,7 +3427,7 @@ function RemoteDebugInkApp({ summary, status, source, logs, onQuit }) {
3256
3427
  }, ...activityItems)), React.createElement(Box, {
3257
3428
  marginTop: 1,
3258
3429
  justifyContent: "space-between"
3259
- }, React.createElement(Text, { dimColor: true }, "q stop"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
3430
+ }, React.createElement(Text, { dimColor: true }, "c configure connection key"), React.createElement(Text, { dimColor: true }, "Ctrl+C stop · second Ctrl+C force exit")));
3260
3431
  }
3261
3432
  function getTuiStatusMeta(status) {
3262
3433
  if (status === "connected") return {
@@ -3273,6 +3444,13 @@ function getTuiStatusMeta(status) {
3273
3444
  color: "gray",
3274
3445
  borderColor: "gray"
3275
3446
  };
3447
+ if (status === "configuring") return {
3448
+ label: "CONFIGURE",
3449
+ marker: "◆",
3450
+ description: "Waiting for a new FastGPT connection key.",
3451
+ color: "cyanBright",
3452
+ borderColor: "cyan"
3453
+ };
3276
3454
  if (status === "closing") return {
3277
3455
  label: "CLOSING",
3278
3456
  marker: "◆",
@@ -3329,7 +3507,7 @@ function formatConnectionKeyExchangeError(error) {
3329
3507
  }
3330
3508
  async function resolveConnectGatewayOptions(options) {
3331
3509
  const info = await exchangeConnectLink(options.connect);
3332
- return {
3510
+ const resolvedOptions = {
3333
3511
  gatewayUrl: normalizeGatewayWsUrl(info.gatewayUrl),
3334
3512
  connectToken: info.connectToken,
3335
3513
  userId: info.tmbId,
@@ -3337,9 +3515,20 @@ async function resolveConnectGatewayOptions(options) {
3337
3515
  fastgptBaseUrl: info.fastgptBaseUrl,
3338
3516
  tokenTtlMs: Math.max(1, info.expiresAt - Date.now()),
3339
3517
  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)
3518
+ reconnectIntervalMs: toPositiveInt(options.reconnectIntervalMs ?? process.env.CONNECTION_GATEWAY_RECONNECT_INTERVAL_MS ?? "2000", "reconnect-interval-ms")
3519
+ };
3520
+ resolvedOptions.resolveReconnectOptions = async () => {
3521
+ const nextOptions = await resolveConnectGatewayOptions(options);
3522
+ if (options.connectPersistOnSuccess === true) {
3523
+ await saveConnectionKey(options.connect);
3524
+ logger.info(`已保存 FastGPT connection key 到本地配置: ${getCliConfigPath()}`);
3525
+ options.connectPersistOnSuccess = false;
3526
+ }
3527
+ resolvedOptions.onResolvedReconnectOptions?.(nextOptions);
3528
+ nextOptions.onResolvedReconnectOptions = resolvedOptions.onResolvedReconnectOptions;
3529
+ return nextOptions;
3342
3530
  };
3531
+ return resolvedOptions;
3343
3532
  }
3344
3533
  function resolveUploadDir({ entryDir, index, total, uploadDir }) {
3345
3534
  if (!uploadDir) return path.resolve(path.join(entryDir, ".fastgpt-plugin-debug/uploads"));
@@ -3719,9 +3908,6 @@ async function run(argv) {
3719
3908
 
3720
3909
  //#endregion
3721
3910
  //#region src/index.ts
3722
- process.on("SIGINT", () => {
3723
- process.exit(130);
3724
- });
3725
3911
  const main = async () => {
3726
3912
  try {
3727
3913
  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",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "publishConfig": {