@nimbus-sh/core 0.1.0

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 (1056) hide show
  1. package/dist/_shared/ai-egress.d.ts +73 -0
  2. package/dist/_shared/ai-egress.d.ts.map +1 -0
  3. package/dist/_shared/ai-egress.js +88 -0
  4. package/dist/_shared/byte-stream.d.ts +59 -0
  5. package/dist/_shared/byte-stream.d.ts.map +1 -0
  6. package/dist/_shared/byte-stream.js +77 -0
  7. package/dist/_shared/bytes.d.ts +18 -0
  8. package/dist/_shared/bytes.d.ts.map +1 -0
  9. package/dist/_shared/bytes.js +17 -0
  10. package/dist/_shared/cache-stats.d.ts +145 -0
  11. package/dist/_shared/cache-stats.d.ts.map +1 -0
  12. package/dist/_shared/cache-stats.js +161 -0
  13. package/dist/_shared/crypto.d.ts +17 -0
  14. package/dist/_shared/crypto.d.ts.map +1 -0
  15. package/dist/_shared/crypto.js +131 -0
  16. package/dist/_shared/esbuild-facet-shim.d.ts +21 -0
  17. package/dist/_shared/esbuild-facet-shim.d.ts.map +1 -0
  18. package/dist/_shared/esbuild-facet-shim.js +22 -0
  19. package/dist/_shared/exports-resolver.d.ts +74 -0
  20. package/dist/_shared/exports-resolver.d.ts.map +1 -0
  21. package/dist/_shared/exports-resolver.js +284 -0
  22. package/dist/_shared/facet-vfs-cursor.d.ts +48 -0
  23. package/dist/_shared/facet-vfs-cursor.d.ts.map +1 -0
  24. package/dist/_shared/facet-vfs-cursor.js +51 -0
  25. package/dist/_shared/install-phase.d.ts +47 -0
  26. package/dist/_shared/install-phase.d.ts.map +1 -0
  27. package/dist/_shared/install-phase.js +1 -0
  28. package/dist/_shared/lru-map.d.ts +25 -0
  29. package/dist/_shared/lru-map.d.ts.map +1 -0
  30. package/dist/_shared/lru-map.js +57 -0
  31. package/dist/_shared/real-node-imports.d.ts +43 -0
  32. package/dist/_shared/real-node-imports.d.ts.map +1 -0
  33. package/dist/_shared/real-node-imports.js +52 -0
  34. package/dist/_shared/retry.d.ts +92 -0
  35. package/dist/_shared/retry.d.ts.map +1 -0
  36. package/dist/_shared/retry.js +155 -0
  37. package/dist/_shared/rpc-dispose.d.ts +4 -0
  38. package/dist/_shared/rpc-dispose.d.ts.map +1 -0
  39. package/dist/_shared/rpc-dispose.js +30 -0
  40. package/dist/_shared/terminal.d.ts +2 -0
  41. package/dist/_shared/terminal.d.ts.map +1 -0
  42. package/dist/_shared/terminal.js +3 -0
  43. package/dist/_shared/typescript-specifiers.d.ts +58 -0
  44. package/dist/_shared/typescript-specifiers.d.ts.map +1 -0
  45. package/dist/_shared/typescript-specifiers.js +84 -0
  46. package/dist/_shared/untrusted-request.d.ts +34 -0
  47. package/dist/_shared/untrusted-request.d.ts.map +1 -0
  48. package/dist/_shared/untrusted-request.js +74 -0
  49. package/dist/_shared/vfs-write-ledger.d.ts +3 -0
  50. package/dist/_shared/vfs-write-ledger.d.ts.map +1 -0
  51. package/dist/_shared/vfs-write-ledger.js +439 -0
  52. package/dist/_shared/w7-frame.d.ts +79 -0
  53. package/dist/_shared/w7-frame.d.ts.map +1 -0
  54. package/dist/_shared/w7-frame.js +839 -0
  55. package/dist/_shared/weighted-credit-pool.d.ts +61 -0
  56. package/dist/_shared/weighted-credit-pool.d.ts.map +1 -0
  57. package/dist/_shared/weighted-credit-pool.js +196 -0
  58. package/dist/constants.d.ts +64 -0
  59. package/dist/constants.d.ts.map +1 -0
  60. package/dist/constants.js +264 -0
  61. package/dist/index.d.ts +16 -0
  62. package/dist/index.d.ts.map +1 -0
  63. package/dist/index.js +13 -0
  64. package/dist/observability/diag-counters.d.ts +236 -0
  65. package/dist/observability/diag-counters.d.ts.map +1 -0
  66. package/dist/observability/diag-counters.js +195 -0
  67. package/dist/observability/heap-estimate.d.ts +225 -0
  68. package/dist/observability/heap-estimate.d.ts.map +1 -0
  69. package/dist/observability/heap-estimate.js +185 -0
  70. package/dist/observability/heavy-alloc-coord.d.ts +93 -0
  71. package/dist/observability/heavy-alloc-coord.d.ts.map +1 -0
  72. package/dist/observability/heavy-alloc-coord.js +181 -0
  73. package/dist/observability/oom-classify.d.ts +138 -0
  74. package/dist/observability/oom-classify.d.ts.map +1 -0
  75. package/dist/observability/oom-classify.js +261 -0
  76. package/dist/observability/oom-discriminator.d.ts +176 -0
  77. package/dist/observability/oom-discriminator.d.ts.map +1 -0
  78. package/dist/observability/oom-discriminator.js +272 -0
  79. package/dist/runtime/assets-loader.d.ts +71 -0
  80. package/dist/runtime/assets-loader.d.ts.map +1 -0
  81. package/dist/runtime/assets-loader.js +139 -0
  82. package/dist/runtime/barrel-detect.d.ts +46 -0
  83. package/dist/runtime/barrel-detect.d.ts.map +1 -0
  84. package/dist/runtime/barrel-detect.js +73 -0
  85. package/dist/runtime/bash/preamble.d.ts +2 -0
  86. package/dist/runtime/bash/preamble.d.ts.map +1 -0
  87. package/dist/runtime/bash/preamble.js +1849 -0
  88. package/dist/runtime/bash/types.d.ts +433 -0
  89. package/dist/runtime/bash/types.d.ts.map +1 -0
  90. package/dist/runtime/bash/types.js +1 -0
  91. package/dist/runtime/bundle-profile.d.ts +14 -0
  92. package/dist/runtime/bundle-profile.d.ts.map +1 -0
  93. package/dist/runtime/bundle-profile.js +20 -0
  94. package/dist/runtime/cirrus-vite-config-rewriter.d.ts +2 -0
  95. package/dist/runtime/cirrus-vite-config-rewriter.d.ts.map +1 -0
  96. package/dist/runtime/cirrus-vite-config-rewriter.js +28 -0
  97. package/dist/runtime/cli-flags.d.ts +2 -0
  98. package/dist/runtime/cli-flags.d.ts.map +1 -0
  99. package/dist/runtime/cli-flags.js +11 -0
  100. package/dist/runtime/comment-strip.d.ts +37 -0
  101. package/dist/runtime/comment-strip.d.ts.map +1 -0
  102. package/dist/runtime/comment-strip.js +74 -0
  103. package/dist/runtime/cpython-preamble.d.ts +32 -0
  104. package/dist/runtime/cpython-preamble.d.ts.map +1 -0
  105. package/dist/runtime/cpython-preamble.js +296 -0
  106. package/dist/runtime/esbuild-service.d.ts +242 -0
  107. package/dist/runtime/esbuild-service.d.ts.map +1 -0
  108. package/dist/runtime/esbuild-service.js +1452 -0
  109. package/dist/runtime/framework-detect.d.ts +70 -0
  110. package/dist/runtime/framework-detect.d.ts.map +1 -0
  111. package/dist/runtime/framework-detect.js +177 -0
  112. package/dist/runtime/import-meta-transform.d.ts +3 -0
  113. package/dist/runtime/import-meta-transform.d.ts.map +1 -0
  114. package/dist/runtime/import-meta-transform.js +15 -0
  115. package/dist/runtime/javascript-ast.d.ts +13 -0
  116. package/dist/runtime/javascript-ast.d.ts.map +1 -0
  117. package/dist/runtime/javascript-ast.js +57 -0
  118. package/dist/runtime/long-running-handle.d.ts +119 -0
  119. package/dist/runtime/long-running-handle.d.ts.map +1 -0
  120. package/dist/runtime/long-running-handle.js +169 -0
  121. package/dist/runtime/module-source-rewriter.d.ts +11 -0
  122. package/dist/runtime/module-source-rewriter.d.ts.map +1 -0
  123. package/dist/runtime/module-source-rewriter.js +61 -0
  124. package/dist/runtime/opentui-wasm-backend.d.ts +160 -0
  125. package/dist/runtime/opentui-wasm-backend.d.ts.map +1 -0
  126. package/dist/runtime/opentui-wasm-backend.js +416 -0
  127. package/dist/runtime/os-contracts.d.ts +436 -0
  128. package/dist/runtime/os-contracts.d.ts.map +1 -0
  129. package/dist/runtime/os-contracts.js +101 -0
  130. package/dist/runtime/port-registry.d.ts +100 -0
  131. package/dist/runtime/port-registry.d.ts.map +1 -0
  132. package/dist/runtime/port-registry.js +265 -0
  133. package/dist/runtime/process-input-routing.d.ts +33 -0
  134. package/dist/runtime/process-input-routing.d.ts.map +1 -0
  135. package/dist/runtime/process-input-routing.js +59 -0
  136. package/dist/runtime/process-input.d.ts +39 -0
  137. package/dist/runtime/process-input.d.ts.map +1 -0
  138. package/dist/runtime/process-input.js +117 -0
  139. package/dist/runtime/process-io-protocol.d.ts +90 -0
  140. package/dist/runtime/process-io-protocol.d.ts.map +1 -0
  141. package/dist/runtime/process-io-protocol.js +81 -0
  142. package/dist/runtime/process-logs.d.ts +352 -0
  143. package/dist/runtime/process-logs.d.ts.map +1 -0
  144. package/dist/runtime/process-logs.js +750 -0
  145. package/dist/runtime/process-table.d.ts +101 -0
  146. package/dist/runtime/process-table.d.ts.map +1 -0
  147. package/dist/runtime/process-table.js +175 -0
  148. package/dist/runtime/project-detect.d.ts +21 -0
  149. package/dist/runtime/project-detect.d.ts.map +1 -0
  150. package/dist/runtime/project-detect.js +46 -0
  151. package/dist/runtime/python-server-adapter.d.ts +33 -0
  152. package/dist/runtime/python-server-adapter.d.ts.map +1 -0
  153. package/dist/runtime/python-server-adapter.js +132 -0
  154. package/dist/runtime/require-resolver.d.ts +44 -0
  155. package/dist/runtime/require-resolver.d.ts.map +1 -0
  156. package/dist/runtime/require-resolver.js +698 -0
  157. package/dist/runtime/router-basename.d.ts +36 -0
  158. package/dist/runtime/router-basename.d.ts.map +1 -0
  159. package/dist/runtime/router-basename.js +310 -0
  160. package/dist/runtime/ruby-green-threads.d.ts +136 -0
  161. package/dist/runtime/ruby-green-threads.d.ts.map +1 -0
  162. package/dist/runtime/ruby-green-threads.js +904 -0
  163. package/dist/runtime/ruby-socket-shim.d.ts +2 -0
  164. package/dist/runtime/ruby-socket-shim.d.ts.map +1 -0
  165. package/dist/runtime/ruby-socket-shim.js +548 -0
  166. package/dist/runtime/session-process-supervisor.d.ts +156 -0
  167. package/dist/runtime/session-process-supervisor.d.ts.map +1 -0
  168. package/dist/runtime/session-process-supervisor.js +238 -0
  169. package/dist/runtime/sqlite-runtime-fs-bridge.d.ts +70 -0
  170. package/dist/runtime/sqlite-runtime-fs-bridge.d.ts.map +1 -0
  171. package/dist/runtime/sqlite-runtime-fs-bridge.js +425 -0
  172. package/dist/runtime/streams.d.ts +13 -0
  173. package/dist/runtime/streams.d.ts.map +1 -0
  174. package/dist/runtime/streams.js +546 -0
  175. package/dist/runtime/undici-shim.d.ts +62 -0
  176. package/dist/runtime/undici-shim.d.ts.map +1 -0
  177. package/dist/runtime/undici-shim.js +516 -0
  178. package/dist/runtime/vfs-manifest.d.ts +63 -0
  179. package/dist/runtime/vfs-manifest.d.ts.map +1 -0
  180. package/dist/runtime/vfs-manifest.js +141 -0
  181. package/dist/runtime/vfs-snapshot.d.ts +75 -0
  182. package/dist/runtime/vfs-snapshot.d.ts.map +1 -0
  183. package/dist/runtime/vfs-snapshot.js +220 -0
  184. package/dist/runtime/virtual-socket-kernel.d.ts +252 -0
  185. package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -0
  186. package/dist/runtime/virtual-socket-kernel.js +1143 -0
  187. package/dist/runtime/vite-config-parser.d.ts +13 -0
  188. package/dist/runtime/vite-config-parser.d.ts.map +1 -0
  189. package/dist/runtime/vite-config-parser.js +258 -0
  190. package/dist/runtime/wasi/types.d.ts +347 -0
  191. package/dist/runtime/wasi/types.d.ts.map +1 -0
  192. package/dist/runtime/wasi/types.js +1 -0
  193. package/dist/runtime/wasi-instance.d.ts +215 -0
  194. package/dist/runtime/wasi-instance.d.ts.map +1 -0
  195. package/dist/runtime/wasi-instance.generated.d.ts +14 -0
  196. package/dist/runtime/wasi-instance.generated.d.ts.map +1 -0
  197. package/dist/runtime/wasi-instance.generated.js +13 -0
  198. package/dist/runtime/wasi-instance.js +179 -0
  199. package/dist/runtime/wasi-threads.d.ts +169 -0
  200. package/dist/runtime/wasi-threads.d.ts.map +1 -0
  201. package/dist/runtime/wasi-threads.js +669 -0
  202. package/dist/runtime/wasm-memory.d.ts +84 -0
  203. package/dist/runtime/wasm-memory.d.ts.map +1 -0
  204. package/dist/runtime/wasm-memory.js +235 -0
  205. package/dist/shell/elevation-commands.d.ts +5 -0
  206. package/dist/shell/elevation-commands.d.ts.map +1 -0
  207. package/dist/shell/elevation-commands.js +109 -0
  208. package/dist/shell/exec-dispatch.d.ts +57 -0
  209. package/dist/shell/exec-dispatch.d.ts.map +1 -0
  210. package/dist/shell/exec-dispatch.js +181 -0
  211. package/dist/shell/features.d.ts +154 -0
  212. package/dist/shell/features.d.ts.map +1 -0
  213. package/dist/shell/features.js +792 -0
  214. package/dist/shell/shell-entrypoints.d.ts +55 -0
  215. package/dist/shell/shell-entrypoints.d.ts.map +1 -0
  216. package/dist/shell/shell-entrypoints.js +230 -0
  217. package/dist/shell/shell-invocation.d.ts +36 -0
  218. package/dist/shell/shell-invocation.d.ts.map +1 -0
  219. package/dist/shell/shell-invocation.js +148 -0
  220. package/dist/shell/unix-accounts.d.ts +27 -0
  221. package/dist/shell/unix-accounts.d.ts.map +1 -0
  222. package/dist/shell/unix-accounts.js +105 -0
  223. package/dist/shell/unix-commands.d.ts +15 -0
  224. package/dist/shell/unix-commands.d.ts.map +1 -0
  225. package/dist/shell/unix-commands.js +4702 -0
  226. package/dist/substrate/lifo/commands/archive/gunzip.d.ts +4 -0
  227. package/dist/substrate/lifo/commands/archive/gunzip.d.ts.map +1 -0
  228. package/dist/substrate/lifo/commands/archive/gunzip.js +58 -0
  229. package/dist/substrate/lifo/commands/archive/gzip.d.ts +4 -0
  230. package/dist/substrate/lifo/commands/archive/gzip.d.ts.map +1 -0
  231. package/dist/substrate/lifo/commands/archive/gzip.js +73 -0
  232. package/dist/substrate/lifo/commands/archive/tar.d.ts +4 -0
  233. package/dist/substrate/lifo/commands/archive/tar.d.ts.map +1 -0
  234. package/dist/substrate/lifo/commands/archive/tar.js +158 -0
  235. package/dist/substrate/lifo/commands/archive/unzip.d.ts +4 -0
  236. package/dist/substrate/lifo/commands/archive/unzip.d.ts.map +1 -0
  237. package/dist/substrate/lifo/commands/archive/unzip.js +114 -0
  238. package/dist/substrate/lifo/commands/archive/zip.d.ts +4 -0
  239. package/dist/substrate/lifo/commands/archive/zip.d.ts.map +1 -0
  240. package/dist/substrate/lifo/commands/archive/zip.js +58 -0
  241. package/dist/substrate/lifo/commands/fs/basename.d.ts +4 -0
  242. package/dist/substrate/lifo/commands/fs/basename.d.ts.map +1 -0
  243. package/dist/substrate/lifo/commands/fs/basename.js +12 -0
  244. package/dist/substrate/lifo/commands/fs/cat.d.ts +4 -0
  245. package/dist/substrate/lifo/commands/fs/cat.d.ts.map +1 -0
  246. package/dist/substrate/lifo/commands/fs/cat.js +52 -0
  247. package/dist/substrate/lifo/commands/fs/chmod.d.ts +25 -0
  248. package/dist/substrate/lifo/commands/fs/chmod.d.ts.map +1 -0
  249. package/dist/substrate/lifo/commands/fs/chmod.js +97 -0
  250. package/dist/substrate/lifo/commands/fs/chown.d.ts +4 -0
  251. package/dist/substrate/lifo/commands/fs/chown.d.ts.map +1 -0
  252. package/dist/substrate/lifo/commands/fs/chown.js +41 -0
  253. package/dist/substrate/lifo/commands/fs/cp.d.ts +4 -0
  254. package/dist/substrate/lifo/commands/fs/cp.d.ts.map +1 -0
  255. package/dist/substrate/lifo/commands/fs/cp.js +29 -0
  256. package/dist/substrate/lifo/commands/fs/df.d.ts +4 -0
  257. package/dist/substrate/lifo/commands/fs/df.d.ts.map +1 -0
  258. package/dist/substrate/lifo/commands/fs/df.js +52 -0
  259. package/dist/substrate/lifo/commands/fs/dirname.d.ts +4 -0
  260. package/dist/substrate/lifo/commands/fs/dirname.d.ts.map +1 -0
  261. package/dist/substrate/lifo/commands/fs/dirname.js +12 -0
  262. package/dist/substrate/lifo/commands/fs/du.d.ts +4 -0
  263. package/dist/substrate/lifo/commands/fs/du.d.ts.map +1 -0
  264. package/dist/substrate/lifo/commands/fs/du.js +104 -0
  265. package/dist/substrate/lifo/commands/fs/file.d.ts +4 -0
  266. package/dist/substrate/lifo/commands/fs/file.d.ts.map +1 -0
  267. package/dist/substrate/lifo/commands/fs/file.js +84 -0
  268. package/dist/substrate/lifo/commands/fs/find.d.ts +4 -0
  269. package/dist/substrate/lifo/commands/fs/find.d.ts.map +1 -0
  270. package/dist/substrate/lifo/commands/fs/find.js +72 -0
  271. package/dist/substrate/lifo/commands/fs/ln.d.ts +4 -0
  272. package/dist/substrate/lifo/commands/fs/ln.d.ts.map +1 -0
  273. package/dist/substrate/lifo/commands/fs/ln.js +15 -0
  274. package/dist/substrate/lifo/commands/fs/ls.d.ts +4 -0
  275. package/dist/substrate/lifo/commands/fs/ls.d.ts.map +1 -0
  276. package/dist/substrate/lifo/commands/fs/ls.js +146 -0
  277. package/dist/substrate/lifo/commands/fs/mkdir.d.ts +4 -0
  278. package/dist/substrate/lifo/commands/fs/mkdir.d.ts.map +1 -0
  279. package/dist/substrate/lifo/commands/fs/mkdir.js +31 -0
  280. package/dist/substrate/lifo/commands/fs/mktemp.d.ts +4 -0
  281. package/dist/substrate/lifo/commands/fs/mktemp.d.ts.map +1 -0
  282. package/dist/substrate/lifo/commands/fs/mktemp.js +48 -0
  283. package/dist/substrate/lifo/commands/fs/mv.d.ts +4 -0
  284. package/dist/substrate/lifo/commands/fs/mv.d.ts.map +1 -0
  285. package/dist/substrate/lifo/commands/fs/mv.js +113 -0
  286. package/dist/substrate/lifo/commands/fs/realpath.d.ts +4 -0
  287. package/dist/substrate/lifo/commands/fs/realpath.d.ts.map +1 -0
  288. package/dist/substrate/lifo/commands/fs/realpath.js +27 -0
  289. package/dist/substrate/lifo/commands/fs/rm.d.ts +4 -0
  290. package/dist/substrate/lifo/commands/fs/rm.d.ts.map +1 -0
  291. package/dist/substrate/lifo/commands/fs/rm.js +48 -0
  292. package/dist/substrate/lifo/commands/fs/rmdir.d.ts +4 -0
  293. package/dist/substrate/lifo/commands/fs/rmdir.d.ts.map +1 -0
  294. package/dist/substrate/lifo/commands/fs/rmdir.js +44 -0
  295. package/dist/substrate/lifo/commands/fs/stat.d.ts +4 -0
  296. package/dist/substrate/lifo/commands/fs/stat.d.ts.map +1 -0
  297. package/dist/substrate/lifo/commands/fs/stat.js +35 -0
  298. package/dist/substrate/lifo/commands/fs/touch.d.ts +4 -0
  299. package/dist/substrate/lifo/commands/fs/touch.d.ts.map +1 -0
  300. package/dist/substrate/lifo/commands/fs/touch.js +26 -0
  301. package/dist/substrate/lifo/commands/fs/tree.d.ts +4 -0
  302. package/dist/substrate/lifo/commands/fs/tree.d.ts.map +1 -0
  303. package/dist/substrate/lifo/commands/fs/tree.js +69 -0
  304. package/dist/substrate/lifo/commands/index.d.ts +3 -0
  305. package/dist/substrate/lifo/commands/index.d.ts.map +1 -0
  306. package/dist/substrate/lifo/commands/index.js +1 -0
  307. package/dist/substrate/lifo/commands/io/dd.d.ts +4 -0
  308. package/dist/substrate/lifo/commands/io/dd.d.ts.map +1 -0
  309. package/dist/substrate/lifo/commands/io/dd.js +256 -0
  310. package/dist/substrate/lifo/commands/io/printf.d.ts +4 -0
  311. package/dist/substrate/lifo/commands/io/printf.d.ts.map +1 -0
  312. package/dist/substrate/lifo/commands/io/printf.js +82 -0
  313. package/dist/substrate/lifo/commands/io/tee.d.ts +4 -0
  314. package/dist/substrate/lifo/commands/io/tee.d.ts.map +1 -0
  315. package/dist/substrate/lifo/commands/io/tee.js +31 -0
  316. package/dist/substrate/lifo/commands/io/xargs.d.ts +4 -0
  317. package/dist/substrate/lifo/commands/io/xargs.d.ts.map +1 -0
  318. package/dist/substrate/lifo/commands/io/xargs.js +44 -0
  319. package/dist/substrate/lifo/commands/io/yes.d.ts +4 -0
  320. package/dist/substrate/lifo/commands/io/yes.d.ts.map +1 -0
  321. package/dist/substrate/lifo/commands/io/yes.js +10 -0
  322. package/dist/substrate/lifo/commands/net/curl.d.ts +6 -0
  323. package/dist/substrate/lifo/commands/net/curl.d.ts.map +1 -0
  324. package/dist/substrate/lifo/commands/net/curl.js +617 -0
  325. package/dist/substrate/lifo/commands/net/dig.d.ts +4 -0
  326. package/dist/substrate/lifo/commands/net/dig.d.ts.map +1 -0
  327. package/dist/substrate/lifo/commands/net/dig.js +77 -0
  328. package/dist/substrate/lifo/commands/net/forward.d.ts +14 -0
  329. package/dist/substrate/lifo/commands/net/forward.d.ts.map +1 -0
  330. package/dist/substrate/lifo/commands/net/forward.js +80 -0
  331. package/dist/substrate/lifo/commands/net/host.d.ts +14 -0
  332. package/dist/substrate/lifo/commands/net/host.d.ts.map +1 -0
  333. package/dist/substrate/lifo/commands/net/host.js +160 -0
  334. package/dist/substrate/lifo/commands/net/ifconfig.d.ts +8 -0
  335. package/dist/substrate/lifo/commands/net/ifconfig.d.ts.map +1 -0
  336. package/dist/substrate/lifo/commands/net/ifconfig.js +120 -0
  337. package/dist/substrate/lifo/commands/net/ip.d.ts +14 -0
  338. package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -0
  339. package/dist/substrate/lifo/commands/net/ip.js +508 -0
  340. package/dist/substrate/lifo/commands/net/netstat.d.ts +8 -0
  341. package/dist/substrate/lifo/commands/net/netstat.d.ts.map +1 -0
  342. package/dist/substrate/lifo/commands/net/netstat.js +94 -0
  343. package/dist/substrate/lifo/commands/net/ping.d.ts +4 -0
  344. package/dist/substrate/lifo/commands/net/ping.d.ts.map +1 -0
  345. package/dist/substrate/lifo/commands/net/ping.js +64 -0
  346. package/dist/substrate/lifo/commands/net/ports.d.ts +8 -0
  347. package/dist/substrate/lifo/commands/net/ports.d.ts.map +1 -0
  348. package/dist/substrate/lifo/commands/net/ports.js +36 -0
  349. package/dist/substrate/lifo/commands/net/route.d.ts +8 -0
  350. package/dist/substrate/lifo/commands/net/route.d.ts.map +1 -0
  351. package/dist/substrate/lifo/commands/net/route.js +128 -0
  352. package/dist/substrate/lifo/commands/net/tunnel-v2.d.ts +4 -0
  353. package/dist/substrate/lifo/commands/net/tunnel-v2.d.ts.map +1 -0
  354. package/dist/substrate/lifo/commands/net/tunnel-v2.js +141 -0
  355. package/dist/substrate/lifo/commands/net/tunnel.d.ts +6 -0
  356. package/dist/substrate/lifo/commands/net/tunnel.d.ts.map +1 -0
  357. package/dist/substrate/lifo/commands/net/tunnel.js +232 -0
  358. package/dist/substrate/lifo/commands/net/wget.d.ts +4 -0
  359. package/dist/substrate/lifo/commands/net/wget.d.ts.map +1 -0
  360. package/dist/substrate/lifo/commands/net/wget.js +78 -0
  361. package/dist/substrate/lifo/commands/registry.d.ts +15 -0
  362. package/dist/substrate/lifo/commands/registry.d.ts.map +1 -0
  363. package/dist/substrate/lifo/commands/registry.js +120 -0
  364. package/dist/substrate/lifo/commands/signal.d.ts +12 -0
  365. package/dist/substrate/lifo/commands/signal.d.ts.map +1 -0
  366. package/dist/substrate/lifo/commands/signal.js +47 -0
  367. package/dist/substrate/lifo/commands/system/bc.d.ts +4 -0
  368. package/dist/substrate/lifo/commands/system/bc.d.ts.map +1 -0
  369. package/dist/substrate/lifo/commands/system/bc.js +243 -0
  370. package/dist/substrate/lifo/commands/system/cal.d.ts +4 -0
  371. package/dist/substrate/lifo/commands/system/cal.d.ts.map +1 -0
  372. package/dist/substrate/lifo/commands/system/cal.js +81 -0
  373. package/dist/substrate/lifo/commands/system/date.d.ts +4 -0
  374. package/dist/substrate/lifo/commands/system/date.d.ts.map +1 -0
  375. package/dist/substrate/lifo/commands/system/date.js +79 -0
  376. package/dist/substrate/lifo/commands/system/env.d.ts +4 -0
  377. package/dist/substrate/lifo/commands/system/env.d.ts.map +1 -0
  378. package/dist/substrate/lifo/commands/system/env.js +7 -0
  379. package/dist/substrate/lifo/commands/system/fastfetch.d.ts +4 -0
  380. package/dist/substrate/lifo/commands/system/fastfetch.d.ts.map +1 -0
  381. package/dist/substrate/lifo/commands/system/fastfetch.js +385 -0
  382. package/dist/substrate/lifo/commands/system/free.d.ts +4 -0
  383. package/dist/substrate/lifo/commands/system/free.d.ts.map +1 -0
  384. package/dist/substrate/lifo/commands/system/free.js +36 -0
  385. package/dist/substrate/lifo/commands/system/help.d.ts +4 -0
  386. package/dist/substrate/lifo/commands/system/help.d.ts.map +1 -0
  387. package/dist/substrate/lifo/commands/system/help.js +46 -0
  388. package/dist/substrate/lifo/commands/system/hostname.d.ts +4 -0
  389. package/dist/substrate/lifo/commands/system/hostname.d.ts.map +1 -0
  390. package/dist/substrate/lifo/commands/system/hostname.js +6 -0
  391. package/dist/substrate/lifo/commands/system/kill.d.ts +4 -0
  392. package/dist/substrate/lifo/commands/system/kill.d.ts.map +1 -0
  393. package/dist/substrate/lifo/commands/system/kill.js +76 -0
  394. package/dist/substrate/lifo/commands/system/lifo.d.ts +41 -0
  395. package/dist/substrate/lifo/commands/system/lifo.d.ts.map +1 -0
  396. package/dist/substrate/lifo/commands/system/lifo.js +446 -0
  397. package/dist/substrate/lifo/commands/system/logout.d.ts +5 -0
  398. package/dist/substrate/lifo/commands/system/logout.d.ts.map +1 -0
  399. package/dist/substrate/lifo/commands/system/logout.js +15 -0
  400. package/dist/substrate/lifo/commands/system/man.d.ts +4 -0
  401. package/dist/substrate/lifo/commands/system/man.d.ts.map +1 -0
  402. package/dist/substrate/lifo/commands/system/man.js +144 -0
  403. package/dist/substrate/lifo/commands/system/node.d.ts +6 -0
  404. package/dist/substrate/lifo/commands/system/node.d.ts.map +1 -0
  405. package/dist/substrate/lifo/commands/system/node.js +1241 -0
  406. package/dist/substrate/lifo/commands/system/npm.d.ts +26 -0
  407. package/dist/substrate/lifo/commands/system/npm.d.ts.map +1 -0
  408. package/dist/substrate/lifo/commands/system/npm.js +934 -0
  409. package/dist/substrate/lifo/commands/system/pkg.d.ts +4 -0
  410. package/dist/substrate/lifo/commands/system/pkg.d.ts.map +1 -0
  411. package/dist/substrate/lifo/commands/system/pkg.js +97 -0
  412. package/dist/substrate/lifo/commands/system/ps.d.ts +5 -0
  413. package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -0
  414. package/dist/substrate/lifo/commands/system/ps.js +35 -0
  415. package/dist/substrate/lifo/commands/system/registry-schemas.d.ts +44 -0
  416. package/dist/substrate/lifo/commands/system/registry-schemas.d.ts.map +1 -0
  417. package/dist/substrate/lifo/commands/system/registry-schemas.js +28 -0
  418. package/dist/substrate/lifo/commands/system/sha256sum.d.ts +4 -0
  419. package/dist/substrate/lifo/commands/system/sha256sum.d.ts.map +1 -0
  420. package/dist/substrate/lifo/commands/system/sha256sum.js +86 -0
  421. package/dist/substrate/lifo/commands/system/sl.d.ts +4 -0
  422. package/dist/substrate/lifo/commands/system/sl.d.ts.map +1 -0
  423. package/dist/substrate/lifo/commands/system/sl.js +204 -0
  424. package/dist/substrate/lifo/commands/system/sleep.d.ts +4 -0
  425. package/dist/substrate/lifo/commands/system/sleep.d.ts.map +1 -0
  426. package/dist/substrate/lifo/commands/system/sleep.js +27 -0
  427. package/dist/substrate/lifo/commands/system/stty.d.ts +4 -0
  428. package/dist/substrate/lifo/commands/system/stty.d.ts.map +1 -0
  429. package/dist/substrate/lifo/commands/system/stty.js +61 -0
  430. package/dist/substrate/lifo/commands/system/systemctl.d.ts +4 -0
  431. package/dist/substrate/lifo/commands/system/systemctl.d.ts.map +1 -0
  432. package/dist/substrate/lifo/commands/system/systemctl.js +164 -0
  433. package/dist/substrate/lifo/commands/system/top.d.ts +4 -0
  434. package/dist/substrate/lifo/commands/system/top.d.ts.map +1 -0
  435. package/dist/substrate/lifo/commands/system/top.js +38 -0
  436. package/dist/substrate/lifo/commands/system/uname.d.ts +4 -0
  437. package/dist/substrate/lifo/commands/system/uname.d.ts.map +1 -0
  438. package/dist/substrate/lifo/commands/system/uname.js +55 -0
  439. package/dist/substrate/lifo/commands/system/uptime.d.ts +4 -0
  440. package/dist/substrate/lifo/commands/system/uptime.d.ts.map +1 -0
  441. package/dist/substrate/lifo/commands/system/uptime.js +16 -0
  442. package/dist/substrate/lifo/commands/system/watch.d.ts +4 -0
  443. package/dist/substrate/lifo/commands/system/watch.d.ts.map +1 -0
  444. package/dist/substrate/lifo/commands/system/watch.js +53 -0
  445. package/dist/substrate/lifo/commands/system/which.d.ts +4 -0
  446. package/dist/substrate/lifo/commands/system/which.d.ts.map +1 -0
  447. package/dist/substrate/lifo/commands/system/which.js +45 -0
  448. package/dist/substrate/lifo/commands/system/whoami.d.ts +4 -0
  449. package/dist/substrate/lifo/commands/system/whoami.d.ts.map +1 -0
  450. package/dist/substrate/lifo/commands/system/whoami.js +6 -0
  451. package/dist/substrate/lifo/commands/text/awk.d.ts +4 -0
  452. package/dist/substrate/lifo/commands/text/awk.d.ts.map +1 -0
  453. package/dist/substrate/lifo/commands/text/awk.js +200 -0
  454. package/dist/substrate/lifo/commands/text/base64.d.ts +4 -0
  455. package/dist/substrate/lifo/commands/text/base64.d.ts.map +1 -0
  456. package/dist/substrate/lifo/commands/text/base64.js +102 -0
  457. package/dist/substrate/lifo/commands/text/cut.d.ts +4 -0
  458. package/dist/substrate/lifo/commands/text/cut.d.ts.map +1 -0
  459. package/dist/substrate/lifo/commands/text/cut.js +91 -0
  460. package/dist/substrate/lifo/commands/text/diff.d.ts +4 -0
  461. package/dist/substrate/lifo/commands/text/diff.d.ts.map +1 -0
  462. package/dist/substrate/lifo/commands/text/diff.js +292 -0
  463. package/dist/substrate/lifo/commands/text/grep.d.ts +4 -0
  464. package/dist/substrate/lifo/commands/text/grep.d.ts.map +1 -0
  465. package/dist/substrate/lifo/commands/text/grep.js +189 -0
  466. package/dist/substrate/lifo/commands/text/head.d.ts +4 -0
  467. package/dist/substrate/lifo/commands/text/head.d.ts.map +1 -0
  468. package/dist/substrate/lifo/commands/text/head.js +111 -0
  469. package/dist/substrate/lifo/commands/text/less.d.ts +4 -0
  470. package/dist/substrate/lifo/commands/text/less.d.ts.map +1 -0
  471. package/dist/substrate/lifo/commands/text/less.js +335 -0
  472. package/dist/substrate/lifo/commands/text/nano.d.ts +4 -0
  473. package/dist/substrate/lifo/commands/text/nano.d.ts.map +1 -0
  474. package/dist/substrate/lifo/commands/text/nano.js +510 -0
  475. package/dist/substrate/lifo/commands/text/nl.d.ts +4 -0
  476. package/dist/substrate/lifo/commands/text/nl.d.ts.map +1 -0
  477. package/dist/substrate/lifo/commands/text/nl.js +64 -0
  478. package/dist/substrate/lifo/commands/text/rev.d.ts +4 -0
  479. package/dist/substrate/lifo/commands/text/rev.d.ts.map +1 -0
  480. package/dist/substrate/lifo/commands/text/rev.js +41 -0
  481. package/dist/substrate/lifo/commands/text/sed.d.ts +21 -0
  482. package/dist/substrate/lifo/commands/text/sed.d.ts.map +1 -0
  483. package/dist/substrate/lifo/commands/text/sed.js +248 -0
  484. package/dist/substrate/lifo/commands/text/seq.d.ts +4 -0
  485. package/dist/substrate/lifo/commands/text/seq.d.ts.map +1 -0
  486. package/dist/substrate/lifo/commands/text/seq.js +69 -0
  487. package/dist/substrate/lifo/commands/text/sort.d.ts +4 -0
  488. package/dist/substrate/lifo/commands/text/sort.d.ts.map +1 -0
  489. package/dist/substrate/lifo/commands/text/sort.js +90 -0
  490. package/dist/substrate/lifo/commands/text/strings.d.ts +4 -0
  491. package/dist/substrate/lifo/commands/text/strings.d.ts.map +1 -0
  492. package/dist/substrate/lifo/commands/text/strings.js +69 -0
  493. package/dist/substrate/lifo/commands/text/tac.d.ts +4 -0
  494. package/dist/substrate/lifo/commands/text/tac.d.ts.map +1 -0
  495. package/dist/substrate/lifo/commands/text/tac.js +32 -0
  496. package/dist/substrate/lifo/commands/text/tail.d.ts +4 -0
  497. package/dist/substrate/lifo/commands/text/tail.d.ts.map +1 -0
  498. package/dist/substrate/lifo/commands/text/tail.js +64 -0
  499. package/dist/substrate/lifo/commands/text/tr.d.ts +4 -0
  500. package/dist/substrate/lifo/commands/text/tr.d.ts.map +1 -0
  501. package/dist/substrate/lifo/commands/text/tr.js +180 -0
  502. package/dist/substrate/lifo/commands/text/uniq.d.ts +4 -0
  503. package/dist/substrate/lifo/commands/text/uniq.d.ts.map +1 -0
  504. package/dist/substrate/lifo/commands/text/uniq.js +82 -0
  505. package/dist/substrate/lifo/commands/text/wc.d.ts +4 -0
  506. package/dist/substrate/lifo/commands/text/wc.d.ts.map +1 -0
  507. package/dist/substrate/lifo/commands/text/wc.js +107 -0
  508. package/dist/substrate/lifo/commands/types.d.ts +49 -0
  509. package/dist/substrate/lifo/commands/types.d.ts.map +1 -0
  510. package/dist/substrate/lifo/commands/types.js +1 -0
  511. package/dist/substrate/lifo/index.d.ts +67 -0
  512. package/dist/substrate/lifo/index.d.ts.map +1 -0
  513. package/dist/substrate/lifo/index.js +66 -0
  514. package/dist/substrate/lifo/kernel/ServiceManager.d.ts +51 -0
  515. package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -0
  516. package/dist/substrate/lifo/kernel/ServiceManager.js +302 -0
  517. package/dist/substrate/lifo/kernel/index.d.ts +40 -0
  518. package/dist/substrate/lifo/kernel/index.d.ts.map +1 -0
  519. package/dist/substrate/lifo/kernel/index.js +170 -0
  520. package/dist/substrate/lifo/kernel/network/Bridge.d.ts +110 -0
  521. package/dist/substrate/lifo/kernel/network/Bridge.d.ts.map +1 -0
  522. package/dist/substrate/lifo/kernel/network/Bridge.js +278 -0
  523. package/dist/substrate/lifo/kernel/network/DNSResolver.d.ts +50 -0
  524. package/dist/substrate/lifo/kernel/network/DNSResolver.d.ts.map +1 -0
  525. package/dist/substrate/lifo/kernel/network/DNSResolver.js +135 -0
  526. package/dist/substrate/lifo/kernel/network/NetworkInterface.d.ts +83 -0
  527. package/dist/substrate/lifo/kernel/network/NetworkInterface.d.ts.map +1 -0
  528. package/dist/substrate/lifo/kernel/network/NetworkInterface.js +183 -0
  529. package/dist/substrate/lifo/kernel/network/NetworkNamespace.d.ts +98 -0
  530. package/dist/substrate/lifo/kernel/network/NetworkNamespace.d.ts.map +1 -0
  531. package/dist/substrate/lifo/kernel/network/NetworkNamespace.js +149 -0
  532. package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +178 -0
  533. package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -0
  534. package/dist/substrate/lifo/kernel/network/NetworkStack.js +460 -0
  535. package/dist/substrate/lifo/kernel/network/PortBridge.d.ts +44 -0
  536. package/dist/substrate/lifo/kernel/network/PortBridge.d.ts.map +1 -0
  537. package/dist/substrate/lifo/kernel/network/PortBridge.js +158 -0
  538. package/dist/substrate/lifo/kernel/network/RoutingTable.d.ts +55 -0
  539. package/dist/substrate/lifo/kernel/network/RoutingTable.d.ts.map +1 -0
  540. package/dist/substrate/lifo/kernel/network/RoutingTable.js +167 -0
  541. package/dist/substrate/lifo/kernel/network/Socket.d.ts +65 -0
  542. package/dist/substrate/lifo/kernel/network/Socket.d.ts.map +1 -0
  543. package/dist/substrate/lifo/kernel/network/Socket.js +201 -0
  544. package/dist/substrate/lifo/kernel/network/index.d.ts +11 -0
  545. package/dist/substrate/lifo/kernel/network/index.d.ts.map +1 -0
  546. package/dist/substrate/lifo/kernel/network/index.js +9 -0
  547. package/dist/substrate/lifo/kernel/network/tunnel/BaseTunnel.d.ts +55 -0
  548. package/dist/substrate/lifo/kernel/network/tunnel/BaseTunnel.d.ts.map +1 -0
  549. package/dist/substrate/lifo/kernel/network/tunnel/BaseTunnel.js +88 -0
  550. package/dist/substrate/lifo/kernel/network/tunnel/VETHPair.d.ts +71 -0
  551. package/dist/substrate/lifo/kernel/network/tunnel/VETHPair.d.ts.map +1 -0
  552. package/dist/substrate/lifo/kernel/network/tunnel/VETHPair.js +191 -0
  553. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +89 -0
  554. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -0
  555. package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +324 -0
  556. package/dist/substrate/lifo/kernel/network/types.d.ts +159 -0
  557. package/dist/substrate/lifo/kernel/network/types.d.ts.map +1 -0
  558. package/dist/substrate/lifo/kernel/network/types.js +5 -0
  559. package/dist/substrate/lifo/kernel/persistence/PersistenceManager.d.ts +12 -0
  560. package/dist/substrate/lifo/kernel/persistence/PersistenceManager.d.ts.map +1 -0
  561. package/dist/substrate/lifo/kernel/persistence/PersistenceManager.js +47 -0
  562. package/dist/substrate/lifo/kernel/persistence/backends.d.ts +15 -0
  563. package/dist/substrate/lifo/kernel/persistence/backends.d.ts.map +1 -0
  564. package/dist/substrate/lifo/kernel/persistence/backends.js +18 -0
  565. package/dist/substrate/lifo/kernel/persistence/index.d.ts +6 -0
  566. package/dist/substrate/lifo/kernel/persistence/index.d.ts.map +1 -0
  567. package/dist/substrate/lifo/kernel/persistence/index.js +3 -0
  568. package/dist/substrate/lifo/kernel/persistence/serializer.d.ts +21 -0
  569. package/dist/substrate/lifo/kernel/persistence/serializer.d.ts.map +1 -0
  570. package/dist/substrate/lifo/kernel/persistence/serializer.js +99 -0
  571. package/dist/substrate/lifo/kernel/samples.d.ts +3 -0
  572. package/dist/substrate/lifo/kernel/samples.d.ts.map +1 -0
  573. package/dist/substrate/lifo/kernel/samples.js +930 -0
  574. package/dist/substrate/lifo/kernel/storage/BlobStore.d.ts +22 -0
  575. package/dist/substrate/lifo/kernel/storage/BlobStore.d.ts.map +1 -0
  576. package/dist/substrate/lifo/kernel/storage/BlobStore.js +84 -0
  577. package/dist/substrate/lifo/kernel/storage/ContentStore.d.ts +54 -0
  578. package/dist/substrate/lifo/kernel/storage/ContentStore.d.ts.map +1 -0
  579. package/dist/substrate/lifo/kernel/storage/ContentStore.js +120 -0
  580. package/dist/substrate/lifo/kernel/storage/index.d.ts +6 -0
  581. package/dist/substrate/lifo/kernel/storage/index.d.ts.map +1 -0
  582. package/dist/substrate/lifo/kernel/storage/index.js +3 -0
  583. package/dist/substrate/lifo/kernel/unit-parser.d.ts +22 -0
  584. package/dist/substrate/lifo/kernel/unit-parser.d.ts.map +1 -0
  585. package/dist/substrate/lifo/kernel/unit-parser.js +80 -0
  586. package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +99 -0
  587. package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -0
  588. package/dist/substrate/lifo/kernel/vfs/VFS.js +679 -0
  589. package/dist/substrate/lifo/kernel/vfs/index.d.ts +8 -0
  590. package/dist/substrate/lifo/kernel/vfs/index.d.ts.map +1 -0
  591. package/dist/substrate/lifo/kernel/vfs/index.js +5 -0
  592. package/dist/substrate/lifo/kernel/vfs/providers/DevProvider.d.ts +21 -0
  593. package/dist/substrate/lifo/kernel/vfs/providers/DevProvider.d.ts.map +1 -0
  594. package/dist/substrate/lifo/kernel/vfs/providers/DevProvider.js +137 -0
  595. package/dist/substrate/lifo/kernel/vfs/providers/NativeFsProvider.d.ts +63 -0
  596. package/dist/substrate/lifo/kernel/vfs/providers/NativeFsProvider.d.ts.map +1 -0
  597. package/dist/substrate/lifo/kernel/vfs/providers/NativeFsProvider.js +175 -0
  598. package/dist/substrate/lifo/kernel/vfs/providers/ProcProvider.d.ts +14 -0
  599. package/dist/substrate/lifo/kernel/vfs/providers/ProcProvider.d.ts.map +1 -0
  600. package/dist/substrate/lifo/kernel/vfs/providers/ProcProvider.js +126 -0
  601. package/dist/substrate/lifo/kernel/vfs/types.d.ts +117 -0
  602. package/dist/substrate/lifo/kernel/vfs/types.d.ts.map +1 -0
  603. package/dist/substrate/lifo/kernel/vfs/types.js +42 -0
  604. package/dist/substrate/lifo/node-compat/buffer.d.ts +44 -0
  605. package/dist/substrate/lifo/node-compat/buffer.d.ts.map +1 -0
  606. package/dist/substrate/lifo/node-compat/buffer.js +331 -0
  607. package/dist/substrate/lifo/node-compat/child_process.d.ts +10 -0
  608. package/dist/substrate/lifo/node-compat/child_process.d.ts.map +1 -0
  609. package/dist/substrate/lifo/node-compat/child_process.js +42 -0
  610. package/dist/substrate/lifo/node-compat/console.d.ts +21 -0
  611. package/dist/substrate/lifo/node-compat/console.d.ts.map +1 -0
  612. package/dist/substrate/lifo/node-compat/console.js +68 -0
  613. package/dist/substrate/lifo/node-compat/crypto.d.ts +24 -0
  614. package/dist/substrate/lifo/node-compat/crypto.d.ts.map +1 -0
  615. package/dist/substrate/lifo/node-compat/crypto.js +216 -0
  616. package/dist/substrate/lifo/node-compat/dns.d.ts +117 -0
  617. package/dist/substrate/lifo/node-compat/dns.d.ts.map +1 -0
  618. package/dist/substrate/lifo/node-compat/dns.js +144 -0
  619. package/dist/substrate/lifo/node-compat/esbuild.d.ts +10 -0
  620. package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -0
  621. package/dist/substrate/lifo/node-compat/esbuild.js +70 -0
  622. package/dist/substrate/lifo/node-compat/events.d.ts +19 -0
  623. package/dist/substrate/lifo/node-compat/events.d.ts.map +1 -0
  624. package/dist/substrate/lifo/node-compat/events.js +83 -0
  625. package/dist/substrate/lifo/node-compat/fs.d.ts +195 -0
  626. package/dist/substrate/lifo/node-compat/fs.d.ts.map +1 -0
  627. package/dist/substrate/lifo/node-compat/fs.js +624 -0
  628. package/dist/substrate/lifo/node-compat/http.d.ts +133 -0
  629. package/dist/substrate/lifo/node-compat/http.d.ts.map +1 -0
  630. package/dist/substrate/lifo/node-compat/http.js +452 -0
  631. package/dist/substrate/lifo/node-compat/index.d.ts +20 -0
  632. package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -0
  633. package/dist/substrate/lifo/node-compat/index.js +291 -0
  634. package/dist/substrate/lifo/node-compat/module.d.ts +55 -0
  635. package/dist/substrate/lifo/node-compat/module.d.ts.map +1 -0
  636. package/dist/substrate/lifo/node-compat/module.js +124 -0
  637. package/dist/substrate/lifo/node-compat/os.d.ts +39 -0
  638. package/dist/substrate/lifo/node-compat/os.d.ts.map +1 -0
  639. package/dist/substrate/lifo/node-compat/os.js +45 -0
  640. package/dist/substrate/lifo/node-compat/path.d.ts +86 -0
  641. package/dist/substrate/lifo/node-compat/path.d.ts.map +1 -0
  642. package/dist/substrate/lifo/node-compat/path.js +49 -0
  643. package/dist/substrate/lifo/node-compat/process.d.ts +127 -0
  644. package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -0
  645. package/dist/substrate/lifo/node-compat/process.js +173 -0
  646. package/dist/substrate/lifo/node-compat/readline.d.ts +85 -0
  647. package/dist/substrate/lifo/node-compat/readline.d.ts.map +1 -0
  648. package/dist/substrate/lifo/node-compat/readline.js +205 -0
  649. package/dist/substrate/lifo/node-compat/rimraf.d.ts +30 -0
  650. package/dist/substrate/lifo/node-compat/rimraf.d.ts.map +1 -0
  651. package/dist/substrate/lifo/node-compat/rimraf.js +63 -0
  652. package/dist/substrate/lifo/node-compat/stream.d.ts +36 -0
  653. package/dist/substrate/lifo/node-compat/stream.d.ts.map +1 -0
  654. package/dist/substrate/lifo/node-compat/stream.js +90 -0
  655. package/dist/substrate/lifo/node-compat/string_decoder.d.ts +11 -0
  656. package/dist/substrate/lifo/node-compat/string_decoder.d.ts.map +1 -0
  657. package/dist/substrate/lifo/node-compat/string_decoder.js +23 -0
  658. package/dist/substrate/lifo/node-compat/timers.d.ts +16 -0
  659. package/dist/substrate/lifo/node-compat/timers.d.ts.map +1 -0
  660. package/dist/substrate/lifo/node-compat/timers.js +18 -0
  661. package/dist/substrate/lifo/node-compat/tty.d.ts +32 -0
  662. package/dist/substrate/lifo/node-compat/tty.d.ts.map +1 -0
  663. package/dist/substrate/lifo/node-compat/tty.js +76 -0
  664. package/dist/substrate/lifo/node-compat/url.d.ts +37 -0
  665. package/dist/substrate/lifo/node-compat/url.d.ts.map +1 -0
  666. package/dist/substrate/lifo/node-compat/url.js +65 -0
  667. package/dist/substrate/lifo/node-compat/util.d.ts +42 -0
  668. package/dist/substrate/lifo/node-compat/util.d.ts.map +1 -0
  669. package/dist/substrate/lifo/node-compat/util.js +132 -0
  670. package/dist/substrate/lifo/node-compat/zlib.d.ts +66 -0
  671. package/dist/substrate/lifo/node-compat/zlib.d.ts.map +1 -0
  672. package/dist/substrate/lifo/node-compat/zlib.js +80 -0
  673. package/dist/substrate/lifo/pkg/PackageManager.d.ts +19 -0
  674. package/dist/substrate/lifo/pkg/PackageManager.d.ts.map +1 -0
  675. package/dist/substrate/lifo/pkg/PackageManager.js +97 -0
  676. package/dist/substrate/lifo/pkg/lifo-dev.d.ts +38 -0
  677. package/dist/substrate/lifo/pkg/lifo-dev.d.ts.map +1 -0
  678. package/dist/substrate/lifo/pkg/lifo-dev.js +98 -0
  679. package/dist/substrate/lifo/pkg/lifo-runtime.d.ts +44 -0
  680. package/dist/substrate/lifo/pkg/lifo-runtime.d.ts.map +1 -0
  681. package/dist/substrate/lifo/pkg/lifo-runtime.js +372 -0
  682. package/dist/substrate/lifo/pkg/loader.d.ts +4 -0
  683. package/dist/substrate/lifo/pkg/loader.d.ts.map +1 -0
  684. package/dist/substrate/lifo/pkg/loader.js +24 -0
  685. package/dist/substrate/lifo/sandbox/HeadlessTerminal.d.ts +18 -0
  686. package/dist/substrate/lifo/sandbox/HeadlessTerminal.d.ts.map +1 -0
  687. package/dist/substrate/lifo/sandbox/HeadlessTerminal.js +28 -0
  688. package/dist/substrate/lifo/sandbox/Sandbox.d.ts +59 -0
  689. package/dist/substrate/lifo/sandbox/Sandbox.d.ts.map +1 -0
  690. package/dist/substrate/lifo/sandbox/Sandbox.js +234 -0
  691. package/dist/substrate/lifo/sandbox/SandboxCommands.d.ts +18 -0
  692. package/dist/substrate/lifo/sandbox/SandboxCommands.d.ts.map +1 -0
  693. package/dist/substrate/lifo/sandbox/SandboxCommands.js +64 -0
  694. package/dist/substrate/lifo/sandbox/SandboxFs.d.ts +43 -0
  695. package/dist/substrate/lifo/sandbox/SandboxFs.d.ts.map +1 -0
  696. package/dist/substrate/lifo/sandbox/SandboxFs.js +136 -0
  697. package/dist/substrate/lifo/sandbox/index.d.ts +3 -0
  698. package/dist/substrate/lifo/sandbox/index.d.ts.map +1 -0
  699. package/dist/substrate/lifo/sandbox/index.js +1 -0
  700. package/dist/substrate/lifo/sandbox/types.d.ts +116 -0
  701. package/dist/substrate/lifo/sandbox/types.d.ts.map +1 -0
  702. package/dist/substrate/lifo/sandbox/types.js +1 -0
  703. package/dist/substrate/lifo/shell/ProcessRegistry.d.ts +142 -0
  704. package/dist/substrate/lifo/shell/ProcessRegistry.d.ts.map +1 -0
  705. package/dist/substrate/lifo/shell/ProcessRegistry.js +256 -0
  706. package/dist/substrate/lifo/shell/Shell.d.ts +170 -0
  707. package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -0
  708. package/dist/substrate/lifo/shell/Shell.js +1863 -0
  709. package/dist/substrate/lifo/shell/completer.d.ts +19 -0
  710. package/dist/substrate/lifo/shell/completer.d.ts.map +1 -0
  711. package/dist/substrate/lifo/shell/completer.js +164 -0
  712. package/dist/substrate/lifo/shell/default-shell.d.ts +9 -0
  713. package/dist/substrate/lifo/shell/default-shell.d.ts.map +1 -0
  714. package/dist/substrate/lifo/shell/default-shell.js +53 -0
  715. package/dist/substrate/lifo/shell/expander.d.ts +48 -0
  716. package/dist/substrate/lifo/shell/expander.d.ts.map +1 -0
  717. package/dist/substrate/lifo/shell/expander.js +1204 -0
  718. package/dist/substrate/lifo/shell/history.d.ts +22 -0
  719. package/dist/substrate/lifo/shell/history.d.ts.map +1 -0
  720. package/dist/substrate/lifo/shell/history.js +121 -0
  721. package/dist/substrate/lifo/shell/interpreter.d.ts +257 -0
  722. package/dist/substrate/lifo/shell/interpreter.d.ts.map +1 -0
  723. package/dist/substrate/lifo/shell/interpreter.js +1490 -0
  724. package/dist/substrate/lifo/shell/jobs.d.ts +21 -0
  725. package/dist/substrate/lifo/shell/jobs.d.ts.map +1 -0
  726. package/dist/substrate/lifo/shell/jobs.js +48 -0
  727. package/dist/substrate/lifo/shell/lexer.d.ts +13 -0
  728. package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -0
  729. package/dist/substrate/lifo/shell/lexer.js +614 -0
  730. package/dist/substrate/lifo/shell/parser.d.ts +7 -0
  731. package/dist/substrate/lifo/shell/parser.d.ts.map +1 -0
  732. package/dist/substrate/lifo/shell/parser.js +573 -0
  733. package/dist/substrate/lifo/shell/pipe.d.ts +15 -0
  734. package/dist/substrate/lifo/shell/pipe.d.ts.map +1 -0
  735. package/dist/substrate/lifo/shell/pipe.js +86 -0
  736. package/dist/substrate/lifo/shell/signals.d.ts +11 -0
  737. package/dist/substrate/lifo/shell/signals.d.ts.map +1 -0
  738. package/dist/substrate/lifo/shell/signals.js +57 -0
  739. package/dist/substrate/lifo/shell/terminal-stdin.d.ts +28 -0
  740. package/dist/substrate/lifo/shell/terminal-stdin.d.ts.map +1 -0
  741. package/dist/substrate/lifo/shell/terminal-stdin.js +100 -0
  742. package/dist/substrate/lifo/shell/test-builtin.d.ts +16 -0
  743. package/dist/substrate/lifo/shell/test-builtin.d.ts.map +1 -0
  744. package/dist/substrate/lifo/shell/test-builtin.js +237 -0
  745. package/dist/substrate/lifo/shell/types.d.ts +140 -0
  746. package/dist/substrate/lifo/shell/types.d.ts.map +1 -0
  747. package/dist/substrate/lifo/shell/types.js +25 -0
  748. package/dist/substrate/lifo/terminal/ITerminal.d.ts +10 -0
  749. package/dist/substrate/lifo/terminal/ITerminal.d.ts.map +1 -0
  750. package/dist/substrate/lifo/terminal/ITerminal.js +1 -0
  751. package/dist/substrate/lifo/utils/archive.d.ts +28 -0
  752. package/dist/substrate/lifo/utils/archive.d.ts.map +1 -0
  753. package/dist/substrate/lifo/utils/archive.js +323 -0
  754. package/dist/substrate/lifo/utils/args.d.ts +18 -0
  755. package/dist/substrate/lifo/utils/args.d.ts.map +1 -0
  756. package/dist/substrate/lifo/utils/args.js +73 -0
  757. package/dist/substrate/lifo/utils/colors.d.ts +27 -0
  758. package/dist/substrate/lifo/utils/colors.d.ts.map +1 -0
  759. package/dist/substrate/lifo/utils/colors.js +26 -0
  760. package/dist/substrate/lifo/utils/encoding.d.ts +4 -0
  761. package/dist/substrate/lifo/utils/encoding.d.ts.map +1 -0
  762. package/dist/substrate/lifo/utils/encoding.js +18 -0
  763. package/dist/substrate/lifo/utils/glob.d.ts +12 -0
  764. package/dist/substrate/lifo/utils/glob.d.ts.map +1 -0
  765. package/dist/substrate/lifo/utils/glob.js +150 -0
  766. package/dist/substrate/lifo/utils/mime.d.ts +4 -0
  767. package/dist/substrate/lifo/utils/mime.d.ts.map +1 -0
  768. package/dist/substrate/lifo/utils/mime.js +107 -0
  769. package/dist/substrate/lifo/utils/path.d.ts +12 -0
  770. package/dist/substrate/lifo/utils/path.d.ts.map +1 -0
  771. package/dist/substrate/lifo/utils/path.js +82 -0
  772. package/dist/vfs/events.d.ts +51 -0
  773. package/dist/vfs/events.d.ts.map +1 -0
  774. package/dist/vfs/events.js +127 -0
  775. package/dist/vfs/path.d.ts +46 -0
  776. package/dist/vfs/path.d.ts.map +1 -0
  777. package/dist/vfs/path.js +69 -0
  778. package/dist/vfs/seed-project.d.ts +62 -0
  779. package/dist/vfs/seed-project.d.ts.map +1 -0
  780. package/dist/vfs/seed-project.js +1054 -0
  781. package/dist/vfs/sqlite-vfs.d.ts +821 -0
  782. package/dist/vfs/sqlite-vfs.d.ts.map +1 -0
  783. package/dist/vfs/sqlite-vfs.js +4135 -0
  784. package/dist/vfs/symlink-registry.d.ts +60 -0
  785. package/dist/vfs/symlink-registry.d.ts.map +1 -0
  786. package/dist/vfs/symlink-registry.js +162 -0
  787. package/dist/workspace/nimbus-workspace.d.ts +94 -0
  788. package/dist/workspace/nimbus-workspace.d.ts.map +1 -0
  789. package/dist/workspace/nimbus-workspace.js +167 -0
  790. package/package.json +69 -0
  791. package/src/_shared/ai-egress.ts +91 -0
  792. package/src/_shared/byte-stream.ts +97 -0
  793. package/src/_shared/bytes.ts +19 -0
  794. package/src/_shared/cache-stats.ts +231 -0
  795. package/src/_shared/crypto.ts +184 -0
  796. package/src/_shared/esbuild-facet-shim.ts +22 -0
  797. package/src/_shared/exports-resolver.ts +308 -0
  798. package/src/_shared/facet-vfs-cursor.ts +58 -0
  799. package/src/_shared/install-phase.ts +56 -0
  800. package/src/_shared/lru-map.ts +59 -0
  801. package/src/_shared/real-node-imports.ts +53 -0
  802. package/src/_shared/retry.ts +202 -0
  803. package/src/_shared/rpc-dispose.ts +31 -0
  804. package/src/_shared/terminal.ts +3 -0
  805. package/src/_shared/typescript-specifiers.ts +86 -0
  806. package/src/_shared/untrusted-request.ts +75 -0
  807. package/src/_shared/vfs-write-ledger.ts +440 -0
  808. package/src/_shared/w7-frame.ts +1012 -0
  809. package/src/_shared/weighted-credit-pool.ts +244 -0
  810. package/src/constants.ts +285 -0
  811. package/src/index.ts +22 -0
  812. package/src/observability/diag-counters.ts +343 -0
  813. package/src/observability/heap-estimate.ts +331 -0
  814. package/src/observability/heavy-alloc-coord.ts +241 -0
  815. package/src/observability/oom-classify.ts +253 -0
  816. package/src/observability/oom-discriminator.ts +405 -0
  817. package/src/runtime/assets-loader.ts +189 -0
  818. package/src/runtime/barrel-detect.ts +69 -0
  819. package/src/runtime/bash/preamble.ts +1519 -0
  820. package/src/runtime/bash/types.ts +497 -0
  821. package/src/runtime/bundle-profile.ts +32 -0
  822. package/src/runtime/cirrus-vite-config-rewriter.ts +28 -0
  823. package/src/runtime/cli-flags.ts +11 -0
  824. package/src/runtime/comment-strip.ts +73 -0
  825. package/src/runtime/cpython-preamble.ts +298 -0
  826. package/src/runtime/esbuild-service.ts +1470 -0
  827. package/src/runtime/framework-detect.ts +245 -0
  828. package/src/runtime/import-meta-transform.ts +16 -0
  829. package/src/runtime/javascript-ast.ts +66 -0
  830. package/src/runtime/long-running-handle.ts +185 -0
  831. package/src/runtime/module-source-rewriter.ts +95 -0
  832. package/src/runtime/opentui-wasm-backend.ts +608 -0
  833. package/src/runtime/os-contracts.ts +529 -0
  834. package/src/runtime/port-registry.ts +298 -0
  835. package/src/runtime/process-input-routing.ts +92 -0
  836. package/src/runtime/process-input.ts +148 -0
  837. package/src/runtime/process-io-protocol.ts +97 -0
  838. package/src/runtime/process-logs.ts +882 -0
  839. package/src/runtime/process-table.ts +225 -0
  840. package/src/runtime/project-detect.ts +42 -0
  841. package/src/runtime/python-server-adapter.ts +133 -0
  842. package/src/runtime/require-resolver.ts +760 -0
  843. package/src/runtime/router-basename.ts +279 -0
  844. package/src/runtime/ruby-green-threads.ts +904 -0
  845. package/src/runtime/ruby-socket-shim.ts +548 -0
  846. package/src/runtime/session-process-supervisor.ts +323 -0
  847. package/src/runtime/sqlite-runtime-fs-bridge.ts +468 -0
  848. package/src/runtime/streams.ts +547 -0
  849. package/src/runtime/undici-shim.ts +517 -0
  850. package/src/runtime/vfs-manifest.ts +170 -0
  851. package/src/runtime/vfs-snapshot.ts +273 -0
  852. package/src/runtime/virtual-socket-kernel.ts +1370 -0
  853. package/src/runtime/vite-config-parser.ts +273 -0
  854. package/src/runtime/wasi/types.ts +448 -0
  855. package/src/runtime/wasi-instance.generated.ts +14 -0
  856. package/src/runtime/wasi-instance.ts +259 -0
  857. package/src/runtime/wasi-threads.ts +682 -0
  858. package/src/runtime/wasm-memory.ts +261 -0
  859. package/src/shell/elevation-commands.ts +117 -0
  860. package/src/shell/exec-dispatch.ts +201 -0
  861. package/src/shell/features.ts +870 -0
  862. package/src/shell/shell-entrypoints.ts +323 -0
  863. package/src/shell/shell-invocation.ts +181 -0
  864. package/src/shell/unix-accounts.ts +128 -0
  865. package/src/shell/unix-commands.ts +4335 -0
  866. package/src/substrate/lifo/UPSTREAM.md +14 -0
  867. package/src/substrate/lifo/commands/archive/gunzip.ts +63 -0
  868. package/src/substrate/lifo/commands/archive/gzip.ts +76 -0
  869. package/src/substrate/lifo/commands/archive/tar.ts +157 -0
  870. package/src/substrate/lifo/commands/archive/unzip.ts +112 -0
  871. package/src/substrate/lifo/commands/archive/zip.ts +70 -0
  872. package/src/substrate/lifo/commands/fs/basename.ts +16 -0
  873. package/src/substrate/lifo/commands/fs/cat.ts +54 -0
  874. package/src/substrate/lifo/commands/fs/chmod.ts +110 -0
  875. package/src/substrate/lifo/commands/fs/chown.ts +45 -0
  876. package/src/substrate/lifo/commands/fs/cp.ts +33 -0
  877. package/src/substrate/lifo/commands/fs/df.ts +56 -0
  878. package/src/substrate/lifo/commands/fs/dirname.ts +16 -0
  879. package/src/substrate/lifo/commands/fs/du.ts +100 -0
  880. package/src/substrate/lifo/commands/fs/file.ts +90 -0
  881. package/src/substrate/lifo/commands/fs/find.ts +76 -0
  882. package/src/substrate/lifo/commands/fs/ln.ts +19 -0
  883. package/src/substrate/lifo/commands/fs/ls.ts +164 -0
  884. package/src/substrate/lifo/commands/fs/mkdir.ts +37 -0
  885. package/src/substrate/lifo/commands/fs/mktemp.ts +56 -0
  886. package/src/substrate/lifo/commands/fs/mv.ts +111 -0
  887. package/src/substrate/lifo/commands/fs/realpath.ts +31 -0
  888. package/src/substrate/lifo/commands/fs/rm.ts +53 -0
  889. package/src/substrate/lifo/commands/fs/rmdir.ts +50 -0
  890. package/src/substrate/lifo/commands/fs/stat.ts +39 -0
  891. package/src/substrate/lifo/commands/fs/touch.ts +30 -0
  892. package/src/substrate/lifo/commands/fs/tree.ts +76 -0
  893. package/src/substrate/lifo/commands/index.ts +2 -0
  894. package/src/substrate/lifo/commands/io/dd.ts +289 -0
  895. package/src/substrate/lifo/commands/io/printf.ts +78 -0
  896. package/src/substrate/lifo/commands/io/tee.ts +37 -0
  897. package/src/substrate/lifo/commands/io/xargs.ts +49 -0
  898. package/src/substrate/lifo/commands/io/yes.ts +15 -0
  899. package/src/substrate/lifo/commands/net/curl.ts +739 -0
  900. package/src/substrate/lifo/commands/net/dig.ts +99 -0
  901. package/src/substrate/lifo/commands/net/forward.ts +90 -0
  902. package/src/substrate/lifo/commands/net/host.ts +182 -0
  903. package/src/substrate/lifo/commands/net/ifconfig.ts +139 -0
  904. package/src/substrate/lifo/commands/net/ip.ts +633 -0
  905. package/src/substrate/lifo/commands/net/netstat.ts +106 -0
  906. package/src/substrate/lifo/commands/net/ping.ts +74 -0
  907. package/src/substrate/lifo/commands/net/ports.ts +45 -0
  908. package/src/substrate/lifo/commands/net/route.ts +153 -0
  909. package/src/substrate/lifo/commands/net/tunnel-v2.ts +183 -0
  910. package/src/substrate/lifo/commands/net/tunnel.ts +272 -0
  911. package/src/substrate/lifo/commands/net/wget.ts +88 -0
  912. package/src/substrate/lifo/commands/registry.ts +141 -0
  913. package/src/substrate/lifo/commands/signal.ts +65 -0
  914. package/src/substrate/lifo/commands/system/bc.ts +265 -0
  915. package/src/substrate/lifo/commands/system/cal.ts +90 -0
  916. package/src/substrate/lifo/commands/system/date.ts +59 -0
  917. package/src/substrate/lifo/commands/system/env.ts +10 -0
  918. package/src/substrate/lifo/commands/system/fastfetch.ts +428 -0
  919. package/src/substrate/lifo/commands/system/free.ts +38 -0
  920. package/src/substrate/lifo/commands/system/help.ts +53 -0
  921. package/src/substrate/lifo/commands/system/hostname.ts +9 -0
  922. package/src/substrate/lifo/commands/system/kill.ts +89 -0
  923. package/src/substrate/lifo/commands/system/lifo.ts +525 -0
  924. package/src/substrate/lifo/commands/system/logout.ts +17 -0
  925. package/src/substrate/lifo/commands/system/man.ts +167 -0
  926. package/src/substrate/lifo/commands/system/node.ts +1305 -0
  927. package/src/substrate/lifo/commands/system/npm.ts +1078 -0
  928. package/src/substrate/lifo/commands/system/pkg.ts +111 -0
  929. package/src/substrate/lifo/commands/system/ps.ts +46 -0
  930. package/src/substrate/lifo/commands/system/registry-schemas.ts +33 -0
  931. package/src/substrate/lifo/commands/system/sha256sum.ts +89 -0
  932. package/src/substrate/lifo/commands/system/sl.ts +249 -0
  933. package/src/substrate/lifo/commands/system/sleep.ts +36 -0
  934. package/src/substrate/lifo/commands/system/stty.ts +69 -0
  935. package/src/substrate/lifo/commands/system/systemctl.ts +184 -0
  936. package/src/substrate/lifo/commands/system/top.ts +53 -0
  937. package/src/substrate/lifo/commands/system/uname.ts +59 -0
  938. package/src/substrate/lifo/commands/system/uptime.ts +19 -0
  939. package/src/substrate/lifo/commands/system/watch.ts +67 -0
  940. package/src/substrate/lifo/commands/system/which.ts +51 -0
  941. package/src/substrate/lifo/commands/system/whoami.ts +9 -0
  942. package/src/substrate/lifo/commands/text/awk.ts +210 -0
  943. package/src/substrate/lifo/commands/text/base64.ts +102 -0
  944. package/src/substrate/lifo/commands/text/cut.ts +92 -0
  945. package/src/substrate/lifo/commands/text/diff.ts +314 -0
  946. package/src/substrate/lifo/commands/text/grep.ts +170 -0
  947. package/src/substrate/lifo/commands/text/head.ts +95 -0
  948. package/src/substrate/lifo/commands/text/less.ts +335 -0
  949. package/src/substrate/lifo/commands/text/nano.ts +513 -0
  950. package/src/substrate/lifo/commands/text/nl.ts +74 -0
  951. package/src/substrate/lifo/commands/text/rev.ts +46 -0
  952. package/src/substrate/lifo/commands/text/sed.ts +297 -0
  953. package/src/substrate/lifo/commands/text/seq.ts +76 -0
  954. package/src/substrate/lifo/commands/text/sort.ts +88 -0
  955. package/src/substrate/lifo/commands/text/strings.ts +73 -0
  956. package/src/substrate/lifo/commands/text/tac.ts +37 -0
  957. package/src/substrate/lifo/commands/text/tail.ts +63 -0
  958. package/src/substrate/lifo/commands/text/tr.ts +192 -0
  959. package/src/substrate/lifo/commands/text/uniq.ts +77 -0
  960. package/src/substrate/lifo/commands/text/wc.ts +104 -0
  961. package/src/substrate/lifo/commands/types.ts +58 -0
  962. package/src/substrate/lifo/index.ts +173 -0
  963. package/src/substrate/lifo/kernel/ServiceManager.ts +371 -0
  964. package/src/substrate/lifo/kernel/index.ts +209 -0
  965. package/src/substrate/lifo/kernel/network/Bridge.ts +336 -0
  966. package/src/substrate/lifo/kernel/network/DNSResolver.ts +158 -0
  967. package/src/substrate/lifo/kernel/network/NetworkInterface.ts +235 -0
  968. package/src/substrate/lifo/kernel/network/NetworkNamespace.ts +182 -0
  969. package/src/substrate/lifo/kernel/network/NetworkStack.ts +590 -0
  970. package/src/substrate/lifo/kernel/network/PortBridge.ts +182 -0
  971. package/src/substrate/lifo/kernel/network/RoutingTable.ts +209 -0
  972. package/src/substrate/lifo/kernel/network/Socket.ts +245 -0
  973. package/src/substrate/lifo/kernel/network/index.ts +29 -0
  974. package/src/substrate/lifo/kernel/network/tunnel/BaseTunnel.ts +124 -0
  975. package/src/substrate/lifo/kernel/network/tunnel/VETHPair.ts +227 -0
  976. package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +387 -0
  977. package/src/substrate/lifo/kernel/network/types.ts +178 -0
  978. package/src/substrate/lifo/kernel/persistence/PersistenceManager.ts +50 -0
  979. package/src/substrate/lifo/kernel/persistence/backends.ts +36 -0
  980. package/src/substrate/lifo/kernel/persistence/index.ts +5 -0
  981. package/src/substrate/lifo/kernel/persistence/serializer.ts +127 -0
  982. package/src/substrate/lifo/kernel/samples.ts +966 -0
  983. package/src/substrate/lifo/kernel/storage/BlobStore.ts +114 -0
  984. package/src/substrate/lifo/kernel/storage/ContentStore.ts +150 -0
  985. package/src/substrate/lifo/kernel/storage/index.ts +5 -0
  986. package/src/substrate/lifo/kernel/unit-parser.ts +95 -0
  987. package/src/substrate/lifo/kernel/vfs/VFS.ts +768 -0
  988. package/src/substrate/lifo/kernel/vfs/index.ts +9 -0
  989. package/src/substrate/lifo/kernel/vfs/providers/DevProvider.ts +172 -0
  990. package/src/substrate/lifo/kernel/vfs/providers/NativeFsProvider.ts +198 -0
  991. package/src/substrate/lifo/kernel/vfs/providers/ProcProvider.ts +144 -0
  992. package/src/substrate/lifo/kernel/vfs/types.ts +145 -0
  993. package/src/substrate/lifo/node-compat/buffer.ts +357 -0
  994. package/src/substrate/lifo/node-compat/child_process.ts +52 -0
  995. package/src/substrate/lifo/node-compat/console.ts +70 -0
  996. package/src/substrate/lifo/node-compat/crypto.ts +186 -0
  997. package/src/substrate/lifo/node-compat/dns.ts +214 -0
  998. package/src/substrate/lifo/node-compat/esbuild.ts +85 -0
  999. package/src/substrate/lifo/node-compat/events.ts +94 -0
  1000. package/src/substrate/lifo/node-compat/fs.ts +762 -0
  1001. package/src/substrate/lifo/node-compat/http.ts +561 -0
  1002. package/src/substrate/lifo/node-compat/index.ts +300 -0
  1003. package/src/substrate/lifo/node-compat/module.ts +148 -0
  1004. package/src/substrate/lifo/node-compat/os.ts +44 -0
  1005. package/src/substrate/lifo/node-compat/path.ts +74 -0
  1006. package/src/substrate/lifo/node-compat/process.ts +178 -0
  1007. package/src/substrate/lifo/node-compat/readline.ts +244 -0
  1008. package/src/substrate/lifo/node-compat/rimraf.ts +80 -0
  1009. package/src/substrate/lifo/node-compat/stream.ts +98 -0
  1010. package/src/substrate/lifo/node-compat/string_decoder.ts +26 -0
  1011. package/src/substrate/lifo/node-compat/timers.ts +21 -0
  1012. package/src/substrate/lifo/node-compat/tty.ts +83 -0
  1013. package/src/substrate/lifo/node-compat/url.ts +76 -0
  1014. package/src/substrate/lifo/node-compat/util.ts +133 -0
  1015. package/src/substrate/lifo/node-compat/zlib.ts +94 -0
  1016. package/src/substrate/lifo/pkg/PackageManager.ts +105 -0
  1017. package/src/substrate/lifo/pkg/lifo-dev.ts +130 -0
  1018. package/src/substrate/lifo/pkg/lifo-runtime.ts +473 -0
  1019. package/src/substrate/lifo/pkg/loader.ts +33 -0
  1020. package/src/substrate/lifo/platform-globals.d.ts +41 -0
  1021. package/src/substrate/lifo/sandbox/HeadlessTerminal.ts +38 -0
  1022. package/src/substrate/lifo/sandbox/Sandbox.ts +279 -0
  1023. package/src/substrate/lifo/sandbox/SandboxCommands.ts +74 -0
  1024. package/src/substrate/lifo/sandbox/SandboxFs.ts +160 -0
  1025. package/src/substrate/lifo/sandbox/index.ts +8 -0
  1026. package/src/substrate/lifo/sandbox/types.ts +117 -0
  1027. package/src/substrate/lifo/shell/ProcessRegistry.ts +379 -0
  1028. package/src/substrate/lifo/shell/Shell.ts +2048 -0
  1029. package/src/substrate/lifo/shell/completer.ts +212 -0
  1030. package/src/substrate/lifo/shell/default-shell.ts +63 -0
  1031. package/src/substrate/lifo/shell/expander.ts +1349 -0
  1032. package/src/substrate/lifo/shell/history.ts +138 -0
  1033. package/src/substrate/lifo/shell/interpreter.ts +1754 -0
  1034. package/src/substrate/lifo/shell/jobs.ts +64 -0
  1035. package/src/substrate/lifo/shell/lexer.ts +698 -0
  1036. package/src/substrate/lifo/shell/parser.ts +711 -0
  1037. package/src/substrate/lifo/shell/pipe.ts +92 -0
  1038. package/src/substrate/lifo/shell/signals.ts +70 -0
  1039. package/src/substrate/lifo/shell/terminal-stdin.ts +109 -0
  1040. package/src/substrate/lifo/shell/test-builtin.ts +316 -0
  1041. package/src/substrate/lifo/shell/types.ts +166 -0
  1042. package/src/substrate/lifo/terminal/ITerminal.ts +9 -0
  1043. package/src/substrate/lifo/utils/archive.ts +389 -0
  1044. package/src/substrate/lifo/utils/args.ts +89 -0
  1045. package/src/substrate/lifo/utils/colors.ts +29 -0
  1046. package/src/substrate/lifo/utils/encoding.ts +21 -0
  1047. package/src/substrate/lifo/utils/glob.ts +172 -0
  1048. package/src/substrate/lifo/utils/mime.ts +114 -0
  1049. package/src/substrate/lifo/utils/path.ts +83 -0
  1050. package/src/types/assets.d.ts +4 -0
  1051. package/src/vfs/events.ts +134 -0
  1052. package/src/vfs/path.ts +71 -0
  1053. package/src/vfs/seed-project.ts +1094 -0
  1054. package/src/vfs/sqlite-vfs.ts +5035 -0
  1055. package/src/vfs/symlink-registry.ts +172 -0
  1056. package/src/workspace/nimbus-workspace.ts +213 -0
@@ -0,0 +1,4702 @@
1
+ /**
2
+ * unix-commands.ts — Nimbus v2.0 Unix command implementations.
3
+ *
4
+ * Every command is a real implementation operating on SqliteVFS.
5
+ * No stubs, no "not implemented" — each does actual work.
6
+ *
7
+ * Commands: which, env, export, unset, history, clear, alias, date,
8
+ * uptime, tree, find, grep -r, head, tail, wc, diff, sort, uniq,
9
+ * sed (s///), awk (field extract), xargs, tee, chown, ln -s,
10
+ * du, man/help, basename, dirname, printf, true, false, seq, sleep,
11
+ * touch, stat, file, xxd, base64, sha256sum, id, hostname, realpath
12
+ */
13
+ import { getSymlinkRegistry } from '../vfs/symlink-registry.js';
14
+ import { requireVfsCred } from '../runtime/os-contracts.js';
15
+ import { dec, enc } from '../_shared/bytes.js';
16
+ import { NIMBUS_VERSION } from '../constants.js';
17
+ import { SinkWriter, streamRange } from '../_shared/byte-stream.js';
18
+ import { fileTypeChar, isCharacterDevice } from '../substrate/lifo/kernel/vfs/index.js';
19
+ import { runSed } from '../substrate/lifo/commands/text/sed.js';
20
+ import { parseArgs } from '../substrate/lifo/utils/args.js';
21
+ import { findUnixGroupName, findUnixUserName, parseChownOwnership, } from './unix-accounts.js';
22
+ import { createSuCommand, createSudoCommand, createUmaskCommand } from './elevation-commands.js';
23
+ function unixVfsFor(sqliteVfs, cred) {
24
+ return {
25
+ ...sqliteVfs.as(cred),
26
+ symlinks: getSymlinkRegistry(sqliteVfs),
27
+ };
28
+ }
29
+ function withInvocationVfs(sqliteVfs, factory) {
30
+ return (ctx) => factory(unixVfsFor(sqliteVfs, requireVfsCred(ctx.cred, 'unix command dispatch')))(ctx);
31
+ }
32
+ function fsErrorMessage(error) {
33
+ if (error instanceof Error) {
34
+ const code = 'code' in error && typeof error.code === 'string' ? error.code : null;
35
+ if (code === 'EACCES' || code === 'EPERM')
36
+ return 'Permission denied';
37
+ if (code === 'ENOENT')
38
+ return 'No such file or directory';
39
+ return error.message;
40
+ }
41
+ return String(error);
42
+ }
43
+ /** `drwxr-xr-x`-style permission string, shared by `ls -l` and `stat %A`. */
44
+ function unixModeString(mode, isDir, isLink) {
45
+ if (isLink)
46
+ return 'lrwxrwxrwx';
47
+ const prefix = fileTypeChar(mode, isDir ? 'directory' : 'file');
48
+ const bits = [
49
+ mode & 0o400 ? 'r' : '-',
50
+ mode & 0o200 ? 'w' : '-',
51
+ mode & 0o100 ? 'x' : '-',
52
+ mode & 0o040 ? 'r' : '-',
53
+ mode & 0o020 ? 'w' : '-',
54
+ mode & 0o010 ? 'x' : '-',
55
+ mode & 0o004 ? 'r' : '-',
56
+ mode & 0o002 ? 'w' : '-',
57
+ mode & 0o001 ? 'x' : '-',
58
+ ].join('');
59
+ return prefix + bits;
60
+ }
61
+ function unixUserLabel(vfs, uid) {
62
+ try {
63
+ return findUnixUserName(vfs, uid) ?? String(uid);
64
+ }
65
+ catch {
66
+ return String(uid);
67
+ }
68
+ }
69
+ function unixGroupLabel(vfs, gid) {
70
+ try {
71
+ return findUnixGroupName(vfs, gid) ?? String(gid);
72
+ }
73
+ catch {
74
+ return String(gid);
75
+ }
76
+ }
77
+ function isRuntimeInstallHintHandler(handler) {
78
+ return !!handler && !!handler.__nimbusRuntimeInstallHint;
79
+ }
80
+ // ── Helpers ─────────────────────────────────────────────────────────────
81
+ function resolvePath(cwd, p) {
82
+ if (p.startsWith('/'))
83
+ return p.replace(/^\/+/, '');
84
+ const c = (cwd || '/home/user').replace(/^\/+/, '');
85
+ const parts = (c + '/' + p).split('/');
86
+ const out = [];
87
+ for (const s of parts) {
88
+ if (s === '..')
89
+ out.pop();
90
+ else if (s !== '.' && s !== '')
91
+ out.push(s);
92
+ }
93
+ return out.join('/');
94
+ }
95
+ function readSymlinkTarget(vfs, path) {
96
+ if (vfs.isSymlink(path))
97
+ return vfs.readlink(path);
98
+ return vfs.symlinks.readlink(path);
99
+ }
100
+ function resolveSymlinkPath(vfs, startPath) {
101
+ let current = resolvePath('/', startPath);
102
+ for (let hops = 0; hops < 40; hops++) {
103
+ const target = readSymlinkTarget(vfs, current);
104
+ if (target === null)
105
+ return current;
106
+ current = target.startsWith('/')
107
+ ? resolvePath('/', target)
108
+ : resolvePath('/' + (current.includes('/') ? current.slice(0, current.lastIndexOf('/')) : ''), target);
109
+ }
110
+ return null;
111
+ }
112
+ function globMatch(pattern, name) {
113
+ const re = pattern
114
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
115
+ .replace(/\*/g, '.*')
116
+ .replace(/\?/g, '.');
117
+ return new RegExp('^' + re + '$').test(name);
118
+ }
119
+ // ── Command implementations ─────────────────────────────────────────────
120
+ /**
121
+ * SHELL-FOLLOWUPS-1 (2026-05-11): POSIX-conformant `which`.
122
+ *
123
+ * Pre-fix: `which clang` printed `clang: nimbus built-in`. Real POSIX
124
+ * which prints the resolved absolute path (`/usr/local/bin/clang`)
125
+ * or exits 1 silently. Scripts using `which` output to feed into
126
+ * `dirname`, `$()`, etc. all broke:
127
+ * PATH_PREFIX=$(dirname $(which clang)) # expected /usr/local/bin
128
+ *
129
+ * Behaviour matches GNU `which`:
130
+ * - PATH search: walk `$PATH`, return first hit's absolute path.
131
+ * - Found in PATH: print path to stdout, exit 0.
132
+ * - Resolved as shell builtin (no -a): exit 1, NO output.
133
+ * - Resolved as shell builtin (with -a): print
134
+ * "<cmd>: shell built-in command" + continue search PATH.
135
+ * - Not found anywhere: stderr "<cmd>: not in (PATH)", exit 1.
136
+ *
137
+ * For our facet-direct runtimes (clang, node, bun, python, ruby,
138
+ * git, npm, etc.), we don't have real on-disk binaries — they
139
+ * dispatch through the registry. To preserve script compatibility,
140
+ * we return canonical POSIX paths under /usr/local/bin and /usr/bin:
141
+ *
142
+ * clang, clang++, cc → /usr/local/bin/clang
143
+ * wasm-ld, lld → /usr/local/bin/wasm-ld
144
+ * node, nodejs → /usr/local/bin/node
145
+ * bun → /usr/local/bin/bun
146
+ * npm, npx → /usr/local/bin/npm
147
+ * git → /usr/bin/git
148
+ * python, python3 → /usr/bin/python3
149
+ * ruby, ruby3 → /usr/bin/ruby
150
+ * wrangler, nimbus-wrangler → /usr/local/bin/wrangler
151
+ * esbuild, tsc, vite, rollup, etc. → /usr/local/bin/<name>
152
+ *
153
+ * These paths are virtual but POSIX-shaped so scripts do the right
154
+ * thing with `dirname $(which X)` etc. The paths don't have to
155
+ * exist on the VFS (real GNU which just stat-walks PATH and
156
+ * doesn't require execute bit at lookup time for stdout — it does
157
+ * for exit code, but match what users expect first).
158
+ *
159
+ * If the user has installed a real binary in PATH (npm install + npx
160
+ * resolution into /home/user/node_modules/.bin/X), prefer the actual
161
+ * VFS-resolved path over the canonical fallback.
162
+ */
163
+ const _CANONICAL_BIN_PATHS = {
164
+ // /usr/local/bin (locally-installed runtimes)
165
+ clang: '/usr/local/bin/clang',
166
+ 'clang++': '/usr/local/bin/clang++',
167
+ cc: '/usr/local/bin/clang',
168
+ 'wasm-ld': '/usr/local/bin/wasm-ld',
169
+ lld: '/usr/local/bin/wasm-ld',
170
+ node: '/usr/local/bin/node',
171
+ nodejs: '/usr/local/bin/node',
172
+ bun: '/usr/local/bin/bun',
173
+ npm: '/usr/local/bin/npm',
174
+ npx: '/usr/local/bin/npx',
175
+ pnpm: '/usr/local/bin/pnpm',
176
+ yarn: '/usr/local/bin/yarn',
177
+ esbuild: '/usr/local/bin/esbuild',
178
+ tsc: '/usr/local/bin/tsc',
179
+ vite: '/usr/local/bin/vite',
180
+ rollup: '/usr/local/bin/rollup',
181
+ webpack: '/usr/local/bin/webpack',
182
+ wrangler: '/usr/local/bin/wrangler',
183
+ 'nimbus-wrangler': '/usr/local/bin/nimbus-wrangler',
184
+ // /usr/bin (system runtimes)
185
+ git: '/usr/bin/git',
186
+ python: '/usr/bin/python3',
187
+ python3: '/usr/bin/python3',
188
+ pip: '/usr/bin/pip',
189
+ pip3: '/usr/bin/pip3',
190
+ ruby: '/usr/bin/ruby',
191
+ ruby3: '/usr/bin/ruby',
192
+ gem: '/usr/bin/gem',
193
+ bundle: '/usr/bin/bundle',
194
+ bundler: '/usr/bin/bundler',
195
+ sh: '/usr/bin/sh',
196
+ bash: '/usr/bin/bash',
197
+ // Framework CLIs
198
+ astro: '/usr/local/bin/astro',
199
+ nuxt: '/usr/local/bin/nuxt',
200
+ nuxi: '/usr/local/bin/nuxi',
201
+ next: '/usr/local/bin/next',
202
+ remix: '/usr/local/bin/remix',
203
+ 'svelte-kit': '/usr/local/bin/svelte-kit',
204
+ husky: '/usr/local/bin/husky',
205
+ lefthook: '/usr/local/bin/lefthook',
206
+ 'simple-git-hooks': '/usr/local/bin/simple-git-hooks',
207
+ 'lint-staged': '/usr/local/bin/lint-staged',
208
+ yorkie: '/usr/local/bin/yorkie',
209
+ // Self
210
+ nimbus: '/usr/local/bin/nimbus',
211
+ };
212
+ function _pathLookup(vfs, name, envPath) {
213
+ const paths = (envPath || '/usr/local/bin:/usr/bin:/bin').split(':');
214
+ for (const dir of paths) {
215
+ if (!dir)
216
+ continue;
217
+ const stripped = dir.replace(/^\/+/, '').replace(/\/+$/, '');
218
+ const fp = stripped + '/' + name;
219
+ if (vfs.exists(fp) && !vfs.isDirectory(fp)) {
220
+ return '/' + fp;
221
+ }
222
+ }
223
+ return null;
224
+ }
225
+ async function _registryResolved(registry, name, options = {}) {
226
+ try {
227
+ const resolved = typeof registry.resolve === 'function'
228
+ ? await registry.resolve(name)
229
+ : null;
230
+ if (resolved && (options.includeInstallHints || !isRuntimeInstallHintHandler(resolved))) {
231
+ return resolved;
232
+ }
233
+ }
234
+ catch {
235
+ // Registry misses are normal for unknown commands.
236
+ }
237
+ return null;
238
+ }
239
+ /** Resolve a command name to a path via PATH-walk + canonical-bin
240
+ * fallback. Returns null if not findable. Skip-canonical when the
241
+ * caller knows the command is a shell builtin (no fallback). */
242
+ async function _whichLookup(vfs, registry, name, envPath) {
243
+ const diskPath = _pathLookup(vfs, name, envPath);
244
+ if (diskPath)
245
+ return diskPath;
246
+ const canonicalPath = _CANONICAL_BIN_PATHS[name];
247
+ if (!canonicalPath)
248
+ return null;
249
+ return await _registryResolved(registry, name, { includeInstallHints: true })
250
+ ? canonicalPath
251
+ : null;
252
+ }
253
+ function mkWhich(vfs, registry) {
254
+ return async (ctx) => {
255
+ // Parse flags. Supports -a (show all matches), -s (silent — no
256
+ // stdout, only exit code). Default behaviour matches GNU which.
257
+ let showAll = false;
258
+ let silent = false;
259
+ const names = [];
260
+ for (const a of ctx.args) {
261
+ if (a === '-a' || a === '--all') {
262
+ showAll = true;
263
+ continue;
264
+ }
265
+ if (a === '-s' || a === '--silent') {
266
+ silent = true;
267
+ continue;
268
+ }
269
+ if (a.startsWith('-') && a !== '-') {
270
+ // Combined short flags
271
+ for (const ch of a.slice(1)) {
272
+ if (ch === 'a')
273
+ showAll = true;
274
+ else if (ch === 's')
275
+ silent = true;
276
+ }
277
+ continue;
278
+ }
279
+ names.push(a);
280
+ }
281
+ if (names.length === 0) {
282
+ ctx.stderr.write('Usage: which [-as] command [command ...]\n');
283
+ return 1;
284
+ }
285
+ let anyMissing = false;
286
+ for (const name of names) {
287
+ // Classify: is it a registry-resolvable command (shell builtin)?
288
+ const resolved = await _registryResolved(registry, name);
289
+ const isBuiltin = !!resolved;
290
+ // 1. PATH-walk + canonical-bin lookup.
291
+ const path = await _whichLookup(vfs, registry, name, ctx.env.PATH || '');
292
+ let found = false;
293
+ if (path) {
294
+ if (!silent)
295
+ ctx.stdout.write(path + '\n');
296
+ found = true;
297
+ }
298
+ // 2. With -a, also report builtins (real GNU which behaviour).
299
+ if (showAll && isBuiltin) {
300
+ if (!silent)
301
+ ctx.stdout.write(`${name}: shell built-in command\n`);
302
+ found = true;
303
+ }
304
+ // 3. Without -a, if no PATH match but is builtin: GNU which
305
+ // exits 1 silently (with -s suppresses stderr too).
306
+ if (!path && isBuiltin && !showAll) {
307
+ // Exit 1; no output. Matches GNU `which` default.
308
+ anyMissing = true;
309
+ continue;
310
+ }
311
+ if (!found) {
312
+ if (!silent)
313
+ ctx.stderr.write(`which: no ${name} in (${ctx.env.PATH || '/usr/local/bin:/usr/bin:/bin'})\n`);
314
+ anyMissing = true;
315
+ }
316
+ }
317
+ return anyMissing ? 1 : 0;
318
+ };
319
+ }
320
+ /**
321
+ * SHELL-FOLLOWUPS-2 (2026-05-11): `whereis` companion to `which`.
322
+ * GNU whereis prints binary, source, and manpage paths. We only have
323
+ * binaries on the virtual VFS; print just the binary path. With no
324
+ * match, print just the name (matches `whereis` behavior on missing).
325
+ */
326
+ function mkWhereis(vfs, registry) {
327
+ return async (ctx) => {
328
+ const names = ctx.args.filter(a => !a.startsWith('-'));
329
+ if (names.length === 0) {
330
+ ctx.stderr.write('Usage: whereis name [name ...]\n');
331
+ return 1;
332
+ }
333
+ for (const name of names) {
334
+ const path = await _whichLookup(vfs, registry, name, ctx.env.PATH || '');
335
+ if (path) {
336
+ ctx.stdout.write(`${name}: ${path}\n`);
337
+ }
338
+ else {
339
+ // GNU whereis prints just "name:" when nothing found.
340
+ ctx.stdout.write(`${name}:\n`);
341
+ }
342
+ }
343
+ return 0;
344
+ };
345
+ }
346
+ /**
347
+ * SHELL-FOLLOWUPS-3 (2026-05-11): POSIX `command -v` / `command -V`.
348
+ * Used by shell scripts as the portable alternative to `which`:
349
+ * command -v clang → prints path, exit 0 if found, exit 1 if not
350
+ * command -V clang → verbose form (similar to `type`)
351
+ * command clang ARG → invoke clang bypassing any function/alias
352
+ *
353
+ * For the invoke case (no -v/-V), we don't have a way to bypass
354
+ * alias/function from here, so we just dispatch to
355
+ * the registry. Aliases are checked at executeLine time so `command
356
+ * X` going through our normal dispatch IS bypassing the alias
357
+ * (because the interpreter only consults aliases
358
+ * for the head word).
359
+ */
360
+ function mkCommand(vfs, registry) {
361
+ return async (ctx) => {
362
+ const args = [...ctx.args];
363
+ let mode = 'invoke';
364
+ if (args[0] === '-v') {
365
+ mode = '-v';
366
+ args.shift();
367
+ }
368
+ else if (args[0] === '-V') {
369
+ mode = '-V';
370
+ args.shift();
371
+ }
372
+ if (args.length === 0) {
373
+ if (mode === 'invoke')
374
+ return 0;
375
+ ctx.stderr.write('command: missing operand\n');
376
+ return 1;
377
+ }
378
+ if (mode === '-v') {
379
+ // Print path or builtin marker; exit 0 if found.
380
+ const name = args[0];
381
+ const path = await _whichLookup(vfs, registry, name, ctx.env.PATH || '');
382
+ if (path) {
383
+ ctx.stdout.write(path + '\n');
384
+ return 0;
385
+ }
386
+ if (await _registryResolved(registry, name)) {
387
+ ctx.stdout.write(name + '\n');
388
+ return 0;
389
+ }
390
+ return 1;
391
+ }
392
+ if (mode === '-V') {
393
+ const name = args[0];
394
+ const path = await _whichLookup(vfs, registry, name, ctx.env.PATH || '');
395
+ if (path) {
396
+ ctx.stdout.write(`${name} is ${path}\n`);
397
+ return 0;
398
+ }
399
+ if (await _registryResolved(registry, name)) {
400
+ ctx.stdout.write(`${name} is a shell builtin\n`);
401
+ return 0;
402
+ }
403
+ ctx.stderr.write(`command: ${name}: not found\n`);
404
+ return 1;
405
+ }
406
+ // invoke mode: dispatch directly via registry. Bypasses aliases
407
+ // because we're calling the resolved cmd not the alias name.
408
+ const name = args[0];
409
+ try {
410
+ const resolved = await registry.resolve(name);
411
+ if (!resolved) {
412
+ ctx.stderr.write(`command: ${name}: not found\n`);
413
+ return 127;
414
+ }
415
+ const subCtx = { ...ctx, args: args.slice(1) };
416
+ const code = await resolved(subCtx);
417
+ return typeof code === 'number' ? code : 0;
418
+ }
419
+ catch (e) {
420
+ ctx.stderr.write(`command: ${name}: ${e?.message || e}\n`);
421
+ return 1;
422
+ }
423
+ };
424
+ }
425
+ /**
426
+ * shell compatibility (2026-05-11): `type` builtin. The shell did not ship
427
+ * one; pre-fix `type echo` → 'type: command not found'. bash's
428
+ * `type X` reports how X would be interpreted (builtin, alias,
429
+ * function, file, or unknown).
430
+ *
431
+ * Our subset (matches bash `type` output for common shapes):
432
+ * type echo → 'echo is a shell builtin' (Shell.builtins entry)
433
+ * type ls → 'ls is a shell builtin' (lazy registry)
434
+ * type rm → 'rm is a shell builtin' (our wrap'd registry)
435
+ * type node → 'node is /usr/bin/node' (registry but facet-direct)
436
+ * type X → 'type: X: not found' + exit 1
437
+ *
438
+ * We can't introspect Shell.builtins from here directly (the ctx
439
+ * doesn't carry shell). Workaround: pass registry which the unix-
440
+ * commands module already has access to; treat any registry resolve
441
+ * as "shell builtin" classification.
442
+ */
443
+ function mkType(_vfs, registry) {
444
+ return async (ctx) => {
445
+ if (ctx.args.length === 0)
446
+ return 0;
447
+ let exit = 0;
448
+ for (const name of ctx.args) {
449
+ try {
450
+ const resolved = typeof registry.resolve === 'function' ? await registry.resolve(name) : null;
451
+ if (resolved && !isRuntimeInstallHintHandler(resolved)) {
452
+ ctx.stdout.write(`${name} is a shell builtin\n`);
453
+ }
454
+ else {
455
+ ctx.stderr.write(`type: ${name}: not found\n`);
456
+ exit = 1;
457
+ }
458
+ }
459
+ catch (_e) {
460
+ ctx.stderr.write(`type: ${name}: not found\n`);
461
+ exit = 1;
462
+ }
463
+ }
464
+ return exit;
465
+ };
466
+ }
467
+ function mkEnv() {
468
+ return (ctx) => {
469
+ for (const [k, v] of Object.entries(ctx.env)) {
470
+ ctx.stdout.write(`${k}=${v}\n`);
471
+ }
472
+ return 0;
473
+ };
474
+ }
475
+ function mkExport() {
476
+ return (ctx) => {
477
+ for (const arg of ctx.args) {
478
+ const eqIdx = arg.indexOf('=');
479
+ if (eqIdx > 0) {
480
+ ctx.env[arg.substring(0, eqIdx)] = arg.substring(eqIdx + 1);
481
+ }
482
+ else if (ctx.env[arg] !== undefined) {
483
+ ctx.stdout.write(`export ${arg}="${ctx.env[arg]}"\n`);
484
+ }
485
+ }
486
+ return 0;
487
+ };
488
+ }
489
+ function mkUnset() {
490
+ return (ctx) => {
491
+ for (const name of ctx.args) {
492
+ delete ctx.env[name];
493
+ }
494
+ return 0;
495
+ };
496
+ }
497
+ function mkClear() {
498
+ return (ctx) => { ctx.stdout.write('\x1b[2J\x1b[H'); return 0; };
499
+ }
500
+ /**
501
+ * shell compatibility (2026-05-11): date strftime format support.
502
+ *
503
+ * Pre-fix mkDate only honoured `-u`, `-I`, and `+%s` literal. Any
504
+ * other `+FMT` was a no-op falling to `now.toString()`. Real shell
505
+ * scripts use `date +%Y-%m-%d`, `date +%H:%M:%S`, `date +%F`, etc.
506
+ *
507
+ * Post-fix: full strftime subset:
508
+ * %Y / %C / %y year (4-digit / century / 2-digit)
509
+ * %m / %B / %b / %h month (numeric / full name / abbrev / abbrev)
510
+ * %d / %e day of month (zero-padded / space-padded)
511
+ * %j day of year
512
+ * %H / %I / %M / %S hour-24 / hour-12 / minute / second
513
+ * %p AM/PM
514
+ * %A / %a weekday (full / abbrev)
515
+ * %u / %w ISO weekday (1=Mon..7=Sun) / weekday (0=Sun..6=Sat)
516
+ * %s unix timestamp (seconds)
517
+ * %N nanoseconds (zero-pad to 9 digits)
518
+ * %F %Y-%m-%d
519
+ * %T / %R %H:%M:%S / %H:%M
520
+ * %D %m/%d/%y
521
+ * %z / %Z timezone offset / name
522
+ * %% literal %
523
+ * %n / %t newline / tab
524
+ */
525
+ function mkDate() {
526
+ return (ctx) => {
527
+ const now = new Date();
528
+ const useUtc = ctx.args.includes('-u') || ctx.args.includes('--utc');
529
+ // Find the `+FMT` arg (if any). Real `date +FMT [args]` accepts
530
+ // only one format; we honour the first.
531
+ const fmtArg = ctx.args.find(a => a.startsWith('+'));
532
+ if (fmtArg) {
533
+ ctx.stdout.write(strftime(now, fmtArg.slice(1), useUtc) + '\n');
534
+ return 0;
535
+ }
536
+ if (ctx.args.includes('-I') || ctx.args.includes('--iso-8601')) {
537
+ ctx.stdout.write(now.toISOString() + '\n');
538
+ return 0;
539
+ }
540
+ if (useUtc) {
541
+ ctx.stdout.write(now.toUTCString() + '\n');
542
+ return 0;
543
+ }
544
+ ctx.stdout.write(now.toString() + '\n');
545
+ return 0;
546
+ };
547
+ }
548
+ const _MONTHS_FULL = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
549
+ const _MONTHS_ABBR = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
550
+ const _DAYS_FULL = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
551
+ const _DAYS_ABBR = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
552
+ function strftime(d, fmt, utc) {
553
+ const get = (m) => {
554
+ switch (m) {
555
+ case 'FullYear': return utc ? d.getUTCFullYear() : d.getFullYear();
556
+ case 'Month': return utc ? d.getUTCMonth() : d.getMonth();
557
+ case 'Date': return utc ? d.getUTCDate() : d.getDate();
558
+ case 'Hours': return utc ? d.getUTCHours() : d.getHours();
559
+ case 'Minutes': return utc ? d.getUTCMinutes() : d.getMinutes();
560
+ case 'Seconds': return utc ? d.getUTCSeconds() : d.getSeconds();
561
+ case 'Day': return utc ? d.getUTCDay() : d.getDay();
562
+ case 'Milliseconds': return utc ? d.getUTCMilliseconds() : d.getMilliseconds();
563
+ default: return 0;
564
+ }
565
+ };
566
+ const pad = (n, w, ch = '0') => String(n).padStart(w, ch);
567
+ const yyyy = get('FullYear');
568
+ const mm0 = get('Month'); // 0..11
569
+ const dd = get('Date');
570
+ const hh = get('Hours');
571
+ const mn = get('Minutes');
572
+ const ss = get('Seconds');
573
+ const dow = get('Day'); // 0..6 (Sun..Sat)
574
+ const ms = get('Milliseconds');
575
+ // Day of year: difference from Jan 1.
576
+ const jan1 = utc
577
+ ? Date.UTC(yyyy, 0, 1)
578
+ : new Date(yyyy, 0, 1).getTime();
579
+ const doy = Math.floor((d.getTime() - jan1) / 86400000) + 1;
580
+ // ISO weekday: 1=Mon..7=Sun.
581
+ const isoDow = dow === 0 ? 7 : dow;
582
+ const ampm = hh < 12 ? 'AM' : 'PM';
583
+ const h12 = hh % 12 === 0 ? 12 : hh % 12;
584
+ // TZ offset in ±HHMM form.
585
+ const tzOff = utc ? '+0000' : (() => {
586
+ const off = -d.getTimezoneOffset();
587
+ const sign = off >= 0 ? '+' : '-';
588
+ const abs = Math.abs(off);
589
+ return sign + pad(Math.floor(abs / 60), 2) + pad(abs % 60, 2);
590
+ })();
591
+ const tzName = utc ? 'UTC' : (() => {
592
+ try {
593
+ const parts = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short' }).formatToParts(d);
594
+ const tz = parts.find(p => p.type === 'timeZoneName');
595
+ return tz ? tz.value : 'UTC';
596
+ }
597
+ catch {
598
+ return 'UTC';
599
+ }
600
+ })();
601
+ let out = '';
602
+ let i = 0;
603
+ while (i < fmt.length) {
604
+ const ch = fmt[i];
605
+ if (ch !== '%') {
606
+ out += ch;
607
+ i++;
608
+ continue;
609
+ }
610
+ i++;
611
+ const spec = fmt[i] || '';
612
+ i++;
613
+ switch (spec) {
614
+ case 'Y':
615
+ out += String(yyyy);
616
+ break;
617
+ case 'C':
618
+ out += pad(Math.floor(yyyy / 100), 2);
619
+ break;
620
+ case 'y':
621
+ out += pad(yyyy % 100, 2);
622
+ break;
623
+ case 'm':
624
+ out += pad(mm0 + 1, 2);
625
+ break;
626
+ case 'B':
627
+ out += _MONTHS_FULL[mm0];
628
+ break;
629
+ case 'b':
630
+ case 'h':
631
+ out += _MONTHS_ABBR[mm0];
632
+ break;
633
+ case 'd':
634
+ out += pad(dd, 2);
635
+ break;
636
+ case 'e':
637
+ out += String(dd).padStart(2, ' ');
638
+ break;
639
+ case 'j':
640
+ out += pad(doy, 3);
641
+ break;
642
+ case 'H':
643
+ out += pad(hh, 2);
644
+ break;
645
+ case 'I':
646
+ out += pad(h12, 2);
647
+ break;
648
+ case 'M':
649
+ out += pad(mn, 2);
650
+ break;
651
+ case 'S':
652
+ out += pad(ss, 2);
653
+ break;
654
+ case 'p':
655
+ out += ampm;
656
+ break;
657
+ case 'P':
658
+ out += ampm.toLowerCase();
659
+ break;
660
+ case 'A':
661
+ out += _DAYS_FULL[dow];
662
+ break;
663
+ case 'a':
664
+ out += _DAYS_ABBR[dow];
665
+ break;
666
+ case 'u':
667
+ out += String(isoDow);
668
+ break;
669
+ case 'w':
670
+ out += String(dow);
671
+ break;
672
+ case 's':
673
+ out += String(Math.floor(d.getTime() / 1000));
674
+ break;
675
+ case 'N':
676
+ out += pad(ms * 1_000_000, 9);
677
+ break;
678
+ case 'F':
679
+ out += `${yyyy}-${pad(mm0 + 1, 2)}-${pad(dd, 2)}`;
680
+ break;
681
+ case 'T':
682
+ out += `${pad(hh, 2)}:${pad(mn, 2)}:${pad(ss, 2)}`;
683
+ break;
684
+ case 'R':
685
+ out += `${pad(hh, 2)}:${pad(mn, 2)}`;
686
+ break;
687
+ case 'D':
688
+ out += `${pad(mm0 + 1, 2)}/${pad(dd, 2)}/${pad(yyyy % 100, 2)}`;
689
+ break;
690
+ case 'z':
691
+ out += tzOff;
692
+ break;
693
+ case 'Z':
694
+ out += tzName;
695
+ break;
696
+ case '%':
697
+ out += '%';
698
+ break;
699
+ case 'n':
700
+ out += '\n';
701
+ break;
702
+ case 't':
703
+ out += '\t';
704
+ break;
705
+ default:
706
+ out += '%' + spec;
707
+ break; // unknown — preserve literal
708
+ }
709
+ }
710
+ return out;
711
+ }
712
+ function mkUptime() {
713
+ const start = Date.now();
714
+ return (ctx) => {
715
+ const secs = Math.floor((Date.now() - start) / 1000);
716
+ const h = Math.floor(secs / 3600);
717
+ const m = Math.floor((secs % 3600) / 60);
718
+ ctx.stdout.write(` ${new Date().toTimeString().split(' ')[0]} up ${h}:${String(m).padStart(2, '0')}, 1 user\n`);
719
+ return 0;
720
+ };
721
+ }
722
+ function mkTree(vfs) {
723
+ return (ctx) => {
724
+ const args = ctx.args.filter(a => !a.startsWith('-') && (ctx.args.indexOf(a) !== ctx.args.indexOf('-L') + 1));
725
+ const root = args[0] ? resolvePath(ctx.cwd, args[0]) : (ctx.cwd || '/home/user').replace(/^\/+/, '');
726
+ const maxDepth = ctx.args.includes('-L') ? parseInt(ctx.args[ctx.args.indexOf('-L') + 1]) || 3 : 3;
727
+ const MAX_ENTRIES = 2000; // Safety limit to prevent hanging on huge repos
728
+ let dirs = 0, files = 0, total = 0;
729
+ let truncated = false;
730
+ function walk(path, prefix, depth) {
731
+ if (depth > maxDepth || truncated)
732
+ return;
733
+ try {
734
+ const entries = vfs.readdir(path).sort((a, b) => a.name.localeCompare(b.name));
735
+ for (let i = 0; i < entries.length; i++) {
736
+ if (total >= MAX_ENTRIES) {
737
+ truncated = true;
738
+ return;
739
+ }
740
+ total++;
741
+ const e = entries[i];
742
+ const isLast = i === entries.length - 1;
743
+ const connector = isLast ? '└── ' : '├── ';
744
+ const childPrefix = isLast ? ' ' : '│ ';
745
+ ctx.stdout.write(prefix + connector + e.name + '\n');
746
+ if (e.type === 'directory') {
747
+ dirs++;
748
+ walk(path + '/' + e.name, prefix + childPrefix, depth + 1);
749
+ }
750
+ else {
751
+ files++;
752
+ }
753
+ }
754
+ }
755
+ catch { }
756
+ }
757
+ const name = root.split('/').pop() || root;
758
+ ctx.stdout.write(name + '\n');
759
+ walk(root, '', 1);
760
+ if (truncated)
761
+ ctx.stdout.write(`\n... truncated at ${MAX_ENTRIES} entries\n`);
762
+ ctx.stdout.write(`\n${dirs} directories, ${files} files\n`);
763
+ return 0;
764
+ };
765
+ }
766
+ /**
767
+ * `find`'s arguments are an EXPRESSION, not a flag list.
768
+ *
769
+ * The flat AND-list this replaced dropped every token it did not recognise,
770
+ * which does not fail — it answers a different question. `find . ! -name x`
771
+ * ran as `find . -name x`, the exact complement of the requested set, and
772
+ * said nothing. An installer's
773
+ *
774
+ * find "$dir" -mindepth 1 -maxdepth 1 -type d | head -n 1
775
+ *
776
+ * silently became `-maxdepth 1 -type d`, which emits the start directory at
777
+ * depth 0, so the pipeline selected the container instead of the tree in it.
778
+ *
779
+ * So the fix is not `-mindepth`. It is to parse the real grammar —
780
+ *
781
+ * expr := or
782
+ * or := and (('-o' | '-or') and)*
783
+ * and := unary (('-a' | '-and')? unary)*
784
+ * unary := ('!' | '-not') unary | '(' expr ')' | primary
785
+ *
786
+ * — with `-a` binding tighter than `-o`, to evaluate it with short-circuit
787
+ * semantics (which is what makes the `-prune -o -print` idiom work), and to
788
+ * REFUSE any token outside the table below rather than ignore it.
789
+ *
790
+ * Every behaviour here was derived by running the same expression under GNU
791
+ * findutils 4.10.0; tests/unit/find-expression-evaluator.mjs carries the
792
+ * differential.
793
+ *
794
+ * Global options -maxdepth N, -mindepth N, -depth
795
+ * Operators !, -not, -a, -and, -o, -or, ( )
796
+ * Tests -name, -iname, -path, -type, -size, -mtime, -newer, -empty
797
+ * Actions -print, -print0, -delete, -exec … {} \\; | +, -prune, -quit
798
+ *
799
+ * Anything else is `find: unknown predicate '-x'`, exit 1 — the same
800
+ * honesty `uname -m` keeps by answering `wasm`.
801
+ */
802
+ /** A usage error carrying the message GNU find prints for it. */
803
+ class FindUsageError extends Error {
804
+ }
805
+ function mkFind(vfs, registry) {
806
+ return async (ctx) => {
807
+ const state = {
808
+ minDepth: 0,
809
+ maxDepth: Infinity,
810
+ depthFirst: false,
811
+ prune: false,
812
+ quit: false,
813
+ };
814
+ const execBatches = [];
815
+ let hasAction = false;
816
+ // ── Emission ──────────────────────────────────────────────────────────
817
+ const emit = (entry, terminator) => {
818
+ ctx.stdout.write(entry.display + terminator);
819
+ };
820
+ /** Run one command through the registry the session resolves through. */
821
+ const runExec = async (argv) => {
822
+ const [name, ...rest] = argv;
823
+ if (!name)
824
+ return false;
825
+ let target;
826
+ try {
827
+ target = await registry.resolve(name);
828
+ }
829
+ catch {
830
+ target = null;
831
+ }
832
+ if (!target) {
833
+ ctx.stderr.write(`find: ${name}: No such file or directory\n`);
834
+ return false;
835
+ }
836
+ try {
837
+ const code = await target({
838
+ pid: ctx.pid,
839
+ cred: ctx.cred,
840
+ args: rest,
841
+ env: ctx.env,
842
+ cwd: ctx.cwd,
843
+ vfs: ctx.vfs,
844
+ stdout: ctx.stdout,
845
+ stderr: ctx.stderr,
846
+ stdin: '',
847
+ signal: ctx.signal,
848
+ setUmask: ctx.setUmask,
849
+ runAs: ctx.runAs,
850
+ execInterpreterDepth: ctx.execInterpreterDepth,
851
+ });
852
+ return code === 0;
853
+ }
854
+ catch (e) {
855
+ ctx.stderr.write(`find: ${name}: ${e?.message || e}\n`);
856
+ return false;
857
+ }
858
+ };
859
+ // ── Parser ────────────────────────────────────────────────────────────
860
+ //
861
+ // Start paths come first: GNU reads operands until the first token that
862
+ // begins an expression, so `find a b -type d` walks both a and b.
863
+ const args = [...ctx.args];
864
+ let pos = 0;
865
+ const startsExpression = (tok) => tok.startsWith('-') || tok === '(' || tok === ')' || tok === '!' || tok === ',';
866
+ const startArgs = [];
867
+ while (pos < args.length && !startsExpression(args[pos]))
868
+ startArgs.push(args[pos++]);
869
+ if (startArgs.length === 0)
870
+ startArgs.push('.');
871
+ const peek = () => args[pos];
872
+ const next = () => args[pos++];
873
+ /** The argument a predicate requires, or GNU's missing-argument error. */
874
+ const value = (pred) => {
875
+ const v = args[pos++];
876
+ if (v === undefined)
877
+ throw new FindUsageError(`missing argument to \`${pred}'`);
878
+ return v;
879
+ };
880
+ const positiveInt = (pred) => {
881
+ const raw = value(pred);
882
+ if (!/^\d+$/.test(raw)) {
883
+ throw new FindUsageError(`Expected a positive decimal integer argument to ${pred}, but got \`${raw}'`);
884
+ }
885
+ return parseInt(raw, 10);
886
+ };
887
+ const TRUE = async () => true;
888
+ /** Size in the unit's own terms: GNU rounds a partial unit UP. */
889
+ const sizeInUnits = (bytes, unit) => unit === 1 ? bytes : Math.ceil(bytes / unit);
890
+ const statOf = (entry) => {
891
+ try {
892
+ return vfs.stat(entry.vfsPath);
893
+ }
894
+ catch {
895
+ return null;
896
+ }
897
+ };
898
+ function parsePrimary() {
899
+ const tok = next();
900
+ if (tok === undefined)
901
+ throw new FindUsageError('missing expression');
902
+ switch (tok) {
903
+ // ── Global options: they configure the walk and evaluate true ──
904
+ case '-maxdepth':
905
+ state.maxDepth = positiveInt('-maxdepth');
906
+ return TRUE;
907
+ case '-mindepth':
908
+ state.minDepth = positiveInt('-mindepth');
909
+ return TRUE;
910
+ case '-depth':
911
+ state.depthFirst = true;
912
+ return TRUE;
913
+ // ── Tests ──
914
+ case '-name': {
915
+ const pattern = value('-name');
916
+ return async (e) => globMatch(pattern, e.name);
917
+ }
918
+ case '-iname': {
919
+ const pattern = value('-iname').toLowerCase();
920
+ return async (e) => globMatch(pattern, e.name.toLowerCase());
921
+ }
922
+ case '-path': {
923
+ const pattern = value('-path');
924
+ return async (e) => globMatch(pattern, e.display);
925
+ }
926
+ case '-type': {
927
+ const letter = value('-type');
928
+ if (letter !== 'f' && letter !== 'd' && letter !== 'l') {
929
+ throw new FindUsageError(`Unknown argument to -type: ${letter}`);
930
+ }
931
+ const wanted = letter === 'f' ? 'file' : letter === 'd' ? 'directory' : 'symlink';
932
+ return async (e) => e.type === wanted;
933
+ }
934
+ case '-size': {
935
+ const raw = value('-size');
936
+ const m = raw.match(/^([+-]?)(\d+)([ckMG]?)$/);
937
+ if (!m)
938
+ throw new FindUsageError(`invalid -size type \`${raw.slice(-1)}'`);
939
+ const cmp = m[1];
940
+ const count = parseInt(m[2], 10);
941
+ const unit = m[3] === 'c' ? 1
942
+ : m[3] === 'k' ? 1024
943
+ : m[3] === 'M' ? 1024 * 1024
944
+ : m[3] === 'G' ? 1024 * 1024 * 1024
945
+ : 512;
946
+ return async (e) => {
947
+ const st = statOf(e);
948
+ if (!st)
949
+ return false;
950
+ const units = sizeInUnits(st.size || 0, unit);
951
+ return cmp === '+' ? units > count : cmp === '-' ? units < count : units === count;
952
+ };
953
+ }
954
+ case '-mtime': {
955
+ const raw = value('-mtime');
956
+ const m = raw.match(/^([+-]?)(\d+)$/);
957
+ if (!m)
958
+ throw new FindUsageError(`invalid argument \`${raw}' to \`-mtime'`);
959
+ const cmp = m[1];
960
+ const dayMs = 86400 * 1000;
961
+ const threshold = parseInt(m[2], 10) * dayMs;
962
+ const now = Date.now();
963
+ return async (e) => {
964
+ const st = statOf(e);
965
+ if (!st)
966
+ return false;
967
+ const age = now - (st.mtime || 0);
968
+ return cmp === '+' ? age > threshold + dayMs
969
+ : cmp === '-' ? age < threshold
970
+ : age >= threshold && age < threshold + dayMs;
971
+ };
972
+ }
973
+ case '-newer': {
974
+ const ref = value('-newer');
975
+ let refMtime;
976
+ try {
977
+ refMtime = vfs.stat(resolvePath(ctx.cwd, ref)).mtime;
978
+ }
979
+ catch {
980
+ throw new FindUsageError(`'${ref}': No such file or directory`);
981
+ }
982
+ return async (e) => {
983
+ const st = statOf(e);
984
+ return !!st && (st.mtime || 0) > refMtime;
985
+ };
986
+ }
987
+ case '-empty':
988
+ return async (e) => {
989
+ if (e.type === 'directory') {
990
+ try {
991
+ return vfs.readdir(e.vfsPath).length === 0;
992
+ }
993
+ catch {
994
+ return false;
995
+ }
996
+ }
997
+ const st = statOf(e);
998
+ return !!st && (st.size || 0) === 0;
999
+ };
1000
+ // ── Actions ──
1001
+ case '-print':
1002
+ hasAction = true;
1003
+ return async (e) => { emit(e, '\n'); return true; };
1004
+ case '-print0':
1005
+ hasAction = true;
1006
+ return async (e) => { emit(e, '\0'); return true; };
1007
+ case '-delete':
1008
+ hasAction = true;
1009
+ // GNU's -delete implies -depth: a directory is removable only once
1010
+ // its children are gone.
1011
+ state.depthFirst = true;
1012
+ return async (e) => {
1013
+ try {
1014
+ if (e.type === 'directory')
1015
+ vfs.rmdir(e.vfsPath);
1016
+ else
1017
+ vfs.unlink(e.vfsPath);
1018
+ return true;
1019
+ }
1020
+ catch (err) {
1021
+ ctx.stderr.write(`find: cannot delete '${e.display}': ${err?.message || err}\n`);
1022
+ return false;
1023
+ }
1024
+ };
1025
+ case '-quit':
1026
+ hasAction = true;
1027
+ return async (_e, st) => { st.quit = true; return true; };
1028
+ case '-prune':
1029
+ // NOT an action: GNU still adds the implicit -print alongside it,
1030
+ // which is what makes `-prune -o -print` print everything else.
1031
+ return async (e, st) => {
1032
+ if (e.type === 'directory')
1033
+ st.prune = true;
1034
+ return true;
1035
+ };
1036
+ case '-exec': {
1037
+ hasAction = true;
1038
+ const argv = [];
1039
+ let terminator = null;
1040
+ while (pos < args.length) {
1041
+ const a = next();
1042
+ // Exactly `;`, as GNU requires. The usual `\;` is the shell's
1043
+ // escaping of it; a quoted '\;' keeps its backslash and GNU
1044
+ // rejects that as a missing terminator, so this does too.
1045
+ if (a === ';') {
1046
+ terminator = ';';
1047
+ break;
1048
+ }
1049
+ // `+` terminates only directly after the {} placeholder.
1050
+ if (a === '+' && argv[argv.length - 1] === '{}') {
1051
+ terminator = '+';
1052
+ break;
1053
+ }
1054
+ argv.push(a);
1055
+ }
1056
+ if (terminator === null) {
1057
+ throw new FindUsageError("missing argument to `-exec'");
1058
+ }
1059
+ if (terminator === ';') {
1060
+ return async (e) => runExec(argv.map((a) => a.split('{}').join(e.display)));
1061
+ }
1062
+ // `-exec … {} +`: every match joins one invocation, flushed after
1063
+ // the walk. The trailing {} is where the paths go.
1064
+ const batch = { argv: argv.slice(0, -1), pending: [] };
1065
+ execBatches.push(batch);
1066
+ return async (e) => { batch.pending.push(e.display); return true; };
1067
+ }
1068
+ // ── Grouping ──
1069
+ case '(': {
1070
+ const inner = parseExpr();
1071
+ if (next() !== ')')
1072
+ throw new FindUsageError("expected expression after `('");
1073
+ return inner;
1074
+ }
1075
+ }
1076
+ throw new FindUsageError(`unknown predicate \`${tok}'`);
1077
+ }
1078
+ function parseUnary() {
1079
+ const tok = peek();
1080
+ if (tok === '!' || tok === '-not') {
1081
+ pos++;
1082
+ const operand = parseUnary();
1083
+ return async (e, st) => !(await operand(e, st));
1084
+ }
1085
+ return parsePrimary();
1086
+ }
1087
+ function parseAnd() {
1088
+ let left = parseUnary();
1089
+ while (pos < args.length) {
1090
+ const tok = peek();
1091
+ if (tok === ')' || tok === '-o' || tok === '-or')
1092
+ break;
1093
+ if (tok === '-a' || tok === '-and')
1094
+ pos++;
1095
+ const right = parseUnary();
1096
+ const l = left;
1097
+ left = async (e, st) => (await l(e, st)) && (await right(e, st));
1098
+ }
1099
+ return left;
1100
+ }
1101
+ function parseExpr() {
1102
+ let left = parseAnd();
1103
+ while (peek() === '-o' || peek() === '-or') {
1104
+ pos++;
1105
+ const right = parseAnd();
1106
+ const l = left;
1107
+ left = async (e, st) => (await l(e, st)) || (await right(e, st));
1108
+ }
1109
+ return left;
1110
+ }
1111
+ let predicate;
1112
+ try {
1113
+ predicate = pos < args.length ? parseExpr() : TRUE;
1114
+ if (pos < args.length) {
1115
+ throw new FindUsageError(`paths must precede expression: \`${args[pos]}'`);
1116
+ }
1117
+ }
1118
+ catch (e) {
1119
+ if (e instanceof FindUsageError) {
1120
+ ctx.stderr.write(`find: ${e.message}\n`);
1121
+ return 1;
1122
+ }
1123
+ throw e;
1124
+ }
1125
+ // With no action anywhere in the expression the whole of it is printed;
1126
+ // -prune deliberately does not count, so `-prune -o -print` still prints.
1127
+ const test = predicate;
1128
+ if (!hasAction) {
1129
+ predicate = async (e, st) => {
1130
+ const matched = await test(e, st);
1131
+ if (matched)
1132
+ emit(e, '\n');
1133
+ return matched;
1134
+ };
1135
+ }
1136
+ // ── Walk ──────────────────────────────────────────────────────────────
1137
+ let status = 0;
1138
+ const visit = async (entry) => {
1139
+ state.prune = false;
1140
+ if (entry.depth >= state.minDepth)
1141
+ await predicate(entry, state);
1142
+ };
1143
+ const walk = async (entry) => {
1144
+ if (state.quit)
1145
+ return;
1146
+ // A pre-order visit must run before the descent it may prune.
1147
+ if (!state.depthFirst) {
1148
+ await visit(entry);
1149
+ if (state.quit || state.prune)
1150
+ return;
1151
+ }
1152
+ if (entry.type === 'directory' && entry.depth < state.maxDepth) {
1153
+ let entries = [];
1154
+ try {
1155
+ entries = vfs.readdir(entry.vfsPath);
1156
+ }
1157
+ catch {
1158
+ entries = [];
1159
+ }
1160
+ for (const child of entries) {
1161
+ if (state.quit)
1162
+ break;
1163
+ await walk({
1164
+ vfsPath: entry.vfsPath + '/' + child.name,
1165
+ display: entry.display + '/' + child.name,
1166
+ name: child.name,
1167
+ type: child.type,
1168
+ depth: entry.depth + 1,
1169
+ });
1170
+ }
1171
+ }
1172
+ // Post-order: the visit that -depth and -delete need, after the
1173
+ // children it must outlive.
1174
+ if (state.depthFirst && !state.quit)
1175
+ await visit(entry);
1176
+ };
1177
+ for (const startArg of startArgs) {
1178
+ if (state.quit)
1179
+ break;
1180
+ // `find dir/` prints `dir/empty.txt`, so the separator is not doubled.
1181
+ const display = startArg.length > 1 ? startArg.replace(/\/+$/, '') : startArg;
1182
+ const vfsPath = resolvePath(ctx.cwd, startArg);
1183
+ let type;
1184
+ try {
1185
+ type = vfs.stat(vfsPath).type;
1186
+ }
1187
+ catch {
1188
+ ctx.stderr.write(`find: '${startArg}': No such file or directory\n`);
1189
+ status = 1;
1190
+ continue;
1191
+ }
1192
+ await walk({
1193
+ vfsPath,
1194
+ display,
1195
+ name: display.split('/').pop() || display,
1196
+ type,
1197
+ depth: 0,
1198
+ });
1199
+ }
1200
+ for (const batch of execBatches) {
1201
+ if (batch.pending.length > 0)
1202
+ await runExec([...batch.argv, ...batch.pending]);
1203
+ }
1204
+ return status;
1205
+ };
1206
+ }
1207
+ /**
1208
+ * shell compatibility (2026-05-11): grep flag handling.
1209
+ *
1210
+ * Pre-fix gaps:
1211
+ * -c X matched lines were printed (count mode ignored from stdin)
1212
+ * -n didn't prepend line number
1213
+ * -w word-boundary not added to regex
1214
+ * -l not implemented (no flag check)
1215
+ * -E flag parsed but didn't enable extended regex (JS RegExp
1216
+ * already does ERE-equivalent)
1217
+ *
1218
+ * Fix: parse flags once into a struct; unify stdin + file + recursive
1219
+ * paths through a single `processLines` helper that honours every
1220
+ * flag consistently.
1221
+ */
1222
+ function mkGrep(vfs) {
1223
+ return (ctx) => {
1224
+ const args = [...ctx.args];
1225
+ // Parse flags. Support combined `-rni` form (single dash + chars).
1226
+ let recursive = false, ignoreCase = false, lineNum = false;
1227
+ let countOnly = false, invertMatch = false, wordMatch = false;
1228
+ let filesOnly = false; // -l
1229
+ let quiet = false; // -q
1230
+ let positional = [];
1231
+ for (let i = 0; i < args.length; i++) {
1232
+ const a = args[i];
1233
+ if (a === '--') {
1234
+ positional.push(...args.slice(i + 1));
1235
+ break;
1236
+ }
1237
+ if (a === '-r' || a === '-R' || a === '--recursive') {
1238
+ recursive = true;
1239
+ continue;
1240
+ }
1241
+ if (a === '-i' || a === '--ignore-case') {
1242
+ ignoreCase = true;
1243
+ continue;
1244
+ }
1245
+ if (a === '-n' || a === '--line-number') {
1246
+ lineNum = true;
1247
+ continue;
1248
+ }
1249
+ if (a === '-c' || a === '--count') {
1250
+ countOnly = true;
1251
+ continue;
1252
+ }
1253
+ if (a === '-v' || a === '--invert-match') {
1254
+ invertMatch = true;
1255
+ continue;
1256
+ }
1257
+ if (a === '-w' || a === '--word-regexp') {
1258
+ wordMatch = true;
1259
+ continue;
1260
+ }
1261
+ if (a === '-l' || a === '--files-with-matches') {
1262
+ filesOnly = true;
1263
+ continue;
1264
+ }
1265
+ if (a === '-q' || a === '--quiet' || a === '--silent') {
1266
+ quiet = true;
1267
+ continue;
1268
+ }
1269
+ if (a === '-E' || a === '--extended-regexp') { /* JS regex is ERE-ish */
1270
+ continue;
1271
+ }
1272
+ if (a === '-F' || a === '--fixed-strings') {
1273
+ // Mark as literal — handled below via escape.
1274
+ args.__fixedStrings = true;
1275
+ continue;
1276
+ }
1277
+ if (a.startsWith('-') && a.length > 1 && !a.startsWith('--')) {
1278
+ // Combined short flags like -rni
1279
+ for (const ch of a.slice(1)) {
1280
+ if (ch === 'r' || ch === 'R')
1281
+ recursive = true;
1282
+ else if (ch === 'i')
1283
+ ignoreCase = true;
1284
+ else if (ch === 'n')
1285
+ lineNum = true;
1286
+ else if (ch === 'c')
1287
+ countOnly = true;
1288
+ else if (ch === 'v')
1289
+ invertMatch = true;
1290
+ else if (ch === 'w')
1291
+ wordMatch = true;
1292
+ else if (ch === 'l')
1293
+ filesOnly = true;
1294
+ else if (ch === 'q')
1295
+ quiet = true;
1296
+ else if (ch === 'E') { /* ERE noop */ }
1297
+ else if (ch === 'F')
1298
+ args.__fixedStrings = true;
1299
+ }
1300
+ continue;
1301
+ }
1302
+ positional.push(a);
1303
+ }
1304
+ if (positional.length < 1) {
1305
+ ctx.stderr.write('Usage: grep [-rnicvlqEFw] PATTERN [FILE...]\n');
1306
+ return 1;
1307
+ }
1308
+ let pattern = positional[0];
1309
+ const targets = positional.slice(1);
1310
+ if (args.__fixedStrings) {
1311
+ // -F: escape regex metacharacters for literal match.
1312
+ pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1313
+ }
1314
+ if (wordMatch)
1315
+ pattern = `\\b(?:${pattern})\\b`;
1316
+ const flags = ignoreCase ? 'i' : '';
1317
+ let re;
1318
+ try {
1319
+ re = new RegExp(pattern, flags);
1320
+ }
1321
+ catch {
1322
+ ctx.stderr.write(`grep: invalid regex: ${pattern}\n`);
1323
+ return 1;
1324
+ }
1325
+ let found = false;
1326
+ let failed = false;
1327
+ function processLines(lines, label) {
1328
+ let count = 0;
1329
+ let matchedHere = false;
1330
+ for (let i = 0; i < lines.length; i++) {
1331
+ const line = lines[i];
1332
+ // Skip a trailing empty line from a content that ended with \\n.
1333
+ if (i === lines.length - 1 && line === '')
1334
+ continue;
1335
+ const isMatch = re.test(line);
1336
+ if (isMatch !== invertMatch) {
1337
+ found = true;
1338
+ matchedHere = true;
1339
+ count++;
1340
+ // -q asks only whether anything matched; printing is the caller's
1341
+ // way of saying it wants to see it.
1342
+ if (quiet)
1343
+ return;
1344
+ if (filesOnly) {
1345
+ // -l: emit file label once, stop scanning.
1346
+ ctx.stdout.write(label + '\n');
1347
+ return;
1348
+ }
1349
+ if (!countOnly) {
1350
+ const labelPrefix = (targets.length > 1 || recursive) && label ? label + ':' : '';
1351
+ const linePrefix = lineNum ? (i + 1) + ':' : '';
1352
+ ctx.stdout.write(labelPrefix + linePrefix + line + '\n');
1353
+ }
1354
+ }
1355
+ }
1356
+ if (countOnly && !quiet) {
1357
+ const labelPrefix = (targets.length > 1 || recursive) && label ? label + ':' : '';
1358
+ ctx.stdout.write(labelPrefix + count + '\n');
1359
+ }
1360
+ void matchedHere;
1361
+ }
1362
+ function grepFile(path, label) {
1363
+ try {
1364
+ const content = vfs.readFileString(path);
1365
+ processLines(content.split('\n'), label);
1366
+ }
1367
+ catch (error) {
1368
+ ctx.stderr.write(`grep: ${label}: ${fsErrorMessage(error)}\n`);
1369
+ failed = true;
1370
+ }
1371
+ }
1372
+ function walkDir(dir) {
1373
+ try {
1374
+ for (const e of vfs.readdir(dir)) {
1375
+ const fp = dir + '/' + e.name;
1376
+ if (e.type === 'file')
1377
+ grepFile(fp, '/' + fp);
1378
+ else if (e.type === 'directory')
1379
+ walkDir(fp);
1380
+ }
1381
+ }
1382
+ catch { }
1383
+ }
1384
+ if (targets.length === 0 && recursive) {
1385
+ walkDir((ctx.cwd || '/home/user').replace(/^\/+/, ''));
1386
+ }
1387
+ else if (targets.length === 0) {
1388
+ // Read from stdin (if piped) — single virtual "file" with no label.
1389
+ if (ctx.stdin) {
1390
+ processLines(ctx.stdin.split('\n'), '');
1391
+ }
1392
+ }
1393
+ else {
1394
+ for (const target of targets) {
1395
+ const fp = resolvePath(ctx.cwd, target);
1396
+ try {
1397
+ if (vfs.exists(fp) && vfs.isDirectory(fp)) {
1398
+ if (recursive)
1399
+ walkDir(fp);
1400
+ }
1401
+ else {
1402
+ grepFile(fp, target);
1403
+ }
1404
+ }
1405
+ catch (error) {
1406
+ ctx.stderr.write(`grep: ${target}: ${fsErrorMessage(error)}\n`);
1407
+ failed = true;
1408
+ }
1409
+ }
1410
+ }
1411
+ return !failed && found ? 0 : 1;
1412
+ };
1413
+ }
1414
+ /**
1415
+ * SHELL-R6-B2 follow-on: streaming head.
1416
+ *
1417
+ * When invoked on a pipeline (`producer | head`), receives a pipe
1418
+ * reader (object with .read()) — NOT a coalesced string — and reads
1419
+ * line-by-line until N lines are emitted. Closes the reader by
1420
+ * draining null (or by the SHELL-R6-2 abort cascade kicking in when
1421
+ * we return).
1422
+ *
1423
+ * Why: the original head implementation read via readAll(), which never returns when
1424
+ * upstream is `yes`. Our SHELL-R6-2 pipeline abort only fires when
1425
+ * the consumer resolves — so head must resolve quickly via its own
1426
+ * line-count termination, which then triggers the cascade.
1427
+ *
1428
+ * Backward compat: file-args (head FILE) and the string-stdin case
1429
+ * (when wrap already coalesced) still work via the same code path.
1430
+ */
1431
+ function mkHead(_vfs) {
1432
+ return async (ctx) => {
1433
+ const parsed = parseHeadArgs(ctx.args);
1434
+ if (parsed.error) {
1435
+ ctx.stderr.write(`head: ${parsed.error}\n`);
1436
+ return 1;
1437
+ }
1438
+ const { lines: n, bytes, files } = parsed;
1439
+ if (bytes !== undefined)
1440
+ return headBytes(ctx, files, bytes);
1441
+ if (files.length === 0) {
1442
+ // Pipe / stdin case.
1443
+ const stdin = ctx.stdin;
1444
+ if (!stdin)
1445
+ return 0;
1446
+ // Streaming pipe-reader path: read chunks until N lines.
1447
+ if (typeof stdin !== 'string' && typeof stdin.read === 'function') {
1448
+ let buffered = '';
1449
+ let emitted = 0;
1450
+ const out = [];
1451
+ while (emitted < n) {
1452
+ const chunk = await stdin.read();
1453
+ if (chunk === null)
1454
+ break;
1455
+ buffered += chunk;
1456
+ // Process complete lines while we have them.
1457
+ let nlIdx;
1458
+ while (emitted < n && (nlIdx = buffered.indexOf('\n')) !== -1) {
1459
+ out.push(buffered.substring(0, nlIdx));
1460
+ buffered = buffered.substring(nlIdx + 1);
1461
+ emitted++;
1462
+ }
1463
+ }
1464
+ // Handle a partial line if N hit mid-buffer and producer hasn't
1465
+ // sent a trailing newline yet — emit it only if we haven't
1466
+ // already hit the N-line cap (POSIX head includes partial
1467
+ // tail).
1468
+ if (emitted < n && buffered.length > 0) {
1469
+ out.push(buffered);
1470
+ }
1471
+ ctx.stdout.write(out.join('\n') + '\n');
1472
+ return 0;
1473
+ }
1474
+ // Legacy string-stdin path (kept for wrap's pre-coalesced case).
1475
+ if (typeof stdin === 'string') {
1476
+ ctx.stdout.write(stdin.split('\n').slice(0, n).join('\n') + '\n');
1477
+ return 0;
1478
+ }
1479
+ return 0;
1480
+ }
1481
+ for (const [index, f] of files.entries()) {
1482
+ const path = absolutePath(ctx.cwd, f);
1483
+ try {
1484
+ const content = readWholeFileString(ctx, path);
1485
+ if (files.length > 1)
1486
+ ctx.stdout.write(`${index > 0 ? '\n' : ''}==> ${f} <==\n`);
1487
+ ctx.stdout.write(content.split('\n').slice(0, n).join('\n') + '\n');
1488
+ }
1489
+ catch (error) {
1490
+ ctx.stderr.write(`head: ${f}: ${fsErrorMessage(error)}\n`);
1491
+ return 1;
1492
+ }
1493
+ }
1494
+ return 0;
1495
+ };
1496
+ }
1497
+ /** `-c N`, `-cN`, `--bytes=N`, `-n N`, `-nN`, `--lines=N`, `-N`, `-q`, `-v`. */
1498
+ function parseHeadArgs(args) {
1499
+ const result = { lines: 10, files: [] };
1500
+ for (let i = 0; i < args.length; i++) {
1501
+ const arg = args[i];
1502
+ const option = matchCountOption(arg, args[i + 1], 'c', 'bytes')
1503
+ ?? matchCountOption(arg, args[i + 1], 'n', 'lines');
1504
+ if (option) {
1505
+ i += option.consumed;
1506
+ const count = parseByteCount(option.value);
1507
+ if (count === null) {
1508
+ const what = option.flag === 'c' ? 'bytes' : 'lines';
1509
+ return { ...result, error: `invalid number of ${what}: '${option.value}'` };
1510
+ }
1511
+ if (option.flag === 'c')
1512
+ result.bytes = count;
1513
+ else
1514
+ result.lines = count;
1515
+ }
1516
+ else if (/^-\d+$/.test(arg)) {
1517
+ result.lines = Number.parseInt(arg.slice(1), 10);
1518
+ }
1519
+ else if (arg === '-q' || arg === '--quiet' || arg === '-v' || arg === '--verbose') {
1520
+ continue;
1521
+ }
1522
+ else if (arg !== '-' && arg.startsWith('-') && !arg.startsWith('--') && arg.length > 1) {
1523
+ // A cluster of switches, `-qn 1`; `c` and `n` take the rest of the
1524
+ // cluster or the next argument.
1525
+ let consumed = false;
1526
+ for (let j = 1; j < arg.length && !consumed; j++) {
1527
+ const flag = arg[j];
1528
+ if (flag === 'q' || flag === 'v')
1529
+ continue;
1530
+ if (flag !== 'c' && flag !== 'n') {
1531
+ return { ...result, error: `invalid option -- '${flag}'` };
1532
+ }
1533
+ const text = arg.slice(j + 1) || (args[++i] ?? '');
1534
+ const count = parseByteCount(text);
1535
+ if (count === null) {
1536
+ const what = flag === 'c' ? 'bytes' : 'lines';
1537
+ return { ...result, error: `invalid number of ${what}: '${text}'` };
1538
+ }
1539
+ if (flag === 'c')
1540
+ result.bytes = count;
1541
+ else
1542
+ result.lines = count;
1543
+ consumed = true;
1544
+ }
1545
+ }
1546
+ else if (arg !== '-' && arg.startsWith('--')) {
1547
+ return { ...result, error: `unrecognized option '${arg}'` };
1548
+ }
1549
+ else {
1550
+ result.files.push(arg);
1551
+ }
1552
+ }
1553
+ return result;
1554
+ }
1555
+ function matchCountOption(arg, next, flag, long) {
1556
+ if (arg === `-${flag}`)
1557
+ return { flag, value: next ?? '', consumed: 1 };
1558
+ if (arg.startsWith(`-${flag}`) && arg.length > 2)
1559
+ return { flag, value: arg.slice(2), consumed: 0 };
1560
+ if (arg === `--${long}`)
1561
+ return { flag, value: next ?? '', consumed: 1 };
1562
+ if (arg.startsWith(`--${long}=`))
1563
+ return { flag, value: arg.slice(long.length + 3), consumed: 0 };
1564
+ return null;
1565
+ }
1566
+ /** `head`/`dd`-style counts: plain digits with an optional binary/SI suffix. */
1567
+ function parseByteCount(value) {
1568
+ const match = /^(\d+)([bkKmMgG]?[Bb]?)$/.exec(value.trim());
1569
+ if (!match)
1570
+ return null;
1571
+ const scale = {
1572
+ '': 1, b: 512, k: 1024, K: 1024, kB: 1000, KB: 1000,
1573
+ m: 1024 ** 2, M: 1024 ** 2, mB: 1000 ** 2, MB: 1000 ** 2,
1574
+ g: 1024 ** 3, G: 1024 ** 3, gB: 1000 ** 3, GB: 1000 ** 3,
1575
+ };
1576
+ const factor = scale[match[2]];
1577
+ if (factor === undefined)
1578
+ return null;
1579
+ return Number.parseInt(match[1], 10) * factor;
1580
+ }
1581
+ /**
1582
+ * `head -c N` — emit the first N bytes. Streams through the positional read
1583
+ * so byte counts hold for any N and character devices such as /dev/zero,
1584
+ * which have no stored content to read whole, work like they do on Unix.
1585
+ */
1586
+ async function headBytes(ctx, files, limit) {
1587
+ const writer = new SinkWriter(ctx.stdout);
1588
+ if (files.length === 0 || (files.length === 1 && files[0] === '-')) {
1589
+ await streamStdinBytes(ctx, writer, limit);
1590
+ writer.end();
1591
+ return 0;
1592
+ }
1593
+ let exit = 0;
1594
+ for (const f of files) {
1595
+ const path = absolutePath(ctx.cwd, f);
1596
+ try {
1597
+ if (files.length > 1)
1598
+ ctx.stdout.write(`==> ${f} <==\n`);
1599
+ streamRange((offset, length) => ctx.vfs.readRange(path, offset, length), writer, {
1600
+ length: limit,
1601
+ signal: ctx.signal,
1602
+ });
1603
+ }
1604
+ catch (error) {
1605
+ ctx.stderr.write(`head: ${f}: ${fsErrorMessage(error)}\n`);
1606
+ exit = 1;
1607
+ }
1608
+ }
1609
+ writer.end();
1610
+ return exit;
1611
+ }
1612
+ /**
1613
+ * Pull at most `limit` bytes from stdin, whether the shell handed us an
1614
+ * already-drained string or a live pipe reader. Reading only the string form
1615
+ * would make `producer | head -c N` emit nothing at all.
1616
+ */
1617
+ async function streamStdinBytes(ctx, writer, limit) {
1618
+ const stdin = ctx.stdin;
1619
+ if (typeof stdin === 'string') {
1620
+ writer.write(enc.encode(stdin).subarray(0, limit));
1621
+ return;
1622
+ }
1623
+ const reader = stdin;
1624
+ if (typeof reader?.read !== 'function')
1625
+ return;
1626
+ let copied = 0;
1627
+ while (copied < limit) {
1628
+ const want = limit - copied;
1629
+ const chunk = reader.readBytes ? await reader.readBytes(want) : await reader.read();
1630
+ if (chunk === null)
1631
+ break;
1632
+ const bytes = enc.encode(chunk).subarray(0, want);
1633
+ writer.write(bytes);
1634
+ copied += bytes.length;
1635
+ }
1636
+ }
1637
+ /** Absolute, mount-aware path — `ctx.vfs` resolves virtual mounts like /dev. */
1638
+ function absolutePath(cwd, target) {
1639
+ return '/' + resolvePath(cwd, target);
1640
+ }
1641
+ function readWholeFileString(ctx, path) {
1642
+ if (ctx.vfs.stat(path).type === 'directory') {
1643
+ throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });
1644
+ }
1645
+ return dec.decode(ctx.vfs.readFile(path));
1646
+ }
1647
+ /**
1648
+ * `tail [-n N] [-n +N] [-N] [-q] [-v] [FILE…]`.
1649
+ *
1650
+ * The previous parse only knew a separate `-n N`, matched its operand by
1651
+ * `indexOf` (so a file literally named like the count vanished), and sliced
1652
+ * the split lines without dropping the empty string a trailing newline leaves
1653
+ * behind — `tail -n 1 file` printed a blank line instead of the last line.
1654
+ */
1655
+ function mkTail(vfs) {
1656
+ return (ctx) => {
1657
+ const parsed = parseTailArgs(ctx.args);
1658
+ if (parsed.error) {
1659
+ ctx.stderr.write(`tail: ${parsed.error}\n`);
1660
+ return 1;
1661
+ }
1662
+ const { count, fromStart, files, verbose } = parsed;
1663
+ const emit = (content) => {
1664
+ const lines = content.split('\n');
1665
+ if (lines[lines.length - 1] === '')
1666
+ lines.pop();
1667
+ const selected = fromStart ? lines.slice(Math.max(0, count - 1)) : lines.slice(-count);
1668
+ if (selected.length > 0)
1669
+ ctx.stdout.write(selected.join('\n') + '\n');
1670
+ };
1671
+ if (files.length === 0) {
1672
+ if (ctx.stdin)
1673
+ emit(ctx.stdin);
1674
+ return 0;
1675
+ }
1676
+ const label = verbose || files.length > 1;
1677
+ let exit = 0;
1678
+ for (const [index, f] of files.entries()) {
1679
+ try {
1680
+ const content = readWholeFileString(ctx, absolutePath(ctx.cwd, f));
1681
+ if (label)
1682
+ ctx.stdout.write(`${index > 0 ? '\n' : ''}==> ${f} <==\n`);
1683
+ emit(content);
1684
+ }
1685
+ catch (error) {
1686
+ ctx.stderr.write(`tail: ${f}: ${fsErrorMessage(error)}\n`);
1687
+ exit = 1;
1688
+ }
1689
+ }
1690
+ void vfs;
1691
+ return exit;
1692
+ };
1693
+ }
1694
+ function applyTailCount(result, spec) {
1695
+ const value = /^([+-]?)(\d+)$/.exec(spec.trim());
1696
+ if (value === null)
1697
+ return `invalid number of lines: '${spec}'`;
1698
+ result.fromStart = value[1] === '+';
1699
+ result.count = Number.parseInt(value[2], 10);
1700
+ return null;
1701
+ }
1702
+ function parseTailArgs(args) {
1703
+ const result = { count: 10, fromStart: false, files: [], verbose: false };
1704
+ let stop = false;
1705
+ for (let i = 0; i < args.length; i++) {
1706
+ const arg = args[i];
1707
+ if (stop || arg === '-' || !arg.startsWith('-')) {
1708
+ result.files.push(arg);
1709
+ continue;
1710
+ }
1711
+ if (arg === '--') {
1712
+ stop = true;
1713
+ continue;
1714
+ }
1715
+ if (arg.startsWith('--lines=')) {
1716
+ const error = applyTailCount(result, arg.slice(8));
1717
+ if (error)
1718
+ return { ...result, error };
1719
+ continue;
1720
+ }
1721
+ if (arg === '--lines') {
1722
+ const error = applyTailCount(result, args[++i] ?? '');
1723
+ if (error)
1724
+ return { ...result, error };
1725
+ continue;
1726
+ }
1727
+ // `-5` is the count on its own; anything else is a cluster of short
1728
+ // options, where `n` takes the rest of the cluster or the next argument.
1729
+ if (/^-\+?\d+$/.test(arg)) {
1730
+ const error = applyTailCount(result, arg.slice(1));
1731
+ if (error)
1732
+ return { ...result, error };
1733
+ continue;
1734
+ }
1735
+ let consumedCount = false;
1736
+ for (let j = 1; j < arg.length && !consumedCount; j++) {
1737
+ const flag = arg[j];
1738
+ if (flag === 'q')
1739
+ result.verbose = false;
1740
+ else if (flag === 'v')
1741
+ result.verbose = true;
1742
+ else if (flag === 'n') {
1743
+ const error = applyTailCount(result, arg.slice(j + 1) || (args[++i] ?? ''));
1744
+ if (error)
1745
+ return { ...result, error };
1746
+ consumedCount = true;
1747
+ }
1748
+ else
1749
+ return { ...result, error: `invalid option -- '${flag}'` };
1750
+ }
1751
+ }
1752
+ return result;
1753
+ }
1754
+ const WC_SPEC = {
1755
+ lines: { type: 'boolean', short: 'l' },
1756
+ words: { type: 'boolean', short: 'w' },
1757
+ bytes: { type: 'boolean', short: 'c' },
1758
+ chars: { type: 'boolean', short: 'm' },
1759
+ };
1760
+ function mkWc(vfs) {
1761
+ return (ctx) => {
1762
+ const { flags: parsed, positional, unknown } = parseArgs(ctx.args, WC_SPEC);
1763
+ if (unknown.length > 0) {
1764
+ ctx.stderr.write(`wc: invalid option -- '${unknown[0].replace(/^-+/, '')}'\n`);
1765
+ return 1;
1766
+ }
1767
+ const hasFlags = parsed.lines === true || parsed.words === true
1768
+ || parsed.bytes === true || parsed.chars === true;
1769
+ const selected = {
1770
+ lines: !hasFlags || parsed.lines === true,
1771
+ words: !hasFlags || parsed.words === true,
1772
+ bytes: !hasFlags || parsed.bytes === true || parsed.chars === true,
1773
+ };
1774
+ const columns = Number(selected.lines) + Number(selected.words) + Number(selected.bytes);
1775
+ const files = positional;
1776
+ // BUG-SWEEP-3 (2026-05-11): the byte count is the raw Uint8Array length,
1777
+ // not enc.encode(decoded).length — decoding a binary file substitutes
1778
+ // U+FFFD for each invalid byte and re-encoding turns one byte into three.
1779
+ const measure = (rawBytes) => {
1780
+ const text = selected.lines || selected.words
1781
+ ? new TextDecoder('utf-8').decode(rawBytes)
1782
+ : '';
1783
+ const counts = [];
1784
+ if (selected.lines)
1785
+ counts.push(text.split('\n').length - (text.endsWith('\n') ? 1 : 0));
1786
+ if (selected.words)
1787
+ counts.push(text.split(/\s+/).filter(Boolean).length);
1788
+ if (selected.bytes)
1789
+ counts.push(rawBytes.length);
1790
+ return counts;
1791
+ };
1792
+ const emit = (counts, width, label) => {
1793
+ ctx.stdout.write(counts.map((c) => String(c).padStart(width)).join(' ') + (label ? ' ' + label : '') + '\n');
1794
+ };
1795
+ if (files.length === 0) {
1796
+ const bytes = enc.encode(ctx.stdin ?? '');
1797
+ // Nothing bounds a stream's counts ahead of time, so a multi-column
1798
+ // report over standard input uses the fixed width GNU falls back to.
1799
+ emit(measure(bytes), columns === 1 ? 0 : 7, '');
1800
+ return 0;
1801
+ }
1802
+ const read = [];
1803
+ let exit = 0;
1804
+ for (const f of files) {
1805
+ try {
1806
+ read.push({ label: f, bytes: vfs.readFile(resolvePath(ctx.cwd, f)) });
1807
+ }
1808
+ catch {
1809
+ ctx.stderr.write(`wc: ${f}: No such file\n`);
1810
+ exit = 1;
1811
+ }
1812
+ }
1813
+ // A file's size bounds every count it can produce, which is the width GNU
1814
+ // lays the columns out to. One column of one file needs no padding.
1815
+ const width = columns === 1 && read.length <= 1
1816
+ ? 1
1817
+ : Math.max(1, ...read.map((entry) => String(entry.bytes.length).length));
1818
+ const totals = new Array(columns).fill(0);
1819
+ for (const entry of read) {
1820
+ const counts = measure(entry.bytes);
1821
+ counts.forEach((count, i) => { totals[i] += count; });
1822
+ emit(counts, width, entry.label);
1823
+ }
1824
+ if (read.length > 1)
1825
+ emit(totals, width, 'total');
1826
+ return exit;
1827
+ };
1828
+ }
1829
+ const SORT_SPEC = {
1830
+ reverse: { type: 'boolean', short: 'r' },
1831
+ numeric: { type: 'boolean', short: 'n' },
1832
+ unique: { type: 'boolean', short: 'u' },
1833
+ 'ignore-case': { type: 'boolean', short: 'f' },
1834
+ };
1835
+ function mkSort(vfs) {
1836
+ return (ctx) => {
1837
+ const { flags, positional, unknown } = parseArgs(ctx.args, SORT_SPEC);
1838
+ if (unknown.length > 0) {
1839
+ ctx.stderr.write(`sort: invalid option -- '${unknown[0].replace(/^-+/, '')}'\n`);
1840
+ return 1;
1841
+ }
1842
+ let input = ctx.stdin || '';
1843
+ if (positional.length > 0 && !input) {
1844
+ try {
1845
+ input = vfs.readFileString(resolvePath(ctx.cwd, positional[0]));
1846
+ }
1847
+ catch {
1848
+ ctx.stderr.write(`sort: ${positional[0]}: No such file\n`);
1849
+ return 1;
1850
+ }
1851
+ }
1852
+ const lines = input.split('\n');
1853
+ if (lines[lines.length - 1] === '')
1854
+ lines.pop();
1855
+ const numeric = flags.numeric === true;
1856
+ const fold = flags['ignore-case'] === true;
1857
+ const key = (line) => (fold ? line.toLowerCase() : line);
1858
+ lines.sort((a, b) => (numeric
1859
+ ? (Number.parseFloat(a) || 0) - (Number.parseFloat(b) || 0)
1860
+ : key(a).localeCompare(key(b))));
1861
+ if (flags.reverse)
1862
+ lines.reverse();
1863
+ // -u drops adjacent duplicates after sorting, so it compares by the same
1864
+ // key the sort used rather than by the whole line.
1865
+ const result = flags.unique
1866
+ ? lines.filter((line, i) => i === 0 || compareSortKeys(lines[i - 1], line, numeric, fold) !== 0)
1867
+ : lines;
1868
+ if (result.length > 0)
1869
+ ctx.stdout.write(result.join('\n') + '\n');
1870
+ return 0;
1871
+ };
1872
+ }
1873
+ function compareSortKeys(a, b, numeric, fold) {
1874
+ if (numeric)
1875
+ return (Number.parseFloat(a) || 0) - (Number.parseFloat(b) || 0);
1876
+ return fold ? a.toLowerCase().localeCompare(b.toLowerCase()) : a.localeCompare(b);
1877
+ }
1878
+ const UNIQ_SPEC = {
1879
+ count: { type: 'boolean', short: 'c' },
1880
+ repeated: { type: 'boolean', short: 'd' },
1881
+ unique: { type: 'boolean', short: 'u' },
1882
+ 'ignore-case': { type: 'boolean', short: 'i' },
1883
+ };
1884
+ function mkUniq(vfs) {
1885
+ return (ctx) => {
1886
+ const { flags, positional, unknown } = parseArgs(ctx.args, UNIQ_SPEC);
1887
+ if (unknown.length > 0) {
1888
+ ctx.stderr.write(`uniq: invalid option -- '${unknown[0].replace(/^-+/, '')}'\n`);
1889
+ return 1;
1890
+ }
1891
+ // File operands were ignored outright, so `uniq file` read stdin and
1892
+ // printed nothing at all.
1893
+ let input = ctx.stdin || '';
1894
+ if (positional.length > 0 && positional[0] !== '-') {
1895
+ try {
1896
+ input = vfs.readFileString(resolvePath(ctx.cwd, positional[0]));
1897
+ }
1898
+ catch {
1899
+ ctx.stderr.write(`uniq: ${positional[0]}: No such file\n`);
1900
+ return 1;
1901
+ }
1902
+ }
1903
+ const lines = input.split('\n');
1904
+ const countFlag = flags.count === true;
1905
+ const dupsOnly = flags.repeated === true;
1906
+ const uniquesOnly = flags.unique === true;
1907
+ const fold = flags['ignore-case'] === true;
1908
+ if (lines[lines.length - 1] === '')
1909
+ lines.pop();
1910
+ const same = (a, b) => fold ? a.toLowerCase() === b.toLowerCase() : a === b;
1911
+ const result = [];
1912
+ const flush = (line, count) => {
1913
+ if (dupsOnly && count < 2)
1914
+ return;
1915
+ if (uniquesOnly && count > 1)
1916
+ return;
1917
+ result.push(countFlag ? `${String(count).padStart(7)} ${line}` : line);
1918
+ };
1919
+ let prev = null;
1920
+ let count = 0;
1921
+ for (const line of lines) {
1922
+ if (prev !== null && same(line, prev)) {
1923
+ count++;
1924
+ continue;
1925
+ }
1926
+ if (prev !== null)
1927
+ flush(prev, count);
1928
+ prev = line;
1929
+ count = 1;
1930
+ }
1931
+ if (prev !== null)
1932
+ flush(prev, count);
1933
+ if (result.length > 0)
1934
+ ctx.stdout.write(result.join('\n') + '\n');
1935
+ return 0;
1936
+ };
1937
+ }
1938
+ function mkSed(vfs) {
1939
+ return (ctx) => runSed({
1940
+ args: ctx.args,
1941
+ cwd: ctx.cwd,
1942
+ vfs,
1943
+ stdout: ctx.stdout,
1944
+ stderr: ctx.stderr,
1945
+ stdin: ctx.stdin === undefined ? undefined : stringInput(ctx.stdin),
1946
+ });
1947
+ }
1948
+ function stringInput(text) {
1949
+ return {
1950
+ readAll: async () => text,
1951
+ };
1952
+ }
1953
+ /**
1954
+ * shell compatibility (2026-05-11): expanded awk subset.
1955
+ *
1956
+ * Pre-fix mkAwk supported only:
1957
+ * {print $N}
1958
+ * /pattern/ [{print}]
1959
+ * Anything else → 'awk: unsupported program'.
1960
+ *
1961
+ * This extension adds (all in pure JS — no embedded awk-interpreter):
1962
+ * BEGIN { stmts } — run before any input line
1963
+ * END { stmts } — run after last line
1964
+ * /pat/ { stmts } — per-line conditional action
1965
+ * { stmts } — per-line unconditional action
1966
+ * $0, $1..$N, $NF — field refs in any expression
1967
+ * NR, NF — record number, field count
1968
+ * print EXPR — write EXPR to stdout + newline (comma-sep)
1969
+ * printf "fmt", a, b — printf-style (%s %d %f %x %o + width.prec)
1970
+ * sum += $N — assignment + compound
1971
+ * simple arithmetic — + - * / % () in expression position
1972
+ * numeric literals — integers and decimals
1973
+ * string literals — "..."
1974
+ * user vars — assigned via name = expr or compound
1975
+ *
1976
+ * NOT supported:
1977
+ * - for/while/if (control flow)
1978
+ * - functions
1979
+ * - arrays (assoc / indexed)
1980
+ * - getline
1981
+ * - regex match operator ~/!~ outside pattern position
1982
+ *
1983
+ * The eval engine is a tiny stmt-list runner that compiles each
1984
+ * statement to a JS closure operating on a shared state {vars,
1985
+ * fields[], NR, NF, separator, stdout, stderr}. Statements are
1986
+ * separated by `;` or `\\n`.
1987
+ *
1988
+ * Failure mode: if we can't parse a statement, write a clear error
1989
+ * to stderr and exit 1 (no silent fail).
1990
+ */
1991
+ function mkAwk(vfs) {
1992
+ return (ctx) => {
1993
+ const allArgs = ctx.args;
1994
+ // Parse -F separator if present.
1995
+ let separator = /\s+/;
1996
+ const programArgs = [];
1997
+ const fileArgs = [];
1998
+ for (let i = 0; i < allArgs.length; i++) {
1999
+ const a = allArgs[i];
2000
+ if (a === '-F') {
2001
+ const s = allArgs[++i];
2002
+ if (s)
2003
+ separator = s.length === 1 ? s : new RegExp(s);
2004
+ }
2005
+ else if (a.startsWith('-F')) {
2006
+ const s = a.slice(2);
2007
+ if (s)
2008
+ separator = s.length === 1 ? s : new RegExp(s);
2009
+ }
2010
+ else if (a.startsWith('-')) {
2011
+ // Ignore other flags (silent compat).
2012
+ }
2013
+ else if (programArgs.length === 0) {
2014
+ programArgs.push(a);
2015
+ }
2016
+ else {
2017
+ fileArgs.push(a);
2018
+ }
2019
+ }
2020
+ const program = programArgs[0] || '';
2021
+ let input = ctx.stdin || '';
2022
+ if (fileArgs.length > 0 && !input) {
2023
+ try {
2024
+ input = vfs.readFileString(resolvePath(ctx.cwd, fileArgs[0]));
2025
+ }
2026
+ catch {
2027
+ ctx.stderr.write(`awk: ${fileArgs[0]}: No such file\n`);
2028
+ return 1;
2029
+ }
2030
+ }
2031
+ const blocks = [];
2032
+ let cursor = 0;
2033
+ const src = program.trim();
2034
+ function skipWS() {
2035
+ while (cursor < src.length && /\s/.test(src[cursor]))
2036
+ cursor++;
2037
+ }
2038
+ function parseBraced() {
2039
+ // Assumes src[cursor] === '{'
2040
+ let depth = 0;
2041
+ let start = cursor;
2042
+ while (cursor < src.length) {
2043
+ const ch = src[cursor];
2044
+ if (ch === '{')
2045
+ depth++;
2046
+ else if (ch === '}') {
2047
+ depth--;
2048
+ if (depth === 0) {
2049
+ cursor++;
2050
+ return src.slice(start + 1, cursor - 1);
2051
+ }
2052
+ }
2053
+ else if (ch === '"' || ch === "'") {
2054
+ const quote = ch;
2055
+ cursor++;
2056
+ while (cursor < src.length && src[cursor] !== quote) {
2057
+ if (src[cursor] === '\\')
2058
+ cursor++;
2059
+ cursor++;
2060
+ }
2061
+ }
2062
+ cursor++;
2063
+ }
2064
+ return src.slice(start + 1, cursor);
2065
+ }
2066
+ while (cursor < src.length) {
2067
+ skipWS();
2068
+ if (cursor >= src.length)
2069
+ break;
2070
+ if (src.startsWith('BEGIN', cursor)) {
2071
+ cursor += 5;
2072
+ skipWS();
2073
+ if (src[cursor] !== '{') {
2074
+ ctx.stderr.write('awk: BEGIN without {\n');
2075
+ return 1;
2076
+ }
2077
+ blocks.push({ kind: 'BEGIN', body: parseBraced() });
2078
+ continue;
2079
+ }
2080
+ if (src.startsWith('END', cursor)) {
2081
+ cursor += 3;
2082
+ skipWS();
2083
+ if (src[cursor] !== '{') {
2084
+ ctx.stderr.write('awk: END without {\n');
2085
+ return 1;
2086
+ }
2087
+ blocks.push({ kind: 'END', body: parseBraced() });
2088
+ continue;
2089
+ }
2090
+ if (src[cursor] === '/') {
2091
+ // Pattern /pat/ optionally followed by {body}
2092
+ const pstart = cursor + 1;
2093
+ cursor++;
2094
+ while (cursor < src.length && src[cursor] !== '/') {
2095
+ if (src[cursor] === '\\')
2096
+ cursor++;
2097
+ cursor++;
2098
+ }
2099
+ const patSrc = src.slice(pstart, cursor);
2100
+ cursor++; // past closing /
2101
+ skipWS();
2102
+ let body = 'print';
2103
+ if (cursor < src.length && src[cursor] === '{')
2104
+ body = parseBraced();
2105
+ let re;
2106
+ try {
2107
+ re = new RegExp(patSrc);
2108
+ }
2109
+ catch (e) {
2110
+ ctx.stderr.write(`awk: bad regex /${patSrc}/: ${e?.message || e}\n`);
2111
+ return 1;
2112
+ }
2113
+ blocks.push({ kind: 'PATTERN', pattern: re, body });
2114
+ continue;
2115
+ }
2116
+ if (src[cursor] === '{') {
2117
+ blocks.push({ kind: 'MAIN', body: parseBraced() });
2118
+ continue;
2119
+ }
2120
+ ctx.stderr.write(`awk: parse error at "${src.slice(cursor, cursor + 20)}"\n`);
2121
+ return 1;
2122
+ }
2123
+ /**
2124
+ * Expression evaluator without `new Function`. workerd CSP blocks
2125
+ * dynamic code generation at request time. This is a small
2126
+ * recursive-descent evaluator for the subset:
2127
+ * - literals: number, string ("...")
2128
+ * - field refs: $0, $N, $NF
2129
+ * - builtins: NR, NF
2130
+ * - user vars: identifier (looked up in st.vars; default 0)
2131
+ * - binary ops: + - * / % (numeric)
2132
+ * - parens: (expr)
2133
+ * - string concat happens via space-join in `print` call sites
2134
+ *
2135
+ * The grammar:
2136
+ * expr := term (('+'|'-') term)*
2137
+ * term := factor (('*'|'/'|'%') factor)*
2138
+ * factor := number | string | '$' (number | 'NF') | ident | '(' expr ')'
2139
+ */
2140
+ function evalExpr(expr, st) {
2141
+ const text = expr.trim();
2142
+ let pos = 0;
2143
+ function skipWs() { while (pos < text.length && /\s/.test(text[pos]))
2144
+ pos++; }
2145
+ function peek() { return text[pos]; }
2146
+ function consume(ch) { skipWs(); if (text[pos] === ch) {
2147
+ pos++;
2148
+ return true;
2149
+ } return false; }
2150
+ function expect(ch) { if (!consume(ch))
2151
+ throw new Error(`expected '${ch}' at "${text.slice(pos, pos + 20)}"`); }
2152
+ function parseExpr() {
2153
+ let left = parseTerm();
2154
+ for (;;) {
2155
+ skipWs();
2156
+ const op = text[pos];
2157
+ if (op === '+' || op === '-') {
2158
+ pos++;
2159
+ const right = parseTerm();
2160
+ const ln = toNum(left), rn = toNum(right);
2161
+ left = op === '+' ? ln + rn : ln - rn;
2162
+ }
2163
+ else
2164
+ break;
2165
+ }
2166
+ return left;
2167
+ }
2168
+ function parseTerm() {
2169
+ let left = parseFactor();
2170
+ for (;;) {
2171
+ skipWs();
2172
+ const op = text[pos];
2173
+ if (op === '*' || op === '/' || op === '%') {
2174
+ pos++;
2175
+ const right = parseFactor();
2176
+ const ln = toNum(left), rn = toNum(right);
2177
+ left = op === '*' ? ln * rn : op === '/' ? ln / rn : ln % rn;
2178
+ }
2179
+ else
2180
+ break;
2181
+ }
2182
+ return left;
2183
+ }
2184
+ function parseFactor() {
2185
+ skipWs();
2186
+ if (pos >= text.length)
2187
+ throw new Error(`unexpected end of expression`);
2188
+ const ch = text[pos];
2189
+ // Number
2190
+ if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(text[pos + 1]))) {
2191
+ let start = pos;
2192
+ while (pos < text.length && /[0-9.]/.test(text[pos]))
2193
+ pos++;
2194
+ return parseFloat(text.slice(start, pos));
2195
+ }
2196
+ // String literal (double or single quotes)
2197
+ if (ch === '"' || ch === "'") {
2198
+ const quote = ch;
2199
+ pos++;
2200
+ let s = '';
2201
+ while (pos < text.length && text[pos] !== quote) {
2202
+ if (text[pos] === '\\' && pos + 1 < text.length) {
2203
+ const esc = text[pos + 1];
2204
+ s += esc === 'n' ? '\n' : esc === 't' ? '\t' : esc === 'r' ? '\r' : esc === '\\' ? '\\' : esc === '"' ? '"' : esc === "'" ? "'" : esc;
2205
+ pos += 2;
2206
+ }
2207
+ else {
2208
+ s += text[pos];
2209
+ pos++;
2210
+ }
2211
+ }
2212
+ if (pos < text.length)
2213
+ pos++; // skip closing quote
2214
+ return s;
2215
+ }
2216
+ // Parenthesised
2217
+ if (ch === '(') {
2218
+ pos++;
2219
+ const v = parseExpr();
2220
+ expect(')');
2221
+ return v;
2222
+ }
2223
+ // Unary minus
2224
+ if (ch === '-') {
2225
+ pos++;
2226
+ return -toNum(parseFactor());
2227
+ }
2228
+ // Unary plus
2229
+ if (ch === '+') {
2230
+ pos++;
2231
+ return toNum(parseFactor());
2232
+ }
2233
+ // Field ref: $N or $NF
2234
+ if (ch === '$') {
2235
+ pos++;
2236
+ skipWs();
2237
+ if (text.startsWith('NF', pos)) {
2238
+ pos += 2;
2239
+ return st.fields[st.NF] ?? '';
2240
+ }
2241
+ // Parens around index? $($1+1) etc — not supported, just digits.
2242
+ let nStart = pos;
2243
+ while (pos < text.length && /[0-9]/.test(text[pos]))
2244
+ pos++;
2245
+ if (nStart === pos)
2246
+ throw new Error(`expected field index after $ at "${text.slice(pos, pos + 10)}"`);
2247
+ const idx = parseInt(text.slice(nStart, pos), 10);
2248
+ return st.fields[idx] ?? '';
2249
+ }
2250
+ // Identifier: NR, NF, user var
2251
+ if (/[A-Za-z_]/.test(ch)) {
2252
+ let start = pos;
2253
+ while (pos < text.length && /[A-Za-z0-9_]/.test(text[pos]))
2254
+ pos++;
2255
+ const name = text.slice(start, pos);
2256
+ if (name === 'NR')
2257
+ return st.NR;
2258
+ if (name === 'NF')
2259
+ return st.NF;
2260
+ return st.vars[name] !== undefined ? st.vars[name] : 0;
2261
+ }
2262
+ throw new Error(`unexpected '${ch}' at "${text.slice(pos, pos + 20)}"`);
2263
+ }
2264
+ function toNum(v) {
2265
+ if (typeof v === 'number')
2266
+ return v;
2267
+ const n = parseFloat(v);
2268
+ return Number.isFinite(n) ? n : 0;
2269
+ }
2270
+ try {
2271
+ const v = parseExpr();
2272
+ skipWs();
2273
+ if (pos < text.length) {
2274
+ // Trailing junk — could be intentional (e.g. tail of stmt is
2275
+ // separator). Be permissive — return what we have.
2276
+ }
2277
+ return v;
2278
+ }
2279
+ catch (e) {
2280
+ throw new Error(`expr error: ${e?.message || e} in "${expr}"`);
2281
+ }
2282
+ }
2283
+ function stripStringsForScan(s) {
2284
+ // Replace string contents with same-length spaces so positions stay aligned.
2285
+ let out = '';
2286
+ let i = 0;
2287
+ while (i < s.length) {
2288
+ const ch = s[i];
2289
+ if (ch === '"' || ch === "'") {
2290
+ out += ch;
2291
+ i++;
2292
+ while (i < s.length && s[i] !== ch) {
2293
+ if (s[i] === '\\') {
2294
+ out += ' ';
2295
+ i++;
2296
+ }
2297
+ out += ' ';
2298
+ i++;
2299
+ }
2300
+ if (i < s.length) {
2301
+ out += ch;
2302
+ i++;
2303
+ }
2304
+ }
2305
+ else {
2306
+ out += ch;
2307
+ i++;
2308
+ }
2309
+ }
2310
+ return out;
2311
+ }
2312
+ function remapUserVars(s) {
2313
+ // Find identifiers (a-z_), skip ones that are reserved or already
2314
+ // remapped. The simple approach: scan tokens outside string
2315
+ // literals.
2316
+ const RESERVED = new Set([
2317
+ '__f', '__nr', '__nf', '__v',
2318
+ 'true', 'false', 'null', 'undefined', 'NaN', 'Infinity',
2319
+ 'Math', 'String', 'Number', 'Array', 'Object',
2320
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite',
2321
+ 'length', // for str/array .length access — not a free identifier here
2322
+ ]);
2323
+ let out = '';
2324
+ let i = 0;
2325
+ while (i < s.length) {
2326
+ const ch = s[i];
2327
+ if (ch === '"' || ch === "'") {
2328
+ out += ch;
2329
+ i++;
2330
+ while (i < s.length && s[i] !== ch) {
2331
+ if (s[i] === '\\') {
2332
+ out += s[i];
2333
+ i++;
2334
+ }
2335
+ out += s[i];
2336
+ i++;
2337
+ }
2338
+ if (i < s.length) {
2339
+ out += s[i];
2340
+ i++;
2341
+ }
2342
+ continue;
2343
+ }
2344
+ if (/[A-Za-z_]/.test(ch)) {
2345
+ let start = i;
2346
+ while (i < s.length && /[A-Za-z0-9_]/.test(s[i]))
2347
+ i++;
2348
+ const ident = s.slice(start, i);
2349
+ // Skip if previous non-ws char is `.` (member access).
2350
+ let prev = start - 1;
2351
+ while (prev >= 0 && /\s/.test(out[prev]))
2352
+ prev--;
2353
+ if (out[prev] === '.') {
2354
+ out += ident;
2355
+ continue;
2356
+ }
2357
+ if (RESERVED.has(ident)) {
2358
+ out += ident;
2359
+ continue;
2360
+ }
2361
+ // Replace with (__v.ident !== undefined ? __v.ident : 0)
2362
+ out += `(__v.${ident}!==undefined?__v.${ident}:0)`;
2363
+ continue;
2364
+ }
2365
+ out += ch;
2366
+ i++;
2367
+ }
2368
+ return out;
2369
+ }
2370
+ function splitStmts(body) {
2371
+ const stmts = [];
2372
+ let depth = 0;
2373
+ let cur = '';
2374
+ let i = 0;
2375
+ while (i < body.length) {
2376
+ const ch = body[i];
2377
+ if (ch === '"' || ch === "'") {
2378
+ cur += ch;
2379
+ i++;
2380
+ while (i < body.length && body[i] !== ch) {
2381
+ if (body[i] === '\\') {
2382
+ cur += body[i];
2383
+ i++;
2384
+ }
2385
+ cur += body[i];
2386
+ i++;
2387
+ }
2388
+ if (i < body.length) {
2389
+ cur += body[i];
2390
+ i++;
2391
+ }
2392
+ continue;
2393
+ }
2394
+ if (ch === '(' || ch === '[' || ch === '{')
2395
+ depth++;
2396
+ else if (ch === ')' || ch === ']' || ch === '}')
2397
+ depth--;
2398
+ if (depth === 0 && (ch === ';' || ch === '\n')) {
2399
+ const t = cur.trim();
2400
+ if (t)
2401
+ stmts.push(t);
2402
+ cur = '';
2403
+ i++;
2404
+ continue;
2405
+ }
2406
+ cur += ch;
2407
+ i++;
2408
+ }
2409
+ const t = cur.trim();
2410
+ if (t)
2411
+ stmts.push(t);
2412
+ return stmts;
2413
+ }
2414
+ function execStmt(stmt, st) {
2415
+ // print [expr[, expr]*]
2416
+ if (stmt === 'print' || stmt.startsWith('print ') || stmt.startsWith('print\t')) {
2417
+ const rest = stmt.slice(5).trim();
2418
+ if (!rest) {
2419
+ ctx.stdout.write(st.fields[0] + '\n');
2420
+ st.printed = true;
2421
+ return;
2422
+ }
2423
+ // Comma-separated exprs (space joiner). We must split at top-level commas only.
2424
+ const parts = splitTopLevel(rest, ',');
2425
+ const out = parts.map(p => stringify(evalExpr(p, st))).join(' ');
2426
+ ctx.stdout.write(out + '\n');
2427
+ st.printed = true;
2428
+ return;
2429
+ }
2430
+ // printf "fmt", arg, arg, ...
2431
+ if (stmt.startsWith('printf ') || stmt.startsWith('printf(')) {
2432
+ let rest = stmt.startsWith('printf(') ? stmt.slice(7).replace(/\)\s*$/, '') : stmt.slice(7);
2433
+ rest = rest.trim();
2434
+ const parts = splitTopLevel(rest, ',');
2435
+ if (parts.length === 0)
2436
+ return;
2437
+ const fmt = evalExpr(parts[0], st);
2438
+ const fargs = parts.slice(1).map(p => evalExpr(p, st));
2439
+ ctx.stdout.write(printfFormat(String(fmt), fargs));
2440
+ st.printed = true;
2441
+ return;
2442
+ }
2443
+ // next: skip rest of body (no-op here since we re-enter each block fresh)
2444
+ if (stmt === 'next')
2445
+ return;
2446
+ // assignment: IDENT [op]= EXPR
2447
+ // We require a top-level `=` not part of `==` `<=` `>=` `!=`.
2448
+ const eqIdx = findAssignmentEq(stmt);
2449
+ if (eqIdx > 0) {
2450
+ const lhs = stmt.slice(0, eqIdx).trim();
2451
+ const rhs = stmt.slice(eqIdx + 1).trim();
2452
+ // Compound: lhs ends with op (e.g. `sum +`).
2453
+ let op = null;
2454
+ let name = lhs;
2455
+ if (/[+\-*/%]$/.test(lhs)) {
2456
+ op = lhs[lhs.length - 1];
2457
+ name = lhs.slice(0, -1).trim();
2458
+ }
2459
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) {
2460
+ throw new Error(`bad assignment target "${name}"`);
2461
+ }
2462
+ const rv = evalExpr(rhs, st);
2463
+ if (op) {
2464
+ const cur = st.vars[name] !== undefined ? st.vars[name] : 0;
2465
+ const lhsNum = typeof cur === 'number' ? cur : parseFloat(cur);
2466
+ const rvNum = typeof rv === 'number' ? rv : parseFloat(rv);
2467
+ const lN = Number.isFinite(lhsNum) ? lhsNum : 0;
2468
+ const rN = Number.isFinite(rvNum) ? rvNum : 0;
2469
+ st.vars[name] =
2470
+ op === '+' ? lN + rN :
2471
+ op === '-' ? lN - rN :
2472
+ op === '*' ? lN * rN :
2473
+ op === '/' ? lN / rN :
2474
+ op === '%' ? lN % rN : rv;
2475
+ }
2476
+ else {
2477
+ st.vars[name] = rv;
2478
+ }
2479
+ return;
2480
+ }
2481
+ // Bare expression — evaluate for side effects (rare in awk).
2482
+ evalExpr(stmt, st);
2483
+ }
2484
+ function findAssignmentEq(s) {
2485
+ let depth = 0;
2486
+ for (let i = 0; i < s.length; i++) {
2487
+ const ch = s[i];
2488
+ if (ch === '"' || ch === "'") {
2489
+ i++;
2490
+ while (i < s.length && s[i] !== ch) {
2491
+ if (s[i] === '\\')
2492
+ i++;
2493
+ i++;
2494
+ }
2495
+ continue;
2496
+ }
2497
+ if (ch === '(' || ch === '[' || ch === '{')
2498
+ depth++;
2499
+ else if (ch === ')' || ch === ']' || ch === '}')
2500
+ depth--;
2501
+ if (depth === 0 && ch === '=') {
2502
+ const next = s[i + 1];
2503
+ const prev = s[i - 1];
2504
+ if (next === '=' || prev === '=' || prev === '!' || prev === '<' || prev === '>')
2505
+ continue;
2506
+ return i;
2507
+ }
2508
+ }
2509
+ return -1;
2510
+ }
2511
+ function splitTopLevel(s, sep) {
2512
+ const out = [];
2513
+ let depth = 0;
2514
+ let cur = '';
2515
+ let i = 0;
2516
+ while (i < s.length) {
2517
+ const ch = s[i];
2518
+ if (ch === '"' || ch === "'") {
2519
+ cur += ch;
2520
+ i++;
2521
+ while (i < s.length && s[i] !== ch) {
2522
+ if (s[i] === '\\') {
2523
+ cur += s[i];
2524
+ i++;
2525
+ }
2526
+ cur += s[i];
2527
+ i++;
2528
+ }
2529
+ if (i < s.length) {
2530
+ cur += s[i];
2531
+ i++;
2532
+ }
2533
+ continue;
2534
+ }
2535
+ if (ch === '(' || ch === '[' || ch === '{')
2536
+ depth++;
2537
+ else if (ch === ')' || ch === ']' || ch === '}')
2538
+ depth--;
2539
+ if (depth === 0 && ch === sep) {
2540
+ out.push(cur.trim());
2541
+ cur = '';
2542
+ i++;
2543
+ continue;
2544
+ }
2545
+ cur += ch;
2546
+ i++;
2547
+ }
2548
+ if (cur.trim())
2549
+ out.push(cur.trim());
2550
+ return out;
2551
+ }
2552
+ function stringify(v) {
2553
+ if (v === undefined || v === null)
2554
+ return '';
2555
+ if (typeof v === 'number') {
2556
+ if (Number.isInteger(v))
2557
+ return String(v);
2558
+ // awk's OFMT default is "%.6g"
2559
+ return printfFormat('%.6g', [v]);
2560
+ }
2561
+ return String(v);
2562
+ }
2563
+ function printfFormat(fmt, fargs) {
2564
+ let out = '';
2565
+ let i = 0;
2566
+ let argIdx = 0;
2567
+ while (i < fmt.length) {
2568
+ const ch = fmt[i];
2569
+ if (ch === '\\' && i + 1 < fmt.length) {
2570
+ const esc = fmt[i + 1];
2571
+ out += esc === 'n' ? '\n' : esc === 't' ? '\t' : esc === 'r' ? '\r' : esc === '\\' ? '\\' : esc;
2572
+ i += 2;
2573
+ continue;
2574
+ }
2575
+ if (ch === '%' && i + 1 < fmt.length) {
2576
+ // Parse: %[flags][width][.prec]specifier
2577
+ let spec = '%';
2578
+ i++;
2579
+ while (i < fmt.length && /[-+ 0#]/.test(fmt[i])) {
2580
+ spec += fmt[i];
2581
+ i++;
2582
+ }
2583
+ while (i < fmt.length && /[0-9]/.test(fmt[i])) {
2584
+ spec += fmt[i];
2585
+ i++;
2586
+ }
2587
+ if (fmt[i] === '.') {
2588
+ spec += fmt[i];
2589
+ i++;
2590
+ while (i < fmt.length && /[0-9]/.test(fmt[i])) {
2591
+ spec += fmt[i];
2592
+ i++;
2593
+ }
2594
+ }
2595
+ const conv = fmt[i];
2596
+ i++;
2597
+ if (conv === '%') {
2598
+ out += '%';
2599
+ continue;
2600
+ }
2601
+ const arg = fargs[argIdx++];
2602
+ out += formatOne(spec + conv, arg);
2603
+ continue;
2604
+ }
2605
+ out += ch;
2606
+ i++;
2607
+ }
2608
+ return out;
2609
+ }
2610
+ function formatOne(spec, arg) {
2611
+ const conv = spec[spec.length - 1];
2612
+ const flagsAndWidth = spec.slice(1, -1);
2613
+ const dotIdx = flagsAndWidth.indexOf('.');
2614
+ const widthPart = dotIdx >= 0 ? flagsAndWidth.slice(0, dotIdx) : flagsAndWidth;
2615
+ const precPart = dotIdx >= 0 ? flagsAndWidth.slice(dotIdx + 1) : '';
2616
+ let flags = '';
2617
+ let widthStr = '';
2618
+ for (const c of widthPart) {
2619
+ if (/[-+ 0#]/.test(c))
2620
+ flags += c;
2621
+ else
2622
+ widthStr += c;
2623
+ }
2624
+ const width = widthStr ? parseInt(widthStr, 10) : 0;
2625
+ const prec = precPart ? parseInt(precPart, 10) : -1;
2626
+ let body;
2627
+ switch (conv) {
2628
+ case 's':
2629
+ body = String(arg ?? '');
2630
+ if (prec >= 0)
2631
+ body = body.slice(0, prec);
2632
+ break;
2633
+ case 'd':
2634
+ case 'i': {
2635
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(arg));
2636
+ body = String(Number.isFinite(n) ? n : 0);
2637
+ break;
2638
+ }
2639
+ case 'f': {
2640
+ const n = typeof arg === 'number' ? arg : parseFloat(arg);
2641
+ const p = prec < 0 ? 6 : prec;
2642
+ body = (Number.isFinite(n) ? n : 0).toFixed(p);
2643
+ break;
2644
+ }
2645
+ case 'g': {
2646
+ const n = typeof arg === 'number' ? arg : parseFloat(arg);
2647
+ const p = prec < 0 ? 6 : prec;
2648
+ body = (Number.isFinite(n) ? n : 0).toPrecision(p).replace(/\.?0+$/, '');
2649
+ break;
2650
+ }
2651
+ case 'x': {
2652
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(arg));
2653
+ body = (Number.isFinite(n) ? n : 0).toString(16);
2654
+ break;
2655
+ }
2656
+ case 'o': {
2657
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(arg));
2658
+ body = (Number.isFinite(n) ? n : 0).toString(8);
2659
+ break;
2660
+ }
2661
+ case 'c': {
2662
+ if (typeof arg === 'number')
2663
+ body = String.fromCharCode(arg);
2664
+ else
2665
+ body = String(arg).charAt(0);
2666
+ break;
2667
+ }
2668
+ default: body = String(arg);
2669
+ }
2670
+ if (width > body.length) {
2671
+ const pad = flags.includes('0') && (conv === 'd' || conv === 'i' || conv === 'f' || conv === 'x' || conv === 'o') ? '0' : ' ';
2672
+ body = flags.includes('-') ? body.padEnd(width, ' ') : body.padStart(width, pad);
2673
+ }
2674
+ return body;
2675
+ }
2676
+ const state = {
2677
+ vars: {},
2678
+ fields: [],
2679
+ NR: 0,
2680
+ NF: 0,
2681
+ printed: false,
2682
+ };
2683
+ function runBlock(block) {
2684
+ const stmts = splitStmts(block.body);
2685
+ for (const s of stmts) {
2686
+ execStmt(s, state);
2687
+ }
2688
+ }
2689
+ try {
2690
+ // BEGIN blocks first.
2691
+ for (const b of blocks)
2692
+ if (b.kind === 'BEGIN')
2693
+ runBlock(b);
2694
+ // Main loop over input lines.
2695
+ const lines = input.split('\n');
2696
+ // awk default: drop the final empty line if input ended with \n.
2697
+ if (lines.length > 0 && lines[lines.length - 1] === '')
2698
+ lines.pop();
2699
+ for (let li = 0; li < lines.length; li++) {
2700
+ const line = lines[li];
2701
+ const parts = typeof separator === 'string' && separator.length === 1
2702
+ ? line.split(separator)
2703
+ : line.split(separator);
2704
+ state.NR = li + 1;
2705
+ state.NF = parts.filter(p => p !== '').length;
2706
+ state.fields = [line, ...parts];
2707
+ for (const b of blocks) {
2708
+ if (b.kind === 'BEGIN' || b.kind === 'END')
2709
+ continue;
2710
+ if (b.kind === 'PATTERN') {
2711
+ if (b.pattern.test(line))
2712
+ runBlock(b);
2713
+ }
2714
+ else {
2715
+ // MAIN block (no pattern) — always runs.
2716
+ runBlock(b);
2717
+ }
2718
+ }
2719
+ }
2720
+ // END blocks last.
2721
+ for (const b of blocks)
2722
+ if (b.kind === 'END')
2723
+ runBlock(b);
2724
+ }
2725
+ catch (e) {
2726
+ ctx.stderr.write(`awk: ${e?.message || e}\n`);
2727
+ return 1;
2728
+ }
2729
+ return 0;
2730
+ };
2731
+ }
2732
+ /**
2733
+ * shell compatibility (2026-05-11): real xargs implementation.
2734
+ *
2735
+ * Pre-fix the impl printed the command-line it WOULD execute and
2736
+ * returned 0. Real xargs runs the command, possibly batched (-n),
2737
+ * with arguments substituted (-I).
2738
+ *
2739
+ * We do cross-command dispatch through the shell registry, so xargs can drive
2740
+ * `echo`, `cat`, `rm`, `seq`, lazy-loaded builtins — anything in the registry. The execution
2741
+ * runs IN-SUPERVISOR (not through facet spawn) which means it
2742
+ * works for pure-builtins but NOT for facet-direct commands like
2743
+ * `node`, `git`, `npm` (the registry resolver returns those by
2744
+ * name but invoking them requires the cp/facet pipeline).
2745
+ *
2746
+ * Supported flags:
2747
+ * -n NUM run command with at most NUM args per invocation
2748
+ * -I REPL replace REPL in command with the input item
2749
+ * -0 null-byte separator (rare; bash xargs -0 idiom)
2750
+ * default args use args.split(/\s+/) from stdin
2751
+ *
2752
+ * Unsupported (document as gap): -P (parallel), -L (per-line), -p (prompt).
2753
+ */
2754
+ function mkXargs(vfs, registry) {
2755
+ return async (ctx) => {
2756
+ const input = (ctx.stdin || '').trim();
2757
+ if (!input)
2758
+ return 0;
2759
+ // Parse flags first
2760
+ const args = [...ctx.args];
2761
+ let batchSize = Infinity;
2762
+ let replaceTok = null;
2763
+ let nullSep = false;
2764
+ while (args.length > 0 && args[0].startsWith('-')) {
2765
+ const a = args.shift();
2766
+ if (a === '-n') {
2767
+ const n = parseInt(args.shift() || '', 10);
2768
+ if (Number.isFinite(n) && n > 0)
2769
+ batchSize = n;
2770
+ }
2771
+ else if (a.startsWith('-n')) {
2772
+ const n = parseInt(a.slice(2), 10);
2773
+ if (Number.isFinite(n) && n > 0)
2774
+ batchSize = n;
2775
+ }
2776
+ else if (a === '-I') {
2777
+ replaceTok = args.shift() || '{}';
2778
+ batchSize = 1; // -I implies one-arg-per-invocation
2779
+ }
2780
+ else if (a === '-0' || a === '--null') {
2781
+ nullSep = true;
2782
+ }
2783
+ else if (a === '--') {
2784
+ break;
2785
+ }
2786
+ else {
2787
+ // Unknown flag — push back as cmd token (best-effort behavior)
2788
+ args.unshift(a);
2789
+ break;
2790
+ }
2791
+ }
2792
+ // Remaining args: cmd + initial-args. Default: echo.
2793
+ const cmdName = args.shift() || 'echo';
2794
+ const cmdArgsInitial = args;
2795
+ // Split stdin into items
2796
+ const items = nullSep
2797
+ ? input.split('\u0000').filter(Boolean)
2798
+ : input.split(/\s+/).filter(Boolean);
2799
+ // Resolve target command from registry (handles both eager + lazy maps).
2800
+ let target;
2801
+ try {
2802
+ target = await registry.resolve(cmdName);
2803
+ }
2804
+ catch (_e) {
2805
+ target = null;
2806
+ }
2807
+ if (!target) {
2808
+ // Defer to write-to-stderr; mimic real xargs which would exec(2) and fail.
2809
+ ctx.stderr.write(`xargs: ${cmdName}: command not found\n`);
2810
+ return 127;
2811
+ }
2812
+ // Run in batches.
2813
+ const newCtx = (newArgs) => ({
2814
+ pid: ctx.pid,
2815
+ cred: ctx.cred,
2816
+ args: newArgs,
2817
+ env: ctx.env,
2818
+ cwd: ctx.cwd,
2819
+ vfs: ctx.vfs,
2820
+ stdout: ctx.stdout,
2821
+ stderr: ctx.stderr,
2822
+ stdin: '', // xargs doesn't pipe its own stdin to children
2823
+ signal: ctx.signal,
2824
+ setUmask: ctx.setUmask,
2825
+ runAs: ctx.runAs,
2826
+ execInterpreterDepth: ctx.execInterpreterDepth,
2827
+ });
2828
+ let exit = 0;
2829
+ if (replaceTok) {
2830
+ // -I: one invocation per item, replacing token in initial args.
2831
+ for (const item of items) {
2832
+ const subbed = cmdArgsInitial.map(a => a.split(replaceTok).join(item));
2833
+ try {
2834
+ const code = await target(newCtx(subbed));
2835
+ if (typeof code === 'number' && code !== 0)
2836
+ exit = code;
2837
+ }
2838
+ catch (e) {
2839
+ ctx.stderr.write(`xargs: ${cmdName}: ${e?.message || e}\n`);
2840
+ exit = 1;
2841
+ }
2842
+ }
2843
+ }
2844
+ else {
2845
+ // -n N (or unlimited): batch items, append to initial args.
2846
+ const step = Number.isFinite(batchSize) ? batchSize : items.length;
2847
+ for (let i = 0; i < items.length; i += step) {
2848
+ const batch = items.slice(i, i + step);
2849
+ try {
2850
+ const code = await target(newCtx([...cmdArgsInitial, ...batch]));
2851
+ if (typeof code === 'number' && code !== 0)
2852
+ exit = code;
2853
+ }
2854
+ catch (e) {
2855
+ ctx.stderr.write(`xargs: ${cmdName}: ${e?.message || e}\n`);
2856
+ exit = 1;
2857
+ }
2858
+ if (!Number.isFinite(batchSize))
2859
+ break; // single batch when no -n
2860
+ }
2861
+ }
2862
+ return exit;
2863
+ };
2864
+ }
2865
+ function mkTee(vfs) {
2866
+ return (ctx) => {
2867
+ const input = ctx.stdin || '';
2868
+ const append = ctx.args.includes('-a');
2869
+ const files = ctx.args.filter(a => !a.startsWith('-'));
2870
+ ctx.stdout.write(input);
2871
+ for (const f of files) {
2872
+ const fp = resolvePath(ctx.cwd, f);
2873
+ if (append && vfs.exists(fp)) {
2874
+ const existing = vfs.readFileString(fp);
2875
+ vfs.writeFile(fp, existing + input);
2876
+ }
2877
+ else {
2878
+ vfs.writeFile(fp, input);
2879
+ }
2880
+ }
2881
+ return 0;
2882
+ };
2883
+ }
2884
+ /**
2885
+ * shell compatibility (2026-05-11): du flag parsing for combined forms.
2886
+ * Pre-fix `du -sh` didn't activate -h because we checked for literal
2887
+ * `-h` only — `-sh` is a single arg containing both flags. POSIX
2888
+ * conformant short-flag stacking.
2889
+ */
2890
+ function mkDu(vfs) {
2891
+ return (ctx) => {
2892
+ // Parse flags supporting stacked short flags like `-sh`, `-ah`.
2893
+ let showAll = false, human = false, sumOnly = false;
2894
+ const positional = [];
2895
+ for (const a of ctx.args) {
2896
+ if (a.startsWith('-') && a !== '-' && !a.startsWith('--')) {
2897
+ for (const ch of a.slice(1)) {
2898
+ if (ch === 'a')
2899
+ showAll = true;
2900
+ else if (ch === 'h')
2901
+ human = true;
2902
+ else if (ch === 's')
2903
+ sumOnly = true;
2904
+ }
2905
+ }
2906
+ else if (a.startsWith('--')) {
2907
+ if (a === '--all')
2908
+ showAll = true;
2909
+ else if (a === '--human-readable')
2910
+ human = true;
2911
+ else if (a === '--summarize')
2912
+ sumOnly = true;
2913
+ }
2914
+ else {
2915
+ positional.push(a);
2916
+ }
2917
+ }
2918
+ const target = positional[0] || '.';
2919
+ const root = resolvePath(ctx.cwd, target);
2920
+ const fmt = (b) => human ? (b >= 1e6 ? (b / 1e6).toFixed(1) + 'M' : b >= 1e3 ? (b / 1e3).toFixed(1) + 'K' : b + 'B') : String(Math.ceil(b / 1024));
2921
+ let total = 0;
2922
+ function walk(path) {
2923
+ let size = 0;
2924
+ try {
2925
+ const entries = vfs.readdir(path);
2926
+ for (const e of entries) {
2927
+ const fp = path + '/' + e.name;
2928
+ if (e.type === 'directory') {
2929
+ const dirSize = walk(fp);
2930
+ size += dirSize;
2931
+ if (!sumOnly)
2932
+ ctx.stdout.write(`${fmt(dirSize)}\t/${fp}\n`);
2933
+ }
2934
+ else {
2935
+ try {
2936
+ const st = vfs.stat(fp);
2937
+ size += st.size;
2938
+ if (showAll && !sumOnly)
2939
+ ctx.stdout.write(`${fmt(st.size)}\t/${fp}\n`);
2940
+ }
2941
+ catch { }
2942
+ }
2943
+ }
2944
+ }
2945
+ catch { }
2946
+ return size;
2947
+ }
2948
+ total = walk(root);
2949
+ if (sumOnly || !showAll)
2950
+ ctx.stdout.write(`${fmt(total)}\t/${root}\n`);
2951
+ return 0;
2952
+ };
2953
+ }
2954
+ function mkDiff(vfs) {
2955
+ return (ctx) => {
2956
+ if (ctx.args.length < 2) {
2957
+ ctx.stderr.write('Usage: diff FILE1 FILE2\n');
2958
+ return 1;
2959
+ }
2960
+ const f1 = resolvePath(ctx.cwd, ctx.args[0]);
2961
+ const f2 = resolvePath(ctx.cwd, ctx.args[1]);
2962
+ try {
2963
+ const a = vfs.readFileString(f1).split('\n');
2964
+ const b = vfs.readFileString(f2).split('\n');
2965
+ let hasDiff = false;
2966
+ const maxLen = Math.max(a.length, b.length);
2967
+ for (let i = 0; i < maxLen; i++) {
2968
+ if (a[i] !== b[i]) {
2969
+ hasDiff = true;
2970
+ if (a[i] !== undefined && b[i] === undefined)
2971
+ ctx.stdout.write(`${i + 1}d${i}\n< ${a[i]}\n`);
2972
+ else if (a[i] === undefined && b[i] !== undefined)
2973
+ ctx.stdout.write(`${i}a${i + 1}\n> ${b[i]}\n`);
2974
+ else
2975
+ ctx.stdout.write(`${i + 1}c${i + 1}\n< ${a[i]}\n---\n> ${b[i]}\n`);
2976
+ }
2977
+ }
2978
+ return hasDiff ? 1 : 0;
2979
+ }
2980
+ catch (e) {
2981
+ ctx.stderr.write(`diff: ${e.message}\n`);
2982
+ return 2;
2983
+ }
2984
+ };
2985
+ }
2986
+ /**
2987
+ * shell compatibility (2026-05-11): POSIX rm with proper -f semantics.
2988
+ *
2989
+ * The original rm implementation called `r.vfs.stat(...)` and caught `e instanceof VFSError`.
2990
+ * Our SqliteVFSProvider's stat method delegates to SqliteVFS.stat which
2991
+ * throws raw `Error("ENOENT: ...")` — NOT VFSError. That rm path
2992
+ * therefore falls through to `else throw e`, the error propagates up,
2993
+ * and executeCommand returns exit 1.
2994
+ *
2995
+ * Real-world impact: every `rm -rf <nonexistent> && ...` short-circuits.
2996
+ * The most common cleanup idiom in shell scripts.
2997
+ *
2998
+ * Fix: register rm in the registry's `commands` map. Treat -f silently when target is
2999
+ * missing (return 0). Handle both files (unlink) and directories
3000
+ * (rmdir recursive when -r). Translate raw errors so the unix-command
3001
+ * contract is honoured.
3002
+ */
3003
+ /**
3004
+ * shell compatibilityb (2026-05-11): registry-level echo so `X | xargs echo`
3005
+ * resolves. `echo` is a Shell.builtins entry, NOT in the
3006
+ * registry map. xargs's cross-command dispatch goes through
3007
+ * registry.resolve(name) — without a registry entry for echo it falls
3008
+ * back to 'command not found'. The init.ts override for echo flag
3009
+ * handling targets Shell.builtins; we additionally register a copy
3010
+ * here so registry-driven callers (xargs) can find it. Behaviour
3011
+ * matches the BUG-SWEEP-4 nimbusEcho impl: -n / -e / -E / combined.
3012
+ */
3013
+ function mkEcho() {
3014
+ return (ctx) => {
3015
+ const args = ctx.args;
3016
+ let interpretEscapes = false;
3017
+ let suppressNewline = false;
3018
+ let i = 0;
3019
+ while (i < args.length) {
3020
+ const a = args[i];
3021
+ if (a === '--') {
3022
+ i++;
3023
+ break;
3024
+ }
3025
+ if (a === '-n') {
3026
+ suppressNewline = true;
3027
+ i++;
3028
+ continue;
3029
+ }
3030
+ if (a === '-e') {
3031
+ interpretEscapes = true;
3032
+ i++;
3033
+ continue;
3034
+ }
3035
+ if (a === '-E') {
3036
+ interpretEscapes = false;
3037
+ i++;
3038
+ continue;
3039
+ }
3040
+ if (/^-[neE]+$/.test(a)) {
3041
+ for (const ch of a.slice(1)) {
3042
+ if (ch === 'n')
3043
+ suppressNewline = true;
3044
+ else if (ch === 'e')
3045
+ interpretEscapes = true;
3046
+ else if (ch === 'E')
3047
+ interpretEscapes = false;
3048
+ }
3049
+ i++;
3050
+ continue;
3051
+ }
3052
+ break;
3053
+ }
3054
+ let out = args.slice(i).join(' ');
3055
+ if (interpretEscapes) {
3056
+ out = out
3057
+ .replace(/\\\\/g, '\u0000')
3058
+ .replace(/\\n/g, '\n')
3059
+ .replace(/\\t/g, '\t')
3060
+ .replace(/\\r/g, '\r')
3061
+ .replace(/\\b/g, '\b')
3062
+ .replace(/\\f/g, '\f')
3063
+ .replace(/\\v/g, '\v')
3064
+ .replace(/\\a/g, '\x07')
3065
+ .replace(/\\0([0-7]{1,3})?/g, (_m, oct) => String.fromCharCode(oct ? parseInt(oct, 8) : 0))
3066
+ .replace(/\\x([0-9a-fA-F]{1,2})/g, (_m, hex) => String.fromCharCode(parseInt(hex, 16)))
3067
+ .replace(/\u0000/g, '\\');
3068
+ }
3069
+ ctx.stdout.write(suppressNewline ? out : out + '\n');
3070
+ return 0;
3071
+ };
3072
+ }
3073
+ /**
3074
+ * SHELL-R6-4 (2026-05-12): symlink-aware `ls`.
3075
+ *
3076
+ * Pre-fix: the lazy `ls` implementation formatted `mode` with first-char
3077
+ * `d` or `-`; it had no symlink concept,
3078
+ * and our SymlinkRegistry entries are not in the VFS dir listing at all,
3079
+ * so `ls -l` after `ln -s t.txt l.txt` showed ONLY `t.txt`.
3080
+ *
3081
+ * Post-fix:
3082
+ * - `ls` lists VFS dir entries AND SymlinkRegistry entries whose link
3083
+ * path lives in the queried directory.
3084
+ * - `ls -l` shows `lrwxrwxrwx 1 user user N <mtime> <name> -> <target>`
3085
+ * for symlinks (`N` = target string length, matches GNU coreutils).
3086
+ * - Non-symlink rows go through the same formatter so columns line up.
3087
+ * - Hidden-file rule (skip if leading `.`) still honored unless `-a`.
3088
+ *
3089
+ * Args supported: `-l` long, `-a` all, `-1` one-per-line, `-n` numeric
3090
+ * ownership, `-d` directory itself, plus path
3091
+ * positional. Matches the shell `ls` flag surface so we don't regress.
3092
+ */
3093
+ function mkLs(vfs) {
3094
+ return (ctx) => {
3095
+ const args = ctx.args;
3096
+ const flags = new Set(args
3097
+ .filter((arg) => arg.startsWith('-') && !arg.startsWith('--'))
3098
+ .flatMap((arg) => [...arg.slice(1)]));
3099
+ const flagLong = flags.has('l') || flags.has('n');
3100
+ const flagAll = flags.has('a');
3101
+ const flagOne = flags.has('1');
3102
+ const flagNumeric = flags.has('n');
3103
+ const flagDirectory = flags.has('d');
3104
+ const positionals = args.filter(a => !a.startsWith('-'));
3105
+ const targets = positionals.length > 0 ? positionals : [ctx.cwd];
3106
+ const reg = vfs.symlinks;
3107
+ const kvfs = ctx.vfs;
3108
+ function fmtTime(mtime) {
3109
+ const d = new Date(mtime);
3110
+ const mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][d.getMonth()];
3111
+ const day = String(d.getDate()).padStart(2, ' ');
3112
+ const hh = String(d.getHours()).padStart(2, '0');
3113
+ const mm = String(d.getMinutes()).padStart(2, '0');
3114
+ return `${mon} ${day} ${hh}:${mm}`;
3115
+ }
3116
+ let exit = 0;
3117
+ function listDir(dirPath) {
3118
+ const fp = resolvePath(ctx.cwd, dirPath);
3119
+ const out = [];
3120
+ // Real entries via ctx.vfs.readdirStat (Kernel.VFS — handles
3121
+ // mounts like /dev) with fallback to closure-captured SqliteVFS.
3122
+ let real = [];
3123
+ try {
3124
+ if (kvfs && typeof kvfs.readdirStat === 'function') {
3125
+ real = kvfs.readdirStat(fp);
3126
+ }
3127
+ else {
3128
+ const names = vfs.readdir(fp);
3129
+ real = names.map(n => {
3130
+ const childPath = (fp === '/' ? '' : fp.replace(/^\/+/, '').replace(/\/+$/, ''))
3131
+ + '/' + n.name;
3132
+ try {
3133
+ const s = vfs.stat(childPath);
3134
+ return { name: n.name, type: n.type, size: s.size ?? 0,
3135
+ mtime: s.mtime ?? Date.now(), mode: s.mode ?? 0o644,
3136
+ uid: s.uid ?? ctx.cred.uid, gid: s.gid ?? ctx.cred.gid };
3137
+ }
3138
+ catch {
3139
+ return { name: n.name, type: n.type, size: 0, mtime: Date.now(), mode: 0o644,
3140
+ uid: ctx.cred.uid, gid: ctx.cred.gid };
3141
+ }
3142
+ });
3143
+ }
3144
+ }
3145
+ catch (e) {
3146
+ ctx.stderr.write(`ls: cannot access '${dirPath}': ${fsErrorMessage(e)}\n`);
3147
+ exit = 2;
3148
+ return [];
3149
+ }
3150
+ for (const r of real) {
3151
+ const type = r.type === 'directory'
3152
+ ? 'directory'
3153
+ : r.type === 'symlink'
3154
+ ? 'symlink'
3155
+ : 'file';
3156
+ const childPath = fp ? `${fp}/${r.name}` : r.name;
3157
+ out.push({
3158
+ name: r.name,
3159
+ type,
3160
+ size: r.size ?? 0,
3161
+ mtime: r.mtime ?? Date.now(),
3162
+ mode: r.mode ?? 0o644,
3163
+ uid: r.uid ?? ctx.cred.uid,
3164
+ gid: r.gid ?? ctx.cred.gid,
3165
+ ...(type === 'symlink'
3166
+ ? { linkTarget: readSymlinkTarget(vfs, childPath) ?? undefined }
3167
+ : {}),
3168
+ });
3169
+ }
3170
+ // Inject symlink entries whose link path is in this directory.
3171
+ const normDir = fp.replace(/^\/+/, '').replace(/\/+$/, '');
3172
+ for (const { link, target } of reg.list()) {
3173
+ const linkNorm = link.replace(/^\/+/, '').replace(/\/+$/, '');
3174
+ const lastSlash = linkNorm.lastIndexOf('/');
3175
+ const linkDir = lastSlash >= 0 ? linkNorm.substring(0, lastSlash) : '';
3176
+ if (linkDir === normDir) {
3177
+ const linkName = lastSlash >= 0 ? linkNorm.substring(lastSlash + 1) : linkNorm;
3178
+ if (out.some(entry => entry.name === linkName))
3179
+ continue;
3180
+ // Filter dotfiles unless -a (consistent with real entries).
3181
+ if (!flagAll && linkName.startsWith('.'))
3182
+ continue;
3183
+ out.push({
3184
+ name: linkName,
3185
+ type: 'symlink',
3186
+ size: target.length,
3187
+ mtime: Date.now(),
3188
+ mode: 0o777,
3189
+ uid: ctx.cred.uid,
3190
+ gid: ctx.cred.gid,
3191
+ linkTarget: target,
3192
+ });
3193
+ }
3194
+ }
3195
+ // Filter dotfiles among real entries unless -a.
3196
+ const filtered = flagAll ? out : out.filter(e => !e.name.startsWith('.'));
3197
+ filtered.sort((a, b) => a.name.localeCompare(b.name));
3198
+ return filtered;
3199
+ }
3200
+ function fmtRow(e, long) {
3201
+ if (!long)
3202
+ return e.name;
3203
+ const isDir = e.type === 'directory';
3204
+ const isLink = e.type === 'symlink';
3205
+ const mode = unixModeString(e.mode, isDir, isLink);
3206
+ const size = String(e.size).padStart(6, ' ');
3207
+ const time = fmtTime(e.mtime);
3208
+ const arrow = isLink && e.linkTarget ? ` -> ${e.linkTarget}` : '';
3209
+ const user = flagNumeric ? String(e.uid) : unixUserLabel(vfs, e.uid);
3210
+ const group = flagNumeric ? String(e.gid) : unixGroupLabel(vfs, e.gid);
3211
+ return `${mode} 1 ${user} ${group} ${size} ${time} ${e.name}${arrow}`;
3212
+ }
3213
+ // First pass: separate file-args from dir-args (real `ls` lists
3214
+ // each file inline; dirs get listed as their contents).
3215
+ const fileEntries = [];
3216
+ const dirArgs = [];
3217
+ for (const arg of targets) {
3218
+ const fp = resolvePath(ctx.cwd, arg);
3219
+ // Symlink check: a symlink-arg is displayed as the link itself
3220
+ // (without -L which we don't implement).
3221
+ const target = readSymlinkTarget(vfs, fp);
3222
+ if (target !== null) {
3223
+ fileEntries.push({
3224
+ name: arg,
3225
+ type: 'symlink',
3226
+ size: target.length,
3227
+ mtime: Date.now(),
3228
+ mode: 0o777,
3229
+ uid: ctx.cred.uid,
3230
+ gid: ctx.cred.gid,
3231
+ linkTarget: target,
3232
+ });
3233
+ continue;
3234
+ }
3235
+ try {
3236
+ const s = kvfs && typeof kvfs.stat === 'function' ? kvfs.stat(fp) : vfs.stat(fp);
3237
+ if (s.type === 'directory' && !flagDirectory) {
3238
+ dirArgs.push(arg);
3239
+ }
3240
+ else {
3241
+ fileEntries.push({
3242
+ name: arg,
3243
+ type: s.type === 'directory' ? 'directory' : 'file',
3244
+ size: s.size ?? 0,
3245
+ mtime: s.mtime ?? Date.now(),
3246
+ mode: s.mode ?? 0o644,
3247
+ uid: s.uid ?? ctx.cred.uid,
3248
+ gid: s.gid ?? ctx.cred.gid,
3249
+ });
3250
+ }
3251
+ }
3252
+ catch (e) {
3253
+ ctx.stderr.write(`ls: cannot access '${arg}': ${e?.message || e}\n`);
3254
+ exit = 1;
3255
+ }
3256
+ }
3257
+ // Render file-args first.
3258
+ if (fileEntries.length > 0) {
3259
+ if (flagLong) {
3260
+ for (const e of fileEntries)
3261
+ ctx.stdout.write(fmtRow(e, true) + '\n');
3262
+ }
3263
+ else if (flagOne) {
3264
+ for (const e of fileEntries)
3265
+ ctx.stdout.write(e.name + '\n');
3266
+ }
3267
+ else {
3268
+ ctx.stdout.write(fileEntries.map(e => e.name).join(' ') + '\n');
3269
+ }
3270
+ }
3271
+ // Then dir-args (with header if multiple).
3272
+ for (let i = 0; i < dirArgs.length; i++) {
3273
+ const d = dirArgs[i];
3274
+ if (dirArgs.length > 1 || fileEntries.length > 0) {
3275
+ if (fileEntries.length > 0 || i > 0)
3276
+ ctx.stdout.write('\n');
3277
+ ctx.stdout.write(`${d}:\n`);
3278
+ }
3279
+ const rows = listDir(d);
3280
+ if (flagLong) {
3281
+ for (const e of rows)
3282
+ ctx.stdout.write(fmtRow(e, true) + '\n');
3283
+ }
3284
+ else if (flagOne) {
3285
+ for (const e of rows)
3286
+ ctx.stdout.write(e.name + '\n');
3287
+ }
3288
+ else if (rows.length > 0) {
3289
+ ctx.stdout.write(rows.map(e => e.name).join(' ') + '\n');
3290
+ }
3291
+ }
3292
+ return exit;
3293
+ };
3294
+ }
3295
+ /**
3296
+ * shell compatibilityc (2026-05-11): registry-level cat (for xargs cross-
3297
+ * command dispatch). Behaves like the shell cat command: reads files (or
3298
+ * stdin if none), concatenates to stdout.
3299
+ *
3300
+ * Operands stream through one shared writer, so concatenating six 1 MB files
3301
+ * emits 6 MB in 64 KiB steps rather than materialising each file whole.
3302
+ */
3303
+ function mkCat(vfs) {
3304
+ return (ctx) => {
3305
+ const files = ctx.args.filter(a => !a.startsWith('-'));
3306
+ if (files.length === 0) {
3307
+ if (ctx.stdin)
3308
+ ctx.stdout.write(ctx.stdin);
3309
+ return 0;
3310
+ }
3311
+ // Resolve both native VFS symlinks and the legacy registry before reads.
3312
+ let exit = 0;
3313
+ const writer = new SinkWriter(ctx.stdout);
3314
+ for (const fOrig of files) {
3315
+ const f = (() => {
3316
+ const fp = resolvePath(ctx.cwd, fOrig);
3317
+ const resolved = resolveSymlinkPath(vfs, fp);
3318
+ if (resolved === null) {
3319
+ // ELOOP: too many hops
3320
+ ctx.stderr.write(`cat: ${fOrig}: Too many levels of symbolic links\n`);
3321
+ return null;
3322
+ }
3323
+ return resolved === fp ? fOrig : '/' + resolved;
3324
+ })();
3325
+ if (f === null) {
3326
+ exit = 1;
3327
+ continue;
3328
+ }
3329
+ try {
3330
+ const path = f.startsWith('/') ? f : `${ctx.cwd}/${f}`;
3331
+ const stat = ctx.vfs.stat(path);
3332
+ if (stat.type === 'directory')
3333
+ throw Object.assign(new Error('Is a directory'), { code: 'EISDIR' });
3334
+ if (stat.size > 0) {
3335
+ // A regular file's size is its exact extent — read precisely that.
3336
+ streamRange((offset, length) => ctx.vfs.readRange(path, offset, length), writer, {
3337
+ length: stat.size,
3338
+ signal: ctx.signal,
3339
+ });
3340
+ }
3341
+ else {
3342
+ // Size 0 covers empty files, /dev/null and synthesised /proc entries.
3343
+ // Endless character devices reject this unbounded read by design.
3344
+ writer.write(ctx.vfs.readFile(path));
3345
+ }
3346
+ }
3347
+ catch (error) {
3348
+ ctx.stderr.write(`cat: ${fOrig}: ${fsErrorMessage(error)}\n`);
3349
+ exit = 1;
3350
+ }
3351
+ }
3352
+ writer.end();
3353
+ return exit;
3354
+ };
3355
+ }
3356
+ function mkRm(vfs) {
3357
+ return (ctx) => {
3358
+ const args = ctx.args;
3359
+ const recursive = args.some(a => a === '-r' || a === '-R' || a === '-rf' || a === '-Rf' || a === '-rR' || a === '--recursive' || (a.startsWith('-') && !a.startsWith('--') && (a.includes('r') || a.includes('R'))));
3360
+ const force = args.some(a => a === '-f' || a === '--force' || (a.startsWith('-') && !a.startsWith('--') && a.includes('f')));
3361
+ const targets = args.filter(a => !a.startsWith('-'));
3362
+ if (targets.length === 0) {
3363
+ if (force)
3364
+ return 0; // POSIX: rm -f with no operands is silent success
3365
+ ctx.stderr.write('rm: missing operand\n');
3366
+ return 1;
3367
+ }
3368
+ // SHELL-R6-5: SymlinkRegistry awareness. Real `rm` removes the
3369
+ // LINK, not the target. Pre-fix this loop went straight to
3370
+ // vfs.exists which is false for registry-only symlink entries
3371
+ // (no real file), producing "No such file or directory" while
3372
+ // `readlink` still reported the registry entry.
3373
+ const reg = vfs.symlinks;
3374
+ let exit = 0;
3375
+ for (const t of targets) {
3376
+ const fp = resolvePath(ctx.cwd, t);
3377
+ // Symlink path FIRST. If `fp` is registered as a symlink we
3378
+ // delete the registry entry and skip the vfs-level operations.
3379
+ // Real `rm` never follows symlinks (it removes the link
3380
+ // itself); recursive flag has no effect on the symlink itself
3381
+ // either — it acts on the link node only.
3382
+ if (reg.isSymlink(fp)) {
3383
+ reg.delete(fp);
3384
+ continue;
3385
+ }
3386
+ if (!vfs.exists(fp)) {
3387
+ if (force)
3388
+ continue; // silent success
3389
+ ctx.stderr.write(`rm: cannot remove '${t}': No such file or directory\n`);
3390
+ exit = 1;
3391
+ continue;
3392
+ }
3393
+ try {
3394
+ if (vfs.isDirectory(fp)) {
3395
+ if (!recursive) {
3396
+ ctx.stderr.write(`rm: cannot remove '${t}': Is a directory\n`);
3397
+ exit = 1;
3398
+ continue;
3399
+ }
3400
+ vfs.removeRecursive(fp);
3401
+ }
3402
+ else {
3403
+ vfs.unlink(fp);
3404
+ }
3405
+ }
3406
+ catch (e) {
3407
+ // -f suppresses ENOENT only (file disappeared mid-loop); other
3408
+ // errors (ENOTEMPTY because of a logic bug, ENOTDIR mismatches,
3409
+ // permission errors) must still surface. Pre-fix the broad
3410
+ // `if (force) continue` masked the readdir-iteration bug that
3411
+ // left directories undeleted.
3412
+ const msg = String(e?.message || e);
3413
+ if (force && /ENOENT/.test(msg))
3414
+ continue;
3415
+ ctx.stderr.write(`rm: cannot remove '${t}': ${fsErrorMessage(e)}\n`);
3416
+ exit = 1;
3417
+ }
3418
+ }
3419
+ return exit;
3420
+ };
3421
+ }
3422
+ function mkTouch(vfs) {
3423
+ return (ctx) => {
3424
+ const targetVfs = ctx.vfs ?? vfs;
3425
+ for (const f of ctx.args.filter(a => !a.startsWith('-'))) {
3426
+ const fp = resolvePath(ctx.cwd, f);
3427
+ // Ensure parent dirs
3428
+ const parts = fp.split('/');
3429
+ for (let i = 1; i < parts.length; i++) {
3430
+ const dir = parts.slice(0, i).join('/');
3431
+ if (dir && !targetVfs.exists(dir))
3432
+ targetVfs.mkdir(dir, { recursive: true });
3433
+ }
3434
+ if (targetVfs.exists(fp) && !targetVfs.isDirectory(fp)) {
3435
+ // Update mtime by re-writing the same content
3436
+ const content = targetVfs.readFile(fp);
3437
+ targetVfs.writeFile(fp, content);
3438
+ }
3439
+ else if (!targetVfs.exists(fp)) {
3440
+ targetVfs.writeFile(fp, '');
3441
+ }
3442
+ }
3443
+ return 0;
3444
+ };
3445
+ }
3446
+ /**
3447
+ * `stat` formatting.
3448
+ *
3449
+ * Every GNU directive is answered, using what this filesystem actually knows:
3450
+ *
3451
+ * - inode (%i): paths are the identity here — there are no hard links, so a
3452
+ * path maps to exactly one file. %i is a stable hash of the path, which
3453
+ * gives (dev,ino) comparisons the right answer instead of the zero an
3454
+ * inode-less filesystem would otherwise report for everything.
3455
+ * - link count (%h) is always 1, device numbers are 0: one store, no
3456
+ * hard links, no device nodes.
3457
+ * - birth time (%w/%W) prints `-`/`0`, GNU's own convention for a
3458
+ * filesystem that does not record it. Change time (%z/%Z) tracks mtime.
3459
+ * - SELinux context (%C) prints `?`, as GNU does where there is none.
3460
+ */
3461
+ const STAT_TERSE_FORMAT = '%n %s %b %f %u %g %D %i %h %t %T %X %Y %Z %W %o %C';
3462
+ const STATFS_TERSE_FORMAT = '%n %i %l %t %s %S %b %f %a %c %d';
3463
+ /** Longest component the VFS accepts, reported by %l and `Namelen`. */
3464
+ const STAT_NAME_MAX = 255;
3465
+ /** Reported by %o: the VFS reads and writes in 64 KiB chunks. */
3466
+ const STAT_IO_BLOCK_SIZE = 65536;
3467
+ /** %b/%B count 512-byte units, as GNU does. */
3468
+ const STAT_BLOCK_UNIT = 512;
3469
+ /** Stable 53-bit identity for a path — see the %i note above. */
3470
+ function statPathId(path) {
3471
+ let high = 0xdeadbeef;
3472
+ let low = 0x41c6ce57;
3473
+ for (let i = 0; i < path.length; i++) {
3474
+ const code = path.charCodeAt(i);
3475
+ high = Math.imul(high ^ code, 2654435761);
3476
+ low = Math.imul(low ^ code, 1597334677);
3477
+ }
3478
+ high = Math.imul(high ^ (high >>> 16), 2246822507) >>> 0;
3479
+ low = Math.imul(low ^ (low >>> 13), 3266489909) >>> 0;
3480
+ return high * 0x200000 + (low >>> 11);
3481
+ }
3482
+ function statDirective(directive, stat, path, labels) {
3483
+ const isDir = stat.type === 'directory';
3484
+ const isLink = stat.type === 'symlink';
3485
+ const changeTime = stat.ctime ?? stat.mtime;
3486
+ switch (directive) {
3487
+ case 'n': return path;
3488
+ case 'N': return `'${path}'`;
3489
+ case 's': return String(stat.size);
3490
+ case 'b': return String(Math.ceil(stat.size / STAT_BLOCK_UNIT));
3491
+ case 'B': return String(STAT_BLOCK_UNIT);
3492
+ case 'o': return String(STAT_IO_BLOCK_SIZE);
3493
+ case 'a': return (stat.mode & 0o7777).toString(8);
3494
+ case 'A': return unixModeString(stat.mode, isDir, isLink);
3495
+ case 'f': return (stat.mode >>> 0).toString(16);
3496
+ case 'u': return String(stat.uid);
3497
+ case 'U': return labels.user;
3498
+ case 'g': return String(stat.gid);
3499
+ case 'G': return labels.group;
3500
+ case 'F':
3501
+ if (isDir)
3502
+ return 'directory';
3503
+ if (isLink)
3504
+ return 'symbolic link';
3505
+ return isCharacterDevice(stat.mode) ? 'character special file' : 'regular file';
3506
+ case 'h': return '1';
3507
+ case 'i': return String(statPathId(path));
3508
+ case 'd': return '0';
3509
+ case 'D': return '0';
3510
+ case 't': return '0';
3511
+ case 'T': return '0';
3512
+ case 'm': return '/';
3513
+ case 'C': return '?';
3514
+ case 'w': return '-';
3515
+ case 'W': return '0';
3516
+ case 'Y': return String(Math.floor(stat.mtime / 1000));
3517
+ case 'y': return new Date(stat.mtime).toISOString();
3518
+ case 'X': return String(Math.floor(stat.atime / 1000));
3519
+ case 'x': return new Date(stat.atime).toISOString();
3520
+ case 'Z': return String(Math.floor(changeTime / 1000));
3521
+ case 'z': return new Date(changeTime).toISOString();
3522
+ case '%': return '%';
3523
+ default: return null;
3524
+ }
3525
+ }
3526
+ function statFsDirective(directive, fs, path) {
3527
+ switch (directive) {
3528
+ case 'n': return path;
3529
+ case 'i': return String(statPathId('/'));
3530
+ case 'l': return String(STAT_NAME_MAX);
3531
+ case 't': return '0';
3532
+ case 'T': return 'nimbus-sqlite';
3533
+ case 's': return String(fs.blockSize);
3534
+ case 'S': return String(fs.blockSize);
3535
+ case 'b': return String(fs.totalBlocks);
3536
+ case 'f': return String(fs.freeBlocks);
3537
+ case 'a': return String(fs.freeBlocks);
3538
+ case 'c': return String(fs.totalInodes);
3539
+ case 'd': return String(fs.freeInodes);
3540
+ case '%': return '%';
3541
+ default: return null;
3542
+ }
3543
+ }
3544
+ function expandStatFormat(format, expand) {
3545
+ let out = '';
3546
+ for (let i = 0; i < format.length; i++) {
3547
+ const ch = format[i];
3548
+ if (ch === '\\' && i + 1 < format.length) {
3549
+ const escape = format[++i];
3550
+ out += escape === 'n' ? '\n' : escape === 't' ? '\t' : escape === '0' ? '\0' : escape;
3551
+ continue;
3552
+ }
3553
+ if (ch !== '%') {
3554
+ out += ch;
3555
+ continue;
3556
+ }
3557
+ const directive = format[++i];
3558
+ if (directive === undefined)
3559
+ return { error: "stat: trailing '%' in format" };
3560
+ const expanded = expand(directive);
3561
+ if (expanded === null)
3562
+ return { error: `stat: unrecognized format directive '%${directive}'` };
3563
+ out += expanded;
3564
+ }
3565
+ return { text: out };
3566
+ }
3567
+ const STAT_USAGE = [
3568
+ 'Usage: stat [OPTION]... FILE...',
3569
+ 'Display file or file system status.',
3570
+ '',
3571
+ ' -L, --dereference follow links (Nimbus always follows)',
3572
+ ' -f, --file-system display file system status instead of file status',
3573
+ ' -c, --format=FORMAT use the specified FORMAT instead of the default',
3574
+ ' --printf=FORMAT like --format, but interpret escapes and omit the newline',
3575
+ ' -t, --terse print the information in terse form',
3576
+ ' --cached=MODE always|default|never (Nimbus attributes are never cached)',
3577
+ ' --help display this help and exit',
3578
+ ' --version output version information and exit',
3579
+ '',
3580
+ ].join('\n');
3581
+ function mkStat(vfs, sqliteVfs) {
3582
+ return (ctx) => {
3583
+ let format = null;
3584
+ // `--printf` differs from `-c` only in not appending a newline.
3585
+ let formatAddsNewline = true;
3586
+ let fileSystemMode = false;
3587
+ let terse = false;
3588
+ const files = [];
3589
+ const args = ctx.args;
3590
+ for (let i = 0; i < args.length; i++) {
3591
+ const arg = args[i];
3592
+ if (arg === '-c' || arg === '--format' || arg === '--printf') {
3593
+ const value = args[++i];
3594
+ if (value === undefined) {
3595
+ ctx.stderr.write(`stat: option '${arg}' requires an argument\n`);
3596
+ return 1;
3597
+ }
3598
+ format = value;
3599
+ formatAddsNewline = arg !== '--printf';
3600
+ }
3601
+ else if (arg.startsWith('--format=') || arg.startsWith('--printf=')) {
3602
+ format = arg.slice(arg.indexOf('=') + 1);
3603
+ formatAddsNewline = !arg.startsWith('--printf=');
3604
+ }
3605
+ else if (arg === '-f' || arg === '--file-system') {
3606
+ fileSystemMode = true;
3607
+ }
3608
+ else if (arg === '-t' || arg === '--terse') {
3609
+ terse = true;
3610
+ }
3611
+ else if (arg === '-L' || arg === '--dereference') {
3612
+ // Symlinks are already followed; accept the flag rather than drop it.
3613
+ }
3614
+ else if (arg.startsWith('--cached=')) {
3615
+ const mode = arg.slice('--cached='.length);
3616
+ if (mode !== 'always' && mode !== 'default' && mode !== 'never') {
3617
+ ctx.stderr.write(`stat: invalid argument '${mode}' for '--cached'\n`);
3618
+ return 1;
3619
+ }
3620
+ // Attributes are read live from the VFS, which satisfies every mode.
3621
+ }
3622
+ else if (arg === '--help') {
3623
+ ctx.stdout.write(STAT_USAGE);
3624
+ return 0;
3625
+ }
3626
+ else if (arg === '--version') {
3627
+ ctx.stdout.write(`stat (nimbus coreutils) ${NIMBUS_VERSION}\n`);
3628
+ return 0;
3629
+ }
3630
+ else if (arg === '--') {
3631
+ files.push(...args.slice(i + 1));
3632
+ break;
3633
+ }
3634
+ else if (arg.startsWith('-') && arg !== '-') {
3635
+ ctx.stderr.write(`stat: invalid option '${arg}'\n`);
3636
+ ctx.stderr.write(STAT_USAGE);
3637
+ return 1;
3638
+ }
3639
+ else {
3640
+ files.push(arg);
3641
+ }
3642
+ }
3643
+ if (files.length === 0) {
3644
+ ctx.stderr.write('stat: missing operand\n');
3645
+ return 1;
3646
+ }
3647
+ const write = (text) => {
3648
+ ctx.stdout.write(formatAddsNewline ? text + '\n' : text);
3649
+ };
3650
+ if (fileSystemMode) {
3651
+ const stats = sqliteVfs.getStats();
3652
+ const facts = {
3653
+ blockSize: STAT_IO_BLOCK_SIZE,
3654
+ totalBlocks: Math.floor(stats.capacityBytes / STAT_IO_BLOCK_SIZE),
3655
+ freeBlocks: Math.max(0, Math.floor((stats.capacityBytes - stats.usedBytes) / STAT_IO_BLOCK_SIZE)),
3656
+ totalInodes: stats.files + stats.directories,
3657
+ freeInodes: 0,
3658
+ };
3659
+ const activeFormat = format ?? (terse ? STATFS_TERSE_FORMAT : null);
3660
+ for (const f of files) {
3661
+ const displayPath = f.startsWith('/') ? f : resolvePath(ctx.cwd, f).replace(/^\/*/, '/');
3662
+ if (activeFormat !== null) {
3663
+ const expanded = expandStatFormat(activeFormat, (directive) => statFsDirective(directive, facts, displayPath));
3664
+ if ('error' in expanded) {
3665
+ ctx.stderr.write(expanded.error + '\n');
3666
+ return 1;
3667
+ }
3668
+ write(expanded.text);
3669
+ continue;
3670
+ }
3671
+ ctx.stdout.write(` File: "${displayPath}"\n`);
3672
+ ctx.stdout.write(` ID: 0 Namelen: ${STAT_NAME_MAX} Type: nimbus-sqlite\n`);
3673
+ ctx.stdout.write(`Block size: ${facts.blockSize} Fundamental block size: ${facts.blockSize}\n`);
3674
+ ctx.stdout.write(`Blocks: Total: ${facts.totalBlocks} Free: ${facts.freeBlocks} Available: ${facts.freeBlocks}\n`);
3675
+ ctx.stdout.write(`Inodes: Total: ${facts.totalInodes} Free: ${facts.freeInodes}\n`);
3676
+ }
3677
+ return 0;
3678
+ }
3679
+ const activeFormat = format ?? (terse ? STAT_TERSE_FORMAT : null);
3680
+ // shell compatibility follow-up: try Kernel.VFS (ctx.vfs) first so /dev
3681
+ // mount paths resolve. Same pattern as mkCat.
3682
+ const kvfs = ctx.vfs;
3683
+ for (const f of files) {
3684
+ let st = null;
3685
+ let displayPath = f;
3686
+ // Try Kernel.VFS first (sees mounts).
3687
+ if (kvfs && typeof kvfs.stat === 'function') {
3688
+ try {
3689
+ st = kvfs.stat(f.startsWith('/') ? f : ctx.cwd + '/' + f);
3690
+ displayPath = f.startsWith('/') ? f : `/${ctx.cwd}/${f}`.replace(/^\/+/, '/');
3691
+ }
3692
+ catch (_e) { /* fall through to SqliteVFS */ }
3693
+ }
3694
+ // Fall back to SqliteVFS direct for non-mounted paths.
3695
+ if (!st) {
3696
+ try {
3697
+ const fp = resolvePath(ctx.cwd, f);
3698
+ st = vfs.stat(fp);
3699
+ displayPath = '/' + fp;
3700
+ }
3701
+ catch (_e) {
3702
+ ctx.stderr.write(`stat: cannot statx '${f}': No such file or directory\n`);
3703
+ return 1;
3704
+ }
3705
+ }
3706
+ const uid = st.uid ?? ctx.cred.uid;
3707
+ const gid = st.gid ?? ctx.cred.gid;
3708
+ const labels = {
3709
+ user: unixUserLabel(vfs, uid),
3710
+ group: unixGroupLabel(vfs, gid),
3711
+ };
3712
+ const facts = {
3713
+ size: st.size,
3714
+ type: st.type,
3715
+ mode: st.mode,
3716
+ uid,
3717
+ gid,
3718
+ atime: st.atime ?? st.mtime,
3719
+ mtime: st.mtime,
3720
+ ctime: st.ctime,
3721
+ };
3722
+ if (activeFormat !== null) {
3723
+ const expanded = expandStatFormat(activeFormat, (directive) => statDirective(directive, facts, displayPath, labels));
3724
+ if ('error' in expanded) {
3725
+ ctx.stderr.write(expanded.error + '\n');
3726
+ return 1;
3727
+ }
3728
+ write(expanded.text);
3729
+ continue;
3730
+ }
3731
+ ctx.stdout.write(` File: ${displayPath}\n`);
3732
+ const kind = isCharacterDevice(st.mode) ? 'character special file' : st.type;
3733
+ ctx.stdout.write(` Size: ${st.size}\tType: ${kind}\n`);
3734
+ ctx.stdout.write(`Access: (0${st.mode.toString(8)}) Uid: (${uid}/${labels.user}) Gid: (${gid}/${labels.group})\n`);
3735
+ ctx.stdout.write(`Modify: ${new Date(st.mtime).toISOString()}\n`);
3736
+ }
3737
+ return 0;
3738
+ };
3739
+ }
3740
+ const BASE64_SPEC = {
3741
+ decode: { type: 'boolean', short: 'd' },
3742
+ 'ignore-garbage': { type: 'boolean', short: 'i' },
3743
+ wrap: { type: 'string', short: 'w' },
3744
+ };
3745
+ /**
3746
+ * Encodes and decodes the real bytes. Reading the input as a string first put
3747
+ * every byte that is not valid UTF-8 through U+FFFD, so encoding any binary
3748
+ * file produced base64 of something else; `-w`, which GNU wraps at 76 columns
3749
+ * by default, was not implemented at all, so `base64 -w 0` read `0` as a file.
3750
+ */
3751
+ function mkBase64(vfs) {
3752
+ return (ctx) => {
3753
+ const { flags, positional, unknown } = parseArgs(ctx.args, BASE64_SPEC);
3754
+ if (unknown.length > 0) {
3755
+ ctx.stderr.write(`base64: invalid option -- '${unknown[0].replace(/^-+/, '')}'\n`);
3756
+ return 1;
3757
+ }
3758
+ const wrapText = typeof flags.wrap === 'string' && flags.wrap !== '' ? flags.wrap : '76';
3759
+ const wrap = Number.parseInt(wrapText, 10);
3760
+ if (Number.isNaN(wrap) || wrap < 0) {
3761
+ ctx.stderr.write(`base64: invalid wrap size: '${wrapText}'\n`);
3762
+ return 1;
3763
+ }
3764
+ const file = positional[0];
3765
+ let bytes;
3766
+ if (file !== undefined && file !== '-') {
3767
+ try {
3768
+ bytes = vfs.readFile(resolvePath(ctx.cwd, file));
3769
+ }
3770
+ catch (error) {
3771
+ ctx.stderr.write(`base64: ${file}: ${fsErrorMessage(error)}\n`);
3772
+ return 1;
3773
+ }
3774
+ }
3775
+ else {
3776
+ bytes = enc.encode(ctx.stdin ?? '');
3777
+ }
3778
+ if (flags.decode) {
3779
+ const source = dec.decode(bytes).replace(/\s+/g, '');
3780
+ let decoded;
3781
+ try {
3782
+ const binary = atob(source);
3783
+ decoded = Uint8Array.from(binary, (c) => c.charCodeAt(0));
3784
+ }
3785
+ catch {
3786
+ ctx.stderr.write('base64: invalid input\n');
3787
+ return 1;
3788
+ }
3789
+ if (ctx.stdout.writeBytes)
3790
+ ctx.stdout.writeBytes(decoded);
3791
+ else
3792
+ ctx.stdout.write(dec.decode(decoded));
3793
+ return 0;
3794
+ }
3795
+ let binary = '';
3796
+ for (const byte of bytes)
3797
+ binary += String.fromCharCode(byte);
3798
+ const encoded = btoa(binary);
3799
+ if (encoded === '')
3800
+ return 0;
3801
+ const lines = wrap > 0
3802
+ ? (encoded.match(new RegExp(`.{1,${wrap}}`, 'g')) ?? [encoded])
3803
+ : [encoded];
3804
+ ctx.stdout.write(lines.join('\n') + '\n');
3805
+ return 0;
3806
+ };
3807
+ }
3808
+ function mkSeq() {
3809
+ return (ctx) => {
3810
+ const nums = ctx.args.map(Number).filter(n => !isNaN(n));
3811
+ let start = 1, step = 1, end = 1;
3812
+ if (nums.length === 1)
3813
+ end = nums[0];
3814
+ else if (nums.length === 2) {
3815
+ start = nums[0];
3816
+ end = nums[1];
3817
+ }
3818
+ else if (nums.length >= 3) {
3819
+ start = nums[0];
3820
+ step = nums[1];
3821
+ end = nums[2];
3822
+ }
3823
+ for (let i = start; step > 0 ? i <= end : i >= end; i += step)
3824
+ ctx.stdout.write(i + '\n');
3825
+ return 0;
3826
+ };
3827
+ }
3828
+ function mkId(sqliteVfs) {
3829
+ return (ctx) => {
3830
+ const vfs = sqliteVfs.as(ctx.cred);
3831
+ const user = findUnixUserName(vfs, ctx.cred.uid) ?? String(ctx.cred.uid);
3832
+ const group = findUnixGroupName(vfs, ctx.cred.gid) ?? String(ctx.cred.gid);
3833
+ const groupIds = [...new Set([ctx.cred.gid, ...ctx.cred.groups])];
3834
+ const groups = groupIds
3835
+ .map((gid) => `${gid}(${findUnixGroupName(vfs, gid) ?? gid})`)
3836
+ .join(',');
3837
+ ctx.stdout.write(`uid=${ctx.cred.uid}(${user}) gid=${ctx.cred.gid}(${group}) groups=${groups}\n`);
3838
+ return 0;
3839
+ };
3840
+ }
3841
+ function mkChown(sqliteVfs) {
3842
+ return (ctx) => {
3843
+ const recursive = ctx.args.includes('-R') || ctx.args.includes('--recursive');
3844
+ const positional = ctx.args.filter((arg) => arg !== '-R' && arg !== '--recursive');
3845
+ if (positional.length < 2) {
3846
+ ctx.stderr.write('chown: missing operand\n');
3847
+ return 1;
3848
+ }
3849
+ const vfs = sqliteVfs.as(ctx.cred);
3850
+ let ownership;
3851
+ try {
3852
+ ownership = parseChownOwnership(vfs, positional[0]);
3853
+ }
3854
+ catch (error) {
3855
+ ctx.stderr.write(`chown: ${error instanceof Error ? error.message : String(error)}\n`);
3856
+ return 1;
3857
+ }
3858
+ let exitCode = 0;
3859
+ const apply = (path) => {
3860
+ if (recursive && vfs.stat(path).type === 'directory') {
3861
+ for (const child of vfs.readdir(path))
3862
+ apply(`${path}/${child.name}`);
3863
+ }
3864
+ vfs.chown(path, ownership.uid, ownership.gid);
3865
+ };
3866
+ for (const file of positional.slice(1)) {
3867
+ try {
3868
+ apply(resolvePath(ctx.cwd, file));
3869
+ }
3870
+ catch (error) {
3871
+ ctx.stderr.write(`chown: ${file}: ${error instanceof Error ? error.message : String(error)}\n`);
3872
+ exitCode = 1;
3873
+ }
3874
+ }
3875
+ return exitCode;
3876
+ };
3877
+ }
3878
+ function mkTest(sqliteVfs) {
3879
+ return (ctx) => {
3880
+ const args = ctx.args.filter((arg) => arg !== ']');
3881
+ if (args.length === 0)
3882
+ return 1;
3883
+ const vfs = sqliteVfs.as(ctx.cred);
3884
+ const path = resolvePath(ctx.cwd, args[1] ?? '');
3885
+ try {
3886
+ if (args[0] === '-r')
3887
+ vfs.access(path, 0o4);
3888
+ else if (args[0] === '-w')
3889
+ vfs.access(path, 0o2);
3890
+ else if (args[0] === '-x')
3891
+ vfs.access(path, 0o1);
3892
+ else if (args[0] === '-f')
3893
+ return vfs.stat(path).type === 'file' ? 0 : 1;
3894
+ else if (args[0] === '-d')
3895
+ return vfs.stat(path).type === 'directory' ? 0 : 1;
3896
+ else if (args[0] === '-e')
3897
+ vfs.stat(path);
3898
+ else if (args[0] === '-z')
3899
+ return (!args[1] || args[1] === '') ? 0 : 1;
3900
+ else if (args[0] === '-n')
3901
+ return args[1] ? 0 : 1;
3902
+ else if (args[1] === '=')
3903
+ return args[0] === args[2] ? 0 : 1;
3904
+ else if (args[1] === '!=')
3905
+ return args[0] !== args[2] ? 0 : 1;
3906
+ else
3907
+ return args[0] ? 0 : 1;
3908
+ return 0;
3909
+ }
3910
+ catch {
3911
+ return 1;
3912
+ }
3913
+ };
3914
+ }
3915
+ function mkHostname() {
3916
+ return (ctx) => { ctx.stdout.write('nimbus\n'); return 0; };
3917
+ }
3918
+ function mkBasename() {
3919
+ return (ctx) => {
3920
+ const p = ctx.args[0] || '';
3921
+ const suffix = ctx.args[1] || '';
3922
+ let base = p.split('/').pop() || '';
3923
+ if (suffix && base.endsWith(suffix))
3924
+ base = base.slice(0, -suffix.length);
3925
+ ctx.stdout.write(base + '\n');
3926
+ return 0;
3927
+ };
3928
+ }
3929
+ function mkDirname() {
3930
+ return (ctx) => {
3931
+ const p = ctx.args[0] || '';
3932
+ const dir = p.includes('/') ? p.substring(0, p.lastIndexOf('/')) : '.';
3933
+ ctx.stdout.write((dir || '/') + '\n');
3934
+ return 0;
3935
+ };
3936
+ }
3937
+ function mkRealpath(vfs) {
3938
+ return (ctx) => {
3939
+ for (const p of ctx.args) {
3940
+ const fp = resolvePath(ctx.cwd, p);
3941
+ if (vfs.exists(fp))
3942
+ ctx.stdout.write('/' + fp + '\n');
3943
+ else {
3944
+ ctx.stderr.write(`realpath: ${p}: No such file\n`);
3945
+ return 1;
3946
+ }
3947
+ }
3948
+ return 0;
3949
+ };
3950
+ }
3951
+ /**
3952
+ * shell compatibility (2026-05-11): printf full POSIX format set.
3953
+ *
3954
+ * Pre-fix mkPrintf only handled %s and %d via simple replace —
3955
+ * `printf "%x\\n" 255` output literal `%x`, `printf "%5d" 7` output
3956
+ * literal `%5d`. Common shell scripts use %x (hex), %o (octal),
3957
+ * %f (float), %g (general), %c (char), width+precision specifiers,
3958
+ * and flag chars (- + 0 # space).
3959
+ *
3960
+ * Real bash printf cycles through args, re-running the format
3961
+ * string if there are more args than format specifiers. We
3962
+ * replicate that.
3963
+ */
3964
+ function mkPrintf() {
3965
+ return (ctx) => {
3966
+ if (ctx.args.length === 0)
3967
+ return 0;
3968
+ const rawFmt = ctx.args[0];
3969
+ const vals = ctx.args.slice(1);
3970
+ // Process backslash escapes in the format string first.
3971
+ const fmt = rawFmt
3972
+ .replace(/\\\\/g, '\u0000') // protect literal \\\\
3973
+ .replace(/\\n/g, '\n')
3974
+ .replace(/\\t/g, '\t')
3975
+ .replace(/\\r/g, '\r')
3976
+ .replace(/\\a/g, '\x07')
3977
+ .replace(/\\b/g, '\b')
3978
+ .replace(/\\f/g, '\f')
3979
+ .replace(/\\v/g, '\v')
3980
+ .replace(/\\0([0-7]{1,3})?/g, (_m, oct) => String.fromCharCode(oct ? parseInt(oct, 8) : 0))
3981
+ .replace(/\\x([0-9a-fA-F]{1,2})/g, (_m, hex) => String.fromCharCode(parseInt(hex, 16)))
3982
+ .replace(/\u0000/g, '\\');
3983
+ let out = '';
3984
+ let argIdx = 0;
3985
+ function applyFormat() {
3986
+ // Run the format string once; return true if it consumed any args.
3987
+ let i = 0;
3988
+ const startArg = argIdx;
3989
+ while (i < fmt.length) {
3990
+ const ch = fmt[i];
3991
+ if (ch !== '%') {
3992
+ out += ch;
3993
+ i++;
3994
+ continue;
3995
+ }
3996
+ if (fmt[i + 1] === '%') {
3997
+ out += '%';
3998
+ i += 2;
3999
+ continue;
4000
+ }
4001
+ // Parse format spec: %[flags][width][.prec]conversion
4002
+ let spec = '%';
4003
+ i++;
4004
+ while (i < fmt.length && /[-+ 0#]/.test(fmt[i])) {
4005
+ spec += fmt[i];
4006
+ i++;
4007
+ }
4008
+ while (i < fmt.length && /[0-9]/.test(fmt[i])) {
4009
+ spec += fmt[i];
4010
+ i++;
4011
+ }
4012
+ if (fmt[i] === '.') {
4013
+ spec += fmt[i];
4014
+ i++;
4015
+ while (i < fmt.length && /[0-9]/.test(fmt[i])) {
4016
+ spec += fmt[i];
4017
+ i++;
4018
+ }
4019
+ }
4020
+ const conv = fmt[i];
4021
+ i++;
4022
+ const arg = vals[argIdx++];
4023
+ out += formatOneArg(spec + conv, arg);
4024
+ }
4025
+ return argIdx > startArg;
4026
+ }
4027
+ // bash printf: re-run the format until args are exhausted; if
4028
+ // format consumes zero args (no %X specifiers), run it once.
4029
+ if (vals.length === 0) {
4030
+ applyFormat();
4031
+ }
4032
+ else {
4033
+ while (argIdx < vals.length) {
4034
+ if (!applyFormat())
4035
+ break;
4036
+ }
4037
+ }
4038
+ ctx.stdout.write(out);
4039
+ return 0;
4040
+ };
4041
+ }
4042
+ function formatOneArg(spec, arg) {
4043
+ const conv = spec[spec.length - 1];
4044
+ const flagsAndWidth = spec.slice(1, -1);
4045
+ const dotIdx = flagsAndWidth.indexOf('.');
4046
+ const widthPart = dotIdx >= 0 ? flagsAndWidth.slice(0, dotIdx) : flagsAndWidth;
4047
+ const precPart = dotIdx >= 0 ? flagsAndWidth.slice(dotIdx + 1) : '';
4048
+ let flags = '';
4049
+ let widthStr = '';
4050
+ for (const c of widthPart) {
4051
+ if (/[-+ 0#]/.test(c))
4052
+ flags += c;
4053
+ else
4054
+ widthStr += c;
4055
+ }
4056
+ const width = widthStr ? parseInt(widthStr, 10) : 0;
4057
+ const prec = precPart ? parseInt(precPart, 10) : -1;
4058
+ let body;
4059
+ switch (conv) {
4060
+ case 's': {
4061
+ body = String(arg ?? '');
4062
+ if (prec >= 0)
4063
+ body = body.slice(0, prec);
4064
+ break;
4065
+ }
4066
+ case 'd':
4067
+ case 'i': {
4068
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(String(arg ?? '0')));
4069
+ const v = Number.isFinite(n) ? n : 0;
4070
+ body = String(Math.abs(v));
4071
+ const sign = v < 0 ? '-' : flags.includes('+') ? '+' : flags.includes(' ') ? ' ' : '';
4072
+ body = sign + body;
4073
+ break;
4074
+ }
4075
+ case 'u': {
4076
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(String(arg ?? '0')));
4077
+ body = String(Math.max(0, Number.isFinite(n) ? n : 0));
4078
+ break;
4079
+ }
4080
+ case 'f':
4081
+ case 'F': {
4082
+ const n = typeof arg === 'number' ? arg : parseFloat(String(arg ?? '0'));
4083
+ const p = prec < 0 ? 6 : prec;
4084
+ body = (Number.isFinite(n) ? n : 0).toFixed(p);
4085
+ if (n >= 0 && flags.includes('+'))
4086
+ body = '+' + body;
4087
+ else if (n >= 0 && flags.includes(' '))
4088
+ body = ' ' + body;
4089
+ break;
4090
+ }
4091
+ case 'e':
4092
+ case 'E': {
4093
+ const n = typeof arg === 'number' ? arg : parseFloat(String(arg ?? '0'));
4094
+ const p = prec < 0 ? 6 : prec;
4095
+ body = (Number.isFinite(n) ? n : 0).toExponential(p);
4096
+ if (conv === 'E')
4097
+ body = body.toUpperCase();
4098
+ break;
4099
+ }
4100
+ case 'g':
4101
+ case 'G': {
4102
+ const n = typeof arg === 'number' ? arg : parseFloat(String(arg ?? '0'));
4103
+ const p = prec < 0 ? 6 : prec || 1;
4104
+ body = (Number.isFinite(n) ? n : 0).toPrecision(p);
4105
+ // Strip trailing zeros + dot (POSIX %g behavior) unless # flag.
4106
+ if (!flags.includes('#'))
4107
+ body = body.replace(/(\.\d*?)0+($|e)/, '$1$2').replace(/\.($|e)/, '$1');
4108
+ if (conv === 'G')
4109
+ body = body.toUpperCase();
4110
+ break;
4111
+ }
4112
+ case 'x':
4113
+ case 'X': {
4114
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(String(arg ?? '0')));
4115
+ body = (Number.isFinite(n) ? n >>> 0 : 0).toString(16);
4116
+ if (conv === 'X')
4117
+ body = body.toUpperCase();
4118
+ if (flags.includes('#') && body !== '0')
4119
+ body = (conv === 'X' ? '0X' : '0x') + body;
4120
+ break;
4121
+ }
4122
+ case 'o': {
4123
+ const n = typeof arg === 'number' ? Math.trunc(arg) : Math.trunc(parseFloat(String(arg ?? '0')));
4124
+ body = (Number.isFinite(n) ? n >>> 0 : 0).toString(8);
4125
+ if (flags.includes('#') && !body.startsWith('0'))
4126
+ body = '0' + body;
4127
+ break;
4128
+ }
4129
+ case 'b': {
4130
+ // bash printf %b: interpret backslash escapes in the arg
4131
+ let s = String(arg ?? '');
4132
+ s = s.replace(/\\\\/g, '\u0000')
4133
+ .replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r')
4134
+ .replace(/\\u0000/g, '\\');
4135
+ body = s;
4136
+ break;
4137
+ }
4138
+ case 'c': {
4139
+ if (typeof arg === 'number')
4140
+ body = String.fromCharCode(arg);
4141
+ else
4142
+ body = String(arg ?? '').charAt(0);
4143
+ break;
4144
+ }
4145
+ case 'q': {
4146
+ // bash printf %q: shell-quote
4147
+ const s = String(arg ?? '');
4148
+ if (/^[A-Za-z0-9_/.,:=+@%-]+$/.test(s))
4149
+ body = s;
4150
+ else
4151
+ body = "'" + s.replace(/'/g, `'\\''`) + "'";
4152
+ break;
4153
+ }
4154
+ default: body = '%' + conv;
4155
+ }
4156
+ // Apply width padding.
4157
+ if (width > body.length) {
4158
+ const zeroPad = flags.includes('0') && /[diouxXfFeEgG]/.test(conv) && !flags.includes('-');
4159
+ const padCh = zeroPad ? '0' : ' ';
4160
+ if (flags.includes('-'))
4161
+ body = body.padEnd(width, ' ');
4162
+ else {
4163
+ // For zero-pad on negative numbers, keep the sign at the front.
4164
+ if (zeroPad && (body.startsWith('-') || body.startsWith('+') || body.startsWith(' '))) {
4165
+ body = body[0] + body.slice(1).padStart(width - 1, padCh);
4166
+ }
4167
+ else {
4168
+ body = body.padStart(width, padCh);
4169
+ }
4170
+ }
4171
+ }
4172
+ return body;
4173
+ }
4174
+ function mkTrue() { return () => 0; }
4175
+ function mkFalse() { return () => 1; }
4176
+ /**
4177
+ * shell compatibility (2026-05-11): readlink stub.
4178
+ *
4179
+ * Real readlink reads the symlink target. Our VFS doesn't yet
4180
+ * support real symlinks (ln -s currently does a regular file
4181
+ * copy — tracked as deferred). For graceful failure:
4182
+ * - if path is a regular file/dir, exit 1 (matches GNU readlink)
4183
+ * - if path is missing, write error to stderr + exit 1
4184
+ * - explicit handling avoids 'readlink: command not found'.
4185
+ *
4186
+ * When real symlinks land in VFS, this command will become the
4187
+ * read-side of the symlink table.
4188
+ */
4189
+ /**
4190
+ * SHELL-FOLLOWUPS-4 (2026-05-11): real symlink readlink.
4191
+ * Pre-fix: ln -s did file-copy; readlink returned exit 1 with no
4192
+ * output (no symlink table existed). Now backed by SymlinkRegistry:
4193
+ * - GNU readlink prints target (relative or absolute as stored)
4194
+ * - exit 1 silently for non-symlinks (matches GNU readlink default)
4195
+ * - exit 1 + stderr for missing files
4196
+ * Flags: -f (canonicalize — follow chain to final target), default
4197
+ * (one-hop). -e variant (verify) deferred.
4198
+ */
4199
+ function mkReadlink(vfs) {
4200
+ return (ctx) => {
4201
+ const args = [...ctx.args];
4202
+ let canonicalize = false;
4203
+ const targets = [];
4204
+ for (const a of args) {
4205
+ if (a === '-f' || a === '--canonicalize') {
4206
+ canonicalize = true;
4207
+ continue;
4208
+ }
4209
+ if (a.startsWith('-') && a !== '-') {
4210
+ for (const ch of a.slice(1))
4211
+ if (ch === 'f')
4212
+ canonicalize = true;
4213
+ continue;
4214
+ }
4215
+ targets.push(a);
4216
+ }
4217
+ if (targets.length === 0) {
4218
+ ctx.stderr.write('readlink: missing operand\n');
4219
+ return 1;
4220
+ }
4221
+ let exit = 0;
4222
+ for (const t of targets) {
4223
+ const fp = resolvePath(ctx.cwd, t);
4224
+ if (canonicalize) {
4225
+ // -f: follow chain; succeed even if target doesn't exist YET
4226
+ // (matches `readlink -f` which canonicalizes anyway).
4227
+ const resolved = resolveSymlinkPath(vfs, fp);
4228
+ if (resolved !== null) {
4229
+ ctx.stdout.write('/' + resolved + '\n');
4230
+ continue;
4231
+ }
4232
+ ctx.stderr.write(`readlink: ${t}: Too many levels of symbolic links\n`);
4233
+ exit = 1;
4234
+ continue;
4235
+ }
4236
+ // Default: one-hop. Print target verbatim (preserves relative/absolute).
4237
+ const direct = readSymlinkTarget(vfs, fp);
4238
+ if (direct !== null) {
4239
+ ctx.stdout.write(direct + '\n');
4240
+ continue;
4241
+ }
4242
+ // Not a symlink. GNU readlink exits 1 silently for regular
4243
+ // files / dirs; emits stderr for missing.
4244
+ if (!vfs.exists(fp)) {
4245
+ ctx.stderr.write(`readlink: ${t}: No such file or directory\n`);
4246
+ }
4247
+ exit = 1;
4248
+ }
4249
+ return exit;
4250
+ };
4251
+ }
4252
+ const SHA256SUM_SPEC = {
4253
+ check: { type: 'boolean', short: 'c' },
4254
+ binary: { type: 'boolean', short: 'b' },
4255
+ text: { type: 'boolean', short: 't' },
4256
+ quiet: { type: 'boolean', short: 'q' },
4257
+ status: { type: 'boolean' },
4258
+ };
4259
+ /**
4260
+ * Real SHA-256 over the file's real bytes.
4261
+ *
4262
+ * The digest used to be taken over `enc.encode(readFileString(path))` — a
4263
+ * UTF-8 decode and re-encode, which replaces every byte that is not valid
4264
+ * UTF-8 with U+FFFD. For any binary file that hashes something the file does
4265
+ * not contain, and it never announced a problem: an installer verifying a
4266
+ * downloaded tarball got a mismatch on a perfectly good download, every time.
4267
+ */
4268
+ function mkSha256sum(vfs) {
4269
+ const digest = async (bytes) => {
4270
+ const ab = await crypto.subtle.digest('SHA-256', bytes);
4271
+ return Array.from(new Uint8Array(ab)).map((b) => b.toString(16).padStart(2, '0')).join('');
4272
+ };
4273
+ return async (ctx) => {
4274
+ const { flags, positional, unknown } = parseArgs(ctx.args, SHA256SUM_SPEC);
4275
+ if (unknown.length > 0) {
4276
+ ctx.stderr.write(`sha256sum: invalid option -- '${unknown[0].replace(/^-+/, '')}'\n`);
4277
+ return 1;
4278
+ }
4279
+ if (flags.check)
4280
+ return verifySha256Sums(ctx, vfs, positional, digest, flags.status === true);
4281
+ if (positional.length === 0 || (positional.length === 1 && positional[0] === '-')) {
4282
+ ctx.stdout.write(`${await digest(enc.encode(ctx.stdin ?? ''))} -\n`);
4283
+ return 0;
4284
+ }
4285
+ let exit = 0;
4286
+ for (const f of positional) {
4287
+ try {
4288
+ ctx.stdout.write(`${await digest(vfs.readFile(resolvePath(ctx.cwd, f)))} ${f}\n`);
4289
+ }
4290
+ catch {
4291
+ ctx.stderr.write(`sha256sum: ${f}: No such file or directory\n`);
4292
+ exit = 1;
4293
+ }
4294
+ }
4295
+ return exit;
4296
+ };
4297
+ }
4298
+ /** `sha256sum -c LIST` — each line is `HASH FILENAME`, as this command prints. */
4299
+ async function verifySha256Sums(ctx, vfs, lists, digest, quiet) {
4300
+ let exit = 0;
4301
+ for (const list of lists) {
4302
+ let body;
4303
+ try {
4304
+ body = vfs.readFileString(resolvePath(ctx.cwd, list));
4305
+ }
4306
+ catch {
4307
+ ctx.stderr.write(`sha256sum: ${list}: No such file or directory\n`);
4308
+ exit = 1;
4309
+ continue;
4310
+ }
4311
+ for (const line of body.split('\n')) {
4312
+ const entry = /^([0-9a-fA-F]{64})\s[\s*](.*)$/.exec(line);
4313
+ if (entry === null)
4314
+ continue;
4315
+ const [, expected, name] = entry;
4316
+ let actual = null;
4317
+ try {
4318
+ actual = await digest(vfs.readFile(resolvePath(ctx.cwd, name)));
4319
+ }
4320
+ catch { /* reported as FAILED open below */ }
4321
+ if (actual === null) {
4322
+ ctx.stderr.write(`sha256sum: ${name}: No such file or directory\n`);
4323
+ if (!quiet)
4324
+ ctx.stdout.write(`${name}: FAILED open or read\n`);
4325
+ exit = 1;
4326
+ }
4327
+ else if (actual.toLowerCase() === expected.toLowerCase()) {
4328
+ if (!quiet)
4329
+ ctx.stdout.write(`${name}: OK\n`);
4330
+ }
4331
+ else {
4332
+ if (!quiet)
4333
+ ctx.stdout.write(`${name}: FAILED\n`);
4334
+ exit = 1;
4335
+ }
4336
+ }
4337
+ }
4338
+ return exit;
4339
+ }
4340
+ function mkFile(vfs) {
4341
+ return (ctx) => {
4342
+ for (const f of ctx.args.filter(a => !a.startsWith('-'))) {
4343
+ const fp = resolvePath(ctx.cwd, f);
4344
+ try {
4345
+ if (vfs.isDirectory(fp)) {
4346
+ ctx.stdout.write(`${f}: directory\n`);
4347
+ continue;
4348
+ }
4349
+ // BUG-SWEEP-3 (2026-05-11): scan raw bytes for NUL or non-text
4350
+ // bytes BEFORE attempting a UTF-8 decode. Pre-fix every binary
4351
+ // file was reported as "UTF-8 text" because readFileString
4352
+ // silently U+FFFD-substituted invalid sequences.
4353
+ const bytes = vfs.readFile(fp);
4354
+ let isBinary = false;
4355
+ const scanLimit = Math.min(bytes.length, 8192);
4356
+ for (let i = 0; i < scanLimit; i++) {
4357
+ const b = bytes[i];
4358
+ if (b === 0) {
4359
+ isBinary = true;
4360
+ break;
4361
+ }
4362
+ // Bytes 0x01-0x08 + 0x0E-0x1F (excluding TAB/LF/CR/FF) are
4363
+ // strong signals of non-text content.
4364
+ if (b < 0x09 || (b > 0x0d && b < 0x20)) {
4365
+ isBinary = true;
4366
+ break;
4367
+ }
4368
+ }
4369
+ if (isBinary) {
4370
+ // Magic-byte sniff for common formats.
4371
+ if (bytes.length >= 4 && bytes[0] === 0x7f && bytes[1] === 0x45 && bytes[2] === 0x4c && bytes[3] === 0x46) {
4372
+ ctx.stdout.write(`${f}: ELF executable\n`);
4373
+ }
4374
+ else if (bytes.length >= 4 && bytes[0] === 0x00 && bytes[1] === 0x61 && bytes[2] === 0x73 && bytes[3] === 0x6d) {
4375
+ ctx.stdout.write(`${f}: WebAssembly (wasm) binary module\n`);
4376
+ }
4377
+ else if (bytes.length >= 8 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
4378
+ ctx.stdout.write(`${f}: PNG image data\n`);
4379
+ }
4380
+ else if (bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b) {
4381
+ ctx.stdout.write(`${f}: gzip compressed data\n`);
4382
+ }
4383
+ else if (bytes.length >= 4 && bytes[0] === 0x50 && bytes[1] === 0x4b && (bytes[2] === 0x03 || bytes[2] === 0x05)) {
4384
+ ctx.stdout.write(`${f}: Zip archive data\n`);
4385
+ }
4386
+ else {
4387
+ ctx.stdout.write(`${f}: data\n`);
4388
+ }
4389
+ continue;
4390
+ }
4391
+ const content = new TextDecoder('utf-8').decode(bytes);
4392
+ if (content.startsWith('<!DOCTYPE') || content.startsWith('<html'))
4393
+ ctx.stdout.write(`${f}: HTML document\n`);
4394
+ else if (content.startsWith('{') || content.startsWith('['))
4395
+ ctx.stdout.write(`${f}: JSON data\n`);
4396
+ else if (content.startsWith('#!'))
4397
+ ctx.stdout.write(`${f}: script, ${content.split('\n')[0]}\n`);
4398
+ else if (f.endsWith('.ts') || f.endsWith('.tsx'))
4399
+ ctx.stdout.write(`${f}: TypeScript source\n`);
4400
+ else if (f.endsWith('.js') || f.endsWith('.mjs'))
4401
+ ctx.stdout.write(`${f}: JavaScript source\n`);
4402
+ else if (f.endsWith('.css'))
4403
+ ctx.stdout.write(`${f}: CSS stylesheet\n`);
4404
+ else
4405
+ ctx.stdout.write(`${f}: ASCII text, ${content.split('\n').length} lines\n`);
4406
+ }
4407
+ catch {
4408
+ ctx.stderr.write(`file: ${f}: No such file\n`);
4409
+ return 1;
4410
+ }
4411
+ }
4412
+ return 0;
4413
+ };
4414
+ }
4415
+ function mkXxd(vfs) {
4416
+ return (ctx) => {
4417
+ const file = ctx.args.find(a => !a.startsWith('-'));
4418
+ if (!file) {
4419
+ ctx.stderr.write('Usage: xxd FILE\n');
4420
+ return 1;
4421
+ }
4422
+ const fp = resolvePath(ctx.cwd, file);
4423
+ try {
4424
+ const data = vfs.readFile(fp);
4425
+ const len = Math.min(data.length, ctx.args.includes('-l') ? parseInt(ctx.args[ctx.args.indexOf('-l') + 1]) || 256 : 256);
4426
+ for (let i = 0; i < len; i += 16) {
4427
+ const hex = Array.from(data.slice(i, i + 16)).map(b => b.toString(16).padStart(2, '0')).join(' ');
4428
+ const ascii = Array.from(data.slice(i, i + 16)).map(b => b >= 32 && b < 127 ? String.fromCharCode(b) : '.').join('');
4429
+ ctx.stdout.write(`${i.toString(16).padStart(8, '0')}: ${hex.padEnd(48)} ${ascii}\n`);
4430
+ }
4431
+ return 0;
4432
+ }
4433
+ catch {
4434
+ ctx.stderr.write(`xxd: ${file}: No such file\n`);
4435
+ return 1;
4436
+ }
4437
+ };
4438
+ }
4439
+ // ── Registration ────────────────────────────────────────────────────────
4440
+ /**
4441
+ * Wrap a sync/async command so it always returns Promise<number>.
4442
+ * The LIFO shell calls .then() on the return value of every command,
4443
+ * so raw numbers cause "E3(...).then is not a function".
4444
+ *
4445
+ * Also resolves ctx.stdin from a stream object to a string.
4446
+ * The shell passes stdin as an object with .readAll() when piping,
4447
+ * but our commands expect a plain string.
4448
+ */
4449
+ /**
4450
+ * SHELL-R6-B2 follow-on: wrapStreaming for commands that handle pipe
4451
+ * readers directly (head, tail, etc — commands that can terminate
4452
+ * before the producer drains).
4453
+ *
4454
+ * Behavior:
4455
+ * - If ctx.stdin is a terminal stdin (has .feed), drain buffered
4456
+ * bytes to a string (same as wrap — terminal stdin's
4457
+ * buffer-then-close pattern doesn't match streaming).
4458
+ * - If ctx.stdin is a pipe reader (has .read), PASS IT THROUGH
4459
+ * unchanged so the command can read line-by-line. The command
4460
+ * is responsible for terminating itself (e.g. head -n N stops
4461
+ * after N lines, triggering the pipeline-abort cascade from
4462
+ * SHELL-R6-2).
4463
+ * - String stdin / other shapes: same as wrap.
4464
+ */
4465
+ function wrapStreaming(fn) {
4466
+ return async (ctx) => {
4467
+ try {
4468
+ if (ctx.stdin && typeof ctx.stdin !== 'string') {
4469
+ const stdinObj = ctx.stdin;
4470
+ const isTerminalStdin = typeof stdinObj.feed === 'function';
4471
+ if (isTerminalStdin) {
4472
+ const buf = Array.isArray(stdinObj.buffer)
4473
+ ? stdinObj.buffer.splice(0)
4474
+ : [];
4475
+ ctx.stdin = buf.join('');
4476
+ }
4477
+ // else: leave as pipe reader for the command to handle.
4478
+ }
4479
+ const result = fn(ctx);
4480
+ return await result;
4481
+ }
4482
+ catch (e) {
4483
+ ctx.stderr.write(`${e?.message || e}\n`);
4484
+ return 1;
4485
+ }
4486
+ };
4487
+ }
4488
+ function wrap(fn) {
4489
+ return async (ctx) => {
4490
+ try {
4491
+ // Resolve stdin: shell passes a stream object with .readAll() when piping.
4492
+ //
4493
+ // BUG-SWEEP fix (2026-05-11): the shell passes its
4494
+ // `terminalStdin` (an Ls instance) as ctx.stdin for EVERY command,
4495
+ // not just piped ones. Ls.readAll() loops until close(), which the
4496
+ // shell only triggers in its executeLine() finally — AFTER the
4497
+ // command returns. Pre-fix, our wrap awaited readAll() and
4498
+ // deadlocked: command waits for stdin EOF, shell waits for command.
4499
+ //
4500
+ // The fix is to distinguish the two stream shapes:
4501
+ // - Pipe reader (Oi.reader): {read, readAll} only. Used when
4502
+ // upstream `echo X |` feeds bytes; upstream calls close()
4503
+ // after writing, so readAll() resolves quickly.
4504
+ // - Terminal stdin (Ls): {feed, close, rawMode, read, readAll,
4505
+ // isWaiting, ...}. close() runs ONLY after the command returns.
4506
+ //
4507
+ // We treat anything with a `feed` method (Ls signature) as the
4508
+ // terminal stdin and drain its already-buffered bytes synchronously
4509
+ // without awaiting EOF. Pipe readers (no `feed`) await readAll().
4510
+ if (ctx.stdin && typeof ctx.stdin !== 'string') {
4511
+ const stdinObj = ctx.stdin;
4512
+ const isTerminalStdin = typeof stdinObj.feed === 'function';
4513
+ if (isTerminalStdin) {
4514
+ // Drain any already-queued bytes (typically empty for the
4515
+ // first command on a line; non-empty if the user typed
4516
+ // text + Enter before the command was dispatched). DO NOT
4517
+ // await — that would wait for the user's next Ctrl-D.
4518
+ const buf = Array.isArray(stdinObj.buffer)
4519
+ ? stdinObj.buffer.splice(0)
4520
+ : [];
4521
+ ctx.stdin = buf.join('');
4522
+ }
4523
+ else if (typeof stdinObj.readAll === 'function') {
4524
+ // Pipe reader — upstream will close() after writing, so
4525
+ // readAll() resolves bounded.
4526
+ ctx.stdin = await stdinObj.readAll();
4527
+ }
4528
+ else if (typeof stdinObj.toString === 'function') {
4529
+ ctx.stdin = stdinObj.toString();
4530
+ }
4531
+ }
4532
+ const result = fn(ctx);
4533
+ return await result;
4534
+ }
4535
+ catch (e) {
4536
+ ctx.stderr.write(`${e?.message || e}\n`);
4537
+ return 1;
4538
+ }
4539
+ };
4540
+ }
4541
+ export function registerUnixCommands(registry, sqliteVfs) {
4542
+ registry.register('which', wrap(withInvocationVfs(sqliteVfs, (vfs) => mkWhich(vfs, registry))));
4543
+ registry.register('whereis', wrap(withInvocationVfs(sqliteVfs, (vfs) => mkWhereis(vfs, registry))));
4544
+ registry.register('command', wrap(withInvocationVfs(sqliteVfs, (vfs) => mkCommand(vfs, registry))));
4545
+ registry.register('type', wrap(withInvocationVfs(sqliteVfs, (vfs) => mkType(vfs, registry))));
4546
+ registry.register('env', wrap(mkEnv()));
4547
+ registry.register('export', wrap(mkExport()));
4548
+ registry.register('unset', wrap(mkUnset()));
4549
+ registry.register('clear', wrap(mkClear()));
4550
+ registry.register('date', wrap(mkDate()));
4551
+ registry.register('uptime', wrap(mkUptime()));
4552
+ registry.register('tree', wrap(withInvocationVfs(sqliteVfs, mkTree)));
4553
+ registry.register('find', wrap(withInvocationVfs(sqliteVfs, (vfs) => mkFind(vfs, registry))));
4554
+ registry.register('grep', wrap(withInvocationVfs(sqliteVfs, mkGrep)));
4555
+ // SHELL-R6-B2: head uses streaming wrap so a pipe reader passes
4556
+ // through (head terminates after N lines, triggering the abort
4557
+ // cascade for upstream producers like `yes`).
4558
+ registry.register('head', wrapStreaming(withInvocationVfs(sqliteVfs, mkHead)));
4559
+ registry.register('tail', wrap(withInvocationVfs(sqliteVfs, mkTail)));
4560
+ registry.register('wc', wrap(withInvocationVfs(sqliteVfs, mkWc)));
4561
+ registry.register('sort', wrap(withInvocationVfs(sqliteVfs, mkSort)));
4562
+ registry.register('uniq', wrap(withInvocationVfs(sqliteVfs, mkUniq)));
4563
+ registry.register('sed', wrap(withInvocationVfs(sqliteVfs, mkSed)));
4564
+ registry.register('awk', wrap(withInvocationVfs(sqliteVfs, mkAwk)));
4565
+ registry.register('xargs', wrap(withInvocationVfs(sqliteVfs, (vfs) => mkXargs(vfs, registry))));
4566
+ registry.register('tee', wrap(withInvocationVfs(sqliteVfs, mkTee)));
4567
+ registry.register('du', wrap(withInvocationVfs(sqliteVfs, mkDu)));
4568
+ registry.register('diff', wrap(withInvocationVfs(sqliteVfs, mkDiff)));
4569
+ // Registry-level echo + cat for xargs cross-command dispatch.
4570
+ // Shell.builtins still wins for direct `echo X` invocations; this
4571
+ // entry is only reached when a command (xargs etc.) looks them up
4572
+ // via the registry path.
4573
+ registry.register('echo', wrap(mkEcho()));
4574
+ registry.register('cat', wrap(withInvocationVfs(sqliteVfs, mkCat)));
4575
+ registry.register('ls', wrap(withInvocationVfs(sqliteVfs, mkLs)));
4576
+ registry.register('rm', wrap(withInvocationVfs(sqliteVfs, mkRm)));
4577
+ registry.register('touch', wrap(withInvocationVfs(sqliteVfs, mkTouch)));
4578
+ registry.register('stat', wrap(withInvocationVfs(sqliteVfs, (v) => mkStat(v, sqliteVfs))));
4579
+ registry.register('base64', wrap(withInvocationVfs(sqliteVfs, mkBase64)));
4580
+ registry.register('seq', wrap(mkSeq()));
4581
+ registry.register('id', wrap(mkId(sqliteVfs)));
4582
+ registry.register('hostname', wrap(mkHostname()));
4583
+ registry.register('basename', wrap(mkBasename()));
4584
+ registry.register('dirname', wrap(mkDirname()));
4585
+ registry.register('realpath', wrap(withInvocationVfs(sqliteVfs, mkRealpath)));
4586
+ registry.register('printf', wrap(mkPrintf()));
4587
+ registry.register('true', wrap(mkTrue()));
4588
+ registry.register('false', wrap(mkFalse()));
4589
+ registry.register('readlink', wrap(withInvocationVfs(sqliteVfs, mkReadlink)));
4590
+ registry.register('sha256sum', wrap(withInvocationVfs(sqliteVfs, mkSha256sum)));
4591
+ registry.register('file', wrap(withInvocationVfs(sqliteVfs, mkFile)));
4592
+ registry.register('xxd', wrap(withInvocationVfs(sqliteVfs, mkXxd)));
4593
+ registry.register('chown', wrap(mkChown(sqliteVfs)));
4594
+ // ln — symlink stub (no-ops on VFS but doesn't error)
4595
+ /**
4596
+ * SHELL-FOLLOWUPS-4 (2026-05-11): real `ln -s` via SymlinkRegistry.
4597
+ * Pre-fix: ln -s did file-copy; modifications to the "link"
4598
+ * created a new file (no two-way reflection); readlink returned
4599
+ * empty.
4600
+ *
4601
+ * Now:
4602
+ * - `ln -s TARGET LINKPATH` registers LINKPATH → TARGET in the
4603
+ * symlink registry.
4604
+ * - Real GNU `ln -s` doesn't require TARGET to exist (dangling
4605
+ * symlinks are valid). We allow that.
4606
+ * - Hard links (`ln` without -s) still do file-copy — Nimbus
4607
+ * VFS doesn't expose inode-level hard-linking and that's
4608
+ * documented out of scope.
4609
+ *
4610
+ * Subsequent operations (cat / ls / cat-via-redirect) need to
4611
+ * consult the registry to see through the symlink. That work
4612
+ * lives in mkCat / mkLs wrappers and the SqliteVFS read-path —
4613
+ * for v1, we wire it in the COMMANDS that need it (cat already
4614
+ * goes through Kernel.VFS which doesn't yet know about the
4615
+ * registry; we patch cat directly here to dereference symlinks).
4616
+ */
4617
+ registry.register('ln', wrap((ctx) => {
4618
+ const vfs = unixVfsFor(sqliteVfs, ctx.cred);
4619
+ const args = ctx.args;
4620
+ const symbolic = args.some(a => a === '-s' || (a.startsWith('-') && !a.startsWith('--') && a.includes('s')));
4621
+ const force = args.some(a => a === '-f' || (a.startsWith('-') && !a.startsWith('--') && a.includes('f')));
4622
+ const positional = args.filter(a => !a.startsWith('-'));
4623
+ if (positional.length < 2) {
4624
+ ctx.stderr.write('ln: missing operand\n');
4625
+ return 1;
4626
+ }
4627
+ const target = positional[0]; // what the link points TO
4628
+ const linkPath = positional[1]; // the link file itself
4629
+ const linkFp = resolvePath(ctx.cwd, linkPath);
4630
+ if (symbolic) {
4631
+ // Symbolic link via registry. Don't require target to exist.
4632
+ const reg = vfs.symlinks;
4633
+ // GNU `ln` without -f errors if link exists.
4634
+ if (!force && (vfs.exists(linkFp) || reg.isSymlink(linkFp))) {
4635
+ ctx.stderr.write(`ln: failed to create symbolic link '${linkPath}': File exists\n`);
4636
+ return 1;
4637
+ }
4638
+ // Remove existing real-file at linkPath if -f and not a symlink.
4639
+ if (force && vfs.exists(linkFp) && !reg.isSymlink(linkFp)) {
4640
+ try {
4641
+ vfs.unlink(linkFp);
4642
+ }
4643
+ catch { /* fail-soft */ }
4644
+ }
4645
+ reg.set(linkFp, target);
4646
+ return 0;
4647
+ }
4648
+ // Hard link mode (default): file-copy semantics (legacy behaviour).
4649
+ const srcFp = resolvePath(ctx.cwd, target);
4650
+ try {
4651
+ const content = vfs.readFileString(srcFp);
4652
+ vfs.writeFile(linkFp, content);
4653
+ }
4654
+ catch (e) {
4655
+ ctx.stderr.write(`ln: ${target}: ${e?.message || e}\n`);
4656
+ return 1;
4657
+ }
4658
+ return 0;
4659
+ }));
4660
+ registry.register('test', wrap(mkTest(sqliteVfs)));
4661
+ registry.register('[', wrap(mkTest(sqliteVfs)));
4662
+ // read — read a line (stub, returns empty for non-interactive)
4663
+ // shell-polish (2026-05-12): `read VAR` is registered here as a
4664
+ // NO-OP fallback (matches the pre-existing stub behaviour). The
4665
+ // REAL working implementation lives in src/session/init.ts as a
4666
+ // shell builtin (shellAny.builtins.set('read', ...)).
4667
+ //
4668
+ // Why two registrations: the interpreter executes registered
4669
+ // shell commands with `ctx.env = { ...this.config.env }` — a SHALLOW
4670
+ // COPY.
4671
+ // Mutating ctx.env inside a registered command therefore CANNOT
4672
+ // propagate the var-assignment back to the shell. Builtins, by
4673
+ // contrast, run inside the interp instance with direct access to
4674
+ // `this.env` (the real shell env). The wait builtin in shell/compat/r6 uses
4675
+ // the same workaround.
4676
+ //
4677
+ // Keep the registry stub so `type read` reports "shell builtin" and
4678
+ // `which read` doesn't error; the builtin always wins dispatch
4679
+ // (interp.executeSimpleCommand checks builtins.get BEFORE
4680
+ // registry.resolve — index-Djm2onjx.js:5182-5186).
4681
+ registry.register('read', wrap((ctx) => {
4682
+ const args = ctx.args.filter((a) => !a.startsWith('-'));
4683
+ const varName = args[0] || 'REPLY';
4684
+ ctx.env[varName] = '';
4685
+ return 0;
4686
+ }));
4687
+ // exit — exit with code
4688
+ registry.register('exit', wrap((ctx) => {
4689
+ return parseInt(ctx.args[0] || '0') || 0;
4690
+ }));
4691
+ // source / . — source a file (stub)
4692
+ registry.register('source', wrap(() => 0));
4693
+ registry.register('.', wrap(() => 0));
4694
+ // noop commands that scripts might call
4695
+ registry.register('set', wrap(() => 0));
4696
+ registry.register('shopt', wrap(() => 0));
4697
+ registry.register('trap', wrap(() => 0));
4698
+ registry.register('umask', createUmaskCommand());
4699
+ registry.register('su', createSuCommand());
4700
+ registry.register('sudo', createSudoCommand());
4701
+ registry.register('ulimit', wrap(() => 0));
4702
+ }