ai-project-manage-cli 6.0.50 → 6.0.52

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 (2) hide show
  1. package/dist/index.js +264 -22
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -81,10 +81,17 @@ function buildAgentWsUrl(httpBase, apiKey) {
81
81
 
82
82
  // src/commands/init.ts
83
83
  import { join as join4 } from "path";
84
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
84
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
85
85
 
86
86
  // src/command-utils.ts
87
- import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
87
+ import {
88
+ copyFileSync,
89
+ existsSync,
90
+ mkdirSync as mkdirSync2,
91
+ readFileSync as readFileSync2,
92
+ readdirSync,
93
+ statSync
94
+ } from "fs";
88
95
  import { basename, dirname, extname, join as join2, resolve as resolve2 } from "path";
89
96
  import { fileURLToPath } from "url";
90
97
 
@@ -141,6 +148,56 @@ var CLI_TEMPLATE_DIR = resolve2(__dirname, "../template");
141
148
  function workspaceApmDir(cwd = resolveWorkdirPath()) {
142
149
  return resolve2(resolve2(cwd), ".apm");
143
150
  }
151
+ function assertWorkspaceApmDirExists(workdir) {
152
+ const apmDir = workspaceApmDir(workdir);
153
+ const fsApmDir = toFsPath(apmDir);
154
+ if (!existsSync(fsApmDir)) {
155
+ throw new Error(
156
+ `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u672A\u68C0\u6D4B\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5728\u8BE5\u4ED3\u5E93\u6839\u76EE\u5F55\u6267\u884C apm init \u5B8C\u6210\u63A5\u5165\u3002`
157
+ );
158
+ }
159
+ const st = statSync(fsApmDir);
160
+ if (!st.isDirectory()) {
161
+ throw new Error(
162
+ `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u4E0B\u7684 .apm \u4E0D\u662F\u76EE\u5F55\uFF0C\u8BF7\u68C0\u67E5\u672C\u5730\u63A5\u5165\u72B6\u6001\u3002`
163
+ );
164
+ }
165
+ }
166
+ var APM_GITIGNORE_PATTERNS = [
167
+ /^\.apm\/?$/,
168
+ /^\.apm\/\*\*$/,
169
+ /^\*\*\/\.apm\/?$/,
170
+ /^\*\*\/\.apm\/\*\*$/,
171
+ /^\/\.apm\/?$/,
172
+ /^\/\.apm\/\*\*$/
173
+ ];
174
+ function normalizeGitignorePattern(line) {
175
+ const trimmed = line.trim();
176
+ if (!trimmed || trimmed.startsWith("#")) return "";
177
+ if (trimmed.startsWith("!")) return "";
178
+ const hashIndex = trimmed.indexOf("#");
179
+ return (hashIndex >= 0 ? trimmed.slice(0, hashIndex) : trimmed).trim();
180
+ }
181
+ function gitignoreIgnoresApm(line) {
182
+ const pattern = normalizeGitignorePattern(line);
183
+ if (!pattern) return false;
184
+ return APM_GITIGNORE_PATTERNS.some((re) => re.test(pattern));
185
+ }
186
+ function assertApmGitignoredInRepo(workdir) {
187
+ const gitignorePath = join2(workdir, ".gitignore");
188
+ const fsGitignorePath = toFsPath(gitignorePath);
189
+ if (!existsSync(fsGitignorePath)) {
190
+ throw new Error(
191
+ `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7F3A\u5C11 .gitignore \u6587\u4EF6\uFF0C\u8BF7\u6DFB\u52A0\u5BF9 .apm \u7684\u5FFD\u7565\u89C4\u5219\uFF08\u4F8B\u5982 **/.apm/**\uFF09\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
192
+ );
193
+ }
194
+ const content = readFileSync2(fsGitignorePath, "utf8");
195
+ if (!content.split(/\r?\n/).some(gitignoreIgnoresApm)) {
196
+ throw new Error(
197
+ `\u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u7684 .gitignore \u672A\u5FFD\u7565 .apm\uFF0C\u8BF7\u6DFB\u52A0 **/.apm/** \u6216 .apm/\uFF0C\u907F\u514D\u672C\u5730\u4F1A\u8BDD\u4E0E\u90E8\u7F72\u51ED\u636E\u88AB\u63D0\u4EA4\u3002`
198
+ );
199
+ }
200
+ }
144
201
  var SESSIONS_SUBDIR = "sessions";
145
202
  var SESSION_DOCS_SUBDIR = "docs";
146
203
  var SESSION_ATTACHMENTS_SUBDIR = "attachments";
@@ -350,6 +407,10 @@ var requestConfig = {
350
407
  method: "GET",
351
408
  path: "/cli/tasks/branch-baseline"
352
409
  }),
410
+ listSessionsForBranchCleanup: defineEndpoint({
411
+ method: "GET",
412
+ path: "/cli/sessions/branch-cleanup"
413
+ }),
353
414
  workspaceBaseline: defineEndpoint({
354
415
  method: "GET",
355
416
  path: "/cli/workspaces/baseline"
@@ -492,7 +553,7 @@ async function runInit(name) {
492
553
  const trimmedName = name?.trim();
493
554
  if (trimmedName) {
494
555
  const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
495
- const config = readFileSync2(apmConfigPath, "utf8");
556
+ const config = readFileSync3(apmConfigPath, "utf8");
496
557
  const configJson = JSON.parse(config);
497
558
  configJson.name = trimmedName;
498
559
  writeFileSync3(
@@ -577,6 +638,7 @@ async function runLogin(opts) {
577
638
  import { execFile as execFile2 } from "child_process";
578
639
  import { promisify as promisify2 } from "util";
579
640
  var execFileAsync2 = promisify2(execFile2);
641
+ var SESSION_BRANCH_PREFIX = "feat/session-";
580
642
  function branchNameForSession(sessionId) {
581
643
  const id = sessionId.trim();
582
644
  if (!id) {
@@ -587,7 +649,15 @@ function branchNameForSession(sessionId) {
587
649
  "[apm] \u4F1A\u8BDD ID \u4E0D\u80FD\u5305\u542B\u7A7A\u767D\u6216\u8DEF\u5F84\u5206\u9694\u7B26\uFF0C\u8BF7\u4F7F\u7528\u5B57\u6BCD\u3001\u6570\u5B57\u3001._- \u7B49"
588
650
  );
589
651
  }
590
- return `feat/session-${id}`;
652
+ return `${SESSION_BRANCH_PREFIX}${id}`;
653
+ }
654
+ function sessionIdFromBranchName(branch) {
655
+ const name = branch.trim().replace(/^origin\//, "");
656
+ if (!name.startsWith(SESSION_BRANCH_PREFIX)) {
657
+ return null;
658
+ }
659
+ const sessionId = name.slice(SESSION_BRANCH_PREFIX.length).trim();
660
+ return sessionId || null;
591
661
  }
592
662
  async function execGit(cwd, args, quiet) {
593
663
  try {
@@ -740,6 +810,171 @@ async function runBranch(sessionId, options = {}) {
740
810
  return ensureFeatureBranch(branch, baselineBranch, options);
741
811
  }
742
812
 
813
+ // src/commands/clean-branches.ts
814
+ import { execFile as execFile3 } from "child_process";
815
+ import { promisify as promisify3 } from "util";
816
+ var execFileAsync3 = promisify3(execFile3);
817
+ async function execGit2(cwd, args, quiet) {
818
+ try {
819
+ const { stdout, stderr } = await execFileAsync3("git", args, {
820
+ cwd,
821
+ encoding: "utf8",
822
+ maxBuffer: 10 * 1024 * 1024
823
+ });
824
+ if (!quiet && stderr.trim()) {
825
+ process.stderr.write(stderr);
826
+ }
827
+ return stdout;
828
+ } catch (err) {
829
+ const e = err;
830
+ const detail = (e.stderr ?? e.message ?? String(err)).trim();
831
+ throw new Error(
832
+ `[apm] git ${args.join(" ")} \u5931\u8D25${detail ? `: ${detail}` : ""}`
833
+ );
834
+ }
835
+ }
836
+ async function ensureGitRepo2(cwd) {
837
+ await execGit2(cwd, ["rev-parse", "--git-dir"], true);
838
+ }
839
+ async function getCurrentBranch2(cwd) {
840
+ return (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
841
+ }
842
+ async function resolveDefaultBranch(cwd) {
843
+ try {
844
+ const ref = (await execGit2(cwd, ["symbolic-ref", "refs/remotes/origin/HEAD"], true)).trim();
845
+ const match = ref.match(/^refs\/remotes\/origin\/(.+)$/);
846
+ if (match?.[1]) {
847
+ return match[1];
848
+ }
849
+ } catch {
850
+ }
851
+ const out = await execGit2(cwd, ["remote", "show", "origin"], true);
852
+ const headLine = out.split(/\r?\n/).find((line) => line.includes("HEAD branch"));
853
+ const branch = headLine?.split(":").pop()?.trim();
854
+ if (branch) {
855
+ return branch;
856
+ }
857
+ throw new Error("[apm] \u65E0\u6CD5\u89E3\u6790 origin \u9ED8\u8BA4\u5206\u652F\uFF0C\u8BF7\u5148\u6267\u884C git fetch origin");
858
+ }
859
+ async function listLocalSessionBranches(cwd) {
860
+ const out = await execGit2(
861
+ cwd,
862
+ [
863
+ "for-each-ref",
864
+ "--format=%(refname:short)",
865
+ "refs/heads/",
866
+ SESSION_BRANCH_PREFIX + "*"
867
+ ],
868
+ true
869
+ );
870
+ return out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
871
+ }
872
+ async function listRemoteSessionBranches(cwd) {
873
+ const out = await execGit2(
874
+ cwd,
875
+ [
876
+ "for-each-ref",
877
+ "--format=%(refname:short)",
878
+ "refs/remotes/origin/",
879
+ SESSION_BRANCH_PREFIX + "*"
880
+ ],
881
+ true
882
+ );
883
+ return out.split(/\r?\n/).map((line) => line.trim().replace(/^origin\//, "")).filter(Boolean);
884
+ }
885
+ async function localBranchExists2(cwd, branch) {
886
+ try {
887
+ await execGit2(
888
+ cwd,
889
+ ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
890
+ true
891
+ );
892
+ return true;
893
+ } catch {
894
+ return false;
895
+ }
896
+ }
897
+ async function remoteBranchExists(cwd, branch) {
898
+ const out = await execGit2(
899
+ cwd,
900
+ ["ls-remote", "--heads", "origin", branch],
901
+ true
902
+ );
903
+ return out.trim().length > 0;
904
+ }
905
+ function reasonForCleanup(sessionId, sessionStatusById) {
906
+ if (!sessionStatusById.has(sessionId)) {
907
+ return "\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D";
908
+ }
909
+ if (sessionStatusById.get(sessionId) === "COMPLETED") {
910
+ return "\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210";
911
+ }
912
+ return "\u4FDD\u7559";
913
+ }
914
+ async function runCleanBranches(options = {}) {
915
+ const cwd = options.cwd ?? process.cwd();
916
+ const dryRun = options.dryRun ?? false;
917
+ await ensureGitRepo2(cwd);
918
+ await execGit2(cwd, ["fetch", "--prune", "origin"], true);
919
+ const cfg = await ensureLoggedConfig();
920
+ const api = createApmApiClient(cfg);
921
+ const { sessions } = await api.cli.listSessionsForBranchCleanup({});
922
+ const sessionStatusById = new Map(
923
+ sessions.map((item) => [item.sessionId, item.taskStatus])
924
+ );
925
+ const branchNames = /* @__PURE__ */ new Set([
926
+ ...await listLocalSessionBranches(cwd),
927
+ ...await listRemoteSessionBranches(cwd)
928
+ ]);
929
+ if (branchNames.size === 0) {
930
+ console.log("[apm] \u672A\u53D1\u73B0 feat/session-* \u5206\u652F");
931
+ return;
932
+ }
933
+ const toDelete = [...branchNames].map((branch) => {
934
+ const sessionId = sessionIdFromBranchName(branch);
935
+ if (!sessionId) {
936
+ return null;
937
+ }
938
+ const reason = reasonForCleanup(sessionId, sessionStatusById);
939
+ if (reason === "\u4FDD\u7559") {
940
+ return null;
941
+ }
942
+ return { branch, sessionId, reason };
943
+ }).filter((item) => item != null).sort((a, b) => a.branch.localeCompare(b.branch));
944
+ if (toDelete.length === 0) {
945
+ console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
946
+ return;
947
+ }
948
+ let currentBranch = await getCurrentBranch2(cwd);
949
+ let defaultBranch = null;
950
+ for (const item of toDelete) {
951
+ const { branch, sessionId, reason } = item;
952
+ const label = `${branch} (${sessionId}: ${reason})`;
953
+ if (dryRun) {
954
+ console.log(`[apm] [dry-run] \u5C06\u5220\u9664 ${label}`);
955
+ continue;
956
+ }
957
+ if (currentBranch === branch) {
958
+ defaultBranch ??= await resolveDefaultBranch(cwd);
959
+ await execGit2(cwd, ["checkout", defaultBranch], true);
960
+ currentBranch = defaultBranch;
961
+ }
962
+ if (await localBranchExists2(cwd, branch)) {
963
+ await execGit2(cwd, ["branch", "-D", branch], true);
964
+ console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
965
+ }
966
+ if (await remoteBranchExists(cwd, branch)) {
967
+ await execGit2(cwd, ["push", "origin", "--delete", branch], true);
968
+ console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
969
+ }
970
+ }
971
+ if (dryRun) {
972
+ console.log(`[apm] [dry-run] \u5171 ${toDelete.length} \u4E2A\u5206\u652F\u5F85\u6E05\u7406`);
973
+ } else {
974
+ console.log(`[apm] \u5DF2\u6E05\u7406 ${toDelete.length} \u4E2A feat/session-* \u5206\u652F`);
975
+ }
976
+ }
977
+
743
978
  // src/commands/pull.ts
744
979
  import { writeFileSync as writeFileSync7 } from "fs";
745
980
  import { join as join8 } from "path";
@@ -775,7 +1010,7 @@ function formatSessionMessagesXml(sessionId, messages) {
775
1010
  }
776
1011
 
777
1012
  // src/commands/sync-session-attachments.ts
778
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
1013
+ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
779
1014
  import { join as join5 } from "path";
780
1015
  var MANIFEST_FILE = ".sync-manifest.json";
781
1016
  async function downloadAttachment(cfg, attachmentId) {
@@ -796,7 +1031,7 @@ function loadManifest(dir) {
796
1031
  }
797
1032
  try {
798
1033
  const parsed = JSON.parse(
799
- readFileSync3(path10, "utf8")
1034
+ readFileSync4(path10, "utf8")
800
1035
  );
801
1036
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
802
1037
  return parsed;
@@ -852,7 +1087,7 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
852
1087
 
853
1088
  // src/rules-sync.ts
854
1089
  import { basename as basename2, extname as extname2, join as join7 } from "path";
855
- import { existsSync as existsSync5, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
1090
+ import { existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
856
1091
 
857
1092
  // src/skills-sync.ts
858
1093
  import {
@@ -960,7 +1195,7 @@ function loadManifest2(rulesDir) {
960
1195
  }
961
1196
  try {
962
1197
  const parsed = JSON.parse(
963
- readFileSync4(toFsPath(path10), "utf8")
1198
+ readFileSync5(toFsPath(path10), "utf8")
964
1199
  );
965
1200
  if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
966
1201
  return parsed;
@@ -985,7 +1220,7 @@ function isRuleUpToDate(entry, rule, dest) {
985
1220
  if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
986
1221
  const updatedAt = rule.updatedAt ?? "";
987
1222
  if (entry.updatedAt !== updatedAt) return false;
988
- const localContent = readFileSync4(toFsPath(dest), "utf8");
1223
+ const localContent = readFileSync5(toFsPath(dest), "utf8");
989
1224
  return localContent === (rule.content ?? "");
990
1225
  }
991
1226
  async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
@@ -1112,7 +1347,7 @@ async function runPull(sessionId, remoteWorkdir) {
1112
1347
  import { spawnSync } from "child_process";
1113
1348
 
1114
1349
  // src/version.ts
1115
- import { readFileSync as readFileSync5 } from "fs";
1350
+ import { readFileSync as readFileSync6 } from "fs";
1116
1351
  import { dirname as dirname2, join as join9 } from "path";
1117
1352
  import { fileURLToPath as fileURLToPath2 } from "url";
1118
1353
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
@@ -1120,7 +1355,7 @@ function readCliVersion() {
1120
1355
  try {
1121
1356
  const dir = dirname2(fileURLToPath2(import.meta.url));
1122
1357
  const pkgPath = join9(dir, "..", "package.json");
1123
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1358
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1124
1359
  return pkg.version ?? "0.0.0";
1125
1360
  } catch {
1126
1361
  return "0.0.0";
@@ -1266,7 +1501,7 @@ import { existsSync as existsSync9 } from "fs";
1266
1501
  import { basename as basename3 } from "path";
1267
1502
 
1268
1503
  // src/commands/sync-session-documents.ts
1269
- import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1504
+ import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
1270
1505
  import { join as join11 } from "path";
1271
1506
  function listLocalMarkdownFiles(docsDir) {
1272
1507
  if (!existsSync8(docsDir)) {
@@ -1285,7 +1520,7 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1285
1520
  }
1286
1521
  async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
1287
1522
  const absPath = join11(docsDir, fileName);
1288
- const content = readFileSync6(absPath, "utf8");
1523
+ const content = readFileSync7(absPath, "utf8");
1289
1524
  const name = documentPlatformName(absPath);
1290
1525
  return api.cli.upsertDocument({
1291
1526
  sessionId,
@@ -1308,7 +1543,7 @@ async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
1308
1543
  let synced = 0;
1309
1544
  for (const fileName of localFiles) {
1310
1545
  const absPath = join11(docsDir, fileName);
1311
- const content = readFileSync6(absPath, "utf8");
1546
+ const content = readFileSync7(absPath, "utf8");
1312
1547
  const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1313
1548
  if (remote && remote.content === content) {
1314
1549
  continue;
@@ -1741,7 +1976,7 @@ ${JSON.stringify(event, null, 2)}
1741
1976
  }
1742
1977
 
1743
1978
  // src/commands/connect/agent-session-registry.ts
1744
- import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
1979
+ import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
1745
1980
  import { dirname as dirname3, resolve as resolve3 } from "node:path";
1746
1981
  function registryPath(workdir, sessionId) {
1747
1982
  return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
@@ -1751,7 +1986,7 @@ function readRegistry(path10) {
1751
1986
  return {};
1752
1987
  }
1753
1988
  try {
1754
- const parsed = JSON.parse(readFileSync7(path10, "utf8"));
1989
+ const parsed = JSON.parse(readFileSync8(path10, "utf8"));
1755
1990
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1756
1991
  const result = {};
1757
1992
  for (const [key, value] of Object.entries(
@@ -2159,6 +2394,8 @@ async function handleInboundMessage(cfg, msg, signal, ctx) {
2159
2394
  };
2160
2395
  try {
2161
2396
  if (signal.aborted) return;
2397
+ assertWorkspaceApmDirExists(workdir);
2398
+ assertApmGitignoredInRepo(workdir);
2162
2399
  await runStep(
2163
2400
  "status-typing",
2164
2401
  () => updateMessageStatus(cfg, messageId, "TYPING")
@@ -2411,7 +2648,7 @@ async function runCreatePr(options) {
2411
2648
  import path5 from "node:path";
2412
2649
 
2413
2650
  // src/commands/deploy/internal/apm-config.ts
2414
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
2651
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
2415
2652
  import { resolve as resolve4 } from "node:path";
2416
2653
  function loadApmConfig(options) {
2417
2654
  const p = resolve4(
@@ -2423,7 +2660,7 @@ function loadApmConfig(options) {
2423
2660
  process.exit(1);
2424
2661
  }
2425
2662
  try {
2426
- const raw = readFileSync8(p, "utf8");
2663
+ const raw = readFileSync9(p, "utf8");
2427
2664
  return JSON.parse(raw);
2428
2665
  } catch (e) {
2429
2666
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -2545,7 +2782,7 @@ import path4 from "node:path";
2545
2782
  import Docker from "dockerode";
2546
2783
 
2547
2784
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
2548
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
2785
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
2549
2786
  import path from "node:path";
2550
2787
  function asOptionalTlsBuffer(value) {
2551
2788
  if (typeof value !== "string") {
@@ -2558,7 +2795,7 @@ function asOptionalTlsBuffer(value) {
2558
2795
  return void 0;
2559
2796
  }
2560
2797
  if (existsSync12(normalized)) {
2561
- return readFileSync9(normalized);
2798
+ return readFileSync10(normalized);
2562
2799
  }
2563
2800
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
2564
2801
  if (looksLikePath) {
@@ -2768,7 +3005,7 @@ var DockerodeClient = class {
2768
3005
  var createDockerodeClient = (config) => new DockerodeClient(config);
2769
3006
 
2770
3007
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2771
- import { existsSync as existsSync13, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
3008
+ import { existsSync as existsSync13, readFileSync as readFileSync11, statSync as statSync5 } from "node:fs";
2772
3009
  import path2 from "node:path";
2773
3010
  function stripSurroundingQuotes(value) {
2774
3011
  const t = value.trim();
@@ -2788,7 +3025,7 @@ function loadEnvFromFile(envFilePath) {
2788
3025
  if (!existsSync13(targetPath) || !statSync5(targetPath).isFile()) {
2789
3026
  return {};
2790
3027
  }
2791
- const raw = readFileSync10(targetPath, "utf-8");
3028
+ const raw = readFileSync11(targetPath, "utf-8");
2792
3029
  const result = {};
2793
3030
  for (const line of raw.split(/\r?\n/)) {
2794
3031
  const normalized = line.trim();
@@ -3624,6 +3861,11 @@ function buildProgram() {
3624
3861
  ).action(async (sessionId, opts) => {
3625
3862
  await runBranch(sessionId, { message: opts.message });
3626
3863
  });
3864
+ program.command("clean-branches").description(
3865
+ "\u6E05\u7406\u672C\u5730\u4E0E\u8FDC\u7A0B feat/session-* \u5206\u652F\uFF1A\u6C9F\u901A\u7FA4\u4E0D\u5728\u4EFB\u52A1\u5217\u8868\u4E2D\uFF0C\u6216\u5173\u8054\u4EFB\u52A1\u5DF2\u5B8C\u6210\u65F6\u5220\u9664"
3866
+ ).option("--dry-run", "\u4EC5\u5217\u51FA\u5C06\u88AB\u5220\u9664\u7684\u5206\u652F\uFF0C\u4E0D\u5B9E\u9645\u6267\u884C").action(async (opts) => {
3867
+ await runCleanBranches({ dryRun: opts.dryRun });
3868
+ });
3627
3869
  program.command("create-pr").description(
3628
3870
  "\u4E3A\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u7684\u4F1A\u8BDD\u7279\u6027\u5206\u652F\u521B\u5EFA PR\uFF08\u6807\u9898\u81EA\u52A8\u52A0 [AI] \u6807\u8BC6\uFF1B\u8FDC\u7A0B\u521B\u5EFA\u5931\u8D25\u65F6\u5E73\u53F0\u6570\u636E\u56DE\u6EDA\uFF09"
3629
3871
  ).requiredOption("--session <sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--title <title>", "PR \u6807\u9898").option("--content <content>", "PR \u6B63\u6587\uFF08Markdown\uFF09", "").action(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.50",
3
+ "version": "6.0.52",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -27,7 +27,7 @@
27
27
  "@types/ssh2-sftp-client": "~9.0.6"
28
28
  },
29
29
  "engines": {
30
- "node": ">=18"
30
+ "node": ">=22.13.0"
31
31
  },
32
32
  "dependencies": {
33
33
  "@bufbuild/protobuf": "1.10.0",