@components-kit/open-workbook 0.1.0
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 +21 -0
- package/README.md +80 -0
- package/assets/backend/dist/addin-rpc-client.d.ts +17 -0
- package/assets/backend/dist/addin-rpc-client.d.ts.map +1 -0
- package/assets/backend/dist/addin-rpc-client.js +66 -0
- package/assets/backend/dist/addin-rpc-client.js.map +1 -0
- package/assets/backend/dist/addin-websocket-server.d.ts +13 -0
- package/assets/backend/dist/addin-websocket-server.d.ts.map +1 -0
- package/assets/backend/dist/addin-websocket-server.js +199 -0
- package/assets/backend/dist/addin-websocket-server.js.map +1 -0
- package/assets/backend/dist/file-bridge.d.ts +2 -0
- package/assets/backend/dist/file-bridge.d.ts.map +1 -0
- package/assets/backend/dist/file-bridge.js +15 -0
- package/assets/backend/dist/file-bridge.js.map +1 -0
- package/assets/backend/dist/index.d.ts +4 -0
- package/assets/backend/dist/index.d.ts.map +1 -0
- package/assets/backend/dist/index.js +17 -0
- package/assets/backend/dist/index.js.map +1 -0
- package/assets/backend/dist/native-file-bridge-server.d.ts +28 -0
- package/assets/backend/dist/native-file-bridge-server.d.ts.map +1 -0
- package/assets/backend/dist/native-file-bridge-server.js +496 -0
- package/assets/backend/dist/native-file-bridge-server.js.map +1 -0
- package/assets/backend/dist/native-file-bridge.d.ts +20 -0
- package/assets/backend/dist/native-file-bridge.d.ts.map +1 -0
- package/assets/backend/dist/native-file-bridge.js +140 -0
- package/assets/backend/dist/native-file-bridge.js.map +1 -0
- package/assets/backend/dist/runtime-service.d.ts +3167 -0
- package/assets/backend/dist/runtime-service.d.ts.map +1 -0
- package/assets/backend/dist/runtime-service.js +6003 -0
- package/assets/backend/dist/runtime-service.js.map +1 -0
- package/assets/backend/dist/session-registry.d.ts +20 -0
- package/assets/backend/dist/session-registry.d.ts.map +1 -0
- package/assets/backend/dist/session-registry.js +53 -0
- package/assets/backend/dist/session-registry.js.map +1 -0
- package/assets/backend/dist/state-store.d.ts +26 -0
- package/assets/backend/dist/state-store.d.ts.map +1 -0
- package/assets/backend/dist/state-store.js +44 -0
- package/assets/backend/dist/state-store.js.map +1 -0
- package/assets/excel-addin/dist/connection.d.ts +26 -0
- package/assets/excel-addin/dist/connection.d.ts.map +1 -0
- package/assets/excel-addin/dist/connection.js +320 -0
- package/assets/excel-addin/dist/connection.js.map +1 -0
- package/assets/excel-addin/dist/excel-executor.d.ts +225 -0
- package/assets/excel-addin/dist/excel-executor.d.ts.map +1 -0
- package/assets/excel-addin/dist/excel-executor.js +2487 -0
- package/assets/excel-addin/dist/excel-executor.js.map +1 -0
- package/assets/excel-addin/dist/taskpane.d.ts +2 -0
- package/assets/excel-addin/dist/taskpane.d.ts.map +1 -0
- package/assets/excel-addin/dist/taskpane.js +28 -0
- package/assets/excel-addin/dist/taskpane.js.map +1 -0
- package/assets/excel-addin/manifest.xml +93 -0
- package/assets/excel-addin/public/taskpane.css +47 -0
- package/assets/excel-addin/public/taskpane.html +35 -0
- package/assets/excel-addin/scripts/dev-server.mjs +128 -0
- package/assets/instructions/open-workbook-excel/SKILL.md +52 -0
- package/assets/instructions/open-workbook-excel/references/multi-agent.md +43 -0
- package/assets/instructions/open-workbook-excel/references/performance.md +41 -0
- package/assets/instructions/open-workbook-excel/references/reliability.md +76 -0
- package/assets/instructions/open-workbook-excel/references/tool-selection.md +82 -0
- package/assets/instructions/open-workbook-excel/references/workflows.md +93 -0
- package/assets/mcp-server/dist/catalog.d.ts +5 -0
- package/assets/mcp-server/dist/catalog.d.ts.map +1 -0
- package/assets/mcp-server/dist/catalog.js +8 -0
- package/assets/mcp-server/dist/catalog.js.map +1 -0
- package/assets/mcp-server/dist/index.d.ts +3 -0
- package/assets/mcp-server/dist/index.d.ts.map +1 -0
- package/assets/mcp-server/dist/index.js +3779 -0
- package/assets/mcp-server/dist/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +721 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { dirname, join, resolve } from "node:path";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const repoRoot = resolve(__dirname, "../../..");
|
|
10
|
+
const packageRoot = resolve(__dirname, "..");
|
|
11
|
+
const publicPackageName = "@components-kit/open-workbook";
|
|
12
|
+
const instructionFileName = "open-workbook-excel.md";
|
|
13
|
+
const sourcePaths = {
|
|
14
|
+
mcpServer: resolve(repoRoot, "apps/mcp-server/dist/index.js"),
|
|
15
|
+
backend: resolve(repoRoot, "apps/backend/dist/index.js"),
|
|
16
|
+
fileBridge: resolve(repoRoot, "apps/backend/dist/file-bridge.js"),
|
|
17
|
+
addinServer: resolve(repoRoot, "apps/excel-addin/scripts/dev-server.mjs"),
|
|
18
|
+
manifest: resolve(repoRoot, "apps/excel-addin/manifest.xml"),
|
|
19
|
+
instructions: resolve(repoRoot, "skills/open-workbook-excel")
|
|
20
|
+
};
|
|
21
|
+
const bundledPaths = {
|
|
22
|
+
mcpServer: resolve(packageRoot, "assets/mcp-server/dist/index.js"),
|
|
23
|
+
backend: resolve(packageRoot, "assets/backend/dist/index.js"),
|
|
24
|
+
fileBridge: resolve(packageRoot, "assets/backend/dist/file-bridge.js"),
|
|
25
|
+
addinServer: resolve(packageRoot, "assets/excel-addin/scripts/dev-server.mjs"),
|
|
26
|
+
manifest: resolve(packageRoot, "assets/excel-addin/manifest.xml"),
|
|
27
|
+
instructions: resolve(packageRoot, "assets/instructions/open-workbook-excel")
|
|
28
|
+
};
|
|
29
|
+
const dependencyPaths = {
|
|
30
|
+
mcpServer: resolve(packageRoot, "node_modules/@components-kit/open-workbook-mcp-server/dist/index.js"),
|
|
31
|
+
backend: resolve(packageRoot, "node_modules/@components-kit/open-workbook-backend/dist/index.js"),
|
|
32
|
+
fileBridge: resolve(packageRoot, "node_modules/@components-kit/open-workbook-backend/dist/file-bridge.js"),
|
|
33
|
+
addinServer: bundledPaths.addinServer,
|
|
34
|
+
manifest: bundledPaths.manifest,
|
|
35
|
+
instructions: bundledPaths.instructions
|
|
36
|
+
};
|
|
37
|
+
const program = new Command();
|
|
38
|
+
program.name("owb").description("Open Workbook local CLI").version("0.1.0");
|
|
39
|
+
program
|
|
40
|
+
.command("mcp")
|
|
41
|
+
.description("Start the Open Workbook MCP adapter and local Excel add-in asset server")
|
|
42
|
+
.option("--agent-name <name>", "Agent name shown in collaboration status")
|
|
43
|
+
.option("--daemon-url <url>", "Daemon base URL", defaultDaemonUrl())
|
|
44
|
+
.option("--standalone", "Start a single-process MCP server with embedded backend")
|
|
45
|
+
.option("--no-addin-server", "Do not start the companion local Excel add-in asset server")
|
|
46
|
+
.action(async (options) => {
|
|
47
|
+
const args = [];
|
|
48
|
+
if (options.agentName !== undefined) {
|
|
49
|
+
args.push("--agent-name", options.agentName);
|
|
50
|
+
}
|
|
51
|
+
if (options.daemonUrl !== undefined) {
|
|
52
|
+
args.push("--daemon-url", options.daemonUrl);
|
|
53
|
+
}
|
|
54
|
+
if (options.standalone) {
|
|
55
|
+
args.push("--standalone");
|
|
56
|
+
}
|
|
57
|
+
const companionProcesses = [];
|
|
58
|
+
if (options.addinServer !== false) {
|
|
59
|
+
const addinServer = await startAddinServerIfNeeded();
|
|
60
|
+
if (addinServer) {
|
|
61
|
+
companionProcesses.push(addinServer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
runNode(resolveAsset("mcpServer"), args, companionProcesses);
|
|
65
|
+
});
|
|
66
|
+
program
|
|
67
|
+
.command("setup")
|
|
68
|
+
.description("Initialize Open Workbook and prepare the Excel add-in manifest")
|
|
69
|
+
.option("--dry-run", "Print setup actions without writing files")
|
|
70
|
+
.option("--instructions-out <path>", "Instruction file path", defaultInstructionsPath())
|
|
71
|
+
.option("--manifest-out <path>", "Manifest path for non-macOS setup", defaultSetupManifestPath())
|
|
72
|
+
.option("--addin-url <url>", "Taskpane base URL", defaultAddinUrl())
|
|
73
|
+
.option("--backend-url <url>", "Backend WebSocket URL", defaultBackendUrl())
|
|
74
|
+
.action((options) => {
|
|
75
|
+
const instructionsPath = resolve(options.instructionsOut);
|
|
76
|
+
const manifestPath = setupManifestPath(options.manifestOut);
|
|
77
|
+
const instructions = generateGenericInstructions();
|
|
78
|
+
const manifest = generateManifest({ addinUrl: options.addinUrl, backendUrl: options.backendUrl });
|
|
79
|
+
if (!options.dryRun) {
|
|
80
|
+
writeFileEnsuringDir(instructionsPath, instructions);
|
|
81
|
+
writeFileEnsuringDir(manifestPath, manifest);
|
|
82
|
+
}
|
|
83
|
+
console.log(options.dryRun ? "Open Workbook setup dry run" : "Open Workbook setup complete");
|
|
84
|
+
console.log("");
|
|
85
|
+
console.log(`${options.dryRun ? "Would write" : "Wrote"} fallback instructions: ${instructionsPath}`);
|
|
86
|
+
console.log(`${options.dryRun ? "Would write" : "Wrote"} Excel add-in manifest: ${manifestPath}`);
|
|
87
|
+
console.log(`Taskpane URL: ${options.addinUrl}`);
|
|
88
|
+
console.log(`Backend URL: ${options.backendUrl}`);
|
|
89
|
+
console.log("");
|
|
90
|
+
printManifestNextSteps(manifestPath);
|
|
91
|
+
console.log("");
|
|
92
|
+
console.log("Paste this generic MCP config into any MCP-capable agent UI:");
|
|
93
|
+
console.log(JSON.stringify(genericMcpConfig(), null, 2));
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log("Install the Open Workbook Excel skill with skills.sh:");
|
|
96
|
+
console.log("npx skills add components-kit/open-workbook --skill open-workbook-excel");
|
|
97
|
+
console.log("");
|
|
98
|
+
console.log("For a global OpenCode install:");
|
|
99
|
+
console.log("npx skills add components-kit/open-workbook --skill open-workbook-excel -a opencode -g -y");
|
|
100
|
+
console.log("");
|
|
101
|
+
console.log("The fallback instruction file above is for clients that do not support skills.sh.");
|
|
102
|
+
console.log("Start the agent UI before opening the Excel add-in so `npx ... mcp` can serve the taskpane and backend.");
|
|
103
|
+
});
|
|
104
|
+
program
|
|
105
|
+
.command("instructions")
|
|
106
|
+
.description("Print or write the generic Open Workbook Excel agent instructions")
|
|
107
|
+
.option("--out <path>", "Write instructions to a file instead of stdout")
|
|
108
|
+
.action((options) => {
|
|
109
|
+
const instructions = generateGenericInstructions();
|
|
110
|
+
if (options.out) {
|
|
111
|
+
writeFileEnsuringDir(resolve(options.out), instructions);
|
|
112
|
+
console.log(`Wrote instructions to ${resolve(options.out)}`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
console.log(instructions);
|
|
116
|
+
});
|
|
117
|
+
const daemon = program.command("daemon").description("Manage the shared Open Workbook daemon");
|
|
118
|
+
daemon
|
|
119
|
+
.command("start")
|
|
120
|
+
.description("Start the shared local Open Workbook daemon")
|
|
121
|
+
.action(() => {
|
|
122
|
+
runNode(resolveAsset("backend"));
|
|
123
|
+
});
|
|
124
|
+
daemon
|
|
125
|
+
.command("status")
|
|
126
|
+
.description("Print daemon status from the local health endpoint")
|
|
127
|
+
.option("--daemon-url <url>", "Daemon base URL", defaultDaemonUrl())
|
|
128
|
+
.action(async (options) => {
|
|
129
|
+
const response = await fetchLocal(`${trimTrailingSlash(options.daemonUrl)}/status`, "Daemon status", "owb daemon start");
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
fail(`Daemon status failed: ${response.status} ${response.statusText}`);
|
|
132
|
+
}
|
|
133
|
+
console.log(JSON.stringify(await response.json(), null, 2));
|
|
134
|
+
});
|
|
135
|
+
daemon
|
|
136
|
+
.command("stop")
|
|
137
|
+
.description("Stop the shared local Open Workbook daemon")
|
|
138
|
+
.option("--daemon-url <url>", "Daemon base URL", defaultDaemonUrl())
|
|
139
|
+
.action(async (options) => {
|
|
140
|
+
const response = await fetchLocal(`${trimTrailingSlash(options.daemonUrl)}/shutdown`, "Daemon stop", "owb daemon start", { method: "POST" });
|
|
141
|
+
if (!response.ok) {
|
|
142
|
+
fail(`Daemon stop failed: ${response.status} ${response.statusText}`);
|
|
143
|
+
}
|
|
144
|
+
console.log("Stopped Open Workbook daemon.");
|
|
145
|
+
});
|
|
146
|
+
const fileBridge = program.command("file-bridge").description("Manage the native workbook file bridge");
|
|
147
|
+
fileBridge
|
|
148
|
+
.command("start")
|
|
149
|
+
.description("Start the local native file bridge for Save As and host file operations")
|
|
150
|
+
.action(() => {
|
|
151
|
+
runNode(resolveAsset("fileBridge"));
|
|
152
|
+
});
|
|
153
|
+
fileBridge
|
|
154
|
+
.command("status")
|
|
155
|
+
.description("Print native file bridge status")
|
|
156
|
+
.option("--bridge-url <url>", "File bridge base URL", defaultFileBridgeUrl())
|
|
157
|
+
.action(async (options) => {
|
|
158
|
+
const response = await fetchLocal(`${trimTrailingSlash(options.bridgeUrl)}/status`, "File bridge status", "owb file-bridge start");
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
fail(`File bridge status failed: ${response.status} ${response.statusText}`);
|
|
161
|
+
}
|
|
162
|
+
console.log(JSON.stringify(await response.json(), null, 2));
|
|
163
|
+
});
|
|
164
|
+
fileBridge
|
|
165
|
+
.command("smoke")
|
|
166
|
+
.description("Run a real Excel host smoke through the native file bridge")
|
|
167
|
+
.requiredOption("--workbook <nameOrPath>", "Open Excel workbook name or full path to match")
|
|
168
|
+
.option("--target <path>", "Target .xlsx path for the smoke output")
|
|
169
|
+
.option("--operation <operation>", "Operation: export-copy or save-as", "export-copy")
|
|
170
|
+
.option("--bridge-url <url>", "File bridge base URL", defaultFileBridgeUrl())
|
|
171
|
+
.option("--bridge-path <path>", "File bridge operation route path")
|
|
172
|
+
.option("--confirm-save-as", "Allow the smoke to run workbook.save_as, which changes the open workbook identity")
|
|
173
|
+
.action(async (options) => {
|
|
174
|
+
const operation = normalizeFileBridgeSmokeOperation(options.operation);
|
|
175
|
+
if (operation === "workbook.save_as" && !options.confirmSaveAs) {
|
|
176
|
+
fail("Refusing to run save-as smoke without --confirm-save-as because it changes the open workbook file identity. Use the default export-copy smoke for non-destructive verification.");
|
|
177
|
+
}
|
|
178
|
+
const bridgeUrl = trimTrailingSlash(options.bridgeUrl);
|
|
179
|
+
const statusResponse = await fetchLocal(`${bridgeUrl}/status`, "File bridge smoke status", "owb file-bridge start");
|
|
180
|
+
if (!statusResponse.ok) {
|
|
181
|
+
fail(`File bridge smoke status failed: ${statusResponse.status} ${statusResponse.statusText}`);
|
|
182
|
+
}
|
|
183
|
+
const status = await statusResponse.json().catch(() => undefined);
|
|
184
|
+
if (!status || typeof status !== "object") {
|
|
185
|
+
fail("File bridge smoke status returned non-JSON output.");
|
|
186
|
+
}
|
|
187
|
+
const route = normalizeBridgePath(options.bridgePath ?? status.route ?? defaultFileBridgePath());
|
|
188
|
+
const targetPath = resolve(options.target ?? defaultHostSmokeTarget(operation));
|
|
189
|
+
const request = {
|
|
190
|
+
operation,
|
|
191
|
+
workbookId: options.workbook,
|
|
192
|
+
targetPath,
|
|
193
|
+
reason: "Open Workbook native file bridge smoke"
|
|
194
|
+
};
|
|
195
|
+
const smokeResponse = await fetchLocal(`${bridgeUrl}${route}`, "File bridge smoke operation", "owb file-bridge start", {
|
|
196
|
+
method: "POST",
|
|
197
|
+
headers: { "content-type": "application/json" },
|
|
198
|
+
body: JSON.stringify(request)
|
|
199
|
+
});
|
|
200
|
+
if (!smokeResponse.ok) {
|
|
201
|
+
fail(`File bridge smoke operation failed: ${smokeResponse.status} ${smokeResponse.statusText}`);
|
|
202
|
+
}
|
|
203
|
+
const result = await smokeResponse.json().catch(() => undefined);
|
|
204
|
+
if (!result || typeof result !== "object") {
|
|
205
|
+
fail("File bridge smoke operation returned non-JSON output.");
|
|
206
|
+
}
|
|
207
|
+
if (result.ok !== true) {
|
|
208
|
+
fail(`File bridge smoke failed: ${result.error ?? "unknown bridge error"}`);
|
|
209
|
+
}
|
|
210
|
+
console.log(JSON.stringify({ ok: true, bridgeUrl, route, request, result }, null, 2));
|
|
211
|
+
});
|
|
212
|
+
fileBridge
|
|
213
|
+
.command("stop")
|
|
214
|
+
.description("Stop the local native file bridge")
|
|
215
|
+
.option("--bridge-url <url>", "File bridge base URL", defaultFileBridgeUrl())
|
|
216
|
+
.action(async (options) => {
|
|
217
|
+
const response = await fetchLocal(`${trimTrailingSlash(options.bridgeUrl)}/shutdown`, "File bridge stop", "owb file-bridge start", { method: "POST" });
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
fail(`File bridge stop failed: ${response.status} ${response.statusText}`);
|
|
220
|
+
}
|
|
221
|
+
console.log("Stopped Open Workbook file bridge.");
|
|
222
|
+
});
|
|
223
|
+
const service = program.command("service").description("Generate local auto-start service wrappers");
|
|
224
|
+
service
|
|
225
|
+
.command("manifest")
|
|
226
|
+
.description("Print or write a launchd, systemd user, or Windows scheduled-task wrapper")
|
|
227
|
+
.option("--target <target>", "Target wrapper: macos, systemd, windows", defaultServiceTarget())
|
|
228
|
+
.option("--service <service>", "Service to run: addin, daemon, or file-bridge", "addin")
|
|
229
|
+
.option("--out <path>", "Write wrapper to a file instead of stdout")
|
|
230
|
+
.option("--command <command>", "Base CLI command to run", defaultServiceCommand())
|
|
231
|
+
.action((options) => {
|
|
232
|
+
const target = normalizeServiceTarget(options.target);
|
|
233
|
+
const serviceName = normalizeServiceName(options.service);
|
|
234
|
+
const manifest = generateServiceManifest({
|
|
235
|
+
target,
|
|
236
|
+
serviceName,
|
|
237
|
+
command: options.command
|
|
238
|
+
});
|
|
239
|
+
if (options.out) {
|
|
240
|
+
writeFileSync(resolve(options.out), manifest, "utf8");
|
|
241
|
+
console.log(`Wrote ${target} ${serviceName} service wrapper to ${resolve(options.out)}`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
console.log(manifest);
|
|
245
|
+
});
|
|
246
|
+
program
|
|
247
|
+
.command("addin")
|
|
248
|
+
.description("Manage the local Excel add-in")
|
|
249
|
+
.argument("<command>", "Command: serve")
|
|
250
|
+
.option("--https", "Serve the add-in over HTTPS with a local certificate")
|
|
251
|
+
.option("--tls-cert <path>", "TLS certificate PEM path for HTTPS add-in serving")
|
|
252
|
+
.option("--tls-key <path>", "TLS private key PEM path for HTTPS add-in serving")
|
|
253
|
+
.action((command, options) => {
|
|
254
|
+
if (command !== "serve") {
|
|
255
|
+
fail(`Unknown addin command: ${command}`);
|
|
256
|
+
}
|
|
257
|
+
if (options.https) {
|
|
258
|
+
process.env.OPEN_WORKBOOK_ADDIN_HTTPS = "1";
|
|
259
|
+
process.env.OPEN_WORKBOOK_ADDIN_PROTOCOL = "https";
|
|
260
|
+
}
|
|
261
|
+
if (options.tlsCert !== undefined) {
|
|
262
|
+
process.env.OPEN_WORKBOOK_ADDIN_TLS_CERT = resolve(options.tlsCert);
|
|
263
|
+
}
|
|
264
|
+
if (options.tlsKey !== undefined) {
|
|
265
|
+
process.env.OPEN_WORKBOOK_ADDIN_TLS_KEY = resolve(options.tlsKey);
|
|
266
|
+
}
|
|
267
|
+
runNode(resolveAsset("addinServer"));
|
|
268
|
+
});
|
|
269
|
+
const sideload = program.command("sideload").description("Sideload the Excel add-in manifest");
|
|
270
|
+
sideload
|
|
271
|
+
.command("mac")
|
|
272
|
+
.description("Copy manifest to the Excel for macOS sideload folder")
|
|
273
|
+
.option("--addin-url <url>", "Taskpane base URL", defaultAddinUrl())
|
|
274
|
+
.option("--backend-url <url>", "Backend WebSocket URL", defaultBackendUrl())
|
|
275
|
+
.action((options) => {
|
|
276
|
+
if (process.platform !== "darwin") {
|
|
277
|
+
fail("Mac sideload copies to the Excel for macOS WEF folder and must be run on macOS. Use `owb sideload manifest --out open-workbook.xml` on other platforms.");
|
|
278
|
+
}
|
|
279
|
+
const targetDir = join(homedir(), "Library/Containers/com.microsoft.Excel/Data/Documents/wef");
|
|
280
|
+
const target = join(targetDir, "open-workbook.xml");
|
|
281
|
+
mkdirSync(targetDir, { recursive: true });
|
|
282
|
+
writeFileSync(target, generateManifest(options), "utf8");
|
|
283
|
+
console.log(`Copied manifest to ${target}`);
|
|
284
|
+
console.log(`Taskpane URL: ${options.addinUrl}`);
|
|
285
|
+
console.log(`Backend URL: ${options.backendUrl}`);
|
|
286
|
+
console.log("Restart Excel, then open the Open Workbook add-in.");
|
|
287
|
+
});
|
|
288
|
+
sideload
|
|
289
|
+
.command("windows")
|
|
290
|
+
.description("Print Windows trusted catalog sideload instructions")
|
|
291
|
+
.option("--out <path>", "Write generated manifest to this path", "open-workbook.xml")
|
|
292
|
+
.option("--addin-url <url>", "Taskpane base URL", defaultAddinUrl())
|
|
293
|
+
.option("--backend-url <url>", "Backend WebSocket URL", defaultBackendUrl())
|
|
294
|
+
.action((options) => {
|
|
295
|
+
const manifest = generateManifest(options);
|
|
296
|
+
const outputPath = resolve(options.out);
|
|
297
|
+
writeFileSync(outputPath, manifest, "utf8");
|
|
298
|
+
console.log(`Wrote manifest to ${outputPath}`);
|
|
299
|
+
console.log("Windows Excel sideloading uses a trusted shared-folder add-in catalog.");
|
|
300
|
+
console.log("");
|
|
301
|
+
console.log("1. Create a folder such as C:\\open-workbook-addins.");
|
|
302
|
+
console.log("2. Share that folder in Windows and note its UNC path, for example \\\\YOUR-PC\\open-workbook-addins.");
|
|
303
|
+
console.log(`3. Copy ${outputPath} into the shared folder.`);
|
|
304
|
+
console.log("4. In Excel: File > Options > Trust Center > Trust Center Settings > Trusted Add-in Catalogs.");
|
|
305
|
+
console.log("5. Add the UNC shared-folder path as a trusted catalog and select Show in Menu.");
|
|
306
|
+
console.log("6. Restart Excel and insert Open Workbook from Shared Folder.");
|
|
307
|
+
});
|
|
308
|
+
sideload
|
|
309
|
+
.command("manifest")
|
|
310
|
+
.description("Print or write a generated Excel add-in manifest")
|
|
311
|
+
.option("--out <path>", "Write manifest to a file instead of stdout")
|
|
312
|
+
.option("--addin-url <url>", "Taskpane base URL", defaultAddinUrl())
|
|
313
|
+
.option("--backend-url <url>", "Backend WebSocket URL", defaultBackendUrl())
|
|
314
|
+
.action((options) => {
|
|
315
|
+
const manifest = generateManifest(options);
|
|
316
|
+
if (options.out) {
|
|
317
|
+
writeFileSync(resolve(options.out), manifest, "utf8");
|
|
318
|
+
console.log(`Wrote manifest to ${resolve(options.out)}`);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
console.log(manifest);
|
|
322
|
+
});
|
|
323
|
+
const opencode = program.command("opencode").description("Generate OpenCode integration snippets");
|
|
324
|
+
opencode
|
|
325
|
+
.command("config")
|
|
326
|
+
.description("Print an OpenCode MCP server config snippet")
|
|
327
|
+
.option("--id <id>", "MCP server id in OpenCode config", "open-workbook")
|
|
328
|
+
.option("--command <command>", "Command to run Open Workbook MCP", "owb")
|
|
329
|
+
.option("--agent-name <name>", "Agent name passed to the MCP adapter")
|
|
330
|
+
.action((options) => {
|
|
331
|
+
const command = [options.command, "mcp"];
|
|
332
|
+
if (options.agentName !== undefined) {
|
|
333
|
+
command.push("--agent-name", options.agentName);
|
|
334
|
+
}
|
|
335
|
+
const config = {
|
|
336
|
+
mcp: {
|
|
337
|
+
[options.id]: {
|
|
338
|
+
type: "local",
|
|
339
|
+
command,
|
|
340
|
+
enabled: true
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
console.log(JSON.stringify(config, null, 2));
|
|
345
|
+
});
|
|
346
|
+
program
|
|
347
|
+
.command("paths")
|
|
348
|
+
.description("Print resolved package paths")
|
|
349
|
+
.action(() => {
|
|
350
|
+
console.log(JSON.stringify({
|
|
351
|
+
mcpServer: resolveAsset("mcpServer"),
|
|
352
|
+
backend: resolveAsset("backend"),
|
|
353
|
+
fileBridge: resolveAsset("fileBridge"),
|
|
354
|
+
addinServer: resolveAsset("addinServer"),
|
|
355
|
+
manifest: resolveAsset("manifest"),
|
|
356
|
+
instructions: resolveAsset("instructions"),
|
|
357
|
+
stateDir: defaultStateDir(),
|
|
358
|
+
exportDir: defaultExportDir(),
|
|
359
|
+
userConfigDir: defaultUserConfigDir(),
|
|
360
|
+
fileBridgeUrl: defaultFileBridgeUrl(),
|
|
361
|
+
mode: existsSync(sourcePaths.mcpServer) ? "source" : "bundled"
|
|
362
|
+
}, null, 2));
|
|
363
|
+
});
|
|
364
|
+
program
|
|
365
|
+
.command("doctor")
|
|
366
|
+
.description("Check local Open Workbook install assets")
|
|
367
|
+
.action(() => {
|
|
368
|
+
const checks = [
|
|
369
|
+
checkPath("MCP server", resolveAsset("mcpServer")),
|
|
370
|
+
checkPath("Backend daemon", resolveAsset("backend")),
|
|
371
|
+
checkPath("File bridge", resolveAsset("fileBridge")),
|
|
372
|
+
checkPath("Add-in server", resolveAsset("addinServer")),
|
|
373
|
+
checkPath("Manifest", resolveAsset("manifest")),
|
|
374
|
+
checkPath("Instructions", resolveAsset("instructions"))
|
|
375
|
+
];
|
|
376
|
+
for (const check of checks) {
|
|
377
|
+
console.log(`${check.ok ? "ok" : "missing"} ${check.label}: ${check.path}`);
|
|
378
|
+
}
|
|
379
|
+
if (checks.some((check) => !check.ok)) {
|
|
380
|
+
process.exitCode = 1;
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
console.log(`Taskpane URL: ${defaultAddinUrl()}`);
|
|
384
|
+
console.log(`Backend URL: ${defaultBackendUrl()}`);
|
|
385
|
+
console.log(`File bridge URL: ${defaultFileBridgeUrl()}`);
|
|
386
|
+
});
|
|
387
|
+
program.parse();
|
|
388
|
+
function resolveAsset(name) {
|
|
389
|
+
if (existsSync(sourcePaths[name])) {
|
|
390
|
+
return sourcePaths[name];
|
|
391
|
+
}
|
|
392
|
+
if (existsSync(bundledPaths[name])) {
|
|
393
|
+
return bundledPaths[name];
|
|
394
|
+
}
|
|
395
|
+
if (existsSync(dependencyPaths[name])) {
|
|
396
|
+
return dependencyPaths[name];
|
|
397
|
+
}
|
|
398
|
+
return sourcePaths[name];
|
|
399
|
+
}
|
|
400
|
+
function runNode(entrypoint, args = [], companionProcesses = []) {
|
|
401
|
+
if (!existsSync(entrypoint)) {
|
|
402
|
+
fail(`Missing built entrypoint: ${entrypoint}\nRun: corepack pnpm build`);
|
|
403
|
+
}
|
|
404
|
+
const cleanup = () => {
|
|
405
|
+
for (const companion of companionProcesses) {
|
|
406
|
+
if (!companion.killed) {
|
|
407
|
+
companion.kill();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const child = spawn(process.execPath, [entrypoint, ...args], {
|
|
412
|
+
stdio: "inherit",
|
|
413
|
+
env: process.env
|
|
414
|
+
});
|
|
415
|
+
process.once("SIGINT", () => {
|
|
416
|
+
cleanup();
|
|
417
|
+
child.kill("SIGINT");
|
|
418
|
+
});
|
|
419
|
+
process.once("SIGTERM", () => {
|
|
420
|
+
cleanup();
|
|
421
|
+
child.kill("SIGTERM");
|
|
422
|
+
});
|
|
423
|
+
child.on("exit", (code, signal) => {
|
|
424
|
+
cleanup();
|
|
425
|
+
if (signal) {
|
|
426
|
+
process.kill(process.pid, signal);
|
|
427
|
+
}
|
|
428
|
+
process.exit(code ?? 0);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
async function startAddinServerIfNeeded() {
|
|
432
|
+
if (await urlAvailable(defaultAddinUrl())) {
|
|
433
|
+
return undefined;
|
|
434
|
+
}
|
|
435
|
+
const entrypoint = resolveAsset("addinServer");
|
|
436
|
+
if (!existsSync(entrypoint)) {
|
|
437
|
+
fail(`Missing add-in server entrypoint: ${entrypoint}\nRun: corepack pnpm build`);
|
|
438
|
+
}
|
|
439
|
+
return spawn(process.execPath, [entrypoint], {
|
|
440
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
441
|
+
env: process.env
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
async function urlAvailable(url) {
|
|
445
|
+
try {
|
|
446
|
+
const response = await fetch(url);
|
|
447
|
+
return response.ok;
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function generateManifest(options) {
|
|
454
|
+
const manifestPath = resolveAsset("manifest");
|
|
455
|
+
if (!existsSync(manifestPath)) {
|
|
456
|
+
fail(`Missing manifest: ${manifestPath}`);
|
|
457
|
+
}
|
|
458
|
+
const addinUrl = trimTrailingSlash(options.addinUrl);
|
|
459
|
+
const taskpaneUrl = `${addinUrl}/taskpane.html?backendUrl=${encodeURIComponent(options.backendUrl)}`;
|
|
460
|
+
return readFileSync(manifestPath, "utf8")
|
|
461
|
+
.replaceAll("http://localhost:37846/taskpane.html", taskpaneUrl)
|
|
462
|
+
.replaceAll("http://localhost:37846", addinUrl);
|
|
463
|
+
}
|
|
464
|
+
function generateGenericInstructions() {
|
|
465
|
+
const instructionsDir = resolveAsset("instructions");
|
|
466
|
+
const skillPath = join(instructionsDir, "SKILL.md");
|
|
467
|
+
if (!existsSync(skillPath)) {
|
|
468
|
+
fail(`Missing instruction source: ${skillPath}`);
|
|
469
|
+
}
|
|
470
|
+
const sections = [
|
|
471
|
+
"# Open Workbook Excel Instructions",
|
|
472
|
+
stripFrontmatter(readFileSync(skillPath, "utf8")).trim()
|
|
473
|
+
];
|
|
474
|
+
const references = [
|
|
475
|
+
["Tool Selection", "tool-selection.md"],
|
|
476
|
+
["Workflows", "workflows.md"],
|
|
477
|
+
["Reliability", "reliability.md"],
|
|
478
|
+
["Performance", "performance.md"],
|
|
479
|
+
["Multi-Agent", "multi-agent.md"]
|
|
480
|
+
];
|
|
481
|
+
for (const [title, fileName] of references) {
|
|
482
|
+
const referencePath = join(instructionsDir, "references", fileName);
|
|
483
|
+
if (existsSync(referencePath)) {
|
|
484
|
+
sections.push(`## ${title}\n\n${readFileSync(referencePath, "utf8").trim()}`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return `${sections.join("\n\n")}\n`;
|
|
488
|
+
}
|
|
489
|
+
function stripFrontmatter(markdown) {
|
|
490
|
+
if (!markdown.startsWith("---\n")) {
|
|
491
|
+
return markdown;
|
|
492
|
+
}
|
|
493
|
+
const end = markdown.indexOf("\n---\n", 4);
|
|
494
|
+
return end === -1 ? markdown : markdown.slice(end + 5);
|
|
495
|
+
}
|
|
496
|
+
function writeFileEnsuringDir(path, content) {
|
|
497
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
498
|
+
writeFileSync(path, content, "utf8");
|
|
499
|
+
}
|
|
500
|
+
function genericMcpConfig() {
|
|
501
|
+
return {
|
|
502
|
+
mcpServers: {
|
|
503
|
+
"open-workbook": {
|
|
504
|
+
command: "npx",
|
|
505
|
+
args: ["-y", `${publicPackageName}@latest`, "mcp"]
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
function printManifestNextSteps(manifestPath) {
|
|
511
|
+
if (process.platform === "darwin") {
|
|
512
|
+
console.log("Restart Excel, then open Insert > Add-ins > Open Workbook.");
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (process.platform === "win32") {
|
|
516
|
+
console.log("Windows Excel sideloading requires a trusted shared-folder add-in catalog:");
|
|
517
|
+
console.log("1. Copy the manifest into a shared folder, for example C:\\open-workbook-addins.");
|
|
518
|
+
console.log("2. In Excel, open File > Options > Trust Center > Trust Center Settings > Trusted Add-in Catalogs.");
|
|
519
|
+
console.log("3. Add the shared-folder UNC path, select Show in Menu, restart Excel, and insert Open Workbook.");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
console.log(`Use the generated manifest at ${manifestPath} with an Office add-in catalog supported by your Excel host.`);
|
|
523
|
+
}
|
|
524
|
+
function generateServiceManifest(options) {
|
|
525
|
+
const args = options.serviceName === "addin" ? ["addin", "serve"] : options.serviceName === "daemon" ? ["daemon", "start"] : ["file-bridge", "start"];
|
|
526
|
+
const label = `com.open-workbook.${options.serviceName}`;
|
|
527
|
+
const description = options.serviceName === "addin"
|
|
528
|
+
? "Open Workbook Excel add-in asset server"
|
|
529
|
+
: options.serviceName === "daemon"
|
|
530
|
+
? "Open Workbook shared daemon"
|
|
531
|
+
: "Open Workbook native file bridge";
|
|
532
|
+
const commandParts = [options.command, ...args];
|
|
533
|
+
switch (options.target) {
|
|
534
|
+
case "macos":
|
|
535
|
+
return generateLaunchdPlist(label, commandParts);
|
|
536
|
+
case "systemd":
|
|
537
|
+
return generateSystemdUnit(label, description, commandParts);
|
|
538
|
+
case "windows":
|
|
539
|
+
return generateWindowsScheduledTask(label, description, commandParts);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
function generateLaunchdPlist(label, commandParts) {
|
|
543
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
544
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
545
|
+
<plist version="1.0">
|
|
546
|
+
<dict>
|
|
547
|
+
<key>Label</key>
|
|
548
|
+
<string>${escapeXml(label)}</string>
|
|
549
|
+
<key>ProgramArguments</key>
|
|
550
|
+
<array>
|
|
551
|
+
${commandParts.map((part) => ` <string>${escapeXml(part)}</string>`).join("\n")}
|
|
552
|
+
</array>
|
|
553
|
+
<key>RunAtLoad</key>
|
|
554
|
+
<true/>
|
|
555
|
+
<key>KeepAlive</key>
|
|
556
|
+
<true/>
|
|
557
|
+
<key>StandardOutPath</key>
|
|
558
|
+
<string>${escapeXml(join(homedir(), "Library/Logs", `${label}.out.log`))}</string>
|
|
559
|
+
<key>StandardErrorPath</key>
|
|
560
|
+
<string>${escapeXml(join(homedir(), "Library/Logs", `${label}.err.log`))}</string>
|
|
561
|
+
</dict>
|
|
562
|
+
</plist>
|
|
563
|
+
`;
|
|
564
|
+
}
|
|
565
|
+
function generateSystemdUnit(label, description, commandParts) {
|
|
566
|
+
return `[Unit]
|
|
567
|
+
Description=${description}
|
|
568
|
+
After=network.target
|
|
569
|
+
|
|
570
|
+
[Service]
|
|
571
|
+
Type=simple
|
|
572
|
+
ExecStart=${commandParts.map(shellQuote).join(" ")}
|
|
573
|
+
Restart=on-failure
|
|
574
|
+
RestartSec=5
|
|
575
|
+
|
|
576
|
+
[Install]
|
|
577
|
+
WantedBy=default.target
|
|
578
|
+
|
|
579
|
+
# Save as ~/.config/systemd/user/${label}.service
|
|
580
|
+
# Enable with: systemctl --user enable --now ${label}.service
|
|
581
|
+
`;
|
|
582
|
+
}
|
|
583
|
+
function generateWindowsScheduledTask(label, description, commandParts) {
|
|
584
|
+
const executable = commandParts[0];
|
|
585
|
+
const argumentsText = commandParts.slice(1).join(" ");
|
|
586
|
+
return `$Action = New-ScheduledTaskAction -Execute ${powerShellQuote(executable)} -Argument ${powerShellQuote(argumentsText)}
|
|
587
|
+
$Trigger = New-ScheduledTaskTrigger -AtLogOn
|
|
588
|
+
$Principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel LeastPrivilege
|
|
589
|
+
$Settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
|
|
590
|
+
Register-ScheduledTask -TaskName ${powerShellQuote(label)} -Description ${powerShellQuote(description)} -Action $Action -Trigger $Trigger -Principal $Principal -Settings $Settings -Force
|
|
591
|
+
`;
|
|
592
|
+
}
|
|
593
|
+
function defaultAddinUrl() {
|
|
594
|
+
const host = process.env.OPEN_WORKBOOK_ADDIN_HOST ?? "127.0.0.1";
|
|
595
|
+
const port = process.env.OPEN_WORKBOOK_ADDIN_PORT ?? "37846";
|
|
596
|
+
const protocol = process.env.OPEN_WORKBOOK_ADDIN_HTTPS === "1" || process.env.OPEN_WORKBOOK_ADDIN_PROTOCOL === "https" ? "https" : "http";
|
|
597
|
+
return `${protocol}://${host}:${port}`;
|
|
598
|
+
}
|
|
599
|
+
function defaultBackendUrl() {
|
|
600
|
+
const host = process.env.OPEN_WORKBOOK_HOST ?? "127.0.0.1";
|
|
601
|
+
const port = process.env.OPEN_WORKBOOK_PORT ?? "37845";
|
|
602
|
+
const path = process.env.OPEN_WORKBOOK_ADDIN_PATH ?? "/addin";
|
|
603
|
+
return `ws://${host}:${port}${path}`;
|
|
604
|
+
}
|
|
605
|
+
function defaultDaemonUrl() {
|
|
606
|
+
const host = process.env.OPEN_WORKBOOK_HOST ?? "127.0.0.1";
|
|
607
|
+
const port = process.env.OPEN_WORKBOOK_PORT ?? "37845";
|
|
608
|
+
return `http://${host}:${port}`;
|
|
609
|
+
}
|
|
610
|
+
function defaultFileBridgeUrl() {
|
|
611
|
+
if (process.env.OPEN_WORKBOOK_FILE_BRIDGE_URL !== undefined) {
|
|
612
|
+
return process.env.OPEN_WORKBOOK_FILE_BRIDGE_URL;
|
|
613
|
+
}
|
|
614
|
+
const host = process.env.OPEN_WORKBOOK_FILE_BRIDGE_HOST ?? "127.0.0.1";
|
|
615
|
+
const port = process.env.OPEN_WORKBOOK_FILE_BRIDGE_PORT ?? "37847";
|
|
616
|
+
return `http://${host}:${port}`;
|
|
617
|
+
}
|
|
618
|
+
function defaultFileBridgePath() {
|
|
619
|
+
return process.env.OPEN_WORKBOOK_FILE_BRIDGE_PATH ?? "/v1/workbook-file";
|
|
620
|
+
}
|
|
621
|
+
function defaultHostSmokeTarget(operation) {
|
|
622
|
+
const suffix = operation === "workbook.export_copy" ? "export-copy" : "save-as";
|
|
623
|
+
return resolve(process.cwd(), ".open-workbook/host-smoke", `open-workbook-${suffix}-${Date.now()}.xlsx`);
|
|
624
|
+
}
|
|
625
|
+
function defaultServiceTarget() {
|
|
626
|
+
if (process.platform === "darwin") {
|
|
627
|
+
return "macos";
|
|
628
|
+
}
|
|
629
|
+
if (process.platform === "win32") {
|
|
630
|
+
return "windows";
|
|
631
|
+
}
|
|
632
|
+
return "systemd";
|
|
633
|
+
}
|
|
634
|
+
function defaultServiceCommand() {
|
|
635
|
+
return process.env.OPEN_WORKBOOK_SERVICE_COMMAND ?? "owb";
|
|
636
|
+
}
|
|
637
|
+
function defaultStateDir() {
|
|
638
|
+
return process.env.OPEN_WORKBOOK_STATE_DIR ?? resolve(process.cwd(), ".open-workbook/state");
|
|
639
|
+
}
|
|
640
|
+
function defaultExportDir() {
|
|
641
|
+
return process.env.OPEN_WORKBOOK_EXPORT_DIR ?? resolve(process.cwd(), ".open-workbook/exports");
|
|
642
|
+
}
|
|
643
|
+
function defaultUserConfigDir() {
|
|
644
|
+
return process.env.OPEN_WORKBOOK_CONFIG_DIR ?? join(homedir(), ".open-workbook");
|
|
645
|
+
}
|
|
646
|
+
function defaultInstructionsPath() {
|
|
647
|
+
return join(defaultUserConfigDir(), "instructions", instructionFileName);
|
|
648
|
+
}
|
|
649
|
+
function defaultSetupManifestPath() {
|
|
650
|
+
return join(defaultUserConfigDir(), "open-workbook.xml");
|
|
651
|
+
}
|
|
652
|
+
function setupManifestPath(manifestOut) {
|
|
653
|
+
if (process.platform === "darwin" && manifestOut === defaultSetupManifestPath()) {
|
|
654
|
+
return join(homedir(), "Library/Containers/com.microsoft.Excel/Data/Documents/wef/open-workbook.xml");
|
|
655
|
+
}
|
|
656
|
+
return resolve(manifestOut);
|
|
657
|
+
}
|
|
658
|
+
function trimTrailingSlash(value) {
|
|
659
|
+
return value.endsWith("/") ? value.slice(0, -1) : value;
|
|
660
|
+
}
|
|
661
|
+
function normalizeBridgePath(value) {
|
|
662
|
+
return value.startsWith("/") ? value : `/${value}`;
|
|
663
|
+
}
|
|
664
|
+
function normalizeFileBridgeSmokeOperation(value) {
|
|
665
|
+
if (value === "export-copy" || value === "export_copy" || value === "workbook.export_copy") {
|
|
666
|
+
return "workbook.export_copy";
|
|
667
|
+
}
|
|
668
|
+
if (value === "save-as" || value === "save_as" || value === "workbook.save_as") {
|
|
669
|
+
return "workbook.save_as";
|
|
670
|
+
}
|
|
671
|
+
fail(`Unknown file bridge smoke operation: ${value}. Use export-copy or save-as.`);
|
|
672
|
+
}
|
|
673
|
+
function normalizeServiceTarget(value) {
|
|
674
|
+
if (value === "mac" || value === "macos" || value === "launchd") {
|
|
675
|
+
return "macos";
|
|
676
|
+
}
|
|
677
|
+
if (value === "win" || value === "windows" || value === "task-scheduler") {
|
|
678
|
+
return "windows";
|
|
679
|
+
}
|
|
680
|
+
if (value === "linux" || value === "systemd") {
|
|
681
|
+
return "systemd";
|
|
682
|
+
}
|
|
683
|
+
fail(`Unknown service target: ${value}`);
|
|
684
|
+
}
|
|
685
|
+
function normalizeServiceName(value) {
|
|
686
|
+
if (value === "addin" || value === "daemon" || value === "file-bridge") {
|
|
687
|
+
return value;
|
|
688
|
+
}
|
|
689
|
+
fail(`Unknown service name: ${value}`);
|
|
690
|
+
}
|
|
691
|
+
function escapeXml(value) {
|
|
692
|
+
return value
|
|
693
|
+
.replaceAll("&", "&")
|
|
694
|
+
.replaceAll("<", "<")
|
|
695
|
+
.replaceAll(">", ">")
|
|
696
|
+
.replaceAll("\"", """)
|
|
697
|
+
.replaceAll("'", "'");
|
|
698
|
+
}
|
|
699
|
+
function shellQuote(value) {
|
|
700
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
701
|
+
}
|
|
702
|
+
function powerShellQuote(value) {
|
|
703
|
+
return `'${value.replaceAll("'", "''")}'`;
|
|
704
|
+
}
|
|
705
|
+
function checkPath(label, path) {
|
|
706
|
+
return { label, path, ok: existsSync(path) };
|
|
707
|
+
}
|
|
708
|
+
async function fetchLocal(url, label, startCommand, init) {
|
|
709
|
+
try {
|
|
710
|
+
return await fetch(url, init);
|
|
711
|
+
}
|
|
712
|
+
catch (error) {
|
|
713
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
714
|
+
fail(`${label} failed: could not connect to ${url}. Start it with \`${startCommand}\` or check the configured port. ${detail}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function fail(message) {
|
|
718
|
+
console.error(message);
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
//# sourceMappingURL=index.js.map
|