thebird 1.2.77 → 1.2.79

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.
@@ -4,41 +4,45 @@ on:
4
4
  push:
5
5
  branches: ['**']
6
6
 
7
+ permissions:
8
+ contents: write
9
+
7
10
  jobs:
8
11
  declaudeify:
9
- if: contains(github.event.head_commit.author.name, 'Claude') || contains(github.event.head_commit.author.email, 'claude')
10
12
  runs-on: ubuntu-latest
11
13
  steps:
12
14
  - name: Checkout
13
15
  uses: actions/checkout@v4
14
16
  with:
15
17
  fetch-depth: 0
18
+ token: ${{ secrets.GITHUB_TOKEN }}
16
19
 
17
20
  - name: Check for Claude commits
18
21
  id: check
19
22
  run: |
20
23
  CLAUDE_COMMITS=$(git log --all --author="Claude" --oneline | wc -l)
21
24
  echo "count=$CLAUDE_COMMITS" >> $GITHUB_OUTPUT
22
- if [ $CLAUDE_COMMITS -gt 0 ]; then
25
+ if [ "$CLAUDE_COMMITS" -gt 0 ]; then
23
26
  echo "Found $CLAUDE_COMMITS Claude commits, will filter"
27
+ else
28
+ echo "No Claude commits found"
24
29
  fi
25
30
 
26
31
  - name: Filter Claude from history
27
- if: steps.check.outputs.count > 0
32
+ if: steps.check.outputs.count != '0'
28
33
  run: |
29
- git filter-branch --force --env-filter \
30
- 'if [ "$GIT_AUTHOR_NAME" = "Claude" ]; then \
31
- export GIT_AUTHOR_NAME="lanmower"; \
32
- export GIT_AUTHOR_EMAIL="lanmower@lanmower.com"; \
33
- fi; \
34
- if [ "$GIT_COMMITTER_NAME" = "Claude" ]; then \
35
- export GIT_COMMITTER_NAME="lanmower"; \
36
- export GIT_COMMITTER_EMAIL="lanmower@lanmower.com"; \
37
- fi' \
38
- --tag-name-filter cat -- --all
34
+ git config user.name "lanmower"
35
+ git config user.email "lanmower@lanmower.com"
36
+ git filter-branch --force --env-filter '
37
+ if [ "$GIT_AUTHOR_NAME" = "Claude" ]; then
38
+ export GIT_AUTHOR_NAME="lanmower"
39
+ export GIT_AUTHOR_EMAIL="lanmower@lanmower.com"
40
+ fi
41
+ if [ "$GIT_COMMITTER_NAME" = "Claude" ]; then
42
+ export GIT_COMMITTER_NAME="lanmower"
43
+ export GIT_COMMITTER_EMAIL="lanmower@lanmower.com"
44
+ fi' --tag-name-filter cat -- --all
39
45
 
40
46
  - name: Force push filtered history
41
- if: steps.check.outputs.count > 0
47
+ if: steps.check.outputs.count != '0'
42
48
  run: git push --all --force
43
- env:
44
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added
4
+ - `docs/node-builtins.js`: Full Node.js module polyfills — path, fs (IDB-backed), events (EventEmitter), url, querystring, Buffer class with encoding support
5
+ - `docs/shell-node.js`: Enhanced Node env — relative require, JSON require, per-file __dirname, process.stdout/stderr/nextTick/argv/hrtime, console.dir/table/time/assert/count, express with route params/middleware/static/json, os/util/crypto/stream modules
6
+ - `docs/shell.js`: Added node -e/-v flags, touch/head/tail/wc/grep/which commands, npm i alias
7
+
8
+ ### Changed
9
+ - `docs/shell-node.js`: Rewritten to import from node-builtins.js; require() resolves relative paths, .json, directory/index.js
10
+ - `docs/shell.js`: Trimmed from 206L to 189L; ls shows directory entries properly
11
+
3
12
  ### Added
4
13
  - `lib/errors.js`: Typed error hierarchy — BridgeError, AuthError, RateLimitError, TimeoutError, ContextWindowError, ContentPolicyError, ProviderError with classifyError factory. GeminiError kept as alias.
5
14
  - `lib/errors.js`: `redactKeys()` — auto-redacts API keys (AIza, sk-, key- patterns) in error messages to `...XXXX`
@@ -0,0 +1,170 @@
1
+ const snap = () => window.__debug?.idbSnapshot || {};
2
+ const toKey = p => p.replace(/^\//, '');
3
+ const persist = () => window.__debug?.idbPersist?.();
4
+ const previewWrite = () => window.__debug?.shell?.onPreviewWrite?.();
5
+
6
+ export function createPath() {
7
+ const sep = '/';
8
+ const normalize = p => {
9
+ const parts = [];
10
+ for (const s of p.split('/')) {
11
+ if (s === '..') parts.pop();
12
+ else if (s && s !== '.') parts.push(s);
13
+ }
14
+ return (p.startsWith('/') ? '/' : '') + parts.join('/');
15
+ };
16
+ return {
17
+ sep,
18
+ normalize,
19
+ join: (...a) => normalize(a.join('/')),
20
+ resolve: (...a) => {
21
+ let r = '';
22
+ for (const p of a) r = p.startsWith('/') ? p : r + '/' + p;
23
+ return normalize(r);
24
+ },
25
+ dirname: p => { const i = p.lastIndexOf('/'); return i <= 0 ? '/' : p.slice(0, i); },
26
+ basename: (p, ext) => { const b = p.split('/').pop() || ''; return ext && b.endsWith(ext) ? b.slice(0, -ext.length) : b; },
27
+ extname: p => { const b = p.split('/').pop() || ''; const i = b.lastIndexOf('.'); return i > 0 ? b.slice(i) : ''; },
28
+ isAbsolute: p => p.startsWith('/'),
29
+ relative: (from, to) => to.replace(from.replace(/\/$/, '') + '/', ''),
30
+ parse: p => {
31
+ const dir = p.slice(0, p.lastIndexOf('/')) || '/';
32
+ const base = p.split('/').pop() || '';
33
+ const ext = base.lastIndexOf('.') > 0 ? base.slice(base.lastIndexOf('.')) : '';
34
+ return { root: '/', dir, base, ext, name: ext ? base.slice(0, -ext.length) : base };
35
+ },
36
+ };
37
+ }
38
+
39
+ export function createFs() {
40
+ const resolveP = p => typeof p === 'string' ? p : p.toString();
41
+ return {
42
+ readFileSync: (p, enc) => {
43
+ const key = toKey(resolveP(p));
44
+ const data = snap()[key];
45
+ if (data == null) throw Object.assign(new Error('ENOENT: ' + p), { code: 'ENOENT' });
46
+ return enc ? data : data;
47
+ },
48
+ writeFileSync: (p, data) => {
49
+ const s = snap();
50
+ s[toKey(resolveP(p))] = typeof data === 'string' ? data : String(data);
51
+ persist();
52
+ previewWrite();
53
+ },
54
+ appendFileSync: (p, data) => {
55
+ const key = toKey(resolveP(p));
56
+ const s = snap();
57
+ s[key] = (s[key] || '') + (typeof data === 'string' ? data : String(data));
58
+ persist();
59
+ },
60
+ existsSync: p => toKey(resolveP(p)) in snap(),
61
+ unlinkSync: p => {
62
+ const key = toKey(resolveP(p));
63
+ if (!(key in snap())) throw Object.assign(new Error('ENOENT: ' + p), { code: 'ENOENT' });
64
+ delete snap()[key];
65
+ persist();
66
+ },
67
+ mkdirSync: (p, opts) => {
68
+ const key = toKey(resolveP(p));
69
+ if (!snap()[key + '/.keep']) { snap()[key + '/.keep'] = ''; persist(); }
70
+ },
71
+ readdirSync: p => {
72
+ const prefix = toKey(resolveP(p));
73
+ const pLen = prefix ? prefix.length + 1 : 0;
74
+ const seen = new Set();
75
+ for (const k of Object.keys(snap())) {
76
+ if (prefix && !k.startsWith(prefix + '/') && k !== prefix) continue;
77
+ if (!prefix && !k.includes('/')) { seen.add(k); continue; }
78
+ const rest = k.slice(pLen);
79
+ const first = rest.split('/')[0];
80
+ if (first && first !== '.keep') seen.add(first);
81
+ }
82
+ return [...seen];
83
+ },
84
+ statSync: p => {
85
+ const key = toKey(resolveP(p));
86
+ const s = snap();
87
+ const isFile = key in s;
88
+ const isDir = !isFile && Object.keys(s).some(k => k.startsWith(key + '/'));
89
+ if (!isFile && !isDir) throw Object.assign(new Error('ENOENT: ' + p), { code: 'ENOENT' });
90
+ return { isFile: () => isFile, isDirectory: () => isDir, size: isFile ? (s[key]?.length || 0) : 0 };
91
+ },
92
+ renameSync: (o, n) => {
93
+ const s = snap();
94
+ const ok = toKey(resolveP(o)), nk = toKey(resolveP(n));
95
+ if (!(ok in s)) throw Object.assign(new Error('ENOENT: ' + o), { code: 'ENOENT' });
96
+ s[nk] = s[ok];
97
+ delete s[ok];
98
+ persist();
99
+ },
100
+ copyFileSync: (s, d) => {
101
+ const src = snap()[toKey(resolveP(s))];
102
+ if (src == null) throw Object.assign(new Error('ENOENT: ' + s), { code: 'ENOENT' });
103
+ snap()[toKey(resolveP(d))] = src;
104
+ persist();
105
+ },
106
+ };
107
+ }
108
+
109
+ export function createEvents() {
110
+ return class EventEmitter {
111
+ constructor() { this._e = {}; }
112
+ on(ev, fn) { (this._e[ev] = this._e[ev] || []).push(fn); return this; }
113
+ once(ev, fn) { const w = (...a) => { this.off(ev, w); fn(...a); }; return this.on(ev, w); }
114
+ off(ev, fn) { this._e[ev] = (this._e[ev] || []).filter(f => f !== fn); return this; }
115
+ removeListener(ev, fn) { return this.off(ev, fn); }
116
+ removeAllListeners(ev) { if (ev) delete this._e[ev]; else this._e = {}; return this; }
117
+ emit(ev, ...a) { for (const fn of (this._e[ev] || [])) fn(...a); return (this._e[ev] || []).length > 0; }
118
+ listeners(ev) { return (this._e[ev] || []).slice(); }
119
+ listenerCount(ev) { return (this._e[ev] || []).length; }
120
+ };
121
+ }
122
+
123
+ export function createUrl() {
124
+ return {
125
+ parse: s => {
126
+ const u = new URL(s);
127
+ return { protocol: u.protocol, host: u.host, hostname: u.hostname, port: u.port, pathname: u.pathname, search: u.search, query: u.search.slice(1), hash: u.hash, href: u.href };
128
+ },
129
+ format: o => {
130
+ const u = new URL('http://x');
131
+ for (const [k, v] of Object.entries(o)) { try { u[k] = v; } catch {} }
132
+ return u.href;
133
+ },
134
+ resolve: (from, to) => new URL(to, from).href,
135
+ };
136
+ }
137
+
138
+ export function createQuerystring() {
139
+ return {
140
+ parse: s => Object.fromEntries(new URLSearchParams(s)),
141
+ stringify: o => new URLSearchParams(o).toString(),
142
+ escape: s => encodeURIComponent(s),
143
+ unescape: s => decodeURIComponent(s),
144
+ };
145
+ }
146
+
147
+ export function createBuffer() {
148
+ class Buf extends Uint8Array {
149
+ toString(enc) {
150
+ if (enc === 'base64') return btoa(String.fromCharCode(...this));
151
+ if (enc === 'hex') return [...this].map(b => b.toString(16).padStart(2, '0')).join('');
152
+ return new TextDecoder().decode(this);
153
+ }
154
+ toJSON() { return { type: 'Buffer', data: [...this] }; }
155
+ slice(s, e) { return Buf.from(super.slice(s, e)); }
156
+ }
157
+ Buf.from = (d, enc) => {
158
+ if (d instanceof Uint8Array) return new Buf(d);
159
+ if (Array.isArray(d)) return new Buf(d);
160
+ if (typeof d !== 'string') return new Buf(0);
161
+ if (enc === 'base64') return new Buf(Uint8Array.from(atob(d), c => c.charCodeAt(0)));
162
+ if (enc === 'hex') return new Buf(d.match(/.{2}/g).map(h => parseInt(h, 16)));
163
+ return new Buf(new TextEncoder().encode(d));
164
+ };
165
+ Buf.alloc = (n, fill) => { const b = new Buf(n); if (fill) b.fill(typeof fill === 'number' ? fill : fill.charCodeAt(0)); return b; };
166
+ Buf.concat = list => { const t = list.reduce((s, b) => s + b.length, 0); const r = new Buf(t); let o = 0; for (const b of list) { r.set(b, o); o += b.length; } return r; };
167
+ Buf.isBuffer = o => o instanceof Buf;
168
+ Buf.byteLength = (s, enc) => Buf.from(s, enc).length;
169
+ return Buf;
170
+ }
@@ -1,106 +1,191 @@
1
+ import { createPath, createFs, createEvents, createUrl, createQuerystring, createBuffer } from './node-builtins.js';
2
+
1
3
  function serializeRoutes(routes) {
2
4
  const out = {};
3
- for (const [method, arr] of Object.entries(routes)) {
4
- out[method] = arr.map(r => ({ path: r.path }));
5
- }
5
+ for (const [method, arr] of Object.entries(routes)) out[method] = arr.map(r => ({ path: r.path }));
6
6
  return out;
7
7
  }
8
8
 
9
- const makeBuiltinModules = term => ({
10
- express: () => () => {
11
- const routes = { GET: [], POST: [], USE: [] };
12
- const app = {
13
- get: (path, fn) => routes.GET.push({ path, fn }),
14
- post: (path, fn) => routes.POST.push({ path, fn }),
15
- use: (fn) => routes.USE.push({ path: '*', fn }),
16
- listen: (port, cb) => {
17
- window.__debug.shell.httpHandlers[port] = { routes };
18
- navigator.serviceWorker?.controller?.postMessage({ type: 'REGISTER_ROUTES', port, routes: serializeRoutes(routes) });
19
- term.write('Express listening on :' + port + '\r\n');
20
- cb?.();
21
- },
9
+ function matchRoute(pattern, path) {
10
+ if (pattern === '*') return {};
11
+ const pp = pattern.split('/'), rp = path.split('/');
12
+ if (pp.length !== rp.length) return null;
13
+ const params = {};
14
+ for (let i = 0; i < pp.length; i++) {
15
+ if (pp[i].startsWith(':')) params[pp[i].slice(1)] = rp[i];
16
+ else if (pp[i] !== rp[i]) return null;
17
+ }
18
+ return params;
19
+ }
20
+
21
+ function createExpress(term, fsmod) {
22
+ return () => {
23
+ const routes = { GET: [], POST: [], PUT: [], DELETE: [], USE: [] };
24
+ const middlewares = [];
25
+ const app = fn => middlewares.push(fn);
26
+ app.get = (p, ...fns) => routes.GET.push({ path: p, fns });
27
+ app.post = (p, ...fns) => routes.POST.push({ path: p, fns });
28
+ app.put = (p, ...fns) => routes.PUT.push({ path: p, fns });
29
+ app.delete = (p, ...fns) => routes.DELETE.push({ path: p, fns });
30
+ app.use = (...args) => {
31
+ if (typeof args[0] === 'function') middlewares.push(args[0]);
32
+ else routes.USE.push({ path: args[0], fn: args[1] });
33
+ };
34
+ app.listen = (port, cb) => {
35
+ window.__debug.shell.httpHandlers[port] = { routes, middlewares };
36
+ navigator.serviceWorker?.controller?.postMessage({ type: 'REGISTER_ROUTES', port, routes: serializeRoutes(routes) });
37
+ term.write('Express listening on :' + port + '\r\n');
38
+ cb?.();
39
+ };
40
+ app.json = () => (req, res, next) => {
41
+ if (typeof req.body === 'string') try { req.body = JSON.parse(req.body); } catch {}
42
+ next?.();
43
+ };
44
+ app.static = dir => (req, res) => {
45
+ const fp = dir.replace(/\/$/, '') + req.path;
46
+ try { res.send(fsmod.readFileSync(fp)); } catch { res.status(404).send('Not Found'); }
22
47
  };
23
48
  return app;
24
- },
25
- 'better-sqlite3': () => class Database {
49
+ };
50
+ }
51
+
52
+ function createSqlite() {
53
+ return class Database {
26
54
  constructor(name) {
27
55
  this._name = name;
28
- if (!window.__sqlJs) throw new Error('sql.js not loaded — call await loadSql() first');
56
+ if (!window.__sqlJs) throw new Error('sql.js not loaded');
29
57
  this._db = new window.__sqlJs.Database();
30
58
  }
31
59
  prepare(sql) {
32
60
  const db = this._db;
33
61
  return {
34
- run: (...params) => { db.run(sql, params); return { changes: 1 }; },
35
- get: (...params) => { const r = db.exec(sql, params); return r[0]?.values[0] ? Object.fromEntries(r[0].columns.map((c, i) => [c, r[0].values[0][i]])) : undefined; },
36
- all: (...params) => { const r = db.exec(sql, params); if (!r[0]) return []; return r[0].values.map(row => Object.fromEntries(r[0].columns.map((c, i) => [c, row[i]]))); },
62
+ run: (...p) => { db.run(sql, p); return { changes: 1 }; },
63
+ get: (...p) => { const r = db.exec(sql, p); return r[0]?.values[0] ? Object.fromEntries(r[0].columns.map((c, i) => [c, r[0].values[0][i]])) : undefined; },
64
+ all: (...p) => { const r = db.exec(sql, p); if (!r[0]) return []; return r[0].values.map(row => Object.fromEntries(r[0].columns.map((c, i) => [c, row[i]]))); },
37
65
  };
38
66
  }
39
- },
40
- });
67
+ close() {}
68
+ };
69
+ }
41
70
 
42
- export function createNodeEnv({ ctx, term }) {
43
- const BUILTIN_MODULES = makeBuiltinModules(term);
44
- const scope = {
45
- process: {
46
- argv: [],
47
- env: ctx.env,
48
- cwd: () => ctx.cwd,
49
- exit: code => term.write('[exit ' + code + ']\r\n'),
50
- },
51
- console: {
52
- log: (...a) => term.write(a.map(String).join(' ') + '\r\n'),
53
- error: (...a) => term.write('\x1b[31m' + a.map(String).join(' ') + '\x1b[0m\r\n'),
54
- warn: (...a) => term.write('\x1b[33m' + a.map(String).join(' ') + '\x1b[0m\r\n'),
55
- },
56
- require: id => {
57
- if (BUILTIN_MODULES[id]) return BUILTIN_MODULES[id]();
58
- const key = 'node_modules/' + id + '/index.js';
59
- const src = (window.__debug.idbSnapshot || {})[key];
60
- if (src == null) throw new Error('module not found: ' + id);
61
- const mod = { exports: {} };
62
- new Function('module', 'exports', 'require', src)(mod, mod.exports, scope.require);
63
- return mod.exports;
71
+ function createConsole(term) {
72
+ const w = s => term.write(s + '\r\n');
73
+ const timers = {};
74
+ return {
75
+ log: (...a) => w(a.map(v => typeof v === 'object' ? JSON.stringify(v, null, 2) : String(v)).join(' ')),
76
+ error: (...a) => term.write('\x1b[31m' + a.map(String).join(' ') + '\x1b[0m\r\n'),
77
+ warn: (...a) => term.write('\x1b[33m' + a.map(String).join(' ') + '\x1b[0m\r\n'),
78
+ info: (...a) => w(a.map(String).join(' ')),
79
+ dir: (o, opts) => w(JSON.stringify(o, null, 2)),
80
+ table: data => {
81
+ if (!Array.isArray(data)) { w(JSON.stringify(data, null, 2)); return; }
82
+ if (!data.length) { w('(empty)'); return; }
83
+ const cols = Object.keys(data[0]);
84
+ w(cols.join('\t'));
85
+ for (const row of data) w(cols.map(c => String(row[c] ?? '')).join('\t'));
64
86
  },
65
- loadSql: async () => {
66
- if (window.__sqlJs) return window.__sqlJs;
67
- await new Promise((res, rej) => {
68
- const s = document.createElement('script');
69
- s.src = './vendor/sql-wasm.js';
70
- s.onload = res; s.onerror = rej;
71
- document.head.appendChild(s);
72
- });
73
- window.__sqlJs = await initSqlJs({ locateFile: f => './vendor/' + f });
74
- return window.__sqlJs;
75
- },
76
- setTimeout, setInterval, clearTimeout, clearInterval, fetch,
77
- Buffer: {
78
- from: (s, enc) => enc === 'base64'
79
- ? new Uint8Array(atob(s).split('').map(c => c.charCodeAt(0)))
80
- : new TextEncoder().encode(s),
81
- toString: (buf, enc) => enc === 'base64'
82
- ? btoa(String.fromCharCode(...buf))
83
- : new TextDecoder().decode(buf),
84
- },
85
- get __filename() { return ctx.cwd + '/repl'; },
86
- get __dirname() { return ctx.cwd; },
87
- http: {
88
- createServer: handler => ({
89
- listen: (port, cb) => {
90
- window.__debug.shell.httpHandlers[port] = handler;
91
- term.write('listening on :' + port + '\r\n');
92
- cb?.();
93
- },
94
- }),
87
+ time: label => { timers[label || 'default'] = performance.now(); },
88
+ timeEnd: label => {
89
+ const k = label || 'default';
90
+ const ms = timers[k] ? (performance.now() - timers[k]).toFixed(3) : 0;
91
+ delete timers[k];
92
+ w(k + ': ' + ms + 'ms');
95
93
  },
94
+ assert: (cond, ...a) => { if (!cond) term.write('\x1b[31mAssertion failed: ' + a.join(' ') + '\x1b[0m\r\n'); },
95
+ count: (() => { const c = {}; return label => { const k = label || 'default'; c[k] = (c[k] || 0) + 1; w(k + ': ' + c[k]); }; })(),
96
+ clear: () => term.clear(),
97
+ trace: (...a) => w('Trace: ' + a.map(String).join(' ')),
98
+ group: () => {},
99
+ groupEnd: () => {},
100
+ };
101
+ }
102
+
103
+ function createProcess(term, ctx) {
104
+ return {
105
+ argv: ['node'],
106
+ env: ctx.env,
107
+ cwd: () => ctx.cwd,
108
+ chdir: d => { ctx.cwd = d; },
109
+ exit: code => term.write('[exit ' + (code || 0) + ']\r\n'),
110
+ platform: 'browser',
111
+ version: 'v20.0.0',
112
+ versions: { node: '20.0.0' },
113
+ pid: 1,
114
+ nextTick: fn => Promise.resolve().then(fn),
115
+ stdout: { write: s => term.write(String(s)) },
116
+ stderr: { write: s => term.write('\x1b[31m' + String(s) + '\x1b[0m') },
117
+ stdin: { on: () => {} },
118
+ on: () => {},
119
+ off: () => {},
120
+ hrtime: { bigint: () => BigInt(Math.round(performance.now() * 1e6)) },
121
+ };
122
+ }
123
+
124
+ export function createNodeEnv({ ctx, term }) {
125
+ const pathmod = createPath();
126
+ const fsmod = createFs();
127
+ const Buf = createBuffer();
128
+ const MODULES = {
129
+ path: () => pathmod,
130
+ fs: () => fsmod,
131
+ events: () => createEvents(),
132
+ url: () => createUrl(),
133
+ querystring: () => createQuerystring(),
134
+ os: () => ({ platform: () => 'browser', homedir: () => '/', tmpdir: () => '/tmp', cpus: () => [{}], totalmem: () => 1073741824, freemem: () => 536870912, hostname: () => 'thebird', EOL: '\n' }),
135
+ util: () => ({ format: (...a) => a.join(' '), inspect: o => JSON.stringify(o, null, 2), promisify: fn => (...a) => new Promise((r, j) => fn(...a, (e, v) => e ? j(e) : r(v))), types: { isPromise: p => p instanceof Promise } }),
136
+ crypto: () => ({ randomBytes: n => Buf.from(Array.from({ length: n }, () => Math.random() * 256 | 0)), randomUUID: () => crypto.randomUUID(), createHash: () => ({ update: () => ({ digest: () => 'stub' }) }) }),
137
+ stream: () => ({ Readable: createEvents(), Writable: createEvents(), Transform: createEvents(), pipeline: (...a) => a.pop()(null) }),
138
+ express: createExpress(term, fsmod),
139
+ 'better-sqlite3': createSqlite,
96
140
  };
97
141
 
98
- return async function nodeEval(code, filename) {
142
+ const cons = createConsole(term);
143
+ const proc = createProcess(term, ctx);
144
+
145
+ function makeRequire(dir) {
146
+ return function require(id) {
147
+ if (MODULES[id]) return MODULES[id]();
148
+ const candidates = id.startsWith('.') ? [
149
+ pathmod.resolve(dir, id) + '.js',
150
+ pathmod.resolve(dir, id),
151
+ pathmod.resolve(dir, id) + '/index.js',
152
+ pathmod.resolve(dir, id, 'index.js'),
153
+ ] : ['node_modules/' + id + '/index.js'];
154
+ const s = snap();
155
+ for (const c of candidates) {
156
+ const key = c.replace(/^\//, '');
157
+ if (key in s) {
158
+ if (key.endsWith('.json')) return JSON.parse(s[key]);
159
+ const mod = { exports: {} };
160
+ const modDir = pathmod.dirname('/' + key);
161
+ new Function('module', 'exports', 'require', '__filename', '__dirname', 'process', 'console', 'Buffer', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval', 'fetch', s[key])(mod, mod.exports, makeRequire(modDir), '/' + key, modDir, proc, cons, Buf, setTimeout, setInterval, clearTimeout, clearInterval, fetch);
162
+ return mod.exports;
163
+ }
164
+ }
165
+ throw new Error('Cannot find module: ' + id);
166
+ };
167
+ }
168
+
169
+ const snap = () => window.__debug?.idbSnapshot || {};
170
+
171
+ async function loadSql() {
172
+ if (window.__sqlJs) return window.__sqlJs;
173
+ await new Promise((res, rej) => { const s = document.createElement('script'); s.src = './vendor/sql-wasm.js'; s.onload = res; s.onerror = rej; document.head.appendChild(s); });
174
+ window.__sqlJs = await initSqlJs({ locateFile: f => './vendor/' + f });
175
+ return window.__sqlJs;
176
+ }
177
+
178
+ return async function nodeEval(code, filename, argv) {
179
+ const dir = filename ? pathmod.dirname(filename) : ctx.cwd;
180
+ const fpath = filename || ctx.cwd + '/repl';
181
+ proc.argv = ['node', fpath, ...(argv || [])];
182
+ const scope = { process: proc, console: cons, require: makeRequire(dir), Buffer: Buf, __filename: fpath, __dirname: dir, setTimeout, setInterval, clearTimeout, clearInterval, fetch, loadSql, module: { exports: {} }, exports: {} };
99
183
  try {
100
184
  const keys = Object.keys(scope);
101
185
  const vals = Object.values(scope);
102
186
  const fn = new Function(...keys, 'return (async () => {\n' + code + '\n})()');
103
- await fn(...vals);
187
+ const result = await fn(...vals);
188
+ if (result !== undefined && !filename) cons.log(result);
104
189
  } catch (e) {
105
190
  term.write('\x1b[31m' + (filename ? filename + ': ' : '') + e.message + '\x1b[0m\r\n');
106
191
  }
package/docs/shell.js CHANGED
@@ -21,9 +21,17 @@ function makeBuiltins(ctx) {
21
21
  const wl = s => w(s + '\r\n');
22
22
  return {
23
23
  ls: ([p]) => {
24
- const prefix = toKey(resolvePath(ctx.cwd, p || '')) + '/';
25
- const keys = prefix === '/' ? Object.keys(snap()) : Object.keys(snap()).filter(k => k.startsWith(prefix));
26
- wl(keys.join('\r\n') || '(empty)');
24
+ const prefix = toKey(resolvePath(ctx.cwd, p || ''));
25
+ const pLen = prefix ? prefix.length + 1 : 0;
26
+ const seen = new Set();
27
+ for (const k of Object.keys(snap())) {
28
+ if (prefix && !k.startsWith(prefix + '/') && k !== prefix) continue;
29
+ if (!prefix && !k.includes('/')) { seen.add(k); continue; }
30
+ const rest = k.slice(pLen);
31
+ const first = rest.split('/')[0];
32
+ if (first && first !== '.keep') seen.add(first);
33
+ }
34
+ wl([...seen].join('\r\n') || '(empty)');
27
35
  },
28
36
  cat: ([f]) => {
29
37
  const c = snap()[toKey(resolvePath(ctx.cwd, f))];
@@ -33,50 +41,49 @@ function makeBuiltins(ctx) {
33
41
  echo: args => wl(args.join(' ')),
34
42
  pwd: () => wl(ctx.cwd),
35
43
  cd: ([p]) => { ctx.cwd = resolvePath(ctx.cwd, p || '~'); },
36
- mkdir: ([p]) => {
37
- window.__debug.idbSnapshot[toKey(resolvePath(ctx.cwd, p)) + '/.keep'] = '';
38
- window.__debug.idbPersist?.();
39
- },
40
- rm: ([f]) => {
41
- delete window.__debug.idbSnapshot[toKey(resolvePath(ctx.cwd, f))];
42
- window.__debug.idbPersist?.();
43
- },
44
- cp: ([s, d]) => {
45
- window.__debug.idbSnapshot[toKey(resolvePath(ctx.cwd, d))] = snap()[toKey(resolvePath(ctx.cwd, s))];
46
- window.__debug.idbPersist?.();
47
- },
44
+ mkdir: ([p]) => { snap()[toKey(resolvePath(ctx.cwd, p)) + '/.keep'] = ''; window.__debug.idbPersist?.(); },
45
+ rm: ([f]) => { delete snap()[toKey(resolvePath(ctx.cwd, f))]; window.__debug.idbPersist?.(); },
46
+ cp: ([s, d]) => { snap()[toKey(resolvePath(ctx.cwd, d))] = snap()[toKey(resolvePath(ctx.cwd, s))]; window.__debug.idbPersist?.(); },
48
47
  mv: ([s, d]) => {
49
48
  const src = toKey(resolvePath(ctx.cwd, s)), dst = toKey(resolvePath(ctx.cwd, d));
50
- window.__debug.idbSnapshot[dst] = snap()[src];
51
- delete window.__debug.idbSnapshot[src];
52
- window.__debug.idbPersist?.();
49
+ snap()[dst] = snap()[src]; delete snap()[src]; window.__debug.idbPersist?.();
50
+ },
51
+ touch: ([f]) => { const k = toKey(resolvePath(ctx.cwd, f)); if (!(k in snap())) { snap()[k] = ''; window.__debug.idbPersist?.(); } },
52
+ head: ([f]) => { const c = snap()[toKey(resolvePath(ctx.cwd, f))]; if (!c) throw new Error('no such file: ' + f); wl(c.split('\n').slice(0, 10).join('\r\n')); },
53
+ tail: ([f]) => { const c = snap()[toKey(resolvePath(ctx.cwd, f))]; if (!c) throw new Error('no such file: ' + f); wl(c.split('\n').slice(-10).join('\r\n')); },
54
+ wc: ([f]) => { const c = snap()[toKey(resolvePath(ctx.cwd, f))]; if (!c) throw new Error('no such file: ' + f); const lines = c.split('\n').length; wl(lines + ' ' + c.length + ' ' + f); },
55
+ grep: ([pat, f]) => {
56
+ const c = snap()[toKey(resolvePath(ctx.cwd, f))];
57
+ if (!c) throw new Error('no such file: ' + f);
58
+ const re = new RegExp(pat, 'g');
59
+ wl(c.split('\n').filter(l => re.test(l)).join('\r\n') || '(no matches)');
53
60
  },
54
61
  env: () => wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n')),
55
62
  export: ([kv]) => { const [k, ...v] = (kv || '').split('='); ctx.env[k] = v.join('='); },
56
63
  clear: () => ctx.term.clear(),
57
64
  help: () => wl(Object.keys(makeBuiltins(ctx)).join(' ')),
58
- exit: (actor) => {
59
- const st = actor.getSnapshot().value;
60
- if (st === 'node-repl') { actor.send({ type: 'EXIT_REPL' }); wl('[shell]'); }
65
+ which: ([cmd]) => wl(makeBuiltins(ctx)[cmd] ? '(builtin) ' + cmd : 'not found: ' + cmd),
66
+ exit: (_, actor) => {
67
+ if (actor.getSnapshot().value === 'node-repl') { actor.send({ type: 'EXIT_REPL' }); wl('[shell]'); }
61
68
  },
62
- node: async ([file], actor) => {
63
- if (!file) { actor.send({ type: 'ENTER_REPL' }); wl('[node repl — type exit to return]'); return; }
64
- const path = resolvePath(ctx.cwd, file);
69
+ node: async (args, actor) => {
70
+ if (!args.length) { actor.send({ type: 'ENTER_REPL' }); wl('[node repl — type exit to return]'); return; }
71
+ if (args[0] === '-v' || args[0] === '--version') { wl('v20.0.0'); return; }
72
+ if (args[0] === '-e' || args[0] === '--eval') { await ctx.nodeEval(args.slice(1).join(' ')); return; }
73
+ const path = resolvePath(ctx.cwd, args[0]);
65
74
  const code = snap()[toKey(path)];
66
75
  if (code == null) throw new Error('no such file: ' + path);
67
76
  actor.send({ type: 'NODE_START' });
68
- await ctx.nodeEval(code, path);
77
+ await ctx.nodeEval(code, path, args.slice(1));
69
78
  },
70
- npm: async (args, actor) => {
71
- if (args[0] !== 'install') throw new Error('only npm install supported');
79
+ npm: async (args) => {
80
+ if (args[0] !== 'install' && args[0] !== 'i') throw new Error('only npm install supported');
72
81
  const pkg = args[1];
73
82
  if (!pkg) throw new Error('npm install <pkg>');
74
- actor.send({ type: 'NPM_START' });
75
83
  w('fetching ' + pkg + '...\r\n');
76
84
  const r = await fetch('https://esm.sh/' + pkg);
77
85
  if (!r.ok) throw new Error('fetch failed: ' + r.status);
78
- const key = 'node_modules/' + pkg + '/index.js';
79
- window.__debug.idbSnapshot[key] = await r.text();
86
+ snap()['node_modules/' + pkg + '/index.js'] = await r.text();
80
87
  window.__debug.idbPersist?.();
81
88
  wl('installed ' + pkg);
82
89
  },
@@ -84,15 +91,14 @@ function makeBuiltins(ctx) {
84
91
  }
85
92
 
86
93
  const machine = createMachine({ id: 'shell', initial: 'idle', states: {
87
- idle: { on: { RUN: 'executing', ENTER_REPL: 'node-repl', NPM_START: 'npm-installing', NODE_START: 'node-running' } },
94
+ idle: { on: { RUN: 'executing', ENTER_REPL: 'node-repl', NODE_START: 'node-running' } },
88
95
  executing: { on: { DONE: 'idle', ERROR: 'idle' } },
89
- 'npm-installing': { on: { DONE: 'idle', ERROR: 'idle' } },
90
96
  'node-running': { on: { DONE: 'idle', ERROR: 'idle' } },
91
97
  'node-repl': { on: { EXIT_REPL: 'idle', RUN: 'node-repl' } },
92
98
  }});
93
99
 
94
100
  export function createShell({ term, onPreviewWrite }) {
95
- const ctx = { term, cwd: '/', env: {}, history: [], httpHandlers: {} };
101
+ const ctx = { term, cwd: '/', env: {}, history: [] };
96
102
  const BUILTINS = makeBuiltins(ctx);
97
103
  ctx.nodeEval = createNodeEnv({ ctx, term });
98
104
 
@@ -100,12 +106,7 @@ export function createShell({ term, onPreviewWrite }) {
100
106
  actor.start();
101
107
 
102
108
  let inputQueue = [];
103
-
104
- function drainQueue(onData) {
105
- const items = inputQueue.slice();
106
- inputQueue = [];
107
- for (const d of items) onData(d);
108
- }
109
+ function drainQueue(onData) { const items = inputQueue.slice(); inputQueue = []; for (const d of items) onData(d); }
109
110
 
110
111
  window.__debug = window.__debug || {};
111
112
  window.__debug.shell = {
@@ -113,11 +114,11 @@ export function createShell({ term, onPreviewWrite }) {
113
114
  get cwd() { return ctx.cwd; },
114
115
  get env() { return ctx.env; },
115
116
  get history() { return ctx.history; },
116
- httpHandlers: ctx.httpHandlers,
117
+ httpHandlers: {},
117
118
  get inputQueue() { return inputQueue.slice(); },
118
119
  };
119
120
 
120
- async function runCmd(line, capture, actor) {
121
+ async function runCmd(line, capture) {
121
122
  if (!line.trim()) return '';
122
123
  const [cmd, ...args] = line.trim().split(/\s+/);
123
124
  const fn = BUILTINS[cmd];
@@ -128,7 +129,7 @@ export function createShell({ term, onPreviewWrite }) {
128
129
  let out = '';
129
130
  const orig = term.write.bind(term);
130
131
  term.write = s => { out += s; };
131
- try { if (fn) await fn(args, actor); else out += 'command not found: ' + cmd + '\r\n'; }
132
+ try { if (fn) await fn(args, actor); else out += 'command not found: ' + cmd; }
132
133
  finally { term.write = orig; }
133
134
  return out;
134
135
  }
@@ -137,13 +138,13 @@ export function createShell({ term, onPreviewWrite }) {
137
138
  if (!line.trim()) return;
138
139
  const st = actor.getSnapshot().value;
139
140
  if (st === 'node-repl' && line.trim() !== 'exit') { await ctx.nodeEval(line); return; }
140
- if (line.trim() === 'exit') { BUILTINS.exit(actor); return; }
141
+ if (line.trim() === 'exit') { BUILTINS.exit([], actor); return; }
141
142
  const [cmd] = line.trim().split(/\s+/);
142
143
  if (cmd !== 'npm' && cmd !== 'node') actor.send({ type: 'RUN' });
143
144
  try {
144
145
  const parts = line.split(' | ');
145
146
  if (parts.length > 1) {
146
- let buf = await runCmd(parts[0], true, actor);
147
+ let buf = await runCmd(parts[0], true);
147
148
  for (const p of parts.slice(1)) {
148
149
  const [c, ...a] = p.trim().split(/\s+/);
149
150
  const fn = BUILTINS[c];
@@ -151,7 +152,7 @@ export function createShell({ term, onPreviewWrite }) {
151
152
  buf = '';
152
153
  }
153
154
  } else {
154
- await runCmd(line, false, actor);
155
+ await runCmd(line, false);
155
156
  }
156
157
  actor.send({ type: 'DONE' });
157
158
  drainQueue(onData);
@@ -163,36 +164,18 @@ export function createShell({ term, onPreviewWrite }) {
163
164
  }
164
165
 
165
166
  function getCompletions(line, word) {
166
- const snap = window.__debug.idbSnapshot || {};
167
- const files = Object.keys(snap);
167
+ const files = Object.keys(window.__debug.idbSnapshot || {});
168
168
  const tokens = line.trim().split(/\s+/);
169
- if (tokens.length <= 1 && !line.includes(' ')) {
170
- const cmds = Object.keys(BUILTINS);
171
- return cmds.filter(c => c.startsWith(word));
172
- }
169
+ if (tokens.length <= 1 && !line.includes(' ')) return Object.keys(BUILTINS).filter(c => c.startsWith(word));
173
170
  return files.filter(f => f.startsWith(word));
174
171
  }
175
172
 
176
- const rl = createReadline({
177
- term,
178
- getCompletions,
179
- getPrompt: () => ctx.cwd,
180
- onLine: line => run(line, onData).then(() => rl.showPrompt()),
181
- });
173
+ const rl = createReadline({ term, getCompletions, getPrompt: () => ctx.cwd, onLine: line => run(line, onData).then(() => rl.showPrompt()) });
182
174
 
183
175
  function onData(data) {
184
- if (data === '\x03') {
185
- actor.send({ type: 'ERROR' });
186
- inputQueue = [];
187
- term.write('^C');
188
- rl.showPrompt();
189
- return;
190
- }
176
+ if (data === '\x03') { actor.send({ type: 'ERROR' }); inputQueue = []; term.write('^C'); rl.showPrompt(); return; }
191
177
  const st = actor.getSnapshot().value;
192
- if (st !== 'idle' && st !== 'node-repl') {
193
- inputQueue.push(data);
194
- return;
195
- }
178
+ if (st !== 'idle' && st !== 'node-repl') { inputQueue.push(data); return; }
196
179
  rl.onData(data);
197
180
  }
198
181
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.77",
3
+ "version": "1.2.79",
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"