thebird 1.2.79 → 1.2.80

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 (78) hide show
  1. package/.github/workflows/publish.yml +9 -1
  2. package/CHANGELOG.md +217 -0
  3. package/CLAUDE.md +16 -0
  4. package/docs/agent-chat.js +7 -4
  5. package/docs/app.js +14 -11
  6. package/docs/defaults.json +1 -1
  7. package/docs/index.html +23 -6
  8. package/docs/kilo-http-stream.js +24 -0
  9. package/docs/node-builtins.js +24 -0
  10. package/docs/preview/index.html +32 -0
  11. package/docs/preview-sw-client.js +37 -6
  12. package/docs/preview-sw.js +55 -51
  13. package/docs/shell-awk.js +113 -0
  14. package/docs/shell-builtins-extra.js +121 -0
  15. package/docs/shell-builtins-text.js +109 -0
  16. package/docs/shell-builtins-util.js +112 -0
  17. package/docs/shell-builtins.js +183 -0
  18. package/docs/shell-bun.js +45 -0
  19. package/docs/shell-control.js +132 -0
  20. package/docs/shell-deno.js +54 -0
  21. package/docs/shell-exec.js +85 -0
  22. package/docs/shell-expand.js +164 -0
  23. package/docs/shell-fd.js +86 -0
  24. package/docs/shell-jobs.js +86 -0
  25. package/docs/shell-node-advanced.js +86 -0
  26. package/docs/shell-node-brotli.js +22 -0
  27. package/docs/shell-node-busnet.js +90 -0
  28. package/docs/shell-node-cipher.js +61 -0
  29. package/docs/shell-node-cluster.js +33 -0
  30. package/docs/shell-node-coreutils.js +36 -0
  31. package/docs/shell-node-crypto.js +137 -0
  32. package/docs/shell-node-dns.js +41 -0
  33. package/docs/shell-node-extras.js +148 -0
  34. package/docs/shell-node-firefox.js +95 -0
  35. package/docs/shell-node-git.js +60 -0
  36. package/docs/shell-node-inspector.js +39 -0
  37. package/docs/shell-node-io.js +131 -0
  38. package/docs/shell-node-ipc.js +15 -0
  39. package/docs/shell-node-keyobject.js +60 -0
  40. package/docs/shell-node-modules.js +157 -0
  41. package/docs/shell-node-native.js +31 -0
  42. package/docs/shell-node-net.js +71 -0
  43. package/docs/shell-node-observe.js +80 -0
  44. package/docs/shell-node-opfs.js +54 -0
  45. package/docs/shell-node-procfs.js +42 -0
  46. package/docs/shell-node-profiler.js +50 -0
  47. package/docs/shell-node-registry.js +24 -0
  48. package/docs/shell-node-resolve.js +147 -0
  49. package/docs/shell-node-runtime.js +83 -0
  50. package/docs/shell-node-srcmap.js +52 -0
  51. package/docs/shell-node-stdlib.js +103 -0
  52. package/docs/shell-node-streams.js +66 -0
  53. package/docs/shell-node-tar.js +47 -0
  54. package/docs/shell-node-testrunner.js +35 -0
  55. package/docs/shell-node-util-extras.js +66 -0
  56. package/docs/shell-node.js +175 -169
  57. package/docs/shell-npm.js +173 -0
  58. package/docs/shell-parser.js +122 -0
  59. package/docs/shell-pm-layout.js +62 -0
  60. package/docs/shell-pm.js +39 -0
  61. package/docs/shell-posix.js +70 -0
  62. package/docs/shell-procsub.js +65 -0
  63. package/docs/shell-readline.js +59 -4
  64. package/docs/shell-runtime.js +37 -0
  65. package/docs/shell-sed.js +83 -0
  66. package/docs/shell-signals.js +54 -0
  67. package/docs/shell-sw-jobs.js +76 -0
  68. package/docs/shell-ts.js +30 -0
  69. package/docs/shell.js +161 -152
  70. package/docs/terminal.js +9 -11
  71. package/docs/todo.html +211 -0
  72. package/package.json +1 -1
  73. package/server.js +43 -4
  74. package/start-kilo.js +17 -0
  75. package/test.js +199 -0
  76. package/.codeinsight +0 -73
  77. package/docs/acp-stream.js +0 -102
  78. package/docs/coi-serviceworker.js +0 -2
package/docs/todo.html ADDED
@@ -0,0 +1,211 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Todo App</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 10px;
27
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
28
+ width: 100%;
29
+ max-width: 500px;
30
+ padding: 30px;
31
+ }
32
+
33
+ h1 {
34
+ color: #333;
35
+ margin-bottom: 20px;
36
+ text-align: center;
37
+ }
38
+
39
+ .input-group {
40
+ display: flex;
41
+ gap: 10px;
42
+ margin-bottom: 20px;
43
+ }
44
+
45
+ input[type="text"] {
46
+ flex: 1;
47
+ padding: 12px;
48
+ border: 2px solid #e0e0e0;
49
+ border-radius: 5px;
50
+ font-size: 16px;
51
+ transition: border-color 0.3s;
52
+ }
53
+
54
+ input[type="text"]:focus {
55
+ outline: none;
56
+ border-color: #667eea;
57
+ }
58
+
59
+ button {
60
+ background: #667eea;
61
+ color: white;
62
+ border: none;
63
+ padding: 12px 25px;
64
+ border-radius: 5px;
65
+ font-size: 16px;
66
+ cursor: pointer;
67
+ transition: background 0.3s;
68
+ }
69
+
70
+ button:hover {
71
+ background: #764ba2;
72
+ }
73
+
74
+ .todo-list {
75
+ list-style: none;
76
+ }
77
+
78
+ .todo-item {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 12px;
82
+ padding: 15px;
83
+ background: #f5f5f5;
84
+ border-radius: 5px;
85
+ margin-bottom: 10px;
86
+ transition: all 0.3s;
87
+ }
88
+
89
+ .todo-item:hover {
90
+ background: #efefef;
91
+ }
92
+
93
+ .todo-item.completed {
94
+ opacity: 0.6;
95
+ }
96
+
97
+ .todo-item.completed .todo-text {
98
+ text-decoration: line-through;
99
+ }
100
+
101
+ .todo-checkbox {
102
+ width: 20px;
103
+ height: 20px;
104
+ cursor: pointer;
105
+ }
106
+
107
+ .todo-text {
108
+ flex: 1;
109
+ color: #333;
110
+ word-break: break-word;
111
+ }
112
+
113
+ .delete-btn {
114
+ background: #ff6b6b;
115
+ padding: 8px 12px;
116
+ font-size: 14px;
117
+ }
118
+
119
+ .delete-btn:hover {
120
+ background: #ff5252;
121
+ }
122
+
123
+ .empty-state {
124
+ text-align: center;
125
+ color: #999;
126
+ padding: 40px 20px;
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div class="container">
132
+ <h1>📝 Todo App</h1>
133
+
134
+ <div class="input-group">
135
+ <input type="text" id="todoInput" placeholder="Add a new todo...">
136
+ <button onclick="addTodo()">Add</button>
137
+ </div>
138
+
139
+ <ul class="todo-list" id="todoList">
140
+ <li class="empty-state">No todos yet. Add one to get started!</li>
141
+ </ul>
142
+ </div>
143
+
144
+ <script>
145
+ let todos = JSON.parse(localStorage.getItem('todos')) || [];
146
+
147
+ function renderTodos() {
148
+ const todoList = document.getElementById('todoList');
149
+
150
+ if (todos.length === 0) {
151
+ todoList.innerHTML = '<li class="empty-state">No todos yet. Add one to get started!</li>';
152
+ return;
153
+ }
154
+
155
+ todoList.innerHTML = todos.map((todo, index) => `
156
+ <li class="todo-item ${todo.completed ? 'completed' : ''}">
157
+ <input type="checkbox" class="todo-checkbox"
158
+ ${todo.completed ? 'checked' : ''}
159
+ onchange="toggleTodo(${index})">
160
+ <span class="todo-text">${escapeHtml(todo.text)}</span>
161
+ <button class="delete-btn" onclick="deleteTodo(${index})">Delete</button>
162
+ </li>
163
+ `).join('');
164
+ }
165
+
166
+ function addTodo() {
167
+ const input = document.getElementById('todoInput');
168
+ const text = input.value.trim();
169
+
170
+ if (text === '') {
171
+ alert('Please enter a todo!');
172
+ return;
173
+ }
174
+
175
+ todos.push({ text, completed: false });
176
+ localStorage.setItem('todos', JSON.stringify(todos));
177
+ input.value = '';
178
+ renderTodos();
179
+ }
180
+
181
+ function toggleTodo(index) {
182
+ todos[index].completed = !todos[index].completed;
183
+ localStorage.setItem('todos', JSON.stringify(todos));
184
+ renderTodos();
185
+ }
186
+
187
+ function deleteTodo(index) {
188
+ todos.splice(index, 1);
189
+ localStorage.setItem('todos', JSON.stringify(todos));
190
+ renderTodos();
191
+ }
192
+
193
+ function escapeHtml(text) {
194
+ const map = {
195
+ '&': '&amp;',
196
+ '<': '&lt;',
197
+ '>': '&gt;',
198
+ '"': '&quot;',
199
+ "'": '&#039;'
200
+ };
201
+ return text.replace(/[&<>"']/g, m => map[m]);
202
+ }
203
+
204
+ document.getElementById('todoInput').addEventListener('keypress', (e) => {
205
+ if (e.key === 'Enter') addTodo();
206
+ });
207
+
208
+ renderTodos();
209
+ </script>
210
+ </body>
211
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.79",
3
+ "version": "1.2.80",
4
4
  "description": "Anthropic SDK to Gemini streaming bridge — drop-in proxy that translates Anthropic message format and tool calls to Google Gemini",
5
5
  "scripts": {
6
6
  "start": "node serve.js"
package/server.js CHANGED
@@ -52,10 +52,44 @@ async function handleMessages(req, res) {
52
52
  res.end();
53
53
  }
54
54
 
55
+ const landingPage = () => `<!DOCTYPE html>
56
+ <html><head><meta charset="UTF-8"><title>thebird proxy</title>
57
+ <style>
58
+ body { font-family: monospace; background: #000; color: #33ff33; padding: 2ch; margin: 0; }
59
+ h1 { margin-top: 0; }
60
+ .stat { border: 1px solid #1a9a1a; padding: 1ch; margin: 1ch 0; }
61
+ code { background: #111; padding: 0 0.5ch; }
62
+ .endpoint { color: #1a9a1a; }
63
+ a { color: #33ff33; }
64
+ </style></head>
65
+ <body>
66
+ <h1>▀█▀ █░█ █▀▀ █▄▄ █ █▀█ █▀▄ — thebird proxy</h1>
67
+ <div class="stat">
68
+ <strong>status:</strong> running on port ${PORT}<br>
69
+ <strong>requests:</strong> ${state.requests} | <strong>errors:</strong> ${state.errors} | <strong>active:</strong> ${state.active}
70
+ </div>
71
+ <h2>endpoints</h2>
72
+ <ul>
73
+ <li><span class="endpoint">POST /v1/messages</span> — Anthropic Messages API (translated to Gemini)</li>
74
+ <li><span class="endpoint">GET /debug/server</span> — <a href="debug/server">live state JSON</a></li>
75
+ <li><span class="endpoint">GET /</span> — this landing page</li>
76
+ </ul>
77
+ <h2>usage</h2>
78
+ <pre>curl -X POST http://localhost:${PORT}/v1/messages \\
79
+ -H "Content-Type: application/json" \\
80
+ -d '{"model":"gemini-2.5-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":100}'</pre>
81
+ <p>Set <code>GEMINI_API_KEY</code> in env before sending messages.</p>
82
+ </body></html>`;
83
+
55
84
  http.createServer(async (req, res) => {
56
85
  state.requests++;
57
86
  state.active++;
58
87
  try {
88
+ if (req.method === 'GET' && (req.url === '/' || req.url === '/index.html')) {
89
+ res.writeHead(200, { 'Content-Type': 'text/html' });
90
+ res.end(landingPage());
91
+ return;
92
+ }
59
93
  if (req.method === 'GET' && req.url === '/debug/server') {
60
94
  res.writeHead(200, { 'Content-Type': 'application/json' });
61
95
  res.end(JSON.stringify(state));
@@ -65,12 +99,17 @@ http.createServer(async (req, res) => {
65
99
  await handleMessages(req, res);
66
100
  return;
67
101
  }
68
- res.writeHead(404);
69
- res.end(JSON.stringify({ error: 'not found' }));
102
+ res.writeHead(404, { 'Content-Type': 'text/html' });
103
+ res.end(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>404</title>
104
+ <style>body{font-family:monospace;background:#000;color:#ff3333;padding:2ch;margin:0}h1{color:#33ff33}a{color:#33ff33}.box{border:1px solid #ff3333;padding:1ch;margin:1ch 0}</style></head>
105
+ <body><h1>404</h1><div class="box">not found: <code>${req.method} ${req.url}</code></div>
106
+ <p><a href="./">← back to landing page</a></p></body></html>`);
70
107
  } catch (err) {
71
108
  state.errors++;
72
- res.writeHead(500);
73
- res.end(JSON.stringify({ error: err.message }));
109
+ res.writeHead(500, { 'Content-Type': 'text/html' });
110
+ res.end(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>500</title>
111
+ <style>body{font-family:monospace;background:#000;color:#ff3333;padding:2ch;margin:0}pre{background:#111;padding:1ch;overflow:auto}</style></head>
112
+ <body><h1>500 — server error</h1><pre>${err.message.replace(/</g, '&lt;')}</pre></body></html>`);
74
113
  } finally {
75
114
  state.active--;
76
115
  }
package/start-kilo.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ // thebird — start local Kilo Code backend for docs/ page
3
+ // usage: node start-kilo.js [--port 4780] [--origin http://localhost:8787]
4
+ const { spawn } = require('child_process');
5
+ const os = require('os');
6
+ const args = process.argv.slice(2);
7
+ const portIdx = args.indexOf('--port');
8
+ const originIdx = args.indexOf('--origin');
9
+ const port = portIdx >= 0 ? args[portIdx + 1] : '4780';
10
+ const origin = originIdx >= 0 ? args[originIdx + 1] : 'http://localhost:8787';
11
+ const isWin = os.platform() === 'win32';
12
+ const kiloWin = process.env.USERPROFILE + '\\AppData\\Roaming\\npm\\node_modules\\@kilocode\\cli\\node_modules\\@kilocode\\cli-windows-x64\\bin\\kilo.exe';
13
+ const kiloUnix = 'kilo';
14
+ const bin = isWin && require('fs').existsSync(kiloWin) ? kiloWin : kiloUnix;
15
+ const child = spawn(bin, ['serve', '--port', port, '--hostname', '127.0.0.1', '--cors', origin], { stdio: 'inherit', env: process.env });
16
+ child.on('exit', c => process.exit(c || 0));
17
+ process.on('SIGINT', () => child.kill());
package/test.js ADDED
@@ -0,0 +1,199 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const assert = require('assert');
4
+ const R = (...p) => fs.readFileSync(path.join(__dirname, ...p), 'utf8');
5
+ const imp = p => import('file:///' + path.resolve(p).replace(/\\/g, '/'));
6
+ const toHex = b => [...b].map(x => x.toString(16).padStart(2, '0')).join('');
7
+
8
+ console.log('=== thebird integration test ===\n');
9
+
10
+ const defaults = JSON.parse(R('docs/defaults.json'));
11
+ ['app.js', 'terminal.js', 'agent-chat.js', 'shell.js', 'shell-node.js', 'shell-node-cipher.js', 'shell-node-advanced.js', 'shell-node-observe.js', 'shell-node-runtime.js', 'shell-pm-layout.js'].forEach(k => assert(defaults[k] !== undefined, k));
12
+ ['pane-chat', 'pane-term', 'app.js', 'preview-sw-client.js'].forEach(s => assert(R('docs/index.html').includes(s), s));
13
+ assert(R('docs/preview-sw.js').includes('EXPRESS_REQUEST'));
14
+ ['read_file','write_file','run_command'].forEach(t => assert(R('docs/agent-chat.js').includes(t + ':'), t));
15
+ [R('docs/app.js'), R('docs/terminal.js'), R('docs/agent-chat.js')].forEach(c => assert(c.includes('window.__debug')));
16
+ console.log('✓ bootstrap/tools/__debug\n');
17
+
18
+ const shellMain = R('docs/shell.js');
19
+ const { expand, expandCmdSub, globToRe } = require('./docs/shell-parser.js');
20
+ [['$?', {}, 42, '42'], ['$HOME', { HOME: '/h' }, 0, '/h'], ['${HOME}', { HOME: '/h' }, 0, '/h']].forEach(([t,e,r,v]) => assert(expand(t,e,r) === v, t));
21
+ assert(expandCmdSub('a_$(echo hi)_b', {}, 0, () => 'hi') === 'a_hi_b');
22
+ assert(globToRe('f[abc]').test('fa') && !globToRe('f[abc]').test('fz'));
23
+ const exp = require('./docs/shell-expand.js');
24
+ assert(exp.evalArith('2+3*4') === 14 && exp.expandBraces('{1..3}').join(',') === '1,2,3');
25
+ [['${FOO:-x}',{},'x'],['${#X}',{X:'abcde'},'5'],['${X:1:3}',{X:'abcdef'},'bcd'],['${X%.js}',{X:'foo.js'},'foo'],['$((2+3))',{},'5']].forEach(([t,e,v]) => assert(exp.fullExpand(t,e,0,[],()=>'hi') === v, t));
26
+ const awk = require('./docs/shell-awk.js'), sed = require('./docs/shell-sed.js');
27
+ assert(awk.runAwk('{print $1}', 'a b\nc d\n') === 'a\nc' && awk.runAwk('BEGIN{print "s"}', '') === 's');
28
+ assert(sed.runSed(['/^#/d'], '#c\nk') === 'k' && sed.runSed(['s/a/A/'], 'a') === 'A');
29
+ console.log('✓ shell parser/expand/awk/sed\n');
30
+
31
+ global.window = { __debug: { idbSnapshot: {}, idbPersist: () => {} } };
32
+ const sigM = require('./docs/shell-signals.js'), fdM = require('./docs/shell-fd.js');
33
+ const ctx2 = { traps: {}, term: { write() {} }, bgJobs: {}, lastExitCode: 0 };
34
+ sigM.createSignals(ctx2).raise('USR1');
35
+ const fdt = fdM.createFdTable(ctx2); fdt.open(3, 'f', 'w'); fdt.writeFd(3, 'x'); fdt.dup2(3, 4);
36
+ assert(fdt.table[4].duped === 3 && global.window.__debug.idbSnapshot.f === 'x');
37
+ const nm = require('./docs/shell-node-modules.js');
38
+ assert(nm.NODE_VERSION === 'v23.10.0' && Object.keys(nm.NODE_VERSIONS).length >= 25);
39
+ const pr = nm.createProcess({ write(){} }, { env: {}, cwd: '/' });
40
+ let t=false; try { pr.exit(3); } catch (e) { t = e.__nodeExit && e.code === 3; } assert(t);
41
+ console.log('✓ signals/fd/process\n');
42
+
43
+ (async () => {
44
+ const stdlib=await imp('docs/shell-node-stdlib.js'), io=await imp('docs/shell-node-io.js'), nb=await imp('docs/node-builtins.js'), cry=await imp('docs/shell-node-crypto.js'), rs=await imp('docs/shell-node-resolve.js');
45
+ const ex=await imp('docs/shell-node-extras.js'), strm=await imp('docs/shell-node-streams.js'), cip=await imp('docs/shell-node-cipher.js'), ko=await imp('docs/shell-node-keyobject.js');
46
+ const adv=await imp('docs/shell-node-advanced.js'), ob=await imp('docs/shell-node-observe.js'), rt=await imp('docs/shell-node-runtime.js');
47
+
48
+ assert(stdlib.inspect({a:1,b:[2,3]}) === '{ a: 1, b: [ 2, 3 ] }');
49
+ assert(stdlib.inspect(10n) === '10n' && stdlib.inspect({[Symbol('k')]:'v'}).includes('Symbol(k)') && stdlib.inspect(42,{colors:true}).includes('\x1b[33m'));
50
+ assert(stdlib.format('hi %s %d', 'a', 5) === 'hi a 5');
51
+
52
+ assert(cry.createHash('sha1').update('hi').digest('hex') === 'c22b5f9178342609428d6f51b2c5af4c0bde6a42');
53
+ assert(cry.createHash('md5').update('hi').digest('hex') === '49f68a5c8493ec2c0bf489821c21fc3b');
54
+ assert(cry.createHmac('sha256', 'k').update('m').digest('hex') === 'b60090e3052297aeb5a080889ce2fc4bca957e756faeb4df7d31800ca1e771ec');
55
+ assert(toHex(cry.pbkdf2Sync('pw', 'salt', 1000, 32, 'sha256')) === '0a38253555ce37f5c72a6b703f996814ebf241f203af146e93dcdeb031c5567e');
56
+
57
+ assert(rs.resolveExports({exports:{'.':{import:'./e.js',require:'./c.js'}}}, '.') === './e.js');
58
+ assert(rs.resolveImports({imports:{'#i/*':'./l/*.js'}}, '#i/x') === './l/x.js');
59
+ assert(rs.makeModuleNotFoundError('x').code === 'MODULE_NOT_FOUND');
60
+
61
+ const Buf = ex.extendBuffer(nb.createBuffer());
62
+ globalThis.Buffer = Buf;
63
+ assert(Buf.from('hello').toString('hex') === '68656c6c6f');
64
+ assert(Buf.from([0,0,0,0,0,0,0,42]).readBigUInt64BE(0) === 42n);
65
+ const pathExt = ex.extendPath(nb.createPath());
66
+ assert(pathExt.win32.sep === '\\' && pathExt.win32.join('a','b') === 'a\\b' && pathExt.posix.sep === '/');
67
+ const urlx = ex.createUrlExt();
68
+ assert(urlx.pathToFileURL('/a/b.js').href === 'file:///a/b.js' && urlx.fileURLToPath('file:///a/b.js') === '/a/b.js');
69
+ const als = new (ex.makeAsyncHooks().AsyncLocalStorage)();
70
+ als.run('v', () => assert(als.getStore() === 'v'));
71
+
72
+ const s = strm.makeStream();
73
+ const r=new s.Readable(); const got=[]; r.on('data',c=>got.push(String(c))); r.push('a'); r.push('b'); r.push(null);
74
+ await new Promise(res=>r.on('end',res)); assert(got.join('')==='ab');
75
+ const tr=new s.Transform({transform:(c,e,cb)=>cb(null,String(c).toUpperCase())}); const tout=[]; tr.on('data',c=>tout.push(String(c))); tr.write('x'); tr.end();
76
+ await new Promise(res=>tr.on('finish',res)); assert(tout[0]==='X');
77
+ console.log('✓ stdlib/crypto/resolve/buffer/path/url/ALS/streams\n');
78
+
79
+ globalThis.crypto = require('crypto').webcrypto;
80
+ const c6 = ko.extendKeys(cip.extendCrypto({}, Buf));
81
+ const rsaKp = await c6.generateKeyPairAsync('rsa', { modulusLength: 2048 });
82
+ const rsig = await c6.signAsync('RSA-SHA256', 'data', rsaKp.privateKey);
83
+ assert(await c6.verifyAsync('RSA-SHA256', 'data', rsaKp.publicKey, rsig));
84
+ const ecKp = await c6.generateKeyPairAsync('ec', { namedCurve: 'P-256' });
85
+ const esig=await c6.signAsync('sha256',Buffer.from('d'),ecKp.privateKey); assert(await c6.verifyAsync('sha256',Buffer.from('d'),ecKp.publicKey,esig));
86
+ const hk=await c6.hkdfAsync('sha256',Buffer.from('i'),Buffer.from('s'),Buffer.from('f'),32); assert(hk.length===32);
87
+ const dh1=c6.createECDH('prime256v1'),dh2=c6.createECDH('prime256v1'); const p1=await dh1.generateKeys(),p2=await dh2.generateKeys();
88
+ assert((await dh1.computeSecret(p2)).equals(await dh2.computeSecret(p1))); assert(c6.createPrivateKey(ecKp.privateKey).asymmetricKeyType==='ec');
89
+ const key=crypto.getRandomValues(new Uint8Array(32)), iv=crypto.getRandomValues(new Uint8Array(16));
90
+ const c1=c6.createCipheriv('aes-256-cbc',key,iv); c1.update(Buf.from('hello')); const ct=await c1.final();
91
+ const d1=c6.createDecipheriv('aes-256-cbc',key,iv); d1.update(ct); assert(new TextDecoder().decode(await d1.final())==='hello');
92
+ assert(c6.getCiphers().length>=6);
93
+ console.log('✓ RSA/ECDSA/HKDF/ECDH/KeyObject/AES\n');
94
+
95
+ const diag=ob.makeDiagnosticsChannel(); let dgot=null; diag.channel('t').subscribe(m=>{dgot=m;}); diag.channel('t').publish({n:7});
96
+ assert(dgot?.n===7 && diag.hasSubscribers('t'));
97
+ const reg6=ob.makeDebugRegistry(); const te6=ob.makeTraceEvents(reg6); te6.createTracing({categories:['c']}).enable();
98
+ let bt=false; try{ob.makeProcessBindings()('unknown');}catch{bt=true;}
99
+ [[te6.getEnabledCategories().includes('c'),'trace'],[ob.makeProcessBindings()('util').isDate(new Date())===true,'binding'],[bt,'binding unknown'],[typeof ob.makePerfMemory({})().heapUsed==='number','mem'],[new (ob.makeFetchPool())({maxSockets:2}).maxSockets===2,'agent']].forEach(([v,n])=>assert(v,n));
100
+ ob.installPrepareStackTraceHook(); Error.prepareStackTrace=(e,frames)=>frames.length; assert(typeof new Error('x').stack==='number'); Error.prepareStackTrace=null;
101
+ console.log('✓ diag/trace/binding/memory/agent/prepareStack\n');
102
+
103
+ const mr=adv.makeModuleRegister(), repl=rt.makeRepl({},{write:()=>{}},async()=>{});
104
+ [[adv.makeVmModule().runInThisContext('2+2')===4,'vm'],[typeof mr.register==='function'&&Array.isArray(mr._hooks),'register'],[typeof adv.makeHttp2().connect==='function'&&adv.makeHttp2().constants.HTTP2_HEADER_METHOD===':method','http2'],[typeof new (adv.makeWasi().WASI)({}).start==='function','wasi'],[repl.isIncomplete('function f() {')===true&&repl.isIncomplete('2 + 2')===false,'repl']].forEach(([v,n])=>assert(v,n));
105
+ assert(typeof rt.makeWorkerThreads(()=>({}),Buf).Worker === 'function');
106
+ assert(typeof rt.makeChildProcessReal(Buf, s).exec === 'function');
107
+ assert([[],[new Error('x',{cause:new Error('y')}).cause.message==='y','cause'],[new AggregateError([new Error('a')],'m').errors.length===1,'aggr']].slice(1).every(([v])=>v));
108
+ console.log('✓ vm/register/http2/wasi/repl/workers/cp/Error.cause\n');
109
+
110
+ console.log('=== pass 7: Firefox + polyfills ===');
111
+ const ff7 = await imp('docs/shell-node-firefox.js'), n7 = await imp('docs/shell-node-net.js'), prof7 = await imp('docs/shell-node-profiler.js'), clu7 = await imp('docs/shell-node-cluster.js'), ins7 = await imp('docs/shell-node-inspector.js');
112
+ const info7 = ff7.detectBrowser();
113
+ assert(typeof info7.vendor === 'string' && Object.keys(info7.capabilities).length >= 10, 'browser detect');
114
+ const Bf = Buf;
115
+ const nm7 = n7.makeNet(Bf);
116
+ assert(typeof nm7.Socket === 'function' && nm7.isIP('1.2.3.4') === 4, 'net');
117
+ const tm7 = n7.makeTls(nm7, Bf);
118
+ assert(new tm7.TLSSocket() instanceof nm7.Socket && tm7.DEFAULT_MIN_VERSION === 'TLSv1.2', 'tls');
119
+ assert(typeof n7.makeDgram(Bf).createSocket('udp4').send === 'function', 'dgram');
120
+ const v7 = prof7.makeV8Profiler({}); const cp7 = new v7.CPUProfile(); cp7.startProfiling('t'); cp7.stopProfiling();
121
+ assert(Array.isArray(cp7.nodes) && typeof v7.getHeapStatistics().used_heap_size === 'number', 'v8');
122
+ global.window = { __debug: { idbSnapshot: {} } };
123
+ prof7.makeHeapSnapshot().writeHeapSnapshot('s.heapsnapshot');
124
+ const hp = JSON.parse(global.window.__debug.idbSnapshot['s.heapsnapshot']);
125
+ assert(Array.isArray(hp.snapshot.meta.node_fields) && hp.nodes.length > 0, 'heap snapshot');
126
+ const c7 = clu7.makeCluster();
127
+ assert(c7 === null || typeof c7.isMaster === 'boolean', 'cluster');
128
+ const i7 = ins7.makeInspector({}); const opened = i7.open(9229, '127.0.0.1', false);
129
+ assert(opened.url.startsWith('ws://') && typeof i7.Session === 'function', 'inspector');
130
+ console.log('✓ UA/capabilities/net/tls/dgram/v8/heap/cluster/inspector\n');
131
+
132
+ console.log('=== pass 8: runtime parity + POSIX ===');
133
+ const rt8 = await imp('docs/shell-runtime.js'), dn8 = await imp('docs/shell-deno.js'), bn8 = await imp('docs/shell-bun.js'), pm8 = await imp('docs/shell-pm.js'), ts8 = await imp('docs/shell-ts.js'), px8 = await imp('docs/shell-posix.js'), rs8 = await imp('docs/shell-node-resolve.js');
134
+ const r8=rt8.detectRuntime();
135
+ [[typeof r8.runtime==='string'&&typeof r8.features==='object','runtime'],[rt8.switchRuntime('#!/usr/bin/env deno')==='deno','deno sh'],[rt8.switchRuntime('#!/usr/bin/env bun')==='bun','bun sh'],[rt8.switchRuntime('')==='node','default sh'],[rs8.rewriteSpecifier('jsr:@f/b')==='https://esm.sh/jsr/@f/b','jsr'],[rs8.rewriteSpecifier('npm:p@1')==='https://esm.sh/p@1','npm'],[pm8.detectPm({'pnpm-lock.yaml':'x'}).pm==='pnpm','pnpm'],[pm8.detectPm({'bun.lock':'x'}).pm==='bun','bun lock'],[pm8.detectPm({'yarn.lock':'x'}).pm==='yarn','yarn'],[pm8.detectPm({'package.json':'{"packageManager":"pnpm@9"}'}).pm==='pnpm','pm field'],[ts8.isTsFile('a.ts')&&!ts8.isTsFile('a.js'),'ts file'],[!ts8.stripTypesSync('const x: number = 5;').includes(': number'),'ts strip']].forEach(([v,n])=>assert(v,n));
136
+ global.window={__debug:{idbSnapshot:{},idbPersist:()=>{}}};
137
+ const fsP=await imp('docs/node-builtins.js'); const pfs=fsP.createFs();
138
+ px8.installPosixFs(pfs,Buf,{umask:0o022}); px8.installFds(pfs,Buf); px8.installTmpAndMisc(pfs,Buf,{umask:0o022});
139
+ pfs.writeFileSync('/a', 'hello');
140
+ pfs.symlinkSync('/a', '/b');
141
+ assert(pfs.readlinkSync('/b') === '/a', 'readlink');
142
+ assert(pfs.readFileSync('/b').toString() === 'hello', 'follow symlink');
143
+ assert(pfs.lstatSync('/b').isSymbolicLink() === true, 'lstat link');
144
+ assert(pfs.statSync('/b').isFile() === true, 'stat follows');
145
+ pfs.chmodSync('/a', 0o755);
146
+ assert((pfs.statSync('/a').mode & 0o777) === 0o755, 'chmod');
147
+ const fdX = pfs.openSync('/c', 'w+');
148
+ pfs.writeSync(fdX, Buf.from('xy'), 0, 2, 0);
149
+ pfs.closeSync(fdX);
150
+ assert(pfs.readFileSync('/c').toString() === 'xy', 'fd write');
151
+ const tmpD = pfs.mkdtempSync('/tmp/t-');
152
+ assert(tmpD.startsWith('/tmp/t-') && pfs.existsSync(tmpD), 'mkdtemp');
153
+ const procMock = { pid: 1, env: { HOME: '/root' }, argv: ['node'], exit: () => {}, cwd: () => '/', on: () => {}, memoryUsage: () => ({}) };
154
+ const dg = dn8.makeDenoGlobal(pfs, procMock, { exec: () => {} }, {}, Buf);
155
+ assert(dg.readTextFileSync('/a') === 'hello' && dg.env.get('HOME') === '/root', 'Deno shim');
156
+ const bg = bn8.makeBunGlobal(pfs, procMock, { exec: () => {} }, {}, Buf, { Readable: class R{} }, {});
157
+ assert(await bg.file('/a').text() === 'hello' && typeof bg.version === 'string', 'Bun shim');
158
+ console.log('✓ runtime/jsr/pm/ts/symlink/fd/mkdtemp/Deno/Bun\n');
159
+
160
+ console.log('=== pass 9: workspaces + yarn lock + runtime history ===');
161
+ const pl9=await imp('docs/shell-pm-layout.js');
162
+ const ws=pl9.parseWorkspaces({'package.json':JSON.stringify({workspaces:['packages/*']}),'packages/a/package.json':JSON.stringify({name:'a',version:'1'})});
163
+ const yl=pl9.writeYarnLockV1({lodash:'^4.17.21'}); const reg={}; rt8.registerRuntime(reg,{runtime:'node',version:'23',features:{}}); rt8.logRuntimeSwitch(reg,'node','deno','x.ts');
164
+ [[ws.length===1&&ws[0].name==='a','workspaces'],[pl9.parseWorkspaces({'pnpm-workspace.yaml':'packages:\n - "apps/*"\n','apps/a/package.json':JSON.stringify({name:'a',version:'1'})}).length===1,'pnpm-ws'],[yl.includes('AUTOGENERATED')&&pl9.parseYarnLockV1(yl)['lodash@^4.17.21'].version==='4.17.21','yarn lock'],[reg.runtime.history.length===1&&reg.runtime.active==='deno','history']].forEach(([v,n])=>assert(v,n));
165
+ console.log('✓ workspaces/pnpm-ws/yarn-lock/runtime-history\n');
166
+
167
+ console.log('=== pass 10: test-runner + util extras ===');
168
+ const tr10=await imp('docs/shell-node-testrunner.js'), ue10=await imp('docs/shell-node-util-extras.js');
169
+ const runner=tr10.makeTestRunner(null); let ran=false;
170
+ await runner.test('t',()=>{ran=true;}); await runner.test('f',()=>{throw new Error('e');});
171
+ const m10=runner.mock.fn(x=>x*2); m10(5);
172
+ const mt=new ue10.MIMEType('text/html; charset=utf-8');
173
+ const logged=[]; const ce=ue10.makeConsoleExtras({log:s=>logged.push(s)},{write:s=>logged.push(s)}); ce.time('x'); ce.timeEnd('x'); ce.count('i');
174
+ [[ran&&runner.results.pass===1&&runner.results.fail===1,'runner'],[m10.mock.calls[0].arguments[0]===5,'mock'],[typeof tr10.makeTapReporter(null).ok==='function','tap'],[ue10.styleText('red','hi').includes('\x1b[31m'),'style'],[ue10.stripVTControlCharacters('\x1b[31mhi\x1b[0m')==='hi','stripVT'],[mt.params.get('charset')==='utf-8','MIME'],[Array.isArray(ue10.getCallSites(3)),'callsites'],[logged.some(l=>l.startsWith('x:'))&&logged.some(l=>l.includes('i:')),'console']].forEach(([v,n])=>assert(v,n));
175
+ console.log('✓ runner/mock/TAP/style/MIME/callsites/console\n');
176
+
177
+ console.log('=== pass 11: procfs/tar/native/coreutils/dns ===');
178
+ const pf11=await imp('docs/shell-node-procfs.js'), nat11=await imp('docs/shell-node-native.js'), core11=await imp('docs/shell-node-coreutils.js'), dns11=await imp('docs/shell-node-dns.js');
179
+ const pfsX=pf11.makeProcFs({argv:['node','x'],env:{},pid:42,cwd:()=>'/'});
180
+ const cout=[]; const cu=core11.makeCoreutils({term:{write:s=>cout.push(s)},env:{USER:'root'},cwd:'/',readFile:()=>''});
181
+ cu.uname(['a']); const l1=cout.slice(); cout.length=0; cu.nproc([]); const l2=cout.slice(); cout.length=0; cu.free([]); const l3=cout.slice();
182
+ [[pfsX.read('/proc/self/cmdline').includes('node'),'cmdline'],[pfsX.read('/etc/hosts').includes('localhost'),'hosts'],[pfsX.read('/etc/passwd').includes('root'),'passwd'],[nat11.makeNativeLoader().list().length>0,'native'],[l1[0].includes('Linux'),'uname'],[/^\d+$/.test(l2[0].trim()),'nproc'],[l3.some(x=>x.includes('Mem:')),'free'],[typeof dns11.makeDns().promises.lookup==='function','dns']].forEach(([v,n])=>assert(v,n));
183
+ console.log('✓ procfs/tar/native/coreutils/dns\n');
184
+
185
+ console.log('=== pass 12: busnet listen/connect ===');
186
+ const bn12=await imp('docs/shell-node-busnet.js');
187
+ const bnet=bn12.makeBusnet();
188
+ const srvData=[], cliData=[];
189
+ bnet.listen(9001,'echo',conn=>{conn.on('data',d=>{srvData.push(d);conn.write('echo:'+d);});});
190
+ const c12=bnet.connect(9001,null,()=>{});
191
+ c12.on('data',d=>cliData.push(d));
192
+ await new Promise(r=>setTimeout(r,30));
193
+ c12.write('hi');
194
+ await new Promise(r=>setTimeout(r,30));
195
+ assert(srvData[0]==='hi' && cliData[0]==='echo:hi', 'busnet roundtrip');
196
+ assert(bnet.getListeners().includes(9001), 'busnet listeners');
197
+ console.log('✓ busnet listen/connect/roundtrip\n');
198
+ console.log('=== all checks passed ===');
199
+ })().catch(e => { console.error(e); process.exit(1); });
package/.codeinsight DELETED
@@ -1,73 +0,0 @@
1
- ## 🎯 thebird v1.1.0 — Anthropic SDK to Gemini streaming bridge — drop-in proxy that translates Anthropic message format and tool calls to Google Gemini
2
-
3
- # 11f 721L 24fn 24cls cx2.2
4
- *Legend: f=files L=lines fn=functions cls=classes cx=avg-complexity | file:line:name(NL)=location Np=params | ↑N=imports-from ↓N=imported-by (N)=occurrences (+N)=more | 🔄circular 🏝️isolated 🔥complex 📋duplicated 📁large*
5
-
6
- **Langs:** JS:78% TS:17% JSON:4%
7
-
8
- ## 🛠️ Tech Stack
9
-
10
- **Patterns:** generateGemini(7), contents.push(6), main(5), main().catch(5), chat(4), allParts.filter(3)
11
- **Top IDs:** type(47), console(39), log(33), result(26), content(23), text(23)
12
-
13
- ## ⚡ Code Patterns
14
-
15
- **Async:** async(38), await(25), Promise(1)
16
- **Errors:** try/catch(6), throw(3)
17
- **Internal calls:** generateGemini(7), contents.push(6), main(5), main().catch(5), chat(4), allParts.filter(3), onStepFinish(3), process.stdout.write(2)
18
-
19
- ## 🔗 I/O & Integration
20
-
21
- **Env vars:** GEMINI_API_KEY
22
- **Storage:** SQL(2), JSON(5)
23
-
24
- ## 📊 Code Organization
25
-
26
- **Long funcs:** index.js:14:createFullStream(57L), examples/streaming.js:26:main(54L)
27
- **Classes:** index.d.ts:0:TextBlock, index.d.ts:0:ImageBlockBase64, index.d.ts:0:ImageBlockUrl, index.d.ts:0:ImageBlockInline, index.d.ts:0:ImageBlockFile, index.d.ts:0:ToolUseBlock, index.d.ts:0:ToolResultBlock, index.d.ts:0:Message (+15)
28
-
29
- ## 🔄 Architecture
30
-
31
- **L0 [pure exports]:** convert(1↓), errors(1↓), client(1↓)
32
- **L3 [pure imports]:** index(3↑)
33
- **Cross-module:** index.js→lib
34
- **External:** @google/genai
35
-
36
- ## 🔌 API Surface
37
-
38
- **Exported fns:** errors.js:11:isRetryable(1p), errors.js:20:withRetry(1p), convert.js:1:cleanSchema(1p), convert.js:12:convertTools(2p), convert.js:21:convertImageBlock(1p), convert.js:38:convertMessages(1p), convert.js:65:extractModelId(1p), convert.js:72:buildConfig(1p), client.js:5:getClient(1p), index.js:5:streamGemini(1p), index.js:72:generateGemini(2p)
39
- **Classes:** TextBlock, ImageBlockBase64, ImageBlockUrl, ImageBlockInline, ImageBlockFile, ToolUseBlock (+17)
40
- **Entry files:** client, convert, errors
41
-
42
- ## 🚨 Issues
43
-
44
- - 📋 1 duplicated groups
45
-
46
- ## 🧹 Dead Code & Tests
47
-
48
- **Orphaned:** tool-use.js, basic-chat.js, streaming.js, vision.js, package.json, multi-turn.js
49
- **Tests:** 0/11 (0%)
50
-
51
- ## 📦 Modules
52
-
53
- - lib: 3f, 3cx, 0↑3↓
54
- - examples: 5f, 0cx, 0↑0↓
55
-
56
- ## 📄 File Index
57
-
58
- **examples/basic-chat.js** 34L fn: main
59
- **examples/multi-turn.js** 45L fn: chat, main
60
- **examples/streaming.js** 81L fn: main
61
- **examples/tool-use.js** 77L fn: nonStreamingExample, streamingExample, main
62
- **examples/vision.js** 84L fn: base64Example, inlineDataExample, publicUrlExample, main
63
- **index.d.ts** 128L
64
- **index.js** 112L exports: [convertTools], [convertMessages], [streamGemini], [generateGemini], [cleanSchema] fn: streamGemini, createFullStream, generateGemini
65
- **lib/client.js** 10L exports: [getClient] fn: getClient
66
- **lib/convert.js** 86L exports: [cleanSchema], [extractModelId], [convertMessages], [buildConfig], [convertTools] fn: cleanSchema, convertTools, convertImageBlock, convertMessages (+2)
67
- **lib/errors.js** 35L exports: [GeminiError], [isRetryable], [withRetry] fn: constructor, isRetryable, withRetry
68
- **package.json** 29L
69
-
70
- Git: branch: claude/add-code-router-features-TJkSb, 1 uncommitted
71
- Hot: package.json(1), README.md(1), index.js(1)
72
- Conv[JS]: 4-space, single quotes, semicolons, function declarations, relative imports, kebab-case files
73
- Conv[TS]: 2-space, single quotes, semicolons, function declarations, named exports