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.
- package/.github/workflows/publish.yml +9 -1
- package/CHANGELOG.md +217 -0
- package/CLAUDE.md +16 -0
- package/docs/agent-chat.js +7 -4
- package/docs/app.js +14 -11
- package/docs/defaults.json +1 -1
- package/docs/index.html +23 -6
- package/docs/kilo-http-stream.js +24 -0
- package/docs/node-builtins.js +24 -0
- package/docs/preview/index.html +32 -0
- package/docs/preview-sw-client.js +37 -6
- package/docs/preview-sw.js +55 -51
- package/docs/shell-awk.js +113 -0
- package/docs/shell-builtins-extra.js +121 -0
- package/docs/shell-builtins-text.js +109 -0
- package/docs/shell-builtins-util.js +112 -0
- package/docs/shell-builtins.js +183 -0
- package/docs/shell-bun.js +45 -0
- package/docs/shell-control.js +132 -0
- package/docs/shell-deno.js +54 -0
- package/docs/shell-exec.js +85 -0
- package/docs/shell-expand.js +164 -0
- package/docs/shell-fd.js +86 -0
- package/docs/shell-jobs.js +86 -0
- package/docs/shell-node-advanced.js +86 -0
- package/docs/shell-node-brotli.js +22 -0
- package/docs/shell-node-busnet.js +90 -0
- package/docs/shell-node-cipher.js +61 -0
- package/docs/shell-node-cluster.js +33 -0
- package/docs/shell-node-coreutils.js +36 -0
- package/docs/shell-node-crypto.js +137 -0
- package/docs/shell-node-dns.js +41 -0
- package/docs/shell-node-extras.js +148 -0
- package/docs/shell-node-firefox.js +95 -0
- package/docs/shell-node-git.js +60 -0
- package/docs/shell-node-inspector.js +39 -0
- package/docs/shell-node-io.js +131 -0
- package/docs/shell-node-ipc.js +15 -0
- package/docs/shell-node-keyobject.js +60 -0
- package/docs/shell-node-modules.js +157 -0
- package/docs/shell-node-native.js +31 -0
- package/docs/shell-node-net.js +71 -0
- package/docs/shell-node-observe.js +80 -0
- package/docs/shell-node-opfs.js +54 -0
- package/docs/shell-node-procfs.js +42 -0
- package/docs/shell-node-profiler.js +50 -0
- package/docs/shell-node-registry.js +24 -0
- package/docs/shell-node-resolve.js +147 -0
- package/docs/shell-node-runtime.js +83 -0
- package/docs/shell-node-srcmap.js +52 -0
- package/docs/shell-node-stdlib.js +103 -0
- package/docs/shell-node-streams.js +66 -0
- package/docs/shell-node-tar.js +47 -0
- package/docs/shell-node-testrunner.js +35 -0
- package/docs/shell-node-util-extras.js +66 -0
- package/docs/shell-node.js +175 -169
- package/docs/shell-npm.js +173 -0
- package/docs/shell-parser.js +122 -0
- package/docs/shell-pm-layout.js +62 -0
- package/docs/shell-pm.js +39 -0
- package/docs/shell-posix.js +70 -0
- package/docs/shell-procsub.js +65 -0
- package/docs/shell-readline.js +59 -4
- package/docs/shell-runtime.js +37 -0
- package/docs/shell-sed.js +83 -0
- package/docs/shell-signals.js +54 -0
- package/docs/shell-sw-jobs.js +76 -0
- package/docs/shell-ts.js +30 -0
- package/docs/shell.js +161 -152
- package/docs/terminal.js +9 -11
- package/docs/todo.html +211 -0
- package/package.json +1 -1
- package/server.js +43 -4
- package/start-kilo.js +17 -0
- package/test.js +199 -0
- package/.codeinsight +0 -73
- package/docs/acp-stream.js +0 -102
- package/docs/coi-serviceworker.js +0 -2
|
@@ -28,7 +28,15 @@ jobs:
|
|
|
28
28
|
|
|
29
29
|
- name: Bump patch version
|
|
30
30
|
run: |
|
|
31
|
-
|
|
31
|
+
git fetch --tags --force
|
|
32
|
+
for i in 1 2 3 4 5; do
|
|
33
|
+
NEW_VERSION=$(npm version patch --no-git-tag-version)
|
|
34
|
+
if git rev-parse "${NEW_VERSION}" >/dev/null 2>&1; then
|
|
35
|
+
echo "Tag ${NEW_VERSION} already exists, bumping again (attempt $i)"
|
|
36
|
+
continue
|
|
37
|
+
fi
|
|
38
|
+
break
|
|
39
|
+
done
|
|
32
40
|
git add package.json
|
|
33
41
|
git commit -m "chore: bump to ${NEW_VERSION}"
|
|
34
42
|
git tag "${NEW_VERSION}"
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,213 @@
|
|
|
1
|
+
|
|
2
|
+
## [unreleased] 2026-04-21 node parity pass 12 — internal listen infrastructure
|
|
3
|
+
- feat: busnet — in-browser TCP-like listen/connect via BroadcastChannel cross-tab fabric
|
|
4
|
+
- feat: net.createServer now uses busnet — apps listen on ports other in-browser apps can connect to
|
|
5
|
+
- feat: busHttp — HTTP request/response framing over busnet
|
|
6
|
+
- feat: service discovery — busnet.discover() returns [{port,service,origin}] from peer tabs
|
|
7
|
+
- feat: netstat builtin — lists local listeners + peer services
|
|
8
|
+
- feat: window.__debug.node.busnet exposes full state
|
|
9
|
+
- feat: same-tab listen+connect works instantly without BroadcastChannel round-trip
|
|
10
|
+
|
|
11
|
+
## [unreleased] 2026-04-21 node parity pass 11 — virtualization + polyfills wave 3
|
|
12
|
+
- feat: virtual /proc filesystem — /proc/self/{cmdline,environ,stat,status,maps,limits}, /proc/{cpuinfo,meminfo,uptime,loadavg,version,stat,mounts,filesystems}
|
|
13
|
+
- feat: virtual /etc — hosts, resolv.conf, passwd, group, os-release, hostname, machine-id, shells
|
|
14
|
+
- feat: isomorphic-git wrapper — git.clone/commit/push/pull/status/log/checkout against real remotes via HTTP
|
|
15
|
+
- feat: tar/tar.gz extract + list — hand-rolled POSIX tar reader + fflate gunzip, works with real npm tarballs
|
|
16
|
+
- feat: DoH DNS polyfill — dns.resolve/resolve4/resolve6/resolveMx/resolveTxt/resolveNs/lookup/reverse via Cloudflare DoH with Google fallback
|
|
17
|
+
- feat: native addon dispatch — .node files route to WASM/JS equivalents (bufferutil, utf-8-validate, bcrypt, argon2, farmhash; sharp/better_sqlite3 placeholders)
|
|
18
|
+
- feat: process.dlopen for native modules
|
|
19
|
+
- feat: coreutils builtins — uname/whoami/hostname/id/df/free/uptime/ps/nproc/arch/yes/seq/tac/rev/nl/fold/od/xxd/dirname/basename/pwd/groups/logname/tty/stty/locale
|
|
20
|
+
- feat: npm registry shim — view/search/deps/tarballUrl/fetchTarball via esm.sh + registry.npmjs.org
|
|
21
|
+
- feat: os.cpus() returns real navigator.hardwareConcurrency, os.networkInterfaces returns lo
|
|
22
|
+
- feat: process.resourceUsage real numbers from performance.memory
|
|
23
|
+
- feat: crypto.secureHeapUsed
|
|
24
|
+
- feat: os.constants.signals/errno populated
|
|
25
|
+
|
|
26
|
+
## [unreleased] 2026-04-21 node parity pass 10 — test runner + util extras + IPC
|
|
27
|
+
- feat: node:test real runner — test/describe/it execute, report pass/fail/skip with colors and timing
|
|
28
|
+
- feat: node:test mock.fn / mock.method with calls recording
|
|
29
|
+
- feat: node:test/reporters TAP reporter (ok/not ok/plan)
|
|
30
|
+
- feat: util.styleText (named styles: red/green/bold/italic/etc)
|
|
31
|
+
- feat: util.stripVTControlCharacters removes ANSI escapes
|
|
32
|
+
- feat: util.getCallSites frame extractor
|
|
33
|
+
- feat: util.MIMEType + util.MIMEParams (RFC-compliant parse/serialize)
|
|
34
|
+
- feat: console.table/group/groupEnd/time/timeEnd/timeLog/count/countReset/dir/trace/assert/clear — full console surface
|
|
35
|
+
- feat: readline.createInterface — real interactive question/answer via xterm, asyncIterator, cursorTo/clearLine
|
|
36
|
+
- feat: fork IPC via BroadcastChannel — process.send/process.on('message')
|
|
37
|
+
- feat: node:sqlite module alias (DatabaseSync) — Node 22+ API
|
|
38
|
+
|
|
39
|
+
## [unreleased] 2026-04-21 node parity pass 9 — pnpm/yarn wired, workspaces, dlx
|
|
40
|
+
- feat: shell dispatches pnpm/yarn/bun/deno/corepack/dlx commands (previously shell-pm.js existed but wasn't wired)
|
|
41
|
+
- feat: workspaces resolution — package.json 'workspaces' field + pnpm-workspace.yaml packages:- syntax
|
|
42
|
+
- feat: Yarn Classic v1 lockfile writer + parser (real format, not JSON placeholder)
|
|
43
|
+
- feat: pnpm dlx / yarn dlx / bun x / npx — unified dlx runner via esm.sh
|
|
44
|
+
- feat: runtime observability — window.__debug.node.runtime.history tracks node→deno→bun switches
|
|
45
|
+
- feat: window.__debug.node.pm — pm command history (200 entries, cwd, ts, args)
|
|
46
|
+
- feat: Deno.stdin/stdout/stderr with ReadableStream/WritableStream surfaces
|
|
47
|
+
- feat: Bun.stdin/stdout/stderr .stream()/.text()/.writer() API
|
|
48
|
+
- feat: tab completion includes pnpm/yarn/bun/deno/npx/corepack
|
|
49
|
+
- feat: pnpm layout scaffold (.pnpm/<name>@<ver>/node_modules/<name> + symlinks) via shell-pm-layout.js
|
|
50
|
+
|
|
51
|
+
## [unreleased] 2026-04-20 node parity pass 8 — Deno/Bun/pnpm/yarn + POSIX
|
|
52
|
+
- feat: runtime detection (Deno, Bun, Node, browser) with capability flags
|
|
53
|
+
- feat: Deno global namespace — readTextFile/writeTextFile/mkdir/remove/stat/serve/Command/permissions/env
|
|
54
|
+
- feat: Bun global namespace — file/write/serve/spawn/shell(\`\`)/hash/password/TOML/nanoseconds/deepEquals
|
|
55
|
+
- feat: package manager dispatcher — auto-detects bun.lock/pnpm-lock.yaml/yarn.lock/package-lock.json + packageManager field
|
|
56
|
+
- feat: pm install/add/remove/run/ls/init/task unified across npm/pnpm/yarn/bun
|
|
57
|
+
- feat: deno task, deno.json/jsonc parsing, bunfig.toml parser, workspaces enumeration hooks
|
|
58
|
+
- feat: jsr: and npm: specifier rewriting to esm.sh
|
|
59
|
+
- feat: TypeScript direct execution — .ts/.tsx strip via regex (sucrase lazy-loaded)
|
|
60
|
+
- feat: shebang dispatch — #!/usr/bin/env deno|bun|node sets matching globals
|
|
61
|
+
- feat: corepack stub (no-op)
|
|
62
|
+
- feat: POSIX symlinks via sentinel entries — symlinkSync/readlinkSync/lstatSync/realpathSync with ELOOP at 40 hops
|
|
63
|
+
- feat: hard links + inode refcounting — linkSync, stat.nlink, stat.ino
|
|
64
|
+
- feat: file mode bits — chmodSync, S_IFREG/S_IFDIR/S_IFLNK/S_IFIFO constants
|
|
65
|
+
- feat: file descriptors — openSync/closeSync/readSync/writeSync/fstatSync/ftruncate
|
|
66
|
+
- feat: process.umask/cwd/chdir
|
|
67
|
+
- feat: mkdtempSync, cpSync(recursive), fs.mkfifoSync stub
|
|
68
|
+
- feat: Stats with isFile/isDirectory/isSymbolicLink/isFIFO, atime/mtime/ctime/birthtime Dates
|
|
69
|
+
|
|
70
|
+
## [unreleased] 2026-04-20 node parity pass 7 — Firefox maximization + polyfills
|
|
71
|
+
- feat: browser detection (vendor, version, 10+ capabilities) + window.__debug.node.polyfills registry
|
|
72
|
+
- feat: OPFS-backed fs.promises when available — real persistence (readFile/writeFile/mkdir/rm/stat/list) via SyncAccessHandle in worker, IDB fallback
|
|
73
|
+
- feat: brotli polyfill via brotli-wasm (compressSync/decompressSync + Transform streams)
|
|
74
|
+
- feat: Error.prepareStackTrace source-map integration via source-map-js — original filenames/lines when process.sourceMapsEnabled
|
|
75
|
+
- feat: net.Socket/tls.connect real polyfill via WebSocket-backed TCP tunnel (window.__plugkit_tcp_relay)
|
|
76
|
+
- feat: dgram.Socket polyfill via WebSocket-wrapped datagrams (window.__plugkit_udp_relay)
|
|
77
|
+
- feat: inspector.open() real CDP endpoint via postMessage channel (Runtime.evaluate/Debugger.enable/Profiler.*)
|
|
78
|
+
- feat: v8 CPU profiler backed by PerformanceObserver (CPUProfile.startProfiling/stopProfiling)
|
|
79
|
+
- feat: v8.writeHeapSnapshot — minimal valid V8 heap snapshot JSON format, Chrome DevTools importable
|
|
80
|
+
- feat: X509Certificate sync access via crypto.preloadX509()
|
|
81
|
+
- feat: vm cross-realm structuredClone boundary — Array/Object instanceof works across iframe
|
|
82
|
+
- feat: cluster module real impl via BroadcastChannel (fork/isMaster/worker.send/'message')
|
|
83
|
+
- feat: CompressionStream native gzip/deflate preferred over fflate when available
|
|
84
|
+
- feat: WebCodecs exposed as 'codecs' module (VideoEncoder/AudioEncoder/etc)
|
|
85
|
+
- feat: web-push module (pushManager.subscribe/getSubscription)
|
|
86
|
+
- feat: process.storage.estimate/persist/persisted + process.storageBuckets
|
|
87
|
+
- feat: FileSystemObserver integration for real fs.watch events on OPFS
|
|
88
|
+
- feat: Firefox Worker module-type compat shim
|
|
89
|
+
- feat: observability panel window.__debug.node.polyfills shows backing (native|wasm|fallback) per feature
|
|
90
|
+
|
|
91
|
+
## [unreleased] 2026-04-20 node parity pass 6 — 23 PRD items shipped
|
|
92
|
+
- feat: crypto ECDSA PEM sign/verify (P-256/P-384/P-521)
|
|
93
|
+
- feat: crypto.hkdf/hkdfAsync via webcrypto deriveBits
|
|
94
|
+
- feat: crypto.createECDH (prime256v1/secp384r1/secp521r1) shared-secret derivation
|
|
95
|
+
- feat: KeyObject (createPrivateKey/createPublicKey) + X509Certificate via @peculiar/x509
|
|
96
|
+
- feat: streaming zlib (createGzip/Gunzip/Deflate/Inflate) via fflate class API
|
|
97
|
+
- feat: vm module (runInThisContext/runInNewContext/runInContext via iframe)
|
|
98
|
+
- feat: module.register() ESM loader hooks (resolve/load pipeline)
|
|
99
|
+
- feat: http2.connect fetch-backed ClientHttp2Session
|
|
100
|
+
- feat: WASI real impl via @bjorn3/browser_wasi_shim
|
|
101
|
+
- feat: diagnostics_channel real pub/sub + tracingChannel
|
|
102
|
+
- feat: trace_events real recorder with event buffer
|
|
103
|
+
- feat: worker_threads.Worker backed by real Web Worker + Blob URL
|
|
104
|
+
- feat: child_process exec/spawn via WebContainer when available
|
|
105
|
+
- feat: fs.watch real events via IDB snapshot diff polling
|
|
106
|
+
- feat: REPL command handling (.clear/.exit/.help/.load/.save/.editor) + multi-line balance detection
|
|
107
|
+
- feat: Buffer pool (Buffer.poolSize=8192) for small allocUnsafe
|
|
108
|
+
- feat: process.binding('util') selective exposure, execArgv, features
|
|
109
|
+
- feat: process.memoryUsage from performance.memory
|
|
110
|
+
- feat: http.Agent / https.Agent real fetch pool with maxSockets
|
|
111
|
+
- feat: Error.prepareStackTrace V8-hook via Object.defineProperty on Error.prototype.stack
|
|
112
|
+
- feat: window.__debug.node registry for runtime observability
|
|
113
|
+
- feat: net/tls stubs with clearer error messages
|
|
114
|
+
|
|
115
|
+
## [unreleased] 2026-04-20 node parity pass 5
|
|
116
|
+
- feat: zlib sync (fflate via esm.sh /es2022 bundle) — gzipSync/gunzipSync/deflateSync/inflateSync
|
|
117
|
+
- feat: crypto.sign/verify + createSign/createVerify with PEM key import (RSA-SHA256 via webcrypto pkcs8/spki)
|
|
118
|
+
- feat: module resolution — full conditional exports (node/import/require/default/browser), subpath patterns (*), #internal imports map, type:module ESM detection
|
|
119
|
+
- feat: util.inspect — BigInt 'n' suffix, Symbol keys, colors:true ANSI, showHidden for non-enumerable
|
|
120
|
+
- feat: Error.captureStackTrace polyfill, process.execArgv from NODE_OPTIONS, --enable-source-maps flag, expanded allowedNodeEnvironmentFlags, process.features
|
|
121
|
+
|
|
122
|
+
## [unreleased] 2026-04-20 node parity pass 3
|
|
123
|
+
- feat: crypto sha1/sha512/md5 pure-JS + hmac (RFC 2104) + pbkdf2Sync + randomBytes via Web Crypto — all byte-for-byte match with real node
|
|
124
|
+
- feat: util.inspect circular refs use node format '<ref *N> { ... [Circular *N] }' exactly
|
|
125
|
+
- feat: package.json exports field resolution + node_modules walk-up parent dirs
|
|
126
|
+
- feat: require('module') with builtinModules, createRequire, _resolveFilename, Module, wrap, wrapper
|
|
127
|
+
- feat: require throws Error with .code='MODULE_NOT_FOUND' and requireStack
|
|
128
|
+
- feat: fs.promises mirrors sync API; fs.watch FSWatcher stub
|
|
129
|
+
- feat: net/dgram throw descriptive errors on use (not silent stubs)
|
|
130
|
+
- feat: worker_threads throws descriptive; execSync throws with explanation
|
|
131
|
+
- feat: process.stdin.setRawMode no-op (inquirer compatibility)
|
|
132
|
+
- feat: globalThis.process/Buffer set during eval (real node globals)
|
|
133
|
+
- feat: __filename/__dirname injected into ESM preamble
|
|
134
|
+
- feat: REPL eval loop — input → try-expr-then-stmt → util.inspect result; .exit/.help/.clear commands; prompt '> ' during REPL
|
|
135
|
+
- new: shell-node-crypto.js (sha1/256/512/md5/hmac/pbkdf2), shell-node-resolve.js (exports/walk-up/module/fs.promises/net/dgram/worker_threads stubs)
|
|
136
|
+
|
|
137
|
+
## [unreleased] 2026-04-20 node parity pass 2
|
|
138
|
+
- feat: util.inspect matches node format (braces-with-spaces, Map(N){k=>v}, arrays, circular, <Buffer ...>)
|
|
139
|
+
- feat: console.log/info/warn/error use util.format (printf-style %s/%d/%o)
|
|
140
|
+
- feat: crypto.createHash pure-JS sha256 — matches node hex output byte-for-byte
|
|
141
|
+
- feat: Buffer.write/compare/equals/indexOf/includes/subarray/readUIntXX/Buffer.compare/allocUnsafe
|
|
142
|
+
- feat: fs.rmSync/rmdirSync/accessSync/realpathSync
|
|
143
|
+
- feat: child_process.spawn/exec route through shell runPipeline, EventEmitter-style stdout/stderr/exit
|
|
144
|
+
- feat: http.request/http.get via fetch, return IncomingMessage-style with statusCode/headers/on(data,end)
|
|
145
|
+
- feat: process.execPath/argv0/title/memoryUsage/uptime/getuid/umask/release; env defaults PATH/HOME/USER/SHELL/TERM/LANG
|
|
146
|
+
- feat: ESM detection — code with import/export wrapped in Blob URL + dynamic import
|
|
147
|
+
- feat: unhandledrejection → lastExitCode=1 + node-style stack
|
|
148
|
+
- feat: stack trace trailer "Node.js v23.10.0" on error
|
|
149
|
+
- feat: .env loading at script start
|
|
150
|
+
- feat: node: prefixed specifiers (node:fs, node:path, etc.)
|
|
151
|
+
- feat: zlib.gzip/gunzip via pako (async, auto-loaded from esm.sh)
|
|
152
|
+
- new: shell-node-stdlib.js (util/crypto/zlib), shell-node-io.js (cp/http/proc env/ESM/stack/dotenv)
|
|
153
|
+
|
|
154
|
+
## [unreleased] 2026-04-20 node/npm CLI parity
|
|
155
|
+
- feat: node reports v23.10.0 + full process.versions map (27 fields, matches real CLI)
|
|
156
|
+
- feat: npm reports 10.9.2, npm_lifecycle_event/npm_package_name/npm_package_version env injection, pre/post<script> lifecycle hooks
|
|
157
|
+
- feat: process.exit(n) throws NodeExit, propagates to ctx.lastExitCode
|
|
158
|
+
- feat: script errors set lastExitCode=1 with stack trace display
|
|
159
|
+
- feat: node reads stdin via pipe (echo x | node script.js) through proc.stdin._feed
|
|
160
|
+
- feat: require.resolve + require.cache for module introspection
|
|
161
|
+
- feat: npx builtin (npx cowsay hi)
|
|
162
|
+
- feat: node -h/--help, node -p fixed (stdout.write not console.log)
|
|
163
|
+
- refactor: extracted runNode + runNpmResult into shell-exec.js (shell.js stays <200L)
|
|
164
|
+
|
|
165
|
+
## [unreleased] 2026-04-18 browser validation
|
|
166
|
+
- fix: require('express') returned instance not factory (MODULES wrapper called createExpress twice)
|
|
167
|
+
- fix: SW registration non-blocking — shell boots immediately, SW registers in background
|
|
168
|
+
- fix: splitTopLevel sep semantics — sep is preceding operator not following, fixes && || chain evaluation
|
|
169
|
+
- fix: lastExitCode-based lastOk — false builtin sets exit code 1 without throwing
|
|
170
|
+
|
|
171
|
+
## [unreleased] 2026-04-18
|
|
172
|
+
- feat: full CLI overhaul — shell-parser.js (tokenize/expand/parsePipes/splitTopLevel/parseRedirects), shell-builtins.js (POSIX builtins: ls -la, rm -r, cp -r, grep -inH, cd -, history), shell-npm.js (install/uninstall/ls/run/init with package.json read/write), shell.js refactored to use modules; IDB_KEY bumped to thebird_fs_v4; defaults.json updated with all new files
|
|
1
173
|
## [Unreleased]
|
|
2
174
|
|
|
175
|
+
### Added
|
|
176
|
+
- `docs/defaults.json`: bundled real project files (package.json, index.js, server.js, lib/*, lib/providers/*) so browser jsh has a working thebird source tree on boot — user can immediately run `npm install && node server.js` without needing to write files first
|
|
177
|
+
- `docs/shell.js`: `npm install` with no args reads cwd `package.json` dependencies + peerDependencies, installs all of them (multi-pkg via single command)
|
|
178
|
+
- `docs/shell-node.js`: `preloadAsyncPkgs` now walks the full require graph from entry file (BFS through relative requires) so transitive external package deps get loaded before sync require runs. Previously only scanned top-level code — server.js → ./index.js → @google/genai chain failed
|
|
179
|
+
- `docs/index.html`: callExpressRoute response object supports both Node http-style (writeHead/write/end/setHeader) and express-style (send/json/status). Request object now has `url`, `method`, async iterator for empty body — matches what `http.createServer` handlers expect
|
|
180
|
+
|
|
181
|
+
### Changed
|
|
182
|
+
- `docs/terminal.js`: IDB_KEY bumped `thebird_fs_v2` → `thebird_fs_v3` to force refresh of browser fs cache (users with stale idb will re-fetch defaults.json with the real project files)
|
|
183
|
+
|
|
184
|
+
### Added (prev)
|
|
185
|
+
- `docs/shell-node.js`: `http` and `https` core builtins — `http.createServer(handler)` registers wildcard route in `window.__debug.shell.httpHandlers[port]` (same mechanism as express.listen), so `node server.js` now works for servers that use raw `require('http')`
|
|
186
|
+
- `docs/shell-node.js`: `buffer`, `child_process`, `net`, `zlib`, `assert` builtin stubs so common Node scripts don't die on trivial requires
|
|
187
|
+
- `docs/shell-node.js`: `preloadAsyncPkgs(code)` scans source for `require('pkg')` calls, resolves each via dynamic `import(esm.sh/pkg)`, populates `pkgCache`. Synchronous `require()` then reads from cache — bridges Node CJS semantics to browser ESM loading
|
|
188
|
+
- `docs/shell-node-modules.js`: new file holding `createExpress`, `createHttp`, `createSqlite`, `createConsole`, `createProcess` factories (split out to keep shell-node.js under 200 lines)
|
|
189
|
+
|
|
190
|
+
### Changed
|
|
191
|
+
- `docs/shell.js`: `npm install` supports multiple packages per invocation; writes an `await import(...)` stub to `node_modules/<pkg>/index.js` as a marker, real resolution happens via `preloadAsyncPkgs` in nodeEval
|
|
192
|
+
- `docs/shell-node.js`: external (non-relative, non-builtin) require throws clear `Cannot find module: X (run: npm install X)` instead of generic error
|
|
193
|
+
- `docs/shell-node-modules.js` createExpress: routes now store `{ path, fn }` where `fn` runs full middleware chain via `runFns`, matching `index.html` callExpressRoute's `match.fn(req, res)` expectation (was previously `{ path, fns }` which broke route invocation)
|
|
194
|
+
|
|
195
|
+
### Fixed
|
|
196
|
+
- `docs/shell.js`: httpHandlers now returned on shell object instead of assigned to window.__debug.shell separately — terminal.js overwrote the debug object (which had httpHandlers) with the createShell() return value (which had none), making express routes invisible to index.html callExpressRoute(). Fix: remove internal window.__debug.shell assignment, include httpHandlers and all debug getters on the returned shell object so terminal.js assignment preserves the reference
|
|
197
|
+
- `test.js`: consolidate e2e-test.js coverage into test.js (express routing e2e + httpHandlers fix regression); delete e2e-test.js to enforce single-test-file policy
|
|
198
|
+
|
|
199
|
+
### Fixed
|
|
200
|
+
- `docs/app.js`: Normalize message content format to Anthropic array structure `[{ type: 'text', text: '...' }]` to prevent double-conversion in streamGemini/streamOpenAI (was sending string content)
|
|
201
|
+
- `docs/agent-chat.js`: Add lastError tracking in window.__debug.agent for error visibility and debugging
|
|
202
|
+
- `docs/shell.js`: Expose onPreviewWrite callback on returned shell object for preview refresh integration
|
|
203
|
+
- `docs/terminal.js`: Add shell reference to window.__debug for tool access; reduce preview refresh debounce 5s → 1s for quicker feedback
|
|
204
|
+
- `docs/preview-sw.js`: Add missing service worker for preview iframe routing (handle EXPRESS_REQUEST messages from main thread)
|
|
205
|
+
- `test.js`: Create integration test suite validating bootstrap, defaults.json, tools, errors, observability structures
|
|
206
|
+
|
|
207
|
+
### Changed
|
|
208
|
+
- `docs/defaults.json`: Split and optimized for Git constraints — reduced from 154.83 MB to 1.23 MB by including only 16 critical bootstrap files (app.js, agent-chat.js, terminal.js, shell.js, vendor/xterm-bundle.js, vendor/xstate.js, vendor/ui-libs.js, vendor/thebird-browser.js, etc.). Excludes large unused vendors (winterjs.wasm 46 MB, wasmer_js_bg.wasm 6.3 MB, rippleui.css 4.5 MB, sql-wasm.wasm 0.6 MB, acp-sdk.js 0.6 MB) not required for WebContainer bootstrap path.
|
|
209
|
+
- `docs/terminal.js`: Updated xterm.js theme with green foreground (#33ff33) to match Claude Code TUI aesthetic. Maintains AAA contrast ratio (14.61:1 on black background).
|
|
210
|
+
|
|
3
211
|
### Added
|
|
4
212
|
- `docs/node-builtins.js`: Full Node.js module polyfills — path, fs (IDB-backed), events (EventEmitter), url, querystring, Buffer class with encoding support
|
|
5
213
|
- `docs/shell-node.js`: Enhanced Node env — relative require, JSON require, per-file __dirname, process.stdout/stderr/nextTick/argv/hrtime, console.dir/table/time/assert/count, express with route params/middleware/static/json, os/util/crypto/stream modules
|
|
@@ -95,3 +303,12 @@
|
|
|
95
303
|
### Added
|
|
96
304
|
- `docs/index.html`: Preview tab with iframe (`#preview-frame`), `switchTab` extended to dispatch over `['chat','term','preview']`.
|
|
97
305
|
- `docs/terminal.js`: DEFAULT_FILES now includes `server.js` (HTTP server on port 3000, JSON status endpoint) and updated `index.js` (loads @anthropic-ai/sdk, hits server). Server auto-starts after `npm install`. `container.on('server-ready')` wires iframe src + `window.__debug.previewUrl`. Shell upgraded from `sh` loop to `jsh` with PTY resize. `window.__debug.srv` and `window.__debug.shell` live.
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
## shell predictability fixes (2026-04-18)
|
|
309
|
+
- Pipe stdin passthrough: grep/sed detect piped content as first arg (stdinFirst pattern)
|
|
310
|
+
- Tokenizer: preserves \\n inside double-quotes so echo -e works correctly
|
|
311
|
+
- $? expansion, $() command substitution, inline var assignment (X=val cmd)
|
|
312
|
+
- echo -e with \\n \\t escape sequences
|
|
313
|
+
- New builtins: sed, sort, uniq, tr
|
|
314
|
+
- shell-builtins.js split into shell-builtins-text.js (both under 200L)
|
package/CLAUDE.md
CHANGED
|
@@ -134,6 +134,20 @@ Run examples against real Gemini API to validate message translation.
|
|
|
134
134
|
- Tool parameter types must be lowercase for Gemini API — `object`, `string`, `number` not `OBJECT`, `STRING`, `NUMBER`. Uppercase types fail schema validation.
|
|
135
135
|
- agentGenerate passes raw Anthropic-format messages to streamGemini internally, which calls convertMessages. Do NOT pre-convert in app.js — double-conversion breaks tool schemas.
|
|
136
136
|
- Anthropic format IS the canonical message format — do NOT add an intermediate transformation layer. Direct translation to provider-native formats is cleaner than another abstraction level.
|
|
137
|
+
- **shell.js httpHandlers visibility chain**: shell.js creates `httpHandlers = {}` and returns it on the shell object. terminal.js assigns `window.__debug.shell = shell`. shell-node.js mutates `window.__debug.shell.httpHandlers[port]` to register routes. index.html reads from `window.__debug.shell.httpHandlers`. All four modules reference the same object; httpHandlers MUST be returned on the shell object or the chain breaks.
|
|
138
|
+
|
|
139
|
+
- **Shell builtin pipe stdin detection**: When a builtin receives piped input (e.g. `echo hello | grep hello`), the pipe buffer arrives as `args[0]`. Builtins with mandatory leading args (grep: pattern, sed: expression) must detect `positional[0].includes('\n')` BEFORE extracting those args, then shift stdin off. `cat`/`wc`/`sort`/`uniq` use the same check. Single-line piped content always has `\n` because `echo` appends `\r\n`.
|
|
140
|
+
- **Tokenizer double-quote escape sequences**: Inside double-quotes, only `"`, `\`, `` ` ``, `$` are valid escape sequences. `\n` in double-quotes must be preserved as literal backslash-n for `echo -e` to process. Fix: when escape=true and quote==='"', re-emit backslash before char if char not in `"\\\`$`.- **Test policy**: Single test.js file (max 139L per constraint). e2e-test.js deleted. Validation via live API calls with exec:nodejs (browser automation unavailable in this environment).
|
|
141
|
+
- **ACP protocolVersion is a number**: Kilo Code's `kilo acp` server rejects string `'0.1'` with `-32602 Invalid params: expected number`. Use integer `1`. acp-stream.js:73 fixed.
|
|
142
|
+
- **Kilo ACP over WebSocket bridge (Windows)**: To consume Kilo Code as the page's ACP provider, bridge its stdio to WS. The npm shim `kilo.cmd` uses `stdio: "inherit"` which detaches from the bridge's pipes, so invoke the platform binary directly: `C:\Users\user\AppData\Roaming\npm\node_modules\@kilocode\cli\node_modules\@kilocode\cli-windows-x64\bin\kilo.exe acp`. Bridge command: `bunx -y stdio-to-ws "<abs-path>\kilo.exe acp" --port 3015`. Bunx cold start is 15–25s; wait for `[stdio-to-ws] WebSocket server listening`. Validated: initialize → result with `protocolVersion: 1, agentInfo: { name: 'Kilo', version: '7.2.0' }, authMethods: [{ id: 'kilo-login' }]`. Actual prompts require `kilo auth login` first.
|
|
143
|
+
- **Kilo ACP `session/new` requires `mcpServers` array**: Kilo's zod schema for `session/new` params requires `mcpServers` (array). Omitting it returns `-32602 Invalid params: expected array, received undefined`. Fix: pass `{ cwd, mcpServers: [] }` — see docs/acp-stream.js:74.
|
|
144
|
+
- **Kilo ACP `session/prompt` shape**: Kilo's schema is `{ sessionId, prompt: [...content blocks] }` — NOT `{ sessionId, message: { role, content: [...] } }`. Wrong shape returns `-32602 Invalid params: prompt expected array, received undefined`. Fix: send `prompt: [{ type:'text', text }]` directly — see docs/acp-stream.js:80.
|
|
145
|
+
- **Kilo ACP `session/update` notification shape**: Kilo emits `{ params: { sessionId, update: { sessionUpdate:'agent_message_chunk', content:{ type:'text', text } } } }` — singular `update`, and the discriminator is `sessionUpdate` not `type`. Iterating `updates[]` (plural) with `item.type==='message_chunk'` never matches real Kilo output. Fix: normalize to `update ? [update] : (updates||[])` and match `sessionUpdate==='agent_message_chunk'` with `content.type==='text'`. See docs/acp-stream.js:93.
|
|
146
|
+
- **Kilo ACP `kilo/*` free models require `kilo auth login`**: Built-in free routes (`kilo/x-ai/grok-code-fast-1:optimized:free`, `kilo/openrouter/free`, etc.) only work after interactive `kilo auth login` stores a credential in `~/.local/share/kilo/auth.json`. `kilo auth list` exposes `OPENAI_API_KEY`/`GEMINI_API_KEY` env vars as alternate auth sources, but those only apply to direct-provider models (`openai/*`, `google/*`) — not to `kilo/*` routes. With an invalid/missing GEMINI_API_KEY, `session/prompt` returns `stopReason:'end_turn'` with zero tokens and no text chunks — silent no-op, not an error. Any real end-to-end prompt validation requires a valid provider key or an authenticated Kilo account.
|
|
147
|
+
- **Kilo ACP `session/set_model` is supported**: Call `session/set_model` with `{ sessionId, modelId }` between `session/new` and `session/prompt` to override the default (`kilo/x-ai/grok-code-fast-1:optimized:free`). Validated with `modelId:'google/gemini-2.5-flash'` — response includes `_meta.opencode.modelId` confirming the switch.
|
|
148
|
+
- **Kilo free models DO work non-interactively without `kilo auth login`**: Earlier assumption was wrong. The bundled `@kilocode/kilo-gateway` provider (`providerID:'kilo'`, source:'custom', env:[KILO_API_KEY]) serves free routes (`x-ai/grok-code-fast-1:optimized:free`, `openrouter/free`, `kilo-auto/free`, etc.) anonymously over the built-in HTTP server. Witnessed via `kilo serve --port N` + `POST /session` + `POST /session/:id/message` with `{ parts:[{type:'text',text}], modelID:'x-ai/grok-code-fast-1:optimized:free', providerID:'kilo' }` → `status 200`, `finish:stop`, real text output in ~5s. Cost: 0. Reference: C:\dev\agentgui\lib\claude-runner-acp.js shows the identical flow working over ACP stdio — key detail is a 5-min (300000ms) timeout because the gm agent plugin (globally installed at `C:\Users\user\.config\kilo\plugins\gm-kilo.mjs`) does git ops on first prompt. gm system prompt is always injected (~18-19k input tokens regardless of agent param).
|
|
149
|
+
- **Kilo agent plugin overrides requested model**: Specifying `modelID:'x-ai/grok-code-fast-1:optimized:free'` on a prompt often gets silently routed to `openrouter/elephant-alpha` because the `gm` agent (auto-selected when `default_agent:'gm'` is in `opencode.json`) has its own model picker. Check `info.modelID` in the response to see what actually ran. To bypass gm routing, delete or rename `C:\Users\user\.config\kilo\opencode.json` and `kilocode.json` which set `default_agent:'gm'`.
|
|
150
|
+
- **Kilo `run` subcommand hangs, `serve` + HTTP works**: The `kilo run` CLI path currently blocks indefinitely after config load (even at DEBUG log level, no provider activity logged). Workaround: use `kilo serve --port N` and POST to `/session/:id/message` — same bundled provider, same free models, responds in seconds. Also applies to ACP stdio: `kilo acp` works but has long cold-start through the gm plugin, so any bridge must allow at least 5 minutes per prompt.
|
|
137
151
|
|
|
138
152
|
## Error Architecture
|
|
139
153
|
|
|
@@ -172,6 +186,8 @@ Run examples against real Gemini API to validate message translation.
|
|
|
172
186
|
- `examples/`: Working examples using Anthropic SDK format
|
|
173
187
|
- `wasi/cli.ts`: Deno streaming CLI — `deno run --allow-net --allow-env wasi/cli.ts [--model M] [--system S] <prompt>`
|
|
174
188
|
- `deno.json`: tasks `cli` (run) and `cli:compile` (→ `dist/thebird` binary)
|
|
189
|
+
- `docs/shell-builtins.js`: FS/IO builtins (ls/cat/echo/cd/mkdir/rm/cp/mv/touch/head/tail/wc) — imports makeTextBuiltins
|
|
190
|
+
- `docs/shell-builtins-text.js`: Text-processing builtins (grep/sed/sort/uniq/tr) + env/export/clear/history/which/exit/true/false/printenv
|
|
175
191
|
|
|
176
192
|
## WebContainer Terminal in docs/
|
|
177
193
|
|
package/docs/agent-chat.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { streamGemini, streamOpenAI } from './vendor/thebird-browser.js';
|
|
2
|
-
import {
|
|
2
|
+
import { streamKiloHTTP } from './kilo-http-stream.js';
|
|
3
3
|
|
|
4
4
|
function idbRead(path) {
|
|
5
5
|
const snap = window.__debug.idbSnapshot;
|
|
@@ -91,8 +91,8 @@ function buildStream(provider) {
|
|
|
91
91
|
if (provider.type === 'gemini') {
|
|
92
92
|
return streamGemini({ model: provider.model, messages: provider.messages, tools: TOOLS, apiKey: provider.apiKey, maxOutputTokens: 8192 }).fullStream;
|
|
93
93
|
}
|
|
94
|
-
if (provider.type === '
|
|
95
|
-
return
|
|
94
|
+
if (provider.type === 'kilo') {
|
|
95
|
+
return streamKiloHTTP({ url: provider.baseUrl, model: provider.model, messages: provider.messages });
|
|
96
96
|
}
|
|
97
97
|
const url = (provider.baseUrl || '').replace(/\/$/, '') + '/chat/completions';
|
|
98
98
|
return streamOpenAI({ url, apiKey: provider.apiKey, messages: provider.messages, model: provider.model, tools: TOOLS, maxOutputTokens: 8192 });
|
|
@@ -100,7 +100,7 @@ function buildStream(provider) {
|
|
|
100
100
|
|
|
101
101
|
export async function agentGenerate(provider, messages, onChunk, onTool) {
|
|
102
102
|
Object.assign(window.__debug = window.__debug || {}, {
|
|
103
|
-
agent: { provider: provider.type, model: provider.model, active: true, lastTool: null },
|
|
103
|
+
agent: { provider: provider.type, model: provider.model, active: true, lastTool: null, lastError: null },
|
|
104
104
|
});
|
|
105
105
|
try {
|
|
106
106
|
for await (const ev of buildStream({ ...provider, messages })) {
|
|
@@ -110,6 +110,9 @@ export async function agentGenerate(provider, messages, onChunk, onTool) {
|
|
|
110
110
|
onTool(ev.toolName, ev.args);
|
|
111
111
|
} else if (ev.type === 'error') throw ev.error;
|
|
112
112
|
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
window.__debug.agent.lastError = { message: e.message, stack: e.stack, timestamp: Date.now() };
|
|
115
|
+
throw e;
|
|
113
116
|
} finally {
|
|
114
117
|
window.__debug.agent.active = false;
|
|
115
118
|
}
|
package/docs/app.js
CHANGED
|
@@ -11,7 +11,7 @@ const PROVIDERS = {
|
|
|
11
11
|
mistral: { label: 'Mistral', baseUrl: 'https://api.mistral.ai/v1', keyPlaceholder: 'MISTRAL_API_KEY', models: ['mistral-large-latest', 'mistral-small-latest', 'codestral-latest'] },
|
|
12
12
|
deepseek: { label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', keyPlaceholder: 'DEEPSEEK_API_KEY', models: ['deepseek-chat', 'deepseek-reasoner'] },
|
|
13
13
|
cerebras: { label: 'Cerebras', baseUrl: 'https://api.cerebras.ai/v1', keyPlaceholder: 'CEREBRAS_API_KEY', models: ['gpt-oss-120b', 'llama3.1-8b'] },
|
|
14
|
-
|
|
14
|
+
kilo: { label: 'Kilo Code', baseUrl: 'http://localhost:4780', keyPlaceholder: '(no key needed)', models: ['x-ai/grok-code-fast-1:optimized:free', 'openrouter/free', 'kilo-auto/free'] },
|
|
15
15
|
custom: { label: 'Custom (OpenAI-compat)', baseUrl: '', keyPlaceholder: 'API_KEY', models: [] },
|
|
16
16
|
};
|
|
17
17
|
|
|
@@ -45,7 +45,8 @@ async function fetchModels(providerType, baseUrl, apiKey) {
|
|
|
45
45
|
class BirdChat extends HTMLElement {
|
|
46
46
|
constructor() {
|
|
47
47
|
super();
|
|
48
|
-
const
|
|
48
|
+
const rawSaved = localStorage.getItem('provider_type') || 'gemini';
|
|
49
|
+
const savedProvider = PROVIDERS[rawSaved] ? rawSaved : (rawSaved === 'acp' || rawSaved === 'kilohttp' ? 'kilo' : 'gemini');
|
|
49
50
|
const savedBaseUrl = localStorage.getItem('provider_base_url') || PROVIDERS[savedProvider]?.baseUrl || '';
|
|
50
51
|
this.state = {
|
|
51
52
|
messages: [], streaming: false,
|
|
@@ -107,11 +108,11 @@ class BirdChat extends HTMLElement {
|
|
|
107
108
|
<div class="tui-toolbar">
|
|
108
109
|
<label>provider:</label>
|
|
109
110
|
<select class="tui-select" onchange=${e => this.setProvider(e.target.value)}>${provOpts}</select>
|
|
110
|
-
${(providerType === 'custom' || providerType === '
|
|
111
|
+
${(providerType === 'custom' || providerType === 'kilo') ? html`
|
|
111
112
|
<input type="text" class="tui-input" style="flex:1;min-width:140px"
|
|
112
|
-
placeholder=${providerType === '
|
|
113
|
+
placeholder=${providerType === 'kilo' ? 'http://localhost:4780' : 'https://your-endpoint/v1'} value=${baseUrl}
|
|
113
114
|
onchange=${e => { localStorage.setItem('provider_base_url', e.target.value); this.setState({ baseUrl: e.target.value }); }} />` : ''}
|
|
114
|
-
${providerType !== '
|
|
115
|
+
${providerType !== 'kilo' ? html`<input id="api-key-input" type="password" class="tui-input" style="flex:1;min-width:120px"
|
|
115
116
|
placeholder=${provDef.keyPlaceholder} value=${apiKey}
|
|
116
117
|
onchange=${e => {
|
|
117
118
|
const v = e.target.value.trim();
|
|
@@ -151,12 +152,14 @@ class BirdChat extends HTMLElement {
|
|
|
151
152
|
const text = input?.value.trim();
|
|
152
153
|
if (!text || this.state.streaming) return;
|
|
153
154
|
const { apiKey, model, providerType, baseUrl } = this.state;
|
|
154
|
-
if (!apiKey && providerType !== '
|
|
155
|
+
if (!apiKey && providerType !== 'kilo') { this.setState({ status: 'Enter an API key above.' }); return; }
|
|
155
156
|
input.value = '';
|
|
156
157
|
input.style.height = 'auto';
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
const normalizedMessages = [...this.state.messages, { role: 'user', content: text }].map(m => ({
|
|
159
|
+
...m, content: typeof m.content === 'string' ? [{ type: 'text', text: m.content }] : m.content
|
|
160
|
+
}));
|
|
161
|
+
this.setState({ messages: normalizedMessages, streaming: true, status: '', streamingText: '' });
|
|
162
|
+
const provider = { type: providerType, apiKey, model, baseUrl: providerType === 'gemini' ? '' : baseUrl, url: baseUrl };
|
|
160
163
|
try {
|
|
161
164
|
let full = '';
|
|
162
165
|
const streamEl = document.createElement('div');
|
|
@@ -169,12 +172,12 @@ class BirdChat extends HTMLElement {
|
|
|
169
172
|
wrap.appendChild(cursor);
|
|
170
173
|
const list = this.querySelector('#msg-list');
|
|
171
174
|
if (list) list.appendChild(wrap);
|
|
172
|
-
await agentGenerate(provider,
|
|
175
|
+
await agentGenerate(provider, normalizedMessages,
|
|
173
176
|
chunk => { full += chunk; streamEl.textContent = full; const l = this.querySelector('#msg-list'); if (l) l.scrollTop = l.scrollHeight; },
|
|
174
177
|
(name, args) => { full += `\n[tool: ${name}(${JSON.stringify(args)})]\n`; streamEl.textContent = full; }
|
|
175
178
|
);
|
|
176
179
|
wrap.remove();
|
|
177
|
-
this.setState({ messages: [...
|
|
180
|
+
this.setState({ messages: [...normalizedMessages, { role: 'assistant', content: [{ type: 'text', text: full || '(empty)' }] }], streaming: false, streamingText: '' });
|
|
178
181
|
const l2 = this.querySelector('#msg-list');
|
|
179
182
|
if (l2) l2.scrollTop = l2.scrollHeight;
|
|
180
183
|
} catch (err) {
|