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 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 sessionLogPath(sessionId, apmRoot) {
111
- return join2(sessionDir(sessionId, apmRoot), "LOG.md");
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 path8 = join4(dir, MANIFEST_FILE);
544
- if (!existsSync2(path8)) {
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(path8, "utf8")
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(sessionLogPath(trimmedId, apmRoot), detail.log ?? "", "utf8");
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
- log: "./LOG.md",
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 path8 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
776
- return statSync2(path8).isDirectory();
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 path8 = join7(BASE_RULES_TEMPLATE_DIR, name);
789
- return statSync2(path8).isFile();
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\u3001LOG.md\u3001docs\u3001attachments\uFF09"
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.24",
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
  }
@@ -22,6 +22,6 @@
22
22
  - 群文档: `docs/xxxx.md`,当需要相关上下文可以在这里查找,按需阅读
23
23
  - 附件列表: `attachments/*`,当有文档中提及附件时,从这里查找
24
24
  - 协作规则: `RULE.md`,在这里可以看到不同成员的协作规则,从而找其他成员协助你一起解决问题,按需阅读
25
- - 团队日志: `LOG.md`,在这里可以看到之前每个成员都做了什么,按需阅读
25
+ - 协作 TODO: `TODO.md`,跨轮次任务清单(待办与已完成项),按需阅读
26
26
  - 历史消息记录: `message.xml`,历史消息列表,数据量很大,按需阅读
27
27
  - 初始目标: `TASK.md`,最初的目标,不一定具体,团队成员会有人负责让这个任务变得具体,仅供参考。如果你是相关的角色,则你需要先读取这个目标,然后规划接下来的动作。