clew-code 0.2.28 → 0.2.30

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 (52) hide show
  1. package/README.md +38 -28
  2. package/bin/clew.cjs +2 -2
  3. package/dist/main.js +2318 -2331
  4. package/docs/architecture.html +90 -91
  5. package/docs/changelog.html +141 -150
  6. package/docs/cli-reference.html +89 -90
  7. package/docs/commands.html +10 -9
  8. package/docs/daemon.html +62 -62
  9. package/docs/generated/providers.html +625 -0
  10. package/docs/generated/tools.html +559 -0
  11. package/docs/index.html +10 -13
  12. package/docs/personal-profile.html +3 -3
  13. package/docs/providers.html +625 -87
  14. package/docs/quick-start.html +81 -81
  15. package/docs/tools.html +551 -175
  16. package/docs/troubleshooting.html +86 -86
  17. package/package.json +165 -165
  18. package/scripts/auto-close-duplicates.ts +277 -0
  19. package/scripts/backfill-duplicate-comments.ts +213 -0
  20. package/scripts/codegraph.ts +221 -0
  21. package/scripts/comment-on-duplicates.sh +95 -0
  22. package/scripts/edit-issue-labels.sh +84 -0
  23. package/scripts/final-peer-rename.mjs +46 -0
  24. package/scripts/fix-encoding.mjs +83 -0
  25. package/scripts/fix-inconsistencies.mjs +67 -0
  26. package/scripts/generate-docs.ts +307 -0
  27. package/scripts/gh.sh +96 -0
  28. package/scripts/install.ps1 +37 -0
  29. package/scripts/install.sh +49 -0
  30. package/scripts/issue-lifecycle.ts +38 -0
  31. package/scripts/lifecycle-comment.ts +53 -0
  32. package/scripts/normalize-html.mjs +37 -0
  33. package/scripts/preload.ts +159 -0
  34. package/scripts/rename-content-peer-to-swarm.mjs +67 -0
  35. package/scripts/rename-hooks-mesh.mjs +6 -0
  36. package/scripts/rename-peer-to-swarm.mjs +62 -0
  37. package/scripts/run_devcontainer_claude_code.ps1 +152 -0
  38. package/scripts/scrape.py +82 -0
  39. package/scripts/session.ts +195 -0
  40. package/scripts/sweep.ts +168 -0
  41. package/scripts/write-discovery-test.cjs +93 -0
  42. package/scripts/write-discovery-test2.cjs +65 -0
  43. package/scripts/write-server-final.cjs +108 -0
  44. package/scripts/write-server-test.cjs +69 -0
  45. package/scripts/write-server-test2.cjs +99 -0
  46. package/scripts/write-server-test3.cjs +59 -0
  47. package/scripts/write-server-test4.cjs +108 -0
  48. package/scripts/write-server-v2.cjs +142 -0
  49. package/scripts/write-server-v2b.cjs +129 -0
  50. package/scripts/write-server-v2c.cjs +136 -0
  51. package/scripts/write-store-test.cjs +12 -0
  52. package/scripts/write-store-test2.cjs +163 -0
@@ -0,0 +1,65 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshDiscovery.test.ts';
3
+ const code = `import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
4
+ import { MeshDiscovery, getGlobalDiscovery } from './MeshDiscovery.js';
5
+
6
+ describe('MeshDiscovery', () => {
7
+ let discovery;
8
+
9
+ afterEach(() => {
10
+ if (discovery) discovery.close();
11
+ });
12
+
13
+ test('constructor generates localId', () => {
14
+ discovery = new MeshDiscovery();
15
+ expect(discovery.meshId).toBeTruthy();
16
+ expect(discovery.meshId.split('-').length).toBeGreaterThanOrEqual(3);
17
+ });
18
+
19
+ test('constructor accepts callbacks', () => {
20
+ let c = false;
21
+ discovery = new MeshDiscovery({ onPeerDiscovered: () => { c = true; } });
22
+ expect(discovery).toBeDefined();
23
+ });
24
+
25
+ test('setLocalName updates hostname and ID', () => {
26
+ discovery = new MeshDiscovery();
27
+ const oid = discovery.meshId;
28
+ discovery.setLocalName('custom');
29
+ expect(discovery.hostname).toBe('custom');
30
+ expect(discovery.meshId).not.toBe(oid);
31
+ expect(discovery.meshId).toContain('custom');
32
+ });
33
+
34
+ test('startAdvertising then close', async () => {
35
+ discovery = new MeshDiscovery();
36
+ expect(discovery.isSharing).toBeFalse();
37
+ await discovery.startAdvertising(9999, '/cwd');
38
+ expect(discovery.isSharing).toBeTrue();
39
+ const mid = discovery.meshId;
40
+ expect(mid).toBeTruthy();
41
+ });
42
+
43
+ test('getPeers empty initially', () => {
44
+ discovery = new MeshDiscovery();
45
+ expect(discovery.getPeers()).toEqual([]);
46
+ });
47
+
48
+ test('getPeer undefined for unknown', () => {
49
+ discovery = new MeshDiscovery();
50
+ expect(discovery.getPeer('nobody')).toBeUndefined();
51
+ });
52
+
53
+ test('close stops advertising', () => {
54
+ discovery = new MeshDiscovery();
55
+ discovery.close();
56
+ expect(discovery.isSharing).toBeFalse();
57
+ });
58
+
59
+ test('getGlobalDiscovery singleton', () => {
60
+ expect(getGlobalDiscovery()).toBe(getGlobalDiscovery());
61
+ });
62
+ });
63
+ `;
64
+ fs.writeFileSync(p, code, 'utf8');
65
+ console.log('MeshDiscovery.test.ts rewritten');
@@ -0,0 +1,108 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
3
+ const code = `import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
4
+ import { MeshServer, getGlobalMeshServer } from './MeshServer.js';
5
+
6
+ function mi(o) { return { id:'tp', hostname:'th', ip:'127.0.0.1', port:0, cwd:'/r', version:'t', lastSeen:Date.now(), status:'online', ...o }; }
7
+
8
+ describe('MeshServer', () => {
9
+ let server;
10
+ beforeEach(() => { server = new MeshServer(); });
11
+ afterEach(() => { server.stop(); });
12
+
13
+ test('constructor creates instance', () => {
14
+ expect(server).toBeInstanceOf(MeshServer);
15
+ expect(server.port).toBe(0);
16
+ });
17
+
18
+ test('constructor accepts callbacks', () => {
19
+ const s = new MeshServer({ onMessage: () => {} });
20
+ expect(s).toBeInstanceOf(MeshServer);
21
+ s.stop();
22
+ });
23
+
24
+ test('getGlobalMeshServer singleton', () => {
25
+ expect(getGlobalMeshServer()).toBe(getGlobalMeshServer());
26
+ });
27
+
28
+ test('setCallbacks merges', () => {
29
+ server.setCallbacks({ onMessage: () => {} });
30
+ server.setCallbacks({ onTodo: () => {} });
31
+ expect(server).toBeInstanceOf(MeshServer);
32
+ });
33
+
34
+ test('isBusy/queueDepth initially false/0', () => {
35
+ expect(server.isBusy).toBeFalse();
36
+ expect(server.queueDepth).toBe(0);
37
+ });
38
+
39
+ test('getTodos empty, updateTodoStatus false for unknown', () => {
40
+ expect(server.getTodos()).toEqual([]);
41
+ expect(server.updateTodoStatus('x','done')).toBeFalse();
42
+ });
43
+
44
+ test('start returns port', async () => {
45
+ const port = await server.start(mi());
46
+ expect(typeof port).toBe('number');
47
+ expect(port).toBeGreaterThan(0);
48
+ expect(server.port).toBe(port);
49
+ });
50
+
51
+ test('start idempotent', async () => {
52
+ const p1 = await server.start(mi());
53
+ const p2 = await server.start(mi());
54
+ expect(p1).toBe(p2);
55
+ });
56
+
57
+ test('stop does not throw', () => {
58
+ expect(() => server.stop()).not.toThrow();
59
+ });
60
+
61
+ test('start-stop-start works', async () => {
62
+ await server.start(mi());
63
+ server.stop();
64
+ const p = await server.start(mi());
65
+ expect(p).toBeGreaterThan(0);
66
+ });
67
+
68
+ test('GET /mesh-info', async () => {
69
+ const port = await server.start(mi({ id:'info-test' }));
70
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
71
+ expect(r.status).toBe(200);
72
+ const d = await r.json();
73
+ expect(d.id).toBe('info-test');
74
+ expect(d.isBusy).toBeFalse();
75
+ });
76
+
77
+ test('POST /mesh-msg fires callback', async () => {
78
+ let m = null;
79
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
80
+ const port = await server.start(mi());
81
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
82
+ method:'POST', body: JSON.stringify({ from:'p2', text:'hello' }),
83
+ });
84
+ expect(r.status).toBe(200);
85
+ expect((await r.json()).ok).toBeTrue();
86
+ expect(m.from).toBe('p2');
87
+ });
88
+
89
+ test('POST /mesh-msg chunk info', async () => {
90
+ let m = null;
91
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
92
+ const port = await server.start(mi());
93
+ await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
94
+ method:'POST', body: JSON.stringify({ from:'p2', chunkGroup:'g1', chunkIndex:0 }),
95
+ });
96
+ expect(m.chunkGroup).toBe('g1');
97
+ });
98
+
99
+ test('POST /mesh-msg defaults', async () => {
100
+ let m = null;
101
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
102
+ const port = await server.start(mi());
103
+ await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, { method:'POST', body: JSON.stringify({}) });
104
+ expect(m.from).toBe('unknown');
105
+ });
106
+ `;
107
+ fs.writeFileSync(p, code, 'utf8');
108
+ console.log('MeshServer.test.ts p1 written');
@@ -0,0 +1,69 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
3
+ const code = `import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
4
+ import { MeshServer, getGlobalMeshServer } from './MeshServer.js';
5
+
6
+ function mi(o) { return { id:'tp', hostname:'th', ip:'127.0.0.1', port:0, cwd:'/r', version:'t', lastSeen:Date.now(), status:'online', ...o }; }
7
+
8
+ describe('MeshServer', () => {
9
+ let server;
10
+ beforeEach(() => { server = new MeshServer(); });
11
+ afterEach(() => { server.stop(); });
12
+
13
+ test('constructor creates instance', () => {
14
+ expect(server).toBeInstanceOf(MeshServer);
15
+ expect(server.port).toBe(0);
16
+ });
17
+
18
+ test('constructor accepts callbacks', () => {
19
+ const s = new MeshServer({ onMessage: () => {} });
20
+ expect(s).toBeInstanceOf(MeshServer);
21
+ s.stop();
22
+ });
23
+
24
+ test('getGlobalMeshServer singleton', () => {
25
+ expect(getGlobalMeshServer()).toBe(getGlobalMeshServer());
26
+ });
27
+
28
+ test('setCallbacks merges', () => {
29
+ server.setCallbacks({ onMessage: () => {} });
30
+ server.setCallbacks({ onTodo: () => {} });
31
+ expect(server).toBeInstanceOf(MeshServer);
32
+ });
33
+
34
+ test('isBusy/queueDepth initially false/0', () => {
35
+ expect(server.isBusy).toBeFalse();
36
+ expect(server.queueDepth).toBe(0);
37
+ });
38
+
39
+ test('getTodos empty, updateTodoStatus false for unknown', () => {
40
+ expect(server.getTodos()).toEqual([]);
41
+ expect(server.updateTodoStatus('x','done')).toBeFalse();
42
+ });
43
+
44
+ test('start returns port', async () => {
45
+ const port = await server.start(mi());
46
+ expect(typeof port).toBe('number');
47
+ expect(port).toBeGreaterThan(0);
48
+ expect(server.port).toBe(port);
49
+ });
50
+
51
+ test('start idempotent', async () => {
52
+ const p1 = await server.start(mi());
53
+ const p2 = await server.start(mi());
54
+ expect(p1).toBe(p2);
55
+ });
56
+
57
+ test('stop does not throw', () => {
58
+ expect(() => server.stop()).not.toThrow();
59
+ });
60
+
61
+ test('start-stop-start works', async () => {
62
+ await server.start(mi());
63
+ server.stop();
64
+ const p = await server.start(mi());
65
+ expect(p).toBeGreaterThan(0);
66
+ });
67
+ `;
68
+ fs.writeFileSync(p, code, 'utf8');
69
+ console.log('MeshServer.test.ts part 1 written');
@@ -0,0 +1,99 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
3
+ const code = `
4
+ test('GET /mesh-info', async () => {
5
+ const port = await server.start(mi({ id:'info-test' }));
6
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
7
+ expect(res.status).toBe(200);
8
+ const d = await res.json();
9
+ expect(d.id).toBe('info-test');
10
+ expect(d.isBusy).toBeFalse();
11
+ });
12
+
13
+ test('POST /mesh-msg fires callback', async () => {
14
+ let m = null;
15
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
16
+ const port = await server.start(mi());
17
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
18
+ method:'POST', headers:{'Content-Type':'application/json'},
19
+ body: JSON.stringify({ from:'p2', fromName:'w2', text:'hello' }),
20
+ });
21
+ expect(res.status).toBe(200);
22
+ expect((await res.json()).ok).toBeTrue();
23
+ expect(m.from).toBe('p2');
24
+ expect(m.text).toBe('hello');
25
+ });
26
+
27
+ test('POST /mesh-msg chunk info', async () => {
28
+ let m = null;
29
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
30
+ const port = await server.start(mi());
31
+ await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
32
+ method:'POST', headers:{'Content-Type':'application/json'},
33
+ body: JSON.stringify({ from:'p2', text:'c', chunkGroup:'g1', chunkIndex:0, chunkTotal:2 }),
34
+ });
35
+ expect(m.chunkGroup).toBe('g1');
36
+ expect(m.chunkIndex).toBe(0);
37
+ expect(m.chunkTotal).toBe(2);
38
+ });
39
+
40
+ test('POST /mesh-msg defaults', async () => {
41
+ let m = null;
42
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
43
+ const port = await server.start(mi());
44
+ await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
45
+ method:'POST', headers:{'Content-Type':'application/json'},
46
+ body: JSON.stringify({}),
47
+ });
48
+ expect(m.from).toBe('unknown');
49
+ expect(m.text).toBe('');
50
+ });
51
+
52
+ test('POST /mesh-todo fires callback', async () => {
53
+ let t = null;
54
+ server.setCallbacks({ onTodo: todo => { t = todo; } });
55
+ const port = await server.start(mi());
56
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
57
+ method:'POST', headers:{'Content-Type':'application/json'},
58
+ body: JSON.stringify({ from:'p2', message:'fix' }),
59
+ });
60
+ expect(res.status).toBe(200);
61
+ expect((await res.json()).ok).toBeTrue();
62
+ expect(t.message).toBe('fix');
63
+ });
64
+
65
+ test('POST /mesh-todo defaults', async () => {
66
+ let t = null;
67
+ server.setCallbacks({ onTodo: todo => { t = todo; } });
68
+ const port = await server.start(mi());
69
+ await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
70
+ method:'POST', headers:{'Content-Type':'application/json'},
71
+ body: JSON.stringify({}),
72
+ });
73
+ expect(t.from).toBe('unknown');
74
+ expect(t.message).toBe('');
75
+ });
76
+
77
+ test('POST /mesh-exec with onExec', async () => {
78
+ server.setCallbacks({ onExec: async () => ({ stdout:'ok', stderr:'', exitCode:0 }) });
79
+ const port = await server.start(mi());
80
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
81
+ method:'POST', headers:{'Content-Type':'application/json'},
82
+ body: JSON.stringify({ command:'echo hi', from:'t' }),
83
+ });
84
+ expect(res.status).toBe(200);
85
+ expect((await res.json()).running).toBeTrue();
86
+ });
87
+
88
+ test('POST /mesh-exec 501 without onExec', async () => {
89
+ const port = await server.start(mi());
90
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, {
91
+ method:'POST', headers:{'Content-Type':'application/json'},
92
+ body: JSON.stringify({ command:'hi', from:'t' }),
93
+ });
94
+ expect(res.status).toBe(501);
95
+ expect((await res.json()).stderr).toBe('Exec not supported');
96
+ });
97
+ `;
98
+ fs.appendFileSync(p, code, 'utf8');
99
+ console.log('MeshServer.test.ts part 2 appended');
@@ -0,0 +1,59 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
3
+ const code = `
4
+ test('POST /mesh-exec queues when busy', async () => {
5
+ let resolve;
6
+ server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
7
+ const port = await server.start(mi());
8
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'long', from:'t' }) });
9
+ expect(server.isBusy).toBeTrue();
10
+ const r2 = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'q', from:'t' }) });
11
+ expect(r2.status).toBe(202);
12
+ expect((await r2.json()).queued).toBeTrue();
13
+ expect(server.queueDepth).toBe(1);
14
+ if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
15
+ await new Promise(r => setTimeout(r, 50));
16
+ });
17
+
18
+ test('POST /mesh-exec 503 queue full', async () => {
19
+ let resolve;
20
+ server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
21
+ const port = await server.start(mi());
22
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'first', from:'t' }) });
23
+ const max = server.maxQueueSize || 50;
24
+ for (let i=0; i<max; i++) {
25
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:\`t-\${i}\`, from:'t' }) });
26
+ }
27
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'x', from:'t' }) });
28
+ expect(r.status).toBe(503);
29
+ expect((await r.json()).error).toBe('Queue full');
30
+ if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
31
+ await new Promise(r => setTimeout(r, 50));
32
+ });
33
+
34
+ test('GET /mesh-queue-status', async () => {
35
+ const port = await server.start(mi());
36
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-status\`);
37
+ expect(r.status).toBe(200);
38
+ const d = await r.json();
39
+ expect(d.isBusy).toBeFalse();
40
+ expect(d.queueDepth).toBe(0);
41
+ expect(d.currentTask).toBeNull();
42
+ });
43
+
44
+ test('POST /mesh-queue-cancel', async () => {
45
+ let resolve;
46
+ server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
47
+ const port = await server.start(mi());
48
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'first', from:'t' }) });
49
+ const qr = await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'x', from:'t' }) });
50
+ const tid = (await qr.json()).id;
51
+ const cr = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id: tid }) });
52
+ expect(cr.status).toBe(200);
53
+ expect((await cr.json()).ok).toBeTrue();
54
+ if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
55
+ await new Promise(r => setTimeout(r, 50));
56
+ });
57
+ `;
58
+ fs.appendFileSync(p, code, 'utf8');
59
+ console.log('MeshServer.test.ts part 3a appended');
@@ -0,0 +1,108 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
3
+ const code = `
4
+ test('POST /mesh-queue-cancel 404', async () => {
5
+ const port = await server.start(mi());
6
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ id:'nope' }) });
7
+ expect(r.status).toBe(404);
8
+ });
9
+
10
+ test('POST /mesh-queue-cancel 400', async () => {
11
+ const port = await server.start(mi());
12
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel\`, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({}) });
13
+ expect(r.status).toBe(400);
14
+ });
15
+
16
+ test('POST /mesh-queue-cancel-all', async () => {
17
+ let resolve;
18
+ server.setCallbacks({ onExec: async () => new Promise(r => { resolve = r; }) });
19
+ const port = await server.start(mi());
20
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'first', from:'t' }) });
21
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'t1', from:'t' }) });
22
+ await fetch(\`http://127.0.0.1:\${port}/mesh-exec\`, { method:'POST', body: JSON.stringify({ command:'t2', from:'t' }) });
23
+ expect(server.queueDepth).toBe(2);
24
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-queue-cancel-all\`, { method:'POST' });
25
+ expect(r.status).toBe(200);
26
+ expect((await r.json()).cancelled).toBe(2);
27
+ if (resolve) resolve({ stdout:'', stderr:'', exitCode:0 });
28
+ await new Promise(r => setTimeout(r, 50));
29
+ });
30
+
31
+ test('cancelAllQueuedTasks when empty', () => {
32
+ expect(server.cancelAllQueuedTasks()).toBe(0);
33
+ });
34
+
35
+ test('GET /mesh-events SSE', async () => {
36
+ const port = await server.start(mi());
37
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
38
+ expect(r.status).toBe(200);
39
+ expect(r.headers.get('content-type')).toContain('text/event-stream');
40
+ await r.body?.cancel();
41
+ });
42
+
43
+ test('SSE connected event', async () => {
44
+ const port = await server.start(mi());
45
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
46
+ const reader = r.body?.getReader();
47
+ let data = '';
48
+ if (reader) { const {value} = await reader.read(); data = new TextDecoder().decode(value); }
49
+ expect(data).toContain('event: connected');
50
+ expect(data).toContain('"status":"ok"');
51
+ await r.body?.cancel();
52
+ });
53
+
54
+ test('broadcastEvent sends SSE', async () => {
55
+ const port = await server.start(mi());
56
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-events\`);
57
+ const reader = r.body?.getReader();
58
+ const dec = new TextDecoder();
59
+ if (reader) await reader.read();
60
+ server.broadcastEvent('new_message', { text:'sse test' });
61
+ let data = '';
62
+ if (reader) { const {value}=await reader.read(); data=dec.decode(value); }
63
+ expect(data).toContain('event: new_message');
64
+ expect(data).toContain('"text":"sse test"');
65
+ await r.body?.cancel();
66
+ });
67
+
68
+ test('updatePeerInfo', async () => {
69
+ const port = await server.start(mi({ id:'orig' }));
70
+ server.updatePeerInfo({ hostname:'upd' });
71
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
72
+ const d = await r.json();
73
+ expect(d.hostname).toBe('upd');
74
+ expect(d.id).toBe('orig');
75
+ });
76
+
77
+ test('extraInfo in mesh-info', async () => {
78
+ const port = await server.start(mi());
79
+ server.extraInfo = { cf: 'cv' };
80
+ expect((await (await fetch(\`http://127.0.0.1:\${port}/mesh-info\`)).json()).cf).toBe('cv');
81
+ });
82
+
83
+ test('cancelQueuedTask false for unknown', () => {
84
+ expect(server.cancelQueuedTask('nope')).toBeFalse();
85
+ });
86
+
87
+ test('getTasks', () => {
88
+ const t = server.getTasks();
89
+ expect(t).toHaveProperty('queue');
90
+ expect(t).toHaveProperty('current');
91
+ expect(t.queue).toEqual([]);
92
+ expect(t.current).toBeNull();
93
+ });
94
+
95
+ test('unknown route 404', async () => {
96
+ const port = await server.start(mi());
97
+ expect((await fetch(\`http://127.0.0.1:\${port}/unknown\`)).status).toBe(404);
98
+ });
99
+
100
+ test('malformed JSON 400', async () => {
101
+ const port = await server.start(mi());
102
+ const r = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, { method:'POST', headers:{'Content-Type':'application/json'}, body:'not-json' });
103
+ expect(r.status).toBe(400);
104
+ });
105
+ });
106
+ `;
107
+ fs.appendFileSync(p, code, 'utf8');
108
+ console.log('MeshServer.test.ts completed');
@@ -0,0 +1,142 @@
1
+ const fs = require('fs');
2
+ const p = 'D:/Projects/Github/clew-code/src/mesh/MeshServer.test.ts';
3
+ const code = `import { describe, expect, test, beforeEach, afterEach } from 'bun:test';
4
+ import { MeshServer, getGlobalMeshServer } from './MeshServer.js';
5
+
6
+ function mi(o) { return { id:'tp', hostname:'th', ip:'127.0.0.1', port:0, cwd:'/r', version:'t', lastSeen:Date.now(), status:'online', ...o }; }
7
+
8
+ describe('MeshServer', () => {
9
+ let server;
10
+ beforeEach(() => { server = new MeshServer(); });
11
+ afterEach(() => { server.stop(); });
12
+
13
+ test('constructor creates instance', () => {
14
+ expect(server).toBeInstanceOf(MeshServer);
15
+ expect(server.port).toBe(0);
16
+ });
17
+
18
+ test('constructor accepts callbacks', () => {
19
+ const s = new MeshServer({ onMessage: () => {} });
20
+ expect(s).toBeInstanceOf(MeshServer);
21
+ s.stop();
22
+ });
23
+
24
+ test('getGlobalMeshServer singleton', () => {
25
+ expect(getGlobalMeshServer()).toBe(getGlobalMeshServer());
26
+ });
27
+
28
+ test('setCallbacks merges', () => {
29
+ server.setCallbacks({ onMessage: () => {} });
30
+ server.setCallbacks({ onTodo: () => {} });
31
+ expect(server).toBeInstanceOf(MeshServer);
32
+ });
33
+
34
+ test('isBusy/queueDepth initially false/0', () => {
35
+ expect(server.isBusy).toBeFalse();
36
+ expect(server.queueDepth).toBe(0);
37
+ });
38
+
39
+ test('getTodos empty, updateTodoStatus false for unknown', () => {
40
+ expect(server.getTodos()).toEqual([]);
41
+ expect(server.updateTodoStatus('x','done')).toBeFalse();
42
+ });
43
+
44
+ test('start returns port', async () => {
45
+ const port = await server.start(mi());
46
+ expect(typeof port).toBe('number');
47
+ expect(port).toBeGreaterThan(0);
48
+ expect(server.port).toBe(port);
49
+ });
50
+
51
+ test('start idempotent', async () => {
52
+ const p1 = await server.start(mi());
53
+ const p2 = await server.start(mi());
54
+ expect(p1).toBe(p2);
55
+ });
56
+
57
+ test('stop does not throw', () => {
58
+ expect(() => server.stop()).not.toThrow();
59
+ });
60
+
61
+ test('start-stop-start works', async () => {
62
+ await server.start(mi());
63
+ server.stop();
64
+ const p = await server.start(mi());
65
+ expect(p).toBeGreaterThan(0);
66
+ });
67
+
68
+ // ── HTTP Endpoints ─────────────────────────────────────────────────
69
+
70
+ test('GET /mesh-info returns peer info', async () => {
71
+ const port = await server.start(mi({ id:'info-test' }));
72
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-info\`);
73
+ expect(res.status).toBe(200);
74
+ const d = await res.json();
75
+ expect(d.id).toBe('info-test');
76
+ expect(d.isBusy).toBeFalse();
77
+ });
78
+
79
+ test('POST /mesh-msg fires callback', async () => {
80
+ let m = null;
81
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
82
+ const port = await server.start(mi());
83
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
84
+ method:'POST', headers:{'Content-Type':'application/json'},
85
+ body: JSON.stringify({ from:'p2', fromName:'w2', text:'hello' }),
86
+ });
87
+ expect(res.status).toBe(200);
88
+ expect((await res.json()).ok).toBeTrue();
89
+ expect(m.from).toBe('p2');
90
+ expect(m.text).toBe('hello');
91
+ });
92
+
93
+ test('POST /mesh-msg handles chunk info', async () => {
94
+ let m = null;
95
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
96
+ const port = await server.start(mi());
97
+ await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
98
+ method:'POST', headers:{'Content-Type':'application/json'},
99
+ body: JSON.stringify({ from:'p2', text:'c', chunkGroup:'g1', chunkIndex:0, chunkTotal:2 }),
100
+ });
101
+ expect(m.chunkGroup).toBe('g1');
102
+ });
103
+
104
+ test('POST /mesh-msg with empty body uses defaults', async () => {
105
+ let m = null;
106
+ server.setCallbacks({ onMessage: msg => { m = msg; } });
107
+ const port = await server.start(mi());
108
+ await fetch(\`http://127.0.0.1:\${port}/mesh-msg\`, {
109
+ method:'POST', headers:{'Content-Type':'application/json'},
110
+ body: JSON.stringify({}),
111
+ });
112
+ expect(m.from).toBe('unknown');
113
+ expect(m.text).toBe('');
114
+ });
115
+
116
+ test('POST /mesh-todo fires callback', async () => {
117
+ let t = null;
118
+ server.setCallbacks({ onTodo: todo => { t = todo; } });
119
+ const port = await server.start(mi());
120
+ const res = await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
121
+ method:'POST', headers:{'Content-Type':'application/json'},
122
+ body: JSON.stringify({ from:'p2', message:'fix' }),
123
+ });
124
+ expect(res.status).toBe(200);
125
+ expect((await res.json()).ok).toBeTrue();
126
+ expect(t.message).toBe('fix');
127
+ });
128
+
129
+ test('POST /mesh-todo empty body defaults', async () => {
130
+ let t = null;
131
+ server.setCallbacks({ onTodo: todo => { t = todo; } });
132
+ const port = await server.start(mi());
133
+ await fetch(\`http://127.0.0.1:\${port}/mesh-todo\`, {
134
+ method:'POST', headers:{'Content-Type':'application/json'},
135
+ body: JSON.stringify({}),
136
+ });
137
+ expect(t.from).toBe('unknown');
138
+ expect(t.message).toBe('');
139
+ });
140
+ `;
141
+ fs.writeFileSync(p, code, 'utf8');
142
+ console.log('MeshServer.test.ts part 1 written');