opencode-mobile 1.1.0 → 1.2.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 (99) hide show
  1. package/bin/audit +31 -0
  2. package/bin/qr +13 -0
  3. package/dist/index.d.ts +11 -4
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +545 -5
  6. package/dist/index.js.map +1 -1
  7. package/dist/src/cli/endpoint-tester.d.ts +19 -0
  8. package/dist/src/cli/endpoint-tester.d.ts.map +1 -0
  9. package/dist/src/cli/endpoint-tester.js +233 -0
  10. package/dist/src/cli/endpoint-tester.js.map +1 -0
  11. package/dist/src/cli/index.d.ts +20 -0
  12. package/dist/src/cli/index.d.ts.map +1 -0
  13. package/dist/src/cli/index.js +166 -0
  14. package/dist/src/cli/index.js.map +1 -0
  15. package/dist/src/cli/push-tester.d.ts +16 -0
  16. package/dist/src/cli/push-tester.d.ts.map +1 -0
  17. package/dist/src/cli/push-tester.js +293 -0
  18. package/dist/src/cli/push-tester.js.map +1 -0
  19. package/dist/src/cli/qr.d.ts +5 -0
  20. package/dist/src/cli/qr.d.ts.map +1 -0
  21. package/dist/src/cli/qr.js +127 -0
  22. package/dist/src/cli/qr.js.map +1 -0
  23. package/dist/src/cli/report.d.ts +25 -0
  24. package/dist/src/cli/report.d.ts.map +1 -0
  25. package/dist/src/cli/report.js +215 -0
  26. package/dist/src/cli/report.js.map +1 -0
  27. package/dist/src/cli/server-manager.d.ts +28 -0
  28. package/dist/src/cli/server-manager.d.ts.map +1 -0
  29. package/dist/src/cli/server-manager.js +340 -0
  30. package/dist/src/cli/server-manager.js.map +1 -0
  31. package/dist/src/cli/test-runner.d.ts +16 -0
  32. package/dist/src/cli/test-runner.d.ts.map +1 -0
  33. package/dist/src/cli/test-runner.js +201 -0
  34. package/dist/src/cli/test-runner.js.map +1 -0
  35. package/dist/src/cli/tunnel-qr.d.ts +8 -0
  36. package/dist/src/cli/tunnel-qr.d.ts.map +1 -0
  37. package/dist/src/cli/tunnel-qr.js +163 -0
  38. package/dist/src/cli/tunnel-qr.js.map +1 -0
  39. package/dist/src/cli/tunnel-tester.d.ts +22 -0
  40. package/dist/src/cli/tunnel-tester.d.ts.map +1 -0
  41. package/dist/src/cli/tunnel-tester.js +360 -0
  42. package/dist/src/cli/tunnel-tester.js.map +1 -0
  43. package/dist/src/cli/types.d.ts +112 -0
  44. package/dist/src/cli/types.d.ts.map +1 -0
  45. package/dist/src/cli/types.js +5 -0
  46. package/dist/src/cli/types.js.map +1 -0
  47. package/dist/src/push/index.d.ts +1 -0
  48. package/dist/src/push/index.d.ts.map +1 -1
  49. package/dist/src/push/index.js +1 -0
  50. package/dist/src/push/index.js.map +1 -1
  51. package/dist/src/push/notification-handler.d.ts +58 -0
  52. package/dist/src/push/notification-handler.d.ts.map +1 -0
  53. package/dist/src/push/notification-handler.js +110 -0
  54. package/dist/src/push/notification-handler.js.map +1 -0
  55. package/dist/src/tunnel/cloudflare.d.ts +32 -3
  56. package/dist/src/tunnel/cloudflare.d.ts.map +1 -1
  57. package/dist/src/tunnel/cloudflare.js +102 -32
  58. package/dist/src/tunnel/cloudflare.js.map +1 -1
  59. package/dist/src/tunnel/localtunnel.d.ts +28 -1
  60. package/dist/src/tunnel/localtunnel.d.ts.map +1 -1
  61. package/dist/src/tunnel/localtunnel.js +53 -11
  62. package/dist/src/tunnel/localtunnel.js.map +1 -1
  63. package/dist/src/tunnel/metadata.d.ts +29 -0
  64. package/dist/src/tunnel/metadata.d.ts.map +1 -0
  65. package/dist/src/tunnel/metadata.js +83 -0
  66. package/dist/src/tunnel/metadata.js.map +1 -0
  67. package/dist/src/tunnel/ngrok.d.ts +12 -0
  68. package/dist/src/tunnel/ngrok.d.ts.map +1 -1
  69. package/dist/src/tunnel/ngrok.js +72 -29
  70. package/dist/src/tunnel/ngrok.js.map +1 -1
  71. package/dist/src/tunnel/qrcode.d.ts +8 -0
  72. package/dist/src/tunnel/qrcode.d.ts.map +1 -1
  73. package/dist/src/tunnel/qrcode.js +60 -9
  74. package/dist/src/tunnel/qrcode.js.map +1 -1
  75. package/package.json +21 -3
  76. package/dist/push-notifications.d.ts +0 -4
  77. package/dist/push-notifications.d.ts.map +0 -1
  78. package/dist/push-notifications.js +0 -260
  79. package/dist/push-notifications.js.map +0 -1
  80. package/dist/reverse-proxy.d.ts +0 -9
  81. package/dist/reverse-proxy.d.ts.map +0 -1
  82. package/dist/reverse-proxy.js +0 -78
  83. package/dist/reverse-proxy.js.map +0 -1
  84. package/dist/sdk-logger.d.ts +0 -19
  85. package/dist/sdk-logger.d.ts.map +0 -1
  86. package/dist/sdk-logger.js +0 -103
  87. package/dist/sdk-logger.js.map +0 -1
  88. package/dist/src/tunnel/index.d.ts +0 -35
  89. package/dist/src/tunnel/index.d.ts.map +0 -1
  90. package/dist/src/tunnel/index.js +0 -191
  91. package/dist/src/tunnel/index.js.map +0 -1
  92. package/dist/src/utils/index.d.ts +0 -5
  93. package/dist/src/utils/index.d.ts.map +0 -1
  94. package/dist/src/utils/index.js +0 -5
  95. package/dist/src/utils/index.js.map +0 -1
  96. package/dist/src/utils/port.d.ts +0 -12
  97. package/dist/src/utils/port.d.ts.map +0 -1
  98. package/dist/src/utils/port.js +0 -41
  99. package/dist/src/utils/port.js.map +0 -1
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Session notification handler
3
+ *
4
+ * Fetches the last assistant message from a session to form push notifications.
5
+ *
6
+ * USAGE: Uncomment and integrate into main plugin when ready.
7
+ *
8
+ * Integration example:
9
+ *
10
+ * import { handleSessionNotification } from "./notification-handler";
11
+ *
12
+ * export const PushNotificationPlugin: Plugin = async (ctx) => {
13
+ * return {
14
+ * event: async ({ event }) => {
15
+ * if (event.type === "chat.message") {
16
+ * await handleSessionNotification(ctx, event);
17
+ * }
18
+ * },
19
+ * };
20
+ * };
21
+ */
22
+ /**
23
+ * Handle session notification - fetch last assistant message for context
24
+ *
25
+ * This enables push notifications that include the assistant's response content,
26
+ * allowing users to see what the AI said without opening the app.
27
+ */
28
+ export async function handleSessionNotification(ctx, event) {
29
+ // UNCOMMENT WHEN READY:
30
+ // const client = (ctx as any).client;
31
+ // if (!client) {
32
+ // logger.warn("No client available for session notification");
33
+ // return null;
34
+ // }
35
+ //
36
+ // try {
37
+ // const response = await client.Session.messages({
38
+ // path: { id: event.sessionID },
39
+ // query: { limit: 50 }
40
+ // });
41
+ //
42
+ // if (!response.data || response.data.length === 0) {
43
+ // logger.warn("No messages found in session");
44
+ // return null;
45
+ // }
46
+ //
47
+ // // Filter for assistant messages and get the last one
48
+ // const assistantMessages = response.data.filter(
49
+ // (msg: any) => msg.info.role === "assistant"
50
+ // );
51
+ //
52
+ // if (assistantMessages.length === 0) {
53
+ // logger.warn("No assistant messages found in session");
54
+ // return null;
55
+ // }
56
+ //
57
+ // const lastAssistant = assistantMessages[assistantMessages.length - 1];
58
+ // const info = lastAssistant.info as any;
59
+ //
60
+ // // Extract text content from parts
61
+ // const textContent = lastAssistant.parts
62
+ // .filter((part: any) => part.type === "text")
63
+ // .map((part: any) => part.text)
64
+ // .join("\n");
65
+ //
66
+ // const result: AssistantMessageInfo = {
67
+ // id: info.id,
68
+ // content: textContent.slice(0, 200) + (textContent.length > 200 ? "..." : ""),
69
+ // model: `${info.providerID}/${info.modelID}`,
70
+ // provider: info.providerID,
71
+ // created: new Date(info.time.created).toISOString(),
72
+ // tokens: info.tokens || { input: 0, output: 0, reasoning: 0 },
73
+ // cost: info.cost || 0,
74
+ // };
75
+ //
76
+ // logger.info("Retrieved last assistant message for notification", {
77
+ // messageID: result.id,
78
+ // model: result.model,
79
+ // contentPreview: result.content.slice(0, 50)
80
+ // });
81
+ //
82
+ // return result;
83
+ //
84
+ // } catch (error) {
85
+ // logger.error("Failed to fetch assistant message for notification", error);
86
+ // return null;
87
+ // }
88
+ return null;
89
+ }
90
+ /**
91
+ * Format assistant message for push notification
92
+ *
93
+ * Creates a notification payload from the assistant message info.
94
+ */
95
+ export function formatAssistantNotification(assistant, sessionName) {
96
+ const title = sessionName ? `Assistant (${sessionName})` : "Assistant Response";
97
+ const body = `Model: ${assistant.model}\n${assistant.content}`;
98
+ return {
99
+ title,
100
+ body: body.slice(0, 150) + (body.length > 150 ? "..." : ""),
101
+ data: {
102
+ type: "assistant_message",
103
+ messageID: assistant.id,
104
+ sessionID: assistant.id,
105
+ model: assistant.model,
106
+ },
107
+ };
108
+ }
109
+ export default handleSessionNotification;
110
+ //# sourceMappingURL=notification-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-handler.js","sourceRoot":"","sources":["../../../src/push/notification-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAqBH;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,GAAgB,EAChB,KAA8D;IAE9D,wBAAwB;IACxB,sCAAsC;IACtC,iBAAiB;IACjB,iEAAiE;IACjE,iBAAiB;IACjB,IAAI;IACJ,GAAG;IACH,QAAQ;IACR,qDAAqD;IACrD,qCAAqC;IACrC,2BAA2B;IAC3B,QAAQ;IACR,GAAG;IACH,wDAAwD;IACxD,mDAAmD;IACnD,mBAAmB;IACnB,MAAM;IACN,GAAG;IACH,0DAA0D;IAC1D,oDAAoD;IACpD,kDAAkD;IAClD,OAAO;IACP,GAAG;IACH,0CAA0C;IAC1C,6DAA6D;IAC7D,mBAAmB;IACnB,MAAM;IACN,GAAG;IACH,2EAA2E;IAC3E,4CAA4C;IAC5C,GAAG;IACH,uCAAuC;IACvC,4CAA4C;IAC5C,mDAAmD;IACnD,qCAAqC;IACrC,mBAAmB;IACnB,GAAG;IACH,2CAA2C;IAC3C,mBAAmB;IACnB,oFAAoF;IACpF,mDAAmD;IACnD,iCAAiC;IACjC,0DAA0D;IAC1D,oEAAoE;IACpE,4BAA4B;IAC5B,OAAO;IACP,GAAG;IACH,uEAAuE;IACvE,4BAA4B;IAC5B,2BAA2B;IAC3B,kDAAkD;IAClD,QAAQ;IACR,GAAG;IACH,mBAAmB;IACnB,GAAG;IACH,oBAAoB;IACpB,+EAA+E;IAC/E,iBAAiB;IACjB,IAAI;IAEJ,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,SAA+B,EAC/B,WAAoB;IAEpB,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,cAAc,WAAW,GAAG,CAAC,CAAC,CAAC,oBAAoB,CAAC;IAChF,MAAM,IAAI,GAAG,UAAU,SAAS,CAAC,KAAK,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;IAE/D,OAAO;QACL,KAAK;QACL,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,EAAE;YACJ,IAAI,EAAE,mBAAmB;YACzB,SAAS,EAAE,SAAS,CAAC,EAAE;YACvB,SAAS,EAAE,SAAS,CAAC,EAAE;YACvB,KAAK,EAAE,SAAS,CAAC,KAAK;SACvB;KACF,CAAC;AACJ,CAAC;AAED,eAAe,yBAAyB,CAAC"}
@@ -1,13 +1,26 @@
1
1
  /**
2
2
  * Cloudflare tunnel provider implementation
3
+ *
4
+ * Refactored for testability:
5
+ * - Optional spawn function for dependency injection
6
+ * - Optional fs.existsSync for testing
7
+ * - Port validation
8
+ * - Clean separation of concerns
3
9
  */
10
+ import { spawn, ChildProcess } from "child_process";
4
11
  import type { TunnelConfig, TunnelInfo } from "./types";
12
+ export type { TunnelConfig, TunnelInfo };
5
13
  /**
6
- * Find cloudflared binary in common locations
14
+ * Find cloudflared binary (extracted for testability)
7
15
  */
8
- export declare function findCloudflared(): string | null;
16
+ export declare function findCloudflared(paths?: string[], existsSync?: (path: string) => boolean): string | null;
9
17
  /**
10
- * Start a Cloudflare tunnel
18
+ * Create a cloudflare tunnel instance
19
+ * This function is testable - accepts external spawn and existsSync
20
+ */
21
+ export declare function createCloudflareTunnel(config: TunnelConfig, spawnFn?: typeof spawn, existsSyncFn?: (path: string) => boolean, onUrl?: (url: string) => void): Promise<TunnelInfo>;
22
+ /**
23
+ * Start a Cloudflare tunnel (legacy function - uses module state)
11
24
  */
12
25
  export declare function startCloudflareTunnel(config: TunnelConfig): Promise<TunnelInfo>;
13
26
  /**
@@ -22,4 +35,20 @@ export declare function isCloudflareInstalled(): Promise<boolean>;
22
35
  * Get the current Cloudflare tunnel URL
23
36
  */
24
37
  export declare function getCloudflareUrl(): string | null;
38
+ /**
39
+ * Get current process (for testing)
40
+ */
41
+ export declare function getProcess(): ChildProcess | null;
42
+ /**
43
+ * Set process (for testing)
44
+ */
45
+ export declare function setProcess(process: ChildProcess | null): void;
46
+ /**
47
+ * Set URL (for testing)
48
+ */
49
+ export declare function setUrl(url: string | null): void;
50
+ /**
51
+ * Clear state (for testing)
52
+ */
53
+ export declare function clearState(): void;
25
54
  //# sourceMappingURL=cloudflare.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/tunnel/cloudflare.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAKxD;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAa/C;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CA2DrF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE9D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD"}
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/tunnel/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AAczC;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,CAAC,EAAE,MAAM,EAAE,EAChB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,GACrC,MAAM,GAAG,IAAI,CAUf;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,OAAO,KAAK,EACtB,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,EACxC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC5B,OAAO,CAAC,UAAU,CAAC,CAgFrB;AAgBD;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAErF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAM1D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE9D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAEhD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,YAAY,GAAG,IAAI,CAEhD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAE7D;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE/C;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAGjC"}
@@ -1,22 +1,32 @@
1
1
  /**
2
2
  * Cloudflare tunnel provider implementation
3
+ *
4
+ * Refactored for testability:
5
+ * - Optional spawn function for dependency injection
6
+ * - Optional fs.existsSync for testing
7
+ * - Port validation
8
+ * - Clean separation of concerns
3
9
  */
4
10
  import { spawn } from "child_process";
5
- let cloudflareProcess = null;
6
- let cloudflareUrl = null;
11
+ // Module-level state (for backward compatibility)
12
+ let _process = null;
13
+ let _url = null;
14
+ // Default paths for cloudflared
15
+ const CLOUDFLARED_PATHS = [
16
+ "/usr/local/bin/cloudflared",
17
+ "/usr/bin/cloudflared",
18
+ `${process.env.HOME || ""}/.cloudflared/cloudflared`,
19
+ "/opt/homebrew/bin/cloudflared",
20
+ ];
7
21
  /**
8
- * Find cloudflared binary in common locations
22
+ * Find cloudflared binary (extracted for testability)
9
23
  */
10
- export function findCloudflared() {
11
- const paths = [
12
- "/usr/local/bin/cloudflared",
13
- "/usr/bin/cloudflared",
14
- `${process.env.HOME}/.cloudflared/cloudflared`,
15
- "/opt/homebrew/bin/cloudflared",
16
- ];
17
- for (const p of paths) {
24
+ export function findCloudflared(paths, existsSync) {
25
+ const searchPaths = paths || CLOUDFLARED_PATHS;
26
+ const checkExists = existsSync || ((p) => require("fs").existsSync(p));
27
+ for (const p of searchPaths) {
18
28
  try {
19
- if (require("fs").existsSync(p))
29
+ if (checkExists(p))
20
30
  return p;
21
31
  }
22
32
  catch { }
@@ -24,30 +34,45 @@ export function findCloudflared() {
24
34
  return null;
25
35
  }
26
36
  /**
27
- * Start a Cloudflare tunnel
37
+ * Create a cloudflare tunnel instance
38
+ * This function is testable - accepts external spawn and existsSync
28
39
  */
29
- export async function startCloudflareTunnel(config) {
30
- const cloudflaredPath = findCloudflared();
31
- if (!cloudflaredPath)
32
- throw new Error("cloudflared not found. Install from https://github.com/cloudflare/cloudflared");
40
+ export function createCloudflareTunnel(config, spawnFn, existsSyncFn, onUrl) {
41
+ // Validate port
42
+ if (!config.port || typeof config.port !== "number") {
43
+ return Promise.reject(new Error("Invalid port: must be a number"));
44
+ }
45
+ // Find cloudflared
46
+ const cloudflaredPath = findCloudflareD(CLOUDFLARED_PATHS, existsSyncFn);
47
+ if (!cloudflaredPath) {
48
+ return Promise.reject(new Error("cloudflared not found. Install from https://github.com/cloudflare/cloudflared"));
49
+ }
50
+ const spawnModule = spawnFn || spawn;
33
51
  return new Promise((resolve, reject) => {
34
52
  const timeout = setTimeout(() => reject(new Error("Timeout waiting for cloudflared URL (60s)")), 60000);
35
- cloudflareProcess = spawn(cloudflaredPath, [
53
+ const process = spawnModule(cloudflaredPath, [
36
54
  "tunnel",
37
55
  "--url",
38
56
  `http://127.0.0.1:${config.port}`,
39
57
  ], { stdio: ["ignore", "pipe", "pipe"] });
58
+ _process = process;
59
+ _url = null;
40
60
  const onData = (data) => {
41
61
  const line = data.toString().trim();
42
62
  const urlMatch = line.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
43
63
  if (urlMatch) {
44
64
  const url = urlMatch[0];
45
- console.log("[Cloudflared] URL:", url);
65
+ _url = url;
66
+ if (onUrl) {
67
+ onUrl(url);
68
+ }
69
+ else {
70
+ console.log("[Cloudflared] URL:", url);
71
+ }
46
72
  clearTimeout(timeout);
47
- cloudflareUrl = url;
48
73
  resolve({
49
- url: cloudflareUrl,
50
- tunnelId: cloudflareUrl.split("://")[1].split(".")[0],
74
+ url: _url,
75
+ tunnelId: _url.split("://")[1].split(".")[0],
51
76
  port: config.port,
52
77
  provider: "cloudflare",
53
78
  });
@@ -56,35 +81,55 @@ export async function startCloudflareTunnel(config) {
56
81
  console.log("[Cloudflared]", line);
57
82
  }
58
83
  };
59
- cloudflareProcess.stdout?.on("data", onData);
60
- cloudflareProcess.stderr?.on("data", onData);
61
- cloudflareProcess.on("error", (err) => {
84
+ process.stdout?.on("data", onData);
85
+ process.stderr?.on("data", onData);
86
+ process.on("error", (err) => {
62
87
  clearTimeout(timeout);
88
+ _process = null;
63
89
  reject(err);
64
90
  });
65
- cloudflareProcess.on("exit", (code) => {
91
+ process.on("exit", (code) => {
66
92
  // If no URL was captured, treat as failure even on clean exit
67
- if (!cloudflareUrl) {
93
+ if (!_url) {
68
94
  clearTimeout(timeout);
95
+ _process = null;
69
96
  reject(new Error("cloudflared exited without providing a tunnel URL"));
70
97
  return;
71
98
  }
72
99
  if (code !== 0 && code !== null) {
73
100
  clearTimeout(timeout);
101
+ _process = null;
74
102
  reject(new Error(`cloudflared exited with code ${code}`));
75
103
  }
76
104
  });
77
105
  });
78
106
  }
107
+ function findCloudflareD(paths, existsSync) {
108
+ const checkExists = existsSync || ((p) => require("fs").existsSync(p));
109
+ for (const p of paths) {
110
+ try {
111
+ if (checkExists(p))
112
+ return p;
113
+ }
114
+ catch { }
115
+ }
116
+ return null;
117
+ }
118
+ /**
119
+ * Start a Cloudflare tunnel (legacy function - uses module state)
120
+ */
121
+ export async function startCloudflareTunnel(config) {
122
+ return createCloudflareTunnel(config);
123
+ }
79
124
  /**
80
125
  * Stop the Cloudflare tunnel
81
126
  */
82
127
  export async function stopCloudflareTunnel() {
83
- if (cloudflareProcess) {
84
- cloudflareProcess.kill("SIGTERM");
85
- cloudflareProcess = null;
128
+ if (_process) {
129
+ _process.kill("SIGTERM");
130
+ _process = null;
86
131
  }
87
- cloudflareUrl = null;
132
+ _url = null;
88
133
  }
89
134
  /**
90
135
  * Check if cloudflared is installed
@@ -96,6 +141,31 @@ export async function isCloudflareInstalled() {
96
141
  * Get the current Cloudflare tunnel URL
97
142
  */
98
143
  export function getCloudflareUrl() {
99
- return cloudflareUrl;
144
+ return _url;
145
+ }
146
+ /**
147
+ * Get current process (for testing)
148
+ */
149
+ export function getProcess() {
150
+ return _process;
151
+ }
152
+ /**
153
+ * Set process (for testing)
154
+ */
155
+ export function setProcess(process) {
156
+ _process = process;
157
+ }
158
+ /**
159
+ * Set URL (for testing)
160
+ */
161
+ export function setUrl(url) {
162
+ _url = url;
163
+ }
164
+ /**
165
+ * Clear state (for testing)
166
+ */
167
+ export function clearState() {
168
+ _process = null;
169
+ _url = null;
100
170
  }
101
171
  //# sourceMappingURL=cloudflare.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../src/tunnel/cloudflare.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../src/tunnel/cloudflare.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,EAAgB,MAAM,eAAe,CAAC;AAMpD,kDAAkD;AAClD,IAAI,QAAQ,GAAwB,IAAI,CAAC;AACzC,IAAI,IAAI,GAAkB,IAAI,CAAC;AAE/B,gCAAgC;AAChC,MAAM,iBAAiB,GAAG;IACxB,4BAA4B;IAC5B,sBAAsB;IACtB,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,2BAA2B;IACpD,+BAA+B;CAChC,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,KAAgB,EAChB,UAAsC;IAEtC,MAAM,WAAW,GAAG,KAAK,IAAI,iBAAiB,CAAC;IAC/C,MAAM,WAAW,GAAG,UAAU,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAE/E,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,IAAI,WAAW,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,MAAoB,EACpB,OAAsB,EACtB,YAAwC,EACxC,KAA6B;IAE7B,gBAAgB;IAChB,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,mBAAmB;IACnB,MAAM,eAAe,GAAG,eAAe,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;IACzE,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,KAAK,CAAC,+EAA+E,CAAC,CAC3F,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,OAAO,IAAI,KAAK,CAAC;IAErC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,UAAU,CACxB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC,EACpE,KAAK,CACN,CAAC;QAEF,MAAM,OAAO,GAAG,WAAW,CAAC,eAAe,EAAE;YAC3C,QAAQ;YACR,OAAO;YACP,oBAAoB,MAAM,CAAC,IAAI,EAAE;SAClC,EAAE,EAAE,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAE1C,QAAQ,GAAG,OAAO,CAAC;QACnB,IAAI,GAAG,IAAI,CAAC;QAEZ,MAAM,MAAM,GAAG,CAAC,IAAY,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;YAEpC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC;YAE3E,IAAI,QAAQ,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACxB,IAAI,GAAG,GAAG,CAAC;gBAEX,IAAI,KAAK,EAAE,CAAC;oBACV,KAAK,CAAC,GAAG,CAAC,CAAC;gBACb,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC;gBACzC,CAAC;gBAED,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,OAAO,CAAC;oBACN,GAAG,EAAE,IAAI;oBACT,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBAC5C,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,QAAQ,EAAE,YAAY;iBACvB,CAAC,CAAC;YACL,CAAC;iBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1D,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC;QAEF,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACnC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;YACjC,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,QAAQ,GAAG,IAAI,CAAC;YAChB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CAAC,CAAC;QACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAmB,EAAE,EAAE;YACzC,8DAA8D;YAC9D,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC,CAAC;gBACvE,OAAO;YACT,CAAC;YACD,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAChC,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,QAAQ,GAAG,IAAI,CAAC;gBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC5D,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,eAAe,CACtB,KAAe,EACf,UAAsC;IAEtC,MAAM,WAAW,GAAG,UAAU,IAAI,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IAE/E,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,IAAI,WAAW,CAAC,CAAC,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,MAAoB;IAC9D,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzB,QAAQ,GAAG,IAAI,CAAC;IAClB,CAAC;IACD,IAAI,GAAG,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,OAAO,eAAe,EAAE,KAAK,IAAI,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,OAA4B;IACrD,QAAQ,GAAG,OAAO,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM,CAAC,GAAkB;IACvC,IAAI,GAAG,GAAG,CAAC;AACb,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,QAAQ,GAAG,IAAI,CAAC;IAChB,IAAI,GAAG,IAAI,CAAC;AACd,CAAC"}
@@ -1,9 +1,24 @@
1
1
  /**
2
2
  * Localtunnel tunnel provider implementation
3
+ *
4
+ * Refactored for testability:
5
+ * - Optional instance parameter for dependency injection
6
+ * - Port validation
7
+ * - Clean separation of concerns
3
8
  */
9
+ import localtunnel from "localtunnel";
4
10
  import type { TunnelConfig, TunnelInfo } from "./types";
11
+ export type { TunnelConfig, TunnelInfo };
5
12
  /**
6
- * Start a localtunnel
13
+ * Create a localtunnel instance
14
+ * This function is testable - accepts external localtunnel for mocking
15
+ */
16
+ export declare function createLocaltunnel(config: TunnelConfig, options?: {
17
+ localtunnelModule?: typeof localtunnel;
18
+ onUrl?: (url: string) => void;
19
+ }): Promise<TunnelInfo>;
20
+ /**
21
+ * Start a localtunnel (legacy function - uses module state)
7
22
  */
8
23
  export declare function startLocaltunnel(config: TunnelConfig): Promise<TunnelInfo>;
9
24
  /**
@@ -14,4 +29,16 @@ export declare function stopLocaltunnel(): Promise<void>;
14
29
  * Get the current localtunnel URL
15
30
  */
16
31
  export declare function getLocaltunnelUrl(): string | null;
32
+ /**
33
+ * Get current instance (for testing)
34
+ */
35
+ export declare function getInstance(): any;
36
+ /**
37
+ * Set instance (for testing)
38
+ */
39
+ export declare function setInstance(instance: any): void;
40
+ /**
41
+ * Clear instance (for testing)
42
+ */
43
+ export declare function clearInstance(): void;
17
44
  //# sourceMappingURL=localtunnel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"localtunnel.d.ts","sourceRoot":"","sources":["../../../src/tunnel/localtunnel.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIxD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BhF;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAKrD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD"}
1
+ {"version":3,"file":"localtunnel.d.ts","sourceRoot":"","sources":["../../../src/tunnel/localtunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,WAAW,MAAM,aAAa,CAAC;AACtC,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGxD,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AAMzC;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,OAAO,GAAE;IACP,iBAAiB,CAAC,EAAE,OAAO,WAAW,CAAC;IACvC,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1B,GACL,OAAO,CAAC,UAAU,CAAC,CA6CrB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC,CAEhF;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAKrD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAEjD;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,GAAG,CAEjC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI,CAE/C;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC"}
@@ -1,25 +1,43 @@
1
1
  /**
2
2
  * Localtunnel tunnel provider implementation
3
+ *
4
+ * Refactored for testability:
5
+ * - Optional instance parameter for dependency injection
6
+ * - Port validation
7
+ * - Clean separation of concerns
3
8
  */
4
9
  import localtunnel from "localtunnel";
5
- let localtunnelInstance = null;
10
+ // Module-level state (for backward compatibility)
11
+ // Use the factory functions for testable code
12
+ let _instance = null;
6
13
  /**
7
- * Start a localtunnel
14
+ * Create a localtunnel instance
15
+ * This function is testable - accepts external localtunnel for mocking
8
16
  */
9
- export async function startLocaltunnel(config) {
17
+ export function createLocaltunnel(config, options = {}) {
18
+ const { localtunnelModule = localtunnel, onUrl } = options;
19
+ // Validate port
20
+ if (!config.port || typeof config.port !== "number") {
21
+ return Promise.reject(new Error("Invalid port: must be a number"));
22
+ }
10
23
  return new Promise((resolve, reject) => {
11
24
  const timeout = setTimeout(() => reject(new Error("Timeout waiting for localtunnel URL (30s)")), 30000);
12
- localtunnel({ port: config.port, subdomain: config.subdomain }, (err, tunnel) => {
25
+ localtunnelModule({ port: config.port, subdomain: config.subdomain }, (err, tunnel) => {
13
26
  if (err) {
14
27
  clearTimeout(timeout);
15
28
  reject(new Error(`Localtunnel failed: ${err.message}`));
16
29
  return;
17
30
  }
18
31
  clearTimeout(timeout);
19
- localtunnelInstance = tunnel;
20
- console.log("[Tunnel] URL:", tunnel.url);
32
+ _instance = tunnel;
33
+ if (onUrl) {
34
+ onUrl(tunnel.url);
35
+ }
36
+ else {
37
+ console.log("[Tunnel] URL:", tunnel.url);
38
+ }
21
39
  tunnel.on("close", () => {
22
- localtunnelInstance = null;
40
+ _instance = null;
23
41
  });
24
42
  resolve({
25
43
  url: tunnel.url,
@@ -30,19 +48,43 @@ export async function startLocaltunnel(config) {
30
48
  });
31
49
  });
32
50
  }
51
+ /**
52
+ * Start a localtunnel (legacy function - uses module state)
53
+ */
54
+ export async function startLocaltunnel(config) {
55
+ return createLocaltunnel(config);
56
+ }
33
57
  /**
34
58
  * Stop the localtunnel
35
59
  */
36
60
  export async function stopLocaltunnel() {
37
- if (localtunnelInstance) {
38
- localtunnelInstance.close();
39
- localtunnelInstance = null;
61
+ if (_instance) {
62
+ _instance.close();
63
+ _instance = null;
40
64
  }
41
65
  }
42
66
  /**
43
67
  * Get the current localtunnel URL
44
68
  */
45
69
  export function getLocaltunnelUrl() {
46
- return localtunnelInstance?.url || null;
70
+ return _instance?.url || null;
71
+ }
72
+ /**
73
+ * Get current instance (for testing)
74
+ */
75
+ export function getInstance() {
76
+ return _instance;
77
+ }
78
+ /**
79
+ * Set instance (for testing)
80
+ */
81
+ export function setInstance(instance) {
82
+ _instance = instance;
83
+ }
84
+ /**
85
+ * Clear instance (for testing)
86
+ */
87
+ export function clearInstance() {
88
+ _instance = null;
47
89
  }
48
90
  //# sourceMappingURL=localtunnel.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"localtunnel.js","sourceRoot":"","sources":["../../../src/tunnel/localtunnel.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,WAAW,MAAM,aAAa,CAAC;AAGtC,IAAI,mBAAmB,GAAQ,IAAI,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAoB;IACzD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,UAAU,CACxB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC,EACpE,KAAK,CACN,CAAC;QAEF,WAAW,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,GAAQ,EAAE,MAAW,EAAE,EAAE;YACxF,IAAI,GAAG,EAAE,CAAC;gBACR,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,mBAAmB,GAAG,MAAM,CAAC;YAE7B,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAEzC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACtB,mBAAmB,GAAG,IAAI,CAAC;YAC7B,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC;gBACN,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAClD,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,QAAQ,EAAE,aAAa;aACxB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,mBAAmB,EAAE,CAAC;QACxB,mBAAmB,CAAC,KAAK,EAAE,CAAC;QAC5B,mBAAmB,GAAG,IAAI,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,mBAAmB,EAAE,GAAG,IAAI,IAAI,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"localtunnel.js","sourceRoot":"","sources":["../../../src/tunnel/localtunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,WAAW,MAAM,aAAa,CAAC;AAMtC,kDAAkD;AAClD,8CAA8C;AAC9C,IAAI,SAAS,GAAQ,IAAI,CAAC;AAE1B;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAoB,EACpB,UAGI,EAAE;IAEN,MAAM,EAAE,iBAAiB,GAAG,WAAW,EAAE,KAAK,EAAE,GAAG,OAAO,CAAC;IAE3D,gBAAgB;IAChB,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpD,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC,CAAC;IACrE,CAAC;IAED,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,OAAO,GAAG,UAAU,CACxB,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC,EACpE,KAAK,CACN,CAAC;QAEF,iBAAiB,CACf,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,EAClD,CAAC,GAAQ,EAAE,MAAW,EAAE,EAAE;YACxB,IAAI,GAAG,EAAE,CAAC;gBACR,YAAY,CAAC,OAAO,CAAC,CAAC;gBACtB,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YAED,YAAY,CAAC,OAAO,CAAC,CAAC;YACtB,SAAS,GAAG,MAAM,CAAC;YAEnB,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3C,CAAC;YAED,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACtB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC;gBACN,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAClD,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,QAAQ,EAAE,aAAa;aACxB,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,MAAoB;IACzD,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,SAAS,EAAE,CAAC;QACd,SAAS,CAAC,KAAK,EAAE,CAAC;QAClB,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,SAAS,EAAE,GAAG,IAAI,IAAI,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,QAAa;IACvC,SAAS,GAAG,QAAQ,CAAC;AACvB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,SAAS,GAAG,IAAI,CAAC;AACnB,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Tunnel metadata storage for .config/opencode/tunnel.json
3
+ */
4
+ export interface TunnelMetadata {
5
+ url: string | null;
6
+ tunnelId: string | null;
7
+ provider: string | null;
8
+ port: number | null;
9
+ targetPort: number | null;
10
+ startedAt: string | null;
11
+ lastUpdated: string | null;
12
+ }
13
+ /**
14
+ * Load tunnel metadata from disk
15
+ */
16
+ export declare function loadTunnelMetadata(): TunnelMetadata;
17
+ /**
18
+ * Save tunnel metadata to disk
19
+ */
20
+ export declare function saveTunnelMetadata(metadata: TunnelMetadata): boolean;
21
+ /**
22
+ * Update tunnel metadata when a tunnel starts
23
+ */
24
+ export declare function updateTunnelMetadata(url: string, tunnelId: string, provider: string, port: number, targetPort: number): void;
25
+ /**
26
+ * Clear tunnel metadata when tunnel stops
27
+ */
28
+ export declare function clearTunnelMetadata(): void;
29
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../../src/tunnel/metadata.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,cAAc,CAkBnD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAapE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,IAAI,CAaN;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAa1C"}