thebird 1.2.80 → 1.2.82

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/docs/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <body>
12
12
  <div style="display:flex;flex-direction:column;height:100%">
13
13
  <div class="tui-header"><pre class="logo" style="margin:0;padding:2px 1ch"> ▀█▀ █░█ █▀▀ █▄▄ █ █▀█ █▀▄ <span class="ver">v1.2</span>
14
- █ █▀█ ██▄ █▄█ █ █▀▄ █▄▀ <span style="color:#1a9a1a">anthropic gemini/openai bridge</span></pre></div>
14
+ █ █▀█ ██▄ █▄█ █ █▀▄ █▄▀ <span style="color:#1a9a1a">instant posix js</span></pre></div>
15
15
  <div class="tui-tabs">
16
16
  <button id="tab-chat" class="tui-tab active" onclick="switchTab('chat')">Chat</button>
17
17
  <button id="tab-term" class="tui-tab" onclick="switchTab('term')">Terminal</button>
@@ -0,0 +1,15 @@
1
+ export async function mirrorFromSandbox(fsBase, _touchedPaths) {
2
+ const listRes = await fetch(fsBase + '/__list');
3
+ if (!listRes.ok) return [];
4
+ const relFiles = await listRes.json();
5
+ const snap = window.__debug.idbSnapshot || (window.__debug.idbSnapshot = {});
6
+ const mirrored = [];
7
+ for (const rel of relFiles) {
8
+ const r = await fetch(fsBase + '/' + rel);
9
+ if (!r.ok) continue;
10
+ const content = await r.text();
11
+ if (snap[rel] !== content) { snap[rel] = content; mirrored.push(rel); }
12
+ }
13
+ if (mirrored.length) { window.__debug.idbPersist?.(); window.__debug.shell?.onPreviewWrite?.(); }
14
+ return mirrored;
15
+ }
@@ -1,24 +1,47 @@
1
+ import { mirrorFromSandbox } from './kilo-fs-mirror.js';
2
+
1
3
  export async function* streamKiloHTTP({ url, model, messages }) {
2
4
  yield { type: 'start-step' };
3
5
  const base = (url || 'http://localhost:4780').replace(/\/$/, '');
6
+ const fsBase = base.replace(/:\d+$/, ':4781');
4
7
  let sessRes;
5
8
  try { sessRes = await fetch(base + '/session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }); }
6
- catch (e) { throw new Error('kilo serve not reachable at ' + base + ' — start it with: kilo serve --port ' + (new URL(base).port || 4780) + ' --cors ' + location.origin); }
9
+ catch (e) { throw new Error('kilo serve not reachable at ' + base + ' — start it with: node start-kilo.js --origin ' + location.origin); }
7
10
  if (!sessRes.ok) throw new Error('kilo /session ' + sessRes.status + ': ' + await sessRes.text());
8
11
  const { id: sessionId } = await sessRes.json();
9
- Object.assign(window.__debug = window.__debug || {}, { kilo: { sessionId, url: base, lastStatus: null } });
12
+ Object.assign(window.__debug = window.__debug || {}, { kilo: { sessionId, url: base, fsBase, writes: [], lastStatus: null } });
13
+
14
+ const es = new EventSource(base + '/event');
15
+ const pendingWrites = new Set();
16
+ es.onmessage = (ev) => {
17
+ try {
18
+ const msg = JSON.parse(ev.data);
19
+ if (msg.type === 'message.part.updated') {
20
+ const part = msg.properties?.part;
21
+ if (part?.type === 'tool' && part.state?.status === 'completed' && (part.tool === 'write' || part.tool === 'edit')) {
22
+ const abs = part.state.input?.filePath;
23
+ if (abs) pendingWrites.add(abs);
24
+ }
25
+ }
26
+ } catch (_) {}
27
+ };
10
28
 
11
29
  const userText = messages.filter(m => m.role === 'user').map(m =>
12
30
  typeof m.content === 'string' ? m.content : (m.content || []).filter(b => b.type === 'text').map(b => b.text).join('')
13
31
  ).join('\n');
14
32
 
15
- const body = { parts: [{ type: 'text', text: userText }], providerID: 'kilo', modelID: model || 'x-ai/grok-code-fast-1:optimized:free', agent: 'hermes-llm' };
33
+ const body = { parts: [{ type: 'text', text: userText }], providerID: 'kilo', modelID: model || 'x-ai/grok-code-fast-1:optimized:free' };
16
34
  const msgRes = await fetch(base + '/session/' + sessionId + '/message', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
17
35
  window.__debug.kilo.lastStatus = msgRes.status;
18
- if (!msgRes.ok) throw new Error('kilo message ' + msgRes.status + ': ' + await msgRes.text());
36
+ if (!msgRes.ok) { es.close(); throw new Error('kilo message ' + msgRes.status + ': ' + await msgRes.text()); }
19
37
  const result = await msgRes.json();
20
38
  window.__debug.kilo.lastResult = result;
39
+ es.close();
40
+ const mirrored = await mirrorFromSandbox(fsBase, [...pendingWrites]);
41
+ window.__debug.kilo.writes = mirrored;
42
+ if (mirrored.length) window.refreshPreview?.();
21
43
  const textParts = (result.parts || []).filter(p => p.type === 'text');
22
44
  for (const tp of textParts) yield { type: 'text-delta', textDelta: tp.text };
45
+ if (mirrored.length) yield { type: 'text-delta', textDelta: '\n\n[mirrored to preview: ' + mirrored.join(', ') + ']' };
23
46
  yield { type: 'finish-step', finishReason: result.info?.finish || 'stop' };
24
47
  }
package/docs/shell.js CHANGED
@@ -35,6 +35,7 @@ export function createShell({ term, onPreviewWrite }) {
35
35
  const toKey = p => p.replace(/^\//, '');
36
36
  const snap = () => window.__debug.idbSnapshot || {};
37
37
 
38
+ let expandTokens, captureRun;
38
39
  const BUILTINS = makeBuiltins(ctx, actor, invokeBuiltin);
39
40
  ctx.builtinsRef = BUILTINS;
40
41
  const _exp = makeExpander(ctx, l => captureRun(l), t => parseRedirect(t));
@@ -55,8 +56,6 @@ export function createShell({ term, onPreviewWrite }) {
55
56
  const runNode = makeNodeRunner(ctx, actor);
56
57
  const runNpmResult = makeNpmResultRunner(ctx, line => run(line));
57
58
 
58
- let expandTokens, captureRun;
59
-
60
59
  async function captureFn(fn) {
61
60
  let out = ''; const orig = term.write.bind(term); term.write = s => { out += s; };
62
61
  try { await fn(); } finally { term.write = orig; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.80",
3
+ "version": "1.2.82",
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/start-kilo.js CHANGED
@@ -1,17 +1,45 @@
1
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
2
  const { spawn } = require('child_process');
3
+ const http = require('http');
4
+ const fs = require('fs');
5
+ const path = require('path');
5
6
  const os = require('os');
6
7
  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';
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 });
12
14
  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());
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(); });