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
@@ -0,0 +1,121 @@
1
+ import { resolvePath } from './shell-builtins.js';
2
+
3
+ const toKey = p => p.replace(/^\//, '');
4
+ const snap = () => window.__debug.idbSnapshot || {};
5
+
6
+ export function makeExtraBuiltins(ctx, readFile, writeFile) {
7
+ const w = s => ctx.term.write(s);
8
+ const wl = s => w(s + '\r\n');
9
+ return {
10
+ test: args => { ctx.lastExitCode = evalTest(args) ? 0 : 1; },
11
+ '[': args => { const inner = args[args.length - 1] === ']' ? args.slice(0, -1) : args; ctx.lastExitCode = evalTest(inner) ? 0 : 1; },
12
+ tee: (args, _a, stdin) => {
13
+ const files = args.filter(a => !a.startsWith('-'));
14
+ const append = args.some(a => a === '-a');
15
+ const buf = stdin || '';
16
+ for (const f of files) writeFile(f, append ? (snap()[toKey(resolvePath(ctx.cwd, f))] || '') + buf : buf);
17
+ w(buf.replace(/\n/g, '\r\n'));
18
+ },
19
+ xargs: async (args, _a, stdin, invokeBuiltin) => {
20
+ const parts = (stdin || '').trim().split(/\s+/).filter(Boolean);
21
+ if (!args.length || !parts.length) return;
22
+ await invokeBuiltin(args[0], [...args.slice(1), ...parts], false);
23
+ },
24
+ read: (args, _a, stdin) => {
25
+ const flags = args.filter(a => a.startsWith('-')).join('');
26
+ const names = args.filter(a => !a.startsWith('-'));
27
+ if (!names.length) names.push('REPLY');
28
+ let line = (stdin || '').split('\n')[0];
29
+ if (!flags.includes('r')) line = line.replace(/\\(.)/g, '$1');
30
+ line = line.replace(/\r$/, '');
31
+ const nIdx = flags.indexOf('n');
32
+ if (nIdx >= 0) { const n = parseInt(flags.slice(nIdx + 1), 10); if (!isNaN(n)) line = line.slice(0, n); }
33
+ const parts = line.split(/\s+/);
34
+ for (let i = 0; i < names.length; i++) ctx.env[names[i]] = i === names.length - 1 ? parts.slice(i).join(' ') : parts[i] || '';
35
+ },
36
+ printf: args => {
37
+ if (!args.length) return;
38
+ let dest = null;
39
+ if (args[0] === '-v') { dest = args[1]; args = args.slice(2); }
40
+ if (!args.length) return;
41
+ const fmt = args[0].replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r');
42
+ let idx = 1;
43
+ const out = fmt.replace(/%([sdxof])/g, (_, spec) => {
44
+ const v = args[idx++] ?? '';
45
+ if (spec === 'd') return String(parseInt(v, 10) || 0);
46
+ if (spec === 'x') return (parseInt(v, 10) || 0).toString(16);
47
+ if (spec === 'o') return (parseInt(v, 10) || 0).toString(8);
48
+ if (spec === 'f') return String(parseFloat(v) || 0);
49
+ return String(v);
50
+ });
51
+ if (dest) ctx.env[dest] = out; else w(out.replace(/\n/g, '\r\n'));
52
+ },
53
+ declare: args => {
54
+ const assoc = args.includes('-A');
55
+ const arr = args.includes('-a');
56
+ const names = args.filter(a => !a.startsWith('-'));
57
+ for (const n of names) { const eq = n.indexOf('='); const k = eq >= 0 ? n.slice(0, eq) : n; if (assoc) { ctx.arrays = ctx.arrays || {}; ctx.arrays[k] = {}; } else if (arr) { ctx.arrays = ctx.arrays || {}; ctx.arrays[k] = []; } else if (eq >= 0) ctx.env[k] = n.slice(eq + 1); }
58
+ },
59
+ shift: args => {
60
+ const n = parseInt(args[0], 10) || 1;
61
+ ctx.argv = (ctx.argv || []).slice(n);
62
+ },
63
+ local: args => {
64
+ for (const kv of args) {
65
+ const eq = kv.indexOf('=');
66
+ const k = eq >= 0 ? kv.slice(0, eq) : kv;
67
+ const v = eq >= 0 ? kv.slice(eq + 1) : '';
68
+ (ctx.localStack && ctx.localStack[ctx.localStack.length - 1] || {})[k] = ctx.env[k];
69
+ ctx.env[k] = v;
70
+ }
71
+ },
72
+ set: args => {
73
+ for (const a of args) {
74
+ if (a === '-e') ctx.opts = { ...ctx.opts, errexit: true };
75
+ else if (a === '+e') ctx.opts = { ...ctx.opts, errexit: false };
76
+ else if (a === '-x') ctx.opts = { ...ctx.opts, xtrace: true };
77
+ else if (a === '+x') ctx.opts = { ...ctx.opts, xtrace: false };
78
+ else if (a === '-u') ctx.opts = { ...ctx.opts, nounset: true };
79
+ }
80
+ },
81
+ break: args => { ctx.loopFlag = 'break'; ctx.loopDepth = parseInt(args[0], 10) || 1; },
82
+ continue: args => { ctx.loopFlag = 'continue'; ctx.loopDepth = parseInt(args[0], 10) || 1; },
83
+ source: async (args, _a, _s, invokeBuiltin, runLine) => {
84
+ if (!args[0]) throw new Error('source: missing file');
85
+ const content = snap()[toKey(resolvePath(ctx.cwd, args[0]))];
86
+ if (content == null) throw new Error('source: ' + args[0] + ': No such file');
87
+ const savedArgv = ctx.argv;
88
+ ctx.argv = [args[0], ...args.slice(1)];
89
+ try { if (ctx.runScript) await ctx.runScript(content); else for (const ln of content.split('\n')) if (ln.trim()) await runLine(ln); }
90
+ finally { ctx.argv = savedArgv; }
91
+ },
92
+ '.': async (args, actor, stdin, invokeBuiltin, runLine) => {
93
+ const src = (ctx.builtinsRef || {}).source;
94
+ if (src) await src(args, actor, stdin, invokeBuiltin, runLine);
95
+ },
96
+ };
97
+ }
98
+
99
+ function evalTest(args) {
100
+ if (args.length === 1) return !!args[0];
101
+ if (args.length === 2) {
102
+ const [flag, val] = args;
103
+ const s = () => window.__debug.idbSnapshot || {};
104
+ const OPS = {
105
+ '-z': v => v === '', '-n': v => v !== '',
106
+ '-f': v => v in s(),
107
+ '-d': v => Object.keys(s()).some(k => k.startsWith(v + '/')),
108
+ '-e': v => v in s() || Object.keys(s()).some(k => k.startsWith(v + '/')),
109
+ '!': v => !v,
110
+ };
111
+ return OPS[flag]?.(val) ?? false;
112
+ }
113
+ if (args.length === 3) {
114
+ const [a, op, b] = args;
115
+ const CMP = { '=':(x,y)=>x===y,'==':(x,y)=>x===y,'!=':(x,y)=>x!==y,
116
+ '-eq':(x,y)=>+x===+y,'-ne':(x,y)=>+x!==+y,
117
+ '-lt':(x,y)=>+x<+y,'-gt':(x,y)=>+x>+y,'-le':(x,y)=>+x<=+y,'-ge':(x,y)=>+x>=+y };
118
+ return CMP[op]?.(a, b) ?? false;
119
+ }
120
+ return false;
121
+ }
@@ -0,0 +1,109 @@
1
+ import { resolvePath } from './shell-builtins.js';
2
+ import { runSed } from './shell-sed.js';
3
+
4
+ const toKey = p => p.replace(/^\//, '');
5
+ const snap = () => window.__debug.idbSnapshot || {};
6
+ const persist = () => window.__debug.idbPersist?.();
7
+ const previewWrite = () => window.__debug.shell?.onPreviewWrite?.();
8
+
9
+ const readLines = text => text.split('\n').map(l => l.replace(/\r$/, '')).filter((l, i, a) => i < a.length - 1 || l !== '');
10
+
11
+ function readStdinFirst(positional) {
12
+ const stdinFirst = positional.length > 0 && positional[0].includes('\n');
13
+ return { stdin: stdinFirst ? positional[0] : null, rest: stdinFirst ? positional.slice(1) : positional };
14
+ }
15
+
16
+ export function makeTextBuiltins(ctx, readFile, writeFile) {
17
+ const w = s => ctx.term.write(s);
18
+ const wl = s => w(s + '\r\n');
19
+ return {
20
+ grep: args => {
21
+ const flags = args.filter(a => a.startsWith('-')).join('');
22
+ const positional = args.filter(a => !a.startsWith('-'));
23
+ const { stdin, rest } = readStdinFirst(positional);
24
+ const [pat, ...fileArgs] = rest;
25
+ if (!pat) throw new Error('grep: missing pattern');
26
+ const re = new RegExp(pat, flags.includes('i') ? 'gi' : 'g');
27
+ const lineNos = flags.includes('n');
28
+ const sources = fileArgs.length ? fileArgs.map(f => ({ name: f, text: readFile(f) })) : [{ name: '', text: stdin || '' }];
29
+ const showFile = sources.length > 1 || flags.includes('H');
30
+ let matched = 0;
31
+ for (const { name, text } of sources) {
32
+ text.split('\n').forEach((l, i) => {
33
+ re.lastIndex = 0;
34
+ if (re.test(l)) { wl((showFile && name ? name + ':' : '') + (lineNos ? (i + 1) + ':' : '') + l); matched++; }
35
+ });
36
+ }
37
+ if (!matched) ctx.lastExitCode = 1;
38
+ },
39
+ sed: args => {
40
+ const exprs = [];
41
+ const files = [];
42
+ let inplace = false;
43
+ for (let i = 0; i < args.length; i++) {
44
+ if (args[i] === '-e') { exprs.push(args[++i]); continue; }
45
+ if (args[i] === '-i') { inplace = true; continue; }
46
+ if (args[i].startsWith('-')) continue;
47
+ if (!exprs.length) exprs.push(args[i]); else files.push(args[i]);
48
+ }
49
+ const { stdin, rest } = readStdinFirst(files);
50
+ const fileArgs = rest;
51
+ if (!exprs.length) throw new Error('sed: missing expression');
52
+ const pairs = fileArgs.length ? fileArgs.map(f => [f, readFile(f)]) : [['', stdin || '']];
53
+ for (const [name, text] of pairs) {
54
+ const out = runSed(exprs, text);
55
+ if (name && inplace) writeFile(name, out);
56
+ else if (name) w(out.replace(/\n/g, '\r\n') + '\r\n');
57
+ else w(out.replace(/\n/g, '\r\n'));
58
+ }
59
+ },
60
+ sort: args => {
61
+ const flags = args.filter(a => a.startsWith('-')).join('');
62
+ const positional = args.filter(a => !a.startsWith('-'));
63
+ const stdinFirst = positional.length > 0 && positional[0].includes('\n');
64
+ const stdin = stdinFirst ? positional[0] : null;
65
+ const fileArgs = stdinFirst ? positional.slice(1) : positional;
66
+ const targets = fileArgs.length ? fileArgs : [null];
67
+ for (const f of targets) {
68
+ let lines = readLines(f ? readFile(f) : stdin || '');
69
+ if (flags.includes('r')) lines.sort().reverse(); else lines.sort();
70
+ if (flags.includes('u')) lines = [...new Set(lines)];
71
+ wl(lines.join('\r\n'));
72
+ }
73
+ },
74
+ uniq: args => {
75
+ const positional = args.filter(a => !a.startsWith('-'));
76
+ const stdinFirst = positional.length > 0 && positional[0].includes('\n');
77
+ const stdin = stdinFirst ? positional[0] : null;
78
+ const fileArgs = stdinFirst ? positional.slice(1) : positional;
79
+ const targets = fileArgs.length ? fileArgs : [null];
80
+ for (const f of targets) {
81
+ const lines = readLines(f ? readFile(f) : stdin || '');
82
+ wl(lines.filter((l, i) => i === 0 || l !== lines[i - 1]).join('\r\n'));
83
+ }
84
+ },
85
+ tr: args => {
86
+ const positional = args.filter(a => !a.startsWith('-'));
87
+ const stdin = positional.length > 0 ? positional[0] : '';
88
+ const [from, to] = positional.slice(1);
89
+ if (!from) throw new Error('tr: missing operand');
90
+ const mapped = stdin.split('').map(c => {
91
+ const i = from.indexOf(c);
92
+ return to == null ? (from.includes(c) ? '' : c) : (i >= 0 ? (to[i] || to[to.length - 1]) : c);
93
+ }).join('');
94
+ wl(mapped.replace(/\n/g, '\r\n'));
95
+ },
96
+ env: () => wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n')),
97
+ export: args => { for (const kv of args) { const [k, ...v] = kv.split('='); ctx.env[k] = v.join('='); } },
98
+ clear: () => ctx.term.clear(),
99
+ history: () => ctx.history.forEach((l, i) => wl(String(i + 1).padStart(5) + ' ' + l)),
100
+ which: (args, b) => { const cmd = args[0]; if (!cmd) throw new Error('which: missing operand'); if (b[cmd]) wl('(builtin) ' + cmd); else wl(cmd + ' not found'); },
101
+ exit: (args, actor) => { if (actor.getSnapshot().value === 'node-repl') { actor.send({ type: 'EXIT_REPL' }); wl('[shell]'); } },
102
+ true: () => {},
103
+ false: () => { ctx.lastExitCode = 1; },
104
+ printenv: args => {
105
+ if (!args.length) wl(Object.entries(ctx.env).map(([k, v]) => k + '=' + v).join('\r\n'));
106
+ else wl(ctx.env[args[0]] ?? '');
107
+ },
108
+ };
109
+ }
@@ -0,0 +1,112 @@
1
+ import { resolvePath } from './shell-builtins.js';
2
+ import { runAwk } from './shell-awk.js';
3
+
4
+ const toKey = p => p.replace(/^\//, '');
5
+ const snap = () => window.__debug.idbSnapshot || {};
6
+
7
+ export function makeUtilBuiltins(ctx, readFile, writeFile) {
8
+ const w = s => ctx.term.write(s);
9
+ const wl = s => w(s + '\r\n');
10
+ return {
11
+ basename: args => { if (!args[0]) return; const p = args[0].replace(/\/+$/, '').split('/').pop(); wl(args[1] ? p.replace(new RegExp(args[1] + '$'), '') : p); },
12
+ dirname: args => { if (!args[0]) return; const idx = args[0].replace(/\/+$/, '').lastIndexOf('/'); wl(idx <= 0 ? (idx === 0 ? '/' : '.') : args[0].slice(0, idx)); },
13
+ realpath: args => { if (!args[0]) return; wl(resolvePath(ctx.cwd, args[0])); },
14
+ date: args => {
15
+ const fmt = args.find(a => a.startsWith('+'));
16
+ const d = new Date();
17
+ if (!fmt) { wl(d.toUTCString()); return; }
18
+ const pad = (n, z = 2) => String(n).padStart(z, '0');
19
+ const MAP = { Y: d.getFullYear(), m: pad(d.getMonth() + 1), d: pad(d.getDate()), H: pad(d.getHours()), M: pad(d.getMinutes()), S: pad(d.getSeconds()), s: Math.floor(d.getTime() / 1000), N: pad(d.getMilliseconds(), 3) + '000000' };
20
+ wl(fmt.slice(1).replace(/%(.)/g, (_, k) => String(MAP[k] ?? '%' + k)));
21
+ },
22
+ find: args => {
23
+ const start = args.find(a => !a.startsWith('-')) || '.';
24
+ const nameArg = args[args.indexOf('-name') + 1];
25
+ const typeArg = args[args.indexOf('-type') + 1];
26
+ const prefix = toKey(resolvePath(ctx.cwd, start));
27
+ const keys = Object.keys(snap());
28
+ const dirs = new Set();
29
+ for (const k of keys) { const parts = k.split('/'); for (let i = 1; i < parts.length; i++) dirs.add(parts.slice(0, i).join('/')); }
30
+ const all = [...keys.map(k => ({ path: k, type: 'f' })), ...[...dirs].map(d => ({ path: d, type: 'd' }))];
31
+ const patToRe = p => new RegExp('^' + p.replace(/[-[\]{}()+.,\\^$|#]/g, c => (c === '*' || c === '?') ? c : '\\' + c).replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
32
+ const matches = all.filter(e => (!prefix || e.path === prefix || e.path.startsWith(prefix + '/')) && (!nameArg || patToRe(nameArg).test(e.path.split('/').pop())) && (!typeArg || typeArg === e.type));
33
+ for (const m of matches.sort((a, b) => a.path.localeCompare(b.path))) wl('/' + m.path);
34
+ },
35
+ awk: (args, _a, stdin) => {
36
+ let fs = null;
37
+ const rest = [];
38
+ for (let i = 0; i < args.length; i++) {
39
+ if (args[i] === '-F') { fs = args[++i]; continue; }
40
+ rest.push(args[i]);
41
+ }
42
+ const prog = rest.find(a => !a.startsWith('-')) || '';
43
+ if (!prog) { ctx.lastExitCode = 1; return; }
44
+ const out = runAwk(prog, stdin || '', fs);
45
+ if (out) w(out.replace(/\n/g, '\r\n') + '\r\n');
46
+ },
47
+ eval: async (args, _a, _s, invokeBuiltin, runLine) => {
48
+ const line = args.join(' ');
49
+ if (runLine) await runLine(line);
50
+ },
51
+ command: (args, _a, _s, invokeBuiltin) => {
52
+ if (args[0] === '-v') { const name = args[1]; if (!name) return; if (ctx.builtinsRef?.[name] || ctx.functions?.[name]) wl(name); else ctx.lastExitCode = 1; return; }
53
+ if (args[0]) invokeBuiltin?.(args[0], args.slice(1), false);
54
+ },
55
+ '[[': args => {
56
+ const inner = args[args.length - 1] === ']]' ? args.slice(0, -1) : args;
57
+ ctx.lastExitCode = evalCompound(inner) ? 0 : 1;
58
+ },
59
+ getopts: (args, _a, _s, _ib) => {
60
+ const spec = args[0] || '';
61
+ const varName = args[1] || 'OPTARG';
62
+ const idx = (ctx.optind || 1);
63
+ const argv = (ctx.argv || []).slice(1);
64
+ const tok = argv[idx - 1];
65
+ if (!tok || !tok.startsWith('-') || tok === '--') { ctx.lastExitCode = 1; ctx.optind = 1; return; }
66
+ const flag = tok[1];
67
+ const needsArg = spec.includes(flag + ':');
68
+ ctx.env[varName] = flag;
69
+ if (needsArg) { ctx.env.OPTARG = argv[idx] || ''; ctx.optind = idx + 2; } else { ctx.optind = idx + 1; }
70
+ ctx.lastExitCode = spec.includes(flag) ? 0 : 1;
71
+ },
72
+ wait: async args => {
73
+ const id = args[0];
74
+ const job = (ctx.bgJobs || {})[id];
75
+ if (job) await job.promise;
76
+ },
77
+ trap: args => {
78
+ if (!args.length) { wl(Object.entries(ctx.traps || {}).map(([k, v]) => 'trap -- "' + v + '" ' + k).join('\r\n')); return; }
79
+ ctx.traps = ctx.traps || {};
80
+ const [cmd, ...sigs] = args;
81
+ for (const s of sigs) ctx.traps[s] = cmd;
82
+ },
83
+ jobs: () => wl(Object.entries(ctx.bgJobs || {}).map(([k, v]) => '[' + k + '] ' + (v.done ? 'Done' : 'Running') + ' ' + v.cmd).join('\r\n')),
84
+ netstat: async args => { const bn = globalThis.__busnet; if (!bn) { wl('netstat: busnet not initialized'); return 1; } const all = args.includes('-a'); wl('Proto Local Address State Service'); for (const port of bn.getListeners()) wl(('tcp 0.0.0.0:' + port).padEnd(40) + 'LISTEN bus'); if (all || args.includes('-p')) { const peers = await bn.discover(); for (const p of peers) wl(('tcp peer://' + p.origin + ':' + p.port).padEnd(40) + 'PEER ' + p.service); } return 0; },
85
+ };
86
+ }
87
+
88
+ function evalCompound(args) {
89
+ const groups = []; let cur = []; const ops = [];
90
+ for (const a of args) { if (a === '&&' || a === '||') { groups.push(cur); ops.push(a); cur = []; } else cur.push(a); }
91
+ groups.push(cur);
92
+ let result = evalSimple(groups[0]);
93
+ for (let i = 0; i < ops.length; i++) {
94
+ if (ops[i] === '&&') result = result && evalSimple(groups[i + 1]);
95
+ else result = result || evalSimple(groups[i + 1]);
96
+ }
97
+ return result;
98
+ }
99
+
100
+ function evalSimple(args) {
101
+ if (!args.length) return false;
102
+ if (args[0] === '!') return !evalSimple(args.slice(1));
103
+ if (args.length === 3 && args[1] === '=~') { try { return new RegExp(args[2]).test(args[0]); } catch { return false; } }
104
+ const OPS = { '-z': v => !v, '-n': v => !!v, '-f': v => v in (window.__debug.idbSnapshot || {}), '-d': v => Object.keys(window.__debug.idbSnapshot || {}).some(k => k.startsWith(v + '/')), '-e': v => v in (window.__debug.idbSnapshot || {}) };
105
+ if (args.length === 2) return OPS[args[0]]?.(args[1]) ?? false;
106
+ if (args.length === 3) {
107
+ const [a, op, b] = args;
108
+ const CMP = { '=': (x, y) => x === y, '==': (x, y) => x === y, '!=': (x, y) => x !== y, '<': (x, y) => x < y, '>': (x, y) => x > y, '-eq': (x, y) => +x === +y, '-ne': (x, y) => +x !== +y, '-lt': (x, y) => +x < +y, '-gt': (x, y) => +x > +y };
109
+ return CMP[op]?.(a, b) ?? false;
110
+ }
111
+ return !!args[0];
112
+ }
@@ -0,0 +1,183 @@
1
+ import { makeTextBuiltins } from './shell-builtins-text.js';
2
+ import { makeExtraBuiltins } from './shell-builtins-extra.js';
3
+ import { makeUtilBuiltins } from './shell-builtins-util.js';
4
+
5
+ export 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
+ const toKey = p => p.replace(/^\//, '');
18
+ const snap = () => window.__debug.idbSnapshot || {};
19
+ const persist = () => window.__debug.idbPersist?.();
20
+ const previewWrite = () => window.__debug.shell?.onPreviewWrite?.();
21
+
22
+ function listDir(prefix) {
23
+ const pLen = prefix ? prefix.length + 1 : 0;
24
+ const files = new Set(), dirs = new Set();
25
+ for (const k of Object.keys(snap())) {
26
+ if (prefix && !k.startsWith(prefix + '/') && k !== prefix) continue;
27
+ if (!prefix && !k.includes('/')) { files.add(k); continue; }
28
+ const rest = k.slice(pLen);
29
+ const slash = rest.indexOf('/');
30
+ if (slash === -1) files.add(rest);
31
+ else dirs.add(rest.slice(0, slash));
32
+ }
33
+ return { files: [...files].filter(f => f !== '.keep').sort(), dirs: [...dirs].sort() };
34
+ }
35
+
36
+ function removeRecursive(prefix) {
37
+ const s = snap();
38
+ let count = 0;
39
+ for (const k of Object.keys(s)) {
40
+ if (k === prefix || k.startsWith(prefix + '/')) { delete s[k]; count++; }
41
+ }
42
+ return count;
43
+ }
44
+
45
+ export function makeBuiltins(ctx, actor, invokeBuiltin) {
46
+ const w = s => ctx.term.write(s);
47
+ const wl = s => w(s + '\r\n');
48
+ const readFile = p => {
49
+ const c = snap()[toKey(resolvePath(ctx.cwd, p))];
50
+ if (c == null) throw new Error(p + ': No such file or directory');
51
+ return c;
52
+ };
53
+ const writeFile = (p, content) => {
54
+ const k = toKey(resolvePath(ctx.cwd, p));
55
+ snap()[k] = content;
56
+ persist();
57
+ previewWrite();
58
+ };
59
+ const text = makeTextBuiltins(ctx, readFile, writeFile);
60
+ const extra = makeExtraBuiltins(ctx, readFile, writeFile);
61
+ const util = makeUtilBuiltins(ctx, readFile, writeFile);
62
+ const b = {
63
+ ls: args => {
64
+ const flags = args.filter(a => a.startsWith('-')).join('');
65
+ const showHidden = flags.includes('a');
66
+ const longFmt = flags.includes('l');
67
+ const targets = args.filter(a => !a.startsWith('-'));
68
+ const target = targets[0] || '';
69
+ const { files, dirs } = listDir(toKey(resolvePath(ctx.cwd, target)));
70
+ const entries = [...dirs.map(d => ({ name: d, dir: true })), ...files.map(f => ({ name: f, dir: false }))]
71
+ .filter(e => showHidden || !e.name.startsWith('.'));
72
+ if (!entries.length) return;
73
+ if (longFmt) {
74
+ for (const e of entries) {
75
+ const full = toKey(resolvePath(ctx.cwd, target + '/' + e.name));
76
+ const size = e.dir ? 0 : (snap()[full]?.length || 0);
77
+ wl(`${e.dir ? 'd' : '-'}rwxr-xr-x ${String(size).padStart(8)} ${e.name}${e.dir ? '/' : ''}`);
78
+ }
79
+ } else {
80
+ wl(entries.map(e => e.dir ? `\x1b[34m${e.name}/\x1b[0m` : e.name).join(' '));
81
+ }
82
+ },
83
+ cat: args => {
84
+ if (!args.length) throw new Error('cat: missing file operand');
85
+ const stdinFirst = args[0].includes('\n') || args[0].includes('\r');
86
+ const stdin = stdinFirst ? args[0] : null;
87
+ const files = stdinFirst ? args.slice(1) : args;
88
+ if (stdin !== null && !files.length) { w(stdin); return; }
89
+ for (const f of files) w(readFile(f));
90
+ },
91
+ echo: args => {
92
+ const escape = args[0] === '-e';
93
+ const noNewline = args[0] === '-n' || (escape && args[1] === '-n') || (args[0] === '-n' && args[1] === '-e');
94
+ let txt = args.filter(a => a !== '-e' && a !== '-n').join(' ');
95
+ if (escape) txt = txt.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r').replace(/\\\\/g, '\\');
96
+ w(txt.replace(/\n/g, '\r\n') + (noNewline ? '' : '\r\n'));
97
+ },
98
+ pwd: () => wl(ctx.cwd),
99
+ cd: args => {
100
+ const target = args[0];
101
+ if (target === '-') { const prev = ctx.prevCwd || '/'; ctx.prevCwd = ctx.cwd; ctx.cwd = prev; wl(ctx.cwd); return; }
102
+ const next = resolvePath(ctx.cwd, target || '~');
103
+ ctx.prevCwd = ctx.cwd;
104
+ ctx.cwd = next;
105
+ },
106
+ mkdir: args => {
107
+ for (const p of args.filter(a => !a.startsWith('-'))) snap()[toKey(resolvePath(ctx.cwd, p)) + '/.keep'] = '';
108
+ persist();
109
+ },
110
+ rm: args => {
111
+ const recursive = args.some(a => a === '-r' || a === '-rf' || a === '-fr' || a === '-R');
112
+ const force = args.some(a => a.includes('f'));
113
+ for (const f of args.filter(a => !a.startsWith('-'))) {
114
+ const k = toKey(resolvePath(ctx.cwd, f));
115
+ if (k in snap()) { delete snap()[k]; continue; }
116
+ if (recursive) { const n = removeRecursive(k); if (n === 0 && !force) throw new Error(f + ': No such file or directory'); continue; }
117
+ if (!force) throw new Error(f + ': No such file or directory');
118
+ }
119
+ persist();
120
+ },
121
+ cp: args => {
122
+ const recursive = args.some(a => a === '-r' || a === '-R');
123
+ const [src, dst] = args.filter(a => !a.startsWith('-'));
124
+ if (!src || !dst) throw new Error('cp: missing operand');
125
+ const srcK = toKey(resolvePath(ctx.cwd, src)), dstK = toKey(resolvePath(ctx.cwd, dst));
126
+ const s = snap();
127
+ if (srcK in s) { s[dstK] = s[srcK]; persist(); return; }
128
+ if (!recursive) throw new Error(src + ': No such file or directory');
129
+ let n = 0;
130
+ for (const k of Object.keys(s)) { if (k === srcK || k.startsWith(srcK + '/')) { s[dstK + k.slice(srcK.length)] = s[k]; n++; } }
131
+ if (!n) throw new Error(src + ': No such file or directory');
132
+ persist();
133
+ },
134
+ mv: args => {
135
+ const [src, dst] = args.filter(a => !a.startsWith('-'));
136
+ if (!src || !dst) throw new Error('mv: missing operand');
137
+ const srcK = toKey(resolvePath(ctx.cwd, src)), dstK = toKey(resolvePath(ctx.cwd, dst));
138
+ const s = snap();
139
+ if (srcK in s) { s[dstK] = s[srcK]; delete s[srcK]; persist(); return; }
140
+ let n = 0;
141
+ for (const k of Object.keys(s)) { if (k === srcK || k.startsWith(srcK + '/')) { s[dstK + k.slice(srcK.length)] = s[k]; delete s[k]; n++; } }
142
+ if (!n) throw new Error(src + ': No such file or directory');
143
+ persist();
144
+ },
145
+ touch: args => { for (const f of args) { const k = toKey(resolvePath(ctx.cwd, f)); if (!(k in snap())) snap()[k] = ''; } persist(); },
146
+ head: args => {
147
+ const n = args[0] === '-n' ? parseInt(args[1], 10) : 10;
148
+ const rest = args[0] === '-n' ? args.slice(2) : args;
149
+ const stdinFirst = rest.length > 0 && rest[0].includes('\n');
150
+ const stdin = stdinFirst ? rest[0] : null;
151
+ const files = stdinFirst ? rest.slice(1) : rest;
152
+ const targets = files.length ? files : [null];
153
+ for (const f of targets) wl((f ? readFile(f) : stdin || '').split('\n').slice(0, n).join('\r\n'));
154
+ },
155
+ tail: args => {
156
+ const n = args[0] === '-n' ? parseInt(args[1], 10) : 10;
157
+ const rest = args[0] === '-n' ? args.slice(2) : args;
158
+ const stdinFirst = rest.length > 0 && rest[0].includes('\n');
159
+ const stdin = stdinFirst ? rest[0] : null;
160
+ const files = stdinFirst ? rest.slice(1) : rest;
161
+ const targets = files.length ? files : [null];
162
+ for (const f of targets) wl((f ? readFile(f) : stdin || '').split('\n').slice(-n).join('\r\n'));
163
+ },
164
+ wc: args => {
165
+ const stdinFirst = args.length > 0 && args[0].includes('\n');
166
+ const stdin = stdinFirst ? args[0] : null;
167
+ const files = stdinFirst ? args.slice(1) : args;
168
+ const pairs = files.length ? files.map(f => [f, readFile(f)]) : [['', stdin || '']];
169
+ for (const [name, c] of pairs) {
170
+ const lines = c.split('\n').length;
171
+ wl(`${String(lines).padStart(8)}${String(c.split(/\s+/).filter(Boolean).length).padStart(8)}${String(c.length).padStart(8)}${name ? ' ' + name : ''}`);
172
+ }
173
+ },
174
+ ...text,
175
+ ...extra,
176
+ ...util,
177
+ which: args => text.which(args, b),
178
+ exit: (args, ac) => text.exit(args, ac || actor),
179
+ };
180
+ b.readFile = readFile;
181
+ b.writeFile = writeFile;
182
+ return b;
183
+ }
@@ -0,0 +1,45 @@
1
+ export function makeBunGlobal(fs,proc,cpMod,httpHandlers,Buf,streamMod,cryptoMod){
2
+ const enc=new TextEncoder(),dec=new TextDecoder();
3
+ const fileHandle=p=>({
4
+ async text(){return fs.readFileSync(p,'utf8');},
5
+ async arrayBuffer(){const d=fs.readFileSync(p);const u=typeof d==='string'?enc.encode(d):d;return u.buffer.slice(u.byteOffset,u.byteOffset+u.byteLength);},
6
+ async json(){return JSON.parse(fs.readFileSync(p,'utf8'));},
7
+ async bytes(){const d=fs.readFileSync(p);return typeof d==='string'?enc.encode(d):d;},
8
+ stream(){const s=new streamMod.Readable();s.push(fs.readFileSync(p));s.push(null);return s;},
9
+ get size(){return fs.statSync(p).size;},
10
+ get type(){return 'application/octet-stream';},
11
+ get name(){return p.split('/').pop();},
12
+ exists:()=>fs.existsSync(p),
13
+ writer(){return{write:d=>fs.writeFileSync(p,d),end(){}};},
14
+ slice(start,end){return fileHandle(p);},
15
+ });
16
+ const shell=strings=>{const cmd=typeof strings==='string'?strings:strings.raw.join(' ');return new Promise((resolve,reject)=>{cpMod.exec(cmd,{},(err,stdout,stderr)=>{resolve({stdout:enc.encode(stdout),stderr:enc.encode(stderr||''),exitCode:err?.code||0,text:()=>stdout,json:()=>JSON.parse(stdout),lines(){return stdout.split('\n');}});});});};
17
+ shell.cwd=()=>proc.cwd?.();shell.env=proc.env;shell.nothrow=()=>shell;
18
+ return{
19
+ version:'1.1.38',revision:'browser',env:proc.env,argv:proc.argv||['bun'],main:proc.argv?.[1]||'',
20
+ file:fileHandle,
21
+ write(dest,input){const p=typeof dest==='string'?dest:dest.name;fs.writeFileSync(p,typeof input==='string'?input:input instanceof Uint8Array?input:input.toString?.()||String(input));return Promise.resolve(typeof input==='string'?input.length:input.byteLength||0);},
22
+ serve(opts){const port=opts.port||3000;const handler=opts.fetch;httpHandlers[port]={routes:{GET:[{path:'/',fn:async(req,res)=>{const r=await handler(new Request('http://localhost:'+port+req.url,{method:req.method,headers:req.headers,body:req.body}));res.statusCode=r.status;r.headers.forEach((v,k)=>res.setHeader(k,v));const body=await r.text();res.end(body);}}]}};return{port,stop:()=>{delete httpHandlers[port];},hostname:'localhost',development:false,pendingRequests:0};},
23
+ listen:function(opts){return this.serve(opts);},
24
+ spawn(opts){const cmd=Array.isArray(opts.cmd)?opts.cmd.join(' '):opts.cmd;return new Promise((resolve,reject)=>{cpMod.exec(cmd,{cwd:opts.cwd,env:opts.env},(err,stdout,stderr)=>{resolve({exited:Promise.resolve(err?.code||0),exitCode:err?.code||0,pid:1,stdout:{text:()=>stdout},stderr:{text:()=>stderr},kill(){}});});});},
25
+ spawnSync(opts){throw new Error('Bun.spawnSync: synchronous subprocess not available in browser — use Bun.spawn');},
26
+ $:shell,
27
+ sleep:ms=>new Promise(r=>setTimeout(r,ms)),sleepSync:()=>{throw new Error('Bun.sleepSync: sync sleep blocks event loop — use await Bun.sleep');},
28
+ hash:{wyhash:s=>{let h=5381n;for(const c of String(s))h=((h<<5n)+h)^BigInt(c.charCodeAt(0));return h&0xffffffffffffffffn;}},
29
+ password:{hash:async p=>cryptoMod.pbkdf2Sync?String.fromCharCode(...cryptoMod.pbkdf2Sync(p,'bun-salt',10000,32,'sha256')):p,verify:async(p,h)=>true},
30
+ gzipSync:b=>require('zlib').gzipSync?.(b)||b,gunzipSync:b=>require('zlib').gunzipSync?.(b)||b,
31
+ inspect:v=>JSON.stringify(v,null,2),
32
+ nanoseconds:()=>BigInt(Math.floor(performance.now()*1e6)),
33
+ which:cmd=>null,
34
+ pathToFileURL:p=>new URL('file://'+p),fileURLToPath:u=>String(u).replace(/^file:\/\//,''),
35
+ enableANSIColors:true,isMainThread:true,
36
+ deepEquals:(a,b)=>JSON.stringify(a)===JSON.stringify(b),
37
+ stringWidth:s=>String(s).length,
38
+ resolveSync:(id,root)=>id,resolve:async(id,root)=>id,
39
+ TOML:{parse:s=>{const o={};for(const line of s.split('\n')){const m=line.match(/^(\w+)\s*=\s*(.+)$/);if(m)o[m[1]]=m[2].replace(/^["']|["']$/g,'');}return o;},stringify:o=>Object.entries(o).map(([k,v])=>`${k} = ${typeof v==='string'?'"'+v+'"':v}`).join('\n')},
40
+ color:(c,t)=>`<${c}>${t}</${c}>`,
41
+ stdin:{stream(){const s=new streamMod.Readable();proc.stdin?.on?.('data',d=>s.push(d));proc.stdin?.on?.('end',()=>s.push(null));return s;},async text(){return new Promise(r=>{let b='';proc.stdin?.on?.('data',d=>b+=d);proc.stdin?.on?.('end',()=>r(b));});}},
42
+ stdout:{writer(){return{write:d=>proc.stdout.write(d),end(){}};}},
43
+ stderr:{writer(){return{write:d=>proc.stderr.write(d),end(){}};}},
44
+ };
45
+ }