@vibelet/cli 0.1.38 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) hide show
  1. package/README.md +80 -0
  2. package/bin/cloudflared-quick-tunnel.mjs +11 -0
  3. package/bin/cloudflared-resolver.mjs +171 -0
  4. package/bin/vibelet-runtime-policy.mjs +36 -0
  5. package/bin/vibelet.cjs +12 -0
  6. package/bin/vibelet.mjs +1235 -0
  7. package/dist/index.cjs +126 -0
  8. package/package.json +24 -22
  9. package/app.json +0 -5
  10. package/dist/advertised-hosts.d.ts +0 -34
  11. package/dist/advertised-hosts.d.ts.map +0 -1
  12. package/dist/advertised-hosts.js +0 -176
  13. package/dist/advertised-hosts.js.map +0 -1
  14. package/dist/advertised-hosts.test.d.ts +0 -2
  15. package/dist/advertised-hosts.test.d.ts.map +0 -1
  16. package/dist/advertised-hosts.test.js +0 -96
  17. package/dist/advertised-hosts.test.js.map +0 -1
  18. package/dist/audit.d.ts +0 -30
  19. package/dist/audit.d.ts.map +0 -1
  20. package/dist/audit.js +0 -73
  21. package/dist/audit.js.map +0 -1
  22. package/dist/audit.test.d.ts +0 -2
  23. package/dist/audit.test.d.ts.map +0 -1
  24. package/dist/audit.test.js +0 -33
  25. package/dist/audit.test.js.map +0 -1
  26. package/dist/auth.d.ts +0 -6
  27. package/dist/auth.d.ts.map +0 -1
  28. package/dist/auth.js +0 -27
  29. package/dist/auth.js.map +0 -1
  30. package/dist/claude-hooks.d.ts +0 -58
  31. package/dist/claude-hooks.d.ts.map +0 -1
  32. package/dist/claude-hooks.js +0 -129
  33. package/dist/claude-hooks.js.map +0 -1
  34. package/dist/cli-version.d.ts +0 -3
  35. package/dist/cli-version.d.ts.map +0 -1
  36. package/dist/cli-version.js +0 -35
  37. package/dist/cli-version.js.map +0 -1
  38. package/dist/cli-version.test.d.ts +0 -2
  39. package/dist/cli-version.test.d.ts.map +0 -1
  40. package/dist/cli-version.test.js +0 -38
  41. package/dist/cli-version.test.js.map +0 -1
  42. package/dist/config.d.ts +0 -30
  43. package/dist/config.d.ts.map +0 -1
  44. package/dist/config.js +0 -327
  45. package/dist/config.js.map +0 -1
  46. package/dist/config.test.d.ts +0 -2
  47. package/dist/config.test.d.ts.map +0 -1
  48. package/dist/config.test.js +0 -184
  49. package/dist/config.test.js.map +0 -1
  50. package/dist/dev-auth.test.d.ts +0 -2
  51. package/dist/dev-auth.test.d.ts.map +0 -1
  52. package/dist/dev-auth.test.js +0 -154
  53. package/dist/dev-auth.test.js.map +0 -1
  54. package/dist/dev-script.test.d.ts +0 -2
  55. package/dist/dev-script.test.d.ts.map +0 -1
  56. package/dist/dev-script.test.js +0 -412
  57. package/dist/dev-script.test.js.map +0 -1
  58. package/dist/drivers/claude.d.ts +0 -34
  59. package/dist/drivers/claude.d.ts.map +0 -1
  60. package/dist/drivers/claude.js +0 -413
  61. package/dist/drivers/claude.js.map +0 -1
  62. package/dist/drivers/claude.test.d.ts +0 -2
  63. package/dist/drivers/claude.test.d.ts.map +0 -1
  64. package/dist/drivers/claude.test.js +0 -951
  65. package/dist/drivers/claude.test.js.map +0 -1
  66. package/dist/drivers/codex.d.ts +0 -38
  67. package/dist/drivers/codex.d.ts.map +0 -1
  68. package/dist/drivers/codex.js +0 -771
  69. package/dist/drivers/codex.js.map +0 -1
  70. package/dist/drivers/codex.test.d.ts +0 -2
  71. package/dist/drivers/codex.test.d.ts.map +0 -1
  72. package/dist/drivers/codex.test.js +0 -939
  73. package/dist/drivers/codex.test.js.map +0 -1
  74. package/dist/drivers/types.d.ts +0 -14
  75. package/dist/drivers/types.d.ts.map +0 -1
  76. package/dist/drivers/types.js +0 -2
  77. package/dist/drivers/types.js.map +0 -1
  78. package/dist/e2e.test.d.ts +0 -2
  79. package/dist/e2e.test.d.ts.map +0 -1
  80. package/dist/e2e.test.js +0 -111
  81. package/dist/e2e.test.js.map +0 -1
  82. package/dist/identity.d.ts +0 -10
  83. package/dist/identity.d.ts.map +0 -1
  84. package/dist/identity.js +0 -66
  85. package/dist/identity.js.map +0 -1
  86. package/dist/identity.test.d.ts +0 -2
  87. package/dist/identity.test.d.ts.map +0 -1
  88. package/dist/identity.test.js +0 -25
  89. package/dist/identity.test.js.map +0 -1
  90. package/dist/index-entry.test.d.ts +0 -2
  91. package/dist/index-entry.test.d.ts.map +0 -1
  92. package/dist/index-entry.test.js +0 -272
  93. package/dist/index-entry.test.js.map +0 -1
  94. package/dist/index.d.ts +0 -2
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/index.js +0 -707
  97. package/dist/index.js.map +0 -1
  98. package/dist/logger.d.ts +0 -31
  99. package/dist/logger.d.ts.map +0 -1
  100. package/dist/logger.js +0 -75
  101. package/dist/logger.js.map +0 -1
  102. package/dist/metrics.d.ts +0 -52
  103. package/dist/metrics.d.ts.map +0 -1
  104. package/dist/metrics.js +0 -89
  105. package/dist/metrics.js.map +0 -1
  106. package/dist/pairing-store.d.ts +0 -29
  107. package/dist/pairing-store.d.ts.map +0 -1
  108. package/dist/pairing-store.js +0 -131
  109. package/dist/pairing-store.js.map +0 -1
  110. package/dist/pairing-store.test.d.ts +0 -2
  111. package/dist/pairing-store.test.d.ts.map +0 -1
  112. package/dist/pairing-store.test.js +0 -47
  113. package/dist/pairing-store.test.js.map +0 -1
  114. package/dist/paths.d.ts +0 -16
  115. package/dist/paths.d.ts.map +0 -1
  116. package/dist/paths.js +0 -18
  117. package/dist/paths.js.map +0 -1
  118. package/dist/perf-compare.d.ts +0 -13
  119. package/dist/perf-compare.d.ts.map +0 -1
  120. package/dist/perf-compare.js +0 -125
  121. package/dist/perf-compare.js.map +0 -1
  122. package/dist/port-conflict.d.ts +0 -9
  123. package/dist/port-conflict.d.ts.map +0 -1
  124. package/dist/port-conflict.js +0 -33
  125. package/dist/port-conflict.js.map +0 -1
  126. package/dist/port-conflict.test.d.ts +0 -2
  127. package/dist/port-conflict.test.d.ts.map +0 -1
  128. package/dist/port-conflict.test.js +0 -38
  129. package/dist/port-conflict.test.js.map +0 -1
  130. package/dist/process-scanner.d.ts +0 -43
  131. package/dist/process-scanner.d.ts.map +0 -1
  132. package/dist/process-scanner.js +0 -453
  133. package/dist/process-scanner.js.map +0 -1
  134. package/dist/process-scanner.perf.test.d.ts +0 -2
  135. package/dist/process-scanner.perf.test.d.ts.map +0 -1
  136. package/dist/process-scanner.perf.test.js +0 -186
  137. package/dist/process-scanner.perf.test.js.map +0 -1
  138. package/dist/process-scanner.test.d.ts +0 -2
  139. package/dist/process-scanner.test.d.ts.map +0 -1
  140. package/dist/process-scanner.test.js +0 -399
  141. package/dist/process-scanner.test.js.map +0 -1
  142. package/dist/push-protocol.d.ts +0 -15
  143. package/dist/push-protocol.d.ts.map +0 -1
  144. package/dist/push-protocol.js +0 -23
  145. package/dist/push-protocol.js.map +0 -1
  146. package/dist/push-protocol.test.d.ts +0 -2
  147. package/dist/push-protocol.test.d.ts.map +0 -1
  148. package/dist/push-protocol.test.js +0 -57
  149. package/dist/push-protocol.test.js.map +0 -1
  150. package/dist/push-store.d.ts +0 -22
  151. package/dist/push-store.d.ts.map +0 -1
  152. package/dist/push-store.js +0 -103
  153. package/dist/push-store.js.map +0 -1
  154. package/dist/push-store.test.d.ts +0 -2
  155. package/dist/push-store.test.d.ts.map +0 -1
  156. package/dist/push-store.test.js +0 -79
  157. package/dist/push-store.test.js.map +0 -1
  158. package/dist/push.d.ts +0 -65
  159. package/dist/push.d.ts.map +0 -1
  160. package/dist/push.js +0 -202
  161. package/dist/push.js.map +0 -1
  162. package/dist/push.test.d.ts +0 -2
  163. package/dist/push.test.d.ts.map +0 -1
  164. package/dist/push.test.js +0 -199
  165. package/dist/push.test.js.map +0 -1
  166. package/dist/safe-stdio.d.ts +0 -3
  167. package/dist/safe-stdio.d.ts.map +0 -1
  168. package/dist/safe-stdio.js +0 -46
  169. package/dist/safe-stdio.js.map +0 -1
  170. package/dist/scanner.d.ts +0 -30
  171. package/dist/scanner.d.ts.map +0 -1
  172. package/dist/scanner.js +0 -859
  173. package/dist/scanner.js.map +0 -1
  174. package/dist/scanner.perf.test.d.ts +0 -2
  175. package/dist/scanner.perf.test.d.ts.map +0 -1
  176. package/dist/scanner.perf.test.js +0 -320
  177. package/dist/scanner.perf.test.js.map +0 -1
  178. package/dist/scanner.test.d.ts +0 -2
  179. package/dist/scanner.test.d.ts.map +0 -1
  180. package/dist/scanner.test.js +0 -948
  181. package/dist/scanner.test.js.map +0 -1
  182. package/dist/session-inventory.d.ts +0 -63
  183. package/dist/session-inventory.d.ts.map +0 -1
  184. package/dist/session-inventory.js +0 -525
  185. package/dist/session-inventory.js.map +0 -1
  186. package/dist/session-inventory.perf.test.d.ts +0 -2
  187. package/dist/session-inventory.perf.test.d.ts.map +0 -1
  188. package/dist/session-inventory.perf.test.js +0 -220
  189. package/dist/session-inventory.perf.test.js.map +0 -1
  190. package/dist/session-inventory.test.d.ts +0 -2
  191. package/dist/session-inventory.test.d.ts.map +0 -1
  192. package/dist/session-inventory.test.js +0 -712
  193. package/dist/session-inventory.test.js.map +0 -1
  194. package/dist/session-manager.d.ts +0 -75
  195. package/dist/session-manager.d.ts.map +0 -1
  196. package/dist/session-manager.js +0 -1515
  197. package/dist/session-manager.js.map +0 -1
  198. package/dist/session-manager.test.d.ts +0 -2
  199. package/dist/session-manager.test.d.ts.map +0 -1
  200. package/dist/session-manager.test.js +0 -2861
  201. package/dist/session-manager.test.js.map +0 -1
  202. package/dist/session-store.d.ts +0 -42
  203. package/dist/session-store.d.ts.map +0 -1
  204. package/dist/session-store.js +0 -163
  205. package/dist/session-store.js.map +0 -1
  206. package/dist/session-store.test.d.ts +0 -2
  207. package/dist/session-store.test.d.ts.map +0 -1
  208. package/dist/session-store.test.js +0 -236
  209. package/dist/session-store.test.js.map +0 -1
  210. package/dist/session-title.d.ts +0 -6
  211. package/dist/session-title.d.ts.map +0 -1
  212. package/dist/session-title.js +0 -105
  213. package/dist/session-title.js.map +0 -1
  214. package/dist/session-title.perf.test.d.ts +0 -2
  215. package/dist/session-title.perf.test.d.ts.map +0 -1
  216. package/dist/session-title.perf.test.js +0 -99
  217. package/dist/session-title.perf.test.js.map +0 -1
  218. package/dist/session-title.test.d.ts +0 -2
  219. package/dist/session-title.test.d.ts.map +0 -1
  220. package/dist/session-title.test.js +0 -199
  221. package/dist/session-title.test.js.map +0 -1
  222. package/dist/shutdown-endpoint.test.d.ts +0 -2
  223. package/dist/shutdown-endpoint.test.d.ts.map +0 -1
  224. package/dist/shutdown-endpoint.test.js +0 -93
  225. package/dist/shutdown-endpoint.test.js.map +0 -1
  226. package/dist/storage-housekeeping.d.ts +0 -28
  227. package/dist/storage-housekeeping.d.ts.map +0 -1
  228. package/dist/storage-housekeeping.js +0 -76
  229. package/dist/storage-housekeeping.js.map +0 -1
  230. package/dist/storage-housekeeping.test.d.ts +0 -2
  231. package/dist/storage-housekeeping.test.d.ts.map +0 -1
  232. package/dist/storage-housekeeping.test.js +0 -65
  233. package/dist/storage-housekeeping.test.js.map +0 -1
  234. package/dist/test-daemon-harness.d.ts +0 -31
  235. package/dist/test-daemon-harness.d.ts.map +0 -1
  236. package/dist/test-daemon-harness.js +0 -337
  237. package/dist/test-daemon-harness.js.map +0 -1
  238. package/dist/token-auth.test.d.ts +0 -2
  239. package/dist/token-auth.test.d.ts.map +0 -1
  240. package/dist/token-auth.test.js +0 -52
  241. package/dist/token-auth.test.js.map +0 -1
  242. package/dist/utils.d.ts +0 -4
  243. package/dist/utils.d.ts.map +0 -1
  244. package/dist/utils.js +0 -40
  245. package/dist/utils.js.map +0 -1
  246. package/dist/utils.test.d.ts +0 -2
  247. package/dist/utils.test.d.ts.map +0 -1
  248. package/dist/utils.test.js +0 -54
  249. package/dist/utils.test.js.map +0 -1
  250. package/dist/ws-data.d.ts +0 -4
  251. package/dist/ws-data.d.ts.map +0 -1
  252. package/dist/ws-data.js +0 -20
  253. package/dist/ws-data.js.map +0 -1
  254. package/dist/ws-data.test.d.ts +0 -2
  255. package/dist/ws-data.test.d.ts.map +0 -1
  256. package/dist/ws-data.test.js +0 -17
  257. package/dist/ws-data.test.js.map +0 -1
  258. package/perf-reporter.mjs +0 -138
  259. package/scripts/build-release.mjs +0 -41
  260. package/scripts/dev.mjs +0 -537
  261. package/src/advertised-hosts.test.ts +0 -125
  262. package/src/advertised-hosts.ts +0 -225
  263. package/src/audit.test.ts +0 -38
  264. package/src/audit.ts +0 -117
  265. package/src/auth.ts +0 -31
  266. package/src/claude-hooks.ts +0 -195
  267. package/src/cli-version.test.ts +0 -36
  268. package/src/cli-version.ts +0 -46
  269. package/src/config.test.ts +0 -254
  270. package/src/config.ts +0 -324
  271. package/src/dev-auth.test.ts +0 -183
  272. package/src/dev-script.test.ts +0 -511
  273. package/src/drivers/claude.test.ts +0 -1186
  274. package/src/drivers/claude.ts +0 -443
  275. package/src/drivers/codex.test.ts +0 -1096
  276. package/src/drivers/codex.ts +0 -879
  277. package/src/drivers/types.ts +0 -15
  278. package/src/e2e.test.ts +0 -139
  279. package/src/identity.test.ts +0 -26
  280. package/src/identity.ts +0 -82
  281. package/src/index-entry.test.ts +0 -336
  282. package/src/index.ts +0 -781
  283. package/src/logger.ts +0 -112
  284. package/src/metrics.ts +0 -117
  285. package/src/pairing-store.test.ts +0 -53
  286. package/src/pairing-store.ts +0 -154
  287. package/src/paths.ts +0 -19
  288. package/src/perf-compare.ts +0 -164
  289. package/src/port-conflict.test.ts +0 -45
  290. package/src/port-conflict.ts +0 -44
  291. package/src/process-scanner.perf.test.ts +0 -222
  292. package/src/process-scanner.test.ts +0 -575
  293. package/src/process-scanner.ts +0 -514
  294. package/src/push-protocol.test.ts +0 -74
  295. package/src/push-protocol.ts +0 -36
  296. package/src/push-store.test.ts +0 -89
  297. package/src/push-store.ts +0 -126
  298. package/src/push.test.ts +0 -234
  299. package/src/push.ts +0 -318
  300. package/src/safe-stdio.ts +0 -51
  301. package/src/scanner.perf.test.ts +0 -359
  302. package/src/scanner.test.ts +0 -1045
  303. package/src/scanner.ts +0 -924
  304. package/src/session-inventory.perf.test.ts +0 -250
  305. package/src/session-inventory.test.ts +0 -1002
  306. package/src/session-inventory.ts +0 -721
  307. package/src/session-manager.test.ts +0 -3430
  308. package/src/session-manager.ts +0 -1775
  309. package/src/session-store.test.ts +0 -276
  310. package/src/session-store.ts +0 -202
  311. package/src/session-title.perf.test.ts +0 -118
  312. package/src/session-title.test.ts +0 -286
  313. package/src/session-title.ts +0 -108
  314. package/src/shutdown-endpoint.test.ts +0 -95
  315. package/src/storage-housekeeping.test.ts +0 -78
  316. package/src/storage-housekeeping.ts +0 -111
  317. package/src/test-daemon-harness.ts +0 -410
  318. package/src/token-auth.test.ts +0 -67
  319. package/src/utils.test.ts +0 -65
  320. package/src/utils.ts +0 -47
  321. package/src/ws-data.test.ts +0 -20
  322. package/src/ws-data.ts +0 -26
  323. package/tsconfig.json +0 -12
@@ -1,15 +0,0 @@
1
- import type { ApprovalRequestPayload, ServerMessage } from '@vibelet/shared';
2
-
3
- export type MessageHandler = (msg: ServerMessage) => void;
4
-
5
- export interface Driver {
6
- start(cwd: string, resumeSessionId?: string, approvalMode?: string): Promise<string>;
7
- sendPrompt(text: string): void;
8
- respondApproval(requestId: string, approved: boolean): boolean;
9
- restorePendingApproval?(approval: ApprovalRequestPayload): void;
10
- interrupt(): void;
11
- stop(): void;
12
- setApprovalMode(mode: string): void;
13
- onMessage(handler: MessageHandler): void;
14
- onExit(handler: (code: number | null) => void): void;
15
- }
package/src/e2e.test.ts DELETED
@@ -1,139 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { mkdirSync } from 'node:fs';
3
- import path from 'node:path';
4
- import test from 'node:test';
5
- import type { SessionInfo } from '@vibelet/shared';
6
- import {
7
- cleanupDaemon,
8
- createWebSocketRecorder,
9
- openWebSocket,
10
- spawnDaemon,
11
- waitForWebSocketClose,
12
- } from './test-daemon-harness.js';
13
-
14
- function send(ws: { send: (data: string) => void }, payload: object): void {
15
- ws.send(JSON.stringify(payload));
16
- }
17
-
18
- function responseData(message: Record<string, unknown>): Record<string, unknown> {
19
- const data = message.data;
20
- assert.ok(data && typeof data === 'object' && !Array.isArray(data));
21
- return data as Record<string, unknown>;
22
- }
23
-
24
- const E2E_WAIT_TIMEOUT_MS = 10_000;
25
-
26
- test('daemon automated e2e covers list/create/send/stream/resume without real agent binaries', async (t) => {
27
- const daemon = await spawnDaemon({ useFakeClaudeCli: true });
28
- t.after(() => cleanupDaemon(daemon));
29
-
30
- const cwd = path.join(daemon.homeDir, 'workspace');
31
- mkdirSync(cwd, { recursive: true });
32
-
33
- const ws1 = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${daemon.token}`);
34
- const recorder1 = createWebSocketRecorder(ws1);
35
- t.after(() => {
36
- recorder1.stop();
37
- try {
38
- ws1.close();
39
- } catch {}
40
- });
41
-
42
- send(ws1, { action: 'sessions.list', id: 'list-1', agent: 'claude', cwd });
43
- const listBefore = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'list-1', E2E_WAIT_TIMEOUT_MS);
44
- assert.equal(listBefore.ok, true);
45
- assert.deepEqual(responseData(listBefore).sessions, []);
46
-
47
- send(ws1, { action: 'session.create', id: 'create-1', agent: 'claude', cwd });
48
- const createResponse = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'create-1', E2E_WAIT_TIMEOUT_MS);
49
- assert.equal(createResponse.ok, true);
50
- const pendingSessionId = String(responseData(createResponse).sessionId);
51
- assert.match(pendingSessionId, /^pending_/);
52
-
53
- const firstPrompt = 'First fake prompt for automated daemon e2e.';
54
- send(ws1, {
55
- action: 'session.send',
56
- id: 'send-1',
57
- sessionId: pendingSessionId,
58
- agent: 'claude',
59
- message: firstPrompt,
60
- });
61
-
62
- const sendResponse = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'send-1', E2E_WAIT_TIMEOUT_MS);
63
- assert.equal(sendResponse.ok, true);
64
-
65
- const idUpdate = await recorder1.waitFor((message) =>
66
- message.type === 'response'
67
- && typeof message.id === 'string'
68
- && message.id.startsWith('id_update_'),
69
- E2E_WAIT_TIMEOUT_MS);
70
- assert.equal(idUpdate.ok, true);
71
- const idUpdateData = responseData(idUpdate);
72
- assert.equal(idUpdateData.oldSessionId, pendingSessionId);
73
- const actualSessionId = String(idUpdateData.sessionId);
74
- assert.notEqual(actualSessionId, pendingSessionId);
75
-
76
- await recorder1.waitFor((message) => message.type === 'session.done' && message.sessionId === actualSessionId, E2E_WAIT_TIMEOUT_MS);
77
- const firstReply = recorder1.messages
78
- .filter((message) => message.type === 'text.delta' && message.sessionId === actualSessionId)
79
- .map((message) => String(message.content ?? ''))
80
- .join('');
81
- assert.equal(firstReply, `fake claude reply: ${firstPrompt}`);
82
-
83
- send(ws1, { action: 'sessions.list', id: 'list-2', agent: 'claude', cwd });
84
- const listAfter = await recorder1.waitFor((message) => message.type === 'response' && message.id === 'list-2', E2E_WAIT_TIMEOUT_MS);
85
- assert.equal(listAfter.ok, true);
86
- const listedSessions = responseData(listAfter).sessions as SessionInfo[];
87
- const listedSession = listedSessions.find((session) => session.sessionId === actualSessionId);
88
- assert.ok(listedSession);
89
- assert.equal(listedSession.agent, 'claude');
90
- assert.ok(listedSession.sources.includes('record'));
91
- if (listedSession.sources.includes('daemon')) {
92
- assert.equal(listedSession.runtime.state, 'daemonActive');
93
- } else {
94
- assert.equal(listedSession.runtime.state, 'idle');
95
- }
96
- assert.match(listedSession.title ?? '', /First fake prompt/);
97
-
98
- ws1.close();
99
- const closeCode = await waitForWebSocketClose(ws1, E2E_WAIT_TIMEOUT_MS);
100
- assert.equal(closeCode, 1005);
101
- recorder1.stop();
102
-
103
- const ws2 = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${daemon.token}`);
104
- const recorder2 = createWebSocketRecorder(ws2);
105
- t.after(() => {
106
- recorder2.stop();
107
- try {
108
- ws2.close();
109
- } catch {}
110
- });
111
-
112
- send(ws2, { action: 'session.resume', id: 'resume-1', sessionId: actualSessionId, agent: 'claude' });
113
- const historyMessage = await recorder2.waitFor((message) => message.type === 'session.history' && message.sessionId === actualSessionId, E2E_WAIT_TIMEOUT_MS);
114
- const resumeResponse = await recorder2.waitFor((message) => message.type === 'response' && message.id === 'resume-1', E2E_WAIT_TIMEOUT_MS);
115
- assert.equal(resumeResponse.ok, true);
116
- assert.deepEqual(historyMessage.messages, [
117
- { role: 'user', content: firstPrompt },
118
- { role: 'assistant', content: `fake claude reply: ${firstPrompt}` },
119
- ]);
120
-
121
- const secondPrompt = 'Second fake prompt after websocket resume.';
122
- send(ws2, {
123
- action: 'session.send',
124
- id: 'send-2',
125
- sessionId: actualSessionId,
126
- agent: 'claude',
127
- message: secondPrompt,
128
- });
129
-
130
- const sendResponse2 = await recorder2.waitFor((message) => message.type === 'response' && message.id === 'send-2', E2E_WAIT_TIMEOUT_MS);
131
- assert.equal(sendResponse2.ok, true);
132
- await recorder2.waitFor((message) => message.type === 'session.done' && message.sessionId === actualSessionId, E2E_WAIT_TIMEOUT_MS);
133
-
134
- const secondReply = recorder2.messages
135
- .filter((message) => message.type === 'text.delta' && message.sessionId === actualSessionId)
136
- .map((message) => String(message.content ?? ''))
137
- .join('');
138
- assert.match(secondReply, /Second fake prompt after websocket resume\./);
139
- });
@@ -1,26 +0,0 @@
1
- import test from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { mkdtemp, readFile, rm } from 'fs/promises';
4
- import { tmpdir } from 'os';
5
- import { join } from 'path';
6
- import { loadOrCreateDaemonIdentity } from './identity.js';
7
-
8
- test('loadOrCreateDaemonIdentity persists identity details and reuses the daemon id', async () => {
9
- const dir = await mkdtemp(join(tmpdir(), 'vibelet-identity-'));
10
- const identityPath = join(dir, 'identity.json');
11
-
12
- try {
13
- const first = loadOrCreateDaemonIdentity(9876, identityPath);
14
- const second = loadOrCreateDaemonIdentity(9999, identityPath);
15
- const persisted = JSON.parse(await readFile(identityPath, 'utf8'));
16
-
17
- assert.match(first.daemonId, /^d_/);
18
- assert.equal(second.daemonId, first.daemonId);
19
- assert.equal(second.daemonSecret, first.daemonSecret);
20
- assert.equal(second.port, 9999);
21
- assert.equal(persisted.port, 9999);
22
- assert.equal(persisted.daemonId, first.daemonId);
23
- } finally {
24
- await rm(dir, { recursive: true, force: true });
25
- }
26
- });
package/src/identity.ts DELETED
@@ -1,82 +0,0 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from 'fs';
2
- import { hostname } from 'os';
3
- import { randomBytes } from 'crypto';
4
- import { dirname } from 'path';
5
- import { IDENTITY_PATH } from './paths.js';
6
-
7
- export interface DaemonIdentity {
8
- daemonId: string;
9
- daemonSecret: string;
10
- displayName: string;
11
- canonicalHost: string;
12
- port: number;
13
- createdAt: string;
14
- }
15
-
16
- function base64Url(bytes: number): string {
17
- return randomBytes(bytes).toString('base64url');
18
- }
19
-
20
- function buildDaemonId(): string {
21
- return `d_${base64Url(12)}`;
22
- }
23
-
24
- function defaultDisplayName(): string {
25
- return hostname();
26
- }
27
-
28
- function defaultCanonicalHost(): string {
29
- const raw = hostname().trim().toLowerCase();
30
- if (!raw) return 'localhost';
31
- return raw.endsWith('.local') ? raw : `${raw}.local`;
32
- }
33
-
34
- function createIdentity(port: number): DaemonIdentity {
35
- return {
36
- daemonId: buildDaemonId(),
37
- daemonSecret: base64Url(32),
38
- displayName: defaultDisplayName(),
39
- canonicalHost: defaultCanonicalHost(),
40
- port,
41
- createdAt: new Date().toISOString(),
42
- };
43
- }
44
-
45
- function writeIdentity(identity: DaemonIdentity, identityPath: string): void {
46
- mkdirSync(dirname(identityPath), { recursive: true });
47
- writeFileSync(identityPath, JSON.stringify(identity, null, 2) + '\n', 'utf8');
48
- }
49
-
50
- export function loadOrCreateDaemonIdentity(port: number, identityPath = IDENTITY_PATH): DaemonIdentity {
51
- try {
52
- const raw = readFileSync(identityPath, 'utf8');
53
- const parsed = JSON.parse(raw) as Partial<DaemonIdentity>;
54
- if (
55
- typeof parsed.daemonId === 'string' &&
56
- typeof parsed.daemonSecret === 'string' &&
57
- typeof parsed.displayName === 'string' &&
58
- typeof parsed.canonicalHost === 'string' &&
59
- typeof parsed.createdAt === 'string'
60
- ) {
61
- const identity: DaemonIdentity = {
62
- daemonId: parsed.daemonId,
63
- daemonSecret: parsed.daemonSecret,
64
- displayName: parsed.displayName,
65
- canonicalHost: parsed.canonicalHost,
66
- port: typeof parsed.port === 'number' && Number.isFinite(parsed.port) ? parsed.port : port,
67
- createdAt: parsed.createdAt,
68
- };
69
- if (identity.port !== port) {
70
- identity.port = port;
71
- writeIdentity(identity, identityPath);
72
- }
73
- return identity;
74
- }
75
- } catch {
76
- // Fall through to create a new identity.
77
- }
78
-
79
- const identity = createIdentity(port);
80
- writeIdentity(identity, identityPath);
81
- return identity;
82
- }
@@ -1,336 +0,0 @@
1
- import assert from 'node:assert/strict';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
3
- import path from 'node:path';
4
- import test from 'node:test';
5
- import type { PairingQrPayload } from '@vibelet/shared';
6
- import {
7
- cleanupDaemon,
8
- openWebSocket,
9
- readAuditLog,
10
- spawnDaemon,
11
- waitForHealth,
12
- waitForStdout,
13
- waitForWebSocketClose,
14
- waitForWebSocketMessage,
15
- } from './test-daemon-harness.js';
16
-
17
- test('health endpoint reports daemon status and pairing flow can create then reset pairings', async (t) => {
18
- const daemon = await spawnDaemon();
19
- t.after(() => cleanupDaemon(daemon));
20
-
21
- const initialHealth = await waitForHealth(daemon.port);
22
- assert.equal(initialHealth.status, 'ok');
23
- assert.equal(initialHealth.pairedDevices, 0);
24
- assert.equal(typeof initialHealth.daemonId, 'string');
25
- assert.equal(typeof initialHealth.metrics, 'object');
26
- assert.equal(typeof initialHealth.sessionInventory, 'object');
27
- assert.equal(
28
- typeof (initialHealth.sessionInventory as { backfillInFlight: unknown }).backfillInFlight,
29
- 'boolean',
30
- );
31
- assert.equal(
32
- typeof (initialHealth.sessionInventory as { cachedSessions: unknown }).cachedSessions,
33
- 'number',
34
- );
35
-
36
- const pairOpenResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' });
37
- assert.equal(pairOpenResponse.status, 200);
38
- const pairPayload = await pairOpenResponse.json() as PairingQrPayload;
39
- assert.equal(pairPayload.type, 'vibelet-pair');
40
- assert.equal(typeof pairPayload.daemonId, 'string');
41
- assert.equal(typeof pairPayload.pairNonce, 'string');
42
-
43
- const createBody = {
44
- daemonId: pairPayload.daemonId,
45
- pairNonce: pairPayload.pairNonce,
46
- deviceId: 'device-1',
47
- deviceName: 'QA Phone',
48
- };
49
-
50
- const createResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
51
- method: 'POST',
52
- headers: { 'Content-Type': 'application/json' },
53
- body: JSON.stringify(createBody),
54
- });
55
- assert.equal(createResponse.status, 200);
56
- const createPayload = await createResponse.json() as Record<string, unknown>;
57
- assert.equal(createPayload.deviceId, 'device-1');
58
- assert.equal(typeof createPayload.pairToken, 'string');
59
-
60
- const reusedNonceResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
61
- method: 'POST',
62
- headers: { 'Content-Type': 'application/json' },
63
- body: JSON.stringify(createBody),
64
- });
65
- assert.equal(reusedNonceResponse.status, 401);
66
- assert.equal((await reusedNonceResponse.json() as Record<string, unknown>).error, 'pair_window_expired');
67
-
68
- const pairedHealth = await waitForHealth(daemon.port);
69
- assert.equal(pairedHealth.pairedDevices, 1);
70
-
71
- const resetResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/reset`, { method: 'POST' });
72
- assert.equal(resetResponse.status, 200);
73
- assert.deepEqual(await resetResponse.json(), { ok: true });
74
-
75
- const resetHealth = await waitForHealth(daemon.port);
76
- assert.equal(resetHealth.pairedDevices, 0);
77
-
78
- const missingFileUrl = new URL(`http://127.0.0.1:${daemon.port}/file`);
79
- missingFileUrl.searchParams.set('token', daemon.token);
80
- missingFileUrl.searchParams.set('path', path.join(daemon.homeDir, 'does-not-exist.png'));
81
-
82
- const missingFileResponse = await fetch(missingFileUrl);
83
- assert.equal(missingFileResponse.status, 404);
84
- assert.equal(await missingFileResponse.text(), 'File not found');
85
- });
86
-
87
- test('pairing endpoints return the relay host and port when VIBELET_RELAY_URL is configured', async (t) => {
88
- const daemon = await spawnDaemon({
89
- env: {
90
- VIBELET_RELAY_URL: 'https://relay.example.com',
91
- },
92
- });
93
- t.after(() => cleanupDaemon(daemon));
94
-
95
- const health = await waitForHealth(daemon.port);
96
- assert.equal(health.canonicalHost, 'relay.example.com');
97
- assert.equal(health.relayUrl, 'https://relay.example.com');
98
-
99
- const pairOpenResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' });
100
- assert.equal(pairOpenResponse.status, 200);
101
- const pairPayload = await pairOpenResponse.json() as PairingQrPayload;
102
- assert.equal(pairPayload.canonicalHost, 'relay.example.com');
103
- assert.equal(pairPayload.port, 443);
104
- assert.ok(Array.isArray(pairPayload.fallbackHosts));
105
- assert.ok((pairPayload.fallbackHosts?.length ?? 0) > 0);
106
- assert.ok(pairPayload.fallbackHosts?.every((host) => host !== pairPayload.canonicalHost));
107
-
108
- const createResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
109
- method: 'POST',
110
- headers: { 'Content-Type': 'application/json' },
111
- body: JSON.stringify({
112
- daemonId: pairPayload.daemonId,
113
- pairNonce: pairPayload.pairNonce,
114
- deviceId: 'device-relay',
115
- deviceName: 'Relay Phone',
116
- }),
117
- });
118
- assert.equal(createResponse.status, 200);
119
-
120
- const createPayload = await createResponse.json() as Record<string, unknown>;
121
- assert.equal(createPayload.canonicalHost, 'relay.example.com');
122
- assert.equal(createPayload.port, 443);
123
- assert.deepEqual(createPayload.fallbackHosts, pairPayload.fallbackHosts);
124
- assert.equal(createPayload.deviceId, 'device-relay');
125
- assert.equal(typeof createPayload.pairToken, 'string');
126
- });
127
-
128
- test('file endpoint resolves relative paths against cwd and returns markdown content type', async (t) => {
129
- const daemon = await spawnDaemon();
130
- t.after(() => cleanupDaemon(daemon));
131
-
132
- const workspaceDir = path.join(daemon.homeDir, 'workspace');
133
- const docsDir = path.join(workspaceDir, 'docs');
134
- mkdirSync(docsDir, { recursive: true });
135
- writeFileSync(path.join(docsDir, 'guide.md'), '# Remote Guide\n', 'utf8');
136
-
137
- const fileUrl = new URL(`http://127.0.0.1:${daemon.port}/file`);
138
- fileUrl.searchParams.set('token', daemon.token);
139
- fileUrl.searchParams.set('path', 'docs/guide.md');
140
- fileUrl.searchParams.set('cwd', workspaceDir);
141
-
142
- const response = await fetch(fileUrl);
143
- assert.equal(response.status, 200);
144
- assert.equal(response.headers.get('content-type'), 'text/markdown; charset=utf-8');
145
- assert.equal(await response.text(), '# Remote Guide\n');
146
- });
147
-
148
- test('upload endpoint stores image files and returns their daemon path', async (t) => {
149
- const daemon = await spawnDaemon();
150
- t.after(() => cleanupDaemon(daemon));
151
-
152
- const uploadUrl = new URL(`http://127.0.0.1:${daemon.port}/upload`);
153
- uploadUrl.searchParams.set('token', daemon.token);
154
-
155
- const response = await fetch(uploadUrl, {
156
- method: 'POST',
157
- headers: { 'Content-Type': 'image/heic' },
158
- body: Buffer.from('fake-heic-data'),
159
- });
160
-
161
- assert.equal(response.status, 200);
162
- const payload = await response.json() as { path: string };
163
- assert.match(payload.path, /\/uploads\/.+\.heic$/);
164
-
165
- const fileResponse = await fetch(`http://127.0.0.1:${daemon.port}/file?token=${encodeURIComponent(daemon.token)}&path=${encodeURIComponent(payload.path)}`);
166
- assert.equal(fileResponse.status, 200);
167
- assert.equal(fileResponse.headers.get('content-type'), 'image/heic');
168
- assert.equal(await fileResponse.text(), 'fake-heic-data');
169
- });
170
-
171
- test('auth.hello can authenticate a device, dirs.list omits hidden entries, and log.report is forwarded to audit/stdout', async (t) => {
172
- const daemon = await spawnDaemon();
173
- t.after(() => cleanupDaemon(daemon));
174
-
175
- const pairPayload = await (await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' })).json() as PairingQrPayload;
176
- const createResponse = await fetch(`http://127.0.0.1:${daemon.port}/pair/create`, {
177
- method: 'POST',
178
- headers: { 'Content-Type': 'application/json' },
179
- body: JSON.stringify({
180
- daemonId: pairPayload.daemonId,
181
- pairNonce: pairPayload.pairNonce,
182
- deviceId: 'device-auth',
183
- deviceName: 'Auth Device',
184
- }),
185
- });
186
- const pairing = await createResponse.json() as Record<string, unknown>;
187
- const pairToken = String(pairing.pairToken);
188
-
189
- const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}`);
190
- t.after(() => {
191
- try {
192
- ws.close();
193
- } catch {}
194
- });
195
-
196
- ws.send(JSON.stringify({
197
- action: 'auth.hello',
198
- id: 'auth-1',
199
- daemonId: pairPayload.daemonId,
200
- deviceId: 'device-auth',
201
- pairToken,
202
- }));
203
-
204
- const authResponse = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'auth-1');
205
- assert.equal(authResponse.ok, true);
206
- assert.deepEqual(authResponse.data, {
207
- daemonId: pairPayload.daemonId,
208
- displayName: authResponse.data && typeof authResponse.data === 'object' ? (authResponse.data as Record<string, unknown>).displayName : undefined,
209
- });
210
-
211
- const dirsRoot = path.join(daemon.homeDir, 'dirs');
212
- mkdirSync(path.join(dirsRoot, 'alpha'), { recursive: true });
213
- writeFileSync(path.join(dirsRoot, 'z-last.txt'), 'z');
214
- writeFileSync(path.join(dirsRoot, 'b.txt'), 'b');
215
- writeFileSync(path.join(dirsRoot, '.hidden.txt'), 'hidden');
216
-
217
- ws.send(JSON.stringify({
218
- action: 'dirs.list',
219
- id: 'dirs-1',
220
- path: dirsRoot,
221
- }));
222
-
223
- const dirsResponse = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'dirs-1');
224
- assert.equal(dirsResponse.ok, true);
225
- assert.deepEqual((dirsResponse.data as { entries: Array<{ name: string; isDirectory: boolean }> }).entries, [
226
- { name: 'alpha', isDirectory: true },
227
- { name: 'b.txt', isDirectory: false },
228
- { name: 'z-last.txt', isDirectory: false },
229
- ]);
230
-
231
- ws.send(JSON.stringify({
232
- action: 'log.report',
233
- id: 'log-1',
234
- entries: [
235
- {
236
- level: 'info',
237
- event: 'app.session.create',
238
- data: { agent: 'codex', cwd: '/tmp/repo' },
239
- ts: '2026-03-29T00:00:00.000Z',
240
- },
241
- ],
242
- }));
243
-
244
- const logResponse = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'log-1');
245
- assert.equal(logResponse.ok, true);
246
-
247
- const auditLog = await readAuditLog(daemon.homeDir);
248
- assert.match(auditLog, /"event":"app\.log"/);
249
- assert.match(auditLog, /"appEvent":"app\.session\.create"/);
250
- assert.match(auditLog, /"agent":"codex"/);
251
-
252
- const stdout = await waitForStdout(daemon, /\[app\] app\.session\.create/);
253
- assert.match(stdout, /\[app\] app\.session\.create/);
254
- });
255
-
256
- test('dirs.list expands tilde paths before reading the directory', async (t) => {
257
- const daemon = await spawnDaemon();
258
- t.after(() => cleanupDaemon(daemon));
259
-
260
- const tildeDir = path.join(daemon.homeDir, 'tilde-browse');
261
- mkdirSync(path.join(tildeDir, 'nested'), { recursive: true });
262
- writeFileSync(path.join(tildeDir, 'notes.txt'), 'browse me', 'utf8');
263
-
264
- const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${encodeURIComponent(daemon.token)}`);
265
- t.after(() => {
266
- try {
267
- ws.close();
268
- } catch {}
269
- });
270
-
271
- ws.send(JSON.stringify({
272
- action: 'dirs.list',
273
- id: 'dirs-tilde',
274
- path: '~/tilde-browse',
275
- }));
276
-
277
- const response = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'dirs-tilde');
278
- assert.equal(response.ok, true);
279
- assert.deepEqual((response.data as { entries: Array<{ name: string; isDirectory: boolean }> }).entries, [
280
- { name: 'nested', isDirectory: true },
281
- { name: 'notes.txt', isDirectory: false },
282
- ]);
283
- });
284
-
285
- test('dirs.list resolves relative paths against the provided cwd', async (t) => {
286
- const daemon = await spawnDaemon();
287
- t.after(() => cleanupDaemon(daemon));
288
-
289
- const repoRoot = path.join(daemon.homeDir, 'repo-root');
290
- const assetsDir = path.join(repoRoot, 'apps', 'aino-desktop', 'src', 'renderer', 'src', 'assets');
291
- mkdirSync(assetsDir, { recursive: true });
292
- writeFileSync(path.join(assetsDir, 'wavy-lines.svg'), '<svg />', 'utf8');
293
-
294
- const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}?token=${encodeURIComponent(daemon.token)}`);
295
- t.after(() => {
296
- try {
297
- ws.close();
298
- } catch {}
299
- });
300
-
301
- ws.send(JSON.stringify({
302
- action: 'dirs.list',
303
- id: 'dirs-relative',
304
- path: 'apps/aino-desktop/src/renderer/src/assets/',
305
- cwd: repoRoot,
306
- }));
307
-
308
- const response = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'dirs-relative');
309
- assert.equal(response.ok, true);
310
- assert.deepEqual((response.data as { entries: Array<{ name: string; isDirectory: boolean }> }).entries, [
311
- { name: 'wavy-lines.svg', isDirectory: false },
312
- ]);
313
- });
314
-
315
- test('auth.hello rejects invalid pair tokens and closes the websocket', async (t) => {
316
- const daemon = await spawnDaemon();
317
- t.after(() => cleanupDaemon(daemon));
318
-
319
- const pairPayload = await (await fetch(`http://127.0.0.1:${daemon.port}/pair/open`, { method: 'POST' })).json() as PairingQrPayload;
320
- const ws = await openWebSocket(`ws://127.0.0.1:${daemon.port}`);
321
-
322
- ws.send(JSON.stringify({
323
- action: 'auth.hello',
324
- id: 'auth-fail',
325
- daemonId: pairPayload.daemonId,
326
- deviceId: 'device-auth',
327
- pairToken: 'invalid-token',
328
- }));
329
-
330
- const response = await waitForWebSocketMessage(ws, (message) => message.type === 'response' && message.id === 'auth-fail');
331
- assert.equal(response.ok, false);
332
- assert.equal(response.error, 'auth_failed');
333
-
334
- const closeCode = await waitForWebSocketClose(ws);
335
- assert.equal(closeCode, 4001);
336
- });