thebird 1.2.45 → 1.2.46

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added
4
+ - `docs/shell.js`: `createShell({ term, onPreviewWrite })` — POSIX shell + Node REPL using browser V8 eval + xstate v5 state machine. Dispatch table of built-ins: ls, cat, echo, pwd, cd, mkdir, rm, cp, mv, env, export, clear, help, node, npm install, exit. Pipe support via ` | ` split. `window.__debug.shell` exposes state, cwd, env, history, httpHandlers, nodeMode. `http.createServer` polyfill registers handlers in httpHandlers map.
5
+ - `docs/shell-node.js`: `createNodeEnv({ ctx, term })` — persistent V8 eval scope with process, console, require (IDB node_modules), Buffer shim, http.createServer polyfill, fetch, timers.
6
+
3
7
  ### Fixed
4
8
  - Gemini tool result wrapping: string results wrapped as `{ output: result }` to satisfy Gemini Struct requirement for `function_response.response`
5
9
  - Browser bundle rebuilt with fix
@@ -0,0 +1,54 @@
1
+ export function createNodeEnv({ ctx, term }) {
2
+ const scope = {
3
+ process: {
4
+ argv: [],
5
+ env: ctx.env,
6
+ cwd: () => ctx.cwd,
7
+ exit: code => term.write('[exit ' + code + ']\r\n'),
8
+ },
9
+ console: {
10
+ log: (...a) => term.write(a.map(String).join(' ') + '\r\n'),
11
+ error: (...a) => term.write('\x1b[31m' + a.map(String).join(' ') + '\x1b[0m\r\n'),
12
+ warn: (...a) => term.write('\x1b[33m' + a.map(String).join(' ') + '\x1b[0m\r\n'),
13
+ },
14
+ require: id => {
15
+ const key = 'node_modules/' + id + '/index.js';
16
+ const src = (window.__debug.idbSnapshot || {})[key];
17
+ if (src == null) throw new Error('module not found: ' + id);
18
+ const mod = { exports: {} };
19
+ new Function('module', 'exports', 'require', src)(mod, mod.exports, scope.require);
20
+ return mod.exports;
21
+ },
22
+ setTimeout, setInterval, clearTimeout, clearInterval, fetch,
23
+ Buffer: {
24
+ from: (s, enc) => enc === 'base64'
25
+ ? new Uint8Array(atob(s).split('').map(c => c.charCodeAt(0)))
26
+ : new TextEncoder().encode(s),
27
+ toString: (buf, enc) => enc === 'base64'
28
+ ? btoa(String.fromCharCode(...buf))
29
+ : new TextDecoder().decode(buf),
30
+ },
31
+ get __filename() { return ctx.cwd + '/repl'; },
32
+ get __dirname() { return ctx.cwd; },
33
+ http: {
34
+ createServer: handler => ({
35
+ listen: (port, cb) => {
36
+ window.__debug.shell.httpHandlers[port] = handler;
37
+ term.write('listening on :' + port + '\r\n');
38
+ cb?.();
39
+ },
40
+ }),
41
+ },
42
+ };
43
+
44
+ return async function nodeEval(code, filename) {
45
+ try {
46
+ const keys = Object.keys(scope);
47
+ const vals = Object.values(scope);
48
+ const fn = new Function(...keys, 'return (async () => {\n' + code + '\n})()');
49
+ await fn(...vals);
50
+ } catch (e) {
51
+ term.write('\x1b[31m' + (filename ? filename + ': ' : '') + e.message + '\x1b[0m\r\n');
52
+ }
53
+ };
54
+ }
package/docs/shell.js ADDED
@@ -0,0 +1,164 @@
1
+ import { createMachine, createActor } from './vendor/xstate.js';
2
+ import { createNodeEnv } from './shell-node.js';
3
+
4
+ function resolvePath(cwd, p) {
5
+ if (!p || p === '~') return '/';
6
+ if (p.startsWith('~/')) p = '/' + p.slice(2);
7
+ if (!p.startsWith('/')) p = cwd.replace(/\/$/, '') + '/' + p;
8
+ const parts = [];
9
+ for (const s of p.split('/')) {
10
+ if (s === '..') parts.pop();
11
+ else if (s && s !== '.') parts.push(s);
12
+ }
13
+ return '/' + parts.join('/');
14
+ }
15
+
16
+ function makeBuiltins(ctx) {
17
+ const snap = () => window.__debug.idbSnapshot || {};
18
+ const w = s => ctx.term.write(s);
19
+ const wl = s => w(s + '\r\n');
20
+ return {
21
+ ls: ([p]) => {
22
+ const prefix = resolvePath(ctx.cwd, p || '') + '/';
23
+ const keys = prefix === '//' ? Object.keys(snap()) : Object.keys(snap()).filter(k => k.startsWith(prefix === '//' ? '/' : prefix));
24
+ wl(keys.join('\r\n') || '(empty)');
25
+ },
26
+ cat: ([f]) => {
27
+ const c = snap()[resolvePath(ctx.cwd, f)];
28
+ if (c == null) throw new Error('no such file: ' + f);
29
+ wl(c);
30
+ },
31
+ echo: args => wl(args.join(' ')),
32
+ pwd: () => wl(ctx.cwd),
33
+ cd: ([p]) => { ctx.cwd = resolvePath(ctx.cwd, p || '~'); },
34
+ mkdir: ([p]) => {
35
+ window.__debug.idbSnapshot[resolvePath(ctx.cwd, p) + '/.keep'] = '';
36
+ window.__debug.idbPersist?.();
37
+ },
38
+ rm: ([f]) => {
39
+ delete window.__debug.idbSnapshot[resolvePath(ctx.cwd, f)];
40
+ window.__debug.idbPersist?.();
41
+ },
42
+ cp: ([s, d]) => {
43
+ window.__debug.idbSnapshot[resolvePath(ctx.cwd, d)] = snap()[resolvePath(ctx.cwd, s)];
44
+ window.__debug.idbPersist?.();
45
+ },
46
+ mv: ([s, d]) => {
47
+ const src = resolvePath(ctx.cwd, s), dst = resolvePath(ctx.cwd, d);
48
+ window.__debug.idbSnapshot[dst] = snap()[src];
49
+ delete window.__debug.idbSnapshot[src];
50
+ window.__debug.idbPersist?.();
51
+ },
52
+ env: () => wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n')),
53
+ export: ([kv]) => { const [k, ...v] = (kv || '').split('='); ctx.env[k] = v.join('='); },
54
+ clear: () => ctx.term.clear(),
55
+ help: () => wl(Object.keys(makeBuiltins(ctx)).join(' ')),
56
+ exit: () => { if (ctx.nodeMode) { ctx.nodeMode = false; wl('[shell]'); } },
57
+ node: async ([file]) => {
58
+ if (!file) { ctx.nodeMode = true; wl('[node repl — type exit to return]'); return; }
59
+ const path = resolvePath(ctx.cwd, file);
60
+ const code = snap()[path];
61
+ if (code == null) throw new Error('no such file: ' + path);
62
+ await ctx.nodeEval(code, path);
63
+ },
64
+ npm: async args => {
65
+ if (args[0] !== 'install') throw new Error('only npm install supported');
66
+ const pkg = args[1];
67
+ if (!pkg) throw new Error('npm install <pkg>');
68
+ w('fetching ' + pkg + '...\r\n');
69
+ const r = await fetch('https://esm.sh/' + pkg);
70
+ if (!r.ok) throw new Error('fetch failed: ' + r.status);
71
+ const key = 'node_modules/' + pkg + '/index.js';
72
+ window.__debug.idbSnapshot[key] = await r.text();
73
+ window.__debug.idbPersist?.();
74
+ wl('installed ' + pkg);
75
+ },
76
+ };
77
+ }
78
+
79
+ const machine = createMachine({ id: 'shell', initial: 'idle', states: {
80
+ idle: { on: { RUN: 'executing' } },
81
+ executing: { on: { DONE: 'idle', ERROR: 'idle' } },
82
+ }});
83
+
84
+ export function createShell({ term, onPreviewWrite }) {
85
+ const ctx = { term, cwd: '/', env: {}, nodeMode: false, history: [], httpHandlers: {} };
86
+ const BUILTINS = makeBuiltins(ctx);
87
+ ctx.nodeEval = createNodeEnv({ ctx, term });
88
+
89
+ const actor = createActor(machine);
90
+ actor.start();
91
+
92
+ window.__debug = window.__debug || {};
93
+ window.__debug.shell = {
94
+ get state() { return actor.getSnapshot().value; },
95
+ get cwd() { return ctx.cwd; },
96
+ get env() { return ctx.env; },
97
+ get history() { return ctx.history; },
98
+ httpHandlers: ctx.httpHandlers,
99
+ get nodeMode() { return ctx.nodeMode; },
100
+ };
101
+
102
+ async function runCmd(line, capture) {
103
+ if (!line.trim()) return '';
104
+ const [cmd, ...args] = line.trim().split(/\s+/);
105
+ const fn = BUILTINS[cmd];
106
+ if (!capture) {
107
+ if (fn) await fn(args); else term.write('command not found: ' + cmd + '\r\n');
108
+ return '';
109
+ }
110
+ let out = '';
111
+ const orig = term.write.bind(term);
112
+ term.write = s => { out += s; };
113
+ try { if (fn) await fn(args); else out += 'command not found: ' + cmd + '\r\n'; }
114
+ finally { term.write = orig; }
115
+ return out;
116
+ }
117
+
118
+ async function run(line) {
119
+ if (!line.trim()) return;
120
+ ctx.history.push(line);
121
+ if (ctx.nodeMode && line.trim() !== 'exit') { await ctx.nodeEval(line); return; }
122
+ if (line.trim() === 'exit') { BUILTINS.exit([]); return; }
123
+ actor.send({ type: 'RUN' });
124
+ try {
125
+ const parts = line.split(' | ');
126
+ if (parts.length > 1) {
127
+ let buf = await runCmd(parts[0], true);
128
+ for (const p of parts.slice(1)) {
129
+ const [cmd, ...args] = p.trim().split(/\s+/);
130
+ const fn = BUILTINS[cmd];
131
+ if (fn) await fn([buf, ...args]); else term.write('command not found: ' + cmd + '\r\n');
132
+ buf = '';
133
+ }
134
+ } else {
135
+ await runCmd(line, false);
136
+ }
137
+ actor.send({ type: 'DONE' });
138
+ } catch (e) {
139
+ term.write('\x1b[31m' + e.message + '\x1b[0m\r\n');
140
+ actor.send({ type: 'ERROR' });
141
+ }
142
+ }
143
+
144
+ const prompt = () => term.write('\r\n\x1b[32m' + ctx.cwd + ' $ \x1b[0m');
145
+ let buf = '';
146
+ term.onData(async data => {
147
+ if (data === '\r') {
148
+ term.write('\r\n');
149
+ const line = buf;
150
+ buf = '';
151
+ await run(line);
152
+ prompt();
153
+ } else if (data === '\x7f') {
154
+ if (buf.length) { buf = buf.slice(0, -1); term.write('\x08 \x08'); }
155
+ } else {
156
+ buf += data;
157
+ term.write(data);
158
+ }
159
+ });
160
+
161
+ onPreviewWrite && (window.__debug.shell.onPreviewWrite = onPreviewWrite);
162
+ prompt();
163
+ return { run };
164
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.45",
3
+ "version": "1.2.46",
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
  "main": "index.js",
6
6
  "types": "index.d.ts",