thebird 1.2.79 → 1.2.81
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.
- package/.github/workflows/publish.yml +9 -1
- package/CHANGELOG.md +217 -0
- package/CLAUDE.md +16 -0
- package/docs/agent-chat.js +7 -4
- package/docs/app.js +14 -11
- package/docs/defaults.json +1 -1
- package/docs/index.html +23 -6
- package/docs/kilo-fs-mirror.js +15 -0
- package/docs/kilo-http-stream.js +47 -0
- package/docs/node-builtins.js +24 -0
- package/docs/preview/index.html +32 -0
- package/docs/preview-sw-client.js +37 -6
- package/docs/preview-sw.js +55 -51
- package/docs/shell-awk.js +113 -0
- package/docs/shell-builtins-extra.js +121 -0
- package/docs/shell-builtins-text.js +109 -0
- package/docs/shell-builtins-util.js +112 -0
- package/docs/shell-builtins.js +183 -0
- package/docs/shell-bun.js +45 -0
- package/docs/shell-control.js +132 -0
- package/docs/shell-deno.js +54 -0
- package/docs/shell-exec.js +85 -0
- package/docs/shell-expand.js +164 -0
- package/docs/shell-fd.js +86 -0
- package/docs/shell-jobs.js +86 -0
- package/docs/shell-node-advanced.js +86 -0
- package/docs/shell-node-brotli.js +22 -0
- package/docs/shell-node-busnet.js +90 -0
- package/docs/shell-node-cipher.js +61 -0
- package/docs/shell-node-cluster.js +33 -0
- package/docs/shell-node-coreutils.js +36 -0
- package/docs/shell-node-crypto.js +137 -0
- package/docs/shell-node-dns.js +41 -0
- package/docs/shell-node-extras.js +148 -0
- package/docs/shell-node-firefox.js +95 -0
- package/docs/shell-node-git.js +60 -0
- package/docs/shell-node-inspector.js +39 -0
- package/docs/shell-node-io.js +131 -0
- package/docs/shell-node-ipc.js +15 -0
- package/docs/shell-node-keyobject.js +60 -0
- package/docs/shell-node-modules.js +157 -0
- package/docs/shell-node-native.js +31 -0
- package/docs/shell-node-net.js +71 -0
- package/docs/shell-node-observe.js +80 -0
- package/docs/shell-node-opfs.js +54 -0
- package/docs/shell-node-procfs.js +42 -0
- package/docs/shell-node-profiler.js +50 -0
- package/docs/shell-node-registry.js +24 -0
- package/docs/shell-node-resolve.js +147 -0
- package/docs/shell-node-runtime.js +83 -0
- package/docs/shell-node-srcmap.js +52 -0
- package/docs/shell-node-stdlib.js +103 -0
- package/docs/shell-node-streams.js +66 -0
- package/docs/shell-node-tar.js +47 -0
- package/docs/shell-node-testrunner.js +35 -0
- package/docs/shell-node-util-extras.js +66 -0
- package/docs/shell-node.js +175 -169
- package/docs/shell-npm.js +173 -0
- package/docs/shell-parser.js +122 -0
- package/docs/shell-pm-layout.js +62 -0
- package/docs/shell-pm.js +39 -0
- package/docs/shell-posix.js +70 -0
- package/docs/shell-procsub.js +65 -0
- package/docs/shell-readline.js +59 -4
- package/docs/shell-runtime.js +37 -0
- package/docs/shell-sed.js +83 -0
- package/docs/shell-signals.js +54 -0
- package/docs/shell-sw-jobs.js +76 -0
- package/docs/shell-ts.js +30 -0
- package/docs/shell.js +161 -152
- package/docs/terminal.js +9 -11
- package/docs/todo.html +211 -0
- package/package.json +1 -1
- package/server.js +43 -4
- package/start-kilo.js +45 -0
- package/test.js +199 -0
- package/.codeinsight +0 -73
- package/docs/acp-stream.js +0 -102
- 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
|
+
'&': '&',
|
|
196
|
+
'<': '<',
|
|
197
|
+
'>': '>',
|
|
198
|
+
'"': '"',
|
|
199
|
+
"'": '''
|
|
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
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(
|
|
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(
|
|
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, '<')}</pre></body></html>`);
|
|
74
113
|
} finally {
|
|
75
114
|
state.active--;
|
|
76
115
|
}
|
package/start-kilo.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const get = (f, d) => { const i = args.indexOf(f); return i >= 0 ? args[i + 1] : d; };
|
|
9
|
+
const kiloPort = get('--port', '4780');
|
|
10
|
+
const fsPort = get('--fs-port', '4781');
|
|
11
|
+
const origin = get('--origin', 'http://localhost:8787');
|
|
12
|
+
const sandbox = path.resolve(get('--sandbox', '.sandbox'));
|
|
13
|
+
fs.mkdirSync(sandbox, { recursive: true });
|
|
14
|
+
const kiloWin = process.env.USERPROFILE + '\\AppData\\Roaming\\npm\\node_modules\\@kilocode\\cli\\node_modules\\@kilocode\\cli-windows-x64\\bin\\kilo.exe';
|
|
15
|
+
const bin = os.platform() === 'win32' && fs.existsSync(kiloWin) ? kiloWin : 'kilo';
|
|
16
|
+
const kilo = spawn(bin, ['serve', '--port', kiloPort, '--hostname', '127.0.0.1', '--cors', origin], { stdio: 'inherit', env: process.env, cwd: sandbox });
|
|
17
|
+
const cors = { 'Access-Control-Allow-Origin': origin, 'Access-Control-Allow-Methods': 'GET,OPTIONS', 'Access-Control-Allow-Headers': 'content-type' };
|
|
18
|
+
const srv = http.createServer((req, res) => {
|
|
19
|
+
if (req.method === 'OPTIONS') { res.writeHead(204, cors); res.end(); return; }
|
|
20
|
+
const rel = decodeURIComponent(req.url.replace(/^\/+/, '').split('?')[0]);
|
|
21
|
+
if (rel === '__list') {
|
|
22
|
+
const out = [];
|
|
23
|
+
const walk = d => { for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
24
|
+
if (e.name.startsWith('.')) continue;
|
|
25
|
+
const full = path.join(d, e.name);
|
|
26
|
+
if (e.isDirectory()) walk(full);
|
|
27
|
+
else out.push(path.relative(sandbox, full).replace(/\\/g, '/'));
|
|
28
|
+
}};
|
|
29
|
+
try { walk(sandbox); } catch (e) {}
|
|
30
|
+
res.writeHead(200, { ...cors, 'Content-Type': 'application/json' });
|
|
31
|
+
res.end(JSON.stringify(out));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const full = path.resolve(path.join(sandbox, rel));
|
|
35
|
+
if (!full.startsWith(sandbox)) { res.writeHead(403, cors); res.end('forbidden'); return; }
|
|
36
|
+
fs.readFile(full, (err, data) => {
|
|
37
|
+
if (err) { res.writeHead(404, cors); res.end('not found'); return; }
|
|
38
|
+
const ct = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.md': 'text/plain' }[path.extname(rel)] || 'application/octet-stream';
|
|
39
|
+
res.writeHead(200, { ...cors, 'Content-Type': ct });
|
|
40
|
+
res.end(data);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
srv.listen(fsPort, '127.0.0.1', () => console.log('[fs-bridge] sandbox=' + sandbox + ' serving http://127.0.0.1:' + fsPort));
|
|
44
|
+
kilo.on('exit', c => { srv.close(); process.exit(c || 0); });
|
|
45
|
+
process.on('SIGINT', () => { kilo.kill(); srv.close(); });
|
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&®.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
|