ai-project-manage-cli 8.0.5 → 8.0.8

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
@@ -154,6 +154,14 @@ var init_request_config = __esm({
154
154
  method: "PUT",
155
155
  path: "/cli/webide/test-cases"
156
156
  }),
157
+ webideUpsertSqlExecution: defineEndpoint({
158
+ method: "PUT",
159
+ path: "/cli/webide/sql-executions"
160
+ }),
161
+ webideFinalizeSqlExecution: defineEndpoint({
162
+ method: "PUT",
163
+ path: "/cli/webide/sql-executions/finalize"
164
+ }),
157
165
  projectBaseBranch: defineEndpoint(
158
166
  {
159
167
  method: "GET",
@@ -1858,7 +1866,7 @@ function buildProgram() {
1858
1866
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
1859
1867
  program.command("login").description(
1860
1868
  "\u9A8C\u8BC1 API Key \u5E76\u5199\u5165 ~/.config/apm/config.json\uFF08GET /api/v1/cli/me\uFF09"
1861
- ).option("--api-key <key>", "\u5BA2\u6237\u673A API Key\uFF08cm_ \u5F00\u5934\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
1869
+ ).option("--api-key <key>", "\u8FDC\u7A0B\u4E3B\u673A API Key\uFF08cm_ \u5F00\u5934\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
1862
1870
  await runLogin(opts);
1863
1871
  });
1864
1872
  program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u6309 WebIDE \u5DE5\u4F5C\u533A\u521D\u59CB\u5316\uFF1A\u5199\u5165\u6307\u5357\u4E0E webide \u89C4\u5219").action(async () => {
@@ -58,6 +58,14 @@ var requestConfig = {
58
58
  method: "PUT",
59
59
  path: "/cli/webide/test-cases"
60
60
  }),
61
+ webideUpsertSqlExecution: defineEndpoint({
62
+ method: "PUT",
63
+ path: "/cli/webide/sql-executions"
64
+ }),
65
+ webideFinalizeSqlExecution: defineEndpoint({
66
+ method: "PUT",
67
+ path: "/cli/webide/sql-executions/finalize"
68
+ }),
61
69
  projectBaseBranch: defineEndpoint(
62
70
  {
63
71
  method: "GET",
@@ -1515,6 +1523,286 @@ function createUpsertWebIdeTestCasesTool(options) {
1515
1523
  };
1516
1524
  }
1517
1525
 
1526
+ // src/commands/connect/tools/mysql-tools.ts
1527
+ import { existsSync as existsSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
1528
+ import { isAbsolute, relative as relative2, resolve as resolve4 } from "node:path";
1529
+ import mysql from "mysql2/promise";
1530
+ var MAX_RESULT_ROWS = 100;
1531
+ var EXEC_TIMEOUT_MS = 6e4;
1532
+ var MAX_SQL_FILE_BYTES = 2 * 1024 * 1024;
1533
+ function asString3(value) {
1534
+ return typeof value === "string" ? value.trim() : "";
1535
+ }
1536
+ function asNumber(value) {
1537
+ if (typeof value === "number" && Number.isFinite(value)) return value;
1538
+ if (typeof value === "string" && value.trim()) {
1539
+ const parsed = Number(value);
1540
+ if (Number.isFinite(parsed)) return parsed;
1541
+ }
1542
+ return void 0;
1543
+ }
1544
+ function isResultSetHeader(value) {
1545
+ return typeof value === "object" && value !== null && "affectedRows" in value && !Array.isArray(value);
1546
+ }
1547
+ function formatOkPacket(packet) {
1548
+ return {
1549
+ kind: "update",
1550
+ affectedRows: packet.affectedRows ?? 0,
1551
+ insertId: packet.insertId ?? 0,
1552
+ changedRows: packet.changedRows,
1553
+ warnings: packet.warningStatus
1554
+ };
1555
+ }
1556
+ function formatSelectRows(rows) {
1557
+ const truncated = rows.slice(0, MAX_RESULT_ROWS);
1558
+ return {
1559
+ kind: "select",
1560
+ rowCount: rows.length,
1561
+ truncated: rows.length > MAX_RESULT_ROWS,
1562
+ rows: truncated
1563
+ };
1564
+ }
1565
+ function formatStatementResult(result) {
1566
+ if (Array.isArray(result)) {
1567
+ return formatSelectRows(result);
1568
+ }
1569
+ if (isResultSetHeader(result)) {
1570
+ return formatOkPacket(result);
1571
+ }
1572
+ return { kind: "unknown", value: result };
1573
+ }
1574
+ function normalizeMultiStatementResults(raw) {
1575
+ if (!Array.isArray(raw)) {
1576
+ return [formatStatementResult(raw)];
1577
+ }
1578
+ if (raw.length === 0) {
1579
+ return [];
1580
+ }
1581
+ const first = raw[0];
1582
+ if (Array.isArray(first)) {
1583
+ return raw.map((item) => {
1584
+ if (Array.isArray(item)) {
1585
+ return formatStatementResult(item[0]);
1586
+ }
1587
+ return formatStatementResult(item);
1588
+ });
1589
+ }
1590
+ if (isResultSetHeader(first)) {
1591
+ return raw.map((item) => formatStatementResult(item));
1592
+ }
1593
+ return [formatStatementResult(raw)];
1594
+ }
1595
+ function resolveSqlArtifactPath(workdir, absolutePath) {
1596
+ if (!isAbsolute(absolutePath)) {
1597
+ return { ok: false, error: "path \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
1598
+ }
1599
+ const workdirAbs = resolve4(toFsPath(workdir));
1600
+ const targetAbs = resolve4(toFsPath(absolutePath));
1601
+ const rel = relative2(workdirAbs, targetAbs);
1602
+ if (!rel || rel.startsWith("..") || isAbsolute(rel)) {
1603
+ return { ok: false, error: "path \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
1604
+ }
1605
+ const fsPath = toFsPath(targetAbs);
1606
+ if (!existsSync3(fsPath)) {
1607
+ return { ok: false, error: `\u6587\u4EF6\u4E0D\u5B58\u5728: ${absolutePath}` };
1608
+ }
1609
+ let st;
1610
+ try {
1611
+ st = statSync3(fsPath);
1612
+ } catch {
1613
+ return { ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6\u6587\u4EF6: ${absolutePath}` };
1614
+ }
1615
+ if (!st.isFile()) {
1616
+ return { ok: false, error: "path \u5FC5\u987B\u6307\u5411\u666E\u901A\u6587\u4EF6" };
1617
+ }
1618
+ if (st.size > MAX_SQL_FILE_BYTES) {
1619
+ return {
1620
+ ok: false,
1621
+ error: `SQL \u6587\u4EF6\u8FC7\u5927\uFF08>${MAX_SQL_FILE_BYTES} bytes\uFF09`
1622
+ };
1623
+ }
1624
+ const displayPath = normalizeWorkdirPath(rel) || normalizeWorkdirPath(absolutePath);
1625
+ return { ok: true, absPath: targetAbs, displayPath };
1626
+ }
1627
+ function readSqlFile(absPath) {
1628
+ return readFileSync3(toFsPath(absPath), "utf8");
1629
+ }
1630
+ function createMysqlExecuteTool(options) {
1631
+ const { taskId, executionId, workdir, upsertSqlExecution } = options;
1632
+ return {
1633
+ MysqlExecute: {
1634
+ description: "Execute SQL from a workspace artifact file against MySQL. Pass the absolute filesystem path of the SQL file (not the SQL text). The CLI reads the file itself to avoid escaping issues with multi-statement scripts.",
1635
+ inputSchema: {
1636
+ type: "object",
1637
+ properties: {
1638
+ host: { type: "string", description: "MySQL host" },
1639
+ port: { type: "number", description: "MySQL port, default 3306" },
1640
+ user: { type: "string", description: "MySQL username" },
1641
+ password: { type: "string", description: "MySQL password" },
1642
+ database: { type: "string", description: "Database name" },
1643
+ path: {
1644
+ type: "string",
1645
+ description: "Absolute filesystem path to the SQL artifact file under the current workspace (CLI reads the file; do not pass SQL text)"
1646
+ }
1647
+ },
1648
+ required: ["host", "user", "password", "database", "path"]
1649
+ },
1650
+ execute: async (args) => {
1651
+ const host = asString3(args.host);
1652
+ const user = asString3(args.user);
1653
+ const password = asString3(args.password);
1654
+ const database = asString3(args.database);
1655
+ const pathArg = asString3(args.path);
1656
+ const port = asNumber(args.port) ?? 3306;
1657
+ if (!host || !user || !database || !pathArg) {
1658
+ return {
1659
+ content: [
1660
+ {
1661
+ type: "text",
1662
+ text: "host / user / database / path \u5747\u4E0D\u80FD\u4E3A\u7A7A"
1663
+ }
1664
+ ],
1665
+ isError: true
1666
+ };
1667
+ }
1668
+ const resolved = resolveSqlArtifactPath(workdir, pathArg);
1669
+ if (!resolved.ok) {
1670
+ try {
1671
+ await upsertSqlExecution({
1672
+ executionId,
1673
+ taskId,
1674
+ status: "FAILED",
1675
+ artifactPath: pathArg,
1676
+ error: resolved.error,
1677
+ host,
1678
+ port,
1679
+ database,
1680
+ dbUser: user
1681
+ });
1682
+ } catch {
1683
+ }
1684
+ return {
1685
+ content: [{ type: "text", text: resolved.error }],
1686
+ isError: true
1687
+ };
1688
+ }
1689
+ let sql;
1690
+ try {
1691
+ sql = readSqlFile(resolved.absPath).trim();
1692
+ } catch (err) {
1693
+ const detail = err instanceof Error ? err.message : String(err);
1694
+ try {
1695
+ await upsertSqlExecution({
1696
+ executionId,
1697
+ taskId,
1698
+ status: "FAILED",
1699
+ artifactPath: resolved.displayPath,
1700
+ error: `\u8BFB\u53D6 SQL \u6587\u4EF6\u5931\u8D25: ${detail}`,
1701
+ host,
1702
+ port,
1703
+ database,
1704
+ dbUser: user
1705
+ });
1706
+ } catch {
1707
+ }
1708
+ return {
1709
+ content: [
1710
+ {
1711
+ type: "text",
1712
+ text: `\u8BFB\u53D6 SQL \u6587\u4EF6\u5931\u8D25: ${detail}`
1713
+ }
1714
+ ],
1715
+ isError: true
1716
+ };
1717
+ }
1718
+ if (!sql) {
1719
+ try {
1720
+ await upsertSqlExecution({
1721
+ executionId,
1722
+ taskId,
1723
+ status: "FAILED",
1724
+ artifactPath: resolved.displayPath,
1725
+ sql: "",
1726
+ error: "SQL \u6587\u4EF6\u4E3A\u7A7A",
1727
+ host,
1728
+ port,
1729
+ database,
1730
+ dbUser: user
1731
+ });
1732
+ } catch {
1733
+ }
1734
+ return {
1735
+ content: [{ type: "text", text: "SQL \u6587\u4EF6\u4E3A\u7A7A" }],
1736
+ isError: true
1737
+ };
1738
+ }
1739
+ let connection;
1740
+ try {
1741
+ connection = await mysql.createConnection({
1742
+ host,
1743
+ port,
1744
+ user,
1745
+ password,
1746
+ database,
1747
+ multipleStatements: true,
1748
+ connectTimeout: 15e3
1749
+ });
1750
+ const [rawResults] = await connection.query({
1751
+ sql,
1752
+ timeout: EXEC_TIMEOUT_MS
1753
+ });
1754
+ const statements = normalizeMultiStatementResults(rawResults);
1755
+ const resultPayload = {
1756
+ ok: true,
1757
+ path: resolved.displayPath,
1758
+ statements,
1759
+ host,
1760
+ port,
1761
+ database,
1762
+ dbUser: user
1763
+ };
1764
+ await upsertSqlExecution({
1765
+ executionId,
1766
+ taskId,
1767
+ status: "SUCCESS",
1768
+ artifactPath: resolved.displayPath,
1769
+ sql,
1770
+ result: resultPayload,
1771
+ host,
1772
+ port,
1773
+ database,
1774
+ dbUser: user
1775
+ });
1776
+ return JSON.stringify(resultPayload, null, 2);
1777
+ } catch (err) {
1778
+ const detail = err instanceof Error ? err.message : String(err);
1779
+ try {
1780
+ await upsertSqlExecution({
1781
+ executionId,
1782
+ taskId,
1783
+ status: "FAILED",
1784
+ artifactPath: resolved.displayPath,
1785
+ sql,
1786
+ error: detail,
1787
+ host: host || void 0,
1788
+ port,
1789
+ database: database || void 0,
1790
+ dbUser: user || void 0
1791
+ });
1792
+ } catch {
1793
+ }
1794
+ return {
1795
+ content: [{ type: "text", text: `MySQL \u6267\u884C\u5931\u8D25: ${detail}` }],
1796
+ isError: true
1797
+ };
1798
+ } finally {
1799
+ await connection?.end().catch(() => void 0);
1800
+ }
1801
+ }
1802
+ }
1803
+ };
1804
+ }
1805
+
1518
1806
  // src/commands/connect/tools/index.ts
1519
1807
  var PLAN_MODE_ASK_QUESTION_HINT = `[SDK \u73AF\u5883\u8BF4\u660E]
1520
1808
  AskQuestion \u5DF2\u901A\u8FC7 MCP \u670D\u52A1\u5668 custom-user-tools \u6CE8\u518C\uFF0C\u5DE5\u5177\u540D\u4E3A AskQuestion\u3002
@@ -1553,6 +1841,17 @@ function createCursorCustomTools(cfg, options) {
1553
1841
  })
1554
1842
  );
1555
1843
  }
1844
+ if (options.enableMysqlTools && options.sqlExecutionId && options.workdir) {
1845
+ Object.assign(
1846
+ tools,
1847
+ createMysqlExecuteTool({
1848
+ taskId,
1849
+ executionId: options.sqlExecutionId,
1850
+ workdir: options.workdir,
1851
+ upsertSqlExecution: (args) => cli.webideUpsertSqlExecution(args)
1852
+ })
1853
+ );
1854
+ }
1556
1855
  }
1557
1856
  return tools;
1558
1857
  }
@@ -1671,6 +1970,8 @@ async function runCursorAgent(cfg, ctx, options) {
1671
1970
  enableAskQuestion: options.enableAskQuestion,
1672
1971
  askQuestionExecute: options.askQuestionExecute,
1673
1972
  enableWebIdePlanTools: options.enableWebIdePlanTools,
1973
+ enableMysqlTools: options.enableMysqlTools,
1974
+ sqlExecutionId: options.sqlExecutionId,
1674
1975
  taskId: options.taskId,
1675
1976
  workdir
1676
1977
  });
@@ -1805,17 +2106,17 @@ async function runCursorAgent(cfg, ctx, options) {
1805
2106
  }
1806
2107
 
1807
2108
  // src/commands/connect/webide-agent-registry.ts
1808
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
1809
- import { dirname as dirname3, resolve as resolve4 } from "node:path";
2109
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2110
+ import { dirname as dirname3, resolve as resolve5 } from "node:path";
1810
2111
  function registryPath(workdir, taskId) {
1811
- return resolve4(workdir, ".apm", "webide", taskId, "cursor-agent.json");
2112
+ return resolve5(workdir, ".apm", "webide", taskId, "cursor-agent.json");
1812
2113
  }
1813
2114
  function readRegistry(path) {
1814
- if (!existsSync3(path)) {
2115
+ if (!existsSync4(path)) {
1815
2116
  return {};
1816
2117
  }
1817
2118
  try {
1818
- const parsed = JSON.parse(readFileSync3(path, "utf8"));
2119
+ const parsed = JSON.parse(readFileSync4(path, "utf8"));
1819
2120
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1820
2121
  const raw = parsed;
1821
2122
  const state = {};
@@ -1886,20 +2187,20 @@ function saveWebIdeAgentId(workdir, taskId, agentId) {
1886
2187
  }
1887
2188
  function clearWebIdeAgentId(workdir, taskId) {
1888
2189
  const path = registryPath(workdir, taskId);
1889
- if (!existsSync3(path)) return;
2190
+ if (!existsSync4(path)) return;
1890
2191
  syncWebIdeTaskState(workdir, taskId, { agentId: "" });
1891
2192
  }
1892
2193
 
1893
2194
  // src/commands/clean-webide-cache.ts
1894
- import { existsSync as existsSync4, rmSync } from "node:fs";
1895
- import { resolve as resolve5 } from "node:path";
2195
+ import { existsSync as existsSync5, rmSync } from "node:fs";
2196
+ import { resolve as resolve6 } from "node:path";
1896
2197
  import { getDefaultSdkStateRoot } from "@cursor/sdk";
1897
2198
  async function purgeCursorAgentStoreForAgent(workdir, agentId) {
1898
2199
  const trimmedAgentId = agentId.trim();
1899
2200
  const trimmedWorkdir = workdir.trim();
1900
2201
  if (!trimmedAgentId || !trimmedWorkdir) return false;
1901
2202
  const stateRoot = getDefaultSdkStateRoot(trimmedWorkdir);
1902
- if (!existsSync4(stateRoot)) return false;
2203
+ if (!existsSync5(stateRoot)) return false;
1903
2204
  const { SqliteLocalAgentStore } = await import(
1904
2205
  /* @vite-ignore */
1905
2206
  "@cursor/sdk/sqlite"
@@ -1963,8 +2264,8 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
1963
2264
  `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
1964
2265
  );
1965
2266
  }
1966
- const dir = resolve5(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
1967
- if (existsSync4(dir)) {
2267
+ const dir = resolve6(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
2268
+ if (existsSync5(dir)) {
1968
2269
  rmSync(dir, { recursive: true, force: true });
1969
2270
  console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
1970
2271
  } else {
@@ -1975,11 +2276,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
1975
2276
  // src/commands/connect/webide-ask-question.ts
1976
2277
  import { setTimeout as delay } from "node:timers/promises";
1977
2278
  var POLL_INTERVAL_MS = 2e3;
1978
- function asString3(value) {
2279
+ function asString4(value) {
1979
2280
  return typeof value === "string" ? value.trim() : "";
1980
2281
  }
1981
2282
  function parseQuestions(args) {
1982
- const title = asString3(args.title) || void 0;
2283
+ const title = asString4(args.title) || void 0;
1983
2284
  const raw = args.questions;
1984
2285
  if (!Array.isArray(raw) || raw.length === 0) {
1985
2286
  throw new Error("AskQuestion \u7F3A\u5C11 questions");
@@ -1988,16 +2289,16 @@ function parseQuestions(args) {
1988
2289
  for (const item of raw) {
1989
2290
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
1990
2291
  const row = item;
1991
- const id = asString3(row.id);
1992
- const prompt = asString3(row.prompt);
2292
+ const id = asString4(row.id);
2293
+ const prompt = asString4(row.prompt);
1993
2294
  const optionsRaw = row.options;
1994
2295
  if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
1995
2296
  const options = [];
1996
2297
  for (const opt of optionsRaw) {
1997
2298
  if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
1998
2299
  const o = opt;
1999
- const oid = asString3(o.id);
2000
- const label = asString3(o.label);
2300
+ const oid = asString4(o.id);
2301
+ const label = asString4(o.label);
2001
2302
  if (oid && label) options.push({ id: oid, label });
2002
2303
  }
2003
2304
  if (options.length < 2) {
@@ -2490,14 +2791,14 @@ async function ensureWebIdePullRequests(cfg, taskId, workdir) {
2490
2791
  }
2491
2792
 
2492
2793
  // src/version.ts
2493
- import { readFileSync as readFileSync4 } from "fs";
2794
+ import { readFileSync as readFileSync5 } from "fs";
2494
2795
  import { dirname as dirname4, join as join4 } from "path";
2495
2796
  import { fileURLToPath as fileURLToPath2 } from "url";
2496
2797
  function readCliVersion() {
2497
2798
  try {
2498
2799
  const dir = dirname4(fileURLToPath2(import.meta.url));
2499
2800
  const pkgPath = join4(dir, "..", "package.json");
2500
- const pkg = JSON.parse(readFileSync4(pkgPath, "utf8"));
2801
+ const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
2501
2802
  return pkg.version ?? "0.0.0";
2502
2803
  } catch {
2503
2804
  return "0.0.0";
@@ -2505,7 +2806,7 @@ function readCliVersion() {
2505
2806
  }
2506
2807
 
2507
2808
  // src/commands/sync-webide-attachments.ts
2508
- import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
2809
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2509
2810
  import { join as join5 } from "path";
2510
2811
  var MANIFEST_FILE = ".sync-manifest.json";
2511
2812
  async function downloadAttachment(cfg, attachmentId) {
@@ -2523,12 +2824,12 @@ async function downloadAttachment(cfg, attachmentId) {
2523
2824
  }
2524
2825
  function loadManifest(dir) {
2525
2826
  const path = join5(dir, MANIFEST_FILE);
2526
- if (!existsSync5(path)) {
2827
+ if (!existsSync6(path)) {
2527
2828
  return { version: 1, attachments: {} };
2528
2829
  }
2529
2830
  try {
2530
2831
  const parsed = JSON.parse(
2531
- readFileSync5(path, "utf8")
2832
+ readFileSync6(path, "utf8")
2532
2833
  );
2533
2834
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
2534
2835
  return parsed;
@@ -2546,7 +2847,7 @@ function saveManifest(dir, manifest) {
2546
2847
  );
2547
2848
  }
2548
2849
  function isAttachmentUpToDate(entry, item, dest) {
2549
- if (!entry || !existsSync5(dest)) return false;
2850
+ if (!entry || !existsSync6(dest)) return false;
2550
2851
  if (entry.name !== item.name) return false;
2551
2852
  const createdAt = item.createdAt ?? "";
2552
2853
  return entry.createdAt === createdAt;
@@ -2593,14 +2894,14 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
2593
2894
 
2594
2895
  // src/utils/project-documents.ts
2595
2896
  import {
2596
- existsSync as existsSync6,
2897
+ existsSync as existsSync7,
2597
2898
  readdirSync as readdirSync3,
2598
- readFileSync as readFileSync6,
2899
+ readFileSync as readFileSync7,
2599
2900
  rmSync as rmSync2,
2600
2901
  writeFileSync as writeFileSync5
2601
2902
  } from "fs";
2602
2903
  import { createHash } from "crypto";
2603
- import { dirname as dirname5, join as join6, relative as relative2, sep } from "path";
2904
+ import { dirname as dirname5, join as join6, relative as relative3, sep } from "path";
2604
2905
  var MANIFEST_FILE2 = "manifest.json";
2605
2906
  function normalizeProjectIdForPath(projectId) {
2606
2907
  const id = projectId.trim();
@@ -2642,12 +2943,12 @@ function readLocalManifest(apmRoot, projectId) {
2642
2943
  projectDocumentsDir(apmRoot, projectId),
2643
2944
  MANIFEST_FILE2
2644
2945
  );
2645
- if (!existsSync6(manifestPath3)) {
2946
+ if (!existsSync7(manifestPath3)) {
2646
2947
  return null;
2647
2948
  }
2648
2949
  try {
2649
2950
  return JSON.parse(
2650
- readFileSync6(manifestPath3, "utf8")
2951
+ readFileSync7(manifestPath3, "utf8")
2651
2952
  );
2652
2953
  } catch {
2653
2954
  return null;
@@ -2655,7 +2956,7 @@ function readLocalManifest(apmRoot, projectId) {
2655
2956
  }
2656
2957
  function listLocalDocumentPaths(apmRoot, projectId) {
2657
2958
  const root = projectDocumentsDir(apmRoot, projectId);
2658
- if (!existsSync6(root)) {
2959
+ if (!existsSync7(root)) {
2659
2960
  return [];
2660
2961
  }
2661
2962
  const paths = [];
@@ -2669,7 +2970,7 @@ function listLocalDocumentPaths(apmRoot, projectId) {
2669
2970
  if (entry.isFile() && entry.name === MANIFEST_FILE2) {
2670
2971
  continue;
2671
2972
  }
2672
- const rel = relative2(root, abs).split(sep).join("/");
2973
+ const rel = relative3(root, abs).split(sep).join("/");
2673
2974
  paths.push(rel);
2674
2975
  }
2675
2976
  };
@@ -2790,7 +3091,7 @@ ${diagnostic ?? ""}`);
2790
3091
  const absPath = toFsPath(
2791
3092
  projectDocumentLocalPath(targetApmDir, projectId, path)
2792
3093
  );
2793
- if (existsSync6(absPath)) {
3094
+ if (existsSync7(absPath)) {
2794
3095
  rmSync2(absPath, { force: true });
2795
3096
  deleted += 1;
2796
3097
  }
@@ -2839,7 +3140,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
2839
3140
  const absPath = toFsPath(
2840
3141
  projectDocumentLocalPath(targetApmDir, projectId, path)
2841
3142
  );
2842
- const content = readFileSync6(absPath, "utf8");
3143
+ const content = readFileSync7(absPath, "utf8");
2843
3144
  const contentHash = hashLocalFileContent(content);
2844
3145
  if (remoteHashByPath.get(path) === contentHash) {
2845
3146
  continue;
@@ -2860,7 +3161,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
2860
3161
  }
2861
3162
 
2862
3163
  // src/commands/connect/cli-version-sync.ts
2863
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3164
+ import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
2864
3165
  import { join as join7 } from "path";
2865
3166
  var CLI_VERSION_FILE = ".cli-version.json";
2866
3167
  function manifestPath2(apmDir) {
@@ -2868,12 +3169,12 @@ function manifestPath2(apmDir) {
2868
3169
  }
2869
3170
  function loadManifest2(apmDir) {
2870
3171
  const path = toFsPath(manifestPath2(apmDir));
2871
- if (!existsSync7(path)) {
3172
+ if (!existsSync8(path)) {
2872
3173
  return null;
2873
3174
  }
2874
3175
  try {
2875
3176
  const parsed = JSON.parse(
2876
- readFileSync7(path, "utf8")
3177
+ readFileSync8(path, "utf8")
2877
3178
  );
2878
3179
  if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
2879
3180
  return parsed;
@@ -2909,6 +3210,24 @@ function markSkillsSyncedForCliVersion(workdir, cliVersion) {
2909
3210
  syncedInSession.set(workdir, cliVersion);
2910
3211
  }
2911
3212
 
3213
+ // src/commands/connect/parse-webide-prompt-payload.ts
3214
+ function parseWebIdePromptPayload(content) {
3215
+ const marker = "\n\npayload=";
3216
+ const index = content.lastIndexOf(marker);
3217
+ if (index < 0) return null;
3218
+ const jsonText = content.slice(index + marker.length).trim();
3219
+ if (!jsonText) return null;
3220
+ try {
3221
+ const parsed = JSON.parse(jsonText);
3222
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
3223
+ return parsed;
3224
+ }
3225
+ } catch {
3226
+ return null;
3227
+ }
3228
+ return null;
3229
+ }
3230
+
2912
3231
  // src/commands/connect/handle-webide-message.ts
2913
3232
  var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
2914
3233
  "skip-plan",
@@ -2921,6 +3240,12 @@ var WEBIDE_CODE_CHANGE_ACTIONS = /* @__PURE__ */ new Set([
2921
3240
  function shouldCommitAfterWebIdeMessage(action) {
2922
3241
  return WEBIDE_CODE_CHANGE_ACTIONS.has(action);
2923
3242
  }
3243
+ function isStartLocalServicesAction(action) {
3244
+ return action === "enter-manual-test" || action === "skip-test";
3245
+ }
3246
+ function isExecuteSqlAction(action) {
3247
+ return action === "execute-sql";
3248
+ }
2924
3249
  function syncLocalTaskStatus(workdir, taskId, msg, status) {
2925
3250
  syncWebIdeTaskState(workdir, taskId, {
2926
3251
  messageId: msg.messageId,
@@ -3129,7 +3454,11 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3129
3454
  "running"
3130
3455
  );
3131
3456
  await syncPrepLog();
3132
- const savedAgentId = loadWebIdeAgentId(workdir, taskId);
3457
+ const startLocalServices = isStartLocalServicesAction(msg.action);
3458
+ const executeSql = isExecuteSqlAction(msg.action);
3459
+ const savedAgentId = startLocalServices ? void 0 : loadWebIdeAgentId(workdir, taskId);
3460
+ const promptPayload = executeSql ? parseWebIdePromptPayload(msg.content) : null;
3461
+ const sqlExecutionId = executeSql && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
3133
3462
  const outcome = await runCursorAgent(
3134
3463
  cfg,
3135
3464
  {
@@ -3146,17 +3475,23 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3146
3475
  forceSend: true,
3147
3476
  eventSession,
3148
3477
  appendMessageContent: (content) => appendContent(cfg, messageId, content),
3149
- askQuestionExecute: createWebIdeAskQuestionExecute({
3478
+ askQuestionExecute: startLocalServices ? void 0 : createWebIdeAskQuestionExecute({
3150
3479
  cfg,
3151
3480
  taskId,
3152
3481
  signal
3153
3482
  }),
3154
- enableWebIdePlanTools: true,
3483
+ enableAskQuestion: !startLocalServices && !executeSql,
3484
+ enableWebIdePlanTools: !startLocalServices && !executeSql,
3485
+ enablePtySessionMcp: startLocalServices,
3486
+ enableMysqlTools: executeSql,
3487
+ sqlExecutionId,
3155
3488
  enableSandbox: false,
3156
- onInvalidatePersistedAgentId: () => clearWebIdeAgentId(workdir, taskId),
3489
+ onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => clearWebIdeAgentId(workdir, taskId),
3157
3490
  taskId,
3158
3491
  createRemoteLogSync: (agentId) => {
3159
- saveWebIdeAgentId(workdir, taskId, agentId);
3492
+ if (!startLocalServices) {
3493
+ saveWebIdeAgentId(workdir, taskId, agentId);
3494
+ }
3160
3495
  logSyncRef.current = createThrottledWebIdeMessageLogSync(
3161
3496
  cfg,
3162
3497
  { taskId, messageId, agentId },
@@ -3170,7 +3505,9 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3170
3505
  return logSyncRef.current;
3171
3506
  },
3172
3507
  onRunStarted: async ({ agentId, runId }) => {
3173
- saveWebIdeAgentId(workdir, taskId, agentId);
3508
+ if (!startLocalServices) {
3509
+ saveWebIdeAgentId(workdir, taskId, agentId);
3510
+ }
3174
3511
  eventSession.addCliStep(
3175
3512
  "\u542F\u52A8 Cursor Agent",
3176
3513
  `agentId=${agentId} runId=${runId}`,
@@ -3182,7 +3519,9 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3182
3519
  }
3183
3520
  }
3184
3521
  );
3185
- saveWebIdeAgentId(workdir, taskId, outcome.agentId);
3522
+ if (!startLocalServices) {
3523
+ saveWebIdeAgentId(workdir, taskId, outcome.agentId);
3524
+ }
3186
3525
  const tokenUsage = outcome.usage != null ? {
3187
3526
  modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
3188
3527
  inputTokens: outcome.usage.inputTokens,
@@ -3202,6 +3541,20 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3202
3541
  detail,
3203
3542
  tokenUsage
3204
3543
  );
3544
+ if (executeSql && sqlExecutionId) {
3545
+ try {
3546
+ await api.cli.webideFinalizeSqlExecution({
3547
+ executionId: sqlExecutionId,
3548
+ status: "FAILED",
3549
+ error: detail
3550
+ });
3551
+ } catch (finalizeErr) {
3552
+ console.warn(
3553
+ "[apm] finalize sql execution failed:",
3554
+ finalizeErr instanceof Error ? finalizeErr.message : finalizeErr
3555
+ );
3556
+ }
3557
+ }
3205
3558
  await setError(cfg, messageId, detail);
3206
3559
  syncLocalTaskStatus(workdir, taskId, msg, "FAILED");
3207
3560
  return;
@@ -3213,6 +3566,20 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
3213
3566
  "\u5DF2\u53D6\u6D88",
3214
3567
  tokenUsage
3215
3568
  );
3569
+ if (executeSql && sqlExecutionId) {
3570
+ try {
3571
+ await api.cli.webideFinalizeSqlExecution({
3572
+ executionId: sqlExecutionId,
3573
+ status: "CANCELLED",
3574
+ error: "\u5DF2\u53D6\u6D88"
3575
+ });
3576
+ } catch (finalizeErr) {
3577
+ console.warn(
3578
+ "[apm] finalize sql execution cancelled:",
3579
+ finalizeErr instanceof Error ? finalizeErr.message : finalizeErr
3580
+ );
3581
+ }
3582
+ }
3216
3583
  await updateStatus(cfg, messageId, "CANCELLED");
3217
3584
  syncLocalTaskStatus(workdir, taskId, msg, "CANCELLED");
3218
3585
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.0.5",
3
+ "version": "8.0.8",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -18,10 +18,10 @@
18
18
  "prepublishOnly": "npm run build"
19
19
  },
20
20
  "devDependencies": {
21
- "@types/ws": "~8.5.14",
22
21
  "@types/node": "^22.0.0",
23
- "typescript": "~5.6.0",
22
+ "@types/ws": "~8.5.14",
24
23
  "esbuild": "~0.28.0",
24
+ "typescript": "~5.6.0",
25
25
  "vitest": "~4.1.5"
26
26
  },
27
27
  "engines": {
@@ -32,9 +32,10 @@
32
32
  "@connectrpc/connect": "~1.7.0",
33
33
  "@connectrpc/connect-node": "~1.7.0",
34
34
  "@cursor/sdk": "^1.0.22",
35
- "ws": "~8.18.0",
36
- "listpage-http": "~0.0.318",
37
35
  "commander": "~14.0.3",
38
- "minio": "~8.0.7"
36
+ "listpage-http": "~0.0.318",
37
+ "minio": "~8.0.7",
38
+ "mysql2": "~3.14.5",
39
+ "ws": "~8.18.0"
39
40
  }
40
41
  }
@@ -14,6 +14,7 @@
14
14
  - `webide_reply.md`:输出通道(每轮必守)
15
15
  - `webide_git_commit.md`:开发 / 修码时的 Git 提交与 push
16
16
  - `webide_sql_change.md`:有表结构或 SQL 变更时,须在 `AppendMessage` 中醒目提示
17
+ - `webide_sql_execute.md`:execute-sql 轮次连库执行 SQL 时 Read
17
18
  - `webide_terminal.md`:长驻进程禁止用 Shell;本地服务由顶栏「启动项目」触发,开发任务不要自行起服务
18
19
  - `webide_testcase.md`:生成测试用例并调用 `UpsertWebIdeTestCases`
19
20
  - `webide_merge.md`:验收通过后调用 `MergeWebIdePullRequests`
@@ -15,9 +15,11 @@
15
15
  在回复中增加小节(标题可用「数据表 / SQL 变更」),至少包含:
16
16
 
17
17
  1. **涉及表**:表名列表与变更类型(如新增列、建表)
18
- 2. **SQL 产物路径**:仓库内相关文件路径(若有)
19
- 3. **待执行 SQL**:完整、可复制执行的语句,放在 ` ```sql ` 代码块中,按执行顺序列出;禁止用 `...` 省略表名/字段名
20
- 4. **执行提醒**:明确告知用户需在对应环境数据库执行(若本轮未自动执行)
18
+ 2. **SQL 产物路径**:仓库内相关文件路径(**必填**;完整、可 Read;多仓写明仓库)
19
+ 3. **待执行 SQL**:完整、可复制的语句预览,放在 ` ```sql ` 代码块中,按执行顺序列出;禁止用 `...` 省略表名/字段名(预览仅供人看,执行时以产物文件为准)
20
+ 4. **执行提醒**:请在目标环境数据库执行;可点 SQL 块上的「执行」由助手连库执行
21
+
22
+ **禁止**写 Flyway、「若启动已自动迁移可跳过」或同类表述。
21
23
 
22
24
  多仓时注明 SQL 属于哪个仓库(如后端仓)。
23
25
 
@@ -0,0 +1,29 @@
1
+ ## WebIDE 执行 SQL(execute-sql 轮次)
2
+
3
+ 适用场景:用户在手工测试阶段点击消息里 SQL 块的「执行」。
4
+
5
+ ### 本轮目标
6
+
7
+ 1. 根据你**此前开发轮次**在 AppendMessage 里写明的 **SQL 产物路径**,定位工作区内的文件(可用 Read / Glob 确认)。
8
+ 2. 从 deploy 文档、`application.yml` / `.env` 等推断 MySQL 连接信息(**禁止瞎猜密码**)。
9
+ 3. 调用 **MysqlExecute**,传入连接参数,以及产物文件的 **绝对路径**(`path`)。**不要**把 SQL 全文作为工具参数。
10
+ 4. 用 **AppendMessage** 汇总执行结果(成功/失败、影响行、必要时的校验说明)。
11
+
12
+ ### MysqlExecute 参数
13
+
14
+ - `host` / `port` / `user` / `password` / `database`
15
+ - `path`:SQL 产物在磁盘上的 **绝对路径**(须在当前工作区内)。CLI 自行读文件并执行多语句脚本。
16
+
17
+ ### 硬性约束
18
+
19
+ - **禁止**把 SQL 正文塞进工具参数;由 CLI 读 `path` 指向的文件。
20
+ - **禁止**使用对话里 SQL 预览正文代替文件内容。
21
+ - **禁止**改代码、Git 提交、启动 dev 服务。
22
+ - 密码不得写入 AppendMessage。
23
+ - 找不到产物文件或路径在工作区外 → AppendMessage 说明原因并以错误结束。
24
+
25
+ ### 连接信息来源
26
+
27
+ 1. Read `.apm/project/<项目ID>/` 下文件名或路径包含 `deploy` 的文档。
28
+ 2. 若无,Glob 工作区内 `application*.yml` / `.env*` 等配置文件。
29
+ 3. 仍无法确定 → 不要猜测,AppendMessage 说明缺少连接配置。
@@ -4,7 +4,7 @@
4
4
 
5
5
  会话与 WebIDE「终端」面板共用同一套 `pty-session-mcp serve`。工具名:`create` / `write` / `read` / `resize` / `kill` / `list`。
6
6
 
7
- 本地服务由用户在顶栏「启动项目」触发(独立 Agent),**开发/修码任务不要自行起或重启长驻进程**。
7
+ 本地服务由用户在顶栏「启动项目」触发,或在进入手工测试时由启动指令触发(独立 Agent)。**开发/修码任务不要自行起或重启长驻进程**。
8
8
 
9
9
  ## 工具
10
10
 
@@ -26,19 +26,20 @@
26
26
  3. 按文档中的 cwd / command / 服务划分创建会话。
27
27
  4. 文档不存在或未写明启动方式 → 结束任务,**禁止猜测命令**。
28
28
 
29
- ## 时机(顶栏启动 / 重启)
29
+ ## 时机(顶栏启动 / 重启 / 进入手工测试)
30
30
 
31
31
  固定顺序:
32
32
 
33
33
  1. **`list`**
34
- 2. 启动:无对应 `name` 的活会话 → 按部署文档 `create` + `write`;已有同名活会话则结束,不重起
35
- 3. 重启:先 `kill` 目标 `name`,再按部署文档 `create` + `write`(不要杀掉其它端)
36
- 4. `read` 等待就绪日志 / 端口即可
34
+ 2. 顶栏启动:无对应 `name` 的活会话 → 按部署文档 `create` + `write`;已有同名活会话则结束,不重起
35
+ 3. 顶栏重启:先 `kill` 目标 `name`,再按部署文档 `create` + `write`(不要杀掉其它端)
36
+ 4. 进入手工测试:文档写明的每一端,无对应 `name` 则启动;**已有同名活会话则重启**(只 kill 该 name,再 `create` + `write`)
37
+ 5. `read` 等待就绪日志 / 端口即可
37
38
 
38
- 用户在「终端」面板看日志,在「浏览器」面板看客户机外网地址。
39
+ 用户在「终端」面板看日志,在「浏览器」面板看远程主机外网地址。
39
40
 
40
41
  ## 规范
41
42
 
42
43
  1. 禁止用 Shell / 后台进程方式起 `dev` / `serve` 等长驻命令。
43
- 2. 观察日志请用户看 WebIDE「终端」面板;页面请用户看「浏览器」面板(客户机外网地址)。
44
+ 2. 观察日志请用户看 WebIDE「终端」面板;页面请用户看「浏览器」面板(远程主机外网地址)。
44
45
  3. 不再需要时可 kill;不要留下孤儿进程。