@vibelet/cli 0.1.34 → 0.1.36

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 (323) hide show
  1. package/app.json +5 -0
  2. package/dist/advertised-hosts.d.ts +34 -0
  3. package/dist/advertised-hosts.d.ts.map +1 -0
  4. package/dist/advertised-hosts.js +176 -0
  5. package/dist/advertised-hosts.js.map +1 -0
  6. package/dist/advertised-hosts.test.d.ts +2 -0
  7. package/dist/advertised-hosts.test.d.ts.map +1 -0
  8. package/dist/advertised-hosts.test.js +96 -0
  9. package/dist/advertised-hosts.test.js.map +1 -0
  10. package/dist/audit.d.ts +30 -0
  11. package/dist/audit.d.ts.map +1 -0
  12. package/dist/audit.js +73 -0
  13. package/dist/audit.js.map +1 -0
  14. package/dist/audit.test.d.ts +2 -0
  15. package/dist/audit.test.d.ts.map +1 -0
  16. package/dist/audit.test.js +33 -0
  17. package/dist/audit.test.js.map +1 -0
  18. package/dist/auth.d.ts +6 -0
  19. package/dist/auth.d.ts.map +1 -0
  20. package/dist/auth.js +27 -0
  21. package/dist/auth.js.map +1 -0
  22. package/dist/claude-hooks.d.ts +58 -0
  23. package/dist/claude-hooks.d.ts.map +1 -0
  24. package/dist/claude-hooks.js +129 -0
  25. package/dist/claude-hooks.js.map +1 -0
  26. package/dist/cli-version.d.ts +3 -0
  27. package/dist/cli-version.d.ts.map +1 -0
  28. package/dist/cli-version.js +35 -0
  29. package/dist/cli-version.js.map +1 -0
  30. package/dist/cli-version.test.d.ts +2 -0
  31. package/dist/cli-version.test.d.ts.map +1 -0
  32. package/dist/cli-version.test.js +38 -0
  33. package/dist/cli-version.test.js.map +1 -0
  34. package/dist/config.d.ts +30 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +327 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/config.test.d.ts +2 -0
  39. package/dist/config.test.d.ts.map +1 -0
  40. package/dist/config.test.js +184 -0
  41. package/dist/config.test.js.map +1 -0
  42. package/dist/dev-auth.test.d.ts +2 -0
  43. package/dist/dev-auth.test.d.ts.map +1 -0
  44. package/dist/dev-auth.test.js +154 -0
  45. package/dist/dev-auth.test.js.map +1 -0
  46. package/dist/dev-script.test.d.ts +2 -0
  47. package/dist/dev-script.test.d.ts.map +1 -0
  48. package/dist/dev-script.test.js +412 -0
  49. package/dist/dev-script.test.js.map +1 -0
  50. package/dist/drivers/claude.d.ts +34 -0
  51. package/dist/drivers/claude.d.ts.map +1 -0
  52. package/dist/drivers/claude.js +413 -0
  53. package/dist/drivers/claude.js.map +1 -0
  54. package/dist/drivers/claude.test.d.ts +2 -0
  55. package/dist/drivers/claude.test.d.ts.map +1 -0
  56. package/dist/drivers/claude.test.js +951 -0
  57. package/dist/drivers/claude.test.js.map +1 -0
  58. package/dist/drivers/codex.d.ts +38 -0
  59. package/dist/drivers/codex.d.ts.map +1 -0
  60. package/dist/drivers/codex.js +771 -0
  61. package/dist/drivers/codex.js.map +1 -0
  62. package/dist/drivers/codex.test.d.ts +2 -0
  63. package/dist/drivers/codex.test.d.ts.map +1 -0
  64. package/dist/drivers/codex.test.js +939 -0
  65. package/dist/drivers/codex.test.js.map +1 -0
  66. package/dist/drivers/types.d.ts +14 -0
  67. package/dist/drivers/types.d.ts.map +1 -0
  68. package/dist/drivers/types.js +2 -0
  69. package/dist/drivers/types.js.map +1 -0
  70. package/dist/e2e.test.d.ts +2 -0
  71. package/dist/e2e.test.d.ts.map +1 -0
  72. package/dist/e2e.test.js +111 -0
  73. package/dist/e2e.test.js.map +1 -0
  74. package/dist/identity.d.ts +10 -0
  75. package/dist/identity.d.ts.map +1 -0
  76. package/dist/identity.js +66 -0
  77. package/dist/identity.js.map +1 -0
  78. package/dist/identity.test.d.ts +2 -0
  79. package/dist/identity.test.d.ts.map +1 -0
  80. package/dist/identity.test.js +25 -0
  81. package/dist/identity.test.js.map +1 -0
  82. package/dist/index-entry.test.d.ts +2 -0
  83. package/dist/index-entry.test.d.ts.map +1 -0
  84. package/dist/index-entry.test.js +272 -0
  85. package/dist/index-entry.test.js.map +1 -0
  86. package/dist/index.d.ts +2 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +707 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/logger.d.ts +31 -0
  91. package/dist/logger.d.ts.map +1 -0
  92. package/dist/logger.js +75 -0
  93. package/dist/logger.js.map +1 -0
  94. package/dist/metrics.d.ts +52 -0
  95. package/dist/metrics.d.ts.map +1 -0
  96. package/dist/metrics.js +89 -0
  97. package/dist/metrics.js.map +1 -0
  98. package/dist/pairing-store.d.ts +29 -0
  99. package/dist/pairing-store.d.ts.map +1 -0
  100. package/dist/pairing-store.js +131 -0
  101. package/dist/pairing-store.js.map +1 -0
  102. package/dist/pairing-store.test.d.ts +2 -0
  103. package/dist/pairing-store.test.d.ts.map +1 -0
  104. package/dist/pairing-store.test.js +47 -0
  105. package/dist/pairing-store.test.js.map +1 -0
  106. package/dist/paths.d.ts +16 -0
  107. package/dist/paths.d.ts.map +1 -0
  108. package/dist/paths.js +18 -0
  109. package/dist/paths.js.map +1 -0
  110. package/dist/perf-compare.d.ts +13 -0
  111. package/dist/perf-compare.d.ts.map +1 -0
  112. package/dist/perf-compare.js +125 -0
  113. package/dist/perf-compare.js.map +1 -0
  114. package/dist/port-conflict.d.ts +9 -0
  115. package/dist/port-conflict.d.ts.map +1 -0
  116. package/dist/port-conflict.js +33 -0
  117. package/dist/port-conflict.js.map +1 -0
  118. package/dist/port-conflict.test.d.ts +2 -0
  119. package/dist/port-conflict.test.d.ts.map +1 -0
  120. package/dist/port-conflict.test.js +38 -0
  121. package/dist/port-conflict.test.js.map +1 -0
  122. package/dist/process-scanner.d.ts +43 -0
  123. package/dist/process-scanner.d.ts.map +1 -0
  124. package/dist/process-scanner.js +453 -0
  125. package/dist/process-scanner.js.map +1 -0
  126. package/dist/process-scanner.perf.test.d.ts +2 -0
  127. package/dist/process-scanner.perf.test.d.ts.map +1 -0
  128. package/dist/process-scanner.perf.test.js +186 -0
  129. package/dist/process-scanner.perf.test.js.map +1 -0
  130. package/dist/process-scanner.test.d.ts +2 -0
  131. package/dist/process-scanner.test.d.ts.map +1 -0
  132. package/dist/process-scanner.test.js +399 -0
  133. package/dist/process-scanner.test.js.map +1 -0
  134. package/dist/push-protocol.d.ts +15 -0
  135. package/dist/push-protocol.d.ts.map +1 -0
  136. package/dist/push-protocol.js +23 -0
  137. package/dist/push-protocol.js.map +1 -0
  138. package/dist/push-protocol.test.d.ts +2 -0
  139. package/dist/push-protocol.test.d.ts.map +1 -0
  140. package/dist/push-protocol.test.js +57 -0
  141. package/dist/push-protocol.test.js.map +1 -0
  142. package/dist/push-store.d.ts +22 -0
  143. package/dist/push-store.d.ts.map +1 -0
  144. package/dist/push-store.js +103 -0
  145. package/dist/push-store.js.map +1 -0
  146. package/dist/push-store.test.d.ts +2 -0
  147. package/dist/push-store.test.d.ts.map +1 -0
  148. package/dist/push-store.test.js +79 -0
  149. package/dist/push-store.test.js.map +1 -0
  150. package/dist/push.d.ts +65 -0
  151. package/dist/push.d.ts.map +1 -0
  152. package/dist/push.js +202 -0
  153. package/dist/push.js.map +1 -0
  154. package/dist/push.test.d.ts +2 -0
  155. package/dist/push.test.d.ts.map +1 -0
  156. package/dist/push.test.js +199 -0
  157. package/dist/push.test.js.map +1 -0
  158. package/dist/safe-stdio.d.ts +3 -0
  159. package/dist/safe-stdio.d.ts.map +1 -0
  160. package/dist/safe-stdio.js +46 -0
  161. package/dist/safe-stdio.js.map +1 -0
  162. package/dist/scanner.d.ts +30 -0
  163. package/dist/scanner.d.ts.map +1 -0
  164. package/dist/scanner.js +859 -0
  165. package/dist/scanner.js.map +1 -0
  166. package/dist/scanner.perf.test.d.ts +2 -0
  167. package/dist/scanner.perf.test.d.ts.map +1 -0
  168. package/dist/scanner.perf.test.js +320 -0
  169. package/dist/scanner.perf.test.js.map +1 -0
  170. package/dist/scanner.test.d.ts +2 -0
  171. package/dist/scanner.test.d.ts.map +1 -0
  172. package/dist/scanner.test.js +948 -0
  173. package/dist/scanner.test.js.map +1 -0
  174. package/dist/session-inventory.d.ts +63 -0
  175. package/dist/session-inventory.d.ts.map +1 -0
  176. package/dist/session-inventory.js +525 -0
  177. package/dist/session-inventory.js.map +1 -0
  178. package/dist/session-inventory.perf.test.d.ts +2 -0
  179. package/dist/session-inventory.perf.test.d.ts.map +1 -0
  180. package/dist/session-inventory.perf.test.js +220 -0
  181. package/dist/session-inventory.perf.test.js.map +1 -0
  182. package/dist/session-inventory.test.d.ts +2 -0
  183. package/dist/session-inventory.test.d.ts.map +1 -0
  184. package/dist/session-inventory.test.js +712 -0
  185. package/dist/session-inventory.test.js.map +1 -0
  186. package/dist/session-manager.d.ts +75 -0
  187. package/dist/session-manager.d.ts.map +1 -0
  188. package/dist/session-manager.js +1515 -0
  189. package/dist/session-manager.js.map +1 -0
  190. package/dist/session-manager.test.d.ts +2 -0
  191. package/dist/session-manager.test.d.ts.map +1 -0
  192. package/dist/session-manager.test.js +2861 -0
  193. package/dist/session-manager.test.js.map +1 -0
  194. package/dist/session-store.d.ts +42 -0
  195. package/dist/session-store.d.ts.map +1 -0
  196. package/dist/session-store.js +163 -0
  197. package/dist/session-store.js.map +1 -0
  198. package/dist/session-store.test.d.ts +2 -0
  199. package/dist/session-store.test.d.ts.map +1 -0
  200. package/dist/session-store.test.js +236 -0
  201. package/dist/session-store.test.js.map +1 -0
  202. package/dist/session-title.d.ts +6 -0
  203. package/dist/session-title.d.ts.map +1 -0
  204. package/dist/session-title.js +105 -0
  205. package/dist/session-title.js.map +1 -0
  206. package/dist/session-title.perf.test.d.ts +2 -0
  207. package/dist/session-title.perf.test.d.ts.map +1 -0
  208. package/dist/session-title.perf.test.js +99 -0
  209. package/dist/session-title.perf.test.js.map +1 -0
  210. package/dist/session-title.test.d.ts +2 -0
  211. package/dist/session-title.test.d.ts.map +1 -0
  212. package/dist/session-title.test.js +199 -0
  213. package/dist/session-title.test.js.map +1 -0
  214. package/dist/shutdown-endpoint.test.d.ts +2 -0
  215. package/dist/shutdown-endpoint.test.d.ts.map +1 -0
  216. package/dist/shutdown-endpoint.test.js +93 -0
  217. package/dist/shutdown-endpoint.test.js.map +1 -0
  218. package/dist/storage-housekeeping.d.ts +28 -0
  219. package/dist/storage-housekeeping.d.ts.map +1 -0
  220. package/dist/storage-housekeeping.js +76 -0
  221. package/dist/storage-housekeeping.js.map +1 -0
  222. package/dist/storage-housekeeping.test.d.ts +2 -0
  223. package/dist/storage-housekeeping.test.d.ts.map +1 -0
  224. package/dist/storage-housekeeping.test.js +65 -0
  225. package/dist/storage-housekeeping.test.js.map +1 -0
  226. package/dist/test-daemon-harness.d.ts +31 -0
  227. package/dist/test-daemon-harness.d.ts.map +1 -0
  228. package/dist/test-daemon-harness.js +337 -0
  229. package/dist/test-daemon-harness.js.map +1 -0
  230. package/dist/token-auth.test.d.ts +2 -0
  231. package/dist/token-auth.test.d.ts.map +1 -0
  232. package/dist/token-auth.test.js +52 -0
  233. package/dist/token-auth.test.js.map +1 -0
  234. package/dist/utils.d.ts +4 -0
  235. package/dist/utils.d.ts.map +1 -0
  236. package/dist/utils.js +40 -0
  237. package/dist/utils.js.map +1 -0
  238. package/dist/utils.test.d.ts +2 -0
  239. package/dist/utils.test.d.ts.map +1 -0
  240. package/dist/utils.test.js +54 -0
  241. package/dist/utils.test.js.map +1 -0
  242. package/dist/ws-data.d.ts +4 -0
  243. package/dist/ws-data.d.ts.map +1 -0
  244. package/dist/ws-data.js +20 -0
  245. package/dist/ws-data.js.map +1 -0
  246. package/dist/ws-data.test.d.ts +2 -0
  247. package/dist/ws-data.test.d.ts.map +1 -0
  248. package/dist/ws-data.test.js +17 -0
  249. package/dist/ws-data.test.js.map +1 -0
  250. package/package.json +24 -27
  251. package/perf-reporter.mjs +138 -0
  252. package/scripts/build-release.mjs +41 -0
  253. package/scripts/dev.mjs +537 -0
  254. package/src/advertised-hosts.test.ts +125 -0
  255. package/src/advertised-hosts.ts +225 -0
  256. package/src/audit.test.ts +38 -0
  257. package/src/audit.ts +117 -0
  258. package/src/auth.ts +31 -0
  259. package/src/claude-hooks.ts +195 -0
  260. package/src/cli-version.test.ts +36 -0
  261. package/src/cli-version.ts +46 -0
  262. package/src/config.test.ts +254 -0
  263. package/src/config.ts +324 -0
  264. package/src/dev-auth.test.ts +183 -0
  265. package/src/dev-script.test.ts +511 -0
  266. package/src/drivers/claude.test.ts +1186 -0
  267. package/src/drivers/claude.ts +443 -0
  268. package/src/drivers/codex.test.ts +1096 -0
  269. package/src/drivers/codex.ts +879 -0
  270. package/src/drivers/types.ts +15 -0
  271. package/src/e2e.test.ts +139 -0
  272. package/src/identity.test.ts +26 -0
  273. package/src/identity.ts +82 -0
  274. package/src/index-entry.test.ts +336 -0
  275. package/src/index.ts +781 -0
  276. package/src/logger.ts +112 -0
  277. package/src/metrics.ts +117 -0
  278. package/src/pairing-store.test.ts +53 -0
  279. package/src/pairing-store.ts +154 -0
  280. package/src/paths.ts +19 -0
  281. package/src/perf-compare.ts +164 -0
  282. package/src/port-conflict.test.ts +45 -0
  283. package/src/port-conflict.ts +44 -0
  284. package/src/process-scanner.perf.test.ts +222 -0
  285. package/src/process-scanner.test.ts +575 -0
  286. package/src/process-scanner.ts +514 -0
  287. package/src/push-protocol.test.ts +74 -0
  288. package/src/push-protocol.ts +36 -0
  289. package/src/push-store.test.ts +89 -0
  290. package/src/push-store.ts +126 -0
  291. package/src/push.test.ts +234 -0
  292. package/src/push.ts +318 -0
  293. package/src/safe-stdio.ts +51 -0
  294. package/src/scanner.perf.test.ts +359 -0
  295. package/src/scanner.test.ts +1045 -0
  296. package/src/scanner.ts +924 -0
  297. package/src/session-inventory.perf.test.ts +250 -0
  298. package/src/session-inventory.test.ts +1002 -0
  299. package/src/session-inventory.ts +721 -0
  300. package/src/session-manager.test.ts +3430 -0
  301. package/src/session-manager.ts +1775 -0
  302. package/src/session-store.test.ts +276 -0
  303. package/src/session-store.ts +202 -0
  304. package/src/session-title.perf.test.ts +118 -0
  305. package/src/session-title.test.ts +286 -0
  306. package/src/session-title.ts +108 -0
  307. package/src/shutdown-endpoint.test.ts +95 -0
  308. package/src/storage-housekeeping.test.ts +78 -0
  309. package/src/storage-housekeeping.ts +111 -0
  310. package/src/test-daemon-harness.ts +410 -0
  311. package/src/token-auth.test.ts +67 -0
  312. package/src/utils.test.ts +65 -0
  313. package/src/utils.ts +47 -0
  314. package/src/ws-data.test.ts +20 -0
  315. package/src/ws-data.ts +26 -0
  316. package/tsconfig.json +12 -0
  317. package/README.md +0 -80
  318. package/bin/cloudflared-quick-tunnel.mjs +0 -11
  319. package/bin/cloudflared-resolver.mjs +0 -68
  320. package/bin/vibelet-runtime-policy.mjs +0 -36
  321. package/bin/vibelet.cjs +0 -12
  322. package/bin/vibelet.mjs +0 -1019
  323. package/dist/index.cjs +0 -123
@@ -0,0 +1,225 @@
1
+ import { execFileSync } from 'child_process';
2
+ import type { NetworkInterfaceInfo } from 'os';
3
+
4
+ export interface AdvertisedHostsOptions {
5
+ canonicalHost: string;
6
+ configuredCanonicalHost?: string;
7
+ configuredFallbackHosts?: string[];
8
+ tailscaleCanonicalHost?: string;
9
+ tailscaleFallbackHosts?: string[];
10
+ interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>;
11
+ }
12
+
13
+ interface TailscaleStatusPayload {
14
+ Self?: {
15
+ DNSName?: string;
16
+ TailscaleIPs?: string[];
17
+ };
18
+ }
19
+
20
+ export interface TailscaleHosts {
21
+ canonicalHost?: string;
22
+ fallbackHosts: string[];
23
+ }
24
+
25
+ export interface AdvertisedConnectionTargetOptions {
26
+ canonicalHost: string;
27
+ fallbackHosts?: string[];
28
+ port: number;
29
+ relayUrl?: string;
30
+ }
31
+
32
+ export interface AdvertisedConnectionTarget {
33
+ canonicalHost: string;
34
+ fallbackHosts: string[];
35
+ port: number;
36
+ }
37
+
38
+ function isIpv4(address: string): boolean {
39
+ const parts = address.split('.');
40
+ if (parts.length !== 4) return false;
41
+ return parts.every((part) => /^\d+$/.test(part) && Number(part) >= 0 && Number(part) <= 255);
42
+ }
43
+
44
+ function isTailscaleIpv4(address: string): boolean {
45
+ if (!isIpv4(address)) return false;
46
+ const [first, second] = address.split('.').map(Number);
47
+ return first === 100 && second >= 64 && second <= 127;
48
+ }
49
+
50
+ function isLinkLocalIpv4(address: string): boolean {
51
+ if (!isIpv4(address)) return false;
52
+ const [first, second] = address.split('.').map(Number);
53
+ return first === 169 && second === 254;
54
+ }
55
+
56
+ function isBenchmarkIpv4(address: string): boolean {
57
+ if (!isIpv4(address)) return false;
58
+ const [first, second] = address.split('.').map(Number);
59
+ return first === 198 && (second === 18 || second === 19);
60
+ }
61
+
62
+ function isUsableHost(address: string): boolean {
63
+ return Boolean(address)
64
+ && !isLinkLocalIpv4(address)
65
+ && !isBenchmarkIpv4(address);
66
+ }
67
+
68
+ function normalizeHost(host: string | undefined): string | undefined {
69
+ if (!host) return undefined;
70
+ const trimmed = host.trim().replace(/\.$/, '');
71
+ return trimmed ? trimmed.toLowerCase() : undefined;
72
+ }
73
+
74
+ function normalizeFallbackHosts(
75
+ hosts: string[] | undefined,
76
+ canonicalHost: string,
77
+ ): string[] {
78
+ return (hosts ?? [])
79
+ .map((host) => normalizeHost(host))
80
+ .filter((host): host is string => Boolean(host))
81
+ .filter((host) => isUsableHost(host))
82
+ .filter((host, index, all) => host !== canonicalHost && all.indexOf(host) === index);
83
+ }
84
+
85
+ export function readConfiguredFallbackHosts(rawValue: string | undefined): string[] {
86
+ if (!rawValue) return [];
87
+ return rawValue
88
+ .split(',')
89
+ .map((entry) => entry.trim())
90
+ .filter(Boolean);
91
+ }
92
+
93
+ function collectInterfaceHosts(interfaces: NodeJS.Dict<NetworkInterfaceInfo[]>): string[] {
94
+ const tailscaleHosts: string[] = [];
95
+ const lanHosts: string[] = [];
96
+
97
+ for (const entries of Object.values(interfaces)) {
98
+ if (!entries) continue;
99
+ for (const entry of entries) {
100
+ if (entry.internal || entry.family !== 'IPv4') continue;
101
+ if (!isUsableHost(entry.address)) continue;
102
+ if (isTailscaleIpv4(entry.address)) {
103
+ tailscaleHosts.push(entry.address);
104
+ } else {
105
+ lanHosts.push(entry.address);
106
+ }
107
+ }
108
+ }
109
+
110
+ return [...tailscaleHosts, ...lanHosts];
111
+ }
112
+
113
+ export function parseTailscaleStatus(rawStatus: string): TailscaleHosts {
114
+ const parsed = JSON.parse(rawStatus) as TailscaleStatusPayload;
115
+ const dnsName = normalizeHost(parsed.Self?.DNSName);
116
+ const tailscaleIps = (parsed.Self?.TailscaleIPs ?? [])
117
+ .map((host) => normalizeHost(host))
118
+ .filter((host): host is string => Boolean(host))
119
+ .filter((host) => isTailscaleIpv4(host) && isUsableHost(host));
120
+ const canonicalHost = tailscaleIps[0] ?? dnsName;
121
+ const fallbackHosts = [
122
+ ...(dnsName && dnsName !== canonicalHost ? [dnsName] : []),
123
+ ...tailscaleIps.filter((host) => host !== canonicalHost),
124
+ ];
125
+
126
+ return {
127
+ canonicalHost,
128
+ fallbackHosts,
129
+ };
130
+ }
131
+
132
+ export function readTailscaleHosts(runCommand: typeof execFileSync = execFileSync): TailscaleHosts {
133
+ const socketPaths = [
134
+ process.env.TAILSCALE_SOCKET,
135
+ '/tmp/tailscale.sock',
136
+ ].filter(Boolean) as string[];
137
+
138
+ // Try with custom socket paths first
139
+ for (const socket of socketPaths) {
140
+ try {
141
+ const rawStatus = runCommand('tailscale', ['--socket', socket, 'status', '--json'], {
142
+ encoding: 'utf8',
143
+ stdio: ['ignore', 'pipe', 'pipe'],
144
+ timeout: 1500,
145
+ });
146
+ return parseTailscaleStatus(rawStatus);
147
+ } catch { /* try next */ }
148
+ }
149
+
150
+ // Fallback to default socket
151
+ try {
152
+ const rawStatus = runCommand('tailscale', ['status', '--json'], {
153
+ encoding: 'utf8',
154
+ stdio: ['ignore', 'pipe', 'pipe'],
155
+ timeout: 1500,
156
+ });
157
+ return parseTailscaleStatus(rawStatus);
158
+ } catch {
159
+ return { fallbackHosts: [] };
160
+ }
161
+ }
162
+
163
+ export function computeAdvertisedConnectionTarget(
164
+ options: AdvertisedConnectionTargetOptions,
165
+ ): AdvertisedConnectionTarget {
166
+ const baseCanonicalHost = normalizeHost(options.canonicalHost) || 'localhost';
167
+ const baseFallbackHosts = normalizeFallbackHosts(options.fallbackHosts, baseCanonicalHost);
168
+
169
+ if (!options.relayUrl) {
170
+ return {
171
+ canonicalHost: baseCanonicalHost,
172
+ fallbackHosts: baseFallbackHosts,
173
+ port: options.port,
174
+ };
175
+ }
176
+
177
+ try {
178
+ const relay = new URL(options.relayUrl);
179
+ const relayCanonicalHost = normalizeHost(relay.hostname);
180
+ if (!relayCanonicalHost) {
181
+ throw new Error('invalid relay hostname');
182
+ }
183
+
184
+ const relayPort = relay.port
185
+ ? Number(relay.port)
186
+ : (relay.protocol === 'https:' ? 443 : 80);
187
+
188
+ return {
189
+ canonicalHost: relayCanonicalHost,
190
+ fallbackHosts: normalizeFallbackHosts([baseCanonicalHost, ...baseFallbackHosts], relayCanonicalHost),
191
+ port: Number.isFinite(relayPort) && relayPort > 0 ? Math.floor(relayPort) : options.port,
192
+ };
193
+ } catch {
194
+ return {
195
+ canonicalHost: baseCanonicalHost,
196
+ fallbackHosts: baseFallbackHosts,
197
+ port: options.port,
198
+ };
199
+ }
200
+ }
201
+
202
+ export function computeAdvertisedHosts(options: AdvertisedHostsOptions): { canonicalHost: string; fallbackHosts: string[] } {
203
+ const baseCanonicalHost = normalizeHost(options.canonicalHost);
204
+ const canonicalHost = normalizeHost(options.configuredCanonicalHost)
205
+ || normalizeHost(options.tailscaleCanonicalHost)
206
+ || baseCanonicalHost
207
+ || 'localhost';
208
+ const fallbackHosts = [
209
+ ...(baseCanonicalHost && baseCanonicalHost !== canonicalHost
210
+ ? [baseCanonicalHost]
211
+ : []),
212
+ ...(options.configuredFallbackHosts ?? []),
213
+ ...(options.tailscaleFallbackHosts ?? []),
214
+ ...collectInterfaceHosts(options.interfaces),
215
+ ]
216
+ .map((host) => normalizeHost(host))
217
+ .filter((host): host is string => Boolean(host))
218
+ .filter((host) => isUsableHost(host))
219
+ .filter((host, index, all) => host !== canonicalHost && all.indexOf(host) === index);
220
+
221
+ return {
222
+ canonicalHost,
223
+ fallbackHosts,
224
+ };
225
+ }
@@ -0,0 +1,38 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { buildAppAuditEntry, sanitizeAppAuditData } from './audit.js';
4
+
5
+ test('sanitizeAppAuditData strips reserved audit fields from client payloads', () => {
6
+ assert.deepEqual(
7
+ sanitizeAppAuditData({
8
+ event: 'override',
9
+ source: 'app',
10
+ level: 'debug',
11
+ appEvent: 'fake',
12
+ ts: '2026-01-01T00:00:00.000Z',
13
+ sessionId: 'session-123',
14
+ agent: 'codex',
15
+ }),
16
+ {
17
+ sessionId: 'session-123',
18
+ agent: 'codex',
19
+ },
20
+ );
21
+ });
22
+
23
+ test('buildAppAuditEntry preserves the audit envelope while keeping client data', () => {
24
+ const entry = buildAppAuditEntry('app.session.send', 'info', {
25
+ sessionId: 'session-123',
26
+ event: 'override',
27
+ level: 'debug',
28
+ }, '2026-03-21T12:00:00.000Z');
29
+
30
+ assert.deepEqual(entry, {
31
+ ts: '2026-03-21T12:00:00.000Z',
32
+ event: 'app.log',
33
+ source: 'app',
34
+ appEvent: 'app.session.send',
35
+ level: 'info',
36
+ sessionId: 'session-123',
37
+ });
38
+ });
package/src/audit.ts ADDED
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Size-bounded audit log for session lifecycle events.
3
+ *
4
+ * Writes JSONL to ~/.vibelet/data/audit.jsonl.
5
+ * Each line is a self-contained event with timestamp, event type, and context.
6
+ *
7
+ * Usage:
8
+ * import { audit } from './audit.js';
9
+ * audit.emit('session.create', { sessionId, agent, cwd });
10
+ * audit.emit('session.done', { sessionId, cost, usage });
11
+ */
12
+
13
+ import { appendFileSync, mkdirSync } from 'fs';
14
+ import { dirname } from 'path';
15
+ import { AUDIT_PATH } from './paths.js';
16
+ import { config } from './config.js';
17
+ import { trimFileTailSync } from './storage-housekeeping.js';
18
+
19
+ export type AuditEvent =
20
+ | 'session.create'
21
+ | 'session.resume'
22
+ | 'session.reconnect'
23
+ | 'session.send'
24
+ | 'session.done'
25
+ | 'session.interrupted'
26
+ | 'session.stop'
27
+ | 'session.delete'
28
+ | 'session.interrupt'
29
+ | 'driver.spawn'
30
+ | 'driver.init'
31
+ | 'driver.exit'
32
+ | 'driver.error'
33
+ | 'driver.idle_timeout'
34
+ | 'driver.stall_timeout'
35
+ | 'approval.request'
36
+ | 'approval.response'
37
+ | 'ws.connect'
38
+ | 'ws.disconnect'
39
+ | 'daemon.start'
40
+ | 'daemon.shutdown'
41
+ | 'daemon.error'
42
+ | 'app.log';
43
+
44
+ export type AuditSource = 'daemon' | 'app';
45
+
46
+ interface AuditEntry {
47
+ ts: string;
48
+ event: AuditEvent | string;
49
+ source?: AuditSource;
50
+ [key: string]: unknown;
51
+ }
52
+
53
+ let initialized = false;
54
+ const APP_AUDIT_RESERVED_FIELDS = new Set(['ts', 'event', 'source', 'appEvent', 'level']);
55
+
56
+ function isTestRuntime(): boolean {
57
+ return process.env.VIBE_TEST === '1' || process.argv.includes('--test');
58
+ }
59
+
60
+ export function sanitizeAppAuditData(data: Record<string, unknown> = {}): Record<string, unknown> {
61
+ const sanitized: Record<string, unknown> = {};
62
+ for (const [key, value] of Object.entries(data)) {
63
+ if (APP_AUDIT_RESERVED_FIELDS.has(key)) continue;
64
+ sanitized[key] = value;
65
+ }
66
+ return sanitized;
67
+ }
68
+
69
+ export function buildAppAuditEntry(
70
+ appEvent: string,
71
+ level: string,
72
+ data: Record<string, unknown> = {},
73
+ clientTs?: string,
74
+ ): AuditEntry {
75
+ return {
76
+ ts: clientTs ?? new Date().toISOString(),
77
+ event: 'app.log',
78
+ source: 'app' as AuditSource,
79
+ appEvent,
80
+ level,
81
+ ...sanitizeAppAuditData(data),
82
+ };
83
+ }
84
+
85
+ function ensureDir(): void {
86
+ if (initialized) return;
87
+ mkdirSync(dirname(AUDIT_PATH), { recursive: true });
88
+ initialized = true;
89
+ }
90
+
91
+ class AuditLog {
92
+ emit(event: AuditEvent, data: Record<string, unknown> = {}): void {
93
+ this.write({ ts: new Date().toISOString(), event, ...data });
94
+ }
95
+
96
+ /** Write an app-reported log entry. Preserves client-side timestamp if provided. */
97
+ emitApp(appEvent: string, level: string, data: Record<string, unknown> = {}, clientTs?: string): void {
98
+ this.write(buildAppAuditEntry(appEvent, level, data, clientTs));
99
+ }
100
+
101
+ private write(entry: Record<string, unknown>): void {
102
+ if (isTestRuntime()) return;
103
+ ensureDir();
104
+ try {
105
+ const line = JSON.stringify(entry) + '\n';
106
+ trimFileTailSync(AUDIT_PATH, {
107
+ maxBytes: config.auditMaxBytes,
108
+ incomingBytes: Buffer.byteLength(line),
109
+ });
110
+ appendFileSync(AUDIT_PATH, line);
111
+ } catch {
112
+ // Audit log is best-effort — never crash the daemon
113
+ }
114
+ }
115
+ }
116
+
117
+ export const audit = new AuditLog();
package/src/auth.ts ADDED
@@ -0,0 +1,31 @@
1
+ export function isLoopbackAddress(address: string | undefined): boolean {
2
+ if (!address) return false;
3
+ return address === '127.0.0.1'
4
+ || address === '::1'
5
+ || address === '::ffff:127.0.0.1';
6
+ }
7
+
8
+ export function readBearerToken(header: string | undefined): string | null {
9
+ if (!header) return null;
10
+ const match = header.match(/^Bearer\s+(.+)$/i);
11
+ return match?.[1]?.trim() || null;
12
+ }
13
+
14
+ export function resolveRequestToken(authHeader: string | undefined, queryToken: string | null): string | null {
15
+ return readBearerToken(authHeader) ?? queryToken;
16
+ }
17
+
18
+ export function isLegacyToken(token: string | null, legacyToken: string): boolean {
19
+ return Boolean(token && legacyToken && token === legacyToken);
20
+ }
21
+
22
+ export function isAuthorizedToken(
23
+ token: string | null,
24
+ legacyToken: string,
25
+ validatePairToken: (token: string, touch?: boolean) => unknown,
26
+ touch = true,
27
+ ): boolean {
28
+ if (!token) return false;
29
+ if (isLegacyToken(token, legacyToken)) return true;
30
+ return Boolean(validatePairToken(token, touch));
31
+ }
@@ -0,0 +1,195 @@
1
+ import { mkdirSync, rmSync, writeFileSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { join } from 'path';
4
+ import { randomUUID } from 'crypto';
5
+
6
+ export const CLAUDE_HOOK_APPROVAL_PREFIX = 'claude-hook:';
7
+ export const CLAUDE_HOOK_SECRET_HEADER = 'x-vibelet-claude-hook-secret';
8
+
9
+ export type ClaudeSessionHookData = {
10
+ session_id?: string;
11
+ sessionId?: string;
12
+ transcript_path?: string;
13
+ transcriptPath?: string;
14
+ cwd?: string;
15
+ hook_event_name?: string;
16
+ hookEventName?: string;
17
+ source?: string;
18
+ [key: string]: unknown;
19
+ };
20
+
21
+ export type ClaudePermissionHookData = {
22
+ session_id?: string;
23
+ sessionId?: string;
24
+ transcript_path?: string;
25
+ transcriptPath?: string;
26
+ cwd?: string;
27
+ hook_event_name?: string;
28
+ hookEventName?: string;
29
+ permission_mode?: string;
30
+ permissionMode?: string;
31
+ tool_name?: string;
32
+ toolName?: string;
33
+ tool_input?: unknown;
34
+ toolInput?: unknown;
35
+ tool_use_id?: string;
36
+ toolUseId?: string;
37
+ permission_suggestions?: unknown;
38
+ permissionSuggestions?: unknown;
39
+ [key: string]: unknown;
40
+ };
41
+
42
+ export type ClaudePermissionHookResponse = {
43
+ continue: boolean;
44
+ suppressOutput?: boolean;
45
+ stopReason?: string;
46
+ systemMessage?: string;
47
+ hookSpecificOutput?: {
48
+ hookEventName?: 'PreToolUse';
49
+ permissionDecision?: 'allow' | 'deny' | 'ask';
50
+ permissionDecisionReason?: string;
51
+ updatedInput?: unknown;
52
+ [key: string]: unknown;
53
+ };
54
+ [key: string]: unknown;
55
+ };
56
+
57
+ export type ClaudeHookFiles = {
58
+ secret: string;
59
+ dirPath: string;
60
+ settingsPath: string;
61
+ sessionScriptPath: string;
62
+ permissionScriptPath: string;
63
+ };
64
+
65
+ export const DEFAULT_CLAUDE_PERMISSION_HOOK_RESPONSE: ClaudePermissionHookResponse = {
66
+ continue: true,
67
+ suppressOutput: true,
68
+ hookSpecificOutput: {
69
+ hookEventName: 'PreToolUse',
70
+ },
71
+ };
72
+
73
+ function buildSessionForwarderScript(port: number, secret: string): string {
74
+ return `#!/usr/bin/env node
75
+ const http = require('http');
76
+ const chunks = [];
77
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
78
+ process.stdin.on('end', () => {
79
+ const body = Buffer.concat(chunks);
80
+ const req = http.request({
81
+ host: '127.0.0.1',
82
+ port: ${port},
83
+ method: 'POST',
84
+ path: '/hook/claude/session-start',
85
+ headers: {
86
+ 'Content-Type': 'application/json',
87
+ 'Content-Length': body.length,
88
+ '${CLAUDE_HOOK_SECRET_HEADER}': ${JSON.stringify(secret)}
89
+ }
90
+ }, (res) => { res.resume(); });
91
+ req.on('error', () => {});
92
+ req.end(body);
93
+ });
94
+ process.stdin.resume();
95
+ `;
96
+ }
97
+
98
+ function buildPermissionForwarderScript(port: number, secret: string): string {
99
+ return `#!/usr/bin/env node
100
+ const http = require('http');
101
+ const fallback = ${JSON.stringify(JSON.stringify(DEFAULT_CLAUDE_PERMISSION_HOOK_RESPONSE))};
102
+ const chunks = [];
103
+ process.stdin.on('data', (chunk) => chunks.push(chunk));
104
+ process.stdin.on('end', () => {
105
+ const body = Buffer.concat(chunks);
106
+ const req = http.request({
107
+ host: '127.0.0.1',
108
+ port: ${port},
109
+ method: 'POST',
110
+ path: '/hook/claude/permission-request',
111
+ headers: {
112
+ 'Content-Type': 'application/json',
113
+ 'Content-Length': body.length,
114
+ '${CLAUDE_HOOK_SECRET_HEADER}': ${JSON.stringify(secret)}
115
+ }
116
+ }, (res) => {
117
+ const responseChunks = [];
118
+ res.on('data', (chunk) => responseChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
119
+ res.on('end', () => {
120
+ const statusCode = res.statusCode || 0;
121
+ if (statusCode < 200 || statusCode >= 300) {
122
+ process.stdout.write(fallback);
123
+ return;
124
+ }
125
+ const payload = Buffer.concat(responseChunks).toString('utf8').trim();
126
+ process.stdout.write(payload || fallback);
127
+ });
128
+ });
129
+ req.on('error', () => {
130
+ process.stdout.write(fallback);
131
+ });
132
+ req.end(body);
133
+ });
134
+ process.stdin.resume();
135
+ `;
136
+ }
137
+
138
+ export function createClaudeHookFiles(port: number, secret: string): ClaudeHookFiles {
139
+ const dirPath = join(tmpdir(), `vibelet-claude-hooks-${process.pid}-${randomUUID()}`);
140
+ mkdirSync(dirPath, { recursive: true });
141
+
142
+ const settingsPath = join(dirPath, 'settings.json');
143
+ const sessionScriptPath = join(dirPath, 'session_hook_forwarder.cjs');
144
+ const permissionScriptPath = join(dirPath, 'permission_hook_forwarder.cjs');
145
+
146
+ writeFileSync(sessionScriptPath, buildSessionForwarderScript(port, secret), 'utf8');
147
+ writeFileSync(permissionScriptPath, buildPermissionForwarderScript(port, secret), 'utf8');
148
+ writeFileSync(
149
+ settingsPath,
150
+ JSON.stringify({
151
+ hooks: {
152
+ SessionStart: [
153
+ {
154
+ matcher: '*',
155
+ hooks: [
156
+ {
157
+ type: 'command',
158
+ command: `${JSON.stringify(process.execPath)} ${JSON.stringify(sessionScriptPath)}`,
159
+ },
160
+ ],
161
+ },
162
+ ],
163
+ PreToolUse: [
164
+ {
165
+ matcher: '*',
166
+ hooks: [
167
+ {
168
+ type: 'command',
169
+ command: `${JSON.stringify(process.execPath)} ${JSON.stringify(permissionScriptPath)}`,
170
+ },
171
+ ],
172
+ },
173
+ ],
174
+ },
175
+ }, null, 2),
176
+ 'utf8',
177
+ );
178
+
179
+ return {
180
+ secret,
181
+ dirPath,
182
+ settingsPath,
183
+ sessionScriptPath,
184
+ permissionScriptPath,
185
+ };
186
+ }
187
+
188
+ export function cleanupClaudeHookFiles(hookFiles: ClaudeHookFiles | null | undefined): void {
189
+ if (!hookFiles) return;
190
+ try {
191
+ rmSync(hookFiles.dirPath, { recursive: true, force: true });
192
+ } catch {
193
+ // best effort cleanup
194
+ }
195
+ }
@@ -0,0 +1,36 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createRequire } from 'node:module';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { resolveCliVersion } from './cli-version.js';
6
+
7
+ const require = createRequire(import.meta.url);
8
+ const packageJson = require('../../../package.json') as { version?: string };
9
+ const daemonPackageDir = fileURLToPath(new URL('..', import.meta.url));
10
+
11
+ test('resolveCliVersion prefers VIBELET_CLI_VERSION when provided', () => {
12
+ const original = process.env.VIBELET_CLI_VERSION;
13
+ process.env.VIBELET_CLI_VERSION = '9.9.9-test';
14
+
15
+ try {
16
+ assert.equal(resolveCliVersion(), '9.9.9-test');
17
+ } finally {
18
+ if (original === undefined) delete process.env.VIBELET_CLI_VERSION;
19
+ else process.env.VIBELET_CLI_VERSION = original;
20
+ }
21
+ });
22
+
23
+ test('resolveCliVersion falls back to the root package version', () => {
24
+ const original = process.env.VIBELET_CLI_VERSION;
25
+ const originalCwd = process.cwd();
26
+ delete process.env.VIBELET_CLI_VERSION;
27
+ process.chdir(daemonPackageDir);
28
+
29
+ try {
30
+ assert.equal(resolveCliVersion(), packageJson.version);
31
+ } finally {
32
+ process.chdir(originalCwd);
33
+ if (original === undefined) delete process.env.VIBELET_CLI_VERSION;
34
+ else process.env.VIBELET_CLI_VERSION = original;
35
+ }
36
+ });
@@ -0,0 +1,46 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+
4
+ type RootPackageJson = {
5
+ name?: string;
6
+ version?: string;
7
+ };
8
+
9
+ const ROOT_PACKAGE_NAME = '@vibelet/cli';
10
+
11
+ function readVersionFromPackageJson(path: string): string | null {
12
+ try {
13
+ const packageJson = JSON.parse(readFileSync(path, 'utf8')) as RootPackageJson;
14
+ if (packageJson.name === ROOT_PACKAGE_NAME && typeof packageJson.version === 'string' && packageJson.version.length > 0) {
15
+ return packageJson.version;
16
+ }
17
+ } catch {
18
+ // Continue through the fallback chain.
19
+ }
20
+
21
+ return null;
22
+ }
23
+
24
+ export function resolveCliVersion(): string {
25
+ if (process.env.VIBELET_CLI_VERSION) {
26
+ return process.env.VIBELET_CLI_VERSION;
27
+ }
28
+
29
+ let currentDir = process.cwd();
30
+ for (let depth = 0; depth < 4; depth += 1) {
31
+ const version = readVersionFromPackageJson(resolve(currentDir, 'package.json'));
32
+ if (version) {
33
+ return version;
34
+ }
35
+
36
+ const parentDir = resolve(currentDir, '..');
37
+ if (parentDir === currentDir) {
38
+ break;
39
+ }
40
+ currentDir = parentDir;
41
+ }
42
+
43
+ return '0.0.0';
44
+ }
45
+
46
+ export const CLI_VERSION = resolveCliVersion();