thebird 1.2.78 → 1.2.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.github/workflows/publish.yml +9 -1
  2. package/CHANGELOG.md +226 -0
  3. package/CLAUDE.md +16 -0
  4. package/docs/agent-chat.js +7 -4
  5. package/docs/app.js +14 -11
  6. package/docs/defaults.json +1 -1
  7. package/docs/index.html +23 -6
  8. package/docs/kilo-http-stream.js +24 -0
  9. package/docs/node-builtins.js +194 -0
  10. package/docs/preview/index.html +32 -0
  11. package/docs/preview-sw-client.js +37 -6
  12. package/docs/preview-sw.js +55 -51
  13. package/docs/shell-awk.js +113 -0
  14. package/docs/shell-builtins-extra.js +121 -0
  15. package/docs/shell-builtins-text.js +109 -0
  16. package/docs/shell-builtins-util.js +112 -0
  17. package/docs/shell-builtins.js +183 -0
  18. package/docs/shell-bun.js +45 -0
  19. package/docs/shell-control.js +132 -0
  20. package/docs/shell-deno.js +54 -0
  21. package/docs/shell-exec.js +85 -0
  22. package/docs/shell-expand.js +164 -0
  23. package/docs/shell-fd.js +86 -0
  24. package/docs/shell-jobs.js +86 -0
  25. package/docs/shell-node-advanced.js +86 -0
  26. package/docs/shell-node-brotli.js +22 -0
  27. package/docs/shell-node-busnet.js +90 -0
  28. package/docs/shell-node-cipher.js +61 -0
  29. package/docs/shell-node-cluster.js +33 -0
  30. package/docs/shell-node-coreutils.js +36 -0
  31. package/docs/shell-node-crypto.js +137 -0
  32. package/docs/shell-node-dns.js +41 -0
  33. package/docs/shell-node-extras.js +148 -0
  34. package/docs/shell-node-firefox.js +95 -0
  35. package/docs/shell-node-git.js +60 -0
  36. package/docs/shell-node-inspector.js +39 -0
  37. package/docs/shell-node-io.js +131 -0
  38. package/docs/shell-node-ipc.js +15 -0
  39. package/docs/shell-node-keyobject.js +60 -0
  40. package/docs/shell-node-modules.js +157 -0
  41. package/docs/shell-node-native.js +31 -0
  42. package/docs/shell-node-net.js +71 -0
  43. package/docs/shell-node-observe.js +80 -0
  44. package/docs/shell-node-opfs.js +54 -0
  45. package/docs/shell-node-procfs.js +42 -0
  46. package/docs/shell-node-profiler.js +50 -0
  47. package/docs/shell-node-registry.js +24 -0
  48. package/docs/shell-node-resolve.js +147 -0
  49. package/docs/shell-node-runtime.js +83 -0
  50. package/docs/shell-node-srcmap.js +52 -0
  51. package/docs/shell-node-stdlib.js +103 -0
  52. package/docs/shell-node-streams.js +66 -0
  53. package/docs/shell-node-tar.js +47 -0
  54. package/docs/shell-node-testrunner.js +35 -0
  55. package/docs/shell-node-util-extras.js +66 -0
  56. package/docs/shell-node.js +188 -97
  57. package/docs/shell-npm.js +173 -0
  58. package/docs/shell-parser.js +122 -0
  59. package/docs/shell-pm-layout.js +62 -0
  60. package/docs/shell-pm.js +39 -0
  61. package/docs/shell-posix.js +70 -0
  62. package/docs/shell-procsub.js +65 -0
  63. package/docs/shell-readline.js +59 -4
  64. package/docs/shell-runtime.js +37 -0
  65. package/docs/shell-sed.js +83 -0
  66. package/docs/shell-signals.js +54 -0
  67. package/docs/shell-sw-jobs.js +76 -0
  68. package/docs/shell-ts.js +30 -0
  69. package/docs/shell.js +159 -167
  70. package/docs/terminal.js +9 -11
  71. package/docs/todo.html +211 -0
  72. package/package.json +1 -1
  73. package/server.js +43 -4
  74. package/start-kilo.js +17 -0
  75. package/test.js +199 -0
  76. package/.codeinsight +0 -73
  77. package/docs/acp-stream.js +0 -102
  78. package/docs/coi-serviceworker.js +0 -2
package/docs/shell.js CHANGED
@@ -1,205 +1,197 @@
1
1
  import { createMachine, createActor } from './vendor/xstate.js';
2
2
  import { createNodeEnv } from './shell-node.js';
3
3
  import { createReadline } from './shell-readline.js';
4
-
5
- function resolvePath(cwd, p) {
6
- if (!p || p === '~') return '/';
7
- if (p.startsWith('~/')) p = '/' + p.slice(2);
8
- if (!p.startsWith('/')) p = cwd.replace(/\/$/, '') + '/' + 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 '/' + parts.join('/');
15
- }
16
-
17
- function makeBuiltins(ctx) {
18
- const snap = () => window.__debug.idbSnapshot || {};
19
- const toKey = p => p.replace(/^\//, '');
20
- const w = s => ctx.term.write(s);
21
- const wl = s => w(s + '\r\n');
22
- return {
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)');
27
- },
28
- cat: ([f]) => {
29
- const c = snap()[toKey(resolvePath(ctx.cwd, f))];
30
- if (c == null) throw new Error('no such file: ' + f);
31
- wl(c);
32
- },
33
- echo: args => wl(args.join(' ')),
34
- pwd: () => wl(ctx.cwd),
35
- 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
- },
48
- mv: ([s, d]) => {
49
- 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?.();
53
- },
54
- env: () => wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n')),
55
- export: ([kv]) => { const [k, ...v] = (kv || '').split('='); ctx.env[k] = v.join('='); },
56
- clear: () => ctx.term.clear(),
57
- 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]'); }
61
- },
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);
65
- const code = snap()[toKey(path)];
66
- if (code == null) throw new Error('no such file: ' + path);
67
- actor.send({ type: 'NODE_START' });
68
- await ctx.nodeEval(code, path);
69
- },
70
- npm: async (args, actor) => {
71
- if (args[0] !== 'install') throw new Error('only npm install supported');
72
- const pkg = args[1];
73
- if (!pkg) throw new Error('npm install <pkg>');
74
- actor.send({ type: 'NPM_START' });
75
- w('fetching ' + pkg + '...\r\n');
76
- const r = await fetch('https://esm.sh/' + pkg);
77
- 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();
80
- window.__debug.idbPersist?.();
81
- wl('installed ' + pkg);
82
- },
83
- };
84
- }
4
+ import { makeBuiltins, resolvePath } from './shell-builtins.js';
5
+ import { makeNpm, makeNpx } from './shell-npm.js';
6
+ import { makePmDispatcher, makeCorepackStub, detectPm } from './shell-pm.js';
7
+ import { makeDlx } from './shell-pm-layout.js';
8
+ import { tokenize, splitTopLevel, parsePipes } from './shell-parser.js';
9
+ import { fullExpand } from './shell-expand.js';
10
+ import { isControlStart, isBlockOpen, runControl, runScript } from './shell-control.js';
11
+ import { createSignals, makeKillBuiltin, makeTrapBuiltin } from './shell-signals.js';
12
+ import { createJobRegistry, makeJobsBuiltin, makeFgBuiltin, makeBgBuiltin, makeDisownBuiltin } from './shell-jobs.js';
13
+ import { createFdTable, makeExecBuiltin } from './shell-fd.js';
14
+ import { readStream } from './shell-procsub.js';
15
+ import { makeExpander, makeCaptureRun, makeNodeRunner, makeNpmResultRunner } from './shell-exec.js';
16
+ import { createSwJobs, makeNohupBuiltin, makeNetcatStub, makeCurlBuiltin } from './shell-sw-jobs.js';
85
17
 
86
18
  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' } },
19
+ idle: { on: { RUN: 'executing', ENTER_REPL: 'node-repl', NODE_START: 'node-running' } },
88
20
  executing: { on: { DONE: 'idle', ERROR: 'idle' } },
89
- 'npm-installing': { on: { DONE: 'idle', ERROR: 'idle' } },
90
21
  'node-running': { on: { DONE: 'idle', ERROR: 'idle' } },
91
22
  'node-repl': { on: { EXIT_REPL: 'idle', RUN: 'node-repl' } },
92
23
  }});
93
24
 
94
25
  export function createShell({ term, onPreviewWrite }) {
95
- const ctx = { term, cwd: '/', env: {}, history: [], httpHandlers: {} };
96
- const BUILTINS = makeBuiltins(ctx);
97
- ctx.nodeEval = createNodeEnv({ ctx, term });
98
-
26
+ const ctx = { term, cwd: '/', prevCwd: '/', env: {}, history: [], lastExitCode: 0, argv: [], functions: {}, opts: {}, localStack: [], loopFlag: null, arrays: {}, bgJobs: {}, traps: {} };
99
27
  const actor = createActor(machine);
100
28
  actor.start();
29
+ const httpHandlers = {};
30
+ window.__debug = window.__debug || {};
101
31
 
102
32
  let inputQueue = [];
33
+ function drainQueue(onData) { const items = inputQueue.slice(); inputQueue = []; for (const d of items) onData(d); }
103
34
 
104
- function drainQueue(onData) {
105
- const items = inputQueue.slice();
106
- inputQueue = [];
107
- for (const d of items) onData(d);
35
+ const toKey = p => p.replace(/^\//, '');
36
+ const snap = () => window.__debug.idbSnapshot || {};
37
+
38
+ const BUILTINS = makeBuiltins(ctx, actor, invokeBuiltin);
39
+ ctx.builtinsRef = BUILTINS;
40
+ const _exp = makeExpander(ctx, l => captureRun(l), t => parseRedirect(t));
41
+ expandTokens = _exp.expandTokens;
42
+ captureRun = makeCaptureRun(ctx, BUILTINS, actor, t => parseRedirect(t), t => expandTokens(t));
43
+ ctx.signals = createSignals(ctx);
44
+ ctx.fdTable = createFdTable(ctx);
45
+ ctx.swJobs = createSwJobs();
46
+ const jobRegistry = createJobRegistry(ctx);
47
+ ctx.jobRegistry = jobRegistry;
48
+ ctx.runPipeline = line => runPipeline(line);
49
+ Object.assign(BUILTINS, { kill: makeKillBuiltin(ctx), trap: makeTrapBuiltin(ctx), jobs: makeJobsBuiltin(ctx, jobRegistry), fg: makeFgBuiltin(ctx, jobRegistry), bg: makeBgBuiltin(ctx, jobRegistry), disown: makeDisownBuiltin(ctx), exec: makeExecBuiltin(ctx, ctx.fdTable), nohup: makeNohupBuiltin(ctx), nc: makeNetcatStub(ctx), curl: makeCurlBuiltin(ctx) });
50
+ ctx.runScript = text => runScript(text, run, ctx);
51
+ ctx.expand = token => fullExpand(token, ctx.env, ctx.lastExitCode, ctx.argv, captureRun, ctx.arrays);
52
+ const npmCmd = makeNpm(ctx); const npxCmd = makeNpx(npmCmd); ctx.exec = line => run(line);
53
+ const pmDispatch = makePmDispatcher(term, null, () => window.__debug.idbPersist?.(), ctx); const corepackCmd = makeCorepackStub(term); const dlxCmd = makeDlx(term, null, ctx, run);
54
+ ctx.nodeEval = createNodeEnv({ ctx, term });
55
+ const runNode = makeNodeRunner(ctx, actor);
56
+ const runNpmResult = makeNpmResultRunner(ctx, line => run(line));
57
+
58
+ let expandTokens, captureRun;
59
+
60
+ async function captureFn(fn) {
61
+ let out = ''; const orig = term.write.bind(term); term.write = s => { out += s; };
62
+ try { await fn(); } finally { term.write = orig; }
63
+ return out;
108
64
  }
109
65
 
110
- window.__debug = window.__debug || {};
111
- window.__debug.shell = {
112
- get state() { return actor.getSnapshot().value; },
113
- get cwd() { return ctx.cwd; },
114
- get env() { return ctx.env; },
115
- get history() { return ctx.history; },
116
- httpHandlers: ctx.httpHandlers,
117
- get inputQueue() { return inputQueue.slice(); },
118
- };
66
+ async function runFunction(name, args) {
67
+ const savedArgv = ctx.argv; ctx.argv = [name, ...args]; ctx.localStack.push({});
68
+ try { await runScript(ctx.functions[name], run, ctx); }
69
+ finally {
70
+ const locals = ctx.localStack.pop();
71
+ for (const k of Object.keys(locals)) { if (locals[k] === undefined) delete ctx.env[k]; else ctx.env[k] = locals[k]; }
72
+ ctx.argv = savedArgv;
73
+ }
74
+ }
119
75
 
120
- async function runCmd(line, capture, actor) {
121
- if (!line.trim()) return '';
122
- const [cmd, ...args] = line.trim().split(/\s+/);
123
- const fn = BUILTINS[cmd];
124
- if (!capture) {
125
- if (fn) await fn(args, actor); else term.write('command not found: ' + cmd + '\r\n');
126
- return '';
76
+ async function invokeBuiltin(name, args, withCaptureInto, stdinBuf) {
77
+ if (ctx.functions[name]) {
78
+ if (!withCaptureInto) { await runFunction(name, args); return ''; }
79
+ return captureFn(() => runFunction(name, args));
127
80
  }
128
- let out = '';
129
- const orig = term.write.bind(term);
130
- term.write = s => { out += s; };
131
- try { if (fn) await fn(args, actor); else out += 'command not found: ' + cmd + '\r\n'; }
132
- finally { term.write = orig; }
133
- return out;
81
+ const fn = BUILTINS[name];
82
+ if (!fn) throw new Error('command not found: ' + name);
83
+ if (!withCaptureInto) { await fn(args, actor, stdinBuf, invokeBuiltin, run); return ''; }
84
+ return captureFn(() => fn(args, actor, stdinBuf, invokeBuiltin, run));
134
85
  }
135
86
 
136
- async function run(line, onData) {
137
- if (!line.trim()) return;
138
- const st = actor.getSnapshot().value;
139
- if (st === 'node-repl' && line.trim() !== 'exit') { await ctx.nodeEval(line); return; }
140
- if (line.trim() === 'exit') { BUILTINS.exit(actor); return; }
141
- const [cmd] = line.trim().split(/\s+/);
142
- if (cmd !== 'npm' && cmd !== 'node') actor.send({ type: 'RUN' });
87
+ function evalKV(kv) { const eq = kv.indexOf('='); return [kv.slice(0, eq), fullExpand(kv.slice(eq + 1), ctx.env, ctx.lastExitCode, ctx.argv, captureRun)]; }
88
+
89
+ async function runSingleCommand(line) {
90
+ const arrM = line.trim().match(/^([A-Za-z_][A-Za-z0-9_]*)=\((.*)\)\s*$/);
91
+ if (arrM) { (ctx.arrays = ctx.arrays || {})[arrM[1]] = tokenize(arrM[2]).map(t => fullExpand(t, ctx.env, ctx.lastExitCode, ctx.argv, captureRun, ctx.arrays)); return; }
92
+ const idxM = line.trim().match(/^([A-Za-z_][A-Za-z0-9_]*)\[([^\]]+)\]=(.*)$/);
93
+ if (idxM) { ctx.arrays = ctx.arrays || {}; if (!ctx.arrays[idxM[1]]) ctx.arrays[idxM[1]] = {}; const a = ctx.arrays[idxM[1]], ex = t => fullExpand(t, ctx.env, ctx.lastExitCode, ctx.argv, captureRun, ctx.arrays), k = ex(idxM[2]), v = ex(idxM[3]); if (Array.isArray(a)) a[parseInt(k, 10)] = v; else a[k] = v; return; }
94
+ const raw = tokenize(line); if (!raw.length) return;
95
+ let i = 0; const varAssigns = [];
96
+ while (i < raw.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(raw[i])) varAssigns.push(raw[i++]);
97
+ const rest = raw.slice(i);
98
+ if (!rest.length) { for (const kv of varAssigns) { const [k, v] = evalKV(kv); ctx.env[k] = v; } return; }
99
+ const { args: [cmd, ...args], stdout: rout, append } = parseRedirect(expandTokens(rest));
100
+ const writeOut = rout ? buf => { const k = toKey(resolvePath(ctx.cwd, rout)); snap()[k] = append ? (snap()[k] || '') + buf : buf; window.__debug.idbPersist?.(); } : null;
101
+ const prevEnv = {}; for (const kv of varAssigns) { const [k, v] = evalKV(kv); prevEnv[k] = ctx.env[k]; ctx.env[k] = v; }
143
102
  try {
144
- const parts = line.split(' | ');
145
- if (parts.length > 1) {
146
- let buf = await runCmd(parts[0], true, actor);
147
- for (const p of parts.slice(1)) {
148
- const [c, ...a] = p.trim().split(/\s+/);
149
- const fn = BUILTINS[c];
150
- if (fn) await fn([buf, ...a], actor); else term.write('command not found: ' + c + '\r\n');
151
- buf = '';
152
- }
153
- } else {
154
- await runCmd(line, false, actor);
103
+ if (cmd === 'npm') { if (writeOut) { writeOut(await captureFn(async () => { await runNpmResult(await npmCmd(args)); })); return; } await runNpmResult(await npmCmd(args)); return; }
104
+ if (cmd === 'npx') { await runNpmResult(await npxCmd(args)); return; }
105
+ if (cmd === 'pnpm' || cmd === 'yarn' || cmd === 'bun') { ctx.lastExitCode = args[0] === 'dlx' || args[0] === 'x' ? await dlxCmd(args.slice(1)) : await pmDispatch(cmd, args[0] || 'install', args.slice(1)); return; }
106
+ if (cmd === 'deno') { if (args[0] === 'run') { await runNode(args.slice(1)); return; } ctx.lastExitCode = await pmDispatch('deno', args[0] || 'task', args.slice(1)); return; }
107
+ if (cmd === 'corepack') { ctx.lastExitCode = await corepackCmd(args); return; }
108
+ if (cmd === 'node') { await runNode(args); return; }
109
+ if (cmd === 'exit') { BUILTINS.exit([], actor); return; }
110
+ if (writeOut) { writeOut(await invokeBuiltin(cmd, args, true)); return; }
111
+ await invokeBuiltin(cmd, args, false);
112
+ } finally { for (const k of Object.keys(prevEnv)) { if (prevEnv[k] === undefined) delete ctx.env[k]; else ctx.env[k] = prevEnv[k]; } }
113
+ }
114
+
115
+ function parseRedirect(tokens) {
116
+ const out = { args: [], stdout: null, append: false };
117
+ for (let i = 0; i < tokens.length; i++) {
118
+ const t = tokens[i];
119
+ if (t === '>' || t === '>>') { out.stdout = tokens[++i]; out.append = t === '>>'; } else out.args.push(t);
120
+ }
121
+ return out;
122
+ }
123
+
124
+ async function runPipeline(line) {
125
+ const pipes = parsePipes(line);
126
+ if (pipes.length === 1) { await runSingleCommand(pipes[0]); return; }
127
+ let buf = '';
128
+ for (let i = 0; i < pipes.length; i++) {
129
+ const isLast = i === pipes.length - 1;
130
+ const { args: [cmd, ...args], stdout: rout, append } = parseRedirect(expandTokens(tokenize(pipes[i])));
131
+ const sArgs = i === 0 ? args : (buf && cmd !== 'node' ? [buf, ...args] : args);
132
+ const stdinForStage = cmd === 'node' ? buf : buf;
133
+ if (isLast && !rout) {
134
+ if (cmd === 'node') { await runNode(args, stdinForStage); buf = ''; continue; }
135
+ await invokeBuiltin(cmd, sArgs, false, stdinForStage); buf = ''; continue;
155
136
  }
156
- actor.send({ type: 'DONE' });
157
- drainQueue(onData);
158
- } catch (e) {
159
- term.write('\x1b[31m' + e.message + '\x1b[0m\r\n');
160
- actor.send({ type: 'ERROR' });
161
- drainQueue(onData);
137
+ const out = cmd === 'node' ? await captureFn(() => runNode(args, stdinForStage)) : await invokeBuiltin(cmd, sArgs, true, stdinForStage);
138
+ if (rout) { const k = toKey(resolvePath(ctx.cwd, rout)); snap()[k] = append ? (snap()[k] || '') + out : out; window.__debug.idbPersist?.(); buf = ''; } else { buf = out; }
162
139
  }
163
140
  }
164
141
 
165
- function getCompletions(line, word) {
166
- const snap = window.__debug.idbSnapshot || {};
167
- const files = Object.keys(snap);
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));
142
+ let blockLines = [];
143
+
144
+ async function run(line, onData) {
145
+ if (!line.trim()) return;
146
+ ctx.history.push(line);
147
+ const st = actor.getSnapshot().value;
148
+ if (st === 'node-repl') {
149
+ const t = line.trim();
150
+ if (t === 'exit' || t === '.exit' || t === '.quit') { actor.send({ type: 'EXIT_REPL' }); return; }
151
+ if (t === '.help') { term.write('.exit Exit the REPL\r\n.help Show this list\r\n.clear Break out of current expression\r\n'); return; }
152
+ if (t === '.clear') return;
153
+ const exprCode = 'try { const __r = (' + line + '); if (__r !== undefined) console.log(require("util").inspect(__r)); } catch (__e1) { try {\n' + line + '\n} catch (__e2) { console.error(__e2.message); } }';
154
+ await ctx.nodeEval(exprCode); return;
155
+ }
156
+ if (ctx.opts.xtrace) term.write('\x1b[90m+ ' + line + '\x1b[0m\r\n');
157
+ const chain = splitTopLevel(line, ['&&', '||', ';', '&']);
158
+ let lastOk = true;
159
+ for (const { cmd, sep } of chain) {
160
+ if (ctx.loopFlag) break;
161
+ if (sep === '&&' && !lastOk) continue;
162
+ if (sep === '||' && lastOk) { lastOk = true; continue; }
163
+ if (sep === '&') { const id = jobRegistry.spawnJob(cmd, runPipeline); ctx.env['!'] = id; term.write('[' + id + '] spawned\r\n'); continue; }
164
+ actor.send({ type: 'RUN' });
165
+ try { ctx.lastExitCode = 0; await runPipeline(cmd); lastOk = ctx.lastExitCode === 0; actor.send({ type: 'DONE' }); }
166
+ catch (e) { term.write('\x1b[31m' + e.message + '\x1b[0m\r\n'); ctx.lastExitCode = 1; lastOk = false; actor.send({ type: 'ERROR' }); }
167
+ if (ctx.opts.errexit && !lastOk) break;
168
+ if (ctx.signals) await ctx.signals.check(l => run(l));
172
169
  }
173
- return files.filter(f => f.startsWith(word));
170
+ if (onData) drainQueue(onData);
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 getCompletions = (line, word) => (line.trim().split(/\s+/).length <= 1 && !line.includes(' ')) ? Object.keys(BUILTINS).concat(['npm', 'node', 'pnpm', 'yarn', 'bun', 'deno', 'npx', 'corepack']).filter(c => c.startsWith(word)) : Object.keys(snap()).filter(f => f.startsWith(word));
182
174
 
183
- function onData(data) {
184
- if (data === '\x03') {
185
- actor.send({ type: 'ERROR' });
186
- inputQueue = [];
187
- term.write('^C');
188
- rl.showPrompt();
175
+ const handleLine = line => {
176
+ if (blockLines.length > 0 || isControlStart(line)) {
177
+ blockLines.push(line); if (isBlockOpen(blockLines)) { rl.showContinuation(); return; }
178
+ const block = blockLines.slice(); blockLines = [];
179
+ runControl(block, run, ctx).then(() => rl.showPrompt()).catch(e => { term.write('\x1b[31m' + e.message + '\x1b[0m\r\n'); rl.showPrompt(); });
189
180
  return;
190
181
  }
182
+ run(line, onData).then(() => rl.showPrompt());
183
+ };
184
+ const rl = createReadline({ term, getCompletions, getPrompt: () => actor.getSnapshot().value === 'node-repl' ? '> ' : ctx.cwd, isBlockOpen: () => blockLines.length > 0, onLine: handleLine });
185
+ function onData(data) {
186
+ if (data === '\x03') { actor.send({ type: 'ERROR' }); inputQueue = []; blockLines = []; term.write('^C'); rl.showPrompt(); return; }
191
187
  const st = actor.getSnapshot().value;
192
- if (st !== 'idle' && st !== 'node-repl') {
193
- inputQueue.push(data);
194
- return;
195
- }
196
- rl.onData(data);
188
+ if (st !== 'idle' && st !== 'node-repl') inputQueue.push(data); else rl.onData(data);
197
189
  }
198
-
199
190
  term.onData(onData);
200
- onPreviewWrite && (window.__debug.shell.onPreviewWrite = onPreviewWrite);
201
- const runPublic = line => run(line, onData);
202
- window.__debug.shell.run = runPublic;
203
191
  rl.showPrompt();
204
- return { run: runPublic };
192
+ return {
193
+ run: line => run(line, onData), onPreviewWrite, httpHandlers, procsubRead: id => readStream(id), fdRead: fd => ctx.fdTable.readFd(fd),
194
+ get state() { return actor.getSnapshot().value; }, get cwd() { return ctx.cwd; }, get env() { return ctx.env; }, get history() { return ctx.history; },
195
+ get lastExitCode() { return ctx.lastExitCode; }, get inputQueue() { return inputQueue.slice(); },
196
+ };
205
197
  }
package/docs/terminal.js CHANGED
@@ -3,7 +3,7 @@ import { createMachine, createActor } from './vendor/xstate.js';
3
3
  import { createShell } from './shell.js';
4
4
  import { registerPreviewSW } from './preview-sw-client.js';
5
5
 
6
- const IDB_KEY = 'thebird_fs_v2';
6
+ const IDB_KEY = 'thebird_fs_v4';
7
7
 
8
8
  async function idbLoad() {
9
9
  return new Promise((res, rej) => {
@@ -44,7 +44,7 @@ function scheduleReload() {
44
44
  clearTimeout(reloadTimer);
45
45
  reloadTimer = setTimeout(() => {
46
46
  if (typeof window.refreshPreview === 'function') window.refreshPreview();
47
- }, 5000);
47
+ }, 1000);
48
48
  }
49
49
 
50
50
  async function boot() {
@@ -57,7 +57,7 @@ async function boot() {
57
57
  window.__debug = window.__debug || {};
58
58
  window.__debug.terminal = { get state() { return bootActor.getSnapshot().value; } };
59
59
 
60
- const term = new Terminal({ theme: { background: '#000000' }, convertEol: true });
60
+ const term = new Terminal({ theme: { background: '#000000', foreground: '#33ff33' }, convertEol: true });
61
61
  const fit = new FitAddon();
62
62
  term.loadAddon(fit);
63
63
  term.open(el);
@@ -78,19 +78,17 @@ async function boot() {
78
78
  window.__debug.term = term;
79
79
  bootActor.send({ type: 'IDB_READY' });
80
80
 
81
- try {
82
- const swPromise = registerPreviewSW();
83
- const swTimeout = new Promise((_, rej) => setTimeout(() => rej(new Error('SW registration timeout')), 3000));
84
- await Promise.race([swPromise, swTimeout]);
81
+ const shell = createShell({ term, onPreviewWrite: scheduleReload });
82
+ window.__debug.shell = shell;
83
+
84
+ registerPreviewSW().then(() => {
85
85
  bootActor.send({ type: 'SW_READY' });
86
- } catch (e) {
86
+ }).catch(e => {
87
87
  console.log('[terminal] SW error:', e.message);
88
88
  window.__debug.sw = window.__debug.sw || {};
89
89
  window.__debug.sw.bootError = e.message;
90
90
  bootActor.send({ type: 'SW_ERROR' });
91
- }
92
-
93
- const shell = createShell({ term, onPreviewWrite: scheduleReload });
91
+ });
94
92
  window.__debug.shellWriter = { write: line => shell.run(line.replace(/\n$/, '')) };
95
93
  }
96
94
 
package/docs/todo.html ADDED
@@ -0,0 +1,211 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Todo App</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ display: flex;
19
+ justify-content: center;
20
+ align-items: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .container {
25
+ background: white;
26
+ border-radius: 10px;
27
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
28
+ width: 100%;
29
+ max-width: 500px;
30
+ padding: 30px;
31
+ }
32
+
33
+ h1 {
34
+ color: #333;
35
+ margin-bottom: 20px;
36
+ text-align: center;
37
+ }
38
+
39
+ .input-group {
40
+ display: flex;
41
+ gap: 10px;
42
+ margin-bottom: 20px;
43
+ }
44
+
45
+ input[type="text"] {
46
+ flex: 1;
47
+ padding: 12px;
48
+ border: 2px solid #e0e0e0;
49
+ border-radius: 5px;
50
+ font-size: 16px;
51
+ transition: border-color 0.3s;
52
+ }
53
+
54
+ input[type="text"]:focus {
55
+ outline: none;
56
+ border-color: #667eea;
57
+ }
58
+
59
+ button {
60
+ background: #667eea;
61
+ color: white;
62
+ border: none;
63
+ padding: 12px 25px;
64
+ border-radius: 5px;
65
+ font-size: 16px;
66
+ cursor: pointer;
67
+ transition: background 0.3s;
68
+ }
69
+
70
+ button:hover {
71
+ background: #764ba2;
72
+ }
73
+
74
+ .todo-list {
75
+ list-style: none;
76
+ }
77
+
78
+ .todo-item {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 12px;
82
+ padding: 15px;
83
+ background: #f5f5f5;
84
+ border-radius: 5px;
85
+ margin-bottom: 10px;
86
+ transition: all 0.3s;
87
+ }
88
+
89
+ .todo-item:hover {
90
+ background: #efefef;
91
+ }
92
+
93
+ .todo-item.completed {
94
+ opacity: 0.6;
95
+ }
96
+
97
+ .todo-item.completed .todo-text {
98
+ text-decoration: line-through;
99
+ }
100
+
101
+ .todo-checkbox {
102
+ width: 20px;
103
+ height: 20px;
104
+ cursor: pointer;
105
+ }
106
+
107
+ .todo-text {
108
+ flex: 1;
109
+ color: #333;
110
+ word-break: break-word;
111
+ }
112
+
113
+ .delete-btn {
114
+ background: #ff6b6b;
115
+ padding: 8px 12px;
116
+ font-size: 14px;
117
+ }
118
+
119
+ .delete-btn:hover {
120
+ background: #ff5252;
121
+ }
122
+
123
+ .empty-state {
124
+ text-align: center;
125
+ color: #999;
126
+ padding: 40px 20px;
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div class="container">
132
+ <h1>📝 Todo App</h1>
133
+
134
+ <div class="input-group">
135
+ <input type="text" id="todoInput" placeholder="Add a new todo...">
136
+ <button onclick="addTodo()">Add</button>
137
+ </div>
138
+
139
+ <ul class="todo-list" id="todoList">
140
+ <li class="empty-state">No todos yet. Add one to get started!</li>
141
+ </ul>
142
+ </div>
143
+
144
+ <script>
145
+ let todos = JSON.parse(localStorage.getItem('todos')) || [];
146
+
147
+ function renderTodos() {
148
+ const todoList = document.getElementById('todoList');
149
+
150
+ if (todos.length === 0) {
151
+ todoList.innerHTML = '<li class="empty-state">No todos yet. Add one to get started!</li>';
152
+ return;
153
+ }
154
+
155
+ todoList.innerHTML = todos.map((todo, index) => `
156
+ <li class="todo-item ${todo.completed ? 'completed' : ''}">
157
+ <input type="checkbox" class="todo-checkbox"
158
+ ${todo.completed ? 'checked' : ''}
159
+ onchange="toggleTodo(${index})">
160
+ <span class="todo-text">${escapeHtml(todo.text)}</span>
161
+ <button class="delete-btn" onclick="deleteTodo(${index})">Delete</button>
162
+ </li>
163
+ `).join('');
164
+ }
165
+
166
+ function addTodo() {
167
+ const input = document.getElementById('todoInput');
168
+ const text = input.value.trim();
169
+
170
+ if (text === '') {
171
+ alert('Please enter a todo!');
172
+ return;
173
+ }
174
+
175
+ todos.push({ text, completed: false });
176
+ localStorage.setItem('todos', JSON.stringify(todos));
177
+ input.value = '';
178
+ renderTodos();
179
+ }
180
+
181
+ function toggleTodo(index) {
182
+ todos[index].completed = !todos[index].completed;
183
+ localStorage.setItem('todos', JSON.stringify(todos));
184
+ renderTodos();
185
+ }
186
+
187
+ function deleteTodo(index) {
188
+ todos.splice(index, 1);
189
+ localStorage.setItem('todos', JSON.stringify(todos));
190
+ renderTodos();
191
+ }
192
+
193
+ function escapeHtml(text) {
194
+ const map = {
195
+ '&': '&amp;',
196
+ '<': '&lt;',
197
+ '>': '&gt;',
198
+ '"': '&quot;',
199
+ "'": '&#039;'
200
+ };
201
+ return text.replace(/[&<>"']/g, m => map[m]);
202
+ }
203
+
204
+ document.getElementById('todoInput').addEventListener('keypress', (e) => {
205
+ if (e.key === 'Enter') addTodo();
206
+ });
207
+
208
+ renderTodos();
209
+ </script>
210
+ </body>
211
+ </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.78",
3
+ "version": "1.2.80",
4
4
  "description": "Anthropic SDK to Gemini streaming bridge — drop-in proxy that translates Anthropic message format and tool calls to Google Gemini",
5
5
  "scripts": {
6
6
  "start": "node serve.js"