claude-live 0.4.8 → 1.1.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 (53) hide show
  1. package/bin/claude-live.js +36 -20
  2. package/package.json +13 -26
  3. package/.claude-plugin/hooks/hooks.json +0 -126
  4. package/.claude-plugin/marketplace.json +0 -27
  5. package/.claude-plugin/plugin.json +0 -14
  6. package/README.md +0 -60
  7. package/bin/check-and-restart.js +0 -63
  8. package/bin/send-hook.sh +0 -34
  9. package/bin/start-server.sh +0 -33
  10. package/client/dist/assets/index-B1BUdq7a.js +0 -47
  11. package/client/dist/assets/index-DjcKbX6b.css +0 -1
  12. package/client/dist/chords/chord_01.wav +0 -0
  13. package/client/dist/chords/chord_02.wav +0 -0
  14. package/client/dist/chords/chord_03.wav +0 -0
  15. package/client/dist/chords/chord_04.wav +0 -0
  16. package/client/dist/chords/chord_05.wav +0 -0
  17. package/client/dist/chords/chord_06.wav +0 -0
  18. package/client/dist/chords/chord_07.wav +0 -0
  19. package/client/dist/chords/chord_08.wav +0 -0
  20. package/client/dist/chords/chord_09.wav +0 -0
  21. package/client/dist/chords/chord_10.wav +0 -0
  22. package/client/dist/chords/chord_11.wav +0 -0
  23. package/client/dist/chords/chord_12.wav +0 -0
  24. package/client/dist/chords/chord_13.wav +0 -0
  25. package/client/dist/chords/chord_14.wav +0 -0
  26. package/client/dist/chords/chord_15.wav +0 -0
  27. package/client/dist/chords/chord_16.wav +0 -0
  28. package/client/dist/index.html +0 -18
  29. package/client/public/chords/chord_01.wav +0 -0
  30. package/client/public/chords/chord_02.wav +0 -0
  31. package/client/public/chords/chord_03.wav +0 -0
  32. package/client/public/chords/chord_04.wav +0 -0
  33. package/client/public/chords/chord_05.wav +0 -0
  34. package/client/public/chords/chord_06.wav +0 -0
  35. package/client/public/chords/chord_07.wav +0 -0
  36. package/client/public/chords/chord_08.wav +0 -0
  37. package/client/public/chords/chord_09.wav +0 -0
  38. package/client/public/chords/chord_10.wav +0 -0
  39. package/client/public/chords/chord_11.wav +0 -0
  40. package/client/public/chords/chord_12.wav +0 -0
  41. package/client/public/chords/chord_13.wav +0 -0
  42. package/client/public/chords/chord_14.wav +0 -0
  43. package/client/public/chords/chord_15.wav +0 -0
  44. package/client/public/chords/chord_16.wav +0 -0
  45. package/commands/claude-live.md +0 -145
  46. package/hooks/hooks.json +0 -166
  47. package/server/index.js +0 -288
  48. package/test-agent-animations-long.js +0 -144
  49. package/test-agent-animations.js +0 -126
  50. package/test-agents.js +0 -61
  51. package/tests/server.test.js +0 -48
  52. package/tests/store.test.ts +0 -141
  53. package/vitest.config.ts +0 -10
package/server/index.js DELETED
@@ -1,288 +0,0 @@
1
- import express from 'express'
2
- import { createHash, randomUUID } from 'crypto'
3
- import { createServer as createHttpServer } from 'http'
4
- import { fileURLToPath } from 'url'
5
- import { join, dirname } from 'path'
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url))
8
- const DIST_DIR = join(__dirname, '../client/dist')
9
- const EVENTS_PER_SESSION = 50 // rolling buffer per session
10
- const SESSION_TIMEOUT_MS = 11 * 60 * 1000 // 11 minutes (server-side cleanup)
11
- const HEARTBEAT_MS = 15000
12
-
13
- // ── Ported from client constants/nodeKeys (JS) ──────────────────────────────
14
-
15
- const TOOL_COLOR_HEX = {
16
- Read: '#4ade80', Edit: '#60a5fa', Write: '#60a5fa',
17
- Bash: '#f59e0b', Grep: '#a78bfa', Glob: '#a78bfa',
18
- WebFetch: '#f472b6', Stop: '#888888', Notification: '#34d399',
19
- }
20
- const DEFAULT_HEX = '#555555'
21
- const FILE_TOOLS = new Set(['Read', 'Edit', 'Write', 'Glob', 'Grep'])
22
-
23
- function nodeKeyFor(event) {
24
- const t = event.tool_name
25
- if (!t) {
26
- if (event.hook_event_name === 'Stop') return 'session:stop'
27
- if (event.hook_event_name === 'Notification') {
28
- const msg = event.tool_input?.message || ''
29
- return `notification:${msg.slice(0, 20)}`
30
- }
31
- return null
32
- }
33
- const input = event.tool_input || {}
34
- if (FILE_TOOLS.has(t)) {
35
- const fp = input.file_path || input.path || null
36
- return fp ? `file:${fp}` : null
37
- }
38
- if (t === 'Bash') return `bash:${input.command || ''}`
39
- if (t === 'WebFetch') {
40
- try { return `web:${new URL(input.url || '').hostname}` } catch { return 'web:unknown' }
41
- }
42
- return `tool:${t}`
43
- }
44
-
45
- function nodeTypeFor(event) {
46
- const t = event.tool_name
47
- if (FILE_TOOLS.has(t || '')) return 'file'
48
- if (t === 'Bash') return 'bash'
49
- if (t === 'WebFetch') return 'web'
50
- if (event.hook_event_name === 'Stop') return 'stop'
51
- if (event.hook_event_name === 'Notification') return 'notification'
52
- return 'tool'
53
- }
54
-
55
- function labelFor(event) {
56
- const t = event.tool_name
57
- const input = event.tool_input || {}
58
- if (FILE_TOOLS.has(t || '')) {
59
- const fp = input.file_path || input.path || ''
60
- return fp.split('/').pop() || fp
61
- }
62
- if (t === 'Bash') return `$ ${(input.command || '').slice(0, 22)}`
63
- if (t === 'WebFetch') { try { return `↗ ${new URL(input.url || '').hostname}` } catch { return '↗ web' } }
64
- if (event.hook_event_name === 'Stop') return '✓ done'
65
- if (event.hook_event_name === 'Notification') return (input.message || 'notification').slice(0, 24)
66
- return t || event.hook_event_name || '?'
67
- }
68
-
69
- // ── State snapshot computation ───────────────────────────────────────────────
70
-
71
- // Compute a lightweight state snapshot from a session's event buffer.
72
- // Returns null if the session has no events.
73
- function computeSessionSnapshot(session_id, events) {
74
- if (events.length === 0) return null
75
-
76
- let label = null
77
- let cwd = null
78
- let stopping = false
79
- let eventCount = events.length
80
-
81
- // file nodes: persistent, grow with each touch
82
- const fileNodes = new Map() // key → { key, nodeType, label, colorHex, baseRadius }
83
- // ephemeral keys seen in recent N events (they decay fast so only show recent ones)
84
- const RECENT_N = 15
85
- const recentEphemeralKeys = new Set()
86
-
87
- for (let i = 0; i < events.length; i++) {
88
- const event = events[i]
89
-
90
- if (event.cwd) cwd = event.cwd
91
- if (cwd && (!label || label.length <= 8)) {
92
- const parts = cwd.split('/').filter(Boolean)
93
- label = parts[parts.length - 1] || null
94
- }
95
-
96
- if (event.hook_event_name === 'Stop') {
97
- stopping = true
98
- } else if (event.hook_event_name !== 'SessionEnd') {
99
- stopping = false
100
- }
101
-
102
- const key = nodeKeyFor(event)
103
- if (!key) continue
104
- const type = nodeTypeFor(event)
105
- const isFile = type === 'file'
106
- const colorHex = TOOL_COLOR_HEX[event.tool_name || event.hook_event_name] ?? DEFAULT_HEX
107
-
108
- if (isFile) {
109
- if (fileNodes.has(key)) {
110
- fileNodes.get(key).baseRadius = Math.min(8, fileNodes.get(key).baseRadius + 0.3)
111
- } else {
112
- fileNodes.set(key, { key, nodeType: type, label: labelFor(event), colorHex, baseRadius: 2.5 })
113
- }
114
- } else if (i >= events.length - RECENT_N) {
115
- recentEphemeralKeys.add(key)
116
- }
117
- }
118
-
119
- // Build node list: files first, then recent ephemerals
120
- const nodes = [...fileNodes.values()]
121
-
122
- // Add recent ephemerals (not already in files)
123
- const fileKeySet = new Set(fileNodes.keys())
124
- for (let i = Math.max(0, events.length - RECENT_N); i < events.length; i++) {
125
- const event = events[i]
126
- const key = nodeKeyFor(event)
127
- if (!key || fileKeySet.has(key)) continue
128
- const type = nodeTypeFor(event)
129
- const colorHex = TOOL_COLOR_HEX[event.tool_name || event.hook_event_name] ?? DEFAULT_HEX
130
- if (!nodes.find(n => n.key === key)) {
131
- nodes.push({ key, nodeType: type, label: labelFor(event), colorHex, baseRadius: 4 })
132
- }
133
- }
134
-
135
- // Ring assignment is handled by the client (server just sends node data)
136
- return {
137
- session_id,
138
- label: label || session_id.slice(0, 8),
139
- cwd,
140
- stopping,
141
- eventCount,
142
- nodes, // [{ key, nodeType, label, colorHex, baseRadius }]
143
- }
144
- }
145
-
146
- // ── Server ───────────────────────────────────────────────────────────────────
147
-
148
- function makeSessionId(ip, ts) {
149
- return 'unknown-' + createHash('sha1').update(ip + ts).digest('hex').slice(0, 8)
150
- }
151
-
152
- function normalizeEvent(raw, remoteIp) {
153
- const session_id = raw.session_id?.trim() || makeSessionId(remoteIp, Date.now().toString())
154
- return {
155
- id: randomUUID(),
156
- session_id,
157
- timestamp: Date.now(),
158
- hook_event_name: raw.hook_event_name ?? null,
159
- tool_name: raw.tool_name ?? null,
160
- tool_input: raw.tool_input ?? null,
161
- tool_response: raw.tool_response ?? null,
162
- agent_id: raw.agent_id ?? null,
163
- agent_type: raw.agent_type ?? null,
164
- cwd: raw.cwd ?? null,
165
- error: raw.error ?? null,
166
- tool_use_id: raw.tool_use_id ?? null,
167
- prompt: raw.prompt ?? null,
168
- model: raw.model ?? null,
169
- source: raw.source ?? null,
170
- reason: raw.reason ?? null,
171
- permission_mode: raw.permission_mode ?? null,
172
- is_interrupt: raw.is_interrupt ?? null,
173
- trigger: raw.trigger ?? null,
174
- compact_summary: raw.compact_summary ?? null,
175
- last_assistant_message: raw.last_assistant_message ?? null,
176
- notification_type: raw.notification_type ?? null,
177
- title: raw.title ?? null,
178
- agent_transcript_path: raw.agent_transcript_path ?? null,
179
- file_path_loaded: raw.file_path ?? null,
180
- memory_type: raw.memory_type ?? null,
181
- }
182
- }
183
-
184
- export function createServer({ port = 43451 } = {}) {
185
- const app = express()
186
- const clients = new Set()
187
- const sessionBuffers = new Map() // session_id -> { events: [], lastEventTime: number }
188
-
189
- function getOrCreateSession(sessionId) {
190
- if (!sessionBuffers.has(sessionId)) {
191
- sessionBuffers.set(sessionId, { events: [], lastEventTime: Date.now() })
192
- }
193
- return sessionBuffers.get(sessionId)
194
- }
195
-
196
- function cleanupStaleSessions() {
197
- const now = Date.now()
198
- for (const [sid, session] of sessionBuffers) {
199
- if (now - session.lastEventTime > SESSION_TIMEOUT_MS) {
200
- sessionBuffers.delete(sid)
201
- }
202
- }
203
- }
204
-
205
- app.use(express.json())
206
-
207
- app.post('/hook', (req, res) => {
208
- const raw = req.body
209
- if (!raw || typeof raw !== 'object') return res.status(400).json({ error: 'invalid json' })
210
- const event = normalizeEvent(raw, req.ip)
211
- const session = getOrCreateSession(event.session_id)
212
- session.events.push(event)
213
- session.lastEventTime = Date.now()
214
- if (session.events.length > EVENTS_PER_SESSION) session.events.shift()
215
-
216
- const data = `data: ${JSON.stringify(event)}\n\n`
217
- for (const client of clients) {
218
- try { client.write(data) } catch { clients.delete(client) }
219
- }
220
- res.json({ ok: true })
221
- })
222
-
223
- app.get('/events', (req, res) => {
224
- res.setHeader('Content-Type', 'text/event-stream')
225
- res.setHeader('Cache-Control', 'no-cache')
226
- res.setHeader('Connection', 'keep-alive')
227
- res.flushHeaders()
228
-
229
- // Send state snapshot instead of replaying raw events
230
- const sessions = []
231
- for (const [sid, session] of sessionBuffers) {
232
- const snap = computeSessionSnapshot(sid, session.events)
233
- if (snap) sessions.push(snap)
234
- }
235
- res.write(`data: ${JSON.stringify({ type: 'state_snapshot', sessions })}\n\n`)
236
-
237
- clients.add(res)
238
- const heartbeat = setInterval(() => {
239
- try { res.write(': heartbeat\n\n') } catch { clients.delete(res); clearInterval(heartbeat) }
240
- }, HEARTBEAT_MS)
241
- req.on('close', () => { clients.delete(res); clearInterval(heartbeat) })
242
- res.on('error', () => { clients.delete(res); clearInterval(heartbeat) })
243
- })
244
-
245
- // expose session buffers for tests
246
- app.get('/buffer', (req, res) => {
247
- const allEvents = []
248
- for (const session of sessionBuffers.values()) {
249
- allEvents.push(...session.events)
250
- }
251
- res.json(allEvents)
252
- })
253
-
254
- // cleanup stale sessions periodically
255
- setInterval(cleanupStaleSessions, 60000)
256
-
257
- // serve static build
258
- app.use(express.static(DIST_DIR))
259
-
260
- return new Promise((resolve, reject) => {
261
- const httpServer = createHttpServer(app)
262
- httpServer.listen(port, () => {
263
- const actualPort = httpServer.address().port
264
- resolve({ server: httpServer, port: actualPort, app })
265
- })
266
- httpServer.on('error', reject)
267
- })
268
- }
269
-
270
- // Run standalone
271
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
272
- const desiredPort = parseInt(process.env.PORT || '43451', 10)
273
- let p = desiredPort
274
- const tryStart = async () => {
275
- try {
276
- const { port } = await createServer({ port: p })
277
- console.log(`claude-live server listening on http://localhost:${port}`)
278
- if (p !== desiredPort) {
279
- console.log(`(port ${desiredPort} was in use, using ${port} instead)`)
280
- console.log(`Update your hook URL to: http://localhost:${port}/hook`)
281
- }
282
- } catch (e) {
283
- if (e.code === 'EADDRINUSE') { p++; tryStart() }
284
- else { console.error(e); process.exit(1) }
285
- }
286
- }
287
- tryStart()
288
- }
@@ -1,144 +0,0 @@
1
- #!/usr/bin/env node
2
- // Extended test with more agents and longer animation sequence
3
-
4
- import http from 'http';
5
- import { URL } from 'url';
6
-
7
- const SERVER = 'http://localhost:43453';
8
- const SESSION_ID = 'test-session-' + Date.now();
9
-
10
- function sendHook(eventName, data = {}) {
11
- return new Promise((resolve, reject) => {
12
- const payload = JSON.stringify({
13
- hook_event_name: eventName,
14
- session_id: SESSION_ID,
15
- timestamp: Date.now(),
16
- ...data
17
- });
18
-
19
- const url = new URL('/hook', SERVER);
20
- const options = {
21
- hostname: url.hostname,
22
- port: url.port || 80,
23
- path: url.pathname,
24
- method: 'POST',
25
- headers: {
26
- 'Content-Type': 'application/json',
27
- 'Content-Length': Buffer.byteLength(payload)
28
- }
29
- };
30
-
31
- const req = http.request(options, (res) => {
32
- resolve(`${eventName}: ${res.statusCode}`);
33
- });
34
-
35
- req.on('error', reject);
36
- req.write(payload);
37
- req.end();
38
- });
39
- }
40
-
41
- async function wait(ms) {
42
- return new Promise(r => setTimeout(r, ms));
43
- }
44
-
45
- async function agentAction(agentId, tool, filePath, delay = 800) {
46
- await sendHook('PreToolUse', {
47
- tool_name: tool,
48
- tool_input: { file_path: filePath, pattern: filePath },
49
- agent_id: agentId
50
- });
51
- await wait(delay);
52
- await sendHook('PostToolUse', {
53
- tool_name: tool,
54
- tool_input: { file_path: filePath, pattern: filePath },
55
- agent_id: agentId
56
- });
57
- }
58
-
59
- async function testAgentAnimationsLong() {
60
- console.log('🚀 Extended Agent Animation Test (4 agents, many actions)\n');
61
-
62
- try {
63
- // Spawn 4 agents
64
- console.log('📍 Spawning 4 agents...');
65
- for (let i = 1; i <= 4; i++) {
66
- await sendHook('SubagentStart', {
67
- agent_id: `agent-${i}`,
68
- agent_type: 'claude-opus-4-6'
69
- });
70
- console.log(` Agent ${i} spawned`);
71
- await wait(300);
72
- }
73
-
74
- await wait(1000);
75
-
76
- // Agent 1: Multiple reads
77
- console.log('\n📖 Agent 1: Multiple read operations...');
78
- await agentAction('agent-1', 'Read', 'client/src/store.ts', 1000);
79
- await wait(500);
80
- await agentAction('agent-1', 'Read', 'client/src/types.ts', 1000);
81
- await wait(500);
82
- await agentAction('agent-1', 'Read', 'README.md', 1000);
83
-
84
- // Agent 2: Grep operations
85
- console.log('\n🔎 Agent 2: Search operations...');
86
- await agentAction('agent-2', 'Grep', 'agentPositionMap', 1000);
87
- await wait(500);
88
- await agentAction('agent-2', 'Grep', 'getAnimationOrigin', 1000);
89
-
90
- // Agent 3: Glob operations
91
- console.log('\n📂 Agent 3: File matching operations...');
92
- await agentAction('agent-3', 'Glob', 'client/src/**/*.tsx', 1200);
93
- await wait(600);
94
- await agentAction('agent-3', 'Glob', '**/*.ts', 1200);
95
-
96
- // Agent 4: Mixed operations
97
- console.log('\n🔄 Agent 4: Mixed operations...');
98
- await agentAction('agent-4', 'Read', 'package.json', 1000);
99
- await wait(500);
100
- await agentAction('agent-4', 'Grep', 'vite', 1000);
101
-
102
- await wait(1000);
103
-
104
- // More concurrent-looking operations
105
- console.log('\n⚡ Agents 1 & 2: Concurrent operations...');
106
- await Promise.all([
107
- agentAction('agent-1', 'Read', 'client/src/canvas/renderer.ts', 1200),
108
- agentAction('agent-2', 'Grep', 'projectile', 1200)
109
- ]);
110
-
111
- await wait(800);
112
-
113
- // Agent 3 continues
114
- console.log('\n📂 Agent 3: More operations...');
115
- await agentAction('agent-3', 'Glob', 'docs/**/*.md', 1200);
116
-
117
- await wait(1000);
118
-
119
- // Final operations before termination
120
- console.log('\n🎬 Final actions before termination...');
121
- await agentAction('agent-1', 'Read', 'client/src/canvas/graph.ts', 1000);
122
- await wait(400);
123
- await agentAction('agent-4', 'Grep', 'animation', 1000);
124
- await wait(400);
125
- await agentAction('agent-2', 'Read', '.gitignore', 1000);
126
-
127
- await wait(1500);
128
-
129
- // Terminate agents one by one
130
- console.log('\n🛑 Terminating agents...');
131
- for (let i = 1; i <= 4; i++) {
132
- await sendHook('SubagentStop', { agent_id: `agent-${i}` });
133
- console.log(` Agent ${i} terminated (star fading out)`);
134
- await wait(600);
135
- }
136
-
137
- console.log('\n✅ Extended test complete! All agents have terminated.\n');
138
- } catch (err) {
139
- console.error('❌ Error:', err.message);
140
- process.exit(1);
141
- }
142
- }
143
-
144
- testAgentAnimationsLong();
@@ -1,126 +0,0 @@
1
- #!/usr/bin/env node
2
- // Test script to trigger agent animation routing by sending hooks to claude-live server
3
-
4
- import http from 'http';
5
- import { URL } from 'url';
6
-
7
- const SERVER = 'http://localhost:43453';
8
- const SESSION_ID = 'test-session-' + Date.now();
9
-
10
- function sendHook(eventName, data = {}) {
11
- return new Promise((resolve, reject) => {
12
- const payload = JSON.stringify({
13
- hook_event_name: eventName,
14
- session_id: SESSION_ID,
15
- timestamp: Date.now(),
16
- ...data
17
- });
18
-
19
- const url = new URL('/hook', SERVER);
20
- const options = {
21
- hostname: url.hostname,
22
- port: url.port || 80,
23
- path: url.pathname,
24
- method: 'POST',
25
- headers: {
26
- 'Content-Type': 'application/json',
27
- 'Content-Length': Buffer.byteLength(payload)
28
- }
29
- };
30
-
31
- const req = http.request(options, (res) => {
32
- resolve(`${eventName}: ${res.statusCode}`);
33
- });
34
-
35
- req.on('error', reject);
36
- req.write(payload);
37
- req.end();
38
- });
39
- }
40
-
41
- async function testAgentAnimations() {
42
- console.log('🚀 Testing Agent Animation Routing...\n');
43
-
44
- try {
45
- // Agent 1: Spawn and read
46
- console.log('📍 Agent 1: Spawning...');
47
- await sendHook('SubagentStart', {
48
- agent_id: 'agent-1',
49
- agent_type: 'claude-opus-4-6'
50
- });
51
- await new Promise(r => setTimeout(r, 500));
52
-
53
- console.log('📖 Agent 1: Reading file...');
54
- await sendHook('PreToolUse', {
55
- tool_name: 'Read',
56
- tool_input: { file_path: 'client/src/store.ts' },
57
- agent_id: 'agent-1'
58
- });
59
- await new Promise(r => setTimeout(r, 300));
60
-
61
- await sendHook('PostToolUse', {
62
- tool_name: 'Read',
63
- tool_input: { file_path: 'client/src/store.ts' },
64
- agent_id: 'agent-1'
65
- });
66
- await new Promise(r => setTimeout(r, 500));
67
-
68
- // Agent 2: Spawn and read
69
- console.log('📍 Agent 2: Spawning...');
70
- await sendHook('SubagentStart', {
71
- agent_id: 'agent-2',
72
- agent_type: 'claude-opus-4-6'
73
- });
74
- await new Promise(r => setTimeout(r, 500));
75
-
76
- console.log('📖 Agent 2: Reading file...');
77
- await sendHook('PreToolUse', {
78
- tool_name: 'Read',
79
- tool_input: { file_path: 'README.md' },
80
- agent_id: 'agent-2'
81
- });
82
- await new Promise(r => setTimeout(r, 300));
83
-
84
- await sendHook('PostToolUse', {
85
- tool_name: 'Read',
86
- tool_input: { file_path: 'README.md' },
87
- agent_id: 'agent-2'
88
- });
89
- await new Promise(r => setTimeout(r, 500));
90
-
91
- // Agent 1: Glob operation
92
- console.log('🔍 Agent 1: Globbing...');
93
- await sendHook('PreToolUse', {
94
- tool_name: 'Glob',
95
- tool_input: { pattern: 'client/src/**/*.tsx' },
96
- agent_id: 'agent-1'
97
- });
98
- await new Promise(r => setTimeout(r, 300));
99
-
100
- await sendHook('PostToolUse', {
101
- tool_name: 'Glob',
102
- tool_input: { pattern: 'client/src/**/*.tsx' },
103
- agent_id: 'agent-1'
104
- });
105
- await new Promise(r => setTimeout(r, 500));
106
-
107
- // Terminate agents
108
- console.log('🛑 Agent 1: Terminating...');
109
- await sendHook('SubagentStop', {
110
- agent_id: 'agent-1'
111
- });
112
- await new Promise(r => setTimeout(r, 300));
113
-
114
- console.log('🛑 Agent 2: Terminating...');
115
- await sendHook('SubagentStop', {
116
- agent_id: 'agent-2'
117
- });
118
-
119
- console.log('\n✅ Test complete! Check http://localhost:43451 for animations.\n');
120
- } catch (err) {
121
- console.error('❌ Error:', err.message);
122
- process.exit(1);
123
- }
124
- }
125
-
126
- testAgentAnimations();
package/test-agents.js DELETED
@@ -1,61 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
-
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
-
7
- // Get all files in current directory
8
- function getAllFiles(dir = ".") {
9
- try {
10
- return fs
11
- .readdirSync(dir)
12
- .filter((f) => fs.statSync(path.join(dir, f)).isFile())
13
- .map((f) => path.join(dir, f));
14
- } catch {
15
- return [];
16
- }
17
- }
18
-
19
- // Pick random items from array
20
- function pickRandom(arr, count) {
21
- const shuffled = [...arr].sort(() => Math.random() - 0.5);
22
- return shuffled.slice(0, Math.min(count, arr.length));
23
- }
24
-
25
- // Agent function
26
- async function agent(id, interval = 2000) {
27
- console.log(`[Agent ${id}] Started`);
28
-
29
- const tick = () => {
30
- const allFiles = getAllFiles();
31
- const randomCount = Math.floor(Math.random() * 3) + 1; // 1-3 files
32
- const randomFiles = pickRandom(allFiles, randomCount);
33
-
34
- console.log(
35
- `[Agent ${id}] Processing ${randomFiles.length} files:`,
36
- randomFiles.join(", ")
37
- );
38
-
39
- // Simulate work
40
- randomFiles.forEach((file) => {
41
- try {
42
- const stats = fs.statSync(file);
43
- console.log(` ├─ ${file}: ${stats.size} bytes`);
44
- } catch {
45
- console.log(` ├─ ${file}: (error reading)`);
46
- }
47
- });
48
- };
49
-
50
- // Run immediately and then every interval
51
- tick();
52
- setInterval(tick, interval);
53
- }
54
-
55
- // Start 3 agents
56
- console.log("Starting 3 test agents...\n");
57
- agent(1);
58
- agent(2);
59
- agent(3);
60
-
61
- console.log("\nPress Ctrl+C to stop\n");
@@ -1,48 +0,0 @@
1
- // tests/server.test.js
2
- import { describe, it, expect, beforeAll, afterAll } from 'vitest'
3
- import { createServer } from '../server/index.js'
4
-
5
- describe('SSE server', () => {
6
- let server, port
7
-
8
- beforeAll(async () => {
9
- ({ server, port } = await createServer({ port: 0 })) // port 0 = OS assigns
10
- })
11
-
12
- afterAll(() => server.close())
13
-
14
- it('returns 200 on POST /hook with valid JSON', async () => {
15
- const res = await fetch(`http://localhost:${port}/hook`, {
16
- method: 'POST',
17
- headers: { 'Content-Type': 'application/json' },
18
- body: JSON.stringify({
19
- hook_event_name: 'PreToolUse',
20
- tool_name: 'Read',
21
- tool_input: { file_path: '/src/foo.ts' },
22
- session_id: 'test-session'
23
- })
24
- })
25
- expect(res.status).toBe(200)
26
- })
27
-
28
- it('normalizes missing session_id to fallback', async () => {
29
- const res = await fetch(`http://localhost:${port}/hook`, {
30
- method: 'POST',
31
- headers: { 'Content-Type': 'application/json' },
32
- body: JSON.stringify({ hook_event_name: 'Stop' })
33
- })
34
- expect(res.status).toBe(200)
35
- // buffer should have an event with a non-empty session_id
36
- const events = await fetch(`http://localhost:${port}/buffer`).then(r => r.json())
37
- expect(events[events.length - 1].session_id).toBeTruthy()
38
- })
39
-
40
- it('returns 400 on invalid JSON', async () => {
41
- const res = await fetch(`http://localhost:${port}/hook`, {
42
- method: 'POST',
43
- headers: { 'Content-Type': 'application/json' },
44
- body: 'not json'
45
- })
46
- expect(res.status).toBe(400)
47
- })
48
- })