ai-project-manage-cli 6.0.25 → 6.0.27

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.
Files changed (2) hide show
  1. package/dist/index.js +204 -15
  2. package/package.json +6 -3
package/dist/index.js CHANGED
@@ -525,13 +525,10 @@ function formatSessionMessagesXml(sessionId, messages) {
525
525
  import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
526
526
  import { join as join4 } from "path";
527
527
  var MANIFEST_FILE = ".sync-manifest.json";
528
- async function downloadAttachment(cfg, sessionId, attachmentId) {
528
+ async function downloadAttachment(cfg, attachmentId) {
529
529
  const base = cfg.baseUrl.trim().replace(/\/+$/, "");
530
- const q = new URLSearchParams({ sessionId, attachmentId });
531
- const url = `${base}/api/v1/cli/attachments/file?${q.toString()}`;
532
- const res = await fetch(url, {
533
- headers: { Authorization: `Bearer ${cfg.token}` }
534
- });
530
+ const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({ attachmentId })}`;
531
+ const res = await fetch(url);
535
532
  if (!res.ok) {
536
533
  throw new Error(
537
534
  `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
@@ -540,13 +537,13 @@ async function downloadAttachment(cfg, sessionId, attachmentId) {
540
537
  return Buffer.from(await res.arrayBuffer());
541
538
  }
542
539
  function loadManifest(dir) {
543
- const path8 = join4(dir, MANIFEST_FILE);
544
- if (!existsSync2(path8)) {
540
+ const path10 = join4(dir, MANIFEST_FILE);
541
+ if (!existsSync2(path10)) {
545
542
  return { version: 1, attachments: {} };
546
543
  }
547
544
  try {
548
545
  const parsed = JSON.parse(
549
- readFileSync3(path8, "utf8")
546
+ readFileSync3(path10, "utf8")
550
547
  );
551
548
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
552
549
  return parsed;
@@ -589,7 +586,7 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
589
586
  );
590
587
  continue;
591
588
  }
592
- const buffer = await downloadAttachment(cfg, sessionId, item.id);
589
+ const buffer = await downloadAttachment(cfg, item.id);
593
590
  writeFileSync3(dest, buffer);
594
591
  nextManifest.attachments[item.id] = {
595
592
  name: item.name,
@@ -772,8 +769,8 @@ function sanitizeSkillDirName(name) {
772
769
  function listBaseSkillDirNames() {
773
770
  if (!existsSync3(BASE_SKILLS_TEMPLATE_DIR)) return [];
774
771
  return readdirSync2(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
775
- const path8 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
776
- return statSync2(path8).isDirectory();
772
+ const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
773
+ return statSync2(path10).isDirectory();
777
774
  });
778
775
  }
779
776
  function syncAgentsGuide(apmDir) {
@@ -785,8 +782,8 @@ function syncAgentsGuide(apmDir) {
785
782
  function listBaseRuleFileNames() {
786
783
  if (!existsSync3(BASE_RULES_TEMPLATE_DIR)) return [];
787
784
  return readdirSync2(BASE_RULES_TEMPLATE_DIR).filter((name) => {
788
- const path8 = join7(BASE_RULES_TEMPLATE_DIR, name);
789
- return statSync2(path8).isFile();
785
+ const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
786
+ return statSync2(path10).isFile();
790
787
  });
791
788
  }
792
789
  function syncBaseRules(rulesDir) {
@@ -1593,6 +1590,31 @@ function resolveFrontendDeployFromApmConfig(cfg) {
1593
1590
  bucket: reqFe(f.bucket, "bucket").trim()
1594
1591
  };
1595
1592
  }
1593
+ function reqWd(v, field) {
1594
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
1595
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
1596
+ process.exit(1);
1597
+ }
1598
+ return v;
1599
+ }
1600
+ function reqWisdomPositiveInt(v, field) {
1601
+ const n = Number(v);
1602
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
1603
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
1604
+ process.exit(1);
1605
+ }
1606
+ return n;
1607
+ }
1608
+ function resolveWisdomDeployFromApmConfig(cfg) {
1609
+ const w = cfg.wisdomDeploy ?? {};
1610
+ return {
1611
+ host: reqWd(w.host, "host").trim(),
1612
+ port: reqWisdomPositiveInt(w.port, "port"),
1613
+ username: reqWd(w.username, "username").trim(),
1614
+ password: reqWd(w.password, "password").trim(),
1615
+ remotePath: reqWd(w.remotePath, "remotePath").trim()
1616
+ };
1617
+ }
1596
1618
 
1597
1619
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
1598
1620
  import path4 from "node:path";
@@ -2444,17 +2466,184 @@ function registerDeployFrontendCommands(program) {
2444
2466
  );
2445
2467
  }
2446
2468
 
2469
+ // src/commands/deploy/sftp.ts
2470
+ import path9 from "node:path";
2471
+
2472
+ // src/commands/deploy/internal/wisdom-sftp.ts
2473
+ import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
2474
+ import path8 from "node:path";
2475
+ import JSZip from "jszip";
2476
+ import SftpClient from "ssh2-sftp-client";
2477
+ async function addDirToZip(dir, zipFolder) {
2478
+ const entries = await readdir3(dir, { withFileTypes: true });
2479
+ for (const entry of entries) {
2480
+ const fullPath = path8.join(dir, entry.name);
2481
+ if (entry.isDirectory()) {
2482
+ const folder = zipFolder.folder(entry.name);
2483
+ if (folder) {
2484
+ await addDirToZip(fullPath, folder);
2485
+ }
2486
+ } else {
2487
+ const content = await readFile2(fullPath);
2488
+ zipFolder.file(entry.name, content);
2489
+ }
2490
+ }
2491
+ }
2492
+ async function zipDirectory(distDir, zipPath) {
2493
+ console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
2494
+ const zip = new JSZip();
2495
+ await addDirToZip(distDir, zip);
2496
+ const content = await zip.generateAsync({
2497
+ type: "nodebuffer",
2498
+ compression: "DEFLATE",
2499
+ compressionOptions: { level: 6 }
2500
+ });
2501
+ await writeFile(zipPath, content);
2502
+ const sizeMb = (content.length / 1024 / 1024).toFixed(2);
2503
+ console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
2504
+ return content.length;
2505
+ }
2506
+ async function ensureRemoteDir(sftp, dir) {
2507
+ const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
2508
+ let current = dir.startsWith("/") ? "" : ".";
2509
+ for (const part of parts) {
2510
+ current = current ? `${current}/${part}` : `/${part}`;
2511
+ try {
2512
+ await sftp.mkdir(current, true);
2513
+ } catch {
2514
+ }
2515
+ }
2516
+ }
2517
+ function execCommand(client, command) {
2518
+ return new Promise((resolve5, reject) => {
2519
+ client.exec(command, (err, stream) => {
2520
+ if (err) return reject(err);
2521
+ let stdout = "";
2522
+ let stderr = "";
2523
+ stream.on("close", (code) => {
2524
+ if (code !== 0) {
2525
+ reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
2526
+ return;
2527
+ }
2528
+ resolve5(stdout);
2529
+ }).on("data", (data) => {
2530
+ stdout += data.toString();
2531
+ });
2532
+ stream.stderr.on("data", (data) => {
2533
+ stderr += data.toString();
2534
+ });
2535
+ });
2536
+ });
2537
+ }
2538
+ async function uploadAndMaybeExtract(settings, localZip, extract) {
2539
+ const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
2540
+ console.error(
2541
+ `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
2542
+ );
2543
+ const sftp = new SftpClient();
2544
+ try {
2545
+ await sftp.connect({
2546
+ host: settings.host,
2547
+ port: settings.port,
2548
+ username: settings.username,
2549
+ password: settings.password,
2550
+ readyTimeout: 2e4,
2551
+ tryKeyboard: true
2552
+ });
2553
+ await ensureRemoteDir(sftp, settings.remotePath);
2554
+ await sftp.put(localZip, remoteZipPath);
2555
+ console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
2556
+ if (extract) {
2557
+ const target = settings.remotePath.replace(/\/$/, "");
2558
+ const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
2559
+ console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
2560
+ await execCommand(
2561
+ sftp.client,
2562
+ unzipCmd
2563
+ );
2564
+ console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
2565
+ }
2566
+ console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
2567
+ } finally {
2568
+ await sftp.end();
2569
+ }
2570
+ }
2571
+ async function runWisdomSftpDeploy(params) {
2572
+ const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
2573
+ const resolvedZipPath = path8.resolve(zipPath);
2574
+ let zipSizeBytes = 0;
2575
+ try {
2576
+ zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
2577
+ await uploadAndMaybeExtract(
2578
+ params.settings,
2579
+ resolvedZipPath,
2580
+ params.extract
2581
+ );
2582
+ } finally {
2583
+ try {
2584
+ await unlink(resolvedZipPath);
2585
+ console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
2586
+ } catch {
2587
+ }
2588
+ }
2589
+ return {
2590
+ ok: true,
2591
+ localDir: params.localDir,
2592
+ host: params.settings.host,
2593
+ remotePath: params.settings.remotePath,
2594
+ zipSizeBytes,
2595
+ extracted: params.extract
2596
+ };
2597
+ }
2598
+
2599
+ // src/commands/deploy/sftp.ts
2600
+ function registerDeploySftpCommands(program) {
2601
+ program.command("deploy-sftp").description(
2602
+ "\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"
2603
+ ).option(
2604
+ "--dir <path>",
2605
+ "\u672C\u5730\u4EA7\u7269\u76EE\u5F55\uFF1B\u5355\u4ED3\u9ED8\u8BA4 apps/web/dist\uFF08\u9700\u5148 rush build / vite build\uFF09",
2606
+ "apps/web/dist"
2607
+ ).option(
2608
+ "--config <path>",
2609
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
2610
+ ).option("--extract", "\u4E0A\u4F20\u540E\u5728\u8FDC\u7A0B\u670D\u52A1\u5668\u89E3\u538B zip \u5230 remotePath").action(
2611
+ async (opts) => {
2612
+ const cfg = loadApmConfig({ configPath: opts.config });
2613
+ const settings = resolveWisdomDeployFromApmConfig(cfg);
2614
+ const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
2615
+ if (!await isDirectoryPath(root)) {
2616
+ console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
2617
+ process.exit(1);
2618
+ }
2619
+ try {
2620
+ const result = await runWisdomSftpDeploy({
2621
+ localDir: root,
2622
+ settings,
2623
+ extract: Boolean(opts.extract)
2624
+ });
2625
+ console.log(JSON.stringify(result, null, 2));
2626
+ } catch (err) {
2627
+ const message = err instanceof Error ? err.message : String(err);
2628
+ console.error(`${opts.extract ? "\u90E8\u7F72" : "\u4E0A\u4F20"}\u5931\u8D25:`, message);
2629
+ process.exit(1);
2630
+ }
2631
+ }
2632
+ );
2633
+ }
2634
+
2447
2635
  // src/commands/deploy/index.ts
2448
2636
  function registerDeployCommands(program) {
2449
2637
  registerDeployBackendCommands(program);
2450
2638
  registerDeployFrontendCommands(program);
2639
+ registerDeploySftpCommands(program);
2451
2640
  }
2452
2641
 
2453
2642
  // src/index.ts
2454
2643
  function buildProgram() {
2455
2644
  const program = new Command();
2456
2645
  program.name("apm").description(
2457
- `OPC \u5E73\u53F0\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
2646
+ `\u6BD4\u90BB\u661F\u56FE\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
2458
2647
  \u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
2459
2648
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
2460
2649
  program.command("login").description(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.25",
3
+ "version": "6.0.27",
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
  }