ai-project-manage-cli 6.0.37 → 6.0.39

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
@@ -84,13 +84,55 @@ import { join as join3 } from "path";
84
84
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
85
85
 
86
86
  // src/command-utils.ts
87
- import { cpSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
88
- import { basename, dirname, extname, join as join2, resolve } from "path";
87
+ import { copyFileSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
88
+ import { basename, dirname, extname, join as join2, resolve as resolve2 } from "path";
89
89
  import { fileURLToPath } from "url";
90
+
91
+ // src/workdir-path.ts
92
+ import { realpathSync } from "fs";
93
+ import { platform } from "os";
94
+ import { resolve } from "path";
95
+ function toFsPath(inputPath) {
96
+ const absolute = resolve(inputPath);
97
+ if (platform() !== "win32") return absolute;
98
+ if (absolute.startsWith("\\\\?\\")) return absolute;
99
+ const normalized = absolute.replace(/\//g, "\\");
100
+ if (normalized.startsWith("\\\\")) {
101
+ return `\\\\?\\UNC\\${normalized.slice(2)}`;
102
+ }
103
+ return `\\\\?\\${normalized}`;
104
+ }
105
+ function normalizeWorkdirPath(path10) {
106
+ let normalized = path10.trim().replace(/\\/g, "/").normalize("NFC");
107
+ if (normalized.startsWith("//?/")) {
108
+ normalized = normalized.slice(4);
109
+ }
110
+ const windowsDrive = /^([A-Za-z]:)\/*(.*)$/.exec(normalized);
111
+ if (windowsDrive) {
112
+ const drive = windowsDrive[1].toLowerCase();
113
+ const rest = windowsDrive[2].replace(/\/+/g, "/").replace(/\/$/, "");
114
+ return rest ? `${drive}/${rest}` : drive;
115
+ }
116
+ normalized = normalized.replace(/\/+/g, "/");
117
+ if (normalized.length > 1 && normalized.endsWith("/")) {
118
+ normalized = normalized.slice(0, -1);
119
+ }
120
+ return normalized;
121
+ }
122
+ function resolveWorkdirPath(cwd = process.cwd()) {
123
+ const absolute = resolve(cwd);
124
+ try {
125
+ return normalizeWorkdirPath(realpathSync.native(absolute));
126
+ } catch {
127
+ return normalizeWorkdirPath(absolute);
128
+ }
129
+ }
130
+
131
+ // src/command-utils.ts
90
132
  var __dirname = dirname(fileURLToPath(import.meta.url));
91
- var CLI_TEMPLATE_DIR = resolve(__dirname, "../template");
92
- function workspaceApmDir(cwd = process.cwd()) {
93
- return resolve(cwd, ".apm");
133
+ var CLI_TEMPLATE_DIR = resolve2(__dirname, "../template");
134
+ function workspaceApmDir(cwd = resolveWorkdirPath()) {
135
+ return resolve2(resolve2(cwd), ".apm");
94
136
  }
95
137
  var SESSIONS_SUBDIR = "sessions";
96
138
  var SESSION_DOCS_SUBDIR = "docs";
@@ -147,78 +189,106 @@ async function ensureLoggedConfig() {
147
189
  async function ensureDirExists(dir) {
148
190
  mkdirSync2(dir, { recursive: true });
149
191
  }
150
- async function ensureWorkspaceApmDirForInit() {
151
- const dir = workspaceApmDir();
152
- if (!existsSync(dir)) {
153
- mkdirSync2(dir, { recursive: true });
192
+ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
193
+ const dir = workspaceApmDir(cwd);
194
+ const fsDir = toFsPath(dir);
195
+ if (!existsSync(fsDir)) {
196
+ mkdirSync2(fsDir, { recursive: true });
154
197
  return;
155
198
  }
156
- const st = statSync(dir);
199
+ const st = statSync(fsDir);
157
200
  if (!st.isDirectory()) {
158
201
  throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${dir}`);
159
202
  }
160
- if (readdirSync(dir).length > 0) {
203
+ if (readdirSync(fsDir).length > 0) {
161
204
  throw new Error(
162
205
  "[apm] .apm \u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF0C\u8BF7\u5148\u5907\u4EFD\u3001\u6E05\u7A7A\u6216\u5220\u9664\u540E\u518D\u6267\u884C init"
163
206
  );
164
207
  }
165
208
  }
166
- async function copyTemplateFiles(targetDir) {
167
- if (!existsSync(CLI_TEMPLATE_DIR)) {
168
- throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${CLI_TEMPLATE_DIR}`);
169
- }
170
- const dirStat = statSync(CLI_TEMPLATE_DIR);
171
- if (!dirStat.isDirectory()) {
172
- throw new Error(`[apm] CLI \u6A21\u677F\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ${CLI_TEMPLATE_DIR}`);
209
+ var WORKSPACE_TEMPLATE_SUBDIRS = [
210
+ "sessions",
211
+ "skills",
212
+ "rules",
213
+ "deploy"
214
+ ];
215
+ function shouldSkipTemplateEntry(name) {
216
+ return name === ".DS_Store" || name === "Thumbs.db";
217
+ }
218
+ function copyTemplateEntry(src, dest) {
219
+ const fsSrc = toFsPath(src);
220
+ const fsDest = toFsPath(dest);
221
+ const st = statSync(fsSrc);
222
+ if (st.isDirectory()) {
223
+ mkdirSync2(fsDest, { recursive: true });
224
+ for (const name of readdirSync(fsSrc)) {
225
+ if (shouldSkipTemplateEntry(name)) continue;
226
+ copyTemplateEntry(join2(src, name), join2(dest, name));
227
+ }
228
+ return;
173
229
  }
174
- const entries = readdirSync(CLI_TEMPLATE_DIR);
175
- if (entries.length === 0) {
176
- throw new Error(`[apm] CLI \u6A21\u677F\u76EE\u5F55\u4E3A\u7A7A: ${CLI_TEMPLATE_DIR}`);
230
+ if (!st.isFile()) return;
231
+ mkdirSync2(toFsPath(dirname(dest)), { recursive: true });
232
+ copyFileSync(fsSrc, fsDest);
233
+ }
234
+ function assertTemplateCopiedToApm(apmDir, workdir) {
235
+ const required = [
236
+ "AGENTS.md",
237
+ "apm.config.json",
238
+ "rules",
239
+ "skills",
240
+ "sessions"
241
+ ];
242
+ for (const item of required) {
243
+ const path10 = join2(apmDir, item);
244
+ if (!existsSync(toFsPath(path10))) {
245
+ throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path10}`);
246
+ }
177
247
  }
178
- for (const name of entries) {
179
- const src = join2(CLI_TEMPLATE_DIR, name);
180
- const dest = join2(targetDir, name);
181
- cpSync(src, dest, { recursive: true, force: false });
248
+ const leakedRules = join2(workdir, "rules");
249
+ const apmRules = join2(apmDir, "rules");
250
+ if (existsSync(toFsPath(leakedRules)) && !existsSync(toFsPath(join2(apmRules, "reply.md")))) {
251
+ throw new Error(
252
+ `[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
253
+ );
182
254
  }
183
255
  }
184
-
185
- // src/workdir-path.ts
186
- import { realpathSync } from "fs";
187
- import { resolve as resolve2 } from "path";
188
- function normalizeWorkdirPath(path10) {
189
- let normalized = path10.trim().replace(/\\/g, "/").normalize("NFC");
190
- if (normalized.startsWith("//?/")) {
191
- normalized = normalized.slice(4);
256
+ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
257
+ const resolvedTarget = resolve2(targetDir);
258
+ const templateDir = resolve2(CLI_TEMPLATE_DIR);
259
+ const fsTemplateDir = toFsPath(templateDir);
260
+ if (!existsSync(fsTemplateDir)) {
261
+ throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
192
262
  }
193
- const windowsDrive = /^([A-Za-z]:)\/*(.*)$/.exec(normalized);
194
- if (windowsDrive) {
195
- const drive = windowsDrive[1].toLowerCase();
196
- const rest = windowsDrive[2].replace(/\/+/g, "/").replace(/\/$/, "");
197
- return rest ? `${drive}/${rest}` : drive;
263
+ const dirStat = statSync(fsTemplateDir);
264
+ if (!dirStat.isDirectory()) {
265
+ throw new Error(`[apm] CLI \u6A21\u677F\u8DEF\u5F84\u4E0D\u662F\u76EE\u5F55: ${templateDir}`);
198
266
  }
199
- normalized = normalized.replace(/\/+/g, "/");
200
- if (normalized.length > 1 && normalized.endsWith("/")) {
201
- normalized = normalized.slice(0, -1);
267
+ const entries = readdirSync(fsTemplateDir).filter(
268
+ (name) => !shouldSkipTemplateEntry(name)
269
+ );
270
+ if (entries.length === 0) {
271
+ throw new Error(`[apm] CLI \u6A21\u677F\u76EE\u5F55\u4E3A\u7A7A: ${templateDir}`);
202
272
  }
203
- return normalized;
204
- }
205
- function resolveWorkdirPath(cwd = process.cwd()) {
206
- const absolute = resolve2(cwd);
207
- try {
208
- return normalizeWorkdirPath(realpathSync.native(absolute));
209
- } catch {
210
- return normalizeWorkdirPath(absolute);
273
+ mkdirSync2(toFsPath(resolvedTarget), { recursive: true });
274
+ for (const name of entries) {
275
+ copyTemplateEntry(join2(templateDir, name), join2(resolvedTarget, name));
276
+ }
277
+ for (const subdir of WORKSPACE_TEMPLATE_SUBDIRS) {
278
+ mkdirSync2(toFsPath(join2(resolvedTarget, subdir)), { recursive: true });
211
279
  }
280
+ assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
212
281
  }
213
282
 
214
283
  // src/commands/init.ts
215
284
  async function runInit(name) {
216
- await ensureWorkspaceApmDirForInit();
217
- const apmDir = workspaceApmDir();
218
- await copyTemplateFiles(apmDir);
285
+ const workdir = resolveWorkdirPath();
286
+ await ensureWorkspaceApmDirForInit(workdir);
287
+ const apmDir = workspaceApmDir(workdir);
288
+ await copyTemplateFiles(apmDir, workdir);
219
289
  const trimmedName = name?.trim();
220
290
  if (trimmedName) {
221
- const apmConfigPath = join3(apmDir, "apm.config.json");
291
+ const apmConfigPath = toFsPath(join3(apmDir, "apm.config.json"));
222
292
  const config = readFileSync2(apmConfigPath, "utf8");
223
293
  const configJson = JSON.parse(config);
224
294
  configJson.name = trimmedName;
@@ -229,7 +299,6 @@ async function runInit(name) {
229
299
  "utf8"
230
300
  );
231
301
  }
232
- const workdir = resolveWorkdirPath();
233
302
  console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
234
303
  console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
235
304
  console.log("[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u5BA2\u6237\u673A\u7BA1\u7406 \u2192 \u5DE5\u4F5C\u7A7A\u95F4\u300D\u767B\u8BB0\u4E0A\u8FF0\u76EE\u5F55\u8DEF\u5F84");
@@ -245,10 +314,12 @@ import { createApiClient } from "listpage-http";
245
314
  import { defineEndpoint } from "listpage-http";
246
315
  var requestConfig = {
247
316
  cli: {
248
- me: defineEndpoint({
249
- method: "GET",
250
- path: "/cli/me"
251
- }),
317
+ me: defineEndpoint(
318
+ {
319
+ method: "GET",
320
+ path: "/cli/me"
321
+ }
322
+ ),
252
323
  sessionDetail: defineEndpoint({
253
324
  method: "GET",
254
325
  path: "/cli/sessions/detail"
@@ -298,6 +369,10 @@ var requestConfig = {
298
369
  listSkills: defineEndpoint({
299
370
  method: "GET",
300
371
  path: "/cli/skills"
372
+ }),
373
+ listRules: defineEndpoint({
374
+ method: "GET",
375
+ path: "/cli/rules"
301
376
  })
302
377
  }
303
378
  };
@@ -535,8 +610,8 @@ async function runBranch(sessionId, options = {}) {
535
610
  }
536
611
 
537
612
  // src/commands/pull.ts
538
- import { writeFileSync as writeFileSync4 } from "fs";
539
- import { join as join5 } from "path";
613
+ import { writeFileSync as writeFileSync6 } from "fs";
614
+ import { dirname as dirname2, join as join7 } from "path";
540
615
  import { stringify as yamlStringify } from "yaml";
541
616
 
542
617
  // src/session-messages-xml.ts
@@ -644,7 +719,201 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
644
719
  saveManifest(dir, nextManifest);
645
720
  }
646
721
 
722
+ // src/rules-sync.ts
723
+ import { basename as basename2, extname as extname2, join as join6 } from "path";
724
+ import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
725
+
726
+ // src/skills-sync.ts
727
+ import {
728
+ copyFileSync as copyFileSync2,
729
+ cpSync,
730
+ existsSync as existsSync3,
731
+ mkdirSync as mkdirSync3,
732
+ readdirSync as readdirSync2,
733
+ rmSync,
734
+ statSync as statSync2,
735
+ writeFileSync as writeFileSync4
736
+ } from "fs";
737
+ import { join as join5 } from "path";
738
+ var AGENTS_TEMPLATE_PATH = join5(CLI_TEMPLATE_DIR, "AGENTS.md");
739
+ var BASE_SKILLS_TEMPLATE_DIR = join5(CLI_TEMPLATE_DIR, "skills");
740
+ var BASE_RULES_TEMPLATE_DIR = join5(CLI_TEMPLATE_DIR, "rules");
741
+ function sanitizeSkillDirName(name) {
742
+ const trimmed = name.trim();
743
+ if (!trimmed) return "skill";
744
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
745
+ }
746
+ function listBaseSkillDirNames() {
747
+ if (!existsSync3(BASE_SKILLS_TEMPLATE_DIR)) return [];
748
+ return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
749
+ const path10 = join5(BASE_SKILLS_TEMPLATE_DIR, name);
750
+ return statSync2(path10).isDirectory();
751
+ });
752
+ }
753
+ function syncAgentsGuide(apmDir) {
754
+ if (!existsSync3(AGENTS_TEMPLATE_PATH)) return false;
755
+ mkdirSync3(apmDir, { recursive: true });
756
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join5(apmDir, "AGENTS.md"));
757
+ return true;
758
+ }
759
+ function listBaseRuleFileNames() {
760
+ if (!existsSync3(BASE_RULES_TEMPLATE_DIR)) return [];
761
+ return readdirSync2(BASE_RULES_TEMPLATE_DIR).filter((name) => {
762
+ const path10 = join5(BASE_RULES_TEMPLATE_DIR, name);
763
+ return statSync2(path10).isFile();
764
+ });
765
+ }
766
+ function syncBaseRules(rulesDir) {
767
+ mkdirSync3(rulesDir, { recursive: true });
768
+ const names = listBaseRuleFileNames();
769
+ for (const name of names) {
770
+ const src = join5(BASE_RULES_TEMPLATE_DIR, name);
771
+ const dest = join5(rulesDir, name);
772
+ copyFileSync2(src, dest);
773
+ }
774
+ return names;
775
+ }
776
+ function syncBaseSkills(skillsDir) {
777
+ mkdirSync3(skillsDir, { recursive: true });
778
+ const names = listBaseSkillDirNames();
779
+ for (const name of names) {
780
+ const src = join5(BASE_SKILLS_TEMPLATE_DIR, name);
781
+ const dest = join5(skillsDir, name);
782
+ cpSync(src, dest, { recursive: true, force: true });
783
+ }
784
+ return names;
785
+ }
786
+ function syncSupplementarySkills(skillsDir, list) {
787
+ const baseNames = new Set(listBaseSkillDirNames());
788
+ const apiDirNames = /* @__PURE__ */ new Set();
789
+ const written = [];
790
+ const skipped = [];
791
+ for (const skill of list) {
792
+ const dirName = sanitizeSkillDirName(skill.name);
793
+ apiDirNames.add(dirName);
794
+ if (baseNames.has(dirName)) {
795
+ skipped.push(dirName);
796
+ continue;
797
+ }
798
+ const skillDir = join5(skillsDir, dirName);
799
+ mkdirSync3(skillDir, { recursive: true });
800
+ writeFileSync4(join5(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
801
+ written.push(dirName);
802
+ }
803
+ const removed = [];
804
+ if (!existsSync3(skillsDir)) return { written, skipped, removed };
805
+ for (const entry of readdirSync2(skillsDir)) {
806
+ const full = join5(skillsDir, entry);
807
+ if (!statSync2(full).isDirectory()) continue;
808
+ if (baseNames.has(entry)) continue;
809
+ if (apiDirNames.has(entry)) continue;
810
+ rmSync(full, { recursive: true, force: true });
811
+ removed.push(entry);
812
+ }
813
+ return { written, skipped, removed };
814
+ }
815
+
816
+ // src/rules-sync.ts
817
+ var MANIFEST_FILE2 = ".rules-sync-manifest.json";
818
+ function ruleLocalFileName(ruleName) {
819
+ const trimmed = ruleName.trim();
820
+ if (!trimmed) return "rule.md";
821
+ const sanitized = trimmed.replace(/[/\\:*?"<>|]/g, "_");
822
+ if (extname2(sanitized).toLowerCase() === ".md") return sanitized;
823
+ return `${sanitized}.md`;
824
+ }
825
+ function loadManifest2(rulesDir) {
826
+ const path10 = join6(rulesDir, MANIFEST_FILE2);
827
+ if (!existsSync4(toFsPath(path10))) {
828
+ return { version: 1, rules: {} };
829
+ }
830
+ try {
831
+ const parsed = JSON.parse(
832
+ readFileSync4(toFsPath(path10), "utf8")
833
+ );
834
+ if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
835
+ return parsed;
836
+ }
837
+ } catch {
838
+ }
839
+ return { version: 1, rules: {} };
840
+ }
841
+ function saveManifest2(rulesDir, manifest) {
842
+ writeFileSync5(
843
+ toFsPath(join6(rulesDir, MANIFEST_FILE2)),
844
+ `${JSON.stringify(manifest, null, 2)}
845
+ `,
846
+ "utf8"
847
+ );
848
+ }
849
+ function isBaseRuleFileName(fileName) {
850
+ return listBaseRuleFileNames().includes(basename2(fileName));
851
+ }
852
+ function isRuleUpToDate(entry, rule, dest) {
853
+ if (!entry || !existsSync4(toFsPath(dest))) return false;
854
+ if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
855
+ const updatedAt = rule.updatedAt ?? "";
856
+ if (entry.updatedAt !== updatedAt) return false;
857
+ const localContent = readFileSync4(toFsPath(dest), "utf8");
858
+ return localContent === (rule.content ?? "");
859
+ }
860
+ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
861
+ const api = createApmApiClient(cfg);
862
+ const baseline = await api.cli.branchBaseline({ sessionId, workdirPath });
863
+ const repositoryId = baseline.repositoryId;
864
+ const rulesDir = join6(apmRoot ?? workspaceApmDir(workdirPath), "rules");
865
+ await ensureDirExists(rulesDir);
866
+ if (!repositoryId) {
867
+ console.log(
868
+ `[apm] \u672A\u5339\u914D\u5230\u7ED1\u5B9A\u4ED3\u5E93\u7684\u5DE5\u4F5C\u7A7A\u95F4\uFF0C\u8DF3\u8FC7\u5E73\u53F0\u89C4\u5219\u540C\u6B65\uFF08\u8DEF\u5F84\uFF1A${workdirPath}\uFF09`
869
+ );
870
+ return { written: [], skipped: [], removed: [], repositoryId: null };
871
+ }
872
+ const { list } = await api.cli.listRules({ repositoryId });
873
+ const manifest = loadManifest2(rulesDir);
874
+ const nextManifest = { version: 1, rules: {} };
875
+ const remoteIds = /* @__PURE__ */ new Set();
876
+ const written = [];
877
+ const skipped = [];
878
+ for (const rule of list) {
879
+ remoteIds.add(rule.id);
880
+ const fileName = ruleLocalFileName(rule.name);
881
+ const dest = join6(rulesDir, fileName);
882
+ const entry = manifest.rules[rule.id];
883
+ const updatedAt = rule.updatedAt ?? "";
884
+ if (isRuleUpToDate(entry, rule, dest)) {
885
+ nextManifest.rules[rule.id] = entry;
886
+ skipped.push(fileName);
887
+ console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
888
+ continue;
889
+ }
890
+ writeFileSync5(toFsPath(dest), rule.content ?? "", "utf8");
891
+ nextManifest.rules[rule.id] = { fileName, updatedAt };
892
+ written.push(fileName);
893
+ console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
894
+ }
895
+ const removed = [];
896
+ for (const [ruleId, entry] of Object.entries(manifest.rules)) {
897
+ if (remoteIds.has(ruleId)) continue;
898
+ if (isBaseRuleFileName(entry.fileName)) continue;
899
+ const dest = join6(rulesDir, entry.fileName);
900
+ if (existsSync4(toFsPath(dest))) {
901
+ rmSync2(toFsPath(dest), { force: true });
902
+ }
903
+ removed.push(entry.fileName);
904
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
905
+ }
906
+ saveManifest2(rulesDir, nextManifest);
907
+ return { written, skipped, removed, repositoryId };
908
+ }
909
+
647
910
  // src/commands/pull.ts
911
+ function resolvePullWorkdir(apmRoot) {
912
+ if (apmRoot) {
913
+ return resolveWorkdirPath(dirname2(apmRoot));
914
+ }
915
+ return resolveWorkdirPath();
916
+ }
648
917
  async function runPull(sessionId, apmRoot) {
649
918
  const trimmedId = sessionId.trim();
650
919
  if (!trimmedId) {
@@ -665,20 +934,20 @@ async function runPull(sessionId, apmRoot) {
665
934
  const dir = sessionDir(trimmedId, apmRoot);
666
935
  const docsDir = sessionDocsDir(trimmedId, apmRoot);
667
936
  await ensureDirExists(docsDir);
668
- writeFileSync4(
937
+ writeFileSync6(
669
938
  sessionRulePath(trimmedId, apmRoot),
670
939
  detail.description ?? "",
671
940
  "utf8"
672
941
  );
673
- writeFileSync4(
942
+ writeFileSync6(
674
943
  sessionTaskPath(trimmedId, apmRoot),
675
944
  detail.task.description ?? "",
676
945
  "utf8"
677
946
  );
678
- writeFileSync4(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
947
+ writeFileSync6(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
679
948
  for (const doc of documents) {
680
949
  const fileName = documentLocalFileName(doc.name);
681
- writeFileSync4(join5(docsDir, fileName), doc.content ?? "", "utf8");
950
+ writeFileSync6(join7(docsDir, fileName), doc.content ?? "", "utf8");
682
951
  }
683
952
  const sessionYaml = yamlStringify(
684
953
  {
@@ -695,18 +964,20 @@ async function runPull(sessionId, apmRoot) {
695
964
  },
696
965
  { lineWidth: 0 }
697
966
  );
698
- writeFileSync4(
967
+ writeFileSync6(
699
968
  sessionYamlPath(trimmedId, apmRoot),
700
969
  sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
701
970
  `,
702
971
  "utf8"
703
972
  );
704
- writeFileSync4(
973
+ writeFileSync6(
705
974
  sessionMessagesXmlPath(trimmedId, apmRoot),
706
975
  formatSessionMessagesXml(trimmedId, messages),
707
976
  "utf8"
708
977
  );
709
978
  await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
979
+ const workdirPath = resolvePullWorkdir(apmRoot);
980
+ await syncPlatformRules(cfg, trimmedId, workdirPath, apmRoot);
710
981
  console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
711
982
  return dir;
712
983
  }
@@ -715,15 +986,15 @@ async function runPull(sessionId, apmRoot) {
715
986
  import { spawnSync } from "child_process";
716
987
 
717
988
  // src/version.ts
718
- import { readFileSync as readFileSync4 } from "fs";
719
- import { dirname as dirname2, join as join6 } from "path";
989
+ import { readFileSync as readFileSync5 } from "fs";
990
+ import { dirname as dirname3, join as join8 } from "path";
720
991
  import { fileURLToPath as fileURLToPath2 } from "url";
721
992
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
722
993
  function readCliVersion() {
723
994
  try {
724
- const dir = dirname2(fileURLToPath2(import.meta.url));
725
- const pkgPath = join6(dir, "..", "package.json");
726
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
995
+ const dir = dirname3(fileURLToPath2(import.meta.url));
996
+ const pkgPath = join8(dir, "..", "package.json");
997
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
727
998
  return pkg.version ?? "0.0.0";
728
999
  } catch {
729
1000
  return "0.0.0";
@@ -790,103 +1061,11 @@ async function runUpdate() {
790
1061
  }
791
1062
 
792
1063
  // src/commands/update-skills.ts
793
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
794
- import { join as join8 } from "path";
795
-
796
- // src/skills-sync.ts
797
- import {
798
- copyFileSync,
799
- cpSync as cpSync2,
800
- existsSync as existsSync3,
801
- mkdirSync as mkdirSync3,
802
- readdirSync as readdirSync2,
803
- rmSync,
804
- statSync as statSync2,
805
- writeFileSync as writeFileSync5
806
- } from "fs";
807
- import { join as join7 } from "path";
808
- var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
809
- var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
810
- var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
811
- function sanitizeSkillDirName(name) {
812
- const trimmed = name.trim();
813
- if (!trimmed) return "skill";
814
- return trimmed.replace(/[/\\:*?"<>|]/g, "_");
815
- }
816
- function listBaseSkillDirNames() {
817
- if (!existsSync3(BASE_SKILLS_TEMPLATE_DIR)) return [];
818
- return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
819
- const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
820
- return statSync2(path10).isDirectory();
821
- });
822
- }
823
- function syncAgentsGuide(apmDir) {
824
- if (!existsSync3(AGENTS_TEMPLATE_PATH)) return false;
825
- mkdirSync3(apmDir, { recursive: true });
826
- copyFileSync(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
827
- return true;
828
- }
829
- function listBaseRuleFileNames() {
830
- if (!existsSync3(BASE_RULES_TEMPLATE_DIR)) return [];
831
- return readdirSync2(BASE_RULES_TEMPLATE_DIR).filter((name) => {
832
- const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
833
- return statSync2(path10).isFile();
834
- });
835
- }
836
- function syncBaseRules(rulesDir) {
837
- mkdirSync3(rulesDir, { recursive: true });
838
- const names = listBaseRuleFileNames();
839
- for (const name of names) {
840
- const src = join7(BASE_RULES_TEMPLATE_DIR, name);
841
- const dest = join7(rulesDir, name);
842
- copyFileSync(src, dest);
843
- }
844
- return names;
845
- }
846
- function syncBaseSkills(skillsDir) {
847
- mkdirSync3(skillsDir, { recursive: true });
848
- const names = listBaseSkillDirNames();
849
- for (const name of names) {
850
- const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
851
- const dest = join7(skillsDir, name);
852
- cpSync2(src, dest, { recursive: true, force: true });
853
- }
854
- return names;
855
- }
856
- function syncSupplementarySkills(skillsDir, list) {
857
- const baseNames = new Set(listBaseSkillDirNames());
858
- const apiDirNames = /* @__PURE__ */ new Set();
859
- const written = [];
860
- const skipped = [];
861
- for (const skill of list) {
862
- const dirName = sanitizeSkillDirName(skill.name);
863
- apiDirNames.add(dirName);
864
- if (baseNames.has(dirName)) {
865
- skipped.push(dirName);
866
- continue;
867
- }
868
- const skillDir = join7(skillsDir, dirName);
869
- mkdirSync3(skillDir, { recursive: true });
870
- writeFileSync5(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
871
- written.push(dirName);
872
- }
873
- const removed = [];
874
- if (!existsSync3(skillsDir)) return { written, skipped, removed };
875
- for (const entry of readdirSync2(skillsDir)) {
876
- const full = join7(skillsDir, entry);
877
- if (!statSync2(full).isDirectory()) continue;
878
- if (baseNames.has(entry)) continue;
879
- if (apiDirNames.has(entry)) continue;
880
- rmSync(full, { recursive: true, force: true });
881
- removed.push(entry);
882
- }
883
- return { written, skipped, removed };
884
- }
885
-
886
- // src/commands/update-skills.ts
1064
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
1065
+ import { join as join9 } from "path";
887
1066
  async function runUpdateSkills() {
888
1067
  const apmDir = workspaceApmDir();
889
- if (!existsSync4(apmDir)) {
1068
+ if (!existsSync5(apmDir)) {
890
1069
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
891
1070
  process.exit(1);
892
1071
  }
@@ -900,12 +1079,12 @@ async function runUpdateSkills() {
900
1079
  if (syncAgentsGuide(apmDir)) {
901
1080
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
902
1081
  }
903
- const rulesDir = join8(apmDir, "rules");
1082
+ const rulesDir = join9(apmDir, "rules");
904
1083
  const ruleNames = syncBaseRules(rulesDir);
905
1084
  for (const name of ruleNames) {
906
1085
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
907
1086
  }
908
- const skillsDir = join8(apmDir, "skills");
1087
+ const skillsDir = join9(apmDir, "skills");
909
1088
  mkdirSync4(skillsDir, { recursive: true });
910
1089
  const baseNames = syncBaseSkills(skillsDir);
911
1090
  for (const name of baseNames) {
@@ -932,14 +1111,14 @@ async function runUpdateSkills() {
932
1111
  }
933
1112
 
934
1113
  // src/commands/sync-document.ts
935
- import { existsSync as existsSync6 } from "fs";
936
- import { basename as basename2 } from "path";
1114
+ import { existsSync as existsSync7 } from "fs";
1115
+ import { basename as basename3 } from "path";
937
1116
 
938
1117
  // src/commands/sync-session-documents.ts
939
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
940
- import { join as join9 } from "path";
1118
+ import { existsSync as existsSync6, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
1119
+ import { join as join10 } from "path";
941
1120
  function listLocalMarkdownFiles(docsDir) {
942
- if (!existsSync5(docsDir)) {
1121
+ if (!existsSync6(docsDir)) {
943
1122
  return [];
944
1123
  }
945
1124
  return readdirSync3(docsDir).filter(
@@ -954,8 +1133,8 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
954
1133
  });
955
1134
  }
956
1135
  async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
957
- const absPath = join9(docsDir, fileName);
958
- const content = readFileSync5(absPath, "utf8");
1136
+ const absPath = join10(docsDir, fileName);
1137
+ const content = readFileSync6(absPath, "utf8");
959
1138
  const name = documentPlatformName(absPath);
960
1139
  return api.cli.upsertDocument({
961
1140
  sessionId,
@@ -977,8 +1156,8 @@ async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
977
1156
  const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
978
1157
  let synced = 0;
979
1158
  for (const fileName of localFiles) {
980
- const absPath = join9(docsDir, fileName);
981
- const content = readFileSync5(absPath, "utf8");
1159
+ const absPath = join10(docsDir, fileName);
1160
+ const content = readFileSync6(absPath, "utf8");
982
1161
  const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
983
1162
  if (remote && remote.content === content) {
984
1163
  continue;
@@ -1011,7 +1190,7 @@ async function runSyncDocument(sessionId, options) {
1011
1190
  process.exit(1);
1012
1191
  }
1013
1192
  const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
1014
- if (!existsSync6(absPath)) {
1193
+ if (!existsSync7(absPath)) {
1015
1194
  const docsDir2 = sessionDocsDir(trimmedSessionId);
1016
1195
  console.error(
1017
1196
  `[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
@@ -1026,7 +1205,7 @@ async function runSyncDocument(sessionId, options) {
1026
1205
  api,
1027
1206
  trimmedSessionId,
1028
1207
  docsDir,
1029
- basename2(absPath)
1208
+ basename3(absPath)
1030
1209
  );
1031
1210
  console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
1032
1211
  }
@@ -1904,19 +2083,19 @@ async function runConnect(options) {
1904
2083
  import path5 from "node:path";
1905
2084
 
1906
2085
  // src/commands/deploy/internal/apm-config.ts
1907
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
2086
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
1908
2087
  import { resolve as resolve4 } from "node:path";
1909
2088
  function loadApmConfig(options) {
1910
2089
  const p = resolve4(
1911
2090
  process.cwd(),
1912
2091
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
1913
2092
  );
1914
- if (!existsSync7(p)) {
2093
+ if (!existsSync8(p)) {
1915
2094
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1916
2095
  process.exit(1);
1917
2096
  }
1918
2097
  try {
1919
- const raw = readFileSync6(p, "utf8");
2098
+ const raw = readFileSync7(p, "utf8");
1920
2099
  return JSON.parse(raw);
1921
2100
  } catch (e) {
1922
2101
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -2038,7 +2217,7 @@ import path4 from "node:path";
2038
2217
  import Docker from "dockerode";
2039
2218
 
2040
2219
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
2041
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
2220
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
2042
2221
  import path from "node:path";
2043
2222
  function asOptionalTlsBuffer(value) {
2044
2223
  if (typeof value !== "string") {
@@ -2050,8 +2229,8 @@ function asOptionalTlsBuffer(value) {
2050
2229
  if (normalized === "") {
2051
2230
  return void 0;
2052
2231
  }
2053
- if (existsSync8(normalized)) {
2054
- return readFileSync7(normalized);
2232
+ if (existsSync9(normalized)) {
2233
+ return readFileSync8(normalized);
2055
2234
  }
2056
2235
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
2057
2236
  if (looksLikePath) {
@@ -2261,7 +2440,7 @@ var DockerodeClient = class {
2261
2440
  var createDockerodeClient = (config) => new DockerodeClient(config);
2262
2441
 
2263
2442
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2264
- import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
2443
+ import { existsSync as existsSync10, readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
2265
2444
  import path2 from "node:path";
2266
2445
  function stripSurroundingQuotes(value) {
2267
2446
  const t = value.trim();
@@ -2278,10 +2457,10 @@ function loadEnvFromFile(envFilePath) {
2278
2457
  return {};
2279
2458
  }
2280
2459
  const targetPath = path2.resolve(envFilePath);
2281
- if (!existsSync9(targetPath) || !statSync4(targetPath).isFile()) {
2460
+ if (!existsSync10(targetPath) || !statSync4(targetPath).isFile()) {
2282
2461
  return {};
2283
2462
  }
2284
- const raw = readFileSync8(targetPath, "utf-8");
2463
+ const raw = readFileSync9(targetPath, "utf-8");
2285
2464
  const result = {};
2286
2465
  for (const line of raw.split(/\r?\n/)) {
2287
2466
  const normalized = line.trim();
@@ -2346,7 +2525,7 @@ function assertDeployImageTag(tag) {
2346
2525
  }
2347
2526
 
2348
2527
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
2349
- import { platform } from "node:os";
2528
+ import { platform as platform2 } from "node:os";
2350
2529
 
2351
2530
  // src/commands/deploy/internal/backend-deploy/command-runner.ts
2352
2531
  import { execSync } from "child_process";
@@ -2406,7 +2585,7 @@ var CommandRunner = class {
2406
2585
 
2407
2586
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
2408
2587
  function dockerBuildPlatformFlags() {
2409
- return platform() === "darwin" ? ["--platform", "linux/amd64"] : [];
2588
+ return platform2() === "darwin" ? ["--platform", "linux/amd64"] : [];
2410
2589
  }
2411
2590
  function buildDockerImageLocally(params, cwd) {
2412
2591
  const platformFlags = dockerBuildPlatformFlags();
@@ -2452,12 +2631,12 @@ function dockerPushImage(params, cwd) {
2452
2631
  }
2453
2632
 
2454
2633
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2455
- import { existsSync as existsSync10 } from "node:fs";
2634
+ import { existsSync as existsSync11 } from "node:fs";
2456
2635
  import path3 from "node:path";
2457
2636
  function resolveDockerBuildPaths(cwd) {
2458
2637
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2459
2638
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2460
- if (!existsSync10(dockerfilePath)) {
2639
+ if (!existsSync11(dockerfilePath)) {
2461
2640
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2462
2641
  }
2463
2642
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.37",
3
+ "version": "6.0.39",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,15 @@
1
+ ## 前端部署
2
+
3
+ 部署测试环境: `npm run deploy:test`
4
+ 部署线上环境: `npm run deploy:online`
5
+
6
+ ## 后端部署(不区分正式环境和测试环境)
7
+ `python scripts/deploy.py`
8
+
9
+ ## 前端产物下载地址
10
+
11
+ http://<服务器地址>/dist.zip
12
+ http://<服务器地址>/backend_update_jar.zip
13
+
14
+ ## 测试地址
15
+ http://<测试环境>
File without changes