@vibegrid/mcp 0.1.3 → 0.2.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 +1005 -979
  2. package/package.json +2 -3
package/dist/index.js CHANGED
@@ -56,72 +56,6 @@ var DEFAULT_WORKSPACE = {
56
56
  iconColor: "#6b7280",
57
57
  order: 0
58
58
  };
59
- var IPC = {
60
- TERMINAL_CREATE: "terminal:create",
61
- TERMINAL_WRITE: "terminal:write",
62
- TERMINAL_RESIZE: "terminal:resize",
63
- TERMINAL_KILL: "terminal:kill",
64
- TERMINAL_DATA: "terminal:data",
65
- TERMINAL_EXIT: "terminal:exit",
66
- CONFIG_LOAD: "config:load",
67
- CONFIG_SAVE: "config:save",
68
- CONFIG_CHANGED: "config:changed",
69
- SESSIONS_GET_PREVIOUS: "sessions:getPrevious",
70
- SESSIONS_CLEAR: "sessions:clear",
71
- SESSIONS_GET_RECENT: "sessions:getRecent",
72
- DIALOG_OPEN_DIRECTORY: "dialog:openDirectory",
73
- IDE_DETECT: "ide:detect",
74
- IDE_OPEN: "ide:open",
75
- GIT_LIST_BRANCHES: "git:listBranches",
76
- GIT_LIST_REMOTE_BRANCHES: "git:listRemoteBranches",
77
- GIT_CREATE_WORKTREE: "git:createWorktree",
78
- GIT_REMOVE_WORKTREE: "git:removeWorktree",
79
- GIT_WORKTREE_DIRTY: "git:worktreeDirty",
80
- GIT_LIST_WORKTREES: "git:listWorktrees",
81
- WORKTREE_CONFIRM_CLEANUP: "worktree:confirmCleanup",
82
- GIT_DIFF_STAT: "git:diffStat",
83
- GIT_DIFF_FULL: "git:diffFull",
84
- GIT_COMMIT: "git:commit",
85
- GIT_PUSH: "git:push",
86
- DIALOG_OPEN_FILE: "dialog:openFile",
87
- SCHEDULER_EXECUTE: "scheduler:execute",
88
- SCHEDULER_MISSED: "scheduler:missed",
89
- SCHEDULER_GET_LOG: "scheduler:getLog",
90
- SCHEDULER_GET_NEXT_RUN: "scheduler:getNextRun",
91
- WORKFLOW_EXECUTION_COMPLETE: "workflow:executionComplete",
92
- WINDOW_MINIMIZE: "window:minimize",
93
- WINDOW_MAXIMIZE: "window:maximize",
94
- WINDOW_CLOSE: "window:close",
95
- WIDGET_STATUS_UPDATE: "widget:status-update",
96
- WIDGET_FOCUS_TERMINAL: "widget:focus-terminal",
97
- WIDGET_HIDE: "widget:hide",
98
- WIDGET_TOGGLE: "widget:toggle",
99
- WIDGET_RENDERER_STATUS: "widget:renderer-status",
100
- WIDGET_SET_ENABLED: "widget:set-enabled",
101
- WIDGET_PERMISSION_REQUEST: "widget:permission-request",
102
- WIDGET_PERMISSION_RESPONSE: "widget:permission-response",
103
- WIDGET_PERMISSION_CANCELLED: "widget:permission-cancelled",
104
- SHELL_CREATE: "shell:create",
105
- UPDATE_DOWNLOADED: "update:downloaded",
106
- UPDATE_INSTALL: "update:install",
107
- TASK_IMAGE_SAVE: "task:imageSave",
108
- TASK_IMAGE_DELETE: "task:imageDelete",
109
- TASK_IMAGE_GET_PATH: "task:imageGetPath",
110
- TASK_IMAGE_CLEANUP: "task:imageCleanup",
111
- DIALOG_OPEN_IMAGE: "dialog:openImage",
112
- SESSION_ARCHIVE: "session:archive",
113
- SESSION_UNARCHIVE: "session:unarchive",
114
- SESSION_LIST_ARCHIVED: "session:listArchived",
115
- HEADLESS_CREATE: "headless:create",
116
- HEADLESS_KILL: "headless:kill",
117
- HEADLESS_DATA: "headless:data",
118
- HEADLESS_EXIT: "headless:exit",
119
- SCRIPT_EXECUTE: "script:execute",
120
- WORKFLOW_RUN_SAVE: "workflowRun:save",
121
- WORKFLOW_RUN_LIST: "workflowRun:list",
122
- WORKFLOW_RUN_LIST_BY_TASK: "workflowRun:listByTask",
123
- AGENT_DETECT_INSTALLED: "agent:detectInstalled"
124
- };
125
59
 
126
60
  // ../server/src/database.ts
127
61
  var CONFIG_DIR = path.join(os.homedir(), ".vibegrid");
@@ -423,6 +357,15 @@ function loadDefaults(d) {
423
357
  },
424
358
  ...map.activeWorkspace !== void 0 && {
425
359
  activeWorkspace: map.activeWorkspace
360
+ },
361
+ ...map.mainViewMode !== void 0 && {
362
+ mainViewMode: map.mainViewMode
363
+ },
364
+ ...map.layoutMode !== void 0 && {
365
+ layoutMode: map.layoutMode
366
+ },
367
+ ...map.updateChannel !== void 0 && {
368
+ updateChannel: map.updateChannel
426
369
  }
427
370
  };
428
371
  }
@@ -801,6 +744,50 @@ function dbUpdateWorkflow(id, updates) {
801
744
  function dbDeleteWorkflow(id) {
802
745
  getDb().prepare("DELETE FROM workflows WHERE id = ?").run(id);
803
746
  }
747
+ function dbListWorkspaces() {
748
+ const rows = getDb().prepare('SELECT * FROM workspaces ORDER BY "order"').all();
749
+ return rows.map(rowToWorkspace);
750
+ }
751
+ function dbInsertWorkspace(workspace) {
752
+ getDb().prepare(`INSERT INTO workspaces (id, name, icon, icon_color, "order") VALUES (?, ?, ?, ?, ?)`).run(
753
+ workspace.id,
754
+ workspace.name,
755
+ workspace.icon ?? null,
756
+ workspace.iconColor ?? null,
757
+ workspace.order
758
+ );
759
+ }
760
+ function dbUpdateWorkspace(id, updates) {
761
+ const sets = [];
762
+ const params = [];
763
+ if (updates.name !== void 0) {
764
+ sets.push("name = ?");
765
+ params.push(updates.name);
766
+ }
767
+ if (updates.icon !== void 0) {
768
+ sets.push("icon = ?");
769
+ params.push(updates.icon);
770
+ }
771
+ if (updates.iconColor !== void 0) {
772
+ sets.push("icon_color = ?");
773
+ params.push(updates.iconColor);
774
+ }
775
+ if (updates.order !== void 0) {
776
+ sets.push('"order" = ?');
777
+ params.push(updates.order);
778
+ }
779
+ if (sets.length === 0) return;
780
+ params.push(id);
781
+ getDb().prepare(`UPDATE workspaces SET ${sets.join(", ")} WHERE id = ?`).run(...params);
782
+ }
783
+ function dbDeleteWorkspace(id) {
784
+ const d = getDb();
785
+ d.transaction(() => {
786
+ d.prepare("UPDATE projects SET workspace_id = 'personal' WHERE workspace_id = ?").run(id);
787
+ d.prepare("UPDATE workflows SET workspace_id = 'personal' WHERE workspace_id = ?").run(id);
788
+ d.prepare("DELETE FROM workspaces WHERE id = ?").run(id);
789
+ })();
790
+ }
804
791
  function rowToTask(r) {
805
792
  return {
806
793
  id: r.id,
@@ -854,6 +841,67 @@ function rowToWorkspace(r) {
854
841
  order: r.order
855
842
  };
856
843
  }
844
+ function listWorkflowRuns(workflowId, limit = 20) {
845
+ const d = getDb();
846
+ const rows = d.prepare("SELECT * FROM workflow_runs WHERE workflow_id = ? ORDER BY started_at DESC LIMIT ?").all(workflowId, limit);
847
+ return rows.map((r) => {
848
+ const nodeRows = d.prepare("SELECT * FROM workflow_run_nodes WHERE run_id = ?").all(r.id);
849
+ return {
850
+ workflowId: r.workflow_id,
851
+ startedAt: r.started_at,
852
+ ...r.completed_at != null && { completedAt: r.completed_at },
853
+ status: r.status,
854
+ ...r.trigger_task_id != null && { triggerTaskId: r.trigger_task_id },
855
+ nodeStates: nodeRows.map((n) => ({
856
+ nodeId: n.node_id,
857
+ status: n.status,
858
+ ...n.started_at != null && { startedAt: n.started_at },
859
+ ...n.completed_at != null && { completedAt: n.completed_at },
860
+ ...n.session_id != null && { sessionId: n.session_id },
861
+ ...n.error != null && { error: n.error },
862
+ ...n.logs != null && { logs: n.logs },
863
+ ...n.task_id != null && { taskId: n.task_id },
864
+ ...n.agent_session_id != null && { agentSessionId: n.agent_session_id }
865
+ }))
866
+ };
867
+ });
868
+ }
869
+ function listWorkflowRunsByTask(taskId, limit = 20) {
870
+ const d = getDb();
871
+ const rows = d.prepare(
872
+ `
873
+ SELECT DISTINCT wr.*, w.name as workflow_name
874
+ FROM workflow_runs wr
875
+ LEFT JOIN workflows w ON w.id = wr.workflow_id
876
+ WHERE wr.trigger_task_id = ?
877
+ OR wr.id IN (SELECT run_id FROM workflow_run_nodes WHERE task_id = ?)
878
+ ORDER BY wr.started_at DESC
879
+ LIMIT ?
880
+ `
881
+ ).all(taskId, taskId, limit);
882
+ return rows.map((r) => {
883
+ const nodeRows = d.prepare("SELECT * FROM workflow_run_nodes WHERE run_id = ?").all(r.id);
884
+ return {
885
+ workflowId: r.workflow_id,
886
+ startedAt: r.started_at,
887
+ ...r.completed_at != null && { completedAt: r.completed_at },
888
+ status: r.status,
889
+ ...r.trigger_task_id != null && { triggerTaskId: r.trigger_task_id },
890
+ ...r.workflow_name != null && { workflowName: r.workflow_name },
891
+ nodeStates: nodeRows.map((n) => ({
892
+ nodeId: n.node_id,
893
+ status: n.status,
894
+ ...n.started_at != null && { startedAt: n.started_at },
895
+ ...n.completed_at != null && { completedAt: n.completed_at },
896
+ ...n.session_id != null && { sessionId: n.session_id },
897
+ ...n.error != null && { error: n.error },
898
+ ...n.logs != null && { logs: n.logs },
899
+ ...n.task_id != null && { taskId: n.task_id },
900
+ ...n.agent_session_id != null && { agentSessionId: n.agent_session_id }
901
+ }))
902
+ };
903
+ });
904
+ }
857
905
 
858
906
  // ../server/src/config-manager.ts
859
907
  var DB_DIR = path2.join(os2.homedir(), ".vibegrid");
@@ -941,840 +989,99 @@ var ConfigManager = class {
941
989
  };
942
990
  var configManager = new ConfigManager();
943
991
 
944
- // ../server/src/pty-manager.ts
945
- import * as pty from "node-pty";
946
- import crypto2 from "crypto";
947
- import os3 from "os";
948
- import { EventEmitter } from "events";
992
+ // src/server.ts
993
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
949
994
 
950
- // ../server/src/git-utils.ts
951
- import { execFileSync } from "child_process";
952
- import path3 from "path";
953
- import fs3 from "fs";
995
+ // src/tools/tasks.ts
954
996
  import crypto from "crypto";
955
- var EXEC_OPTS = {
956
- encoding: "utf-8",
957
- stdio: ["pipe", "pipe", "pipe"]
997
+ import path3 from "path";
998
+ import { z as z2 } from "zod";
999
+
1000
+ // src/validation.ts
1001
+ import { z } from "zod";
1002
+ var safeName = z.string().min(1, "Name must not be empty").max(200, "Name must be 200 characters or less").refine((s) => !s.includes("..") && !s.includes("/") && !s.includes("\\"), {
1003
+ message: "Name must not contain path traversal characters (.. / \\)"
1004
+ });
1005
+ var safeId = z.string().min(1, "ID must not be empty").max(100, "ID must be 100 characters or less");
1006
+ var safeTitle = z.string().min(1, "Title must not be empty").max(500, "Title must be 500 characters or less");
1007
+ var safeDescription = z.string().max(5e3, "Description must be 5000 characters or less");
1008
+ var safeShortText = z.string().max(200, "Value must be 200 characters or less");
1009
+ var safePrompt = z.string().max(1e4, "Prompt must be 10000 characters or less");
1010
+ var safeAbsolutePath = z.string().min(1, "Path must not be empty").max(1e3, "Path must be 1000 characters or less").refine((s) => s.startsWith("/"), { message: "Path must be absolute (start with /)" });
1011
+ var safeHexColor = z.string().regex(/^#[0-9a-fA-F]{3,8}$/, "Must be a valid hex color (e.g. #6366f1)");
1012
+ var V = {
1013
+ name: safeName,
1014
+ id: safeId,
1015
+ title: safeTitle,
1016
+ description: safeDescription,
1017
+ shortText: safeShortText,
1018
+ prompt: safePrompt,
1019
+ absolutePath: safeAbsolutePath,
1020
+ hexColor: safeHexColor
958
1021
  };
959
- function getGitBranch(projectPath) {
960
- try {
961
- const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
962
- cwd: projectPath,
963
- ...EXEC_OPTS,
964
- timeout: 3e3
965
- }).trim();
966
- return branch && branch !== "HEAD" ? branch : null;
967
- } catch {
968
- return null;
969
- }
970
- }
971
- function listBranches(projectPath) {
972
- try {
973
- const output = execFileSync("git", ["branch", "--format=%(refname:short)"], {
974
- cwd: projectPath,
975
- ...EXEC_OPTS,
976
- timeout: 5e3
977
- }).trim();
978
- return output ? output.split("\n").map((b) => b.trim()).filter(Boolean) : [];
979
- } catch {
980
- return [];
981
- }
982
- }
983
- function checkoutBranch(projectPath, branch) {
984
- try {
985
- execFileSync("git", ["checkout", branch], {
986
- cwd: projectPath,
987
- ...EXEC_OPTS,
988
- timeout: 1e4
989
- });
990
- return true;
991
- } catch {
992
- return false;
993
- }
994
- }
995
- function createWorktree(projectPath, branch) {
996
- const projectName = path3.basename(projectPath);
997
- const shortId = crypto.randomUUID().slice(0, 8);
998
- const baseDir = path3.join(path3.dirname(projectPath), ".vibegrid-worktrees", projectName);
999
- const worktreeDir = path3.join(baseDir, `${branch}-${shortId}`);
1000
- fs3.mkdirSync(baseDir, { recursive: true });
1001
- const localBranches = listBranches(projectPath);
1002
- if (localBranches.includes(branch)) {
1003
- try {
1004
- execFileSync("git", ["worktree", "add", worktreeDir, branch], {
1005
- cwd: projectPath,
1006
- ...EXEC_OPTS,
1007
- timeout: 3e4
1008
- });
1009
- } catch {
1010
- const newBranch = `${branch}-worktree-${shortId}`;
1011
- execFileSync("git", ["worktree", "add", "-b", newBranch, worktreeDir, branch], {
1012
- cwd: projectPath,
1013
- ...EXEC_OPTS,
1014
- timeout: 3e4
1015
- });
1016
- return { worktreePath: worktreeDir, branch: newBranch };
1017
- }
1018
- } else {
1019
- execFileSync("git", ["worktree", "add", "-b", branch, worktreeDir], {
1020
- cwd: projectPath,
1021
- ...EXEC_OPTS,
1022
- timeout: 3e4
1023
- });
1024
- }
1025
- return { worktreePath: worktreeDir, branch };
1026
- }
1027
- function getGitDiffStat(cwd) {
1028
- try {
1029
- const output = execFileSync("git", ["diff", "HEAD", "--numstat"], {
1030
- cwd,
1031
- ...EXEC_OPTS,
1032
- timeout: 1e4
1033
- }).trim();
1034
- if (!output) return { filesChanged: 0, insertions: 0, deletions: 0 };
1035
- let insertions = 0;
1036
- let deletions = 0;
1037
- let filesChanged = 0;
1038
- for (const line of output.split("\n")) {
1039
- const parts = line.split(" ");
1040
- if (parts[0] === "-") {
1041
- filesChanged++;
1042
- continue;
1022
+
1023
+ // src/tools/tasks.ts
1024
+ var TASK_STATUSES = ["todo", "in_progress", "in_review", "done", "cancelled"];
1025
+ var AGENT_TYPES = [
1026
+ "claude",
1027
+ "copilot",
1028
+ "codex",
1029
+ "opencode",
1030
+ "gemini"
1031
+ ];
1032
+ function registerTaskTools(server) {
1033
+ server.tool(
1034
+ "list_tasks",
1035
+ "List tasks, optionally filtered by project, status, assigned agent, or workspace",
1036
+ {
1037
+ project_name: V.name.optional().describe("Filter by project name"),
1038
+ status: z2.enum(TASK_STATUSES).optional().describe("Filter by status"),
1039
+ assigned_agent: z2.enum(AGENT_TYPES).optional().describe("Filter by assigned agent type"),
1040
+ workspace_id: V.id.optional().describe("Filter by workspace ID (returns tasks from all projects in that workspace)")
1041
+ },
1042
+ async (args) => {
1043
+ let tasks = dbListTasks(args.project_name, args.status);
1044
+ if (args.workspace_id) {
1045
+ const projects = dbListProjects();
1046
+ const wsProjectNames = new Set(
1047
+ projects.filter((p) => (p.workspaceId ?? "personal") === args.workspace_id).map((p) => p.name)
1048
+ );
1049
+ tasks = tasks.filter((t) => wsProjectNames.has(t.projectName));
1043
1050
  }
1044
- insertions += parseInt(parts[0], 10) || 0;
1045
- deletions += parseInt(parts[1], 10) || 0;
1046
- filesChanged++;
1047
- }
1048
- return { filesChanged, insertions, deletions };
1049
- } catch {
1050
- return null;
1051
- }
1052
- }
1053
- function getGitDiffFull(cwd) {
1054
- try {
1055
- const stat = getGitDiffStat(cwd);
1056
- if (!stat) return null;
1057
- const MAX_DIFF_SIZE = 500 * 1024;
1058
- let rawDiff = execFileSync("git", ["diff", "HEAD", "-U3"], {
1059
- cwd,
1060
- ...EXEC_OPTS,
1061
- timeout: 15e3,
1062
- maxBuffer: MAX_DIFF_SIZE * 2
1063
- });
1064
- if (rawDiff.length > MAX_DIFF_SIZE) {
1065
- rawDiff = rawDiff.slice(0, MAX_DIFF_SIZE) + "\n\n... diff truncated (too large) ...\n";
1066
- }
1067
- const numstatOutput = execFileSync("git", ["diff", "HEAD", "--numstat"], {
1068
- cwd,
1069
- ...EXEC_OPTS,
1070
- timeout: 1e4
1071
- }).trim();
1072
- const fileStats = /* @__PURE__ */ new Map();
1073
- if (numstatOutput) {
1074
- for (const line of numstatOutput.split("\n")) {
1075
- const parts = line.split(" ");
1076
- if (parts.length >= 3) {
1077
- const ins = parts[0] === "-" ? 0 : parseInt(parts[0], 10) || 0;
1078
- const del = parts[1] === "-" ? 0 : parseInt(parts[1], 10) || 0;
1079
- fileStats.set(parts.slice(2).join(" "), { insertions: ins, deletions: del });
1080
- }
1051
+ if (args.assigned_agent) {
1052
+ tasks = tasks.filter((t) => t.assignedAgent === args.assigned_agent);
1081
1053
  }
1054
+ return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
1082
1055
  }
1083
- const fileDiffs = [];
1084
- const diffSections = rawDiff.split(/^diff --git /m).filter(Boolean);
1085
- for (const section of diffSections) {
1086
- const fullSection = "diff --git " + section;
1087
- const plusMatch = fullSection.match(/^\+\+\+ b\/(.+)$/m);
1088
- const minusMatch = fullSection.match(/^--- a\/(.+)$/m);
1089
- const filePath = plusMatch?.[1] || minusMatch?.[1]?.replace(/^\/dev\/null$/, "") || "unknown";
1090
- let status = "modified";
1091
- if (fullSection.includes("--- /dev/null")) {
1092
- status = "added";
1093
- } else if (fullSection.includes("+++ /dev/null")) {
1094
- status = "deleted";
1095
- } else if (fullSection.includes("rename from")) {
1096
- status = "renamed";
1056
+ );
1057
+ server.tool(
1058
+ "create_task",
1059
+ "Create a new task in a project",
1060
+ {
1061
+ project_name: V.name.describe("Project name (must match existing project)"),
1062
+ title: V.title.describe("Task title"),
1063
+ description: V.description.optional().describe("Task description (markdown)"),
1064
+ status: z2.enum(TASK_STATUSES).optional().describe("Task status (default: todo)"),
1065
+ branch: V.shortText.optional().describe("Git branch for this task"),
1066
+ use_worktree: z2.boolean().optional().describe("Create a git worktree for this task"),
1067
+ assigned_agent: z2.enum(AGENT_TYPES).optional().describe("Assign to an agent type")
1068
+ },
1069
+ async (args) => {
1070
+ const project = dbGetProject(args.project_name);
1071
+ if (!project) {
1072
+ return {
1073
+ content: [{ type: "text", text: `Error: project "${args.project_name}" not found` }],
1074
+ isError: true
1075
+ };
1097
1076
  }
1098
- const stats = fileStats.get(filePath) || { insertions: 0, deletions: 0 };
1099
- fileDiffs.push({
1100
- filePath,
1101
- status,
1102
- insertions: stats.insertions,
1103
- deletions: stats.deletions,
1104
- diff: fullSection
1105
- });
1106
- }
1107
- return { stat, files: fileDiffs };
1108
- } catch {
1109
- return null;
1110
- }
1111
- }
1112
-
1113
- // ../server/src/agent-launch.ts
1114
- import { execFileSync as execFileSync3 } from "child_process";
1115
-
1116
- // ../server/src/process-utils.ts
1117
- import { execFileSync as execFileSync2 } from "child_process";
1118
- function getUserShellEnv() {
1119
- if (process.platform === "win32") return { ...process.env };
1120
- try {
1121
- const shell = process.env.SHELL || "/bin/zsh";
1122
- const output = execFileSync2(shell, ["-ilc", "env"], {
1123
- encoding: "utf-8",
1124
- timeout: 5e3,
1125
- stdio: ["pipe", "pipe", "pipe"]
1126
- });
1127
- const env = {};
1128
- for (const line of output.split("\n")) {
1129
- const idx = line.indexOf("=");
1130
- if (idx > 0) {
1131
- env[line.substring(0, idx)] = line.substring(idx + 1);
1132
- }
1133
- }
1134
- return env;
1135
- } catch {
1136
- return { ...process.env };
1137
- }
1138
- }
1139
- var resolvedEnv = getUserShellEnv();
1140
- function getDefaultShell() {
1141
- if (process.platform === "win32") {
1142
- return process.env.COMSPEC || "powershell.exe";
1143
- }
1144
- return process.env.SHELL || "/bin/zsh";
1145
- }
1146
- function shellEscape(s) {
1147
- if (/^[a-zA-Z0-9_./:=@%+,-]+$/.test(s)) return s;
1148
- return "'" + s.replace(/'/g, "'\\''") + "'";
1149
- }
1150
- var SENSITIVE_ENV_PREFIXES = [
1151
- "AWS_SECRET",
1152
- "AWS_SESSION",
1153
- "GITHUB_TOKEN",
1154
- "GH_TOKEN",
1155
- "OPENAI_API",
1156
- "ANTHROPIC_API",
1157
- "GOOGLE_API",
1158
- "STRIPE_",
1159
- "DATABASE_URL",
1160
- "DB_PASSWORD",
1161
- "SECRET_",
1162
- "PRIVATE_KEY",
1163
- "NPM_TOKEN",
1164
- "NODE_AUTH_TOKEN"
1165
- ];
1166
- var STRIP_ENV_KEYS = ["CLAUDECODE"];
1167
- function getSafeEnv() {
1168
- const env = {};
1169
- for (const [key, val] of Object.entries(resolvedEnv)) {
1170
- if (val === void 0) continue;
1171
- if (SENSITIVE_ENV_PREFIXES.some((p) => key.toUpperCase().startsWith(p))) continue;
1172
- if (STRIP_ENV_KEYS.includes(key)) continue;
1173
- env[key] = val;
1174
- }
1175
- return env;
1176
- }
1177
-
1178
- // ../server/src/agent-launch.ts
1179
- function commandExists(cmd, env) {
1180
- try {
1181
- const bin = process.platform === "win32" ? "where" : "which";
1182
- execFileSync3(bin, [cmd], { stdio: "pipe", timeout: 3e3, env });
1183
- return true;
1184
- } catch {
1185
- return false;
1186
- }
1187
- }
1188
- function resolveAgentCommand(config, env) {
1189
- if (commandExists(config.command, env)) {
1190
- return { command: config.command, args: config.args };
1191
- }
1192
- if (config.fallbackCommand && commandExists(config.fallbackCommand, env)) {
1193
- return { command: config.fallbackCommand, args: config.fallbackArgs ?? [] };
1194
- }
1195
- return { command: config.command, args: config.args };
1196
- }
1197
- function buildAgentLaunchLine(payload, agentCommands, env) {
1198
- const cmdConfig = agentCommands[payload.agentType] || DEFAULT_AGENT_COMMANDS[payload.agentType];
1199
- const cmd = resolveAgentCommand(cmdConfig, env);
1200
- const effectiveArgs = payload.args !== void 0 ? payload.args : cmd.args;
1201
- let launchLine = [cmd.command, ...effectiveArgs.map((a) => shellEscape(a))].join(" ");
1202
- if (payload.resumeSessionId) {
1203
- switch (payload.agentType) {
1204
- case "claude":
1205
- launchLine += ` --resume ${payload.resumeSessionId}`;
1206
- break;
1207
- case "copilot":
1208
- launchLine += ` --resume ${payload.resumeSessionId}`;
1209
- break;
1210
- case "codex":
1211
- launchLine = `${cmd.command} resume ${payload.resumeSessionId}`;
1212
- break;
1213
- case "opencode":
1214
- launchLine += ` --session ${payload.resumeSessionId}`;
1215
- break;
1216
- case "gemini":
1217
- launchLine += ` --resume latest`;
1218
- break;
1219
- }
1220
- }
1221
- if (payload.initialPrompt) {
1222
- const escaped = shellEscape(payload.initialPrompt);
1223
- switch (payload.agentType) {
1224
- case "copilot":
1225
- launchLine += ` -i ${escaped}`;
1226
- break;
1227
- case "gemini":
1228
- launchLine += ` -i ${escaped}`;
1229
- break;
1230
- case "opencode":
1231
- launchLine += ` --prompt ${escaped}`;
1232
- break;
1233
- default:
1234
- launchLine += ` ${escaped}`;
1235
- break;
1236
- }
1237
- }
1238
- return launchLine;
1239
- }
1240
-
1241
- // ../server/src/pty-manager.ts
1242
- var PtyManager = class extends EventEmitter {
1243
- ptys = /* @__PURE__ */ new Map();
1244
- sessions = /* @__PURE__ */ new Map();
1245
- agentCommands = { ...DEFAULT_AGENT_COMMANDS };
1246
- remoteHosts = [];
1247
- dataBuffers = /* @__PURE__ */ new Map();
1248
- flushTimers = /* @__PURE__ */ new Map();
1249
- setRemoteHosts(hosts) {
1250
- this.remoteHosts = hosts;
1251
- }
1252
- setAgentCommands(overrides) {
1253
- this.agentCommands = { ...DEFAULT_AGENT_COMMANDS };
1254
- if (overrides) {
1255
- for (const [key, val] of Object.entries(overrides)) {
1256
- if (val) {
1257
- this.agentCommands[key] = val;
1258
- }
1259
- }
1260
- }
1261
- }
1262
- buildAgentLaunchLine(payload) {
1263
- return buildAgentLaunchLine(payload, this.agentCommands, getSafeEnv());
1264
- }
1265
- createPty(payload) {
1266
- const id = crypto2.randomUUID();
1267
- const shell = getDefaultShell();
1268
- const remoteHost = payload.remoteHostId ? this.remoteHosts.find((h) => h.id === payload.remoteHostId) : void 0;
1269
- const session = remoteHost ? this.createRemotePty(id, shell, payload, remoteHost) : this.createLocalPty(id, shell, payload);
1270
- this.emit("session-created", session, payload);
1271
- return session;
1272
- }
1273
- createLocalPty(id, shell, payload) {
1274
- let effectivePath = payload.projectPath;
1275
- let worktreePath;
1276
- let effectiveBranch;
1277
- if (payload.existingWorktreePath) {
1278
- effectivePath = payload.existingWorktreePath;
1279
- worktreePath = payload.existingWorktreePath;
1280
- effectiveBranch = payload.branch;
1281
- } else if (payload.useWorktree && payload.branch) {
1282
- const result = createWorktree(payload.projectPath, payload.branch);
1283
- effectivePath = result.worktreePath;
1284
- worktreePath = result.worktreePath;
1285
- effectiveBranch = result.branch;
1286
- } else if (payload.branch) {
1287
- const currentBranch = getGitBranch(payload.projectPath);
1288
- if (currentBranch !== payload.branch) {
1289
- checkoutBranch(payload.projectPath, payload.branch);
1290
- }
1291
- effectiveBranch = payload.branch;
1292
- }
1293
- const ptyProcess = pty.spawn(shell, ["-l"], {
1294
- name: "xterm-256color",
1295
- cols: 80,
1296
- rows: 24,
1297
- cwd: effectivePath,
1298
- env: getSafeEnv()
1299
- });
1300
- const launchLine = this.buildAgentLaunchLine(payload);
1301
- setTimeout(() => ptyProcess.write(launchLine + "\r"), 300);
1302
- this.setupPtyEvents(id, ptyProcess);
1303
- this.ptys.set(id, ptyProcess);
1304
- const branch = effectiveBranch || getGitBranch(effectivePath);
1305
- const session = {
1306
- id,
1307
- agentType: payload.agentType,
1308
- projectName: payload.projectName,
1309
- projectPath: payload.projectPath,
1310
- status: "running",
1311
- createdAt: Date.now(),
1312
- pid: ptyProcess.pid,
1313
- ...payload.displayName ? { displayName: payload.displayName } : {},
1314
- ...branch ? { branch } : {},
1315
- ...worktreePath ? { worktreePath, isWorktree: true } : {}
1316
- };
1317
- this.sessions.set(id, session);
1318
- return session;
1319
- }
1320
- createRemotePty(id, shell, payload, host) {
1321
- const ptyProcess = pty.spawn(shell, ["-l"], {
1322
- name: "xterm-256color",
1323
- cols: 80,
1324
- rows: 24,
1325
- cwd: os3.homedir(),
1326
- env: getSafeEnv()
1327
- });
1328
- const sshParts = ["ssh", "-t"];
1329
- if (host.port !== 22) sshParts.push("-p", String(host.port));
1330
- if (host.sshKeyPath) sshParts.push("-i", host.sshKeyPath);
1331
- if (host.sshOptions) {
1332
- const opts = host.sshOptions.split(/\s+/).filter(Boolean);
1333
- sshParts.push(...opts);
1334
- }
1335
- sshParts.push(`${host.user}@${host.hostname}`);
1336
- const agentLine = this.buildAgentLaunchLine(payload);
1337
- const remoteCmd = `cd ${shellEscape(payload.projectPath)} && ${agentLine}`;
1338
- setTimeout(() => {
1339
- if (this.ptys.has(id)) ptyProcess.write(sshParts.join(" ") + "\r");
1340
- }, 300);
1341
- let connected = false;
1342
- const fallbackTimer = setTimeout(() => {
1343
- if (!connected) {
1344
- connected = true;
1345
- if (this.ptys.has(id)) ptyProcess.write(remoteCmd + "\r");
1346
- }
1347
- }, 5e3);
1348
- const promptListener = ptyProcess.onData((data) => {
1349
- if (!connected && /[$#>]\s*$/.test(data)) {
1350
- connected = true;
1351
- clearTimeout(fallbackTimer);
1352
- setTimeout(() => {
1353
- if (this.ptys.has(id)) ptyProcess.write(remoteCmd + "\r");
1354
- }, 100);
1355
- }
1356
- });
1357
- this.setupPtyEvents(id, ptyProcess);
1358
- this.ptys.set(id, ptyProcess);
1359
- const cleanup = () => {
1360
- promptListener.dispose();
1361
- };
1362
- const checkConnected = setInterval(() => {
1363
- if (connected) {
1364
- cleanup();
1365
- clearInterval(checkConnected);
1366
- }
1367
- }, 200);
1368
- setTimeout(() => {
1369
- cleanup();
1370
- clearInterval(checkConnected);
1371
- }, 6e3);
1372
- const session = {
1373
- id,
1374
- agentType: payload.agentType,
1375
- projectName: payload.projectName,
1376
- projectPath: payload.projectPath,
1377
- status: "running",
1378
- createdAt: Date.now(),
1379
- pid: ptyProcess.pid,
1380
- remoteHostId: host.id,
1381
- remoteHostLabel: host.label,
1382
- ...payload.displayName ? { displayName: payload.displayName } : {}
1383
- };
1384
- this.sessions.set(id, session);
1385
- return session;
1386
- }
1387
- createShellPty(cwd) {
1388
- const id = crypto2.randomUUID();
1389
- const shell = getDefaultShell();
1390
- const ptyProcess = pty.spawn(shell, ["-l"], {
1391
- name: "xterm-256color",
1392
- cols: 80,
1393
- rows: 24,
1394
- cwd: cwd || os3.homedir(),
1395
- env: getSafeEnv()
1396
- });
1397
- this.setupPtyEvents(id, ptyProcess);
1398
- this.ptys.set(id, ptyProcess);
1399
- return { id, pid: ptyProcess.pid };
1400
- }
1401
- bufferData(id, data) {
1402
- const existing = this.dataBuffers.get(id);
1403
- this.dataBuffers.set(id, existing ? existing + data : data);
1404
- if (!this.flushTimers.has(id)) {
1405
- this.flushTimers.set(
1406
- id,
1407
- setTimeout(() => this.flushBuffer(id), 50)
1408
- );
1409
- }
1410
- }
1411
- flushBuffer(id) {
1412
- const data = this.dataBuffers.get(id);
1413
- this.dataBuffers.delete(id);
1414
- this.flushTimers.delete(id);
1415
- if (data) {
1416
- this.emit("client-message", IPC.TERMINAL_DATA, { id, data });
1417
- }
1418
- }
1419
- clearBuffer(id) {
1420
- const timer = this.flushTimers.get(id);
1421
- if (timer) clearTimeout(timer);
1422
- this.flushTimers.delete(id);
1423
- this.dataBuffers.delete(id);
1424
- }
1425
- setupPtyEvents(id, ptyProcess) {
1426
- ptyProcess.onData((data) => {
1427
- this.bufferData(id, data);
1428
- });
1429
- ptyProcess.onExit(({ exitCode }) => {
1430
- const pendingTimer = this.flushTimers.get(id);
1431
- if (pendingTimer) {
1432
- clearTimeout(pendingTimer);
1433
- this.flushBuffer(id);
1434
- }
1435
- this.clearBuffer(id);
1436
- this.ptys.delete(id);
1437
- const session = this.sessions.get(id);
1438
- if (session) {
1439
- this.emit("session-exit", session);
1440
- session.status = "idle";
1441
- if (session.worktreePath) {
1442
- this.emit("client-message", IPC.WORKTREE_CONFIRM_CLEANUP, {
1443
- id: session.id,
1444
- projectPath: session.projectPath,
1445
- worktreePath: session.worktreePath
1446
- });
1447
- }
1448
- }
1449
- this.emit("client-message", IPC.TERMINAL_EXIT, { id, exitCode });
1450
- });
1451
- }
1452
- writeToPty(id, data) {
1453
- this.ptys.get(id)?.write(data);
1454
- }
1455
- resizePty(id, cols, rows) {
1456
- this.ptys.get(id)?.resize(cols, rows);
1457
- }
1458
- killPty(id) {
1459
- const p = this.ptys.get(id);
1460
- const pendingTimer = this.flushTimers.get(id);
1461
- if (pendingTimer) {
1462
- clearTimeout(pendingTimer);
1463
- this.flushBuffer(id);
1464
- }
1465
- this.clearBuffer(id);
1466
- const session = this.sessions.get(id);
1467
- this.sessions.delete(id);
1468
- this.ptys.delete(id);
1469
- if (session) {
1470
- this.emit("session-exit", session);
1471
- if (session.worktreePath) {
1472
- this.emit("client-message", IPC.WORKTREE_CONFIRM_CLEANUP, {
1473
- id: session.id,
1474
- projectPath: session.projectPath,
1475
- worktreePath: session.worktreePath
1476
- });
1477
- }
1478
- }
1479
- if (p) {
1480
- setImmediate(() => {
1481
- try {
1482
- p.kill();
1483
- } catch (err) {
1484
- logger_default.warn(`[pty] kill failed for ${id} (already dead?):`, err);
1485
- }
1486
- });
1487
- } else {
1488
- this.emit("client-message", IPC.TERMINAL_EXIT, { id, exitCode: 0 });
1489
- }
1490
- }
1491
- killAll() {
1492
- for (const timer of this.flushTimers.values()) {
1493
- clearTimeout(timer);
1494
- }
1495
- this.dataBuffers.clear();
1496
- this.flushTimers.clear();
1497
- for (const [id, p] of this.ptys) {
1498
- p.kill();
1499
- this.ptys.delete(id);
1500
- }
1501
- this.sessions.clear();
1502
- }
1503
- getActiveSessions() {
1504
- return Array.from(this.sessions.values());
1505
- }
1506
- updateSessionStatus(id, status) {
1507
- const session = this.sessions.get(id);
1508
- if (session) {
1509
- session.status = status;
1510
- this.emit("client-message", IPC.TERMINAL_DATA, { id, data: "" });
1511
- }
1512
- }
1513
- /**
1514
- * Finds the most-recently-created terminal matching cwd that:
1515
- * - is NOT already linked to a Claude session (no hookSessionId)
1516
- * - is NOT in the excludeIds set (already claimed by another session_id)
1517
- */
1518
- findUnlinkedSessionByCwd(cwd, excludeIds) {
1519
- const normalizedCwd = cwd.replace(/\/+$/, "");
1520
- let best;
1521
- let bestTime = 0;
1522
- for (const session of this.sessions.values()) {
1523
- if (session.hookSessionId) continue;
1524
- if (excludeIds.has(session.id)) continue;
1525
- const sessionPath = (session.worktreePath || session.projectPath).replace(/\/+$/, "");
1526
- if (sessionPath === normalizedCwd && session.createdAt > bestTime) {
1527
- best = session;
1528
- bestTime = session.createdAt;
1529
- }
1530
- }
1531
- return best;
1532
- }
1533
- };
1534
- var ptyManager = new PtyManager();
1535
-
1536
- // ../server/src/scheduler.ts
1537
- import cron from "node-cron";
1538
- import fs4 from "fs";
1539
- import path4 from "path";
1540
- import os4 from "os";
1541
- import { EventEmitter as EventEmitter2 } from "events";
1542
- var LOCK_DIR = path4.join(os4.homedir(), ".vibegrid");
1543
- function acquireExecutionLock(workflowId) {
1544
- const minuteKey = Math.floor(Date.now() / 6e4);
1545
- const lockFile = path4.join(LOCK_DIR, `scheduler-${workflowId}-${minuteKey}.lock`);
1546
- try {
1547
- fs4.writeFileSync(lockFile, String(process.pid), { flag: "wx" });
1548
- cleanStaleLocks(workflowId, minuteKey);
1549
- return true;
1550
- } catch {
1551
- return false;
1552
- }
1553
- }
1554
- function cleanStaleLocks(workflowId, currentKey) {
1555
- try {
1556
- const prefix = `scheduler-${workflowId}-`;
1557
- for (const f of fs4.readdirSync(LOCK_DIR)) {
1558
- if (f.startsWith(prefix) && f.endsWith(".lock")) {
1559
- const key = parseInt(f.slice(prefix.length, -5), 10);
1560
- if (!isNaN(key) && key < currentKey) {
1561
- fs4.unlinkSync(path4.join(LOCK_DIR, f));
1562
- }
1563
- }
1564
- }
1565
- } catch {
1566
- }
1567
- }
1568
- function getTriggerConfig(wf) {
1569
- const triggerNode = wf.nodes.find((n) => n.type === "trigger");
1570
- if (!triggerNode) return null;
1571
- return triggerNode.config;
1572
- }
1573
- var Scheduler = class extends EventEmitter2 {
1574
- cronJobs = /* @__PURE__ */ new Map();
1575
- timeouts = /* @__PURE__ */ new Map();
1576
- syncSchedules(workflows) {
1577
- logger_default.info(
1578
- `[scheduler] syncing ${workflows.length} workflows (active crons: ${this.cronJobs.size}, timeouts: ${this.timeouts.size})`
1579
- );
1580
- for (const [id] of this.cronJobs) {
1581
- const wf = workflows.find((w) => w.id === id);
1582
- const trigger = wf ? getTriggerConfig(wf) : null;
1583
- if (!wf || !wf.enabled || trigger?.triggerType !== "recurring") {
1584
- this.cronJobs.get(id)?.stop();
1585
- this.cronJobs.delete(id);
1586
- }
1587
- }
1588
- for (const [id] of this.timeouts) {
1589
- const wf = workflows.find((w) => w.id === id);
1590
- const trigger = wf ? getTriggerConfig(wf) : null;
1591
- if (!wf || !wf.enabled || trigger?.triggerType !== "once") {
1592
- clearTimeout(this.timeouts.get(id));
1593
- this.timeouts.delete(id);
1594
- }
1595
- }
1596
- for (const wf of workflows) {
1597
- if (!wf.enabled) {
1598
- logger_default.info(`[scheduler] skipping disabled workflow "${wf.name}"`);
1599
- continue;
1600
- }
1601
- const trigger = getTriggerConfig(wf);
1602
- if (!trigger) {
1603
- logger_default.info(`[scheduler] no trigger node for workflow "${wf.name}"`);
1604
- continue;
1605
- }
1606
- logger_default.info(`[scheduler] workflow "${wf.name}" trigger=${trigger.triggerType}`);
1607
- if (trigger.triggerType === "recurring" && !this.cronJobs.has(wf.id)) {
1608
- logger_default.info(
1609
- `[scheduler] registering recurring workflow "${wf.name}" cron="${trigger.cron}" enabled=${wf.enabled}`
1610
- );
1611
- if (!cron.validate(trigger.cron)) {
1612
- logger_default.error(
1613
- `[scheduler] invalid cron expression for workflow "${wf.name}": ${trigger.cron}`
1614
- );
1615
- continue;
1616
- }
1617
- try {
1618
- const task = cron.schedule(trigger.cron, () => this.executeWorkflow(wf.id), {
1619
- timezone: trigger.timezone || Intl.DateTimeFormat().resolvedOptions().timeZone
1620
- });
1621
- this.cronJobs.set(wf.id, task);
1622
- } catch (err) {
1623
- logger_default.error(`[scheduler] failed to schedule workflow "${wf.name}":`, err);
1624
- }
1625
- }
1626
- if (trigger.triggerType === "once" && !this.timeouts.has(wf.id)) {
1627
- const runAt = new Date(trigger.runAt).getTime();
1628
- if (isNaN(runAt)) {
1629
- logger_default.error(`[scheduler] invalid runAt date for workflow "${wf.name}": ${trigger.runAt}`);
1630
- continue;
1631
- }
1632
- const delay = runAt - Date.now();
1633
- if (delay > 0) {
1634
- const MAX_DELAY = 24 * 60 * 60 * 1e3;
1635
- const safeDelay = Math.min(delay, MAX_DELAY);
1636
- const timer = setTimeout(() => {
1637
- if (safeDelay < delay) {
1638
- this.timeouts.delete(wf.id);
1639
- this.syncSchedules(configManager.loadConfig().workflows ?? []);
1640
- } else {
1641
- this.executeWorkflow(wf.id);
1642
- }
1643
- }, safeDelay);
1644
- this.timeouts.set(wf.id, timer);
1645
- }
1646
- }
1647
- }
1648
- }
1649
- executeWorkflow(workflowId) {
1650
- if (!acquireExecutionLock(workflowId)) {
1651
- logger_default.info(`[scheduler] skipping workflow ${workflowId} \u2014 already executed by another instance`);
1652
- this.timeouts.delete(workflowId);
1653
- return;
1654
- }
1655
- logger_default.info(`[scheduler] executing workflow ${workflowId}`);
1656
- this.emit("client-message", IPC.SCHEDULER_EXECUTE, { workflowId });
1657
- this.timeouts.delete(workflowId);
1658
- }
1659
- checkMissedSchedules(workflows) {
1660
- const missed = [];
1661
- for (const wf of workflows) {
1662
- if (!wf.enabled) continue;
1663
- const trigger = getTriggerConfig(wf);
1664
- if (trigger?.triggerType === "once") {
1665
- const runAt = new Date(trigger.runAt).getTime();
1666
- if (runAt < Date.now() && !wf.lastRunAt) {
1667
- missed.push({ workflow: wf, scheduledFor: trigger.runAt });
1668
- }
1669
- }
1670
- }
1671
- return missed;
1672
- }
1673
- getNextRun(workflowId, workflows) {
1674
- const wf = workflows.find((w) => w.id === workflowId);
1675
- if (!wf || !wf.enabled) return null;
1676
- const trigger = getTriggerConfig(wf);
1677
- if (!trigger) return null;
1678
- if (trigger.triggerType === "once") {
1679
- const runAt = new Date(trigger.runAt).getTime();
1680
- return runAt > Date.now() ? trigger.runAt : null;
1681
- }
1682
- if (trigger.triggerType === "recurring") {
1683
- return trigger.cron;
1684
- }
1685
- return null;
1686
- }
1687
- stopAll() {
1688
- for (const [, job] of this.cronJobs) job.stop();
1689
- for (const [, timer] of this.timeouts) clearTimeout(timer);
1690
- this.cronJobs.clear();
1691
- this.timeouts.clear();
1692
- }
1693
- };
1694
- var scheduler = new Scheduler();
1695
-
1696
- // src/server.ts
1697
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1698
-
1699
- // src/tools/tasks.ts
1700
- import crypto3 from "crypto";
1701
- import path5 from "path";
1702
- import { z as z2 } from "zod";
1703
-
1704
- // src/validation.ts
1705
- import { z } from "zod";
1706
- var safeName = z.string().min(1, "Name must not be empty").max(200, "Name must be 200 characters or less").refine((s) => !s.includes("..") && !s.includes("/") && !s.includes("\\"), {
1707
- message: "Name must not contain path traversal characters (.. / \\)"
1708
- });
1709
- var safeId = z.string().min(1, "ID must not be empty").max(100, "ID must be 100 characters or less");
1710
- var safeTitle = z.string().min(1, "Title must not be empty").max(500, "Title must be 500 characters or less");
1711
- var safeDescription = z.string().max(5e3, "Description must be 5000 characters or less");
1712
- var safeShortText = z.string().max(200, "Value must be 200 characters or less");
1713
- var safePrompt = z.string().max(1e4, "Prompt must be 10000 characters or less");
1714
- var safeAbsolutePath = z.string().min(1, "Path must not be empty").max(1e3, "Path must be 1000 characters or less").refine((s) => s.startsWith("/"), { message: "Path must be absolute (start with /)" });
1715
- var safeHexColor = z.string().regex(/^#[0-9a-fA-F]{3,8}$/, "Must be a valid hex color (e.g. #6366f1)");
1716
- var V = {
1717
- name: safeName,
1718
- id: safeId,
1719
- title: safeTitle,
1720
- description: safeDescription,
1721
- shortText: safeShortText,
1722
- prompt: safePrompt,
1723
- absolutePath: safeAbsolutePath,
1724
- hexColor: safeHexColor
1725
- };
1726
-
1727
- // src/tools/tasks.ts
1728
- var TASK_STATUSES = ["todo", "in_progress", "in_review", "done", "cancelled"];
1729
- var AGENT_TYPES = [
1730
- "claude",
1731
- "copilot",
1732
- "codex",
1733
- "opencode",
1734
- "gemini"
1735
- ];
1736
- function registerTaskTools(server, deps) {
1737
- const { configManager: configManager2 } = deps;
1738
- server.tool(
1739
- "list_tasks",
1740
- "List tasks, optionally filtered by project and/or status",
1741
- {
1742
- project_name: V.name.optional().describe("Filter by project name"),
1743
- status: z2.enum(TASK_STATUSES).optional().describe("Filter by status")
1744
- },
1745
- async (args) => {
1746
- const tasks = dbListTasks(args.project_name, args.status);
1747
- return { content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }] };
1748
- }
1749
- );
1750
- server.tool(
1751
- "create_task",
1752
- "Create a new task in a project",
1753
- {
1754
- project_name: V.name.describe("Project name (must match existing project)"),
1755
- title: V.title.describe("Task title"),
1756
- description: V.description.optional().describe("Task description (markdown)"),
1757
- status: z2.enum(TASK_STATUSES).optional().describe("Task status (default: todo)"),
1758
- branch: V.shortText.optional().describe("Git branch for this task"),
1759
- use_worktree: z2.boolean().optional().describe("Create a git worktree for this task"),
1760
- assigned_agent: z2.enum(AGENT_TYPES).optional().describe("Assign to an agent type")
1761
- },
1762
- async (args) => {
1763
- const project = dbGetProject(args.project_name);
1764
- if (!project) {
1765
- return {
1766
- content: [{ type: "text", text: `Error: project "${args.project_name}" not found` }],
1767
- isError: true
1768
- };
1769
- }
1770
- const maxOrder = dbGetMaxTaskOrder(args.project_name);
1771
- const now = (/* @__PURE__ */ new Date()).toISOString();
1772
- const status = args.status ?? "todo";
1773
- const task = {
1774
- id: crypto3.randomUUID(),
1775
- projectName: args.project_name,
1776
- title: args.title,
1777
- description: args.description ?? "",
1077
+ const maxOrder = dbGetMaxTaskOrder(args.project_name);
1078
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1079
+ const status = args.status ?? "todo";
1080
+ const task = {
1081
+ id: crypto.randomUUID(),
1082
+ projectName: args.project_name,
1083
+ title: args.title,
1084
+ description: args.description ?? "",
1778
1085
  status,
1779
1086
  order: maxOrder + 1,
1780
1087
  createdAt: now,
@@ -1785,7 +1092,6 @@ function registerTaskTools(server, deps) {
1785
1092
  ...(status === "done" || status === "cancelled") && { completedAt: now }
1786
1093
  };
1787
1094
  dbInsertTask(task);
1788
- configManager2.notifyChanged();
1789
1095
  return { content: [{ type: "text", text: JSON.stringify(task, null, 2) }] };
1790
1096
  }
1791
1097
  );
@@ -1837,7 +1143,6 @@ function registerTaskTools(server, deps) {
1837
1143
  if (!isDone && wasDone) updates.completedAt = void 0;
1838
1144
  }
1839
1145
  dbUpdateTask(args.id, updates);
1840
- configManager2.notifyChanged();
1841
1146
  const updated = dbGetTask(args.id);
1842
1147
  return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
1843
1148
  }
@@ -1855,7 +1160,6 @@ function registerTaskTools(server, deps) {
1855
1160
  };
1856
1161
  }
1857
1162
  dbDeleteTask(args.id);
1858
- configManager2.notifyChanged();
1859
1163
  return { content: [{ type: "text", text: `Deleted task: ${task.title}` }] };
1860
1164
  }
1861
1165
  );
@@ -1902,12 +1206,12 @@ function registerTaskTools(server, deps) {
1902
1206
  };
1903
1207
  }
1904
1208
  const cwd = args.cwd || process.cwd();
1905
- const normalizedCwd = path5.resolve(cwd);
1209
+ const normalizedCwd = path3.resolve(cwd);
1906
1210
  const projects = dbListProjects();
1907
1211
  let matchedProject = null;
1908
1212
  let matchLen = 0;
1909
1213
  for (const p of projects) {
1910
- const normalizedPath = path5.resolve(p.path);
1214
+ const normalizedPath = path3.resolve(p.path);
1911
1215
  if (normalizedCwd.startsWith(normalizedPath) && normalizedPath.length > matchLen) {
1912
1216
  matchedProject = p;
1913
1217
  matchLen = normalizedPath.length;
@@ -1935,7 +1239,7 @@ function registerTaskTools(server, deps) {
1935
1239
  let matchedTask = null;
1936
1240
  for (const t of projectTasks) {
1937
1241
  if (t.worktreePath) {
1938
- const normalizedWorktree = path5.resolve(t.worktreePath);
1242
+ const normalizedWorktree = path3.resolve(t.worktreePath);
1939
1243
  if (normalizedCwd.startsWith(normalizedWorktree)) {
1940
1244
  matchedTask = t;
1941
1245
  break;
@@ -1979,12 +1283,21 @@ var AGENT_TYPES2 = [
1979
1283
  "opencode",
1980
1284
  "gemini"
1981
1285
  ];
1982
- function registerProjectTools(server, deps) {
1983
- const { configManager: configManager2 } = deps;
1984
- server.tool("list_projects", "List all projects", async () => {
1985
- const projects = dbListProjects();
1986
- return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
1987
- });
1286
+ function registerProjectTools(server) {
1287
+ server.tool(
1288
+ "list_projects",
1289
+ "List all projects, optionally filtered by workspace",
1290
+ {
1291
+ workspace_id: V.id.optional().describe('Filter by workspace ID (e.g. "personal")')
1292
+ },
1293
+ async (args) => {
1294
+ let projects = dbListProjects();
1295
+ if (args.workspace_id) {
1296
+ projects = projects.filter((p) => (p.workspaceId ?? "personal") === args.workspace_id);
1297
+ }
1298
+ return { content: [{ type: "text", text: JSON.stringify(projects, null, 2) }] };
1299
+ }
1300
+ );
1988
1301
  server.tool(
1989
1302
  "create_project",
1990
1303
  "Create a new project",
@@ -2010,7 +1323,6 @@ function registerProjectTools(server, deps) {
2010
1323
  ...args.icon_color && { iconColor: args.icon_color }
2011
1324
  };
2012
1325
  dbInsertProject(project);
2013
- configManager2.notifyChanged();
2014
1326
  return { content: [{ type: "text", text: JSON.stringify(project, null, 2) }] };
2015
1327
  }
2016
1328
  );
@@ -2038,7 +1350,6 @@ function registerProjectTools(server, deps) {
2038
1350
  if (args.icon !== void 0) updates.icon = args.icon;
2039
1351
  if (args.icon_color !== void 0) updates.iconColor = args.icon_color;
2040
1352
  dbUpdateProject(args.name, updates);
2041
- configManager2.notifyChanged();
2042
1353
  const updated = dbGetProject(args.name);
2043
1354
  return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
2044
1355
  }
@@ -2055,7 +1366,6 @@ function registerProjectTools(server, deps) {
2055
1366
  };
2056
1367
  }
2057
1368
  dbDeleteProject(args.name);
2058
- configManager2.notifyChanged();
2059
1369
  return { content: [{ type: "text", text: `Deleted project: ${args.name}` }] };
2060
1370
  }
2061
1371
  );
@@ -2063,6 +1373,78 @@ function registerProjectTools(server, deps) {
2063
1373
 
2064
1374
  // src/tools/sessions.ts
2065
1375
  import { z as z4 } from "zod";
1376
+
1377
+ // src/ws-client.ts
1378
+ import fs3 from "fs";
1379
+ import path4 from "path";
1380
+ import os3 from "os";
1381
+ import { WebSocket } from "ws";
1382
+ var PORT_FILE = path4.join(os3.homedir(), ".vibegrid", "ws-port");
1383
+ var TIMEOUT_MS = 1e4;
1384
+ var rpcId = 0;
1385
+ function readPort() {
1386
+ try {
1387
+ const raw = fs3.readFileSync(PORT_FILE, "utf-8").trim();
1388
+ const port = parseInt(raw, 10);
1389
+ return Number.isFinite(port) && port > 0 ? port : null;
1390
+ } catch {
1391
+ return null;
1392
+ }
1393
+ }
1394
+ async function rpcCall(method, params) {
1395
+ const port = readPort();
1396
+ if (!port) {
1397
+ throw new Error("VibeGrid app is not running. Start VibeGrid to use session management tools.");
1398
+ }
1399
+ return new Promise((resolve, reject) => {
1400
+ const ws = new WebSocket(`ws://127.0.0.1:${port}/ws`);
1401
+ const id = ++rpcId;
1402
+ const timer = setTimeout(() => {
1403
+ ws.close();
1404
+ reject(new Error(`RPC call "${method}" timed out after ${TIMEOUT_MS}ms`));
1405
+ }, TIMEOUT_MS);
1406
+ ws.on("open", () => {
1407
+ ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
1408
+ });
1409
+ ws.on("message", (raw) => {
1410
+ try {
1411
+ const msg = JSON.parse(raw.toString());
1412
+ if (msg.id !== id) return;
1413
+ clearTimeout(timer);
1414
+ ws.close();
1415
+ if (msg.error) {
1416
+ reject(new Error(msg.error.message));
1417
+ } else {
1418
+ resolve(msg.result);
1419
+ }
1420
+ } catch {
1421
+ }
1422
+ });
1423
+ ws.on("error", (err) => {
1424
+ clearTimeout(timer);
1425
+ reject(new Error(`Cannot connect to VibeGrid server: ${err.message}. Is the app running?`));
1426
+ });
1427
+ });
1428
+ }
1429
+ async function rpcNotify(method, params) {
1430
+ const port = readPort();
1431
+ if (!port) {
1432
+ throw new Error("VibeGrid app is not running. Start VibeGrid to use session management tools.");
1433
+ }
1434
+ return new Promise((resolve, reject) => {
1435
+ const ws = new WebSocket(`ws://127.0.0.1:${port}/ws`);
1436
+ ws.on("open", () => {
1437
+ ws.send(JSON.stringify({ jsonrpc: "2.0", method, params }));
1438
+ ws.close();
1439
+ resolve();
1440
+ });
1441
+ ws.on("error", (err) => {
1442
+ reject(new Error(`Cannot connect to VibeGrid server: ${err.message}. Is the app running?`));
1443
+ });
1444
+ });
1445
+ }
1446
+
1447
+ // src/tools/sessions.ts
2066
1448
  var AGENT_TYPES3 = [
2067
1449
  "claude",
2068
1450
  "copilot",
@@ -2070,37 +1452,86 @@ var AGENT_TYPES3 = [
2070
1452
  "opencode",
2071
1453
  "gemini"
2072
1454
  ];
2073
- function registerSessionTools(server, deps) {
2074
- const { ptyManager: ptyManager2 } = deps;
2075
- server.tool("list_sessions", "List all active terminal sessions", async () => {
2076
- const sessions = ptyManager2.getActiveSessions();
2077
- const summary = sessions.map((s) => ({
2078
- id: s.id,
2079
- agentType: s.agentType,
2080
- projectName: s.projectName,
2081
- status: s.status,
2082
- displayName: s.displayName,
2083
- branch: s.branch,
2084
- pid: s.pid
2085
- }));
2086
- return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
2087
- });
1455
+ function registerSessionTools(server) {
2088
1456
  server.tool(
2089
- "launch_agent",
2090
- "Launch an AI agent in a new terminal session",
1457
+ "list_sessions",
1458
+ "List all active terminal sessions. Requires the VibeGrid app to be running.",
2091
1459
  {
2092
- agent_type: z4.enum(AGENT_TYPES3).describe("Agent type to launch"),
2093
- project_name: V.name.describe("Project name"),
2094
- project_path: V.absolutePath.describe("Absolute path to project directory"),
2095
- prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
2096
- branch: V.shortText.optional().describe("Git branch to checkout"),
2097
- use_worktree: z4.boolean().optional().describe("Create a git worktree"),
2098
- display_name: V.shortText.optional().describe("Display name for the session")
1460
+ project_name: V.name.optional().describe("Filter by project name")
2099
1461
  },
2100
1462
  async (args) => {
2101
- const payload = {
2102
- agentType: args.agent_type,
2103
- projectName: args.project_name,
1463
+ try {
1464
+ let sessions = await rpcCall("terminal:listActive");
1465
+ if (args.project_name) {
1466
+ sessions = sessions.filter((s) => s.projectName === args.project_name);
1467
+ }
1468
+ const summary = sessions.map((s) => ({
1469
+ id: s.id,
1470
+ agentType: s.agentType,
1471
+ projectName: s.projectName,
1472
+ status: s.status,
1473
+ displayName: s.displayName,
1474
+ branch: s.branch,
1475
+ pid: s.pid
1476
+ }));
1477
+ return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
1478
+ } catch (err) {
1479
+ return {
1480
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1481
+ isError: true
1482
+ };
1483
+ }
1484
+ }
1485
+ );
1486
+ server.tool(
1487
+ "list_recent_sessions",
1488
+ "List recent session history for a project. Requires the VibeGrid app to be running.",
1489
+ {
1490
+ project_path: V.absolutePath.optional().describe("Filter by project path")
1491
+ },
1492
+ async (args) => {
1493
+ try {
1494
+ const sessions = await rpcCall("sessions:getRecent", args.project_path);
1495
+ return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
1496
+ } catch (err) {
1497
+ return {
1498
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1499
+ isError: true
1500
+ };
1501
+ }
1502
+ }
1503
+ );
1504
+ server.tool(
1505
+ "list_archived_sessions",
1506
+ "List archived sessions. Requires the VibeGrid app to be running.",
1507
+ async () => {
1508
+ try {
1509
+ const sessions = await rpcCall("session:listArchived");
1510
+ return { content: [{ type: "text", text: JSON.stringify(sessions, null, 2) }] };
1511
+ } catch (err) {
1512
+ return {
1513
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1514
+ isError: true
1515
+ };
1516
+ }
1517
+ }
1518
+ );
1519
+ server.tool(
1520
+ "launch_agent",
1521
+ "Launch an AI agent in a new terminal session. Requires the VibeGrid app to be running.",
1522
+ {
1523
+ agent_type: z4.enum(AGENT_TYPES3).describe("Agent type to launch"),
1524
+ project_name: V.name.describe("Project name"),
1525
+ project_path: V.absolutePath.describe("Absolute path to project directory"),
1526
+ prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
1527
+ branch: V.shortText.optional().describe("Git branch to checkout"),
1528
+ use_worktree: z4.boolean().optional().describe("Create a git worktree"),
1529
+ display_name: V.shortText.optional().describe("Display name for the session")
1530
+ },
1531
+ async (args) => {
1532
+ const payload = {
1533
+ agentType: args.agent_type,
1534
+ projectName: args.project_name,
2104
1535
  projectPath: args.project_path,
2105
1536
  ...args.prompt && { initialPrompt: args.prompt },
2106
1537
  ...args.branch && { branch: args.branch },
@@ -2108,7 +1539,7 @@ function registerSessionTools(server, deps) {
2108
1539
  ...args.display_name && { displayName: args.display_name }
2109
1540
  };
2110
1541
  try {
2111
- const session = ptyManager2.createPty(payload);
1542
+ const session = await rpcCall("terminal:create", payload);
2112
1543
  return {
2113
1544
  content: [
2114
1545
  {
@@ -2128,37 +1559,134 @@ function registerSessionTools(server, deps) {
2128
1559
  ]
2129
1560
  };
2130
1561
  } catch (err) {
2131
- return { content: [{ type: "text", text: `Error launching agent: ${err}` }], isError: true };
1562
+ return {
1563
+ content: [
1564
+ {
1565
+ type: "text",
1566
+ text: `Error launching agent: ${err instanceof Error ? err.message : err}`
1567
+ }
1568
+ ],
1569
+ isError: true
1570
+ };
1571
+ }
1572
+ }
1573
+ );
1574
+ server.tool(
1575
+ "launch_headless",
1576
+ "Launch a headless (no UI) agent session. Requires the VibeGrid app to be running.",
1577
+ {
1578
+ agent_type: z4.enum(AGENT_TYPES3).describe("Agent type to launch"),
1579
+ project_name: V.name.describe("Project name"),
1580
+ project_path: V.absolutePath.describe("Absolute path to project directory"),
1581
+ prompt: V.prompt.optional().describe("Initial prompt to send to the agent"),
1582
+ branch: V.shortText.optional().describe("Git branch to checkout"),
1583
+ use_worktree: z4.boolean().optional().describe("Create a git worktree"),
1584
+ display_name: V.shortText.optional().describe("Display name for the session")
1585
+ },
1586
+ async (args) => {
1587
+ const payload = {
1588
+ agentType: args.agent_type,
1589
+ projectName: args.project_name,
1590
+ projectPath: args.project_path,
1591
+ ...args.prompt && { initialPrompt: args.prompt },
1592
+ ...args.branch && { branch: args.branch },
1593
+ ...args.use_worktree && { useWorktree: args.use_worktree },
1594
+ ...args.display_name && { displayName: args.display_name }
1595
+ };
1596
+ try {
1597
+ const session = await rpcCall("headless:create", payload);
1598
+ return {
1599
+ content: [
1600
+ {
1601
+ type: "text",
1602
+ text: JSON.stringify(
1603
+ {
1604
+ id: session.id,
1605
+ agentType: session.agentType,
1606
+ projectName: session.projectName,
1607
+ pid: session.pid,
1608
+ status: session.status
1609
+ },
1610
+ null,
1611
+ 2
1612
+ )
1613
+ }
1614
+ ]
1615
+ };
1616
+ } catch (err) {
1617
+ return {
1618
+ content: [
1619
+ {
1620
+ type: "text",
1621
+ text: `Error launching headless agent: ${err instanceof Error ? err.message : err}`
1622
+ }
1623
+ ],
1624
+ isError: true
1625
+ };
2132
1626
  }
2133
1627
  }
2134
1628
  );
2135
1629
  server.tool(
2136
1630
  "kill_session",
2137
- "Kill a terminal session",
1631
+ "Kill a terminal session. Requires the VibeGrid app to be running.",
2138
1632
  { id: V.id.describe("Session ID to kill") },
2139
1633
  async (args) => {
2140
1634
  try {
2141
- ptyManager2.killPty(args.id);
1635
+ await rpcCall("terminal:kill", args.id);
2142
1636
  return { content: [{ type: "text", text: `Killed session: ${args.id}` }] };
2143
1637
  } catch (err) {
2144
- return { content: [{ type: "text", text: `Error killing session: ${err}` }], isError: true };
1638
+ return {
1639
+ content: [
1640
+ {
1641
+ type: "text",
1642
+ text: `Error killing session: ${err instanceof Error ? err.message : err}`
1643
+ }
1644
+ ],
1645
+ isError: true
1646
+ };
1647
+ }
1648
+ }
1649
+ );
1650
+ server.tool(
1651
+ "kill_headless",
1652
+ "Kill a headless agent session. Requires the VibeGrid app to be running.",
1653
+ { id: V.id.describe("Headless session ID to kill") },
1654
+ async (args) => {
1655
+ try {
1656
+ await rpcCall("headless:kill", args.id);
1657
+ return { content: [{ type: "text", text: `Killed headless session: ${args.id}` }] };
1658
+ } catch (err) {
1659
+ return {
1660
+ content: [
1661
+ {
1662
+ type: "text",
1663
+ text: `Error killing headless session: ${err instanceof Error ? err.message : err}`
1664
+ }
1665
+ ],
1666
+ isError: true
1667
+ };
2145
1668
  }
2146
1669
  }
2147
1670
  );
2148
1671
  server.tool(
2149
1672
  "write_to_terminal",
2150
- "Send input to a running terminal session",
1673
+ "Send input to a running terminal session. Requires the VibeGrid app to be running.",
2151
1674
  {
2152
1675
  id: V.id.describe("Session ID"),
2153
1676
  data: z4.string().max(5e4, "Data must be 50000 characters or less").describe("Data to write (text input to send to the agent)")
2154
1677
  },
2155
1678
  async (args) => {
2156
1679
  try {
2157
- ptyManager2.writeToPty(args.id, args.data);
1680
+ await rpcNotify("terminal:write", { id: args.id, data: args.data });
2158
1681
  return { content: [{ type: "text", text: `Wrote to session: ${args.id}` }] };
2159
1682
  } catch (err) {
2160
1683
  return {
2161
- content: [{ type: "text", text: `Error writing to terminal: ${err}` }],
1684
+ content: [
1685
+ {
1686
+ type: "text",
1687
+ text: `Error writing to terminal: ${err instanceof Error ? err.message : err}`
1688
+ }
1689
+ ],
2162
1690
  isError: true
2163
1691
  };
2164
1692
  }
@@ -2167,7 +1695,7 @@ function registerSessionTools(server, deps) {
2167
1695
  }
2168
1696
 
2169
1697
  // src/tools/workflows.ts
2170
- import crypto4 from "crypto";
1698
+ import crypto2 from "crypto";
2171
1699
  import { z as z5 } from "zod";
2172
1700
  var launchAgentConfigSchema = z5.object({
2173
1701
  agentType: z5.enum(["claude", "copilot", "codex", "opencode", "gemini"]),
@@ -2215,7 +1743,7 @@ function buildGraphFromFlat(trigger, actions) {
2215
1743
  const nodes = [];
2216
1744
  const edges = [];
2217
1745
  const triggerNode = {
2218
- id: crypto4.randomUUID(),
1746
+ id: crypto2.randomUUID(),
2219
1747
  type: "trigger",
2220
1748
  label: trigger.triggerType === "manual" ? "Manual Trigger" : trigger.triggerType === "once" ? "Schedule (Once)" : trigger.triggerType === "recurring" ? "Schedule (Recurring)" : trigger.triggerType === "taskCreated" ? "When Task Created" : trigger.triggerType === "taskStatusChanged" ? "When Task Status Changes" : "Trigger",
2221
1749
  config: trigger,
@@ -2226,7 +1754,7 @@ function buildGraphFromFlat(trigger, actions) {
2226
1754
  const NODE_GAP = 140;
2227
1755
  for (let i = 0; i < actions.length; i++) {
2228
1756
  const action = actions[i];
2229
- const nodeId = crypto4.randomUUID();
1757
+ const nodeId = crypto2.randomUUID();
2230
1758
  nodes.push({
2231
1759
  id: nodeId,
2232
1760
  type: "launchAgent",
@@ -2235,7 +1763,7 @@ function buildGraphFromFlat(trigger, actions) {
2235
1763
  position: { x: 0, y: (i + 1) * NODE_GAP }
2236
1764
  });
2237
1765
  edges.push({
2238
- id: crypto4.randomUUID(),
1766
+ id: crypto2.randomUUID(),
2239
1767
  source: prevId,
2240
1768
  target: nodeId
2241
1769
  });
@@ -2243,12 +1771,21 @@ function buildGraphFromFlat(trigger, actions) {
2243
1771
  }
2244
1772
  return { nodes, edges };
2245
1773
  }
2246
- function registerWorkflowTools(server, deps) {
2247
- const { configManager: configManager2, scheduler: scheduler2 } = deps;
2248
- server.tool("list_workflows", "List all workflows", async () => {
2249
- const workflows = dbListWorkflows();
2250
- return { content: [{ type: "text", text: JSON.stringify(workflows, null, 2) }] };
2251
- });
1774
+ function registerWorkflowTools(server) {
1775
+ server.tool(
1776
+ "list_workflows",
1777
+ "List all workflows, optionally filtered by workspace",
1778
+ {
1779
+ workspace_id: V.id.optional().describe("Filter by workspace ID")
1780
+ },
1781
+ async (args) => {
1782
+ let workflows = dbListWorkflows();
1783
+ if (args.workspace_id) {
1784
+ workflows = workflows.filter((w) => (w.workspaceId ?? "personal") === args.workspace_id);
1785
+ }
1786
+ return { content: [{ type: "text", text: JSON.stringify(workflows, null, 2) }] };
1787
+ }
1788
+ );
2252
1789
  server.tool(
2253
1790
  "create_workflow",
2254
1791
  "Create a new workflow. Accepts either full nodes/edges or a convenience flat format (trigger + actions array).",
@@ -2277,7 +1814,7 @@ function registerWorkflowTools(server, deps) {
2277
1814
  edges = graph.edges;
2278
1815
  }
2279
1816
  const workflow = {
2280
- id: crypto4.randomUUID(),
1817
+ id: crypto2.randomUUID(),
2281
1818
  name: args.name,
2282
1819
  icon: args.icon ?? "zap",
2283
1820
  iconColor: args.icon_color ?? "#6366f1",
@@ -2287,8 +1824,6 @@ function registerWorkflowTools(server, deps) {
2287
1824
  ...args.stagger_delay_ms && { staggerDelayMs: args.stagger_delay_ms }
2288
1825
  };
2289
1826
  dbInsertWorkflow(workflow);
2290
- scheduler2.syncSchedules(dbListWorkflows());
2291
- configManager2.notifyChanged();
2292
1827
  return { content: [{ type: "text", text: JSON.stringify(workflow, null, 2) }] };
2293
1828
  }
2294
1829
  );
@@ -2323,8 +1858,6 @@ function registerWorkflowTools(server, deps) {
2323
1858
  if (args.enabled !== void 0) updates.enabled = args.enabled;
2324
1859
  if (args.stagger_delay_ms !== void 0) updates.staggerDelayMs = args.stagger_delay_ms;
2325
1860
  dbUpdateWorkflow(args.id, updates);
2326
- scheduler2.syncSchedules(dbListWorkflows());
2327
- configManager2.notifyChanged();
2328
1861
  return {
2329
1862
  content: [{ type: "text", text: JSON.stringify({ ...workflow, ...updates }, null, 2) }]
2330
1863
  };
@@ -2344,11 +1877,308 @@ function registerWorkflowTools(server, deps) {
2344
1877
  };
2345
1878
  }
2346
1879
  dbDeleteWorkflow(args.id);
2347
- scheduler2.syncSchedules(dbListWorkflows());
2348
- configManager2.notifyChanged();
2349
1880
  return { content: [{ type: "text", text: `Deleted workflow: ${workflow.name}` }] };
2350
1881
  }
2351
1882
  );
1883
+ server.tool(
1884
+ "list_workflow_runs",
1885
+ "List execution history for a workflow",
1886
+ {
1887
+ workflow_id: V.id.describe("Workflow ID"),
1888
+ limit: z5.number().int().min(1).max(100).optional().describe("Max results (default: 20)")
1889
+ },
1890
+ async (args) => {
1891
+ const runs = listWorkflowRuns(args.workflow_id, args.limit ?? 20);
1892
+ return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
1893
+ }
1894
+ );
1895
+ server.tool(
1896
+ "list_workflow_runs_by_task",
1897
+ "List workflow executions triggered by a specific task",
1898
+ {
1899
+ task_id: V.id.describe("Task ID"),
1900
+ limit: z5.number().int().min(1).max(100).optional().describe("Max results (default: 20)")
1901
+ },
1902
+ async (args) => {
1903
+ const runs = listWorkflowRunsByTask(args.task_id, args.limit ?? 20);
1904
+ return { content: [{ type: "text", text: JSON.stringify(runs, null, 2) }] };
1905
+ }
1906
+ );
1907
+ server.tool(
1908
+ "get_scheduler_log",
1909
+ "Get scheduler execution log for a workflow. Requires the VibeGrid app to be running.",
1910
+ {
1911
+ workflow_id: V.id.optional().describe("Workflow ID (omit for all workflows)")
1912
+ },
1913
+ async (args) => {
1914
+ try {
1915
+ const log2 = await rpcCall("scheduler:getLog", args.workflow_id);
1916
+ return { content: [{ type: "text", text: JSON.stringify(log2, null, 2) }] };
1917
+ } catch (err) {
1918
+ return {
1919
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1920
+ isError: true
1921
+ };
1922
+ }
1923
+ }
1924
+ );
1925
+ server.tool(
1926
+ "get_next_scheduled_run",
1927
+ "Get the next scheduled run time for a workflow. Requires the VibeGrid app to be running.",
1928
+ {
1929
+ workflow_id: V.id.describe("Workflow ID")
1930
+ },
1931
+ async (args) => {
1932
+ try {
1933
+ const nextRun = await rpcCall("scheduler:getNextRun", args.workflow_id);
1934
+ return {
1935
+ content: [
1936
+ {
1937
+ type: "text",
1938
+ text: nextRun ? JSON.stringify({ nextRun }, null, 2) : "No scheduled run (workflow may be manual or disabled)"
1939
+ }
1940
+ ]
1941
+ };
1942
+ } catch (err) {
1943
+ return {
1944
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : err}` }],
1945
+ isError: true
1946
+ };
1947
+ }
1948
+ }
1949
+ );
1950
+ }
1951
+
1952
+ // src/tools/git.ts
1953
+ import { z as z6 } from "zod";
1954
+
1955
+ // ../server/src/git-utils.ts
1956
+ import { execFileSync } from "child_process";
1957
+ import path5 from "path";
1958
+ import fs4 from "fs";
1959
+ import crypto3 from "crypto";
1960
+ var EXEC_OPTS = {
1961
+ encoding: "utf-8",
1962
+ stdio: ["pipe", "pipe", "pipe"]
1963
+ };
1964
+ function getGitBranch(projectPath) {
1965
+ try {
1966
+ const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
1967
+ cwd: projectPath,
1968
+ ...EXEC_OPTS,
1969
+ timeout: 3e3
1970
+ }).trim();
1971
+ return branch && branch !== "HEAD" ? branch : null;
1972
+ } catch {
1973
+ return null;
1974
+ }
1975
+ }
1976
+ function listBranches(projectPath) {
1977
+ try {
1978
+ const output = execFileSync("git", ["branch", "--format=%(refname:short)"], {
1979
+ cwd: projectPath,
1980
+ ...EXEC_OPTS,
1981
+ timeout: 5e3
1982
+ }).trim();
1983
+ return output ? output.split("\n").map((b) => b.trim()).filter(Boolean) : [];
1984
+ } catch {
1985
+ return [];
1986
+ }
1987
+ }
1988
+ function listRemoteBranches(projectPath) {
1989
+ try {
1990
+ execFileSync("git", ["fetch", "--prune"], {
1991
+ cwd: projectPath,
1992
+ ...EXEC_OPTS,
1993
+ timeout: 15e3
1994
+ });
1995
+ const output = execFileSync("git", ["branch", "-r", "--format=%(refname:short)"], {
1996
+ cwd: projectPath,
1997
+ ...EXEC_OPTS,
1998
+ timeout: 5e3
1999
+ }).trim();
2000
+ return output ? output.split("\n").map((b) => b.trim().replace(/^origin\//, "")).filter((b) => b && b !== "HEAD") : [];
2001
+ } catch {
2002
+ return [];
2003
+ }
2004
+ }
2005
+ function createWorktree(projectPath, branch) {
2006
+ const projectName = path5.basename(projectPath);
2007
+ const shortId = crypto3.randomUUID().slice(0, 8);
2008
+ const baseDir = path5.join(path5.dirname(projectPath), ".vibegrid-worktrees", projectName);
2009
+ const worktreeDir = path5.join(baseDir, `${branch}-${shortId}`);
2010
+ fs4.mkdirSync(baseDir, { recursive: true });
2011
+ const localBranches = listBranches(projectPath);
2012
+ if (localBranches.includes(branch)) {
2013
+ try {
2014
+ execFileSync("git", ["worktree", "add", worktreeDir, branch], {
2015
+ cwd: projectPath,
2016
+ ...EXEC_OPTS,
2017
+ timeout: 3e4
2018
+ });
2019
+ } catch {
2020
+ const newBranch = `${branch}-worktree-${shortId}`;
2021
+ execFileSync("git", ["worktree", "add", "-b", newBranch, worktreeDir, branch], {
2022
+ cwd: projectPath,
2023
+ ...EXEC_OPTS,
2024
+ timeout: 3e4
2025
+ });
2026
+ return { worktreePath: worktreeDir, branch: newBranch };
2027
+ }
2028
+ } else {
2029
+ execFileSync("git", ["worktree", "add", "-b", branch, worktreeDir], {
2030
+ cwd: projectPath,
2031
+ ...EXEC_OPTS,
2032
+ timeout: 3e4
2033
+ });
2034
+ }
2035
+ return { worktreePath: worktreeDir, branch };
2036
+ }
2037
+ function isWorktreeDirty(worktreePath) {
2038
+ try {
2039
+ const output = execFileSync("git", ["status", "--porcelain"], {
2040
+ cwd: worktreePath,
2041
+ ...EXEC_OPTS,
2042
+ timeout: 5e3
2043
+ }).trim();
2044
+ return output.length > 0;
2045
+ } catch {
2046
+ return true;
2047
+ }
2048
+ }
2049
+ function getGitDiffStat(cwd) {
2050
+ try {
2051
+ const output = execFileSync("git", ["diff", "HEAD", "--numstat"], {
2052
+ cwd,
2053
+ ...EXEC_OPTS,
2054
+ timeout: 1e4
2055
+ }).trim();
2056
+ if (!output) return { filesChanged: 0, insertions: 0, deletions: 0 };
2057
+ let insertions = 0;
2058
+ let deletions = 0;
2059
+ let filesChanged = 0;
2060
+ for (const line of output.split("\n")) {
2061
+ const parts = line.split(" ");
2062
+ if (parts[0] === "-") {
2063
+ filesChanged++;
2064
+ continue;
2065
+ }
2066
+ insertions += parseInt(parts[0], 10) || 0;
2067
+ deletions += parseInt(parts[1], 10) || 0;
2068
+ filesChanged++;
2069
+ }
2070
+ return { filesChanged, insertions, deletions };
2071
+ } catch {
2072
+ return null;
2073
+ }
2074
+ }
2075
+ function getGitDiffFull(cwd) {
2076
+ try {
2077
+ const stat = getGitDiffStat(cwd);
2078
+ if (!stat) return null;
2079
+ const MAX_DIFF_SIZE = 500 * 1024;
2080
+ let rawDiff = execFileSync("git", ["diff", "HEAD", "-U3"], {
2081
+ cwd,
2082
+ ...EXEC_OPTS,
2083
+ timeout: 15e3,
2084
+ maxBuffer: MAX_DIFF_SIZE * 2
2085
+ });
2086
+ if (rawDiff.length > MAX_DIFF_SIZE) {
2087
+ rawDiff = rawDiff.slice(0, MAX_DIFF_SIZE) + "\n\n... diff truncated (too large) ...\n";
2088
+ }
2089
+ const numstatOutput = execFileSync("git", ["diff", "HEAD", "--numstat"], {
2090
+ cwd,
2091
+ ...EXEC_OPTS,
2092
+ timeout: 1e4
2093
+ }).trim();
2094
+ const fileStats = /* @__PURE__ */ new Map();
2095
+ if (numstatOutput) {
2096
+ for (const line of numstatOutput.split("\n")) {
2097
+ const parts = line.split(" ");
2098
+ if (parts.length >= 3) {
2099
+ const ins = parts[0] === "-" ? 0 : parseInt(parts[0], 10) || 0;
2100
+ const del = parts[1] === "-" ? 0 : parseInt(parts[1], 10) || 0;
2101
+ fileStats.set(parts.slice(2).join(" "), { insertions: ins, deletions: del });
2102
+ }
2103
+ }
2104
+ }
2105
+ const fileDiffs = [];
2106
+ const diffSections = rawDiff.split(/^diff --git /m).filter(Boolean);
2107
+ for (const section of diffSections) {
2108
+ const fullSection = "diff --git " + section;
2109
+ const plusMatch = fullSection.match(/^\+\+\+ b\/(.+)$/m);
2110
+ const minusMatch = fullSection.match(/^--- a\/(.+)$/m);
2111
+ const filePath = plusMatch?.[1] || minusMatch?.[1]?.replace(/^\/dev\/null$/, "") || "unknown";
2112
+ let status = "modified";
2113
+ if (fullSection.includes("--- /dev/null")) {
2114
+ status = "added";
2115
+ } else if (fullSection.includes("+++ /dev/null")) {
2116
+ status = "deleted";
2117
+ } else if (fullSection.includes("rename from")) {
2118
+ status = "renamed";
2119
+ }
2120
+ const stats = fileStats.get(filePath) || { insertions: 0, deletions: 0 };
2121
+ fileDiffs.push({
2122
+ filePath,
2123
+ status,
2124
+ insertions: stats.insertions,
2125
+ deletions: stats.deletions,
2126
+ diff: fullSection
2127
+ });
2128
+ }
2129
+ return { stat, files: fileDiffs };
2130
+ } catch {
2131
+ return null;
2132
+ }
2133
+ }
2134
+ function gitCommit(cwd, message, includeUnstaged) {
2135
+ try {
2136
+ if (includeUnstaged) {
2137
+ execFileSync("git", ["add", "-A"], { cwd, ...EXEC_OPTS, timeout: 1e4 });
2138
+ }
2139
+ execFileSync("git", ["commit", "-m", message], {
2140
+ cwd,
2141
+ ...EXEC_OPTS,
2142
+ timeout: 15e3
2143
+ });
2144
+ return { success: true };
2145
+ } catch (err) {
2146
+ const msg = err instanceof Error ? err.message : String(err);
2147
+ return { success: false, error: msg };
2148
+ }
2149
+ }
2150
+ function gitPush(cwd) {
2151
+ try {
2152
+ execFileSync("git", ["push"], { cwd, ...EXEC_OPTS, timeout: 3e4 });
2153
+ return { success: true };
2154
+ } catch (err) {
2155
+ const msg = err instanceof Error ? err.message : String(err);
2156
+ return { success: false, error: msg };
2157
+ }
2158
+ }
2159
+ function listWorktrees(projectPath) {
2160
+ try {
2161
+ const output = execFileSync("git", ["worktree", "list", "--porcelain"], {
2162
+ cwd: projectPath,
2163
+ ...EXEC_OPTS,
2164
+ timeout: 5e3
2165
+ }).trim();
2166
+ if (!output) return [];
2167
+ const worktrees = [];
2168
+ const blocks = output.split("\n\n");
2169
+ for (const block of blocks) {
2170
+ const lines = block.split("\n");
2171
+ const wtPath = lines.find((l) => l.startsWith("worktree "))?.replace("worktree ", "");
2172
+ const branchLine = lines.find((l) => l.startsWith("branch "));
2173
+ const branch = branchLine?.replace("branch refs/heads/", "") || "detached";
2174
+ if (wtPath) {
2175
+ worktrees.push({ path: wtPath, branch, isMain: worktrees.length === 0 });
2176
+ }
2177
+ }
2178
+ return worktrees;
2179
+ } catch {
2180
+ return [];
2181
+ }
2352
2182
  }
2353
2183
 
2354
2184
  // src/tools/git.ts
@@ -2372,6 +2202,22 @@ function registerGitTools(server) {
2372
2202
  }
2373
2203
  }
2374
2204
  );
2205
+ server.tool(
2206
+ "list_remote_branches",
2207
+ "List remote git branches for a project",
2208
+ { project_path: V.absolutePath.describe("Absolute path to project directory") },
2209
+ async (args) => {
2210
+ try {
2211
+ const remote = listRemoteBranches(args.project_path);
2212
+ return { content: [{ type: "text", text: JSON.stringify(remote, null, 2) }] };
2213
+ } catch (err) {
2214
+ return {
2215
+ content: [{ type: "text", text: `Error listing remote branches: ${err}` }],
2216
+ isError: true
2217
+ };
2218
+ }
2219
+ }
2220
+ );
2375
2221
  server.tool(
2376
2222
  "get_diff",
2377
2223
  "Get git diff for a project (staged and unstaged changes)",
@@ -2390,30 +2236,218 @@ function registerGitTools(server) {
2390
2236
  }
2391
2237
  }
2392
2238
  );
2239
+ server.tool(
2240
+ "get_diff_stat",
2241
+ "Get a summary of git changes (files changed, insertions, deletions)",
2242
+ { project_path: V.absolutePath.describe("Absolute path to project directory") },
2243
+ async (args) => {
2244
+ try {
2245
+ const result = getGitDiffStat(args.project_path);
2246
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2247
+ } catch (err) {
2248
+ return {
2249
+ content: [{ type: "text", text: `Error getting diff stat: ${err}` }],
2250
+ isError: true
2251
+ };
2252
+ }
2253
+ }
2254
+ );
2255
+ server.tool(
2256
+ "git_commit",
2257
+ "Create a git commit",
2258
+ {
2259
+ project_path: V.absolutePath.describe("Absolute path to project directory"),
2260
+ message: V.description.describe("Commit message"),
2261
+ include_unstaged: z6.boolean().optional().describe("Stage all changes before committing")
2262
+ },
2263
+ async (args) => {
2264
+ try {
2265
+ const result = gitCommit(args.project_path, args.message, args.include_unstaged ?? false);
2266
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2267
+ } catch (err) {
2268
+ return {
2269
+ content: [{ type: "text", text: `Error committing: ${err}` }],
2270
+ isError: true
2271
+ };
2272
+ }
2273
+ }
2274
+ );
2275
+ server.tool(
2276
+ "git_push",
2277
+ "Push commits to the remote repository",
2278
+ { project_path: V.absolutePath.describe("Absolute path to project directory") },
2279
+ async (args) => {
2280
+ try {
2281
+ const result = gitPush(args.project_path);
2282
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
2283
+ } catch (err) {
2284
+ return {
2285
+ content: [{ type: "text", text: `Error pushing: ${err}` }],
2286
+ isError: true
2287
+ };
2288
+ }
2289
+ }
2290
+ );
2291
+ server.tool(
2292
+ "list_worktrees",
2293
+ "List git worktrees for a project",
2294
+ { project_path: V.absolutePath.describe("Absolute path to project directory") },
2295
+ async (args) => {
2296
+ try {
2297
+ const worktrees = listWorktrees(args.project_path);
2298
+ return { content: [{ type: "text", text: JSON.stringify(worktrees, null, 2) }] };
2299
+ } catch (err) {
2300
+ return {
2301
+ content: [{ type: "text", text: `Error listing worktrees: ${err}` }],
2302
+ isError: true
2303
+ };
2304
+ }
2305
+ }
2306
+ );
2307
+ server.tool(
2308
+ "create_worktree",
2309
+ "Create a git worktree for a branch",
2310
+ {
2311
+ project_path: V.absolutePath.describe("Absolute path to project directory"),
2312
+ branch: V.shortText.describe("Branch name for the worktree")
2313
+ },
2314
+ async (args) => {
2315
+ try {
2316
+ const worktreePath = createWorktree(args.project_path, args.branch);
2317
+ return {
2318
+ content: [{ type: "text", text: JSON.stringify({ path: worktreePath }, null, 2) }]
2319
+ };
2320
+ } catch (err) {
2321
+ return {
2322
+ content: [{ type: "text", text: `Error creating worktree: ${err}` }],
2323
+ isError: true
2324
+ };
2325
+ }
2326
+ }
2327
+ );
2328
+ server.tool(
2329
+ "worktree_dirty",
2330
+ "Check if a worktree has uncommitted changes",
2331
+ { worktree_path: V.absolutePath.describe("Absolute path to the worktree") },
2332
+ async (args) => {
2333
+ try {
2334
+ const dirty = isWorktreeDirty(args.worktree_path);
2335
+ return { content: [{ type: "text", text: JSON.stringify({ dirty }, null, 2) }] };
2336
+ } catch (err) {
2337
+ return {
2338
+ content: [{ type: "text", text: `Error checking worktree: ${err}` }],
2339
+ isError: true
2340
+ };
2341
+ }
2342
+ }
2343
+ );
2393
2344
  }
2394
2345
 
2395
2346
  // src/tools/config.ts
2396
- function registerConfigTools(server, deps) {
2397
- const { configManager: configManager2 } = deps;
2347
+ function registerConfigTools(server) {
2398
2348
  server.tool(
2399
2349
  "get_config",
2400
2350
  "Get the full VibeGrid configuration (projects, tasks, workflows, settings)",
2401
2351
  async () => {
2402
- const config = configManager2.loadConfig();
2352
+ const config = configManager.loadConfig();
2403
2353
  return { content: [{ type: "text", text: JSON.stringify(config, null, 2) }] };
2404
2354
  }
2405
2355
  );
2406
2356
  }
2407
2357
 
2358
+ // src/tools/workspaces.ts
2359
+ import crypto4 from "crypto";
2360
+ import { z as z7 } from "zod";
2361
+ function registerWorkspaceTools(server) {
2362
+ server.tool("list_workspaces", "List all workspaces", async () => {
2363
+ const workspaces = dbListWorkspaces();
2364
+ return { content: [{ type: "text", text: JSON.stringify(workspaces, null, 2) }] };
2365
+ });
2366
+ server.tool(
2367
+ "create_workspace",
2368
+ "Create a new workspace for organizing projects",
2369
+ {
2370
+ name: V.name.describe("Workspace name"),
2371
+ icon: V.shortText.optional().describe("Lucide icon name"),
2372
+ icon_color: V.hexColor.optional().describe("Hex color for icon")
2373
+ },
2374
+ async (args) => {
2375
+ const existing = dbListWorkspaces();
2376
+ const maxOrder = existing.reduce((max, w) => Math.max(max, w.order), 0);
2377
+ const workspace = {
2378
+ id: crypto4.randomUUID(),
2379
+ name: args.name,
2380
+ order: maxOrder + 1,
2381
+ ...args.icon && { icon: args.icon },
2382
+ ...args.icon_color && { iconColor: args.icon_color }
2383
+ };
2384
+ dbInsertWorkspace(workspace);
2385
+ return { content: [{ type: "text", text: JSON.stringify(workspace, null, 2) }] };
2386
+ }
2387
+ );
2388
+ server.tool(
2389
+ "update_workspace",
2390
+ "Update a workspace's properties",
2391
+ {
2392
+ id: V.id.describe("Workspace ID"),
2393
+ name: V.name.optional().describe("New name"),
2394
+ icon: V.shortText.optional().describe("Lucide icon name"),
2395
+ icon_color: V.hexColor.optional().describe("Hex color for icon"),
2396
+ order: z7.number().int().min(0).optional().describe("Sort order")
2397
+ },
2398
+ async (args) => {
2399
+ const existing = dbListWorkspaces();
2400
+ if (!existing.find((w) => w.id === args.id)) {
2401
+ return {
2402
+ content: [{ type: "text", text: `Error: workspace "${args.id}" not found` }],
2403
+ isError: true
2404
+ };
2405
+ }
2406
+ const updates = {};
2407
+ if (args.name !== void 0) updates.name = args.name;
2408
+ if (args.icon !== void 0) updates.icon = args.icon;
2409
+ if (args.icon_color !== void 0) updates.iconColor = args.icon_color;
2410
+ if (args.order !== void 0) updates.order = args.order;
2411
+ dbUpdateWorkspace(args.id, updates);
2412
+ const updated = dbListWorkspaces().find((w) => w.id === args.id);
2413
+ return { content: [{ type: "text", text: JSON.stringify(updated, null, 2) }] };
2414
+ }
2415
+ );
2416
+ server.tool(
2417
+ "delete_workspace",
2418
+ "Delete a workspace",
2419
+ { id: V.id.describe("Workspace ID") },
2420
+ async (args) => {
2421
+ if (args.id === "personal") {
2422
+ return {
2423
+ content: [{ type: "text", text: "Error: cannot delete the default workspace" }],
2424
+ isError: true
2425
+ };
2426
+ }
2427
+ const existing = dbListWorkspaces();
2428
+ const workspace = existing.find((w) => w.id === args.id);
2429
+ if (!workspace) {
2430
+ return {
2431
+ content: [{ type: "text", text: `Error: workspace "${args.id}" not found` }],
2432
+ isError: true
2433
+ };
2434
+ }
2435
+ dbDeleteWorkspace(args.id);
2436
+ return { content: [{ type: "text", text: `Deleted workspace: ${workspace.name}` }] };
2437
+ }
2438
+ );
2439
+ }
2440
+
2408
2441
  // src/server.ts
2409
- function createMcpServer(deps, version) {
2442
+ function createMcpServer(version) {
2410
2443
  const server = new McpServer({ name: "vibegrid", version }, { capabilities: { tools: {} } });
2411
2444
  registerGitTools(server);
2412
- registerConfigTools(server, deps);
2413
- registerProjectTools(server, deps);
2414
- registerTaskTools(server, deps);
2415
- registerSessionTools(server, deps);
2416
- registerWorkflowTools(server, deps);
2445
+ registerConfigTools(server);
2446
+ registerProjectTools(server);
2447
+ registerTaskTools(server);
2448
+ registerSessionTools(server);
2449
+ registerWorkflowTools(server);
2450
+ registerWorkspaceTools(server);
2417
2451
  return server;
2418
2452
  }
2419
2453
 
@@ -2426,19 +2460,11 @@ console.warn = (...args) => _origError("[mcp:warn]", ...args);
2426
2460
  console.error = (...args) => _origError("[mcp:error]", ...args);
2427
2461
  async function main() {
2428
2462
  configManager.init();
2429
- const config = configManager.loadConfig();
2430
- if (config.agentCommands) {
2431
- ptyManager.setAgentCommands(config.agentCommands);
2432
- }
2433
- ptyManager.setRemoteHosts(config.remoteHosts ?? []);
2434
- scheduler.syncSchedules(config.workflows ?? []);
2435
- const version = true ? "0.1.3" : createRequire(import.meta.url)("../package.json").version;
2436
- const server = createMcpServer({ configManager, ptyManager, scheduler }, version);
2463
+ const version = true ? "0.2.1" : createRequire(import.meta.url)("../package.json").version;
2464
+ const server = createMcpServer(version);
2437
2465
  const transport = new StdioServerTransport();
2438
2466
  await server.connect(transport);
2439
2467
  transport.onclose = () => {
2440
- scheduler.stopAll();
2441
- ptyManager.killAll();
2442
2468
  configManager.close();
2443
2469
  process.exit(0);
2444
2470
  };