ai-project-manage-cli 7.0.2 → 7.0.4

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
  }
@@ -456,6 +453,10 @@ var requestConfig = {
456
453
  method: "PUT",
457
454
  path: "/cli/mailbox-messages/claim"
458
455
  }),
456
+ appendMailboxDraft: defineEndpoint({
457
+ method: "PUT",
458
+ path: "/cli/mailbox-messages/append-draft"
459
+ }),
459
460
  completeMailboxMessage: defineEndpoint({
460
461
  method: "PUT",
461
462
  path: "/cli/mailbox-messages/complete"
@@ -574,66 +575,170 @@ ${diagnostic ?? ""}
574
575
 
575
576
  // src/repository-project-documents-sync.ts
576
577
  import {
577
- existsSync as existsSync3,
578
+ existsSync as existsSync4,
578
579
  readdirSync as readdirSync2,
579
- readFileSync as readFileSync3,
580
+ readFileSync as readFileSync4,
580
581
  rmSync,
581
- writeFileSync as writeFileSync4
582
+ writeFileSync as writeFileSync5
582
583
  } 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");
584
+ import { dirname as dirname2, join as join5, relative, sep } from "path";
585
+
586
+ // src/apm-manifest.ts
587
+ import { createHash } from "crypto";
588
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
589
+ import { join as join4 } from "path";
590
+ var APM_MANIFEST_FILE = "manifest.json";
591
+ var LEGACY_PROJECT_MANIFEST = join4("project", APM_MANIFEST_FILE);
592
+ function apmManifestPath(apmRoot) {
593
+ return join4(apmRoot, APM_MANIFEST_FILE);
587
594
  }
588
- function projectDocumentLocalPath(apmRoot, documentPath) {
589
- const normalized = normalizeLocalDocumentPath(documentPath);
590
- return join4(projectDocumentsDir(apmRoot), ...normalized.split("/"));
595
+ function hashApmFileContent(content) {
596
+ return createHash("sha256").update(content, "utf8").digest("hex");
591
597
  }
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}`);
598
+ function toProjectManifestPath(localPath) {
599
+ const trimmed = localPath.trim().replace(/\\/g, "/");
600
+ return `project/${trimmed}`;
601
+ }
602
+ function fromProjectManifestPath(manifestPath) {
603
+ const prefix = "project/";
604
+ if (!manifestPath.startsWith(prefix)) {
605
+ throw new Error(`\u975E\u6CD5\u9879\u76EE\u6587\u6863 manifest \u8DEF\u5F84: ${manifestPath}`);
600
606
  }
601
- return segments.join("/");
607
+ return manifestPath.slice(prefix.length);
602
608
  }
603
- function readLocalManifest(apmRoot) {
604
- const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
605
- if (!existsSync3(manifestPath)) {
609
+ function toRuleManifestPath(fileName) {
610
+ return `rules/${fileName}`;
611
+ }
612
+ function toSkillManifestPath(skillDirName) {
613
+ return `skills/${skillDirName}/SKILL.md`;
614
+ }
615
+ function manifestSectionPrefix(section) {
616
+ return `${section}/`;
617
+ }
618
+ function readApmManifest(apmRoot) {
619
+ const primary = apmManifestPath(apmRoot);
620
+ if (existsSync3(toFsPath(primary))) {
621
+ return parseApmManifest(readFileSync3(toFsPath(primary), "utf8"));
622
+ }
623
+ const legacy = join4(apmRoot, LEGACY_PROJECT_MANIFEST);
624
+ if (!existsSync3(toFsPath(legacy))) {
606
625
  return null;
607
626
  }
627
+ const legacyManifest = parseApmManifest(readFileSync3(toFsPath(legacy), "utf8"));
628
+ if (!legacyManifest) {
629
+ return null;
630
+ }
631
+ return {
632
+ ...legacyManifest,
633
+ documents: legacyManifest.documents.map((entry) => ({
634
+ ...entry,
635
+ path: entry.path.startsWith("project/") ? entry.path : toProjectManifestPath(entry.path)
636
+ }))
637
+ };
638
+ }
639
+ function parseApmManifest(raw) {
608
640
  try {
609
- return JSON.parse(
610
- readFileSync3(manifestPath, "utf8")
611
- );
641
+ const parsed = JSON.parse(raw);
642
+ if (parsed?.version === 1 && Array.isArray(parsed.documents) && (parsed.repositoryId === null || typeof parsed.repositoryId === "string")) {
643
+ return parsed;
644
+ }
612
645
  } catch {
613
- return null;
614
646
  }
647
+ return null;
648
+ }
649
+ function writeApmManifest(apmRoot, manifest) {
650
+ writeFileSync4(
651
+ toFsPath(apmManifestPath(apmRoot)),
652
+ `${JSON.stringify(
653
+ {
654
+ ...manifest,
655
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
656
+ documents: [...manifest.documents].sort(
657
+ (a, b) => a.path.localeCompare(b.path)
658
+ )
659
+ },
660
+ null,
661
+ 2
662
+ )}
663
+ `,
664
+ "utf8"
665
+ );
666
+ }
667
+ function mergeManifestSection(manifest, section, entries, repositoryId) {
668
+ const prefix = manifestSectionPrefix(section);
669
+ const kept = (manifest?.documents ?? []).filter(
670
+ (entry) => !entry.path.startsWith(prefix)
671
+ );
672
+ return {
673
+ version: 1,
674
+ repositoryId: repositoryId !== void 0 ? repositoryId : manifest?.repositoryId ?? null,
675
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
676
+ documents: [...kept, ...entries]
677
+ };
678
+ }
679
+ function buildManifestEntryFromFile(input) {
680
+ return {
681
+ path: input.path,
682
+ description: input.description ?? null,
683
+ contentHash: hashApmFileContent(input.content),
684
+ updatedAt: input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
685
+ size: Buffer.byteLength(input.content, "utf8"),
686
+ ...input.remoteId ? { remoteId: input.remoteId } : {}
687
+ };
615
688
  }
616
- function diffManifestPaths(remote, local) {
689
+ function diffManifestPaths(remote, local, section) {
690
+ const prefix = manifestSectionPrefix(section);
617
691
  const remoteMap = new Map(
618
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
692
+ (remote?.documents ?? []).filter((doc) => doc.path.startsWith(prefix)).map((doc) => [doc.path, doc.contentHash])
619
693
  );
620
694
  const localMap = new Map(
621
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
695
+ (local?.documents ?? []).filter((doc) => doc.path.startsWith(prefix)).map((doc) => [doc.path, doc.contentHash])
622
696
  );
623
697
  const download = [];
624
- for (const [path10, hash] of remoteMap) {
625
- if (localMap.get(path10) !== hash) {
626
- download.push(path10);
698
+ for (const [path13, hash] of remoteMap) {
699
+ if (localMap.get(path13) !== hash) {
700
+ download.push(path13);
627
701
  }
628
702
  }
629
703
  const deleteLocal = [];
630
- for (const path10 of localMap.keys()) {
631
- if (!remoteMap.has(path10)) {
632
- deleteLocal.push(path10);
704
+ for (const path13 of localMap.keys()) {
705
+ if (!remoteMap.has(path13)) {
706
+ deleteLocal.push(path13);
633
707
  }
634
708
  }
635
709
  return { download, deleteLocal };
636
710
  }
711
+
712
+ // src/repository-project-documents-sync.ts
713
+ function projectDocumentsDir(apmRoot) {
714
+ return join5(apmRoot ?? workspaceApmDir(), "project");
715
+ }
716
+ function projectDocumentLocalPath(apmRoot, documentPath) {
717
+ const normalized = normalizeLocalDocumentPath(documentPath);
718
+ return join5(projectDocumentsDir(apmRoot), ...normalized.split("/"));
719
+ }
720
+ function normalizeLocalDocumentPath(path13) {
721
+ const trimmed = path13.trim().replace(/\\/g, "/");
722
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
723
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path13}`);
724
+ }
725
+ const segments = trimmed.split("/").filter(Boolean);
726
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
727
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path13}`);
728
+ }
729
+ return segments.join("/");
730
+ }
731
+ function toRemoteProjectManifest(repositoryId, remoteManifest) {
732
+ return {
733
+ version: 1,
734
+ repositoryId,
735
+ generatedAt: remoteManifest.generatedAt,
736
+ documents: remoteManifest.documents.map((doc) => ({
737
+ ...doc,
738
+ path: toProjectManifestPath(doc.path)
739
+ }))
740
+ };
741
+ }
637
742
  async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
638
743
  const empty = {
639
744
  synced: false,
@@ -654,10 +759,8 @@ async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
654
759
  workdirPath
655
760
  );
656
761
  if (!repositoryId) {
657
- console.log(
658
- `[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
659
- ${diagnostic ?? ""}`
660
- );
762
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
763
+ ${diagnostic ?? ""}`);
661
764
  return empty;
662
765
  }
663
766
  const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
@@ -665,43 +768,54 @@ ${diagnostic ?? ""}`
665
768
  await ensureDirExists(projectDir);
666
769
  const { manifest: remoteManifest } = await api.cli.getRepositoryProjectDocumentManifest({ repositoryId });
667
770
  if (!remoteManifest) {
668
- console.log(
669
- `[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`
670
- );
771
+ console.log(`[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
671
772
  return { ...empty, repositoryId };
672
773
  }
673
- const localManifest = readLocalManifest(targetApmDir);
774
+ const remoteApmManifest = toRemoteProjectManifest(
775
+ repositoryId,
776
+ remoteManifest
777
+ );
778
+ const localManifest = readApmManifest(targetApmDir);
674
779
  const { download, deleteLocal } = diffManifestPaths(
675
- remoteManifest,
676
- localManifest
780
+ remoteApmManifest,
781
+ localManifest,
782
+ "project"
677
783
  );
678
784
  let downloaded = 0;
679
785
  if (download.length > 0) {
680
786
  const { list } = await api.cli.listRepositoryProjectDocuments({
681
787
  repositoryId,
682
- paths: download.join(",")
788
+ paths: download.map(fromProjectManifestPath).join(",")
683
789
  });
684
790
  for (const doc of list) {
685
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
791
+ const absPath = toFsPath(
792
+ projectDocumentLocalPath(targetApmDir, doc.path)
793
+ );
686
794
  await ensureDirExists(dirname2(absPath));
687
- writeFileSync4(absPath, doc.content, "utf8");
795
+ writeFileSync5(absPath, doc.content, "utf8");
688
796
  downloaded += 1;
689
797
  }
690
798
  }
691
799
  let deleted = 0;
692
- for (const path10 of deleteLocal) {
693
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
694
- if (existsSync3(absPath)) {
800
+ for (const manifestPath of deleteLocal) {
801
+ const absPath = toFsPath(
802
+ projectDocumentLocalPath(
803
+ targetApmDir,
804
+ fromProjectManifestPath(manifestPath)
805
+ )
806
+ );
807
+ if (existsSync4(absPath)) {
695
808
  rmSync(absPath, { force: true });
696
809
  deleted += 1;
697
810
  }
698
811
  }
699
- writeFileSync4(
700
- toFsPath(join4(projectDir, MANIFEST_FILE)),
701
- `${JSON.stringify(remoteManifest, null, 2)}
702
- `,
703
- "utf8"
812
+ const nextManifest = mergeManifestSection(
813
+ localManifest,
814
+ "project",
815
+ remoteApmManifest.documents,
816
+ repositoryId
704
817
  );
818
+ writeApmManifest(targetApmDir, nextManifest);
705
819
  console.log(
706
820
  `[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
707
821
  );
@@ -713,6 +827,162 @@ ${diagnostic ?? ""}`
713
827
  };
714
828
  }
715
829
 
830
+ // src/skills-sync.ts
831
+ import {
832
+ copyFileSync as copyFileSync2,
833
+ cpSync,
834
+ existsSync as existsSync5,
835
+ mkdirSync as mkdirSync3,
836
+ readdirSync as readdirSync3,
837
+ readFileSync as readFileSync5,
838
+ rmSync as rmSync2,
839
+ statSync as statSync2,
840
+ writeFileSync as writeFileSync6
841
+ } from "fs";
842
+ import { join as join6 } from "path";
843
+ var AGENTS_TEMPLATE_PATH = join6(CLI_TEMPLATE_DIR, "AGENTS.md");
844
+ var BASE_SKILLS_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "skills");
845
+ var BASE_RULES_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "rules");
846
+ function sanitizeSkillDirName(name) {
847
+ const trimmed = name.trim();
848
+ if (!trimmed) return "skill";
849
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
850
+ }
851
+ function listBaseSkillDirNames() {
852
+ if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
853
+ return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
854
+ const path13 = join6(BASE_SKILLS_TEMPLATE_DIR, name);
855
+ return statSync2(path13).isDirectory();
856
+ });
857
+ }
858
+ function syncAgentsGuide(apmDir) {
859
+ if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
860
+ mkdirSync3(apmDir, { recursive: true });
861
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join6(apmDir, "AGENTS.md"));
862
+ return true;
863
+ }
864
+ function listBaseRuleFileNames() {
865
+ if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
866
+ return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
867
+ const path13 = join6(BASE_RULES_TEMPLATE_DIR, name);
868
+ return statSync2(path13).isFile();
869
+ });
870
+ }
871
+ function syncBaseRules(rulesDir) {
872
+ mkdirSync3(rulesDir, { recursive: true });
873
+ const names = listBaseRuleFileNames();
874
+ for (const name of names) {
875
+ const src = join6(BASE_RULES_TEMPLATE_DIR, name);
876
+ const dest = join6(rulesDir, name);
877
+ copyFileSync2(src, dest);
878
+ }
879
+ return names;
880
+ }
881
+ function syncBaseSkills(skillsDir) {
882
+ mkdirSync3(skillsDir, { recursive: true });
883
+ const names = listBaseSkillDirNames();
884
+ for (const name of names) {
885
+ const src = join6(BASE_SKILLS_TEMPLATE_DIR, name);
886
+ const dest = join6(skillsDir, name);
887
+ cpSync(src, dest, { recursive: true, force: true });
888
+ }
889
+ return names;
890
+ }
891
+ function syncSupplementarySkills(skillsDir, list) {
892
+ const baseNames = new Set(listBaseSkillDirNames());
893
+ const apiDirNames = /* @__PURE__ */ new Set();
894
+ const written = [];
895
+ const skipped = [];
896
+ for (const skill of list) {
897
+ const dirName = sanitizeSkillDirName(skill.name);
898
+ apiDirNames.add(dirName);
899
+ if (baseNames.has(dirName)) {
900
+ skipped.push(dirName);
901
+ continue;
902
+ }
903
+ const skillDir = join6(skillsDir, dirName);
904
+ mkdirSync3(skillDir, { recursive: true });
905
+ writeFileSync6(join6(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
906
+ written.push(dirName);
907
+ }
908
+ const removed = [];
909
+ if (!existsSync5(skillsDir)) return { written, skipped, removed };
910
+ for (const entry of readdirSync3(skillsDir)) {
911
+ const full = join6(skillsDir, entry);
912
+ if (!statSync2(full).isDirectory()) continue;
913
+ if (baseNames.has(entry)) continue;
914
+ if (apiDirNames.has(entry)) continue;
915
+ rmSync2(full, { recursive: true, force: true });
916
+ removed.push(entry);
917
+ }
918
+ return { written, skipped, removed };
919
+ }
920
+ function syncSkillsManifest(apmDir, supplementarySkills = []) {
921
+ const skillsDir = join6(apmDir, "skills");
922
+ if (!existsSync5(skillsDir)) {
923
+ return;
924
+ }
925
+ const supplementaryByDir = new Map(
926
+ supplementarySkills.map((skill) => [
927
+ sanitizeSkillDirName(skill.name),
928
+ skill
929
+ ])
930
+ );
931
+ const manifest = readApmManifest(apmDir);
932
+ const existingByPath = new Map(
933
+ (manifest?.documents ?? []).filter((entry) => entry.path.startsWith("skills/")).map((entry) => [entry.path, entry])
934
+ );
935
+ const entries = [];
936
+ for (const entry of readdirSync3(skillsDir)) {
937
+ const skillDir = join6(skillsDir, entry);
938
+ if (!statSync2(skillDir).isDirectory()) continue;
939
+ const skillFile = join6(skillDir, "SKILL.md");
940
+ if (!existsSync5(skillFile)) continue;
941
+ const content = readFileSync5(skillFile, "utf8");
942
+ const supplementary = supplementaryByDir.get(entry);
943
+ const manifestPath = toSkillManifestPath(entry);
944
+ const existing = existingByPath.get(manifestPath);
945
+ entries.push(
946
+ buildManifestEntryFromFile({
947
+ path: manifestPath,
948
+ content,
949
+ description: supplementary?.description ?? existing?.description ?? null,
950
+ remoteId: supplementary?.id ?? existing?.remoteId
951
+ })
952
+ );
953
+ }
954
+ writeApmManifest(apmDir, mergeManifestSection(manifest, "skills", entries));
955
+ }
956
+ function syncRulesManifest(apmDir) {
957
+ const rulesDir = join6(apmDir, "rules");
958
+ if (!existsSync5(rulesDir)) {
959
+ return;
960
+ }
961
+ const manifest = readApmManifest(apmDir);
962
+ const existingByPath = new Map(
963
+ (manifest?.documents ?? []).filter((entry) => entry.path.startsWith("rules/")).map((entry) => [entry.path, entry])
964
+ );
965
+ const entries = [];
966
+ for (const fileName of readdirSync3(rulesDir)) {
967
+ const dest = join6(rulesDir, fileName);
968
+ if (!statSync2(dest).isFile()) continue;
969
+ if (fileName === ".rules-sync-manifest.json") continue;
970
+ const manifestPath = toRuleManifestPath(fileName);
971
+ const content = readFileSync5(dest, "utf8");
972
+ const existing = existingByPath.get(manifestPath);
973
+ entries.push(
974
+ buildManifestEntryFromFile({
975
+ path: manifestPath,
976
+ content,
977
+ description: existing?.description ?? null,
978
+ updatedAt: existing?.updatedAt,
979
+ remoteId: existing?.remoteId
980
+ })
981
+ );
982
+ }
983
+ writeApmManifest(apmDir, mergeManifestSection(manifest, "rules", entries));
984
+ }
985
+
716
986
  // src/git-utils.ts
717
987
  import { execFile as execFile2 } from "child_process";
718
988
  import { promisify as promisify2 } from "util";
@@ -789,15 +1059,17 @@ async function ensureWorkspaceInitialized(workdir, options) {
789
1059
  }
790
1060
  const apmDir = workspaceApmDir(workdir);
791
1061
  await copyTemplateFiles(apmDir, workdir);
1062
+ syncRulesManifest(apmDir);
1063
+ syncSkillsManifest(apmDir);
792
1064
  const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
793
1065
  await syncRepositoryProjectDocumentsPull(workdir, apmDir);
794
1066
  const trimmedName = options?.name?.trim();
795
1067
  if (trimmedName) {
796
- const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
797
- const config = readFileSync4(apmConfigPath, "utf8");
1068
+ const apmConfigPath = toFsPath(join7(apmDir, "apm.config.json"));
1069
+ const config = readFileSync6(apmConfigPath, "utf8");
798
1070
  const configJson = JSON.parse(config);
799
1071
  configJson.name = trimmedName;
800
- writeFileSync5(
1072
+ writeFileSync7(
801
1073
  apmConfigPath,
802
1074
  `${JSON.stringify(configJson, null, 2)}
803
1075
  `,
@@ -886,15 +1158,15 @@ async function runLogin(opts) {
886
1158
  import { spawnSync } from "child_process";
887
1159
 
888
1160
  // src/version.ts
889
- import { readFileSync as readFileSync5 } from "fs";
890
- import { dirname as dirname3, join as join6 } from "path";
1161
+ import { readFileSync as readFileSync7 } from "fs";
1162
+ import { dirname as dirname3, join as join8 } from "path";
891
1163
  import { fileURLToPath as fileURLToPath2 } from "url";
892
1164
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
893
1165
  function readCliVersion() {
894
1166
  try {
895
1167
  const dir = dirname3(fileURLToPath2(import.meta.url));
896
- const pkgPath = join6(dir, "..", "package.json");
897
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1168
+ const pkgPath = join8(dir, "..", "package.json");
1169
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
898
1170
  return pkg.version ?? "0.0.0";
899
1171
  } catch {
900
1172
  return "0.0.0";
@@ -967,104 +1239,12 @@ async function runUpdate() {
967
1239
  }
968
1240
 
969
1241
  // 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
1242
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
1243
+ import { join as join9 } from "path";
1064
1244
  async function syncWorkspaceSkills(cfg, workdir) {
1065
1245
  const apmDir = workspaceApmDir(workdir);
1066
1246
  const fsApmDir = toFsPath(apmDir);
1067
- if (!existsSync5(fsApmDir)) {
1247
+ if (!existsSync6(fsApmDir)) {
1068
1248
  throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1069
1249
  }
1070
1250
  const apmStat = statSync3(fsApmDir);
@@ -1076,12 +1256,13 @@ async function syncWorkspaceSkills(cfg, workdir) {
1076
1256
  if (syncAgentsGuide(apmDir)) {
1077
1257
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
1078
1258
  }
1079
- const rulesDir = join8(apmDir, "rules");
1259
+ const rulesDir = join9(apmDir, "rules");
1080
1260
  const ruleNames = syncBaseRules(rulesDir);
1081
1261
  for (const name of ruleNames) {
1082
1262
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
1083
1263
  }
1084
- const skillsDir = join8(apmDir, "skills");
1264
+ syncRulesManifest(apmDir);
1265
+ const skillsDir = join9(apmDir, "skills");
1085
1266
  mkdirSync4(toFsPath(skillsDir), { recursive: true });
1086
1267
  const baseNames = syncBaseSkills(skillsDir);
1087
1268
  for (const name of baseNames) {
@@ -1102,13 +1283,14 @@ async function syncWorkspaceSkills(cfg, workdir) {
1102
1283
  for (const name of removed) {
1103
1284
  console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
1104
1285
  }
1286
+ syncSkillsManifest(apmDir, list);
1105
1287
  console.log(
1106
1288
  `[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
1289
  );
1108
1290
  }
1109
1291
  async function runUpdateSkills() {
1110
1292
  const apmDir = workspaceApmDir();
1111
- if (!existsSync5(apmDir)) {
1293
+ if (!existsSync6(apmDir)) {
1112
1294
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1113
1295
  process.exit(1);
1114
1296
  }
@@ -1121,7 +1303,7 @@ async function runUpdateSkills() {
1121
1303
  }
1122
1304
 
1123
1305
  // src/commands/connect.ts
1124
- import { spawnSync as spawnSync2 } from "child_process";
1306
+ import { spawnSync as spawnSync3 } from "child_process";
1125
1307
  import WebSocket from "ws";
1126
1308
 
1127
1309
  // src/ws/protocol.ts
@@ -1212,15 +1394,104 @@ function validateAgentWsMessage(value, kind) {
1212
1394
  return validateReceivedMailPush(o);
1213
1395
  }
1214
1396
 
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";
1397
+ // src/commands/deploy/internal/deploy-log-syncer.ts
1219
1398
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
1399
+ function createDeployLogSyncer(api, deploymentRunId) {
1400
+ let lastSyncedLog = "";
1401
+ let latestLog = "";
1402
+ const syncIfChanged = async () => {
1403
+ if (!latestLog || latestLog === lastSyncedLog) {
1404
+ return;
1405
+ }
1406
+ await api.cli.syncTaskDeploymentLog({
1407
+ id: deploymentRunId,
1408
+ log: latestLog
1409
+ });
1410
+ lastSyncedLog = latestLog;
1411
+ };
1412
+ const timer = setInterval(() => {
1413
+ void syncIfChanged().catch((error) => {
1414
+ console.error(
1415
+ "[apm] deploy log sync failed:",
1416
+ error instanceof Error ? error.message : String(error)
1417
+ );
1418
+ });
1419
+ }, DEPLOY_LOG_SYNC_INTERVAL_MS);
1420
+ return {
1421
+ updateLog(log2) {
1422
+ latestLog = log2;
1423
+ },
1424
+ async flush() {
1425
+ clearInterval(timer);
1426
+ await syncIfChanged();
1427
+ },
1428
+ dispose() {
1429
+ clearInterval(timer);
1430
+ }
1431
+ };
1432
+ }
1433
+
1434
+ // src/commands/deploy/internal/deploy-shell.ts
1435
+ import { spawn as spawn2 } from "node:child_process";
1436
+ import { readFileSync as readFileSync8 } from "node:fs";
1437
+ import { join as join10 } from "node:path";
1438
+
1439
+ // src/commands/deploy/internal/shell-exec.ts
1440
+ import {
1441
+ spawn,
1442
+ spawnSync as spawnSync2
1443
+ } from "node:child_process";
1444
+ import { platform as platform2 } from "node:os";
1445
+ function spawnShellOptions() {
1446
+ if (platform2() === "win32") {
1447
+ return {
1448
+ shell: process.env.ComSpec ?? "cmd.exe",
1449
+ windowsHide: true
1450
+ };
1451
+ }
1452
+ return { shell: true, windowsHide: true };
1453
+ }
1454
+ function terminateChildProcess(child) {
1455
+ try {
1456
+ if (platform2() === "win32") {
1457
+ child.kill();
1458
+ } else {
1459
+ child.kill("SIGTERM");
1460
+ }
1461
+ } catch {
1462
+ }
1463
+ }
1464
+ function normalizeMavenLocalRepoPath(repoPath) {
1465
+ return repoPath.replace(/\\/g, "/");
1466
+ }
1467
+ function quoteShellArg(value) {
1468
+ if (!/\s/.test(value)) {
1469
+ return value;
1470
+ }
1471
+ if (platform2() === "win32") {
1472
+ return `"${value.replace(/"/g, '""')}"`;
1473
+ }
1474
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
1475
+ }
1476
+ function expandUserHomePath(pathStr) {
1477
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
1478
+ return pathStr.replace(/^~(?=$|[\\/])/, home);
1479
+ }
1480
+ function spawnSyncShellCommand(command, options = {}) {
1481
+ return spawnSync2(command, {
1482
+ ...options,
1483
+ ...spawnShellOptions()
1484
+ });
1485
+ }
1486
+
1487
+ // src/commands/deploy/internal/deploy-shell.ts
1488
+ function isDeployEnvironment(value) {
1489
+ return value === "test" || value === "online";
1490
+ }
1220
1491
  function readDeployConfig(workdir) {
1221
- const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
1492
+ const configPath = join10(workspaceApmDir(workdir), "apm.config.json");
1222
1493
  try {
1223
- const raw = readFileSync6(configPath, "utf8");
1494
+ const raw = readFileSync8(configPath, "utf8");
1224
1495
  const parsed = JSON.parse(raw);
1225
1496
  return parsed.deploy;
1226
1497
  } catch {
@@ -1240,88 +1511,91 @@ function missingDeployCommandMessage(environment) {
1240
1511
  function buildDeployLog(stdout, stderr) {
1241
1512
  return [stdout, stderr].filter(Boolean).join("\n");
1242
1513
  }
1243
- function runShellCommand(command, cwd, signal, onOutput) {
1514
+ async function runDeployShellCommand(command, cwd, options) {
1515
+ const signal = options?.signal ?? new AbortController().signal;
1516
+ const shellOpts = spawnShellOptions();
1517
+ if (options?.inheritStdio) {
1518
+ return new Promise((resolve5, reject) => {
1519
+ const child = spawn2(command, {
1520
+ cwd,
1521
+ env: process.env,
1522
+ stdio: "inherit",
1523
+ ...shellOpts
1524
+ });
1525
+ const onAbort = () => {
1526
+ terminateChildProcess(child);
1527
+ };
1528
+ if (signal.aborted) {
1529
+ onAbort();
1530
+ } else {
1531
+ signal.addEventListener("abort", onAbort, { once: true });
1532
+ }
1533
+ child.on("error", (error) => {
1534
+ signal.removeEventListener("abort", onAbort);
1535
+ reject(error);
1536
+ });
1537
+ child.on("close", (code) => {
1538
+ signal.removeEventListener("abort", onAbort);
1539
+ if (code === 0) {
1540
+ resolve5({ log: "" });
1541
+ return;
1542
+ }
1543
+ reject(new Error(`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`));
1544
+ });
1545
+ });
1546
+ }
1244
1547
  return new Promise((resolve5, reject) => {
1245
- const child = spawn(command, {
1548
+ const child = spawn2(command, {
1246
1549
  cwd,
1247
- shell: true,
1248
1550
  env: process.env,
1249
- windowsHide: true
1551
+ ...shellOpts
1250
1552
  });
1251
1553
  let stdout = "";
1252
1554
  let stderr = "";
1253
1555
  const emitLog = () => {
1254
- onOutput?.(buildDeployLog(stdout, stderr));
1556
+ options?.onOutput?.(buildDeployLog(stdout, stderr));
1255
1557
  };
1256
1558
  const onAbort = () => {
1257
- child.kill("SIGTERM");
1559
+ terminateChildProcess(child);
1258
1560
  };
1259
1561
  if (signal.aborted) {
1260
1562
  onAbort();
1261
1563
  } else {
1262
1564
  signal.addEventListener("abort", onAbort, { once: true });
1263
1565
  }
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
- });
1566
+ if (child.stdout) {
1567
+ child.stdout.on("data", (chunk) => {
1568
+ stdout += String(chunk);
1569
+ emitLog();
1570
+ });
1571
+ }
1572
+ if (child.stderr) {
1573
+ child.stderr.on("data", (chunk) => {
1574
+ stderr += String(chunk);
1575
+ emitLog();
1576
+ });
1577
+ }
1272
1578
  child.on("error", (error) => {
1273
1579
  signal.removeEventListener("abort", onAbort);
1274
1580
  reject(error);
1275
1581
  });
1276
1582
  child.on("close", (code) => {
1277
1583
  signal.removeEventListener("abort", onAbort);
1278
- const log = buildDeployLog(stdout, stderr);
1584
+ const log2 = buildDeployLog(stdout, stderr);
1279
1585
  if (code === 0) {
1280
- resolve5({ log });
1586
+ resolve5({ log: log2 });
1281
1587
  return;
1282
1588
  }
1283
1589
  const error = new Error(
1284
1590
  `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
1285
1591
  );
1286
- error.log = log;
1592
+ error.log = log2;
1287
1593
  reject(error);
1288
1594
  });
1289
1595
  });
1290
1596
  }
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)
1309
- );
1310
- });
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
- };
1324
- }
1597
+
1598
+ // src/commands/connect/deploy-run.ts
1325
1599
  async function handleInboundDeploy(cfg, msg, signal) {
1326
1600
  const api = createApmApiClient(cfg);
1327
1601
  const deploymentRunId = msg.deploymentRunId;
@@ -1331,9 +1605,10 @@ async function handleInboundDeploy(cfg, msg, signal) {
1331
1605
  status: "DEPLOYING"
1332
1606
  });
1333
1607
  const workdir = requireRemoteWorkdir(msg.workdir);
1334
- const command = resolveDeployCommand(workdir, msg.environment);
1608
+ const environment = msg.environment;
1609
+ const command = resolveDeployCommand(workdir, environment);
1335
1610
  if (!command) {
1336
- const error = missingDeployCommandMessage(msg.environment);
1611
+ const error = missingDeployCommandMessage(environment);
1337
1612
  console.error(`[apm] ${error}`);
1338
1613
  await api.cli.completeTaskDeployment({
1339
1614
  id: deploymentRunId,
@@ -1344,34 +1619,37 @@ async function handleInboundDeploy(cfg, msg, signal) {
1344
1619
  return;
1345
1620
  }
1346
1621
  console.log(
1347
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
1622
+ `[apm] deploy start id=${deploymentRunId} env=${environment} cwd=${workdir}`
1348
1623
  );
1349
1624
  console.log(`[apm] deploy command: ${command}`);
1350
1625
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
1351
1626
  let latestLog = "";
1352
1627
  try {
1353
- const { log } = await runShellCommand(command, workdir, signal, (log2) => {
1354
- latestLog = log2;
1355
- logSyncer.updateLog(log2);
1628
+ const { log: log2 } = await runDeployShellCommand(command, workdir, {
1629
+ signal,
1630
+ onOutput: (log3) => {
1631
+ latestLog = log3;
1632
+ logSyncer.updateLog(log3);
1633
+ }
1356
1634
  });
1357
- latestLog = log;
1358
- logSyncer.updateLog(log);
1635
+ latestLog = log2;
1636
+ logSyncer.updateLog(log2);
1359
1637
  await logSyncer.flush();
1360
1638
  await api.cli.completeTaskDeployment({
1361
1639
  id: deploymentRunId,
1362
1640
  status: "SUCCESS",
1363
- log
1641
+ log: log2
1364
1642
  });
1365
1643
  console.log(`[apm] deploy success id=${deploymentRunId}`);
1366
1644
  } catch (error) {
1367
1645
  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);
1646
+ const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
1647
+ logSyncer.updateLog(log2);
1370
1648
  await logSyncer.flush();
1371
1649
  await api.cli.completeTaskDeployment({
1372
1650
  id: deploymentRunId,
1373
1651
  status: "FAILED",
1374
- log,
1652
+ log: log2,
1375
1653
  error: detail
1376
1654
  });
1377
1655
  console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
@@ -1381,10 +1659,10 @@ async function handleInboundDeploy(cfg, msg, signal) {
1381
1659
  }
1382
1660
 
1383
1661
  // 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";
1662
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
1663
+ import { join as join11 } from "path";
1386
1664
  function listLocalMarkdownFiles(docsDir) {
1387
- if (!existsSync6(docsDir)) {
1665
+ if (!existsSync7(docsDir)) {
1388
1666
  return [];
1389
1667
  }
1390
1668
  return readdirSync4(docsDir).filter(
@@ -1399,8 +1677,8 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1399
1677
  });
1400
1678
  }
1401
1679
  async function upsertLocalDocumentFile(api, taskId, docsDir, fileName) {
1402
- const content = readFileSync7(join10(docsDir, fileName), "utf8");
1403
- const name = documentPlatformName(join10(docsDir, fileName));
1680
+ const content = readFileSync9(join11(docsDir, fileName), "utf8");
1681
+ const name = documentPlatformName(join11(docsDir, fileName));
1404
1682
  return api.cli.upsertDocument({
1405
1683
  taskId,
1406
1684
  name,
@@ -1422,7 +1700,7 @@ async function syncTaskDocuments(cfg, taskId, workdir, options) {
1422
1700
  const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ taskId: trimmedTaskId });
1423
1701
  let synced = 0;
1424
1702
  for (const fileName of localFiles) {
1425
- const content = readFileSync7(join10(docsDir, fileName), "utf8");
1703
+ const content = readFileSync9(join11(docsDir, fileName), "utf8");
1426
1704
  const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1427
1705
  if (remote && remote.content === content) {
1428
1706
  continue;
@@ -1450,8 +1728,8 @@ import {
1450
1728
  import { setMaxListeners as setMaxListeners2 } from "node:events";
1451
1729
 
1452
1730
  // src/commands/connect/plan-document.ts
1453
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
1454
- import { join as join11 } from "node:path";
1731
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
1732
+ import { join as join12 } from "node:path";
1455
1733
  function normalizePlanContent(plan) {
1456
1734
  return plan.replace(/\\n/g, "\n").trim();
1457
1735
  }
@@ -1521,10 +1799,10 @@ function saveMemberPlanDocument(input) {
1521
1799
  input.workdir
1522
1800
  );
1523
1801
  mkdirSync5(toFsPath(docsDir), { recursive: true });
1524
- const filePath = join11(docsDir, fileName);
1802
+ const filePath = join12(docsDir, fileName);
1525
1803
  const normalized = trimmedContent.endsWith("\n") ? trimmedContent : `${trimmedContent}
1526
1804
  `;
1527
- writeFileSync7(toFsPath(filePath), normalized, "utf8");
1805
+ writeFileSync8(toFsPath(filePath), normalized, "utf8");
1528
1806
  console.log(`[apm] \u5DF2\u4FDD\u5B58\u8BA1\u5212\u6587\u6863: ${fileName}`);
1529
1807
  return fileName;
1530
1808
  }
@@ -1682,17 +1960,17 @@ function logAbortSignalStats(signal, label) {
1682
1960
  }
1683
1961
 
1684
1962
  // src/commands/connect/agent-task-registry.ts
1685
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
1963
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
1686
1964
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
1687
1965
  function registryPath(workdir, taskId) {
1688
1966
  return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
1689
1967
  }
1690
- function readRegistry(path10) {
1691
- if (!existsSync7(path10)) {
1968
+ function readRegistry(path13) {
1969
+ if (!existsSync8(path13)) {
1692
1970
  return {};
1693
1971
  }
1694
1972
  try {
1695
- const parsed = JSON.parse(readFileSync8(path10, "utf8"));
1973
+ const parsed = JSON.parse(readFileSync10(path13, "utf8"));
1696
1974
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1697
1975
  const result = {};
1698
1976
  for (const [key, value] of Object.entries(
@@ -1708,28 +1986,28 @@ function readRegistry(path10) {
1708
1986
  }
1709
1987
  return {};
1710
1988
  }
1711
- function writeRegistry(path10, registry) {
1712
- mkdirSync6(dirname4(path10), { recursive: true });
1713
- writeFileSync8(path10, `${JSON.stringify(registry, null, 2)}
1989
+ function writeRegistry(path13, registry) {
1990
+ mkdirSync6(dirname4(path13), { recursive: true });
1991
+ writeFileSync9(path13, `${JSON.stringify(registry, null, 2)}
1714
1992
  `, "utf8");
1715
1993
  }
1716
1994
  function loadTaskAgentId(workdir, taskId, user) {
1717
1995
  return readRegistry(registryPath(workdir, taskId))[user];
1718
1996
  }
1719
1997
  function saveTaskAgentId(workdir, taskId, user, agentId) {
1720
- const path10 = registryPath(workdir, taskId);
1721
- const registry = readRegistry(path10);
1998
+ const path13 = registryPath(workdir, taskId);
1999
+ const registry = readRegistry(path13);
1722
2000
  registry[user] = agentId;
1723
- writeRegistry(path10, registry);
2001
+ writeRegistry(path13, registry);
1724
2002
  }
1725
2003
  function clearTaskAgentId(workdir, taskId, user) {
1726
- const path10 = registryPath(workdir, taskId);
1727
- const registry = readRegistry(path10);
2004
+ const path13 = registryPath(workdir, taskId);
2005
+ const registry = readRegistry(path13);
1728
2006
  if (!(user in registry)) {
1729
2007
  return;
1730
2008
  }
1731
2009
  delete registry[user];
1732
- writeRegistry(path10, registry);
2010
+ writeRegistry(path13, registry);
1733
2011
  }
1734
2012
 
1735
2013
  // src/commands/connect/cursor-log.ts
@@ -2007,7 +2285,7 @@ function removeMailById(mailId) {
2007
2285
  }
2008
2286
 
2009
2287
  // src/commands/connect/reply-mail-tool.ts
2010
- function createMailReplyDraft() {
2288
+ function createMailReplyDraft(input) {
2011
2289
  const parts = [];
2012
2290
  const tool = {
2013
2291
  description: "\u5411\u53D1\u4FE1\u4EBA\u589E\u91CF\u66F4\u65B0\u56DE\u590D\u5185\u5BB9\u3002\u53EF\u591A\u6B21\u8C03\u7528\u8FFD\u52A0\u8FDB\u5C55\uFF08\u5982\u5148\u786E\u8BA4\u6536\u5230\u3001\u518D\u6C47\u62A5\u7ED3\u679C\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\uFF0C\u65E0\u9700\u53E6\u884C\u6536\u5C3E\u3002",
@@ -2030,6 +2308,7 @@ function createMailReplyDraft() {
2030
2308
  };
2031
2309
  }
2032
2310
  parts.push(content);
2311
+ await input.appendDraft(content);
2033
2312
  console.log(`[apm] \u589E\u91CF\u66F4\u65B0\u56DE\u4FE1\uFF08\u7B2C ${parts.length} \u6BB5\uFF09`);
2034
2313
  return "\u5DF2\u8FFD\u52A0\u5230\u56DE\u4FE1\u8349\u7A3F\uFF0CAgent \u7ED3\u675F\u540E\u5C06\u4E00\u5E76\u63D0\u4EA4\u3002";
2035
2314
  }
@@ -2042,14 +2321,13 @@ function createMailReplyDraft() {
2042
2321
  }
2043
2322
 
2044
2323
  // src/commands/connect/task-pull.ts
2045
- import { writeFileSync as writeFileSync10 } from "fs";
2046
- import { join as join13 } from "path";
2324
+ import { writeFileSync as writeFileSync11 } from "fs";
2325
+ import { join as join14 } from "path";
2047
2326
  import { stringify as yamlStringify } from "yaml";
2048
2327
 
2049
2328
  // src/rules-sync.ts
2050
- import { basename as basename2, extname, join as join12 } from "path";
2051
- import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
2052
- var MANIFEST_FILE2 = ".rules-sync-manifest.json";
2329
+ import { basename as basename2, extname, join as join13 } from "path";
2330
+ import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
2053
2331
  function ruleLocalFileName(ruleName) {
2054
2332
  const trimmed = ruleName.trim();
2055
2333
  if (!trimmed) return "rule.md";
@@ -2057,46 +2335,31 @@ function ruleLocalFileName(ruleName) {
2057
2335
  if (extname(sanitized).toLowerCase() === ".md") return sanitized;
2058
2336
  return `${sanitized}.md`;
2059
2337
  }
2060
- function loadManifest(rulesDir) {
2061
- const path10 = join12(rulesDir, MANIFEST_FILE2);
2062
- if (!existsSync8(toFsPath(path10))) {
2063
- return { version: 1, rules: {} };
2064
- }
2065
- try {
2066
- const parsed = JSON.parse(
2067
- readFileSync9(toFsPath(path10), "utf8")
2068
- );
2069
- if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
2070
- return parsed;
2071
- }
2072
- } catch {
2073
- }
2074
- return { version: 1, rules: {} };
2075
- }
2076
- function saveManifest(rulesDir, manifest) {
2077
- writeFileSync9(
2078
- toFsPath(join12(rulesDir, MANIFEST_FILE2)),
2079
- `${JSON.stringify(manifest, null, 2)}
2080
- `,
2081
- "utf8"
2082
- );
2083
- }
2084
2338
  function isBaseRuleFileName(fileName) {
2085
2339
  return listBaseRuleFileNames().includes(basename2(fileName));
2086
2340
  }
2341
+ function findRuleManifestEntry(manifest, rule, fileName) {
2342
+ const manifestPath = toRuleManifestPath(fileName);
2343
+ return (manifest?.documents ?? []).find(
2344
+ (entry) => entry.remoteId === rule.id || entry.path === manifestPath
2345
+ );
2346
+ }
2087
2347
  function isRuleUpToDate(entry, rule, dest) {
2088
- if (!entry || !existsSync8(toFsPath(dest))) return false;
2089
- if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
2348
+ if (!entry || !existsSync9(toFsPath(dest))) return false;
2349
+ if (entry.path !== toRuleManifestPath(ruleLocalFileName(rule.name))) {
2350
+ return false;
2351
+ }
2090
2352
  const updatedAt = rule.updatedAt ?? "";
2091
2353
  if (entry.updatedAt !== updatedAt) return false;
2092
- const localContent = readFileSync9(toFsPath(dest), "utf8");
2354
+ const localContent = readFileSync11(toFsPath(dest), "utf8");
2093
2355
  return localContent === (rule.content ?? "");
2094
2356
  }
2095
2357
  async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2096
2358
  const api = createApmApiClient(cfg);
2097
2359
  const baseline = await api.cli.workspaceBaseline({ workdirPath });
2098
2360
  const repositoryId = baseline.repositoryId;
2099
- const rulesDir = join12(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2361
+ const targetApmRoot = apmRoot ?? workspaceApmDir(workdirPath);
2362
+ const rulesDir = join13(targetApmRoot, "rules");
2100
2363
  await ensureDirExists(rulesDir);
2101
2364
  if (!repositoryId) {
2102
2365
  console.log(
@@ -2105,40 +2368,65 @@ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2105
2368
  return { written: [], skipped: [], removed: [], repositoryId: null };
2106
2369
  }
2107
2370
  const { list } = await api.cli.listRules({ repositoryId });
2108
- const manifest = loadManifest(rulesDir);
2109
- const nextManifest = { version: 1, rules: {} };
2371
+ const manifest = readApmManifest(targetApmRoot);
2110
2372
  const remoteIds = /* @__PURE__ */ new Set();
2111
2373
  const written = [];
2112
2374
  const skipped = [];
2375
+ const ruleEntries = [];
2113
2376
  for (const rule of list) {
2114
2377
  remoteIds.add(rule.id);
2115
2378
  const fileName = ruleLocalFileName(rule.name);
2116
- const dest = join12(rulesDir, fileName);
2117
- const entry = manifest.rules[rule.id];
2379
+ const dest = join13(rulesDir, fileName);
2380
+ const entry = findRuleManifestEntry(manifest, rule, fileName);
2118
2381
  const updatedAt = rule.updatedAt ?? "";
2119
2382
  if (isRuleUpToDate(entry, rule, dest)) {
2120
- nextManifest.rules[rule.id] = entry;
2383
+ ruleEntries.push(entry);
2121
2384
  skipped.push(fileName);
2122
2385
  console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2123
2386
  continue;
2124
2387
  }
2125
- writeFileSync9(toFsPath(dest), rule.content ?? "", "utf8");
2126
- nextManifest.rules[rule.id] = { fileName, updatedAt };
2388
+ writeFileSync10(toFsPath(dest), rule.content ?? "", "utf8");
2389
+ ruleEntries.push(
2390
+ buildManifestEntryFromFile({
2391
+ path: toRuleManifestPath(fileName),
2392
+ content: rule.content ?? "",
2393
+ updatedAt,
2394
+ remoteId: rule.id
2395
+ })
2396
+ );
2127
2397
  written.push(fileName);
2128
2398
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2129
2399
  }
2130
2400
  const removed = [];
2131
- for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2132
- if (remoteIds.has(ruleId)) continue;
2133
- if (isBaseRuleFileName(entry.fileName)) continue;
2134
- const dest = join12(rulesDir, entry.fileName);
2135
- if (existsSync8(toFsPath(dest))) {
2401
+ for (const entry of manifest?.documents ?? []) {
2402
+ if (!entry.path.startsWith("rules/")) continue;
2403
+ if (!entry.remoteId || remoteIds.has(entry.remoteId)) continue;
2404
+ const fileName = basename2(entry.path);
2405
+ if (isBaseRuleFileName(fileName)) continue;
2406
+ const dest = join13(rulesDir, fileName);
2407
+ if (existsSync9(toFsPath(dest))) {
2136
2408
  rmSync3(toFsPath(dest), { force: true });
2137
2409
  }
2138
- removed.push(entry.fileName);
2139
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
2410
+ removed.push(fileName);
2411
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2412
+ }
2413
+ const coveredPaths = new Set(ruleEntries.map((entry) => entry.path));
2414
+ for (const fileName of readdirSync5(rulesDir)) {
2415
+ const dest = join13(rulesDir, fileName);
2416
+ if (!statSync4(dest).isFile()) continue;
2417
+ const manifestPath = toRuleManifestPath(fileName);
2418
+ if (coveredPaths.has(manifestPath)) continue;
2419
+ ruleEntries.push(
2420
+ buildManifestEntryFromFile({
2421
+ path: manifestPath,
2422
+ content: readFileSync11(toFsPath(dest), "utf8")
2423
+ })
2424
+ );
2140
2425
  }
2141
- saveManifest(rulesDir, nextManifest);
2426
+ writeApmManifest(
2427
+ targetApmRoot,
2428
+ mergeManifestSection(manifest, "rules", ruleEntries, repositoryId)
2429
+ );
2142
2430
  return { written, skipped, removed, repositoryId };
2143
2431
  }
2144
2432
 
@@ -2150,15 +2438,14 @@ async function runTaskPull(cfg, detail) {
2150
2438
  const dir = taskDir(taskId, apmRoot, workdir);
2151
2439
  const docsDir = taskDocsDir(taskId, apmRoot, workdir);
2152
2440
  await ensureDirExists(docsDir);
2153
- writeFileSync10(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
2154
- writeFileSync10(
2441
+ writeFileSync11(
2155
2442
  taskTaskPath(taskId, apmRoot, workdir),
2156
2443
  detail.task.description ?? "",
2157
2444
  "utf8"
2158
2445
  );
2159
2446
  for (const doc of detail.documents) {
2160
2447
  const fileName = documentLocalFileName(doc.name);
2161
- writeFileSync10(join13(docsDir, fileName), doc.content ?? "", "utf8");
2448
+ writeFileSync11(join14(docsDir, fileName), doc.content ?? "", "utf8");
2162
2449
  }
2163
2450
  const members = detail.studio?.members ?? [];
2164
2451
  const taskYaml = yamlStringify(
@@ -2176,7 +2463,7 @@ async function runTaskPull(cfg, detail) {
2176
2463
  },
2177
2464
  { lineWidth: 0 }
2178
2465
  );
2179
- writeFileSync10(
2466
+ writeFileSync11(
2180
2467
  taskYamlPath(taskId, apmRoot, workdir),
2181
2468
  taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
2182
2469
  `,
@@ -2226,15 +2513,17 @@ async function processMail(mail, options) {
2226
2513
  );
2227
2514
  return;
2228
2515
  }
2229
- if (detail.status !== "PENDING") {
2516
+ if (detail.status !== "UNREAD") {
2230
2517
  console.log(`[apm] \u4FE1\u4EF6 id=${mail.id} \u72B6\u6001\u4E3A ${detail.status}\uFF0C\u8DF3\u8FC7\u5904\u7406`);
2231
- if (detail.status === "SUCCEEDED" || detail.status === "FAILED") {
2518
+ if (detail.status === "READ") {
2232
2519
  markMailReplied(mail.id);
2233
2520
  }
2234
2521
  return;
2235
2522
  }
2523
+ let draftReplyId;
2236
2524
  try {
2237
- await api.cli.claimMailboxMessage({ id: mail.id });
2525
+ const claimed = await api.cli.claimMailboxMessage({ id: mail.id });
2526
+ draftReplyId = claimed.draftReplyId;
2238
2527
  } catch (err) {
2239
2528
  console.error(
2240
2529
  `[apm] \u8BA4\u9886\u4FE1\u4EF6\u5931\u8D25 id=${mail.id}:`,
@@ -2247,7 +2536,11 @@ async function processMail(mail, options) {
2247
2536
  tool: replyTool,
2248
2537
  getReplyContent,
2249
2538
  hasReplyContent
2250
- } = createMailReplyDraft();
2539
+ } = createMailReplyDraft({
2540
+ appendDraft: async (content) => {
2541
+ await api.cli.appendMailboxDraft({ id: draftReplyId, content });
2542
+ }
2543
+ });
2251
2544
  try {
2252
2545
  const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
2253
2546
  const result = await runCursorAgent(
@@ -2280,7 +2573,7 @@ async function processMail(mail, options) {
2280
2573
  });
2281
2574
  }
2282
2575
  await api.cli.completeMailboxMessage({
2283
- id: detail.id,
2576
+ id: draftReplyId,
2284
2577
  status: "SUCCEEDED",
2285
2578
  replyContent
2286
2579
  });
@@ -2295,7 +2588,7 @@ async function processMail(mail, options) {
2295
2588
  console.error(`[apm] \u4FE1\u4EF6\u5904\u7406\u5931\u8D25 id=${mail.id}: ${message}`);
2296
2589
  try {
2297
2590
  await api.cli.completeMailboxMessage({
2298
- id: detail.id,
2591
+ id: draftReplyId,
2299
2592
  status: "FAILED",
2300
2593
  error: message
2301
2594
  });
@@ -2420,7 +2713,7 @@ function reexecConnect(options) {
2420
2713
  if (server) {
2421
2714
  args.push("--server", server);
2422
2715
  }
2423
- const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
2716
+ const result = spawnSync3(process.execPath, args, { stdio: "inherit" });
2424
2717
  if (result.error) {
2425
2718
  console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
2426
2719
  process.exit(1);
@@ -2548,19 +2841,19 @@ async function runConnect(options) {
2548
2841
  import path5 from "node:path";
2549
2842
 
2550
2843
  // src/commands/deploy/internal/apm-config.ts
2551
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
2844
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
2552
2845
  import { resolve as resolve4 } from "node:path";
2553
2846
  function loadApmConfig(options) {
2554
2847
  const p = resolve4(
2555
2848
  process.cwd(),
2556
2849
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
2557
2850
  );
2558
- if (!existsSync9(p)) {
2851
+ if (!existsSync10(p)) {
2559
2852
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
2560
2853
  process.exit(1);
2561
2854
  }
2562
2855
  try {
2563
- const raw = readFileSync10(p, "utf8");
2856
+ const raw = readFileSync12(p, "utf8");
2564
2857
  return JSON.parse(raw);
2565
2858
  } catch (e) {
2566
2859
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -2674,6 +2967,60 @@ function resolveWisdomDeployFromApmConfig(cfg) {
2674
2967
  remotePath: reqWd(w.remotePath, "remotePath").trim()
2675
2968
  };
2676
2969
  }
2970
+ function reqHc(v, field) {
2971
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2972
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2973
+ process.exit(1);
2974
+ }
2975
+ return v;
2976
+ }
2977
+ function reqHealthPositiveInt(v, field) {
2978
+ const n = Number(v);
2979
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2980
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2981
+ process.exit(1);
2982
+ }
2983
+ return n;
2984
+ }
2985
+ function resolveHealthCheckFromApmConfig(cfg) {
2986
+ const h = cfg.healthCheck ?? {};
2987
+ return {
2988
+ port: reqHealthPositiveInt(h.port, "port"),
2989
+ context: reqHc(h.context, "context").trim(),
2990
+ timeout: reqHealthPositiveInt(h.timeout, "timeout")
2991
+ };
2992
+ }
2993
+ function resolveWisdomJarDeployFromApmConfig(cfg) {
2994
+ const base = resolveWisdomDeployFromApmConfig(cfg);
2995
+ const w = cfg.wisdomDeploy ?? {};
2996
+ const jarPath = reqWd(w.jarPath, "jarPath").trim().replace(/\\/g, "/");
2997
+ const remoteAppDir = posixDirname(jarPath);
2998
+ const projectName = reqTopLevelName(cfg);
2999
+ return {
3000
+ ...base,
3001
+ projectName,
3002
+ jarPath,
3003
+ mavenLocalRepo: reqWd(w.mavenLocalRepo, "mavenLocalRepo").trim(),
3004
+ remoteAppDir,
3005
+ remoteLibDir: `${remoteAppDir}/lib`,
3006
+ startupJar: posixBasename(jarPath),
3007
+ packageName: `${projectName}.jar.zip`,
3008
+ healthCheck: resolveHealthCheckFromApmConfig(cfg)
3009
+ };
3010
+ }
3011
+ function posixDirname(p) {
3012
+ const normalized = p.replace(/\\/g, "/");
3013
+ const idx = normalized.lastIndexOf("/");
3014
+ if (idx <= 0) {
3015
+ return normalized.startsWith("/") ? "/" : ".";
3016
+ }
3017
+ return normalized.slice(0, idx);
3018
+ }
3019
+ function posixBasename(p) {
3020
+ const normalized = p.replace(/\\/g, "/");
3021
+ const idx = normalized.lastIndexOf("/");
3022
+ return idx >= 0 ? normalized.slice(idx + 1) : normalized;
3023
+ }
2677
3024
 
2678
3025
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
2679
3026
  import path4 from "node:path";
@@ -2682,7 +3029,7 @@ import path4 from "node:path";
2682
3029
  import Docker from "dockerode";
2683
3030
 
2684
3031
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
2685
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
3032
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
2686
3033
  import path from "node:path";
2687
3034
  function asOptionalTlsBuffer(value) {
2688
3035
  if (typeof value !== "string") {
@@ -2694,8 +3041,8 @@ function asOptionalTlsBuffer(value) {
2694
3041
  if (normalized === "") {
2695
3042
  return void 0;
2696
3043
  }
2697
- if (existsSync10(normalized)) {
2698
- return readFileSync11(normalized);
3044
+ if (existsSync11(normalized)) {
3045
+ return readFileSync13(normalized);
2699
3046
  }
2700
3047
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
2701
3048
  if (looksLikePath) {
@@ -2905,7 +3252,7 @@ var DockerodeClient = class {
2905
3252
  var createDockerodeClient = (config) => new DockerodeClient(config);
2906
3253
 
2907
3254
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2908
- import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
3255
+ import { existsSync as existsSync12, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
2909
3256
  import path2 from "node:path";
2910
3257
  function stripSurroundingQuotes(value) {
2911
3258
  const t = value.trim();
@@ -2922,10 +3269,10 @@ function loadEnvFromFile(envFilePath) {
2922
3269
  return {};
2923
3270
  }
2924
3271
  const targetPath = path2.resolve(envFilePath);
2925
- if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
3272
+ if (!existsSync12(targetPath) || !statSync5(targetPath).isFile()) {
2926
3273
  return {};
2927
3274
  }
2928
- const raw = readFileSync12(targetPath, "utf-8");
3275
+ const raw = readFileSync14(targetPath, "utf-8");
2929
3276
  const result = {};
2930
3277
  for (const line of raw.split(/\r?\n/)) {
2931
3278
  const normalized = line.trim();
@@ -2990,7 +3337,7 @@ function assertDeployImageTag(tag) {
2990
3337
  }
2991
3338
 
2992
3339
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
2993
- import { platform as platform2 } from "node:os";
3340
+ import { platform as platform3 } from "node:os";
2994
3341
 
2995
3342
  // src/commands/deploy/internal/backend-deploy/command-runner.ts
2996
3343
  import { execSync } from "child_process";
@@ -3050,7 +3397,7 @@ var CommandRunner = class {
3050
3397
 
3051
3398
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
3052
3399
  function dockerBuildPlatformFlags() {
3053
- return platform2() === "darwin" ? ["--platform", "linux/amd64"] : [];
3400
+ return platform3() === "darwin" ? ["--platform", "linux/amd64"] : [];
3054
3401
  }
3055
3402
  function buildDockerImageLocally(params, cwd) {
3056
3403
  const platformFlags = dockerBuildPlatformFlags();
@@ -3096,12 +3443,12 @@ function dockerPushImage(params, cwd) {
3096
3443
  }
3097
3444
 
3098
3445
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
3099
- import { existsSync as existsSync12 } from "node:fs";
3446
+ import { existsSync as existsSync13 } from "node:fs";
3100
3447
  import path3 from "node:path";
3101
3448
  function resolveDockerBuildPaths(cwd) {
3102
3449
  const dockerfilePath = path3.join(cwd, "Dockerfile");
3103
3450
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
3104
- if (!existsSync12(dockerfilePath)) {
3451
+ if (!existsSync13(dockerfilePath)) {
3105
3452
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
3106
3453
  }
3107
3454
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -3230,14 +3577,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
3230
3577
  import path7 from "node:path";
3231
3578
 
3232
3579
  // src/commands/deploy/internal/minio.ts
3233
- import { statSync as statSync5 } from "node:fs";
3580
+ import { statSync as statSync6 } from "node:fs";
3234
3581
  import { readdir, readFile } from "node:fs/promises";
3235
3582
  import path6 from "node:path";
3236
3583
  import * as Minio from "minio";
3237
3584
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
3238
3585
  async function isDirectoryPath(dir) {
3239
3586
  try {
3240
- const st = statSync5(dir);
3587
+ const st = statSync6(dir);
3241
3588
  return st.isDirectory();
3242
3589
  } catch {
3243
3590
  return false;
@@ -3267,7 +3614,7 @@ async function collectFiles(root) {
3267
3614
  if (e.isDirectory()) {
3268
3615
  await walk(abs, rel);
3269
3616
  } else if (e.isFile()) {
3270
- const st = statSync5(abs);
3617
+ const st = statSync6(abs);
3271
3618
  out.push({
3272
3619
  absPath: abs,
3273
3620
  relativePath: rel.replace(/\\/g, "/"),
@@ -3525,18 +3872,47 @@ function registerDeployFrontendCommands(program) {
3525
3872
  );
3526
3873
  }
3527
3874
 
3875
+ // src/commands/deploy/run.ts
3876
+ import path8 from "node:path";
3877
+ function registerDeployRunCommands(program) {
3878
+ program.command("deploy").description(
3879
+ "\u8BFB\u53D6 apm.config.json \u4E2D deploy.<env> \u5E76\u4EE5 shell \u6267\u884C\uFF08\u73AF\u5883\uFF1Atest | online\uFF09"
3880
+ ).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) => {
3881
+ if (!isDeployEnvironment(env)) {
3882
+ console.error(`\u65E0\u6548\u90E8\u7F72\u73AF\u5883 "${env}"\uFF0C\u4EC5\u652F\u6301 test \u6216 online`);
3883
+ process.exit(1);
3884
+ }
3885
+ const workdir = path8.resolve(opts.cwd ?? process.cwd());
3886
+ const command = resolveDeployCommand(workdir, env);
3887
+ if (!command) {
3888
+ console.error(missingDeployCommandMessage(env));
3889
+ process.exit(1);
3890
+ }
3891
+ console.log(`[apm] deploy env=${env} cwd=${workdir}`);
3892
+ console.log(`[apm] deploy command: ${command}`);
3893
+ try {
3894
+ await runDeployShellCommand(command, workdir, { inheritStdio: true });
3895
+ console.log(`[apm] deploy success env=${env}`);
3896
+ } catch (error) {
3897
+ const detail = error instanceof Error ? error.message : String(error);
3898
+ console.error(`[apm] deploy failed env=${env}: ${detail}`);
3899
+ process.exit(1);
3900
+ }
3901
+ });
3902
+ }
3903
+
3528
3904
  // src/commands/deploy/sftp.ts
3529
- import path9 from "node:path";
3905
+ import path10 from "node:path";
3530
3906
 
3531
3907
  // src/commands/deploy/internal/wisdom-sftp.ts
3532
3908
  import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
3533
- import path8 from "node:path";
3909
+ import path9 from "node:path";
3534
3910
  import JSZip from "jszip";
3535
3911
  import SftpClient from "ssh2-sftp-client";
3536
3912
  async function addDirToZip(dir, zipFolder) {
3537
3913
  const entries = await readdir3(dir, { withFileTypes: true });
3538
3914
  for (const entry of entries) {
3539
- const fullPath = path8.join(dir, entry.name);
3915
+ const fullPath = path9.join(dir, entry.name);
3540
3916
  if (entry.isDirectory()) {
3541
3917
  const folder = zipFolder.folder(entry.name);
3542
3918
  if (folder) {
@@ -3651,8 +4027,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
3651
4027
  }
3652
4028
  }
3653
4029
  async function runWisdomSftpDeploy(params) {
3654
- const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
3655
- const resolvedZipPath = path8.resolve(zipPath);
4030
+ const zipPath = path9.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4031
+ const resolvedZipPath = path9.resolve(zipPath);
3656
4032
  let zipSizeBytes = 0;
3657
4033
  try {
3658
4034
  zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
@@ -3693,7 +4069,7 @@ function registerDeploySftpCommands(program) {
3693
4069
  async (opts) => {
3694
4070
  const cfg = loadApmConfig({ configPath: opts.config });
3695
4071
  const settings = resolveWisdomDeployFromApmConfig(cfg);
3696
- const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
4072
+ const root = path10.resolve(process.cwd(), opts.dir || "apps/web/dist");
3697
4073
  if (!await isDirectoryPath(root)) {
3698
4074
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
3699
4075
  process.exit(1);
@@ -3714,11 +4090,501 @@ function registerDeploySftpCommands(program) {
3714
4090
  );
3715
4091
  }
3716
4092
 
4093
+ // src/commands/deploy/wisdom-jar.ts
4094
+ import path12 from "node:path";
4095
+
4096
+ // src/commands/deploy/internal/wisdom-jar-deploy.ts
4097
+ import {
4098
+ existsSync as existsSync14,
4099
+ mkdirSync as mkdirSync7,
4100
+ readFileSync as readFileSync15,
4101
+ readdirSync as readdirSync6,
4102
+ statSync as statSync7,
4103
+ writeFileSync as writeFileSync12
4104
+ } from "node:fs";
4105
+ import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
4106
+ import path11 from "node:path";
4107
+ import JSZip2 from "jszip";
4108
+ import SftpClient2 from "ssh2-sftp-client";
4109
+ var DEFAULT_MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
4110
+ var DEFAULT_MAVEN_PROFILE = "dev";
4111
+ var SPRINGBOOT_SCRIPT = "springboot.sh";
4112
+ function log(message) {
4113
+ const now = /* @__PURE__ */ new Date();
4114
+ const hh = String(now.getHours()).padStart(2, "0");
4115
+ const mm = String(now.getMinutes()).padStart(2, "0");
4116
+ const ss = String(now.getSeconds()).padStart(2, "0");
4117
+ console.log(`[${hh}:${mm}:${ss}] ${message}`);
4118
+ }
4119
+ function fail(message) {
4120
+ log(`ERROR: ${message}`);
4121
+ process.exit(1);
4122
+ }
4123
+ function shellSingleQuote2(value) {
4124
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
4125
+ }
4126
+ function expandPath(pathStr) {
4127
+ return path11.resolve(expandUserHomePath(pathStr));
4128
+ }
4129
+ function relativeKey(projectRoot, filePath) {
4130
+ return path11.relative(projectRoot, path11.resolve(filePath)).split(path11.sep).join("/");
4131
+ }
4132
+ function fileSignature(filePath) {
4133
+ const stat2 = statSync7(filePath);
4134
+ return { size: stat2.size, mtime: stat2.mtimeMs };
4135
+ }
4136
+ function loadManifest(manifestFile) {
4137
+ if (!existsSync14(manifestFile)) {
4138
+ return {};
4139
+ }
4140
+ return JSON.parse(readFileSync15(manifestFile, "utf8"));
4141
+ }
4142
+ function saveManifest(manifestFile, manifest) {
4143
+ const dir = path11.dirname(manifestFile);
4144
+ mkdirSync7(dir, { recursive: true });
4145
+ writeFileSync12(manifestFile, JSON.stringify(manifest, null, 2), "utf8");
4146
+ }
4147
+ function isProjectLibJar(jarName) {
4148
+ return jarName.startsWith("jeecg-");
4149
+ }
4150
+ function shouldUploadLibFile(projectRoot, localPath, remoteSize, manifest) {
4151
+ if (remoteSize === void 0) {
4152
+ return { shouldUpload: false, reason: "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7" };
4153
+ }
4154
+ const localSize = statSync7(localPath).size;
4155
+ if (localSize !== remoteSize) {
4156
+ return {
4157
+ shouldUpload: true,
4158
+ reason: `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`
4159
+ };
4160
+ }
4161
+ const jarName = path11.basename(localPath);
4162
+ if (isProjectLibJar(jarName) && manifest) {
4163
+ const key = relativeKey(projectRoot, localPath);
4164
+ const current = fileSignature(localPath);
4165
+ const previous = manifest[key];
4166
+ if (!previous) {
4167
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55" };
4168
+ }
4169
+ if (previous.size !== current.size) {
4170
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316" };
4171
+ }
4172
+ if (previous.mtime < current.mtime) {
4173
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA" };
4174
+ }
4175
+ }
4176
+ return { shouldUpload: false, reason: "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7" };
4177
+ }
4178
+ function listLibFilesToUpload(projectRoot, localLibDir, remoteStats, manifest) {
4179
+ const entries = [];
4180
+ const jarFiles = readdirSync6(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4181
+ for (const jarName of jarFiles) {
4182
+ const jarPath = path11.join(localLibDir, jarName);
4183
+ const { shouldUpload, reason } = shouldUploadLibFile(
4184
+ projectRoot,
4185
+ jarPath,
4186
+ remoteStats.get(jarName),
4187
+ manifest
4188
+ );
4189
+ if (shouldUpload) {
4190
+ entries.push({ path: jarPath, arcname: jarName, reason });
4191
+ }
4192
+ }
4193
+ return entries;
4194
+ }
4195
+ async function createUpdatePackage(entries, packageName, cacheDir) {
4196
+ mkdirSync7(cacheDir, { recursive: true });
4197
+ const zipPath = path11.join(cacheDir, packageName);
4198
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${packageName}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4199
+ const zip = new JSZip2();
4200
+ for (const entry of entries) {
4201
+ const content = await readFile3(entry.path);
4202
+ zip.file(entry.arcname, content);
4203
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4204
+ }
4205
+ const buffer = await zip.generateAsync({
4206
+ type: "nodebuffer",
4207
+ compression: "DEFLATE",
4208
+ compressionOptions: { level: 6 }
4209
+ });
4210
+ await writeFile2(zipPath, buffer);
4211
+ return zipPath;
4212
+ }
4213
+ function getMvnExecutable() {
4214
+ const candidates = process.platform === "win32" ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
4215
+ for (const name of candidates) {
4216
+ const result = spawnSyncShellCommand(`${name} --version`, {
4217
+ encoding: "utf8"
4218
+ });
4219
+ if (result.status === 0) {
4220
+ return name;
4221
+ }
4222
+ }
4223
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
4224
+ return "mvn";
4225
+ }
4226
+ function runMavenBuild(projectRoot, mavenRepo, profile) {
4227
+ const mvn = getMvnExecutable();
4228
+ const repoArg = quoteShellArg(normalizeMavenLocalRepoPath(mavenRepo));
4229
+ const command = `${mvn} clean package -P${profile} -Dmaven.repo.local=${repoArg} -DskipTests`;
4230
+ log(`\u5F00\u59CB Maven \u6784\u5EFA: ${command}`);
4231
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
4232
+ const result = spawnSyncShellCommand(command, {
4233
+ cwd: projectRoot,
4234
+ stdio: "inherit"
4235
+ });
4236
+ if (result.status !== 0) {
4237
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? "unknown"}`);
4238
+ }
4239
+ }
4240
+ function locateLibDir(projectRoot, mavenModule) {
4241
+ const targetDir = path11.join(projectRoot, mavenModule, "target");
4242
+ if (!existsSync14(targetDir)) {
4243
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4244
+ }
4245
+ const libDir = path11.join(targetDir, "lib");
4246
+ if (!existsSync14(libDir) || !statSync7(libDir).isDirectory()) {
4247
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4248
+ }
4249
+ const libJars = readdirSync6(libDir).filter((name) => name.endsWith(".jar"));
4250
+ if (libJars.length === 0) {
4251
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
4252
+ }
4253
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4254
+ return libDir;
4255
+ }
4256
+ async function getRemoteFileStats(sftp, remoteDir) {
4257
+ const stats = /* @__PURE__ */ new Map();
4258
+ try {
4259
+ const list = await sftp.list(remoteDir);
4260
+ for (const item of list) {
4261
+ if (item.name.endsWith(".jar")) {
4262
+ stats.set(item.name, item.size);
4263
+ }
4264
+ }
4265
+ } catch {
4266
+ }
4267
+ return stats;
4268
+ }
4269
+ async function uploadUpdatePackage(sftp, zipPath, settings) {
4270
+ const remoteDir = settings.remotePath.replace(/\/$/, "");
4271
+ const remotePath = `${remoteDir}/${path11.basename(zipPath)}`;
4272
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4273
+ try {
4274
+ await sftp.put(zipPath, remotePath);
4275
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
4276
+ } catch (err) {
4277
+ const message = err instanceof Error ? err.message : String(err);
4278
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${message}`);
4279
+ }
4280
+ return remotePath;
4281
+ }
4282
+ function updateManifestEntries(projectRoot, manifest, entries) {
4283
+ for (const entry of entries) {
4284
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
4285
+ }
4286
+ return manifest;
4287
+ }
4288
+ function runRemoteCommand(client, command, options = {}) {
4289
+ const { check = true, stream = false, getPty = false } = options;
4290
+ log(`\u8FDC\u7A0B\u6267\u884C: ${command}`);
4291
+ return new Promise((resolve5, reject) => {
4292
+ client.exec(command, { pty: getPty }, (err, execStream) => {
4293
+ if (err) {
4294
+ reject(err);
4295
+ return;
4296
+ }
4297
+ let stdout = "";
4298
+ let stderr = "";
4299
+ execStream.on("data", (data) => {
4300
+ const text = data.toString("utf8");
4301
+ stdout += text;
4302
+ if (stream) {
4303
+ process.stdout.write(text);
4304
+ }
4305
+ });
4306
+ execStream.stderr.on("data", (data) => {
4307
+ const text = data.toString("utf8");
4308
+ stderr += text;
4309
+ if (stream) {
4310
+ process.stdout.write(text);
4311
+ }
4312
+ });
4313
+ execStream.on("close", (code) => {
4314
+ const exitCode = code ?? 0;
4315
+ const out = stdout.trim();
4316
+ const errText = getPty ? "" : stderr.trim();
4317
+ if (!stream) {
4318
+ if (out) {
4319
+ console.log(out);
4320
+ }
4321
+ if (errText) {
4322
+ console.error(errText);
4323
+ }
4324
+ } else if (stdout && !stdout.endsWith("\n")) {
4325
+ console.log();
4326
+ }
4327
+ if (check && exitCode !== 0) {
4328
+ fail(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (exit ${exitCode}): ${command}`);
4329
+ }
4330
+ resolve5({ exitCode, stdout: out, stderr: errText });
4331
+ });
4332
+ });
4333
+ });
4334
+ }
4335
+ async function extractUpdatePackageOnRemote(client, settings, remoteZipPath) {
4336
+ const remoteLibDir = settings.remoteLibDir;
4337
+ const quotedZip = shellSingleQuote2(remoteZipPath);
4338
+ const quotedLib = shellSingleQuote2(remoteLibDir);
4339
+ const script = `
4340
+ set -e
4341
+ TMP=$(mktemp -d)
4342
+ trap 'rm -rf "$TMP"' EXIT
4343
+ unzip -oq ${quotedZip} -d "$TMP"
4344
+ updated=0
4345
+ while IFS= read -r -d '' src; do
4346
+ name=$(basename "$src")
4347
+ dest=${quotedLib}/"$name"
4348
+ if [ -f "$dest" ]; then
4349
+ cp -f "$src" "$dest"
4350
+ echo "\u8986\u76D6: $name"
4351
+ updated=$((updated + 1))
4352
+ else
4353
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
4354
+ fi
4355
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
4356
+ echo "UPDATED_COUNT=$updated"
4357
+ `;
4358
+ const { stdout } = await runRemoteCommand(client, script);
4359
+ const match = stdout.match(/UPDATED_COUNT=(\d+)/);
4360
+ if (!match || match[1] === void 0) {
4361
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
4362
+ \u8F93\u51FA: ${stdout || "(\u7A7A)"}`);
4363
+ return 0;
4364
+ }
4365
+ const updated = Number.parseInt(match[1], 10);
4366
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
4367
+ return updated;
4368
+ }
4369
+ function springbootOutputIndicatesSuccess(action, combined) {
4370
+ const lower = combined.toLowerCase();
4371
+ if (action === "health") {
4372
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
4373
+ }
4374
+ if (action === "start" || action === "restart") {
4375
+ return combined.includes("is starting") || lower.includes("is running");
4376
+ }
4377
+ if (action === "stop") {
4378
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
4379
+ }
4380
+ if (action === "status") {
4381
+ return lower.includes("running") || lower.includes("not running");
4382
+ }
4383
+ return true;
4384
+ }
4385
+ async function runSpringbootAction(client, settings, action, args = []) {
4386
+ if (action === "start" || action === "restart") {
4387
+ if (args.length === 0) {
4388
+ fail(`\u8FDC\u7A0B ${action} \u7F3A\u5C11 jar \u53C2\u6570`);
4389
+ }
4390
+ const jar = args[0].trim().split(/\r?\n/)[0]?.trim() ?? "";
4391
+ if (!jar) {
4392
+ fail(`\u65E0\u6548\u7684 jar \u540D\u79F0: ${JSON.stringify(args[0])}`);
4393
+ }
4394
+ }
4395
+ const quotedAppDir = shellSingleQuote2(settings.remoteAppDir);
4396
+ const scriptArgs = [action, ...args].map(shellSingleQuote2).join(" ");
4397
+ const command = `cd ${quotedAppDir} && ./${SPRINGBOOT_SCRIPT} ${scriptArgs}`;
4398
+ const { exitCode, stdout, stderr } = await runRemoteCommand(client, command, {
4399
+ check: false
4400
+ });
4401
+ const combined = `${stdout}
4402
+ ${stderr}`.trim();
4403
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
4404
+ if (action === "health") {
4405
+ if (exitCode !== 0 || !outputOk) {
4406
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
4407
+ \u547D\u4EE4: ${command}
4408
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4409
+ }
4410
+ return combined;
4411
+ }
4412
+ if (exitCode !== 0 && !outputOk) {
4413
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25: ${args.join(" ")}
4414
+ ${combined}`);
4415
+ }
4416
+ if ((action === "start" || action === "restart") && !outputOk) {
4417
+ fail(
4418
+ `\u8FDC\u7A0B ${action} \u672A\u6210\u529F: ${args.join(" ")}
4419
+ \u547D\u4EE4: ${command}
4420
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`
4421
+ );
4422
+ }
4423
+ return combined;
4424
+ }
4425
+ async function getRunningJar(client, settings) {
4426
+ const combined = await runSpringbootAction(client, settings, "status", [
4427
+ settings.startupJar
4428
+ ]);
4429
+ const text = combined.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").trim().toLowerCase();
4430
+ if (text.includes("not running")) {
4431
+ return null;
4432
+ }
4433
+ if (text.includes("running")) {
4434
+ return settings.startupJar;
4435
+ }
4436
+ return null;
4437
+ }
4438
+ async function healthCheckService(client, settings) {
4439
+ const port = String(settings.healthCheck.port);
4440
+ const context = settings.healthCheck.context.trim();
4441
+ const timeout = String(settings.healthCheck.timeout);
4442
+ log(`\u5065\u5EB7\u68C0\u67E5: springboot.sh health ${port} ${context} ${timeout}`);
4443
+ await runSpringbootAction(client, settings, "health", [
4444
+ port,
4445
+ context,
4446
+ timeout
4447
+ ]);
4448
+ }
4449
+ async function runWisdomJarDeploy(params) {
4450
+ const {
4451
+ projectRoot,
4452
+ deployCacheDir,
4453
+ configPath,
4454
+ settings,
4455
+ mavenModule = DEFAULT_MAVEN_MODULE,
4456
+ mavenProfile = DEFAULT_MAVEN_PROFILE,
4457
+ skipBuild = false
4458
+ } = params;
4459
+ const manifestFile = path11.join(deployCacheDir, "manifest.json");
4460
+ const mavenRepo = expandPath(settings.mavenLocalRepo);
4461
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${settings.projectName} ===`);
4462
+ log(`\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
4463
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4464
+ if (!skipBuild) {
4465
+ runMavenBuild(projectRoot, mavenRepo, mavenProfile);
4466
+ } else {
4467
+ log("\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF08--skip-build\uFF09");
4468
+ }
4469
+ const libDir = locateLibDir(projectRoot, mavenModule);
4470
+ const manifest = loadManifest(manifestFile);
4471
+ const sftp = new SftpClient2();
4472
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${settings.username}@${settings.host}:${settings.port}`);
4473
+ try {
4474
+ await sftp.connect({
4475
+ host: settings.host,
4476
+ port: settings.port,
4477
+ username: settings.username,
4478
+ password: settings.password,
4479
+ readyTimeout: 3e4,
4480
+ tryKeyboard: true
4481
+ });
4482
+ const client = sftp.client;
4483
+ let needRestart = false;
4484
+ let libUploadEntries = [];
4485
+ const remoteLibStats = await getRemoteFileStats(
4486
+ sftp,
4487
+ settings.remoteLibDir
4488
+ );
4489
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
4490
+ libUploadEntries = listLibFilesToUpload(
4491
+ projectRoot,
4492
+ libDir,
4493
+ remoteLibStats,
4494
+ manifest
4495
+ );
4496
+ let updated = 0;
4497
+ if (libUploadEntries.length > 0) {
4498
+ const zipPath = await createUpdatePackage(
4499
+ libUploadEntries,
4500
+ settings.packageName,
4501
+ deployCacheDir
4502
+ );
4503
+ const remoteZipPath = await uploadUpdatePackage(sftp, zipPath, settings);
4504
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
4505
+ updated = await extractUpdatePackageOnRemote(
4506
+ client,
4507
+ settings,
4508
+ remoteZipPath
4509
+ );
4510
+ } else {
4511
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
4512
+ }
4513
+ const runningJar = await getRunningJar(client, settings);
4514
+ needRestart = updated > 0;
4515
+ if (!needRestart && !runningJar) {
4516
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
4517
+ needRestart = true;
4518
+ } else if (!needRestart) {
4519
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
4520
+ }
4521
+ if (needRestart) {
4522
+ log("\u91CD\u542F\u670D\u52A1...");
4523
+ await runSpringbootAction(client, settings, "restart", [
4524
+ settings.startupJar
4525
+ ]);
4526
+ }
4527
+ await healthCheckService(client, settings);
4528
+ if (libUploadEntries.length > 0) {
4529
+ saveManifest(
4530
+ manifestFile,
4531
+ updateManifestEntries(projectRoot, manifest, libUploadEntries)
4532
+ );
4533
+ }
4534
+ } finally {
4535
+ await sftp.end();
4536
+ }
4537
+ log("\u90E8\u7F72\u5B8C\u6210");
4538
+ }
4539
+
4540
+ // src/commands/deploy/wisdom-jar.ts
4541
+ function registerDeployWisdomJarCommands(program) {
4542
+ program.command("deploy-wisdom-jar").description(
4543
+ "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"
4544
+ ).option(
4545
+ "--config <path>",
4546
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
4547
+ ).option(
4548
+ "--module <path>",
4549
+ "Maven \u6A21\u5757\u76F8\u5BF9\u8DEF\u5F84\uFF08\u9ED8\u8BA4 jeecg-module-system/jeecg-system-start\uFF09",
4550
+ "jeecg-module-system/jeecg-system-start"
4551
+ ).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(
4552
+ async (opts) => {
4553
+ const cfg = loadApmConfig({ configPath: opts.config });
4554
+ const settings = resolveWisdomJarDeployFromApmConfig(cfg);
4555
+ const projectRoot = process.cwd();
4556
+ const apmDir = workspaceApmDir(projectRoot);
4557
+ const deployCacheDir = path12.join(apmDir, "deploy", ".deploy_cache");
4558
+ const configPath = path12.resolve(
4559
+ projectRoot,
4560
+ opts.config ?? path12.join(apmDir, "apm.config.json")
4561
+ );
4562
+ try {
4563
+ await runWisdomJarDeploy({
4564
+ projectRoot,
4565
+ deployCacheDir,
4566
+ configPath,
4567
+ settings,
4568
+ mavenModule: opts.module,
4569
+ mavenProfile: opts.profile,
4570
+ skipBuild: Boolean(opts.skipBuild)
4571
+ });
4572
+ } catch (err) {
4573
+ const message = err instanceof Error ? err.message : String(err);
4574
+ console.error("\u90E8\u7F72\u5931\u8D25:", message);
4575
+ process.exit(1);
4576
+ }
4577
+ }
4578
+ );
4579
+ }
4580
+
3717
4581
  // src/commands/deploy/index.ts
3718
4582
  function registerDeployCommands(program) {
4583
+ registerDeployRunCommands(program);
3719
4584
  registerDeployBackendCommands(program);
3720
4585
  registerDeployFrontendCommands(program);
3721
4586
  registerDeploySftpCommands(program);
4587
+ registerDeployWisdomJarCommands(program);
3722
4588
  }
3723
4589
 
3724
4590
  // src/index.ts