maxsimcli 4.6.0 → 4.7.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.
- package/dist/assets/CHANGELOG.md +39 -0
- package/dist/cli.cjs +470 -961
- package/dist/cli.cjs.map +1 -1
- package/dist/{core-RRjCSt0G.cjs → core-D5zUr9cb.cjs} +4 -3
- package/dist/core-D5zUr9cb.cjs.map +1 -0
- package/dist/install.cjs +3 -195
- package/dist/install.cjs.map +1 -1
- package/dist/mcp-server.cjs +2853 -217
- package/dist/mcp-server.cjs.map +1 -1
- package/dist/{skills-MYlMkYNt.cjs → skills-CjFWZIGM.cjs} +6 -6
- package/dist/{skills-MYlMkYNt.cjs.map → skills-CjFWZIGM.cjs.map} +1 -1
- package/package.json +1 -7
- package/dist/.tsbuildinfo +0 -1
- package/dist/assets/dashboard/client/assets/index-C199D4Eb.css +0 -32
- package/dist/assets/dashboard/client/assets/index-nAXJLp0_.js +0 -233
- package/dist/assets/dashboard/client/index.html +0 -19
- package/dist/assets/dashboard/server.js +0 -78813
- package/dist/backend/index.d.ts +0 -4
- package/dist/backend/index.d.ts.map +0 -1
- package/dist/backend/index.js +0 -12
- package/dist/backend/index.js.map +0 -1
- package/dist/backend/lifecycle.d.ts +0 -13
- package/dist/backend/lifecycle.d.ts.map +0 -1
- package/dist/backend/lifecycle.js +0 -168
- package/dist/backend/lifecycle.js.map +0 -1
- package/dist/backend/server.d.ts +0 -13
- package/dist/backend/server.d.ts.map +0 -1
- package/dist/backend/server.js +0 -1013
- package/dist/backend/server.js.map +0 -1
- package/dist/backend/terminal.d.ts +0 -49
- package/dist/backend/terminal.d.ts.map +0 -1
- package/dist/backend/terminal.js +0 -209
- package/dist/backend/terminal.js.map +0 -1
- package/dist/backend/types.d.ts +0 -77
- package/dist/backend/types.d.ts.map +0 -1
- package/dist/backend/types.js +0 -6
- package/dist/backend/types.js.map +0 -1
- package/dist/backend-server.cjs +0 -80672
- package/dist/backend-server.cjs.map +0 -1
- package/dist/backend-server.d.cts +0 -2
- package/dist/backend-server.d.ts +0 -11
- package/dist/backend-server.d.ts.map +0 -1
- package/dist/backend-server.js +0 -43
- package/dist/backend-server.js.map +0 -1
- package/dist/cli.d.cts +0 -2
- package/dist/cli.d.ts +0 -7
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -510
- package/dist/cli.js.map +0 -1
- package/dist/core/artefakte.d.ts +0 -12
- package/dist/core/artefakte.d.ts.map +0 -1
- package/dist/core/artefakte.js +0 -152
- package/dist/core/artefakte.js.map +0 -1
- package/dist/core/commands.d.ts +0 -26
- package/dist/core/commands.d.ts.map +0 -1
- package/dist/core/commands.js +0 -550
- package/dist/core/commands.js.map +0 -1
- package/dist/core/config.d.ts +0 -10
- package/dist/core/config.d.ts.map +0 -1
- package/dist/core/config.js +0 -143
- package/dist/core/config.js.map +0 -1
- package/dist/core/context-loader.d.ts +0 -21
- package/dist/core/context-loader.d.ts.map +0 -1
- package/dist/core/context-loader.js +0 -212
- package/dist/core/context-loader.js.map +0 -1
- package/dist/core/core.d.ts +0 -91
- package/dist/core/core.d.ts.map +0 -1
- package/dist/core/core.js +0 -823
- package/dist/core/core.js.map +0 -1
- package/dist/core/dashboard-launcher.d.ts +0 -56
- package/dist/core/dashboard-launcher.d.ts.map +0 -1
- package/dist/core/dashboard-launcher.js +0 -246
- package/dist/core/dashboard-launcher.js.map +0 -1
- package/dist/core/drift.d.ts +0 -37
- package/dist/core/drift.d.ts.map +0 -1
- package/dist/core/drift.js +0 -213
- package/dist/core/drift.js.map +0 -1
- package/dist/core/frontmatter.d.ts +0 -33
- package/dist/core/frontmatter.d.ts.map +0 -1
- package/dist/core/frontmatter.js +0 -193
- package/dist/core/frontmatter.js.map +0 -1
- package/dist/core/index.d.ts +0 -28
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -189
- package/dist/core/index.js.map +0 -1
- package/dist/core/init.d.ts +0 -287
- package/dist/core/init.d.ts.map +0 -1
- package/dist/core/init.js +0 -816
- package/dist/core/init.js.map +0 -1
- package/dist/core/milestone.d.ts +0 -9
- package/dist/core/milestone.d.ts.map +0 -1
- package/dist/core/milestone.js +0 -230
- package/dist/core/milestone.js.map +0 -1
- package/dist/core/phase.d.ts +0 -53
- package/dist/core/phase.d.ts.map +0 -1
- package/dist/core/phase.js +0 -891
- package/dist/core/phase.js.map +0 -1
- package/dist/core/roadmap.d.ts +0 -10
- package/dist/core/roadmap.d.ts.map +0 -1
- package/dist/core/roadmap.js +0 -165
- package/dist/core/roadmap.js.map +0 -1
- package/dist/core/skills.d.ts +0 -20
- package/dist/core/skills.d.ts.map +0 -1
- package/dist/core/skills.js +0 -144
- package/dist/core/skills.js.map +0 -1
- package/dist/core/start.d.ts +0 -15
- package/dist/core/start.d.ts.map +0 -1
- package/dist/core/start.js +0 -80
- package/dist/core/start.js.map +0 -1
- package/dist/core/state.d.ts +0 -32
- package/dist/core/state.d.ts.map +0 -1
- package/dist/core/state.js +0 -582
- package/dist/core/state.js.map +0 -1
- package/dist/core/template.d.ts +0 -30
- package/dist/core/template.d.ts.map +0 -1
- package/dist/core/template.js +0 -223
- package/dist/core/template.js.map +0 -1
- package/dist/core/types.d.ts +0 -519
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -60
- package/dist/core/types.js.map +0 -1
- package/dist/core/verify.d.ts +0 -128
- package/dist/core/verify.d.ts.map +0 -1
- package/dist/core/verify.js +0 -754
- package/dist/core/verify.js.map +0 -1
- package/dist/core-RRjCSt0G.cjs.map +0 -1
- package/dist/esm-iIOBzmdz.cjs +0 -1561
- package/dist/esm-iIOBzmdz.cjs.map +0 -1
- package/dist/hooks/index.d.ts +0 -11
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -18
- package/dist/hooks/index.js.map +0 -1
- package/dist/hooks/maxsim-check-update.d.ts +0 -17
- package/dist/hooks/maxsim-check-update.d.ts.map +0 -1
- package/dist/hooks/maxsim-check-update.js +0 -101
- package/dist/hooks/maxsim-check-update.js.map +0 -1
- package/dist/hooks/maxsim-context-monitor.d.ts +0 -21
- package/dist/hooks/maxsim-context-monitor.d.ts.map +0 -1
- package/dist/hooks/maxsim-context-monitor.js +0 -131
- package/dist/hooks/maxsim-context-monitor.js.map +0 -1
- package/dist/hooks/maxsim-statusline.d.ts +0 -19
- package/dist/hooks/maxsim-statusline.d.ts.map +0 -1
- package/dist/hooks/maxsim-statusline.js +0 -146
- package/dist/hooks/maxsim-statusline.js.map +0 -1
- package/dist/hooks/shared.d.ts +0 -11
- package/dist/hooks/shared.d.ts.map +0 -1
- package/dist/hooks/shared.js +0 -29
- package/dist/hooks/shared.js.map +0 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3
- package/dist/index.js.map +0 -1
- package/dist/install/adapters.d.ts +0 -6
- package/dist/install/adapters.d.ts.map +0 -1
- package/dist/install/adapters.js +0 -65
- package/dist/install/adapters.js.map +0 -1
- package/dist/install/copy.d.ts +0 -6
- package/dist/install/copy.d.ts.map +0 -1
- package/dist/install/copy.js +0 -71
- package/dist/install/copy.js.map +0 -1
- package/dist/install/dashboard.d.ts +0 -16
- package/dist/install/dashboard.d.ts.map +0 -1
- package/dist/install/dashboard.js +0 -273
- package/dist/install/dashboard.js.map +0 -1
- package/dist/install/hooks.d.ts +0 -31
- package/dist/install/hooks.d.ts.map +0 -1
- package/dist/install/hooks.js +0 -260
- package/dist/install/hooks.js.map +0 -1
- package/dist/install/index.d.ts +0 -2
- package/dist/install/index.d.ts.map +0 -1
- package/dist/install/index.js +0 -534
- package/dist/install/index.js.map +0 -1
- package/dist/install/manifest.d.ts +0 -23
- package/dist/install/manifest.d.ts.map +0 -1
- package/dist/install/manifest.js +0 -133
- package/dist/install/manifest.js.map +0 -1
- package/dist/install/patches.d.ts +0 -10
- package/dist/install/patches.d.ts.map +0 -1
- package/dist/install/patches.js +0 -124
- package/dist/install/patches.js.map +0 -1
- package/dist/install/shared.d.ts +0 -56
- package/dist/install/shared.d.ts.map +0 -1
- package/dist/install/shared.js +0 -181
- package/dist/install/shared.js.map +0 -1
- package/dist/install/uninstall.d.ts +0 -5
- package/dist/install/uninstall.d.ts.map +0 -1
- package/dist/install/uninstall.js +0 -222
- package/dist/install/uninstall.js.map +0 -1
- package/dist/install/utils.d.ts +0 -27
- package/dist/install/utils.d.ts.map +0 -1
- package/dist/install/utils.js +0 -99
- package/dist/install/utils.js.map +0 -1
- package/dist/install.d.cts +0 -2
- package/dist/lifecycle-DxCru7rk.cjs +0 -136
- package/dist/lifecycle-DxCru7rk.cjs.map +0 -1
- package/dist/mcp/config-tools.d.ts +0 -13
- package/dist/mcp/config-tools.d.ts.map +0 -1
- package/dist/mcp/config-tools.js +0 -66
- package/dist/mcp/config-tools.js.map +0 -1
- package/dist/mcp/context-tools.d.ts +0 -13
- package/dist/mcp/context-tools.d.ts.map +0 -1
- package/dist/mcp/context-tools.js +0 -176
- package/dist/mcp/context-tools.js.map +0 -1
- package/dist/mcp/index.d.ts +0 -11
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -26
- package/dist/mcp/index.js.map +0 -1
- package/dist/mcp/phase-tools.d.ts +0 -13
- package/dist/mcp/phase-tools.d.ts.map +0 -1
- package/dist/mcp/phase-tools.js +0 -177
- package/dist/mcp/phase-tools.js.map +0 -1
- package/dist/mcp/roadmap-tools.d.ts +0 -13
- package/dist/mcp/roadmap-tools.d.ts.map +0 -1
- package/dist/mcp/roadmap-tools.js +0 -79
- package/dist/mcp/roadmap-tools.js.map +0 -1
- package/dist/mcp/state-tools.d.ts +0 -13
- package/dist/mcp/state-tools.d.ts.map +0 -1
- package/dist/mcp/state-tools.js +0 -185
- package/dist/mcp/state-tools.js.map +0 -1
- package/dist/mcp/todo-tools.d.ts +0 -13
- package/dist/mcp/todo-tools.d.ts.map +0 -1
- package/dist/mcp/todo-tools.js +0 -143
- package/dist/mcp/todo-tools.js.map +0 -1
- package/dist/mcp/utils.d.ts +0 -27
- package/dist/mcp/utils.d.ts.map +0 -1
- package/dist/mcp/utils.js +0 -82
- package/dist/mcp/utils.js.map +0 -1
- package/dist/mcp-server.d.cts +0 -2
- package/dist/mcp-server.d.ts +0 -12
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/mcp-server.js +0 -31
- package/dist/mcp-server.js.map +0 -1
- package/dist/server-By0TN-nC.cjs +0 -2995
- package/dist/server-By0TN-nC.cjs.map +0 -1
package/dist/server-By0TN-nC.cjs
DELETED
|
@@ -1,2995 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
const require_cli = require('./cli.cjs');
|
|
3
|
-
let node_fs = require("node:fs");
|
|
4
|
-
node_fs = require_cli.__toESM(node_fs);
|
|
5
|
-
let node_path = require("node:path");
|
|
6
|
-
node_path = require_cli.__toESM(node_path);
|
|
7
|
-
let node_os = require("node:os");
|
|
8
|
-
node_os = require_cli.__toESM(node_os);
|
|
9
|
-
let node_http = require("node:http");
|
|
10
|
-
let express = require("express");
|
|
11
|
-
express = require_cli.__toESM(express);
|
|
12
|
-
let ws = require("ws");
|
|
13
|
-
let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
14
|
-
let _modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
15
|
-
let detect_port = require("detect-port");
|
|
16
|
-
detect_port = require_cli.__toESM(detect_port);
|
|
17
|
-
let zod = require("zod");
|
|
18
|
-
|
|
19
|
-
//#region src/mcp/utils.ts
|
|
20
|
-
/**
|
|
21
|
-
* MCP Utilities — Shared helpers for MCP tools
|
|
22
|
-
*
|
|
23
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
24
|
-
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
25
|
-
*/
|
|
26
|
-
/**
|
|
27
|
-
* Walk up from startDir to find a directory containing `.planning/`.
|
|
28
|
-
* Returns the directory containing `.planning/` or null if not found.
|
|
29
|
-
*/
|
|
30
|
-
let _cachedRoot;
|
|
31
|
-
function detectProjectRoot(startDir) {
|
|
32
|
-
if (startDir === void 0 && _cachedRoot !== void 0) return _cachedRoot;
|
|
33
|
-
let dir = startDir || process.cwd();
|
|
34
|
-
for (let i = 0; i < 100; i++) {
|
|
35
|
-
const planningDir = node_path.default.join(dir, ".planning");
|
|
36
|
-
try {
|
|
37
|
-
if (node_fs.default.statSync(planningDir).isDirectory()) {
|
|
38
|
-
if (startDir === void 0) _cachedRoot = dir;
|
|
39
|
-
return dir;
|
|
40
|
-
}
|
|
41
|
-
} catch {}
|
|
42
|
-
const parent = node_path.default.dirname(dir);
|
|
43
|
-
if (parent === dir) {
|
|
44
|
-
if (startDir === void 0) _cachedRoot = null;
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
dir = parent;
|
|
48
|
-
}
|
|
49
|
-
if (startDir === void 0) _cachedRoot = null;
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Return a structured MCP success response.
|
|
54
|
-
*/
|
|
55
|
-
function mcpSuccess(data, summary) {
|
|
56
|
-
return { content: [{
|
|
57
|
-
type: "text",
|
|
58
|
-
text: JSON.stringify({
|
|
59
|
-
success: true,
|
|
60
|
-
data,
|
|
61
|
-
summary
|
|
62
|
-
}, null, 2)
|
|
63
|
-
}] };
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Return a structured MCP error response.
|
|
67
|
-
*/
|
|
68
|
-
function mcpError(error, summary) {
|
|
69
|
-
return {
|
|
70
|
-
content: [{
|
|
71
|
-
type: "text",
|
|
72
|
-
text: JSON.stringify({
|
|
73
|
-
success: false,
|
|
74
|
-
error,
|
|
75
|
-
summary
|
|
76
|
-
}, null, 2)
|
|
77
|
-
}],
|
|
78
|
-
isError: true
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
//#endregion
|
|
83
|
-
//#region src/mcp/phase-tools.ts
|
|
84
|
-
/**
|
|
85
|
-
* Phase CRUD MCP Tools — Phase operations exposed as MCP tools
|
|
86
|
-
*
|
|
87
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
88
|
-
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
89
|
-
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
90
|
-
*/
|
|
91
|
-
/**
|
|
92
|
-
* Register all phase CRUD tools on the MCP server.
|
|
93
|
-
*/
|
|
94
|
-
function registerPhaseTools(server) {
|
|
95
|
-
server.tool("mcp_find_phase", "Find a phase directory by number or name. Returns phase details including plans, summaries, and status.", { phase: zod.z.string().describe("Phase number or name (e.g. \"01\", \"1\", \"01A\", \"1.1\")") }, async ({ phase }) => {
|
|
96
|
-
try {
|
|
97
|
-
const cwd = detectProjectRoot();
|
|
98
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
99
|
-
const result = require_cli.findPhaseInternal(cwd, phase);
|
|
100
|
-
if (!result) return mcpError(`Phase ${phase} not found`, "Phase not found");
|
|
101
|
-
return mcpSuccess({
|
|
102
|
-
found: result.found,
|
|
103
|
-
directory: result.directory,
|
|
104
|
-
phase_number: result.phase_number,
|
|
105
|
-
phase_name: result.phase_name,
|
|
106
|
-
phase_slug: result.phase_slug,
|
|
107
|
-
plans: result.plans,
|
|
108
|
-
summaries: result.summaries,
|
|
109
|
-
incomplete_plans: result.incomplete_plans,
|
|
110
|
-
has_research: result.has_research,
|
|
111
|
-
has_context: result.has_context,
|
|
112
|
-
has_verification: result.has_verification,
|
|
113
|
-
archived: result.archived ?? null
|
|
114
|
-
}, `Found phase ${result.phase_number}: ${result.phase_name ?? "unnamed"}`);
|
|
115
|
-
} catch (e) {
|
|
116
|
-
return mcpError(e.message, "Operation failed");
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
server.tool("mcp_list_phases", "List phase directories with pagination. Returns sorted phases with offset/limit support.", {
|
|
120
|
-
include_archived: zod.z.boolean().optional().default(false).describe("Include archived phases from completed milestones"),
|
|
121
|
-
offset: zod.z.number().optional().default(0).describe("Number of phases to skip (for pagination)"),
|
|
122
|
-
limit: zod.z.number().optional().default(20).describe("Maximum number of phases to return")
|
|
123
|
-
}, async ({ include_archived, offset, limit }) => {
|
|
124
|
-
try {
|
|
125
|
-
const cwd = detectProjectRoot();
|
|
126
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
127
|
-
const phasesDir = require_cli.phasesPath(cwd);
|
|
128
|
-
if (!node_fs.default.existsSync(phasesDir)) return mcpSuccess({
|
|
129
|
-
directories: [],
|
|
130
|
-
count: 0,
|
|
131
|
-
total_count: 0,
|
|
132
|
-
offset,
|
|
133
|
-
limit,
|
|
134
|
-
has_more: false
|
|
135
|
-
}, "No phases directory found");
|
|
136
|
-
let dirs = require_cli.listSubDirs(phasesDir);
|
|
137
|
-
if (include_archived) {
|
|
138
|
-
const archived = require_cli.getArchivedPhaseDirs(cwd);
|
|
139
|
-
for (const a of archived) dirs.push(`${a.name} [${a.milestone}]`);
|
|
140
|
-
}
|
|
141
|
-
dirs.sort((a, b) => require_cli.comparePhaseNum(a, b));
|
|
142
|
-
const total_count = dirs.length;
|
|
143
|
-
const paginated = dirs.slice(offset, offset + limit);
|
|
144
|
-
const has_more = offset + limit < total_count;
|
|
145
|
-
return mcpSuccess({
|
|
146
|
-
directories: paginated,
|
|
147
|
-
count: paginated.length,
|
|
148
|
-
total_count,
|
|
149
|
-
offset,
|
|
150
|
-
limit,
|
|
151
|
-
has_more
|
|
152
|
-
}, `Showing ${paginated.length} of ${total_count} phase(s)`);
|
|
153
|
-
} catch (e) {
|
|
154
|
-
return mcpError(e.message, "Operation failed");
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
server.tool("mcp_create_phase", "Create a new phase. Adds the next sequential phase directory and appends to ROADMAP.md.", { name: zod.z.string().describe("Phase description/name (e.g. \"Authentication System\")") }, async ({ name }) => {
|
|
158
|
-
try {
|
|
159
|
-
const cwd = detectProjectRoot();
|
|
160
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
161
|
-
if (!name || !name.trim()) return mcpError("Phase name must not be empty", "Validation failed");
|
|
162
|
-
const result = await require_cli.phaseAddCore(cwd, name, { includeStubs: true });
|
|
163
|
-
return mcpSuccess({
|
|
164
|
-
phase_number: result.phase_number,
|
|
165
|
-
padded: result.padded,
|
|
166
|
-
name: result.description,
|
|
167
|
-
slug: result.slug,
|
|
168
|
-
directory: result.directory
|
|
169
|
-
}, `Created Phase ${result.phase_number}: ${result.description}`);
|
|
170
|
-
} catch (e) {
|
|
171
|
-
return mcpError(e.message, "Operation failed");
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
server.tool("mcp_insert_phase", "Insert a decimal phase after a specified phase (e.g. 01.1 after 01). Creates directory and updates ROADMAP.md.", {
|
|
175
|
-
name: zod.z.string().describe("Phase description/name"),
|
|
176
|
-
after: zod.z.string().describe("Phase number to insert after (e.g. \"01\", \"1\")")
|
|
177
|
-
}, async ({ name, after }) => {
|
|
178
|
-
try {
|
|
179
|
-
const cwd = detectProjectRoot();
|
|
180
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
181
|
-
if (!name || !name.trim()) return mcpError("Phase name must not be empty", "Validation failed");
|
|
182
|
-
const result = await require_cli.phaseInsertCore(cwd, after, name, { includeStubs: true });
|
|
183
|
-
return mcpSuccess({
|
|
184
|
-
phase_number: result.phase_number,
|
|
185
|
-
after_phase: result.after_phase,
|
|
186
|
-
name: result.description,
|
|
187
|
-
slug: result.slug,
|
|
188
|
-
directory: result.directory
|
|
189
|
-
}, `Inserted Phase ${result.phase_number}: ${result.description} after Phase ${result.after_phase}`);
|
|
190
|
-
} catch (e) {
|
|
191
|
-
return mcpError(e.message, "Operation failed");
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
server.tool("mcp_complete_phase", "Mark a phase as complete. Updates ROADMAP.md checkbox, progress table, plan count, STATE.md, and REQUIREMENTS.md.", { phase: zod.z.string().describe("Phase number to complete (e.g. \"01\", \"1\", \"1.1\")") }, async ({ phase }) => {
|
|
195
|
-
try {
|
|
196
|
-
const cwd = detectProjectRoot();
|
|
197
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
198
|
-
const result = await require_cli.phaseCompleteCore(cwd, phase);
|
|
199
|
-
return mcpSuccess({
|
|
200
|
-
completed_phase: result.completed_phase,
|
|
201
|
-
phase_name: result.phase_name,
|
|
202
|
-
plans_executed: result.plans_executed,
|
|
203
|
-
next_phase: result.next_phase,
|
|
204
|
-
next_phase_name: result.next_phase_name,
|
|
205
|
-
is_last_phase: result.is_last_phase,
|
|
206
|
-
date: result.date,
|
|
207
|
-
roadmap_updated: result.roadmap_updated,
|
|
208
|
-
state_updated: result.state_updated
|
|
209
|
-
}, `Phase ${phase} marked as complete${result.next_phase ? `, next: Phase ${result.next_phase}` : ""}`);
|
|
210
|
-
} catch (e) {
|
|
211
|
-
return mcpError(e.message, "Operation failed");
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
//#endregion
|
|
217
|
-
//#region src/mcp/todo-tools.ts
|
|
218
|
-
/**
|
|
219
|
-
* Todo CRUD MCP Tools — Todo operations exposed as MCP tools
|
|
220
|
-
*
|
|
221
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
222
|
-
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
223
|
-
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
224
|
-
*/
|
|
225
|
-
/**
|
|
226
|
-
* Register all todo CRUD tools on the MCP server.
|
|
227
|
-
*/
|
|
228
|
-
function registerTodoTools(server) {
|
|
229
|
-
server.tool("mcp_add_todo", "Create a new todo item in .planning/todos/pending/ with frontmatter metadata.", {
|
|
230
|
-
title: zod.z.string().describe("Title of the todo item"),
|
|
231
|
-
description: zod.z.string().optional().describe("Optional description body"),
|
|
232
|
-
area: zod.z.string().optional().default("general").describe("Area/category (default: general)"),
|
|
233
|
-
phase: zod.z.string().optional().describe("Associated phase number")
|
|
234
|
-
}, async ({ title, description, area, phase }) => {
|
|
235
|
-
try {
|
|
236
|
-
const cwd = detectProjectRoot();
|
|
237
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
238
|
-
const pendingDir = require_cli.planningPath(cwd, "todos", "pending");
|
|
239
|
-
node_fs.default.mkdirSync(pendingDir, { recursive: true });
|
|
240
|
-
const today = require_cli.todayISO();
|
|
241
|
-
const slug = require_cli.generateSlugInternal(title) || "untitled";
|
|
242
|
-
const filename = `${Date.now()}-${slug}.md`;
|
|
243
|
-
const filePath = node_path.default.join(pendingDir, filename);
|
|
244
|
-
const content = `---\ncreated: ${today}\ntitle: ${title}\narea: ${area || "general"}\nphase: ${phase || "unassigned"}\n---\n${description || ""}\n`;
|
|
245
|
-
node_fs.default.writeFileSync(filePath, content, "utf-8");
|
|
246
|
-
return mcpSuccess({
|
|
247
|
-
file: filename,
|
|
248
|
-
path: `.planning/todos/pending/${filename}`,
|
|
249
|
-
title,
|
|
250
|
-
area: area || "general"
|
|
251
|
-
}, `Todo created: ${title}`);
|
|
252
|
-
} catch (e) {
|
|
253
|
-
return mcpError(e.message, "Operation failed");
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
|
-
server.tool("mcp_complete_todo", "Mark a pending todo as completed by moving it from pending/ to completed/ with a completion timestamp.", { todo_id: zod.z.string().describe("Filename of the todo (e.g., 1234567890-my-task.md)") }, async ({ todo_id }) => {
|
|
257
|
-
try {
|
|
258
|
-
const cwd = detectProjectRoot();
|
|
259
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
260
|
-
const pendingDir = require_cli.planningPath(cwd, "todos", "pending");
|
|
261
|
-
const completedDir = require_cli.planningPath(cwd, "todos", "completed");
|
|
262
|
-
const sourcePath = node_path.default.join(pendingDir, todo_id);
|
|
263
|
-
if (!node_fs.default.existsSync(sourcePath)) return mcpError(`Todo not found in pending: ${todo_id}`, "Todo not found");
|
|
264
|
-
node_fs.default.mkdirSync(completedDir, { recursive: true });
|
|
265
|
-
let content = node_fs.default.readFileSync(sourcePath, "utf-8");
|
|
266
|
-
const today = require_cli.todayISO();
|
|
267
|
-
content = `completed: ${today}\n` + content;
|
|
268
|
-
node_fs.default.writeFileSync(node_path.default.join(completedDir, todo_id), content, "utf-8");
|
|
269
|
-
node_fs.default.unlinkSync(sourcePath);
|
|
270
|
-
return mcpSuccess({
|
|
271
|
-
completed: true,
|
|
272
|
-
file: todo_id,
|
|
273
|
-
date: today
|
|
274
|
-
}, `Todo completed: ${todo_id}`);
|
|
275
|
-
} catch (e) {
|
|
276
|
-
return mcpError(e.message, "Operation failed");
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
server.tool("mcp_list_todos", "List todo items, optionally filtered by area and status (pending, completed, or all).", {
|
|
280
|
-
area: zod.z.string().optional().describe("Filter by area/category"),
|
|
281
|
-
status: zod.z.enum([
|
|
282
|
-
"pending",
|
|
283
|
-
"completed",
|
|
284
|
-
"all"
|
|
285
|
-
]).optional().default("pending").describe("Which todos to list (default: pending)")
|
|
286
|
-
}, async ({ area, status }) => {
|
|
287
|
-
try {
|
|
288
|
-
const cwd = detectProjectRoot();
|
|
289
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
290
|
-
const todosBase = require_cli.planningPath(cwd, "todos");
|
|
291
|
-
const dirs = [];
|
|
292
|
-
if (status === "pending" || status === "all") dirs.push(node_path.default.join(todosBase, "pending"));
|
|
293
|
-
if (status === "completed" || status === "all") dirs.push(node_path.default.join(todosBase, "completed"));
|
|
294
|
-
const todos = [];
|
|
295
|
-
for (const dir of dirs) {
|
|
296
|
-
const dirStatus = dir.endsWith("pending") ? "pending" : "completed";
|
|
297
|
-
let files = [];
|
|
298
|
-
try {
|
|
299
|
-
files = node_fs.default.readdirSync(dir).filter((f) => f.endsWith(".md"));
|
|
300
|
-
} catch {
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
303
|
-
for (const file of files) try {
|
|
304
|
-
const fm = require_cli.parseTodoFrontmatter(node_fs.default.readFileSync(node_path.default.join(dir, file), "utf-8"));
|
|
305
|
-
if (area && fm.area !== area) continue;
|
|
306
|
-
todos.push({
|
|
307
|
-
file,
|
|
308
|
-
created: fm.created,
|
|
309
|
-
title: fm.title,
|
|
310
|
-
area: fm.area,
|
|
311
|
-
status: dirStatus,
|
|
312
|
-
path: `.planning/todos/${dirStatus}/${file}`
|
|
313
|
-
});
|
|
314
|
-
} catch {}
|
|
315
|
-
}
|
|
316
|
-
return mcpSuccess({
|
|
317
|
-
count: todos.length,
|
|
318
|
-
todos
|
|
319
|
-
}, `${todos.length} todos found`);
|
|
320
|
-
} catch (e) {
|
|
321
|
-
return mcpError(e.message, "Operation failed");
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
//#endregion
|
|
327
|
-
//#region src/mcp/state-tools.ts
|
|
328
|
-
/**
|
|
329
|
-
* State Management MCP Tools — STATE.md operations exposed as MCP tools
|
|
330
|
-
*
|
|
331
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
332
|
-
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
333
|
-
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
334
|
-
*/
|
|
335
|
-
/**
|
|
336
|
-
* Register all state management tools on the MCP server.
|
|
337
|
-
*/
|
|
338
|
-
function registerStateTools(server) {
|
|
339
|
-
server.tool("mcp_get_state", "Read STATE.md content — full file, a specific **field:** value, or a ## section.", { field: zod.z.string().optional().describe("Specific field or section name, or omit for full STATE.md") }, async ({ field }) => {
|
|
340
|
-
try {
|
|
341
|
-
const cwd = detectProjectRoot();
|
|
342
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
343
|
-
const stPath = require_cli.statePath(cwd);
|
|
344
|
-
if (!node_fs.default.existsSync(stPath)) return mcpError("STATE.md not found", "STATE.md missing");
|
|
345
|
-
const content = node_fs.default.readFileSync(stPath, "utf-8");
|
|
346
|
-
if (!field) return mcpSuccess({ content }, "Full STATE.md retrieved");
|
|
347
|
-
const fieldValue = require_cli.stateExtractField(content, field);
|
|
348
|
-
if (fieldValue) return mcpSuccess({
|
|
349
|
-
content: fieldValue,
|
|
350
|
-
field
|
|
351
|
-
}, `State field retrieved: ${field}`);
|
|
352
|
-
const fieldEscaped = require_cli.escapeStringRegexp(field);
|
|
353
|
-
const sectionPattern = new RegExp(`##\\s*${fieldEscaped}\\s*\n([\\s\\S]*?)(?=\\n##|$)`, "i");
|
|
354
|
-
const sectionMatch = content.match(sectionPattern);
|
|
355
|
-
if (sectionMatch) return mcpSuccess({
|
|
356
|
-
content: sectionMatch[1].trim(),
|
|
357
|
-
field
|
|
358
|
-
}, `State section retrieved: ${field}`);
|
|
359
|
-
return mcpError(`Section or field "${field}" not found in STATE.md`, "Field not found");
|
|
360
|
-
} catch (e) {
|
|
361
|
-
return mcpError(e.message, "Operation failed");
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
server.tool("mcp_update_state", "Update a **field:** value in STATE.md (e.g., \"Status\", \"Current focus\").", {
|
|
365
|
-
field: zod.z.string().describe("Field name (e.g., \"Status\", \"Current focus\")"),
|
|
366
|
-
value: zod.z.string().describe("New value for the field")
|
|
367
|
-
}, async ({ field, value }) => {
|
|
368
|
-
try {
|
|
369
|
-
const cwd = detectProjectRoot();
|
|
370
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
371
|
-
const stPath = require_cli.statePath(cwd);
|
|
372
|
-
if (!node_fs.default.existsSync(stPath)) return mcpError("STATE.md not found", "STATE.md missing");
|
|
373
|
-
const updated = require_cli.stateReplaceField(node_fs.default.readFileSync(stPath, "utf-8"), field, value);
|
|
374
|
-
if (!updated) return mcpError(`Field "${field}" not found in STATE.md`, "Field not found");
|
|
375
|
-
node_fs.default.writeFileSync(stPath, updated, "utf-8");
|
|
376
|
-
return mcpSuccess({
|
|
377
|
-
updated: true,
|
|
378
|
-
field,
|
|
379
|
-
value
|
|
380
|
-
}, `State updated: ${field}`);
|
|
381
|
-
} catch (e) {
|
|
382
|
-
return mcpError(e.message, "Operation failed");
|
|
383
|
-
}
|
|
384
|
-
});
|
|
385
|
-
server.tool("mcp_add_decision", "Record a decision in the Decisions section of STATE.md.", {
|
|
386
|
-
summary: zod.z.string().describe("Decision summary"),
|
|
387
|
-
rationale: zod.z.string().optional().describe("Optional rationale"),
|
|
388
|
-
phase: zod.z.string().optional().describe("Associated phase number")
|
|
389
|
-
}, async ({ summary, rationale, phase }) => {
|
|
390
|
-
try {
|
|
391
|
-
const cwd = detectProjectRoot();
|
|
392
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
393
|
-
const stPath = require_cli.statePath(cwd);
|
|
394
|
-
if (!node_fs.default.existsSync(stPath)) return mcpError("STATE.md not found", "STATE.md missing");
|
|
395
|
-
const content = node_fs.default.readFileSync(stPath, "utf-8");
|
|
396
|
-
const entry = `- [Phase ${phase || "?"}]: ${summary}${rationale ? ` -- ${rationale}` : ""}`;
|
|
397
|
-
const updated = require_cli.appendToStateSection(content, /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, entry, [/None yet\.?\s*\n?/gi, /No decisions yet\.?\s*\n?/gi]);
|
|
398
|
-
if (!updated) return mcpError("Decisions section not found in STATE.md", "Section not found");
|
|
399
|
-
node_fs.default.writeFileSync(stPath, updated, "utf-8");
|
|
400
|
-
return mcpSuccess({
|
|
401
|
-
added: true,
|
|
402
|
-
decision: entry
|
|
403
|
-
}, "Decision recorded");
|
|
404
|
-
} catch (e) {
|
|
405
|
-
return mcpError(e.message, "Operation failed");
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
server.tool("mcp_add_blocker", "Add a blocker entry to the Blockers section of STATE.md.", { text: zod.z.string().describe("Blocker description") }, async ({ text }) => {
|
|
409
|
-
try {
|
|
410
|
-
const cwd = detectProjectRoot();
|
|
411
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
412
|
-
const stPath = require_cli.statePath(cwd);
|
|
413
|
-
if (!node_fs.default.existsSync(stPath)) return mcpError("STATE.md not found", "STATE.md missing");
|
|
414
|
-
const updated = require_cli.appendToStateSection(node_fs.default.readFileSync(stPath, "utf-8"), /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, `- ${text}`, [/None\.?\s*\n?/gi, /None yet\.?\s*\n?/gi]);
|
|
415
|
-
if (!updated) return mcpError("Blockers section not found in STATE.md", "Section not found");
|
|
416
|
-
node_fs.default.writeFileSync(stPath, updated, "utf-8");
|
|
417
|
-
return mcpSuccess({
|
|
418
|
-
added: true,
|
|
419
|
-
blocker: text
|
|
420
|
-
}, "Blocker added");
|
|
421
|
-
} catch (e) {
|
|
422
|
-
return mcpError(e.message, "Operation failed");
|
|
423
|
-
}
|
|
424
|
-
});
|
|
425
|
-
server.tool("mcp_resolve_blocker", "Remove a blocker from STATE.md by matching text (case-insensitive partial match).", { text: zod.z.string().describe("Text to match against blocker entries (case-insensitive partial match)") }, async ({ text }) => {
|
|
426
|
-
try {
|
|
427
|
-
const cwd = detectProjectRoot();
|
|
428
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
429
|
-
const stPath = require_cli.statePath(cwd);
|
|
430
|
-
if (!node_fs.default.existsSync(stPath)) return mcpError("STATE.md not found", "STATE.md missing");
|
|
431
|
-
let content = node_fs.default.readFileSync(stPath, "utf-8");
|
|
432
|
-
const sectionPattern = /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i;
|
|
433
|
-
const match = content.match(sectionPattern);
|
|
434
|
-
if (!match) return mcpError("Blockers section not found in STATE.md", "Section not found");
|
|
435
|
-
let newBody = match[2].split("\n").filter((line) => {
|
|
436
|
-
if (!line.startsWith("- ")) return true;
|
|
437
|
-
return !line.toLowerCase().includes(text.toLowerCase());
|
|
438
|
-
}).join("\n");
|
|
439
|
-
if (!newBody.trim() || !newBody.includes("- ")) newBody = "None\n";
|
|
440
|
-
content = content.replace(sectionPattern, (_match, header) => `${header}${newBody}`);
|
|
441
|
-
node_fs.default.writeFileSync(stPath, content, "utf-8");
|
|
442
|
-
return mcpSuccess({
|
|
443
|
-
resolved: true,
|
|
444
|
-
blocker: text
|
|
445
|
-
}, "Blocker resolved");
|
|
446
|
-
} catch (e) {
|
|
447
|
-
return mcpError(e.message, "Operation failed");
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
//#endregion
|
|
453
|
-
//#region src/mcp/context-tools.ts
|
|
454
|
-
/**
|
|
455
|
-
* Context Query MCP Tools — Project context exposed as MCP tools
|
|
456
|
-
*
|
|
457
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
458
|
-
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
459
|
-
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
460
|
-
*/
|
|
461
|
-
/**
|
|
462
|
-
* Register all context query tools on the MCP server.
|
|
463
|
-
*/
|
|
464
|
-
function registerContextTools(server) {
|
|
465
|
-
server.tool("mcp_get_active_phase", "Get the currently active phase and next phase from roadmap analysis and STATE.md.", {}, async () => {
|
|
466
|
-
try {
|
|
467
|
-
const cwd = detectProjectRoot();
|
|
468
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
469
|
-
const roadmapResult = await require_cli.cmdRoadmapAnalyze(cwd);
|
|
470
|
-
let current_phase = null;
|
|
471
|
-
let next_phase = null;
|
|
472
|
-
let phase_name = null;
|
|
473
|
-
let status = null;
|
|
474
|
-
if (roadmapResult.ok) {
|
|
475
|
-
const data = roadmapResult.result;
|
|
476
|
-
current_phase = data.current_phase ?? null;
|
|
477
|
-
next_phase = data.next_phase ?? null;
|
|
478
|
-
}
|
|
479
|
-
const stateContent = require_cli.safeReadFile(require_cli.planningPath(cwd, "STATE.md"));
|
|
480
|
-
if (stateContent) {
|
|
481
|
-
const statePhase = require_cli.stateExtractField(stateContent, "Current Phase");
|
|
482
|
-
if (statePhase) phase_name = statePhase;
|
|
483
|
-
const stateStatus = require_cli.stateExtractField(stateContent, "Status");
|
|
484
|
-
if (stateStatus) status = stateStatus;
|
|
485
|
-
}
|
|
486
|
-
return mcpSuccess({
|
|
487
|
-
current_phase,
|
|
488
|
-
next_phase,
|
|
489
|
-
phase_name,
|
|
490
|
-
status
|
|
491
|
-
}, `Active phase: ${phase_name ?? current_phase ?? "unknown"}`);
|
|
492
|
-
} catch (e) {
|
|
493
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
494
|
-
}
|
|
495
|
-
});
|
|
496
|
-
server.tool("mcp_get_guidelines", "Get project guidelines: PROJECT.md vision, config, and optionally phase-specific context.", { phase: zod.z.string().optional().describe("Optional phase number to include phase-specific context") }, async ({ phase }) => {
|
|
497
|
-
try {
|
|
498
|
-
const cwd = detectProjectRoot();
|
|
499
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
500
|
-
const project_vision = require_cli.safeReadFile(require_cli.planningPath(cwd, "PROJECT.md"));
|
|
501
|
-
const config = require_cli.loadConfig(cwd);
|
|
502
|
-
let phase_context = null;
|
|
503
|
-
if (phase) {
|
|
504
|
-
const phaseInfo = require_cli.findPhaseInternal(cwd, phase);
|
|
505
|
-
if (phaseInfo) phase_context = require_cli.safeReadFile(node_path.default.join(phaseInfo.directory, `${phaseInfo.phase_number}-CONTEXT.md`));
|
|
506
|
-
}
|
|
507
|
-
return mcpSuccess({
|
|
508
|
-
project_vision,
|
|
509
|
-
config,
|
|
510
|
-
phase_context
|
|
511
|
-
}, `Guidelines loaded${phase ? ` with phase ${phase} context` : ""}`);
|
|
512
|
-
} catch (e) {
|
|
513
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
server.tool("mcp_get_context_for_task", "Load context files for a task. Includes project context, roadmap, artefakte, and codebase docs filtered by topic. Topic keywords select relevant codebase docs: \"ui/frontend\" loads CONVENTIONS+STRUCTURE, \"api/backend\" loads ARCHITECTURE+CONVENTIONS, \"testing\" loads TESTING+CONVENTIONS, \"database\" loads ARCHITECTURE+STACK, \"refactor\" loads CONCERNS+ARCHITECTURE. Without topic, defaults to STACK+ARCHITECTURE.", {
|
|
517
|
-
phase: zod.z.string().optional().describe("Phase number to scope context to"),
|
|
518
|
-
topic: zod.z.string().optional().describe("Topic keywords to filter codebase docs (e.g. \"frontend\", \"api\", \"testing\", \"database\", \"refactor\")")
|
|
519
|
-
}, async ({ phase, topic }) => {
|
|
520
|
-
try {
|
|
521
|
-
const cwd = detectProjectRoot();
|
|
522
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
523
|
-
const result = require_cli.cmdContextLoad(cwd, phase, topic, true);
|
|
524
|
-
if (!result.ok) return mcpError(result.error, "Context load failed");
|
|
525
|
-
return mcpSuccess({ context: result.result }, `Context loaded${phase ? ` for phase ${phase}` : ""}${topic ? ` topic "${topic}"` : ""}`);
|
|
526
|
-
} catch (e) {
|
|
527
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
server.tool("mcp_get_project_overview", "Get a high-level project overview: PROJECT.md, REQUIREMENTS.md, and STATE.md contents.", {}, async () => {
|
|
531
|
-
try {
|
|
532
|
-
const cwd = detectProjectRoot();
|
|
533
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
534
|
-
return mcpSuccess({
|
|
535
|
-
project: require_cli.safeReadFile(require_cli.planningPath(cwd, "PROJECT.md")),
|
|
536
|
-
requirements: require_cli.safeReadFile(require_cli.planningPath(cwd, "REQUIREMENTS.md")),
|
|
537
|
-
state: require_cli.safeReadFile(require_cli.planningPath(cwd, "STATE.md"))
|
|
538
|
-
}, "Project overview loaded");
|
|
539
|
-
} catch (e) {
|
|
540
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
541
|
-
}
|
|
542
|
-
});
|
|
543
|
-
server.tool("mcp_get_phase_detail", "Get detailed information about a specific phase including all its files (plans, summaries, context, research, verification).", { phase: zod.z.string().describe("Phase number or name (e.g. \"01\", \"1\", \"01A\")") }, async ({ phase }) => {
|
|
544
|
-
try {
|
|
545
|
-
const cwd = detectProjectRoot();
|
|
546
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
547
|
-
const phaseInfo = require_cli.findPhaseInternal(cwd, phase);
|
|
548
|
-
if (!phaseInfo) return mcpError(`Phase ${phase} not found`, "Phase not found");
|
|
549
|
-
const files = [];
|
|
550
|
-
try {
|
|
551
|
-
const entries = node_fs.default.readdirSync(phaseInfo.directory);
|
|
552
|
-
for (const entry of entries) {
|
|
553
|
-
const fullPath = node_path.default.join(phaseInfo.directory, entry);
|
|
554
|
-
if (node_fs.default.statSync(fullPath).isFile()) files.push({
|
|
555
|
-
name: entry,
|
|
556
|
-
content: require_cli.safeReadFile(fullPath)
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
} catch {}
|
|
560
|
-
return mcpSuccess({
|
|
561
|
-
phase_number: phaseInfo.phase_number,
|
|
562
|
-
phase_name: phaseInfo.phase_name,
|
|
563
|
-
directory: phaseInfo.directory,
|
|
564
|
-
files
|
|
565
|
-
}, `Phase ${phaseInfo.phase_number} detail: ${files.length} file(s)`);
|
|
566
|
-
} catch (e) {
|
|
567
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
//#endregion
|
|
573
|
-
//#region src/mcp/roadmap-tools.ts
|
|
574
|
-
/**
|
|
575
|
-
* Register all roadmap query tools on the MCP server.
|
|
576
|
-
*/
|
|
577
|
-
function registerRoadmapTools(server) {
|
|
578
|
-
server.tool("mcp_get_roadmap", "Get the full roadmap analysis including all phases, their status, and progress.", {}, async () => {
|
|
579
|
-
try {
|
|
580
|
-
const cwd = detectProjectRoot();
|
|
581
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
582
|
-
const result = await require_cli.cmdRoadmapAnalyze(cwd);
|
|
583
|
-
if (!result.ok) return mcpError(result.error, "Roadmap analysis failed");
|
|
584
|
-
return mcpSuccess({ roadmap: result.result }, "Roadmap analysis complete");
|
|
585
|
-
} catch (e) {
|
|
586
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
server.tool("mcp_get_roadmap_progress", "Get a focused progress summary: total phases, completed, in-progress, not started, and progress percentage.", {}, async () => {
|
|
590
|
-
try {
|
|
591
|
-
const cwd = detectProjectRoot();
|
|
592
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
593
|
-
const result = await require_cli.cmdRoadmapAnalyze(cwd);
|
|
594
|
-
if (!result.ok) return mcpError(result.error, "Roadmap analysis failed");
|
|
595
|
-
const data = result.result;
|
|
596
|
-
const phases = data.phases ?? [];
|
|
597
|
-
const total_phases = phases.length;
|
|
598
|
-
let completed = 0;
|
|
599
|
-
let in_progress = 0;
|
|
600
|
-
let not_started = 0;
|
|
601
|
-
for (const p of phases) {
|
|
602
|
-
const status = String(p.status ?? "").toLowerCase();
|
|
603
|
-
if (status === "completed" || status === "done") completed++;
|
|
604
|
-
else if (status === "in-progress" || status === "in_progress" || status === "active") in_progress++;
|
|
605
|
-
else not_started++;
|
|
606
|
-
}
|
|
607
|
-
const progress_percent = total_phases > 0 ? Math.round(completed / total_phases * 100) : 0;
|
|
608
|
-
return mcpSuccess({
|
|
609
|
-
total_phases,
|
|
610
|
-
completed,
|
|
611
|
-
in_progress,
|
|
612
|
-
not_started,
|
|
613
|
-
progress_percent,
|
|
614
|
-
current_phase: data.current_phase ?? null,
|
|
615
|
-
next_phase: data.next_phase ?? null
|
|
616
|
-
}, `Progress: ${completed}/${total_phases} phases complete (${progress_percent}%)`);
|
|
617
|
-
} catch (e) {
|
|
618
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
619
|
-
}
|
|
620
|
-
});
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
//#endregion
|
|
624
|
-
//#region src/mcp/config-tools.ts
|
|
625
|
-
/**
|
|
626
|
-
* Config Query MCP Tools — Project configuration exposed as MCP tools
|
|
627
|
-
*
|
|
628
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
629
|
-
* CRITICAL: Never write to stdout — it is reserved for MCP JSON-RPC protocol.
|
|
630
|
-
* CRITICAL: Never call process.exit() — the server must stay alive after every tool call.
|
|
631
|
-
*/
|
|
632
|
-
/**
|
|
633
|
-
* Register all config query tools on the MCP server.
|
|
634
|
-
*/
|
|
635
|
-
function registerConfigTools(server) {
|
|
636
|
-
server.tool("mcp_get_config", "Get project configuration. Optionally provide a key path to get a specific value.", { key: zod.z.string().optional().describe("Optional dot-separated key path (e.g. \"model_profile\", \"branching.strategy\")") }, async ({ key }) => {
|
|
637
|
-
try {
|
|
638
|
-
const cwd = detectProjectRoot();
|
|
639
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
640
|
-
if (key) {
|
|
641
|
-
const result = require_cli.cmdConfigGet(cwd, key, true);
|
|
642
|
-
if (!result.ok) return mcpError(result.error, "Config get failed");
|
|
643
|
-
return mcpSuccess({
|
|
644
|
-
key,
|
|
645
|
-
value: result.rawValue ?? result.result
|
|
646
|
-
}, `Config value for "${key}"`);
|
|
647
|
-
}
|
|
648
|
-
return mcpSuccess({ config: require_cli.loadConfig(cwd) }, "Full configuration loaded");
|
|
649
|
-
} catch (e) {
|
|
650
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
server.tool("mcp_update_config", "Update a project configuration value by key path.", {
|
|
654
|
-
key: zod.z.string().describe("Dot-separated key path (e.g. \"model_profile\", \"branching.strategy\")"),
|
|
655
|
-
value: zod.z.string().describe("New value to set")
|
|
656
|
-
}, async ({ key, value }) => {
|
|
657
|
-
try {
|
|
658
|
-
const cwd = detectProjectRoot();
|
|
659
|
-
if (!cwd) return mcpError("No .planning/ directory found", "Project not detected");
|
|
660
|
-
const result = require_cli.cmdConfigSet(cwd, key, value, true);
|
|
661
|
-
if (!result.ok) return mcpError(result.error, "Config update failed");
|
|
662
|
-
return mcpSuccess({
|
|
663
|
-
updated: true,
|
|
664
|
-
key,
|
|
665
|
-
value
|
|
666
|
-
}, `Config "${key}" updated to "${value}"`);
|
|
667
|
-
} catch (e) {
|
|
668
|
-
return mcpError("Failed: " + e.message, "Error occurred");
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
//#endregion
|
|
674
|
-
//#region src/mcp/index.ts
|
|
675
|
-
/**
|
|
676
|
-
* Register all MCP tools on the given server instance.
|
|
677
|
-
*/
|
|
678
|
-
function registerAllTools(server) {
|
|
679
|
-
registerPhaseTools(server);
|
|
680
|
-
registerTodoTools(server);
|
|
681
|
-
registerStateTools(server);
|
|
682
|
-
registerContextTools(server);
|
|
683
|
-
registerRoadmapTools(server);
|
|
684
|
-
registerConfigTools(server);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
//#endregion
|
|
688
|
-
//#region ../../node_modules/node-pty/lib/utils.js
|
|
689
|
-
var require_utils = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
690
|
-
/**
|
|
691
|
-
* Copyright (c) 2017, Daniel Imms (MIT License).
|
|
692
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
693
|
-
*/
|
|
694
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
695
|
-
exports.loadNativeModule = exports.assign = void 0;
|
|
696
|
-
function assign(target) {
|
|
697
|
-
var sources = [];
|
|
698
|
-
for (var _i = 1; _i < arguments.length; _i++) sources[_i - 1] = arguments[_i];
|
|
699
|
-
sources.forEach(function(source) {
|
|
700
|
-
return Object.keys(source).forEach(function(key) {
|
|
701
|
-
return target[key] = source[key];
|
|
702
|
-
});
|
|
703
|
-
});
|
|
704
|
-
return target;
|
|
705
|
-
}
|
|
706
|
-
exports.assign = assign;
|
|
707
|
-
function loadNativeModule(name) {
|
|
708
|
-
var dirs = [
|
|
709
|
-
"build/Release",
|
|
710
|
-
"build/Debug",
|
|
711
|
-
"prebuilds/" + process.platform + "-" + process.arch
|
|
712
|
-
];
|
|
713
|
-
var relative = ["..", "."];
|
|
714
|
-
var lastError;
|
|
715
|
-
for (var _i = 0, dirs_1 = dirs; _i < dirs_1.length; _i++) {
|
|
716
|
-
var d = dirs_1[_i];
|
|
717
|
-
for (var _a = 0, relative_1 = relative; _a < relative_1.length; _a++) {
|
|
718
|
-
var dir = relative_1[_a] + "/" + d + "/";
|
|
719
|
-
try {
|
|
720
|
-
return {
|
|
721
|
-
dir,
|
|
722
|
-
module: require(dir + "/" + name + ".node")
|
|
723
|
-
};
|
|
724
|
-
} catch (e) {
|
|
725
|
-
lastError = e;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
throw new Error("Failed to load native module: " + name + ".node, checked: " + dirs.join(", ") + ": " + lastError);
|
|
730
|
-
}
|
|
731
|
-
exports.loadNativeModule = loadNativeModule;
|
|
732
|
-
}));
|
|
733
|
-
|
|
734
|
-
//#endregion
|
|
735
|
-
//#region ../../node_modules/node-pty/lib/eventEmitter2.js
|
|
736
|
-
var require_eventEmitter2 = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
737
|
-
/**
|
|
738
|
-
* Copyright (c) 2019, Microsoft Corporation (MIT License).
|
|
739
|
-
*/
|
|
740
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
741
|
-
exports.EventEmitter2 = void 0;
|
|
742
|
-
var EventEmitter2 = function() {
|
|
743
|
-
function EventEmitter2() {
|
|
744
|
-
this._listeners = [];
|
|
745
|
-
}
|
|
746
|
-
Object.defineProperty(EventEmitter2.prototype, "event", {
|
|
747
|
-
get: function() {
|
|
748
|
-
var _this = this;
|
|
749
|
-
if (!this._event) this._event = function(listener) {
|
|
750
|
-
_this._listeners.push(listener);
|
|
751
|
-
return { dispose: function() {
|
|
752
|
-
for (var i = 0; i < _this._listeners.length; i++) if (_this._listeners[i] === listener) {
|
|
753
|
-
_this._listeners.splice(i, 1);
|
|
754
|
-
return;
|
|
755
|
-
}
|
|
756
|
-
} };
|
|
757
|
-
};
|
|
758
|
-
return this._event;
|
|
759
|
-
},
|
|
760
|
-
enumerable: false,
|
|
761
|
-
configurable: true
|
|
762
|
-
});
|
|
763
|
-
EventEmitter2.prototype.fire = function(data) {
|
|
764
|
-
var queue = [];
|
|
765
|
-
for (var i = 0; i < this._listeners.length; i++) queue.push(this._listeners[i]);
|
|
766
|
-
for (var i = 0; i < queue.length; i++) queue[i].call(void 0, data);
|
|
767
|
-
};
|
|
768
|
-
return EventEmitter2;
|
|
769
|
-
}();
|
|
770
|
-
exports.EventEmitter2 = EventEmitter2;
|
|
771
|
-
}));
|
|
772
|
-
|
|
773
|
-
//#endregion
|
|
774
|
-
//#region ../../node_modules/node-pty/lib/terminal.js
|
|
775
|
-
var require_terminal = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
776
|
-
/**
|
|
777
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
|
778
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
779
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
780
|
-
*/
|
|
781
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
782
|
-
exports.Terminal = exports.DEFAULT_ROWS = exports.DEFAULT_COLS = void 0;
|
|
783
|
-
var events_1 = require("events");
|
|
784
|
-
var eventEmitter2_1 = require_eventEmitter2();
|
|
785
|
-
exports.DEFAULT_COLS = 80;
|
|
786
|
-
exports.DEFAULT_ROWS = 24;
|
|
787
|
-
/**
|
|
788
|
-
* Default messages to indicate PAUSE/RESUME for automatic flow control.
|
|
789
|
-
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
|
|
790
|
-
* the sequences can be customized in `IPtyForkOptions`.
|
|
791
|
-
*/
|
|
792
|
-
var FLOW_CONTROL_PAUSE = "";
|
|
793
|
-
var FLOW_CONTROL_RESUME = "";
|
|
794
|
-
var Terminal = function() {
|
|
795
|
-
function Terminal(opt) {
|
|
796
|
-
this._pid = 0;
|
|
797
|
-
this._fd = 0;
|
|
798
|
-
this._cols = 0;
|
|
799
|
-
this._rows = 0;
|
|
800
|
-
this._readable = false;
|
|
801
|
-
this._writable = false;
|
|
802
|
-
this._onData = new eventEmitter2_1.EventEmitter2();
|
|
803
|
-
this._onExit = new eventEmitter2_1.EventEmitter2();
|
|
804
|
-
this._internalee = new events_1.EventEmitter();
|
|
805
|
-
this.handleFlowControl = !!(opt === null || opt === void 0 ? void 0 : opt.handleFlowControl);
|
|
806
|
-
this._flowControlPause = (opt === null || opt === void 0 ? void 0 : opt.flowControlPause) || FLOW_CONTROL_PAUSE;
|
|
807
|
-
this._flowControlResume = (opt === null || opt === void 0 ? void 0 : opt.flowControlResume) || FLOW_CONTROL_RESUME;
|
|
808
|
-
if (!opt) return;
|
|
809
|
-
this._checkType("name", opt.name ? opt.name : void 0, "string");
|
|
810
|
-
this._checkType("cols", opt.cols ? opt.cols : void 0, "number");
|
|
811
|
-
this._checkType("rows", opt.rows ? opt.rows : void 0, "number");
|
|
812
|
-
this._checkType("cwd", opt.cwd ? opt.cwd : void 0, "string");
|
|
813
|
-
this._checkType("env", opt.env ? opt.env : void 0, "object");
|
|
814
|
-
this._checkType("uid", opt.uid ? opt.uid : void 0, "number");
|
|
815
|
-
this._checkType("gid", opt.gid ? opt.gid : void 0, "number");
|
|
816
|
-
this._checkType("encoding", opt.encoding ? opt.encoding : void 0, "string");
|
|
817
|
-
}
|
|
818
|
-
Object.defineProperty(Terminal.prototype, "onData", {
|
|
819
|
-
get: function() {
|
|
820
|
-
return this._onData.event;
|
|
821
|
-
},
|
|
822
|
-
enumerable: false,
|
|
823
|
-
configurable: true
|
|
824
|
-
});
|
|
825
|
-
Object.defineProperty(Terminal.prototype, "onExit", {
|
|
826
|
-
get: function() {
|
|
827
|
-
return this._onExit.event;
|
|
828
|
-
},
|
|
829
|
-
enumerable: false,
|
|
830
|
-
configurable: true
|
|
831
|
-
});
|
|
832
|
-
Object.defineProperty(Terminal.prototype, "pid", {
|
|
833
|
-
get: function() {
|
|
834
|
-
return this._pid;
|
|
835
|
-
},
|
|
836
|
-
enumerable: false,
|
|
837
|
-
configurable: true
|
|
838
|
-
});
|
|
839
|
-
Object.defineProperty(Terminal.prototype, "cols", {
|
|
840
|
-
get: function() {
|
|
841
|
-
return this._cols;
|
|
842
|
-
},
|
|
843
|
-
enumerable: false,
|
|
844
|
-
configurable: true
|
|
845
|
-
});
|
|
846
|
-
Object.defineProperty(Terminal.prototype, "rows", {
|
|
847
|
-
get: function() {
|
|
848
|
-
return this._rows;
|
|
849
|
-
},
|
|
850
|
-
enumerable: false,
|
|
851
|
-
configurable: true
|
|
852
|
-
});
|
|
853
|
-
Terminal.prototype.write = function(data) {
|
|
854
|
-
if (this.handleFlowControl) {
|
|
855
|
-
if (data === this._flowControlPause) {
|
|
856
|
-
this.pause();
|
|
857
|
-
return;
|
|
858
|
-
}
|
|
859
|
-
if (data === this._flowControlResume) {
|
|
860
|
-
this.resume();
|
|
861
|
-
return;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
this._write(data);
|
|
865
|
-
};
|
|
866
|
-
Terminal.prototype._forwardEvents = function() {
|
|
867
|
-
var _this = this;
|
|
868
|
-
this.on("data", function(e) {
|
|
869
|
-
return _this._onData.fire(e);
|
|
870
|
-
});
|
|
871
|
-
this.on("exit", function(exitCode, signal) {
|
|
872
|
-
return _this._onExit.fire({
|
|
873
|
-
exitCode,
|
|
874
|
-
signal
|
|
875
|
-
});
|
|
876
|
-
});
|
|
877
|
-
};
|
|
878
|
-
Terminal.prototype._checkType = function(name, value, type, allowArray) {
|
|
879
|
-
if (allowArray === void 0) allowArray = false;
|
|
880
|
-
if (value === void 0) return;
|
|
881
|
-
if (allowArray) {
|
|
882
|
-
if (Array.isArray(value)) {
|
|
883
|
-
value.forEach(function(v, i) {
|
|
884
|
-
if (typeof v !== type) throw new Error(name + "[" + i + "] must be a " + type + " (not a " + typeof v[i] + ")");
|
|
885
|
-
});
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
if (typeof value !== type) throw new Error(name + " must be a " + type + " (not a " + typeof value + ")");
|
|
890
|
-
};
|
|
891
|
-
/** See net.Socket.end */
|
|
892
|
-
Terminal.prototype.end = function(data) {
|
|
893
|
-
this._socket.end(data);
|
|
894
|
-
};
|
|
895
|
-
/** See stream.Readable.pipe */
|
|
896
|
-
Terminal.prototype.pipe = function(dest, options) {
|
|
897
|
-
return this._socket.pipe(dest, options);
|
|
898
|
-
};
|
|
899
|
-
/** See net.Socket.pause */
|
|
900
|
-
Terminal.prototype.pause = function() {
|
|
901
|
-
return this._socket.pause();
|
|
902
|
-
};
|
|
903
|
-
/** See net.Socket.resume */
|
|
904
|
-
Terminal.prototype.resume = function() {
|
|
905
|
-
return this._socket.resume();
|
|
906
|
-
};
|
|
907
|
-
/** See net.Socket.setEncoding */
|
|
908
|
-
Terminal.prototype.setEncoding = function(encoding) {
|
|
909
|
-
if (this._socket._decoder) delete this._socket._decoder;
|
|
910
|
-
if (encoding) this._socket.setEncoding(encoding);
|
|
911
|
-
};
|
|
912
|
-
Terminal.prototype.addListener = function(eventName, listener) {
|
|
913
|
-
this.on(eventName, listener);
|
|
914
|
-
};
|
|
915
|
-
Terminal.prototype.on = function(eventName, listener) {
|
|
916
|
-
if (eventName === "close") {
|
|
917
|
-
this._internalee.on("close", listener);
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
this._socket.on(eventName, listener);
|
|
921
|
-
};
|
|
922
|
-
Terminal.prototype.emit = function(eventName) {
|
|
923
|
-
var args = [];
|
|
924
|
-
for (var _i = 1; _i < arguments.length; _i++) args[_i - 1] = arguments[_i];
|
|
925
|
-
if (eventName === "close") return this._internalee.emit.apply(this._internalee, arguments);
|
|
926
|
-
return this._socket.emit.apply(this._socket, arguments);
|
|
927
|
-
};
|
|
928
|
-
Terminal.prototype.listeners = function(eventName) {
|
|
929
|
-
return this._socket.listeners(eventName);
|
|
930
|
-
};
|
|
931
|
-
Terminal.prototype.removeListener = function(eventName, listener) {
|
|
932
|
-
this._socket.removeListener(eventName, listener);
|
|
933
|
-
};
|
|
934
|
-
Terminal.prototype.removeAllListeners = function(eventName) {
|
|
935
|
-
this._socket.removeAllListeners(eventName);
|
|
936
|
-
};
|
|
937
|
-
Terminal.prototype.once = function(eventName, listener) {
|
|
938
|
-
this._socket.once(eventName, listener);
|
|
939
|
-
};
|
|
940
|
-
Terminal.prototype._close = function() {
|
|
941
|
-
this._socket.readable = false;
|
|
942
|
-
this.write = function() {};
|
|
943
|
-
this.end = function() {};
|
|
944
|
-
this._writable = false;
|
|
945
|
-
this._readable = false;
|
|
946
|
-
};
|
|
947
|
-
Terminal.prototype._parseEnv = function(env) {
|
|
948
|
-
var keys = Object.keys(env || {});
|
|
949
|
-
var pairs = [];
|
|
950
|
-
for (var i = 0; i < keys.length; i++) {
|
|
951
|
-
if (keys[i] === void 0) continue;
|
|
952
|
-
pairs.push(keys[i] + "=" + env[keys[i]]);
|
|
953
|
-
}
|
|
954
|
-
return pairs;
|
|
955
|
-
};
|
|
956
|
-
return Terminal;
|
|
957
|
-
}();
|
|
958
|
-
exports.Terminal = Terminal;
|
|
959
|
-
}));
|
|
960
|
-
|
|
961
|
-
//#endregion
|
|
962
|
-
//#region ../../node_modules/node-pty/lib/shared/conout.js
|
|
963
|
-
var require_conout = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
964
|
-
/**
|
|
965
|
-
* Copyright (c) 2020, Microsoft Corporation (MIT License).
|
|
966
|
-
*/
|
|
967
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
968
|
-
exports.getWorkerPipeName = void 0;
|
|
969
|
-
function getWorkerPipeName(conoutPipeName) {
|
|
970
|
-
return conoutPipeName + "-worker";
|
|
971
|
-
}
|
|
972
|
-
exports.getWorkerPipeName = getWorkerPipeName;
|
|
973
|
-
}));
|
|
974
|
-
|
|
975
|
-
//#endregion
|
|
976
|
-
//#region ../../node_modules/node-pty/lib/windowsConoutConnection.js
|
|
977
|
-
var require_windowsConoutConnection = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
978
|
-
/**
|
|
979
|
-
* Copyright (c) 2020, Microsoft Corporation (MIT License).
|
|
980
|
-
*/
|
|
981
|
-
var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
|
|
982
|
-
function adopt(value) {
|
|
983
|
-
return value instanceof P ? value : new P(function(resolve) {
|
|
984
|
-
resolve(value);
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
return new (P || (P = Promise))(function(resolve, reject) {
|
|
988
|
-
function fulfilled(value) {
|
|
989
|
-
try {
|
|
990
|
-
step(generator.next(value));
|
|
991
|
-
} catch (e) {
|
|
992
|
-
reject(e);
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
function rejected(value) {
|
|
996
|
-
try {
|
|
997
|
-
step(generator["throw"](value));
|
|
998
|
-
} catch (e) {
|
|
999
|
-
reject(e);
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
function step(result) {
|
|
1003
|
-
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
1004
|
-
}
|
|
1005
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
1006
|
-
});
|
|
1007
|
-
};
|
|
1008
|
-
var __generator = exports && exports.__generator || function(thisArg, body) {
|
|
1009
|
-
var _ = {
|
|
1010
|
-
label: 0,
|
|
1011
|
-
sent: function() {
|
|
1012
|
-
if (t[0] & 1) throw t[1];
|
|
1013
|
-
return t[1];
|
|
1014
|
-
},
|
|
1015
|
-
trys: [],
|
|
1016
|
-
ops: []
|
|
1017
|
-
}, f, y, t, g;
|
|
1018
|
-
return g = {
|
|
1019
|
-
next: verb(0),
|
|
1020
|
-
"throw": verb(1),
|
|
1021
|
-
"return": verb(2)
|
|
1022
|
-
}, typeof Symbol === "function" && (g[Symbol.iterator] = function() {
|
|
1023
|
-
return this;
|
|
1024
|
-
}), g;
|
|
1025
|
-
function verb(n) {
|
|
1026
|
-
return function(v) {
|
|
1027
|
-
return step([n, v]);
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
function step(op) {
|
|
1031
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
1032
|
-
while (_) try {
|
|
1033
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
1034
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
1035
|
-
switch (op[0]) {
|
|
1036
|
-
case 0:
|
|
1037
|
-
case 1:
|
|
1038
|
-
t = op;
|
|
1039
|
-
break;
|
|
1040
|
-
case 4:
|
|
1041
|
-
_.label++;
|
|
1042
|
-
return {
|
|
1043
|
-
value: op[1],
|
|
1044
|
-
done: false
|
|
1045
|
-
};
|
|
1046
|
-
case 5:
|
|
1047
|
-
_.label++;
|
|
1048
|
-
y = op[1];
|
|
1049
|
-
op = [0];
|
|
1050
|
-
continue;
|
|
1051
|
-
case 7:
|
|
1052
|
-
op = _.ops.pop();
|
|
1053
|
-
_.trys.pop();
|
|
1054
|
-
continue;
|
|
1055
|
-
default:
|
|
1056
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
|
|
1057
|
-
_ = 0;
|
|
1058
|
-
continue;
|
|
1059
|
-
}
|
|
1060
|
-
if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
|
|
1061
|
-
_.label = op[1];
|
|
1062
|
-
break;
|
|
1063
|
-
}
|
|
1064
|
-
if (op[0] === 6 && _.label < t[1]) {
|
|
1065
|
-
_.label = t[1];
|
|
1066
|
-
t = op;
|
|
1067
|
-
break;
|
|
1068
|
-
}
|
|
1069
|
-
if (t && _.label < t[2]) {
|
|
1070
|
-
_.label = t[2];
|
|
1071
|
-
_.ops.push(op);
|
|
1072
|
-
break;
|
|
1073
|
-
}
|
|
1074
|
-
if (t[2]) _.ops.pop();
|
|
1075
|
-
_.trys.pop();
|
|
1076
|
-
continue;
|
|
1077
|
-
}
|
|
1078
|
-
op = body.call(thisArg, _);
|
|
1079
|
-
} catch (e) {
|
|
1080
|
-
op = [6, e];
|
|
1081
|
-
y = 0;
|
|
1082
|
-
} finally {
|
|
1083
|
-
f = t = 0;
|
|
1084
|
-
}
|
|
1085
|
-
if (op[0] & 5) throw op[1];
|
|
1086
|
-
return {
|
|
1087
|
-
value: op[0] ? op[1] : void 0,
|
|
1088
|
-
done: true
|
|
1089
|
-
};
|
|
1090
|
-
}
|
|
1091
|
-
};
|
|
1092
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1093
|
-
exports.ConoutConnection = void 0;
|
|
1094
|
-
var worker_threads_1 = require("worker_threads");
|
|
1095
|
-
var conout_1 = require_conout();
|
|
1096
|
-
var path_1 = require("path");
|
|
1097
|
-
var eventEmitter2_1 = require_eventEmitter2();
|
|
1098
|
-
/**
|
|
1099
|
-
* The amount of time to wait for additional data after the conpty shell process has exited before
|
|
1100
|
-
* shutting down the worker and sockets. The timer will be reset if a new data event comes in after
|
|
1101
|
-
* the timer has started.
|
|
1102
|
-
*/
|
|
1103
|
-
var FLUSH_DATA_INTERVAL = 1e3;
|
|
1104
|
-
/**
|
|
1105
|
-
* Connects to and manages the lifecycle of the conout socket. This socket must be drained on
|
|
1106
|
-
* another thread in order to avoid deadlocks where Conpty waits for the out socket to drain
|
|
1107
|
-
* when `ClosePseudoConsole` is called. This happens when data is being written to the terminal when
|
|
1108
|
-
* the pty is closed.
|
|
1109
|
-
*
|
|
1110
|
-
* See also:
|
|
1111
|
-
* - https://github.com/microsoft/node-pty/issues/375
|
|
1112
|
-
* - https://github.com/microsoft/vscode/issues/76548
|
|
1113
|
-
* - https://github.com/microsoft/terminal/issues/1810
|
|
1114
|
-
* - https://docs.microsoft.com/en-us/windows/console/closepseudoconsole
|
|
1115
|
-
*/
|
|
1116
|
-
var ConoutConnection = function() {
|
|
1117
|
-
function ConoutConnection(_conoutPipeName, _useConptyDll) {
|
|
1118
|
-
var _this = this;
|
|
1119
|
-
this._conoutPipeName = _conoutPipeName;
|
|
1120
|
-
this._useConptyDll = _useConptyDll;
|
|
1121
|
-
this._isDisposed = false;
|
|
1122
|
-
this._onReady = new eventEmitter2_1.EventEmitter2();
|
|
1123
|
-
var workerData = { conoutPipeName: _conoutPipeName };
|
|
1124
|
-
var scriptPath = __dirname.replace("node_modules.asar", "node_modules.asar.unpacked");
|
|
1125
|
-
this._worker = new worker_threads_1.Worker(path_1.join(scriptPath, "worker/conoutSocketWorker.js"), { workerData });
|
|
1126
|
-
this._worker.on("message", function(message) {
|
|
1127
|
-
switch (message) {
|
|
1128
|
-
case 1:
|
|
1129
|
-
_this._onReady.fire();
|
|
1130
|
-
return;
|
|
1131
|
-
default: console.warn("Unexpected ConoutWorkerMessage", message);
|
|
1132
|
-
}
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
Object.defineProperty(ConoutConnection.prototype, "onReady", {
|
|
1136
|
-
get: function() {
|
|
1137
|
-
return this._onReady.event;
|
|
1138
|
-
},
|
|
1139
|
-
enumerable: false,
|
|
1140
|
-
configurable: true
|
|
1141
|
-
});
|
|
1142
|
-
ConoutConnection.prototype.dispose = function() {
|
|
1143
|
-
if (!this._useConptyDll && this._isDisposed) return;
|
|
1144
|
-
this._isDisposed = true;
|
|
1145
|
-
this._drainDataAndClose();
|
|
1146
|
-
};
|
|
1147
|
-
ConoutConnection.prototype.connectSocket = function(socket) {
|
|
1148
|
-
socket.connect(conout_1.getWorkerPipeName(this._conoutPipeName));
|
|
1149
|
-
};
|
|
1150
|
-
ConoutConnection.prototype._drainDataAndClose = function() {
|
|
1151
|
-
var _this = this;
|
|
1152
|
-
if (this._drainTimeout) clearTimeout(this._drainTimeout);
|
|
1153
|
-
this._drainTimeout = setTimeout(function() {
|
|
1154
|
-
return _this._destroySocket();
|
|
1155
|
-
}, FLUSH_DATA_INTERVAL);
|
|
1156
|
-
};
|
|
1157
|
-
ConoutConnection.prototype._destroySocket = function() {
|
|
1158
|
-
return __awaiter(this, void 0, void 0, function() {
|
|
1159
|
-
return __generator(this, function(_a) {
|
|
1160
|
-
switch (_a.label) {
|
|
1161
|
-
case 0: return [4, this._worker.terminate()];
|
|
1162
|
-
case 1:
|
|
1163
|
-
_a.sent();
|
|
1164
|
-
return [2];
|
|
1165
|
-
}
|
|
1166
|
-
});
|
|
1167
|
-
});
|
|
1168
|
-
};
|
|
1169
|
-
return ConoutConnection;
|
|
1170
|
-
}();
|
|
1171
|
-
exports.ConoutConnection = ConoutConnection;
|
|
1172
|
-
}));
|
|
1173
|
-
|
|
1174
|
-
//#endregion
|
|
1175
|
-
//#region ../../node_modules/node-pty/lib/windowsPtyAgent.js
|
|
1176
|
-
var require_windowsPtyAgent = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
1177
|
-
/**
|
|
1178
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
|
1179
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
1180
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
1181
|
-
*/
|
|
1182
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1183
|
-
exports.argsToCommandLine = exports.WindowsPtyAgent = void 0;
|
|
1184
|
-
var fs$1 = require("fs");
|
|
1185
|
-
var os = require("os");
|
|
1186
|
-
var path$1 = require("path");
|
|
1187
|
-
var child_process_1 = require("child_process");
|
|
1188
|
-
var net_1 = require("net");
|
|
1189
|
-
var windowsConoutConnection_1 = require_windowsConoutConnection();
|
|
1190
|
-
var utils_1 = require_utils();
|
|
1191
|
-
var conptyNative;
|
|
1192
|
-
var winptyNative;
|
|
1193
|
-
/**
|
|
1194
|
-
* The amount of time to wait for additional data after the conpty shell process has exited before
|
|
1195
|
-
* shutting down the socket. The timer will be reset if a new data event comes in after the timer
|
|
1196
|
-
* has started.
|
|
1197
|
-
*/
|
|
1198
|
-
var FLUSH_DATA_INTERVAL = 1e3;
|
|
1199
|
-
/**
|
|
1200
|
-
* This agent sits between the WindowsTerminal class and provides a common interface for both conpty
|
|
1201
|
-
* and winpty.
|
|
1202
|
-
*/
|
|
1203
|
-
var WindowsPtyAgent = function() {
|
|
1204
|
-
function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty, _useConptyDll, conptyInheritCursor) {
|
|
1205
|
-
var _this = this;
|
|
1206
|
-
if (_useConptyDll === void 0) _useConptyDll = false;
|
|
1207
|
-
if (conptyInheritCursor === void 0) conptyInheritCursor = false;
|
|
1208
|
-
this._useConpty = _useConpty;
|
|
1209
|
-
this._useConptyDll = _useConptyDll;
|
|
1210
|
-
this._pid = 0;
|
|
1211
|
-
this._innerPid = 0;
|
|
1212
|
-
if (this._useConpty === void 0 || this._useConpty === true) this._useConpty = this._getWindowsBuildNumber() >= 18309;
|
|
1213
|
-
if (this._useConpty) {
|
|
1214
|
-
if (!conptyNative) conptyNative = utils_1.loadNativeModule("conpty").module;
|
|
1215
|
-
} else if (!winptyNative) winptyNative = utils_1.loadNativeModule("pty").module;
|
|
1216
|
-
this._ptyNative = this._useConpty ? conptyNative : winptyNative;
|
|
1217
|
-
cwd = path$1.resolve(cwd);
|
|
1218
|
-
var commandLine = argsToCommandLine(file, args);
|
|
1219
|
-
var term;
|
|
1220
|
-
if (this._useConpty) term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
|
|
1221
|
-
else {
|
|
1222
|
-
term = this._ptyNative.startProcess(file, commandLine, env, cwd, cols, rows, debug);
|
|
1223
|
-
this._pid = term.pid;
|
|
1224
|
-
this._innerPid = term.innerPid;
|
|
1225
|
-
}
|
|
1226
|
-
this._fd = term.fd;
|
|
1227
|
-
this._pty = term.pty;
|
|
1228
|
-
this._outSocket = new net_1.Socket();
|
|
1229
|
-
this._outSocket.setEncoding("utf8");
|
|
1230
|
-
this._conoutSocketWorker = new windowsConoutConnection_1.ConoutConnection(term.conout, this._useConptyDll);
|
|
1231
|
-
this._conoutSocketWorker.onReady(function() {
|
|
1232
|
-
_this._conoutSocketWorker.connectSocket(_this._outSocket);
|
|
1233
|
-
});
|
|
1234
|
-
this._outSocket.on("connect", function() {
|
|
1235
|
-
_this._outSocket.emit("ready_datapipe");
|
|
1236
|
-
});
|
|
1237
|
-
var inSocketFD = fs$1.openSync(term.conin, "w");
|
|
1238
|
-
this._inSocket = new net_1.Socket({
|
|
1239
|
-
fd: inSocketFD,
|
|
1240
|
-
readable: false,
|
|
1241
|
-
writable: true
|
|
1242
|
-
});
|
|
1243
|
-
this._inSocket.setEncoding("utf8");
|
|
1244
|
-
if (this._useConpty) this._innerPid = this._ptyNative.connect(this._pty, commandLine, cwd, env, this._useConptyDll, function(c) {
|
|
1245
|
-
return _this._$onProcessExit(c);
|
|
1246
|
-
}).pid;
|
|
1247
|
-
}
|
|
1248
|
-
Object.defineProperty(WindowsPtyAgent.prototype, "inSocket", {
|
|
1249
|
-
get: function() {
|
|
1250
|
-
return this._inSocket;
|
|
1251
|
-
},
|
|
1252
|
-
enumerable: false,
|
|
1253
|
-
configurable: true
|
|
1254
|
-
});
|
|
1255
|
-
Object.defineProperty(WindowsPtyAgent.prototype, "outSocket", {
|
|
1256
|
-
get: function() {
|
|
1257
|
-
return this._outSocket;
|
|
1258
|
-
},
|
|
1259
|
-
enumerable: false,
|
|
1260
|
-
configurable: true
|
|
1261
|
-
});
|
|
1262
|
-
Object.defineProperty(WindowsPtyAgent.prototype, "fd", {
|
|
1263
|
-
get: function() {
|
|
1264
|
-
return this._fd;
|
|
1265
|
-
},
|
|
1266
|
-
enumerable: false,
|
|
1267
|
-
configurable: true
|
|
1268
|
-
});
|
|
1269
|
-
Object.defineProperty(WindowsPtyAgent.prototype, "innerPid", {
|
|
1270
|
-
get: function() {
|
|
1271
|
-
return this._innerPid;
|
|
1272
|
-
},
|
|
1273
|
-
enumerable: false,
|
|
1274
|
-
configurable: true
|
|
1275
|
-
});
|
|
1276
|
-
Object.defineProperty(WindowsPtyAgent.prototype, "pty", {
|
|
1277
|
-
get: function() {
|
|
1278
|
-
return this._pty;
|
|
1279
|
-
},
|
|
1280
|
-
enumerable: false,
|
|
1281
|
-
configurable: true
|
|
1282
|
-
});
|
|
1283
|
-
WindowsPtyAgent.prototype.resize = function(cols, rows) {
|
|
1284
|
-
if (this._useConpty) {
|
|
1285
|
-
if (this._exitCode !== void 0) throw new Error("Cannot resize a pty that has already exited");
|
|
1286
|
-
this._ptyNative.resize(this._pty, cols, rows, this._useConptyDll);
|
|
1287
|
-
return;
|
|
1288
|
-
}
|
|
1289
|
-
this._ptyNative.resize(this._pid, cols, rows);
|
|
1290
|
-
};
|
|
1291
|
-
WindowsPtyAgent.prototype.clear = function() {
|
|
1292
|
-
if (this._useConpty) this._ptyNative.clear(this._pty, this._useConptyDll);
|
|
1293
|
-
};
|
|
1294
|
-
WindowsPtyAgent.prototype.kill = function() {
|
|
1295
|
-
var _this = this;
|
|
1296
|
-
if (this._useConpty) if (!this._useConptyDll) {
|
|
1297
|
-
this._inSocket.readable = false;
|
|
1298
|
-
this._outSocket.readable = false;
|
|
1299
|
-
this._getConsoleProcessList().then(function(consoleProcessList) {
|
|
1300
|
-
consoleProcessList.forEach(function(pid) {
|
|
1301
|
-
try {
|
|
1302
|
-
process.kill(pid);
|
|
1303
|
-
} catch (e) {}
|
|
1304
|
-
});
|
|
1305
|
-
});
|
|
1306
|
-
this._ptyNative.kill(this._pty, this._useConptyDll);
|
|
1307
|
-
this._conoutSocketWorker.dispose();
|
|
1308
|
-
} else {
|
|
1309
|
-
this._inSocket.destroy();
|
|
1310
|
-
this._ptyNative.kill(this._pty, this._useConptyDll);
|
|
1311
|
-
this._outSocket.on("data", function() {
|
|
1312
|
-
_this._conoutSocketWorker.dispose();
|
|
1313
|
-
});
|
|
1314
|
-
}
|
|
1315
|
-
else {
|
|
1316
|
-
var processList = this._ptyNative.getProcessList(this._pid);
|
|
1317
|
-
this._ptyNative.kill(this._pid, this._innerPid);
|
|
1318
|
-
processList.forEach(function(pid) {
|
|
1319
|
-
try {
|
|
1320
|
-
process.kill(pid);
|
|
1321
|
-
} catch (e) {}
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1324
|
-
};
|
|
1325
|
-
WindowsPtyAgent.prototype._getConsoleProcessList = function() {
|
|
1326
|
-
var _this = this;
|
|
1327
|
-
return new Promise(function(resolve) {
|
|
1328
|
-
var agent = child_process_1.fork(path$1.join(__dirname, "conpty_console_list_agent"), [_this._innerPid.toString()]);
|
|
1329
|
-
agent.on("message", function(message) {
|
|
1330
|
-
clearTimeout(timeout);
|
|
1331
|
-
resolve(message.consoleProcessList);
|
|
1332
|
-
});
|
|
1333
|
-
var timeout = setTimeout(function() {
|
|
1334
|
-
agent.kill();
|
|
1335
|
-
resolve([_this._innerPid]);
|
|
1336
|
-
}, 5e3);
|
|
1337
|
-
});
|
|
1338
|
-
};
|
|
1339
|
-
Object.defineProperty(WindowsPtyAgent.prototype, "exitCode", {
|
|
1340
|
-
get: function() {
|
|
1341
|
-
if (this._useConpty) return this._exitCode;
|
|
1342
|
-
var winptyExitCode = this._ptyNative.getExitCode(this._innerPid);
|
|
1343
|
-
return winptyExitCode === -1 ? void 0 : winptyExitCode;
|
|
1344
|
-
},
|
|
1345
|
-
enumerable: false,
|
|
1346
|
-
configurable: true
|
|
1347
|
-
});
|
|
1348
|
-
WindowsPtyAgent.prototype._getWindowsBuildNumber = function() {
|
|
1349
|
-
var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os.release());
|
|
1350
|
-
var buildNumber = 0;
|
|
1351
|
-
if (osVersion && osVersion.length === 4) buildNumber = parseInt(osVersion[3]);
|
|
1352
|
-
return buildNumber;
|
|
1353
|
-
};
|
|
1354
|
-
WindowsPtyAgent.prototype._generatePipeName = function() {
|
|
1355
|
-
return "conpty-" + Math.random() * 1e7;
|
|
1356
|
-
};
|
|
1357
|
-
/**
|
|
1358
|
-
* Triggered from the native side when a contpy process exits.
|
|
1359
|
-
*/
|
|
1360
|
-
WindowsPtyAgent.prototype._$onProcessExit = function(exitCode) {
|
|
1361
|
-
var _this = this;
|
|
1362
|
-
this._exitCode = exitCode;
|
|
1363
|
-
if (!this._useConptyDll) {
|
|
1364
|
-
this._flushDataAndCleanUp();
|
|
1365
|
-
this._outSocket.on("data", function() {
|
|
1366
|
-
return _this._flushDataAndCleanUp();
|
|
1367
|
-
});
|
|
1368
|
-
}
|
|
1369
|
-
};
|
|
1370
|
-
WindowsPtyAgent.prototype._flushDataAndCleanUp = function() {
|
|
1371
|
-
var _this = this;
|
|
1372
|
-
if (this._useConptyDll) return;
|
|
1373
|
-
if (this._closeTimeout) clearTimeout(this._closeTimeout);
|
|
1374
|
-
this._closeTimeout = setTimeout(function() {
|
|
1375
|
-
return _this._cleanUpProcess();
|
|
1376
|
-
}, FLUSH_DATA_INTERVAL);
|
|
1377
|
-
};
|
|
1378
|
-
WindowsPtyAgent.prototype._cleanUpProcess = function() {
|
|
1379
|
-
if (this._useConptyDll) return;
|
|
1380
|
-
this._inSocket.readable = false;
|
|
1381
|
-
this._outSocket.readable = false;
|
|
1382
|
-
this._outSocket.destroy();
|
|
1383
|
-
};
|
|
1384
|
-
return WindowsPtyAgent;
|
|
1385
|
-
}();
|
|
1386
|
-
exports.WindowsPtyAgent = WindowsPtyAgent;
|
|
1387
|
-
function argsToCommandLine(file, args) {
|
|
1388
|
-
if (isCommandLine(args)) {
|
|
1389
|
-
if (args.length === 0) return file;
|
|
1390
|
-
return argsToCommandLine(file, []) + " " + args;
|
|
1391
|
-
}
|
|
1392
|
-
var argv = [file];
|
|
1393
|
-
Array.prototype.push.apply(argv, args);
|
|
1394
|
-
var result = "";
|
|
1395
|
-
for (var argIndex = 0; argIndex < argv.length; argIndex++) {
|
|
1396
|
-
if (argIndex > 0) result += " ";
|
|
1397
|
-
var arg = argv[argIndex];
|
|
1398
|
-
var hasLopsidedEnclosingQuote = xOr(arg[0] !== "\"", arg[arg.length - 1] !== "\"");
|
|
1399
|
-
var hasNoEnclosingQuotes = arg[0] !== "\"" && arg[arg.length - 1] !== "\"";
|
|
1400
|
-
var quote = arg === "" || (arg.indexOf(" ") !== -1 || arg.indexOf(" ") !== -1) && arg.length > 1 && (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes);
|
|
1401
|
-
if (quote) result += "\"";
|
|
1402
|
-
var bsCount = 0;
|
|
1403
|
-
for (var i = 0; i < arg.length; i++) {
|
|
1404
|
-
var p = arg[i];
|
|
1405
|
-
if (p === "\\") bsCount++;
|
|
1406
|
-
else if (p === "\"") {
|
|
1407
|
-
result += repeatText("\\", bsCount * 2 + 1);
|
|
1408
|
-
result += "\"";
|
|
1409
|
-
bsCount = 0;
|
|
1410
|
-
} else {
|
|
1411
|
-
result += repeatText("\\", bsCount);
|
|
1412
|
-
bsCount = 0;
|
|
1413
|
-
result += p;
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
if (quote) {
|
|
1417
|
-
result += repeatText("\\", bsCount * 2);
|
|
1418
|
-
result += "\"";
|
|
1419
|
-
} else result += repeatText("\\", bsCount);
|
|
1420
|
-
}
|
|
1421
|
-
return result;
|
|
1422
|
-
}
|
|
1423
|
-
exports.argsToCommandLine = argsToCommandLine;
|
|
1424
|
-
function isCommandLine(args) {
|
|
1425
|
-
return typeof args === "string";
|
|
1426
|
-
}
|
|
1427
|
-
function repeatText(text, count) {
|
|
1428
|
-
var result = "";
|
|
1429
|
-
for (var i = 0; i < count; i++) result += text;
|
|
1430
|
-
return result;
|
|
1431
|
-
}
|
|
1432
|
-
function xOr(arg1, arg2) {
|
|
1433
|
-
return arg1 && !arg2 || !arg1 && arg2;
|
|
1434
|
-
}
|
|
1435
|
-
}));
|
|
1436
|
-
|
|
1437
|
-
//#endregion
|
|
1438
|
-
//#region ../../node_modules/node-pty/lib/windowsTerminal.js
|
|
1439
|
-
var require_windowsTerminal = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
1440
|
-
/**
|
|
1441
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
|
1442
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
1443
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
1444
|
-
*/
|
|
1445
|
-
var __extends = exports && exports.__extends || (function() {
|
|
1446
|
-
var extendStatics = function(d, b) {
|
|
1447
|
-
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d, b) {
|
|
1448
|
-
d.__proto__ = b;
|
|
1449
|
-
} || function(d, b) {
|
|
1450
|
-
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
|
|
1451
|
-
};
|
|
1452
|
-
return extendStatics(d, b);
|
|
1453
|
-
};
|
|
1454
|
-
return function(d, b) {
|
|
1455
|
-
extendStatics(d, b);
|
|
1456
|
-
function __() {
|
|
1457
|
-
this.constructor = d;
|
|
1458
|
-
}
|
|
1459
|
-
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
1460
|
-
};
|
|
1461
|
-
})();
|
|
1462
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1463
|
-
exports.WindowsTerminal = void 0;
|
|
1464
|
-
var terminal_1 = require_terminal();
|
|
1465
|
-
var windowsPtyAgent_1 = require_windowsPtyAgent();
|
|
1466
|
-
var utils_1 = require_utils();
|
|
1467
|
-
var DEFAULT_FILE = "cmd.exe";
|
|
1468
|
-
var DEFAULT_NAME = "Windows Shell";
|
|
1469
|
-
var WindowsTerminal = function(_super) {
|
|
1470
|
-
__extends(WindowsTerminal, _super);
|
|
1471
|
-
function WindowsTerminal(file, args, opt) {
|
|
1472
|
-
var _this = _super.call(this, opt) || this;
|
|
1473
|
-
_this._checkType("args", args, "string", true);
|
|
1474
|
-
args = args || [];
|
|
1475
|
-
file = file || DEFAULT_FILE;
|
|
1476
|
-
opt = opt || {};
|
|
1477
|
-
opt.env = opt.env || process.env;
|
|
1478
|
-
if (opt.encoding) console.warn("Setting encoding on Windows is not supported");
|
|
1479
|
-
var env = utils_1.assign({}, opt.env);
|
|
1480
|
-
_this._cols = opt.cols || terminal_1.DEFAULT_COLS;
|
|
1481
|
-
_this._rows = opt.rows || terminal_1.DEFAULT_ROWS;
|
|
1482
|
-
var cwd = opt.cwd || process.cwd();
|
|
1483
|
-
var name = opt.name || env.TERM || DEFAULT_NAME;
|
|
1484
|
-
var parsedEnv = _this._parseEnv(env);
|
|
1485
|
-
_this._isReady = false;
|
|
1486
|
-
_this._deferreds = [];
|
|
1487
|
-
_this._agent = new windowsPtyAgent_1.WindowsPtyAgent(file, args, parsedEnv, cwd, _this._cols, _this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
|
|
1488
|
-
_this._socket = _this._agent.outSocket;
|
|
1489
|
-
_this._pid = _this._agent.innerPid;
|
|
1490
|
-
_this._fd = _this._agent.fd;
|
|
1491
|
-
_this._pty = _this._agent.pty;
|
|
1492
|
-
_this._socket.on("ready_datapipe", function() {
|
|
1493
|
-
_this._socket.once("data", function() {
|
|
1494
|
-
if (!_this._isReady) {
|
|
1495
|
-
_this._isReady = true;
|
|
1496
|
-
_this._deferreds.forEach(function(fn) {
|
|
1497
|
-
fn.run();
|
|
1498
|
-
});
|
|
1499
|
-
_this._deferreds = [];
|
|
1500
|
-
}
|
|
1501
|
-
});
|
|
1502
|
-
_this._socket.on("error", function(err) {
|
|
1503
|
-
_this._close();
|
|
1504
|
-
if (err.code) {
|
|
1505
|
-
if (~err.code.indexOf("errno 5") || ~err.code.indexOf("EIO")) return;
|
|
1506
|
-
}
|
|
1507
|
-
if (_this.listeners("error").length < 2) throw err;
|
|
1508
|
-
});
|
|
1509
|
-
_this._socket.on("close", function() {
|
|
1510
|
-
_this.emit("exit", _this._agent.exitCode);
|
|
1511
|
-
_this._close();
|
|
1512
|
-
});
|
|
1513
|
-
});
|
|
1514
|
-
_this._file = file;
|
|
1515
|
-
_this._name = name;
|
|
1516
|
-
_this._readable = true;
|
|
1517
|
-
_this._writable = true;
|
|
1518
|
-
_this._forwardEvents();
|
|
1519
|
-
return _this;
|
|
1520
|
-
}
|
|
1521
|
-
WindowsTerminal.prototype._write = function(data) {
|
|
1522
|
-
this._defer(this._doWrite, data);
|
|
1523
|
-
};
|
|
1524
|
-
WindowsTerminal.prototype._doWrite = function(data) {
|
|
1525
|
-
this._agent.inSocket.write(data);
|
|
1526
|
-
};
|
|
1527
|
-
/**
|
|
1528
|
-
* openpty
|
|
1529
|
-
*/
|
|
1530
|
-
WindowsTerminal.open = function(options) {
|
|
1531
|
-
throw new Error("open() not supported on windows, use Fork() instead.");
|
|
1532
|
-
};
|
|
1533
|
-
/**
|
|
1534
|
-
* TTY
|
|
1535
|
-
*/
|
|
1536
|
-
WindowsTerminal.prototype.resize = function(cols, rows) {
|
|
1537
|
-
var _this = this;
|
|
1538
|
-
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) throw new Error("resizing must be done using positive cols and rows");
|
|
1539
|
-
this._deferNoArgs(function() {
|
|
1540
|
-
_this._agent.resize(cols, rows);
|
|
1541
|
-
_this._cols = cols;
|
|
1542
|
-
_this._rows = rows;
|
|
1543
|
-
});
|
|
1544
|
-
};
|
|
1545
|
-
WindowsTerminal.prototype.clear = function() {
|
|
1546
|
-
var _this = this;
|
|
1547
|
-
this._deferNoArgs(function() {
|
|
1548
|
-
_this._agent.clear();
|
|
1549
|
-
});
|
|
1550
|
-
};
|
|
1551
|
-
WindowsTerminal.prototype.destroy = function() {
|
|
1552
|
-
var _this = this;
|
|
1553
|
-
this._deferNoArgs(function() {
|
|
1554
|
-
_this.kill();
|
|
1555
|
-
});
|
|
1556
|
-
};
|
|
1557
|
-
WindowsTerminal.prototype.kill = function(signal) {
|
|
1558
|
-
var _this = this;
|
|
1559
|
-
this._deferNoArgs(function() {
|
|
1560
|
-
if (signal) throw new Error("Signals not supported on windows.");
|
|
1561
|
-
_this._close();
|
|
1562
|
-
_this._agent.kill();
|
|
1563
|
-
});
|
|
1564
|
-
};
|
|
1565
|
-
WindowsTerminal.prototype._deferNoArgs = function(deferredFn) {
|
|
1566
|
-
var _this = this;
|
|
1567
|
-
if (this._isReady) {
|
|
1568
|
-
deferredFn.call(this);
|
|
1569
|
-
return;
|
|
1570
|
-
}
|
|
1571
|
-
this._deferreds.push({ run: function() {
|
|
1572
|
-
return deferredFn.call(_this);
|
|
1573
|
-
} });
|
|
1574
|
-
};
|
|
1575
|
-
WindowsTerminal.prototype._defer = function(deferredFn, arg) {
|
|
1576
|
-
var _this = this;
|
|
1577
|
-
if (this._isReady) {
|
|
1578
|
-
deferredFn.call(this, arg);
|
|
1579
|
-
return;
|
|
1580
|
-
}
|
|
1581
|
-
this._deferreds.push({ run: function() {
|
|
1582
|
-
return deferredFn.call(_this, arg);
|
|
1583
|
-
} });
|
|
1584
|
-
};
|
|
1585
|
-
Object.defineProperty(WindowsTerminal.prototype, "process", {
|
|
1586
|
-
get: function() {
|
|
1587
|
-
return this._name;
|
|
1588
|
-
},
|
|
1589
|
-
enumerable: false,
|
|
1590
|
-
configurable: true
|
|
1591
|
-
});
|
|
1592
|
-
Object.defineProperty(WindowsTerminal.prototype, "master", {
|
|
1593
|
-
get: function() {
|
|
1594
|
-
throw new Error("master is not supported on Windows");
|
|
1595
|
-
},
|
|
1596
|
-
enumerable: false,
|
|
1597
|
-
configurable: true
|
|
1598
|
-
});
|
|
1599
|
-
Object.defineProperty(WindowsTerminal.prototype, "slave", {
|
|
1600
|
-
get: function() {
|
|
1601
|
-
throw new Error("slave is not supported on Windows");
|
|
1602
|
-
},
|
|
1603
|
-
enumerable: false,
|
|
1604
|
-
configurable: true
|
|
1605
|
-
});
|
|
1606
|
-
return WindowsTerminal;
|
|
1607
|
-
}(terminal_1.Terminal);
|
|
1608
|
-
exports.WindowsTerminal = WindowsTerminal;
|
|
1609
|
-
}));
|
|
1610
|
-
|
|
1611
|
-
//#endregion
|
|
1612
|
-
//#region ../../node_modules/node-pty/lib/unixTerminal.js
|
|
1613
|
-
var require_unixTerminal = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
1614
|
-
var __extends = exports && exports.__extends || (function() {
|
|
1615
|
-
var extendStatics = function(d, b) {
|
|
1616
|
-
extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function(d, b) {
|
|
1617
|
-
d.__proto__ = b;
|
|
1618
|
-
} || function(d, b) {
|
|
1619
|
-
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
|
|
1620
|
-
};
|
|
1621
|
-
return extendStatics(d, b);
|
|
1622
|
-
};
|
|
1623
|
-
return function(d, b) {
|
|
1624
|
-
extendStatics(d, b);
|
|
1625
|
-
function __() {
|
|
1626
|
-
this.constructor = d;
|
|
1627
|
-
}
|
|
1628
|
-
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
1629
|
-
};
|
|
1630
|
-
})();
|
|
1631
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1632
|
-
exports.UnixTerminal = void 0;
|
|
1633
|
-
/**
|
|
1634
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
|
|
1635
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
1636
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
1637
|
-
*/
|
|
1638
|
-
var fs = require("fs");
|
|
1639
|
-
var path = require("path");
|
|
1640
|
-
var tty = require("tty");
|
|
1641
|
-
var terminal_1 = require_terminal();
|
|
1642
|
-
var utils_1 = require_utils();
|
|
1643
|
-
var native = utils_1.loadNativeModule("pty");
|
|
1644
|
-
var pty = native.module;
|
|
1645
|
-
var helperPath = native.dir + "/spawn-helper";
|
|
1646
|
-
helperPath = path.resolve(__dirname, helperPath);
|
|
1647
|
-
helperPath = helperPath.replace("app.asar", "app.asar.unpacked");
|
|
1648
|
-
helperPath = helperPath.replace("node_modules.asar", "node_modules.asar.unpacked");
|
|
1649
|
-
var DEFAULT_FILE = "sh";
|
|
1650
|
-
var DEFAULT_NAME = "xterm";
|
|
1651
|
-
var DESTROY_SOCKET_TIMEOUT_MS = 200;
|
|
1652
|
-
var UnixTerminal = function(_super) {
|
|
1653
|
-
__extends(UnixTerminal, _super);
|
|
1654
|
-
function UnixTerminal(file, args, opt) {
|
|
1655
|
-
var _a, _b;
|
|
1656
|
-
var _this = _super.call(this, opt) || this;
|
|
1657
|
-
_this._boundClose = false;
|
|
1658
|
-
_this._emittedClose = false;
|
|
1659
|
-
if (typeof args === "string") throw new Error("args as a string is not supported on unix.");
|
|
1660
|
-
args = args || [];
|
|
1661
|
-
file = file || DEFAULT_FILE;
|
|
1662
|
-
opt = opt || {};
|
|
1663
|
-
opt.env = opt.env || process.env;
|
|
1664
|
-
_this._cols = opt.cols || terminal_1.DEFAULT_COLS;
|
|
1665
|
-
_this._rows = opt.rows || terminal_1.DEFAULT_ROWS;
|
|
1666
|
-
var uid = (_a = opt.uid) !== null && _a !== void 0 ? _a : -1;
|
|
1667
|
-
var gid = (_b = opt.gid) !== null && _b !== void 0 ? _b : -1;
|
|
1668
|
-
var env = utils_1.assign({}, opt.env);
|
|
1669
|
-
if (opt.env === process.env) _this._sanitizeEnv(env);
|
|
1670
|
-
var cwd = opt.cwd || process.cwd();
|
|
1671
|
-
env.PWD = cwd;
|
|
1672
|
-
var name = opt.name || env.TERM || DEFAULT_NAME;
|
|
1673
|
-
env.TERM = name;
|
|
1674
|
-
var parsedEnv = _this._parseEnv(env);
|
|
1675
|
-
var encoding = opt.encoding === void 0 ? "utf8" : opt.encoding;
|
|
1676
|
-
var onexit = function(code, signal) {
|
|
1677
|
-
if (!_this._emittedClose) {
|
|
1678
|
-
if (_this._boundClose) return;
|
|
1679
|
-
_this._boundClose = true;
|
|
1680
|
-
var timeout_1 = setTimeout(function() {
|
|
1681
|
-
timeout_1 = null;
|
|
1682
|
-
_this._socket.destroy();
|
|
1683
|
-
}, DESTROY_SOCKET_TIMEOUT_MS);
|
|
1684
|
-
_this.once("close", function() {
|
|
1685
|
-
if (timeout_1 !== null) clearTimeout(timeout_1);
|
|
1686
|
-
_this.emit("exit", code, signal);
|
|
1687
|
-
});
|
|
1688
|
-
return;
|
|
1689
|
-
}
|
|
1690
|
-
_this.emit("exit", code, signal);
|
|
1691
|
-
};
|
|
1692
|
-
var term = pty.fork(file, args, parsedEnv, cwd, _this._cols, _this._rows, uid, gid, encoding === "utf8", helperPath, onexit);
|
|
1693
|
-
_this._socket = new tty.ReadStream(term.fd);
|
|
1694
|
-
if (encoding !== null) _this._socket.setEncoding(encoding);
|
|
1695
|
-
_this._writeStream = new CustomWriteStream(term.fd, encoding || void 0);
|
|
1696
|
-
_this._socket.on("error", function(err) {
|
|
1697
|
-
if (err.code) {
|
|
1698
|
-
if (~err.code.indexOf("EAGAIN")) return;
|
|
1699
|
-
}
|
|
1700
|
-
_this._close();
|
|
1701
|
-
if (!_this._emittedClose) {
|
|
1702
|
-
_this._emittedClose = true;
|
|
1703
|
-
_this.emit("close");
|
|
1704
|
-
}
|
|
1705
|
-
if (err.code) {
|
|
1706
|
-
if (~err.code.indexOf("errno 5") || ~err.code.indexOf("EIO")) return;
|
|
1707
|
-
}
|
|
1708
|
-
if (_this.listeners("error").length < 2) throw err;
|
|
1709
|
-
});
|
|
1710
|
-
_this._pid = term.pid;
|
|
1711
|
-
_this._fd = term.fd;
|
|
1712
|
-
_this._pty = term.pty;
|
|
1713
|
-
_this._file = file;
|
|
1714
|
-
_this._name = name;
|
|
1715
|
-
_this._readable = true;
|
|
1716
|
-
_this._writable = true;
|
|
1717
|
-
_this._socket.on("close", function() {
|
|
1718
|
-
if (_this._emittedClose) return;
|
|
1719
|
-
_this._emittedClose = true;
|
|
1720
|
-
_this._close();
|
|
1721
|
-
_this.emit("close");
|
|
1722
|
-
});
|
|
1723
|
-
_this._forwardEvents();
|
|
1724
|
-
return _this;
|
|
1725
|
-
}
|
|
1726
|
-
Object.defineProperty(UnixTerminal.prototype, "master", {
|
|
1727
|
-
get: function() {
|
|
1728
|
-
return this._master;
|
|
1729
|
-
},
|
|
1730
|
-
enumerable: false,
|
|
1731
|
-
configurable: true
|
|
1732
|
-
});
|
|
1733
|
-
Object.defineProperty(UnixTerminal.prototype, "slave", {
|
|
1734
|
-
get: function() {
|
|
1735
|
-
return this._slave;
|
|
1736
|
-
},
|
|
1737
|
-
enumerable: false,
|
|
1738
|
-
configurable: true
|
|
1739
|
-
});
|
|
1740
|
-
UnixTerminal.prototype._write = function(data) {
|
|
1741
|
-
this._writeStream.write(data);
|
|
1742
|
-
};
|
|
1743
|
-
Object.defineProperty(UnixTerminal.prototype, "fd", {
|
|
1744
|
-
get: function() {
|
|
1745
|
-
return this._fd;
|
|
1746
|
-
},
|
|
1747
|
-
enumerable: false,
|
|
1748
|
-
configurable: true
|
|
1749
|
-
});
|
|
1750
|
-
Object.defineProperty(UnixTerminal.prototype, "ptsName", {
|
|
1751
|
-
get: function() {
|
|
1752
|
-
return this._pty;
|
|
1753
|
-
},
|
|
1754
|
-
enumerable: false,
|
|
1755
|
-
configurable: true
|
|
1756
|
-
});
|
|
1757
|
-
/**
|
|
1758
|
-
* openpty
|
|
1759
|
-
*/
|
|
1760
|
-
UnixTerminal.open = function(opt) {
|
|
1761
|
-
var self = Object.create(UnixTerminal.prototype);
|
|
1762
|
-
opt = opt || {};
|
|
1763
|
-
if (arguments.length > 1) opt = {
|
|
1764
|
-
cols: arguments[1],
|
|
1765
|
-
rows: arguments[2]
|
|
1766
|
-
};
|
|
1767
|
-
var cols = opt.cols || terminal_1.DEFAULT_COLS;
|
|
1768
|
-
var rows = opt.rows || terminal_1.DEFAULT_ROWS;
|
|
1769
|
-
var encoding = opt.encoding === void 0 ? "utf8" : opt.encoding;
|
|
1770
|
-
var term = pty.open(cols, rows);
|
|
1771
|
-
self._master = new tty.ReadStream(term.master);
|
|
1772
|
-
if (encoding !== null) self._master.setEncoding(encoding);
|
|
1773
|
-
self._master.resume();
|
|
1774
|
-
self._slave = new tty.ReadStream(term.slave);
|
|
1775
|
-
if (encoding !== null) self._slave.setEncoding(encoding);
|
|
1776
|
-
self._slave.resume();
|
|
1777
|
-
self._socket = self._master;
|
|
1778
|
-
self._pid = -1;
|
|
1779
|
-
self._fd = term.master;
|
|
1780
|
-
self._pty = term.pty;
|
|
1781
|
-
self._file = process.argv[0] || "node";
|
|
1782
|
-
self._name = process.env.TERM || "";
|
|
1783
|
-
self._readable = true;
|
|
1784
|
-
self._writable = true;
|
|
1785
|
-
self._socket.on("error", function(err) {
|
|
1786
|
-
self._close();
|
|
1787
|
-
if (self.listeners("error").length < 2) throw err;
|
|
1788
|
-
});
|
|
1789
|
-
self._socket.on("close", function() {
|
|
1790
|
-
self._close();
|
|
1791
|
-
});
|
|
1792
|
-
return self;
|
|
1793
|
-
};
|
|
1794
|
-
UnixTerminal.prototype.destroy = function() {
|
|
1795
|
-
var _this = this;
|
|
1796
|
-
this._close();
|
|
1797
|
-
this._socket.once("close", function() {
|
|
1798
|
-
_this.kill("SIGHUP");
|
|
1799
|
-
});
|
|
1800
|
-
this._socket.destroy();
|
|
1801
|
-
this._writeStream.dispose();
|
|
1802
|
-
};
|
|
1803
|
-
UnixTerminal.prototype.kill = function(signal) {
|
|
1804
|
-
try {
|
|
1805
|
-
process.kill(this.pid, signal || "SIGHUP");
|
|
1806
|
-
} catch (e) {}
|
|
1807
|
-
};
|
|
1808
|
-
Object.defineProperty(UnixTerminal.prototype, "process", {
|
|
1809
|
-
get: function() {
|
|
1810
|
-
if (process.platform === "darwin") {
|
|
1811
|
-
var title = pty.process(this._fd);
|
|
1812
|
-
return title !== "kernel_task" ? title : this._file;
|
|
1813
|
-
}
|
|
1814
|
-
return pty.process(this._fd, this._pty) || this._file;
|
|
1815
|
-
},
|
|
1816
|
-
enumerable: false,
|
|
1817
|
-
configurable: true
|
|
1818
|
-
});
|
|
1819
|
-
/**
|
|
1820
|
-
* TTY
|
|
1821
|
-
*/
|
|
1822
|
-
UnixTerminal.prototype.resize = function(cols, rows) {
|
|
1823
|
-
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) throw new Error("resizing must be done using positive cols and rows");
|
|
1824
|
-
pty.resize(this._fd, cols, rows);
|
|
1825
|
-
this._cols = cols;
|
|
1826
|
-
this._rows = rows;
|
|
1827
|
-
};
|
|
1828
|
-
UnixTerminal.prototype.clear = function() {};
|
|
1829
|
-
UnixTerminal.prototype._sanitizeEnv = function(env) {
|
|
1830
|
-
delete env["TMUX"];
|
|
1831
|
-
delete env["TMUX_PANE"];
|
|
1832
|
-
delete env["STY"];
|
|
1833
|
-
delete env["WINDOW"];
|
|
1834
|
-
delete env["WINDOWID"];
|
|
1835
|
-
delete env["TERMCAP"];
|
|
1836
|
-
delete env["COLUMNS"];
|
|
1837
|
-
delete env["LINES"];
|
|
1838
|
-
};
|
|
1839
|
-
return UnixTerminal;
|
|
1840
|
-
}(terminal_1.Terminal);
|
|
1841
|
-
exports.UnixTerminal = UnixTerminal;
|
|
1842
|
-
/**
|
|
1843
|
-
* A custom write stream that writes directly to a file descriptor with proper
|
|
1844
|
-
* handling of backpressure and errors. This avoids some event loop exhaustion
|
|
1845
|
-
* issues that can occur when using the standard APIs in Node.
|
|
1846
|
-
*/
|
|
1847
|
-
var CustomWriteStream = function() {
|
|
1848
|
-
function CustomWriteStream(_fd, _encoding) {
|
|
1849
|
-
this._fd = _fd;
|
|
1850
|
-
this._encoding = _encoding;
|
|
1851
|
-
this._writeQueue = [];
|
|
1852
|
-
}
|
|
1853
|
-
CustomWriteStream.prototype.dispose = function() {
|
|
1854
|
-
clearImmediate(this._writeImmediate);
|
|
1855
|
-
this._writeImmediate = void 0;
|
|
1856
|
-
};
|
|
1857
|
-
CustomWriteStream.prototype.write = function(data) {
|
|
1858
|
-
var buffer = typeof data === "string" ? Buffer.from(data, this._encoding) : Buffer.from(data);
|
|
1859
|
-
if (buffer.byteLength !== 0) {
|
|
1860
|
-
this._writeQueue.push({
|
|
1861
|
-
buffer,
|
|
1862
|
-
offset: 0
|
|
1863
|
-
});
|
|
1864
|
-
if (this._writeQueue.length === 1) this._processWriteQueue();
|
|
1865
|
-
}
|
|
1866
|
-
};
|
|
1867
|
-
CustomWriteStream.prototype._processWriteQueue = function() {
|
|
1868
|
-
var _this = this;
|
|
1869
|
-
this._writeImmediate = void 0;
|
|
1870
|
-
if (this._writeQueue.length === 0) return;
|
|
1871
|
-
var task = this._writeQueue[0];
|
|
1872
|
-
fs.write(this._fd, task.buffer, task.offset, function(err, written) {
|
|
1873
|
-
if (err) {
|
|
1874
|
-
if ("code" in err && err.code === "EAGAIN") _this._writeImmediate = setImmediate(function() {
|
|
1875
|
-
return _this._processWriteQueue();
|
|
1876
|
-
});
|
|
1877
|
-
else {
|
|
1878
|
-
_this._writeQueue.length = 0;
|
|
1879
|
-
console.error("Unhandled pty write error", err);
|
|
1880
|
-
}
|
|
1881
|
-
return;
|
|
1882
|
-
}
|
|
1883
|
-
task.offset += written;
|
|
1884
|
-
if (task.offset >= task.buffer.byteLength) _this._writeQueue.shift();
|
|
1885
|
-
_this._processWriteQueue();
|
|
1886
|
-
});
|
|
1887
|
-
};
|
|
1888
|
-
return CustomWriteStream;
|
|
1889
|
-
}();
|
|
1890
|
-
}));
|
|
1891
|
-
|
|
1892
|
-
//#endregion
|
|
1893
|
-
//#region ../../node_modules/node-pty/lib/index.js
|
|
1894
|
-
var require_lib = /* @__PURE__ */ require_cli.__commonJSMin(((exports) => {
|
|
1895
|
-
/**
|
|
1896
|
-
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
|
|
1897
|
-
* Copyright (c) 2016, Daniel Imms (MIT License).
|
|
1898
|
-
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
1899
|
-
*/
|
|
1900
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1901
|
-
exports.native = exports.open = exports.createTerminal = exports.fork = exports.spawn = void 0;
|
|
1902
|
-
var utils_1 = require_utils();
|
|
1903
|
-
var terminalCtor;
|
|
1904
|
-
if (process.platform === "win32") terminalCtor = require_windowsTerminal().WindowsTerminal;
|
|
1905
|
-
else terminalCtor = require_unixTerminal().UnixTerminal;
|
|
1906
|
-
/**
|
|
1907
|
-
* Forks a process as a pseudoterminal.
|
|
1908
|
-
* @param file The file to launch.
|
|
1909
|
-
* @param args The file's arguments as argv (string[]) or in a pre-escaped
|
|
1910
|
-
* CommandLine format (string). Note that the CommandLine option is only
|
|
1911
|
-
* available on Windows and is expected to be escaped properly.
|
|
1912
|
-
* @param options The options of the terminal.
|
|
1913
|
-
* @throws When the file passed to spawn with does not exists.
|
|
1914
|
-
* @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
|
|
1915
|
-
* @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
|
|
1916
|
-
* @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
|
|
1917
|
-
*/
|
|
1918
|
-
function spawn(file, args, opt) {
|
|
1919
|
-
return new terminalCtor(file, args, opt);
|
|
1920
|
-
}
|
|
1921
|
-
exports.spawn = spawn;
|
|
1922
|
-
/** @deprecated */
|
|
1923
|
-
function fork(file, args, opt) {
|
|
1924
|
-
return new terminalCtor(file, args, opt);
|
|
1925
|
-
}
|
|
1926
|
-
exports.fork = fork;
|
|
1927
|
-
/** @deprecated */
|
|
1928
|
-
function createTerminal(file, args, opt) {
|
|
1929
|
-
return new terminalCtor(file, args, opt);
|
|
1930
|
-
}
|
|
1931
|
-
exports.createTerminal = createTerminal;
|
|
1932
|
-
function open(options) {
|
|
1933
|
-
return terminalCtor.open(options);
|
|
1934
|
-
}
|
|
1935
|
-
exports.open = open;
|
|
1936
|
-
/**
|
|
1937
|
-
* Expose the native API when not Windows, note that this is not public API and
|
|
1938
|
-
* could be removed at any time.
|
|
1939
|
-
*/
|
|
1940
|
-
exports.native = process.platform !== "win32" ? utils_1.loadNativeModule("pty").module : null;
|
|
1941
|
-
}));
|
|
1942
|
-
|
|
1943
|
-
//#endregion
|
|
1944
|
-
//#region src/backend/terminal.ts
|
|
1945
|
-
const MAX_SCROLLBACK = 5e4;
|
|
1946
|
-
var SessionStore = class {
|
|
1947
|
-
scrollback = [];
|
|
1948
|
-
append(data) {
|
|
1949
|
-
this.scrollback.push(data);
|
|
1950
|
-
if (this.scrollback.length > MAX_SCROLLBACK * 1.5) this.scrollback = this.scrollback.slice(-MAX_SCROLLBACK);
|
|
1951
|
-
}
|
|
1952
|
-
getAll() {
|
|
1953
|
-
return this.scrollback.join("");
|
|
1954
|
-
}
|
|
1955
|
-
clear() {
|
|
1956
|
-
this.scrollback = [];
|
|
1957
|
-
}
|
|
1958
|
-
};
|
|
1959
|
-
let pty = null;
|
|
1960
|
-
let ptyLoadError = null;
|
|
1961
|
-
try {
|
|
1962
|
-
pty = require_lib();
|
|
1963
|
-
} catch (err) {
|
|
1964
|
-
ptyLoadError = err instanceof Error ? err.message : String(err);
|
|
1965
|
-
}
|
|
1966
|
-
const DISCONNECT_TIMEOUT_MS = 6e4;
|
|
1967
|
-
const STATUS_INTERVAL_MS = 1e3;
|
|
1968
|
-
const ACTIVE_THRESHOLD_MS = 2e3;
|
|
1969
|
-
function ptyLog(level, ...args) {
|
|
1970
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1971
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
1972
|
-
console.error(`[${ts}] [${level}] [pty-manager] ${msg}`);
|
|
1973
|
-
}
|
|
1974
|
-
var PtyManager = class PtyManager {
|
|
1975
|
-
static instance = null;
|
|
1976
|
-
session = null;
|
|
1977
|
-
connectedClients = /* @__PURE__ */ new Set();
|
|
1978
|
-
lastOutputTime = 0;
|
|
1979
|
-
statusInterval = null;
|
|
1980
|
-
static getInstance() {
|
|
1981
|
-
if (!PtyManager.instance) PtyManager.instance = new PtyManager();
|
|
1982
|
-
return PtyManager.instance;
|
|
1983
|
-
}
|
|
1984
|
-
spawn(opts) {
|
|
1985
|
-
if (!pty) {
|
|
1986
|
-
ptyLog("ERROR", `node-pty not available: ${ptyLoadError}`);
|
|
1987
|
-
this.broadcastToClients({
|
|
1988
|
-
type: "output",
|
|
1989
|
-
data: `\r\n\x1b[31mTerminal unavailable: node-pty is not installed.\r\nError: ${ptyLoadError}\r\nRun: npm install node-pty\x1b[0m\r\n`
|
|
1990
|
-
});
|
|
1991
|
-
return;
|
|
1992
|
-
}
|
|
1993
|
-
if (this.session) {
|
|
1994
|
-
ptyLog("INFO", "Killing existing session before spawn");
|
|
1995
|
-
this.kill();
|
|
1996
|
-
}
|
|
1997
|
-
const isWin = process.platform === "win32";
|
|
1998
|
-
const shell = isWin ? "cmd.exe" : "/bin/sh";
|
|
1999
|
-
const claudeCmd = `claude${opts.skipPermissions ? " --dangerously-skip-permissions" : ""}`;
|
|
2000
|
-
const shellArgs = isWin ? ["/c", claudeCmd] : ["-c", claudeCmd];
|
|
2001
|
-
ptyLog("INFO", `Spawning: shell=${shell}, args=${JSON.stringify(shellArgs)}, cwd=${opts.cwd}`);
|
|
2002
|
-
const proc = pty.spawn(shell, shellArgs, {
|
|
2003
|
-
name: "xterm-256color",
|
|
2004
|
-
cols: opts.cols ?? 120,
|
|
2005
|
-
rows: opts.rows ?? 30,
|
|
2006
|
-
cwd: opts.cwd,
|
|
2007
|
-
env: process.env
|
|
2008
|
-
});
|
|
2009
|
-
ptyLog("INFO", `Process spawned with pid=${proc.pid}`);
|
|
2010
|
-
const store = new SessionStore();
|
|
2011
|
-
this.session = {
|
|
2012
|
-
process: proc,
|
|
2013
|
-
pid: proc.pid,
|
|
2014
|
-
startTime: Date.now(),
|
|
2015
|
-
cwd: opts.cwd,
|
|
2016
|
-
skipPermissions: opts.skipPermissions,
|
|
2017
|
-
disconnectTimer: null,
|
|
2018
|
-
store
|
|
2019
|
-
};
|
|
2020
|
-
this.lastOutputTime = Date.now();
|
|
2021
|
-
proc.onData((data) => {
|
|
2022
|
-
this.lastOutputTime = Date.now();
|
|
2023
|
-
store.append(data);
|
|
2024
|
-
if (this.session?.pid === proc.pid) this.broadcastToClients({
|
|
2025
|
-
type: "output",
|
|
2026
|
-
data
|
|
2027
|
-
});
|
|
2028
|
-
});
|
|
2029
|
-
proc.onExit(({ exitCode }) => {
|
|
2030
|
-
ptyLog("INFO", `Process exited with code=${exitCode}`);
|
|
2031
|
-
if (this.session?.pid !== proc.pid) {
|
|
2032
|
-
ptyLog("INFO", `Ignoring stale exit for old pid=${proc.pid}`);
|
|
2033
|
-
return;
|
|
2034
|
-
}
|
|
2035
|
-
this.broadcastToClients({
|
|
2036
|
-
type: "exit",
|
|
2037
|
-
code: exitCode
|
|
2038
|
-
});
|
|
2039
|
-
this.stopStatusBroadcast();
|
|
2040
|
-
this.session = null;
|
|
2041
|
-
});
|
|
2042
|
-
this.broadcastToClients({
|
|
2043
|
-
type: "started",
|
|
2044
|
-
pid: proc.pid
|
|
2045
|
-
});
|
|
2046
|
-
this.startStatusBroadcast();
|
|
2047
|
-
}
|
|
2048
|
-
write(data) {
|
|
2049
|
-
if (this.session) this.session.process.write(data);
|
|
2050
|
-
}
|
|
2051
|
-
resize(cols, rows) {
|
|
2052
|
-
if (this.session) this.session.process.resize(cols, rows);
|
|
2053
|
-
}
|
|
2054
|
-
kill() {
|
|
2055
|
-
if (this.session) {
|
|
2056
|
-
this.stopStatusBroadcast();
|
|
2057
|
-
try {
|
|
2058
|
-
this.session.process.kill();
|
|
2059
|
-
} catch {}
|
|
2060
|
-
if (this.session.disconnectTimer) clearTimeout(this.session.disconnectTimer);
|
|
2061
|
-
this.session = null;
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
getStatus() {
|
|
2065
|
-
if (!this.session) return null;
|
|
2066
|
-
return {
|
|
2067
|
-
pid: this.session.pid,
|
|
2068
|
-
uptime: Math.floor((Date.now() - this.session.startTime) / 1e3),
|
|
2069
|
-
cwd: this.session.cwd,
|
|
2070
|
-
memoryMB: Math.round(process.memoryUsage().rss / 1024 / 1024 * 10) / 10,
|
|
2071
|
-
isActive: Date.now() - this.lastOutputTime < ACTIVE_THRESHOLD_MS,
|
|
2072
|
-
skipPermissions: this.session.skipPermissions,
|
|
2073
|
-
alive: true
|
|
2074
|
-
};
|
|
2075
|
-
}
|
|
2076
|
-
addClient(ws) {
|
|
2077
|
-
this.connectedClients.add(ws);
|
|
2078
|
-
if (this.session?.disconnectTimer) {
|
|
2079
|
-
clearTimeout(this.session.disconnectTimer);
|
|
2080
|
-
this.session.disconnectTimer = null;
|
|
2081
|
-
}
|
|
2082
|
-
if (this.session) {
|
|
2083
|
-
const scrollback = this.session.store.getAll();
|
|
2084
|
-
if (scrollback) ws.send(JSON.stringify({
|
|
2085
|
-
type: "scrollback",
|
|
2086
|
-
data: scrollback
|
|
2087
|
-
}));
|
|
2088
|
-
const status = this.getStatus();
|
|
2089
|
-
if (status) ws.send(JSON.stringify({
|
|
2090
|
-
type: "status",
|
|
2091
|
-
...status
|
|
2092
|
-
}));
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
removeClient(ws) {
|
|
2096
|
-
this.connectedClients.delete(ws);
|
|
2097
|
-
if (this.connectedClients.size === 0 && this.session) this.session.disconnectTimer = setTimeout(() => {
|
|
2098
|
-
console.error("[pty] No clients connected for 60s, killing process");
|
|
2099
|
-
this.kill();
|
|
2100
|
-
}, DISCONNECT_TIMEOUT_MS);
|
|
2101
|
-
}
|
|
2102
|
-
isAlive() {
|
|
2103
|
-
return this.session !== null;
|
|
2104
|
-
}
|
|
2105
|
-
isAvailable() {
|
|
2106
|
-
return pty !== null;
|
|
2107
|
-
}
|
|
2108
|
-
broadcastToClients(message) {
|
|
2109
|
-
const data = JSON.stringify(message);
|
|
2110
|
-
for (const client of this.connectedClients) if (client.readyState === 1) client.send(data);
|
|
2111
|
-
}
|
|
2112
|
-
startStatusBroadcast() {
|
|
2113
|
-
this.stopStatusBroadcast();
|
|
2114
|
-
this.statusInterval = setInterval(() => {
|
|
2115
|
-
const status = this.getStatus();
|
|
2116
|
-
if (status) this.broadcastToClients({
|
|
2117
|
-
type: "status",
|
|
2118
|
-
...status
|
|
2119
|
-
});
|
|
2120
|
-
}, STATUS_INTERVAL_MS);
|
|
2121
|
-
}
|
|
2122
|
-
stopStatusBroadcast() {
|
|
2123
|
-
if (this.statusInterval) {
|
|
2124
|
-
clearInterval(this.statusInterval);
|
|
2125
|
-
this.statusInterval = null;
|
|
2126
|
-
}
|
|
2127
|
-
}
|
|
2128
|
-
};
|
|
2129
|
-
|
|
2130
|
-
//#endregion
|
|
2131
|
-
//#region src/backend/server.ts
|
|
2132
|
-
/**
|
|
2133
|
-
* MAXSIM Backend Server — Unified persistent backend service
|
|
2134
|
-
*
|
|
2135
|
-
* Consolidates HTTP API, WebSocket, MCP endpoint, terminal management,
|
|
2136
|
-
* and file watching into a single per-project process.
|
|
2137
|
-
*
|
|
2138
|
-
* CRITICAL: Never import output() or error() from core — they call process.exit().
|
|
2139
|
-
* CRITICAL: Never write to stdout directly — stdout may be reserved for protocol use.
|
|
2140
|
-
* All logging must go to stderr via console.error().
|
|
2141
|
-
*/
|
|
2142
|
-
function log(level, tag, ...args) {
|
|
2143
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2144
|
-
const msg = args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ");
|
|
2145
|
-
console.error(`[${ts}] [${level}] [${tag}] ${msg}`);
|
|
2146
|
-
}
|
|
2147
|
-
function isWithinPlanning(cwd, targetPath) {
|
|
2148
|
-
const planningDir = node_path.resolve(cwd, ".planning");
|
|
2149
|
-
return node_path.resolve(cwd, targetPath).startsWith(planningDir);
|
|
2150
|
-
}
|
|
2151
|
-
function normalizeFsPath(p) {
|
|
2152
|
-
return p.replace(/\\/g, "/");
|
|
2153
|
-
}
|
|
2154
|
-
function parseRoadmap(cwd) {
|
|
2155
|
-
const roadmapPath = node_path.join(cwd, ".planning", "ROADMAP.md");
|
|
2156
|
-
if (!node_fs.existsSync(roadmapPath)) return null;
|
|
2157
|
-
const content = node_fs.readFileSync(roadmapPath, "utf-8").replace(/\r\n/g, "\n");
|
|
2158
|
-
const phasesDir = node_path.join(cwd, ".planning", "phases");
|
|
2159
|
-
const phasePattern = require_cli.getPhasePattern();
|
|
2160
|
-
const phases = [];
|
|
2161
|
-
let match;
|
|
2162
|
-
while ((match = phasePattern.exec(content)) !== null) {
|
|
2163
|
-
const phaseNum = match[1];
|
|
2164
|
-
const phaseName = match[2].replace(/\(INSERTED\)/i, "").trim();
|
|
2165
|
-
const sectionStart = match.index;
|
|
2166
|
-
const nextHeader = content.slice(sectionStart).match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
2167
|
-
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
2168
|
-
const section = content.slice(sectionStart, sectionEnd);
|
|
2169
|
-
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
|
2170
|
-
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
2171
|
-
const dependsMatch = section.match(/\*\*Depends on:\*\*\s*([^\n]+)/i);
|
|
2172
|
-
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
|
2173
|
-
const normalized = require_cli.normalizePhaseName(phaseNum);
|
|
2174
|
-
let diskStatus = "no_directory";
|
|
2175
|
-
let planCount = 0;
|
|
2176
|
-
let summaryCount = 0;
|
|
2177
|
-
let hasContext = false;
|
|
2178
|
-
let hasResearch = false;
|
|
2179
|
-
try {
|
|
2180
|
-
const dirMatch = node_fs.readdirSync(phasesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).find((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
2181
|
-
if (dirMatch) {
|
|
2182
|
-
const phaseFiles = node_fs.readdirSync(node_path.join(phasesDir, dirMatch));
|
|
2183
|
-
planCount = phaseFiles.filter((f) => f.endsWith("-PLAN.md") || f === "PLAN.md").length;
|
|
2184
|
-
summaryCount = phaseFiles.filter((f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md").length;
|
|
2185
|
-
hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
2186
|
-
hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
2187
|
-
if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
|
|
2188
|
-
else if (summaryCount > 0) diskStatus = "partial";
|
|
2189
|
-
else if (planCount > 0) diskStatus = "planned";
|
|
2190
|
-
else if (hasResearch) diskStatus = "researched";
|
|
2191
|
-
else if (hasContext) diskStatus = "discussed";
|
|
2192
|
-
else diskStatus = "empty";
|
|
2193
|
-
}
|
|
2194
|
-
} catch {}
|
|
2195
|
-
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(".", "\\.")}`, "i");
|
|
2196
|
-
const checkboxMatch = content.match(checkboxPattern);
|
|
2197
|
-
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === "x" : false;
|
|
2198
|
-
phases.push({
|
|
2199
|
-
number: phaseNum,
|
|
2200
|
-
name: phaseName,
|
|
2201
|
-
goal,
|
|
2202
|
-
depends_on,
|
|
2203
|
-
plan_count: planCount,
|
|
2204
|
-
summary_count: summaryCount,
|
|
2205
|
-
has_context: hasContext,
|
|
2206
|
-
has_research: hasResearch,
|
|
2207
|
-
disk_status: diskStatus,
|
|
2208
|
-
roadmap_complete: roadmapComplete
|
|
2209
|
-
});
|
|
2210
|
-
}
|
|
2211
|
-
const milestones = [];
|
|
2212
|
-
const milestonePattern = /##\s*(.*v(\d+\.\d+)[^(\n]*)/gi;
|
|
2213
|
-
let mMatch;
|
|
2214
|
-
while ((mMatch = milestonePattern.exec(content)) !== null) milestones.push({
|
|
2215
|
-
heading: mMatch[1].trim(),
|
|
2216
|
-
version: "v" + mMatch[2]
|
|
2217
|
-
});
|
|
2218
|
-
const currentPhase = phases.find((p) => p.disk_status === "planned" || p.disk_status === "partial") || null;
|
|
2219
|
-
const nextPhase = phases.find((p) => p.disk_status === "empty" || p.disk_status === "no_directory" || p.disk_status === "discussed" || p.disk_status === "researched") || null;
|
|
2220
|
-
const totalPlans = phases.reduce((sum, p) => sum + p.plan_count, 0);
|
|
2221
|
-
const totalSummaries = phases.reduce((sum, p) => sum + p.summary_count, 0);
|
|
2222
|
-
const completedPhases = phases.filter((p) => p.disk_status === "complete").length;
|
|
2223
|
-
return {
|
|
2224
|
-
milestones,
|
|
2225
|
-
phases,
|
|
2226
|
-
phase_count: phases.length,
|
|
2227
|
-
completed_phases: completedPhases,
|
|
2228
|
-
total_plans: totalPlans,
|
|
2229
|
-
total_summaries: totalSummaries,
|
|
2230
|
-
progress_percent: totalPlans > 0 ? Math.min(100, Math.round(totalSummaries / totalPlans * 100)) : 0,
|
|
2231
|
-
current_phase: currentPhase ? currentPhase.number : null,
|
|
2232
|
-
next_phase: nextPhase ? nextPhase.number : null,
|
|
2233
|
-
missing_phase_details: null
|
|
2234
|
-
};
|
|
2235
|
-
}
|
|
2236
|
-
function parseState(cwd) {
|
|
2237
|
-
const statePath = node_path.join(cwd, ".planning", "STATE.md");
|
|
2238
|
-
if (!node_fs.existsSync(statePath)) return null;
|
|
2239
|
-
const content = node_fs.readFileSync(statePath, "utf-8").replace(/\r\n/g, "\n");
|
|
2240
|
-
const position = require_cli.stateExtractField(content, "Current Position") || require_cli.stateExtractField(content, "Phase");
|
|
2241
|
-
const lastActivity = require_cli.stateExtractField(content, "Last activity") || require_cli.stateExtractField(content, "Last Activity");
|
|
2242
|
-
const currentPhase = require_cli.stateExtractField(content, "Current Phase") || require_cli.stateExtractField(content, "Phase");
|
|
2243
|
-
const currentPlan = require_cli.stateExtractField(content, "Current Plan") || require_cli.stateExtractField(content, "Plan");
|
|
2244
|
-
const status = require_cli.stateExtractField(content, "Status");
|
|
2245
|
-
const progress = require_cli.stateExtractField(content, "Progress");
|
|
2246
|
-
const decisions = [];
|
|
2247
|
-
const decisionsMatch = content.match(/###?\s*Decisions\s*\n([\s\S]*?)(?=\n###?|\n##[^#]|$)/i);
|
|
2248
|
-
if (decisionsMatch) {
|
|
2249
|
-
const items = decisionsMatch[1].match(/^-\s+(.+)$/gm) || [];
|
|
2250
|
-
for (const item of items) decisions.push(item.replace(/^-\s+/, "").trim());
|
|
2251
|
-
}
|
|
2252
|
-
const blockers = [];
|
|
2253
|
-
const blockersMatch = content.match(/###?\s*(?:Blockers|Blockers\/Concerns)\s*\n([\s\S]*?)(?=\n###?|\n##[^#]|$)/i);
|
|
2254
|
-
if (blockersMatch) {
|
|
2255
|
-
const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
|
|
2256
|
-
for (const item of items) blockers.push(item.replace(/^-\s+/, "").trim());
|
|
2257
|
-
}
|
|
2258
|
-
return {
|
|
2259
|
-
position,
|
|
2260
|
-
lastActivity,
|
|
2261
|
-
currentPhase,
|
|
2262
|
-
currentPlan,
|
|
2263
|
-
status,
|
|
2264
|
-
progress,
|
|
2265
|
-
decisions,
|
|
2266
|
-
blockers,
|
|
2267
|
-
content
|
|
2268
|
-
};
|
|
2269
|
-
}
|
|
2270
|
-
function parsePhases(cwd) {
|
|
2271
|
-
const phasesDir = node_path.join(cwd, ".planning", "phases");
|
|
2272
|
-
if (!node_fs.existsSync(phasesDir)) return [];
|
|
2273
|
-
const phases = [];
|
|
2274
|
-
try {
|
|
2275
|
-
const dirs = node_fs.readdirSync(phasesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort((a, b) => require_cli.comparePhaseNum(a, b));
|
|
2276
|
-
for (const dir of dirs) {
|
|
2277
|
-
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)?)-?(.*)/i);
|
|
2278
|
-
const phaseNum = dm ? dm[1] : dir;
|
|
2279
|
-
const phaseName = dm && dm[2] ? dm[2].replace(/-/g, " ") : "";
|
|
2280
|
-
const phaseFiles = node_fs.readdirSync(node_path.join(phasesDir, dir));
|
|
2281
|
-
const planCount = phaseFiles.filter((f) => f.endsWith("-PLAN.md") || f === "PLAN.md").length;
|
|
2282
|
-
const summaryCount = phaseFiles.filter((f) => f.endsWith("-SUMMARY.md") || f === "SUMMARY.md").length;
|
|
2283
|
-
const hasContext = phaseFiles.some((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
2284
|
-
const hasResearch = phaseFiles.some((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
2285
|
-
let diskStatus = "no_directory";
|
|
2286
|
-
if (summaryCount >= planCount && planCount > 0) diskStatus = "complete";
|
|
2287
|
-
else if (summaryCount > 0) diskStatus = "partial";
|
|
2288
|
-
else if (planCount > 0) diskStatus = "planned";
|
|
2289
|
-
else if (hasResearch) diskStatus = "researched";
|
|
2290
|
-
else if (hasContext) diskStatus = "discussed";
|
|
2291
|
-
else diskStatus = "empty";
|
|
2292
|
-
phases.push({
|
|
2293
|
-
number: phaseNum,
|
|
2294
|
-
name: phaseName,
|
|
2295
|
-
goal: "",
|
|
2296
|
-
dependsOn: [],
|
|
2297
|
-
planCount,
|
|
2298
|
-
summaryCount,
|
|
2299
|
-
diskStatus,
|
|
2300
|
-
roadmapComplete: diskStatus === "complete",
|
|
2301
|
-
hasContext,
|
|
2302
|
-
hasResearch
|
|
2303
|
-
});
|
|
2304
|
-
}
|
|
2305
|
-
} catch {}
|
|
2306
|
-
return phases;
|
|
2307
|
-
}
|
|
2308
|
-
function parsePhaseDetail(cwd, phaseId) {
|
|
2309
|
-
const phasesDir = node_path.join(cwd, ".planning", "phases");
|
|
2310
|
-
if (!node_fs.existsSync(phasesDir)) return null;
|
|
2311
|
-
const normalized = require_cli.normalizePhaseName(phaseId);
|
|
2312
|
-
try {
|
|
2313
|
-
const dirMatch = node_fs.readdirSync(phasesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).find((d) => d.startsWith(normalized + "-") || d === normalized);
|
|
2314
|
-
if (!dirMatch) return null;
|
|
2315
|
-
const phaseDir = node_path.join(phasesDir, dirMatch);
|
|
2316
|
-
const phaseFiles = node_fs.readdirSync(phaseDir);
|
|
2317
|
-
const planFileNames = phaseFiles.filter((f) => f.endsWith("-PLAN.md") || f === "PLAN.md").sort();
|
|
2318
|
-
const plans = [];
|
|
2319
|
-
for (const planFileName of planFileNames) {
|
|
2320
|
-
const planPath = node_path.join(phaseDir, planFileName);
|
|
2321
|
-
const content = node_fs.readFileSync(planPath, "utf-8").replace(/\r\n/g, "\n");
|
|
2322
|
-
const frontmatter = require_cli.extractFrontmatter(content);
|
|
2323
|
-
const tasks = [];
|
|
2324
|
-
const taskRegex = /<task\s+type="([^"]*)"[^>]*>\s*<name>([^<]+)<\/name>([\s\S]*?)<\/task>/g;
|
|
2325
|
-
let taskMatch;
|
|
2326
|
-
while ((taskMatch = taskRegex.exec(content)) !== null) {
|
|
2327
|
-
const taskType = taskMatch[1];
|
|
2328
|
-
const taskName = taskMatch[2].trim();
|
|
2329
|
-
const taskBody = taskMatch[3];
|
|
2330
|
-
const filesMatch = taskBody.match(/<files>([\s\S]*?)<\/files>/);
|
|
2331
|
-
const actionMatch = taskBody.match(/<action>([\s\S]*?)<\/action>/);
|
|
2332
|
-
const verifyMatch = taskBody.match(/<verify>([\s\S]*?)<\/verify>/);
|
|
2333
|
-
const doneMatch = taskBody.match(/<done>([\s\S]*?)<\/done>/);
|
|
2334
|
-
const files = filesMatch ? filesMatch[1].trim().split("\n").map((f) => f.trim()).filter(Boolean) : [];
|
|
2335
|
-
const doneText = doneMatch ? doneMatch[1].trim() : "";
|
|
2336
|
-
tasks.push({
|
|
2337
|
-
name: taskName,
|
|
2338
|
-
type: taskType,
|
|
2339
|
-
files,
|
|
2340
|
-
action: actionMatch ? actionMatch[1].trim() : "",
|
|
2341
|
-
verify: verifyMatch ? verifyMatch[1].trim() : "",
|
|
2342
|
-
done: doneText,
|
|
2343
|
-
completed: /^\[x\]/i.test(doneText)
|
|
2344
|
-
});
|
|
2345
|
-
}
|
|
2346
|
-
plans.push({
|
|
2347
|
-
path: node_path.join(".planning", "phases", dirMatch, planFileName),
|
|
2348
|
-
content,
|
|
2349
|
-
frontmatter,
|
|
2350
|
-
tasks
|
|
2351
|
-
});
|
|
2352
|
-
}
|
|
2353
|
-
let context = null;
|
|
2354
|
-
const contextFile = phaseFiles.find((f) => f.endsWith("-CONTEXT.md") || f === "CONTEXT.md");
|
|
2355
|
-
if (contextFile) context = node_fs.readFileSync(node_path.join(phaseDir, contextFile), "utf-8");
|
|
2356
|
-
let research = null;
|
|
2357
|
-
const researchFile = phaseFiles.find((f) => f.endsWith("-RESEARCH.md") || f === "RESEARCH.md");
|
|
2358
|
-
if (researchFile) research = node_fs.readFileSync(node_path.join(phaseDir, researchFile), "utf-8");
|
|
2359
|
-
return {
|
|
2360
|
-
plans,
|
|
2361
|
-
context,
|
|
2362
|
-
research
|
|
2363
|
-
};
|
|
2364
|
-
} catch {
|
|
2365
|
-
return null;
|
|
2366
|
-
}
|
|
2367
|
-
}
|
|
2368
|
-
function parseTodos(cwd) {
|
|
2369
|
-
const pendingDir = node_path.join(cwd, ".planning", "todos", "pending");
|
|
2370
|
-
const completedDir = node_path.join(cwd, ".planning", "todos", "completed");
|
|
2371
|
-
const pending = [];
|
|
2372
|
-
const completed = [];
|
|
2373
|
-
if (node_fs.existsSync(pendingDir)) try {
|
|
2374
|
-
const files = node_fs.readdirSync(pendingDir).filter((f) => f.endsWith(".md"));
|
|
2375
|
-
for (const file of files) try {
|
|
2376
|
-
const titleMatch = node_fs.readFileSync(node_path.join(pendingDir, file), "utf-8").match(/^title:\s*(.+)$/m);
|
|
2377
|
-
pending.push({
|
|
2378
|
-
text: titleMatch ? titleMatch[1].trim() : file.replace(".md", ""),
|
|
2379
|
-
completed: false,
|
|
2380
|
-
file
|
|
2381
|
-
});
|
|
2382
|
-
} catch {}
|
|
2383
|
-
} catch {}
|
|
2384
|
-
if (node_fs.existsSync(completedDir)) try {
|
|
2385
|
-
const files = node_fs.readdirSync(completedDir).filter((f) => f.endsWith(".md"));
|
|
2386
|
-
for (const file of files) try {
|
|
2387
|
-
const titleMatch = node_fs.readFileSync(node_path.join(completedDir, file), "utf-8").match(/^title:\s*(.+)$/m);
|
|
2388
|
-
completed.push({
|
|
2389
|
-
text: titleMatch ? titleMatch[1].trim() : file.replace(".md", ""),
|
|
2390
|
-
completed: true,
|
|
2391
|
-
file
|
|
2392
|
-
});
|
|
2393
|
-
} catch {}
|
|
2394
|
-
} catch {}
|
|
2395
|
-
return {
|
|
2396
|
-
pending,
|
|
2397
|
-
completed
|
|
2398
|
-
};
|
|
2399
|
-
}
|
|
2400
|
-
function parseProject(cwd) {
|
|
2401
|
-
const projectPath = node_path.join(cwd, ".planning", "PROJECT.md");
|
|
2402
|
-
const requirementsPath = node_path.join(cwd, ".planning", "REQUIREMENTS.md");
|
|
2403
|
-
return {
|
|
2404
|
-
project: node_fs.existsSync(projectPath) ? node_fs.readFileSync(projectPath, "utf-8") : null,
|
|
2405
|
-
requirements: node_fs.existsSync(requirementsPath) ? node_fs.readFileSync(requirementsPath, "utf-8") : null
|
|
2406
|
-
};
|
|
2407
|
-
}
|
|
2408
|
-
function createBackendServer(config) {
|
|
2409
|
-
const { projectCwd, host, enableTerminal, enableFileWatcher, enableMcp, logDir } = config;
|
|
2410
|
-
let resolvedPort = config.port;
|
|
2411
|
-
const startTime = Date.now();
|
|
2412
|
-
let serverReady = false;
|
|
2413
|
-
node_fs.mkdirSync(logDir, { recursive: true });
|
|
2414
|
-
const suppressedPaths = /* @__PURE__ */ new Map();
|
|
2415
|
-
const SUPPRESS_TTL_MS = 500;
|
|
2416
|
-
function suppressPath(filePath) {
|
|
2417
|
-
suppressedPaths.set(normalizeFsPath(filePath), Date.now());
|
|
2418
|
-
}
|
|
2419
|
-
function isSuppressed(filePath) {
|
|
2420
|
-
const normalized = normalizeFsPath(filePath);
|
|
2421
|
-
const timestamp = suppressedPaths.get(normalized);
|
|
2422
|
-
if (timestamp === void 0) return false;
|
|
2423
|
-
if (Date.now() - timestamp > SUPPRESS_TTL_MS) {
|
|
2424
|
-
suppressedPaths.delete(normalized);
|
|
2425
|
-
return false;
|
|
2426
|
-
}
|
|
2427
|
-
return true;
|
|
2428
|
-
}
|
|
2429
|
-
const cleanupInterval = setInterval(() => {
|
|
2430
|
-
const now = Date.now();
|
|
2431
|
-
for (const [p, ts] of suppressedPaths.entries()) if (now - ts > SUPPRESS_TTL_MS) suppressedPaths.delete(p);
|
|
2432
|
-
}, 6e4);
|
|
2433
|
-
cleanupInterval.unref();
|
|
2434
|
-
const questionQueue = [];
|
|
2435
|
-
const pendingAnswers = /* @__PURE__ */ new Map();
|
|
2436
|
-
let clientCount = 0;
|
|
2437
|
-
const wss = new ws.WebSocketServer({ noServer: true });
|
|
2438
|
-
wss.on("connection", (ws$1) => {
|
|
2439
|
-
clientCount++;
|
|
2440
|
-
log("INFO", "ws", `Client connected (${clientCount} total)`);
|
|
2441
|
-
ws$1.on("close", () => {
|
|
2442
|
-
clientCount--;
|
|
2443
|
-
log("INFO", "ws", `Client disconnected (${clientCount} total)`);
|
|
2444
|
-
});
|
|
2445
|
-
ws$1.on("error", (err) => {
|
|
2446
|
-
log("ERROR", "ws", `Client error: ${err.message}`);
|
|
2447
|
-
});
|
|
2448
|
-
ws$1.send(JSON.stringify({
|
|
2449
|
-
type: "connected",
|
|
2450
|
-
timestamp: Date.now()
|
|
2451
|
-
}));
|
|
2452
|
-
if (questionQueue.length > 0) ws$1.send(JSON.stringify({
|
|
2453
|
-
type: "questions-queued",
|
|
2454
|
-
questions: questionQueue,
|
|
2455
|
-
count: questionQueue.length
|
|
2456
|
-
}));
|
|
2457
|
-
});
|
|
2458
|
-
function broadcast(message) {
|
|
2459
|
-
const data = JSON.stringify(message);
|
|
2460
|
-
for (const client of wss.clients) if (client.readyState === ws.WebSocket.OPEN) client.send(data);
|
|
2461
|
-
}
|
|
2462
|
-
let watcher = null;
|
|
2463
|
-
async function setupWatcher() {
|
|
2464
|
-
if (!enableFileWatcher) return;
|
|
2465
|
-
const planningDir = node_path.join(projectCwd, ".planning");
|
|
2466
|
-
if (!node_fs.existsSync(planningDir)) {
|
|
2467
|
-
log("WARN", "watcher", `.planning/ directory not found at ${planningDir}`);
|
|
2468
|
-
return;
|
|
2469
|
-
}
|
|
2470
|
-
try {
|
|
2471
|
-
const chokidar = await import("chokidar");
|
|
2472
|
-
const changedPaths = /* @__PURE__ */ new Set();
|
|
2473
|
-
let flushTimer = null;
|
|
2474
|
-
function flushChanges() {
|
|
2475
|
-
if (changedPaths.size > 0) {
|
|
2476
|
-
const changes = Array.from(changedPaths);
|
|
2477
|
-
changedPaths.clear();
|
|
2478
|
-
log("INFO", "watcher", `Broadcasting ${changes.length} change(s)`);
|
|
2479
|
-
broadcast({
|
|
2480
|
-
type: "file-changes",
|
|
2481
|
-
changes,
|
|
2482
|
-
timestamp: Date.now()
|
|
2483
|
-
});
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
|
-
function onFileChange(filePath) {
|
|
2487
|
-
const normalized = normalizeFsPath(filePath);
|
|
2488
|
-
if (isSuppressed(normalized)) return;
|
|
2489
|
-
changedPaths.add(normalized);
|
|
2490
|
-
if (flushTimer) clearTimeout(flushTimer);
|
|
2491
|
-
flushTimer = setTimeout(flushChanges, 500);
|
|
2492
|
-
}
|
|
2493
|
-
const w = chokidar.watch(planningDir, {
|
|
2494
|
-
persistent: true,
|
|
2495
|
-
ignoreInitial: true,
|
|
2496
|
-
depth: 5
|
|
2497
|
-
});
|
|
2498
|
-
w.on("add", onFileChange);
|
|
2499
|
-
w.on("change", onFileChange);
|
|
2500
|
-
w.on("unlink", onFileChange);
|
|
2501
|
-
w.on("error", (err) => {
|
|
2502
|
-
log("ERROR", "watcher", `Error: ${err.message}`);
|
|
2503
|
-
});
|
|
2504
|
-
watcher = w;
|
|
2505
|
-
log("INFO", "watcher", `Watching ${planningDir}`);
|
|
2506
|
-
} catch (err) {
|
|
2507
|
-
log("ERROR", "watcher", `Failed to start file watcher: ${err.message}`);
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
const app = (0, express.default)();
|
|
2511
|
-
app.use(express.default.json());
|
|
2512
|
-
app.get("/api/health", (_req, res) => {
|
|
2513
|
-
res.json({
|
|
2514
|
-
status: "ok",
|
|
2515
|
-
ready: serverReady,
|
|
2516
|
-
port: resolvedPort,
|
|
2517
|
-
cwd: projectCwd,
|
|
2518
|
-
uptime: (Date.now() - startTime) / 1e3,
|
|
2519
|
-
pid: process.pid,
|
|
2520
|
-
mcpEndpoint: enableMcp ? `http://127.0.0.1:${resolvedPort}/mcp` : null,
|
|
2521
|
-
terminalAvailable: enableTerminal && PtyManager.getInstance().isAvailable(),
|
|
2522
|
-
connectedClients: clientCount
|
|
2523
|
-
});
|
|
2524
|
-
});
|
|
2525
|
-
app.get("/api/ready", (_req, res) => {
|
|
2526
|
-
if (serverReady) return res.json({
|
|
2527
|
-
ready: true,
|
|
2528
|
-
port: resolvedPort,
|
|
2529
|
-
cwd: projectCwd
|
|
2530
|
-
});
|
|
2531
|
-
return res.status(503).json({
|
|
2532
|
-
ready: false,
|
|
2533
|
-
message: "Server is starting up"
|
|
2534
|
-
});
|
|
2535
|
-
});
|
|
2536
|
-
app.get("/api/roadmap", (_req, res) => {
|
|
2537
|
-
try {
|
|
2538
|
-
const data = parseRoadmap(projectCwd);
|
|
2539
|
-
if (!data) return res.status(404).json({ error: "ROADMAP.md not found" });
|
|
2540
|
-
return res.json(data);
|
|
2541
|
-
} catch (err) {
|
|
2542
|
-
log("ERROR", "api", `GET /api/roadmap failed: ${err.message}`);
|
|
2543
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2544
|
-
}
|
|
2545
|
-
});
|
|
2546
|
-
app.patch("/api/roadmap", (req, res) => {
|
|
2547
|
-
try {
|
|
2548
|
-
const roadmapPath = node_path.join(projectCwd, ".planning", "ROADMAP.md");
|
|
2549
|
-
if (!node_fs.existsSync(roadmapPath)) return res.status(404).json({ error: "ROADMAP.md not found" });
|
|
2550
|
-
const { phaseNumber, checked } = req.body;
|
|
2551
|
-
if (!phaseNumber || checked === void 0) return res.status(400).json({ error: "phaseNumber and checked are required" });
|
|
2552
|
-
let content = node_fs.readFileSync(roadmapPath, "utf-8").replace(/\r\n/g, "\n");
|
|
2553
|
-
const escapedNum = phaseNumber.replace(".", "\\.");
|
|
2554
|
-
const pattern = new RegExp(`(-\\s*\\[)(x| )(\\]\\s*.*Phase\\s+${escapedNum})`, "i");
|
|
2555
|
-
if (!content.match(pattern)) return res.status(404).json({ error: `Phase ${phaseNumber} checkbox not found` });
|
|
2556
|
-
content = content.replace(pattern, `$1${checked ? "x" : " "}$3`);
|
|
2557
|
-
suppressPath(roadmapPath);
|
|
2558
|
-
node_fs.writeFileSync(roadmapPath, content, "utf-8");
|
|
2559
|
-
return res.json({
|
|
2560
|
-
updated: true,
|
|
2561
|
-
phaseNumber,
|
|
2562
|
-
checked
|
|
2563
|
-
});
|
|
2564
|
-
} catch (err) {
|
|
2565
|
-
log("ERROR", "api", `PATCH /api/roadmap failed: ${err.message}`);
|
|
2566
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2567
|
-
}
|
|
2568
|
-
});
|
|
2569
|
-
app.get("/api/state", (_req, res) => {
|
|
2570
|
-
try {
|
|
2571
|
-
const data = parseState(projectCwd);
|
|
2572
|
-
if (!data) return res.status(404).json({ error: "STATE.md not found" });
|
|
2573
|
-
return res.json(data);
|
|
2574
|
-
} catch (err) {
|
|
2575
|
-
log("ERROR", "api", `GET /api/state failed: ${err.message}`);
|
|
2576
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2577
|
-
}
|
|
2578
|
-
});
|
|
2579
|
-
app.patch("/api/state", (req, res) => {
|
|
2580
|
-
try {
|
|
2581
|
-
const statePath = node_path.join(projectCwd, ".planning", "STATE.md");
|
|
2582
|
-
if (!node_fs.existsSync(statePath)) return res.status(404).json({ error: "STATE.md not found" });
|
|
2583
|
-
const { field, value } = req.body;
|
|
2584
|
-
if (!field || value === void 0) return res.status(400).json({ error: "field and value are required" });
|
|
2585
|
-
const updated = require_cli.stateReplaceField(node_fs.readFileSync(statePath, "utf-8").replace(/\r\n/g, "\n"), field, value);
|
|
2586
|
-
if (!updated) return res.status(404).json({ error: `Field "${field}" not found in STATE.md` });
|
|
2587
|
-
suppressPath(statePath);
|
|
2588
|
-
node_fs.writeFileSync(statePath, updated, "utf-8");
|
|
2589
|
-
return res.json({
|
|
2590
|
-
updated: true,
|
|
2591
|
-
field
|
|
2592
|
-
});
|
|
2593
|
-
} catch (err) {
|
|
2594
|
-
log("ERROR", "api", `PATCH /api/state failed: ${err.message}`);
|
|
2595
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2596
|
-
}
|
|
2597
|
-
});
|
|
2598
|
-
function ensureStateMd(statePath) {
|
|
2599
|
-
if (node_fs.existsSync(statePath)) return;
|
|
2600
|
-
const planningDir = node_path.dirname(statePath);
|
|
2601
|
-
node_fs.mkdirSync(planningDir, { recursive: true });
|
|
2602
|
-
const template = `# Project State
|
|
2603
|
-
|
|
2604
|
-
## Current Position
|
|
2605
|
-
|
|
2606
|
-
Phase: 1
|
|
2607
|
-
Status: In progress
|
|
2608
|
-
Last activity: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]} — State file created
|
|
2609
|
-
|
|
2610
|
-
## Accumulated Context
|
|
2611
|
-
|
|
2612
|
-
### Decisions
|
|
2613
|
-
|
|
2614
|
-
None yet.
|
|
2615
|
-
|
|
2616
|
-
### Blockers/Concerns
|
|
2617
|
-
|
|
2618
|
-
None yet.
|
|
2619
|
-
`;
|
|
2620
|
-
node_fs.writeFileSync(statePath, template, "utf-8");
|
|
2621
|
-
}
|
|
2622
|
-
function appendToStateSection(statePath, sectionPattern, entry, fallbackSection) {
|
|
2623
|
-
let content = node_fs.readFileSync(statePath, "utf-8").replace(/\r\n/g, "\n");
|
|
2624
|
-
const match = content.match(sectionPattern);
|
|
2625
|
-
if (match) {
|
|
2626
|
-
let sectionBody = match[2];
|
|
2627
|
-
sectionBody = sectionBody.replace(/None yet\.?\s*\n?/gi, "").replace(/No decisions yet\.?\s*\n?/gi, "").replace(/None\.?\s*\n?/gi, "");
|
|
2628
|
-
sectionBody = sectionBody.trimEnd() + "\n" + entry + "\n";
|
|
2629
|
-
content = content.replace(sectionPattern, (_m, header) => `${header}${sectionBody}`);
|
|
2630
|
-
} else content = content.trimEnd() + "\n\n" + fallbackSection + "\n" + entry + "\n";
|
|
2631
|
-
suppressPath(statePath);
|
|
2632
|
-
node_fs.writeFileSync(statePath, content, "utf-8");
|
|
2633
|
-
}
|
|
2634
|
-
app.post("/api/state/decision", (req, res) => {
|
|
2635
|
-
try {
|
|
2636
|
-
const statePath = node_path.join(projectCwd, ".planning", "STATE.md");
|
|
2637
|
-
ensureStateMd(statePath);
|
|
2638
|
-
const { phase, text } = req.body;
|
|
2639
|
-
if (!text?.trim()) return res.status(400).json({ error: "text is required" });
|
|
2640
|
-
const entry = `- [Phase ${phase?.trim() || "?"}]: ${text.trim()}`;
|
|
2641
|
-
appendToStateSection(statePath, /(###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, entry, "### Decisions");
|
|
2642
|
-
return res.json({
|
|
2643
|
-
added: true,
|
|
2644
|
-
decision: entry
|
|
2645
|
-
});
|
|
2646
|
-
} catch (err) {
|
|
2647
|
-
log("ERROR", "api", `POST /api/state/decision failed: ${err.message}`);
|
|
2648
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2649
|
-
}
|
|
2650
|
-
});
|
|
2651
|
-
app.post("/api/state/blocker", (req, res) => {
|
|
2652
|
-
try {
|
|
2653
|
-
const statePath = node_path.join(projectCwd, ".planning", "STATE.md");
|
|
2654
|
-
ensureStateMd(statePath);
|
|
2655
|
-
const { text } = req.body;
|
|
2656
|
-
if (!text?.trim()) return res.status(400).json({ error: "text is required" });
|
|
2657
|
-
appendToStateSection(statePath, /(###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n)([\s\S]*?)(?=\n###?|\n##[^#]|$)/i, `- ${text.trim()}`, "### Blockers/Concerns");
|
|
2658
|
-
return res.json({
|
|
2659
|
-
added: true,
|
|
2660
|
-
blocker: text.trim()
|
|
2661
|
-
});
|
|
2662
|
-
} catch (err) {
|
|
2663
|
-
log("ERROR", "api", `POST /api/state/blocker failed: ${err.message}`);
|
|
2664
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2665
|
-
}
|
|
2666
|
-
});
|
|
2667
|
-
app.get("/api/phases", (_req, res) => {
|
|
2668
|
-
try {
|
|
2669
|
-
return res.json(parsePhases(projectCwd));
|
|
2670
|
-
} catch (err) {
|
|
2671
|
-
log("ERROR", "api", `GET /api/phases failed: ${err.message}`);
|
|
2672
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2673
|
-
}
|
|
2674
|
-
});
|
|
2675
|
-
app.get("/api/phase/:id", (req, res) => {
|
|
2676
|
-
try {
|
|
2677
|
-
const data = parsePhaseDetail(projectCwd, req.params.id);
|
|
2678
|
-
if (!data) return res.status(404).json({ error: `Phase ${req.params.id} not found` });
|
|
2679
|
-
return res.json(data);
|
|
2680
|
-
} catch (err) {
|
|
2681
|
-
log("ERROR", "api", `GET /api/phase/:id failed: ${err.message}`);
|
|
2682
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2683
|
-
}
|
|
2684
|
-
});
|
|
2685
|
-
app.get("/api/todos", (_req, res) => {
|
|
2686
|
-
try {
|
|
2687
|
-
return res.json(parseTodos(projectCwd));
|
|
2688
|
-
} catch (err) {
|
|
2689
|
-
log("ERROR", "api", `GET /api/todos failed: ${err.message}`);
|
|
2690
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2691
|
-
}
|
|
2692
|
-
});
|
|
2693
|
-
app.post("/api/todos", (req, res) => {
|
|
2694
|
-
try {
|
|
2695
|
-
const pendingDir = node_path.join(projectCwd, ".planning", "todos", "pending");
|
|
2696
|
-
const { text } = req.body;
|
|
2697
|
-
if (!text) return res.status(400).json({ error: "text is required" });
|
|
2698
|
-
node_fs.mkdirSync(pendingDir, { recursive: true });
|
|
2699
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2700
|
-
const filename = `${timestamp}-${text.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40)}.md`;
|
|
2701
|
-
const filePath = node_path.join(pendingDir, filename);
|
|
2702
|
-
const content = `title: ${text}\ncreated: ${timestamp}\narea: general\n\n${text}\n`;
|
|
2703
|
-
suppressPath(filePath);
|
|
2704
|
-
node_fs.writeFileSync(filePath, content, "utf-8");
|
|
2705
|
-
return res.json({
|
|
2706
|
-
created: true,
|
|
2707
|
-
file: filename,
|
|
2708
|
-
text
|
|
2709
|
-
});
|
|
2710
|
-
} catch (err) {
|
|
2711
|
-
log("ERROR", "api", `POST /api/todos failed: ${err.message}`);
|
|
2712
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2713
|
-
}
|
|
2714
|
-
});
|
|
2715
|
-
app.patch("/api/todos", (req, res) => {
|
|
2716
|
-
try {
|
|
2717
|
-
const pendingDir = node_path.join(projectCwd, ".planning", "todos", "pending");
|
|
2718
|
-
const completedDir = node_path.join(projectCwd, ".planning", "todos", "completed");
|
|
2719
|
-
const { file, completed } = req.body;
|
|
2720
|
-
if (!file) return res.status(400).json({ error: "file is required" });
|
|
2721
|
-
if (file.includes("/") || file.includes("\\") || file.includes("..")) return res.status(400).json({ error: "Invalid filename" });
|
|
2722
|
-
if (completed) {
|
|
2723
|
-
const sourcePath = node_path.join(pendingDir, file);
|
|
2724
|
-
if (!node_fs.existsSync(sourcePath)) return res.status(404).json({ error: "Todo not found in pending" });
|
|
2725
|
-
node_fs.mkdirSync(completedDir, { recursive: true });
|
|
2726
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2727
|
-
let content = node_fs.readFileSync(sourcePath, "utf-8");
|
|
2728
|
-
content = `completed: ${today}\n` + content;
|
|
2729
|
-
const destPath = node_path.join(completedDir, file);
|
|
2730
|
-
suppressPath(sourcePath);
|
|
2731
|
-
suppressPath(destPath);
|
|
2732
|
-
node_fs.writeFileSync(destPath, content, "utf-8");
|
|
2733
|
-
node_fs.unlinkSync(sourcePath);
|
|
2734
|
-
return res.json({
|
|
2735
|
-
completed: true,
|
|
2736
|
-
file,
|
|
2737
|
-
date: today
|
|
2738
|
-
});
|
|
2739
|
-
} else {
|
|
2740
|
-
const sourcePath = node_path.join(completedDir, file);
|
|
2741
|
-
if (!node_fs.existsSync(sourcePath)) return res.status(404).json({ error: "Todo not found in completed" });
|
|
2742
|
-
node_fs.mkdirSync(pendingDir, { recursive: true });
|
|
2743
|
-
let content = node_fs.readFileSync(sourcePath, "utf-8");
|
|
2744
|
-
content = content.replace(/^completed:\s*.+\n/m, "");
|
|
2745
|
-
const destPath = node_path.join(pendingDir, file);
|
|
2746
|
-
suppressPath(sourcePath);
|
|
2747
|
-
suppressPath(destPath);
|
|
2748
|
-
node_fs.writeFileSync(destPath, content, "utf-8");
|
|
2749
|
-
node_fs.unlinkSync(sourcePath);
|
|
2750
|
-
return res.json({
|
|
2751
|
-
completed: false,
|
|
2752
|
-
file
|
|
2753
|
-
});
|
|
2754
|
-
}
|
|
2755
|
-
} catch (err) {
|
|
2756
|
-
log("ERROR", "api", `PATCH /api/todos failed: ${err.message}`);
|
|
2757
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2758
|
-
}
|
|
2759
|
-
});
|
|
2760
|
-
app.get("/api/project", (_req, res) => {
|
|
2761
|
-
try {
|
|
2762
|
-
return res.json(parseProject(projectCwd));
|
|
2763
|
-
} catch (err) {
|
|
2764
|
-
log("ERROR", "api", `GET /api/project failed: ${err.message}`);
|
|
2765
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2766
|
-
}
|
|
2767
|
-
});
|
|
2768
|
-
app.get("/api/plan/*", (req, res) => {
|
|
2769
|
-
try {
|
|
2770
|
-
const pathSegments = req.params["0"].split("/");
|
|
2771
|
-
const relativePath = node_path.join(".planning", ...pathSegments);
|
|
2772
|
-
if (!isWithinPlanning(projectCwd, relativePath)) return res.status(403).json({ error: "Path traversal not allowed" });
|
|
2773
|
-
const fullPath = node_path.join(projectCwd, relativePath);
|
|
2774
|
-
if (!node_fs.existsSync(fullPath)) return res.status(404).json({ error: "File not found" });
|
|
2775
|
-
const content = node_fs.readFileSync(fullPath, "utf-8");
|
|
2776
|
-
return res.json({
|
|
2777
|
-
path: relativePath,
|
|
2778
|
-
content
|
|
2779
|
-
});
|
|
2780
|
-
} catch (err) {
|
|
2781
|
-
log("ERROR", "api", `GET /api/plan/* failed: ${err.message}`);
|
|
2782
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2783
|
-
}
|
|
2784
|
-
});
|
|
2785
|
-
app.put("/api/plan/*", (req, res) => {
|
|
2786
|
-
try {
|
|
2787
|
-
const pathSegments = req.params["0"].split("/");
|
|
2788
|
-
const relativePath = node_path.join(".planning", ...pathSegments);
|
|
2789
|
-
if (!isWithinPlanning(projectCwd, relativePath)) return res.status(403).json({ error: "Path traversal not allowed" });
|
|
2790
|
-
const { content } = req.body;
|
|
2791
|
-
if (content === void 0) return res.status(400).json({ error: "content is required" });
|
|
2792
|
-
const fullPath = node_path.join(projectCwd, relativePath);
|
|
2793
|
-
const dir = node_path.dirname(fullPath);
|
|
2794
|
-
if (!node_fs.existsSync(dir)) node_fs.mkdirSync(dir, { recursive: true });
|
|
2795
|
-
suppressPath(fullPath);
|
|
2796
|
-
node_fs.writeFileSync(fullPath, content, "utf-8");
|
|
2797
|
-
return res.json({
|
|
2798
|
-
written: true,
|
|
2799
|
-
path: relativePath
|
|
2800
|
-
});
|
|
2801
|
-
} catch (err) {
|
|
2802
|
-
log("ERROR", "api", `PUT /api/plan/* failed: ${err.message}`);
|
|
2803
|
-
return res.status(500).json({ error: "Internal server error" });
|
|
2804
|
-
}
|
|
2805
|
-
});
|
|
2806
|
-
app.get("/api/server-info", (_req, res) => {
|
|
2807
|
-
const localNetworkIp = getLocalNetworkIp();
|
|
2808
|
-
return res.json({
|
|
2809
|
-
localUrl: `http://127.0.0.1:${resolvedPort}`,
|
|
2810
|
-
networkUrl: localNetworkIp ? `http://${localNetworkIp}:${resolvedPort}` : null,
|
|
2811
|
-
projectName: node_path.basename(projectCwd),
|
|
2812
|
-
projectCwd
|
|
2813
|
-
});
|
|
2814
|
-
});
|
|
2815
|
-
let shutdownFn = null;
|
|
2816
|
-
app.post("/api/shutdown", (_req, res) => {
|
|
2817
|
-
res.json({ shutdown: true });
|
|
2818
|
-
setTimeout(() => shutdownFn?.(), 100);
|
|
2819
|
-
});
|
|
2820
|
-
app.post("/api/mcp-answer", (req, res) => {
|
|
2821
|
-
const { questionId, answer } = req.body;
|
|
2822
|
-
if (!questionId || !answer) return res.status(400).json({ error: "questionId and answer are required" });
|
|
2823
|
-
const resolve = pendingAnswers.get(questionId);
|
|
2824
|
-
if (!resolve) return res.status(404).json({ error: "No pending question with that ID" });
|
|
2825
|
-
pendingAnswers.delete(questionId);
|
|
2826
|
-
resolve(answer);
|
|
2827
|
-
return res.json({ answered: true });
|
|
2828
|
-
});
|
|
2829
|
-
if (enableMcp) {
|
|
2830
|
-
app.post("/mcp", async (req, res) => {
|
|
2831
|
-
const mcpServer = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
|
|
2832
|
-
name: "maxsim-backend",
|
|
2833
|
-
version: "1.0.0"
|
|
2834
|
-
});
|
|
2835
|
-
registerAllTools(mcpServer);
|
|
2836
|
-
try {
|
|
2837
|
-
const transport = new _modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
2838
|
-
await mcpServer.connect(transport);
|
|
2839
|
-
await transport.handleRequest(req, res, req.body);
|
|
2840
|
-
res.on("close", () => {
|
|
2841
|
-
transport.close();
|
|
2842
|
-
mcpServer.close();
|
|
2843
|
-
});
|
|
2844
|
-
} catch (error) {
|
|
2845
|
-
log("ERROR", "mcp", `Error handling MCP POST request: ${error}`);
|
|
2846
|
-
if (!res.headersSent) res.status(500).json({
|
|
2847
|
-
jsonrpc: "2.0",
|
|
2848
|
-
error: {
|
|
2849
|
-
code: -32603,
|
|
2850
|
-
message: "Internal server error"
|
|
2851
|
-
},
|
|
2852
|
-
id: null
|
|
2853
|
-
});
|
|
2854
|
-
}
|
|
2855
|
-
});
|
|
2856
|
-
app.get("/mcp", (_req, res) => {
|
|
2857
|
-
res.writeHead(405).end(JSON.stringify({
|
|
2858
|
-
jsonrpc: "2.0",
|
|
2859
|
-
error: {
|
|
2860
|
-
code: -32e3,
|
|
2861
|
-
message: "Method not allowed."
|
|
2862
|
-
},
|
|
2863
|
-
id: null
|
|
2864
|
-
}));
|
|
2865
|
-
});
|
|
2866
|
-
app.delete("/mcp", (_req, res) => {
|
|
2867
|
-
res.status(200).end();
|
|
2868
|
-
});
|
|
2869
|
-
}
|
|
2870
|
-
const terminalWss = new ws.WebSocketServer({ noServer: true });
|
|
2871
|
-
const ptyManager = enableTerminal ? PtyManager.getInstance() : null;
|
|
2872
|
-
if (ptyManager && !ptyManager.isAvailable()) log("WARN", "server", "node-pty not available — terminal features disabled");
|
|
2873
|
-
terminalWss.on("connection", (ws$2) => {
|
|
2874
|
-
if (!ptyManager) return;
|
|
2875
|
-
log("INFO", "terminal-ws", "Client connected");
|
|
2876
|
-
ptyManager.addClient(ws$2);
|
|
2877
|
-
if (!ptyManager.isAvailable()) ws$2.send(JSON.stringify({
|
|
2878
|
-
type: "unavailable",
|
|
2879
|
-
reason: "node-pty is not installed"
|
|
2880
|
-
}));
|
|
2881
|
-
ws$2.on("message", (raw) => {
|
|
2882
|
-
try {
|
|
2883
|
-
const msg = JSON.parse(typeof raw === "string" ? raw : raw.toString());
|
|
2884
|
-
switch (msg.type) {
|
|
2885
|
-
case "input":
|
|
2886
|
-
ptyManager.write(msg.data);
|
|
2887
|
-
break;
|
|
2888
|
-
case "resize":
|
|
2889
|
-
ptyManager.resize(msg.cols, msg.rows);
|
|
2890
|
-
break;
|
|
2891
|
-
case "spawn":
|
|
2892
|
-
try {
|
|
2893
|
-
ptyManager.spawn({
|
|
2894
|
-
skipPermissions: !!msg.skipPermissions,
|
|
2895
|
-
cwd: projectCwd,
|
|
2896
|
-
cols: msg.cols,
|
|
2897
|
-
rows: msg.rows
|
|
2898
|
-
});
|
|
2899
|
-
} catch (err) {
|
|
2900
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2901
|
-
ws$2.send(JSON.stringify({
|
|
2902
|
-
type: "output",
|
|
2903
|
-
data: `\r\n\x1b[31mFailed to start terminal: ${errMsg}\x1b[0m\r\n`
|
|
2904
|
-
}));
|
|
2905
|
-
}
|
|
2906
|
-
break;
|
|
2907
|
-
case "kill":
|
|
2908
|
-
ptyManager.kill();
|
|
2909
|
-
break;
|
|
2910
|
-
}
|
|
2911
|
-
} catch (err) {
|
|
2912
|
-
log("ERROR", "terminal-ws", `Message handling error: ${err.message}`);
|
|
2913
|
-
}
|
|
2914
|
-
});
|
|
2915
|
-
ws$2.on("close", () => {
|
|
2916
|
-
log("INFO", "terminal-ws", "Client disconnected");
|
|
2917
|
-
ptyManager.removeClient(ws$2);
|
|
2918
|
-
});
|
|
2919
|
-
ws$2.on("error", (err) => {
|
|
2920
|
-
log("ERROR", "terminal-ws", `Client error: ${err.message}`);
|
|
2921
|
-
});
|
|
2922
|
-
});
|
|
2923
|
-
const server = (0, node_http.createServer)(app);
|
|
2924
|
-
server.on("upgrade", (req, socket, head) => {
|
|
2925
|
-
const url = req.url || "/";
|
|
2926
|
-
if (url === "/ws/terminal" || url.startsWith("/ws/terminal?")) terminalWss.handleUpgrade(req, socket, head, (ws$3) => {
|
|
2927
|
-
terminalWss.emit("connection", ws$3, req);
|
|
2928
|
-
});
|
|
2929
|
-
else if (url === "/api/ws" || url.startsWith("/api/ws?")) wss.handleUpgrade(req, socket, head, (ws$4) => {
|
|
2930
|
-
wss.emit("connection", ws$4, req);
|
|
2931
|
-
});
|
|
2932
|
-
else socket.destroy();
|
|
2933
|
-
});
|
|
2934
|
-
async function start() {
|
|
2935
|
-
const port = await (0, detect_port.default)(config.port);
|
|
2936
|
-
resolvedPort = port;
|
|
2937
|
-
await setupWatcher();
|
|
2938
|
-
return new Promise((resolve) => {
|
|
2939
|
-
server.listen(port, host, () => {
|
|
2940
|
-
serverReady = true;
|
|
2941
|
-
log("INFO", "server", `Backend ready on ${host}:${port} for ${projectCwd}`);
|
|
2942
|
-
if (enableMcp) log("INFO", "mcp", `MCP endpoint available at http://127.0.0.1:${port}/mcp`);
|
|
2943
|
-
resolve();
|
|
2944
|
-
});
|
|
2945
|
-
});
|
|
2946
|
-
}
|
|
2947
|
-
async function stop() {
|
|
2948
|
-
log("INFO", "server", "Shutting down...");
|
|
2949
|
-
clearInterval(cleanupInterval);
|
|
2950
|
-
if (ptyManager) ptyManager.kill();
|
|
2951
|
-
if (watcher) await watcher.close().catch(() => {});
|
|
2952
|
-
terminalWss.close(() => {});
|
|
2953
|
-
wss.close(() => {});
|
|
2954
|
-
return new Promise((resolve) => {
|
|
2955
|
-
server.close(() => {
|
|
2956
|
-
log("INFO", "server", "Server closed");
|
|
2957
|
-
resolve();
|
|
2958
|
-
});
|
|
2959
|
-
});
|
|
2960
|
-
}
|
|
2961
|
-
shutdownFn = () => {
|
|
2962
|
-
stop().then(() => process.exit(0)).catch(() => process.exit(1));
|
|
2963
|
-
};
|
|
2964
|
-
function getStatus() {
|
|
2965
|
-
return {
|
|
2966
|
-
status: serverReady ? "ok" : "starting",
|
|
2967
|
-
ready: serverReady,
|
|
2968
|
-
port: resolvedPort,
|
|
2969
|
-
cwd: projectCwd,
|
|
2970
|
-
uptime: (Date.now() - startTime) / 1e3,
|
|
2971
|
-
pid: process.pid,
|
|
2972
|
-
mcpEndpoint: enableMcp ? `http://127.0.0.1:${resolvedPort}/mcp` : null,
|
|
2973
|
-
terminalAvailable: ptyManager?.isAvailable() ?? false,
|
|
2974
|
-
connectedClients: clientCount
|
|
2975
|
-
};
|
|
2976
|
-
}
|
|
2977
|
-
function getPort() {
|
|
2978
|
-
return resolvedPort;
|
|
2979
|
-
}
|
|
2980
|
-
return {
|
|
2981
|
-
start,
|
|
2982
|
-
stop,
|
|
2983
|
-
getStatus,
|
|
2984
|
-
getPort
|
|
2985
|
-
};
|
|
2986
|
-
}
|
|
2987
|
-
function getLocalNetworkIp() {
|
|
2988
|
-
const ifaces = node_os.networkInterfaces();
|
|
2989
|
-
for (const iface of Object.values(ifaces)) for (const info of iface ?? []) if (info.family === "IPv4" && !info.internal) return info.address;
|
|
2990
|
-
return null;
|
|
2991
|
-
}
|
|
2992
|
-
|
|
2993
|
-
//#endregion
|
|
2994
|
-
exports.createBackendServer = createBackendServer;
|
|
2995
|
-
//# sourceMappingURL=server-By0TN-nC.cjs.map
|