thebird 1.2.110 → 1.2.112
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/agent-chat.js +4 -2
- package/docs/defaults.json +1 -1
- package/docs/index.html +17 -4
- package/docs/shell-builtins-fs.js +149 -0
- package/docs/shell-builtins-text.js +23 -3
- package/docs/shell-builtins.js +16 -0
- package/docs/shell-python.js +140 -0
- package/docs/shell.js +7 -2
- package/package.json +3 -2
- package/.gm/lastskill +0 -1
package/docs/index.html
CHANGED
|
@@ -564,7 +564,7 @@
|
|
|
564
564
|
<tr><td>license</td><td>MIT</td></tr>
|
|
565
565
|
<tr><td>runtime</td><td>browser (V8) + node ≥18</td></tr>
|
|
566
566
|
<tr><td>server required</td><td><strong style="color:var(--green)">none</strong> — 100% serverless</td></tr>
|
|
567
|
-
<tr><td>deps</td><td>acptoapi, @google/genai, xstate
|
|
567
|
+
<tr><td>deps</td><td>acptoapi, floosie, @google/genai, xstate</td></tr>
|
|
568
568
|
<tr><td>frontend</td><td>no framework · vanilla js + webjsx</td></tr>
|
|
569
569
|
<tr><td>storage</td><td>IndexedDB (no server, no cloud)</td></tr>
|
|
570
570
|
<tr><td>api key</td><td>localStorage only · never leaves browser</td></tr>
|
|
@@ -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
|
|
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
|
-
|
|
49
|
+
let count = 0;
|
|
50
|
+
(typeof text === 'string' ? text : '').split('\n').forEach((l, i) => {
|
|
33
51
|
re.lastIndex = 0;
|
|
34
|
-
|
|
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
|
},
|
package/docs/shell-builtins.js
CHANGED
|
@@ -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
|
|
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.
|
|
3
|
+
"version": "1.2.112",
|
|
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"
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"url": "https://github.com/AnEntrypoint/thebird.git"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"acptoapi": "file:../acptoapi"
|
|
41
|
+
"acptoapi": "file:../acptoapi",
|
|
42
|
+
"floosie": "^0.6.4"
|
|
42
43
|
},
|
|
43
44
|
"engines": {
|
|
44
45
|
"node": ">=18"
|
package/.gm/lastskill
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
planning
|