ai-project-manage-cli 7.0.1 → 7.0.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/dist/index.js CHANGED
@@ -83,8 +83,8 @@ function buildAgentWsUrl(httpBase, apiKey) {
83
83
  }
84
84
 
85
85
  // src/commands/init.ts
86
- import { join as join5 } from "path";
87
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
86
+ import { join as join7 } from "path";
87
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
88
88
 
89
89
  // src/command-utils.ts
90
90
  import {
@@ -113,8 +113,8 @@ function toFsPath(inputPath) {
113
113
  }
114
114
  return `\\\\?\\${normalized}`;
115
115
  }
116
- function normalizeWorkdirPath(path10) {
117
- let normalized = path10.trim().replace(/\\/g, "/").normalize("NFC");
116
+ function normalizeWorkdirPath(path13) {
117
+ let normalized = path13.trim().replace(/\\/g, "/").normalize("NFC");
118
118
  if (normalized.startsWith("//?/")) {
119
119
  normalized = normalized.slice(4);
120
120
  }
@@ -297,9 +297,9 @@ function assertTemplateCopiedToApm(apmDir, workdir) {
297
297
  "project"
298
298
  ];
299
299
  for (const item of required) {
300
- const path10 = join2(apmDir, item);
301
- if (!existsSync2(toFsPath(path10))) {
302
- throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path10}`);
300
+ const path13 = join2(apmDir, item);
301
+ if (!existsSync2(toFsPath(path13))) {
302
+ throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path13}`);
303
303
  }
304
304
  }
305
305
  const leakedRules = join2(workdir, "rules");
@@ -348,9 +348,6 @@ function taskDir(taskId, apmRoot, workdir) {
348
348
  function taskDocsDir(taskId, apmRoot, workdir) {
349
349
  return join2(taskDir(taskId, apmRoot, workdir), TASK_DOCS_SUBDIR);
350
350
  }
351
- function taskRulePath(taskId, apmRoot, workdir) {
352
- return join2(taskDir(taskId, apmRoot, workdir), "RULE.md");
353
- }
354
351
  function taskTaskPath(taskId, apmRoot, workdir) {
355
352
  return join2(taskDir(taskId, apmRoot, workdir), "TASK.md");
356
353
  }
@@ -574,66 +571,170 @@ ${diagnostic ?? ""}
574
571
 
575
572
  // src/repository-project-documents-sync.ts
576
573
  import {
577
- existsSync as existsSync3,
574
+ existsSync as existsSync4,
578
575
  readdirSync as readdirSync2,
579
- readFileSync as readFileSync3,
576
+ readFileSync as readFileSync4,
580
577
  rmSync,
581
- writeFileSync as writeFileSync4
578
+ writeFileSync as writeFileSync5
582
579
  } from "fs";
583
- import { dirname as dirname2, join as join4, relative, sep } from "path";
584
- var MANIFEST_FILE = "manifest.json";
585
- function projectDocumentsDir(apmRoot) {
586
- return join4(apmRoot ?? workspaceApmDir(), "project");
580
+ import { dirname as dirname2, join as join5, relative, sep } from "path";
581
+
582
+ // src/apm-manifest.ts
583
+ import { createHash } from "crypto";
584
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
585
+ import { join as join4 } from "path";
586
+ var APM_MANIFEST_FILE = "manifest.json";
587
+ var LEGACY_PROJECT_MANIFEST = join4("project", APM_MANIFEST_FILE);
588
+ function apmManifestPath(apmRoot) {
589
+ return join4(apmRoot, APM_MANIFEST_FILE);
587
590
  }
588
- function projectDocumentLocalPath(apmRoot, documentPath) {
589
- const normalized = normalizeLocalDocumentPath(documentPath);
590
- return join4(projectDocumentsDir(apmRoot), ...normalized.split("/"));
591
+ function hashApmFileContent(content) {
592
+ return createHash("sha256").update(content, "utf8").digest("hex");
591
593
  }
592
- function normalizeLocalDocumentPath(path10) {
593
- const trimmed = path10.trim().replace(/\\/g, "/");
594
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
595
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
596
- }
597
- const segments = trimmed.split("/").filter(Boolean);
598
- if (segments.some((segment) => segment === ".." || segment === ".")) {
599
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
594
+ function toProjectManifestPath(localPath) {
595
+ const trimmed = localPath.trim().replace(/\\/g, "/");
596
+ return `project/${trimmed}`;
597
+ }
598
+ function fromProjectManifestPath(manifestPath) {
599
+ const prefix = "project/";
600
+ if (!manifestPath.startsWith(prefix)) {
601
+ throw new Error(`\u975E\u6CD5\u9879\u76EE\u6587\u6863 manifest \u8DEF\u5F84: ${manifestPath}`);
600
602
  }
601
- return segments.join("/");
603
+ return manifestPath.slice(prefix.length);
604
+ }
605
+ function toRuleManifestPath(fileName) {
606
+ return `rules/${fileName}`;
607
+ }
608
+ function toSkillManifestPath(skillDirName) {
609
+ return `skills/${skillDirName}/SKILL.md`;
610
+ }
611
+ function manifestSectionPrefix(section) {
612
+ return `${section}/`;
602
613
  }
603
- function readLocalManifest(apmRoot) {
604
- const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
605
- if (!existsSync3(manifestPath)) {
614
+ function readApmManifest(apmRoot) {
615
+ const primary = apmManifestPath(apmRoot);
616
+ if (existsSync3(toFsPath(primary))) {
617
+ return parseApmManifest(readFileSync3(toFsPath(primary), "utf8"));
618
+ }
619
+ const legacy = join4(apmRoot, LEGACY_PROJECT_MANIFEST);
620
+ if (!existsSync3(toFsPath(legacy))) {
621
+ return null;
622
+ }
623
+ const legacyManifest = parseApmManifest(readFileSync3(toFsPath(legacy), "utf8"));
624
+ if (!legacyManifest) {
606
625
  return null;
607
626
  }
627
+ return {
628
+ ...legacyManifest,
629
+ documents: legacyManifest.documents.map((entry) => ({
630
+ ...entry,
631
+ path: entry.path.startsWith("project/") ? entry.path : toProjectManifestPath(entry.path)
632
+ }))
633
+ };
634
+ }
635
+ function parseApmManifest(raw) {
608
636
  try {
609
- return JSON.parse(
610
- readFileSync3(manifestPath, "utf8")
611
- );
637
+ const parsed = JSON.parse(raw);
638
+ if (parsed?.version === 1 && Array.isArray(parsed.documents) && (parsed.repositoryId === null || typeof parsed.repositoryId === "string")) {
639
+ return parsed;
640
+ }
612
641
  } catch {
613
- return null;
614
642
  }
643
+ return null;
644
+ }
645
+ function writeApmManifest(apmRoot, manifest) {
646
+ writeFileSync4(
647
+ toFsPath(apmManifestPath(apmRoot)),
648
+ `${JSON.stringify(
649
+ {
650
+ ...manifest,
651
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
652
+ documents: [...manifest.documents].sort(
653
+ (a, b) => a.path.localeCompare(b.path)
654
+ )
655
+ },
656
+ null,
657
+ 2
658
+ )}
659
+ `,
660
+ "utf8"
661
+ );
662
+ }
663
+ function mergeManifestSection(manifest, section, entries, repositoryId) {
664
+ const prefix = manifestSectionPrefix(section);
665
+ const kept = (manifest?.documents ?? []).filter(
666
+ (entry) => !entry.path.startsWith(prefix)
667
+ );
668
+ return {
669
+ version: 1,
670
+ repositoryId: repositoryId !== void 0 ? repositoryId : manifest?.repositoryId ?? null,
671
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
672
+ documents: [...kept, ...entries]
673
+ };
615
674
  }
616
- function diffManifestPaths(remote, local) {
675
+ function buildManifestEntryFromFile(input) {
676
+ return {
677
+ path: input.path,
678
+ description: input.description ?? null,
679
+ contentHash: hashApmFileContent(input.content),
680
+ updatedAt: input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
681
+ size: Buffer.byteLength(input.content, "utf8"),
682
+ ...input.remoteId ? { remoteId: input.remoteId } : {}
683
+ };
684
+ }
685
+ function diffManifestPaths(remote, local, section) {
686
+ const prefix = manifestSectionPrefix(section);
617
687
  const remoteMap = new Map(
618
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
688
+ (remote?.documents ?? []).filter((doc) => doc.path.startsWith(prefix)).map((doc) => [doc.path, doc.contentHash])
619
689
  );
620
690
  const localMap = new Map(
621
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
691
+ (local?.documents ?? []).filter((doc) => doc.path.startsWith(prefix)).map((doc) => [doc.path, doc.contentHash])
622
692
  );
623
693
  const download = [];
624
- for (const [path10, hash] of remoteMap) {
625
- if (localMap.get(path10) !== hash) {
626
- download.push(path10);
694
+ for (const [path13, hash] of remoteMap) {
695
+ if (localMap.get(path13) !== hash) {
696
+ download.push(path13);
627
697
  }
628
698
  }
629
699
  const deleteLocal = [];
630
- for (const path10 of localMap.keys()) {
631
- if (!remoteMap.has(path10)) {
632
- deleteLocal.push(path10);
700
+ for (const path13 of localMap.keys()) {
701
+ if (!remoteMap.has(path13)) {
702
+ deleteLocal.push(path13);
633
703
  }
634
704
  }
635
705
  return { download, deleteLocal };
636
706
  }
707
+
708
+ // src/repository-project-documents-sync.ts
709
+ function projectDocumentsDir(apmRoot) {
710
+ return join5(apmRoot ?? workspaceApmDir(), "project");
711
+ }
712
+ function projectDocumentLocalPath(apmRoot, documentPath) {
713
+ const normalized = normalizeLocalDocumentPath(documentPath);
714
+ return join5(projectDocumentsDir(apmRoot), ...normalized.split("/"));
715
+ }
716
+ function normalizeLocalDocumentPath(path13) {
717
+ const trimmed = path13.trim().replace(/\\/g, "/");
718
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
719
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path13}`);
720
+ }
721
+ const segments = trimmed.split("/").filter(Boolean);
722
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
723
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path13}`);
724
+ }
725
+ return segments.join("/");
726
+ }
727
+ function toRemoteProjectManifest(repositoryId, remoteManifest) {
728
+ return {
729
+ version: 1,
730
+ repositoryId,
731
+ generatedAt: remoteManifest.generatedAt,
732
+ documents: remoteManifest.documents.map((doc) => ({
733
+ ...doc,
734
+ path: toProjectManifestPath(doc.path)
735
+ }))
736
+ };
737
+ }
637
738
  async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
638
739
  const empty = {
639
740
  synced: false,
@@ -654,10 +755,8 @@ async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
654
755
  workdirPath
655
756
  );
656
757
  if (!repositoryId) {
657
- console.log(
658
- `[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
659
- ${diagnostic ?? ""}`
660
- );
758
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
759
+ ${diagnostic ?? ""}`);
661
760
  return empty;
662
761
  }
663
762
  const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
@@ -665,43 +764,54 @@ ${diagnostic ?? ""}`
665
764
  await ensureDirExists(projectDir);
666
765
  const { manifest: remoteManifest } = await api.cli.getRepositoryProjectDocumentManifest({ repositoryId });
667
766
  if (!remoteManifest) {
668
- console.log(
669
- `[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`
670
- );
767
+ console.log(`[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
671
768
  return { ...empty, repositoryId };
672
769
  }
673
- const localManifest = readLocalManifest(targetApmDir);
770
+ const remoteApmManifest = toRemoteProjectManifest(
771
+ repositoryId,
772
+ remoteManifest
773
+ );
774
+ const localManifest = readApmManifest(targetApmDir);
674
775
  const { download, deleteLocal } = diffManifestPaths(
675
- remoteManifest,
676
- localManifest
776
+ remoteApmManifest,
777
+ localManifest,
778
+ "project"
677
779
  );
678
780
  let downloaded = 0;
679
781
  if (download.length > 0) {
680
782
  const { list } = await api.cli.listRepositoryProjectDocuments({
681
783
  repositoryId,
682
- paths: download.join(",")
784
+ paths: download.map(fromProjectManifestPath).join(",")
683
785
  });
684
786
  for (const doc of list) {
685
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
787
+ const absPath = toFsPath(
788
+ projectDocumentLocalPath(targetApmDir, doc.path)
789
+ );
686
790
  await ensureDirExists(dirname2(absPath));
687
- writeFileSync4(absPath, doc.content, "utf8");
791
+ writeFileSync5(absPath, doc.content, "utf8");
688
792
  downloaded += 1;
689
793
  }
690
794
  }
691
795
  let deleted = 0;
692
- for (const path10 of deleteLocal) {
693
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
694
- if (existsSync3(absPath)) {
796
+ for (const manifestPath of deleteLocal) {
797
+ const absPath = toFsPath(
798
+ projectDocumentLocalPath(
799
+ targetApmDir,
800
+ fromProjectManifestPath(manifestPath)
801
+ )
802
+ );
803
+ if (existsSync4(absPath)) {
695
804
  rmSync(absPath, { force: true });
696
805
  deleted += 1;
697
806
  }
698
807
  }
699
- writeFileSync4(
700
- toFsPath(join4(projectDir, MANIFEST_FILE)),
701
- `${JSON.stringify(remoteManifest, null, 2)}
702
- `,
703
- "utf8"
808
+ const nextManifest = mergeManifestSection(
809
+ localManifest,
810
+ "project",
811
+ remoteApmManifest.documents,
812
+ repositoryId
704
813
  );
814
+ writeApmManifest(targetApmDir, nextManifest);
705
815
  console.log(
706
816
  `[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
707
817
  );
@@ -713,6 +823,162 @@ ${diagnostic ?? ""}`
713
823
  };
714
824
  }
715
825
 
826
+ // src/skills-sync.ts
827
+ import {
828
+ copyFileSync as copyFileSync2,
829
+ cpSync,
830
+ existsSync as existsSync5,
831
+ mkdirSync as mkdirSync3,
832
+ readdirSync as readdirSync3,
833
+ readFileSync as readFileSync5,
834
+ rmSync as rmSync2,
835
+ statSync as statSync2,
836
+ writeFileSync as writeFileSync6
837
+ } from "fs";
838
+ import { join as join6 } from "path";
839
+ var AGENTS_TEMPLATE_PATH = join6(CLI_TEMPLATE_DIR, "AGENTS.md");
840
+ var BASE_SKILLS_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "skills");
841
+ var BASE_RULES_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "rules");
842
+ function sanitizeSkillDirName(name) {
843
+ const trimmed = name.trim();
844
+ if (!trimmed) return "skill";
845
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
846
+ }
847
+ function listBaseSkillDirNames() {
848
+ if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
849
+ return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
850
+ const path13 = join6(BASE_SKILLS_TEMPLATE_DIR, name);
851
+ return statSync2(path13).isDirectory();
852
+ });
853
+ }
854
+ function syncAgentsGuide(apmDir) {
855
+ if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
856
+ mkdirSync3(apmDir, { recursive: true });
857
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join6(apmDir, "AGENTS.md"));
858
+ return true;
859
+ }
860
+ function listBaseRuleFileNames() {
861
+ if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
862
+ return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
863
+ const path13 = join6(BASE_RULES_TEMPLATE_DIR, name);
864
+ return statSync2(path13).isFile();
865
+ });
866
+ }
867
+ function syncBaseRules(rulesDir) {
868
+ mkdirSync3(rulesDir, { recursive: true });
869
+ const names = listBaseRuleFileNames();
870
+ for (const name of names) {
871
+ const src = join6(BASE_RULES_TEMPLATE_DIR, name);
872
+ const dest = join6(rulesDir, name);
873
+ copyFileSync2(src, dest);
874
+ }
875
+ return names;
876
+ }
877
+ function syncBaseSkills(skillsDir) {
878
+ mkdirSync3(skillsDir, { recursive: true });
879
+ const names = listBaseSkillDirNames();
880
+ for (const name of names) {
881
+ const src = join6(BASE_SKILLS_TEMPLATE_DIR, name);
882
+ const dest = join6(skillsDir, name);
883
+ cpSync(src, dest, { recursive: true, force: true });
884
+ }
885
+ return names;
886
+ }
887
+ function syncSupplementarySkills(skillsDir, list) {
888
+ const baseNames = new Set(listBaseSkillDirNames());
889
+ const apiDirNames = /* @__PURE__ */ new Set();
890
+ const written = [];
891
+ const skipped = [];
892
+ for (const skill of list) {
893
+ const dirName = sanitizeSkillDirName(skill.name);
894
+ apiDirNames.add(dirName);
895
+ if (baseNames.has(dirName)) {
896
+ skipped.push(dirName);
897
+ continue;
898
+ }
899
+ const skillDir = join6(skillsDir, dirName);
900
+ mkdirSync3(skillDir, { recursive: true });
901
+ writeFileSync6(join6(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
902
+ written.push(dirName);
903
+ }
904
+ const removed = [];
905
+ if (!existsSync5(skillsDir)) return { written, skipped, removed };
906
+ for (const entry of readdirSync3(skillsDir)) {
907
+ const full = join6(skillsDir, entry);
908
+ if (!statSync2(full).isDirectory()) continue;
909
+ if (baseNames.has(entry)) continue;
910
+ if (apiDirNames.has(entry)) continue;
911
+ rmSync2(full, { recursive: true, force: true });
912
+ removed.push(entry);
913
+ }
914
+ return { written, skipped, removed };
915
+ }
916
+ function syncSkillsManifest(apmDir, supplementarySkills = []) {
917
+ const skillsDir = join6(apmDir, "skills");
918
+ if (!existsSync5(skillsDir)) {
919
+ return;
920
+ }
921
+ const supplementaryByDir = new Map(
922
+ supplementarySkills.map((skill) => [
923
+ sanitizeSkillDirName(skill.name),
924
+ skill
925
+ ])
926
+ );
927
+ const manifest = readApmManifest(apmDir);
928
+ const existingByPath = new Map(
929
+ (manifest?.documents ?? []).filter((entry) => entry.path.startsWith("skills/")).map((entry) => [entry.path, entry])
930
+ );
931
+ const entries = [];
932
+ for (const entry of readdirSync3(skillsDir)) {
933
+ const skillDir = join6(skillsDir, entry);
934
+ if (!statSync2(skillDir).isDirectory()) continue;
935
+ const skillFile = join6(skillDir, "SKILL.md");
936
+ if (!existsSync5(skillFile)) continue;
937
+ const content = readFileSync5(skillFile, "utf8");
938
+ const supplementary = supplementaryByDir.get(entry);
939
+ const manifestPath = toSkillManifestPath(entry);
940
+ const existing = existingByPath.get(manifestPath);
941
+ entries.push(
942
+ buildManifestEntryFromFile({
943
+ path: manifestPath,
944
+ content,
945
+ description: supplementary?.description ?? existing?.description ?? null,
946
+ remoteId: supplementary?.id ?? existing?.remoteId
947
+ })
948
+ );
949
+ }
950
+ writeApmManifest(apmDir, mergeManifestSection(manifest, "skills", entries));
951
+ }
952
+ function syncRulesManifest(apmDir) {
953
+ const rulesDir = join6(apmDir, "rules");
954
+ if (!existsSync5(rulesDir)) {
955
+ return;
956
+ }
957
+ const manifest = readApmManifest(apmDir);
958
+ const existingByPath = new Map(
959
+ (manifest?.documents ?? []).filter((entry) => entry.path.startsWith("rules/")).map((entry) => [entry.path, entry])
960
+ );
961
+ const entries = [];
962
+ for (const fileName of readdirSync3(rulesDir)) {
963
+ const dest = join6(rulesDir, fileName);
964
+ if (!statSync2(dest).isFile()) continue;
965
+ if (fileName === ".rules-sync-manifest.json") continue;
966
+ const manifestPath = toRuleManifestPath(fileName);
967
+ const content = readFileSync5(dest, "utf8");
968
+ const existing = existingByPath.get(manifestPath);
969
+ entries.push(
970
+ buildManifestEntryFromFile({
971
+ path: manifestPath,
972
+ content,
973
+ description: existing?.description ?? null,
974
+ updatedAt: existing?.updatedAt,
975
+ remoteId: existing?.remoteId
976
+ })
977
+ );
978
+ }
979
+ writeApmManifest(apmDir, mergeManifestSection(manifest, "rules", entries));
980
+ }
981
+
716
982
  // src/git-utils.ts
717
983
  import { execFile as execFile2 } from "child_process";
718
984
  import { promisify as promisify2 } from "util";
@@ -789,15 +1055,17 @@ async function ensureWorkspaceInitialized(workdir, options) {
789
1055
  }
790
1056
  const apmDir = workspaceApmDir(workdir);
791
1057
  await copyTemplateFiles(apmDir, workdir);
1058
+ syncRulesManifest(apmDir);
1059
+ syncSkillsManifest(apmDir);
792
1060
  const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
793
1061
  await syncRepositoryProjectDocumentsPull(workdir, apmDir);
794
1062
  const trimmedName = options?.name?.trim();
795
1063
  if (trimmedName) {
796
- const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
797
- const config = readFileSync4(apmConfigPath, "utf8");
1064
+ const apmConfigPath = toFsPath(join7(apmDir, "apm.config.json"));
1065
+ const config = readFileSync6(apmConfigPath, "utf8");
798
1066
  const configJson = JSON.parse(config);
799
1067
  configJson.name = trimmedName;
800
- writeFileSync5(
1068
+ writeFileSync7(
801
1069
  apmConfigPath,
802
1070
  `${JSON.stringify(configJson, null, 2)}
803
1071
  `,
@@ -886,15 +1154,15 @@ async function runLogin(opts) {
886
1154
  import { spawnSync } from "child_process";
887
1155
 
888
1156
  // src/version.ts
889
- import { readFileSync as readFileSync5 } from "fs";
890
- import { dirname as dirname3, join as join6 } from "path";
1157
+ import { readFileSync as readFileSync7 } from "fs";
1158
+ import { dirname as dirname3, join as join8 } from "path";
891
1159
  import { fileURLToPath as fileURLToPath2 } from "url";
892
1160
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
893
1161
  function readCliVersion() {
894
1162
  try {
895
1163
  const dir = dirname3(fileURLToPath2(import.meta.url));
896
- const pkgPath = join6(dir, "..", "package.json");
897
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1164
+ const pkgPath = join8(dir, "..", "package.json");
1165
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
898
1166
  return pkg.version ?? "0.0.0";
899
1167
  } catch {
900
1168
  return "0.0.0";
@@ -967,104 +1235,12 @@ async function runUpdate() {
967
1235
  }
968
1236
 
969
1237
  // src/commands/update-skills.ts
970
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
971
- import { join as join8 } from "path";
972
-
973
- // src/skills-sync.ts
974
- import {
975
- copyFileSync as copyFileSync2,
976
- cpSync,
977
- existsSync as existsSync4,
978
- mkdirSync as mkdirSync3,
979
- readdirSync as readdirSync3,
980
- rmSync as rmSync2,
981
- statSync as statSync2,
982
- writeFileSync as writeFileSync6
983
- } from "fs";
984
- import { join as join7 } from "path";
985
- var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
986
- var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
987
- var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
988
- function sanitizeSkillDirName(name) {
989
- const trimmed = name.trim();
990
- if (!trimmed) return "skill";
991
- return trimmed.replace(/[/\\:*?"<>|]/g, "_");
992
- }
993
- function listBaseSkillDirNames() {
994
- if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
995
- return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
996
- const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
997
- return statSync2(path10).isDirectory();
998
- });
999
- }
1000
- function syncAgentsGuide(apmDir) {
1001
- if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
1002
- mkdirSync3(apmDir, { recursive: true });
1003
- copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
1004
- return true;
1005
- }
1006
- function listBaseRuleFileNames() {
1007
- if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
1008
- return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
1009
- const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
1010
- return statSync2(path10).isFile();
1011
- });
1012
- }
1013
- function syncBaseRules(rulesDir) {
1014
- mkdirSync3(rulesDir, { recursive: true });
1015
- const names = listBaseRuleFileNames();
1016
- for (const name of names) {
1017
- const src = join7(BASE_RULES_TEMPLATE_DIR, name);
1018
- const dest = join7(rulesDir, name);
1019
- copyFileSync2(src, dest);
1020
- }
1021
- return names;
1022
- }
1023
- function syncBaseSkills(skillsDir) {
1024
- mkdirSync3(skillsDir, { recursive: true });
1025
- const names = listBaseSkillDirNames();
1026
- for (const name of names) {
1027
- const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1028
- const dest = join7(skillsDir, name);
1029
- cpSync(src, dest, { recursive: true, force: true });
1030
- }
1031
- return names;
1032
- }
1033
- function syncSupplementarySkills(skillsDir, list) {
1034
- const baseNames = new Set(listBaseSkillDirNames());
1035
- const apiDirNames = /* @__PURE__ */ new Set();
1036
- const written = [];
1037
- const skipped = [];
1038
- for (const skill of list) {
1039
- const dirName = sanitizeSkillDirName(skill.name);
1040
- apiDirNames.add(dirName);
1041
- if (baseNames.has(dirName)) {
1042
- skipped.push(dirName);
1043
- continue;
1044
- }
1045
- const skillDir = join7(skillsDir, dirName);
1046
- mkdirSync3(skillDir, { recursive: true });
1047
- writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1048
- written.push(dirName);
1049
- }
1050
- const removed = [];
1051
- if (!existsSync4(skillsDir)) return { written, skipped, removed };
1052
- for (const entry of readdirSync3(skillsDir)) {
1053
- const full = join7(skillsDir, entry);
1054
- if (!statSync2(full).isDirectory()) continue;
1055
- if (baseNames.has(entry)) continue;
1056
- if (apiDirNames.has(entry)) continue;
1057
- rmSync2(full, { recursive: true, force: true });
1058
- removed.push(entry);
1059
- }
1060
- return { written, skipped, removed };
1061
- }
1062
-
1063
- // src/commands/update-skills.ts
1238
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
1239
+ import { join as join9 } from "path";
1064
1240
  async function syncWorkspaceSkills(cfg, workdir) {
1065
1241
  const apmDir = workspaceApmDir(workdir);
1066
1242
  const fsApmDir = toFsPath(apmDir);
1067
- if (!existsSync5(fsApmDir)) {
1243
+ if (!existsSync6(fsApmDir)) {
1068
1244
  throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1069
1245
  }
1070
1246
  const apmStat = statSync3(fsApmDir);
@@ -1076,12 +1252,13 @@ async function syncWorkspaceSkills(cfg, workdir) {
1076
1252
  if (syncAgentsGuide(apmDir)) {
1077
1253
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
1078
1254
  }
1079
- const rulesDir = join8(apmDir, "rules");
1255
+ const rulesDir = join9(apmDir, "rules");
1080
1256
  const ruleNames = syncBaseRules(rulesDir);
1081
1257
  for (const name of ruleNames) {
1082
1258
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
1083
1259
  }
1084
- const skillsDir = join8(apmDir, "skills");
1260
+ syncRulesManifest(apmDir);
1261
+ const skillsDir = join9(apmDir, "skills");
1085
1262
  mkdirSync4(toFsPath(skillsDir), { recursive: true });
1086
1263
  const baseNames = syncBaseSkills(skillsDir);
1087
1264
  for (const name of baseNames) {
@@ -1102,13 +1279,14 @@ async function syncWorkspaceSkills(cfg, workdir) {
1102
1279
  for (const name of removed) {
1103
1280
  console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
1104
1281
  }
1282
+ syncSkillsManifest(apmDir, list);
1105
1283
  console.log(
1106
1284
  `[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
1107
1285
  );
1108
1286
  }
1109
1287
  async function runUpdateSkills() {
1110
1288
  const apmDir = workspaceApmDir();
1111
- if (!existsSync5(apmDir)) {
1289
+ if (!existsSync6(apmDir)) {
1112
1290
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1113
1291
  process.exit(1);
1114
1292
  }
@@ -1121,7 +1299,7 @@ async function runUpdateSkills() {
1121
1299
  }
1122
1300
 
1123
1301
  // src/commands/connect.ts
1124
- import { spawnSync as spawnSync2 } from "child_process";
1302
+ import { spawnSync as spawnSync3 } from "child_process";
1125
1303
  import WebSocket from "ws";
1126
1304
 
1127
1305
  // src/ws/protocol.ts
@@ -1212,15 +1390,104 @@ function validateAgentWsMessage(value, kind) {
1212
1390
  return validateReceivedMailPush(o);
1213
1391
  }
1214
1392
 
1215
- // src/commands/connect/deploy-run.ts
1216
- import { spawn } from "node:child_process";
1217
- import { readFileSync as readFileSync6 } from "node:fs";
1218
- import { join as join9 } from "node:path";
1393
+ // src/commands/deploy/internal/deploy-log-syncer.ts
1219
1394
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
1395
+ function createDeployLogSyncer(api, deploymentRunId) {
1396
+ let lastSyncedLog = "";
1397
+ let latestLog = "";
1398
+ const syncIfChanged = async () => {
1399
+ if (!latestLog || latestLog === lastSyncedLog) {
1400
+ return;
1401
+ }
1402
+ await api.cli.syncTaskDeploymentLog({
1403
+ id: deploymentRunId,
1404
+ log: latestLog
1405
+ });
1406
+ lastSyncedLog = latestLog;
1407
+ };
1408
+ const timer = setInterval(() => {
1409
+ void syncIfChanged().catch((error) => {
1410
+ console.error(
1411
+ "[apm] deploy log sync failed:",
1412
+ error instanceof Error ? error.message : String(error)
1413
+ );
1414
+ });
1415
+ }, DEPLOY_LOG_SYNC_INTERVAL_MS);
1416
+ return {
1417
+ updateLog(log2) {
1418
+ latestLog = log2;
1419
+ },
1420
+ async flush() {
1421
+ clearInterval(timer);
1422
+ await syncIfChanged();
1423
+ },
1424
+ dispose() {
1425
+ clearInterval(timer);
1426
+ }
1427
+ };
1428
+ }
1429
+
1430
+ // src/commands/deploy/internal/deploy-shell.ts
1431
+ import { spawn as spawn2 } from "node:child_process";
1432
+ import { readFileSync as readFileSync8 } from "node:fs";
1433
+ import { join as join10 } from "node:path";
1434
+
1435
+ // src/commands/deploy/internal/shell-exec.ts
1436
+ import {
1437
+ spawn,
1438
+ spawnSync as spawnSync2
1439
+ } from "node:child_process";
1440
+ import { platform as platform2 } from "node:os";
1441
+ function spawnShellOptions() {
1442
+ if (platform2() === "win32") {
1443
+ return {
1444
+ shell: process.env.ComSpec ?? "cmd.exe",
1445
+ windowsHide: true
1446
+ };
1447
+ }
1448
+ return { shell: true, windowsHide: true };
1449
+ }
1450
+ function terminateChildProcess(child) {
1451
+ try {
1452
+ if (platform2() === "win32") {
1453
+ child.kill();
1454
+ } else {
1455
+ child.kill("SIGTERM");
1456
+ }
1457
+ } catch {
1458
+ }
1459
+ }
1460
+ function normalizeMavenLocalRepoPath(repoPath) {
1461
+ return repoPath.replace(/\\/g, "/");
1462
+ }
1463
+ function quoteShellArg(value) {
1464
+ if (!/\s/.test(value)) {
1465
+ return value;
1466
+ }
1467
+ if (platform2() === "win32") {
1468
+ return `"${value.replace(/"/g, '""')}"`;
1469
+ }
1470
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
1471
+ }
1472
+ function expandUserHomePath(pathStr) {
1473
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
1474
+ return pathStr.replace(/^~(?=$|[\\/])/, home);
1475
+ }
1476
+ function spawnSyncShellCommand(command, options = {}) {
1477
+ return spawnSync2(command, {
1478
+ ...options,
1479
+ ...spawnShellOptions()
1480
+ });
1481
+ }
1482
+
1483
+ // src/commands/deploy/internal/deploy-shell.ts
1484
+ function isDeployEnvironment(value) {
1485
+ return value === "test" || value === "online";
1486
+ }
1220
1487
  function readDeployConfig(workdir) {
1221
- const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
1488
+ const configPath = join10(workspaceApmDir(workdir), "apm.config.json");
1222
1489
  try {
1223
- const raw = readFileSync6(configPath, "utf8");
1490
+ const raw = readFileSync8(configPath, "utf8");
1224
1491
  const parsed = JSON.parse(raw);
1225
1492
  return parsed.deploy;
1226
1493
  } catch {
@@ -1240,88 +1507,91 @@ function missingDeployCommandMessage(environment) {
1240
1507
  function buildDeployLog(stdout, stderr) {
1241
1508
  return [stdout, stderr].filter(Boolean).join("\n");
1242
1509
  }
1243
- function runShellCommand(command, cwd, signal, onOutput) {
1510
+ async function runDeployShellCommand(command, cwd, options) {
1511
+ const signal = options?.signal ?? new AbortController().signal;
1512
+ const shellOpts = spawnShellOptions();
1513
+ if (options?.inheritStdio) {
1514
+ return new Promise((resolve5, reject) => {
1515
+ const child = spawn2(command, {
1516
+ cwd,
1517
+ env: process.env,
1518
+ stdio: "inherit",
1519
+ ...shellOpts
1520
+ });
1521
+ const onAbort = () => {
1522
+ terminateChildProcess(child);
1523
+ };
1524
+ if (signal.aborted) {
1525
+ onAbort();
1526
+ } else {
1527
+ signal.addEventListener("abort", onAbort, { once: true });
1528
+ }
1529
+ child.on("error", (error) => {
1530
+ signal.removeEventListener("abort", onAbort);
1531
+ reject(error);
1532
+ });
1533
+ child.on("close", (code) => {
1534
+ signal.removeEventListener("abort", onAbort);
1535
+ if (code === 0) {
1536
+ resolve5({ log: "" });
1537
+ return;
1538
+ }
1539
+ reject(new Error(`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`));
1540
+ });
1541
+ });
1542
+ }
1244
1543
  return new Promise((resolve5, reject) => {
1245
- const child = spawn(command, {
1544
+ const child = spawn2(command, {
1246
1545
  cwd,
1247
- shell: true,
1248
1546
  env: process.env,
1249
- windowsHide: true
1547
+ ...shellOpts
1250
1548
  });
1251
1549
  let stdout = "";
1252
1550
  let stderr = "";
1253
1551
  const emitLog = () => {
1254
- onOutput?.(buildDeployLog(stdout, stderr));
1552
+ options?.onOutput?.(buildDeployLog(stdout, stderr));
1255
1553
  };
1256
1554
  const onAbort = () => {
1257
- child.kill("SIGTERM");
1555
+ terminateChildProcess(child);
1258
1556
  };
1259
1557
  if (signal.aborted) {
1260
1558
  onAbort();
1261
1559
  } else {
1262
1560
  signal.addEventListener("abort", onAbort, { once: true });
1263
1561
  }
1264
- child.stdout.on("data", (chunk) => {
1265
- stdout += String(chunk);
1266
- emitLog();
1267
- });
1268
- child.stderr.on("data", (chunk) => {
1269
- stderr += String(chunk);
1270
- emitLog();
1271
- });
1562
+ if (child.stdout) {
1563
+ child.stdout.on("data", (chunk) => {
1564
+ stdout += String(chunk);
1565
+ emitLog();
1566
+ });
1567
+ }
1568
+ if (child.stderr) {
1569
+ child.stderr.on("data", (chunk) => {
1570
+ stderr += String(chunk);
1571
+ emitLog();
1572
+ });
1573
+ }
1272
1574
  child.on("error", (error) => {
1273
1575
  signal.removeEventListener("abort", onAbort);
1274
1576
  reject(error);
1275
1577
  });
1276
- child.on("close", (code) => {
1277
- signal.removeEventListener("abort", onAbort);
1278
- const log = buildDeployLog(stdout, stderr);
1279
- if (code === 0) {
1280
- resolve5({ log });
1281
- return;
1282
- }
1283
- const error = new Error(
1284
- `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
1285
- );
1286
- error.log = log;
1287
- reject(error);
1288
- });
1289
- });
1290
- }
1291
- function createDeployLogSyncer(api, deploymentRunId) {
1292
- let lastSyncedLog = "";
1293
- let latestLog = "";
1294
- const syncIfChanged = async () => {
1295
- if (!latestLog || latestLog === lastSyncedLog) {
1296
- return;
1297
- }
1298
- await api.cli.syncTaskDeploymentLog({
1299
- id: deploymentRunId,
1300
- log: latestLog
1301
- });
1302
- lastSyncedLog = latestLog;
1303
- };
1304
- const timer = setInterval(() => {
1305
- void syncIfChanged().catch((error) => {
1306
- console.error(
1307
- "[apm] deploy log sync failed:",
1308
- error instanceof Error ? error.message : String(error)
1578
+ child.on("close", (code) => {
1579
+ signal.removeEventListener("abort", onAbort);
1580
+ const log2 = buildDeployLog(stdout, stderr);
1581
+ if (code === 0) {
1582
+ resolve5({ log: log2 });
1583
+ return;
1584
+ }
1585
+ const error = new Error(
1586
+ `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
1309
1587
  );
1588
+ error.log = log2;
1589
+ reject(error);
1310
1590
  });
1311
- }, DEPLOY_LOG_SYNC_INTERVAL_MS);
1312
- return {
1313
- updateLog(log) {
1314
- latestLog = log;
1315
- },
1316
- async flush() {
1317
- clearInterval(timer);
1318
- await syncIfChanged();
1319
- },
1320
- dispose() {
1321
- clearInterval(timer);
1322
- }
1323
- };
1591
+ });
1324
1592
  }
1593
+
1594
+ // src/commands/connect/deploy-run.ts
1325
1595
  async function handleInboundDeploy(cfg, msg, signal) {
1326
1596
  const api = createApmApiClient(cfg);
1327
1597
  const deploymentRunId = msg.deploymentRunId;
@@ -1331,9 +1601,10 @@ async function handleInboundDeploy(cfg, msg, signal) {
1331
1601
  status: "DEPLOYING"
1332
1602
  });
1333
1603
  const workdir = requireRemoteWorkdir(msg.workdir);
1334
- const command = resolveDeployCommand(workdir, msg.environment);
1604
+ const environment = msg.environment;
1605
+ const command = resolveDeployCommand(workdir, environment);
1335
1606
  if (!command) {
1336
- const error = missingDeployCommandMessage(msg.environment);
1607
+ const error = missingDeployCommandMessage(environment);
1337
1608
  console.error(`[apm] ${error}`);
1338
1609
  await api.cli.completeTaskDeployment({
1339
1610
  id: deploymentRunId,
@@ -1344,34 +1615,37 @@ async function handleInboundDeploy(cfg, msg, signal) {
1344
1615
  return;
1345
1616
  }
1346
1617
  console.log(
1347
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
1618
+ `[apm] deploy start id=${deploymentRunId} env=${environment} cwd=${workdir}`
1348
1619
  );
1349
1620
  console.log(`[apm] deploy command: ${command}`);
1350
1621
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
1351
1622
  let latestLog = "";
1352
1623
  try {
1353
- const { log } = await runShellCommand(command, workdir, signal, (log2) => {
1354
- latestLog = log2;
1355
- logSyncer.updateLog(log2);
1624
+ const { log: log2 } = await runDeployShellCommand(command, workdir, {
1625
+ signal,
1626
+ onOutput: (log3) => {
1627
+ latestLog = log3;
1628
+ logSyncer.updateLog(log3);
1629
+ }
1356
1630
  });
1357
- latestLog = log;
1358
- logSyncer.updateLog(log);
1631
+ latestLog = log2;
1632
+ logSyncer.updateLog(log2);
1359
1633
  await logSyncer.flush();
1360
1634
  await api.cli.completeTaskDeployment({
1361
1635
  id: deploymentRunId,
1362
1636
  status: "SUCCESS",
1363
- log
1637
+ log: log2
1364
1638
  });
1365
1639
  console.log(`[apm] deploy success id=${deploymentRunId}`);
1366
1640
  } catch (error) {
1367
1641
  const detail = error instanceof Error ? error.message : String(error);
1368
- const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
1369
- logSyncer.updateLog(log);
1642
+ const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
1643
+ logSyncer.updateLog(log2);
1370
1644
  await logSyncer.flush();
1371
1645
  await api.cli.completeTaskDeployment({
1372
1646
  id: deploymentRunId,
1373
1647
  status: "FAILED",
1374
- log,
1648
+ log: log2,
1375
1649
  error: detail
1376
1650
  });
1377
1651
  console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
@@ -1381,10 +1655,10 @@ async function handleInboundDeploy(cfg, msg, signal) {
1381
1655
  }
1382
1656
 
1383
1657
  // src/commands/sync-task-documents.ts
1384
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
1385
- import { join as join10 } from "path";
1658
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
1659
+ import { join as join11 } from "path";
1386
1660
  function listLocalMarkdownFiles(docsDir) {
1387
- if (!existsSync6(docsDir)) {
1661
+ if (!existsSync7(docsDir)) {
1388
1662
  return [];
1389
1663
  }
1390
1664
  return readdirSync4(docsDir).filter(
@@ -1399,8 +1673,8 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1399
1673
  });
1400
1674
  }
1401
1675
  async function upsertLocalDocumentFile(api, taskId, docsDir, fileName) {
1402
- const content = readFileSync7(join10(docsDir, fileName), "utf8");
1403
- const name = documentPlatformName(join10(docsDir, fileName));
1676
+ const content = readFileSync9(join11(docsDir, fileName), "utf8");
1677
+ const name = documentPlatformName(join11(docsDir, fileName));
1404
1678
  return api.cli.upsertDocument({
1405
1679
  taskId,
1406
1680
  name,
@@ -1422,7 +1696,7 @@ async function syncTaskDocuments(cfg, taskId, workdir, options) {
1422
1696
  const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ taskId: trimmedTaskId });
1423
1697
  let synced = 0;
1424
1698
  for (const fileName of localFiles) {
1425
- const content = readFileSync7(join10(docsDir, fileName), "utf8");
1699
+ const content = readFileSync9(join11(docsDir, fileName), "utf8");
1426
1700
  const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1427
1701
  if (remote && remote.content === content) {
1428
1702
  continue;
@@ -1449,6 +1723,86 @@ import {
1449
1723
  } from "@cursor/sdk";
1450
1724
  import { setMaxListeners as setMaxListeners2 } from "node:events";
1451
1725
 
1726
+ // src/commands/connect/plan-document.ts
1727
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
1728
+ import { join as join12 } from "node:path";
1729
+ function normalizePlanContent(plan) {
1730
+ return plan.replace(/\\n/g, "\n").trim();
1731
+ }
1732
+ function extractPlanFromArgs(args) {
1733
+ if (args == null) {
1734
+ return void 0;
1735
+ }
1736
+ let parsed = args;
1737
+ if (typeof args === "string") {
1738
+ const trimmed = args.trim();
1739
+ if (!trimmed) {
1740
+ return void 0;
1741
+ }
1742
+ try {
1743
+ parsed = JSON.parse(trimmed);
1744
+ } catch {
1745
+ return normalizePlanContent(trimmed);
1746
+ }
1747
+ }
1748
+ if (typeof parsed === "object" && parsed !== null && "plan" in parsed) {
1749
+ const plan = parsed.plan;
1750
+ if (typeof plan === "string" && plan.trim()) {
1751
+ return normalizePlanContent(plan);
1752
+ }
1753
+ }
1754
+ return void 0;
1755
+ }
1756
+ function memberPlanFileName(displayName) {
1757
+ const sanitized = displayName.trim().replace(/[/\\:*?"<>|]/g, "-").replace(/\s+/g, " ").trim() || "member";
1758
+ return `${sanitized}-PLAN.md`;
1759
+ }
1760
+ function collectCreatePlanContent(events) {
1761
+ for (let i = events.length - 1; i >= 0; i--) {
1762
+ const event = events[i];
1763
+ if (event.type !== "tool_call") continue;
1764
+ if (event.name !== "createPlan") continue;
1765
+ if (event.status !== "completed") continue;
1766
+ const content = extractPlanFromArgs(event.args);
1767
+ if (content) {
1768
+ return content;
1769
+ }
1770
+ }
1771
+ return void 0;
1772
+ }
1773
+ function extractCreatePlanFromSessionEvents(events) {
1774
+ for (let i = events.length - 1; i >= 0; i--) {
1775
+ const event = events[i];
1776
+ if (event.type !== "tool_call") continue;
1777
+ if (event.name !== "createPlan") continue;
1778
+ if (event.status !== "completed") continue;
1779
+ const content = extractPlanFromArgs(event.args);
1780
+ if (content) {
1781
+ return content;
1782
+ }
1783
+ }
1784
+ return void 0;
1785
+ }
1786
+ function saveMemberPlanDocument(input) {
1787
+ const trimmedContent = input.content.trim();
1788
+ if (!trimmedContent) {
1789
+ throw new Error("\u8BA1\u5212\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u6587\u6863");
1790
+ }
1791
+ const fileName = memberPlanFileName(input.memberDisplayName);
1792
+ const docsDir = taskDocsDir(
1793
+ input.taskId,
1794
+ workspaceApmDir(input.workdir),
1795
+ input.workdir
1796
+ );
1797
+ mkdirSync5(toFsPath(docsDir), { recursive: true });
1798
+ const filePath = join12(docsDir, fileName);
1799
+ const normalized = trimmedContent.endsWith("\n") ? trimmedContent : `${trimmedContent}
1800
+ `;
1801
+ writeFileSync8(toFsPath(filePath), normalized, "utf8");
1802
+ console.log(`[apm] \u5DF2\u4FDD\u5B58\u8BA1\u5212\u6587\u6863: ${fileName}`);
1803
+ return fileName;
1804
+ }
1805
+
1452
1806
  // src/event-session.ts
1453
1807
  var EventSession = class {
1454
1808
  events = [];
@@ -1573,6 +1927,9 @@ var EventSession = class {
1573
1927
  getAssistantText() {
1574
1928
  return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
1575
1929
  }
1930
+ getCreatePlanContent() {
1931
+ return extractCreatePlanFromSessionEvents(this.events);
1932
+ }
1576
1933
  };
1577
1934
 
1578
1935
  // src/commands/connect/abort-signal-debug.ts
@@ -1599,17 +1956,17 @@ function logAbortSignalStats(signal, label) {
1599
1956
  }
1600
1957
 
1601
1958
  // src/commands/connect/agent-task-registry.ts
1602
- import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "node:fs";
1959
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
1603
1960
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
1604
1961
  function registryPath(workdir, taskId) {
1605
1962
  return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
1606
1963
  }
1607
- function readRegistry(path10) {
1608
- if (!existsSync7(path10)) {
1964
+ function readRegistry(path13) {
1965
+ if (!existsSync8(path13)) {
1609
1966
  return {};
1610
1967
  }
1611
1968
  try {
1612
- const parsed = JSON.parse(readFileSync8(path10, "utf8"));
1969
+ const parsed = JSON.parse(readFileSync10(path13, "utf8"));
1613
1970
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1614
1971
  const result = {};
1615
1972
  for (const [key, value] of Object.entries(
@@ -1625,28 +1982,28 @@ function readRegistry(path10) {
1625
1982
  }
1626
1983
  return {};
1627
1984
  }
1628
- function writeRegistry(path10, registry) {
1629
- mkdirSync5(dirname4(path10), { recursive: true });
1630
- writeFileSync7(path10, `${JSON.stringify(registry, null, 2)}
1985
+ function writeRegistry(path13, registry) {
1986
+ mkdirSync6(dirname4(path13), { recursive: true });
1987
+ writeFileSync9(path13, `${JSON.stringify(registry, null, 2)}
1631
1988
  `, "utf8");
1632
1989
  }
1633
1990
  function loadTaskAgentId(workdir, taskId, user) {
1634
1991
  return readRegistry(registryPath(workdir, taskId))[user];
1635
1992
  }
1636
1993
  function saveTaskAgentId(workdir, taskId, user, agentId) {
1637
- const path10 = registryPath(workdir, taskId);
1638
- const registry = readRegistry(path10);
1994
+ const path13 = registryPath(workdir, taskId);
1995
+ const registry = readRegistry(path13);
1639
1996
  registry[user] = agentId;
1640
- writeRegistry(path10, registry);
1997
+ writeRegistry(path13, registry);
1641
1998
  }
1642
1999
  function clearTaskAgentId(workdir, taskId, user) {
1643
- const path10 = registryPath(workdir, taskId);
1644
- const registry = readRegistry(path10);
2000
+ const path13 = registryPath(workdir, taskId);
2001
+ const registry = readRegistry(path13);
1645
2002
  if (!(user in registry)) {
1646
2003
  return;
1647
2004
  }
1648
2005
  delete registry[user];
1649
- writeRegistry(path10, registry);
2006
+ writeRegistry(path13, registry);
1650
2007
  }
1651
2008
 
1652
2009
  // src/commands/connect/cursor-log.ts
@@ -1766,7 +2123,10 @@ async function obtainAgent(ctx) {
1766
2123
  }
1767
2124
  }
1768
2125
  }
1769
- const agent = await Agent.create(agentOptions);
2126
+ const agent = await Agent.create({
2127
+ ...agentOptions,
2128
+ ...ctx.mode ? { mode: ctx.mode } : {}
2129
+ });
1770
2130
  if (ctx.user) {
1771
2131
  saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
1772
2132
  }
@@ -1788,8 +2148,9 @@ async function runCursorAgent(cfg, ctx, options) {
1788
2148
 
1789
2149
  ---
1790
2150
  \u8BF7\u5B8C\u6210\u4E0A\u8FF0\u4EFB\u52A1\u3002\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BF7\u7528 append_mail_reply \u589E\u91CF\u66F4\u65B0\u7ED9\u53D1\u4FE1\u4EBA\u7684\u56DE\u590D\uFF08\u53EF\u5148\u7B80\u77ED\u786E\u8BA4\uFF0C\u6709\u8FDB\u5C55\u7EE7\u7EED\u8FFD\u52A0\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\u3002\u5982\u9700\u7F16\u5199\u6587\u6863\uFF0C\u4FDD\u5B58\u5230 \`.apm/tasks/${ctx.taskId}/docs/\` \u76EE\u5F55\u3002`;
2151
+ const mode = ctx.mode ?? "agent";
1791
2152
  console.log(
1792
- `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} cwd=${workdir}`
2153
+ `[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
1793
2154
  );
1794
2155
  const { agent, resumed } = await obtainAgent({
1795
2156
  apiKey,
@@ -1797,6 +2158,7 @@ async function runCursorAgent(cfg, ctx, options) {
1797
2158
  workdir,
1798
2159
  taskId: ctx.taskId,
1799
2160
  user: ctx.user,
2161
+ mode,
1800
2162
  customTools
1801
2163
  });
1802
2164
  const eventSession = new EventSession(prompt);
@@ -1822,8 +2184,13 @@ async function runCursorAgent(cfg, ctx, options) {
1822
2184
  signal?.addEventListener("abort", abortRun, { once: true });
1823
2185
  const streamEvents = [];
1824
2186
  try {
2187
+ const forceSend = options?.forceSend ?? resumed;
1825
2188
  const run = await agent.send(prompt, {
1826
- local: { customTools }
2189
+ mode,
2190
+ local: {
2191
+ customTools,
2192
+ ...forceSend ? { force: true } : {}
2193
+ }
1827
2194
  });
1828
2195
  activeRun = run;
1829
2196
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
@@ -1839,6 +2206,7 @@ async function runCursorAgent(cfg, ctx, options) {
1839
2206
  await syncRemoteLog.flush(eventSession);
1840
2207
  const result = await run.wait();
1841
2208
  const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
2209
+ const createPlanContent = eventSession.getCreatePlanContent() || collectCreatePlanContent(streamEvents);
1842
2210
  if (result.status === "error") {
1843
2211
  if (resumed) {
1844
2212
  clearTaskAgentId(workdir, ctx.taskId, ctx.user);
@@ -1855,7 +2223,8 @@ async function runCursorAgent(cfg, ctx, options) {
1855
2223
  runId: result.id,
1856
2224
  agentId: agent.agentId,
1857
2225
  status: result.status,
1858
- assistantText: assistantText || result.result || ""
2226
+ assistantText: assistantText || result.result || "",
2227
+ ...createPlanContent ? { createPlanContent } : {}
1859
2228
  };
1860
2229
  } catch (err) {
1861
2230
  if (err instanceof CursorAgentError) {
@@ -1947,14 +2316,13 @@ function createMailReplyDraft() {
1947
2316
  }
1948
2317
 
1949
2318
  // src/commands/connect/task-pull.ts
1950
- import { writeFileSync as writeFileSync9 } from "fs";
1951
- import { join as join12 } from "path";
2319
+ import { writeFileSync as writeFileSync11 } from "fs";
2320
+ import { join as join14 } from "path";
1952
2321
  import { stringify as yamlStringify } from "yaml";
1953
2322
 
1954
2323
  // src/rules-sync.ts
1955
- import { basename as basename2, extname, join as join11 } from "path";
1956
- import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
1957
- var MANIFEST_FILE2 = ".rules-sync-manifest.json";
2324
+ import { basename as basename2, extname, join as join13 } from "path";
2325
+ import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
1958
2326
  function ruleLocalFileName(ruleName) {
1959
2327
  const trimmed = ruleName.trim();
1960
2328
  if (!trimmed) return "rule.md";
@@ -1962,46 +2330,31 @@ function ruleLocalFileName(ruleName) {
1962
2330
  if (extname(sanitized).toLowerCase() === ".md") return sanitized;
1963
2331
  return `${sanitized}.md`;
1964
2332
  }
1965
- function loadManifest(rulesDir) {
1966
- const path10 = join11(rulesDir, MANIFEST_FILE2);
1967
- if (!existsSync8(toFsPath(path10))) {
1968
- return { version: 1, rules: {} };
1969
- }
1970
- try {
1971
- const parsed = JSON.parse(
1972
- readFileSync9(toFsPath(path10), "utf8")
1973
- );
1974
- if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
1975
- return parsed;
1976
- }
1977
- } catch {
1978
- }
1979
- return { version: 1, rules: {} };
1980
- }
1981
- function saveManifest(rulesDir, manifest) {
1982
- writeFileSync8(
1983
- toFsPath(join11(rulesDir, MANIFEST_FILE2)),
1984
- `${JSON.stringify(manifest, null, 2)}
1985
- `,
1986
- "utf8"
1987
- );
1988
- }
1989
2333
  function isBaseRuleFileName(fileName) {
1990
2334
  return listBaseRuleFileNames().includes(basename2(fileName));
1991
2335
  }
2336
+ function findRuleManifestEntry(manifest, rule, fileName) {
2337
+ const manifestPath = toRuleManifestPath(fileName);
2338
+ return (manifest?.documents ?? []).find(
2339
+ (entry) => entry.remoteId === rule.id || entry.path === manifestPath
2340
+ );
2341
+ }
1992
2342
  function isRuleUpToDate(entry, rule, dest) {
1993
- if (!entry || !existsSync8(toFsPath(dest))) return false;
1994
- if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
2343
+ if (!entry || !existsSync9(toFsPath(dest))) return false;
2344
+ if (entry.path !== toRuleManifestPath(ruleLocalFileName(rule.name))) {
2345
+ return false;
2346
+ }
1995
2347
  const updatedAt = rule.updatedAt ?? "";
1996
2348
  if (entry.updatedAt !== updatedAt) return false;
1997
- const localContent = readFileSync9(toFsPath(dest), "utf8");
2349
+ const localContent = readFileSync11(toFsPath(dest), "utf8");
1998
2350
  return localContent === (rule.content ?? "");
1999
2351
  }
2000
2352
  async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2001
2353
  const api = createApmApiClient(cfg);
2002
2354
  const baseline = await api.cli.workspaceBaseline({ workdirPath });
2003
2355
  const repositoryId = baseline.repositoryId;
2004
- const rulesDir = join11(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2356
+ const targetApmRoot = apmRoot ?? workspaceApmDir(workdirPath);
2357
+ const rulesDir = join13(targetApmRoot, "rules");
2005
2358
  await ensureDirExists(rulesDir);
2006
2359
  if (!repositoryId) {
2007
2360
  console.log(
@@ -2010,40 +2363,65 @@ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2010
2363
  return { written: [], skipped: [], removed: [], repositoryId: null };
2011
2364
  }
2012
2365
  const { list } = await api.cli.listRules({ repositoryId });
2013
- const manifest = loadManifest(rulesDir);
2014
- const nextManifest = { version: 1, rules: {} };
2366
+ const manifest = readApmManifest(targetApmRoot);
2015
2367
  const remoteIds = /* @__PURE__ */ new Set();
2016
2368
  const written = [];
2017
2369
  const skipped = [];
2370
+ const ruleEntries = [];
2018
2371
  for (const rule of list) {
2019
2372
  remoteIds.add(rule.id);
2020
2373
  const fileName = ruleLocalFileName(rule.name);
2021
- const dest = join11(rulesDir, fileName);
2022
- const entry = manifest.rules[rule.id];
2374
+ const dest = join13(rulesDir, fileName);
2375
+ const entry = findRuleManifestEntry(manifest, rule, fileName);
2023
2376
  const updatedAt = rule.updatedAt ?? "";
2024
2377
  if (isRuleUpToDate(entry, rule, dest)) {
2025
- nextManifest.rules[rule.id] = entry;
2378
+ ruleEntries.push(entry);
2026
2379
  skipped.push(fileName);
2027
2380
  console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2028
2381
  continue;
2029
2382
  }
2030
- writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
2031
- nextManifest.rules[rule.id] = { fileName, updatedAt };
2383
+ writeFileSync10(toFsPath(dest), rule.content ?? "", "utf8");
2384
+ ruleEntries.push(
2385
+ buildManifestEntryFromFile({
2386
+ path: toRuleManifestPath(fileName),
2387
+ content: rule.content ?? "",
2388
+ updatedAt,
2389
+ remoteId: rule.id
2390
+ })
2391
+ );
2032
2392
  written.push(fileName);
2033
2393
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2034
2394
  }
2035
2395
  const removed = [];
2036
- for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2037
- if (remoteIds.has(ruleId)) continue;
2038
- if (isBaseRuleFileName(entry.fileName)) continue;
2039
- const dest = join11(rulesDir, entry.fileName);
2040
- if (existsSync8(toFsPath(dest))) {
2396
+ for (const entry of manifest?.documents ?? []) {
2397
+ if (!entry.path.startsWith("rules/")) continue;
2398
+ if (!entry.remoteId || remoteIds.has(entry.remoteId)) continue;
2399
+ const fileName = basename2(entry.path);
2400
+ if (isBaseRuleFileName(fileName)) continue;
2401
+ const dest = join13(rulesDir, fileName);
2402
+ if (existsSync9(toFsPath(dest))) {
2041
2403
  rmSync3(toFsPath(dest), { force: true });
2042
2404
  }
2043
- removed.push(entry.fileName);
2044
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
2405
+ removed.push(fileName);
2406
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2407
+ }
2408
+ const coveredPaths = new Set(ruleEntries.map((entry) => entry.path));
2409
+ for (const fileName of readdirSync5(rulesDir)) {
2410
+ const dest = join13(rulesDir, fileName);
2411
+ if (!statSync4(dest).isFile()) continue;
2412
+ const manifestPath = toRuleManifestPath(fileName);
2413
+ if (coveredPaths.has(manifestPath)) continue;
2414
+ ruleEntries.push(
2415
+ buildManifestEntryFromFile({
2416
+ path: manifestPath,
2417
+ content: readFileSync11(toFsPath(dest), "utf8")
2418
+ })
2419
+ );
2045
2420
  }
2046
- saveManifest(rulesDir, nextManifest);
2421
+ writeApmManifest(
2422
+ targetApmRoot,
2423
+ mergeManifestSection(manifest, "rules", ruleEntries, repositoryId)
2424
+ );
2047
2425
  return { written, skipped, removed, repositoryId };
2048
2426
  }
2049
2427
 
@@ -2055,15 +2433,14 @@ async function runTaskPull(cfg, detail) {
2055
2433
  const dir = taskDir(taskId, apmRoot, workdir);
2056
2434
  const docsDir = taskDocsDir(taskId, apmRoot, workdir);
2057
2435
  await ensureDirExists(docsDir);
2058
- writeFileSync9(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
2059
- writeFileSync9(
2436
+ writeFileSync11(
2060
2437
  taskTaskPath(taskId, apmRoot, workdir),
2061
2438
  detail.task.description ?? "",
2062
2439
  "utf8"
2063
2440
  );
2064
2441
  for (const doc of detail.documents) {
2065
2442
  const fileName = documentLocalFileName(doc.name);
2066
- writeFileSync9(join12(docsDir, fileName), doc.content ?? "", "utf8");
2443
+ writeFileSync11(join14(docsDir, fileName), doc.content ?? "", "utf8");
2067
2444
  }
2068
2445
  const members = detail.studio?.members ?? [];
2069
2446
  const taskYaml = yamlStringify(
@@ -2081,7 +2458,7 @@ async function runTaskPull(cfg, detail) {
2081
2458
  },
2082
2459
  { lineWidth: 0 }
2083
2460
  );
2084
- writeFileSync9(
2461
+ writeFileSync11(
2085
2462
  taskYamlPath(taskId, apmRoot, workdir),
2086
2463
  taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
2087
2464
  `,
@@ -2095,6 +2472,9 @@ async function runTaskPull(cfg, detail) {
2095
2472
  }
2096
2473
 
2097
2474
  // src/commands/connect/mail-processor.ts
2475
+ function resolveCursorAgentMode(phase) {
2476
+ return phase === "PLANNING" ? "plan" : "agent";
2477
+ }
2098
2478
  async function processMail(mail, options) {
2099
2479
  const api = createApmApiClient(options.cfg);
2100
2480
  console.log(`[apm] \u5F00\u59CB\u5904\u7406\u4FE1\u4EF6 id=${mail.id}`);
@@ -2151,6 +2531,7 @@ async function processMail(mail, options) {
2151
2531
  hasReplyContent
2152
2532
  } = createMailReplyDraft();
2153
2533
  try {
2534
+ const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
2154
2535
  const result = await runCursorAgent(
2155
2536
  options.cfg,
2156
2537
  {
@@ -2160,7 +2541,8 @@ async function processMail(mail, options) {
2160
2541
  model: detail.recipient.model?.trim() || "default",
2161
2542
  apiKey: cursorApiKey,
2162
2543
  workdir: detail.workdir,
2163
- user: detail.recipient.displayName
2544
+ user: detail.recipient.displayName,
2545
+ mode: cursorMode
2164
2546
  },
2165
2547
  {
2166
2548
  signal: options.signal,
@@ -2171,6 +2553,14 @@ async function processMail(mail, options) {
2171
2553
  if (!replyContent) {
2172
2554
  throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
2173
2555
  }
2556
+ if (cursorMode === "plan" && result.createPlanContent) {
2557
+ saveMemberPlanDocument({
2558
+ workdir: detail.workdir,
2559
+ taskId: detail.taskId,
2560
+ memberDisplayName: detail.recipient.displayName,
2561
+ content: result.createPlanContent
2562
+ });
2563
+ }
2174
2564
  await api.cli.completeMailboxMessage({
2175
2565
  id: detail.id,
2176
2566
  status: "SUCCEEDED",
@@ -2197,6 +2587,7 @@ async function processMail(mail, options) {
2197
2587
  completeErr instanceof Error ? completeErr.message : completeErr
2198
2588
  );
2199
2589
  }
2590
+ markMailReplied(detail.id);
2200
2591
  }
2201
2592
  }
2202
2593
  function createMailProcessor(options) {
@@ -2311,7 +2702,7 @@ function reexecConnect(options) {
2311
2702
  if (server) {
2312
2703
  args.push("--server", server);
2313
2704
  }
2314
- const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
2705
+ const result = spawnSync3(process.execPath, args, { stdio: "inherit" });
2315
2706
  if (result.error) {
2316
2707
  console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
2317
2708
  process.exit(1);
@@ -2439,19 +2830,19 @@ async function runConnect(options) {
2439
2830
  import path5 from "node:path";
2440
2831
 
2441
2832
  // src/commands/deploy/internal/apm-config.ts
2442
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
2833
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
2443
2834
  import { resolve as resolve4 } from "node:path";
2444
2835
  function loadApmConfig(options) {
2445
2836
  const p = resolve4(
2446
2837
  process.cwd(),
2447
2838
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
2448
2839
  );
2449
- if (!existsSync9(p)) {
2840
+ if (!existsSync10(p)) {
2450
2841
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
2451
2842
  process.exit(1);
2452
2843
  }
2453
2844
  try {
2454
- const raw = readFileSync10(p, "utf8");
2845
+ const raw = readFileSync12(p, "utf8");
2455
2846
  return JSON.parse(raw);
2456
2847
  } catch (e) {
2457
2848
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -2565,6 +2956,60 @@ function resolveWisdomDeployFromApmConfig(cfg) {
2565
2956
  remotePath: reqWd(w.remotePath, "remotePath").trim()
2566
2957
  };
2567
2958
  }
2959
+ function reqHc(v, field) {
2960
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2961
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2962
+ process.exit(1);
2963
+ }
2964
+ return v;
2965
+ }
2966
+ function reqHealthPositiveInt(v, field) {
2967
+ const n = Number(v);
2968
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2969
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2970
+ process.exit(1);
2971
+ }
2972
+ return n;
2973
+ }
2974
+ function resolveHealthCheckFromApmConfig(cfg) {
2975
+ const h = cfg.healthCheck ?? {};
2976
+ return {
2977
+ port: reqHealthPositiveInt(h.port, "port"),
2978
+ context: reqHc(h.context, "context").trim(),
2979
+ timeout: reqHealthPositiveInt(h.timeout, "timeout")
2980
+ };
2981
+ }
2982
+ function resolveWisdomJarDeployFromApmConfig(cfg) {
2983
+ const base = resolveWisdomDeployFromApmConfig(cfg);
2984
+ const w = cfg.wisdomDeploy ?? {};
2985
+ const jarPath = reqWd(w.jarPath, "jarPath").trim().replace(/\\/g, "/");
2986
+ const remoteAppDir = posixDirname(jarPath);
2987
+ const projectName = reqTopLevelName(cfg);
2988
+ return {
2989
+ ...base,
2990
+ projectName,
2991
+ jarPath,
2992
+ mavenLocalRepo: reqWd(w.mavenLocalRepo, "mavenLocalRepo").trim(),
2993
+ remoteAppDir,
2994
+ remoteLibDir: `${remoteAppDir}/lib`,
2995
+ startupJar: posixBasename(jarPath),
2996
+ packageName: `${projectName}.jar.zip`,
2997
+ healthCheck: resolveHealthCheckFromApmConfig(cfg)
2998
+ };
2999
+ }
3000
+ function posixDirname(p) {
3001
+ const normalized = p.replace(/\\/g, "/");
3002
+ const idx = normalized.lastIndexOf("/");
3003
+ if (idx <= 0) {
3004
+ return normalized.startsWith("/") ? "/" : ".";
3005
+ }
3006
+ return normalized.slice(0, idx);
3007
+ }
3008
+ function posixBasename(p) {
3009
+ const normalized = p.replace(/\\/g, "/");
3010
+ const idx = normalized.lastIndexOf("/");
3011
+ return idx >= 0 ? normalized.slice(idx + 1) : normalized;
3012
+ }
2568
3013
 
2569
3014
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
2570
3015
  import path4 from "node:path";
@@ -2573,7 +3018,7 @@ import path4 from "node:path";
2573
3018
  import Docker from "dockerode";
2574
3019
 
2575
3020
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
2576
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
3021
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
2577
3022
  import path from "node:path";
2578
3023
  function asOptionalTlsBuffer(value) {
2579
3024
  if (typeof value !== "string") {
@@ -2585,8 +3030,8 @@ function asOptionalTlsBuffer(value) {
2585
3030
  if (normalized === "") {
2586
3031
  return void 0;
2587
3032
  }
2588
- if (existsSync10(normalized)) {
2589
- return readFileSync11(normalized);
3033
+ if (existsSync11(normalized)) {
3034
+ return readFileSync13(normalized);
2590
3035
  }
2591
3036
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
2592
3037
  if (looksLikePath) {
@@ -2796,7 +3241,7 @@ var DockerodeClient = class {
2796
3241
  var createDockerodeClient = (config) => new DockerodeClient(config);
2797
3242
 
2798
3243
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2799
- import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
3244
+ import { existsSync as existsSync12, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
2800
3245
  import path2 from "node:path";
2801
3246
  function stripSurroundingQuotes(value) {
2802
3247
  const t = value.trim();
@@ -2813,10 +3258,10 @@ function loadEnvFromFile(envFilePath) {
2813
3258
  return {};
2814
3259
  }
2815
3260
  const targetPath = path2.resolve(envFilePath);
2816
- if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
3261
+ if (!existsSync12(targetPath) || !statSync5(targetPath).isFile()) {
2817
3262
  return {};
2818
3263
  }
2819
- const raw = readFileSync12(targetPath, "utf-8");
3264
+ const raw = readFileSync14(targetPath, "utf-8");
2820
3265
  const result = {};
2821
3266
  for (const line of raw.split(/\r?\n/)) {
2822
3267
  const normalized = line.trim();
@@ -2881,7 +3326,7 @@ function assertDeployImageTag(tag) {
2881
3326
  }
2882
3327
 
2883
3328
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
2884
- import { platform as platform2 } from "node:os";
3329
+ import { platform as platform3 } from "node:os";
2885
3330
 
2886
3331
  // src/commands/deploy/internal/backend-deploy/command-runner.ts
2887
3332
  import { execSync } from "child_process";
@@ -2941,7 +3386,7 @@ var CommandRunner = class {
2941
3386
 
2942
3387
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
2943
3388
  function dockerBuildPlatformFlags() {
2944
- return platform2() === "darwin" ? ["--platform", "linux/amd64"] : [];
3389
+ return platform3() === "darwin" ? ["--platform", "linux/amd64"] : [];
2945
3390
  }
2946
3391
  function buildDockerImageLocally(params, cwd) {
2947
3392
  const platformFlags = dockerBuildPlatformFlags();
@@ -2987,12 +3432,12 @@ function dockerPushImage(params, cwd) {
2987
3432
  }
2988
3433
 
2989
3434
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2990
- import { existsSync as existsSync12 } from "node:fs";
3435
+ import { existsSync as existsSync13 } from "node:fs";
2991
3436
  import path3 from "node:path";
2992
3437
  function resolveDockerBuildPaths(cwd) {
2993
3438
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2994
3439
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2995
- if (!existsSync12(dockerfilePath)) {
3440
+ if (!existsSync13(dockerfilePath)) {
2996
3441
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2997
3442
  }
2998
3443
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -3121,14 +3566,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
3121
3566
  import path7 from "node:path";
3122
3567
 
3123
3568
  // src/commands/deploy/internal/minio.ts
3124
- import { statSync as statSync5 } from "node:fs";
3569
+ import { statSync as statSync6 } from "node:fs";
3125
3570
  import { readdir, readFile } from "node:fs/promises";
3126
3571
  import path6 from "node:path";
3127
3572
  import * as Minio from "minio";
3128
3573
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
3129
3574
  async function isDirectoryPath(dir) {
3130
3575
  try {
3131
- const st = statSync5(dir);
3576
+ const st = statSync6(dir);
3132
3577
  return st.isDirectory();
3133
3578
  } catch {
3134
3579
  return false;
@@ -3158,7 +3603,7 @@ async function collectFiles(root) {
3158
3603
  if (e.isDirectory()) {
3159
3604
  await walk(abs, rel);
3160
3605
  } else if (e.isFile()) {
3161
- const st = statSync5(abs);
3606
+ const st = statSync6(abs);
3162
3607
  out.push({
3163
3608
  absPath: abs,
3164
3609
  relativePath: rel.replace(/\\/g, "/"),
@@ -3416,18 +3861,47 @@ function registerDeployFrontendCommands(program) {
3416
3861
  );
3417
3862
  }
3418
3863
 
3864
+ // src/commands/deploy/run.ts
3865
+ import path8 from "node:path";
3866
+ function registerDeployRunCommands(program) {
3867
+ program.command("deploy").description(
3868
+ "\u8BFB\u53D6 apm.config.json \u4E2D deploy.<env> \u5E76\u4EE5 shell \u6267\u884C\uFF08\u73AF\u5883\uFF1Atest | online\uFF09"
3869
+ ).argument("<env>", "\u90E8\u7F72\u73AF\u5883\uFF1Atest \u6216 online").option("--cwd <path>", "\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").action(async (env, opts) => {
3870
+ if (!isDeployEnvironment(env)) {
3871
+ console.error(`\u65E0\u6548\u90E8\u7F72\u73AF\u5883 "${env}"\uFF0C\u4EC5\u652F\u6301 test \u6216 online`);
3872
+ process.exit(1);
3873
+ }
3874
+ const workdir = path8.resolve(opts.cwd ?? process.cwd());
3875
+ const command = resolveDeployCommand(workdir, env);
3876
+ if (!command) {
3877
+ console.error(missingDeployCommandMessage(env));
3878
+ process.exit(1);
3879
+ }
3880
+ console.log(`[apm] deploy env=${env} cwd=${workdir}`);
3881
+ console.log(`[apm] deploy command: ${command}`);
3882
+ try {
3883
+ await runDeployShellCommand(command, workdir, { inheritStdio: true });
3884
+ console.log(`[apm] deploy success env=${env}`);
3885
+ } catch (error) {
3886
+ const detail = error instanceof Error ? error.message : String(error);
3887
+ console.error(`[apm] deploy failed env=${env}: ${detail}`);
3888
+ process.exit(1);
3889
+ }
3890
+ });
3891
+ }
3892
+
3419
3893
  // src/commands/deploy/sftp.ts
3420
- import path9 from "node:path";
3894
+ import path10 from "node:path";
3421
3895
 
3422
3896
  // src/commands/deploy/internal/wisdom-sftp.ts
3423
3897
  import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
3424
- import path8 from "node:path";
3898
+ import path9 from "node:path";
3425
3899
  import JSZip from "jszip";
3426
3900
  import SftpClient from "ssh2-sftp-client";
3427
3901
  async function addDirToZip(dir, zipFolder) {
3428
3902
  const entries = await readdir3(dir, { withFileTypes: true });
3429
3903
  for (const entry of entries) {
3430
- const fullPath = path8.join(dir, entry.name);
3904
+ const fullPath = path9.join(dir, entry.name);
3431
3905
  if (entry.isDirectory()) {
3432
3906
  const folder = zipFolder.folder(entry.name);
3433
3907
  if (folder) {
@@ -3542,8 +4016,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
3542
4016
  }
3543
4017
  }
3544
4018
  async function runWisdomSftpDeploy(params) {
3545
- const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
3546
- const resolvedZipPath = path8.resolve(zipPath);
4019
+ const zipPath = path9.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4020
+ const resolvedZipPath = path9.resolve(zipPath);
3547
4021
  let zipSizeBytes = 0;
3548
4022
  try {
3549
4023
  zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
@@ -3584,7 +4058,7 @@ function registerDeploySftpCommands(program) {
3584
4058
  async (opts) => {
3585
4059
  const cfg = loadApmConfig({ configPath: opts.config });
3586
4060
  const settings = resolveWisdomDeployFromApmConfig(cfg);
3587
- const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
4061
+ const root = path10.resolve(process.cwd(), opts.dir || "apps/web/dist");
3588
4062
  if (!await isDirectoryPath(root)) {
3589
4063
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
3590
4064
  process.exit(1);
@@ -3605,11 +4079,501 @@ function registerDeploySftpCommands(program) {
3605
4079
  );
3606
4080
  }
3607
4081
 
4082
+ // src/commands/deploy/wisdom-jar.ts
4083
+ import path12 from "node:path";
4084
+
4085
+ // src/commands/deploy/internal/wisdom-jar-deploy.ts
4086
+ import {
4087
+ existsSync as existsSync14,
4088
+ mkdirSync as mkdirSync7,
4089
+ readFileSync as readFileSync15,
4090
+ readdirSync as readdirSync6,
4091
+ statSync as statSync7,
4092
+ writeFileSync as writeFileSync12
4093
+ } from "node:fs";
4094
+ import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
4095
+ import path11 from "node:path";
4096
+ import JSZip2 from "jszip";
4097
+ import SftpClient2 from "ssh2-sftp-client";
4098
+ var DEFAULT_MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
4099
+ var DEFAULT_MAVEN_PROFILE = "dev";
4100
+ var SPRINGBOOT_SCRIPT = "springboot.sh";
4101
+ function log(message) {
4102
+ const now = /* @__PURE__ */ new Date();
4103
+ const hh = String(now.getHours()).padStart(2, "0");
4104
+ const mm = String(now.getMinutes()).padStart(2, "0");
4105
+ const ss = String(now.getSeconds()).padStart(2, "0");
4106
+ console.log(`[${hh}:${mm}:${ss}] ${message}`);
4107
+ }
4108
+ function fail(message) {
4109
+ log(`ERROR: ${message}`);
4110
+ process.exit(1);
4111
+ }
4112
+ function shellSingleQuote2(value) {
4113
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
4114
+ }
4115
+ function expandPath(pathStr) {
4116
+ return path11.resolve(expandUserHomePath(pathStr));
4117
+ }
4118
+ function relativeKey(projectRoot, filePath) {
4119
+ return path11.relative(projectRoot, path11.resolve(filePath)).split(path11.sep).join("/");
4120
+ }
4121
+ function fileSignature(filePath) {
4122
+ const stat2 = statSync7(filePath);
4123
+ return { size: stat2.size, mtime: stat2.mtimeMs };
4124
+ }
4125
+ function loadManifest(manifestFile) {
4126
+ if (!existsSync14(manifestFile)) {
4127
+ return {};
4128
+ }
4129
+ return JSON.parse(readFileSync15(manifestFile, "utf8"));
4130
+ }
4131
+ function saveManifest(manifestFile, manifest) {
4132
+ const dir = path11.dirname(manifestFile);
4133
+ mkdirSync7(dir, { recursive: true });
4134
+ writeFileSync12(manifestFile, JSON.stringify(manifest, null, 2), "utf8");
4135
+ }
4136
+ function isProjectLibJar(jarName) {
4137
+ return jarName.startsWith("jeecg-");
4138
+ }
4139
+ function shouldUploadLibFile(projectRoot, localPath, remoteSize, manifest) {
4140
+ if (remoteSize === void 0) {
4141
+ return { shouldUpload: false, reason: "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7" };
4142
+ }
4143
+ const localSize = statSync7(localPath).size;
4144
+ if (localSize !== remoteSize) {
4145
+ return {
4146
+ shouldUpload: true,
4147
+ reason: `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`
4148
+ };
4149
+ }
4150
+ const jarName = path11.basename(localPath);
4151
+ if (isProjectLibJar(jarName) && manifest) {
4152
+ const key = relativeKey(projectRoot, localPath);
4153
+ const current = fileSignature(localPath);
4154
+ const previous = manifest[key];
4155
+ if (!previous) {
4156
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55" };
4157
+ }
4158
+ if (previous.size !== current.size) {
4159
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316" };
4160
+ }
4161
+ if (previous.mtime < current.mtime) {
4162
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA" };
4163
+ }
4164
+ }
4165
+ return { shouldUpload: false, reason: "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7" };
4166
+ }
4167
+ function listLibFilesToUpload(projectRoot, localLibDir, remoteStats, manifest) {
4168
+ const entries = [];
4169
+ const jarFiles = readdirSync6(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4170
+ for (const jarName of jarFiles) {
4171
+ const jarPath = path11.join(localLibDir, jarName);
4172
+ const { shouldUpload, reason } = shouldUploadLibFile(
4173
+ projectRoot,
4174
+ jarPath,
4175
+ remoteStats.get(jarName),
4176
+ manifest
4177
+ );
4178
+ if (shouldUpload) {
4179
+ entries.push({ path: jarPath, arcname: jarName, reason });
4180
+ }
4181
+ }
4182
+ return entries;
4183
+ }
4184
+ async function createUpdatePackage(entries, packageName, cacheDir) {
4185
+ mkdirSync7(cacheDir, { recursive: true });
4186
+ const zipPath = path11.join(cacheDir, packageName);
4187
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${packageName}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4188
+ const zip = new JSZip2();
4189
+ for (const entry of entries) {
4190
+ const content = await readFile3(entry.path);
4191
+ zip.file(entry.arcname, content);
4192
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4193
+ }
4194
+ const buffer = await zip.generateAsync({
4195
+ type: "nodebuffer",
4196
+ compression: "DEFLATE",
4197
+ compressionOptions: { level: 6 }
4198
+ });
4199
+ await writeFile2(zipPath, buffer);
4200
+ return zipPath;
4201
+ }
4202
+ function getMvnExecutable() {
4203
+ const candidates = process.platform === "win32" ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
4204
+ for (const name of candidates) {
4205
+ const result = spawnSyncShellCommand(`${name} --version`, {
4206
+ encoding: "utf8"
4207
+ });
4208
+ if (result.status === 0) {
4209
+ return name;
4210
+ }
4211
+ }
4212
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
4213
+ return "mvn";
4214
+ }
4215
+ function runMavenBuild(projectRoot, mavenRepo, profile) {
4216
+ const mvn = getMvnExecutable();
4217
+ const repoArg = quoteShellArg(normalizeMavenLocalRepoPath(mavenRepo));
4218
+ const command = `${mvn} clean package -P${profile} -Dmaven.repo.local=${repoArg} -DskipTests`;
4219
+ log(`\u5F00\u59CB Maven \u6784\u5EFA: ${command}`);
4220
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
4221
+ const result = spawnSyncShellCommand(command, {
4222
+ cwd: projectRoot,
4223
+ stdio: "inherit"
4224
+ });
4225
+ if (result.status !== 0) {
4226
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? "unknown"}`);
4227
+ }
4228
+ }
4229
+ function locateLibDir(projectRoot, mavenModule) {
4230
+ const targetDir = path11.join(projectRoot, mavenModule, "target");
4231
+ if (!existsSync14(targetDir)) {
4232
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4233
+ }
4234
+ const libDir = path11.join(targetDir, "lib");
4235
+ if (!existsSync14(libDir) || !statSync7(libDir).isDirectory()) {
4236
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4237
+ }
4238
+ const libJars = readdirSync6(libDir).filter((name) => name.endsWith(".jar"));
4239
+ if (libJars.length === 0) {
4240
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
4241
+ }
4242
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4243
+ return libDir;
4244
+ }
4245
+ async function getRemoteFileStats(sftp, remoteDir) {
4246
+ const stats = /* @__PURE__ */ new Map();
4247
+ try {
4248
+ const list = await sftp.list(remoteDir);
4249
+ for (const item of list) {
4250
+ if (item.name.endsWith(".jar")) {
4251
+ stats.set(item.name, item.size);
4252
+ }
4253
+ }
4254
+ } catch {
4255
+ }
4256
+ return stats;
4257
+ }
4258
+ async function uploadUpdatePackage(sftp, zipPath, settings) {
4259
+ const remoteDir = settings.remotePath.replace(/\/$/, "");
4260
+ const remotePath = `${remoteDir}/${path11.basename(zipPath)}`;
4261
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4262
+ try {
4263
+ await sftp.put(zipPath, remotePath);
4264
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
4265
+ } catch (err) {
4266
+ const message = err instanceof Error ? err.message : String(err);
4267
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${message}`);
4268
+ }
4269
+ return remotePath;
4270
+ }
4271
+ function updateManifestEntries(projectRoot, manifest, entries) {
4272
+ for (const entry of entries) {
4273
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
4274
+ }
4275
+ return manifest;
4276
+ }
4277
+ function runRemoteCommand(client, command, options = {}) {
4278
+ const { check = true, stream = false, getPty = false } = options;
4279
+ log(`\u8FDC\u7A0B\u6267\u884C: ${command}`);
4280
+ return new Promise((resolve5, reject) => {
4281
+ client.exec(command, { pty: getPty }, (err, execStream) => {
4282
+ if (err) {
4283
+ reject(err);
4284
+ return;
4285
+ }
4286
+ let stdout = "";
4287
+ let stderr = "";
4288
+ execStream.on("data", (data) => {
4289
+ const text = data.toString("utf8");
4290
+ stdout += text;
4291
+ if (stream) {
4292
+ process.stdout.write(text);
4293
+ }
4294
+ });
4295
+ execStream.stderr.on("data", (data) => {
4296
+ const text = data.toString("utf8");
4297
+ stderr += text;
4298
+ if (stream) {
4299
+ process.stdout.write(text);
4300
+ }
4301
+ });
4302
+ execStream.on("close", (code) => {
4303
+ const exitCode = code ?? 0;
4304
+ const out = stdout.trim();
4305
+ const errText = getPty ? "" : stderr.trim();
4306
+ if (!stream) {
4307
+ if (out) {
4308
+ console.log(out);
4309
+ }
4310
+ if (errText) {
4311
+ console.error(errText);
4312
+ }
4313
+ } else if (stdout && !stdout.endsWith("\n")) {
4314
+ console.log();
4315
+ }
4316
+ if (check && exitCode !== 0) {
4317
+ fail(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (exit ${exitCode}): ${command}`);
4318
+ }
4319
+ resolve5({ exitCode, stdout: out, stderr: errText });
4320
+ });
4321
+ });
4322
+ });
4323
+ }
4324
+ async function extractUpdatePackageOnRemote(client, settings, remoteZipPath) {
4325
+ const remoteLibDir = settings.remoteLibDir;
4326
+ const quotedZip = shellSingleQuote2(remoteZipPath);
4327
+ const quotedLib = shellSingleQuote2(remoteLibDir);
4328
+ const script = `
4329
+ set -e
4330
+ TMP=$(mktemp -d)
4331
+ trap 'rm -rf "$TMP"' EXIT
4332
+ unzip -oq ${quotedZip} -d "$TMP"
4333
+ updated=0
4334
+ while IFS= read -r -d '' src; do
4335
+ name=$(basename "$src")
4336
+ dest=${quotedLib}/"$name"
4337
+ if [ -f "$dest" ]; then
4338
+ cp -f "$src" "$dest"
4339
+ echo "\u8986\u76D6: $name"
4340
+ updated=$((updated + 1))
4341
+ else
4342
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
4343
+ fi
4344
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
4345
+ echo "UPDATED_COUNT=$updated"
4346
+ `;
4347
+ const { stdout } = await runRemoteCommand(client, script);
4348
+ const match = stdout.match(/UPDATED_COUNT=(\d+)/);
4349
+ if (!match || match[1] === void 0) {
4350
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
4351
+ \u8F93\u51FA: ${stdout || "(\u7A7A)"}`);
4352
+ return 0;
4353
+ }
4354
+ const updated = Number.parseInt(match[1], 10);
4355
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
4356
+ return updated;
4357
+ }
4358
+ function springbootOutputIndicatesSuccess(action, combined) {
4359
+ const lower = combined.toLowerCase();
4360
+ if (action === "health") {
4361
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
4362
+ }
4363
+ if (action === "start" || action === "restart") {
4364
+ return combined.includes("is starting") || lower.includes("is running");
4365
+ }
4366
+ if (action === "stop") {
4367
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
4368
+ }
4369
+ if (action === "status") {
4370
+ return lower.includes("running") || lower.includes("not running");
4371
+ }
4372
+ return true;
4373
+ }
4374
+ async function runSpringbootAction(client, settings, action, args = []) {
4375
+ if (action === "start" || action === "restart") {
4376
+ if (args.length === 0) {
4377
+ fail(`\u8FDC\u7A0B ${action} \u7F3A\u5C11 jar \u53C2\u6570`);
4378
+ }
4379
+ const jar = args[0].trim().split(/\r?\n/)[0]?.trim() ?? "";
4380
+ if (!jar) {
4381
+ fail(`\u65E0\u6548\u7684 jar \u540D\u79F0: ${JSON.stringify(args[0])}`);
4382
+ }
4383
+ }
4384
+ const quotedAppDir = shellSingleQuote2(settings.remoteAppDir);
4385
+ const scriptArgs = [action, ...args].map(shellSingleQuote2).join(" ");
4386
+ const command = `cd ${quotedAppDir} && ./${SPRINGBOOT_SCRIPT} ${scriptArgs}`;
4387
+ const { exitCode, stdout, stderr } = await runRemoteCommand(client, command, {
4388
+ check: false
4389
+ });
4390
+ const combined = `${stdout}
4391
+ ${stderr}`.trim();
4392
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
4393
+ if (action === "health") {
4394
+ if (exitCode !== 0 || !outputOk) {
4395
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
4396
+ \u547D\u4EE4: ${command}
4397
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4398
+ }
4399
+ return combined;
4400
+ }
4401
+ if (exitCode !== 0 && !outputOk) {
4402
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25: ${args.join(" ")}
4403
+ ${combined}`);
4404
+ }
4405
+ if ((action === "start" || action === "restart") && !outputOk) {
4406
+ fail(
4407
+ `\u8FDC\u7A0B ${action} \u672A\u6210\u529F: ${args.join(" ")}
4408
+ \u547D\u4EE4: ${command}
4409
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`
4410
+ );
4411
+ }
4412
+ return combined;
4413
+ }
4414
+ async function getRunningJar(client, settings) {
4415
+ const combined = await runSpringbootAction(client, settings, "status", [
4416
+ settings.startupJar
4417
+ ]);
4418
+ const text = combined.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").trim().toLowerCase();
4419
+ if (text.includes("not running")) {
4420
+ return null;
4421
+ }
4422
+ if (text.includes("running")) {
4423
+ return settings.startupJar;
4424
+ }
4425
+ return null;
4426
+ }
4427
+ async function healthCheckService(client, settings) {
4428
+ const port = String(settings.healthCheck.port);
4429
+ const context = settings.healthCheck.context.trim();
4430
+ const timeout = String(settings.healthCheck.timeout);
4431
+ log(`\u5065\u5EB7\u68C0\u67E5: springboot.sh health ${port} ${context} ${timeout}`);
4432
+ await runSpringbootAction(client, settings, "health", [
4433
+ port,
4434
+ context,
4435
+ timeout
4436
+ ]);
4437
+ }
4438
+ async function runWisdomJarDeploy(params) {
4439
+ const {
4440
+ projectRoot,
4441
+ deployCacheDir,
4442
+ configPath,
4443
+ settings,
4444
+ mavenModule = DEFAULT_MAVEN_MODULE,
4445
+ mavenProfile = DEFAULT_MAVEN_PROFILE,
4446
+ skipBuild = false
4447
+ } = params;
4448
+ const manifestFile = path11.join(deployCacheDir, "manifest.json");
4449
+ const mavenRepo = expandPath(settings.mavenLocalRepo);
4450
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${settings.projectName} ===`);
4451
+ log(`\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
4452
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4453
+ if (!skipBuild) {
4454
+ runMavenBuild(projectRoot, mavenRepo, mavenProfile);
4455
+ } else {
4456
+ log("\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF08--skip-build\uFF09");
4457
+ }
4458
+ const libDir = locateLibDir(projectRoot, mavenModule);
4459
+ const manifest = loadManifest(manifestFile);
4460
+ const sftp = new SftpClient2();
4461
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${settings.username}@${settings.host}:${settings.port}`);
4462
+ try {
4463
+ await sftp.connect({
4464
+ host: settings.host,
4465
+ port: settings.port,
4466
+ username: settings.username,
4467
+ password: settings.password,
4468
+ readyTimeout: 3e4,
4469
+ tryKeyboard: true
4470
+ });
4471
+ const client = sftp.client;
4472
+ let needRestart = false;
4473
+ let libUploadEntries = [];
4474
+ const remoteLibStats = await getRemoteFileStats(
4475
+ sftp,
4476
+ settings.remoteLibDir
4477
+ );
4478
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
4479
+ libUploadEntries = listLibFilesToUpload(
4480
+ projectRoot,
4481
+ libDir,
4482
+ remoteLibStats,
4483
+ manifest
4484
+ );
4485
+ let updated = 0;
4486
+ if (libUploadEntries.length > 0) {
4487
+ const zipPath = await createUpdatePackage(
4488
+ libUploadEntries,
4489
+ settings.packageName,
4490
+ deployCacheDir
4491
+ );
4492
+ const remoteZipPath = await uploadUpdatePackage(sftp, zipPath, settings);
4493
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
4494
+ updated = await extractUpdatePackageOnRemote(
4495
+ client,
4496
+ settings,
4497
+ remoteZipPath
4498
+ );
4499
+ } else {
4500
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
4501
+ }
4502
+ const runningJar = await getRunningJar(client, settings);
4503
+ needRestart = updated > 0;
4504
+ if (!needRestart && !runningJar) {
4505
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
4506
+ needRestart = true;
4507
+ } else if (!needRestart) {
4508
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
4509
+ }
4510
+ if (needRestart) {
4511
+ log("\u91CD\u542F\u670D\u52A1...");
4512
+ await runSpringbootAction(client, settings, "restart", [
4513
+ settings.startupJar
4514
+ ]);
4515
+ }
4516
+ await healthCheckService(client, settings);
4517
+ if (libUploadEntries.length > 0) {
4518
+ saveManifest(
4519
+ manifestFile,
4520
+ updateManifestEntries(projectRoot, manifest, libUploadEntries)
4521
+ );
4522
+ }
4523
+ } finally {
4524
+ await sftp.end();
4525
+ }
4526
+ log("\u90E8\u7F72\u5B8C\u6210");
4527
+ }
4528
+
4529
+ // src/commands/deploy/wisdom-jar.ts
4530
+ function registerDeployWisdomJarCommands(program) {
4531
+ program.command("deploy-wisdom-jar").description(
4532
+ "Maven \u6784\u5EFA Spring Boot \u9879\u76EE\uFF0C\u589E\u91CF\u4E0A\u4F20 lib JAR \u5E76\u901A\u8FC7 springboot.sh \u91CD\u542F\uFF08\u914D\u7F6E\u89C1 apm.config.json wisdomDeploy / healthCheck\uFF09"
4533
+ ).option(
4534
+ "--config <path>",
4535
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
4536
+ ).option(
4537
+ "--module <path>",
4538
+ "Maven \u6A21\u5757\u76F8\u5BF9\u8DEF\u5F84\uFF08\u9ED8\u8BA4 jeecg-module-system/jeecg-system-start\uFF09",
4539
+ "jeecg-module-system/jeecg-system-start"
4540
+ ).option("--profile <name>", "Maven profile\uFF08\u9ED8\u8BA4 dev\uFF09", "dev").option("--skip-build", "\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF0C\u4EC5\u4E0A\u4F20\u4E0E\u91CD\u542F").action(
4541
+ async (opts) => {
4542
+ const cfg = loadApmConfig({ configPath: opts.config });
4543
+ const settings = resolveWisdomJarDeployFromApmConfig(cfg);
4544
+ const projectRoot = process.cwd();
4545
+ const apmDir = workspaceApmDir(projectRoot);
4546
+ const deployCacheDir = path12.join(apmDir, "deploy", ".deploy_cache");
4547
+ const configPath = path12.resolve(
4548
+ projectRoot,
4549
+ opts.config ?? path12.join(apmDir, "apm.config.json")
4550
+ );
4551
+ try {
4552
+ await runWisdomJarDeploy({
4553
+ projectRoot,
4554
+ deployCacheDir,
4555
+ configPath,
4556
+ settings,
4557
+ mavenModule: opts.module,
4558
+ mavenProfile: opts.profile,
4559
+ skipBuild: Boolean(opts.skipBuild)
4560
+ });
4561
+ } catch (err) {
4562
+ const message = err instanceof Error ? err.message : String(err);
4563
+ console.error("\u90E8\u7F72\u5931\u8D25:", message);
4564
+ process.exit(1);
4565
+ }
4566
+ }
4567
+ );
4568
+ }
4569
+
3608
4570
  // src/commands/deploy/index.ts
3609
4571
  function registerDeployCommands(program) {
4572
+ registerDeployRunCommands(program);
3610
4573
  registerDeployBackendCommands(program);
3611
4574
  registerDeployFrontendCommands(program);
3612
4575
  registerDeploySftpCommands(program);
4576
+ registerDeployWisdomJarCommands(program);
3613
4577
  }
3614
4578
 
3615
4579
  // src/index.ts