typescript-virtual-container 1.5.3 → 1.5.5

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 (371) hide show
  1. package/README.md +44 -532
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/VirtualPackageManager/index.js +10 -0
  5. package/dist/VirtualShell/shell.js +158 -11
  6. package/dist/commands/basename.d.ts +13 -0
  7. package/dist/commands/basename.js +45 -0
  8. package/dist/commands/bc.d.ts +2 -0
  9. package/dist/commands/bc.js +28 -0
  10. package/dist/commands/file.d.ts +8 -0
  11. package/dist/commands/file.js +57 -0
  12. package/dist/commands/fun.d.ts +32 -0
  13. package/dist/commands/fun.js +172 -0
  14. package/dist/commands/ip.d.ts +7 -0
  15. package/dist/commands/ip.js +52 -0
  16. package/dist/commands/jobs.d.ts +4 -0
  17. package/dist/commands/jobs.js +27 -0
  18. package/dist/commands/last.d.ts +13 -0
  19. package/dist/commands/last.js +68 -0
  20. package/dist/commands/manuals-bundle.js +598 -6
  21. package/dist/commands/registry.js +30 -2
  22. package/dist/commands/runtime.js +24 -3
  23. package/dist/commands/set.js +20 -0
  24. package/dist/commands/sh.js +74 -1
  25. package/dist/commands/tput.d.ts +13 -0
  26. package/dist/commands/tput.js +76 -0
  27. package/dist/commands/w.d.ts +7 -0
  28. package/dist/commands/w.js +38 -0
  29. package/dist/utils/expand.d.ts +12 -0
  30. package/dist/utils/expand.js +87 -1
  31. package/package.json +9 -3
  32. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  33. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  34. package/.github/dependabot.yml +0 -27
  35. package/.github/pull_request_template.md +0 -21
  36. package/.github/workflows/create-pull-request.yml +0 -85
  37. package/.github/workflows/publish.yml +0 -25
  38. package/.github/workflows/test-battery.yml +0 -102
  39. package/.vscode/settings.json +0 -20
  40. package/CODE_OF_CONDUCT.md +0 -39
  41. package/CONTRIBUTING.md +0 -59
  42. package/HONEYPOT.md +0 -358
  43. package/SECURITY.md +0 -33
  44. package/benchmark-results.txt +0 -40
  45. package/benchmark-virtualshell.ts +0 -88
  46. package/biome.json +0 -37
  47. package/build.js +0 -22
  48. package/builds/fortune-nyx-v1.5.3-directbash-k6.1.0.mjs +0 -1764
  49. package/builds/fortune-nyx-v1.5.3-ssh-nosftp.js +0 -1764
  50. package/builds/fortune-nyx-v1.5.3-ssh.cjs +0 -1765
  51. package/builds/fortune-nyx-v1.5.3-web.min.js +0 -17036
  52. package/bun.lock +0 -244
  53. package/docs/.nojekyll +0 -1
  54. package/docs/app.js +0 -1751
  55. package/docs/assets/hierarchy.js +0 -1
  56. package/docs/assets/highlight.css +0 -162
  57. package/docs/assets/icons.js +0 -18
  58. package/docs/assets/icons.svg +0 -1
  59. package/docs/assets/main.js +0 -60
  60. package/docs/assets/navigation.js +0 -1
  61. package/docs/assets/search.js +0 -1
  62. package/docs/assets/style.css +0 -1633
  63. package/docs/classes/HoneyPot.html +0 -31
  64. package/docs/classes/IdleManager.html +0 -162
  65. package/docs/classes/SshClient.html +0 -66
  66. package/docs/classes/VirtualFileSystem.html +0 -279
  67. package/docs/classes/VirtualPackageManager.html +0 -63
  68. package/docs/classes/VirtualSftpServer.html +0 -169
  69. package/docs/classes/VirtualShell.html +0 -285
  70. package/docs/classes/VirtualSshServer.html +0 -182
  71. package/docs/classes/VirtualUserManager.html +0 -276
  72. package/docs/demo.html +0 -82
  73. package/docs/functions/assertDiff.html +0 -6
  74. package/docs/functions/diffSnapshots.html +0 -7
  75. package/docs/functions/formatDiff.html +0 -6
  76. package/docs/functions/getArg.html +0 -13
  77. package/docs/functions/getFlag.html +0 -15
  78. package/docs/functions/ifFlag.html +0 -11
  79. package/docs/hierarchy.html +0 -1
  80. package/docs/index.html +0 -1869
  81. package/docs/interfaces/AuditLogEntry.html +0 -6
  82. package/docs/interfaces/CommandContext.html +0 -22
  83. package/docs/interfaces/CommandResult.html +0 -26
  84. package/docs/interfaces/ExecStream.html +0 -11
  85. package/docs/interfaces/HoneyPotStats.html +0 -16
  86. package/docs/interfaces/IdleManagerOptions.html +0 -7
  87. package/docs/interfaces/InstalledPackage.html +0 -20
  88. package/docs/interfaces/NanoEditorSession.html +0 -8
  89. package/docs/interfaces/PackageDefinition.html +0 -30
  90. package/docs/interfaces/PackageFile.html +0 -8
  91. package/docs/interfaces/PasswordChallenge.html +0 -16
  92. package/docs/interfaces/RemoveOptions.html +0 -4
  93. package/docs/interfaces/ShellEnv.html +0 -6
  94. package/docs/interfaces/ShellModule.html +0 -14
  95. package/docs/interfaces/ShellProperties.html +0 -14
  96. package/docs/interfaces/ShellStream.html +0 -11
  97. package/docs/interfaces/SudoChallenge.html +0 -24
  98. package/docs/interfaces/VfsBaseNode.html +0 -12
  99. package/docs/interfaces/VfsDiff.html +0 -10
  100. package/docs/interfaces/VfsDiffEntry.html +0 -6
  101. package/docs/interfaces/VfsDiffModified.html +0 -10
  102. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  103. package/docs/interfaces/VfsFileNode.html +0 -17
  104. package/docs/interfaces/VfsOptions.html +0 -26
  105. package/docs/interfaces/VfsSnapshot.html +0 -3
  106. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  107. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  108. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  109. package/docs/interfaces/VirtualActiveSession.html +0 -12
  110. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  111. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  112. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  113. package/docs/interfaces/WriteFileOptions.html +0 -6
  114. package/docs/media/LICENSE +0 -21
  115. package/docs/modules.html +0 -1
  116. package/docs/types/ArgParseOptions.html +0 -4
  117. package/docs/types/CommandMode.html +0 -2
  118. package/docs/types/CommandOutcome.html +0 -2
  119. package/docs/types/IdleState.html +0 -1
  120. package/docs/types/VfsNodeStats.html +0 -2
  121. package/docs/types/VfsNodeType.html +0 -2
  122. package/docs/types/VfsPersistenceMode.html +0 -5
  123. package/docs/types/VfsSnapshotNode.html +0 -2
  124. package/examples/README.md +0 -288
  125. package/examples/app.js +0 -1751
  126. package/examples/app.ts +0 -299
  127. package/examples/build.js +0 -27
  128. package/examples/demo.html +0 -33
  129. package/examples/honeypot-audit.ts +0 -180
  130. package/examples/honeypot-export.ts +0 -253
  131. package/examples/honeypot-quickstart.ts +0 -110
  132. package/examples/index.html +0 -82
  133. package/examples/server.js +0 -55
  134. package/polyfills/buffer.js +0 -117
  135. package/polyfills/node_child_process/index.js +0 -2
  136. package/polyfills/node_crypto/index.js +0 -167
  137. package/polyfills/node_events/index.js +0 -9
  138. package/polyfills/node_fs/index.js +0 -202
  139. package/polyfills/node_fs/promises.js +0 -4
  140. package/polyfills/node_os/index.js +0 -9
  141. package/polyfills/node_path/index.js +0 -28
  142. package/polyfills/node_vm/index.js +0 -7
  143. package/polyfills/node_zlib/index.js +0 -3
  144. package/polyfills/process.js +0 -14
  145. package/polyfills/ssh2/index.js +0 -75
  146. package/scripts/build-all.mjs +0 -226
  147. package/scripts/build-names.mjs +0 -43
  148. package/scripts/generate-manuals-bundle.mjs +0 -49
  149. package/scripts/postinstall.js +0 -42
  150. package/scripts/publish-package.sh +0 -70
  151. package/src/Honeypot/index.ts +0 -457
  152. package/src/SSHClient/index.ts +0 -270
  153. package/src/SSHMimic/exec.ts +0 -49
  154. package/src/SSHMimic/executor.ts +0 -251
  155. package/src/SSHMimic/hostKey.ts +0 -21
  156. package/src/SSHMimic/index.ts +0 -337
  157. package/src/SSHMimic/loginBanner.ts +0 -36
  158. package/src/SSHMimic/loginFormat.ts +0 -10
  159. package/src/SSHMimic/prompt.ts +0 -14
  160. package/src/SSHMimic/sftp.ts +0 -883
  161. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  162. package/src/VirtualFileSystem/index.ts +0 -1193
  163. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  164. package/src/VirtualFileSystem/journal.ts +0 -171
  165. package/src/VirtualFileSystem/path.ts +0 -74
  166. package/src/VirtualPackageManager/index.ts +0 -996
  167. package/src/VirtualShell/idleManager.ts +0 -137
  168. package/src/VirtualShell/index.ts +0 -475
  169. package/src/VirtualShell/shell.ts +0 -700
  170. package/src/VirtualShell/shellParser.ts +0 -285
  171. package/src/VirtualUserManager/index.ts +0 -758
  172. package/src/bun.d.ts +0 -1
  173. package/src/commands/adduser.ts +0 -103
  174. package/src/commands/alias.ts +0 -69
  175. package/src/commands/apt.ts +0 -233
  176. package/src/commands/awk.ts +0 -168
  177. package/src/commands/base64.ts +0 -29
  178. package/src/commands/cat.ts +0 -52
  179. package/src/commands/cd.ts +0 -25
  180. package/src/commands/chmod.ts +0 -85
  181. package/src/commands/clear.ts +0 -15
  182. package/src/commands/command-helpers.ts +0 -286
  183. package/src/commands/cp.ts +0 -83
  184. package/src/commands/curl.ts +0 -147
  185. package/src/commands/cut.ts +0 -36
  186. package/src/commands/date.ts +0 -30
  187. package/src/commands/declare.ts +0 -49
  188. package/src/commands/deluser.ts +0 -98
  189. package/src/commands/df.ts +0 -23
  190. package/src/commands/diff.ts +0 -43
  191. package/src/commands/dpkg.ts +0 -180
  192. package/src/commands/du.ts +0 -56
  193. package/src/commands/echo.ts +0 -58
  194. package/src/commands/env.ts +0 -23
  195. package/src/commands/exit.ts +0 -18
  196. package/src/commands/export.ts +0 -34
  197. package/src/commands/find.ts +0 -68
  198. package/src/commands/free.ts +0 -47
  199. package/src/commands/grep.ts +0 -116
  200. package/src/commands/groups.ts +0 -19
  201. package/src/commands/gzip.ts +0 -88
  202. package/src/commands/head.ts +0 -52
  203. package/src/commands/help.ts +0 -152
  204. package/src/commands/helpers.ts +0 -234
  205. package/src/commands/history.ts +0 -34
  206. package/src/commands/hostname.ts +0 -14
  207. package/src/commands/htop.ts +0 -20
  208. package/src/commands/id.ts +0 -19
  209. package/src/commands/index.ts +0 -9
  210. package/src/commands/kill.ts +0 -19
  211. package/src/commands/ln.ts +0 -71
  212. package/src/commands/ls.ts +0 -243
  213. package/src/commands/lsb-release.ts +0 -63
  214. package/src/commands/man.ts +0 -31
  215. package/src/commands/manuals/adduser.txt +0 -11
  216. package/src/commands/manuals/apt-cache.txt +0 -12
  217. package/src/commands/manuals/apt.txt +0 -20
  218. package/src/commands/manuals/awk.txt +0 -13
  219. package/src/commands/manuals/cat.txt +0 -14
  220. package/src/commands/manuals/cd.txt +0 -16
  221. package/src/commands/manuals/chmod.txt +0 -16
  222. package/src/commands/manuals/clear.txt +0 -10
  223. package/src/commands/manuals/cp.txt +0 -10
  224. package/src/commands/manuals/curl.txt +0 -20
  225. package/src/commands/manuals/date.txt +0 -14
  226. package/src/commands/manuals/declare.txt +0 -12
  227. package/src/commands/manuals/deluser.txt +0 -10
  228. package/src/commands/manuals/df.txt +0 -10
  229. package/src/commands/manuals/dpkg-query.txt +0 -11
  230. package/src/commands/manuals/dpkg.txt +0 -14
  231. package/src/commands/manuals/du.txt +0 -11
  232. package/src/commands/manuals/echo.txt +0 -11
  233. package/src/commands/manuals/false.txt +0 -10
  234. package/src/commands/manuals/find.txt +0 -11
  235. package/src/commands/manuals/free.txt +0 -12
  236. package/src/commands/manuals/grep.txt +0 -13
  237. package/src/commands/manuals/groups.txt +0 -10
  238. package/src/commands/manuals/gzip.txt +0 -11
  239. package/src/commands/manuals/head.txt +0 -10
  240. package/src/commands/manuals/help.txt +0 -11
  241. package/src/commands/manuals/history.txt +0 -11
  242. package/src/commands/manuals/hostname.txt +0 -10
  243. package/src/commands/manuals/id.txt +0 -10
  244. package/src/commands/manuals/kill.txt +0 -13
  245. package/src/commands/manuals/ls.txt +0 -20
  246. package/src/commands/manuals/lsb_release.txt +0 -14
  247. package/src/commands/manuals/mkdir.txt +0 -10
  248. package/src/commands/manuals/mv.txt +0 -10
  249. package/src/commands/manuals/nano.txt +0 -11
  250. package/src/commands/manuals/neofetch.txt +0 -10
  251. package/src/commands/manuals/node.txt +0 -13
  252. package/src/commands/manuals/npm.txt +0 -13
  253. package/src/commands/manuals/npx.txt +0 -13
  254. package/src/commands/manuals/passwd.txt +0 -11
  255. package/src/commands/manuals/ping.txt +0 -10
  256. package/src/commands/manuals/printf.txt +0 -11
  257. package/src/commands/manuals/ps.txt +0 -10
  258. package/src/commands/manuals/pwd.txt +0 -10
  259. package/src/commands/manuals/python3.txt +0 -13
  260. package/src/commands/manuals/readlink.txt +0 -10
  261. package/src/commands/manuals/return.txt +0 -10
  262. package/src/commands/manuals/rm.txt +0 -10
  263. package/src/commands/manuals/sed.txt +0 -11
  264. package/src/commands/manuals/set.txt +0 -11
  265. package/src/commands/manuals/shift.txt +0 -10
  266. package/src/commands/manuals/sleep.txt +0 -10
  267. package/src/commands/manuals/sort.txt +0 -12
  268. package/src/commands/manuals/source.txt +0 -11
  269. package/src/commands/manuals/ssh.txt +0 -11
  270. package/src/commands/manuals/stat.txt +0 -10
  271. package/src/commands/manuals/su.txt +0 -13
  272. package/src/commands/manuals/sudo.txt +0 -11
  273. package/src/commands/manuals/tail.txt +0 -10
  274. package/src/commands/manuals/tar.txt +0 -19
  275. package/src/commands/manuals/tee.txt +0 -10
  276. package/src/commands/manuals/test.txt +0 -11
  277. package/src/commands/manuals/touch.txt +0 -11
  278. package/src/commands/manuals/tr.txt +0 -10
  279. package/src/commands/manuals/trap.txt +0 -10
  280. package/src/commands/manuals/true.txt +0 -10
  281. package/src/commands/manuals/type.txt +0 -10
  282. package/src/commands/manuals/uname.txt +0 -12
  283. package/src/commands/manuals/uniq.txt +0 -12
  284. package/src/commands/manuals/unset.txt +0 -10
  285. package/src/commands/manuals/uptime.txt +0 -11
  286. package/src/commands/manuals/wc.txt +0 -12
  287. package/src/commands/manuals/wget.txt +0 -12
  288. package/src/commands/manuals/which.txt +0 -10
  289. package/src/commands/manuals/whoami.txt +0 -10
  290. package/src/commands/manuals/xargs.txt +0 -10
  291. package/src/commands/manuals-bundle.ts +0 -898
  292. package/src/commands/mkdir.ts +0 -31
  293. package/src/commands/mv.ts +0 -50
  294. package/src/commands/nano.ts +0 -38
  295. package/src/commands/neofetch.ts +0 -53
  296. package/src/commands/node.ts +0 -341
  297. package/src/commands/npm.ts +0 -132
  298. package/src/commands/passwd.ts +0 -50
  299. package/src/commands/ping.ts +0 -32
  300. package/src/commands/printf.ts +0 -129
  301. package/src/commands/ps.ts +0 -58
  302. package/src/commands/pwd.ts +0 -9
  303. package/src/commands/python.ts +0 -2229
  304. package/src/commands/read.ts +0 -46
  305. package/src/commands/registry.ts +0 -249
  306. package/src/commands/rm.ts +0 -42
  307. package/src/commands/runtime.ts +0 -421
  308. package/src/commands/sed.ts +0 -68
  309. package/src/commands/seq.ts +0 -43
  310. package/src/commands/set.ts +0 -29
  311. package/src/commands/sh.ts +0 -467
  312. package/src/commands/shift.ts +0 -63
  313. package/src/commands/sleep.ts +0 -20
  314. package/src/commands/sort.ts +0 -46
  315. package/src/commands/source.ts +0 -52
  316. package/src/commands/stat.ts +0 -61
  317. package/src/commands/su.ts +0 -72
  318. package/src/commands/sudo.ts +0 -76
  319. package/src/commands/tail.ts +0 -53
  320. package/src/commands/tar.ts +0 -102
  321. package/src/commands/tee.ts +0 -36
  322. package/src/commands/test.ts +0 -137
  323. package/src/commands/touch.ts +0 -28
  324. package/src/commands/tr.ts +0 -70
  325. package/src/commands/tree.ts +0 -20
  326. package/src/commands/true.ts +0 -27
  327. package/src/commands/type.ts +0 -48
  328. package/src/commands/uname.ts +0 -29
  329. package/src/commands/uniq.ts +0 -39
  330. package/src/commands/unset.ts +0 -17
  331. package/src/commands/uptime.ts +0 -54
  332. package/src/commands/wc.ts +0 -55
  333. package/src/commands/wget.ts +0 -148
  334. package/src/commands/which.ts +0 -37
  335. package/src/commands/who.ts +0 -25
  336. package/src/commands/whoami.ts +0 -14
  337. package/src/commands/xargs.ts +0 -31
  338. package/src/index.ts +0 -67
  339. package/src/modules/linuxRootfs.ts +0 -1961
  340. package/src/modules/neofetch.ts +0 -358
  341. package/src/modules/shellInteractive.ts +0 -57
  342. package/src/modules/shellRuntime.ts +0 -76
  343. package/src/self-standalone.ts +0 -542
  344. package/src/standalone-wo-sftp.ts +0 -38
  345. package/src/standalone.ts +0 -72
  346. package/src/types/commands.ts +0 -146
  347. package/src/types/pipeline.ts +0 -52
  348. package/src/types/streams.ts +0 -32
  349. package/src/types/tar-stream.d.ts +0 -38
  350. package/src/types/vfs.ts +0 -98
  351. package/src/utils/expand.ts +0 -491
  352. package/src/utils/perfLogger.ts +0 -72
  353. package/src/utils/tokenize.ts +0 -98
  354. package/src/utils/vfsDiff.ts +0 -275
  355. package/tests/command-helpers.test.ts +0 -116
  356. package/tests/commands-admin-net.test.ts +0 -441
  357. package/tests/commands-advanced.test.ts +0 -456
  358. package/tests/commands-core.test.ts +0 -562
  359. package/tests/commands-missing.test.ts +0 -570
  360. package/tests/commands-specific-units.test.ts +0 -327
  361. package/tests/commands-text-sys.test.ts +0 -445
  362. package/tests/expand.test.ts +0 -170
  363. package/tests/helpers.test.ts +0 -97
  364. package/tests/new-features.test.ts +0 -1036
  365. package/tests/parser-executor.test.ts +0 -37
  366. package/tests/sftp.test.ts +0 -323
  367. package/tests/ssh-exec.test.ts +0 -45
  368. package/tests/test-helper.ts +0 -79
  369. package/tests/users.test.ts +0 -86
  370. package/tsconfig.json +0 -49
  371. package/typedoc.json +0 -47
@@ -1,467 +0,0 @@
1
- import type {
2
- CommandContext,
3
- CommandResult,
4
- ShellModule,
5
- } from "../types/commands";
6
- import { evalArith, expandAsync, expandBraces } from "../utils/expand";
7
- import { ifFlag } from "./command-helpers";
8
- import { resolvePath } from "./helpers";
9
- import { runCommand } from "./runtime";
10
-
11
- /** Alias for clarity inside sh.ts */
12
- type ShellContext = CommandContext;
13
-
14
- /**
15
- * Expand all shell forms including $(cmd) substitution.
16
- * Delegates to centralised expandAsync (single-quote-aware, depth-tracked).
17
- */
18
- async function expandVars(
19
- line: string,
20
- env: Record<string, string>,
21
- lastExit: number,
22
- ctx: ShellContext,
23
- ): Promise<string> {
24
- return expandAsync(line, env, lastExit, (sub) =>
25
- runCommand(
26
- sub,
27
- ctx.authUser,
28
- ctx.hostname,
29
- ctx.mode,
30
- ctx.cwd,
31
- ctx.shell,
32
- undefined,
33
- ctx.env,
34
- ).then((r) => r.stdout ?? ""),
35
- );
36
- }
37
-
38
- type Block =
39
- | {
40
- type: "if";
41
- cond: string;
42
- then_: string[];
43
- elif: Array<{ cond: string; body: string[] }>;
44
- else_: string[];
45
- }
46
- | { type: "for"; var: string; list: string; body: string[] }
47
- | { type: "while"; cond: string; body: string[] }
48
- | { type: "func"; name: string; body: string[] }
49
- | { type: "arith"; expr: string }
50
- | { type: "cmd"; line: string };
51
-
52
- /** Very small shell interpreter: supports if/elif/else/fi, for/do/done, while/do/done */
53
- function parseBlocks(lines: string[]): Block[] {
54
- const blocks: Block[] = [];
55
- let i = 0;
56
- while (i < lines.length) {
57
- const line = lines[i]!.trim();
58
- if (!line || line.startsWith("#")) {
59
- i++;
60
- continue;
61
- }
62
-
63
- // Function definition: name() { or function name { or name() { body }
64
- const funcMatchInline = line.match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{(.+)\}\s*$/);
65
- const funcMatch = funcMatchInline ?? (
66
- line.match(/^(?:function\s+)?(\w+)\s*\(\s*\)\s*\{?\s*$/) ||
67
- line.match(/^function\s+(\w+)\s*\{?\s*$/)
68
- );
69
- if (funcMatch) {
70
- const funcName = funcMatch[1]!;
71
- const body: string[] = [];
72
- // Inline: name() { cmd; } — single-line form
73
- if (funcMatchInline) {
74
- body.push(...funcMatchInline[2]!.split(";").map((s: string) => s.trim()).filter(Boolean));
75
- blocks.push({ type: "func", name: funcName, body });
76
- i++;
77
- continue;
78
- }
79
- i++;
80
- while (i < lines.length && lines[i]?.trim() !== "}" && i < lines.length + 1) {
81
- const l = lines[i]!.trim().replace(/^do\s+/, "");
82
- if (l && l !== "{") body.push(l);
83
- i++;
84
- }
85
- i++; // skip closing }
86
- blocks.push({ type: "func", name: funcName, body });
87
- continue;
88
- }
89
-
90
- // (( expr )) arithmetic statement
91
- const arithMatch = line.match(/^\(\(\s*(.+?)\s*\)\)$/);
92
- if (arithMatch) {
93
- blocks.push({ type: "arith", expr: arithMatch[1]! });
94
- i++;
95
- continue;
96
- }
97
-
98
- if (line.startsWith("if ") || line === "if") {
99
- const cond = line
100
- .replace(/^if\s+/, "")
101
- .replace(/;\s*then\s*$/, "")
102
- .trim();
103
- const thenLines: string[] = [];
104
- const elifBlocks: Array<{ cond: string; body: string[] }> = [];
105
- const elseLines: string[] = [];
106
- let section: "then" | "elif" | "else" = "then";
107
- let elifCond = "";
108
- i++;
109
- while (i < lines.length && lines[i]?.trim() !== "fi") {
110
- const l = lines[i]!.trim();
111
- if (l.startsWith("elif ")) {
112
- section = "elif";
113
- elifCond = l
114
- .replace(/^elif\s+/, "")
115
- .replace(/;\s*then\s*$/, "")
116
- .trim();
117
- elifBlocks.push({ cond: elifCond, body: [] });
118
- } else if (l === "else") {
119
- section = "else";
120
- } else if (l !== "then") {
121
- if (section === "then") thenLines.push(l);
122
- else if (section === "elif" && elifBlocks.length > 0)
123
- elifBlocks[elifBlocks.length - 1]!.body.push(l);
124
- else elseLines.push(l);
125
- }
126
- i++;
127
- }
128
- blocks.push({
129
- type: "if",
130
- cond,
131
- then_: thenLines,
132
- elif: elifBlocks,
133
- else_: elseLines,
134
- });
135
- } else if (line.startsWith("for ")) {
136
- const m = line.match(/^for\s+(\w+)\s+in\s+(.+?)(?:\s*;\s*do)?$/);
137
- if (m) {
138
- const body: string[] = [];
139
- i++;
140
- while (i < lines.length && lines[i]?.trim() !== "done") {
141
- const l = lines[i]!.trim().replace(/^do\s+/, "");
142
- if (l && l !== "do") body.push(l);
143
- i++;
144
- }
145
- blocks.push({ type: "for", var: m[1]!, list: m[2]!, body });
146
- } else {
147
- blocks.push({ type: "cmd", line });
148
- }
149
- } else if (line.startsWith("while ")) {
150
- const cond = line
151
- .replace(/^while\s+/, "")
152
- .replace(/;\s*do\s*$/, "")
153
- .trim();
154
- const body: string[] = [];
155
- i++;
156
- while (i < lines.length && lines[i]?.trim() !== "done") {
157
- const l = lines[i]!.trim().replace(/^do\s+/, "");
158
- if (l && l !== "do") body.push(l);
159
- i++;
160
- }
161
- blocks.push({ type: "while", cond, body });
162
- } else {
163
- blocks.push({ type: "cmd", line });
164
- }
165
- i++;
166
- }
167
- return blocks;
168
- }
169
-
170
- async function evalCondition(
171
- cond: string,
172
- ctx: CommandContext,
173
- ): Promise<boolean> {
174
- const expanded = await expandVars(
175
- cond,
176
- ctx.env.vars,
177
- ctx.env.lastExitCode,
178
- ctx,
179
- );
180
- // test -f / test -d / [ ... ]
181
- const testMatch = expanded.match(/^\[?\s*(.+?)\s*\]?$/);
182
- if (testMatch) {
183
- const expr = testMatch[1]!;
184
- // -f file
185
- const fTest = expr.match(/^-([fdeznr])\s+(.+)$/);
186
- if (fTest) {
187
- const [, flag, arg] = fTest;
188
- const p = resolvePath(ctx.cwd, arg!);
189
- if (flag === "f")
190
- return ctx.shell.vfs.exists(p) && ctx.shell.vfs.stat(p).type === "file";
191
- if (flag === "d")
192
- return (
193
- ctx.shell.vfs.exists(p) && ctx.shell.vfs.stat(p).type === "directory"
194
- );
195
- if (flag === "e") return ctx.shell.vfs.exists(p);
196
- if (flag === "z") return (arg ?? "").length === 0;
197
- if (flag === "n") return (arg ?? "").length > 0;
198
- }
199
- // string comparison
200
- const cmpMatch = expr.match(/^"?([^"]*)"?\s*(==|!=|=|<|>)\s*"?([^"]*)"?$/);
201
- if (cmpMatch) {
202
- const [, a, op, b] = cmpMatch;
203
- if (op === "==" || op === "=") return a === b;
204
- if (op === "!=") return a !== b;
205
- }
206
- // numeric
207
- const numMatch = expr.match(/^(\S+)\s+(-eq|-ne|-lt|-le|-gt|-ge)\s+(\S+)$/);
208
- if (numMatch) {
209
- const [, a, op, b] = numMatch;
210
- const na = Number(a),
211
- nb = Number(b);
212
- if (op === "-eq") return na === nb;
213
- if (op === "-ne") return na !== nb;
214
- if (op === "-lt") return na < nb;
215
- if (op === "-le") return na <= nb;
216
- if (op === "-gt") return na > nb;
217
- if (op === "-ge") return na >= nb;
218
- }
219
- }
220
- // fallback: run command and check exit code
221
- const r = await runCommand(
222
- expanded,
223
- ctx.authUser,
224
- ctx.hostname,
225
- ctx.mode,
226
- ctx.cwd,
227
- ctx.shell,
228
- undefined,
229
- ctx.env,
230
- );
231
- return (r.exitCode ?? 0) === 0;
232
- }
233
-
234
- async function runBlocks(
235
- blocks: Block[],
236
- ctx: CommandContext,
237
- ): Promise<CommandResult> {
238
- let lastResult: CommandResult = { exitCode: 0 };
239
- let output = "";
240
-
241
- for (const block of blocks) {
242
- if (block.type === "cmd") {
243
- const expanded = await expandVars(
244
- block.line,
245
- ctx.env.vars,
246
- ctx.env.lastExitCode,
247
- ctx,
248
- );
249
-
250
- // Bare VAR=val assignment(s) — handle before dispatching to runCommand
251
- const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)/;
252
- const tokens = expanded.trim().split(/\s+/);
253
- if (tokens.length > 0 && assignRe.test(tokens[0]!)) {
254
- const allAssign = tokens.every((t) => assignRe.test(t));
255
- if (allAssign) {
256
- for (const tok of tokens) {
257
- const m = tok.match(assignRe)!;
258
- ctx.env.vars[m[1]!] = m[2]!;
259
- }
260
- ctx.env.lastExitCode = 0;
261
- continue;
262
- }
263
- }
264
-
265
- const r = await (async () => {
266
- // Check if expanded matches a registered function
267
- const cmdName = expanded.trim().split(/\s+/)[0] ?? "";
268
- const funcBody = ctx.env.vars[`__func_${cmdName}`];
269
- if (funcBody) {
270
- // Set positional params $1 $2 ... from remaining args
271
- const funcArgs = expanded.trim().split(/\s+/).slice(1);
272
- const savedVars = { ...ctx.env.vars };
273
- funcArgs.forEach((a, i) => { ctx.env.vars[String(i + 1)] = a; });
274
- ctx.env.vars["0"] = cmdName;
275
- const funcLines = funcBody.split("\n");
276
- const funcResult = await runBlocks(parseBlocks(funcLines), ctx);
277
- // Restore positional params
278
- for (let pi = 1; pi <= funcArgs.length; pi++) delete ctx.env.vars[String(pi)];
279
- Object.assign(ctx.env.vars, { ...savedVars, ...ctx.env.vars });
280
- return funcResult;
281
- }
282
- return runCommand(
283
- expanded,
284
- ctx.authUser,
285
- ctx.hostname,
286
- ctx.mode,
287
- ctx.cwd,
288
- ctx.shell,
289
- undefined,
290
- ctx.env,
291
- );
292
- })();
293
- ctx.env.lastExitCode = r.exitCode ?? 0;
294
- if (r.stdout) output += `${r.stdout}\n`;
295
- if (r.stderr) return { ...r, stdout: output.trim() };
296
- lastResult = r;
297
- } else if (block.type === "if") {
298
- let ran = false;
299
- if (await evalCondition(block.cond, ctx)) {
300
- const sub = await runBlocks(parseBlocks(block.then_), ctx);
301
- if (sub.stdout) output += `${sub.stdout}\n`;
302
- ran = true;
303
- } else {
304
- for (const elif of block.elif) {
305
- if (await evalCondition(elif.cond, ctx)) {
306
- const sub = await runBlocks(parseBlocks(elif.body), ctx);
307
- if (sub.stdout) output += `${sub.stdout}\n`;
308
- ran = true;
309
- break;
310
- }
311
- }
312
- if (!ran && block.else_.length > 0) {
313
- const sub = await runBlocks(parseBlocks(block.else_), ctx);
314
- if (sub.stdout) output += `${sub.stdout}\n`;
315
- }
316
- }
317
- } else if (block.type === "func") {
318
- // Register function in env vars as __func_<name>=<body>
319
- ctx.env.vars[`__func_${block.name}`] = block.body.join("\n");
320
- } else if (block.type === "arith") {
321
- // (( expr )) — evaluate arithmetic, update vars
322
- const expr = block.expr.trim();
323
- // Handle i++ / i-- / i+=N / i-=N
324
- const incMatch = expr.match(/^(\w+)\s*(\+\+|--)$/);
325
- if (incMatch) {
326
- const val = parseInt(ctx.env.vars[incMatch[1]!] ?? "0", 10);
327
- ctx.env.vars[incMatch[1]!] = String(incMatch[2] === "++" ? val + 1 : val - 1);
328
- } else {
329
- const assignMatch = expr.match(/^(\w+)\s*([+\-*/])=\s*(.+)$/);
330
- if (assignMatch) {
331
- const lhs = parseInt(ctx.env.vars[assignMatch[1]!] ?? "0", 10);
332
- const rhs = parseInt(assignMatch[3]!, 10);
333
- const ops: Record<string, number> = { "+": lhs + rhs, "-": lhs - rhs, "*": lhs * rhs, "/": Math.floor(lhs / rhs) };
334
- ctx.env.vars[assignMatch[1]!] = String(ops[assignMatch[2]!] ?? lhs);
335
- } else {
336
- const value = evalArith(expr, ctx.env.vars);
337
- if (!Number.isNaN(value)) {
338
- ctx.env.lastExitCode = value === 0 ? 1 : 0;
339
- }
340
- }
341
- }
342
- } else if (block.type === "for") {
343
- const listExpanded = await expandVars(
344
- block.list,
345
- ctx.env.vars,
346
- ctx.env.lastExitCode,
347
- ctx,
348
- );
349
- // Apply brace expansion to each token in the list
350
- const items = listExpanded.trim().split(/\s+/).flatMap(expandBraces);
351
- for (const item of items) {
352
- ctx.env.vars[block.var] = item;
353
- const sub = await runBlocks(parseBlocks(block.body), ctx);
354
- if (sub.stdout) output += `${sub.stdout}\n`;
355
- if (sub.closeSession) return sub;
356
- }
357
- } else if (block.type === "while") {
358
- let iterations = 0;
359
- while (iterations < 1000 && (await evalCondition(block.cond, ctx))) {
360
- const sub = await runBlocks(parseBlocks(block.body), ctx);
361
- if (sub.stdout) output += `${sub.stdout}\n`;
362
- if (sub.closeSession) return sub;
363
- iterations++;
364
- }
365
- }
366
- }
367
- return { ...lastResult, stdout: output.trim() || lastResult.stdout };
368
- }
369
-
370
- /**
371
- * Execute shell scripts or commands with a minimal shell interpreter.
372
- * Supports if/elif/else, for loops, while loops, and variable expansion.
373
- * @category shell
374
- * @params ["-c <script>", "[<file>]"]
375
- */
376
-
377
- /**
378
- * Split a sh script into logical lines, respecting:
379
- * - `{...}` braces (function bodies)
380
- * - Newlines and semicolons at depth 0 only
381
- */
382
- function splitShScript(script: string): string[] {
383
- const lines: string[] = [];
384
- let current = "";
385
- let depth = 0;
386
- let inSingleQ = false;
387
- let inDoubleQ = false;
388
- let i = 0;
389
- while (i < script.length) {
390
- const ch = script[i]!;
391
- if (!inSingleQ && !inDoubleQ) {
392
- if (ch === "'") { inSingleQ = true; current += ch; i++; continue; }
393
- if (ch === '"') { inDoubleQ = true; current += ch; i++; continue; }
394
- if (ch === "{") { depth++; current += ch; i++; continue; }
395
- if (ch === "}") {
396
- depth--;
397
- current += ch;
398
- i++;
399
- // At depth 0, closing } ends the function body line
400
- if (depth === 0) {
401
- const t = current.trim();
402
- if (t) lines.push(t);
403
- current = "";
404
- // Skip trailing ; or whitespace
405
- while (i < script.length && (script[i] === ";" || script[i] === " ")) i++;
406
- }
407
- continue;
408
- }
409
- if (depth === 0 && (ch === ";" || ch === "\n")) {
410
- const t = current.trim();
411
- if (t && !t.startsWith("#")) lines.push(t);
412
- current = "";
413
- i++;
414
- continue;
415
- }
416
- } else if (inSingleQ && ch === "'") {
417
- inSingleQ = false;
418
- } else if (inDoubleQ && ch === '"') {
419
- inDoubleQ = false;
420
- }
421
- current += ch;
422
- i++;
423
- }
424
- const t = current.trim();
425
- if (t && !t.startsWith("#")) lines.push(t);
426
- return lines;
427
- }
428
-
429
- export const shCommand: ShellModule = {
430
- name: "sh",
431
- aliases: ["bash"],
432
- description: "Execute shell script or command",
433
- category: "shell",
434
- params: ["-c <script>", "[<file>]"],
435
- run: async (ctx: CommandContext) => {
436
- const { args, shell, cwd } = ctx;
437
-
438
- // sh -c "inline script"
439
- if (ifFlag(args, "-c")) {
440
- const script = args[args.indexOf("-c") + 1] ?? "";
441
- if (!script) return { stderr: "sh: -c requires a script", exitCode: 1 };
442
- const lines = splitShScript(script);
443
- const blocks = parseBlocks(lines);
444
- return runBlocks(blocks, ctx);
445
- }
446
-
447
- // sh <file>
448
- const fileArg = args[0];
449
- if (fileArg) {
450
- const p = resolvePath(cwd, fileArg);
451
- if (!shell.vfs.exists(p))
452
- return {
453
- stderr: `sh: ${fileArg}: No such file or directory`,
454
- exitCode: 1,
455
- };
456
- const content = shell.vfs.readFile(p);
457
- const lines = splitShScript(content);
458
- const blocks = parseBlocks(lines);
459
- return runBlocks(blocks, ctx);
460
- }
461
-
462
- return {
463
- stderr: "sh: invalid usage. Use: sh -c 'cmd' or sh <file>",
464
- exitCode: 1,
465
- };
466
- },
467
- };
@@ -1,63 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Shift positional parameters (remove first N arguments).
5
- * @category shell
6
- * @params ["[n]"]
7
- */
8
- export const shiftCommand: ShellModule = {
9
- name: "shift",
10
- description: "Shift positional parameters",
11
- category: "shell",
12
- params: ["[n]"],
13
- // shift is meaningful only inside sh scripts where positional params exist.
14
- // In the current impl, positional params ($1 $2 …) aren't tracked in env by default.
15
- // We store them under env.vars.__argv and shift there if present.
16
- run: ({ args, env }) => {
17
- if (!env) return { exitCode: 0 };
18
- const n = parseInt(args[0] ?? "1", 10) || 1;
19
- const argv = env.vars.__argv?.split("\x00").filter(Boolean) ?? [];
20
- env.vars.__argv = argv.slice(n).join("\x00");
21
- // Update $1 $2 … in env
22
- const shifted = argv.slice(n);
23
- for (let i = 1; i <= 9; i++) {
24
- env.vars[String(i)] = shifted[i - 1] ?? "";
25
- }
26
- return { exitCode: 0 };
27
- },
28
- };
29
-
30
- /**
31
- * Trap signals and execute actions on signal receipt or shell exit.
32
- * @category shell
33
- * @params ["[action] [signal...]"]
34
- */
35
- export const trapCommand: ShellModule = {
36
- name: "trap",
37
- description: "Trap signals and events",
38
- category: "shell",
39
- params: ["[action] [signal...]"],
40
- // Store trap handlers in env for EXIT signal support
41
- run: ({ args, env }) => {
42
- if (!env || args.length === 0) return { exitCode: 0 };
43
- const action = args[0] ?? "";
44
- const signals = args.slice(1);
45
- for (const sig of signals) {
46
- env.vars[`__trap_${sig.toUpperCase()}`] = action;
47
- }
48
- return { exitCode: 0 };
49
- },
50
- };
51
-
52
- export const returnCommand: ShellModule = {
53
- name: "return",
54
- description: "Return from a shell function",
55
- category: "shell",
56
- params: ["[n]"],
57
- run: ({ args, env }) => {
58
- const code = parseInt(args[0] ?? "0", 10);
59
- if (env) env.lastExitCode = code;
60
- // Signal the caller via exitCode; function return is handled by runBlocks
61
- return { exitCode: code };
62
- },
63
- };
@@ -1,20 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
-
3
- /**
4
- * Delay execution for a specified number of seconds.
5
- * @category system
6
- * @params ["<seconds>"]
7
- */
8
- export const sleepCommand: ShellModule = {
9
- name: "sleep",
10
- description: "Delay execution",
11
- category: "system",
12
- params: ["<seconds>"],
13
- run: async ({ args }) => {
14
- const secs = parseFloat(args[0] ?? "1");
15
- if (Number.isNaN(secs) || secs < 0)
16
- return { stderr: "sleep: invalid time", exitCode: 1 };
17
- await new Promise((r) => setTimeout(r, secs * 1000));
18
- return { exitCode: 0 };
19
- },
20
- };
@@ -1,46 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { ifFlag } from "./command-helpers";
3
- import { assertPathAccess, resolvePath } from "./helpers";
4
-
5
- /**
6
- * Sort lines of text with various options (reverse, numeric, unique).
7
- * @category text
8
- * @params ["[-r] [-n] [-u] [-k <col>] [file...]"]
9
- */
10
- export const sortCommand: ShellModule = {
11
- name: "sort",
12
- description: "Sort lines of text",
13
- category: "text",
14
- params: ["[-r] [-n] [-u] [-k <col>] [file...]"],
15
- run: ({ authUser, shell, cwd, args, stdin }) => {
16
- const reverse = ifFlag(args, ["-r"]);
17
- const numeric = ifFlag(args, ["-n"]);
18
- const unique = ifFlag(args, ["-u"]);
19
- const files = args.filter((a) => !a.startsWith("-"));
20
-
21
- const getContent = (): string => {
22
- if (files.length > 0) {
23
- return files
24
- .map((f) => {
25
- try {
26
- assertPathAccess(authUser, resolvePath(cwd, f), "sort");
27
- return shell.vfs.readFile(resolvePath(cwd, f));
28
- } catch {
29
- return "";
30
- }
31
- })
32
- .join("\n");
33
- }
34
- return stdin ?? "";
35
- };
36
-
37
- const lines = getContent().split("\n").filter(Boolean);
38
- const sorted = [...lines].sort((a, b) => {
39
- if (numeric) return Number(a) - Number(b);
40
- return a.localeCompare(b);
41
- });
42
- const result = reverse ? sorted.reverse() : sorted;
43
- const out = unique ? [...new Set(result)] : result;
44
- return { stdout: out.join("\n"), exitCode: 0 };
45
- },
46
- };
@@ -1,52 +0,0 @@
1
- import type { ShellModule } from "../types/commands";
2
- import { resolvePath } from "./helpers";
3
- import { runCommand } from "./runtime";
4
-
5
- /**
6
- * Execute commands from a file in the current shell environment.
7
- * @category shell
8
- * @params ["<file> [args...]"]
9
- */
10
- export const sourceCommand: ShellModule = {
11
- name: "source",
12
- aliases: ["."],
13
- description: "Execute commands from a file in the current shell environment",
14
- category: "shell",
15
- params: ["<file> [args...]"],
16
- run: async ({ args, authUser, hostname, cwd, shell, env }) => {
17
- const fileArg = args[0];
18
- if (!fileArg) {
19
- return { stderr: "source: missing filename", exitCode: 1 };
20
- }
21
-
22
- const filePath = resolvePath(cwd, fileArg);
23
- if (!shell.vfs.exists(filePath)) {
24
- return {
25
- stderr: `source: ${fileArg}: No such file or directory`,
26
- exitCode: 1,
27
- };
28
- }
29
-
30
- const content = shell.vfs.readFile(filePath);
31
- let lastExitCode = 0;
32
-
33
- for (const line of content.split("\n")) {
34
- const l = line.trim();
35
- if (!l || l.startsWith("#")) continue;
36
- const result = await runCommand(
37
- l,
38
- authUser,
39
- hostname,
40
- "shell",
41
- cwd,
42
- shell,
43
- undefined,
44
- env,
45
- );
46
- lastExitCode = result.exitCode ?? 0;
47
- if (result.closeSession || result.switchUser) return result;
48
- }
49
-
50
- return { exitCode: lastExitCode };
51
- },
52
- };