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 +40 -83
- package/package.json +2 -2
- package/template/AGENTS.md +2 -1
- package/template/skills/apm-dev/SKILL.md +16 -17
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.
|
|
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
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
271
|
+
clientMachineId: "",
|
|
260
272
|
token: ""
|
|
261
273
|
});
|
|
262
274
|
let data;
|
|
263
275
|
try {
|
|
264
276
|
data = await api.auth.login({
|
|
265
|
-
|
|
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
|
|
292
|
+
const clientMachineId = data?.clientMachine?.id;
|
|
281
293
|
const token = data?.token;
|
|
282
|
-
if (!
|
|
294
|
+
if (!clientMachineId || !token) {
|
|
283
295
|
console.error(
|
|
284
|
-
"[apm] \u54CD\u5E94\u7F3A\u5C11
|
|
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
|
-
|
|
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(
|
|
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
|
-
)}"
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1158
|
-
|
|
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,
|
|
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\
|
|
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 =
|
|
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
package/template/AGENTS.md
CHANGED
|
@@ -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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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. 如果为
|
|
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。
|