ai-project-manage-cli 5.0.13 → 5.0.15

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
@@ -10,6 +10,12 @@ import { join } from "path";
10
10
  var APM_CONFIG_DIR = join(homedir(), ".config", "apm");
11
11
  var APM_CONFIG_PATH = join(APM_CONFIG_DIR, "config.json");
12
12
  var DEFAULT_BASE_URL = "http://127.0.0.1:3000";
13
+ function resolveClientMachineId(cfg) {
14
+ return (cfg.clientMachineId ?? cfg.userId ?? "").trim();
15
+ }
16
+ function resolveAccount(cfg) {
17
+ return (cfg.account ?? cfg.email ?? "").trim();
18
+ }
13
19
  async function readApmConfig() {
14
20
  try {
15
21
  const raw = readFileSync(APM_CONFIG_PATH, "utf8");
@@ -18,14 +24,17 @@ async function readApmConfig() {
18
24
  return null;
19
25
  }
20
26
  const rawCfg = v;
21
- if (typeof rawCfg.baseUrl !== "string" || typeof rawCfg.userId !== "string" || typeof rawCfg.token !== "string") {
27
+ if (typeof rawCfg.baseUrl !== "string" || typeof rawCfg.token !== "string") {
22
28
  return null;
23
29
  }
24
30
  const cfg = v;
31
+ const clientMachineId = resolveClientMachineId(cfg);
32
+ const account = resolveAccount(cfg);
25
33
  return {
26
34
  baseUrl: cfg.baseUrl.trim().replace(/\/+$/, ""),
27
- userId: cfg.userId,
28
35
  token: cfg.token,
36
+ ...clientMachineId ? { clientMachineId } : {},
37
+ ...account ? { account } : {},
29
38
  ...typeof cfg.email === "string" ? { email: cfg.email } : {}
30
39
  };
31
40
  } catch {
@@ -35,8 +44,9 @@ async function readApmConfig() {
35
44
  function defaultApmConfig() {
36
45
  return {
37
46
  baseUrl: DEFAULT_BASE_URL,
38
- userId: "",
47
+ clientMachineId: "",
39
48
  token: "",
49
+ account: "",
40
50
  email: ""
41
51
  };
42
52
  }
@@ -54,11 +64,13 @@ async function ensureApmConfig() {
54
64
  return defaults;
55
65
  }
56
66
  async function writeApmConfig(cfg) {
67
+ const clientMachineId = resolveClientMachineId(cfg);
68
+ const account = resolveAccount(cfg);
57
69
  const normalized = {
58
70
  baseUrl: cfg.baseUrl.trim().replace(/\/+$/, ""),
59
- userId: cfg.userId,
60
71
  token: cfg.token,
61
- ...typeof cfg.email === "string" ? { email: cfg.email } : {}
72
+ ...clientMachineId ? { clientMachineId } : {},
73
+ ...account ? { account, email: account } : {}
62
74
  };
63
75
  mkdirSync(APM_CONFIG_DIR, { recursive: true });
64
76
  writeFileSync(APM_CONFIG_PATH, JSON.stringify(normalized, null, 2) + "\n", {
@@ -256,13 +268,13 @@ async function runLogin(opts) {
256
268
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
257
269
  const api = createApmApiClient({
258
270
  baseUrl,
259
- userId: "",
271
+ clientMachineId: "",
260
272
  token: ""
261
273
  });
262
274
  let data;
263
275
  try {
264
276
  data = await api.auth.login({
265
- email: opts.email,
277
+ account: opts.email,
266
278
  password: opts.password
267
279
  });
268
280
  } catch (raw) {
@@ -277,24 +289,29 @@ async function runLogin(opts) {
277
289
  console.error("[apm] \u8BF7\u6C42\u5931\u8D25:", raw instanceof Error ? raw.message : raw);
278
290
  process.exit(1);
279
291
  }
280
- const userId = data?.user?.id;
292
+ const clientMachineId = data?.clientMachine?.id;
281
293
  const token = data?.token;
282
- if (!userId || !token) {
294
+ if (!clientMachineId || !token) {
283
295
  console.error(
284
- "[apm] \u54CD\u5E94\u7F3A\u5C11 user.id / token\uFF08\u8BF7\u786E\u8BA4\u670D\u52A1\u7AEF\u4E3A /api/v1/auth/login\uFF09"
296
+ "[apm] \u54CD\u5E94\u7F3A\u5C11 clientMachine.id / token\uFF08\u8BF7\u786E\u8BA4\u670D\u52A1\u7AEF\u4E3A /api/v1/auth/login\uFF09"
285
297
  );
286
298
  process.exit(1);
287
299
  }
288
300
  const cfg = {
289
301
  baseUrl,
290
- userId,
302
+ clientMachineId,
291
303
  token,
304
+ account: opts.email,
292
305
  email: opts.email
293
306
  };
294
307
  await writeApmConfig(cfg);
295
308
  console.error(`[apm] \u5DF2\u4FDD\u5B58\u767B\u5F55\u4FE1\u606F: ${APM_CONFIG_PATH}`);
296
309
  console.log(
297
- JSON.stringify({ userId: cfg.userId, baseUrl: cfg.baseUrl }, null, 2)
310
+ JSON.stringify(
311
+ { clientMachineId: cfg.clientMachineId, baseUrl: cfg.baseUrl },
312
+ null,
313
+ 2
314
+ )
298
315
  );
299
316
  }
300
317
 
@@ -489,7 +506,7 @@ function formatSessionMessagesXml(sessionId, messages) {
489
506
  lines.push(
490
507
  ` <message id="${escapeXmlAttr(message.id)}" name="${escapeXmlAttr(
491
508
  message.name
492
- )}" position="${escapeXmlAttr(message.position)}"${roundAttr}>`,
509
+ )}" expert="${escapeXmlAttr(message.expert)}"${roundAttr}>`,
493
510
  ` <content>${wrapCdata(message.content)}</content>`,
494
511
  " </message>"
495
512
  );
@@ -613,7 +630,7 @@ async function runPull(sessionId, apmRoot) {
613
630
  description: "./RULE.md",
614
631
  members: members.map((m) => ({
615
632
  name: m.displayName,
616
- position: m.position,
633
+ expert: m.expert,
617
634
  system_persona: m.systemPersona,
618
635
  skills: []
619
636
  })),
@@ -1102,6 +1119,7 @@ async function handleInboundMessage(cfg, raw) {
1102
1119
  try {
1103
1120
  await runBranch(msg.sessionId, { cwd: msg.workdir });
1104
1121
  await runPull(msg.sessionId, workspaceApmDir(msg.workdir));
1122
+ await commitWorkingTreeIfDirty(msg.workdir, "fix: apm pull");
1105
1123
  await updateMessageStatus(cfg, messageId, "TYPING");
1106
1124
  await runCursorAgent(cfg, {
1107
1125
  messageId: msg.messageId,
@@ -1134,13 +1152,13 @@ async function handleInboundMessage(cfg, raw) {
1134
1152
  }
1135
1153
  }
1136
1154
  }
1137
- function startHeartbeat(ws, userId) {
1155
+ function startHeartbeat(ws, clientMachineId) {
1138
1156
  const send = () => {
1139
1157
  if (ws.readyState === WebSocket.OPEN) {
1140
1158
  ws.send(
1141
1159
  serializeAgentWsMessage({
1142
1160
  type: "heartbeat",
1143
- userId
1161
+ userId: clientMachineId
1144
1162
  })
1145
1163
  );
1146
1164
  }
@@ -1154,8 +1172,9 @@ async function runConnect(options) {
1154
1172
  if (options.server?.trim()) {
1155
1173
  cfg.baseUrl = options.server.trim().replace(/\/+$/, "");
1156
1174
  }
1157
- if (!cfg.userId?.trim()) {
1158
- console.error("[apm] config \u7F3A\u5C11 userId\uFF0C\u8BF7\u91CD\u65B0 apm login");
1175
+ const clientMachineId = resolveClientMachineId(cfg);
1176
+ if (!clientMachineId) {
1177
+ console.error("[apm] config \u7F3A\u5C11 clientMachineId\uFF0C\u8BF7\u91CD\u65B0 apm login");
1159
1178
  process.exit(1);
1160
1179
  }
1161
1180
  const url = buildAgentWsUrl(cfg.baseUrl, cfg.token);
@@ -1178,7 +1197,7 @@ async function runConnect(options) {
1178
1197
  };
1179
1198
  ws.on("open", () => {
1180
1199
  console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
1181
- stopHeartbeat = startHeartbeat(ws, cfg.userId);
1200
+ stopHeartbeat = startHeartbeat(ws, clientMachineId);
1182
1201
  });
1183
1202
  ws.on("message", (data) => {
1184
1203
  const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
@@ -1862,40 +1881,6 @@ function registerDeployBackendCommands(program) {
1862
1881
  import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
1863
1882
  import path7 from "node:path";
1864
1883
 
1865
- // src/commands/deploy/internal/load-apm-dotenv.ts
1866
- import { existsSync as existsSync10, readFileSync as readFileSync9 } from "node:fs";
1867
- import { join as join9 } from "node:path";
1868
- function loadApmDotEnvIfPresent() {
1869
- const p = join9(workspaceApmDir(), ".env");
1870
- if (!existsSync10(p)) {
1871
- return;
1872
- }
1873
- let text;
1874
- try {
1875
- text = readFileSync9(p, "utf8");
1876
- } catch {
1877
- return;
1878
- }
1879
- for (const line of text.split("\n")) {
1880
- const t = line.trim();
1881
- if (!t || t.startsWith("#")) {
1882
- continue;
1883
- }
1884
- const eq = t.indexOf("=");
1885
- if (eq <= 0) {
1886
- continue;
1887
- }
1888
- const key = t.slice(0, eq).trim();
1889
- let val = t.slice(eq + 1).trim();
1890
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
1891
- val = val.slice(1, -1);
1892
- }
1893
- if (process.env[key] === void 0) {
1894
- process.env[key] = val;
1895
- }
1896
- }
1897
- }
1898
-
1899
1884
  // src/commands/deploy/internal/minio.ts
1900
1885
  import { statSync as statSync5 } from "node:fs";
1901
1886
  import { readdir, readFile } from "node:fs/promises";
@@ -2059,31 +2044,6 @@ function artifactObjectKey(namePrefix, branchSegment, relativePath) {
2059
2044
  const rel = sanitizeRelativePath(relativePath);
2060
2045
  return `${base}/${branchSegment}/dist/${rel}`;
2061
2046
  }
2062
- function mergeMinioFromEnv(settings) {
2063
- const ep = process.env.MINIO_ENDPOINT?.trim();
2064
- const portRaw = process.env.MINIO_PORT?.trim();
2065
- const sslRaw = process.env.MINIO_USE_SSL?.trim().toLowerCase();
2066
- const ak = process.env.MINIO_ACCESS_KEY?.trim();
2067
- const sk = process.env.MINIO_SECRET_KEY?.trim();
2068
- const bucket = process.env.MINIO_BUCKET?.trim();
2069
- const port = portRaw ? Number.parseInt(portRaw, 10) : void 0;
2070
- let useSsl = settings.useSsl;
2071
- if (sslRaw === "true" || sslRaw === "1") {
2072
- useSsl = true;
2073
- }
2074
- if (sslRaw === "false" || sslRaw === "0") {
2075
- useSsl = false;
2076
- }
2077
- return {
2078
- ...settings,
2079
- endpoint: ep || settings.endpoint,
2080
- port: port !== void 0 && Number.isFinite(port) && port > 0 ? port : settings.port,
2081
- useSsl,
2082
- accessKey: ak || settings.accessKey,
2083
- secretKey: sk || settings.secretKey,
2084
- bucket: bucket || settings.bucket
2085
- };
2086
- }
2087
2047
  async function ensureArtifactRootIndexHtml(root) {
2088
2048
  const indexHtmlPath = path7.join(root, "index.html");
2089
2049
  try {
@@ -2124,7 +2084,7 @@ async function ensureArtifactRootIndexHtml(root) {
2124
2084
  }
2125
2085
  function registerDeployFrontendCommands(program) {
2126
2086
  program.command("deploy-frontend").description(
2127
- "\u9012\u5F52\u4E0A\u4F20\u524D\u7AEF\u4EA7\u7269\u76EE\u5F55\u5230 MinIO\uFF0C\u5E76\u5728\u6210\u529F\u540E\u4E3A\u8BE5\u6876\u8BBE\u7F6E\u533F\u540D\u53EF\u8BFB\u7B56\u7565\uFF08\u53EF\u5148\u914D\u7F6E .apm/.env \u7684 MINIO_*\uFF0C\u5DF2\u6709\u73AF\u5883\u53D8\u91CF\u4F18\u5148\uFF09"
2087
+ "\u9012\u5F52\u4E0A\u4F20\u524D\u7AEF\u4EA7\u7269\u76EE\u5F55\u5230 MinIO\uFF0C\u5E76\u5728\u6210\u529F\u540E\u4E3A\u8BE5\u6876\u8BBE\u7F6E\u533F\u540D\u53EF\u8BFB\u7B56\u7565\uFF08MinIO \u8FDE\u63A5\u4FE1\u606F\u4EC5\u6765\u81EA apm.config.json frontendDeploy\uFF09"
2128
2088
  ).argument("[name]", "\u73AF\u5883\u540D", "online").option(
2129
2089
  "--dir <path>",
2130
2090
  "\u4EA7\u7269\u76EE\u5F55\uFF1B\u5355\u4ED3\u9ED8\u8BA4 apps/web/dist\uFF08\u9700\u5148 rush build / vite build\uFF09",
@@ -2138,12 +2098,9 @@ function registerDeployFrontendCommands(program) {
2138
2098
  (v) => Number.parseInt(String(v), 10)
2139
2099
  ).action(
2140
2100
  async (name, opts) => {
2141
- loadApmDotEnvIfPresent();
2142
2101
  const cfg = loadApmConfig({ configPath: opts.config });
2143
2102
  const namePrefix = resolveArtifactNamePrefix(cfg);
2144
- const settings = mergeMinioFromEnv(
2145
- resolveFrontendDeployFromApmConfig(cfg)
2146
- );
2103
+ const settings = resolveFrontendDeployFromApmConfig(cfg);
2147
2104
  const minio = new MinioClient({
2148
2105
  endPoint: settings.endpoint,
2149
2106
  port: settings.port,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "5.0.13",
3
+ "version": "5.0.15",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -37,4 +37,4 @@
37
37
  "minio": "~8.0.7",
38
38
  "dockerode": "~5.0.0"
39
39
  }
40
- }
40
+ }
@@ -17,7 +17,8 @@
17
17
  1. 读取 `.apm/sessions/<会话ID>/session.yaml`,必要时读取`messages.xml` 了解当前会话状态与历史消息
18
18
  2. 根据你的名字从 session.yaml 中找到你对应的人设(system_persona)
19
19
  3. 根据人设完成用户指定的任务
20
- 4. 任务有阶段行进展或者任务完成后必须使用 `apm append-message` 回复消息
20
+ 4. 任务有阶段行进展或者任务完成后必须使用 `apm append-message` 回复消息,回复要求简洁不用对本轮工作进行总结
21
+ 5. 工作日志放到 `.apm/sessions/<会话ID>/docs/<expert>.md` 中,并执行`apm sync-document <会话ID> --file=<expert>`同步到远程
21
22
 
22
23
  ## 目录声明
23
24
 
@@ -1,28 +1,27 @@
1
1
  ## 工作流程
2
+
2
3
  1. 选择开发模式:**Quick/Spec**
3
4
 
4
- **Quick 开发**(满足越多越适用):
5
- - 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。
6
- - 无新表结构/大规模迁移/权限模型变更。
7
- - PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。
8
- - 不需要跨多服务的架构裁定即可开工。
9
- **Spec 开发**:(命中任一条即可):
10
- - 前后端联动、多包改造或新公共抽象。
11
- - 新数据模型、迁移、或安全/审计/权限相关。
12
- - PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。
13
- - 评估认为不先产出 **proposal / design / specs /
5
+ **Quick 开发**(满足越多越适用): - 影响范围局部:少量文件或单一层次(例如仅前端组件、或仅一个后端模块小改)。 - 无新表结构/大规模迁移/权限模型变更。 - PRD 验收点清晰且数量少(经验上 **≤3** 条独立验收维度)。 - 不需要跨多服务的架构裁定即可开工。
6
+ **Spec 开发**:(命中任一条即可): - 前后端联动、多包改造或新公共抽象。 - 新数据模型、迁移、或安全/审计/权限相关。 - PRD 范围大、条款多,或存在明显「待确认/多方案」需先规划。 - 评估认为不先产出 **proposal / design / specs / tasks** 不宜直接编码。
14
7
 
15
- 2. 如果为 **Quick 开发**(子Agent中)执行:
8
+ 2. 如果为 **Quick 开发**(子 Agent 中)执行:
16
9
 
17
- + 父 Agent 已通过 **Read** 掌握 `PRD.md`;若启动新子 Agent,在委派提示中写明会话ID、消息ID、工作项路径、以及「实现须严格对照 PRD,改动范围最小化」。
18
- + 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
19
- - 按需 **Read** `.apm/sessions/<会话ID>/docs/PRD.md`
20
- - 按 PRD 直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
21
- - 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若子 Agent 跑了 `rushx build` / 测试等则写明结果)。
10
+ - 父 Agent 已通过 **Read** 掌握 `PRD.md`;若启动新子 Agent,在委派提示中写明会话 ID、消息 ID、工作项路径、以及「实现须严格对照 PRD,改动范围最小化;完成后若有代码改动须单独 `git commit`」。
11
+ - 使用 **Task** 工具,`subagent_type: generalPurpose`,**readonly: false**,委派子 Agent:
12
+ - 按需 **Read** `.apm/sessions/<会话ID>/docs/PRD.md`
13
+ - 按 PRD 直接改代码;遵守本仓库构建与依赖约定(AGENTS.md)。
14
+ - **Git**:实现与自洽验收通过后,若有代码改动,**立即 `git add` + `git commit` 一次**(Quick 通常为单次交付,**一次实现 = 一个 commit**;勿拆成无意义碎 commit)。提交信息建议包含 `seesionId`(可从 `session.yaml` 或 PRD 获取)与 PRD 摘要或验收点简述。
15
+ - 完成后在返回中说明:改了哪些路径、如何对照验收、是否通过本地可执行的检查(若子 Agent 跑了 `rushx build` / 测试等则写明结果);若有 commit,写明 **short-sha** 与 **subject**,无代码改动则注明跳过 commit。
22
16
 
23
- 3. 如果为 **Spec 开发** (子Agent中)执行:
17
+ 3. 如果为 **Spec 开发** (子 Agent 中)执行:
24
18
 
25
19
  - 父 Agent **Read** **apm-propose**、**apm-apply-change** 的 `SKILL.md`(**仅** `.apm/skills/` 下路径,见上节)。
26
20
  - **子 Agent A(规划)**:Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-propose/SKILL.md` 并完整遵循:在 `.apm/sessions/<sessionId>/` 生成 **proposal、design、specs、tasks** 等工件(顺序与依赖以 SKILL 为准)。
27
21
  - **子 Agent B(实现)**:待 A 成功落盘后,再 Task `generalPurpose`,提示其自行 **Read** `.apm/skills/apm-apply-change/SKILL.md` 并完整遵循:按 **`tasks.md`** 驱动实现与勾选;遵守该技能中的停止条件与 commit 约定。
28
22
  - 若 **apm-propose** 未产出可用 **`tasks.md`**,不得强行进入 **apm-apply-change**;表格中标记阻塞原因。
23
+
24
+ 4. 提交并 push 代码
25
+
26
+ - Quick / Spec 子 Agent 完成且本地已有 commit 时,父 Agent **立即 `git push`**(当前分支首次 push 用 `git push -u origin HEAD`)。
27
+ - 无本地 commit、无远程或未配置 upstream 时说明原因,勿强行 push。