ai-project-manage-cli 6.0.24 → 6.0.26
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 +204 -12
- package/package.json +6 -3
- package/template/AGENTS.md +1 -1
package/dist/index.js
CHANGED
|
@@ -107,8 +107,8 @@ function sessionRulePath(sessionId, apmRoot) {
|
|
|
107
107
|
function sessionTaskPath(sessionId, apmRoot) {
|
|
108
108
|
return join2(sessionDir(sessionId, apmRoot), "TASK.md");
|
|
109
109
|
}
|
|
110
|
-
function
|
|
111
|
-
return join2(sessionDir(sessionId, apmRoot), "
|
|
110
|
+
function sessionTodoPath(sessionId, apmRoot) {
|
|
111
|
+
return join2(sessionDir(sessionId, apmRoot), "TODO.md");
|
|
112
112
|
}
|
|
113
113
|
function sessionYamlPath(sessionId, apmRoot) {
|
|
114
114
|
return join2(sessionDir(sessionId, apmRoot), "session.yaml");
|
|
@@ -540,13 +540,13 @@ async function downloadAttachment(cfg, sessionId, attachmentId) {
|
|
|
540
540
|
return Buffer.from(await res.arrayBuffer());
|
|
541
541
|
}
|
|
542
542
|
function loadManifest(dir) {
|
|
543
|
-
const
|
|
544
|
-
if (!existsSync2(
|
|
543
|
+
const path10 = join4(dir, MANIFEST_FILE);
|
|
544
|
+
if (!existsSync2(path10)) {
|
|
545
545
|
return { version: 1, attachments: {} };
|
|
546
546
|
}
|
|
547
547
|
try {
|
|
548
548
|
const parsed = JSON.parse(
|
|
549
|
-
readFileSync3(
|
|
549
|
+
readFileSync3(path10, "utf8")
|
|
550
550
|
);
|
|
551
551
|
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
552
552
|
return parsed;
|
|
@@ -631,7 +631,7 @@ async function runPull(sessionId, apmRoot) {
|
|
|
631
631
|
detail.task.description ?? "",
|
|
632
632
|
"utf8"
|
|
633
633
|
);
|
|
634
|
-
writeFileSync4(
|
|
634
|
+
writeFileSync4(sessionTodoPath(trimmedId, apmRoot), detail.todo ?? "", "utf8");
|
|
635
635
|
for (const doc of documents) {
|
|
636
636
|
const fileName = documentLocalFileName(doc.name);
|
|
637
637
|
writeFileSync4(join5(docsDir, fileName), doc.content ?? "", "utf8");
|
|
@@ -640,7 +640,7 @@ async function runPull(sessionId, apmRoot) {
|
|
|
640
640
|
{
|
|
641
641
|
name: detail.title,
|
|
642
642
|
task: "./TASK.md",
|
|
643
|
-
|
|
643
|
+
todo: "./TODO.md",
|
|
644
644
|
rule: "./RULE.md",
|
|
645
645
|
members: members.map((m) => ({
|
|
646
646
|
name: m.displayName,
|
|
@@ -772,8 +772,8 @@ function sanitizeSkillDirName(name) {
|
|
|
772
772
|
function listBaseSkillDirNames() {
|
|
773
773
|
if (!existsSync3(BASE_SKILLS_TEMPLATE_DIR)) return [];
|
|
774
774
|
return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
|
|
775
|
-
const
|
|
776
|
-
return statSync2(
|
|
775
|
+
const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
776
|
+
return statSync2(path10).isDirectory();
|
|
777
777
|
});
|
|
778
778
|
}
|
|
779
779
|
function syncAgentsGuide(apmDir) {
|
|
@@ -785,8 +785,8 @@ function syncAgentsGuide(apmDir) {
|
|
|
785
785
|
function listBaseRuleFileNames() {
|
|
786
786
|
if (!existsSync3(BASE_RULES_TEMPLATE_DIR)) return [];
|
|
787
787
|
return readdirSync2(BASE_RULES_TEMPLATE_DIR).filter((name) => {
|
|
788
|
-
const
|
|
789
|
-
return statSync2(
|
|
788
|
+
const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
789
|
+
return statSync2(path10).isFile();
|
|
790
790
|
});
|
|
791
791
|
}
|
|
792
792
|
function syncBaseRules(rulesDir) {
|
|
@@ -1593,6 +1593,31 @@ function resolveFrontendDeployFromApmConfig(cfg) {
|
|
|
1593
1593
|
bucket: reqFe(f.bucket, "bucket").trim()
|
|
1594
1594
|
};
|
|
1595
1595
|
}
|
|
1596
|
+
function reqWd(v, field) {
|
|
1597
|
+
if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
|
|
1598
|
+
console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
|
|
1599
|
+
process.exit(1);
|
|
1600
|
+
}
|
|
1601
|
+
return v;
|
|
1602
|
+
}
|
|
1603
|
+
function reqWisdomPositiveInt(v, field) {
|
|
1604
|
+
const n = Number(v);
|
|
1605
|
+
if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
|
|
1606
|
+
console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
|
|
1607
|
+
process.exit(1);
|
|
1608
|
+
}
|
|
1609
|
+
return n;
|
|
1610
|
+
}
|
|
1611
|
+
function resolveWisdomDeployFromApmConfig(cfg) {
|
|
1612
|
+
const w = cfg.wisdomDeploy ?? {};
|
|
1613
|
+
return {
|
|
1614
|
+
host: reqWd(w.host, "host").trim(),
|
|
1615
|
+
port: reqWisdomPositiveInt(w.port, "port"),
|
|
1616
|
+
username: reqWd(w.username, "username").trim(),
|
|
1617
|
+
password: reqWd(w.password, "password").trim(),
|
|
1618
|
+
remotePath: reqWd(w.remotePath, "remotePath").trim()
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1596
1621
|
|
|
1597
1622
|
// src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
|
|
1598
1623
|
import path4 from "node:path";
|
|
@@ -2444,10 +2469,177 @@ function registerDeployFrontendCommands(program) {
|
|
|
2444
2469
|
);
|
|
2445
2470
|
}
|
|
2446
2471
|
|
|
2472
|
+
// src/commands/deploy/sftp.ts
|
|
2473
|
+
import path9 from "node:path";
|
|
2474
|
+
|
|
2475
|
+
// src/commands/deploy/internal/wisdom-sftp.ts
|
|
2476
|
+
import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
|
|
2477
|
+
import path8 from "node:path";
|
|
2478
|
+
import JSZip from "jszip";
|
|
2479
|
+
import SftpClient from "ssh2-sftp-client";
|
|
2480
|
+
async function addDirToZip(dir, zipFolder) {
|
|
2481
|
+
const entries = await readdir3(dir, { withFileTypes: true });
|
|
2482
|
+
for (const entry of entries) {
|
|
2483
|
+
const fullPath = path8.join(dir, entry.name);
|
|
2484
|
+
if (entry.isDirectory()) {
|
|
2485
|
+
const folder = zipFolder.folder(entry.name);
|
|
2486
|
+
if (folder) {
|
|
2487
|
+
await addDirToZip(fullPath, folder);
|
|
2488
|
+
}
|
|
2489
|
+
} else {
|
|
2490
|
+
const content = await readFile2(fullPath);
|
|
2491
|
+
zipFolder.file(entry.name, content);
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
async function zipDirectory(distDir, zipPath) {
|
|
2496
|
+
console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
|
|
2497
|
+
const zip = new JSZip();
|
|
2498
|
+
await addDirToZip(distDir, zip);
|
|
2499
|
+
const content = await zip.generateAsync({
|
|
2500
|
+
type: "nodebuffer",
|
|
2501
|
+
compression: "DEFLATE",
|
|
2502
|
+
compressionOptions: { level: 6 }
|
|
2503
|
+
});
|
|
2504
|
+
await writeFile(zipPath, content);
|
|
2505
|
+
const sizeMb = (content.length / 1024 / 1024).toFixed(2);
|
|
2506
|
+
console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
|
|
2507
|
+
return content.length;
|
|
2508
|
+
}
|
|
2509
|
+
async function ensureRemoteDir(sftp, dir) {
|
|
2510
|
+
const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
2511
|
+
let current = dir.startsWith("/") ? "" : ".";
|
|
2512
|
+
for (const part of parts) {
|
|
2513
|
+
current = current ? `${current}/${part}` : `/${part}`;
|
|
2514
|
+
try {
|
|
2515
|
+
await sftp.mkdir(current, true);
|
|
2516
|
+
} catch {
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
function execCommand(client, command) {
|
|
2521
|
+
return new Promise((resolve5, reject) => {
|
|
2522
|
+
client.exec(command, (err, stream) => {
|
|
2523
|
+
if (err) return reject(err);
|
|
2524
|
+
let stdout = "";
|
|
2525
|
+
let stderr = "";
|
|
2526
|
+
stream.on("close", (code) => {
|
|
2527
|
+
if (code !== 0) {
|
|
2528
|
+
reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
resolve5(stdout);
|
|
2532
|
+
}).on("data", (data) => {
|
|
2533
|
+
stdout += data.toString();
|
|
2534
|
+
});
|
|
2535
|
+
stream.stderr.on("data", (data) => {
|
|
2536
|
+
stderr += data.toString();
|
|
2537
|
+
});
|
|
2538
|
+
});
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
async function uploadAndMaybeExtract(settings, localZip, extract) {
|
|
2542
|
+
const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
|
|
2543
|
+
console.error(
|
|
2544
|
+
`\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
|
|
2545
|
+
);
|
|
2546
|
+
const sftp = new SftpClient();
|
|
2547
|
+
try {
|
|
2548
|
+
await sftp.connect({
|
|
2549
|
+
host: settings.host,
|
|
2550
|
+
port: settings.port,
|
|
2551
|
+
username: settings.username,
|
|
2552
|
+
password: settings.password,
|
|
2553
|
+
readyTimeout: 2e4,
|
|
2554
|
+
tryKeyboard: true
|
|
2555
|
+
});
|
|
2556
|
+
await ensureRemoteDir(sftp, settings.remotePath);
|
|
2557
|
+
await sftp.put(localZip, remoteZipPath);
|
|
2558
|
+
console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
|
|
2559
|
+
if (extract) {
|
|
2560
|
+
const target = settings.remotePath.replace(/\/$/, "");
|
|
2561
|
+
const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
|
|
2562
|
+
console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
|
|
2563
|
+
await execCommand(
|
|
2564
|
+
sftp.client,
|
|
2565
|
+
unzipCmd
|
|
2566
|
+
);
|
|
2567
|
+
console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
|
|
2568
|
+
}
|
|
2569
|
+
console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
|
|
2570
|
+
} finally {
|
|
2571
|
+
await sftp.end();
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
async function runWisdomSftpDeploy(params) {
|
|
2575
|
+
const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
|
|
2576
|
+
const resolvedZipPath = path8.resolve(zipPath);
|
|
2577
|
+
let zipSizeBytes = 0;
|
|
2578
|
+
try {
|
|
2579
|
+
zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
|
|
2580
|
+
await uploadAndMaybeExtract(
|
|
2581
|
+
params.settings,
|
|
2582
|
+
resolvedZipPath,
|
|
2583
|
+
params.extract
|
|
2584
|
+
);
|
|
2585
|
+
} finally {
|
|
2586
|
+
try {
|
|
2587
|
+
await unlink(resolvedZipPath);
|
|
2588
|
+
console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
|
|
2589
|
+
} catch {
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
return {
|
|
2593
|
+
ok: true,
|
|
2594
|
+
localDir: params.localDir,
|
|
2595
|
+
host: params.settings.host,
|
|
2596
|
+
remotePath: params.settings.remotePath,
|
|
2597
|
+
zipSizeBytes,
|
|
2598
|
+
extracted: params.extract
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2602
|
+
// src/commands/deploy/sftp.ts
|
|
2603
|
+
function registerDeploySftpCommands(program) {
|
|
2604
|
+
program.command("deploy-sftp").description(
|
|
2605
|
+
"\u5C06\u6307\u5B9A\u76EE\u5F55\u538B\u7F29\u4E3A zip \u540E\u901A\u8FC7 SFTP \u4E0A\u4F20\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\uFF08\u8FDE\u63A5\u4FE1\u606F\u89C1 apm.config.json wisdomDeploy\uFF09"
|
|
2606
|
+
).option(
|
|
2607
|
+
"--dir <path>",
|
|
2608
|
+
"\u672C\u5730\u4EA7\u7269\u76EE\u5F55\uFF1B\u5355\u4ED3\u9ED8\u8BA4 apps/web/dist\uFF08\u9700\u5148 rush build / vite build\uFF09",
|
|
2609
|
+
"apps/web/dist"
|
|
2610
|
+
).option(
|
|
2611
|
+
"--config <path>",
|
|
2612
|
+
"apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
|
|
2613
|
+
).option("--extract", "\u4E0A\u4F20\u540E\u5728\u8FDC\u7A0B\u670D\u52A1\u5668\u89E3\u538B zip \u5230 remotePath").action(
|
|
2614
|
+
async (opts) => {
|
|
2615
|
+
const cfg = loadApmConfig({ configPath: opts.config });
|
|
2616
|
+
const settings = resolveWisdomDeployFromApmConfig(cfg);
|
|
2617
|
+
const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
|
|
2618
|
+
if (!await isDirectoryPath(root)) {
|
|
2619
|
+
console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
|
|
2620
|
+
process.exit(1);
|
|
2621
|
+
}
|
|
2622
|
+
try {
|
|
2623
|
+
const result = await runWisdomSftpDeploy({
|
|
2624
|
+
localDir: root,
|
|
2625
|
+
settings,
|
|
2626
|
+
extract: Boolean(opts.extract)
|
|
2627
|
+
});
|
|
2628
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2629
|
+
} catch (err) {
|
|
2630
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2631
|
+
console.error(`${opts.extract ? "\u90E8\u7F72" : "\u4E0A\u4F20"}\u5931\u8D25:`, message);
|
|
2632
|
+
process.exit(1);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2447
2638
|
// src/commands/deploy/index.ts
|
|
2448
2639
|
function registerDeployCommands(program) {
|
|
2449
2640
|
registerDeployBackendCommands(program);
|
|
2450
2641
|
registerDeployFrontendCommands(program);
|
|
2642
|
+
registerDeploySftpCommands(program);
|
|
2451
2643
|
}
|
|
2452
2644
|
|
|
2453
2645
|
// src/index.ts
|
|
@@ -2476,7 +2668,7 @@ function buildProgram() {
|
|
|
2476
2668
|
await runUpdateSkills();
|
|
2477
2669
|
});
|
|
2478
2670
|
program.command("pull").description(
|
|
2479
|
-
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF08session.yaml\u3001RULE.md\u3001TASK.md\
|
|
2671
|
+
"\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF08session.yaml\u3001RULE.md\u3001TASK.md\u3001TODO.md\u3001docs\u3001attachments\uFF09"
|
|
2480
2672
|
).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
|
|
2481
2673
|
await runPull(sessionId);
|
|
2482
2674
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-project-manage-cli",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.26",
|
|
4
4
|
"description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"typescript": "~5.6.0",
|
|
24
24
|
"esbuild": "~0.28.0",
|
|
25
25
|
"vitest": "~4.1.5",
|
|
26
|
-
"@types/dockerode": "~4.0.1"
|
|
26
|
+
"@types/dockerode": "~4.0.1",
|
|
27
|
+
"@types/ssh2-sftp-client": "~9.0.6"
|
|
27
28
|
},
|
|
28
29
|
"engines": {
|
|
29
30
|
"node": ">=18"
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
"commander": "~14.0.3",
|
|
36
37
|
"yaml": "~2.8.4",
|
|
37
38
|
"minio": "~8.0.7",
|
|
38
|
-
"dockerode": "~5.0.0"
|
|
39
|
+
"dockerode": "~5.0.0",
|
|
40
|
+
"ssh2-sftp-client": "~12.0.1",
|
|
41
|
+
"jszip": "~3.10.1"
|
|
39
42
|
}
|
|
40
43
|
}
|
package/template/AGENTS.md
CHANGED
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
- 群文档: `docs/xxxx.md`,当需要相关上下文可以在这里查找,按需阅读
|
|
23
23
|
- 附件列表: `attachments/*`,当有文档中提及附件时,从这里查找
|
|
24
24
|
- 协作规则: `RULE.md`,在这里可以看到不同成员的协作规则,从而找其他成员协助你一起解决问题,按需阅读
|
|
25
|
-
-
|
|
25
|
+
- 协作 TODO: `TODO.md`,跨轮次任务清单(待办与已完成项),按需阅读
|
|
26
26
|
- 历史消息记录: `message.xml`,历史消息列表,数据量很大,按需阅读
|
|
27
27
|
- 初始目标: `TASK.md`,最初的目标,不一定具体,团队成员会有人负责让这个任务变得具体,仅供参考。如果你是相关的角色,则你需要先读取这个目标,然后规划接下来的动作。
|