thebird 1.2.49 → 1.2.51

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.
@@ -13,6 +13,7 @@ function idbWrite(path, content) {
13
13
  if (!snap) throw new Error('idb snapshot not ready');
14
14
  snap[path] = content;
15
15
  window.__debug.idbPersist?.();
16
+ window.__debug.shell?.onPreviewWrite?.();
16
17
  }
17
18
 
18
19
  const TOOLS = {
package/docs/index.html CHANGED
@@ -45,5 +45,6 @@ function switchTab(t) {
45
45
  </script>
46
46
  <script type="module" src="app.js"></script>
47
47
  <script type="module" src="terminal.js"></script>
48
+ <script type="module" src="preview-sw-client.js"></script>
48
49
  </body>
49
50
  </html>
@@ -20,3 +20,5 @@ export async function registerPreviewSW() {
20
20
  throw err;
21
21
  }
22
22
  }
23
+
24
+ registerPreviewSW();
package/docs/shell.js CHANGED
@@ -53,18 +53,23 @@ function makeBuiltins(ctx) {
53
53
  export: ([kv]) => { const [k, ...v] = (kv || '').split('='); ctx.env[k] = v.join('='); },
54
54
  clear: () => ctx.term.clear(),
55
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; }
56
+ exit: (actor) => {
57
+ const st = actor.getSnapshot().value;
58
+ if (st === 'node-repl') { actor.send({ type: 'EXIT_REPL' }); wl('[shell]'); }
59
+ },
60
+ node: async ([file], actor) => {
61
+ if (!file) { actor.send({ type: 'ENTER_REPL' }); wl('[node repl — type exit to return]'); return; }
59
62
  const path = resolvePath(ctx.cwd, file);
60
63
  const code = snap()[path];
61
64
  if (code == null) throw new Error('no such file: ' + path);
65
+ actor.send({ type: 'NODE_START' });
62
66
  await ctx.nodeEval(code, path);
63
67
  },
64
- npm: async args => {
68
+ npm: async (args, actor) => {
65
69
  if (args[0] !== 'install') throw new Error('only npm install supported');
66
70
  const pkg = args[1];
67
71
  if (!pkg) throw new Error('npm install <pkg>');
72
+ actor.send({ type: 'NPM_START' });
68
73
  w('fetching ' + pkg + '...\r\n');
69
74
  const r = await fetch('https://esm.sh/' + pkg);
70
75
  if (!r.ok) throw new Error('fetch failed: ' + r.status);
@@ -77,18 +82,29 @@ function makeBuiltins(ctx) {
77
82
  }
78
83
 
79
84
  const machine = createMachine({ id: 'shell', initial: 'idle', states: {
80
- idle: { on: { RUN: 'executing' } },
85
+ idle: { on: { RUN: 'executing', ENTER_REPL: 'node-repl', NPM_START: 'npm-installing', NODE_START: 'node-running' } },
81
86
  executing: { on: { DONE: 'idle', ERROR: 'idle' } },
87
+ 'npm-installing': { on: { DONE: 'idle', ERROR: 'idle' } },
88
+ 'node-running': { on: { DONE: 'idle', ERROR: 'idle' } },
89
+ 'node-repl': { on: { EXIT_REPL: 'idle', RUN: 'node-repl' } },
82
90
  }});
83
91
 
84
92
  export function createShell({ term, onPreviewWrite }) {
85
- const ctx = { term, cwd: '/', env: {}, nodeMode: false, history: [], httpHandlers: {} };
93
+ const ctx = { term, cwd: '/', env: {}, history: [], httpHandlers: {} };
86
94
  const BUILTINS = makeBuiltins(ctx);
87
95
  ctx.nodeEval = createNodeEnv({ ctx, term });
88
96
 
89
97
  const actor = createActor(machine);
90
98
  actor.start();
91
99
 
100
+ let inputQueue = [];
101
+
102
+ function drainQueue(onData) {
103
+ const items = inputQueue.slice();
104
+ inputQueue = [];
105
+ for (const d of items) onData(d);
106
+ }
107
+
92
108
  window.__debug = window.__debug || {};
93
109
  window.__debug.shell = {
94
110
  get state() { return actor.getSnapshot().value; },
@@ -96,69 +112,87 @@ export function createShell({ term, onPreviewWrite }) {
96
112
  get env() { return ctx.env; },
97
113
  get history() { return ctx.history; },
98
114
  httpHandlers: ctx.httpHandlers,
99
- get nodeMode() { return ctx.nodeMode; },
115
+ get inputQueue() { return inputQueue.slice(); },
100
116
  };
101
117
 
102
- async function runCmd(line, capture) {
118
+ async function runCmd(line, capture, actor) {
103
119
  if (!line.trim()) return '';
104
120
  const [cmd, ...args] = line.trim().split(/\s+/);
105
121
  const fn = BUILTINS[cmd];
106
122
  if (!capture) {
107
- if (fn) await fn(args); else term.write('command not found: ' + cmd + '\r\n');
123
+ if (fn) await fn(args, actor); else term.write('command not found: ' + cmd + '\r\n');
108
124
  return '';
109
125
  }
110
126
  let out = '';
111
127
  const orig = term.write.bind(term);
112
128
  term.write = s => { out += s; };
113
- try { if (fn) await fn(args); else out += 'command not found: ' + cmd + '\r\n'; }
129
+ try { if (fn) await fn(args, actor); else out += 'command not found: ' + cmd + '\r\n'; }
114
130
  finally { term.write = orig; }
115
131
  return out;
116
132
  }
117
133
 
118
- async function run(line) {
134
+ async function run(line, onData) {
119
135
  if (!line.trim()) return;
120
136
  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' });
137
+ const st = actor.getSnapshot().value;
138
+ if (st === 'node-repl' && line.trim() !== 'exit') { await ctx.nodeEval(line); return; }
139
+ if (line.trim() === 'exit') { BUILTINS.exit(actor); return; }
140
+ const [cmd] = line.trim().split(/\s+/);
141
+ if (cmd !== 'npm' && cmd !== 'node') actor.send({ type: 'RUN' });
124
142
  try {
125
143
  const parts = line.split(' | ');
126
144
  if (parts.length > 1) {
127
- let buf = await runCmd(parts[0], true);
145
+ let buf = await runCmd(parts[0], true, actor);
128
146
  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');
147
+ const [c, ...a] = p.trim().split(/\s+/);
148
+ const fn = BUILTINS[c];
149
+ if (fn) await fn([buf, ...a], actor); else term.write('command not found: ' + c + '\r\n');
132
150
  buf = '';
133
151
  }
134
152
  } else {
135
- await runCmd(line, false);
153
+ await runCmd(line, false, actor);
136
154
  }
137
155
  actor.send({ type: 'DONE' });
156
+ drainQueue(onData);
138
157
  } catch (e) {
139
158
  term.write('\x1b[31m' + e.message + '\x1b[0m\r\n');
140
159
  actor.send({ type: 'ERROR' });
160
+ drainQueue(onData);
141
161
  }
142
162
  }
143
163
 
144
164
  const prompt = () => term.write('\r\n\x1b[32m' + ctx.cwd + ' $ \x1b[0m');
145
165
  let buf = '';
146
- term.onData(async data => {
166
+
167
+ function onData(data) {
168
+ if (data === '\x03') {
169
+ actor.send({ type: 'ERROR' });
170
+ inputQueue = [];
171
+ buf = '';
172
+ term.write('^C');
173
+ prompt();
174
+ return;
175
+ }
176
+ const st = actor.getSnapshot().value;
177
+ if (st !== 'idle' && st !== 'node-repl') {
178
+ inputQueue.push(data);
179
+ return;
180
+ }
147
181
  if (data === '\r') {
148
182
  term.write('\r\n');
149
183
  const line = buf;
150
184
  buf = '';
151
- await run(line);
152
- prompt();
185
+ run(line, onData).then(() => prompt());
153
186
  } else if (data === '\x7f') {
154
187
  if (buf.length) { buf = buf.slice(0, -1); term.write('\x08 \x08'); }
155
188
  } else {
156
189
  buf += data;
157
190
  term.write(data);
158
191
  }
159
- });
192
+ }
160
193
 
194
+ term.onData(onData);
161
195
  onPreviewWrite && (window.__debug.shell.onPreviewWrite = onPreviewWrite);
162
196
  prompt();
163
- return { run };
197
+ return { run: line => run(line, onData) };
164
198
  }
package/docs/terminal.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { Terminal, FitAddon } from './vendor/xterm-bundle.js';
2
+ import { createMachine, createActor } from './vendor/xstate.js';
2
3
  import { createShell } from './shell.js';
3
4
  import { registerPreviewSW } from './preview-sw-client.js';
4
5
 
@@ -31,6 +32,13 @@ async function idbSave(data) {
31
32
  });
32
33
  }
33
34
 
35
+ const bootMachine = createMachine({ id: 'terminal', initial: 'loading-idb', states: {
36
+ 'loading-idb': { on: { IDB_READY: 'registering-sw' } },
37
+ 'registering-sw': { on: { SW_READY: 'ready', SW_ERROR: 'ready' } },
38
+ 'ready': {},
39
+ 'error': {},
40
+ }});
41
+
34
42
  let reloadTimer = null;
35
43
  function scheduleReload() {
36
44
  clearTimeout(reloadTimer);
@@ -44,6 +52,12 @@ async function boot() {
44
52
  const el = document.getElementById('term-container');
45
53
  if (!el) return;
46
54
 
55
+ const bootActor = createActor(bootMachine);
56
+ bootActor.start();
57
+
58
+ window.__debug = window.__debug || {};
59
+ window.__debug.terminal = { get state() { return bootActor.getSnapshot().value; } };
60
+
47
61
  const term = new Terminal({ theme: { background: '#000000' }, convertEol: true });
48
62
  const fit = new FitAddon();
49
63
  term.loadAddon(fit);
@@ -60,15 +74,17 @@ async function boot() {
60
74
  files = await r.json();
61
75
  }
62
76
 
63
- window.__debug = window.__debug || {};
64
77
  window.__debug.idbSnapshot = files;
65
78
  window.__debug.idbPersist = () => idbSave(JSON.stringify(window.__debug.idbSnapshot));
66
79
  window.__debug.term = term;
80
+ bootActor.send({ type: 'IDB_READY' });
67
81
 
68
82
  try {
69
83
  await registerPreviewSW();
84
+ bootActor.send({ type: 'SW_READY' });
70
85
  } catch (e) {
71
86
  term.write('\x1b[33mSW: ' + e.message + '\x1b[0m\r\n');
87
+ bootActor.send({ type: 'SW_ERROR' });
72
88
  }
73
89
 
74
90
  createShell({ term, onPreviewWrite: scheduleReload });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.49",
3
+ "version": "1.2.51",
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",