ai-project-manage-cli 7.1.37 → 7.1.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
@@ -1000,8 +1000,8 @@ import { Command } from "commander";
1000
1000
  init_config();
1001
1001
 
1002
1002
  // src/commands/init.ts
1003
- import { join as join6 } from "path";
1004
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1003
+ import { join as join3 } from "path";
1004
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
1005
1005
 
1006
1006
  // src/command-utils.ts
1007
1007
  init_config();
@@ -1145,8 +1145,12 @@ var WORKSPACE_TEMPLATE_SUBDIRS = [
1145
1145
  "rules",
1146
1146
  "deploy"
1147
1147
  ];
1148
+ var WEBIDE_AGENTS_TEMPLATE_NAME = "AGENTS.webide.md";
1148
1149
  function shouldSkipTemplateEntry(name) {
1149
- return name === ".DS_Store" || name === "Thumbs.db";
1150
+ return name === ".DS_Store" || name === "Thumbs.db" || name === WEBIDE_AGENTS_TEMPLATE_NAME;
1151
+ }
1152
+ function isWebIdeRuleFileName(name) {
1153
+ return name.startsWith("webide");
1150
1154
  }
1151
1155
  function copyTemplateEntry(src, dest) {
1152
1156
  const fsSrc = toFsPath(src);
@@ -1212,11 +1216,126 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
1212
1216
  }
1213
1217
  assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
1214
1218
  }
1219
+ function listWebIdeRuleFileNames(rulesDir) {
1220
+ const fsRulesDir = toFsPath(rulesDir);
1221
+ if (!existsSync(fsRulesDir) || !statSync(fsRulesDir).isDirectory()) {
1222
+ return [];
1223
+ }
1224
+ return readdirSync(fsRulesDir).filter((name) => {
1225
+ if (shouldSkipTemplateEntry(name) || !isWebIdeRuleFileName(name)) {
1226
+ return false;
1227
+ }
1228
+ return statSync(toFsPath(join2(rulesDir, name))).isFile();
1229
+ });
1230
+ }
1231
+ function assertWebIdeTemplateCopiedToApm(apmDir, workdir) {
1232
+ const required = ["AGENTS.md", "apm.config.json", "rules"];
1233
+ for (const item of required) {
1234
+ const path19 = join2(apmDir, item);
1235
+ if (!existsSync(toFsPath(path19))) {
1236
+ throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path19}`);
1237
+ }
1238
+ }
1239
+ const rulesDir = join2(apmDir, "rules");
1240
+ const ruleNames = listWebIdeRuleFileNames(rulesDir);
1241
+ if (ruleNames.length === 0) {
1242
+ throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11 webide \u89C4\u5219: ${rulesDir}`);
1243
+ }
1244
+ const leakedRules = join2(workdir, "rules");
1245
+ const apmRules = join2(apmDir, "rules");
1246
+ if (existsSync(toFsPath(leakedRules)) && !existsSync(toFsPath(join2(apmRules, "webide_reply.md")))) {
1247
+ throw new Error(
1248
+ `[apm] \u6A21\u677F\u88AB\u590D\u5236\u5230\u9519\u8BEF\u4F4D\u7F6E: ${leakedRules}\uFF08\u5E94\u5728 ${apmRules}\uFF09`
1249
+ );
1250
+ }
1251
+ }
1252
+ async function copyWebIdeTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
1253
+ const resolvedTarget = resolve2(targetDir);
1254
+ const templateDir = resolve2(CLI_TEMPLATE_DIR);
1255
+ const fsTemplateDir = toFsPath(templateDir);
1256
+ if (!existsSync(fsTemplateDir) || !statSync(fsTemplateDir).isDirectory()) {
1257
+ throw new Error(`[apm] \u672A\u627E\u5230 CLI \u6A21\u677F\u76EE\u5F55: ${templateDir}`);
1258
+ }
1259
+ const agentsSrc = join2(templateDir, WEBIDE_AGENTS_TEMPLATE_NAME);
1260
+ if (!existsSync(toFsPath(agentsSrc))) {
1261
+ throw new Error(`[apm] \u672A\u627E\u5230 WebIDE \u6307\u5357: ${agentsSrc}`);
1262
+ }
1263
+ const rulesSrc = join2(templateDir, "rules");
1264
+ const ruleNames = listWebIdeRuleFileNames(rulesSrc);
1265
+ if (ruleNames.length === 0) {
1266
+ throw new Error(`[apm] CLI \u6A21\u677F\u4E2D\u6CA1\u6709 webide \u89C4\u5219: ${rulesSrc}`);
1267
+ }
1268
+ mkdirSync2(toFsPath(resolvedTarget), { recursive: true });
1269
+ copyFileSync(toFsPath(agentsSrc), toFsPath(join2(resolvedTarget, "AGENTS.md")));
1270
+ copyTemplateEntry(
1271
+ join2(templateDir, "apm.config.json"),
1272
+ join2(resolvedTarget, "apm.config.json")
1273
+ );
1274
+ const rulesDest = join2(resolvedTarget, "rules");
1275
+ mkdirSync2(toFsPath(rulesDest), { recursive: true });
1276
+ for (const name of ruleNames) {
1277
+ copyTemplateEntry(join2(rulesSrc, name), join2(rulesDest, name));
1278
+ }
1279
+ assertWebIdeTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
1280
+ }
1281
+
1282
+ // src/commands/init.ts
1283
+ function applyWorkspaceName(apmDir, name) {
1284
+ const trimmedName = name?.trim();
1285
+ if (!trimmedName) return;
1286
+ const apmConfigPath = toFsPath(join3(apmDir, "apm.config.json"));
1287
+ const config = readFileSync2(apmConfigPath, "utf8");
1288
+ const configJson = JSON.parse(config);
1289
+ configJson.name = trimmedName;
1290
+ writeFileSync2(
1291
+ apmConfigPath,
1292
+ `${JSON.stringify(configJson, null, 2)}
1293
+ `,
1294
+ "utf8"
1295
+ );
1296
+ }
1297
+ async function initializeWorkspace(workdir, options) {
1298
+ await ensureWorkspaceApmDirForInit(workdir);
1299
+ const apmDir = workspaceApmDir(workdir);
1300
+ if (options?.webide) {
1301
+ await copyWebIdeTemplateFiles(apmDir, workdir);
1302
+ } else {
1303
+ await copyTemplateFiles(apmDir, workdir);
1304
+ }
1305
+ applyWorkspaceName(apmDir, options?.name);
1306
+ console.log(
1307
+ options?.webide ? `[apm] \u5DF2\u6309 WebIDE \u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}` : `[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`
1308
+ );
1309
+ return { didInit: true };
1310
+ }
1311
+ async function ensureWorkspaceInitialized(workdir, options) {
1312
+ if (isWorkspaceApmInitialized(workdir)) {
1313
+ return { didInit: false };
1314
+ }
1315
+ console.log(
1316
+ options?.webide ? `[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u6309 WebIDE \u81EA\u52A8\u521D\u59CB\u5316\u2026` : `[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
1317
+ );
1318
+ return initializeWorkspace(workdir, options);
1319
+ }
1320
+ async function runInit(name, options) {
1321
+ const workdir = resolveWorkdirPath();
1322
+ await initializeWorkspace(workdir, {
1323
+ name,
1324
+ webide: options?.webide === true
1325
+ });
1326
+ console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
1327
+ }
1328
+
1329
+ // src/commands/login.ts
1330
+ init_config();
1331
+ init_client();
1332
+ import { existsSync as existsSync3 } from "fs";
1333
+ import { ApiError } from "listpage-http";
1215
1334
 
1216
1335
  // src/deployment-config-sync.ts
1217
1336
  init_client();
1218
- import { join as join4 } from "path";
1219
- import { writeFileSync as writeFileSync3 } from "fs";
1337
+ import { join as join5 } from "path";
1338
+ import { writeFileSync as writeFileSync4 } from "fs";
1220
1339
 
1221
1340
  // src/git-remote.ts
1222
1341
  import { execFile } from "child_process";
@@ -1455,16 +1574,16 @@ init_config();
1455
1574
  import {
1456
1575
  existsSync as existsSync2,
1457
1576
  mkdirSync as mkdirSync3,
1458
- readFileSync as readFileSync2,
1577
+ readFileSync as readFileSync3,
1459
1578
  readdirSync as readdirSync2,
1460
1579
  statSync as statSync2,
1461
- writeFileSync as writeFileSync2
1580
+ writeFileSync as writeFileSync3
1462
1581
  } from "fs";
1463
- import { basename as basename2, dirname as dirname2, join as join3, relative, resolve as resolve3 } from "path";
1582
+ import { basename as basename2, dirname as dirname2, join as join4, relative, resolve as resolve3 } from "path";
1464
1583
  var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
1465
1584
  var WORKSPACE_REPOS_VERSION = 1;
1466
1585
  function manifestPath(workdir) {
1467
- return join3(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
1586
+ return join4(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
1468
1587
  }
1469
1588
  function absoluteRepoPath(workdir, entry) {
1470
1589
  if (entry.path === "." || entry.path === "") {
@@ -1493,7 +1612,7 @@ function readManifest(workdir) {
1493
1612
  }
1494
1613
  try {
1495
1614
  const raw = JSON.parse(
1496
- readFileSync2(path19, "utf8")
1615
+ readFileSync3(path19, "utf8")
1497
1616
  );
1498
1617
  if (raw?.version !== WORKSPACE_REPOS_VERSION) {
1499
1618
  return null;
@@ -1526,7 +1645,7 @@ function writeManifest(workdir, manifest) {
1526
1645
  const apmDir = toFsPath(workspaceApmDir(workdir));
1527
1646
  mkdirSync3(apmDir, { recursive: true });
1528
1647
  const path19 = toFsPath(manifestPath(workdir));
1529
- writeFileSync2(path19, `${JSON.stringify(manifest, null, 2)}
1648
+ writeFileSync3(path19, `${JSON.stringify(manifest, null, 2)}
1530
1649
  `, "utf8");
1531
1650
  }
1532
1651
  function isPathInsideOrEqual(parentAbs, childAbs) {
@@ -1679,8 +1798,8 @@ async function writeDeploymentConfigContent(apmDir, content, configName) {
1679
1798
  );
1680
1799
  return false;
1681
1800
  }
1682
- const apmConfigPath = toFsPath(join4(apmDir, "apm.config.json"));
1683
- writeFileSync3(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
1801
+ const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
1802
+ writeFileSync4(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
1684
1803
  `, "utf8");
1685
1804
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
1686
1805
  console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
@@ -1732,324 +1851,7 @@ ${diagnostic ?? ""}
1732
1851
  return { synced: true, repositoryId, configName: config.name };
1733
1852
  }
1734
1853
 
1735
- // src/project-documents-sync.ts
1736
- init_client();
1737
- init_config();
1738
- import {
1739
- existsSync as existsSync3,
1740
- readdirSync as readdirSync3,
1741
- readFileSync as readFileSync3,
1742
- rmSync,
1743
- writeFileSync as writeFileSync4
1744
- } from "fs";
1745
- import { createHash } from "crypto";
1746
- import { dirname as dirname3, join as join5, relative as relative2, sep } from "path";
1747
- var MANIFEST_FILE = "manifest.json";
1748
- function normalizeProjectIdForPath(projectId) {
1749
- const id = projectId.trim();
1750
- if (!id) {
1751
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
1752
- }
1753
- if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
1754
- throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
1755
- }
1756
- return id;
1757
- }
1758
- function projectDocumentsDir(apmRoot, projectId) {
1759
- const id = normalizeProjectIdForPath(projectId);
1760
- return join5(apmRoot ?? workspaceApmDir(), "project", id);
1761
- }
1762
- function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
1763
- const normalized = normalizeLocalDocumentPath(documentPath);
1764
- return join5(
1765
- projectDocumentsDir(apmRoot, projectId),
1766
- ...normalized.split("/")
1767
- );
1768
- }
1769
- function normalizeLocalDocumentPath(path19) {
1770
- const trimmed = path19.trim().replace(/\\/g, "/");
1771
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
1772
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
1773
- }
1774
- const segments = trimmed.split("/").filter(Boolean);
1775
- if (segments.some((segment) => segment === ".." || segment === ".")) {
1776
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
1777
- }
1778
- return segments.join("/");
1779
- }
1780
- function hashLocalFileContent(content) {
1781
- return createHash("sha256").update(content, "utf8").digest("hex");
1782
- }
1783
- function readLocalManifest(apmRoot, projectId) {
1784
- const manifestPath3 = join5(
1785
- projectDocumentsDir(apmRoot, projectId),
1786
- MANIFEST_FILE
1787
- );
1788
- if (!existsSync3(manifestPath3)) {
1789
- return null;
1790
- }
1791
- try {
1792
- return JSON.parse(
1793
- readFileSync3(manifestPath3, "utf8")
1794
- );
1795
- } catch {
1796
- return null;
1797
- }
1798
- }
1799
- function listLocalDocumentPaths(apmRoot, projectId) {
1800
- const root = projectDocumentsDir(apmRoot, projectId);
1801
- if (!existsSync3(root)) {
1802
- return [];
1803
- }
1804
- const paths = [];
1805
- const walk = (dir) => {
1806
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
1807
- const abs = join5(dir, entry.name);
1808
- if (entry.isDirectory()) {
1809
- walk(abs);
1810
- continue;
1811
- }
1812
- if (entry.isFile() && entry.name === MANIFEST_FILE) {
1813
- continue;
1814
- }
1815
- const rel = relative2(root, abs).split(sep).join("/");
1816
- paths.push(rel);
1817
- }
1818
- };
1819
- walk(root);
1820
- return paths.sort();
1821
- }
1822
- function diffManifestPaths(remote, local) {
1823
- const remoteMap = new Map(
1824
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
1825
- );
1826
- const localMap = new Map(
1827
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
1828
- );
1829
- const download = [];
1830
- for (const [path19, hash] of remoteMap) {
1831
- if (localMap.get(path19) !== hash) {
1832
- download.push(path19);
1833
- }
1834
- }
1835
- const deleteLocal = [];
1836
- for (const path19 of localMap.keys()) {
1837
- if (!remoteMap.has(path19)) {
1838
- deleteLocal.push(path19);
1839
- }
1840
- }
1841
- return { download, deleteLocal };
1842
- }
1843
- async function resolveProjectIdForSync(api, workdirPath, options) {
1844
- const explicit = options?.projectId?.trim();
1845
- if (explicit) {
1846
- return { projectId: explicit, diagnostic: null };
1847
- }
1848
- try {
1849
- const baseline = await api.cli.workspaceBaseline({ workdirPath });
1850
- const projectId = baseline.projectId?.trim() || null;
1851
- if (projectId) {
1852
- return { projectId, diagnostic: null };
1853
- }
1854
- const projectIds = baseline.projectIds ?? [];
1855
- if (projectIds.length === 0) {
1856
- return {
1857
- projectId: null,
1858
- diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
1859
- \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
1860
- };
1861
- }
1862
- return {
1863
- projectId: null,
1864
- diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
1865
- \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
1866
- };
1867
- } catch (err) {
1868
- const detail = err instanceof Error ? err.message : String(err);
1869
- return {
1870
- projectId: null,
1871
- diagnostic: detail.replace(/^\[apm\]\s*/, "")
1872
- };
1873
- }
1874
- }
1875
- async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
1876
- const empty = {
1877
- synced: false,
1878
- projectId: null,
1879
- downloaded: 0,
1880
- deleted: 0
1881
- };
1882
- const cfg = await tryReadApmConfig();
1883
- if (!cfg || !resolveApiKey(cfg)) {
1884
- console.log(
1885
- "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
1886
- );
1887
- return empty;
1888
- }
1889
- const api = createApmApiClient(cfg);
1890
- const { projectId, diagnostic } = await resolveProjectIdForSync(
1891
- api,
1892
- workdirPath,
1893
- options
1894
- );
1895
- if (!projectId) {
1896
- console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
1897
- ${diagnostic ?? ""}`);
1898
- return empty;
1899
- }
1900
- const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
1901
- const docsDir = projectDocumentsDir(targetApmDir, projectId);
1902
- await ensureDirExists(docsDir);
1903
- const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
1904
- { projectId }
1905
- );
1906
- if (!remoteManifest) {
1907
- console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
1908
- return { ...empty, projectId };
1909
- }
1910
- const localManifest = readLocalManifest(targetApmDir, projectId);
1911
- const { download, deleteLocal } = diffManifestPaths(
1912
- remoteManifest,
1913
- localManifest
1914
- );
1915
- let downloaded = 0;
1916
- if (download.length > 0) {
1917
- const { list } = await api.cli.listProjectDocuments({
1918
- projectId,
1919
- paths: download.join(",")
1920
- });
1921
- for (const doc of list) {
1922
- const absPath = toFsPath(
1923
- projectDocumentLocalPath(targetApmDir, projectId, doc.path)
1924
- );
1925
- await ensureDirExists(dirname3(absPath));
1926
- writeFileSync4(absPath, doc.content, "utf8");
1927
- downloaded += 1;
1928
- }
1929
- }
1930
- let deleted = 0;
1931
- for (const path19 of deleteLocal) {
1932
- const absPath = toFsPath(
1933
- projectDocumentLocalPath(targetApmDir, projectId, path19)
1934
- );
1935
- if (existsSync3(absPath)) {
1936
- rmSync(absPath, { force: true });
1937
- deleted += 1;
1938
- }
1939
- }
1940
- writeFileSync4(
1941
- toFsPath(join5(docsDir, MANIFEST_FILE)),
1942
- `${JSON.stringify(remoteManifest, null, 2)}
1943
- `,
1944
- "utf8"
1945
- );
1946
- console.log(
1947
- `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
1948
- );
1949
- return {
1950
- synced: true,
1951
- projectId,
1952
- downloaded,
1953
- deleted
1954
- };
1955
- }
1956
- async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
1957
- const api = createApmApiClient(cfg);
1958
- const { projectId } = await resolveProjectIdForSync(
1959
- api,
1960
- workdirPath,
1961
- options
1962
- );
1963
- if (!projectId) {
1964
- return 0;
1965
- }
1966
- const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
1967
- const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
1968
- if (localPaths.length === 0) {
1969
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
1970
- return 0;
1971
- }
1972
- const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
1973
- const remoteHashByPath = new Map(
1974
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
1975
- );
1976
- const remoteDescriptionByPath = new Map(
1977
- (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
1978
- );
1979
- let synced = 0;
1980
- for (const path19 of localPaths) {
1981
- const absPath = toFsPath(
1982
- projectDocumentLocalPath(targetApmDir, projectId, path19)
1983
- );
1984
- const content = readFileSync3(absPath, "utf8");
1985
- const contentHash = hashLocalFileContent(content);
1986
- if (remoteHashByPath.get(path19) === contentHash) {
1987
- continue;
1988
- }
1989
- await api.cli.upsertProjectDocument({
1990
- projectId,
1991
- path: path19,
1992
- content,
1993
- description: remoteDescriptionByPath.get(path19) ?? void 0
1994
- });
1995
- synced += 1;
1996
- console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
1997
- }
1998
- if (synced === 0) {
1999
- console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2000
- }
2001
- return synced;
2002
- }
2003
-
2004
- // src/commands/init.ts
2005
- async function initializeWorkspace(workdir, options) {
2006
- await ensureWorkspaceApmDirForInit(workdir);
2007
- const apmDir = workspaceApmDir(workdir);
2008
- await copyTemplateFiles(apmDir, workdir);
2009
- const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
2010
- await syncProjectDocumentsPull(workdir, apmDir);
2011
- const trimmedName = options?.name?.trim();
2012
- if (trimmedName) {
2013
- const apmConfigPath = toFsPath(join6(apmDir, "apm.config.json"));
2014
- const config = readFileSync4(apmConfigPath, "utf8");
2015
- const configJson = JSON.parse(config);
2016
- configJson.name = trimmedName;
2017
- writeFileSync5(
2018
- apmConfigPath,
2019
- `${JSON.stringify(configJson, null, 2)}
2020
- `,
2021
- "utf8"
2022
- );
2023
- }
2024
- console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
2025
- return { didInit: true, syncResult };
2026
- }
2027
- async function ensureWorkspaceInitialized(workdir, options) {
2028
- if (isWorkspaceApmInitialized(workdir)) {
2029
- return { didInit: false };
2030
- }
2031
- console.log(
2032
- `[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
2033
- );
2034
- return initializeWorkspace(workdir, options);
2035
- }
2036
- async function runInit(name) {
2037
- const workdir = resolveWorkdirPath();
2038
- const { syncResult } = await initializeWorkspace(workdir, { name });
2039
- console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
2040
- if (syncResult && !syncResult.synced) {
2041
- console.log(
2042
- "[apm] \u5F53\u524D .apm/apm.config.json \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u6267\u884C apm sync-deploy-config"
2043
- );
2044
- }
2045
- console.log("[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u63A5\u5165\u7BA1\u7406 \u2192 \u5DE5\u4F5C\u7A7A\u95F4\u300D\u767B\u8BB0\u4E0A\u8FF0\u76EE\u5F55\u8DEF\u5F84");
2046
- }
2047
-
2048
1854
  // src/commands/login.ts
2049
- init_config();
2050
- init_client();
2051
- import { existsSync as existsSync4 } from "fs";
2052
- import { ApiError } from "listpage-http";
2053
1855
  async function runLogin(opts) {
2054
1856
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
2055
1857
  const apiKey = (opts.apiKey?.trim() || process.env.APM_API_KEY?.trim() || "").replace(/^Bearer\s+/i, "");
@@ -2103,7 +1905,7 @@ async function runLogin(opts) {
2103
1905
  );
2104
1906
  const workdir = resolveWorkdirPath();
2105
1907
  const apmDir = workspaceApmDir(workdir);
2106
- if (existsSync4(apmDir)) {
1908
+ if (existsSync3(apmDir)) {
2107
1909
  await syncRemoteDeploymentConfig(workdir, apmDir);
2108
1910
  }
2109
1911
  }
@@ -2593,9 +2395,9 @@ function formatSessionMessagesXml(sessionId, messages) {
2593
2395
  }
2594
2396
 
2595
2397
  // src/commands/sync-session-attachments.ts
2596
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
2597
- import { join as join7 } from "path";
2598
- var MANIFEST_FILE2 = ".sync-manifest.json";
2398
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
2399
+ import { join as join6 } from "path";
2400
+ var MANIFEST_FILE = ".sync-manifest.json";
2599
2401
  async function downloadAttachment(cfg, attachmentId) {
2600
2402
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
2601
2403
  const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
@@ -2610,13 +2412,13 @@ async function downloadAttachment(cfg, attachmentId) {
2610
2412
  return Buffer.from(await res.arrayBuffer());
2611
2413
  }
2612
2414
  function loadManifest(dir) {
2613
- const path19 = join7(dir, MANIFEST_FILE2);
2614
- if (!existsSync5(path19)) {
2415
+ const path19 = join6(dir, MANIFEST_FILE);
2416
+ if (!existsSync4(path19)) {
2615
2417
  return { version: 1, attachments: {} };
2616
2418
  }
2617
2419
  try {
2618
2420
  const parsed = JSON.parse(
2619
- readFileSync5(path19, "utf8")
2421
+ readFileSync4(path19, "utf8")
2620
2422
  );
2621
2423
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
2622
2424
  return parsed;
@@ -2626,15 +2428,15 @@ function loadManifest(dir) {
2626
2428
  return { version: 1, attachments: {} };
2627
2429
  }
2628
2430
  function saveManifest(dir, manifest) {
2629
- writeFileSync6(
2630
- join7(dir, MANIFEST_FILE2),
2431
+ writeFileSync5(
2432
+ join6(dir, MANIFEST_FILE),
2631
2433
  `${JSON.stringify(manifest, null, 2)}
2632
2434
  `,
2633
2435
  "utf8"
2634
2436
  );
2635
2437
  }
2636
2438
  function isAttachmentUpToDate(entry, item, dest) {
2637
- if (!entry || !existsSync5(dest)) return false;
2439
+ if (!entry || !existsSync4(dest)) return false;
2638
2440
  if (entry.name !== item.name) return false;
2639
2441
  const createdAt = item.createdAt ?? "";
2640
2442
  return entry.createdAt === createdAt;
@@ -2649,7 +2451,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
2649
2451
  const nextManifest = { version: 1, attachments: {} };
2650
2452
  const names = [];
2651
2453
  for (const item of attachments) {
2652
- const dest = join7(dir, item.name);
2454
+ const dest = join6(dir, item.name);
2653
2455
  const entry = manifest.attachments[item.id];
2654
2456
  const createdAt = item.createdAt ?? "";
2655
2457
  names.push(item.name);
@@ -2659,7 +2461,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
2659
2461
  continue;
2660
2462
  }
2661
2463
  const buffer = await downloadAttachment(cfg, item.id);
2662
- writeFileSync6(dest, buffer);
2464
+ writeFileSync5(dest, buffer);
2663
2465
  nextManifest.attachments[item.id] = {
2664
2466
  name: item.name,
2665
2467
  createdAt
@@ -2670,7 +2472,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
2670
2472
  return names;
2671
2473
  }
2672
2474
  async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
2673
- const dir = join7(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
2475
+ const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
2674
2476
  return syncAttachmentsToDirectory(
2675
2477
  cfg,
2676
2478
  attachments,
@@ -2681,46 +2483,46 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
2681
2483
 
2682
2484
  // src/rules-sync.ts
2683
2485
  init_client();
2684
- import { basename as basename3, extname as extname2, join as join9 } from "path";
2685
- import { existsSync as existsSync7, readFileSync as readFileSync6, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
2486
+ import { basename as basename3, extname as extname2, join as join8 } from "path";
2487
+ import { existsSync as existsSync6, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
2686
2488
 
2687
2489
  // src/skills-sync.ts
2688
2490
  import {
2689
2491
  copyFileSync as copyFileSync2,
2690
2492
  cpSync,
2691
- existsSync as existsSync6,
2493
+ existsSync as existsSync5,
2692
2494
  mkdirSync as mkdirSync4,
2693
- readdirSync as readdirSync4,
2694
- rmSync as rmSync2,
2495
+ readdirSync as readdirSync3,
2496
+ rmSync,
2695
2497
  statSync as statSync3,
2696
- writeFileSync as writeFileSync7
2498
+ writeFileSync as writeFileSync6
2697
2499
  } from "fs";
2698
- import { join as join8 } from "path";
2699
- var AGENTS_TEMPLATE_PATH = join8(CLI_TEMPLATE_DIR, "AGENTS.md");
2700
- var BASE_SKILLS_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "skills");
2701
- var BASE_RULES_TEMPLATE_DIR = join8(CLI_TEMPLATE_DIR, "rules");
2500
+ import { join as join7 } from "path";
2501
+ var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
2502
+ var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
2503
+ var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
2702
2504
  function sanitizeSkillDirName(name) {
2703
2505
  const trimmed = name.trim();
2704
2506
  if (!trimmed) return "skill";
2705
2507
  return trimmed.replace(/[/\\:*?"<>|]/g, "_");
2706
2508
  }
2707
2509
  function listBaseSkillDirNames() {
2708
- if (!existsSync6(BASE_SKILLS_TEMPLATE_DIR)) return [];
2709
- return readdirSync4(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
2710
- const path19 = join8(BASE_SKILLS_TEMPLATE_DIR, name);
2510
+ if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
2511
+ return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
2512
+ const path19 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
2711
2513
  return statSync3(path19).isDirectory();
2712
2514
  });
2713
2515
  }
2714
2516
  function syncAgentsGuide(apmDir) {
2715
- if (!existsSync6(AGENTS_TEMPLATE_PATH)) return false;
2517
+ if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
2716
2518
  mkdirSync4(apmDir, { recursive: true });
2717
- copyFileSync2(AGENTS_TEMPLATE_PATH, join8(apmDir, "AGENTS.md"));
2519
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
2718
2520
  return true;
2719
2521
  }
2720
2522
  function listBaseRuleFileNames() {
2721
- if (!existsSync6(BASE_RULES_TEMPLATE_DIR)) return [];
2722
- return readdirSync4(BASE_RULES_TEMPLATE_DIR).filter((name) => {
2723
- const path19 = join8(BASE_RULES_TEMPLATE_DIR, name);
2523
+ if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
2524
+ return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
2525
+ const path19 = join7(BASE_RULES_TEMPLATE_DIR, name);
2724
2526
  return statSync3(path19).isFile();
2725
2527
  });
2726
2528
  }
@@ -2728,8 +2530,8 @@ function syncBaseRules(rulesDir) {
2728
2530
  mkdirSync4(rulesDir, { recursive: true });
2729
2531
  const names = listBaseRuleFileNames();
2730
2532
  for (const name of names) {
2731
- const src = join8(BASE_RULES_TEMPLATE_DIR, name);
2732
- const dest = join8(rulesDir, name);
2533
+ const src = join7(BASE_RULES_TEMPLATE_DIR, name);
2534
+ const dest = join7(rulesDir, name);
2733
2535
  copyFileSync2(src, dest);
2734
2536
  }
2735
2537
  return names;
@@ -2738,8 +2540,8 @@ function syncBaseSkills(skillsDir) {
2738
2540
  mkdirSync4(skillsDir, { recursive: true });
2739
2541
  const names = listBaseSkillDirNames();
2740
2542
  for (const name of names) {
2741
- const src = join8(BASE_SKILLS_TEMPLATE_DIR, name);
2742
- const dest = join8(skillsDir, name);
2543
+ const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
2544
+ const dest = join7(skillsDir, name);
2743
2545
  cpSync(src, dest, { recursive: true, force: true });
2744
2546
  }
2745
2547
  return names;
@@ -2756,26 +2558,26 @@ function syncSupplementarySkills(skillsDir, list) {
2756
2558
  skipped.push(dirName);
2757
2559
  continue;
2758
2560
  }
2759
- const skillDir = join8(skillsDir, dirName);
2561
+ const skillDir = join7(skillsDir, dirName);
2760
2562
  mkdirSync4(skillDir, { recursive: true });
2761
- writeFileSync7(join8(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
2563
+ writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
2762
2564
  written.push(dirName);
2763
2565
  }
2764
2566
  const removed = [];
2765
- if (!existsSync6(skillsDir)) return { written, skipped, removed };
2766
- for (const entry of readdirSync4(skillsDir)) {
2767
- const full = join8(skillsDir, entry);
2567
+ if (!existsSync5(skillsDir)) return { written, skipped, removed };
2568
+ for (const entry of readdirSync3(skillsDir)) {
2569
+ const full = join7(skillsDir, entry);
2768
2570
  if (!statSync3(full).isDirectory()) continue;
2769
2571
  if (baseNames.has(entry)) continue;
2770
2572
  if (apiDirNames.has(entry)) continue;
2771
- rmSync2(full, { recursive: true, force: true });
2573
+ rmSync(full, { recursive: true, force: true });
2772
2574
  removed.push(entry);
2773
2575
  }
2774
2576
  return { written, skipped, removed };
2775
2577
  }
2776
2578
 
2777
2579
  // src/rules-sync.ts
2778
- var MANIFEST_FILE3 = ".rules-sync-manifest.json";
2580
+ var MANIFEST_FILE2 = ".rules-sync-manifest.json";
2779
2581
  function ruleLocalFileName(ruleName) {
2780
2582
  const trimmed = ruleName.trim();
2781
2583
  if (!trimmed) return "rule.md";
@@ -2784,13 +2586,13 @@ function ruleLocalFileName(ruleName) {
2784
2586
  return `${sanitized}.md`;
2785
2587
  }
2786
2588
  function loadManifest2(rulesDir) {
2787
- const path19 = join9(rulesDir, MANIFEST_FILE3);
2788
- if (!existsSync7(toFsPath(path19))) {
2589
+ const path19 = join8(rulesDir, MANIFEST_FILE2);
2590
+ if (!existsSync6(toFsPath(path19))) {
2789
2591
  return { version: 1, rules: {} };
2790
2592
  }
2791
2593
  try {
2792
2594
  const parsed = JSON.parse(
2793
- readFileSync6(toFsPath(path19), "utf8")
2595
+ readFileSync5(toFsPath(path19), "utf8")
2794
2596
  );
2795
2597
  if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
2796
2598
  return parsed;
@@ -2800,8 +2602,8 @@ function loadManifest2(rulesDir) {
2800
2602
  return { version: 1, rules: {} };
2801
2603
  }
2802
2604
  function saveManifest2(rulesDir, manifest) {
2803
- writeFileSync8(
2804
- toFsPath(join9(rulesDir, MANIFEST_FILE3)),
2605
+ writeFileSync7(
2606
+ toFsPath(join8(rulesDir, MANIFEST_FILE2)),
2805
2607
  `${JSON.stringify(manifest, null, 2)}
2806
2608
  `,
2807
2609
  "utf8"
@@ -2811,18 +2613,18 @@ function isBaseRuleFileName(fileName) {
2811
2613
  return listBaseRuleFileNames().includes(basename3(fileName));
2812
2614
  }
2813
2615
  function isRuleUpToDate(entry, rule, dest) {
2814
- if (!entry || !existsSync7(toFsPath(dest))) return false;
2616
+ if (!entry || !existsSync6(toFsPath(dest))) return false;
2815
2617
  if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
2816
2618
  const updatedAt = rule.updatedAt ?? "";
2817
2619
  if (entry.updatedAt !== updatedAt) return false;
2818
- const localContent = readFileSync6(toFsPath(dest), "utf8");
2620
+ const localContent = readFileSync5(toFsPath(dest), "utf8");
2819
2621
  return localContent === (rule.content ?? "");
2820
2622
  }
2821
2623
  async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2822
2624
  const api = createApmApiClient(cfg);
2823
2625
  const baseline = await resolveBranchBaseline(api, sessionId, workdirPath);
2824
2626
  const repositoryId = baseline.repositoryId;
2825
- const rulesDir = join9(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2627
+ const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2826
2628
  await ensureDirExists(rulesDir);
2827
2629
  if (!repositoryId) {
2828
2630
  console.log(
@@ -2839,7 +2641,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2839
2641
  for (const rule of list) {
2840
2642
  remoteIds.add(rule.id);
2841
2643
  const fileName = ruleLocalFileName(rule.name);
2842
- const dest = join9(rulesDir, fileName);
2644
+ const dest = join8(rulesDir, fileName);
2843
2645
  const entry = manifest.rules[rule.id];
2844
2646
  const updatedAt = rule.updatedAt ?? "";
2845
2647
  if (isRuleUpToDate(entry, rule, dest)) {
@@ -2848,7 +2650,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2848
2650
  console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2849
2651
  continue;
2850
2652
  }
2851
- writeFileSync8(toFsPath(dest), rule.content ?? "", "utf8");
2653
+ writeFileSync7(toFsPath(dest), rule.content ?? "", "utf8");
2852
2654
  nextManifest.rules[rule.id] = { fileName, updatedAt };
2853
2655
  written.push(fileName);
2854
2656
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
@@ -2857,9 +2659,9 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2857
2659
  for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2858
2660
  if (remoteIds.has(ruleId)) continue;
2859
2661
  if (isBaseRuleFileName(entry.fileName)) continue;
2860
- const dest = join9(rulesDir, entry.fileName);
2861
- if (existsSync7(toFsPath(dest))) {
2862
- rmSync3(toFsPath(dest), { force: true });
2662
+ const dest = join8(rulesDir, entry.fileName);
2663
+ if (existsSync6(toFsPath(dest))) {
2664
+ rmSync2(toFsPath(dest), { force: true });
2863
2665
  }
2864
2666
  removed.push(entry.fileName);
2865
2667
  console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
@@ -2868,6 +2670,275 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
2868
2670
  return { written, skipped, removed, repositoryId };
2869
2671
  }
2870
2672
 
2673
+ // src/project-documents-sync.ts
2674
+ init_client();
2675
+ init_config();
2676
+ import {
2677
+ existsSync as existsSync7,
2678
+ readdirSync as readdirSync4,
2679
+ readFileSync as readFileSync6,
2680
+ rmSync as rmSync3,
2681
+ writeFileSync as writeFileSync8
2682
+ } from "fs";
2683
+ import { createHash } from "crypto";
2684
+ import { dirname as dirname3, join as join9, relative as relative2, sep } from "path";
2685
+ var MANIFEST_FILE3 = "manifest.json";
2686
+ function normalizeProjectIdForPath(projectId) {
2687
+ const id = projectId.trim();
2688
+ if (!id) {
2689
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
2690
+ }
2691
+ if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
2692
+ throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
2693
+ }
2694
+ return id;
2695
+ }
2696
+ function projectDocumentsDir(apmRoot, projectId) {
2697
+ const id = normalizeProjectIdForPath(projectId);
2698
+ return join9(apmRoot ?? workspaceApmDir(), "project", id);
2699
+ }
2700
+ function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
2701
+ const normalized = normalizeLocalDocumentPath(documentPath);
2702
+ return join9(
2703
+ projectDocumentsDir(apmRoot, projectId),
2704
+ ...normalized.split("/")
2705
+ );
2706
+ }
2707
+ function normalizeLocalDocumentPath(path19) {
2708
+ const trimmed = path19.trim().replace(/\\/g, "/");
2709
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
2710
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
2711
+ }
2712
+ const segments = trimmed.split("/").filter(Boolean);
2713
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
2714
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
2715
+ }
2716
+ return segments.join("/");
2717
+ }
2718
+ function hashLocalFileContent(content) {
2719
+ return createHash("sha256").update(content, "utf8").digest("hex");
2720
+ }
2721
+ function readLocalManifest(apmRoot, projectId) {
2722
+ const manifestPath3 = join9(
2723
+ projectDocumentsDir(apmRoot, projectId),
2724
+ MANIFEST_FILE3
2725
+ );
2726
+ if (!existsSync7(manifestPath3)) {
2727
+ return null;
2728
+ }
2729
+ try {
2730
+ return JSON.parse(
2731
+ readFileSync6(manifestPath3, "utf8")
2732
+ );
2733
+ } catch {
2734
+ return null;
2735
+ }
2736
+ }
2737
+ function listLocalDocumentPaths(apmRoot, projectId) {
2738
+ const root = projectDocumentsDir(apmRoot, projectId);
2739
+ if (!existsSync7(root)) {
2740
+ return [];
2741
+ }
2742
+ const paths = [];
2743
+ const walk = (dir) => {
2744
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
2745
+ const abs = join9(dir, entry.name);
2746
+ if (entry.isDirectory()) {
2747
+ walk(abs);
2748
+ continue;
2749
+ }
2750
+ if (entry.isFile() && entry.name === MANIFEST_FILE3) {
2751
+ continue;
2752
+ }
2753
+ const rel = relative2(root, abs).split(sep).join("/");
2754
+ paths.push(rel);
2755
+ }
2756
+ };
2757
+ walk(root);
2758
+ return paths.sort();
2759
+ }
2760
+ function diffManifestPaths(remote, local) {
2761
+ const remoteMap = new Map(
2762
+ (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2763
+ );
2764
+ const localMap = new Map(
2765
+ (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2766
+ );
2767
+ const download = [];
2768
+ for (const [path19, hash] of remoteMap) {
2769
+ if (localMap.get(path19) !== hash) {
2770
+ download.push(path19);
2771
+ }
2772
+ }
2773
+ const deleteLocal = [];
2774
+ for (const path19 of localMap.keys()) {
2775
+ if (!remoteMap.has(path19)) {
2776
+ deleteLocal.push(path19);
2777
+ }
2778
+ }
2779
+ return { download, deleteLocal };
2780
+ }
2781
+ async function resolveProjectIdForSync(api, workdirPath, options) {
2782
+ const explicit = options?.projectId?.trim();
2783
+ if (explicit) {
2784
+ return { projectId: explicit, diagnostic: null };
2785
+ }
2786
+ try {
2787
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
2788
+ const projectId = baseline.projectId?.trim() || null;
2789
+ if (projectId) {
2790
+ return { projectId, diagnostic: null };
2791
+ }
2792
+ const projectIds = baseline.projectIds ?? [];
2793
+ if (projectIds.length === 0) {
2794
+ return {
2795
+ projectId: null,
2796
+ diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
2797
+ \u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
2798
+ };
2799
+ }
2800
+ return {
2801
+ projectId: null,
2802
+ diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
2803
+ \u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
2804
+ };
2805
+ } catch (err) {
2806
+ const detail = err instanceof Error ? err.message : String(err);
2807
+ return {
2808
+ projectId: null,
2809
+ diagnostic: detail.replace(/^\[apm\]\s*/, "")
2810
+ };
2811
+ }
2812
+ }
2813
+ async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
2814
+ const empty = {
2815
+ synced: false,
2816
+ projectId: null,
2817
+ downloaded: 0,
2818
+ deleted: 0
2819
+ };
2820
+ const cfg = await tryReadApmConfig();
2821
+ if (!cfg || !resolveApiKey(cfg)) {
2822
+ console.log(
2823
+ "[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
2824
+ );
2825
+ return empty;
2826
+ }
2827
+ const api = createApmApiClient(cfg);
2828
+ const { projectId, diagnostic } = await resolveProjectIdForSync(
2829
+ api,
2830
+ workdirPath,
2831
+ options
2832
+ );
2833
+ if (!projectId) {
2834
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
2835
+ ${diagnostic ?? ""}`);
2836
+ return empty;
2837
+ }
2838
+ const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
2839
+ const docsDir = projectDocumentsDir(targetApmDir, projectId);
2840
+ await ensureDirExists(docsDir);
2841
+ const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
2842
+ { projectId }
2843
+ );
2844
+ if (!remoteManifest) {
2845
+ console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
2846
+ return { ...empty, projectId };
2847
+ }
2848
+ const localManifest = readLocalManifest(targetApmDir, projectId);
2849
+ const { download, deleteLocal } = diffManifestPaths(
2850
+ remoteManifest,
2851
+ localManifest
2852
+ );
2853
+ let downloaded = 0;
2854
+ if (download.length > 0) {
2855
+ const { list } = await api.cli.listProjectDocuments({
2856
+ projectId,
2857
+ paths: download.join(",")
2858
+ });
2859
+ for (const doc of list) {
2860
+ const absPath = toFsPath(
2861
+ projectDocumentLocalPath(targetApmDir, projectId, doc.path)
2862
+ );
2863
+ await ensureDirExists(dirname3(absPath));
2864
+ writeFileSync8(absPath, doc.content, "utf8");
2865
+ downloaded += 1;
2866
+ }
2867
+ }
2868
+ let deleted = 0;
2869
+ for (const path19 of deleteLocal) {
2870
+ const absPath = toFsPath(
2871
+ projectDocumentLocalPath(targetApmDir, projectId, path19)
2872
+ );
2873
+ if (existsSync7(absPath)) {
2874
+ rmSync3(absPath, { force: true });
2875
+ deleted += 1;
2876
+ }
2877
+ }
2878
+ writeFileSync8(
2879
+ toFsPath(join9(docsDir, MANIFEST_FILE3)),
2880
+ `${JSON.stringify(remoteManifest, null, 2)}
2881
+ `,
2882
+ "utf8"
2883
+ );
2884
+ console.log(
2885
+ `[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
2886
+ );
2887
+ return {
2888
+ synced: true,
2889
+ projectId,
2890
+ downloaded,
2891
+ deleted
2892
+ };
2893
+ }
2894
+ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
2895
+ const api = createApmApiClient(cfg);
2896
+ const { projectId } = await resolveProjectIdForSync(
2897
+ api,
2898
+ workdirPath,
2899
+ options
2900
+ );
2901
+ if (!projectId) {
2902
+ return 0;
2903
+ }
2904
+ const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
2905
+ const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
2906
+ if (localPaths.length === 0) {
2907
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2908
+ return 0;
2909
+ }
2910
+ const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
2911
+ const remoteHashByPath = new Map(
2912
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
2913
+ );
2914
+ const remoteDescriptionByPath = new Map(
2915
+ (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
2916
+ );
2917
+ let synced = 0;
2918
+ for (const path19 of localPaths) {
2919
+ const absPath = toFsPath(
2920
+ projectDocumentLocalPath(targetApmDir, projectId, path19)
2921
+ );
2922
+ const content = readFileSync6(absPath, "utf8");
2923
+ const contentHash = hashLocalFileContent(content);
2924
+ if (remoteHashByPath.get(path19) === contentHash) {
2925
+ continue;
2926
+ }
2927
+ await api.cli.upsertProjectDocument({
2928
+ projectId,
2929
+ path: path19,
2930
+ content,
2931
+ description: remoteDescriptionByPath.get(path19) ?? void 0
2932
+ });
2933
+ synced += 1;
2934
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
2935
+ }
2936
+ if (synced === 0) {
2937
+ console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
2938
+ }
2939
+ return synced;
2940
+ }
2941
+
2871
2942
  // src/commands/pull.ts
2872
2943
  async function runPull(sessionId, remoteWorkdir) {
2873
2944
  const trimmedId = sessionId.trim();
@@ -10557,10 +10628,11 @@ function buildProgram() {
10557
10628
  ).option("--api-key <key>", "\u5BA2\u6237\u673A API Key\uFF08cm_ \u5F00\u5934\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
10558
10629
  await runLogin(opts);
10559
10630
  });
10560
- program.command("init").description(
10561
- "\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521B\u5EFA .apm \u5E76\u521D\u59CB\u5316\u6A21\u677F\uFF1B\u5DF2\u767B\u5F55\u4E14\u5E73\u53F0\u6709\u5339\u914D\u90E8\u7F72\u914D\u7F6E\u65F6\u4F1A\u540C\u6B65 apm.config.json"
10562
- ).option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
10563
- await runInit(opts.name);
10631
+ program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521B\u5EFA .apm \u5E76\u521D\u59CB\u5316\u6A21\u677F").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").option(
10632
+ "--webide",
10633
+ "\u6309 WebIDE \u5DE5\u4F5C\u533A\u521D\u59CB\u5316\uFF1A\u4EC5 webide \u89C4\u5219\u4E0E\u6307\u5357\uFF0C\u4E0D\u542B sessions / skills / deploy"
10634
+ ).action(async (opts) => {
10635
+ await runInit(opts.name, { webide: opts.webide === true });
10564
10636
  });
10565
10637
  program.command("update").description(
10566
10638
  `\u901A\u8FC7 npm \u5168\u5C40\u5B89\u88C5 ${CLI_PACKAGE_NAME}\uFF0C\u9ED8\u8BA4\u66F4\u65B0\u5230\u5F53\u524D\u5927\u7248\u672C\uFF08${parseMajorVersion(