@openacp/cli 0.6.10 → 2026.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/README.md +34 -16
  2. package/dist/cli.d.ts +11 -0
  3. package/dist/cli.js +27787 -467
  4. package/dist/cli.js.map +1 -1
  5. package/dist/data/registry-snapshot.json +1 -1
  6. package/dist/index.d.ts +1944 -463
  7. package/dist/index.js +17365 -102
  8. package/dist/index.js.map +1 -1
  9. package/package.json +13 -7
  10. package/dist/action-detect-P7ZE4NEM.js +0 -16
  11. package/dist/action-detect-P7ZE4NEM.js.map +0 -1
  12. package/dist/adapter-ZOANORGM.js +0 -799
  13. package/dist/adapter-ZOANORGM.js.map +0 -1
  14. package/dist/admin-6SYB6XCZ.js +0 -23
  15. package/dist/admin-6SYB6XCZ.js.map +0 -1
  16. package/dist/agent-catalog-FC3HGDEQ.js +0 -11
  17. package/dist/agent-catalog-FC3HGDEQ.js.map +0 -1
  18. package/dist/agent-dependencies-4OWBMZWZ.js +0 -24
  19. package/dist/agent-dependencies-4OWBMZWZ.js.map +0 -1
  20. package/dist/agent-registry-WT4NXPYG.js +0 -9
  21. package/dist/agent-registry-WT4NXPYG.js.map +0 -1
  22. package/dist/agent-store-VZLFPTZU.js +0 -9
  23. package/dist/agent-store-VZLFPTZU.js.map +0 -1
  24. package/dist/agents-QO7DKARJ.js +0 -15
  25. package/dist/agents-QO7DKARJ.js.map +0 -1
  26. package/dist/api-client-CFQT5U7D.js +0 -14
  27. package/dist/api-client-CFQT5U7D.js.map +0 -1
  28. package/dist/autostart-X33OGMX6.js +0 -23
  29. package/dist/autostart-X33OGMX6.js.map +0 -1
  30. package/dist/chunk-2CJ46J3C.js +0 -154
  31. package/dist/chunk-2CJ46J3C.js.map +0 -1
  32. package/dist/chunk-2HMQOC7N.js +0 -134
  33. package/dist/chunk-2HMQOC7N.js.map +0 -1
  34. package/dist/chunk-33RP6K2O.js +0 -435
  35. package/dist/chunk-33RP6K2O.js.map +0 -1
  36. package/dist/chunk-34M4OS5P.js +0 -83
  37. package/dist/chunk-34M4OS5P.js.map +0 -1
  38. package/dist/chunk-4CTX774K.js +0 -265
  39. package/dist/chunk-4CTX774K.js.map +0 -1
  40. package/dist/chunk-7QJS2XBD.js +0 -92
  41. package/dist/chunk-7QJS2XBD.js.map +0 -1
  42. package/dist/chunk-BNLGTZ34.js +0 -122
  43. package/dist/chunk-BNLGTZ34.js.map +0 -1
  44. package/dist/chunk-CS3KCJ5D.js +0 -4788
  45. package/dist/chunk-CS3KCJ5D.js.map +0 -1
  46. package/dist/chunk-GAK6PIBW.js +0 -224
  47. package/dist/chunk-GAK6PIBW.js.map +0 -1
  48. package/dist/chunk-I7WC6E5S.js +0 -71
  49. package/dist/chunk-I7WC6E5S.js.map +0 -1
  50. package/dist/chunk-J4SJTKIK.js +0 -203
  51. package/dist/chunk-J4SJTKIK.js.map +0 -1
  52. package/dist/chunk-JHYXKVV2.js +0 -183
  53. package/dist/chunk-JHYXKVV2.js.map +0 -1
  54. package/dist/chunk-JKBFUAJK.js +0 -282
  55. package/dist/chunk-JKBFUAJK.js.map +0 -1
  56. package/dist/chunk-KIRH7TUJ.js +0 -219
  57. package/dist/chunk-KIRH7TUJ.js.map +0 -1
  58. package/dist/chunk-LBIKITQT.js +0 -22
  59. package/dist/chunk-LBIKITQT.js.map +0 -1
  60. package/dist/chunk-LCRLAV4G.js +0 -1085
  61. package/dist/chunk-LCRLAV4G.js.map +0 -1
  62. package/dist/chunk-LGP2YGRL.js +0 -4880
  63. package/dist/chunk-LGP2YGRL.js.map +0 -1
  64. package/dist/chunk-MKHUZLII.js +0 -738
  65. package/dist/chunk-MKHUZLII.js.map +0 -1
  66. package/dist/chunk-NAMYZIS5.js +0 -1
  67. package/dist/chunk-NAMYZIS5.js.map +0 -1
  68. package/dist/chunk-NVPG6JCL.js +0 -724
  69. package/dist/chunk-NVPG6JCL.js.map +0 -1
  70. package/dist/chunk-O7CPGUAI.js +0 -298
  71. package/dist/chunk-O7CPGUAI.js.map +0 -1
  72. package/dist/chunk-OWP7RZ62.js +0 -697
  73. package/dist/chunk-OWP7RZ62.js.map +0 -1
  74. package/dist/chunk-S64CB6J3.js +0 -98
  75. package/dist/chunk-S64CB6J3.js.map +0 -1
  76. package/dist/chunk-UKT3G5IA.js +0 -484
  77. package/dist/chunk-UKT3G5IA.js.map +0 -1
  78. package/dist/chunk-V5GZQEIY.js +0 -101
  79. package/dist/chunk-V5GZQEIY.js.map +0 -1
  80. package/dist/chunk-VOIJ6OY4.js +0 -63
  81. package/dist/chunk-VOIJ6OY4.js.map +0 -1
  82. package/dist/chunk-VUNV25KB.js +0 -16
  83. package/dist/chunk-VUNV25KB.js.map +0 -1
  84. package/dist/chunk-W3EYKZNQ.js +0 -45
  85. package/dist/chunk-W3EYKZNQ.js.map +0 -1
  86. package/dist/chunk-WTZDAYZX.js +0 -172
  87. package/dist/chunk-WTZDAYZX.js.map +0 -1
  88. package/dist/chunk-XANPHG7W.js +0 -145
  89. package/dist/chunk-XANPHG7W.js.map +0 -1
  90. package/dist/config-6S355X75.js +0 -15
  91. package/dist/config-6S355X75.js.map +0 -1
  92. package/dist/config-editor-QQTZMWGD.js +0 -13
  93. package/dist/config-editor-QQTZMWGD.js.map +0 -1
  94. package/dist/config-registry-AHYI4MYL.js +0 -18
  95. package/dist/config-registry-AHYI4MYL.js.map +0 -1
  96. package/dist/daemon-4CS6HMB5.js +0 -30
  97. package/dist/daemon-4CS6HMB5.js.map +0 -1
  98. package/dist/discord-OMC52Y54.js +0 -2239
  99. package/dist/discord-OMC52Y54.js.map +0 -1
  100. package/dist/dist-UHQK5CXN.js +0 -21151
  101. package/dist/dist-UHQK5CXN.js.map +0 -1
  102. package/dist/doctor-HZZ5BSHB.js +0 -10
  103. package/dist/doctor-HZZ5BSHB.js.map +0 -1
  104. package/dist/doctor-OLYBO3V3.js +0 -15
  105. package/dist/doctor-OLYBO3V3.js.map +0 -1
  106. package/dist/install-cloudflared-Z7VCGOVG.js +0 -33
  107. package/dist/install-cloudflared-Z7VCGOVG.js.map +0 -1
  108. package/dist/install-jq-HUYSQWKR.js +0 -32
  109. package/dist/install-jq-HUYSQWKR.js.map +0 -1
  110. package/dist/integrate-PNEHRY2I.js +0 -373
  111. package/dist/integrate-PNEHRY2I.js.map +0 -1
  112. package/dist/log-NXABYJTT.js +0 -24
  113. package/dist/log-NXABYJTT.js.map +0 -1
  114. package/dist/main-XOZCLFUK.js +0 -238
  115. package/dist/main-XOZCLFUK.js.map +0 -1
  116. package/dist/menu-YY5MKHEK.js +0 -16
  117. package/dist/menu-YY5MKHEK.js.map +0 -1
  118. package/dist/new-session-FEO4J4VU.js +0 -17
  119. package/dist/new-session-FEO4J4VU.js.map +0 -1
  120. package/dist/post-upgrade-CJG5I7M2.js +0 -80
  121. package/dist/post-upgrade-CJG5I7M2.js.map +0 -1
  122. package/dist/session-IUSI7P5S.js +0 -20
  123. package/dist/session-IUSI7P5S.js.map +0 -1
  124. package/dist/settings-RQPAM4KC.js +0 -14
  125. package/dist/settings-RQPAM4KC.js.map +0 -1
  126. package/dist/setup-XHS4OMPM.js +0 -37
  127. package/dist/setup-XHS4OMPM.js.map +0 -1
  128. package/dist/suggest-7D6B542M.js +0 -38
  129. package/dist/suggest-7D6B542M.js.map +0 -1
  130. package/dist/tunnel-service-CJLUH6SZ.js +0 -1174
  131. package/dist/tunnel-service-CJLUH6SZ.js.map +0 -1
  132. package/dist/version-NQZBM5M7.js +0 -16
  133. package/dist/version-NQZBM5M7.js.map +0 -1
@@ -1,1174 +0,0 @@
1
- import {
2
- commandExists
3
- } from "./chunk-JKBFUAJK.js";
4
- import {
5
- createChildLogger
6
- } from "./chunk-GAK6PIBW.js";
7
- import "./chunk-VUNV25KB.js";
8
-
9
- // src/tunnel/tunnel-service.ts
10
- import { serve } from "@hono/node-server";
11
-
12
- // src/tunnel/tunnel-registry.ts
13
- import fs2 from "fs";
14
- import path2 from "path";
15
- import os2 from "os";
16
-
17
- // src/tunnel/providers/cloudflare.ts
18
- import { spawn } from "child_process";
19
- import fs from "fs";
20
- import path from "path";
21
- import os from "os";
22
- var log = createChildLogger({ module: "cloudflare-tunnel" });
23
- var CloudflareTunnelProvider = class {
24
- child = null;
25
- publicUrl = "";
26
- options;
27
- constructor(options = {}) {
28
- this.options = options;
29
- }
30
- async start(localPort) {
31
- let binaryPath = this.findBinary();
32
- if (!binaryPath) {
33
- log.warn("cloudflared not found locally, attempting auto-install as fallback...");
34
- try {
35
- const { ensureCloudflared } = await import("./install-cloudflared-Z7VCGOVG.js");
36
- binaryPath = await ensureCloudflared();
37
- } catch (err) {
38
- throw new Error(`cloudflared is not installed and auto-install failed: ${err.message}`);
39
- }
40
- }
41
- const args = ["tunnel", "--url", `http://localhost:${localPort}`];
42
- if (this.options.domain) {
43
- args.push("--hostname", String(this.options.domain));
44
- }
45
- return new Promise((resolve2, reject) => {
46
- const timeout = setTimeout(() => {
47
- this.stop();
48
- reject(new Error("Cloudflare tunnel timed out after 30s"));
49
- }, 3e4);
50
- try {
51
- this.child = spawn(binaryPath, args, { stdio: ["ignore", "pipe", "pipe"] });
52
- } catch {
53
- clearTimeout(timeout);
54
- reject(new Error(`Failed to start cloudflared at ${binaryPath}`));
55
- return;
56
- }
57
- const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/;
58
- const onData = (data) => {
59
- const line = data.toString();
60
- log.debug(line.trim());
61
- const match = line.match(urlPattern);
62
- if (match) {
63
- clearTimeout(timeout);
64
- this.publicUrl = match[0];
65
- log.info({ url: this.publicUrl }, "Cloudflare tunnel ready");
66
- resolve2(this.publicUrl);
67
- }
68
- };
69
- this.child.stdout?.on("data", onData);
70
- this.child.stderr?.on("data", onData);
71
- this.child.on("error", (err) => {
72
- clearTimeout(timeout);
73
- reject(new Error(`cloudflared failed to start: ${err.message}`));
74
- });
75
- this.child.on("exit", (code) => {
76
- if (!this.publicUrl) {
77
- clearTimeout(timeout);
78
- reject(new Error(`cloudflared exited with code ${code} before establishing tunnel`));
79
- }
80
- });
81
- });
82
- }
83
- async stop() {
84
- if (this.child) {
85
- this.child.kill("SIGTERM");
86
- this.child = null;
87
- log.info("Cloudflare tunnel stopped");
88
- }
89
- }
90
- getPublicUrl() {
91
- return this.publicUrl;
92
- }
93
- findBinary() {
94
- if (commandExists("cloudflared")) return "cloudflared";
95
- const binPath = path.join(os.homedir(), ".openacp", "bin", "cloudflared");
96
- if (fs.existsSync(binPath)) return binPath;
97
- return null;
98
- }
99
- };
100
-
101
- // src/tunnel/providers/ngrok.ts
102
- import { spawn as spawn2 } from "child_process";
103
- var log2 = createChildLogger({ module: "ngrok-tunnel" });
104
- var NgrokTunnelProvider = class {
105
- child = null;
106
- publicUrl = "";
107
- options;
108
- constructor(options = {}) {
109
- this.options = options;
110
- }
111
- async start(localPort) {
112
- const args = ["http", String(localPort), "--log", "stdout", "--log-format", "json"];
113
- if (this.options.authtoken) {
114
- args.push("--authtoken", String(this.options.authtoken));
115
- }
116
- if (this.options.domain) {
117
- args.push("--domain", String(this.options.domain));
118
- }
119
- if (this.options.region) {
120
- args.push("--region", String(this.options.region));
121
- }
122
- return new Promise((resolve2, reject) => {
123
- const timeout = setTimeout(() => {
124
- this.stop();
125
- reject(new Error("ngrok tunnel timed out after 30s. Is ngrok installed?"));
126
- }, 3e4);
127
- try {
128
- this.child = spawn2("ngrok", args, { stdio: ["ignore", "pipe", "pipe"] });
129
- } catch {
130
- clearTimeout(timeout);
131
- reject(new Error(
132
- "Failed to start ngrok. Install it from https://ngrok.com/download"
133
- ));
134
- return;
135
- }
136
- const urlPattern = /https:\/\/[a-zA-Z0-9-]+\.ngrok(-free)?\.app/;
137
- const onData = (data) => {
138
- const line = data.toString();
139
- log2.debug(line.trim());
140
- const match = line.match(urlPattern);
141
- if (match) {
142
- clearTimeout(timeout);
143
- this.publicUrl = match[0];
144
- log2.info({ url: this.publicUrl }, "ngrok tunnel ready");
145
- resolve2(this.publicUrl);
146
- }
147
- };
148
- this.child.stdout?.on("data", onData);
149
- this.child.stderr?.on("data", onData);
150
- this.child.on("error", (err) => {
151
- clearTimeout(timeout);
152
- reject(new Error(
153
- `ngrok failed to start: ${err.message}. Install it from https://ngrok.com/download`
154
- ));
155
- });
156
- this.child.on("exit", (code) => {
157
- if (!this.publicUrl) {
158
- clearTimeout(timeout);
159
- reject(new Error(`ngrok exited with code ${code} before establishing tunnel`));
160
- }
161
- });
162
- });
163
- }
164
- async stop() {
165
- if (this.child) {
166
- this.child.kill("SIGTERM");
167
- this.child = null;
168
- log2.info("ngrok tunnel stopped");
169
- }
170
- }
171
- getPublicUrl() {
172
- return this.publicUrl;
173
- }
174
- };
175
-
176
- // src/tunnel/providers/bore.ts
177
- import { spawn as spawn3 } from "child_process";
178
- var log3 = createChildLogger({ module: "bore-tunnel" });
179
- var BoreTunnelProvider = class {
180
- child = null;
181
- publicUrl = "";
182
- options;
183
- constructor(options = {}) {
184
- this.options = options;
185
- }
186
- async start(localPort) {
187
- const server = String(this.options.server || "bore.pub");
188
- const args = ["local", String(localPort), "--to", server];
189
- if (this.options.port) {
190
- args.push("--port", String(this.options.port));
191
- }
192
- if (this.options.secret) {
193
- args.push("--secret", String(this.options.secret));
194
- }
195
- return new Promise((resolve2, reject) => {
196
- const timeout = setTimeout(() => {
197
- this.stop();
198
- reject(new Error("Bore tunnel timed out after 30s. Is bore installed?"));
199
- }, 3e4);
200
- try {
201
- this.child = spawn3("bore", args, { stdio: ["ignore", "pipe", "pipe"] });
202
- } catch {
203
- clearTimeout(timeout);
204
- reject(new Error(
205
- "Failed to start bore. Install it from https://github.com/ekzhang/bore"
206
- ));
207
- return;
208
- }
209
- const urlPattern = /listening at ([^\s]+):(\d+)/;
210
- const onData = (data) => {
211
- const line = data.toString();
212
- log3.debug(line.trim());
213
- const match = line.match(urlPattern);
214
- if (match) {
215
- clearTimeout(timeout);
216
- this.publicUrl = `http://${match[1]}:${match[2]}`;
217
- log3.info({ url: this.publicUrl }, "Bore tunnel ready");
218
- resolve2(this.publicUrl);
219
- }
220
- };
221
- this.child.stdout?.on("data", onData);
222
- this.child.stderr?.on("data", onData);
223
- this.child.on("error", (err) => {
224
- clearTimeout(timeout);
225
- reject(new Error(
226
- `bore failed to start: ${err.message}. Install it from https://github.com/ekzhang/bore`
227
- ));
228
- });
229
- this.child.on("exit", (code) => {
230
- if (!this.publicUrl) {
231
- clearTimeout(timeout);
232
- reject(new Error(`bore exited with code ${code} before establishing tunnel`));
233
- }
234
- });
235
- });
236
- }
237
- async stop() {
238
- if (this.child) {
239
- this.child.kill("SIGTERM");
240
- this.child = null;
241
- log3.info("Bore tunnel stopped");
242
- }
243
- }
244
- getPublicUrl() {
245
- return this.publicUrl;
246
- }
247
- };
248
-
249
- // src/tunnel/providers/tailscale.ts
250
- import { spawn as spawn4, execSync } from "child_process";
251
- var log4 = createChildLogger({ module: "tailscale-tunnel" });
252
- var TailscaleTunnelProvider = class {
253
- child = null;
254
- publicUrl = "";
255
- options;
256
- constructor(options = {}) {
257
- this.options = options;
258
- }
259
- async start(localPort) {
260
- let hostname = "";
261
- try {
262
- const statusJson = execSync("tailscale status --json", { encoding: "utf-8" });
263
- const status = JSON.parse(statusJson);
264
- hostname = String(status.Self.DNSName).replace(/\.$/, "");
265
- log4.debug({ hostname }, "Resolved Tailscale hostname");
266
- } catch (err) {
267
- log4.warn("Failed to resolve Tailscale hostname via status --json");
268
- }
269
- const args = ["funnel", String(localPort)];
270
- if (this.options.bg) {
271
- args.push("--bg");
272
- }
273
- return new Promise((resolve2, reject) => {
274
- const timeout = setTimeout(() => {
275
- this.stop();
276
- reject(new Error("Tailscale funnel timed out after 30s. Is tailscale installed?"));
277
- }, 3e4);
278
- try {
279
- this.child = spawn4("tailscale", args, { stdio: ["ignore", "pipe", "pipe"] });
280
- } catch {
281
- clearTimeout(timeout);
282
- reject(new Error(
283
- "Failed to start tailscale. Install it from https://tailscale.com/download"
284
- ));
285
- return;
286
- }
287
- const urlPattern = /https:\/\/[^\s]+/;
288
- const onData = (data) => {
289
- const line = data.toString();
290
- log4.debug(line.trim());
291
- const match = line.match(urlPattern);
292
- if (match) {
293
- clearTimeout(timeout);
294
- this.publicUrl = match[0];
295
- log4.info({ url: this.publicUrl }, "Tailscale funnel ready");
296
- resolve2(this.publicUrl);
297
- }
298
- };
299
- this.child.stdout?.on("data", onData);
300
- this.child.stderr?.on("data", onData);
301
- this.child.on("error", (err) => {
302
- clearTimeout(timeout);
303
- reject(new Error(
304
- `tailscale failed to start: ${err.message}. Install it from https://tailscale.com/download`
305
- ));
306
- });
307
- this.child.on("exit", (code) => {
308
- if (!this.publicUrl) {
309
- clearTimeout(timeout);
310
- if (hostname) {
311
- this.publicUrl = `https://${hostname}`;
312
- log4.info({ url: this.publicUrl }, "Tailscale funnel ready (constructed from hostname)");
313
- resolve2(this.publicUrl);
314
- } else {
315
- reject(new Error(`tailscale exited with code ${code} before establishing funnel`));
316
- }
317
- }
318
- });
319
- });
320
- }
321
- async stop() {
322
- if (this.child) {
323
- this.child.kill("SIGTERM");
324
- this.child = null;
325
- log4.info("Tailscale funnel stopped");
326
- }
327
- }
328
- getPublicUrl() {
329
- return this.publicUrl;
330
- }
331
- };
332
-
333
- // src/tunnel/tunnel-registry.ts
334
- var log5 = createChildLogger({ module: "tunnel-registry" });
335
- var REGISTRY_PATH = path2.join(os2.homedir(), ".openacp", "tunnels.json");
336
- var TunnelRegistry = class {
337
- entries = /* @__PURE__ */ new Map();
338
- saveTimeout = null;
339
- maxUserTunnels;
340
- providerOptions;
341
- constructor(opts = {}) {
342
- this.maxUserTunnels = opts.maxUserTunnels ?? 5;
343
- this.providerOptions = opts.providerOptions ?? {};
344
- }
345
- async add(port, opts) {
346
- if (this.entries.has(port)) {
347
- const existing = this.entries.get(port);
348
- if (existing.entry.status === "active" || existing.entry.status === "starting") {
349
- throw new Error(`Port ${port} is already tunneled \u2192 ${existing.entry.publicUrl || "starting..."}`);
350
- }
351
- this.entries.delete(port);
352
- }
353
- if (opts.type === "user") {
354
- const userCount = this.list(false).filter((e) => e.status === "active" || e.status === "starting").length;
355
- if (userCount >= this.maxUserTunnels) {
356
- throw new Error(`Max user tunnels (${this.maxUserTunnels}) reached. Stop a tunnel first.`);
357
- }
358
- }
359
- const entry = {
360
- port,
361
- type: opts.type,
362
- provider: opts.provider,
363
- label: opts.label,
364
- sessionId: opts.sessionId,
365
- status: "starting",
366
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
367
- };
368
- const provider = this.createProvider(opts.provider);
369
- const spawnPromise = provider.start(port).then((url) => {
370
- entry.publicUrl = url;
371
- entry.status = "active";
372
- log5.info({ port, url, label: opts.label }, "Tunnel active");
373
- this.scheduleSave();
374
- return url;
375
- }).catch((err) => {
376
- entry.status = "failed";
377
- log5.error({ port, err: err.message }, "Tunnel failed to start");
378
- this.scheduleSave();
379
- throw err;
380
- });
381
- this.entries.set(port, { entry, process: provider, spawnPromise });
382
- this.scheduleSave();
383
- await spawnPromise;
384
- return entry;
385
- }
386
- async stop(port) {
387
- const live = this.entries.get(port);
388
- if (!live) return;
389
- if (live.entry.type === "system") {
390
- throw new Error("Cannot stop system tunnel");
391
- }
392
- if (live.spawnPromise) {
393
- try {
394
- await live.spawnPromise;
395
- } catch {
396
- }
397
- }
398
- if (live.process) {
399
- await live.process.stop();
400
- }
401
- this.entries.delete(port);
402
- this.scheduleSave();
403
- log5.info({ port, label: live.entry.label }, "Tunnel stopped");
404
- }
405
- async stopBySession(sessionId) {
406
- const stopped = [];
407
- const toStop = this.getBySession(sessionId);
408
- for (const entry of toStop) {
409
- try {
410
- await this.stop(entry.port);
411
- stopped.push(entry);
412
- } catch {
413
- }
414
- }
415
- return stopped;
416
- }
417
- async stopAllUser() {
418
- const userEntries = this.list(false);
419
- for (const entry of userEntries) {
420
- try {
421
- await this.stop(entry.port);
422
- } catch {
423
- }
424
- }
425
- }
426
- async shutdown() {
427
- for (const [, live] of this.entries) {
428
- if (live.spawnPromise) {
429
- try {
430
- await live.spawnPromise;
431
- } catch {
432
- }
433
- }
434
- if (live.process) {
435
- await live.process.stop();
436
- }
437
- }
438
- this.entries.clear();
439
- this.scheduleSave();
440
- }
441
- list(includeSystem = false) {
442
- const entries = Array.from(this.entries.values()).map((l) => l.entry);
443
- if (includeSystem) return entries;
444
- return entries.filter((e) => e.type === "user");
445
- }
446
- get(port) {
447
- return this.entries.get(port)?.entry ?? null;
448
- }
449
- getBySession(sessionId) {
450
- return this.list(false).filter((e) => e.sessionId === sessionId);
451
- }
452
- getSystemEntry() {
453
- for (const live of this.entries.values()) {
454
- if (live.entry.type === "system") return live.entry;
455
- }
456
- return null;
457
- }
458
- async restore() {
459
- if (!fs2.existsSync(REGISTRY_PATH)) return;
460
- try {
461
- const raw = JSON.parse(fs2.readFileSync(REGISTRY_PATH, "utf-8"));
462
- log5.info({ count: raw.length }, "Restoring tunnels");
463
- const userEntries = raw.filter((e) => e.type === "user");
464
- for (const persisted of userEntries) {
465
- try {
466
- await this.add(persisted.port, {
467
- type: persisted.type,
468
- provider: persisted.provider,
469
- label: persisted.label,
470
- sessionId: persisted.sessionId
471
- });
472
- } catch (err) {
473
- log5.warn({ port: persisted.port, err: err.message }, "Failed to restore tunnel");
474
- }
475
- }
476
- } catch (err) {
477
- log5.warn({ err: err.message }, "Failed to read tunnels.json");
478
- }
479
- }
480
- createProvider(name) {
481
- switch (name) {
482
- case "cloudflare":
483
- return new CloudflareTunnelProvider(this.providerOptions);
484
- case "ngrok":
485
- return new NgrokTunnelProvider(this.providerOptions);
486
- case "bore":
487
- return new BoreTunnelProvider(this.providerOptions);
488
- case "tailscale":
489
- return new TailscaleTunnelProvider(this.providerOptions);
490
- default:
491
- log5.warn({ provider: name }, "Unknown provider, falling back to cloudflare");
492
- return new CloudflareTunnelProvider(this.providerOptions);
493
- }
494
- }
495
- scheduleSave() {
496
- if (this.saveTimeout) clearTimeout(this.saveTimeout);
497
- this.saveTimeout = setTimeout(() => this.save(), 2e3);
498
- }
499
- save() {
500
- const data = Array.from(this.entries.values()).map((l) => ({
501
- port: l.entry.port,
502
- type: l.entry.type,
503
- provider: l.entry.provider,
504
- label: l.entry.label,
505
- sessionId: l.entry.sessionId,
506
- createdAt: l.entry.createdAt
507
- }));
508
- try {
509
- const dir = path2.dirname(REGISTRY_PATH);
510
- fs2.mkdirSync(dir, { recursive: true });
511
- fs2.writeFileSync(REGISTRY_PATH, JSON.stringify(data, null, 2));
512
- } catch (err) {
513
- log5.error({ err: err.message }, "Failed to save tunnels.json");
514
- }
515
- }
516
- flush() {
517
- if (this.saveTimeout) {
518
- clearTimeout(this.saveTimeout);
519
- this.saveTimeout = null;
520
- }
521
- this.save();
522
- }
523
- };
524
-
525
- // src/tunnel/viewer-store.ts
526
- import * as path3 from "path";
527
- import { nanoid } from "nanoid";
528
- var log6 = createChildLogger({ module: "viewer-store" });
529
- var MAX_CONTENT_SIZE = 1e6;
530
- var EXTENSION_LANGUAGE = {
531
- ".ts": "typescript",
532
- ".tsx": "typescript",
533
- ".js": "javascript",
534
- ".jsx": "javascript",
535
- ".py": "python",
536
- ".rs": "rust",
537
- ".go": "go",
538
- ".java": "java",
539
- ".kt": "kotlin",
540
- ".rb": "ruby",
541
- ".php": "php",
542
- ".c": "c",
543
- ".cpp": "cpp",
544
- ".h": "c",
545
- ".hpp": "cpp",
546
- ".cs": "csharp",
547
- ".swift": "swift",
548
- ".sh": "bash",
549
- ".zsh": "bash",
550
- ".bash": "bash",
551
- ".json": "json",
552
- ".yaml": "yaml",
553
- ".yml": "yaml",
554
- ".toml": "toml",
555
- ".xml": "xml",
556
- ".html": "html",
557
- ".css": "css",
558
- ".scss": "scss",
559
- ".sql": "sql",
560
- ".md": "markdown",
561
- ".dockerfile": "dockerfile",
562
- ".tf": "hcl",
563
- ".vue": "xml",
564
- ".svelte": "xml"
565
- };
566
- var ViewerStore = class {
567
- entries = /* @__PURE__ */ new Map();
568
- cleanupTimer;
569
- ttlMs;
570
- constructor(ttlMinutes = 60) {
571
- this.ttlMs = ttlMinutes * 60 * 1e3;
572
- this.cleanupTimer = setInterval(() => this.cleanup(), 5 * 60 * 1e3);
573
- }
574
- storeFile(sessionId, filePath, content, workingDirectory) {
575
- if (!this.isPathAllowed(filePath, workingDirectory)) {
576
- log6.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
577
- return null;
578
- }
579
- if (content.length > MAX_CONTENT_SIZE) {
580
- log6.debug({ filePath, size: content.length }, "File too large for viewer");
581
- return null;
582
- }
583
- const id = nanoid(12);
584
- const now = Date.now();
585
- this.entries.set(id, {
586
- id,
587
- type: "file",
588
- filePath,
589
- content,
590
- language: this.detectLanguage(filePath),
591
- sessionId,
592
- workingDirectory,
593
- createdAt: now,
594
- expiresAt: now + this.ttlMs
595
- });
596
- log6.debug({ id, filePath }, "Stored file for viewing");
597
- return id;
598
- }
599
- storeDiff(sessionId, filePath, oldContent, newContent, workingDirectory) {
600
- if (!this.isPathAllowed(filePath, workingDirectory)) {
601
- log6.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
602
- return null;
603
- }
604
- const combined = oldContent.length + newContent.length;
605
- if (combined > MAX_CONTENT_SIZE) {
606
- log6.debug({ filePath, size: combined }, "Diff content too large for viewer");
607
- return null;
608
- }
609
- const id = nanoid(12);
610
- const now = Date.now();
611
- this.entries.set(id, {
612
- id,
613
- type: "diff",
614
- filePath,
615
- content: newContent,
616
- oldContent,
617
- language: this.detectLanguage(filePath),
618
- sessionId,
619
- workingDirectory,
620
- createdAt: now,
621
- expiresAt: now + this.ttlMs
622
- });
623
- log6.debug({ id, filePath }, "Stored diff for viewing");
624
- return id;
625
- }
626
- get(id) {
627
- const entry = this.entries.get(id);
628
- if (!entry) return void 0;
629
- if (Date.now() > entry.expiresAt) {
630
- this.entries.delete(id);
631
- return void 0;
632
- }
633
- return entry;
634
- }
635
- cleanup() {
636
- const now = Date.now();
637
- let removed = 0;
638
- for (const [id, entry] of this.entries) {
639
- if (now > entry.expiresAt) {
640
- this.entries.delete(id);
641
- removed++;
642
- }
643
- }
644
- if (removed > 0) {
645
- log6.debug({ removed, remaining: this.entries.size }, "Cleaned up expired viewer entries");
646
- }
647
- }
648
- isPathAllowed(filePath, workingDirectory) {
649
- const resolved = path3.resolve(workingDirectory, filePath);
650
- return resolved.startsWith(path3.resolve(workingDirectory));
651
- }
652
- detectLanguage(filePath) {
653
- const ext = path3.extname(filePath).toLowerCase();
654
- return EXTENSION_LANGUAGE[ext];
655
- }
656
- destroy() {
657
- clearInterval(this.cleanupTimer);
658
- this.entries.clear();
659
- }
660
- };
661
-
662
- // src/tunnel/server.ts
663
- import { Hono } from "hono";
664
-
665
- // src/tunnel/templates/file-viewer.ts
666
- function escapeHtml(text) {
667
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
668
- }
669
- var MONACO_LANGUAGE = {
670
- typescript: "typescript",
671
- javascript: "javascript",
672
- python: "python",
673
- rust: "rust",
674
- go: "go",
675
- java: "java",
676
- kotlin: "kotlin",
677
- ruby: "ruby",
678
- php: "php",
679
- c: "c",
680
- cpp: "cpp",
681
- csharp: "csharp",
682
- swift: "swift",
683
- bash: "shell",
684
- json: "json",
685
- yaml: "yaml",
686
- toml: "ini",
687
- xml: "xml",
688
- html: "html",
689
- css: "css",
690
- scss: "scss",
691
- sql: "sql",
692
- markdown: "markdown",
693
- dockerfile: "dockerfile",
694
- hcl: "hcl",
695
- plaintext: "plaintext"
696
- };
697
- function getMonacoLang(lang) {
698
- if (!lang) return "plaintext";
699
- return MONACO_LANGUAGE[lang] || "plaintext";
700
- }
701
- function renderFileViewer(entry) {
702
- const fileName = entry.filePath || "untitled";
703
- const lang = getMonacoLang(entry.language);
704
- const safeContent = JSON.stringify(entry.content).replace(/<\//g, "<\\/");
705
- return `<!DOCTYPE html>
706
- <html lang="en">
707
- <head>
708
- <meta charset="UTF-8">
709
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
710
- <title>${escapeHtml(fileName)} - OpenACP</title>
711
- <style>
712
- * { margin: 0; padding: 0; box-sizing: border-box; }
713
- body { background: #1e1e1e; color: #d4d4d4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; }
714
- .header { background: #252526; border-bottom: 1px solid #3c3c3c; padding: 8px 16px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; z-index: 10; }
715
- .file-info { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }
716
- .file-icon { font-size: 14px; flex-shrink: 0; }
717
- .file-path { color: #969696; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
718
- .file-name { color: #e0e0e0; font-weight: 500; flex-shrink: 0; }
719
- .actions { display: flex; gap: 6px; flex-shrink: 0; }
720
- .btn { background: #3c3c3c; color: #d4d4d4; border: 1px solid #505050; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.15s; }
721
- .btn:hover { background: #505050; }
722
- .btn.active { background: #0e639c; border-color: #1177bb; }
723
- #editor-container { flex: 1; overflow: hidden; }
724
- .status-bar { background: #007acc; color: #fff; padding: 2px 16px; font-size: 12px; display: flex; justify-content: space-between; flex-shrink: 0; }
725
- </style>
726
- </head>
727
- <body>
728
- <div class="header">
729
- <div class="file-info">
730
- <span class="file-icon">\u{1F4C4}</span>
731
- ${formatBreadcrumb(fileName)}
732
- </div>
733
- <div class="actions">
734
- ${lang === "markdown" ? '<button class="btn" onclick="togglePreview()" id="btn-preview">Preview</button>' : ""}
735
- <button class="btn" onclick="toggleWordWrap()" id="btn-wrap">Wrap</button>
736
- <button class="btn" onclick="toggleMinimap()" id="btn-minimap">Minimap</button>
737
- <button class="btn" onclick="toggleTheme()" id="btn-theme">Light</button>
738
- <button class="btn" onclick="copyCode()">Copy</button>
739
- </div>
740
- </div>
741
- <div id="editor-container"></div>
742
- <div id="preview-wrapper" style="display:none; flex:1; overflow-y:auto;">
743
- <div id="preview-container" style="padding:24px 48px; max-width:900px; margin:0 auto; width:100%;"></div>
744
- </div>
745
- <div class="status-bar">
746
- <span>${escapeHtml(entry.language || "plaintext")} | ${entry.content.split("\n").length} lines</span>
747
- <span>OpenACP Viewer (read-only)</span>
748
- </div>
749
-
750
- ${lang === "markdown" ? '<script src="https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js"></script>' : ""}
751
- <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
752
- <script>
753
- const content = ${safeContent};
754
- const lang = ${JSON.stringify(lang)};
755
- let editor;
756
- let isDark = true;
757
- let wordWrap = false;
758
- let minimap = true;
759
-
760
- require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } });
761
- require(['vs/editor/editor.main'], function () {
762
- editor = monaco.editor.create(document.getElementById('editor-container'), {
763
- value: content,
764
- language: lang,
765
- theme: 'vs-dark',
766
- readOnly: true,
767
- automaticLayout: true,
768
- minimap: { enabled: true },
769
- scrollBeyondLastLine: false,
770
- fontSize: 13,
771
- fontFamily: "'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace",
772
- lineNumbers: 'on',
773
- renderLineHighlight: 'all',
774
- wordWrap: 'off',
775
- padding: { top: 8 },
776
- });
777
-
778
- // Handle line range from URL hash: #L42 or #L42-L55
779
- function highlightFromHash() {
780
- const hash = location.hash.slice(1);
781
- const match = hash.match(/^L(\\d+)(?:-L?(\\d+))?$/);
782
- if (!match) return;
783
- const startLine = parseInt(match[1], 10);
784
- const endLine = match[2] ? parseInt(match[2], 10) : startLine;
785
- editor.revealLineInCenter(startLine);
786
- editor.setSelection(new monaco.Selection(startLine, 1, endLine + 1, 1));
787
- }
788
- highlightFromHash();
789
- window.addEventListener('hashchange', highlightFromHash);
790
- });
791
-
792
- function toggleTheme() {
793
- isDark = !isDark;
794
- monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');
795
- document.body.style.background = isDark ? '#1e1e1e' : '#ffffff';
796
- document.querySelector('.header').style.background = isDark ? '#252526' : '#f3f3f3';
797
- document.querySelector('.header').style.borderColor = isDark ? '#3c3c3c' : '#e0e0e0';
798
- document.getElementById('btn-theme').textContent = isDark ? 'Light' : 'Dark';
799
- }
800
-
801
- function toggleWordWrap() {
802
- wordWrap = !wordWrap;
803
- editor.updateOptions({ wordWrap: wordWrap ? 'on' : 'off' });
804
- document.getElementById('btn-wrap').classList.toggle('active', wordWrap);
805
- }
806
-
807
- function toggleMinimap() {
808
- minimap = !minimap;
809
- editor.updateOptions({ minimap: { enabled: minimap } });
810
- document.getElementById('btn-minimap').classList.toggle('active', !minimap);
811
- }
812
-
813
- function copyCode() {
814
- navigator.clipboard.writeText(content).then(() => {
815
- const btn = event.target;
816
- btn.textContent = 'Copied!';
817
- setTimeout(() => btn.textContent = 'Copy', 2000);
818
- });
819
- }
820
-
821
- let previewMode = false;
822
- function togglePreview() {
823
- previewMode = !previewMode;
824
- const editorEl = document.getElementById('editor-container');
825
- const wrapperEl = document.getElementById('preview-wrapper');
826
- const previewEl = document.getElementById('preview-container');
827
- const btn = document.getElementById('btn-preview');
828
- if (previewMode) {
829
- editorEl.style.display = 'none';
830
- wrapperEl.style.display = 'block';
831
- previewEl.innerHTML = typeof marked !== 'undefined' ? marked.parse(content) : content.replace(/\\n/g, '<br>');
832
- previewEl.style.color = isDark ? '#d4d4d4' : '#1e1e1e';
833
- wrapperEl.style.background = isDark ? '#1e1e1e' : '#ffffff';
834
- btn.classList.add('active');
835
- btn.textContent = 'Editor';
836
- } else {
837
- editorEl.style.display = 'block';
838
- wrapperEl.style.display = 'none';
839
- btn.classList.remove('active');
840
- btn.textContent = 'Preview';
841
- }
842
- }
843
- </script>
844
- <style>
845
- #preview-container { font-size: 15px; line-height: 1.7; }
846
- #preview-container h1 { font-size: 2em; margin: 0.5em 0 0.3em; border-bottom: 1px solid #3c3c3c; padding-bottom: 0.3em; }
847
- #preview-container h2 { font-size: 1.5em; margin: 0.5em 0 0.3em; border-bottom: 1px solid #3c3c3c; padding-bottom: 0.2em; }
848
- #preview-container h3 { font-size: 1.25em; margin: 0.4em 0 0.2em; }
849
- #preview-container p { margin: 0.5em 0; }
850
- #preview-container code { background: rgba(128,128,128,0.2); padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }
851
- #preview-container pre { background: rgba(0,0,0,0.3); padding: 16px; border-radius: 6px; overflow-x: auto; margin: 0.5em 0; }
852
- #preview-container pre code { background: none; padding: 0; }
853
- #preview-container blockquote { border-left: 3px solid #505050; padding-left: 16px; margin: 0.5em 0; color: #969696; }
854
- #preview-container ul, #preview-container ol { padding-left: 24px; margin: 0.5em 0; }
855
- #preview-container table { border-collapse: collapse; margin: 0.5em 0; width: 100%; }
856
- #preview-container th, #preview-container td { border: 1px solid #3c3c3c; padding: 6px 12px; text-align: left; }
857
- #preview-container th { background: rgba(128,128,128,0.15); }
858
- #preview-container a { color: #3794ff; }
859
- #preview-container img { max-width: 100%; }
860
- </style>
861
- </body>
862
- </html>`;
863
- }
864
- function formatBreadcrumb(filePath) {
865
- const parts = filePath.split("/");
866
- if (parts.length <= 1) return `<span class="file-name">${escapeHtml(filePath)}</span>`;
867
- const dir = parts.slice(0, -1).join(" / ");
868
- const name = parts[parts.length - 1];
869
- return `<span class="file-path">${escapeHtml(dir)} /</span> <span class="file-name">${escapeHtml(name)}</span>`;
870
- }
871
-
872
- // src/tunnel/templates/diff-viewer.ts
873
- import { createPatch } from "diff";
874
- function escapeHtml2(text) {
875
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
876
- }
877
- var MONACO_LANGUAGE2 = {
878
- typescript: "typescript",
879
- javascript: "javascript",
880
- python: "python",
881
- rust: "rust",
882
- go: "go",
883
- java: "java",
884
- kotlin: "kotlin",
885
- ruby: "ruby",
886
- php: "php",
887
- c: "c",
888
- cpp: "cpp",
889
- csharp: "csharp",
890
- swift: "swift",
891
- bash: "shell",
892
- json: "json",
893
- yaml: "yaml",
894
- toml: "ini",
895
- xml: "xml",
896
- html: "html",
897
- css: "css",
898
- scss: "scss",
899
- sql: "sql",
900
- markdown: "markdown",
901
- dockerfile: "dockerfile",
902
- hcl: "hcl",
903
- plaintext: "plaintext"
904
- };
905
- function getMonacoLang2(lang) {
906
- if (!lang) return "plaintext";
907
- return MONACO_LANGUAGE2[lang] || "plaintext";
908
- }
909
- function renderDiffViewer(entry) {
910
- const fileName = entry.filePath || "untitled";
911
- const lang = getMonacoLang2(entry.language);
912
- const oldContent = entry.oldContent || "";
913
- const newContent = entry.content;
914
- const patch = createPatch(fileName, oldContent, newContent, "before", "after");
915
- const adds = (patch.match(/^\+[^+]/gm) || []).length;
916
- const dels = (patch.match(/^-[^-]/gm) || []).length;
917
- const safeOld = JSON.stringify(oldContent).replace(/<\//g, "<\\/");
918
- const safeNew = JSON.stringify(newContent).replace(/<\//g, "<\\/");
919
- return `<!DOCTYPE html>
920
- <html lang="en">
921
- <head>
922
- <meta charset="UTF-8">
923
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
924
- <title>${escapeHtml2(fileName)} (diff) - OpenACP</title>
925
- <style>
926
- * { margin: 0; padding: 0; box-sizing: border-box; }
927
- body { background: #1e1e1e; color: #d4d4d4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; }
928
- .header { background: #252526; border-bottom: 1px solid #3c3c3c; padding: 8px 16px; display: flex; align-items: center; justify-content: space-between; flex-shrink: 0; z-index: 10; }
929
- .file-info { display: flex; align-items: center; gap: 8px; font-size: 13px; }
930
- .file-icon { font-size: 14px; }
931
- .file-name { color: #e0e0e0; font-weight: 500; }
932
- .stats { font-size: 12px; margin-left: 12px; }
933
- .stats .add { color: #4ec9b0; }
934
- .stats .del { color: #f14c4c; }
935
- .actions { display: flex; gap: 6px; }
936
- .btn { background: #3c3c3c; color: #d4d4d4; border: 1px solid #505050; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.15s; }
937
- .btn:hover { background: #505050; }
938
- .btn.active { background: #0e639c; border-color: #1177bb; }
939
- #editor-container { flex: 1; overflow: hidden; }
940
- .status-bar { background: #007acc; color: #fff; padding: 2px 16px; font-size: 12px; display: flex; justify-content: space-between; flex-shrink: 0; }
941
- </style>
942
- </head>
943
- <body>
944
- <div class="header">
945
- <div class="file-info">
946
- <span class="file-icon">\u{1F4DD}</span>
947
- <span class="file-name">${escapeHtml2(fileName)}</span>
948
- <span class="stats"><span class="add">+${adds}</span> / <span class="del">-${dels}</span></span>
949
- </div>
950
- <div class="actions">
951
- <button class="btn active" id="btn-side" onclick="setView('side')">Side by Side</button>
952
- <button class="btn" id="btn-inline" onclick="setView('inline')">Inline</button>
953
- <button class="btn" onclick="toggleTheme()" id="btn-theme">Light</button>
954
- </div>
955
- </div>
956
- <div id="editor-container"></div>
957
- <div class="status-bar">
958
- <span>${escapeHtml2(entry.language || "plaintext")} | <span class="add">+${adds}</span> <span class="del">-${dels}</span></span>
959
- <span>OpenACP Diff Viewer</span>
960
- </div>
961
-
962
- <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
963
- <script>
964
- const oldContent = ${safeOld};
965
- const newContent = ${safeNew};
966
- const lang = ${JSON.stringify(lang)};
967
- let diffEditor;
968
- let isDark = true;
969
- let renderSideBySide = true;
970
-
971
- require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } });
972
- require(['vs/editor/editor.main'], function () {
973
- const originalModel = monaco.editor.createModel(oldContent, lang);
974
- const modifiedModel = monaco.editor.createModel(newContent, lang);
975
-
976
- diffEditor = monaco.editor.createDiffEditor(document.getElementById('editor-container'), {
977
- theme: 'vs-dark',
978
- readOnly: true,
979
- automaticLayout: true,
980
- renderSideBySide: true,
981
- scrollBeyondLastLine: false,
982
- fontSize: 13,
983
- fontFamily: "'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace",
984
- padding: { top: 8 },
985
- enableSplitViewResizing: true,
986
- renderOverviewRuler: true,
987
- });
988
-
989
- diffEditor.setModel({ original: originalModel, modified: modifiedModel });
990
- });
991
-
992
- function setView(mode) {
993
- renderSideBySide = mode === 'side';
994
- diffEditor.updateOptions({ renderSideBySide });
995
- document.getElementById('btn-side').classList.toggle('active', renderSideBySide);
996
- document.getElementById('btn-inline').classList.toggle('active', !renderSideBySide);
997
- }
998
-
999
- function toggleTheme() {
1000
- isDark = !isDark;
1001
- monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');
1002
- document.body.style.background = isDark ? '#1e1e1e' : '#ffffff';
1003
- document.querySelector('.header').style.background = isDark ? '#252526' : '#f3f3f3';
1004
- document.querySelector('.header').style.borderColor = isDark ? '#3c3c3c' : '#e0e0e0';
1005
- document.getElementById('btn-theme').textContent = isDark ? 'Light' : 'Dark';
1006
- }
1007
- </script>
1008
- </body>
1009
- </html>`;
1010
- }
1011
-
1012
- // src/tunnel/server.ts
1013
- function notFoundPage() {
1014
- return `<!DOCTYPE html>
1015
- <html><head><meta charset="UTF-8"><title>Not Found - OpenACP</title>
1016
- <style>body{background:#0d1117;color:#c9d1d9;font-family:sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}
1017
- .box{text-align:center;padding:40px}.code{font-size:72px;font-weight:bold;color:#484f58}p{margin-top:16px;color:#8b949e}</style>
1018
- </head><body><div class="box"><div class="code">404</div><p>This viewer link has expired or does not exist.</p></div></body></html>`;
1019
- }
1020
- function createTunnelServer(store, authToken) {
1021
- const app = new Hono();
1022
- if (authToken) {
1023
- app.use("*", async (c, next) => {
1024
- if (c.req.path === "/health") return next();
1025
- const bearer = c.req.header("Authorization")?.replace("Bearer ", "");
1026
- const query = c.req.query("token");
1027
- if (bearer !== authToken && query !== authToken) {
1028
- return c.text("Unauthorized", 401);
1029
- }
1030
- return next();
1031
- });
1032
- }
1033
- app.get("/health", (c) => c.json({ status: "ok" }));
1034
- app.get("/view/:id", (c) => {
1035
- const entry = store.get(c.req.param("id"));
1036
- if (!entry || entry.type !== "file") {
1037
- return c.html(notFoundPage(), 404);
1038
- }
1039
- return c.html(renderFileViewer(entry));
1040
- });
1041
- app.get("/diff/:id", (c) => {
1042
- const entry = store.get(c.req.param("id"));
1043
- if (!entry || entry.type !== "diff") {
1044
- return c.html(notFoundPage(), 404);
1045
- }
1046
- return c.html(renderDiffViewer(entry));
1047
- });
1048
- app.get("/api/file/:id", (c) => {
1049
- const entry = store.get(c.req.param("id"));
1050
- if (!entry || entry.type !== "file") {
1051
- return c.json({ error: "not found" }, 404);
1052
- }
1053
- return c.json({ filePath: entry.filePath, content: entry.content, language: entry.language });
1054
- });
1055
- app.get("/api/diff/:id", (c) => {
1056
- const entry = store.get(c.req.param("id"));
1057
- if (!entry || entry.type !== "diff") {
1058
- return c.json({ error: "not found" }, 404);
1059
- }
1060
- return c.json({ filePath: entry.filePath, oldContent: entry.oldContent, newContent: entry.content, language: entry.language });
1061
- });
1062
- return app;
1063
- }
1064
-
1065
- // src/tunnel/tunnel-service.ts
1066
- var log7 = createChildLogger({ module: "tunnel" });
1067
- var TunnelService = class {
1068
- registry;
1069
- store;
1070
- server = null;
1071
- config;
1072
- systemPort = 0;
1073
- constructor(config) {
1074
- this.config = config;
1075
- this.store = new ViewerStore(config.storeTtlMinutes);
1076
- this.registry = new TunnelRegistry({
1077
- maxUserTunnels: config.maxUserTunnels ?? 5,
1078
- providerOptions: config.options
1079
- });
1080
- }
1081
- async start() {
1082
- const authToken = this.config.auth.enabled ? this.config.auth.token : void 0;
1083
- const app = createTunnelServer(this.store, authToken);
1084
- let actualPort = this.config.port;
1085
- const maxRetries = 10;
1086
- for (let i = 0; i < maxRetries; i++) {
1087
- const port = this.config.port + i;
1088
- const server = serve({ fetch: app.fetch, port });
1089
- const ok = await new Promise((resolve2) => {
1090
- server.on("listening", () => resolve2(true));
1091
- server.on("error", () => resolve2(false));
1092
- });
1093
- if (ok) {
1094
- this.server = server;
1095
- actualPort = port;
1096
- if (i > 0) {
1097
- log7.info({ configuredPort: this.config.port, actualPort }, "Configured port in use, using next available");
1098
- }
1099
- log7.info({ port: actualPort }, "Tunnel HTTP server started");
1100
- break;
1101
- }
1102
- server.close();
1103
- }
1104
- if (!this.server) {
1105
- log7.warn({ port: this.config.port }, "Could not find available port for tunnel HTTP server");
1106
- return `http://localhost:${this.config.port}`;
1107
- }
1108
- this.systemPort = actualPort;
1109
- try {
1110
- await this.registry.add(actualPort, {
1111
- type: "system",
1112
- provider: this.config.provider,
1113
- label: "File Viewer"
1114
- });
1115
- } catch (err) {
1116
- log7.warn({ err: err.message }, "System tunnel failed, running on localhost");
1117
- }
1118
- await this.registry.restore();
1119
- const systemEntry = this.registry.getSystemEntry();
1120
- return systemEntry?.publicUrl || `http://localhost:${actualPort}`;
1121
- }
1122
- async stop() {
1123
- await this.registry.shutdown();
1124
- this.registry.flush();
1125
- if (this.server) {
1126
- this.server.close();
1127
- this.server = null;
1128
- }
1129
- this.store.destroy();
1130
- log7.info("Tunnel service stopped");
1131
- }
1132
- // --- User tunnel management ---
1133
- async addTunnel(port, opts) {
1134
- return this.registry.add(port, {
1135
- type: "user",
1136
- provider: this.config.provider,
1137
- label: opts?.label,
1138
- sessionId: opts?.sessionId
1139
- });
1140
- }
1141
- async stopTunnel(port) {
1142
- return this.registry.stop(port);
1143
- }
1144
- async stopAllUser() {
1145
- return this.registry.stopAllUser();
1146
- }
1147
- async stopBySession(sessionId) {
1148
- return this.registry.stopBySession(sessionId);
1149
- }
1150
- listTunnels() {
1151
- return this.registry.list(false);
1152
- }
1153
- getTunnel(port) {
1154
- return this.registry.get(port);
1155
- }
1156
- // --- Viewer (system tunnel) ---
1157
- getPublicUrl() {
1158
- const system = this.registry.getSystemEntry();
1159
- return system?.publicUrl || `http://localhost:${this.systemPort || this.config.port}`;
1160
- }
1161
- getStore() {
1162
- return this.store;
1163
- }
1164
- fileUrl(entryId) {
1165
- return `${this.getPublicUrl()}/view/${entryId}`;
1166
- }
1167
- diffUrl(entryId) {
1168
- return `${this.getPublicUrl()}/diff/${entryId}`;
1169
- }
1170
- };
1171
- export {
1172
- TunnelService
1173
- };
1174
- //# sourceMappingURL=tunnel-service-CJLUH6SZ.js.map