thebird 1.2.111 → 1.2.113

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
@@ -654,8 +654,11 @@
654
654
  <div id="pane-chat" style="flex:1;overflow:hidden;display:flex;flex-direction:column">
655
655
  <bird-chat></bird-chat>
656
656
  </div>
657
- <div id="pane-term" class="hidden" style="flex:1;overflow:hidden;display:flex;flex-direction:column">
657
+ <div id="pane-term" class="hidden" style="flex:1;overflow:hidden;display:flex;flex-direction:column;position:relative">
658
658
  <div id="term-container" style="flex:1"></div>
659
+ <div id="term-image-overlay" style="display:none;position:absolute;inset:0;background:rgba(0,0,0,0.82);z-index:10;align-items:center;justify-content:center;cursor:pointer" onclick="this.style.display='none'">
660
+ <img id="term-image-el" style="max-width:90%;max-height:90%;object-fit:contain;border:1px solid var(--ink-hair)" src="" alt="">
661
+ </div>
659
662
  </div>
660
663
  <div id="pane-preview" class="hidden" style="flex:1;overflow:hidden;display:flex;flex-direction:column">
661
664
  <div class="tui-toolbar">
@@ -738,8 +741,8 @@ async function refreshPreview() {
738
741
  const result = await callExpressRoute('/');
739
742
  if (result) { iframe.srcdoc = result.body; return; }
740
743
  const snap = window.__debug?.idbSnapshot || {};
741
- const htmlFiles = Object.keys(snap).filter(k => k.endsWith('.html'));
742
- const pick = htmlFiles.find(k => k === 'index.html') || htmlFiles[0];
744
+ const htmlFiles = Object.keys(snap).filter(k => k.endsWith('.html') && !k.startsWith('sys/'));
745
+ const pick = htmlFiles.find(k => k === 'home/index.html') || htmlFiles.find(k => k === 'index.html') || htmlFiles[0];
743
746
  if (pick) { iframe.srcdoc = snap[pick]; return; }
744
747
  const cs = getComputedStyle(document.documentElement);
745
748
  const fg = cs.getPropertyValue('--ink').trim(), bg = cs.getPropertyValue('--paper').trim();
@@ -808,6 +811,16 @@ async function githubLogin() {
808
811
 
809
812
  window.updateGhBadge = updateGhBadge;
810
813
  document.addEventListener('DOMContentLoaded', updateGhBadge);
814
+ document.addEventListener('term-image', e => {
815
+ const ov = document.getElementById('term-image-overlay');
816
+ const img = document.getElementById('term-image-el');
817
+ if (!ov || !img) return;
818
+ img.src = e.detail.src;
819
+ img.alt = e.detail.path;
820
+ ov.style.display = 'flex';
821
+ const pane = document.getElementById('pane-term');
822
+ if (pane && pane.classList.contains('hidden')) switchTab('term');
823
+ });
811
824
  </script>
812
825
  <script type="module" src="app.js"></script>
813
826
  <script type="module" src="terminal.js"></script>
@@ -0,0 +1,149 @@
1
+ import { resolvePath } from './shell-builtins.js';
2
+
3
+ const toKey = p => p.replace(/^\//, '');
4
+ const snap = () => window.__debug?.idbSnapshot || {};
5
+ const persist = () => window.__debug?.idbPersist?.();
6
+
7
+ export function makeFsBuiltins(ctx, readFile, writeFile) {
8
+ const w = s => ctx.term.write(s);
9
+ const wl = s => w(s + '\r\n');
10
+ const rp = p => resolvePath(ctx.cwd, p);
11
+
12
+ const aliases = ctx.aliases || (ctx.aliases = {});
13
+
14
+ function gzipDeflate(str) {
15
+ const enc = new TextEncoder().encode(str);
16
+ const cs = new CompressionStream('gzip');
17
+ const writer = cs.writable.getWriter();
18
+ writer.write(enc); writer.close();
19
+ return new Response(cs.readable).arrayBuffer();
20
+ }
21
+
22
+ async function gzipInflate(buf) {
23
+ const ds = new DecompressionStream('gzip');
24
+ const writer = ds.writable.getWriter();
25
+ writer.write(buf); writer.close();
26
+ const ab = await new Response(ds.readable).arrayBuffer();
27
+ return new TextDecoder().decode(ab);
28
+ }
29
+
30
+ function b64ToAb(b64) {
31
+ const bin = atob(b64); const arr = new Uint8Array(bin.length);
32
+ for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i);
33
+ return arr.buffer;
34
+ }
35
+
36
+ function abToB64(ab) {
37
+ const arr = new Uint8Array(ab); let bin = '';
38
+ for (const b of arr) bin += String.fromCharCode(b);
39
+ return btoa(bin);
40
+ }
41
+
42
+ return {
43
+ ln: args => {
44
+ const sym = args.includes('-s') || args.includes('-sf');
45
+ const paths = args.filter(a => !a.startsWith('-'));
46
+ const [src, dst] = paths;
47
+ if (!src || !dst) throw new Error('ln: missing operand');
48
+ const srcK = toKey(rp(src)), dstK = toKey(rp(dst));
49
+ const s = snap();
50
+ if (sym) { s[dstK] = s[srcK] ?? ''; persist(); }
51
+ else { if (!(srcK in s)) throw new Error(src + ': No such file'); s[dstK] = s[srcK]; persist(); }
52
+ },
53
+ chmod: args => {
54
+ const paths = args.filter(a => !a.startsWith('-') && !/^\d+$/.test(a));
55
+ for (const p of paths) { const k = toKey(rp(p)); if (!(k in snap()) && !Object.keys(snap()).some(x => x.startsWith(k + '/'))) throw new Error(p + ': No such file'); }
56
+ },
57
+ stat: args => {
58
+ const path = args.find(a => !a.startsWith('-'));
59
+ if (!path) throw new Error('stat: missing operand');
60
+ const k = toKey(rp(path));
61
+ const s = snap();
62
+ const isDir = !(k in s) && Object.keys(s).some(x => x.startsWith(k + '/'));
63
+ if (!isDir && !(k in s)) throw new Error(path + ': No such file or directory');
64
+ const size = isDir ? 0 : (s[k]?.length || 0);
65
+ wl(' File: ' + path);
66
+ wl(' Size: ' + size + '\t\tBlocks: ' + Math.ceil(size / 512) + '\t' + (isDir ? 'directory' : 'regular file'));
67
+ wl('Access: -rwxr-xr-x Uid: 1000 Gid: 1000');
68
+ },
69
+ alias: args => {
70
+ if (!args.length) { for (const [k, v] of Object.entries(aliases)) wl('alias ' + k + '=\'' + v + '\''); return; }
71
+ for (const a of args) { const eq = a.indexOf('='); if (eq < 0) { wl(aliases[a] ? 'alias ' + a + '=\'' + aliases[a] + '\'' : a + ': not found'); } else { aliases[a.slice(0, eq)] = a.slice(eq + 1).replace(/^['"]|['"]$/g, ''); } }
72
+ },
73
+ unalias: args => { for (const a of args.filter(x => x !== '-a')) delete aliases[a]; if (args.includes('-a')) Object.keys(aliases).forEach(k => delete aliases[k]); },
74
+ gzip: async args => {
75
+ const keep = args.includes('-k');
76
+ const decomp = args.includes('-d');
77
+ const paths = args.filter(a => !a.startsWith('-'));
78
+ const s = snap();
79
+ for (const p of paths) {
80
+ const k = toKey(rp(p));
81
+ if (decomp || p.endsWith('.gz')) {
82
+ const src = s[k]; if (src == null) throw new Error(p + ': No such file');
83
+ const raw = src.startsWith('data:application/gzip;base64,') ? src.slice(29) : src;
84
+ const text = await gzipInflate(b64ToAb(raw));
85
+ const out = k.replace(/\.gz$/, '') || k + '.out';
86
+ s[out] = text; if (!keep) delete s[k]; persist(); wl(p + ' → /' + out);
87
+ } else {
88
+ const src = s[k]; if (src == null) throw new Error(p + ': No such file');
89
+ const ab = await gzipDeflate(src);
90
+ s[k + '.gz'] = 'data:application/gzip;base64,' + abToB64(ab);
91
+ if (!keep) delete s[k]; persist(); wl(p + ' → /' + k + '.gz');
92
+ }
93
+ }
94
+ },
95
+ gunzip: async args => {
96
+ const keep = args.includes('-k');
97
+ const paths = args.filter(a => !a.startsWith('-'));
98
+ const s = snap();
99
+ for (const p of paths) {
100
+ const k = toKey(rp(p));
101
+ const src = s[k]; if (src == null) throw new Error(p + ': No such file');
102
+ const raw = src.startsWith('data:application/gzip;base64,') ? src.slice(29) : src;
103
+ const text = await gzipInflate(b64ToAb(raw));
104
+ const out = k.replace(/\.gz$/, '');
105
+ s[out] = text; if (!keep) delete s[k]; persist(); wl(p + ' → /' + out);
106
+ }
107
+ },
108
+ md5sum: args => {
109
+ const stdinFirst = args.length > 0 && args[0].includes('\n');
110
+ const stdin = stdinFirst ? args[0] : null;
111
+ const files = stdinFirst ? args.slice(1) : args;
112
+ const pairs = files.length ? files.map(f => [f, readFile(f)]) : [['', stdin || '']];
113
+ for (const [name, c] of pairs) {
114
+ let h = 0x811c9dc5;
115
+ for (let i = 0; i < c.length; i++) { h ^= c.charCodeAt(i); h = (h * 0x01000193) >>> 0; }
116
+ wl(h.toString(16).padStart(8, '0').repeat(4) + ' ' + (name || '-'));
117
+ }
118
+ },
119
+ file: args => {
120
+ for (const p of args.filter(a => !a.startsWith('-'))) {
121
+ const k = toKey(rp(p));
122
+ const s = snap();
123
+ if (!(k in s)) { wl(p + ': No such file'); continue; }
124
+ const c = s[k] || '';
125
+ const type = c.startsWith('data:image') ? 'image data' : c.startsWith('{') || c.startsWith('[') ? 'JSON data' : c.startsWith('#!') ? 'script' : 'ASCII text';
126
+ wl(p + ': ' + type + ', ' + c.length + ' bytes');
127
+ }
128
+ },
129
+ du: args => {
130
+ const human = args.includes('-h');
131
+ const path = args.find(a => !a.startsWith('-')) || '.';
132
+ const prefix = toKey(rp(path));
133
+ let total = 0;
134
+ for (const [k, v] of Object.entries(snap())) {
135
+ if (k === prefix || k.startsWith(prefix + '/')) total += (typeof v === 'string' ? v.length : 0);
136
+ }
137
+ const fmt = human ? (total > 1048576 ? (total / 1048576).toFixed(1) + 'M' : total > 1024 ? (total / 1024).toFixed(1) + 'K' : total + 'B') : String(Math.ceil(total / 512));
138
+ wl(fmt + '\t' + path);
139
+ },
140
+ df: args => {
141
+ const human = args.includes('-h');
142
+ const used = Object.values(snap()).reduce((s, v) => s + (typeof v === 'string' ? v.length : 0), 0);
143
+ const total = 50 * 1024 * 1024;
144
+ const fmt = n => human ? (n > 1048576 ? (n / 1048576).toFixed(0) + 'M' : (n / 1024).toFixed(0) + 'K') : String(Math.ceil(n / 1024));
145
+ wl('Filesystem Size Used Avail Use% Mounted on');
146
+ wl('idb ' + fmt(total) + ' ' + fmt(used) + ' ' + fmt(total - used) + ' ' + Math.round(used / total * 100) + '% /');
147
+ },
148
+ };
149
+ }
@@ -25,14 +25,34 @@ export function makeTextBuiltins(ctx, readFile, writeFile) {
25
25
  if (!pat) throw new Error('grep: missing pattern');
26
26
  const re = new RegExp(pat, flags.includes('i') ? 'gi' : 'g');
27
27
  const lineNos = flags.includes('n');
28
- const sources = fileArgs.length ? fileArgs.map(f => ({ name: f, text: readFile(f) })) : [{ name: '', text: stdin || '' }];
28
+ const countOnly = flags.includes('c');
29
+ const invertMatch = flags.includes('v');
30
+ const recursive = flags.includes('r') || flags.includes('R');
31
+ let sources = [];
32
+ if (fileArgs.length) {
33
+ for (const f of fileArgs) {
34
+ if (recursive) {
35
+ const prefix = toKey(resolvePath(ctx.cwd, f));
36
+ for (const k of Object.keys(snap())) {
37
+ if (k === prefix || k.startsWith(prefix + '/')) sources.push({ name: '/' + k, text: snap()[k] });
38
+ }
39
+ } else {
40
+ sources.push({ name: f, text: readFile(f) });
41
+ }
42
+ }
43
+ } else {
44
+ sources = [{ name: '', text: stdin || '' }];
45
+ }
29
46
  const showFile = sources.length > 1 || flags.includes('H');
30
47
  let matched = 0;
31
48
  for (const { name, text } of sources) {
32
- text.split('\n').forEach((l, i) => {
49
+ let count = 0;
50
+ (typeof text === 'string' ? text : '').split('\n').forEach((l, i) => {
33
51
  re.lastIndex = 0;
34
- if (re.test(l)) { wl((showFile && name ? name + ':' : '') + (lineNos ? (i + 1) + ':' : '') + l); matched++; }
52
+ const hit = re.test(l);
53
+ if (hit !== invertMatch) { count++; matched++; if (!countOnly) wl((showFile && name ? name + ':' : '') + (lineNos ? (i + 1) + ':' : '') + l); }
35
54
  });
55
+ if (countOnly) wl((showFile && name ? name + ':' : '') + count);
36
56
  }
37
57
  if (!matched) ctx.lastExitCode = 1;
38
58
  },
@@ -1,6 +1,8 @@
1
1
  import { makeTextBuiltins } from './shell-builtins-text.js';
2
2
  import { makeExtraBuiltins } from './shell-builtins-extra.js';
3
3
  import { makeUtilBuiltins } from './shell-builtins-util.js';
4
+ import { makePythonBuiltin } from './shell-python.js';
5
+ import { makeFsBuiltins } from './shell-builtins-fs.js';
4
6
 
5
7
  export function resolvePath(cwd, p) {
6
8
  if (!p || p === '~') return '/';
@@ -59,6 +61,8 @@ export function makeBuiltins(ctx, actor, invokeBuiltin) {
59
61
  const text = makeTextBuiltins(ctx, readFile, writeFile);
60
62
  const extra = makeExtraBuiltins(ctx, readFile, writeFile);
61
63
  const util = makeUtilBuiltins(ctx, readFile, writeFile);
64
+ const pyBuiltins = makePythonBuiltin(ctx);
65
+ const fsExtra = makeFsBuiltins(ctx, readFile, writeFile);
62
66
  const b = {
63
67
  ls: args => {
64
68
  const flags = args.filter(a => a.startsWith('-')).join('');
@@ -143,6 +147,16 @@ export function makeBuiltins(ctx, actor, invokeBuiltin) {
143
147
  persist();
144
148
  },
145
149
  touch: args => { for (const f of args) { const k = toKey(resolvePath(ctx.cwd, f)); if (!(k in snap())) snap()[k] = ''; } persist(); },
150
+ imgcat: args => {
151
+ if (!args.length) throw new Error('imgcat: missing file operand');
152
+ const path = args[0];
153
+ const content = readFile(path);
154
+ const ext = path.split('.').pop().toLowerCase();
155
+ const mime = ext === 'png' ? 'image/png' : ext === 'gif' ? 'image/gif' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
156
+ const src = content.startsWith('data:') ? content : 'data:' + mime + ';base64,' + btoa(unescape(encodeURIComponent(content)));
157
+ document.dispatchEvent(new CustomEvent('term-image', { detail: { src, path } }));
158
+ wl('\x1b[32m[image: ' + path + ']\x1b[0m');
159
+ },
146
160
  head: args => {
147
161
  const n = args[0] === '-n' ? parseInt(args[1], 10) : 10;
148
162
  const rest = args[0] === '-n' ? args.slice(2) : args;
@@ -174,6 +188,8 @@ export function makeBuiltins(ctx, actor, invokeBuiltin) {
174
188
  ...text,
175
189
  ...extra,
176
190
  ...util,
191
+ ...pyBuiltins,
192
+ ...fsExtra,
177
193
  which: args => text.which(args, b),
178
194
  exit: (args, ac) => text.exit(args, ac || actor),
179
195
  };
@@ -0,0 +1,140 @@
1
+ const MICROPYTHON_URL = 'https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript@1.25.0/micropython.mjs';
2
+
3
+ let mpPromise = null;
4
+ let mpInstance = null;
5
+
6
+ async function getMp(onStdout) {
7
+ if (mpInstance) return mpInstance;
8
+ if (mpPromise) return mpPromise;
9
+ mpPromise = (async () => {
10
+ const mod = await import(MICROPYTHON_URL);
11
+ mpInstance = await mod.loadMicroPython({ stdout: line => onStdout(line + '\n') });
12
+ return mpInstance;
13
+ })();
14
+ return mpPromise;
15
+ }
16
+
17
+ export function makePythonBuiltin(ctx) {
18
+ const w = s => ctx.term.write(s);
19
+ const wl = s => w(s + '\r\n');
20
+ const snap = () => window.__debug?.idbSnapshot || {};
21
+ const persist = () => window.__debug?.idbPersist?.();
22
+ const toKey = p => p.replace(/^\//, '');
23
+
24
+ function cwdKey(rel) {
25
+ if (rel.startsWith('/')) return toKey(rel);
26
+ return toKey(ctx.cwd.replace(/\/$/, '') + '/' + rel);
27
+ }
28
+
29
+ async function getInterpreter() {
30
+ return getMp(line => w(line.replace(/\n/g, '\r\n')));
31
+ }
32
+
33
+ async function bridgeFs(instance) {
34
+ instance.globals.set('_idb_snap', snap());
35
+ instance.globals.set('_idb_persist', persist);
36
+ await instance.runPythonAsync(`
37
+ import sys, os
38
+ _snap = _idb_snap
39
+ class _Open:
40
+ def __init__(self, key, mode):
41
+ self._key = key; self._buf = _snap.get(key, ''); self._pos = 0; self._mode = mode
42
+ if 'w' in mode: self._buf = ''
43
+ def read(self, n=-1):
44
+ if n < 0: d = self._buf[self._pos:]; self._pos = len(self._buf)
45
+ else: d = self._buf[self._pos:self._pos+n]; self._pos += len(d)
46
+ return d
47
+ def write(self, s): self._buf += s
48
+ def readlines(self): return self._buf.splitlines(True)
49
+ def __iter__(self): return iter(self._buf.splitlines(True))
50
+ def __enter__(self): return self
51
+ def __exit__(self, *a):
52
+ if 'w' in self._mode or 'a' in self._mode:
53
+ _snap[self._key] = self._buf
54
+ _idb_persist()
55
+ _builtin_open = open
56
+ def open(path, mode='r', *a, **kw):
57
+ key = path.lstrip('/')
58
+ if key in _snap or 'w' in mode or 'a' in mode: return _Open(key, mode)
59
+ return _builtin_open(path, mode, *a, **kw)
60
+ del _idb_snap
61
+ `);
62
+ }
63
+
64
+ async function runCode(code, argv) {
65
+ const instance = await getInterpreter();
66
+ instance.globals.set('__mp_argv', argv);
67
+ await bridgeFs(instance);
68
+ await instance.runPythonAsync('import sys; sys.argv = list(__mp_argv)');
69
+ await instance.runPythonAsync(code);
70
+ }
71
+
72
+ async function pipInstall(pkgs) {
73
+ const instance = await getInterpreter();
74
+ wl('\x1b[33mInstalling via micropython-lib (mip)...\x1b[0m');
75
+ for (const pkg of pkgs) {
76
+ wl(' → ' + pkg);
77
+ const url = 'https://micropython.org/pi/v2/package/6/micropython-' + pkg + '/latest.json';
78
+ try {
79
+ const res = await fetch(url);
80
+ if (!res.ok) throw new Error('not found in micropython-lib');
81
+ const meta = await res.json();
82
+ for (const [path, fileUrl] of Object.entries(meta.hashes || {})) {
83
+ const r = await fetch('https://micropython.org/pi/v2/' + fileUrl);
84
+ const text = await r.text();
85
+ const key = 'lib/' + path;
86
+ snap()[key] = text;
87
+ }
88
+ if (meta.urls) {
89
+ for (const [path, fileUrl] of meta.urls) {
90
+ const r = await fetch(fileUrl);
91
+ const text = await r.text();
92
+ snap()[path] = text;
93
+ }
94
+ }
95
+ persist();
96
+ wl(' \x1b[32m✓ ' + pkg + '\x1b[0m');
97
+ } catch (e) {
98
+ wl(' \x1b[31m✗ ' + pkg + ': ' + e.message + '\x1b[0m');
99
+ }
100
+ }
101
+ }
102
+
103
+ async function pythonBuiltin(args, _actor, stdin) {
104
+ const cFlag = args.indexOf('-c');
105
+ if (cFlag >= 0) {
106
+ await runCode(args[cFlag + 1] || '', ['python', ...args.slice(cFlag + 2)]);
107
+ return;
108
+ }
109
+ if (!args.length) {
110
+ if (stdin) { await runCode(stdin, ['python']); return; }
111
+ wl('MicroPython — use: python script.py | python -c "code" | echo "code" | python');
112
+ return;
113
+ }
114
+ const scriptKey = cwdKey(args[0]);
115
+ const src = snap()[scriptKey];
116
+ if (src == null) throw new Error('python: ' + args[0] + ': No such file');
117
+ await runCode(src, args);
118
+ }
119
+
120
+ async function pipBuiltin(args) {
121
+ const sub = args[0];
122
+ if (sub === 'install' || sub === 'i') {
123
+ const pkgs = args.slice(1).filter(a => !a.startsWith('-'));
124
+ if (!pkgs.length) throw new Error('pip install: no packages specified');
125
+ await pipInstall(pkgs);
126
+ return;
127
+ }
128
+ if (sub === 'list') {
129
+ const keys = Object.keys(snap()).filter(k => k.startsWith('lib/') && k.endsWith('.py'));
130
+ if (!keys.length) { wl('(no micropython packages installed)'); return; }
131
+ wl('Package Location');
132
+ wl('-'.repeat(50));
133
+ for (const k of keys) wl(k.replace('lib/', '').replace('.py', '').padEnd(26) + k);
134
+ return;
135
+ }
136
+ wl('pip: subcommands: install, list');
137
+ }
138
+
139
+ return { python: pythonBuiltin, python3: pythonBuiltin, pip: pipBuiltin, pip3: pipBuiltin };
140
+ }
package/docs/shell.js CHANGED
@@ -24,7 +24,7 @@ const machine = createMachine({ id: 'shell', initial: 'idle', states: {
24
24
  }});
25
25
 
26
26
  export function createShell({ term, onPreviewWrite }) {
27
- const ctx = { term, cwd: '/', prevCwd: '/', env: {}, history: [], lastExitCode: 0, argv: [], functions: {}, opts: {}, localStack: [], loopFlag: null, arrays: {}, bgJobs: {}, traps: {} };
27
+ const ctx = { term, cwd: '/home', prevCwd: '/home', env: {}, history: [], lastExitCode: 0, argv: [], functions: {}, opts: {}, localStack: [], loopFlag: null, arrays: {}, bgJobs: {}, traps: {} };
28
28
  const actor = createActor(machine);
29
29
  actor.start();
30
30
  const httpHandlers = {};
@@ -97,7 +97,12 @@ export function createShell({ term, onPreviewWrite }) {
97
97
  while (i < raw.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(raw[i])) varAssigns.push(raw[i++]);
98
98
  const rest = raw.slice(i);
99
99
  if (!rest.length) { for (const kv of varAssigns) { const [k, v] = evalKV(kv); ctx.env[k] = v; } return; }
100
- const { args: [cmd, ...args], stdout: rout, append } = parseRedirect(expandTokens(rest));
100
+ const expanded = expandTokens(rest);
101
+ if (expanded.length && ctx.aliases?.[expanded[0]]) {
102
+ const aliasTokens = tokenize(ctx.aliases[expanded[0]]);
103
+ expanded.splice(0, 1, ...aliasTokens);
104
+ }
105
+ const { args: [cmd, ...args], stdout: rout, append } = parseRedirect(expanded);
101
106
  const writeOut = rout ? buf => { const k = toKey(resolvePath(ctx.cwd, rout)); snap()[k] = append ? (snap()[k] || '') + buf : buf; window.__debug.idbPersist?.(); } : null;
102
107
  const prevEnv = {}; for (const kv of varAssigns) { const [k, v] = evalKV(kv); prevEnv[k] = ctx.env[k]; ctx.env[k] = v; }
103
108
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thebird",
3
- "version": "1.2.111",
3
+ "version": "1.2.113",
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/.gm/lastskill DELETED
@@ -1 +0,0 @@
1
- planning