typescript-virtual-container 1.5.2 → 1.5.4

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 (364) hide show
  1. package/README.md +43 -23
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/SSHMimic/executor.js +23 -5
  4. package/dist/commands/basename.d.ts +13 -0
  5. package/dist/commands/basename.js +45 -0
  6. package/dist/commands/file.d.ts +8 -0
  7. package/dist/commands/file.js +57 -0
  8. package/dist/commands/fun.d.ts +32 -0
  9. package/dist/commands/fun.js +172 -0
  10. package/dist/commands/ifconfig.d.ts +7 -0
  11. package/dist/commands/ifconfig.js +52 -0
  12. package/dist/commands/last.d.ts +13 -0
  13. package/dist/commands/last.js +68 -0
  14. package/dist/commands/manuals-bundle.js +598 -6
  15. package/dist/commands/registry.js +24 -2
  16. package/dist/commands/runtime.js +159 -106
  17. package/dist/commands/sh.js +5 -0
  18. package/dist/commands/tput.d.ts +13 -0
  19. package/dist/commands/tput.js +76 -0
  20. package/dist/commands/w.d.ts +7 -0
  21. package/dist/commands/w.js +38 -0
  22. package/dist/utils/expand.d.ts +12 -0
  23. package/dist/utils/expand.js +84 -0
  24. package/package.json +9 -3
  25. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -50
  26. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -31
  27. package/.github/dependabot.yml +0 -27
  28. package/.github/pull_request_template.md +0 -21
  29. package/.github/workflows/create-pull-request.yml +0 -85
  30. package/.github/workflows/publish.yml +0 -25
  31. package/.github/workflows/test-battery.yml +0 -102
  32. package/.vscode/settings.json +0 -20
  33. package/CODE_OF_CONDUCT.md +0 -39
  34. package/CONTRIBUTING.md +0 -59
  35. package/HONEYPOT.md +0 -358
  36. package/SECURITY.md +0 -33
  37. package/benchmark-results.txt +0 -40
  38. package/benchmark-virtualshell.ts +0 -88
  39. package/biome.json +0 -37
  40. package/build.js +0 -22
  41. package/builds/fortune-nyx-v1.5.1-directbash-k6.1.0.mjs +0 -1768
  42. package/builds/fortune-nyx-v1.5.1-ssh-nosftp.js +0 -1768
  43. package/builds/fortune-nyx-v1.5.1-ssh.cjs +0 -1769
  44. package/builds/fortune-nyx-v1.5.1-web.min.js +0 -17022
  45. package/bun.lock +0 -244
  46. package/docs/.nojekyll +0 -1
  47. package/docs/app.js +0 -1755
  48. package/docs/assets/hierarchy.js +0 -1
  49. package/docs/assets/highlight.css +0 -162
  50. package/docs/assets/icons.js +0 -18
  51. package/docs/assets/icons.svg +0 -1
  52. package/docs/assets/main.js +0 -60
  53. package/docs/assets/navigation.js +0 -1
  54. package/docs/assets/search.js +0 -1
  55. package/docs/assets/style.css +0 -1633
  56. package/docs/classes/HoneyPot.html +0 -31
  57. package/docs/classes/IdleManager.html +0 -162
  58. package/docs/classes/SshClient.html +0 -66
  59. package/docs/classes/VirtualFileSystem.html +0 -279
  60. package/docs/classes/VirtualPackageManager.html +0 -63
  61. package/docs/classes/VirtualSftpServer.html +0 -169
  62. package/docs/classes/VirtualShell.html +0 -285
  63. package/docs/classes/VirtualSshServer.html +0 -182
  64. package/docs/classes/VirtualUserManager.html +0 -276
  65. package/docs/demo.html +0 -82
  66. package/docs/functions/assertDiff.html +0 -6
  67. package/docs/functions/diffSnapshots.html +0 -7
  68. package/docs/functions/formatDiff.html +0 -6
  69. package/docs/functions/getArg.html +0 -13
  70. package/docs/functions/getFlag.html +0 -15
  71. package/docs/functions/ifFlag.html +0 -11
  72. package/docs/hierarchy.html +0 -1
  73. package/docs/index.html +0 -1869
  74. package/docs/interfaces/AuditLogEntry.html +0 -6
  75. package/docs/interfaces/CommandContext.html +0 -22
  76. package/docs/interfaces/CommandResult.html +0 -26
  77. package/docs/interfaces/ExecStream.html +0 -11
  78. package/docs/interfaces/HoneyPotStats.html +0 -16
  79. package/docs/interfaces/IdleManagerOptions.html +0 -7
  80. package/docs/interfaces/InstalledPackage.html +0 -20
  81. package/docs/interfaces/NanoEditorSession.html +0 -8
  82. package/docs/interfaces/PackageDefinition.html +0 -30
  83. package/docs/interfaces/PackageFile.html +0 -8
  84. package/docs/interfaces/PasswordChallenge.html +0 -16
  85. package/docs/interfaces/RemoveOptions.html +0 -4
  86. package/docs/interfaces/ShellEnv.html +0 -6
  87. package/docs/interfaces/ShellModule.html +0 -14
  88. package/docs/interfaces/ShellProperties.html +0 -14
  89. package/docs/interfaces/ShellStream.html +0 -11
  90. package/docs/interfaces/SudoChallenge.html +0 -24
  91. package/docs/interfaces/VfsBaseNode.html +0 -12
  92. package/docs/interfaces/VfsDiff.html +0 -10
  93. package/docs/interfaces/VfsDiffEntry.html +0 -6
  94. package/docs/interfaces/VfsDiffModified.html +0 -10
  95. package/docs/interfaces/VfsDirectoryNode.html +0 -15
  96. package/docs/interfaces/VfsFileNode.html +0 -17
  97. package/docs/interfaces/VfsOptions.html +0 -26
  98. package/docs/interfaces/VfsSnapshot.html +0 -3
  99. package/docs/interfaces/VfsSnapshotBaseNode.html +0 -8
  100. package/docs/interfaces/VfsSnapshotDirectoryNode.html +0 -10
  101. package/docs/interfaces/VfsSnapshotFileNode.html +0 -12
  102. package/docs/interfaces/VirtualActiveSession.html +0 -12
  103. package/docs/interfaces/VirtualSftpServerOptions.html +0 -7
  104. package/docs/interfaces/VirtualShellVfsLike.html +0 -15
  105. package/docs/interfaces/VirtualShellVfsOptions.html +0 -3
  106. package/docs/interfaces/WriteFileOptions.html +0 -6
  107. package/docs/media/LICENSE +0 -21
  108. package/docs/modules.html +0 -1
  109. package/docs/types/ArgParseOptions.html +0 -4
  110. package/docs/types/CommandMode.html +0 -2
  111. package/docs/types/CommandOutcome.html +0 -2
  112. package/docs/types/IdleState.html +0 -1
  113. package/docs/types/VfsNodeStats.html +0 -2
  114. package/docs/types/VfsNodeType.html +0 -2
  115. package/docs/types/VfsPersistenceMode.html +0 -5
  116. package/docs/types/VfsSnapshotNode.html +0 -2
  117. package/examples/README.md +0 -288
  118. package/examples/app.js +0 -1755
  119. package/examples/app.ts +0 -299
  120. package/examples/build.js +0 -27
  121. package/examples/demo.html +0 -33
  122. package/examples/honeypot-audit.ts +0 -180
  123. package/examples/honeypot-export.ts +0 -253
  124. package/examples/honeypot-quickstart.ts +0 -110
  125. package/examples/index.html +0 -82
  126. package/examples/server.js +0 -55
  127. package/polyfills/buffer.js +0 -117
  128. package/polyfills/node_child_process/index.js +0 -2
  129. package/polyfills/node_crypto/index.js +0 -167
  130. package/polyfills/node_events/index.js +0 -9
  131. package/polyfills/node_fs/index.js +0 -202
  132. package/polyfills/node_fs/promises.js +0 -4
  133. package/polyfills/node_os/index.js +0 -9
  134. package/polyfills/node_path/index.js +0 -28
  135. package/polyfills/node_vm/index.js +0 -7
  136. package/polyfills/node_zlib/index.js +0 -3
  137. package/polyfills/process.js +0 -14
  138. package/polyfills/ssh2/index.js +0 -75
  139. package/scripts/build-all.mjs +0 -226
  140. package/scripts/build-names.mjs +0 -43
  141. package/scripts/generate-manuals-bundle.mjs +0 -49
  142. package/scripts/postinstall.js +0 -42
  143. package/scripts/publish-package.sh +0 -70
  144. package/src/Honeypot/index.ts +0 -457
  145. package/src/SSHClient/index.ts +0 -270
  146. package/src/SSHMimic/exec.ts +0 -49
  147. package/src/SSHMimic/executor.ts +0 -251
  148. package/src/SSHMimic/hostKey.ts +0 -21
  149. package/src/SSHMimic/index.ts +0 -337
  150. package/src/SSHMimic/loginBanner.ts +0 -36
  151. package/src/SSHMimic/loginFormat.ts +0 -10
  152. package/src/SSHMimic/prompt.ts +0 -14
  153. package/src/SSHMimic/sftp.ts +0 -883
  154. package/src/VirtualFileSystem/binaryPack.ts +0 -258
  155. package/src/VirtualFileSystem/index.ts +0 -1193
  156. package/src/VirtualFileSystem/internalTypes.ts +0 -43
  157. package/src/VirtualFileSystem/journal.ts +0 -171
  158. package/src/VirtualFileSystem/path.ts +0 -74
  159. package/src/VirtualPackageManager/index.ts +0 -1006
  160. package/src/VirtualShell/idleManager.ts +0 -137
  161. package/src/VirtualShell/index.ts +0 -475
  162. package/src/VirtualShell/shell.ts +0 -700
  163. package/src/VirtualShell/shellParser.ts +0 -285
  164. package/src/VirtualUserManager/index.ts +0 -758
  165. package/src/bun.d.ts +0 -1
  166. package/src/commands/adduser.ts +0 -103
  167. package/src/commands/alias.ts +0 -69
  168. package/src/commands/apt.ts +0 -233
  169. package/src/commands/awk.ts +0 -168
  170. package/src/commands/base64.ts +0 -29
  171. package/src/commands/cat.ts +0 -52
  172. package/src/commands/cd.ts +0 -25
  173. package/src/commands/chmod.ts +0 -85
  174. package/src/commands/clear.ts +0 -15
  175. package/src/commands/command-helpers.ts +0 -286
  176. package/src/commands/cp.ts +0 -83
  177. package/src/commands/curl.ts +0 -147
  178. package/src/commands/cut.ts +0 -36
  179. package/src/commands/date.ts +0 -30
  180. package/src/commands/declare.ts +0 -49
  181. package/src/commands/deluser.ts +0 -98
  182. package/src/commands/df.ts +0 -23
  183. package/src/commands/diff.ts +0 -43
  184. package/src/commands/dpkg.ts +0 -180
  185. package/src/commands/du.ts +0 -56
  186. package/src/commands/echo.ts +0 -58
  187. package/src/commands/env.ts +0 -23
  188. package/src/commands/exit.ts +0 -18
  189. package/src/commands/export.ts +0 -34
  190. package/src/commands/find.ts +0 -68
  191. package/src/commands/free.ts +0 -47
  192. package/src/commands/grep.ts +0 -116
  193. package/src/commands/groups.ts +0 -19
  194. package/src/commands/gzip.ts +0 -88
  195. package/src/commands/head.ts +0 -52
  196. package/src/commands/help.ts +0 -152
  197. package/src/commands/helpers.ts +0 -234
  198. package/src/commands/history.ts +0 -34
  199. package/src/commands/hostname.ts +0 -14
  200. package/src/commands/htop.ts +0 -20
  201. package/src/commands/id.ts +0 -19
  202. package/src/commands/index.ts +0 -9
  203. package/src/commands/kill.ts +0 -19
  204. package/src/commands/ln.ts +0 -71
  205. package/src/commands/ls.ts +0 -243
  206. package/src/commands/lsb-release.ts +0 -63
  207. package/src/commands/man.ts +0 -31
  208. package/src/commands/manuals/adduser.txt +0 -11
  209. package/src/commands/manuals/apt-cache.txt +0 -12
  210. package/src/commands/manuals/apt.txt +0 -20
  211. package/src/commands/manuals/awk.txt +0 -13
  212. package/src/commands/manuals/cat.txt +0 -14
  213. package/src/commands/manuals/cd.txt +0 -16
  214. package/src/commands/manuals/chmod.txt +0 -16
  215. package/src/commands/manuals/clear.txt +0 -10
  216. package/src/commands/manuals/cp.txt +0 -10
  217. package/src/commands/manuals/curl.txt +0 -20
  218. package/src/commands/manuals/date.txt +0 -14
  219. package/src/commands/manuals/declare.txt +0 -12
  220. package/src/commands/manuals/deluser.txt +0 -10
  221. package/src/commands/manuals/df.txt +0 -10
  222. package/src/commands/manuals/dpkg-query.txt +0 -11
  223. package/src/commands/manuals/dpkg.txt +0 -14
  224. package/src/commands/manuals/du.txt +0 -11
  225. package/src/commands/manuals/echo.txt +0 -11
  226. package/src/commands/manuals/false.txt +0 -10
  227. package/src/commands/manuals/find.txt +0 -11
  228. package/src/commands/manuals/free.txt +0 -12
  229. package/src/commands/manuals/grep.txt +0 -13
  230. package/src/commands/manuals/groups.txt +0 -10
  231. package/src/commands/manuals/gzip.txt +0 -11
  232. package/src/commands/manuals/head.txt +0 -10
  233. package/src/commands/manuals/help.txt +0 -11
  234. package/src/commands/manuals/history.txt +0 -11
  235. package/src/commands/manuals/hostname.txt +0 -10
  236. package/src/commands/manuals/id.txt +0 -10
  237. package/src/commands/manuals/kill.txt +0 -13
  238. package/src/commands/manuals/ls.txt +0 -20
  239. package/src/commands/manuals/lsb_release.txt +0 -14
  240. package/src/commands/manuals/mkdir.txt +0 -10
  241. package/src/commands/manuals/mv.txt +0 -10
  242. package/src/commands/manuals/nano.txt +0 -11
  243. package/src/commands/manuals/neofetch.txt +0 -10
  244. package/src/commands/manuals/node.txt +0 -13
  245. package/src/commands/manuals/npm.txt +0 -13
  246. package/src/commands/manuals/npx.txt +0 -13
  247. package/src/commands/manuals/passwd.txt +0 -11
  248. package/src/commands/manuals/ping.txt +0 -10
  249. package/src/commands/manuals/printf.txt +0 -11
  250. package/src/commands/manuals/ps.txt +0 -10
  251. package/src/commands/manuals/pwd.txt +0 -10
  252. package/src/commands/manuals/python3.txt +0 -13
  253. package/src/commands/manuals/readlink.txt +0 -10
  254. package/src/commands/manuals/return.txt +0 -10
  255. package/src/commands/manuals/rm.txt +0 -10
  256. package/src/commands/manuals/sed.txt +0 -11
  257. package/src/commands/manuals/set.txt +0 -11
  258. package/src/commands/manuals/shift.txt +0 -10
  259. package/src/commands/manuals/sleep.txt +0 -10
  260. package/src/commands/manuals/sort.txt +0 -12
  261. package/src/commands/manuals/source.txt +0 -11
  262. package/src/commands/manuals/ssh.txt +0 -11
  263. package/src/commands/manuals/stat.txt +0 -10
  264. package/src/commands/manuals/su.txt +0 -13
  265. package/src/commands/manuals/sudo.txt +0 -11
  266. package/src/commands/manuals/tail.txt +0 -10
  267. package/src/commands/manuals/tar.txt +0 -19
  268. package/src/commands/manuals/tee.txt +0 -10
  269. package/src/commands/manuals/test.txt +0 -11
  270. package/src/commands/manuals/touch.txt +0 -11
  271. package/src/commands/manuals/tr.txt +0 -10
  272. package/src/commands/manuals/trap.txt +0 -10
  273. package/src/commands/manuals/true.txt +0 -10
  274. package/src/commands/manuals/type.txt +0 -10
  275. package/src/commands/manuals/uname.txt +0 -12
  276. package/src/commands/manuals/uniq.txt +0 -12
  277. package/src/commands/manuals/unset.txt +0 -10
  278. package/src/commands/manuals/uptime.txt +0 -11
  279. package/src/commands/manuals/wc.txt +0 -12
  280. package/src/commands/manuals/wget.txt +0 -12
  281. package/src/commands/manuals/which.txt +0 -10
  282. package/src/commands/manuals/whoami.txt +0 -10
  283. package/src/commands/manuals/xargs.txt +0 -10
  284. package/src/commands/manuals-bundle.ts +0 -898
  285. package/src/commands/mkdir.ts +0 -31
  286. package/src/commands/mv.ts +0 -50
  287. package/src/commands/nano.ts +0 -38
  288. package/src/commands/neofetch.ts +0 -53
  289. package/src/commands/node.ts +0 -341
  290. package/src/commands/npm.ts +0 -132
  291. package/src/commands/passwd.ts +0 -50
  292. package/src/commands/ping.ts +0 -32
  293. package/src/commands/printf.ts +0 -129
  294. package/src/commands/ps.ts +0 -58
  295. package/src/commands/pwd.ts +0 -9
  296. package/src/commands/python.ts +0 -2229
  297. package/src/commands/read.ts +0 -46
  298. package/src/commands/registry.ts +0 -249
  299. package/src/commands/rm.ts +0 -42
  300. package/src/commands/runtime.ts +0 -378
  301. package/src/commands/sed.ts +0 -68
  302. package/src/commands/seq.ts +0 -43
  303. package/src/commands/set.ts +0 -29
  304. package/src/commands/sh.ts +0 -467
  305. package/src/commands/shift.ts +0 -63
  306. package/src/commands/sleep.ts +0 -20
  307. package/src/commands/sort.ts +0 -46
  308. package/src/commands/source.ts +0 -52
  309. package/src/commands/stat.ts +0 -61
  310. package/src/commands/su.ts +0 -72
  311. package/src/commands/sudo.ts +0 -76
  312. package/src/commands/tail.ts +0 -53
  313. package/src/commands/tar.ts +0 -102
  314. package/src/commands/tee.ts +0 -36
  315. package/src/commands/test.ts +0 -137
  316. package/src/commands/touch.ts +0 -28
  317. package/src/commands/tr.ts +0 -70
  318. package/src/commands/tree.ts +0 -20
  319. package/src/commands/true.ts +0 -27
  320. package/src/commands/type.ts +0 -48
  321. package/src/commands/uname.ts +0 -29
  322. package/src/commands/uniq.ts +0 -39
  323. package/src/commands/unset.ts +0 -17
  324. package/src/commands/uptime.ts +0 -54
  325. package/src/commands/wc.ts +0 -55
  326. package/src/commands/wget.ts +0 -148
  327. package/src/commands/which.ts +0 -37
  328. package/src/commands/who.ts +0 -25
  329. package/src/commands/whoami.ts +0 -14
  330. package/src/commands/xargs.ts +0 -31
  331. package/src/index.ts +0 -67
  332. package/src/modules/linuxRootfs.ts +0 -1961
  333. package/src/modules/neofetch.ts +0 -358
  334. package/src/modules/shellInteractive.ts +0 -57
  335. package/src/modules/shellRuntime.ts +0 -76
  336. package/src/self-standalone.ts +0 -542
  337. package/src/standalone-wo-sftp.ts +0 -38
  338. package/src/standalone.ts +0 -72
  339. package/src/types/commands.ts +0 -146
  340. package/src/types/pipeline.ts +0 -52
  341. package/src/types/streams.ts +0 -32
  342. package/src/types/tar-stream.d.ts +0 -38
  343. package/src/types/vfs.ts +0 -98
  344. package/src/utils/expand.ts +0 -491
  345. package/src/utils/perfLogger.ts +0 -72
  346. package/src/utils/tokenize.ts +0 -98
  347. package/src/utils/vfsDiff.ts +0 -275
  348. package/tests/command-helpers.test.ts +0 -116
  349. package/tests/commands-admin-net.test.ts +0 -441
  350. package/tests/commands-advanced.test.ts +0 -456
  351. package/tests/commands-core.test.ts +0 -562
  352. package/tests/commands-missing.test.ts +0 -570
  353. package/tests/commands-specific-units.test.ts +0 -327
  354. package/tests/commands-text-sys.test.ts +0 -445
  355. package/tests/expand.test.ts +0 -170
  356. package/tests/helpers.test.ts +0 -97
  357. package/tests/new-features.test.ts +0 -1036
  358. package/tests/parser-executor.test.ts +0 -37
  359. package/tests/sftp.test.ts +0 -323
  360. package/tests/ssh-exec.test.ts +0 -45
  361. package/tests/test-helper.ts +0 -79
  362. package/tests/users.test.ts +0 -86
  363. package/tsconfig.json +0 -49
  364. package/typedoc.json +0 -47
@@ -3,6 +3,7 @@ import { aliasCommand, unaliasCommand } from "./alias";
3
3
  import { aptCacheCommand, aptCommand } from "./apt";
4
4
  import { awkCommand } from "./awk";
5
5
  import { base64Command } from "./base64";
6
+ import { basenameCommand, dirnameCommand } from "./basename";
6
7
  import { catCommand } from "./cat";
7
8
  import { cdCommand } from "./cd";
8
9
  import { chmodCommand } from "./chmod";
@@ -21,8 +22,10 @@ import { echoCommand } from "./echo";
21
22
  import { envCommand } from "./env";
22
23
  import { exitCommand } from "./exit";
23
24
  import { exportCommand } from "./export";
25
+ import { fileCommand } from "./file";
24
26
  import { findCommand } from "./find";
25
27
  import { freeCommand } from "./free";
28
+ import { cmatrixCommand, cowsayCommand, cowthinkCommand, fortuneCommand, slCommand, yesCommand } from "./fun";
26
29
  import { grepCommand } from "./grep";
27
30
  import { groupsCommand } from "./groups";
28
31
  import { gunzipCommand, gzipCommand } from "./gzip";
@@ -32,10 +35,10 @@ import { historyCommand } from "./history";
32
35
  import { hostnameCommand } from "./hostname";
33
36
  import { htopCommand } from "./htop";
34
37
  import { idCommand } from "./id";
38
+ import { ifconfigCommand } from "./ifconfig";
35
39
  import { killCommand } from "./kill";
40
+ import { dmesgCommand, lastCommand } from "./last";
36
41
  import { lnCommand, readlinkCommand } from "./ln";
37
- import { seqCommand } from "./seq";
38
- import { statCommand } from "./stat";
39
42
  import { lsCommand } from "./ls";
40
43
  import { lsbReleaseCommand } from "./lsb-release";
41
44
  import { manCommand } from "./man";
@@ -54,12 +57,14 @@ import { python3Command } from "./python";
54
57
  import { readCommand } from "./read";
55
58
  import { rmCommand } from "./rm";
56
59
  import { sedCommand } from "./sed";
60
+ import { seqCommand } from "./seq";
57
61
  import { setCommand } from "./set";
58
62
  import { shCommand } from "./sh";
59
63
  import { returnCommand, shiftCommand, trapCommand } from "./shift";
60
64
  import { sleepCommand } from "./sleep";
61
65
  import { sortCommand } from "./sort";
62
66
  import { sourceCommand } from "./source";
67
+ import { statCommand } from "./stat";
63
68
  import { suCommand } from "./su";
64
69
  import { sudoCommand } from "./sudo";
65
70
  import { tailCommand } from "./tail";
@@ -67,6 +72,7 @@ import { tarCommand } from "./tar";
67
72
  import { teeCommand } from "./tee";
68
73
  import { testCommand } from "./test";
69
74
  import { touchCommand } from "./touch";
75
+ import { sttyCommand, tputCommand } from "./tput";
70
76
  import { trCommand } from "./tr";
71
77
  import { treeCommand } from "./tree";
72
78
  import { falseCommand, trueCommand } from "./true";
@@ -81,6 +87,7 @@ import { whichCommand } from "./which";
81
87
  import { whoCommand } from "./who";
82
88
  import { whoamiCommand } from "./whoami";
83
89
  import { xargsCommand } from "./xargs";
90
+ import { wCommand } from "./w";
84
91
  const BASE_COMMANDS = [
85
92
  // Navigation
86
93
  pwdCommand,
@@ -144,6 +151,21 @@ const BASE_COMMANDS = [
144
151
  exitCommand,
145
152
  // Editors
146
153
  nanoCommand,
154
+ wCommand,
155
+ basenameCommand,
156
+ dirnameCommand,
157
+ fileCommand,
158
+ tputCommand,
159
+ sttyCommand,
160
+ lastCommand,
161
+ dmesgCommand,
162
+ ifconfigCommand,
163
+ yesCommand,
164
+ fortuneCommand,
165
+ cowsayCommand,
166
+ cowthinkCommand,
167
+ cmatrixCommand,
168
+ slCommand,
147
169
  htopCommand,
148
170
  // Network
149
171
  curlCommand,
@@ -1,7 +1,7 @@
1
1
  /** biome-ignore-all lint/style/useNamingConvention: ENV VARIABLES */
2
2
  import { executeStatements } from "../SSHMimic/executor";
3
3
  import { parseScript } from "../VirtualShell/shellParser";
4
- import { expandAsync, expandBraces } from "../utils/expand";
4
+ import { expandAsync, expandBraces, expandGlob } from "../utils/expand";
5
5
  import { tokenizeCommand } from "../utils/tokenize";
6
6
  import { resolveModule } from "./registry";
7
7
  /** Returns the home directory path for a given user. Root lives at /root. */
@@ -61,7 +61,25 @@ function resolveVfsBinary(name, env, shell, authUser) {
61
61
  }
62
62
  return null;
63
63
  }
64
+ const MAX_CALL_DEPTH = 8;
65
+ let _callDepth = 0;
64
66
  export async function runCommandDirect(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
67
+ // Anti-loop guard: track call depth via env to avoid infinite recursion
68
+ _callDepth++;
69
+ // console.debug(`[depth=${_callDepth}] runCommandDirect: ${name}`);
70
+ if (_callDepth > MAX_CALL_DEPTH) {
71
+ _callDepth--;
72
+ // console.debug(`[LOOP DETECTED] runCommandDirect blocked: ${name}`);
73
+ return { stderr: `${name}: maximum call depth (${MAX_CALL_DEPTH}) exceeded`, exitCode: 126 };
74
+ }
75
+ try {
76
+ return await _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env);
77
+ }
78
+ finally {
79
+ _callDepth--;
80
+ }
81
+ }
82
+ async function _runCommandDirectInner(name, args, authUser, hostname, mode, cwd, shell, stdin, env) {
65
83
  const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
66
84
  const invocation = [name, ...args];
67
85
  let assignCount = 0;
@@ -118,6 +136,8 @@ export async function runCommandDirect(name, args, authUser, hostname, mode, cwd
118
136
  env,
119
137
  });
120
138
  }
139
+ // builtin not found — stop here, don't fall through to sh -c (avoids infinite loop)
140
+ return { stderr: `${name}: exec builtin '${builtinMatch[1]}' not found`, exitCode: 127 };
121
141
  }
122
142
  const shMod = resolveModule("sh");
123
143
  if (shMod) {
@@ -163,84 +183,129 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
163
183
  if (trimmed.length === 0)
164
184
  return { exitCode: 0 };
165
185
  const shellEnv = env ?? makeDefaultEnv(authUser, hostname);
166
- const rawTokens = tokenizeCommand(trimmed);
167
- const rawFirstWord = rawTokens[0]?.toLowerCase() ?? "";
168
- const aliasVal = shellEnv.vars[`__alias_${rawFirstWord}`];
169
- const aliasExpanded = aliasVal
170
- ? trimmed.replace(rawFirstWord, aliasVal)
171
- : trimmed;
172
- // Detect sh-syntax constructs that must be handled by the sh interpreter
173
- const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) ||
174
- /\bwhile\s+/.test(aliasExpanded) ||
175
- /\bif\s+/.test(aliasExpanded) ||
176
- /\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) ||
177
- /\bfunction\s+\w+/.test(aliasExpanded) ||
178
- /\(\(\s*.+\s*\)\)/.test(aliasExpanded);
179
- const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) ||
180
- aliasExpanded.includes(">") ||
181
- aliasExpanded.includes("<") ||
182
- aliasExpanded.includes("&&") ||
183
- aliasExpanded.includes("||") ||
184
- aliasExpanded.includes(";");
185
- if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
186
- // sh-syntax: route through sh interpreter to handle for/while/functions
187
- if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
188
- const shMod = resolveModule("sh");
189
- if (shMod) {
190
- return await shMod.run({
191
- authUser, hostname,
192
- activeSessions: shell.users.listActiveSessions(),
193
- rawInput: aliasExpanded,
194
- mode,
195
- args: ["-c", aliasExpanded],
196
- stdin: undefined,
197
- cwd,
198
- shell,
199
- env: shellEnv,
200
- });
186
+ // Anti-loop guard: check depth here too — catches sh -c recursive calls
187
+ _callDepth++;
188
+ // console.debug(`[depth=${_callDepth}] runCommand: ${trimmed.slice(0, 60)}`);
189
+ if (_callDepth > MAX_CALL_DEPTH) {
190
+ _callDepth--;
191
+ // console.debug(`[LOOP DETECTED] runCommand blocked: ${trimmed.slice(0, 60)}`);
192
+ return { stderr: `${trimmed.split(" ")[0]}: maximum call depth (${MAX_CALL_DEPTH}) exceeded`, exitCode: 126 };
193
+ }
194
+ try {
195
+ // History expansion: !! and !n
196
+ if (trimmed === '!!' || /^!-?\d+$/.test(trimmed) || trimmed.startsWith('!! ')) {
197
+ const histPath = `${shellEnv.vars.HOME ?? `/home/${authUser}`}/.bash_history`;
198
+ if (shell.vfs.exists(histPath)) {
199
+ const lines = shell.vfs.readFile(histPath).split('\n').filter(Boolean);
200
+ let cmd;
201
+ if (trimmed === '!!' || trimmed.startsWith('!! ')) {
202
+ cmd = lines[lines.length - 1];
203
+ }
204
+ else {
205
+ const n = parseInt(trimmed.slice(1), 10);
206
+ cmd = n > 0 ? lines[n - 1] : lines[lines.length + n];
207
+ }
208
+ if (cmd) {
209
+ const suffix = trimmed.startsWith('!! ') ? trimmed.slice(3) : '';
210
+ return runCommand(`${cmd}${suffix ? ` ${suffix}` : ''}`, authUser, hostname, mode, cwd, shell, stdin, shellEnv);
211
+ }
201
212
  }
213
+ return { stderr: `${trimmed}: event not found`, exitCode: 1 };
202
214
  }
203
- const script = parseScript(aliasExpanded);
204
- if (!script.isValid)
205
- return { stderr: script.error || "Syntax error", exitCode: 1 };
206
- try {
207
- return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
215
+ const rawTokens = tokenizeCommand(trimmed);
216
+ const rawFirstWord = rawTokens[0]?.toLowerCase() ?? "";
217
+ const aliasVal = shellEnv.vars[`__alias_${rawFirstWord}`];
218
+ const aliasExpanded = aliasVal
219
+ ? trimmed.replace(rawFirstWord, aliasVal)
220
+ : trimmed;
221
+ // Detect sh-syntax constructs that must be handled by the sh interpreter
222
+ const isShScript = /\bfor\s+\w+\s+in\b/.test(aliasExpanded) ||
223
+ /\bwhile\s+/.test(aliasExpanded) ||
224
+ /\bif\s+/.test(aliasExpanded) ||
225
+ /\w+\s*\(\s*\)\s*\{/.test(aliasExpanded) ||
226
+ /\bfunction\s+\w+/.test(aliasExpanded) ||
227
+ /\(\(\s*.+\s*\)\)/.test(aliasExpanded);
228
+ const hasOperators = /(?<![|&])[|](?![|])/.test(aliasExpanded) ||
229
+ aliasExpanded.includes(">") ||
230
+ aliasExpanded.includes("<") ||
231
+ aliasExpanded.includes("&&") ||
232
+ aliasExpanded.includes("||") ||
233
+ aliasExpanded.includes(";");
234
+ if ((isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") || hasOperators) {
235
+ // sh-syntax: route through sh interpreter to handle for/while/functions
236
+ if (isShScript && rawFirstWord !== "sh" && rawFirstWord !== "bash") {
237
+ const shMod = resolveModule("sh");
238
+ if (shMod) {
239
+ return await shMod.run({
240
+ authUser, hostname,
241
+ activeSessions: shell.users.listActiveSessions(),
242
+ rawInput: aliasExpanded,
243
+ mode,
244
+ args: ["-c", aliasExpanded],
245
+ stdin: undefined,
246
+ cwd,
247
+ shell,
248
+ env: shellEnv,
249
+ });
250
+ }
251
+ }
252
+ const script = parseScript(aliasExpanded);
253
+ if (!script.isValid)
254
+ return { stderr: script.error || "Syntax error", exitCode: 1 };
255
+ try {
256
+ return await executeStatements(script.statements, authUser, hostname, mode, cwd, shell, shellEnv);
257
+ }
258
+ catch (error) {
259
+ return {
260
+ stderr: error instanceof Error ? error.message : "Execution failed",
261
+ exitCode: 1,
262
+ };
263
+ }
208
264
  }
209
- catch (error) {
210
- return {
211
- stderr: error instanceof Error ? error.message : "Execution failed",
212
- exitCode: 1,
213
- };
265
+ const expanded = await expandAsync(aliasExpanded, shellEnv.vars, shellEnv.lastExitCode, (sub) => runCommand(sub, authUser, hostname, mode, cwd, shell, undefined, shellEnv).then((r) => r.stdout ?? ""));
266
+ const parts = tokenizeCommand(expanded.trim());
267
+ if (parts.length === 0)
268
+ return { exitCode: 0 };
269
+ const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
270
+ if (assignRe.test(parts[0])) {
271
+ return runCommandDirect(parts[0], parts.slice(1), authUser, hostname, mode, cwd, shell, stdin, shellEnv);
214
272
  }
215
- }
216
- const expanded = await expandAsync(aliasExpanded, shellEnv.vars, shellEnv.lastExitCode, (sub) => runCommand(sub, authUser, hostname, mode, cwd, shell, undefined, shellEnv).then((r) => r.stdout ?? ""));
217
- const parts = tokenizeCommand(expanded.trim());
218
- if (parts.length === 0)
219
- return { exitCode: 0 };
220
- const assignRe = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
221
- if (assignRe.test(parts[0])) {
222
- return runCommandDirect(parts[0], parts.slice(1), authUser, hostname, mode, cwd, shell, stdin, shellEnv);
223
- }
224
- const commandName = parts[0]?.toLowerCase() ?? "";
225
- // Apply brace expansion to each arg token
226
- const args = parts.slice(1).flatMap(expandBraces);
227
- const mod = resolveModule(commandName);
228
- if (!mod) {
229
- const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
230
- if (vfsBinary) {
231
- const stubContent = shell.vfs.readFile(vfsBinary);
232
- const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
233
- if (builtinMatch) {
234
- const builtinName = builtinMatch[1];
235
- const builtinMod = resolveModule(builtinName);
236
- if (builtinMod) {
237
- return await builtinMod.run({
273
+ const commandName = parts[0]?.toLowerCase() ?? "";
274
+ // Apply brace expansion to each arg token
275
+ const args = parts.slice(1).flatMap(expandBraces).flatMap(token => expandGlob(token, cwd, shell.vfs));
276
+ const mod = resolveModule(commandName);
277
+ if (!mod) {
278
+ const vfsBinary = resolveVfsBinary(commandName, shellEnv, shell, authUser);
279
+ if (vfsBinary) {
280
+ const stubContent = shell.vfs.readFile(vfsBinary);
281
+ const builtinMatch = stubContent.match(/exec\s+builtin\s+(\S+)/);
282
+ if (builtinMatch) {
283
+ const builtinName = builtinMatch[1];
284
+ const builtinMod = resolveModule(builtinName);
285
+ if (builtinMod) {
286
+ return await builtinMod.run({
287
+ authUser,
288
+ hostname,
289
+ activeSessions: shell.users.listActiveSessions(),
290
+ rawInput: [commandName, ...args].join(" "),
291
+ mode,
292
+ args,
293
+ stdin,
294
+ cwd,
295
+ shell,
296
+ env: shellEnv,
297
+ });
298
+ }
299
+ }
300
+ const shMod = resolveModule("sh");
301
+ if (shMod) {
302
+ return await shMod.run({
238
303
  authUser,
239
304
  hostname,
240
305
  activeSessions: shell.users.listActiveSessions(),
241
- rawInput: [commandName, ...args].join(" "),
306
+ rawInput: `sh -c ${JSON.stringify(stubContent)}`,
242
307
  mode,
243
- args,
308
+ args: ["-c", stubContent, "--", ...args],
244
309
  stdin,
245
310
  cwd,
246
311
  shell,
@@ -248,42 +313,30 @@ export async function runCommand(rawInput, authUser, hostname, mode, cwd, shell,
248
313
  });
249
314
  }
250
315
  }
251
- const shMod = resolveModule("sh");
252
- if (shMod) {
253
- return await shMod.run({
254
- authUser,
255
- hostname,
256
- activeSessions: shell.users.listActiveSessions(),
257
- rawInput: `sh -c ${JSON.stringify(stubContent)}`,
258
- mode,
259
- args: ["-c", stubContent, "--", ...args],
260
- stdin,
261
- cwd,
262
- shell,
263
- env: shellEnv,
264
- });
265
- }
316
+ return { stderr: `${commandName}: command not found`, exitCode: 127 };
317
+ }
318
+ try {
319
+ return await mod.run({
320
+ authUser,
321
+ hostname,
322
+ activeSessions: shell.users.listActiveSessions(),
323
+ rawInput: expanded,
324
+ mode,
325
+ args,
326
+ stdin,
327
+ cwd,
328
+ shell,
329
+ env: shellEnv,
330
+ });
331
+ }
332
+ catch (error) {
333
+ return {
334
+ stderr: error instanceof Error ? error.message : "Command failed",
335
+ exitCode: 1,
336
+ };
266
337
  }
267
- return { stderr: `${commandName}: command not found`, exitCode: 127 };
268
- }
269
- try {
270
- return await mod.run({
271
- authUser,
272
- hostname,
273
- activeSessions: shell.users.listActiveSessions(),
274
- rawInput: expanded,
275
- mode,
276
- args,
277
- stdin,
278
- cwd,
279
- shell,
280
- env: shellEnv,
281
- });
282
338
  }
283
- catch (error) {
284
- return {
285
- stderr: error instanceof Error ? error.message : "Command failed",
286
- exitCode: 1,
287
- };
339
+ finally {
340
+ _callDepth--;
288
341
  }
289
342
  }
@@ -369,6 +369,11 @@ function splitShScript(script) {
369
369
  }
370
370
  continue;
371
371
  }
372
+ // Backslash-newline continuation: join lines
373
+ if (!inSingleQ && ch === '\\' && i + 1 < script.length && script[i + 1] === '\n') {
374
+ i += 2; // skip \ and \n
375
+ continue;
376
+ }
372
377
  if (depth === 0 && (ch === ";" || ch === "\n")) {
373
378
  const t = current.trim();
374
379
  if (t && !t.startsWith("#"))
@@ -0,0 +1,13 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Terminal control.
4
+ * @category shell
5
+ * @params ["<cap> [args...]"]
6
+ */
7
+ export declare const tputCommand: ShellModule;
8
+ /**
9
+ * Print or set terminal line settings.
10
+ * @category shell
11
+ * @params ["[args...]"]
12
+ */
13
+ export declare const sttyCommand: ShellModule;
@@ -0,0 +1,76 @@
1
+ const CAPS = {
2
+ cols: 220, lines: 50, colors: 256,
3
+ bold: "\x1b[1m", dim: "\x1b[2m", smul: "\x1b[4m", rmul: "\x1b[24m",
4
+ rev: "\x1b[7m", smso: "\x1b[7m", rmso: "\x1b[27m",
5
+ sgr0: "\x1b[0m", el: "\x1b[K", ed: "\x1b[J",
6
+ clear: "\x1b[2J\x1b[H", cup: "", setaf: "", setab: "",
7
+ };
8
+ const ANSI_COLORS = [
9
+ "30", "31", "32", "33", "34", "35", "36", "37",
10
+ "90", "91", "92", "93", "94", "95", "96", "97",
11
+ ];
12
+ /**
13
+ * Terminal control.
14
+ * @category shell
15
+ * @params ["<cap> [args...]"]
16
+ */
17
+ export const tputCommand = {
18
+ name: "tput",
19
+ description: "Query terminfo database",
20
+ category: "shell",
21
+ params: ["<cap> [args...]"],
22
+ run: ({ args }) => {
23
+ const cap = args[0];
24
+ if (!cap)
25
+ return { stderr: "tput: missing capability", exitCode: 1 };
26
+ if (cap === "setaf" && args[1] !== undefined) {
27
+ const n = parseInt(args[1], 10);
28
+ const code = ANSI_COLORS[n] ?? "39";
29
+ return { stdout: `\x1b[${code}m`, exitCode: 0 };
30
+ }
31
+ if (cap === "setab" && args[1] !== undefined) {
32
+ const n = parseInt(args[1], 10);
33
+ const code = ANSI_COLORS[n]?.replace(/3/, "4").replace(/9/, "10") ?? "49";
34
+ return { stdout: `\x1b[${code}m`, exitCode: 0 };
35
+ }
36
+ if (cap === "cup" && args[1] !== undefined && args[2] !== undefined) {
37
+ return { stdout: `\x1b[${parseInt(args[1], 10) + 1};${parseInt(args[2], 10) + 1}H`, exitCode: 0 };
38
+ }
39
+ const val = CAPS[cap];
40
+ if (val === undefined)
41
+ return { stderr: `tput: unknown terminal capability '${cap}'`, exitCode: 1 };
42
+ return { stdout: String(val), exitCode: 0 };
43
+ },
44
+ };
45
+ /**
46
+ * Print or set terminal line settings.
47
+ * @category shell
48
+ * @params ["[args...]"]
49
+ */
50
+ export const sttyCommand = {
51
+ name: "stty",
52
+ description: "Change and print terminal line settings",
53
+ category: "shell",
54
+ params: ["[args...]"],
55
+ run: ({ args }) => {
56
+ if (args.includes("-a") || args.includes("--all")) {
57
+ return {
58
+ stdout: [
59
+ "speed 38400 baud; rows 50; columns 220; line = 0;",
60
+ "intr = ^C; quit = ^\\; erase = ^?; kill = ^U; eof = ^D;",
61
+ "eol = M-^?; eol2 = M-^?; swtch = <undef>; start = ^Q; stop = ^S;",
62
+ "-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts",
63
+ "brkint -icrnl ixon -ixoff -iuclc ixany imaxbel -iutf8",
64
+ "opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0",
65
+ "isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke",
66
+ ].join("\n"),
67
+ exitCode: 0,
68
+ };
69
+ }
70
+ if (args.includes("size")) {
71
+ return { stdout: "50 220", exitCode: 0 };
72
+ }
73
+ // silently accept set operations
74
+ return { exitCode: 0 };
75
+ },
76
+ };
@@ -0,0 +1,7 @@
1
+ import type { ShellModule } from "../types/commands";
2
+ /**
3
+ * Show who is logged on and what they are doing.
4
+ * @category system
5
+ * @params ["[user]"]
6
+ */
7
+ export declare const wCommand: ShellModule;
@@ -0,0 +1,38 @@
1
+ import { userHome } from "./runtime";
2
+ /**
3
+ * Show who is logged on and what they are doing.
4
+ * @category system
5
+ * @params ["[user]"]
6
+ */
7
+ export const wCommand = {
8
+ name: "w",
9
+ description: "Show who is logged on and what they are doing",
10
+ category: "system",
11
+ params: ["[user]"],
12
+ run: ({ shell, authUser }) => {
13
+ const now = new Date();
14
+ const upSecs = Math.floor(performance.now() / 1000);
15
+ const upMins = Math.floor(upSecs / 60);
16
+ const upHours = Math.floor(upMins / 60);
17
+ const uptimeStr = upHours > 0
18
+ ? `${upHours}:${String(upMins % 60).padStart(2, "0")}`
19
+ : `${upMins} min`;
20
+ const timeStr = now.toTimeString().slice(0, 5);
21
+ // Read active sessions
22
+ shell.users.listActiveSessions?.();
23
+ const logPath = `${userHome(authUser)}/.lastlog`;
24
+ let loginTime = timeStr;
25
+ if (shell.vfs.exists(logPath)) {
26
+ try {
27
+ const log = JSON.parse(shell.vfs.readFile(logPath));
28
+ loginTime = new Date(log.at).toTimeString().slice(0, 5);
29
+ }
30
+ catch { }
31
+ }
32
+ const header = ` ${timeStr} up ${uptimeStr}, 1 user, load average: 0.${Math.floor(Math.random() * 30).toString().padStart(2, "0")}, 0.${Math.floor(Math.random() * 15).toString().padStart(2, "0")}, 0.${Math.floor(Math.random() * 10).toString().padStart(2, "0")}`;
33
+ const colHeader = "USER TTY FROM LOGIN@ IDLE JCPU PCPU WHAT";
34
+ const idle = "0.00s";
35
+ const row = `${authUser.padEnd(8)} pts/0 browser ${loginTime} ${idle} 0.01s 0.00s -bash`;
36
+ return { stdout: [header, colHeader, row].join("\n"), exitCode: 0 };
37
+ },
38
+ };
@@ -56,3 +56,15 @@ export declare function expandSync(input: string, env: Record<string, string>, l
56
56
  * @param runCmd Async callback to execute a command and return its stdout.
57
57
  */
58
58
  export declare function expandAsync(input: string, env: Record<string, string>, lastExit: number, runCmd: (cmd: string) => Promise<string>): Promise<string>;
59
+ /**
60
+ * Expand a glob pattern against a VirtualShell VFS.
61
+ * Supports * (any chars in segment) and ** (any path).
62
+ * Returns the original pattern if no matches found (bash behavior).
63
+ */
64
+ export declare function expandGlob(pattern: string, cwd: string, vfs: {
65
+ list: (p: string) => string[];
66
+ exists: (p: string) => boolean;
67
+ stat: (p: string) => {
68
+ type: string;
69
+ };
70
+ }): string[];
@@ -474,3 +474,87 @@ export async function expandAsync(input, env, lastExit, runCmd) {
474
474
  env[depthKey] = String(currentDepth);
475
475
  }
476
476
  }
477
+ // ─── Glob expansion ──────────────────────────────────────────────────────────
478
+ /**
479
+ * Expand a glob pattern against a VirtualShell VFS.
480
+ * Supports * (any chars in segment) and ** (any path).
481
+ * Returns the original pattern if no matches found (bash behavior).
482
+ */
483
+ export function expandGlob(pattern, cwd, vfs) {
484
+ // No glob chars → return as-is
485
+ if (!pattern.includes('*') && !pattern.includes('?'))
486
+ return [pattern];
487
+ const isAbsolute = pattern.startsWith('/');
488
+ const base = isAbsolute ? '/' : cwd;
489
+ const relPattern = isAbsolute ? pattern.slice(1) : pattern;
490
+ const results = matchGlob(base, relPattern.split('/'), vfs);
491
+ if (results.length === 0)
492
+ return [pattern]; // no match → literal
493
+ return results.sort();
494
+ }
495
+ function matchGlob(dir, segments, vfs) {
496
+ if (segments.length === 0)
497
+ return [dir];
498
+ const [seg, ...rest] = segments;
499
+ if (!seg)
500
+ return [dir];
501
+ // ** matches zero or more path segments
502
+ if (seg === '**') {
503
+ const all = walkAll(dir, vfs);
504
+ return rest.length === 0 ? all : all.flatMap(d => {
505
+ try {
506
+ if (vfs.stat(d).type === 'directory')
507
+ return matchGlob(d, rest, vfs);
508
+ }
509
+ catch { }
510
+ return [];
511
+ });
512
+ }
513
+ let entries = [];
514
+ try {
515
+ entries = vfs.list(dir);
516
+ }
517
+ catch {
518
+ return [];
519
+ }
520
+ const re = globToRegex(seg);
521
+ return entries
522
+ .filter(e => !e.startsWith('.') || seg.startsWith('.'))
523
+ .filter(e => re.test(e))
524
+ .flatMap(e => {
525
+ const full = dir === '/' ? `/${e}` : `${dir}/${e}`;
526
+ if (rest.length === 0)
527
+ return [full];
528
+ try {
529
+ if (vfs.stat(full).type === 'directory')
530
+ return matchGlob(full, rest, vfs);
531
+ }
532
+ catch { }
533
+ return [];
534
+ });
535
+ }
536
+ function walkAll(dir, vfs) {
537
+ const results = [dir];
538
+ let entries = [];
539
+ try {
540
+ entries = vfs.list(dir);
541
+ }
542
+ catch {
543
+ return results;
544
+ }
545
+ for (const e of entries) {
546
+ const full = dir === '/' ? `/${e}` : `${dir}/${e}`;
547
+ try {
548
+ if (vfs.stat(full).type === 'directory')
549
+ results.push(...walkAll(full, vfs));
550
+ }
551
+ catch { }
552
+ }
553
+ return results;
554
+ }
555
+ function globToRegex(pattern) {
556
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&')
557
+ .replace(/\*/g, '.*')
558
+ .replace(/\?/g, '.');
559
+ return new RegExp(`^${escaped}$`);
560
+ }