labanalyze 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fei Fang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # labanalyze — Azure Local Lab MCP Server
2
+
3
+ `labanalyze` is an MCP server that gives AI assistants (Copilot CLI, Claude, etc.) direct access to Azure Local lab **Deployment Virtual Machines (DVMs)** and cluster nodes via PowerShell remoting (WinRM). It exposes 15 tools covering connection management, cluster health, log retrieval, and arbitrary PowerShell execution — no credentials are ever stored on disk.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌──────────────┐ stdio ┌──────────────┐ WinRM/PS ┌─────────┐
9
+ │ Copilot CLI │ ◄────────────► │ labanalyze │ ──────────────► │ DVM │ (discovery only)
10
+ │ (or other │ (MCP proto) │ MCP server │ (Administrator)└─────────┘
11
+ │ MCP client) │ │ │ WinRM/PS ┌──────────────┐
12
+ └──────────────┘ │ (Node.js, │ ──────────────► │ Cluster Node │ (direct access)
13
+ │ spawns │ (domain\hci- │ ⭐ Seed Node │ (deploy logs)
14
+ │ pwsh) │ deploymentuser)│ │
15
+ └──────────────┘ └──────────────┘
16
+ ```
17
+
18
+ The server runs as a Node.js process started by the MCP client. It manages a single long-lived `pwsh` (or Windows `powershell.exe`) child process that holds persistent `PSSession` connections — one to the DVM (for AD/cluster discovery, opened as `Administrator`) and one per cluster node (opened lazily as `<domain>\hcideploymentuser` for direct queries). The WinRM client lives inside the `pwsh` child; Node communicates with it over stdin/stdout via a delimiter-framed protocol. Credentials live only in process memory.
19
+
20
+ ## Quick start (recommended)
21
+
22
+ Add the following snippet to your MCP configuration (e.g. `~/.copilot/mcp-config.json`):
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "labanalyze": {
28
+ "type": "stdio",
29
+ "command": "npx",
30
+ "args": ["-y", "labanalyze"]
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ > **Note**: This works once the package is published to npm. For local development from the tarball, see [Build from source](#build-from-source).
37
+
38
+ ## Prerequisites
39
+
40
+ - Node.js 18+
41
+ - PowerShell 7+ (`pwsh`) recommended; on Windows the server falls back to `powershell.exe` automatically
42
+ - Network access to the lab (VPN connected)
43
+ - DVM password (provided at runtime via `connect_to_dvm` — never stored)
44
+
45
+ ## Lab conventions
46
+
47
+ - **DVM** is used for discovery only — finding the cluster name/IP and node names/IPs. Accessed as `Administrator` (no domain prefix).
48
+ - **Cluster nodes** are accessed **directly** via WinRM using `<domain>\hcideploymentuser` with the same password as the DVM.
49
+ - **Seed node** is the first node alphabetically — Azure Local deployment logs are always at `C:\CloudDeployment\Logs` on this node.
50
+ - Cluster objects in AD have names like `*-cl` or `s-cluster*`.
51
+
52
+ ## Usage
53
+
54
+ ### 1. Connect to DVM (just IP + password)
55
+ ```
56
+ User: Connect to the DVM at 10.57.64.5 with password <secret>
57
+ → connect_to_dvm(ip="10.57.64.5", password="...")
58
+ ```
59
+ Domain is auto-discovered. Username defaults to `Administrator`.
60
+
61
+ ### 2. List cluster nodes
62
+ ```
63
+ User: List the cluster nodes
64
+ → list_cluster_nodes()
65
+ ```
66
+ Returns cluster name, node IPs, and identifies the seed node.
67
+
68
+ ### 3. Check cluster status (defaults to seed node)
69
+ ```
70
+ User: What's the cluster status?
71
+ → get_cluster_status() ← automatically uses seed node
72
+ ```
73
+
74
+ ### 4. Get ECE deployment logs (from seed node)
75
+ ```
76
+ User: Show the last 100 lines of ECE logs
77
+ → get_ece_logs(tail_lines=100) ← automatically targets seed node
78
+ ```
79
+
80
+ ### 5. Run arbitrary commands
81
+ ```
82
+ User: Run Get-Process on node hci-node-1
83
+ → run_command_on_node(node="hci-node-1", command="Get-Process")
84
+ ```
85
+
86
+ ### 6. Disconnect
87
+ ```
88
+ User: Disconnect from the lab
89
+ → disconnect_from_dvm()
90
+ ```
91
+
92
+ ## Available tools
93
+
94
+ | Category | Tool | Description |
95
+ |----------|------|-------------|
96
+ | **Connection** | `connect_to_dvm` | Connect to DVM (IP + password, username defaults to Administrator) |
97
+ | | `disconnect_from_dvm` | Disconnect and clear credentials from memory |
98
+ | | `get_connection_status` | Show connection state, cluster info, seed node |
99
+ | | `list_cluster_nodes` | Discover cluster + nodes from DVM (marks seed node) |
100
+ | **Cluster** | `get_cluster_status` | Overall cluster health (defaults to seed node) |
101
+ | | `get_cluster_resources` | List cluster resources and status |
102
+ | | `get_cluster_network_info` | Cluster networks and interfaces |
103
+ | | `get_node_info` | Node details — OS, uptime, hardware |
104
+ | | `get_storage_info` | Storage pools and virtual disks |
105
+ | **Logs** | `get_event_logs` | Windows Event Log entries (defaults to seed node) |
106
+ | | `get_ece_logs` | ECE deployment logs from `C:\CloudDeployment\Logs` |
107
+ | | `get_deployment_status` | Azure Local deployment action plan status |
108
+ | | `get_file_content` | Read any file from a cluster node |
109
+ | **Commands** | `run_command_on_dvm` | Execute arbitrary PowerShell on DVM |
110
+ | | `run_command_on_node` | Execute arbitrary PowerShell on cluster node (direct) |
111
+
112
+ ## Security
113
+
114
+ - **No hardcoded credentials** — password passed as tool parameter at runtime
115
+ - **In-memory only** — credentials and sessions exist only in process memory
116
+ - **No config file secrets** — MCP config contains only the command to start the server
117
+ - **Lab/test use only** — the general-purpose command tools execute arbitrary PowerShell by design
118
+
119
+ ## Troubleshooting
120
+
121
+ ### "Not connected to a DVM"
122
+ Call `connect_to_dvm` first with the DVM IP and password.
123
+
124
+ ### WinRM connection fails
125
+ - Ensure you are connected to the lab VPN
126
+ - Verify the DVM IP is correct and reachable (`ping <ip>`)
127
+ - Check that WinRM is enabled on the DVM (port 5985 for HTTP)
128
+
129
+ ### "No seed node discovered"
130
+ Call `list_cluster_nodes` after connecting to the DVM. This discovers the cluster and its nodes.
131
+
132
+ ### "Access denied" on cluster nodes
133
+ - Cluster nodes are accessed directly as `<domain>\hcideploymentuser`
134
+ - Domain is auto-discovered from the DVM — ensure the DVM is domain-joined
135
+ - The password is the same as the DVM password
136
+
137
+ ### "PowerShell host not found"
138
+ Install PowerShell 7 (`pwsh`) from https://github.com/PowerShell/PowerShell/releases, or run on Windows where `powershell.exe` is available (the server falls back automatically).
139
+
140
+ ## Build from source
141
+
142
+ ```bash
143
+ cd labanalyzemcp
144
+ npm install
145
+ npm run build
146
+ node dist/index.js
147
+ ```
148
+
149
+ Tests run with `npm test` (53 tests across formatting, state, powershellHost, and connectionManager).
150
+
151
+ ## Project structure
152
+
153
+ ```
154
+ labanalyzemcp/
155
+ ├── package.json # name=labanalyze, bin → dist/index.js
156
+ ├── tsconfig.json
157
+ ├── README.md
158
+ ├── .gitignore
159
+ ├── .npmignore
160
+ ├── src/
161
+ │ ├── index.ts # bin entry: #!/usr/bin/env node + main()
162
+ │ ├── server.ts # MCP server bootstrap, registers tools, stdio transport
163
+ │ ├── state.ts # ServerState, DvmConnectionInfo, CaseInsensitiveMap
164
+ │ ├── connection/
165
+ │ │ ├── powershellHost.ts # long-lived pwsh child + delimiter protocol
166
+ │ │ └── connectionManager.ts # DVM/node PSSession lifecycle, AD discovery
167
+ │ ├── helpers/
168
+ │ │ └── formatting.ts # markdown output helpers
169
+ │ └── tools/
170
+ │ ├── _helpers.ts # requireConnected / resolveNode (shared)
171
+ │ ├── connectionTools.ts # connect/disconnect/status/list_cluster_nodes (4)
172
+ │ ├── clusterTools.ts # cluster status/resources/network/node/storage (5)
173
+ │ ├── logTools.ts # event/ECE/deployment-status/file-content (4)
174
+ │ └── commandTools.ts # arbitrary command on DVM / node (2)
175
+ └── tests/
176
+ ├── state.test.ts
177
+ ├── formatting.test.ts
178
+ ├── powershellHost.test.ts
179
+ ├── connectionManager.test.ts
180
+ └── fixtures/
181
+ └── mock-pwsh.mjs # Node-based pwsh stand-in for unit tests
182
+ ```
183
+
@@ -0,0 +1,163 @@
1
+ /** Wraps a user-supplied string value in single quotes, doubling any embedded single quotes. */
2
+ function psQuote(s) {
3
+ return "'" + s.replaceAll("'", "''") + "'";
4
+ }
5
+ export class ConnectionManager {
6
+ state;
7
+ _host;
8
+ constructor(state, host) {
9
+ this.state = state;
10
+ this._host = host;
11
+ }
12
+ get isConnected() {
13
+ return this.state.connected && this._host.isAlive();
14
+ }
15
+ ensureConnected() {
16
+ if (!this.isConnected) {
17
+ throw new Error("Not connected to a DVM. Call connect_to_dvm first.");
18
+ }
19
+ }
20
+ async connect(info) {
21
+ this.state.connectionInfo = info;
22
+ try {
23
+ if (!this._host.isAlive()) {
24
+ await this._host.start();
25
+ }
26
+ await this._host.setEnvVar("LABMCP_PWD", info.password);
27
+ const usernameQ = psQuote(info.username);
28
+ const ipQ = psQuote(info.ip);
29
+ const sslSwitch = info.useSsl ? " -UseSSL" : "";
30
+ const connectScript = [
31
+ `$script:DvmCred = New-Object System.Management.Automation.PSCredential(${usernameQ}, (ConvertTo-SecureString $env:LABMCP_PWD -AsPlainText -Force))`,
32
+ `Remove-Item Env:\\LABMCP_PWD -ErrorAction SilentlyContinue`,
33
+ `$so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -OperationTimeout 60000 -OpenTimeout 30000`,
34
+ `$script:DvmSession = New-PSSession -ComputerName ${ipQ} -Port ${info.winRmPort} -Authentication Negotiate -Credential $script:DvmCred -SessionOption $so${sslSwitch}`,
35
+ `Invoke-Command -Session $script:DvmSession -ScriptBlock { hostname }`,
36
+ ].join("\n");
37
+ const result = await this._host.runScript(connectScript);
38
+ if (result.stderr || !result.stdout.trim()) {
39
+ this.state.connected = false;
40
+ this.state.lastError = result.stderr;
41
+ return `Failed to connect to DVM at ${info.ip}: ${result.stderr}`;
42
+ }
43
+ const hostname = result.stdout.trim();
44
+ // Auto-discover domain from DVM
45
+ const domainResult = await this._host.runScript(`Invoke-Command -Session $script:DvmSession -ScriptBlock { (Get-CimInstance Win32_ComputerSystem).Domain }`);
46
+ if (!domainResult.stderr && domainResult.stdout.trim()) {
47
+ const fqdn = domainResult.stdout.trim();
48
+ this.state.domain = fqdn.includes(".") ? fqdn.split(".")[0] : fqdn;
49
+ }
50
+ this.state.connected = true;
51
+ this.state.lastError = "";
52
+ return `Connected to DVM at ${info.ip} (hostname: ${hostname}, domain: ${this.state.domain})`;
53
+ }
54
+ catch (ex) {
55
+ this.state.connected = false;
56
+ this.state.lastError = ex instanceof Error ? ex.message : String(ex);
57
+ return `Failed to connect to DVM at ${info.ip}: ${this.state.lastError}`;
58
+ }
59
+ }
60
+ async disconnect() {
61
+ const ip = this.state.connectionInfo.ip;
62
+ const disconnectScript = [
63
+ `if ($script:DvmSession) { Remove-PSSession $script:DvmSession -ErrorAction SilentlyContinue; $script:DvmSession = $null }`,
64
+ `if ($script:NodeSessions) { $script:NodeSessions.Values | ForEach-Object { Remove-PSSession $_ -ErrorAction SilentlyContinue }; $script:NodeSessions = @{} }`,
65
+ `$script:DvmCred = $null; $script:ClusterCred = $null`,
66
+ ].join("\n");
67
+ await this._host.runScript(disconnectScript);
68
+ this.state.reset();
69
+ return `Disconnected from DVM at ${ip}`;
70
+ }
71
+ async runOnDvm(command) {
72
+ this.ensureConnected();
73
+ return this._host.runScript(`Invoke-Command -Session $script:DvmSession -ScriptBlock { ${command} }`);
74
+ }
75
+ async runOnNode(node, command) {
76
+ this.ensureConnected();
77
+ const ip = this.state.nodeIpMap.get(node) ?? node;
78
+ const info = this.state.connectionInfo;
79
+ const clusterUserQ = psQuote(this.state.clusterQualifiedUsername);
80
+ const ipQ = psQuote(ip);
81
+ const sslSwitch = info.useSsl ? " -UseSSL" : "";
82
+ try {
83
+ const nodeScript = [
84
+ `if (-not $script:NodeSessions) { $script:NodeSessions = @{} }`,
85
+ `if (-not $script:ClusterCred) {`,
86
+ ` $script:ClusterCred = New-Object System.Management.Automation.PSCredential(${clusterUserQ}, $script:DvmCred.Password)`,
87
+ `}`,
88
+ `if (-not $script:NodeSessions[${ipQ}]) {`,
89
+ ` $so = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -OperationTimeout 60000 -OpenTimeout 30000`,
90
+ ` $script:NodeSessions[${ipQ}] = New-PSSession -ComputerName ${ipQ} -Port ${info.winRmPort} -Authentication Negotiate -Credential $script:ClusterCred -SessionOption $so${sslSwitch}`,
91
+ `}`,
92
+ `Invoke-Command -Session $script:NodeSessions[${ipQ}] -ScriptBlock { ${command} }`,
93
+ ].join("\n");
94
+ return await this._host.runScript(nodeScript);
95
+ }
96
+ catch (err) {
97
+ return { stdout: "", stderr: `Failed to connect to node ${node} (${ip}): ${err}`, exitCode: -1 };
98
+ }
99
+ }
100
+ async discoverNodes() {
101
+ this.ensureConnected();
102
+ // Query AD for cluster objects matching lab naming patterns (*-cl or s-cluster)
103
+ // Copied character-for-character from ConnectionManager.cs
104
+ const clusterQuery = "try { " +
105
+ " $clusters = Get-ADComputer -Filter 'Name -like \"*-cl\" -or Name -like \"s-cluster*\"' -Properties IPv4Address -ErrorAction Stop; " +
106
+ " $clusters | ForEach-Object { \"$($_.Name)|$($_.IPv4Address)\" } " +
107
+ "} catch { 'AD_QUERY_FAILED' }";
108
+ const clusterResult = await this.runOnDvm(clusterQuery);
109
+ if (clusterResult.exitCode === 0 &&
110
+ clusterResult.stdout.trim() &&
111
+ clusterResult.stdout.trim() !== "AD_QUERY_FAILED") {
112
+ const lines = clusterResult.stdout
113
+ .trim()
114
+ .split("\n")
115
+ .filter((l) => l.trim());
116
+ for (const line of lines) {
117
+ const parts = line.trim().split("|");
118
+ if (parts.length >= 2) {
119
+ this.state.clusterName = parts[0].trim();
120
+ this.state.clusterIp = parts[1].trim();
121
+ break; // Take the first cluster found
122
+ }
123
+ }
124
+ }
125
+ // Query AD for all computers except DVM and cluster object, to find actual nodes
126
+ // Copied character-for-character from ConnectionManager.cs
127
+ const nodesQuery = "try { " +
128
+ " $dvmName = $env:COMPUTERNAME; " +
129
+ " Get-ADComputer -Filter * -Properties IPv4Address, OperatingSystem -ErrorAction Stop | " +
130
+ " Where-Object { $_.Name -ne $dvmName -and $_.Name -notlike '*-cl' -and $_.Name -notlike 's-cluster*' -and $_.OperatingSystem -like '*Azure Stack HCI*' } | " +
131
+ " ForEach-Object { \"$($_.Name)|$($_.IPv4Address)\" } " +
132
+ "} catch { 'AD_QUERY_FAILED' }";
133
+ const nodesResult = await this.runOnDvm(nodesQuery);
134
+ if (nodesResult.exitCode === 0 &&
135
+ nodesResult.stdout.trim() &&
136
+ nodesResult.stdout.trim() !== "AD_QUERY_FAILED") {
137
+ const tempMap = new Map();
138
+ const lines = nodesResult.stdout
139
+ .trim()
140
+ .split("\n")
141
+ .filter((l) => l.trim());
142
+ for (const line of lines) {
143
+ const parts = line.trim().split("|");
144
+ if (parts.length >= 2 && parts[0].trim() && parts[1].trim()) {
145
+ tempMap.set(parts[0].trim(), parts[1].trim());
146
+ }
147
+ }
148
+ const sortedNodes = Array.from(tempMap.keys()).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }));
149
+ this.state.discoveredNodes = sortedNodes;
150
+ this.state.nodeIpMap.clear();
151
+ for (const [name, nodeIp] of tempMap) {
152
+ this.state.nodeIpMap.set(name, nodeIp);
153
+ }
154
+ this.state.seedNode = sortedNodes.length > 0 ? sortedNodes[0] : "";
155
+ return sortedNodes;
156
+ }
157
+ return [];
158
+ }
159
+ async dispose() {
160
+ await this._host.dispose();
161
+ }
162
+ }
163
+ //# sourceMappingURL=connectionManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connectionManager.js","sourceRoot":"","sources":["../../src/connection/connectionManager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,192 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createInterface } from "node:readline";
3
+ const DEFAULT_TIMEOUT_MS = 60_000;
4
+ const PS_ARGS = ["-NoProfile", "-NoLogo", "-NonInteractive", "-Command", "-"];
5
+ export class PowerShellHost {
6
+ _opts;
7
+ _child;
8
+ _rlOut;
9
+ _rlErr;
10
+ _alive = false;
11
+ _id = 0;
12
+ _queue = Promise.resolve();
13
+ _pendingReject;
14
+ _lineHandlers = [];
15
+ constructor(opts) {
16
+ this._opts = opts ?? {};
17
+ }
18
+ _log(msg) {
19
+ if (this._opts.logger) {
20
+ this._opts.logger(msg);
21
+ }
22
+ else {
23
+ process.stderr.write(`[pwsh] ${msg}\n`);
24
+ }
25
+ }
26
+ async start() {
27
+ if (this._child)
28
+ return;
29
+ const child = await this._spawnChild();
30
+ this._child = child;
31
+ this._alive = true;
32
+ this._rlOut = createInterface({ input: child.stdout, crlfDelay: Infinity });
33
+ this._rlErr = createInterface({ input: child.stderr, crlfDelay: Infinity });
34
+ this._rlErr.on("line", (line) => {
35
+ this._log(line);
36
+ });
37
+ this._rlOut.on("line", (line) => {
38
+ const trimmed = line.replace(/\r$/, "");
39
+ for (const handler of this._lineHandlers) {
40
+ handler(trimmed);
41
+ }
42
+ });
43
+ child.once("exit", (code) => {
44
+ this._alive = false;
45
+ if (this._pendingReject) {
46
+ const reject = this._pendingReject;
47
+ this._pendingReject = undefined;
48
+ reject(new Error(`PowerShell host exited (code ${code})`));
49
+ }
50
+ });
51
+ }
52
+ _spawnChild() {
53
+ if (this._opts.hostCommand) {
54
+ const { cmd, args } = this._opts.hostCommand;
55
+ return new Promise((resolve, reject) => {
56
+ const child = spawn(cmd, args, { stdio: ["pipe", "pipe", "pipe"] });
57
+ child.once("error", reject);
58
+ child.once("spawn", () => {
59
+ child.removeListener("error", reject);
60
+ resolve(child);
61
+ });
62
+ });
63
+ }
64
+ return new Promise((resolve, reject) => {
65
+ const tryFallback = () => {
66
+ const child2 = spawn("powershell.exe", PS_ARGS, { stdio: ["pipe", "pipe", "pipe"] });
67
+ child2.once("error", reject);
68
+ child2.once("spawn", () => {
69
+ child2.removeListener("error", reject);
70
+ resolve(child2);
71
+ });
72
+ };
73
+ const onError = (err) => {
74
+ if (err.code === "ENOENT" && process.platform === "win32") {
75
+ tryFallback();
76
+ }
77
+ else {
78
+ reject(err);
79
+ }
80
+ };
81
+ const child1 = spawn("pwsh", PS_ARGS, { stdio: ["pipe", "pipe", "pipe"] });
82
+ child1.once("error", onError);
83
+ child1.once("spawn", () => {
84
+ child1.removeListener("error", onError);
85
+ resolve(child1);
86
+ });
87
+ });
88
+ }
89
+ async runScript(script) {
90
+ const result = this._queue.then(() => this._runScriptImpl(script));
91
+ this._queue = result.then(() => undefined, () => undefined);
92
+ return result;
93
+ }
94
+ _runScriptImpl(script) {
95
+ if (!this._alive || !this._child) {
96
+ return Promise.reject(new Error("PowerShell host is not running"));
97
+ }
98
+ const id = ++this._id;
99
+ const endMarker = `__LABMCP_END__${id}__`;
100
+ const wrapper = `try { & { ${script} } *>&1 | ForEach-Object { if ($_ -is [System.Management.Automation.ErrorRecord]) { Write-Output ('__LABMCP_ERR__' + $_.ToString()) } else { Write-Output $_ } } } catch { Write-Output ('__LABMCP_ERR__' + $_.ToString()) }\n` +
101
+ `Write-Output '${endMarker}'\n`;
102
+ const outLines = [];
103
+ const errLines = [];
104
+ const timeoutMs = this._opts.commandTimeoutMs ?? DEFAULT_TIMEOUT_MS;
105
+ return new Promise((resolve, reject) => {
106
+ let settled = false;
107
+ const settle = (fn) => {
108
+ if (settled)
109
+ return;
110
+ settled = true;
111
+ clearTimeout(timeoutHandle);
112
+ this._pendingReject = undefined;
113
+ const idx = this._lineHandlers.indexOf(lineHandler);
114
+ if (idx >= 0)
115
+ this._lineHandlers.splice(idx, 1);
116
+ fn();
117
+ };
118
+ const lineHandler = (line) => {
119
+ if (line === endMarker) {
120
+ settle(() => resolve({
121
+ stdout: outLines.join("\n"),
122
+ stderr: errLines.join("\n"),
123
+ exitCode: errLines.length ? 1 : 0,
124
+ }));
125
+ }
126
+ else if (line.startsWith("__LABMCP_ERR__")) {
127
+ errLines.push(line.slice("__LABMCP_ERR__".length));
128
+ }
129
+ else {
130
+ outLines.push(line);
131
+ }
132
+ };
133
+ this._lineHandlers.push(lineHandler);
134
+ this._pendingReject = (err) => {
135
+ settle(() => reject(err));
136
+ };
137
+ const timeoutHandle = setTimeout(() => {
138
+ this._alive = false;
139
+ try {
140
+ this._child?.kill("SIGKILL");
141
+ }
142
+ catch { /* ignore */ }
143
+ settle(() => reject(new Error("PowerShell command timed out")));
144
+ }, timeoutMs);
145
+ try {
146
+ this._child.stdin.write(wrapper);
147
+ }
148
+ catch (err) {
149
+ settle(() => reject(err instanceof Error ? err : new Error(String(err))));
150
+ }
151
+ });
152
+ }
153
+ async setEnvVar(name, value) {
154
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
155
+ throw new Error(`Invalid environment variable name: ${name}`);
156
+ }
157
+ const escaped = value.replaceAll("'", "''");
158
+ await this.runScript(`$env:${name} = '${escaped}'`);
159
+ }
160
+ async clearEnvVar(name) {
161
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
162
+ throw new Error(`Invalid environment variable name: ${name}`);
163
+ }
164
+ await this.runScript(`Remove-Item Env:\\${name} -ErrorAction SilentlyContinue`);
165
+ }
166
+ isAlive() {
167
+ return this._alive;
168
+ }
169
+ async dispose() {
170
+ if (!this._alive)
171
+ return;
172
+ this._alive = false;
173
+ const child = this._child;
174
+ if (!child)
175
+ return;
176
+ return new Promise((resolve) => {
177
+ child.once("close", resolve);
178
+ try {
179
+ child.stdin.write("exit\n");
180
+ child.stdin.end();
181
+ }
182
+ catch { /* stdin may already be closed */ }
183
+ setTimeout(() => {
184
+ try {
185
+ child.kill("SIGKILL");
186
+ }
187
+ catch { /* ignore */ }
188
+ }, 2000);
189
+ });
190
+ }
191
+ }
192
+ //# sourceMappingURL=powershellHost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"powershellHost.js","sourceRoot":"","sources":["../../src/connection/powershellHost.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ export function formatCommandResult(title, stdout, stderr, exitCode) {
2
+ const parts = [`## ${title}`];
3
+ parts.push(exitCode !== 0
4
+ ? `**Status**: ❌ Failed (exit code ${exitCode})`
5
+ : "**Status**: ✅ Success");
6
+ if (stdout.trim().length > 0)
7
+ parts.push(`\n### Output\n\`\`\`\n${stdout.trim()}\n\`\`\``);
8
+ if (stderr.trim().length > 0)
9
+ parts.push(`\n### Errors\n\`\`\`\n${stderr.trim()}\n\`\`\``);
10
+ return parts.join("\n");
11
+ }
12
+ export function formatTable(headers, rows) {
13
+ if (rows.length === 0)
14
+ return "(no data)";
15
+ const lines = [
16
+ "| " + headers.join(" | ") + " |",
17
+ "| " + headers.map(() => "---").join(" | ") + " |",
18
+ ];
19
+ for (const row of rows)
20
+ lines.push("| " + row.join(" | ") + " |");
21
+ return lines.join("\n");
22
+ }
23
+ export function formatConnectionStatus(connected, ip, domain, username, nodes, clusterName, clusterIp, seedNode) {
24
+ const parts = [
25
+ "## DVM Connection Status",
26
+ `**Connected**: ${connected ? "✅ Yes" : "❌ No"}`,
27
+ ];
28
+ if (connected) {
29
+ parts.push(`**DVM IP**: ${ip}`);
30
+ parts.push(`**Domain**: ${domain === "" ? "(auto-discovering...)" : domain}`);
31
+ parts.push(`**Username**: ${username}`);
32
+ if (clusterName !== "")
33
+ parts.push(`**Cluster**: ${clusterName} (${clusterIp})`);
34
+ if (seedNode !== "")
35
+ parts.push(`**Seed Node**: ${seedNode} (deployment logs here)`);
36
+ parts.push(nodes.length > 0
37
+ ? `**Discovered Nodes**: ${nodes.join(", ")}`
38
+ : "**Discovered Nodes**: (none yet — call list_cluster_nodes)");
39
+ }
40
+ return parts.join("\n");
41
+ }
42
+ export function formatError(message) {
43
+ return `❌ **Error**: ${message}`;
44
+ }
45
+ export function formatNodeList(nodes, nodeIpMap, seedNode, clusterName, clusterIp) {
46
+ if (nodes.length === 0)
47
+ return "No cluster nodes discovered. The DVM may not have cluster cmdlets installed, or the cluster may not be deployed yet.";
48
+ const parts = ["## Cluster Nodes"];
49
+ if (clusterName !== "")
50
+ parts.push(`**Cluster**: ${clusterName} (${clusterIp})`);
51
+ parts.push("");
52
+ for (let i = 0; i < nodes.length; i++) {
53
+ const node = nodes[i];
54
+ const ip = nodeIpMap.get(node) ?? "unknown";
55
+ const marker = node.toLowerCase() === seedNode.toLowerCase() ? " ⭐ seed node" : "";
56
+ parts.push(`${i + 1}. \`${node}\` (${ip})${marker}`);
57
+ }
58
+ parts.push(`\n**Total**: ${nodes.length} node(s)`);
59
+ return parts.join("\n");
60
+ }
61
+ //# sourceMappingURL=formatting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.js","sourceRoot":"","sources":["../../src/helpers/formatting.ts"],"names":[],"mappings":"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"}