@vibegrid/mcp 0.3.2 → 0.4.0-beta.1

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 +157 -538
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -300,6 +300,23 @@ function createSchema() {
300
300
  CREATE INDEX IF NOT EXISTS idx_workflow_runs_task ON workflow_runs(trigger_task_id);
301
301
  CREATE INDEX IF NOT EXISTS idx_workflow_run_nodes_run ON workflow_run_nodes(run_id);
302
302
  CREATE INDEX IF NOT EXISTS idx_workflow_run_nodes_task ON workflow_run_nodes(task_id);
303
+
304
+ CREATE TABLE IF NOT EXISTS session_logs (
305
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
306
+ task_id TEXT NOT NULL,
307
+ session_id TEXT NOT NULL,
308
+ agent_type TEXT,
309
+ branch TEXT,
310
+ status TEXT NOT NULL DEFAULT 'running',
311
+ started_at TEXT NOT NULL,
312
+ completed_at TEXT,
313
+ exit_code INTEGER,
314
+ logs TEXT,
315
+ project_name TEXT
316
+ );
317
+
318
+ CREATE INDEX IF NOT EXISTS idx_session_logs_task ON session_logs(task_id);
319
+ CREATE INDEX IF NOT EXISTS idx_session_logs_session ON session_logs(session_id);
303
320
  `);
304
321
  migrateSchema(d);
305
322
  }
@@ -1525,59 +1542,37 @@ var AGENT_TYPES3 = [
1525
1542
  function registerSessionTools(server) {
1526
1543
  server.tool(
1527
1544
  "list_sessions",
1528
- "List all active terminal sessions. Requires the VibeGrid app to be running.",
1545
+ 'List terminal sessions. Filter by status: "active" (running terminals), "recent" (past sessions), or "archived".',
1529
1546
  {
1530
- project_name: V.name.optional().describe("Filter by project name")
1547
+ filter: z4.enum(["active", "recent", "archived"]).optional().describe("Session filter (default: active)"),
1548
+ project_name: V.name.optional().describe("Filter by project name"),
1549
+ project_path: V.absolutePath.optional().describe("Filter by project path (for recent sessions)")
1531
1550
  },
1532
1551
  async (args) => {
1552
+ const filter = args.filter ?? "active";
1533
1553
  try {
1534
- let sessions = await rpcCall("terminal:listActive");
1535
- if (args.project_name) {
1536
- sessions = sessions.filter((s) => s.projectName === args.project_name);
1554
+ if (filter === "active") {
1555
+ let sessions = await rpcCall("terminal:listActive");
1556
+ if (args.project_name) {
1557
+ sessions = sessions.filter((s) => s.projectName === args.project_name);
1558
+ }
1559
+ const summary = sessions.map((s) => ({
1560
+ id: s.id,
1561
+ agentType: s.agentType,
1562
+ projectName: s.projectName,
1563
+ status: s.status,
1564
+ displayName: s.displayName,
1565
+ branch: s.branch,
1566
+ pid: s.pid
1567
+ }));
1568
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
1569
+ } else if (filter === "recent") {
1570
+ const sessions = await rpcCall("sessions:getRecent", args.project_path);
1571
+ return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
1572
+ } else {
1573
+ const sessions = await rpcCall("session:listArchived");
1574
+ return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
1537
1575
  }
1538
- const summary = sessions.map((s) => ({
1539
- id: s.id,
1540
- agentType: s.agentType,
1541
- projectName: s.projectName,
1542
- status: s.status,
1543
- displayName: s.displayName,
1544
- branch: s.branch,
1545
- pid: s.pid
1546
- }));
1547
- return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
1548
- } catch (err) {
1549
- return {
1550
- content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1551
- isError: true
1552
- };
1553
- }
1554
- }
1555
- );
1556
- server.tool(
1557
- "list_recent_sessions",
1558
- "List recent session history for a project. Requires the VibeGrid app to be running.",
1559
- {
1560
- project_path: V.absolutePath.optional().describe("Filter by project path")
1561
- },
1562
- async (args) => {
1563
- try {
1564
- const sessions = await rpcCall("sessions:getRecent", args.project_path);
1565
- return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
1566
- } catch (err) {
1567
- return {
1568
- content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1569
- isError: true
1570
- };
1571
- }
1572
- }
1573
- );
1574
- server.tool(
1575
- "list_archived_sessions",
1576
- "List archived sessions. Requires the VibeGrid app to be running.",
1577
- async () => {
1578
- try {
1579
- const sessions = await rpcCall("session:listArchived");
1580
- return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
1581
1576
  } catch (err) {
1582
1577
  return {
1583
1578
  content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
@@ -1587,8 +1582,8 @@ function registerSessionTools(server) {
1587
1582
  }
1588
1583
  );
1589
1584
  server.tool(
1590
- "launch_agent",
1591
- "Launch an AI agent in a new terminal session. Requires the VibeGrid app to be running.",
1585
+ "launch_session",
1586
+ "Launch an AI agent session (interactive terminal or headless). Requires the VibeGrid app to be running.",
1592
1587
  {
1593
1588
  agent_type: z4.enum(AGENT_TYPES3).describe("Agent type to launch"),
1594
1589
  project_name: V.name.describe("Project name"),
@@ -1596,7 +1591,8 @@ function registerSessionTools(server) {
1596
1591
  prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
1597
1592
  branch: V.shortText.optional().describe("Git branch to checkout"),
1598
1593
  use_worktree: z4.boolean().optional().describe("Create a git worktree"),
1599
- display_name: V.shortText.optional().describe("Display name for the session")
1594
+ display_name: V.shortText.optional().describe("Display name for the session"),
1595
+ headless: z4.boolean().optional().describe("Launch as headless (no UI) session")
1600
1596
  },
1601
1597
  async (args) => {
1602
1598
  const payload = {
@@ -1608,8 +1604,10 @@ function registerSessionTools(server) {
1608
1604
  ...args.use_worktree && { useWorktree: args.use_worktree },
1609
1605
  ...args.display_name && { displayName: args.display_name }
1610
1606
  };
1607
+ const rpcMethod = args.headless ? "headless:create" : "terminal:create";
1608
+ const label = args.headless ? "headless" : "terminal";
1611
1609
  try {
1612
- const session = await rpcCall("terminal:create", payload);
1610
+ const session = await rpcCall(rpcMethod, payload);
1613
1611
  return {
1614
1612
  content: [
1615
1613
  {
@@ -1633,7 +1631,7 @@ function registerSessionTools(server) {
1633
1631
  content: [
1634
1632
  {
1635
1633
  type: "text",
1636
- text: `Error launching agent: ${err instanceof Error ? err.message : err}`
1634
+ text: `Error launching ${label} agent: ${err instanceof Error ? err.message : err}`
1637
1635
  }
1638
1636
  ],
1639
1637
  isError: true
@@ -1642,53 +1640,50 @@ function registerSessionTools(server) {
1642
1640
  }
1643
1641
  );
1644
1642
  server.tool(
1645
- "launch_headless",
1646
- "Launch a headless (no UI) agent session. Requires the VibeGrid app to be running.",
1643
+ "kill_session",
1644
+ "Kill a terminal or headless session. Requires the VibeGrid app to be running.",
1647
1645
  {
1648
- agent_type: z4.enum(AGENT_TYPES3).describe("Agent type to launch"),
1649
- project_name: V.name.describe("Project name"),
1650
- project_path: V.absolutePath.describe("Absolute path to project directory"),
1651
- prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
1652
- branch: V.shortText.optional().describe("Git branch to checkout"),
1653
- use_worktree: z4.boolean().optional().describe("Create a git worktree"),
1654
- display_name: V.shortText.optional().describe("Display name for the session")
1646
+ id: V.id.describe("Session ID to kill"),
1647
+ headless: z4.boolean().optional().describe("Kill a headless session instead of a terminal")
1655
1648
  },
1656
1649
  async (args) => {
1657
- const payload = {
1658
- agentType: args.agent_type,
1659
- projectName: args.project_name,
1660
- projectPath: args.project_path,
1661
- ...args.prompt && { initialPrompt: args.prompt },
1662
- ...args.branch && { branch: args.branch },
1663
- ...args.use_worktree && { useWorktree: args.use_worktree },
1664
- ...args.display_name && { displayName: args.display_name }
1665
- };
1650
+ const rpcMethod = args.headless ? "headless:kill" : "terminal:kill";
1651
+ const label = args.headless ? "headless session" : "session";
1666
1652
  try {
1667
- const session = await rpcCall("headless:create", payload);
1653
+ await rpcCall(rpcMethod, args.id);
1654
+ return { content: [{ type: "text", text: `Killed ${label}: ${args.id}` }] };
1655
+ } catch (err) {
1668
1656
  return {
1669
1657
  content: [
1670
1658
  {
1671
1659
  type: "text",
1672
- text: JSON.stringify(
1673
- {
1674
- id: session.id,
1675
- agentType: session.agentType,
1676
- projectName: session.projectName,
1677
- pid: session.pid,
1678
- status: session.status
1679
- },
1680
- null,
1681
- 2
1682
- )
1660
+ text: `Error killing ${label}: ${err instanceof Error ? err.message : err}`
1683
1661
  }
1684
- ]
1662
+ ],
1663
+ isError: true
1664
+ };
1665
+ }
1666
+ }
1667
+ );
1668
+ server.tool(
1669
+ "rename_session",
1670
+ "Rename a terminal session. Changes the display name shown in the UI.",
1671
+ {
1672
+ id: V.id.describe("Session ID"),
1673
+ display_name: V.shortText.describe("New display name")
1674
+ },
1675
+ async (args) => {
1676
+ try {
1677
+ await rpcCall("terminal:rename", { id: args.id, displayName: args.display_name });
1678
+ return {
1679
+ content: [{ type: "text", text: `Renamed session ${args.id} to "${args.display_name}"` }]
1685
1680
  };
1686
1681
  } catch (err) {
1687
1682
  return {
1688
1683
  content: [
1689
1684
  {
1690
1685
  type: "text",
1691
- text: `Error launching headless agent: ${err instanceof Error ? err.message : err}`
1686
+ text: `Error renaming session: ${err instanceof Error ? err.message : err}`
1692
1687
  }
1693
1688
  ],
1694
1689
  isError: true
@@ -1697,19 +1692,28 @@ function registerSessionTools(server) {
1697
1692
  }
1698
1693
  );
1699
1694
  server.tool(
1700
- "kill_session",
1701
- "Kill a terminal session. Requires the VibeGrid app to be running.",
1702
- { id: V.id.describe("Session ID to kill") },
1695
+ "reorder_sessions",
1696
+ "Reorder terminal sessions in the grid. Provide session IDs in the desired display order.",
1697
+ {
1698
+ session_ids: z4.array(V.id).min(1, "At least one session ID is required").describe("Session IDs in desired order")
1699
+ },
1703
1700
  async (args) => {
1704
1701
  try {
1705
- await rpcCall("terminal:kill", args.id);
1706
- return { content: [{ type: "text", text: `Killed session: ${args.id}` }] };
1702
+ await rpcCall("terminal:reorder", args.session_ids);
1703
+ return {
1704
+ content: [
1705
+ {
1706
+ type: "text",
1707
+ text: `Reordered ${args.session_ids.length} sessions`
1708
+ }
1709
+ ]
1710
+ };
1707
1711
  } catch (err) {
1708
1712
  return {
1709
1713
  content: [
1710
1714
  {
1711
1715
  type: "text",
1712
- text: `Error killing session: ${err instanceof Error ? err.message : err}`
1716
+ text: `Error reordering sessions: ${err instanceof Error ? err.message : err}`
1713
1717
  }
1714
1718
  ],
1715
1719
  isError: true
@@ -1718,19 +1722,27 @@ function registerSessionTools(server) {
1718
1722
  }
1719
1723
  );
1720
1724
  server.tool(
1721
- "kill_headless",
1722
- "Kill a headless agent session. Requires the VibeGrid app to be running.",
1723
- { id: V.id.describe("Headless session ID to kill") },
1725
+ "read_session_output",
1726
+ "Read terminal output from a running session. Output is stored in a rolling 1000-line buffer with ANSI codes stripped.",
1727
+ {
1728
+ id: V.id.describe("Session ID"),
1729
+ lines: z4.number().int().min(1).max(1e3).optional().describe("Number of lines to read from the end (default: all)")
1730
+ },
1724
1731
  async (args) => {
1725
1732
  try {
1726
- await rpcCall("headless:kill", args.id);
1727
- return { content: [{ type: "text", text: `Killed headless session: ${args.id}` }] };
1733
+ const output = await rpcCall("terminal:readOutput", {
1734
+ id: args.id,
1735
+ lines: args.lines
1736
+ });
1737
+ return {
1738
+ content: [{ type: "text", text: output.join("\n") }]
1739
+ };
1728
1740
  } catch (err) {
1729
1741
  return {
1730
1742
  content: [
1731
1743
  {
1732
1744
  type: "text",
1733
- text: `Error killing headless session: ${err instanceof Error ? err.message : err}`
1745
+ text: `Error reading session output: ${err instanceof Error ? err.message : err}`
1734
1746
  }
1735
1747
  ],
1736
1748
  isError: true
@@ -1747,7 +1759,9 @@ function registerSessionTools(server) {
1747
1759
  },
1748
1760
  async (args) => {
1749
1761
  try {
1750
- await rpcNotify("terminal:write", { id: args.id, data: args.data });
1762
+ const trimmed = args.data.replace(/[\r\n]+$/, "");
1763
+ const data = trimmed + "\r";
1764
+ await rpcNotify("terminal:write", { id: args.id, data });
1751
1765
  return { content: [{ type: "text", text: `Wrote to session: ${args.id}` }] };
1752
1766
  } catch (err) {
1753
1767
  return {
@@ -1955,460 +1969,66 @@ function registerWorkflowTools(server) {
1955
1969
  );
1956
1970
  server.tool(
1957
1971
  "list_workflow_runs",
1958
- "List execution history for a workflow",
1972
+ "List workflow execution history. Filter by workflow_id or task_id.",
1959
1973
  {
1960
- workflow_id: V.id.describe("Workflow ID"),
1974
+ workflow_id: V.id.optional().describe("Filter by workflow ID"),
1975
+ task_id: V.id.optional().describe("Filter by task ID (runs triggered by this task)"),
1961
1976
  limit: z5.number().int().min(1).max(100).optional().describe("Max results (default: 20)")
1962
1977
  },
1963
1978
  async (args) => {
1964
- const runs = listWorkflowRuns(args.workflow_id, args.limit ?? 20);
1965
- return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
1966
- }
1967
- );
1968
- server.tool(
1969
- "list_workflow_runs_by_task",
1970
- "List workflow executions triggered by a specific task",
1971
- {
1972
- task_id: V.id.describe("Task ID"),
1973
- limit: z5.number().int().min(1).max(100).optional().describe("Max results (default: 20)")
1974
- },
1975
- async (args) => {
1976
- const runs = listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
1977
- return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
1978
- }
1979
- );
1980
- server.tool(
1981
- "get_scheduler_log",
1982
- "Get scheduler execution log for a workflow. Requires the VibeGrid app to be running.",
1983
- {
1984
- workflow_id: V.id.optional().describe("Workflow ID (omit for all workflows)")
1985
- },
1986
- async (args) => {
1987
- try {
1988
- const log2 = await rpcCall("scheduler:getLog", args.workflow_id);
1989
- return { content: [{ type: "text", text: JSON.stringify(log2, null, 2) }] };
1990
- } catch (err) {
1991
- return {
1992
- content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1993
- isError: true
1994
- };
1995
- }
1996
- }
1997
- );
1998
- server.tool(
1999
- "get_next_scheduled_run",
2000
- "Get the next scheduled run time for a workflow. Requires the VibeGrid app to be running.",
2001
- {
2002
- workflow_id: V.id.describe("Workflow ID")
2003
- },
2004
- async (args) => {
2005
- try {
2006
- const nextRun = await rpcCall("scheduler:getNextRun", args.workflow_id);
1979
+ if (args.workflow_id && args.task_id) {
2007
1980
  return {
2008
- content: [
2009
- {
2010
- type: "text",
2011
- text: nextRun ? JSON.stringify({ nextRun }, null, 2) : "No scheduled run (workflow may be manual or disabled)"
2012
- }
2013
- ]
2014
- };
2015
- } catch (err) {
2016
- return {
2017
- content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1981
+ content: [{ type: "text", text: "Error: provide workflow_id or task_id, not both" }],
2018
1982
  isError: true
2019
1983
  };
2020
1984
  }
2021
- }
2022
- );
2023
- }
2024
-
2025
- // src/tools/git.ts
2026
- import { z as z6 } from "zod";
2027
-
2028
- // ../server/src/git-utils.ts
2029
- import { execFileSync } from "child_process";
2030
- import path5 from "path";
2031
- import fs4 from "fs";
2032
- import crypto3 from "crypto";
2033
- var EXEC_OPTS = {
2034
- encoding: "utf-8",
2035
- stdio: ["pipe", "pipe", "pipe"]
2036
- };
2037
- function getGitBranch(projectPath) {
2038
- try {
2039
- const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
2040
- cwd: projectPath,
2041
- ...EXEC_OPTS,
2042
- timeout: 3e3
2043
- }).trim();
2044
- return branch && branch !== "HEAD" ? branch : null;
2045
- } catch {
2046
- return null;
2047
- }
2048
- }
2049
- function listBranches(projectPath) {
2050
- try {
2051
- const output = execFileSync("git", ["branch", "--format=%(refname:short)"], {
2052
- cwd: projectPath,
2053
- ...EXEC_OPTS,
2054
- timeout: 5e3
2055
- }).trim();
2056
- return output ? output.split("\n").map((b) => b.trim()).filter(Boolean) : [];
2057
- } catch {
2058
- return [];
2059
- }
2060
- }
2061
- function listRemoteBranches(projectPath) {
2062
- try {
2063
- execFileSync("git", ["fetch", "--prune"], {
2064
- cwd: projectPath,
2065
- ...EXEC_OPTS,
2066
- timeout: 15e3
2067
- });
2068
- const output = execFileSync("git", ["branch", "-r", "--format=%(refname:short)"], {
2069
- cwd: projectPath,
2070
- ...EXEC_OPTS,
2071
- timeout: 5e3
2072
- }).trim();
2073
- return output ? output.split("\n").map((b) => b.trim().replace(/^origin\//, "")).filter((b) => b && b !== "HEAD") : [];
2074
- } catch {
2075
- return [];
2076
- }
2077
- }
2078
- function createWorktree(projectPath, branch) {
2079
- const projectName = path5.basename(projectPath);
2080
- const shortId = crypto3.randomUUID().slice(0, 8);
2081
- const baseDir = path5.join(path5.dirname(projectPath), ".vibegrid-worktrees", projectName);
2082
- const worktreeDir = path5.join(baseDir, `${branch}-${shortId}`);
2083
- fs4.mkdirSync(baseDir, { recursive: true });
2084
- const localBranches = listBranches(projectPath);
2085
- if (localBranches.includes(branch)) {
2086
- try {
2087
- execFileSync("git", ["worktree", "add", worktreeDir, branch], {
2088
- cwd: projectPath,
2089
- ...EXEC_OPTS,
2090
- timeout: 3e4
2091
- });
2092
- } catch {
2093
- const newBranch = `${branch}-worktree-${shortId}`;
2094
- execFileSync("git", ["worktree", "add", "-b", newBranch, worktreeDir, branch], {
2095
- cwd: projectPath,
2096
- ...EXEC_OPTS,
2097
- timeout: 3e4
2098
- });
2099
- return { worktreePath: worktreeDir, branch: newBranch };
2100
- }
2101
- } else {
2102
- execFileSync("git", ["worktree", "add", "-b", branch, worktreeDir], {
2103
- cwd: projectPath,
2104
- ...EXEC_OPTS,
2105
- timeout: 3e4
2106
- });
2107
- }
2108
- return { worktreePath: worktreeDir, branch };
2109
- }
2110
- function isWorktreeDirty(worktreePath) {
2111
- try {
2112
- const output = execFileSync("git", ["status", "--porcelain"], {
2113
- cwd: worktreePath,
2114
- ...EXEC_OPTS,
2115
- timeout: 5e3
2116
- }).trim();
2117
- return output.length > 0;
2118
- } catch {
2119
- return true;
2120
- }
2121
- }
2122
- function getGitDiffStat(cwd) {
2123
- try {
2124
- const output = execFileSync("git", ["diff", "HEAD", "--numstat"], {
2125
- cwd,
2126
- ...EXEC_OPTS,
2127
- timeout: 1e4
2128
- }).trim();
2129
- if (!output) return { filesChanged: 0, insertions: 0, deletions: 0 };
2130
- let insertions = 0;
2131
- let deletions = 0;
2132
- let filesChanged = 0;
2133
- for (const line of output.split("\n")) {
2134
- const parts = line.split(" ");
2135
- if (parts[0] === "-") {
2136
- filesChanged++;
2137
- continue;
2138
- }
2139
- insertions += parseInt(parts[0], 10) || 0;
2140
- deletions += parseInt(parts[1], 10) || 0;
2141
- filesChanged++;
2142
- }
2143
- return { filesChanged, insertions, deletions };
2144
- } catch {
2145
- return null;
2146
- }
2147
- }
2148
- function getGitDiffFull(cwd) {
2149
- try {
2150
- const stat = getGitDiffStat(cwd);
2151
- if (!stat) return null;
2152
- const MAX_DIFF_SIZE = 500 * 1024;
2153
- let rawDiff = execFileSync("git", ["diff", "HEAD", "-U3"], {
2154
- cwd,
2155
- ...EXEC_OPTS,
2156
- timeout: 15e3,
2157
- maxBuffer: MAX_DIFF_SIZE * 2
2158
- });
2159
- if (rawDiff.length > MAX_DIFF_SIZE) {
2160
- rawDiff = rawDiff.slice(0, MAX_DIFF_SIZE) + "\n\n... diff truncated (too large) ...\n";
2161
- }
2162
- const numstatOutput = execFileSync("git", ["diff", "HEAD", "--numstat"], {
2163
- cwd,
2164
- ...EXEC_OPTS,
2165
- timeout: 1e4
2166
- }).trim();
2167
- const fileStats = /* @__PURE__ */ new Map();
2168
- if (numstatOutput) {
2169
- for (const line of numstatOutput.split("\n")) {
2170
- const parts = line.split(" ");
2171
- if (parts.length >= 3) {
2172
- const ins = parts[0] === "-" ? 0 : parseInt(parts[0], 10) || 0;
2173
- const del = parts[1] === "-" ? 0 : parseInt(parts[1], 10) || 0;
2174
- fileStats.set(parts.slice(2).join(" "), { insertions: ins, deletions: del });
2175
- }
2176
- }
2177
- }
2178
- const fileDiffs = [];
2179
- const diffSections = rawDiff.split(/^diff --git /m).filter(Boolean);
2180
- for (const section of diffSections) {
2181
- const fullSection = "diff --git " + section;
2182
- const plusMatch = fullSection.match(/^\+\+\+ b\/(.+)$/m);
2183
- const minusMatch = fullSection.match(/^--- a\/(.+)$/m);
2184
- const filePath = plusMatch?.[1] || minusMatch?.[1]?.replace(/^\/dev\/null$/, "") || "unknown";
2185
- let status = "modified";
2186
- if (fullSection.includes("--- /dev/null")) {
2187
- status = "added";
2188
- } else if (fullSection.includes("+++ /dev/null")) {
2189
- status = "deleted";
2190
- } else if (fullSection.includes("rename from")) {
2191
- status = "renamed";
2192
- }
2193
- const stats = fileStats.get(filePath) || { insertions: 0, deletions: 0 };
2194
- fileDiffs.push({
2195
- filePath,
2196
- status,
2197
- insertions: stats.insertions,
2198
- deletions: stats.deletions,
2199
- diff: fullSection
2200
- });
2201
- }
2202
- return { stat, files: fileDiffs };
2203
- } catch {
2204
- return null;
2205
- }
2206
- }
2207
- function gitCommit(cwd, message, includeUnstaged) {
2208
- try {
2209
- if (includeUnstaged) {
2210
- execFileSync("git", ["add", "-A"], { cwd, ...EXEC_OPTS, timeout: 1e4 });
2211
- }
2212
- execFileSync("git", ["commit", "-m", message], {
2213
- cwd,
2214
- ...EXEC_OPTS,
2215
- timeout: 15e3
2216
- });
2217
- return { success: true };
2218
- } catch (err) {
2219
- const msg = err instanceof Error ? err.message : String(err);
2220
- return { success: false, error: msg };
2221
- }
2222
- }
2223
- function gitPush(cwd) {
2224
- try {
2225
- execFileSync("git", ["push"], { cwd, ...EXEC_OPTS, timeout: 3e4 });
2226
- return { success: true };
2227
- } catch (err) {
2228
- const msg = err instanceof Error ? err.message : String(err);
2229
- return { success: false, error: msg };
2230
- }
2231
- }
2232
- function listWorktrees(projectPath) {
2233
- try {
2234
- const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
2235
- cwd: projectPath,
2236
- ...EXEC_OPTS,
2237
- timeout: 5e3
2238
- }).trim();
2239
- if (!output) return [];
2240
- const worktrees = [];
2241
- const blocks = output.split("\n\n");
2242
- for (const block of blocks) {
2243
- const lines = block.split("\n");
2244
- const wtPath = lines.find((l) => l.startsWith("worktree "))?.replace("worktree ", "");
2245
- const branchLine = lines.find((l) => l.startsWith("branch "));
2246
- const branch = branchLine?.replace("branch refs/heads/", "") || "detached";
2247
- if (wtPath) {
2248
- worktrees.push({ path: wtPath, branch, isMain: worktrees.length === 0 });
2249
- }
2250
- }
2251
- return worktrees;
2252
- } catch {
2253
- return [];
2254
- }
2255
- }
2256
-
2257
- // src/tools/git.ts
2258
- function registerGitTools(server) {
2259
- server.tool(
2260
- "list_branches",
2261
- "List git branches for a project",
2262
- { project_path: V.absolutePath.describe("Absolute path to project directory") },
2263
- async (args) => {
2264
- try {
2265
- const local = listBranches(args.project_path);
2266
- const current = getGitBranch(args.project_path);
2267
- return {
2268
- content: [{ type: "text", text: JSON.stringify({ current, branches: local }, null, 2) }]
2269
- };
2270
- } catch (err) {
2271
- return {
2272
- content: [{ type: "text", text: `Error listing branches: ${err}` }],
2273
- isError: true
2274
- };
1985
+ if (args.task_id) {
1986
+ const runs = listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
1987
+ return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
2275
1988
  }
2276
- }
2277
- );
2278
- server.tool(
2279
- "list_remote_branches",
2280
- "List remote git branches for a project",
2281
- { project_path: V.absolutePath.describe("Absolute path to project directory") },
2282
- async (args) => {
2283
- try {
2284
- const remote = listRemoteBranches(args.project_path);
2285
- return { content: [{ type: "text", text: JSON.stringify(remote, null, 2) }] };
2286
- } catch (err) {
2287
- return {
2288
- content: [{ type: "text", text: `Error listing remote branches: ${err}` }],
2289
- isError: true
2290
- };
1989
+ if (args.workflow_id) {
1990
+ const runs = listWorkflowRuns(args.workflow_id, args.limit ?? 20);
1991
+ return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
2291
1992
  }
1993
+ return {
1994
+ content: [{ type: "text", text: "Error: provide either workflow_id or task_id" }],
1995
+ isError: true
1996
+ };
2292
1997
  }
2293
1998
  );
2294
1999
  server.tool(
2295
- "get_diff",
2296
- "Get git diff for a project (staged and unstaged changes)",
2297
- { project_path: V.absolutePath.describe("Absolute path to project directory") },
2000
+ "get_workflow_schedule",
2001
+ "Get scheduler info for a workflow: execution log or next scheduled run. Requires the VibeGrid app to be running.",
2002
+ {
2003
+ workflow_id: V.id.optional().describe("Workflow ID (required for next_run, optional for log)"),
2004
+ info: z5.enum(["log", "next_run"]).optional().describe("What to retrieve (default: log)")
2005
+ },
2298
2006
  async (args) => {
2007
+ const info = args.info ?? "log";
2299
2008
  try {
2300
- const result = getGitDiffFull(args.project_path);
2301
- if (!result) {
2009
+ if (info === "next_run") {
2010
+ if (!args.workflow_id) {
2011
+ return {
2012
+ content: [{ type: "text", text: "Error: workflow_id is required for next_run" }],
2013
+ isError: true
2014
+ };
2015
+ }
2016
+ const nextRun = await rpcCall("scheduler:getNextRun", args.workflow_id);
2302
2017
  return {
2303
- content: [{ type: "text", text: "No changes detected or not a git repository" }]
2018
+ content: [
2019
+ {
2020
+ type: "text",
2021
+ text: nextRun ? JSON.stringify({ nextRun }, null, 2) : "No scheduled run (workflow may be manual or disabled)"
2022
+ }
2023
+ ]
2304
2024
  };
2025
+ } else {
2026
+ const log2 = await rpcCall("scheduler:getLog", args.workflow_id);
2027
+ return { content: [{ type: "text", text: JSON.stringify(log2, null, 2) }] };
2305
2028
  }
2306
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2307
- } catch (err) {
2308
- return { content: [{ type: "text", text: `Error getting diff: ${err}` }], isError: true };
2309
- }
2310
- }
2311
- );
2312
- server.tool(
2313
- "get_diff_stat",
2314
- "Get a summary of git changes (files changed, insertions, deletions)",
2315
- { project_path: V.absolutePath.describe("Absolute path to project directory") },
2316
- async (args) => {
2317
- try {
2318
- const result = getGitDiffStat(args.project_path);
2319
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2320
- } catch (err) {
2321
- return {
2322
- content: [{ type: "text", text: `Error getting diff stat: ${err}` }],
2323
- isError: true
2324
- };
2325
- }
2326
- }
2327
- );
2328
- server.tool(
2329
- "git_commit",
2330
- "Create a git commit",
2331
- {
2332
- project_path: V.absolutePath.describe("Absolute path to project directory"),
2333
- message: V.description.describe("Commit message"),
2334
- include_unstaged: z6.boolean().optional().describe("Stage all changes before committing")
2335
- },
2336
- async (args) => {
2337
- try {
2338
- const result = gitCommit(args.project_path, args.message, args.include_unstaged ?? false);
2339
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2340
- } catch (err) {
2341
- return {
2342
- content: [{ type: "text", text: `Error committing: ${err}` }],
2343
- isError: true
2344
- };
2345
- }
2346
- }
2347
- );
2348
- server.tool(
2349
- "git_push",
2350
- "Push commits to the remote repository",
2351
- { project_path: V.absolutePath.describe("Absolute path to project directory") },
2352
- async (args) => {
2353
- try {
2354
- const result = gitPush(args.project_path);
2355
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2356
- } catch (err) {
2357
- return {
2358
- content: [{ type: "text", text: `Error pushing: ${err}` }],
2359
- isError: true
2360
- };
2361
- }
2362
- }
2363
- );
2364
- server.tool(
2365
- "list_worktrees",
2366
- "List git worktrees for a project",
2367
- { project_path: V.absolutePath.describe("Absolute path to project directory") },
2368
- async (args) => {
2369
- try {
2370
- const worktrees = listWorktrees(args.project_path);
2371
- return { content: [{ type: "text", text: JSON.stringify(worktrees, null, 2) }] };
2372
2029
  } catch (err) {
2373
2030
  return {
2374
- content: [{ type: "text", text: `Error listing worktrees: ${err}` }],
2375
- isError: true
2376
- };
2377
- }
2378
- }
2379
- );
2380
- server.tool(
2381
- "create_worktree",
2382
- "Create a git worktree for a branch",
2383
- {
2384
- project_path: V.absolutePath.describe("Absolute path to project directory"),
2385
- branch: V.shortText.describe("Branch name for the worktree")
2386
- },
2387
- async (args) => {
2388
- try {
2389
- const worktreePath = createWorktree(args.project_path, args.branch);
2390
- return {
2391
- content: [{ type: "text", text: JSON.stringify({ path: worktreePath }, null, 2) }]
2392
- };
2393
- } catch (err) {
2394
- return {
2395
- content: [{ type: "text", text: `Error creating worktree: ${err}` }],
2396
- isError: true
2397
- };
2398
- }
2399
- }
2400
- );
2401
- server.tool(
2402
- "worktree_dirty",
2403
- "Check if a worktree has uncommitted changes",
2404
- { worktree_path: V.absolutePath.describe("Absolute path to the worktree") },
2405
- async (args) => {
2406
- try {
2407
- const dirty = isWorktreeDirty(args.worktree_path);
2408
- return { content: [{ type: "text", text: JSON.stringify({ dirty }, null, 2) }] };
2409
- } catch (err) {
2410
- return {
2411
- content: [{ type: "text", text: `Error checking worktree: ${err}` }],
2031
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
2412
2032
  isError: true
2413
2033
  };
2414
2034
  }
@@ -2429,8 +2049,8 @@ function registerConfigTools(server) {
2429
2049
  }
2430
2050
 
2431
2051
  // src/tools/workspaces.ts
2432
- import crypto4 from "crypto";
2433
- import { z as z7 } from "zod";
2052
+ import crypto3 from "crypto";
2053
+ import { z as z6 } from "zod";
2434
2054
  function registerWorkspaceTools(server) {
2435
2055
  server.tool("list_workspaces", "List all workspaces", async () => {
2436
2056
  const workspaces = dbListWorkspaces();
@@ -2448,7 +2068,7 @@ function registerWorkspaceTools(server) {
2448
2068
  const existing = dbListWorkspaces();
2449
2069
  const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
2450
2070
  const workspace = {
2451
- id: crypto4.randomUUID(),
2071
+ id: crypto3.randomUUID(),
2452
2072
  name: args.name,
2453
2073
  order: maxOrder + 1,
2454
2074
  ...args.icon && { icon: args.icon },
@@ -2467,7 +2087,7 @@ function registerWorkspaceTools(server) {
2467
2087
  name: V.name.optional().describe("New name"),
2468
2088
  icon: V.shortText.optional().describe("Lucide icon name"),
2469
2089
  icon_color: V.hexColor.optional().describe("Hex color for icon"),
2470
- order: z7.number().int().min(0).optional().describe("Sort order")
2090
+ order: z6.number().int().min(0).optional().describe("Sort order")
2471
2091
  },
2472
2092
  async (args) => {
2473
2093
  const existing = dbListWorkspaces();
@@ -2517,7 +2137,6 @@ function registerWorkspaceTools(server) {
2517
2137
  // src/server.ts
2518
2138
  function createMcpServer(version) {
2519
2139
  const server = new McpServer({ name: "vibegrid", version }, { capabilities: { tools: {} } });
2520
- registerGitTools(server);
2521
2140
  registerConfigTools(server);
2522
2141
  registerProjectTools(server);
2523
2142
  registerTaskTools(server);
@@ -2536,7 +2155,7 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
2536
2155
  console.error = (...args) => _origError("[mcp:error]", ...args);
2537
2156
  async function main() {
2538
2157
  configManager.init();
2539
- const version = true ? "0.3.2" : createRequire(import.meta.url)("../package.json").version;
2158
+ const version = true ? "0.4.0-beta.1" : createRequire(import.meta.url)("../package.json").version;
2540
2159
  const server = createMcpServer(version);
2541
2160
  const transport = new StdioServerTransport();
2542
2161
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibegrid/mcp",
3
- "version": "0.3.2",
3
+ "version": "0.4.0-beta.1",
4
4
  "description": "VibeGrid MCP server — task management, git, and workflow tools for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -33,7 +33,7 @@
33
33
  "dependencies": {
34
34
  "@modelcontextprotocol/sdk": "^1.28.0",
35
35
  "libsql": "^0.5.22",
36
- "pino": "^9.6.0",
36
+ "pino": "^10.3.1",
37
37
  "ws": "^8.18.0",
38
38
  "zod": "^3.23.0"
39
39
  },