@openacp/cli 0.5.2 → 0.6.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.
Files changed (96) hide show
  1. package/README.md +40 -14
  2. package/dist/action-detect-6M5GCGAU.js +15 -0
  3. package/dist/admin-IKPS5PFC.js +16 -0
  4. package/dist/agents-55NX3DHM.js +14 -0
  5. package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
  6. package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
  7. package/dist/chunk-3WPG7GXA.js +134 -0
  8. package/dist/chunk-3WPG7GXA.js.map +1 -0
  9. package/dist/{chunk-NDR5JCS7.js → chunk-437NLISU.js} +2 -2
  10. package/dist/chunk-5NBWM7P6.js +438 -0
  11. package/dist/chunk-5NBWM7P6.js.map +1 -0
  12. package/dist/{chunk-F4TB4UBK.js → chunk-6Q7PZWCL.js} +171 -26
  13. package/dist/chunk-6Q7PZWCL.js.map +1 -0
  14. package/dist/chunk-7G5QKLLF.js +105 -0
  15. package/dist/chunk-7G5QKLLF.js.map +1 -0
  16. package/dist/chunk-AKIU4JBF.js +145 -0
  17. package/dist/chunk-AKIU4JBF.js.map +1 -0
  18. package/dist/{chunk-JRF4G4X7.js → chunk-DWQKUECJ.js} +13 -2
  19. package/dist/chunk-DWQKUECJ.js.map +1 -0
  20. package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
  21. package/dist/chunk-EVFJW45N.js.map +1 -0
  22. package/dist/chunk-H7ZMPBZC.js +203 -0
  23. package/dist/chunk-H7ZMPBZC.js.map +1 -0
  24. package/dist/chunk-I7WC6E5S.js +71 -0
  25. package/dist/chunk-I7WC6E5S.js.map +1 -0
  26. package/dist/{chunk-4ZGMSNRP.js → chunk-MHFCZGRW.js} +114 -16
  27. package/dist/chunk-MHFCZGRW.js.map +1 -0
  28. package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
  29. package/dist/chunk-PMGNLNSH.js.map +1 -0
  30. package/dist/chunk-SM3G6UAX.js +122 -0
  31. package/dist/chunk-SM3G6UAX.js.map +1 -0
  32. package/dist/{chunk-IRGYTNLP.js → chunk-SPX7CKWV.js} +76 -2
  33. package/dist/chunk-SPX7CKWV.js.map +1 -0
  34. package/dist/chunk-T22OLSET.js +265 -0
  35. package/dist/chunk-T22OLSET.js.map +1 -0
  36. package/dist/chunk-THBR6OXH.js +62 -0
  37. package/dist/chunk-THBR6OXH.js.map +1 -0
  38. package/dist/{chunk-65XE66HK.js → chunk-V2V767XI.js} +373 -489
  39. package/dist/chunk-V2V767XI.js.map +1 -0
  40. package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
  41. package/dist/chunk-W3EYKZNQ.js.map +1 -0
  42. package/dist/{chunk-VBEWSWVL.js → chunk-YYQXWA62.js} +52 -9
  43. package/dist/chunk-YYQXWA62.js.map +1 -0
  44. package/dist/cli.js +121 -32
  45. package/dist/cli.js.map +1 -1
  46. package/dist/{config-PCPIBPUA.js → config-KF2MQWAP.js} +2 -2
  47. package/dist/config-editor-OTODXUF7.js +12 -0
  48. package/dist/{daemon-JZLFRUW6.js → daemon-U6UC7OM4.js} +3 -3
  49. package/dist/discord-SLLKRUP7.js +2034 -0
  50. package/dist/discord-SLLKRUP7.js.map +1 -0
  51. package/dist/doctor-DB5PRQ6D.js +14 -0
  52. package/dist/doctor-DB5PRQ6D.js.map +1 -0
  53. package/dist/doctor-SYWNJFYK.js +9 -0
  54. package/dist/doctor-SYWNJFYK.js.map +1 -0
  55. package/dist/index.d.ts +47 -7
  56. package/dist/index.js +11 -9
  57. package/dist/{integrate-BLETI3UO.js → integrate-VOUYBPPZ.js} +106 -3
  58. package/dist/integrate-VOUYBPPZ.js.map +1 -0
  59. package/dist/{main-AH3NCVM3.js → main-M6RH3SS5.js} +31 -23
  60. package/dist/main-M6RH3SS5.js.map +1 -0
  61. package/dist/new-session-DRRP2J7E.js +16 -0
  62. package/dist/new-session-DRRP2J7E.js.map +1 -0
  63. package/dist/session-FVFLBREJ.js +19 -0
  64. package/dist/session-FVFLBREJ.js.map +1 -0
  65. package/dist/settings-LPOLJ6SA.js +12 -0
  66. package/dist/settings-LPOLJ6SA.js.map +1 -0
  67. package/dist/{setup-7JINXQOA.js → setup-LI5CKYDK.js} +9 -5
  68. package/dist/setup-LI5CKYDK.js.map +1 -0
  69. package/dist/{tunnel-service-LEVPLXAZ.js → tunnel-service-U6V4HQOO.js} +263 -47
  70. package/dist/tunnel-service-U6V4HQOO.js.map +1 -0
  71. package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
  72. package/dist/version-ALWGGVKM.js.map +1 -0
  73. package/package.json +2 -1
  74. package/dist/chunk-4ZGMSNRP.js.map +0 -1
  75. package/dist/chunk-65XE66HK.js.map +0 -1
  76. package/dist/chunk-F4TB4UBK.js.map +0 -1
  77. package/dist/chunk-IRGYTNLP.js.map +0 -1
  78. package/dist/chunk-JRF4G4X7.js.map +0 -1
  79. package/dist/chunk-KSIQZC3J.js.map +0 -1
  80. package/dist/chunk-OORPX73T.js.map +0 -1
  81. package/dist/chunk-VBEWSWVL.js.map +0 -1
  82. package/dist/chunk-X6LLG7XN.js.map +0 -1
  83. package/dist/config-editor-DDF3ZFJK.js +0 -12
  84. package/dist/doctor-N2HKKUUQ.js +0 -9
  85. package/dist/integrate-BLETI3UO.js.map +0 -1
  86. package/dist/main-AH3NCVM3.js.map +0 -1
  87. package/dist/tunnel-service-LEVPLXAZ.js.map +0 -1
  88. /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
  89. /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
  90. /package/dist/{config-PCPIBPUA.js.map → agents-55NX3DHM.js.map} +0 -0
  91. /package/dist/{config-editor-DDF3ZFJK.js.map → api-client-BH2JFHQW.js.map} +0 -0
  92. /package/dist/{daemon-JZLFRUW6.js.map → autostart-A7JRU4WJ.js.map} +0 -0
  93. /package/dist/{chunk-NDR5JCS7.js.map → chunk-437NLISU.js.map} +0 -0
  94. /package/dist/{doctor-N2HKKUUQ.js.map → config-KF2MQWAP.js.map} +0 -0
  95. /package/dist/{setup-7JINXQOA.js.map → config-editor-OTODXUF7.js.map} +0 -0
  96. /package/dist/{version-VC5CPXBX.js.map → daemon-U6UC7OM4.js.map} +0 -0
@@ -8,6 +8,11 @@ import {
8
8
  // src/tunnel/tunnel-service.ts
9
9
  import { serve } from "@hono/node-server";
10
10
 
11
+ // src/tunnel/tunnel-registry.ts
12
+ import fs from "fs";
13
+ import path from "path";
14
+ import os from "os";
15
+
11
16
  // src/tunnel/providers/cloudflare.ts
12
17
  import { spawn } from "child_process";
13
18
  var log = createChildLogger({ module: "cloudflare-tunnel" });
@@ -306,10 +311,202 @@ var TailscaleTunnelProvider = class {
306
311
  }
307
312
  };
308
313
 
314
+ // src/tunnel/tunnel-registry.ts
315
+ var log5 = createChildLogger({ module: "tunnel-registry" });
316
+ var REGISTRY_PATH = path.join(os.homedir(), ".openacp", "tunnels.json");
317
+ var TunnelRegistry = class {
318
+ entries = /* @__PURE__ */ new Map();
319
+ saveTimeout = null;
320
+ maxUserTunnels;
321
+ providerOptions;
322
+ constructor(opts = {}) {
323
+ this.maxUserTunnels = opts.maxUserTunnels ?? 5;
324
+ this.providerOptions = opts.providerOptions ?? {};
325
+ }
326
+ async add(port, opts) {
327
+ if (this.entries.has(port)) {
328
+ const existing = this.entries.get(port);
329
+ if (existing.entry.status === "active" || existing.entry.status === "starting") {
330
+ throw new Error(`Port ${port} is already tunneled \u2192 ${existing.entry.publicUrl || "starting..."}`);
331
+ }
332
+ this.entries.delete(port);
333
+ }
334
+ if (opts.type === "user") {
335
+ const userCount = this.list(false).filter((e) => e.status === "active" || e.status === "starting").length;
336
+ if (userCount >= this.maxUserTunnels) {
337
+ throw new Error(`Max user tunnels (${this.maxUserTunnels}) reached. Stop a tunnel first.`);
338
+ }
339
+ }
340
+ const entry = {
341
+ port,
342
+ type: opts.type,
343
+ provider: opts.provider,
344
+ label: opts.label,
345
+ sessionId: opts.sessionId,
346
+ status: "starting",
347
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
348
+ };
349
+ const provider = this.createProvider(opts.provider);
350
+ const spawnPromise = provider.start(port).then((url) => {
351
+ entry.publicUrl = url;
352
+ entry.status = "active";
353
+ log5.info({ port, url, label: opts.label }, "Tunnel active");
354
+ this.scheduleSave();
355
+ return url;
356
+ }).catch((err) => {
357
+ entry.status = "failed";
358
+ log5.error({ port, err: err.message }, "Tunnel failed to start");
359
+ this.scheduleSave();
360
+ throw err;
361
+ });
362
+ this.entries.set(port, { entry, process: provider, spawnPromise });
363
+ this.scheduleSave();
364
+ await spawnPromise;
365
+ return entry;
366
+ }
367
+ async stop(port) {
368
+ const live = this.entries.get(port);
369
+ if (!live) return;
370
+ if (live.entry.type === "system") {
371
+ throw new Error("Cannot stop system tunnel");
372
+ }
373
+ if (live.spawnPromise) {
374
+ try {
375
+ await live.spawnPromise;
376
+ } catch {
377
+ }
378
+ }
379
+ if (live.process) {
380
+ await live.process.stop();
381
+ }
382
+ this.entries.delete(port);
383
+ this.scheduleSave();
384
+ log5.info({ port, label: live.entry.label }, "Tunnel stopped");
385
+ }
386
+ async stopBySession(sessionId) {
387
+ const stopped = [];
388
+ const toStop = this.getBySession(sessionId);
389
+ for (const entry of toStop) {
390
+ try {
391
+ await this.stop(entry.port);
392
+ stopped.push(entry);
393
+ } catch {
394
+ }
395
+ }
396
+ return stopped;
397
+ }
398
+ async stopAllUser() {
399
+ const userEntries = this.list(false);
400
+ for (const entry of userEntries) {
401
+ try {
402
+ await this.stop(entry.port);
403
+ } catch {
404
+ }
405
+ }
406
+ }
407
+ async shutdown() {
408
+ for (const [, live] of this.entries) {
409
+ if (live.spawnPromise) {
410
+ try {
411
+ await live.spawnPromise;
412
+ } catch {
413
+ }
414
+ }
415
+ if (live.process) {
416
+ await live.process.stop();
417
+ }
418
+ }
419
+ this.entries.clear();
420
+ this.scheduleSave();
421
+ }
422
+ list(includeSystem = false) {
423
+ const entries = Array.from(this.entries.values()).map((l) => l.entry);
424
+ if (includeSystem) return entries;
425
+ return entries.filter((e) => e.type === "user");
426
+ }
427
+ get(port) {
428
+ return this.entries.get(port)?.entry ?? null;
429
+ }
430
+ getBySession(sessionId) {
431
+ return this.list(false).filter((e) => e.sessionId === sessionId);
432
+ }
433
+ getSystemEntry() {
434
+ for (const live of this.entries.values()) {
435
+ if (live.entry.type === "system") return live.entry;
436
+ }
437
+ return null;
438
+ }
439
+ async restore() {
440
+ if (!fs.existsSync(REGISTRY_PATH)) return;
441
+ try {
442
+ const raw = JSON.parse(fs.readFileSync(REGISTRY_PATH, "utf-8"));
443
+ log5.info({ count: raw.length }, "Restoring tunnels");
444
+ const userEntries = raw.filter((e) => e.type === "user");
445
+ for (const persisted of userEntries) {
446
+ try {
447
+ await this.add(persisted.port, {
448
+ type: persisted.type,
449
+ provider: persisted.provider,
450
+ label: persisted.label,
451
+ sessionId: persisted.sessionId
452
+ });
453
+ } catch (err) {
454
+ log5.warn({ port: persisted.port, err: err.message }, "Failed to restore tunnel");
455
+ }
456
+ }
457
+ } catch (err) {
458
+ log5.warn({ err: err.message }, "Failed to read tunnels.json");
459
+ }
460
+ }
461
+ createProvider(name) {
462
+ switch (name) {
463
+ case "cloudflare":
464
+ return new CloudflareTunnelProvider(this.providerOptions);
465
+ case "ngrok":
466
+ return new NgrokTunnelProvider(this.providerOptions);
467
+ case "bore":
468
+ return new BoreTunnelProvider(this.providerOptions);
469
+ case "tailscale":
470
+ return new TailscaleTunnelProvider(this.providerOptions);
471
+ default:
472
+ log5.warn({ provider: name }, "Unknown provider, falling back to cloudflare");
473
+ return new CloudflareTunnelProvider(this.providerOptions);
474
+ }
475
+ }
476
+ scheduleSave() {
477
+ if (this.saveTimeout) clearTimeout(this.saveTimeout);
478
+ this.saveTimeout = setTimeout(() => this.save(), 2e3);
479
+ }
480
+ save() {
481
+ const data = Array.from(this.entries.values()).map((l) => ({
482
+ port: l.entry.port,
483
+ type: l.entry.type,
484
+ provider: l.entry.provider,
485
+ label: l.entry.label,
486
+ sessionId: l.entry.sessionId,
487
+ createdAt: l.entry.createdAt
488
+ }));
489
+ try {
490
+ const dir = path.dirname(REGISTRY_PATH);
491
+ fs.mkdirSync(dir, { recursive: true });
492
+ fs.writeFileSync(REGISTRY_PATH, JSON.stringify(data, null, 2));
493
+ } catch (err) {
494
+ log5.error({ err: err.message }, "Failed to save tunnels.json");
495
+ }
496
+ }
497
+ flush() {
498
+ if (this.saveTimeout) {
499
+ clearTimeout(this.saveTimeout);
500
+ this.saveTimeout = null;
501
+ }
502
+ this.save();
503
+ }
504
+ };
505
+
309
506
  // src/tunnel/viewer-store.ts
310
- import * as path from "path";
507
+ import * as path2 from "path";
311
508
  import { nanoid } from "nanoid";
312
- var log5 = createChildLogger({ module: "viewer-store" });
509
+ var log6 = createChildLogger({ module: "viewer-store" });
313
510
  var MAX_CONTENT_SIZE = 1e6;
314
511
  var EXTENSION_LANGUAGE = {
315
512
  ".ts": "typescript",
@@ -357,11 +554,11 @@ var ViewerStore = class {
357
554
  }
358
555
  storeFile(sessionId, filePath, content, workingDirectory) {
359
556
  if (!this.isPathAllowed(filePath, workingDirectory)) {
360
- log5.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
557
+ log6.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
361
558
  return null;
362
559
  }
363
560
  if (content.length > MAX_CONTENT_SIZE) {
364
- log5.debug({ filePath, size: content.length }, "File too large for viewer");
561
+ log6.debug({ filePath, size: content.length }, "File too large for viewer");
365
562
  return null;
366
563
  }
367
564
  const id = nanoid(12);
@@ -377,17 +574,17 @@ var ViewerStore = class {
377
574
  createdAt: now,
378
575
  expiresAt: now + this.ttlMs
379
576
  });
380
- log5.debug({ id, filePath }, "Stored file for viewing");
577
+ log6.debug({ id, filePath }, "Stored file for viewing");
381
578
  return id;
382
579
  }
383
580
  storeDiff(sessionId, filePath, oldContent, newContent, workingDirectory) {
384
581
  if (!this.isPathAllowed(filePath, workingDirectory)) {
385
- log5.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
582
+ log6.warn({ filePath, workingDirectory }, "Path outside workspace, rejecting");
386
583
  return null;
387
584
  }
388
585
  const combined = oldContent.length + newContent.length;
389
586
  if (combined > MAX_CONTENT_SIZE) {
390
- log5.debug({ filePath, size: combined }, "Diff content too large for viewer");
587
+ log6.debug({ filePath, size: combined }, "Diff content too large for viewer");
391
588
  return null;
392
589
  }
393
590
  const id = nanoid(12);
@@ -404,7 +601,7 @@ var ViewerStore = class {
404
601
  createdAt: now,
405
602
  expiresAt: now + this.ttlMs
406
603
  });
407
- log5.debug({ id, filePath }, "Stored diff for viewing");
604
+ log6.debug({ id, filePath }, "Stored diff for viewing");
408
605
  return id;
409
606
  }
410
607
  get(id) {
@@ -426,15 +623,15 @@ var ViewerStore = class {
426
623
  }
427
624
  }
428
625
  if (removed > 0) {
429
- log5.debug({ removed, remaining: this.entries.size }, "Cleaned up expired viewer entries");
626
+ log6.debug({ removed, remaining: this.entries.size }, "Cleaned up expired viewer entries");
430
627
  }
431
628
  }
432
629
  isPathAllowed(filePath, workingDirectory) {
433
- const resolved = path.resolve(workingDirectory, filePath);
434
- return resolved.startsWith(path.resolve(workingDirectory));
630
+ const resolved = path2.resolve(workingDirectory, filePath);
631
+ return resolved.startsWith(path2.resolve(workingDirectory));
435
632
  }
436
633
  detectLanguage(filePath) {
437
- const ext = path.extname(filePath).toLowerCase();
634
+ const ext = path2.extname(filePath).toLowerCase();
438
635
  return EXTENSION_LANGUAGE[ext];
439
636
  }
440
637
  destroy() {
@@ -847,17 +1044,20 @@ function createTunnelServer(store, authToken) {
847
1044
  }
848
1045
 
849
1046
  // src/tunnel/tunnel-service.ts
850
- var log6 = createChildLogger({ module: "tunnel" });
1047
+ var log7 = createChildLogger({ module: "tunnel" });
851
1048
  var TunnelService = class {
852
- provider;
1049
+ registry;
853
1050
  store;
854
1051
  server = null;
855
- publicUrl = "";
856
1052
  config;
1053
+ systemPort = 0;
857
1054
  constructor(config) {
858
1055
  this.config = config;
859
1056
  this.store = new ViewerStore(config.storeTtlMinutes);
860
- this.provider = this.createProvider(config.provider, config.options);
1057
+ this.registry = new TunnelRegistry({
1058
+ maxUserTunnels: config.maxUserTunnels ?? 5,
1059
+ providerOptions: config.options
1060
+ });
861
1061
  }
862
1062
  async start() {
863
1063
  const authToken = this.config.auth.enabled ? this.config.auth.token : void 0;
@@ -875,65 +1075,81 @@ var TunnelService = class {
875
1075
  this.server = server;
876
1076
  actualPort = port;
877
1077
  if (i > 0) {
878
- log6.info({ configuredPort: this.config.port, actualPort }, "Configured port in use, using next available");
1078
+ log7.info({ configuredPort: this.config.port, actualPort }, "Configured port in use, using next available");
879
1079
  }
880
- log6.info({ port: actualPort }, "Tunnel HTTP server started");
1080
+ log7.info({ port: actualPort }, "Tunnel HTTP server started");
881
1081
  break;
882
1082
  }
883
1083
  server.close();
884
1084
  }
885
1085
  if (!this.server) {
886
- log6.warn({ port: this.config.port }, "Could not find available port for tunnel HTTP server");
887
- this.publicUrl = `http://localhost:${this.config.port}`;
888
- return this.publicUrl;
1086
+ log7.warn({ port: this.config.port }, "Could not find available port for tunnel HTTP server");
1087
+ return `http://localhost:${this.config.port}`;
889
1088
  }
1089
+ this.systemPort = actualPort;
890
1090
  try {
891
- this.publicUrl = await this.provider.start(actualPort);
892
- log6.info({ url: this.publicUrl }, "Tunnel public URL ready");
1091
+ await this.registry.add(actualPort, {
1092
+ type: "system",
1093
+ provider: this.config.provider,
1094
+ label: "File Viewer"
1095
+ });
893
1096
  } catch (err) {
894
- log6.warn({ err }, "Tunnel provider failed to start, running without public URL");
895
- this.publicUrl = `http://localhost:${actualPort}`;
1097
+ log7.warn({ err: err.message }, "System tunnel failed, running on localhost");
896
1098
  }
897
- return this.publicUrl;
1099
+ await this.registry.restore();
1100
+ const systemEntry = this.registry.getSystemEntry();
1101
+ return systemEntry?.publicUrl || `http://localhost:${actualPort}`;
898
1102
  }
899
1103
  async stop() {
900
- await this.provider.stop();
1104
+ await this.registry.shutdown();
1105
+ this.registry.flush();
901
1106
  if (this.server) {
902
1107
  this.server.close();
903
1108
  this.server = null;
904
1109
  }
905
1110
  this.store.destroy();
906
- log6.info("Tunnel service stopped");
1111
+ log7.info("Tunnel service stopped");
1112
+ }
1113
+ // --- User tunnel management ---
1114
+ async addTunnel(port, opts) {
1115
+ return this.registry.add(port, {
1116
+ type: "user",
1117
+ provider: this.config.provider,
1118
+ label: opts?.label,
1119
+ sessionId: opts?.sessionId
1120
+ });
1121
+ }
1122
+ async stopTunnel(port) {
1123
+ return this.registry.stop(port);
1124
+ }
1125
+ async stopAllUser() {
1126
+ return this.registry.stopAllUser();
1127
+ }
1128
+ async stopBySession(sessionId) {
1129
+ return this.registry.stopBySession(sessionId);
1130
+ }
1131
+ listTunnels() {
1132
+ return this.registry.list(false);
907
1133
  }
1134
+ getTunnel(port) {
1135
+ return this.registry.get(port);
1136
+ }
1137
+ // --- Viewer (system tunnel) ---
908
1138
  getPublicUrl() {
909
- return this.publicUrl;
1139
+ const system = this.registry.getSystemEntry();
1140
+ return system?.publicUrl || `http://localhost:${this.systemPort || this.config.port}`;
910
1141
  }
911
1142
  getStore() {
912
1143
  return this.store;
913
1144
  }
914
1145
  fileUrl(entryId) {
915
- return `${this.publicUrl}/view/${entryId}`;
1146
+ return `${this.getPublicUrl()}/view/${entryId}`;
916
1147
  }
917
1148
  diffUrl(entryId) {
918
- return `${this.publicUrl}/diff/${entryId}`;
919
- }
920
- createProvider(name, options) {
921
- switch (name) {
922
- case "cloudflare":
923
- return new CloudflareTunnelProvider(options);
924
- case "ngrok":
925
- return new NgrokTunnelProvider(options);
926
- case "bore":
927
- return new BoreTunnelProvider(options);
928
- case "tailscale":
929
- return new TailscaleTunnelProvider(options);
930
- default:
931
- log6.warn({ provider: name }, "Unknown tunnel provider, falling back to cloudflare");
932
- return new CloudflareTunnelProvider(options);
933
- }
1149
+ return `${this.getPublicUrl()}/diff/${entryId}`;
934
1150
  }
935
1151
  };
936
1152
  export {
937
1153
  TunnelService
938
1154
  };
939
- //# sourceMappingURL=tunnel-service-LEVPLXAZ.js.map
1155
+ //# sourceMappingURL=tunnel-service-U6V4HQOO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/tunnel/tunnel-service.ts","../../src/tunnel/tunnel-registry.ts","../../src/tunnel/providers/cloudflare.ts","../../src/tunnel/providers/ngrok.ts","../../src/tunnel/providers/bore.ts","../../src/tunnel/providers/tailscale.ts","../../src/tunnel/viewer-store.ts","../../src/tunnel/server.ts","../../src/tunnel/templates/file-viewer.ts","../../src/tunnel/templates/diff-viewer.ts"],"sourcesContent":["import { serve } from '@hono/node-server'\nimport type { TunnelConfig } from '../core/config.js'\nimport { createChildLogger } from '../core/log.js'\nimport { TunnelRegistry, type TunnelEntry } from './tunnel-registry.js'\nimport { ViewerStore } from './viewer-store.js'\nimport { createTunnelServer } from './server.js'\n\nconst log = createChildLogger({ module: 'tunnel' })\n\nexport class TunnelService {\n private registry: TunnelRegistry\n private store: ViewerStore\n private server: ReturnType<typeof serve> | null = null\n private config: TunnelConfig\n private systemPort = 0\n\n constructor(config: TunnelConfig) {\n this.config = config\n this.store = new ViewerStore(config.storeTtlMinutes)\n this.registry = new TunnelRegistry({\n maxUserTunnels: config.maxUserTunnels ?? 5,\n providerOptions: config.options,\n })\n }\n\n async start(): Promise<string> {\n // 1. Start HTTP viewer server — try configured port, then auto-increment\n const authToken = this.config.auth.enabled ? this.config.auth.token : undefined\n const app = createTunnelServer(this.store, authToken)\n\n let actualPort = this.config.port\n const maxRetries = 10\n\n for (let i = 0; i < maxRetries; i++) {\n const port = this.config.port + i\n const server = serve({ fetch: app.fetch, port })\n\n const ok = await new Promise<boolean>((resolve) => {\n server.on('listening', () => resolve(true))\n server.on('error', () => resolve(false))\n })\n\n if (ok) {\n this.server = server\n actualPort = port\n if (i > 0) {\n log.info({ configuredPort: this.config.port, actualPort }, 'Configured port in use, using next available')\n }\n log.info({ port: actualPort }, 'Tunnel HTTP server started')\n break\n }\n\n server.close()\n }\n\n if (!this.server) {\n log.warn({ port: this.config.port }, 'Could not find available port for tunnel HTTP server')\n return `http://localhost:${this.config.port}`\n }\n\n this.systemPort = actualPort\n\n // 2. Register system tunnel (file viewer)\n try {\n await this.registry.add(actualPort, {\n type: 'system',\n provider: this.config.provider,\n label: 'File Viewer',\n })\n } catch (err) {\n log.warn({ err: (err as Error).message }, 'System tunnel failed, running on localhost')\n }\n\n // 3. Restore persisted user tunnels\n await this.registry.restore()\n\n const systemEntry = this.registry.getSystemEntry()\n return systemEntry?.publicUrl || `http://localhost:${actualPort}`\n }\n\n async stop(): Promise<void> {\n await this.registry.shutdown()\n this.registry.flush()\n if (this.server) {\n this.server.close()\n this.server = null\n }\n this.store.destroy()\n log.info('Tunnel service stopped')\n }\n\n // --- User tunnel management ---\n\n async addTunnel(port: number, opts?: { label?: string; sessionId?: string }): Promise<TunnelEntry> {\n return this.registry.add(port, {\n type: 'user',\n provider: this.config.provider,\n label: opts?.label,\n sessionId: opts?.sessionId,\n })\n }\n\n async stopTunnel(port: number): Promise<void> {\n return this.registry.stop(port)\n }\n\n async stopAllUser(): Promise<void> {\n return this.registry.stopAllUser()\n }\n\n async stopBySession(sessionId: string): Promise<TunnelEntry[]> {\n return this.registry.stopBySession(sessionId)\n }\n\n listTunnels(): TunnelEntry[] {\n return this.registry.list(false) // user only\n }\n\n getTunnel(port: number): TunnelEntry | null {\n return this.registry.get(port)\n }\n\n // --- Viewer (system tunnel) ---\n\n getPublicUrl(): string {\n const system = this.registry.getSystemEntry()\n return system?.publicUrl || `http://localhost:${this.systemPort || this.config.port}`\n }\n\n getStore(): ViewerStore {\n return this.store\n }\n\n fileUrl(entryId: string): string {\n return `${this.getPublicUrl()}/view/${entryId}`\n }\n\n diffUrl(entryId: string): string {\n return `${this.getPublicUrl()}/diff/${entryId}`\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport os from 'node:os'\nimport { createChildLogger } from '../core/log.js'\nimport type { TunnelProvider } from './provider.js'\nimport { CloudflareTunnelProvider } from './providers/cloudflare.js'\nimport { NgrokTunnelProvider } from './providers/ngrok.js'\nimport { BoreTunnelProvider } from './providers/bore.js'\nimport { TailscaleTunnelProvider } from './providers/tailscale.js'\n\nconst log = createChildLogger({ module: 'tunnel-registry' })\n\nexport interface TunnelEntry {\n port: number\n type: 'system' | 'user'\n provider: string\n label?: string\n publicUrl?: string\n sessionId?: string\n status: 'stopped' | 'starting' | 'active' | 'failed'\n createdAt: string\n}\n\ninterface PersistedEntry {\n port: number\n type: 'system' | 'user'\n provider: string\n label?: string\n sessionId?: string\n createdAt: string\n}\n\ninterface LiveEntry {\n entry: TunnelEntry\n process: TunnelProvider | null\n spawnPromise: Promise<string> | null\n}\n\nconst REGISTRY_PATH = path.join(os.homedir(), '.openacp', 'tunnels.json')\n\nexport class TunnelRegistry {\n private entries: Map<number, LiveEntry> = new Map()\n private saveTimeout: ReturnType<typeof setTimeout> | null = null\n private maxUserTunnels: number\n private providerOptions: Record<string, unknown>\n\n constructor(opts: { maxUserTunnels?: number; providerOptions?: Record<string, unknown> } = {}) {\n this.maxUserTunnels = opts.maxUserTunnels ?? 5\n this.providerOptions = opts.providerOptions ?? {}\n }\n\n async add(port: number, opts: {\n type: 'system' | 'user'\n provider: string\n label?: string\n sessionId?: string\n }): Promise<TunnelEntry> {\n // Check if port already registered\n if (this.entries.has(port)) {\n const existing = this.entries.get(port)!\n if (existing.entry.status === 'active' || existing.entry.status === 'starting') {\n throw new Error(`Port ${port} is already tunneled → ${existing.entry.publicUrl || 'starting...'}`)\n }\n // Stopped/failed entry — remove and re-add\n this.entries.delete(port)\n }\n\n // Check max user tunnels\n if (opts.type === 'user') {\n const userCount = this.list(false).filter(e => e.status === 'active' || e.status === 'starting').length\n if (userCount >= this.maxUserTunnels) {\n throw new Error(`Max user tunnels (${this.maxUserTunnels}) reached. Stop a tunnel first.`)\n }\n }\n\n const entry: TunnelEntry = {\n port,\n type: opts.type,\n provider: opts.provider,\n label: opts.label,\n sessionId: opts.sessionId,\n status: 'starting',\n createdAt: new Date().toISOString(),\n }\n\n const provider = this.createProvider(opts.provider)\n const spawnPromise = provider.start(port).then(url => {\n entry.publicUrl = url\n entry.status = 'active'\n log.info({ port, url, label: opts.label }, 'Tunnel active')\n this.scheduleSave()\n return url\n }).catch(err => {\n entry.status = 'failed'\n log.error({ port, err: (err as Error).message }, 'Tunnel failed to start')\n this.scheduleSave()\n throw err\n })\n\n this.entries.set(port, { entry, process: provider, spawnPromise })\n this.scheduleSave()\n\n // Await spawn — caller gets the URL or error\n await spawnPromise\n return entry\n }\n\n async stop(port: number): Promise<void> {\n const live = this.entries.get(port)\n if (!live) return\n\n if (live.entry.type === 'system') {\n throw new Error('Cannot stop system tunnel')\n }\n\n // Wait for spawn to finish if still starting\n if (live.spawnPromise) {\n try { await live.spawnPromise } catch { /* ignore spawn error */ }\n }\n\n if (live.process) {\n await live.process.stop()\n }\n\n this.entries.delete(port)\n this.scheduleSave()\n log.info({ port, label: live.entry.label }, 'Tunnel stopped')\n }\n\n async stopBySession(sessionId: string): Promise<TunnelEntry[]> {\n const stopped: TunnelEntry[] = []\n const toStop = this.getBySession(sessionId)\n for (const entry of toStop) {\n try {\n await this.stop(entry.port)\n stopped.push(entry)\n } catch { /* ignore */ }\n }\n return stopped\n }\n\n async stopAllUser(): Promise<void> {\n const userEntries = this.list(false)\n for (const entry of userEntries) {\n try { await this.stop(entry.port) } catch { /* ignore */ }\n }\n }\n\n async shutdown(): Promise<void> {\n for (const [, live] of this.entries) {\n if (live.spawnPromise) {\n try { await live.spawnPromise } catch { /* ignore */ }\n }\n if (live.process) {\n await live.process.stop()\n }\n }\n this.entries.clear()\n this.scheduleSave()\n }\n\n list(includeSystem = false): TunnelEntry[] {\n const entries = Array.from(this.entries.values()).map(l => l.entry)\n if (includeSystem) return entries\n return entries.filter(e => e.type === 'user')\n }\n\n get(port: number): TunnelEntry | null {\n return this.entries.get(port)?.entry ?? null\n }\n\n getBySession(sessionId: string): TunnelEntry[] {\n return this.list(false).filter(e => e.sessionId === sessionId)\n }\n\n getSystemEntry(): TunnelEntry | null {\n for (const live of this.entries.values()) {\n if (live.entry.type === 'system') return live.entry\n }\n return null\n }\n\n async restore(): Promise<void> {\n if (!fs.existsSync(REGISTRY_PATH)) return\n\n try {\n const raw = JSON.parse(fs.readFileSync(REGISTRY_PATH, 'utf-8')) as PersistedEntry[]\n log.info({ count: raw.length }, 'Restoring tunnels')\n\n // Only restore user tunnels — system tunnel is registered separately by TunnelService.start()\n const userEntries = raw.filter(e => e.type === 'user')\n for (const persisted of userEntries) {\n try {\n await this.add(persisted.port, {\n type: persisted.type,\n provider: persisted.provider,\n label: persisted.label,\n sessionId: persisted.sessionId,\n })\n } catch (err) {\n log.warn({ port: persisted.port, err: (err as Error).message }, 'Failed to restore tunnel')\n }\n }\n } catch (err) {\n log.warn({ err: (err as Error).message }, 'Failed to read tunnels.json')\n }\n }\n\n private createProvider(name: string): TunnelProvider {\n switch (name) {\n case 'cloudflare':\n return new CloudflareTunnelProvider(this.providerOptions)\n case 'ngrok':\n return new NgrokTunnelProvider(this.providerOptions)\n case 'bore':\n return new BoreTunnelProvider(this.providerOptions)\n case 'tailscale':\n return new TailscaleTunnelProvider(this.providerOptions)\n default:\n log.warn({ provider: name }, 'Unknown provider, falling back to cloudflare')\n return new CloudflareTunnelProvider(this.providerOptions)\n }\n }\n\n private scheduleSave(): void {\n if (this.saveTimeout) clearTimeout(this.saveTimeout)\n this.saveTimeout = setTimeout(() => this.save(), 2000)\n }\n\n private save(): void {\n const data: PersistedEntry[] = Array.from(this.entries.values()).map(l => ({\n port: l.entry.port,\n type: l.entry.type,\n provider: l.entry.provider,\n label: l.entry.label,\n sessionId: l.entry.sessionId,\n createdAt: l.entry.createdAt,\n }))\n\n try {\n const dir = path.dirname(REGISTRY_PATH)\n fs.mkdirSync(dir, { recursive: true })\n fs.writeFileSync(REGISTRY_PATH, JSON.stringify(data, null, 2))\n } catch (err) {\n log.error({ err: (err as Error).message }, 'Failed to save tunnels.json')\n }\n }\n\n flush(): void {\n if (this.saveTimeout) {\n clearTimeout(this.saveTimeout)\n this.saveTimeout = null\n }\n this.save()\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { createChildLogger } from '../../core/log.js'\nimport type { TunnelProvider } from '../provider.js'\nimport { ensureCloudflared } from './install-cloudflared.js'\n\nconst log = createChildLogger({ module: 'cloudflare-tunnel' })\n\nexport class CloudflareTunnelProvider implements TunnelProvider {\n private child: ChildProcess | null = null\n private publicUrl = ''\n private options: Record<string, unknown>\n\n constructor(options: Record<string, unknown> = {}) {\n this.options = options\n }\n\n async start(localPort: number): Promise<string> {\n // Auto-install cloudflared if not present\n const binaryPath = await ensureCloudflared()\n\n const args = ['tunnel', '--url', `http://localhost:${localPort}`]\n if (this.options.domain) {\n args.push('--hostname', String(this.options.domain))\n }\n\n return new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.stop()\n reject(new Error('Cloudflare tunnel timed out after 30s'))\n }, 30_000)\n\n try {\n this.child = spawn(binaryPath, args, { stdio: ['ignore', 'pipe', 'pipe'] })\n } catch {\n clearTimeout(timeout)\n reject(new Error(`Failed to start cloudflared at ${binaryPath}`))\n return\n }\n\n const urlPattern = /https:\\/\\/[a-zA-Z0-9-]+\\.trycloudflare\\.com/\n\n const onData = (data: Buffer) => {\n const line = data.toString()\n log.debug(line.trim())\n const match = line.match(urlPattern)\n if (match) {\n clearTimeout(timeout)\n this.publicUrl = match[0]\n log.info({ url: this.publicUrl }, 'Cloudflare tunnel ready')\n resolve(this.publicUrl)\n }\n }\n\n this.child.stdout?.on('data', onData)\n this.child.stderr?.on('data', onData)\n\n this.child.on('error', (err) => {\n clearTimeout(timeout)\n reject(new Error(`cloudflared failed to start: ${err.message}`))\n })\n\n this.child.on('exit', (code) => {\n if (!this.publicUrl) {\n clearTimeout(timeout)\n reject(new Error(`cloudflared exited with code ${code} before establishing tunnel`))\n }\n })\n })\n }\n\n async stop(): Promise<void> {\n if (this.child) {\n this.child.kill('SIGTERM')\n this.child = null\n log.info('Cloudflare tunnel stopped')\n }\n }\n\n getPublicUrl(): string {\n return this.publicUrl\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { createChildLogger } from '../../core/log.js'\nimport type { TunnelProvider } from '../provider.js'\n\nconst log = createChildLogger({ module: 'ngrok-tunnel' })\n\nexport class NgrokTunnelProvider implements TunnelProvider {\n private child: ChildProcess | null = null\n private publicUrl = ''\n private options: Record<string, unknown>\n\n constructor(options: Record<string, unknown> = {}) {\n this.options = options\n }\n\n async start(localPort: number): Promise<string> {\n const args = ['http', String(localPort), '--log', 'stdout', '--log-format', 'json']\n if (this.options.authtoken) {\n args.push('--authtoken', String(this.options.authtoken))\n }\n if (this.options.domain) {\n args.push('--domain', String(this.options.domain))\n }\n if (this.options.region) {\n args.push('--region', String(this.options.region))\n }\n\n return new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.stop()\n reject(new Error('ngrok tunnel timed out after 30s. Is ngrok installed?'))\n }, 30_000)\n\n try {\n this.child = spawn('ngrok', args, { stdio: ['ignore', 'pipe', 'pipe'] })\n } catch {\n clearTimeout(timeout)\n reject(new Error(\n 'Failed to start ngrok. Install it from https://ngrok.com/download'\n ))\n return\n }\n\n const urlPattern = /https:\\/\\/[a-zA-Z0-9-]+\\.ngrok(-free)?\\.app/\n\n const onData = (data: Buffer) => {\n const line = data.toString()\n log.debug(line.trim())\n const match = line.match(urlPattern)\n if (match) {\n clearTimeout(timeout)\n this.publicUrl = match[0]\n log.info({ url: this.publicUrl }, 'ngrok tunnel ready')\n resolve(this.publicUrl)\n }\n }\n\n this.child.stdout?.on('data', onData)\n this.child.stderr?.on('data', onData)\n\n this.child.on('error', (err) => {\n clearTimeout(timeout)\n reject(new Error(\n `ngrok failed to start: ${err.message}. Install it from https://ngrok.com/download`\n ))\n })\n\n this.child.on('exit', (code) => {\n if (!this.publicUrl) {\n clearTimeout(timeout)\n reject(new Error(`ngrok exited with code ${code} before establishing tunnel`))\n }\n })\n })\n }\n\n async stop(): Promise<void> {\n if (this.child) {\n this.child.kill('SIGTERM')\n this.child = null\n log.info('ngrok tunnel stopped')\n }\n }\n\n getPublicUrl(): string {\n return this.publicUrl\n }\n}\n","import { spawn, type ChildProcess } from 'node:child_process'\nimport { createChildLogger } from '../../core/log.js'\nimport type { TunnelProvider } from '../provider.js'\n\nconst log = createChildLogger({ module: 'bore-tunnel' })\n\nexport class BoreTunnelProvider implements TunnelProvider {\n private child: ChildProcess | null = null\n private publicUrl = ''\n private options: Record<string, unknown>\n\n constructor(options: Record<string, unknown> = {}) {\n this.options = options\n }\n\n async start(localPort: number): Promise<string> {\n const server = String(this.options.server || 'bore.pub')\n const args = ['local', String(localPort), '--to', server]\n if (this.options.port) {\n args.push('--port', String(this.options.port))\n }\n if (this.options.secret) {\n args.push('--secret', String(this.options.secret))\n }\n\n return new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.stop()\n reject(new Error('Bore tunnel timed out after 30s. Is bore installed?'))\n }, 30_000)\n\n try {\n this.child = spawn('bore', args, { stdio: ['ignore', 'pipe', 'pipe'] })\n } catch {\n clearTimeout(timeout)\n reject(new Error(\n 'Failed to start bore. Install it from https://github.com/ekzhang/bore'\n ))\n return\n }\n\n const urlPattern = /listening at ([^\\s]+):(\\d+)/\n\n const onData = (data: Buffer) => {\n const line = data.toString()\n log.debug(line.trim())\n const match = line.match(urlPattern)\n if (match) {\n clearTimeout(timeout)\n this.publicUrl = `http://${match[1]}:${match[2]}`\n log.info({ url: this.publicUrl }, 'Bore tunnel ready')\n resolve(this.publicUrl)\n }\n }\n\n this.child.stdout?.on('data', onData)\n this.child.stderr?.on('data', onData)\n\n this.child.on('error', (err) => {\n clearTimeout(timeout)\n reject(new Error(\n `bore failed to start: ${err.message}. Install it from https://github.com/ekzhang/bore`\n ))\n })\n\n this.child.on('exit', (code) => {\n if (!this.publicUrl) {\n clearTimeout(timeout)\n reject(new Error(`bore exited with code ${code} before establishing tunnel`))\n }\n })\n })\n }\n\n async stop(): Promise<void> {\n if (this.child) {\n this.child.kill('SIGTERM')\n this.child = null\n log.info('Bore tunnel stopped')\n }\n }\n\n getPublicUrl(): string {\n return this.publicUrl\n }\n}\n","import { spawn, execSync, type ChildProcess } from 'node:child_process'\nimport { createChildLogger } from '../../core/log.js'\nimport type { TunnelProvider } from '../provider.js'\n\nconst log = createChildLogger({ module: 'tailscale-tunnel' })\n\nexport class TailscaleTunnelProvider implements TunnelProvider {\n private child: ChildProcess | null = null\n private publicUrl = ''\n private options: Record<string, unknown>\n\n constructor(options: Record<string, unknown> = {}) {\n this.options = options\n }\n\n async start(localPort: number): Promise<string> {\n let hostname = ''\n try {\n const statusJson = execSync('tailscale status --json', { encoding: 'utf-8' })\n const status = JSON.parse(statusJson)\n hostname = String(status.Self.DNSName).replace(/\\.$/, '')\n log.debug({ hostname }, 'Resolved Tailscale hostname')\n } catch (err) {\n log.warn('Failed to resolve Tailscale hostname via status --json')\n }\n\n const args = ['funnel', String(localPort)]\n if (this.options.bg) {\n args.push('--bg')\n }\n\n return new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.stop()\n reject(new Error('Tailscale funnel timed out after 30s. Is tailscale installed?'))\n }, 30_000)\n\n try {\n this.child = spawn('tailscale', args, { stdio: ['ignore', 'pipe', 'pipe'] })\n } catch {\n clearTimeout(timeout)\n reject(new Error(\n 'Failed to start tailscale. Install it from https://tailscale.com/download'\n ))\n return\n }\n\n const urlPattern = /https:\\/\\/[^\\s]+/\n\n const onData = (data: Buffer) => {\n const line = data.toString()\n log.debug(line.trim())\n const match = line.match(urlPattern)\n if (match) {\n clearTimeout(timeout)\n this.publicUrl = match[0]\n log.info({ url: this.publicUrl }, 'Tailscale funnel ready')\n resolve(this.publicUrl)\n }\n }\n\n this.child.stdout?.on('data', onData)\n this.child.stderr?.on('data', onData)\n\n this.child.on('error', (err) => {\n clearTimeout(timeout)\n reject(new Error(\n `tailscale failed to start: ${err.message}. Install it from https://tailscale.com/download`\n ))\n })\n\n this.child.on('exit', (code) => {\n if (!this.publicUrl) {\n clearTimeout(timeout)\n if (hostname) {\n this.publicUrl = `https://${hostname}`\n log.info({ url: this.publicUrl }, 'Tailscale funnel ready (constructed from hostname)')\n resolve(this.publicUrl)\n } else {\n reject(new Error(`tailscale exited with code ${code} before establishing funnel`))\n }\n }\n })\n })\n }\n\n async stop(): Promise<void> {\n if (this.child) {\n this.child.kill('SIGTERM')\n this.child = null\n log.info('Tailscale funnel stopped')\n }\n }\n\n getPublicUrl(): string {\n return this.publicUrl\n }\n}\n","import * as path from 'node:path'\nimport { nanoid } from 'nanoid'\nimport { createChildLogger } from '../core/log.js'\n\nconst log = createChildLogger({ module: 'viewer-store' })\n\nconst MAX_CONTENT_SIZE = 1_000_000 // 1MB\n\nconst EXTENSION_LANGUAGE: Record<string, string> = {\n '.ts': 'typescript', '.tsx': 'typescript', '.js': 'javascript', '.jsx': 'javascript',\n '.py': 'python', '.rs': 'rust', '.go': 'go', '.java': 'java', '.kt': 'kotlin',\n '.rb': 'ruby', '.php': 'php', '.c': 'c', '.cpp': 'cpp', '.h': 'c', '.hpp': 'cpp',\n '.cs': 'csharp', '.swift': 'swift', '.sh': 'bash', '.zsh': 'bash', '.bash': 'bash',\n '.json': 'json', '.yaml': 'yaml', '.yml': 'yaml', '.toml': 'toml',\n '.xml': 'xml', '.html': 'html', '.css': 'css', '.scss': 'scss',\n '.sql': 'sql', '.md': 'markdown', '.dockerfile': 'dockerfile',\n '.tf': 'hcl', '.vue': 'xml', '.svelte': 'xml',\n}\n\nexport interface ViewerEntry {\n id: string\n type: 'file' | 'diff'\n filePath?: string\n content: string\n oldContent?: string\n language?: string\n sessionId: string\n workingDirectory: string\n createdAt: number\n expiresAt: number\n}\n\nexport class ViewerStore {\n private entries = new Map<string, ViewerEntry>()\n private cleanupTimer: ReturnType<typeof setInterval>\n private ttlMs: number\n\n constructor(ttlMinutes: number = 60) {\n this.ttlMs = ttlMinutes * 60 * 1000\n this.cleanupTimer = setInterval(() => this.cleanup(), 5 * 60 * 1000)\n }\n\n storeFile(sessionId: string, filePath: string, content: string, workingDirectory: string): string | null {\n if (!this.isPathAllowed(filePath, workingDirectory)) {\n log.warn({ filePath, workingDirectory }, 'Path outside workspace, rejecting')\n return null\n }\n if (content.length > MAX_CONTENT_SIZE) {\n log.debug({ filePath, size: content.length }, 'File too large for viewer')\n return null\n }\n\n const id = nanoid(12)\n const now = Date.now()\n this.entries.set(id, {\n id,\n type: 'file',\n filePath,\n content,\n language: this.detectLanguage(filePath),\n sessionId,\n workingDirectory,\n createdAt: now,\n expiresAt: now + this.ttlMs,\n })\n log.debug({ id, filePath }, 'Stored file for viewing')\n return id\n }\n\n storeDiff(sessionId: string, filePath: string, oldContent: string, newContent: string, workingDirectory: string): string | null {\n if (!this.isPathAllowed(filePath, workingDirectory)) {\n log.warn({ filePath, workingDirectory }, 'Path outside workspace, rejecting')\n return null\n }\n const combined = oldContent.length + newContent.length\n if (combined > MAX_CONTENT_SIZE) {\n log.debug({ filePath, size: combined }, 'Diff content too large for viewer')\n return null\n }\n\n const id = nanoid(12)\n const now = Date.now()\n this.entries.set(id, {\n id,\n type: 'diff',\n filePath,\n content: newContent,\n oldContent,\n language: this.detectLanguage(filePath),\n sessionId,\n workingDirectory,\n createdAt: now,\n expiresAt: now + this.ttlMs,\n })\n log.debug({ id, filePath }, 'Stored diff for viewing')\n return id\n }\n\n get(id: string): ViewerEntry | undefined {\n const entry = this.entries.get(id)\n if (!entry) return undefined\n if (Date.now() > entry.expiresAt) {\n this.entries.delete(id)\n return undefined\n }\n return entry\n }\n\n private cleanup(): void {\n const now = Date.now()\n let removed = 0\n for (const [id, entry] of this.entries) {\n if (now > entry.expiresAt) {\n this.entries.delete(id)\n removed++\n }\n }\n if (removed > 0) {\n log.debug({ removed, remaining: this.entries.size }, 'Cleaned up expired viewer entries')\n }\n }\n\n private isPathAllowed(filePath: string, workingDirectory: string): boolean {\n const resolved = path.resolve(workingDirectory, filePath)\n return resolved.startsWith(path.resolve(workingDirectory))\n }\n\n private detectLanguage(filePath: string): string | undefined {\n const ext = path.extname(filePath).toLowerCase()\n return EXTENSION_LANGUAGE[ext]\n }\n\n destroy(): void {\n clearInterval(this.cleanupTimer)\n this.entries.clear()\n }\n}\n","import { Hono } from 'hono'\nimport type { ViewerStore } from './viewer-store.js'\nimport { renderFileViewer } from './templates/file-viewer.js'\nimport { renderDiffViewer } from './templates/diff-viewer.js'\n\nfunction notFoundPage(): string {\n return `<!DOCTYPE html>\n<html><head><meta charset=\"UTF-8\"><title>Not Found - OpenACP</title>\n<style>body{background:#0d1117;color:#c9d1d9;font-family:sans-serif;display:flex;align-items:center;justify-content:center;min-height:100vh;margin:0}\n.box{text-align:center;padding:40px}.code{font-size:72px;font-weight:bold;color:#484f58}p{margin-top:16px;color:#8b949e}</style>\n</head><body><div class=\"box\"><div class=\"code\">404</div><p>This viewer link has expired or does not exist.</p></div></body></html>`\n}\n\nexport function createTunnelServer(store: ViewerStore, authToken?: string): Hono {\n const app = new Hono()\n\n // Auth middleware\n if (authToken) {\n app.use('*', async (c, next) => {\n if (c.req.path === '/health') return next()\n const bearer = c.req.header('Authorization')?.replace('Bearer ', '')\n const query = c.req.query('token')\n if (bearer !== authToken && query !== authToken) {\n return c.text('Unauthorized', 401)\n }\n return next()\n })\n }\n\n app.get('/health', (c) => c.json({ status: 'ok' }))\n\n app.get('/view/:id', (c) => {\n const entry = store.get(c.req.param('id'))\n if (!entry || entry.type !== 'file') {\n return c.html(notFoundPage(), 404)\n }\n return c.html(renderFileViewer(entry))\n })\n\n app.get('/diff/:id', (c) => {\n const entry = store.get(c.req.param('id'))\n if (!entry || entry.type !== 'diff') {\n return c.html(notFoundPage(), 404)\n }\n return c.html(renderDiffViewer(entry))\n })\n\n app.get('/api/file/:id', (c) => {\n const entry = store.get(c.req.param('id'))\n if (!entry || entry.type !== 'file') {\n return c.json({ error: 'not found' }, 404)\n }\n return c.json({ filePath: entry.filePath, content: entry.content, language: entry.language })\n })\n\n app.get('/api/diff/:id', (c) => {\n const entry = store.get(c.req.param('id'))\n if (!entry || entry.type !== 'diff') {\n return c.json({ error: 'not found' }, 404)\n }\n return c.json({ filePath: entry.filePath, oldContent: entry.oldContent, newContent: entry.content, language: entry.language })\n })\n\n return app\n}\n","import type { ViewerEntry } from '../viewer-store.js'\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\n// Map our language IDs to Monaco language IDs\nconst MONACO_LANGUAGE: Record<string, string> = {\n typescript: 'typescript', javascript: 'javascript', python: 'python',\n rust: 'rust', go: 'go', java: 'java', kotlin: 'kotlin', ruby: 'ruby',\n php: 'php', c: 'c', cpp: 'cpp', csharp: 'csharp', swift: 'swift',\n bash: 'shell', json: 'json', yaml: 'yaml', toml: 'ini', xml: 'xml',\n html: 'html', css: 'css', scss: 'scss', sql: 'sql', markdown: 'markdown',\n dockerfile: 'dockerfile', hcl: 'hcl', plaintext: 'plaintext',\n}\n\nfunction getMonacoLang(lang?: string): string {\n if (!lang) return 'plaintext'\n return MONACO_LANGUAGE[lang] || 'plaintext'\n}\n\nexport function renderFileViewer(entry: ViewerEntry): string {\n const fileName = entry.filePath || 'untitled'\n const lang = getMonacoLang(entry.language)\n // Escape </script> inside content to prevent premature tag closure\n const safeContent = JSON.stringify(entry.content).replace(/<\\//g, '<\\\\/')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(fileName)} - OpenACP</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { background: #1e1e1e; color: #d4d4d4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; }\n .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; }\n .file-info { display: flex; align-items: center; gap: 8px; font-size: 13px; min-width: 0; }\n .file-icon { font-size: 14px; flex-shrink: 0; }\n .file-path { color: #969696; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n .file-name { color: #e0e0e0; font-weight: 500; flex-shrink: 0; }\n .actions { display: flex; gap: 6px; flex-shrink: 0; }\n .btn { background: #3c3c3c; color: #d4d4d4; border: 1px solid #505050; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.15s; }\n .btn:hover { background: #505050; }\n .btn.active { background: #0e639c; border-color: #1177bb; }\n #editor-container { flex: 1; overflow: hidden; }\n .status-bar { background: #007acc; color: #fff; padding: 2px 16px; font-size: 12px; display: flex; justify-content: space-between; flex-shrink: 0; }\n </style>\n</head>\n<body>\n <div class=\"header\">\n <div class=\"file-info\">\n <span class=\"file-icon\">📄</span>\n ${formatBreadcrumb(fileName)}\n </div>\n <div class=\"actions\">\n ${lang === 'markdown' ? '<button class=\"btn\" onclick=\"togglePreview()\" id=\"btn-preview\">Preview</button>' : ''}\n <button class=\"btn\" onclick=\"toggleWordWrap()\" id=\"btn-wrap\">Wrap</button>\n <button class=\"btn\" onclick=\"toggleMinimap()\" id=\"btn-minimap\">Minimap</button>\n <button class=\"btn\" onclick=\"toggleTheme()\" id=\"btn-theme\">Light</button>\n <button class=\"btn\" onclick=\"copyCode()\">Copy</button>\n </div>\n </div>\n <div id=\"editor-container\"></div>\n <div id=\"preview-wrapper\" style=\"display:none; flex:1; overflow-y:auto;\">\n <div id=\"preview-container\" style=\"padding:24px 48px; max-width:900px; margin:0 auto; width:100%;\"></div>\n </div>\n <div class=\"status-bar\">\n <span>${escapeHtml(entry.language || 'plaintext')} | ${entry.content.split('\\n').length} lines</span>\n <span>OpenACP Viewer (read-only)</span>\n </div>\n\n ${lang === 'markdown' ? '<script src=\"https://cdn.jsdelivr.net/npm/marked@15.0.0/marked.min.js\"></script>' : ''}\n <script src=\"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js\"></script>\n <script>\n const content = ${safeContent};\n const lang = ${JSON.stringify(lang)};\n let editor;\n let isDark = true;\n let wordWrap = false;\n let minimap = true;\n\n require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } });\n require(['vs/editor/editor.main'], function () {\n editor = monaco.editor.create(document.getElementById('editor-container'), {\n value: content,\n language: lang,\n theme: 'vs-dark',\n readOnly: true,\n automaticLayout: true,\n minimap: { enabled: true },\n scrollBeyondLastLine: false,\n fontSize: 13,\n fontFamily: \"'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace\",\n lineNumbers: 'on',\n renderLineHighlight: 'all',\n wordWrap: 'off',\n padding: { top: 8 },\n });\n\n // Handle line range from URL hash: #L42 or #L42-L55\n function highlightFromHash() {\n const hash = location.hash.slice(1);\n const match = hash.match(/^L(\\\\d+)(?:-L?(\\\\d+))?$/);\n if (!match) return;\n const startLine = parseInt(match[1], 10);\n const endLine = match[2] ? parseInt(match[2], 10) : startLine;\n editor.revealLineInCenter(startLine);\n editor.setSelection(new monaco.Selection(startLine, 1, endLine + 1, 1));\n }\n highlightFromHash();\n window.addEventListener('hashchange', highlightFromHash);\n });\n\n function toggleTheme() {\n isDark = !isDark;\n monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');\n document.body.style.background = isDark ? '#1e1e1e' : '#ffffff';\n document.querySelector('.header').style.background = isDark ? '#252526' : '#f3f3f3';\n document.querySelector('.header').style.borderColor = isDark ? '#3c3c3c' : '#e0e0e0';\n document.getElementById('btn-theme').textContent = isDark ? 'Light' : 'Dark';\n }\n\n function toggleWordWrap() {\n wordWrap = !wordWrap;\n editor.updateOptions({ wordWrap: wordWrap ? 'on' : 'off' });\n document.getElementById('btn-wrap').classList.toggle('active', wordWrap);\n }\n\n function toggleMinimap() {\n minimap = !minimap;\n editor.updateOptions({ minimap: { enabled: minimap } });\n document.getElementById('btn-minimap').classList.toggle('active', !minimap);\n }\n\n function copyCode() {\n navigator.clipboard.writeText(content).then(() => {\n const btn = event.target;\n btn.textContent = 'Copied!';\n setTimeout(() => btn.textContent = 'Copy', 2000);\n });\n }\n\n let previewMode = false;\n function togglePreview() {\n previewMode = !previewMode;\n const editorEl = document.getElementById('editor-container');\n const wrapperEl = document.getElementById('preview-wrapper');\n const previewEl = document.getElementById('preview-container');\n const btn = document.getElementById('btn-preview');\n if (previewMode) {\n editorEl.style.display = 'none';\n wrapperEl.style.display = 'block';\n previewEl.innerHTML = typeof marked !== 'undefined' ? marked.parse(content) : content.replace(/\\\\n/g, '<br>');\n previewEl.style.color = isDark ? '#d4d4d4' : '#1e1e1e';\n wrapperEl.style.background = isDark ? '#1e1e1e' : '#ffffff';\n btn.classList.add('active');\n btn.textContent = 'Editor';\n } else {\n editorEl.style.display = 'block';\n wrapperEl.style.display = 'none';\n btn.classList.remove('active');\n btn.textContent = 'Preview';\n }\n }\n </script>\n <style>\n #preview-container { font-size: 15px; line-height: 1.7; }\n #preview-container h1 { font-size: 2em; margin: 0.5em 0 0.3em; border-bottom: 1px solid #3c3c3c; padding-bottom: 0.3em; }\n #preview-container h2 { font-size: 1.5em; margin: 0.5em 0 0.3em; border-bottom: 1px solid #3c3c3c; padding-bottom: 0.2em; }\n #preview-container h3 { font-size: 1.25em; margin: 0.4em 0 0.2em; }\n #preview-container p { margin: 0.5em 0; }\n #preview-container code { background: rgba(128,128,128,0.2); padding: 2px 6px; border-radius: 3px; font-size: 0.9em; }\n #preview-container pre { background: rgba(0,0,0,0.3); padding: 16px; border-radius: 6px; overflow-x: auto; margin: 0.5em 0; }\n #preview-container pre code { background: none; padding: 0; }\n #preview-container blockquote { border-left: 3px solid #505050; padding-left: 16px; margin: 0.5em 0; color: #969696; }\n #preview-container ul, #preview-container ol { padding-left: 24px; margin: 0.5em 0; }\n #preview-container table { border-collapse: collapse; margin: 0.5em 0; width: 100%; }\n #preview-container th, #preview-container td { border: 1px solid #3c3c3c; padding: 6px 12px; text-align: left; }\n #preview-container th { background: rgba(128,128,128,0.15); }\n #preview-container a { color: #3794ff; }\n #preview-container img { max-width: 100%; }\n </style>\n</body>\n</html>`\n}\n\nfunction formatBreadcrumb(filePath: string): string {\n const parts = filePath.split('/')\n if (parts.length <= 1) return `<span class=\"file-name\">${escapeHtml(filePath)}</span>`\n const dir = parts.slice(0, -1).join(' / ')\n const name = parts[parts.length - 1]\n return `<span class=\"file-path\">${escapeHtml(dir)} /</span> <span class=\"file-name\">${escapeHtml(name)}</span>`\n}\n","import { createPatch } from 'diff'\nimport type { ViewerEntry } from '../viewer-store.js'\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n}\n\nconst MONACO_LANGUAGE: Record<string, string> = {\n typescript: 'typescript', javascript: 'javascript', python: 'python',\n rust: 'rust', go: 'go', java: 'java', kotlin: 'kotlin', ruby: 'ruby',\n php: 'php', c: 'c', cpp: 'cpp', csharp: 'csharp', swift: 'swift',\n bash: 'shell', json: 'json', yaml: 'yaml', toml: 'ini', xml: 'xml',\n html: 'html', css: 'css', scss: 'scss', sql: 'sql', markdown: 'markdown',\n dockerfile: 'dockerfile', hcl: 'hcl', plaintext: 'plaintext',\n}\n\nfunction getMonacoLang(lang?: string): string {\n if (!lang) return 'plaintext'\n return MONACO_LANGUAGE[lang] || 'plaintext'\n}\n\nexport function renderDiffViewer(entry: ViewerEntry): string {\n const fileName = entry.filePath || 'untitled'\n const lang = getMonacoLang(entry.language)\n const oldContent = entry.oldContent || ''\n const newContent = entry.content\n\n // Count changes for stats\n const patch = createPatch(fileName, oldContent, newContent, 'before', 'after')\n const adds = (patch.match(/^\\+[^+]/gm) || []).length\n const dels = (patch.match(/^-[^-]/gm) || []).length\n\n // Escape </script> in content\n const safeOld = JSON.stringify(oldContent).replace(/<\\//g, '<\\\\/')\n const safeNew = JSON.stringify(newContent).replace(/<\\//g, '<\\\\/')\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${escapeHtml(fileName)} (diff) - OpenACP</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { background: #1e1e1e; color: #d4d4d4; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; flex-direction: column; height: 100vh; }\n .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; }\n .file-info { display: flex; align-items: center; gap: 8px; font-size: 13px; }\n .file-icon { font-size: 14px; }\n .file-name { color: #e0e0e0; font-weight: 500; }\n .stats { font-size: 12px; margin-left: 12px; }\n .stats .add { color: #4ec9b0; }\n .stats .del { color: #f14c4c; }\n .actions { display: flex; gap: 6px; }\n .btn { background: #3c3c3c; color: #d4d4d4; border: 1px solid #505050; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; transition: background 0.15s; }\n .btn:hover { background: #505050; }\n .btn.active { background: #0e639c; border-color: #1177bb; }\n #editor-container { flex: 1; overflow: hidden; }\n .status-bar { background: #007acc; color: #fff; padding: 2px 16px; font-size: 12px; display: flex; justify-content: space-between; flex-shrink: 0; }\n </style>\n</head>\n<body>\n <div class=\"header\">\n <div class=\"file-info\">\n <span class=\"file-icon\">📝</span>\n <span class=\"file-name\">${escapeHtml(fileName)}</span>\n <span class=\"stats\"><span class=\"add\">+${adds}</span> / <span class=\"del\">-${dels}</span></span>\n </div>\n <div class=\"actions\">\n <button class=\"btn active\" id=\"btn-side\" onclick=\"setView('side')\">Side by Side</button>\n <button class=\"btn\" id=\"btn-inline\" onclick=\"setView('inline')\">Inline</button>\n <button class=\"btn\" onclick=\"toggleTheme()\" id=\"btn-theme\">Light</button>\n </div>\n </div>\n <div id=\"editor-container\"></div>\n <div class=\"status-bar\">\n <span>${escapeHtml(entry.language || 'plaintext')} | <span class=\"add\">+${adds}</span> <span class=\"del\">-${dels}</span></span>\n <span>OpenACP Diff Viewer</span>\n </div>\n\n <script src=\"https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js\"></script>\n <script>\n const oldContent = ${safeOld};\n const newContent = ${safeNew};\n const lang = ${JSON.stringify(lang)};\n let diffEditor;\n let isDark = true;\n let renderSideBySide = true;\n\n require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } });\n require(['vs/editor/editor.main'], function () {\n const originalModel = monaco.editor.createModel(oldContent, lang);\n const modifiedModel = monaco.editor.createModel(newContent, lang);\n\n diffEditor = monaco.editor.createDiffEditor(document.getElementById('editor-container'), {\n theme: 'vs-dark',\n readOnly: true,\n automaticLayout: true,\n renderSideBySide: true,\n scrollBeyondLastLine: false,\n fontSize: 13,\n fontFamily: \"'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace\",\n padding: { top: 8 },\n enableSplitViewResizing: true,\n renderOverviewRuler: true,\n });\n\n diffEditor.setModel({ original: originalModel, modified: modifiedModel });\n });\n\n function setView(mode) {\n renderSideBySide = mode === 'side';\n diffEditor.updateOptions({ renderSideBySide });\n document.getElementById('btn-side').classList.toggle('active', renderSideBySide);\n document.getElementById('btn-inline').classList.toggle('active', !renderSideBySide);\n }\n\n function toggleTheme() {\n isDark = !isDark;\n monaco.editor.setTheme(isDark ? 'vs-dark' : 'vs');\n document.body.style.background = isDark ? '#1e1e1e' : '#ffffff';\n document.querySelector('.header').style.background = isDark ? '#252526' : '#f3f3f3';\n document.querySelector('.header').style.borderColor = isDark ? '#3c3c3c' : '#e0e0e0';\n document.getElementById('btn-theme').textContent = isDark ? 'Light' : 'Dark';\n }\n </script>\n</body>\n</html>`\n}\n"],"mappings":"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","names":["resolve","spawn","log","resolve","spawn","spawn","log","resolve","spawn","spawn","log","resolve","spawn","log","path","log","escapeHtml","MONACO_LANGUAGE","getMonacoLang","log","resolve"]}
@@ -4,7 +4,7 @@ import {
4
4
  getCurrentVersion,
5
5
  getLatestVersion,
6
6
  runUpdate
7
- } from "./chunk-KSIQZC3J.js";
7
+ } from "./chunk-EVFJW45N.js";
8
8
  export {
9
9
  checkAndPromptUpdate,
10
10
  compareVersions,
@@ -12,4 +12,4 @@ export {
12
12
  getLatestVersion,
13
13
  runUpdate
14
14
  };
15
- //# sourceMappingURL=version-VC5CPXBX.js.map
15
+ //# sourceMappingURL=version-ALWGGVKM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openacp/cli",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Self-hosted bridge for AI coding agents via ACP protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "@inquirer/prompts": "^8.3.2",
28
28
  "@zed-industries/claude-agent-acp": "^0.22.2",
29
29
  "diff": "^8.0.3",
30
+ "discord.js": "^14.25.1",
30
31
  "fastest-levenshtein": "^1.0.16",
31
32
  "grammy": "^1.41.1",
32
33
  "hono": "^4.12.8",