ai-project-manage-cli 6.0.77 → 6.0.80

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.
Files changed (3) hide show
  1. package/README.md +22 -0
  2. package/dist/index.js +464 -54
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -33,6 +33,28 @@ apm update-skills
33
33
  apm branch <sessionId>
34
34
  ```
35
35
 
36
+ ## 常驻连接(后台守护)
37
+
38
+ ```bash
39
+ apm connect --daemon # 长期在线(后台)
40
+ apm connect --daemon -f # 长期在线,并在当前终端实时看日志
41
+ apm connect --daemon --server https://your-server.com
42
+
43
+ # 运维
44
+ apm daemon status
45
+ apm daemon logs
46
+ apm daemon logs -f # 随时重新跟踪日志
47
+ apm daemon restart
48
+ apm daemon stop
49
+ apm daemon delete
50
+
51
+ # 备选启动
52
+ apm daemon start
53
+ apm daemon start -f
54
+ ```
55
+
56
+ PM2 已内置;同一时刻仅允许一个 connect。启动时会自动检测 CLI 更新并刷新守护配置。
57
+
36
58
  `apm pull` 会自动将平台登记的**仓库项目文档**同步到 `.apm/project/`(含 `manifest.json`)。Agent 修改 `.apm/project/` 下文件后,`apm connect` 处理完消息会自动推回平台。
37
59
 
38
60
  详见仓库根目录 [docs/CLI.md](../../docs/CLI.md)。
package/dist/index.js CHANGED
@@ -2126,7 +2126,6 @@ async function runUpdateMessageStatus(options) {
2126
2126
  }
2127
2127
 
2128
2128
  // src/commands/connect.ts
2129
- import { spawnSync as spawnSync2 } from "child_process";
2130
2129
  import WebSocket from "ws";
2131
2130
 
2132
2131
  // src/ws/protocol.ts
@@ -3232,6 +3231,337 @@ function createRunSlotPool(maxConcurrent = DEFAULT_MAX_CONCURRENT) {
3232
3231
  return { acquire, release };
3233
3232
  }
3234
3233
 
3234
+ // src/commands/connect-lock.ts
3235
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync11, unlinkSync, writeFileSync as writeFileSync12 } from "fs";
3236
+ import { join as join14 } from "path";
3237
+ var CONNECT_LOCK_PATH = join14(APM_CONFIG_DIR, "connect.lock");
3238
+ function isProcessAlive(pid) {
3239
+ if (!Number.isInteger(pid) || pid <= 0) return false;
3240
+ try {
3241
+ process.kill(pid, 0);
3242
+ return true;
3243
+ } catch {
3244
+ return false;
3245
+ }
3246
+ }
3247
+ function readConnectLock() {
3248
+ if (!existsSync13(CONNECT_LOCK_PATH)) return null;
3249
+ try {
3250
+ const raw = readFileSync11(CONNECT_LOCK_PATH, "utf8");
3251
+ const parsed = JSON.parse(raw);
3252
+ if (typeof parsed.pid !== "number" || parsed.mode !== "foreground" && parsed.mode !== "pm2" || typeof parsed.startedAt !== "string") {
3253
+ return null;
3254
+ }
3255
+ return parsed;
3256
+ } catch {
3257
+ return null;
3258
+ }
3259
+ }
3260
+ function pruneStaleConnectLock() {
3261
+ const lock = readConnectLock();
3262
+ if (!lock) return;
3263
+ if (isProcessAlive(lock.pid)) return;
3264
+ try {
3265
+ unlinkSync(CONNECT_LOCK_PATH);
3266
+ } catch {
3267
+ }
3268
+ }
3269
+ function acquireConnectLock(mode) {
3270
+ pruneStaleConnectLock();
3271
+ const existing = readConnectLock();
3272
+ if (existing && isProcessAlive(existing.pid)) {
3273
+ console.error(
3274
+ `[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${existing.pid}, mode=${existing.mode})`
3275
+ );
3276
+ process.exit(1);
3277
+ }
3278
+ mkdirSync6(APM_CONFIG_DIR, { recursive: true });
3279
+ const lock = {
3280
+ pid: process.pid,
3281
+ mode,
3282
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
3283
+ };
3284
+ writeFileSync12(CONNECT_LOCK_PATH, JSON.stringify(lock, null, 2) + "\n", "utf8");
3285
+ }
3286
+ function releaseConnectLock() {
3287
+ const lock = readConnectLock();
3288
+ if (lock?.pid !== process.pid) return;
3289
+ try {
3290
+ unlinkSync(CONNECT_LOCK_PATH);
3291
+ } catch {
3292
+ }
3293
+ }
3294
+ function forceReleaseConnectLock() {
3295
+ try {
3296
+ if (existsSync13(CONNECT_LOCK_PATH)) {
3297
+ unlinkSync(CONNECT_LOCK_PATH);
3298
+ }
3299
+ } catch {
3300
+ }
3301
+ }
3302
+
3303
+ // src/commands/daemon.ts
3304
+ import { spawnSync as spawnSync2 } from "child_process";
3305
+ import { createRequire } from "node:module";
3306
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, writeFileSync as writeFileSync13 } from "fs";
3307
+ import { join as join15 } from "path";
3308
+ var PM2_APP_NAME = "apm-connect";
3309
+ var PM2_ECOSYSTEM_PATH = join15(APM_CONFIG_DIR, "connect.ecosystem.cjs");
3310
+ function resolveConnectArgs(server) {
3311
+ const args = ["connect"];
3312
+ const trimmed = server?.trim();
3313
+ if (trimmed) {
3314
+ args.push("--server", trimmed.replace(/\/+$/, ""));
3315
+ }
3316
+ return args;
3317
+ }
3318
+ function buildConnectPm2Ecosystem(options) {
3319
+ const env = {};
3320
+ const baseUrl = options.baseUrl?.trim().replace(/\/+$/, "");
3321
+ if (baseUrl) {
3322
+ env.AI_PM_SERVER = baseUrl;
3323
+ }
3324
+ return {
3325
+ apps: [
3326
+ {
3327
+ name: PM2_APP_NAME,
3328
+ script: options.apmScript,
3329
+ interpreter: options.nodePath,
3330
+ args: options.connectArgs,
3331
+ autorestart: true,
3332
+ min_uptime: "10s",
3333
+ max_restarts: 100,
3334
+ restart_delay: 3e3,
3335
+ exp_backoff_restart_delay: 1e3,
3336
+ max_memory_restart: "1G",
3337
+ env
3338
+ }
3339
+ ]
3340
+ };
3341
+ }
3342
+ function formatConnectPm2EcosystemFile(ecosystem) {
3343
+ return `module.exports = ${JSON.stringify(ecosystem, null, 2)};
3344
+ `;
3345
+ }
3346
+ function resolvePm2Bin() {
3347
+ const require2 = createRequire(import.meta.url);
3348
+ return require2.resolve("pm2/bin/pm2");
3349
+ }
3350
+ var useNpmShell2 = process.platform === "win32";
3351
+ function resolveApmEntryPath(entryArg = process.argv[1]) {
3352
+ const fromArgv = entryArg?.trim();
3353
+ if (fromArgv && existsSync14(fromArgv)) {
3354
+ return fromArgv;
3355
+ }
3356
+ const npmResult = spawnSync2(useNpmShell2 ? "npm.cmd" : "npm", ["root", "-g"], {
3357
+ encoding: "utf8",
3358
+ shell: useNpmShell2,
3359
+ stdio: ["ignore", "pipe", "pipe"]
3360
+ });
3361
+ if (npmResult.status === 0) {
3362
+ const globalRoot = npmResult.stdout?.toString().trim();
3363
+ if (globalRoot) {
3364
+ const candidate = join15(globalRoot, CLI_PACKAGE_NAME, "dist", "index.js");
3365
+ if (existsSync14(candidate)) {
3366
+ return candidate;
3367
+ }
3368
+ }
3369
+ }
3370
+ if (fromArgv) {
3371
+ return fromArgv;
3372
+ }
3373
+ console.error("[apm] \u65E0\u6CD5\u89E3\u6790 apm \u5165\u53E3\u8DEF\u5F84");
3374
+ process.exit(1);
3375
+ }
3376
+ function isRunningUnderPm2() {
3377
+ return process.env.name === PM2_APP_NAME && process.env.pm_id !== void 0;
3378
+ }
3379
+ function runPm2(args, options) {
3380
+ let pm2Bin;
3381
+ try {
3382
+ pm2Bin = resolvePm2Bin();
3383
+ } catch {
3384
+ console.error(
3385
+ "[apm] \u672A\u627E\u5230\u5185\u7F6E pm2\uFF0C\u8BF7\u91CD\u65B0\u5B89\u88C5 apm CLI\uFF1Anpm install -g ai-project-manage-cli@latest"
3386
+ );
3387
+ process.exit(1);
3388
+ }
3389
+ const spawnOptions = {
3390
+ encoding: "utf8",
3391
+ stdio: options?.inherit ? "inherit" : ["ignore", "pipe", "pipe"]
3392
+ };
3393
+ const result = spawnSync2(process.execPath, [pm2Bin, ...args], spawnOptions);
3394
+ if (result.error) {
3395
+ console.error("[apm] \u6267\u884C pm2 \u5931\u8D25:", result.error.message);
3396
+ process.exit(1);
3397
+ }
3398
+ if (result.status !== 0) {
3399
+ const stderr = result.stderr?.toString().trim();
3400
+ const stdout = result.stdout?.toString().trim();
3401
+ const detail = stderr || stdout || `exit code ${result.status}`;
3402
+ console.error(`[apm] pm2 ${args.join(" ")} \u5931\u8D25: ${detail}`);
3403
+ process.exit(result.status ?? 1);
3404
+ }
3405
+ }
3406
+ function runPm2Json(args) {
3407
+ let pm2Bin;
3408
+ try {
3409
+ pm2Bin = resolvePm2Bin();
3410
+ } catch {
3411
+ return "[]";
3412
+ }
3413
+ const result = spawnSync2(process.execPath, [pm2Bin, ...args], {
3414
+ encoding: "utf8",
3415
+ stdio: ["ignore", "pipe", "pipe"]
3416
+ });
3417
+ if (result.status !== 0) return "[]";
3418
+ return result.stdout?.toString() ?? "[]";
3419
+ }
3420
+ function isPm2ConnectOnline() {
3421
+ try {
3422
+ const raw = runPm2Json(["jlist"]);
3423
+ const list = JSON.parse(raw);
3424
+ return list.some(
3425
+ (app) => app.name === PM2_APP_NAME && (app.pm2_env?.status === "online" || app.pm2_env?.status === "launching")
3426
+ );
3427
+ } catch {
3428
+ return false;
3429
+ }
3430
+ }
3431
+ function assertConnectNotRunning() {
3432
+ pruneStaleConnectLock();
3433
+ const lock = readConnectLock();
3434
+ if (lock && isProcessAlive(lock.pid)) {
3435
+ console.error(
3436
+ `[apm] \u5DF2\u6709 apm connect \u5728\u8FD0\u884C (pid=${lock.pid}, mode=${lock.mode})\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u542F\u52A8`
3437
+ );
3438
+ process.exit(1);
3439
+ }
3440
+ if (isPm2ConnectOnline()) {
3441
+ console.error(
3442
+ "[apm] \u5DF2\u6709 apm connect \u5B88\u62A4\u8FDB\u7A0B\u5728\u8FD0\u884C\uFF0C\u8BF7\u5148\u6267\u884C apm daemon stop"
3443
+ );
3444
+ process.exit(1);
3445
+ }
3446
+ }
3447
+ function resolveRuntimePaths() {
3448
+ return {
3449
+ apmScript: resolveApmEntryPath(),
3450
+ nodePath: process.execPath
3451
+ };
3452
+ }
3453
+ function reexecConnectWithResolvedPath(options) {
3454
+ const apmScript = resolveApmEntryPath();
3455
+ const args = [apmScript, "connect"];
3456
+ const server = options.server?.trim();
3457
+ if (server) {
3458
+ args.push("--server", server);
3459
+ }
3460
+ const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
3461
+ if (result.error) {
3462
+ console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
3463
+ process.exit(1);
3464
+ }
3465
+ process.exit(result.status ?? 0);
3466
+ }
3467
+ async function handleConnectAfterUpdate(options) {
3468
+ console.log("[apm] \u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u4EE5\u65B0\u7248\u672C\u91CD\u65B0\u8FDE\u63A5\u2026");
3469
+ await prepareEcosystem(options.server);
3470
+ if (isRunningUnderPm2()) {
3471
+ runPm2(["startOrRestart", PM2_ECOSYSTEM_PATH, "--update-env"], {
3472
+ inherit: true
3473
+ });
3474
+ console.log("[apm] \u5DF2\u901A\u77E5 PM2 \u4F7F\u7528\u65B0\u7248\u672C\u91CD\u542F connect");
3475
+ process.exit(0);
3476
+ }
3477
+ reexecConnectWithResolvedPath(options);
3478
+ }
3479
+ async function resolveBaseUrl(server) {
3480
+ if (server?.trim()) {
3481
+ return server.trim().replace(/\/+$/, "");
3482
+ }
3483
+ const cfg = await tryReadApmConfig();
3484
+ return cfg?.baseUrl;
3485
+ }
3486
+ function writeEcosystemFile(options) {
3487
+ mkdirSync7(APM_CONFIG_DIR, { recursive: true });
3488
+ const ecosystem = buildConnectPm2Ecosystem(options);
3489
+ writeFileSync13(
3490
+ PM2_ECOSYSTEM_PATH,
3491
+ formatConnectPm2EcosystemFile(ecosystem),
3492
+ "utf8"
3493
+ );
3494
+ }
3495
+ async function prepareEcosystem(server) {
3496
+ await ensureLoggedConfig();
3497
+ const cfg = await ensureApmConfig();
3498
+ const { apmScript, nodePath } = resolveRuntimePaths();
3499
+ const connectArgs = resolveConnectArgs(server);
3500
+ const baseUrl = await resolveBaseUrl(server);
3501
+ writeEcosystemFile({
3502
+ apmScript,
3503
+ nodePath,
3504
+ connectArgs,
3505
+ baseUrl
3506
+ });
3507
+ return cfg;
3508
+ }
3509
+ async function runDaemonStart(options) {
3510
+ assertConnectNotRunning();
3511
+ await runUpdate();
3512
+ const cfg = await prepareEcosystem(options.server);
3513
+ runPm2(["start", PM2_ECOSYSTEM_PATH, "--update-env"], { inherit: true });
3514
+ console.log(
3515
+ `[apm] ${PM2_APP_NAME} \u5DF2\u7531 PM2 \u542F\u52A8\uFF08server=${options.server?.trim() || cfg.baseUrl}\uFF09`
3516
+ );
3517
+ if (options.follow) {
3518
+ console.log(
3519
+ "[apm] \u6B63\u5728\u8DDF\u8E2A\u65E5\u5FD7\uFF08Ctrl+C \u4EC5\u9000\u51FA\u65E5\u5FD7\u67E5\u770B\uFF0C\u5B88\u62A4\u8FDB\u7A0B\u7EE7\u7EED\u8FD0\u884C\uFF09"
3520
+ );
3521
+ runDaemonLogs({ follow: true });
3522
+ return;
3523
+ }
3524
+ console.log("[apm] \u67E5\u770B\u72B6\u6001: apm daemon status");
3525
+ console.log("[apm] \u67E5\u770B\u65E5\u5FD7: apm daemon logs -f");
3526
+ console.log("[apm] \u505C\u6B62: apm daemon stop");
3527
+ }
3528
+ async function runDaemonStop() {
3529
+ runPm2(["stop", PM2_APP_NAME], { inherit: true });
3530
+ forceReleaseConnectLock();
3531
+ console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u505C\u6B62`);
3532
+ }
3533
+ async function runDaemonRestart(options) {
3534
+ pruneStaleConnectLock();
3535
+ const lock = readConnectLock();
3536
+ if (lock && isProcessAlive(lock.pid) && !isPm2ConnectOnline()) {
3537
+ console.error(
3538
+ `[apm] \u5DF2\u6709\u524D\u53F0 apm connect \u5728\u8FD0\u884C (pid=${lock.pid})\uFF0C\u8BF7\u5148\u505C\u6B62\u540E\u518D\u542F\u52A8\u5B88\u62A4\u8FDB\u7A0B`
3539
+ );
3540
+ process.exit(1);
3541
+ }
3542
+ await runUpdate();
3543
+ await prepareEcosystem(options.server);
3544
+ runPm2(["startOrRestart", PM2_ECOSYSTEM_PATH, "--update-env"], {
3545
+ inherit: true
3546
+ });
3547
+ console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u91CD\u542F`);
3548
+ }
3549
+ async function runDaemonDelete() {
3550
+ runPm2(["delete", PM2_APP_NAME], { inherit: true });
3551
+ forceReleaseConnectLock();
3552
+ console.log(`[apm] ${PM2_APP_NAME} \u5DF2\u4ECE PM2 \u79FB\u9664`);
3553
+ }
3554
+ async function runDaemonStatus() {
3555
+ runPm2(["describe", PM2_APP_NAME], { inherit: true });
3556
+ }
3557
+ async function runDaemonLogs(options) {
3558
+ const args = ["logs", PM2_APP_NAME, "--lines", String(options.lines ?? 100)];
3559
+ if (!options.follow) {
3560
+ args.push("--nostream");
3561
+ }
3562
+ runPm2(args, { inherit: true });
3563
+ }
3564
+
3235
3565
  // src/commands/connect.ts
3236
3566
  var HEARTBEAT_MS = 3e4;
3237
3567
  async function updateMessageStatus(cfg, messageId, status) {
@@ -3401,23 +3731,10 @@ function startHeartbeat(ws, clientMachineId) {
3401
3731
  const timer = setInterval(send, HEARTBEAT_MS);
3402
3732
  return () => clearInterval(timer);
3403
3733
  }
3404
- function reexecConnect(options) {
3405
- const args = [process.argv[1], "connect"];
3406
- const server = options.server?.trim();
3407
- if (server) {
3408
- args.push("--server", server);
3409
- }
3410
- const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
3411
- if (result.error) {
3412
- console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
3413
- process.exit(1);
3414
- }
3415
- process.exit(result.status ?? 0);
3416
- }
3417
3734
  async function runConnect(options) {
3418
3735
  const { didUpdate } = await runUpdate();
3419
3736
  if (didUpdate) {
3420
- reexecConnect(options);
3737
+ await handleConnectAfterUpdate(options);
3421
3738
  }
3422
3739
  const cfg = await ensureLoggedConfig();
3423
3740
  if (options.server?.trim()) {
@@ -3428,6 +3745,9 @@ async function runConnect(options) {
3428
3745
  console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
3429
3746
  process.exit(1);
3430
3747
  }
3748
+ assertConnectNotRunning();
3749
+ acquireConnectLock("foreground");
3750
+ process.on("exit", releaseConnectLock);
3431
3751
  const url = buildAgentWsUrl(cfg.baseUrl, resolveApiKey(cfg));
3432
3752
  console.log(`[apm] \u8FDE\u63A5 ${cfg.baseUrl} \u2026`);
3433
3753
  await new Promise((resolve5, reject) => {
@@ -3459,6 +3779,7 @@ async function runConnect(options) {
3459
3779
  ]);
3460
3780
  } catch {
3461
3781
  }
3782
+ releaseConnectLock();
3462
3783
  resolve5();
3463
3784
  process.exit(code);
3464
3785
  };
@@ -3615,20 +3936,20 @@ async function runCreatePr(options) {
3615
3936
  import { spawnSync as spawnSync5 } from "node:child_process";
3616
3937
 
3617
3938
  // src/commands/deploy/internal/apm-config.ts
3618
- import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
3939
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
3619
3940
  import { homedir as homedir2 } from "node:os";
3620
- import { join as join14, resolve as resolve4 } from "node:path";
3941
+ import { join as join16, resolve as resolve4 } from "node:path";
3621
3942
  function loadApmConfig(options) {
3622
3943
  const p = resolve4(
3623
3944
  process.cwd(),
3624
3945
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3625
3946
  );
3626
- if (!existsSync13(p)) {
3947
+ if (!existsSync15(p)) {
3627
3948
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3628
3949
  process.exit(1);
3629
3950
  }
3630
3951
  try {
3631
- const raw = readFileSync11(p, "utf8");
3952
+ const raw = readFileSync12(p, "utf8");
3632
3953
  return JSON.parse(raw);
3633
3954
  } catch (e) {
3634
3955
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -3812,12 +4133,12 @@ function readMavenLocalRepoFromEnv(env = process.env) {
3812
4133
  return null;
3813
4134
  }
3814
4135
  function readMavenLocalRepoFromSettings() {
3815
- const settingsPath = join14(homedir2(), ".m2", "settings.xml");
3816
- if (!existsSync13(settingsPath)) {
4136
+ const settingsPath = join16(homedir2(), ".m2", "settings.xml");
4137
+ if (!existsSync15(settingsPath)) {
3817
4138
  return null;
3818
4139
  }
3819
4140
  try {
3820
- const xml = readFileSync11(settingsPath, "utf8");
4141
+ const xml = readFileSync12(settingsPath, "utf8");
3821
4142
  const match = xml.match(
3822
4143
  /<localRepository>\s*([^<]+?)\s*<\/localRepository>/
3823
4144
  );
@@ -3848,11 +4169,24 @@ function resolveMavenLocalRepoWithSource() {
3848
4169
  };
3849
4170
  }
3850
4171
  return {
3851
- path: join14(homedir2(), ".m2", "repository"),
4172
+ path: join16(homedir2(), ".m2", "repository"),
3852
4173
  source: "default",
3853
4174
  sourceDetail: "~/.m2/repository"
3854
4175
  };
3855
4176
  }
4177
+ function resolveWisdomDeployMode(raw) {
4178
+ if (raw === void 0 || raw === null || raw === "") {
4179
+ return "default";
4180
+ }
4181
+ const mode = String(raw).trim().toLowerCase();
4182
+ if (mode === "default" || mode === "full") {
4183
+ return mode;
4184
+ }
4185
+ console.error(
4186
+ `apm.config.json \u4E2D wisdomDeploy.mode \u53EA\u80FD\u4E3A default \u6216 full\uFF0C\u5F53\u524D\u503C: ${raw}`
4187
+ );
4188
+ process.exit(1);
4189
+ }
3856
4190
  function resolveWisdomBackendDeployFromApmConfig(cfg) {
3857
4191
  const projectName = reqTopLevelName(cfg);
3858
4192
  const w = cfg.wisdomDeploy ?? {};
@@ -3886,7 +4220,8 @@ function resolveWisdomBackendDeployFromApmConfig(cfg) {
3886
4220
  mavenLocalRepoSourceDetail: mavenLocalRepo.sourceDetail,
3887
4221
  healthCheckPort: healthPort,
3888
4222
  healthCheckContext: reqHc(h.context, "context").trim(),
3889
- healthCheckTimeout: healthTimeout
4223
+ healthCheckTimeout: healthTimeout,
4224
+ mode: resolveWisdomDeployMode(w.mode)
3890
4225
  };
3891
4226
  }
3892
4227
  function posixDirname(p) {
@@ -3904,18 +4239,18 @@ function posixBasename(p) {
3904
4239
  }
3905
4240
 
3906
4241
  // src/commands/deploy/internal/wisdom-auto-deploy.ts
3907
- import { existsSync as existsSync15, readFileSync as readFileSync13 } from "node:fs";
4242
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
3908
4243
  import path3 from "node:path";
3909
4244
  import { spawnSync as spawnSync4 } from "node:child_process";
3910
4245
 
3911
4246
  // src/commands/deploy/internal/wisdom-backend-deploy.ts
3912
4247
  import {
3913
- existsSync as existsSync14,
3914
- mkdirSync as mkdirSync6,
4248
+ existsSync as existsSync16,
4249
+ mkdirSync as mkdirSync8,
3915
4250
  readdirSync as readdirSync5,
3916
- readFileSync as readFileSync12,
4251
+ readFileSync as readFileSync13,
3917
4252
  statSync as statSync5,
3918
- writeFileSync as writeFileSync12
4253
+ writeFileSync as writeFileSync14
3919
4254
  } from "node:fs";
3920
4255
  import { spawnSync as spawnSync3 } from "node:child_process";
3921
4256
  import path2 from "node:path";
@@ -4122,15 +4457,15 @@ function fileSignature(filePath) {
4122
4457
  }
4123
4458
  function loadManifest4() {
4124
4459
  const manifestPath2 = manifestFilePath();
4125
- if (!existsSync14(manifestPath2)) {
4460
+ if (!existsSync16(manifestPath2)) {
4126
4461
  return {};
4127
4462
  }
4128
- return JSON.parse(readFileSync12(manifestPath2, "utf8"));
4463
+ return JSON.parse(readFileSync13(manifestPath2, "utf8"));
4129
4464
  }
4130
4465
  function saveManifest4(manifest) {
4131
4466
  const dir = deployCacheDir();
4132
- mkdirSync6(dir, { recursive: true });
4133
- writeFileSync12(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
4467
+ mkdirSync8(dir, { recursive: true });
4468
+ writeFileSync14(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
4134
4469
  }
4135
4470
  function isProjectLibJar(jarName) {
4136
4471
  return jarName.startsWith("jeecg-");
@@ -4186,12 +4521,12 @@ function updateManifestEntries(manifest, entries, projectRoot) {
4186
4521
  }
4187
4522
  async function createUpdatePackage(entries, packageName) {
4188
4523
  const dir = deployCacheDir();
4189
- mkdirSync6(dir, { recursive: true });
4524
+ mkdirSync8(dir, { recursive: true });
4190
4525
  const zipPath = path2.join(dir, packageName);
4191
4526
  log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4192
4527
  const zip = new JSZip2();
4193
4528
  for (const entry of entries) {
4194
- const content = readFileSync12(entry.path);
4529
+ const content = readFileSync13(entry.path);
4195
4530
  zip.file(entry.arcname, content);
4196
4531
  log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4197
4532
  }
@@ -4200,7 +4535,7 @@ async function createUpdatePackage(entries, packageName) {
4200
4535
  compression: "DEFLATE",
4201
4536
  compressionOptions: { level: 6 }
4202
4537
  });
4203
- writeFileSync12(zipPath, buffer);
4538
+ writeFileSync14(zipPath, buffer);
4204
4539
  return zipPath;
4205
4540
  }
4206
4541
  function getMvnExecutable() {
@@ -4249,11 +4584,11 @@ function runMavenBuild(projectRoot, mavenLocalRepo, repoSource) {
4249
4584
  }
4250
4585
  function locateLibDir(projectRoot) {
4251
4586
  const targetDir = getTargetDir(projectRoot);
4252
- if (!existsSync14(targetDir)) {
4587
+ if (!existsSync16(targetDir)) {
4253
4588
  fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4254
4589
  }
4255
4590
  const libDir = path2.join(targetDir, "lib");
4256
- if (!existsSync14(libDir) || !statSync5(libDir).isDirectory()) {
4591
+ if (!existsSync16(libDir) || !statSync5(libDir).isDirectory()) {
4257
4592
  fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4258
4593
  }
4259
4594
  const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
@@ -4263,6 +4598,19 @@ function locateLibDir(projectRoot) {
4263
4598
  log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4264
4599
  return libDir;
4265
4600
  }
4601
+ function locateMainJar(projectRoot) {
4602
+ const targetDir = getTargetDir(projectRoot);
4603
+ if (!existsSync16(targetDir)) {
4604
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4605
+ }
4606
+ const jarFiles = readdirSync5(targetDir).filter((name) => name.endsWith(".jar") && !name.endsWith(".jar.original")).map((name) => path2.join(targetDir, name)).sort((a, b) => statSync5(b).mtimeMs - statSync5(a).mtimeMs);
4607
+ if (jarFiles.length === 0) {
4608
+ fail(`target \u76EE\u5F55\u4E0B\u6CA1\u6709\u4E3B JAR: ${targetDir}`);
4609
+ }
4610
+ const mainJar = jarFiles[0];
4611
+ log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path2.basename(mainJar)}`);
4612
+ return mainJar;
4613
+ }
4266
4614
  async function connectSsh(config) {
4267
4615
  const client = new Client();
4268
4616
  log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
@@ -4319,6 +4667,18 @@ async function uploadUpdatePackage(sftp, zipPath, config) {
4319
4667
  }
4320
4668
  return remotePath;
4321
4669
  }
4670
+ async function uploadFullJar(sftp, localJarPath, config) {
4671
+ const remoteDir = config.remoteAppDir.replace(/\/$/, "");
4672
+ const remotePath = `${remoteDir}/${config.startupJar}`;
4673
+ log(`\u4E0A\u4F20\u5168\u91CF JAR (${path2.basename(localJarPath)}) -> ${remotePath}`);
4674
+ try {
4675
+ await sftp.mkdir(remoteDir, true);
4676
+ await sftp.fastPut(localJarPath, remotePath);
4677
+ log("\u5168\u91CF JAR \u4E0A\u4F20\u6210\u529F");
4678
+ } catch (err) {
4679
+ fail(`\u5168\u91CF JAR \u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
4680
+ }
4681
+ }
4322
4682
  async function runRemoteCommand(client, command, options) {
4323
4683
  const check = options?.check ?? true;
4324
4684
  const stream = options?.stream ?? false;
@@ -4574,14 +4934,26 @@ async function runWisdomBackendDeploy(options) {
4574
4934
  log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
4575
4935
  log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
4576
4936
  log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4937
+ log(
4938
+ `\u90E8\u7F72\u6A21\u5F0F: ${config.mode}${config.mode === "full" ? "\uFF08\u5168\u91CF JAR\uFF0C\u8DF3\u8FC7 lib \u68C0\u67E5\uFF09" : "\uFF08\u589E\u91CF lib \u66F4\u65B0\uFF09"}`
4939
+ );
4577
4940
  runMavenBuild(projectRoot, config.mavenLocalRepo, {
4578
4941
  source: config.mavenLocalRepoSource,
4579
4942
  sourceDetail: config.mavenLocalRepoSourceDetail
4580
4943
  });
4581
- const libDir = locateLibDir(projectRoot);
4582
- let manifest = loadManifest4();
4583
4944
  const conn = await connectSsh(config);
4584
4945
  try {
4946
+ if (config.mode === "full") {
4947
+ const mainJar = locateMainJar(projectRoot);
4948
+ await uploadFullJar(conn.sftp, mainJar, config);
4949
+ log("\u91CD\u542F\u670D\u52A1...");
4950
+ await restartRemoteService(conn.client, config);
4951
+ await healthCheckService(conn.client, config);
4952
+ log("\u90E8\u7F72\u5B8C\u6210");
4953
+ return;
4954
+ }
4955
+ const libDir = locateLibDir(projectRoot);
4956
+ let manifest = loadManifest4();
4585
4957
  const remoteLibStats = await getRemoteFileStats(
4586
4958
  conn.sftp,
4587
4959
  config.remoteLibDir
@@ -4647,15 +5019,15 @@ function isWisdomLegacyDeploy(cfg) {
4647
5019
  return !hasNewFrontend && !hasNewBackend;
4648
5020
  }
4649
5021
  function detectWisdomProjectType(cwd) {
4650
- return existsSync15(path3.join(cwd, "package.json")) ? "frontend" : "backend";
5022
+ return existsSync17(path3.join(cwd, "package.json")) ? "frontend" : "backend";
4651
5023
  }
4652
5024
  function readPackageScripts(cwd) {
4653
5025
  const pkgPath = path3.join(cwd, "package.json");
4654
- if (!existsSync15(pkgPath)) {
5026
+ if (!existsSync17(pkgPath)) {
4655
5027
  return {};
4656
5028
  }
4657
5029
  try {
4658
- const raw = readFileSync13(pkgPath, "utf8");
5030
+ const raw = readFileSync14(pkgPath, "utf8");
4659
5031
  const parsed = JSON.parse(raw);
4660
5032
  return parsed.scripts ?? {};
4661
5033
  } catch {
@@ -4765,7 +5137,7 @@ import path7 from "node:path";
4765
5137
  import Docker from "dockerode";
4766
5138
 
4767
5139
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
4768
- import { existsSync as existsSync16, readFileSync as readFileSync14 } from "node:fs";
5140
+ import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
4769
5141
  import path4 from "node:path";
4770
5142
  function asOptionalTlsBuffer(value) {
4771
5143
  if (typeof value !== "string") {
@@ -4777,8 +5149,8 @@ function asOptionalTlsBuffer(value) {
4777
5149
  if (normalized === "") {
4778
5150
  return void 0;
4779
5151
  }
4780
- if (existsSync16(normalized)) {
4781
- return readFileSync14(normalized);
5152
+ if (existsSync18(normalized)) {
5153
+ return readFileSync15(normalized);
4782
5154
  }
4783
5155
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
4784
5156
  if (looksLikePath) {
@@ -4988,7 +5360,7 @@ var DockerodeClient = class {
4988
5360
  var createDockerodeClient = (config) => new DockerodeClient(config);
4989
5361
 
4990
5362
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
4991
- import { existsSync as existsSync17, readFileSync as readFileSync15, statSync as statSync6 } from "node:fs";
5363
+ import { existsSync as existsSync19, readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
4992
5364
  import path5 from "node:path";
4993
5365
  function stripSurroundingQuotes(value) {
4994
5366
  const t = value.trim();
@@ -5005,10 +5377,10 @@ function loadEnvFromFile(envFilePath) {
5005
5377
  return {};
5006
5378
  }
5007
5379
  const targetPath = path5.resolve(envFilePath);
5008
- if (!existsSync17(targetPath) || !statSync6(targetPath).isFile()) {
5380
+ if (!existsSync19(targetPath) || !statSync6(targetPath).isFile()) {
5009
5381
  return {};
5010
5382
  }
5011
- const raw = readFileSync15(targetPath, "utf-8");
5383
+ const raw = readFileSync16(targetPath, "utf-8");
5012
5384
  const result = {};
5013
5385
  for (const line of raw.split(/\r?\n/)) {
5014
5386
  const normalized = line.trim();
@@ -5179,12 +5551,12 @@ function dockerPushImage(params, cwd) {
5179
5551
  }
5180
5552
 
5181
5553
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
5182
- import { existsSync as existsSync18 } from "node:fs";
5554
+ import { existsSync as existsSync20 } from "node:fs";
5183
5555
  import path6 from "node:path";
5184
5556
  function resolveDockerBuildPaths(cwd) {
5185
5557
  const dockerfilePath = path6.join(cwd, "Dockerfile");
5186
5558
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
5187
- if (!existsSync18(dockerfilePath)) {
5559
+ if (!existsSync20(dockerfilePath)) {
5188
5560
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
5189
5561
  }
5190
5562
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -5648,7 +6020,7 @@ function registerDeploySftpCommands(program) {
5648
6020
  // src/commands/deploy/wisdom-backend.ts
5649
6021
  function registerDeployWisdomBackendCommands(program) {
5650
6022
  program.command("deploy-wisdom-backend").description(
5651
- "Maven \u6784\u5EFA\u540E\u901A\u8FC7 SFTP \u589E\u91CF\u66F4\u65B0\u8FDC\u7A0B lib \u76EE\u5F55\u5E76\u91CD\u542F Spring Boot \u670D\u52A1\uFF08\u914D\u7F6E\u89C1 apm.config.json \u7684 wisdomDeploy \u4E0E healthCheck\uFF09"
6023
+ "Maven \u6784\u5EFA\u540E\u90E8\u7F72 Spring Boot \u670D\u52A1\uFF08\u914D\u7F6E\u89C1 apm.config.json \u7684 wisdomDeploy \u4E0E healthCheck\uFF1Bmode=default \u589E\u91CF\u66F4\u65B0 lib\uFF0Cmode=full \u53D1\u5E03\u5168\u91CF JAR\uFF09"
5652
6024
  ).option(
5653
6025
  "--config <path>",
5654
6026
  "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
@@ -5734,8 +6106,46 @@ function buildProgram() {
5734
6106
  });
5735
6107
  program.command("connect").description(
5736
6108
  "\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C message\uFF08TYPING \u2192 Cursor \u2192 SUCCESS/FAILED\uFF09\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u5F53\u524D\u5927\u7248\u672C\u6700\u65B0\u7248"
5737
- ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
5738
- await runConnect(opts);
6109
+ ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").option(
6110
+ "--daemon",
6111
+ "\u540E\u53F0\u5B88\u62A4\u8FD0\u884C\uFF08\u5185\u7F6E PM2\uFF0C\u65AD\u7EBF\u6216\u9000\u51FA\u540E\u81EA\u52A8\u91CD\u542F\uFF1B\u63A8\u8350\u7528\u4E8E\u957F\u671F\u5728\u7EBF\uFF09"
6112
+ ).option("-f, --follow", "\u4E0E --daemon \u5408\u7528\uFF1A\u542F\u52A8\u540E\u5728\u5F53\u524D\u7EC8\u7AEF\u5B9E\u65F6\u8DDF\u8E2A\u65E5\u5FD7").action(
6113
+ async (opts) => {
6114
+ if (opts.follow && !opts.daemon) {
6115
+ console.error("[apm] --follow \u4EC5\u53EF\u4E0E --daemon \u540C\u65F6\u4F7F\u7528");
6116
+ process.exit(1);
6117
+ }
6118
+ if (opts.daemon === true) {
6119
+ await runDaemonStart({ server: opts.server, follow: opts.follow });
6120
+ return;
6121
+ }
6122
+ await runConnect({ server: opts.server });
6123
+ }
6124
+ );
6125
+ const daemon = program.command("daemon").description(
6126
+ "\u540E\u53F0 connect \u8FD0\u7EF4\u547D\u4EE4\uFF08\u542F\u52A8\u8BF7\u4F18\u5148\u4F7F\u7528 apm connect --daemon\uFF1B\u6B64\u5904\u4F9B status/logs/stop \u7B49\uFF09"
6127
+ );
6128
+ daemon.command("start").description("\u542F\u52A8\u540E\u53F0 connect\uFF08\u7B49\u6548 apm connect --daemon\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").option("-f, --follow", "\u542F\u52A8\u540E\u5728\u5F53\u524D\u7EC8\u7AEF\u5B9E\u65F6\u8DDF\u8E2A\u65E5\u5FD7").action(async (opts) => {
6129
+ await runDaemonStart({ server: opts.server, follow: opts.follow });
6130
+ });
6131
+ daemon.command("stop").description("\u505C\u6B62 PM2 \u5B88\u62A4\u7684 apm connect").action(async () => {
6132
+ await runDaemonStop();
6133
+ });
6134
+ daemon.command("restart").description("\u91CD\u542F PM2 \u5B88\u62A4\u7684 apm connect\uFF08\u672A\u542F\u52A8\u65F6\u4F1A\u76F4\u63A5\u542F\u52A8\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
6135
+ await runDaemonRestart(opts);
6136
+ });
6137
+ daemon.command("delete").description("\u4ECE PM2 \u4E2D\u79FB\u9664 apm connect \u8FDB\u7A0B").action(async () => {
6138
+ await runDaemonDelete();
6139
+ });
6140
+ daemon.command("status").description("\u67E5\u770B PM2 \u5B88\u62A4\u7684 apm connect \u72B6\u6001").action(async () => {
6141
+ await runDaemonStatus();
6142
+ });
6143
+ daemon.command("logs").description("\u67E5\u770B PM2 \u5B88\u62A4\u7684 apm connect \u65E5\u5FD7").option("-f, --follow", "\u6301\u7EED\u8DDF\u8E2A\u65E5\u5FD7").option("-n, --lines <n>", "\u663E\u793A\u884C\u6570", "100").action(async (opts) => {
6144
+ const lines = Number.parseInt(opts.lines ?? "100", 10);
6145
+ await runDaemonLogs({
6146
+ follow: opts.follow === true,
6147
+ lines: Number.isFinite(lines) && lines > 0 ? lines : 100
6148
+ });
5739
6149
  });
5740
6150
  program.command("branch").description("\u5207\u6362\u6216\u521B\u5EFA\u4F1A\u8BDD\u5206\u652F feat/session-<sessionId>").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
5741
6151
  "-m, --message <text>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.77",
3
+ "version": "6.0.80",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -44,6 +44,7 @@
44
44
  "dockerode": "~5.0.0",
45
45
  "ssh2": "~1.16.0",
46
46
  "ssh2-sftp-client": "~12.0.1",
47
- "jszip": "~3.10.1"
47
+ "jszip": "~3.10.1",
48
+ "pm2": "~5.4.3"
48
49
  }
49
50
  }