thebird 1.2.79 → 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 +217 -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 +24 -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 +175 -169
  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 +161 -152
  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,122 @@
1
+ export function tokenize(line) {
2
+ const tokens = [];
3
+ let cur = '';
4
+ let quote = null;
5
+ let escape = false;
6
+ for (let i = 0; i < line.length; i++) {
7
+ const c = line[i];
8
+ if (escape) {
9
+ if (quote === '"' && !'"\\`$'.includes(c)) cur += '\\';
10
+ cur += c; escape = false; continue;
11
+ }
12
+ if (c === '\\' && quote !== "'") { escape = true; continue; }
13
+ if (quote) {
14
+ if (c === quote) { quote = null; continue; }
15
+ cur += c;
16
+ continue;
17
+ }
18
+ if (c === '"' || c === "'") { quote = c; continue; }
19
+ if (/\s/.test(c)) {
20
+ if (cur) { tokens.push(cur); cur = ''; }
21
+ continue;
22
+ }
23
+ cur += c;
24
+ }
25
+ if (cur) tokens.push(cur);
26
+ return tokens;
27
+ }
28
+
29
+ export function expand(token, env, lastExitCode, argv) {
30
+ return token.replace(/\$\(([^)]+)\)|\$\{?(\?|#|@|[0-9]|[A-Za-z_][A-Za-z0-9_]*)\}?/g, (match, sub, name) => {
31
+ if (sub) return match;
32
+ if (name === '?') return String(lastExitCode ?? 0);
33
+ if (name === '#') return String((argv || []).length);
34
+ if (name === '@') return (argv || []).join(' ');
35
+ if (name === '0') return (argv || [])[0] || '';
36
+ if (/^[1-9]$/.test(name)) return (argv || [])[parseInt(name)] || '';
37
+ return env[name] ?? '';
38
+ });
39
+ }
40
+
41
+ export function expandCmdSub(token, env, lastExitCode, runCapture, argv) {
42
+ if (!token.includes('$(')) return expand(token, env, lastExitCode, argv);
43
+ return token.replace(/\$\(([^)]+)\)/g, (_, cmd) => runCapture ? runCapture(cmd) : '');
44
+ }
45
+
46
+ export function parsePipeline(line) {
47
+ const chunks = splitTopLevel(line, ['&&', '||', ';']);
48
+ return chunks;
49
+ }
50
+
51
+ export function splitTopLevel(line, seps) {
52
+ const cmds = [];
53
+ const separators = [];
54
+ let cur = '';
55
+ let quote = null;
56
+ let escape = false;
57
+ for (let i = 0; i < line.length; i++) {
58
+ const c = line[i];
59
+ if (escape) { cur += c; escape = false; continue; }
60
+ if (c === '\\' && quote !== "'") { cur += c; escape = true; continue; }
61
+ if (quote) {
62
+ if (c === quote) quote = null;
63
+ cur += c;
64
+ continue;
65
+ }
66
+ if (c === '"' || c === "'") { quote = c; cur += c; continue; }
67
+ let matched = null;
68
+ for (const sep of seps) if (line.startsWith(sep, i)) { matched = sep; break; }
69
+ if (matched) {
70
+ cmds.push(cur.trim());
71
+ separators.push(matched);
72
+ cur = '';
73
+ i += matched.length - 1;
74
+ continue;
75
+ }
76
+ cur += c;
77
+ }
78
+ if (cur.trim()) cmds.push(cur.trim());
79
+ return cmds.map((cmd, i) => ({ cmd, sep: separators[i - 1] || null }));
80
+ }
81
+
82
+ export function parseRedirects(tokens) {
83
+ const out = { args: [], stdout: null, stdoutAppend: false, stdin: null };
84
+ for (let i = 0; i < tokens.length; i++) {
85
+ const t = tokens[i];
86
+ if (t === '>' || t === '>>') { out.stdout = tokens[++i]; out.stdoutAppend = t === '>>'; continue; }
87
+ if (t === '<') { out.stdin = tokens[++i]; continue; }
88
+ out.args.push(t);
89
+ }
90
+ return out;
91
+ }
92
+
93
+ export function parsePipes(line) {
94
+ return splitTopLevel(line, ['|']).map(p => p.cmd);
95
+ }
96
+
97
+ export function globToRe(pattern) {
98
+ let re = '';
99
+ let i = 0;
100
+ while (i < pattern.length) {
101
+ const c = pattern[i];
102
+ if (c === '[') {
103
+ const close = pattern.indexOf(']', i + 1);
104
+ if (close < 0) { re += '\\['; i++; continue; }
105
+ let cls = pattern.slice(i + 1, close);
106
+ if (cls[0] === '!') cls = '^' + cls.slice(1);
107
+ re += '[' + cls + ']';
108
+ i = close + 1; continue;
109
+ }
110
+ if (c === '*') { re += pattern[i + 1] === '*' ? ((i += 2), '.*') : ((i++), '[^/]*'); continue; }
111
+ if (c === '?') { re += '[^/]'; i++; continue; }
112
+ if ('-{}()+.,\\^$|#'.includes(c)) { re += '\\' + c; i++; continue; }
113
+ re += c; i++;
114
+ }
115
+ return new RegExp('^' + re + '$');
116
+ }
117
+
118
+ export function parseCommand(line, env) {
119
+ const raw = tokenize(line);
120
+ const expanded = raw.map(t => expand(t, env));
121
+ return parseRedirects(expanded);
122
+ }
@@ -0,0 +1,62 @@
1
+ import { detectPm } from './shell-pm.js';
2
+
3
+ const hashOf=(name,ver)=>{let h=0;for(const c of name+'@'+ver)h=((h<<5)-h+c.charCodeAt(0))|0;return(h>>>0).toString(36);};
4
+
5
+ export function makePnpmLayout(fs,snap,persist,ctx){
6
+ const storeBase=()=>{const d=ctx.cwd.replace(/^\//,'').replace(/\/$/,'');return(d?d+'/':'')+'node_modules/.pnpm';};
7
+ return{
8
+ install(name,ver){
9
+ if(!fs.symlinkSync)throw new Error('pnpm layout requires symlink support');
10
+ const base=ctx.cwd.replace(/^\//,'').replace(/\/$/,'');
11
+ const store=storeBase()+'/'+name+'@'+ver+'/node_modules/'+name;
12
+ const linkPath='/'+(base?base+'/':'')+'node_modules/'+name;
13
+ const storePath='/'+store;
14
+ if(!fs.existsSync('/'+store+'/package.json')){
15
+ fs.mkdirSync('/'+store,{recursive:true});
16
+ snap()['/'+store+'/package.json'.replace(/^\//,'')]=JSON.stringify({name,version:ver,main:'index.js'});
17
+ snap()['/'+store+'/index.js'.replace(/^\//,'')]=`import('https://esm.sh/${name}@${ver}').then(m=>module.exports=m.default||m);`;
18
+ }
19
+ if(!fs.existsSync(linkPath))fs.symlinkSync(storePath,linkPath);
20
+ persist();
21
+ },
22
+ resolve(name){const base=ctx.cwd.replace(/^\//,'').replace(/\/$/,'');return'/'+(base?base+'/':'')+'node_modules/'+name;}
23
+ };
24
+ }
25
+
26
+ export function parseWorkspaces(snap,cwd=''){
27
+ const base=cwd.replace(/^\//,'').replace(/\/$/,'');
28
+ const pj=snap[(base?base+'/':'')+'package.json'];
29
+ const wsYaml=snap[(base?base+'/':'')+'pnpm-workspace.yaml'];
30
+ let patterns=[];
31
+ if(pj){try{const p=JSON.parse(pj);if(Array.isArray(p.workspaces))patterns=p.workspaces;else if(p.workspaces?.packages)patterns=p.workspaces.packages;}catch{}}
32
+ if(wsYaml){const m=wsYaml.match(/packages:\s*([\s\S]+?)(?=\n\w|$)/);if(m){for(const line of m[1].split('\n')){const x=line.match(/^\s*-\s*['"]?([^'"\n]+)['"]?/);if(x)patterns.push(x[1]);}}}
33
+ const members=[];
34
+ for(const pat of patterns){
35
+ const re=new RegExp('^'+(base?base+'/':'')+pat.replace(/\*\*/g,'.+').replace(/\*/g,'[^/]+')+'/package.json$');
36
+ for(const k of Object.keys(snap))if(re.test(k)){const p=JSON.parse(snap[k]);members.push({path:k.replace(/\/package\.json$/,''),name:p.name,version:p.version,deps:p.dependencies||{}});}
37
+ }
38
+ return members;
39
+ }
40
+
41
+ export function parseYarnLockV1(src){
42
+ const entries={};
43
+ const blocks=src.split(/\n\n/).filter(b=>b.trim()&&!b.startsWith('#'));
44
+ for(const block of blocks){
45
+ const lines=block.split('\n');const header=lines[0].replace(/:$/,'').replace(/"/g,'');
46
+ const meta={};for(const line of lines.slice(1)){const m=line.match(/^\s+(\w+)\s+"?([^"]+)"?/);if(m)meta[m[1]]=m[2];}
47
+ for(const key of header.split(',').map(s=>s.trim()))entries[key]={version:meta.version,resolved:meta.resolved,integrity:meta.integrity};
48
+ }
49
+ return entries;
50
+ }
51
+
52
+ export function writeYarnLockV1(deps){
53
+ const lines=['# THIS IS AN AUTOGENERATED FILE.',''];
54
+ for(const[name,ver] of Object.entries(deps)){
55
+ lines.push(`"${name}@${ver}":`,` version "${ver.replace(/^[\^~]/,'')}"`,` resolved "https://registry.npmjs.org/${name}/-/${name}-${ver.replace(/^[\^~]/,'')}.tgz"`,'');
56
+ }
57
+ return lines.join('\n');
58
+ }
59
+
60
+ export function makeDlx(term,fs,ctx,runCmd){
61
+ return async args=>{const pkg=args[0];if(!pkg)return 1;const spec=pkg.match(/^(@?[^@]+)(?:@(.+))?$/);const name=spec[1];term.write(`dlx: loading ${pkg} from esm.sh\r\n`);try{const mod=await import('https://esm.sh/'+pkg);const main=mod.default||mod;if(typeof main==='function')return main(args.slice(1))||0;return 0;}catch(e){term.write(`dlx: ${e.message}\r\n`);return 1;}};
62
+ }
@@ -0,0 +1,39 @@
1
+ const LOCKFILES={bun:['bun.lock','bun.lockb'],pnpm:['pnpm-lock.yaml'],yarn:['yarn.lock'],npm:['package-lock.json']};
2
+
3
+ function stripJsonc(s){return s.replace(/\/\*[\s\S]*?\*\//g,'').replace(/(^|[^:])\/\/.*$/gm,'$1');}
4
+
5
+ export function detectPm(snap,cwd=''){
6
+ const base=cwd.replace(/^\//,'').replace(/\/$/,'');
7
+ const pj=snap[(base?base+'/':'')+'package.json'];
8
+ if(pj){try{const p=JSON.parse(pj);if(p.packageManager){const m=p.packageManager.match(/^(\w+)@([\d.]+)/);if(m)return{pm:m[1],version:m[2],source:'packageManager'};}}catch{}}
9
+ for(const[pm,files] of Object.entries(LOCKFILES))for(const f of files)if(((base?base+'/':'')+f) in snap)return{pm,version:'latest',source:'lockfile:'+f};
10
+ return{pm:'npm',version:'latest',source:'default'};
11
+ }
12
+
13
+ export function parseBunfig(src){const o={};for(const line of src.split('\n')){const m=line.match(/^\s*(\w+)\s*=\s*(.+)$/);if(m)o[m[1]]=m[2].trim().replace(/^["']|["']$/g,'');}return o;}
14
+
15
+ export function parseDenoJson(src){try{return JSON.parse(stripJsonc(src));}catch{return{};}}
16
+
17
+ export function makePmDispatcher(term,fs,persist,ctx){
18
+ const snap=()=>globalThis.window?.__debug?.idbSnapshot||{};
19
+ const log=(pm,cmd,args)=>{const reg=globalThis.window?.__debug?.node;if(!reg)return;reg.pm=reg.pm||{history:[]};reg.pm.history.push({ts:Date.now(),pm,cmd,args,cwd:ctx.cwd});if(reg.pm.history.length>200)reg.pm.history.shift();reg.pm.lastPm=pm;};
20
+ const pkgPath=()=>{const d=ctx.cwd.replace(/^\//,'').replace(/\/$/,'');return(d?d+'/':'')+'package.json';};
21
+ const readPj=()=>{const p=snap()[pkgPath()];return p?JSON.parse(p):{name:'pkg',version:'0.0.0',dependencies:{}};};
22
+ const writePj=o=>{snap()[pkgPath()]=JSON.stringify(o,null,2);persist();};
23
+ const writeLock=(pm,deps)=>{const base=ctx.cwd.replace(/^\//,'').replace(/\/$/,'');const key=(base?base+'/':'')+(LOCKFILES[pm]?.[0]||'package-lock.json');snap()[key]=JSON.stringify({name:pm,version:1,dependencies:deps},null,2);persist();};
24
+ const cmds={
25
+ async add(pm,args){const pj=readPj();pj.dependencies=pj.dependencies||{};const dev=args.includes('-D')||args.includes('--save-dev')||args.includes('--dev');const pkgs=args.filter(a=>!a.startsWith('-'));for(const spec of pkgs){const m=spec.match(/^(@?[^@]+)(?:@(.+))?$/);const name=m?.[1]||spec;const ver=m?.[2]||'latest';if(dev){pj.devDependencies=pj.devDependencies||{};pj.devDependencies[name]=ver;}else pj.dependencies[name]=ver;term.write(`${pm} added ${name}@${ver}\r\n`);}writePj(pj);writeLock(pm,pj.dependencies);return 0;},
26
+ async remove(pm,args){const pj=readPj();const pkgs=args.filter(a=>!a.startsWith('-'));for(const name of pkgs){delete pj.dependencies?.[name];delete pj.devDependencies?.[name];term.write(`${pm} removed ${name}\r\n`);}writePj(pj);writeLock(pm,pj.dependencies||{});return 0;},
27
+ async install(pm,args){const pj=readPj();const deps={...(pj.dependencies||{}),...(pj.devDependencies||{})};term.write(`${pm} install — ${Object.keys(deps).length} packages resolved via esm.sh\r\n`);writeLock(pm,pj.dependencies||{});return 0;},
28
+ async run(pm,args){const script=args[0];const pj=readPj();const s=pj.scripts?.[script];if(!s){term.write(`${pm} run: no script '${script}'\r\n`);return 1;}return ctx.exec?.(s)||0;},
29
+ async task(pm,args){const script=args[0];const base=ctx.cwd.replace(/^\//,'').replace(/\/$/,'');const dj=snap()[(base?base+'/':'')+'deno.json']||snap()[(base?base+'/':'')+'deno.jsonc'];if(!dj)return 1;const cfg=parseDenoJson(dj);const s=cfg.tasks?.[script];if(!s){term.write(`deno task: no task '${script}'\r\n`);return 1;}return ctx.exec?.(s)||0;},
30
+ async ls(pm,args){const pj=readPj();const deps={...(pj.dependencies||{}),...(pj.devDependencies||{})};for(const[n,v] of Object.entries(deps))term.write(` ${n}@${v}\r\n`);return 0;},
31
+ async init(pm,args){writePj({name:'pkg',version:'0.1.0',type:'module',scripts:{test:'echo test'}});term.write(`${pm} init — package.json created\r\n`);return 0;},
32
+ };
33
+ cmds.i=cmds.install;cmds.uninstall=cmds.remove;cmds.rm=cmds.remove;
34
+ return async(pm,subcmd,args=[])=>{log(pm,subcmd,args);const fn=cmds[subcmd];if(!fn){term.write(`${pm}: unknown subcommand '${subcmd}'\r\n`);return 1;}return fn(pm,args);};
35
+ }
36
+
37
+ export function makeCorepackStub(term){
38
+ return async(args)=>{term.write(`corepack: ${args.join(' ')} — no-op in browser (all PMs built-in)\r\n`);return 0;};
39
+ }
@@ -0,0 +1,70 @@
1
+ const SYMLOOP_MAX=40;
2
+ const S_IFREG=0o100000,S_IFDIR=0o040000,S_IFLNK=0o120000,S_IFIFO=0o010000,S_IFMT=0o170000;
3
+ const isMeta=v=>v!==null&&typeof v==='object'&&!ArrayBuffer.isView(v)&&(v.__symlink||v.__fifo||('data' in v&&'mode' in v));
4
+ const unwrapData=v=>isMeta(v)?v.data:v;
5
+ const toKey=p=>p.replace(/^\//,'');
6
+ let nextIno=1;const inodes=new Map();
7
+
8
+ export function installPosixFs(fs,Buf,ctx){
9
+ const snap=()=>globalThis.window?.__debug?.idbSnapshot||{};
10
+ const persist=()=>globalThis.window?.__debug?.idbPersist?.();
11
+ const newIno=()=>++nextIno;
12
+ const ensureInode=(key,s)=>{let e=s[key];if(!isMeta(e)){s[key]={data:e==null?'':e,mode:0o100644,ino:newIno(),nlink:1,uid:0,gid:0,atime:Date.now(),mtime:Date.now(),ctime:Date.now(),birthtime:Date.now()};e=s[key];}return e;};
13
+
14
+ const resolveLink=(p,depth=0)=>{
15
+ if(depth>SYMLOOP_MAX){const e=new Error('ELOOP: '+p);e.code='ELOOP';throw e;}
16
+ const key=toKey(p);const entry=snap()[key];
17
+ if(isMeta(entry)&&entry.__symlink){const tgt=entry.__symlink.startsWith('/')?entry.__symlink:p.replace(/[^/]+$/,'')+entry.__symlink;return resolveLink(tgt,depth+1);}
18
+ return p;
19
+ };
20
+
21
+ const origRead=fs.readFileSync,origWrite=fs.writeFileSync,origStat=fs.statSync,origExists=fs.existsSync,origRm=fs.unlinkSync;
22
+ fs.readFileSync=(p,enc)=>{const real=resolveLink(p);const entry=snap()[toKey(real)];if(entry==null){const e=new Error('ENOENT: '+p);e.code='ENOENT';throw e;}const data=unwrapData(entry);return enc?(typeof data==='string'?data:new TextDecoder(enc==='utf-8'?'utf-8':enc).decode(data)):(typeof data==='string'?data:Buf.from(data));};
23
+ fs.writeFileSync=(p,data,opts)=>{const real=resolveLink(p);const s=snap();const key=toKey(real);const mode=S_IFREG|((typeof opts?.mode==='number'?opts.mode:0o666)&~(ctx.umask||0o022));const existing=s[key];if(isMeta(existing)&&!existing.__symlink){existing.data=data;existing.mtime=Date.now();existing.ctime=Date.now();}else if(isMeta(existing)){existing.data=data;}else{s[key]={data,mode,ino:newIno(),nlink:1,uid:0,gid:0,atime:Date.now(),mtime:Date.now(),ctime:Date.now(),birthtime:Date.now()};}persist();};
24
+ fs.existsSync=p=>{try{const real=resolveLink(p);const key=toKey(real);const s=snap();return key in s||Object.keys(s).some(k=>k.startsWith(key+'/'));}catch{return false;}};
25
+ fs.statSync=p=>{const real=resolveLink(p);const s=snap();const key=toKey(real);const entry=s[key];const hasDirChildren=Object.keys(s).some(k=>k.startsWith(key+'/'));if(entry==null){if(hasDirChildren)return makeStats({mode:S_IFDIR|0o755,ino:newIno(),size:0});const e=new Error('ENOENT: '+p);e.code='ENOENT';throw e;}if(isMeta(entry)){const m=entry.mode||S_IFREG|0o644;return makeStats({...entry,mode:m,size:typeof entry.data==='string'?entry.data.length:entry.data?.byteLength||0});}return makeStats({mode:S_IFREG|0o644,ino:newIno(),size:typeof entry==='string'?entry.length:entry?.byteLength||0});};
26
+ fs.lstatSync=p=>{const s=snap();const entry=s[toKey(p)];if(entry==null){if(!origExists(p)){const e=new Error('ENOENT: '+p);e.code='ENOENT';throw e;}return makeStats({mode:S_IFDIR|0o755,ino:newIno(),size:0});}if(isMeta(entry)&&entry.__symlink)return makeStats({mode:S_IFLNK|0o777,ino:newIno(),size:entry.__symlink.length});return fs.statSync(p);};
27
+ fs.readlinkSync=p=>{const entry=snap()[toKey(p)];if(!isMeta(entry)||!entry.__symlink){const e=new Error('EINVAL: '+p);e.code='EINVAL';throw e;}return entry.__symlink;};
28
+ fs.symlinkSync=(target,path)=>{snap()[toKey(path)]={__symlink:target,mode:S_IFLNK|0o777,ino:newIno()};persist();};
29
+ fs.linkSync=(src,dst)=>{const s=snap();const entry=ensureInode(toKey(resolveLink(src)),s);s[toKey(dst)]={...entry,nlink:(entry.nlink||1)+1};entry.nlink=(entry.nlink||1)+1;persist();};
30
+ fs.unlinkSync=p=>{const key=toKey(p);const s=snap();const entry=s[key];if(entry==null){const e=new Error('ENOENT: '+p);e.code='ENOENT';throw e;}delete s[key];persist();};
31
+ fs.chmodSync=(p,mode)=>{const real=resolveLink(p);const entry=ensureInode(toKey(real),snap());entry.mode=(entry.mode&S_IFMT)|(mode&0o7777);entry.ctime=Date.now();persist();};
32
+ fs.realpathSync=p=>resolveLink(p);
33
+ fs.realpathSync.native=fs.realpathSync;
34
+ fs.cpSync=(src,dst,opts={})=>{const sKey=toKey(src),dKey=toKey(dst);const s=snap();const entry=s[sKey];if(entry!=null){s[dKey]=isMeta(entry)?{...entry,ino:newIno(),nlink:1}:entry;}if(opts.recursive){for(const k of Object.keys(s))if(k.startsWith(sKey+'/')){const rel=k.slice(sKey.length);const sub=s[k];s[dKey+rel]=isMeta(sub)?{...sub,ino:newIno(),nlink:1}:sub;}}persist();};
35
+ fs.constants=fs.constants||{};Object.assign(fs.constants,{S_IFREG,S_IFDIR,S_IFLNK,S_IFIFO,S_IFMT,S_IXUSR:0o100,S_IWUSR:0o200,S_IRUSR:0o400,O_RDONLY:0,O_WRONLY:1,O_RDWR:2,O_CREAT:64,O_EXCL:128,O_TRUNC:512,O_APPEND:1024});
36
+ return fs;
37
+ }
38
+
39
+ function makeStats(o){
40
+ const mode=o.mode||0o100644;
41
+ return{
42
+ dev:1,ino:o.ino||0,mode,nlink:o.nlink||1,uid:o.uid||0,gid:o.gid||0,rdev:0,size:o.size||0,blksize:4096,blocks:Math.ceil((o.size||0)/512),
43
+ atimeMs:o.atime||Date.now(),mtimeMs:o.mtime||Date.now(),ctimeMs:o.ctime||Date.now(),birthtimeMs:o.birthtime||Date.now(),
44
+ atime:new Date(o.atime||Date.now()),mtime:new Date(o.mtime||Date.now()),ctime:new Date(o.ctime||Date.now()),birthtime:new Date(o.birthtime||Date.now()),
45
+ isFile(){return(mode&S_IFMT)===S_IFREG;},
46
+ isDirectory(){return(mode&S_IFMT)===S_IFDIR;},
47
+ isSymbolicLink(){return(mode&S_IFMT)===S_IFLNK;},
48
+ isFIFO(){return(mode&S_IFMT)===S_IFIFO;},
49
+ isBlockDevice(){return false;},isCharacterDevice(){return false;},isSocket(){return false;},
50
+ };
51
+ }
52
+
53
+ export function installFds(fs,Buf){
54
+ const fdTable=new Map();let nextFd=3;
55
+ fs.openSync=(p,flags='r',mode=0o644)=>{const f=nextFd++;const exists=fs.existsSync(p);if(flags.includes('x')&&exists){const e=new Error('EEXIST: '+p);e.code='EEXIST';throw e;}if((flags.includes('w')||flags.includes('a'))&&!exists)fs.writeFileSync(p,'');if(flags.includes('w')&&exists)fs.writeFileSync(p,'');fdTable.set(f,{path:p,flags,position:flags.includes('a')?(fs.statSync(p).size):0,mode});return f;};
56
+ fs.closeSync=fd=>{fdTable.delete(fd);};
57
+ fs.readSync=(fd,buf,offset,length,position)=>{const e=fdTable.get(fd);if(!e)throw Object.assign(new Error('EBADF'),{code:'EBADF'});const data=fs.readFileSync(e.path);const bytes=typeof data==='string'?new TextEncoder().encode(data):data;const pos=position==null?e.position:position;const n=Math.min(length,bytes.length-pos);for(let i=0;i<n;i++)buf[offset+i]=bytes[pos+i];if(position==null)e.position+=n;return n;};
58
+ fs.writeSync=(fd,buf,offset,length,position)=>{const e=fdTable.get(fd);if(!e)throw Object.assign(new Error('EBADF'),{code:'EBADF'});const existing=fs.existsSync(e.path)?fs.readFileSync(e.path):'';const existingBytes=typeof existing==='string'?new TextEncoder().encode(existing):existing;const pos=position==null?e.position:position;const slice=buf.slice(offset||0,(offset||0)+(length||buf.length));const out=new Uint8Array(Math.max(existingBytes.length,pos+slice.length));out.set(existingBytes);out.set(slice,pos);fs.writeFileSync(e.path,Buf.from(out));if(position==null)e.position=pos+slice.length;return slice.length;};
59
+ fs.fstatSync=fd=>{const e=fdTable.get(fd);return fs.statSync(e.path);};
60
+ fs.fsyncSync=()=>{};fs.ftruncateSync=(fd,len=0)=>{const e=fdTable.get(fd);const cur=fs.readFileSync(e.path);const bytes=typeof cur==='string'?new TextEncoder().encode(cur):cur;fs.writeFileSync(e.path,Buf.from(bytes.slice(0,len)));};
61
+ return fs;
62
+ }
63
+
64
+ export function installTmpAndMisc(fs,Buf,ctx){
65
+ const snap=()=>globalThis.window?.__debug?.idbSnapshot||{};
66
+ fs.mkdtempSync=prefix=>{const suffix=Math.random().toString(36).slice(2,8);const p=prefix+suffix;fs.mkdirSync(p);return p;};
67
+ fs.mkfifoSync=p=>{snap()[p.replace(/^\//,'')]={__fifo:{buf:[],readers:[],writers:[]},mode:0o010644,ino:0};};
68
+ ctx.umask=ctx.umask||0o022;
69
+ return fs;
70
+ }
@@ -0,0 +1,65 @@
1
+ const streams = new Map();
2
+ let nextStreamId = 1000;
3
+
4
+ export function registerStream(data) {
5
+ const id = nextStreamId++;
6
+ streams.set(id, { data, ts: Date.now() });
7
+ setTimeout(() => streams.delete(id), 60000);
8
+ return '/procsub/' + id;
9
+ }
10
+
11
+ export function readStream(id) {
12
+ const s = streams.get(+id);
13
+ return s ? s.data : null;
14
+ }
15
+
16
+ if (typeof navigator !== 'undefined' && navigator.serviceWorker) {
17
+ navigator.serviceWorker.addEventListener('message', ev => {
18
+ if (ev.data?.type === 'PROCSUB_READ') {
19
+ const data = readStream(ev.data.id);
20
+ ev.ports[0]?.postMessage({ data: data || '', found: data !== null });
21
+ }
22
+ });
23
+ }
24
+
25
+ export async function expandProcSub(token, captureRun, ctx) {
26
+ const out = [];
27
+ let i = 0;
28
+ while (i < token.length) {
29
+ if (token[i] === '<' && token[i + 1] === '(') {
30
+ const end = findMatch(token, i + 1);
31
+ if (end < 0) { out.push(token[i++]); continue; }
32
+ const cmd = token.slice(i + 2, end);
33
+ const data = captureRun ? captureRun(cmd) : '';
34
+ out.push(registerStream(data));
35
+ i = end + 1; continue;
36
+ }
37
+ if (token[i] === '>' && token[i + 1] === '(') {
38
+ const end = findMatch(token, i + 1);
39
+ if (end < 0) { out.push(token[i++]); continue; }
40
+ const cmd = token.slice(i + 2, end);
41
+ const path = registerStream('');
42
+ ctx.pendingWrites = ctx.pendingWrites || [];
43
+ ctx.pendingWrites.push({ path, cmd });
44
+ out.push(path);
45
+ i = end + 1; continue;
46
+ }
47
+ out.push(token[i++]);
48
+ }
49
+ return out.join('');
50
+ }
51
+
52
+ function findMatch(s, start) {
53
+ let depth = 0;
54
+ for (let i = start; i < s.length; i++) {
55
+ if (s[i] === '(') depth++;
56
+ else if (s[i] === ')') { depth--; if (depth === 0) return i; }
57
+ }
58
+ return -1;
59
+ }
60
+
61
+ export function swFetchProcSub(path) {
62
+ const m = path.match(/^\/procsub\/(\d+)$/);
63
+ if (!m) return null;
64
+ return readStream(m[1]);
65
+ }
@@ -1,9 +1,12 @@
1
- export function createReadline({ term, getCompletions, onLine, getPrompt }) {
1
+ export function createReadline({ term, getCompletions, onLine, getPrompt, isBlockOpen }) {
2
2
  let buf = '';
3
3
  let pos = 0;
4
4
  let histIdx = -1;
5
5
  let escBuf = '';
6
6
  let inEsc = false;
7
+ let heredocTag = null;
8
+ let heredocBody = '';
9
+ let heredocPrefix = '';
7
10
 
8
11
  const write = s => term.write(s);
9
12
 
@@ -71,12 +74,52 @@ export function createReadline({ term, getCompletions, onLine, getPrompt }) {
71
74
  function commit() {
72
75
  const line = buf;
73
76
  write('\r\n');
77
+ if (heredocTag !== null) {
78
+ if (line === heredocTag) {
79
+ const full = heredocPrefix + " '" + heredocBody.replace(/'/g, "'\\''") + "'";
80
+ heredocTag = null; heredocBody = ''; heredocPrefix = '';
81
+ buf = ''; pos = 0; histIdx = -1;
82
+ onLine(full);
83
+ return;
84
+ }
85
+ heredocBody += line + '\n';
86
+ buf = ''; pos = 0;
87
+ write('\x1b[32m> \x1b[0m');
88
+ return;
89
+ }
90
+ const hd = line.match(/^(.*?)<<-?\s*(['"]?)(\w+)\2\s*$/);
91
+ if (hd) {
92
+ heredocPrefix = hd[1].trim();
93
+ heredocTag = hd[3];
94
+ heredocBody = '';
95
+ buf = ''; pos = 0;
96
+ write('\x1b[32m> \x1b[0m');
97
+ return;
98
+ }
99
+ const hereStr = line.match(/^(.*?)<<<\s*(.+)$/);
100
+ if (hereStr) {
101
+ const body = hereStr[2].replace(/^["']|["']$/g, '');
102
+ const full = hereStr[1].trim() + ' "' + body.replace(/"/g, '\\"') + '"';
103
+ buf = ''; pos = 0; histIdx = -1;
104
+ const hist = getHistory();
105
+ if (line.trim()) hist.unshift(line);
106
+ onLine(full);
107
+ return;
108
+ }
109
+ if (line.endsWith('\\')) {
110
+ buf = line.slice(0, -1) + '\n';
111
+ pos = buf.length;
112
+ write('\x1b[32m> \x1b[0m');
113
+ return;
114
+ }
74
115
  const hist = getHistory();
75
- if (line.trim()) hist.unshift(line);
116
+ const expanded = expandBang(line, hist);
117
+ if (expanded !== line) { write('\x1b[33m' + expanded + '\x1b[0m\r\n'); }
118
+ if (expanded.trim()) hist.unshift(expanded);
76
119
  buf = '';
77
120
  pos = 0;
78
121
  histIdx = -1;
79
- onLine(line);
122
+ onLine(expanded);
80
123
  }
81
124
 
82
125
  function getHistory() { return window.__debug?.shell?.history || []; }
@@ -126,5 +169,17 @@ export function createReadline({ term, getCompletions, onLine, getPrompt }) {
126
169
  if (data >= ' ') { insert(data); return; }
127
170
  }
128
171
 
129
- return { onData, showPrompt };
172
+ function showContinuation() { write('\x1b[32m> \x1b[0m'); }
173
+
174
+ return { onData, showPrompt, showContinuation };
175
+ }
176
+
177
+ function expandBang(line, hist) {
178
+ if (!line.includes('!') || !hist.length) return line;
179
+ return line.replace(/!(!|-?\d+|[A-Za-z]\w*)/g, (m, ref) => {
180
+ if (ref === '!') return hist[0] || m;
181
+ if (/^-?\d+$/.test(ref)) { const n = +ref; return (n < 0 ? hist[-n - 1] : hist[hist.length - n]) || m; }
182
+ const found = hist.find(h => h.startsWith(ref));
183
+ return found || m;
184
+ });
130
185
  }
@@ -0,0 +1,37 @@
1
+ export function detectRuntime(){
2
+ const g=globalThis;
3
+ let runtime='browser',version='0';
4
+ if(g.Deno?.version){runtime='deno';version=g.Deno.version.deno;}
5
+ else if(g.Bun?.version){runtime='bun';version=g.Bun.version;}
6
+ else if(g.process?.versions?.bun){runtime='bun';version=g.process.versions.bun;}
7
+ else if(g.process?.versions?.node){runtime='node';version=g.process.versions.node;}
8
+ const features={
9
+ jsr:runtime==='deno'||runtime==='bun'||runtime==='browser',
10
+ npmSpecifier:runtime==='deno'||runtime==='bun'||runtime==='browser',
11
+ bunServe:typeof g.Bun?.serve==='function'||runtime==='browser',
12
+ denoPermissions:runtime==='deno',
13
+ typeStrip:runtime==='bun'||runtime==='deno'||runtime==='browser',
14
+ workspaceRoot:true,
15
+ shebangDispatch:true,
16
+ };
17
+ return{runtime,version,features};
18
+ }
19
+
20
+ export function registerRuntime(reg,rt){
21
+ reg.runtime={active:rt.runtime,version:rt.version,features:rt.features,available:['node','deno','bun','browser'],history:[]};
22
+ return reg;
23
+ }
24
+
25
+ export function logRuntimeSwitch(reg,from,to,reason){
26
+ if(!reg?.runtime)return;
27
+ reg.runtime.history.push({ts:Date.now(),from,to,reason});
28
+ if(reg.runtime.history.length>50)reg.runtime.history.shift();
29
+ reg.runtime.active=to;
30
+ }
31
+
32
+ export function switchRuntime(shebang){
33
+ if(!shebang)return'node';
34
+ if(shebang.includes('deno'))return'deno';
35
+ if(shebang.includes('bun'))return'bun';
36
+ return'node';
37
+ }
@@ -0,0 +1,83 @@
1
+ export function runSed(exprs, stdin) {
2
+ const ops = exprs.flatMap(parseSed);
3
+ const labels = {};
4
+ ops.forEach((op, i) => { if (op.cmd === ':') labels[op.label] = i; });
5
+ const lines = stdin.split('\n');
6
+ const out = [];
7
+ let pat = null, hold = '';
8
+ let nr = 0;
9
+ let i = 0;
10
+ while (i < lines.length) {
11
+ pat = lines[i]; nr = i + 1;
12
+ let pc = 0, deleted = false, lastSubOk = false;
13
+ while (pc < ops.length) {
14
+ const op = ops[pc];
15
+ if (op.cmd === ':') { pc++; continue; }
16
+ if (op.addr != null && !addrMatch(op.addr, nr, pat, lines.length)) { pc++; continue; }
17
+ if (op.cmd === 's') { const before = pat; pat = pat.replace(op.re, op.rep); lastSubOk = pat !== before; pc++; continue; }
18
+ if (op.cmd === 'd') { deleted = true; break; }
19
+ if (op.cmd === 'p') { out.push(pat); pc++; continue; }
20
+ if (op.cmd === 'P') { out.push(pat.split('\n')[0]); pc++; continue; }
21
+ if (op.cmd === 'h') { hold = pat; pc++; continue; }
22
+ if (op.cmd === 'H') { hold += '\n' + pat; pc++; continue; }
23
+ if (op.cmd === 'g') { pat = hold; pc++; continue; }
24
+ if (op.cmd === 'G') { pat += '\n' + hold; pc++; continue; }
25
+ if (op.cmd === 'x') { const t = pat; pat = hold; hold = t; pc++; continue; }
26
+ if (op.cmd === 'n') { out.push(pat); i++; if (i >= lines.length) { pat = null; break; } pat = lines[i]; nr = i + 1; pc++; continue; }
27
+ if (op.cmd === 'N') { i++; if (i >= lines.length) break; pat += '\n' + lines[i]; nr = i + 1; pc++; continue; }
28
+ if (op.cmd === 'D') { const nl = pat.indexOf('\n'); if (nl < 0) { deleted = true; break; } pat = pat.slice(nl + 1); pc = 0; continue; }
29
+ if (op.cmd === 'b') { pc = op.label ? (labels[op.label] ?? ops.length) : ops.length; continue; }
30
+ if (op.cmd === 't') { if (lastSubOk) { lastSubOk = false; pc = op.label ? (labels[op.label] ?? ops.length) : ops.length; continue; } pc++; continue; }
31
+ if (op.cmd === 'a') { out.push(pat); out.push(op.text); pat = null; break; }
32
+ if (op.cmd === 'i') { out.push(op.text); pc++; continue; }
33
+ if (op.cmd === 'c') { pat = op.text; pc++; continue; }
34
+ if (op.cmd === 'q') { if (!deleted && pat != null) out.push(pat); return out.join('\n'); }
35
+ pc++;
36
+ }
37
+ if (!deleted && pat != null) out.push(pat);
38
+ i++;
39
+ }
40
+ return out.join('\n');
41
+ }
42
+
43
+ function parseSed(expr) {
44
+ const out = [];
45
+ for (const part of splitExprs(expr)) {
46
+ const t = part.trim();
47
+ if (!t) continue;
48
+ const lbl = t.match(/^:(\w+)$/);
49
+ if (lbl) { out.push({ cmd: ':', label: lbl[1] }); continue; }
50
+ const addrM = t.match(/^(\d+|\/[^/]+\/|\$)(.+)$/);
51
+ let addr = null; let rest = t;
52
+ if (addrM && !t.startsWith('s')) { addr = addrM[1]; rest = addrM[2]; }
53
+ const sM = rest.match(/^s(.)(.+?)\1(.*?)\1([gip]*)$/);
54
+ if (sM) { out.push({ cmd: 's', addr, re: new RegExp(sM[2], sM[4].includes('g') ? 'g' : ''), rep: sM[3] }); continue; }
55
+ const br = rest.match(/^([bt])\s*(\w*)$/);
56
+ if (br) { out.push({ cmd: br[1], addr, label: br[2] || null }); continue; }
57
+ const plain = rest.match(/^([dpPhHgGxnNDq])$/);
58
+ if (plain) { out.push({ cmd: plain[1], addr }); continue; }
59
+ const textM = rest.match(/^([aic])\\?\s*(.*)$/);
60
+ if (textM) { out.push({ cmd: textM[1], addr, text: textM[2] }); continue; }
61
+ }
62
+ return out;
63
+ }
64
+
65
+ function splitExprs(s) {
66
+ const out = []; let cur = ''; let escape = false;
67
+ for (const c of s) {
68
+ if (escape) { cur += c; escape = false; continue; }
69
+ if (c === '\\') { cur += c; escape = true; continue; }
70
+ if (c === ';') { if (cur) out.push(cur); cur = ''; continue; }
71
+ cur += c;
72
+ }
73
+ if (cur) out.push(cur);
74
+ return out;
75
+ }
76
+
77
+ function addrMatch(addr, n, line, totalLines) {
78
+ if (addr === '$') return n === totalLines;
79
+ if (/^\d+$/.test(addr)) return +addr === n;
80
+ const re = addr.match(/^\/(.+)\/$/);
81
+ if (re) return new RegExp(re[1]).test(line);
82
+ return false;
83
+ }
@@ -0,0 +1,54 @@
1
+ export function createSignals(ctx) {
2
+ const pending = [];
3
+ const handlers = ctx.traps || (ctx.traps = {});
4
+ return {
5
+ raise(sig) { pending.push(sig); },
6
+ async check(run) {
7
+ while (pending.length) {
8
+ const sig = pending.shift();
9
+ if (sig === 'KILL') { const j = ctx.currentJob; if (j) j.killed = true; throw new Error('killed by SIGKILL'); }
10
+ const h = handlers[sig];
11
+ if (h) { try { await run(h); } catch (e) { ctx.term.write('\x1b[31mtrap: ' + e.message + '\x1b[0m\r\n'); } }
12
+ if (sig === 'INT' && !h && ctx.currentJob) ctx.currentJob.killed = true;
13
+ }
14
+ },
15
+ pending: () => pending.slice(),
16
+ };
17
+ }
18
+
19
+ export function makeKillBuiltin(ctx) {
20
+ return args => {
21
+ let sig = 'TERM';
22
+ const targets = [];
23
+ for (const a of args) {
24
+ if (a.startsWith('-')) sig = a.slice(1).replace(/^SIG/, '');
25
+ else targets.push(a);
26
+ }
27
+ for (const t of targets) {
28
+ const id = t.startsWith('%') ? t.slice(1) : t;
29
+ const job = ctx.bgJobs?.[id];
30
+ if (!job) { ctx.term.write('kill: ' + t + ': no such job\r\n'); ctx.lastExitCode = 1; continue; }
31
+ if (job.actor) job.actor.send({ type: 'SIGNAL', sig });
32
+ if (sig === 'KILL' || sig === '9') { job.killed = true; if (job.reject) job.reject(new Error('killed')); }
33
+ if (sig === 'STOP' || sig === 'TSTP') { if (job.actor) job.actor.send({ type: 'STOP' }); job.stopped = true; }
34
+ if (sig === 'CONT') { if (job.actor) job.actor.send({ type: 'CONT' }); job.stopped = false; }
35
+ }
36
+ };
37
+ }
38
+
39
+ export function makeTrapBuiltin(ctx) {
40
+ return args => {
41
+ const handlers = ctx.traps || (ctx.traps = {});
42
+ if (!args.length) {
43
+ for (const [sig, cmd] of Object.entries(handlers)) ctx.term.write("trap -- '" + cmd + "' " + sig + '\r\n');
44
+ return;
45
+ }
46
+ if (args[0] === '-l') { ctx.term.write('HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE ALRM TERM STOP TSTP CONT CHLD TTIN TTOU URG XCPU XFSZ VTALRM PROF WINCH IO PWR SYS\r\n'); return; }
47
+ const [cmd, ...sigs] = args;
48
+ for (const s of sigs) {
49
+ const norm = s.replace(/^SIG/, '').toUpperCase();
50
+ if (cmd === '-' || cmd === '') delete handlers[norm];
51
+ else handlers[norm] = cmd;
52
+ }
53
+ };
54
+ }