ai-project-manage-cli 6.0.27 → 6.0.29
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 +154 -52
- package/package.json +1 -1
- package/template/rules/write_doc.md +4 -2
package/dist/index.js
CHANGED
|
@@ -885,7 +885,73 @@ async function runUpdateSkills() {
|
|
|
885
885
|
}
|
|
886
886
|
|
|
887
887
|
// src/commands/sync-document.ts
|
|
888
|
-
import { existsSync as
|
|
888
|
+
import { existsSync as existsSync6 } from "fs";
|
|
889
|
+
import { basename as basename2 } from "path";
|
|
890
|
+
|
|
891
|
+
// src/commands/sync-session-documents.ts
|
|
892
|
+
import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync5 } from "fs";
|
|
893
|
+
import { join as join9 } from "path";
|
|
894
|
+
function listLocalMarkdownFiles(docsDir) {
|
|
895
|
+
if (!existsSync5(docsDir)) {
|
|
896
|
+
return [];
|
|
897
|
+
}
|
|
898
|
+
return readdirSync3(docsDir).filter(
|
|
899
|
+
(name) => name.toLowerCase().endsWith(".md")
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
function remoteDocumentByLocalName(remoteDocuments, localFileName) {
|
|
903
|
+
const platformName = documentPlatformName(localFileName);
|
|
904
|
+
return remoteDocuments.find((doc) => {
|
|
905
|
+
const remoteLocalName = documentLocalFileName(doc.name);
|
|
906
|
+
return remoteLocalName === localFileName || documentPlatformName(doc.name) === platformName;
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
async function upsertLocalDocumentFile(api, sessionId, docsDir, fileName) {
|
|
910
|
+
const absPath = join9(docsDir, fileName);
|
|
911
|
+
const content = readFileSync5(absPath, "utf8");
|
|
912
|
+
const name = documentPlatformName(absPath);
|
|
913
|
+
return api.cli.upsertDocument({
|
|
914
|
+
sessionId,
|
|
915
|
+
name,
|
|
916
|
+
content
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
async function syncSessionDocuments(cfg, sessionId, apmRoot, options) {
|
|
920
|
+
const trimmedSessionId = sessionId.trim();
|
|
921
|
+
if (!trimmedSessionId) {
|
|
922
|
+
return 0;
|
|
923
|
+
}
|
|
924
|
+
const docsDir = sessionDocsDir(trimmedSessionId, apmRoot);
|
|
925
|
+
const localFiles = listLocalMarkdownFiles(docsDir);
|
|
926
|
+
if (localFiles.length === 0) {
|
|
927
|
+
return 0;
|
|
928
|
+
}
|
|
929
|
+
const api = options?.api ?? createApmApiClient(cfg);
|
|
930
|
+
const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ sessionId: trimmedSessionId });
|
|
931
|
+
let synced = 0;
|
|
932
|
+
for (const fileName of localFiles) {
|
|
933
|
+
const absPath = join9(docsDir, fileName);
|
|
934
|
+
const content = readFileSync5(absPath, "utf8");
|
|
935
|
+
const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
|
|
936
|
+
if (remote && remote.content === content) {
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
const doc = await upsertLocalDocumentFile(
|
|
940
|
+
api,
|
|
941
|
+
trimmedSessionId,
|
|
942
|
+
docsDir,
|
|
943
|
+
fileName
|
|
944
|
+
);
|
|
945
|
+
synced += 1;
|
|
946
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
947
|
+
}
|
|
948
|
+
if (synced === 0) {
|
|
949
|
+
console.log("[apm] \u4F1A\u8BDD\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
950
|
+
}
|
|
951
|
+
return synced;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// src/commands/sync-document.ts
|
|
889
955
|
async function runSyncDocument(sessionId, options) {
|
|
890
956
|
const trimmedSessionId = sessionId.trim();
|
|
891
957
|
if (!trimmedSessionId) {
|
|
@@ -898,24 +964,24 @@ async function runSyncDocument(sessionId, options) {
|
|
|
898
964
|
process.exit(1);
|
|
899
965
|
}
|
|
900
966
|
const absPath = resolveSessionDocumentPath(trimmedSessionId, fileArg);
|
|
901
|
-
if (!
|
|
902
|
-
const
|
|
967
|
+
if (!existsSync6(absPath)) {
|
|
968
|
+
const docsDir2 = sessionDocsDir(trimmedSessionId);
|
|
903
969
|
console.error(
|
|
904
970
|
`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
|
|
905
|
-
[apm] \u8BF7\u786E\u8BA4\u5DF2 pull\uFF0C\u4E14 ${
|
|
971
|
+
[apm] \u8BF7\u786E\u8BA4\u5DF2 pull\uFF0C\u4E14 ${docsDir2} \u4E0B\u5B58\u5728\u5BF9\u5E94\u6587\u4EF6`
|
|
906
972
|
);
|
|
907
973
|
process.exit(1);
|
|
908
974
|
}
|
|
909
|
-
const content = readFileSync5(absPath, "utf8");
|
|
910
|
-
const name = documentPlatformName(absPath);
|
|
911
975
|
const cfg = await ensureLoggedConfig();
|
|
912
976
|
const api = createApmApiClient(cfg);
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
977
|
+
const docsDir = sessionDocsDir(trimmedSessionId);
|
|
978
|
+
const doc = await upsertLocalDocumentFile(
|
|
979
|
+
api,
|
|
980
|
+
trimmedSessionId,
|
|
981
|
+
docsDir,
|
|
982
|
+
basename2(absPath)
|
|
983
|
+
);
|
|
984
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${doc.name} (id=${doc.id})`);
|
|
919
985
|
}
|
|
920
986
|
|
|
921
987
|
// src/commands/append-message.ts
|
|
@@ -1038,6 +1104,7 @@ function validateAgentWsMessage(value, kind) {
|
|
|
1038
1104
|
|
|
1039
1105
|
// src/commands/connect/cursor-agent.ts
|
|
1040
1106
|
import { Agent, CursorAgentError } from "@cursor/sdk";
|
|
1107
|
+
import { setMaxListeners } from "node:events";
|
|
1041
1108
|
import { resolve as resolve3 } from "path";
|
|
1042
1109
|
|
|
1043
1110
|
// src/session-utils.ts
|
|
@@ -1199,18 +1266,6 @@ ${JSON.stringify(event, null, 2)}
|
|
|
1199
1266
|
}
|
|
1200
1267
|
|
|
1201
1268
|
// src/commands/connect/cursor-message-log.ts
|
|
1202
|
-
function assistantStreamChunk(event) {
|
|
1203
|
-
if (event.type !== "assistant") {
|
|
1204
|
-
return "";
|
|
1205
|
-
}
|
|
1206
|
-
let text = "";
|
|
1207
|
-
for (const block of event.message.content) {
|
|
1208
|
-
if (block.type === "text" && block.text) {
|
|
1209
|
-
text += block.text;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
return text;
|
|
1213
|
-
}
|
|
1214
1269
|
var CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS = 2e3;
|
|
1215
1270
|
function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
|
|
1216
1271
|
let lastRunAt = 0;
|
|
@@ -1285,12 +1340,17 @@ async function syncCursorMessageLog(cfg, ctx, events) {
|
|
|
1285
1340
|
}
|
|
1286
1341
|
|
|
1287
1342
|
// src/commands/connect/cursor-agent.ts
|
|
1343
|
+
setMaxListeners(50);
|
|
1288
1344
|
var logCtx = (ctx, agentId) => ({
|
|
1289
1345
|
sessionId: ctx.sessionId,
|
|
1290
1346
|
messageId: ctx.messageId,
|
|
1291
1347
|
agentId
|
|
1292
1348
|
});
|
|
1293
|
-
async function runCursorAgent(cfg, ctx) {
|
|
1349
|
+
async function runCursorAgent(cfg, ctx, options) {
|
|
1350
|
+
const signal = options?.signal;
|
|
1351
|
+
if (signal?.aborted) {
|
|
1352
|
+
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
1353
|
+
}
|
|
1294
1354
|
const apiKey = ctx.apiKey.trim();
|
|
1295
1355
|
if (!apiKey) {
|
|
1296
1356
|
throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
|
|
@@ -1320,12 +1380,22 @@ async function runCursorAgent(cfg, ctx) {
|
|
|
1320
1380
|
);
|
|
1321
1381
|
}
|
|
1322
1382
|
);
|
|
1383
|
+
let activeRun;
|
|
1384
|
+
const abortRun = () => {
|
|
1385
|
+
if (!activeRun?.supports("cancel")) return;
|
|
1386
|
+
void activeRun.cancel().catch(() => void 0);
|
|
1387
|
+
};
|
|
1388
|
+
signal?.addEventListener("abort", abortRun, { once: true });
|
|
1323
1389
|
try {
|
|
1324
1390
|
const run = await agent.send(prompt);
|
|
1391
|
+
activeRun = run;
|
|
1325
1392
|
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
1326
1393
|
for await (const event of run.stream()) {
|
|
1394
|
+
if (signal?.aborted) {
|
|
1395
|
+
abortRun();
|
|
1396
|
+
throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
|
|
1397
|
+
}
|
|
1327
1398
|
eventSession.addEvent(event);
|
|
1328
|
-
const chunk = assistantStreamChunk(event);
|
|
1329
1399
|
syncRemoteLog.schedule(eventSession);
|
|
1330
1400
|
}
|
|
1331
1401
|
await syncRemoteLog.flush(eventSession);
|
|
@@ -1345,6 +1415,7 @@ async function runCursorAgent(cfg, ctx) {
|
|
|
1345
1415
|
}
|
|
1346
1416
|
throw err;
|
|
1347
1417
|
} finally {
|
|
1418
|
+
signal?.removeEventListener("abort", abortRun);
|
|
1348
1419
|
await agent[Symbol.asyncDispose]();
|
|
1349
1420
|
}
|
|
1350
1421
|
}
|
|
@@ -1361,7 +1432,9 @@ async function appendMessageContent(cfg, messageId, content) {
|
|
|
1361
1432
|
await api.cli.appendMessageContent({ id: messageId, content });
|
|
1362
1433
|
console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
|
|
1363
1434
|
}
|
|
1364
|
-
|
|
1435
|
+
var SHUTDOWN_DRAIN_MS = 3e3;
|
|
1436
|
+
async function handleInboundMessage(cfg, raw, signal) {
|
|
1437
|
+
if (signal.aborted) return;
|
|
1365
1438
|
const parsed = parseAgentWsMessage(raw);
|
|
1366
1439
|
if (parsed === null) {
|
|
1367
1440
|
console.error("[apm] \u6536\u5230\u65E0\u6548 JSON");
|
|
@@ -1381,18 +1454,35 @@ async function handleInboundMessage(cfg, raw) {
|
|
|
1381
1454
|
const msg = validated.data;
|
|
1382
1455
|
const messageId = msg.messageId;
|
|
1383
1456
|
try {
|
|
1457
|
+
if (signal.aborted) return;
|
|
1384
1458
|
await runBranch(msg.sessionId, { cwd: msg.workdir });
|
|
1459
|
+
if (signal.aborted) return;
|
|
1385
1460
|
await runPull(msg.sessionId, workspaceApmDir(msg.workdir));
|
|
1461
|
+
if (signal.aborted) return;
|
|
1386
1462
|
await commitWorkingTreeIfDirty(msg.workdir, "fix: apm pull");
|
|
1463
|
+
if (signal.aborted) return;
|
|
1387
1464
|
await updateMessageStatus(cfg, messageId, "TYPING");
|
|
1388
|
-
await runCursorAgent(
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1465
|
+
await runCursorAgent(
|
|
1466
|
+
cfg,
|
|
1467
|
+
{
|
|
1468
|
+
messageId: msg.messageId,
|
|
1469
|
+
sessionId: msg.sessionId,
|
|
1470
|
+
prompt: msg.content,
|
|
1471
|
+
model: msg.model,
|
|
1472
|
+
apiKey: msg.apiKey,
|
|
1473
|
+
workdir: msg.workdir
|
|
1474
|
+
},
|
|
1475
|
+
{ signal }
|
|
1476
|
+
);
|
|
1477
|
+
await syncSessionDocuments(
|
|
1478
|
+
cfg,
|
|
1479
|
+
msg.sessionId,
|
|
1480
|
+
workspaceApmDir(msg.workdir)
|
|
1481
|
+
);
|
|
1482
|
+
await commitWorkingTreeIfDirty(
|
|
1483
|
+
msg.workdir,
|
|
1484
|
+
"chore(apm): sync session documents"
|
|
1485
|
+
);
|
|
1396
1486
|
await updateMessageStatus(cfg, messageId, "SUCCESS");
|
|
1397
1487
|
} catch (err) {
|
|
1398
1488
|
console.error(
|
|
@@ -1446,32 +1536,42 @@ async function runConnect(options) {
|
|
|
1446
1536
|
const ws = new WebSocket(url);
|
|
1447
1537
|
let stopHeartbeat;
|
|
1448
1538
|
let shuttingDown = false;
|
|
1539
|
+
const shutdownAbort = new AbortController();
|
|
1449
1540
|
let inboundQueue = Promise.resolve();
|
|
1450
|
-
const shutdown = (code = 0) => {
|
|
1541
|
+
const shutdown = async (code = 0) => {
|
|
1451
1542
|
if (shuttingDown) return;
|
|
1452
1543
|
shuttingDown = true;
|
|
1544
|
+
shutdownAbort.abort();
|
|
1453
1545
|
stopHeartbeat?.();
|
|
1454
1546
|
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
|
|
1455
|
-
ws.
|
|
1547
|
+
ws.terminate();
|
|
1456
1548
|
}
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1549
|
+
try {
|
|
1550
|
+
await Promise.race([
|
|
1551
|
+
inboundQueue,
|
|
1552
|
+
new Promise((r) => setTimeout(r, SHUTDOWN_DRAIN_MS))
|
|
1553
|
+
]);
|
|
1554
|
+
} catch {
|
|
1460
1555
|
}
|
|
1556
|
+
resolve5();
|
|
1557
|
+
process.exit(code);
|
|
1461
1558
|
};
|
|
1462
1559
|
ws.on("open", () => {
|
|
1463
1560
|
console.log("[apm] WebSocket \u5DF2\u8FDE\u63A5");
|
|
1464
1561
|
stopHeartbeat = startHeartbeat(ws, clientMachineId);
|
|
1465
1562
|
});
|
|
1466
1563
|
ws.on("message", (data) => {
|
|
1564
|
+
if (shuttingDown) return;
|
|
1467
1565
|
const text = Buffer.isBuffer(data) ? data.toString("utf8") : String(data);
|
|
1468
|
-
inboundQueue = inboundQueue.then(
|
|
1566
|
+
inboundQueue = inboundQueue.then(
|
|
1567
|
+
() => handleInboundMessage(cfg, text, shutdownAbort.signal)
|
|
1568
|
+
);
|
|
1469
1569
|
});
|
|
1470
1570
|
ws.on("close", (code, reason) => {
|
|
1471
1571
|
console.log(
|
|
1472
1572
|
`[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
|
|
1473
1573
|
);
|
|
1474
|
-
shutdown();
|
|
1574
|
+
void shutdown();
|
|
1475
1575
|
});
|
|
1476
1576
|
ws.on("error", (err) => {
|
|
1477
1577
|
console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
|
|
@@ -1479,9 +1579,11 @@ async function runConnect(options) {
|
|
|
1479
1579
|
});
|
|
1480
1580
|
process.on("SIGINT", () => {
|
|
1481
1581
|
console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
|
|
1482
|
-
shutdown();
|
|
1582
|
+
void shutdown();
|
|
1583
|
+
});
|
|
1584
|
+
process.on("SIGTERM", () => {
|
|
1585
|
+
void shutdown();
|
|
1483
1586
|
});
|
|
1484
|
-
process.on("SIGTERM", () => shutdown());
|
|
1485
1587
|
});
|
|
1486
1588
|
}
|
|
1487
1589
|
|
|
@@ -1489,14 +1591,14 @@ async function runConnect(options) {
|
|
|
1489
1591
|
import path5 from "node:path";
|
|
1490
1592
|
|
|
1491
1593
|
// src/commands/deploy/internal/apm-config.ts
|
|
1492
|
-
import { existsSync as
|
|
1594
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
1493
1595
|
import { resolve as resolve4 } from "node:path";
|
|
1494
1596
|
function loadApmConfig(options) {
|
|
1495
1597
|
const p = resolve4(
|
|
1496
1598
|
process.cwd(),
|
|
1497
1599
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
1498
1600
|
);
|
|
1499
|
-
if (!
|
|
1601
|
+
if (!existsSync7(p)) {
|
|
1500
1602
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
1501
1603
|
process.exit(1);
|
|
1502
1604
|
}
|
|
@@ -1623,7 +1725,7 @@ import path4 from "node:path";
|
|
|
1623
1725
|
import Docker from "dockerode";
|
|
1624
1726
|
|
|
1625
1727
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
1626
|
-
import { existsSync as
|
|
1728
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
1627
1729
|
import path from "node:path";
|
|
1628
1730
|
function asOptionalTlsBuffer(value) {
|
|
1629
1731
|
if (typeof value !== "string") {
|
|
@@ -1635,7 +1737,7 @@ function asOptionalTlsBuffer(value) {
|
|
|
1635
1737
|
if (normalized === "") {
|
|
1636
1738
|
return void 0;
|
|
1637
1739
|
}
|
|
1638
|
-
if (
|
|
1740
|
+
if (existsSync8(normalized)) {
|
|
1639
1741
|
return readFileSync7(normalized);
|
|
1640
1742
|
}
|
|
1641
1743
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
@@ -1846,7 +1948,7 @@ var DockerodeClient = class {
|
|
|
1846
1948
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
1847
1949
|
|
|
1848
1950
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
1849
|
-
import { existsSync as
|
|
1951
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync4 } from "node:fs";
|
|
1850
1952
|
import path2 from "node:path";
|
|
1851
1953
|
function stripSurroundingQuotes(value) {
|
|
1852
1954
|
const t = value.trim();
|
|
@@ -1863,7 +1965,7 @@ function loadEnvFromFile(envFilePath) {
|
|
|
1863
1965
|
return {};
|
|
1864
1966
|
}
|
|
1865
1967
|
const targetPath = path2.resolve(envFilePath);
|
|
1866
|
-
if (!
|
|
1968
|
+
if (!existsSync9(targetPath) || !statSync4(targetPath).isFile()) {
|
|
1867
1969
|
return {};
|
|
1868
1970
|
}
|
|
1869
1971
|
const raw = readFileSync8(targetPath, "utf-8");
|
|
@@ -2037,12 +2139,12 @@ function dockerPushImage(params, cwd) {
|
|
|
2037
2139
|
}
|
|
2038
2140
|
|
|
2039
2141
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
2040
|
-
import { existsSync as
|
|
2142
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2041
2143
|
import path3 from "node:path";
|
|
2042
2144
|
function resolveDockerBuildPaths(cwd) {
|
|
2043
2145
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
2044
2146
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
2045
|
-
if (!
|
|
2147
|
+
if (!existsSync10(dockerfilePath)) {
|
|
2046
2148
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
2047
2149
|
}
|
|
2048
2150
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
package/package.json
CHANGED
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
|
|
19
19
|
## 在保存完成之后需要同步到远程
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
通过 `apm connect` 连接平台时,每轮 Agent 结束后会自动推送 `docs/` 下变更的 Markdown,一般无需手动同步。
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
手动同步命令: `apm sync-document <会话 ID> --file=<文档名称>`
|
|
24
|
+
|
|
25
|
+
示例: `apm sync-document <会话ID> --file=张三-工作日志.md`
|
|
24
26
|
|
|
25
27
|
## 注意事项
|
|
26
28
|
|