typescript-virtual-container 1.1.0 → 1.1.1-c

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 (229) hide show
  1. package/.vscode/settings.json +18 -0
  2. package/README.md +45 -5
  3. package/dist/SSHClient/index.d.ts +138 -0
  4. package/dist/SSHClient/index.d.ts.map +1 -0
  5. package/dist/SSHClient/index.js +216 -0
  6. package/dist/SSHMimic/exec.d.ts +4 -0
  7. package/dist/SSHMimic/exec.d.ts.map +1 -0
  8. package/dist/SSHMimic/exec.js +21 -0
  9. package/dist/SSHMimic/executor.d.ts +9 -0
  10. package/dist/SSHMimic/executor.d.ts.map +1 -0
  11. package/dist/SSHMimic/executor.js +131 -0
  12. package/dist/SSHMimic/hostKey.d.ts +2 -0
  13. package/dist/SSHMimic/hostKey.d.ts.map +1 -0
  14. package/dist/SSHMimic/hostKey.js +17 -0
  15. package/dist/SSHMimic/index.d.ts +39 -0
  16. package/dist/SSHMimic/index.d.ts.map +1 -0
  17. package/dist/SSHMimic/index.js +113 -0
  18. package/dist/SSHMimic/loginFormat.d.ts +2 -0
  19. package/dist/SSHMimic/loginFormat.d.ts.map +1 -0
  20. package/dist/SSHMimic/loginFormat.js +10 -0
  21. package/dist/SSHMimic/prompt.d.ts +2 -0
  22. package/dist/SSHMimic/prompt.d.ts.map +1 -0
  23. package/dist/SSHMimic/prompt.js +9 -0
  24. package/dist/VirtualFileSystem/archive.d.ts +5 -0
  25. package/dist/VirtualFileSystem/archive.d.ts.map +1 -0
  26. package/dist/VirtualFileSystem/archive.js +56 -0
  27. package/dist/VirtualFileSystem/index.d.ts +131 -0
  28. package/dist/VirtualFileSystem/index.d.ts.map +1 -0
  29. package/dist/VirtualFileSystem/index.js +355 -0
  30. package/dist/VirtualFileSystem/internalTypes.d.ts +18 -0
  31. package/dist/VirtualFileSystem/internalTypes.d.ts.map +1 -0
  32. package/dist/VirtualFileSystem/internalTypes.js +0 -0
  33. package/dist/VirtualFileSystem/path.d.ts +9 -0
  34. package/dist/VirtualFileSystem/path.d.ts.map +1 -0
  35. package/dist/VirtualFileSystem/path.js +49 -0
  36. package/dist/VirtualFileSystem/snapshot.d.ts +5 -0
  37. package/dist/VirtualFileSystem/snapshot.d.ts.map +1 -0
  38. package/dist/VirtualFileSystem/snapshot.js +59 -0
  39. package/dist/VirtualFileSystem/tree.d.ts +3 -0
  40. package/dist/VirtualFileSystem/tree.d.ts.map +1 -0
  41. package/dist/VirtualFileSystem/tree.js +19 -0
  42. package/dist/VirtualShell/index.d.ts +86 -0
  43. package/dist/VirtualShell/index.d.ts.map +1 -0
  44. package/dist/VirtualShell/index.js +129 -0
  45. package/dist/VirtualShell/shell.d.ts +5 -0
  46. package/dist/VirtualShell/shell.d.ts.map +1 -0
  47. package/dist/VirtualShell/shell.js +473 -0
  48. package/dist/VirtualShell/shellParser.d.ts +4 -0
  49. package/dist/VirtualShell/shellParser.d.ts.map +1 -0
  50. package/dist/VirtualShell/shellParser.js +207 -0
  51. package/dist/VirtualUserManager/index.d.ts +168 -0
  52. package/dist/VirtualUserManager/index.d.ts.map +1 -0
  53. package/dist/VirtualUserManager/index.js +375 -0
  54. package/dist/commands/adduser.d.ts +3 -0
  55. package/dist/commands/adduser.d.ts.map +1 -0
  56. package/dist/commands/adduser.js +18 -0
  57. package/dist/commands/cat.d.ts +3 -0
  58. package/dist/commands/cat.d.ts.map +1 -0
  59. package/dist/commands/cat.js +15 -0
  60. package/dist/commands/cd.d.ts +3 -0
  61. package/dist/commands/cd.d.ts.map +1 -0
  62. package/dist/commands/cd.js +17 -0
  63. package/dist/commands/clear.d.ts +3 -0
  64. package/dist/commands/clear.d.ts.map +1 -0
  65. package/dist/commands/clear.js +5 -0
  66. package/dist/commands/command-helpers.d.ts +23 -0
  67. package/dist/commands/command-helpers.d.ts.map +1 -0
  68. package/dist/commands/command-helpers.js +139 -0
  69. package/dist/commands/curl.d.ts +3 -0
  70. package/dist/commands/curl.d.ts.map +1 -0
  71. package/dist/commands/curl.js +44 -0
  72. package/dist/commands/deluser.d.ts +3 -0
  73. package/dist/commands/deluser.d.ts.map +1 -0
  74. package/dist/commands/deluser.js +15 -0
  75. package/dist/commands/echo.d.ts +3 -0
  76. package/dist/commands/echo.d.ts.map +1 -0
  77. package/dist/commands/echo.js +22 -0
  78. package/dist/commands/env.d.ts +3 -0
  79. package/dist/commands/env.d.ts.map +1 -0
  80. package/dist/commands/env.js +18 -0
  81. package/dist/commands/exit.d.ts +3 -0
  82. package/dist/commands/exit.d.ts.map +1 -0
  83. package/dist/commands/exit.js +5 -0
  84. package/dist/commands/export.d.ts +3 -0
  85. package/dist/commands/export.d.ts.map +1 -0
  86. package/dist/commands/export.js +34 -0
  87. package/dist/commands/grep.d.ts +3 -0
  88. package/dist/commands/grep.d.ts.map +1 -0
  89. package/dist/commands/grep.js +69 -0
  90. package/dist/commands/help.d.ts +3 -0
  91. package/dist/commands/help.d.ts.map +1 -0
  92. package/dist/commands/help.js +7 -0
  93. package/dist/commands/helpers.d.ts +26 -0
  94. package/dist/commands/helpers.d.ts.map +1 -0
  95. package/dist/commands/helpers.js +160 -0
  96. package/dist/commands/hostname.d.ts +3 -0
  97. package/dist/commands/hostname.d.ts.map +1 -0
  98. package/dist/commands/hostname.js +5 -0
  99. package/dist/commands/htop.d.ts +3 -0
  100. package/dist/commands/htop.d.ts.map +1 -0
  101. package/dist/commands/htop.js +10 -0
  102. package/dist/commands/index.d.ts +8 -0
  103. package/dist/commands/index.d.ts.map +1 -0
  104. package/dist/commands/index.js +212 -0
  105. package/dist/commands/ls.d.ts +3 -0
  106. package/dist/commands/ls.d.ts.map +1 -0
  107. package/dist/commands/ls.js +47 -0
  108. package/dist/commands/mkdir.d.ts +3 -0
  109. package/dist/commands/mkdir.d.ts.map +1 -0
  110. package/dist/commands/mkdir.js +21 -0
  111. package/dist/commands/nano.d.ts +3 -0
  112. package/dist/commands/nano.d.ts.map +1 -0
  113. package/dist/commands/nano.js +27 -0
  114. package/dist/commands/neofetch.d.ts +3 -0
  115. package/dist/commands/neofetch.d.ts.map +1 -0
  116. package/dist/commands/neofetch.js +32 -0
  117. package/dist/commands/pwd.d.ts +3 -0
  118. package/dist/commands/pwd.d.ts.map +1 -0
  119. package/dist/commands/pwd.js +5 -0
  120. package/dist/commands/rm.d.ts +3 -0
  121. package/dist/commands/rm.d.ts.map +1 -0
  122. package/dist/commands/rm.js +29 -0
  123. package/dist/commands/set.d.ts +7 -0
  124. package/dist/commands/set.d.ts.map +1 -0
  125. package/dist/commands/set.js +64 -0
  126. package/dist/commands/sh.d.ts +4 -0
  127. package/dist/commands/sh.d.ts.map +1 -0
  128. package/dist/commands/sh.js +45 -0
  129. package/dist/commands/su.d.ts +3 -0
  130. package/dist/commands/su.d.ts.map +1 -0
  131. package/dist/commands/su.js +24 -0
  132. package/dist/commands/sudo.d.ts +3 -0
  133. package/dist/commands/sudo.d.ts.map +1 -0
  134. package/dist/commands/sudo.js +47 -0
  135. package/dist/commands/touch.d.ts +3 -0
  136. package/dist/commands/touch.d.ts.map +1 -0
  137. package/dist/commands/touch.js +18 -0
  138. package/dist/commands/tree.d.ts +3 -0
  139. package/dist/commands/tree.d.ts.map +1 -0
  140. package/dist/commands/tree.js +11 -0
  141. package/dist/commands/unset.d.ts +3 -0
  142. package/dist/commands/unset.d.ts.map +1 -0
  143. package/dist/commands/unset.js +15 -0
  144. package/dist/commands/wget.d.ts +3 -0
  145. package/dist/commands/wget.d.ts.map +1 -0
  146. package/dist/commands/wget.js +113 -0
  147. package/dist/commands/who.d.ts +3 -0
  148. package/dist/commands/who.d.ts.map +1 -0
  149. package/dist/commands/who.js +15 -0
  150. package/dist/commands/whoami.d.ts +3 -0
  151. package/dist/commands/whoami.d.ts.map +1 -0
  152. package/dist/commands/whoami.js +5 -0
  153. package/dist/index.d.ts +11 -0
  154. package/dist/index.d.ts.map +1 -0
  155. package/dist/index.js +7 -0
  156. package/dist/modules/neofetch.d.ts +19 -0
  157. package/dist/modules/neofetch.d.ts.map +1 -0
  158. package/dist/modules/neofetch.js +284 -0
  159. package/dist/modules/shellInteractive.d.ts +6 -0
  160. package/dist/modules/shellInteractive.d.ts.map +1 -0
  161. package/dist/modules/shellInteractive.js +26 -0
  162. package/dist/modules/shellRuntime.d.ts +11 -0
  163. package/dist/modules/shellRuntime.d.ts.map +1 -0
  164. package/dist/modules/shellRuntime.js +52 -0
  165. package/dist/standalone.d.ts +2 -0
  166. package/dist/standalone.d.ts.map +1 -0
  167. package/dist/standalone.js +25 -0
  168. package/dist/types/commands.d.ts +89 -0
  169. package/dist/types/commands.d.ts.map +1 -0
  170. package/dist/types/commands.js +0 -0
  171. package/dist/types/pipeline.d.ts +23 -0
  172. package/dist/types/pipeline.d.ts.map +1 -0
  173. package/dist/types/pipeline.js +0 -0
  174. package/dist/types/streams.d.ts +32 -0
  175. package/dist/types/streams.d.ts.map +1 -0
  176. package/dist/types/streams.js +0 -0
  177. package/dist/types/vfs.d.ts +71 -0
  178. package/dist/types/vfs.d.ts.map +1 -0
  179. package/dist/types/vfs.js +0 -0
  180. package/package.json +4 -2
  181. package/src/{SSHMimic/client.ts → SSHClient/index.ts} +2 -2
  182. package/src/SSHMimic/exec.ts +1 -1
  183. package/src/SSHMimic/executor.ts +8 -8
  184. package/src/VirtualFileSystem/index.ts +26 -0
  185. package/src/VirtualShell/index.ts +17 -1
  186. package/src/VirtualShell/shell.ts +19 -107
  187. package/src/VirtualShell/shellParser.ts +32 -7
  188. package/src/VirtualUserManager/index.ts +149 -0
  189. package/src/{VirtualShell/commands → commands}/adduser.ts +1 -1
  190. package/src/{VirtualShell/commands → commands}/cat.ts +1 -1
  191. package/src/{VirtualShell/commands → commands}/cd.ts +1 -1
  192. package/src/{VirtualShell/commands → commands}/clear.ts +1 -1
  193. package/src/{VirtualShell/commands → commands}/curl.ts +2 -2
  194. package/src/{VirtualShell/commands → commands}/deluser.ts +1 -1
  195. package/src/{VirtualShell/commands → commands}/echo.ts +1 -1
  196. package/src/{VirtualShell/commands → commands}/env.ts +1 -1
  197. package/src/{VirtualShell/commands → commands}/exit.ts +1 -1
  198. package/src/{VirtualShell/commands → commands}/export.ts +1 -1
  199. package/src/{VirtualShell/commands → commands}/grep.ts +1 -1
  200. package/src/{VirtualShell/commands → commands}/help.ts +1 -1
  201. package/src/{VirtualShell/commands → commands}/helpers.ts +1 -1
  202. package/src/{VirtualShell/commands → commands}/hostname.ts +1 -1
  203. package/src/{VirtualShell/commands → commands}/htop.ts +1 -1
  204. package/src/{VirtualShell/commands → commands}/index.ts +7 -4
  205. package/src/{VirtualShell/commands → commands}/ls.ts +1 -1
  206. package/src/{VirtualShell/commands → commands}/mkdir.ts +1 -1
  207. package/src/{VirtualShell/commands → commands}/nano.ts +1 -1
  208. package/src/{VirtualShell/commands → commands}/neofetch.ts +2 -2
  209. package/src/{VirtualShell/commands → commands}/pwd.ts +1 -1
  210. package/src/{VirtualShell/commands → commands}/rm.ts +1 -1
  211. package/src/{VirtualShell/commands → commands}/set.ts +1 -1
  212. package/src/{VirtualShell/commands → commands}/sh.ts +1 -1
  213. package/src/{VirtualShell/commands → commands}/su.ts +1 -1
  214. package/src/{VirtualShell/commands → commands}/sudo.ts +1 -1
  215. package/src/{VirtualShell/commands → commands}/touch.ts +2 -2
  216. package/src/{VirtualShell/commands → commands}/tree.ts +1 -1
  217. package/src/{VirtualShell/commands → commands}/unset.ts +1 -1
  218. package/src/{VirtualShell/commands → commands}/wget.ts +2 -2
  219. package/src/{VirtualShell/commands → commands}/who.ts +2 -2
  220. package/src/{VirtualShell/commands → commands}/whoami.ts +1 -1
  221. package/src/index.ts +2 -2
  222. package/{modules → src/modules}/neofetch.ts +56 -51
  223. package/src/modules/shellInteractive.ts +57 -0
  224. package/src/modules/shellRuntime.ts +76 -0
  225. package/tests/command-helpers.test.ts +1 -1
  226. package/tests/helpers.test.ts +1 -1
  227. package/tests/users.test.ts +60 -0
  228. package/tsconfig.json +19 -8
  229. /package/src/{VirtualShell/commands → commands}/command-helpers.ts +0 -0
@@ -0,0 +1,18 @@
1
+ {
2
+ "files.exclude": {
3
+ "**/.git": true,
4
+ "**/.hg": true,
5
+ "**/.svn": true,
6
+ "**/.DS_Store": true,
7
+ "**/Thumbs.db": true,
8
+ "**/node_modules": true,
9
+
10
+ ".ssh-mimic": true,
11
+ ".vfs": true,
12
+
13
+ "LICENSE": true,
14
+ "*.md": true,
15
+ "biome.json": true,
16
+ "tsconfig.tsbuildinfo": true
17
+ }
18
+ }
package/README.md CHANGED
@@ -83,7 +83,7 @@ bun add typescript-virtual-container
83
83
  ### From source (development)
84
84
 
85
85
  ```bash
86
- git clone <repo-url>
86
+ git clone https://github.com/itsrealfortune/typescript-virtual-container/
87
87
  cd virtual-env-js
88
88
  bun install
89
89
  bun format # Format code per Biome
@@ -745,6 +745,46 @@ Revokes sudo privileges. Cannot remove root.
745
745
  await users.removeSudoer("charlie");
746
746
  ```
747
747
 
748
+ ##### `async setQuotaBytes(username: string, maxBytes: number): Promise<void>`
749
+
750
+ Sets an optional per-user quota (bytes) for writes under `/home/<username>`.
751
+
752
+ ```typescript
753
+ await users.setQuotaBytes("alice", 5 * 1024 * 1024); // 5 MB
754
+ ```
755
+
756
+ ##### `async clearQuota(username: string): Promise<void>`
757
+
758
+ Removes quota limit for a user.
759
+
760
+ ```typescript
761
+ await users.clearQuota("alice");
762
+ ```
763
+
764
+ ##### `getQuotaBytes(username: string): number | null`
765
+
766
+ Returns configured quota in bytes, or `null` if unlimited.
767
+
768
+ ```typescript
769
+ console.log(users.getQuotaBytes("alice"));
770
+ ```
771
+
772
+ ##### `getUsageBytes(username: string): number`
773
+
774
+ Returns current stored usage in bytes under `/home/<username>`.
775
+
776
+ ```typescript
777
+ console.log(users.getUsageBytes("alice"));
778
+ ```
779
+
780
+ ##### `assertWriteWithinQuota(username: string, targetPath: string, nextContent: string | Buffer): void`
781
+
782
+ Validates a write operation against quota rules; throws when projected usage exceeds quota.
783
+
784
+ ```typescript
785
+ users.assertWriteWithinQuota("alice", "/home/alice/data.txt", "payload");
786
+ ```
787
+
748
788
  ##### `registerSession(username: string, remoteAddress: string): VirtualActiveSession`
749
789
 
750
790
  Creates active session (called on SSH auth). Returns session descriptor with UUID, tty, start time.
@@ -1358,9 +1398,9 @@ MIT License. See LICENSE file for details.
1358
1398
 
1359
1399
  ## Roadmap
1360
1400
 
1361
- - [ ] Custom command plugin API
1362
- - [ ] Optional per-user quotas for virtual filesystem usage
1363
- - [ ] Improved shell compatibility for complex piping and redirection
1401
+ - [x] Custom command plugin API
1402
+ - [x] Optional per-user quotas for virtual filesystem usage
1403
+ - [x] Improved shell compatibility for complex piping and redirection
1364
1404
  - [ ] Snapshot diff tooling for test assertions
1365
- - [ ] Structured event hooks (session open/close, file write, sudo challenge)
1405
+ - [x] Structured event hooks (session open/close, file write, sudo challenge)
1366
1406
  - [ ] WebSocket-based remote shell client (experimental)
@@ -0,0 +1,138 @@
1
+ import type { CommandResult } from "../types/commands";
2
+ import type { VirtualShell } from "../VirtualShell";
3
+ /**
4
+ * Programmatic client for executing shell commands against a virtual shell.
5
+ *
6
+ * Maintains working-directory state across invocations and runs commands as a
7
+ * single authenticated user without SSH transport overhead.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const shell = new VirtualShell("typescript-vm");
12
+ * const client = new SshClient(shell, "alice");
13
+ * const result = await client.cd("/tmp");
14
+ * const list = await client.ls();
15
+ * ```
16
+ */
17
+ export declare class SshClient {
18
+ private shell;
19
+ private username;
20
+ private currentCwd;
21
+ /**
22
+ * Creates a programmatic client bound to a virtual shell and user.
23
+ *
24
+ * @param shell Parent virtual shell instance.
25
+ * @param username Login user for all commands.
26
+ */
27
+ constructor(shell: VirtualShell, username: string);
28
+ /**
29
+ * Executes raw shell command.
30
+ *
31
+ * @param command Unparsed command line.
32
+ * @returns Command result with stdout/stderr/exitCode.
33
+ */
34
+ exec(command: string): Promise<CommandResult>;
35
+ /**
36
+ * Lists directory contents.
37
+ *
38
+ * @param path Target directory, defaults to cwd.
39
+ * @returns Result with directory listing in stdout.
40
+ */
41
+ ls(path?: string): Promise<CommandResult>;
42
+ /**
43
+ * Prints current working directory.
44
+ *
45
+ * @returns Result with cwd path in stdout.
46
+ */
47
+ pwd(): Promise<CommandResult>;
48
+ /**
49
+ * Changes working directory.
50
+ *
51
+ * @param path Target directory path.
52
+ * @returns Result; updates internal cwd on success.
53
+ */
54
+ cd(path: string): Promise<CommandResult>;
55
+ /**
56
+ * Reads file content.
57
+ *
58
+ * @param path File path.
59
+ * @returns Result with file content in stdout.
60
+ */
61
+ cat(path: string): Promise<CommandResult>;
62
+ /**
63
+ * Creates directory.
64
+ *
65
+ * @param path Directory path.
66
+ * @param recursive When true, create parents.
67
+ * @returns Result from mkdir command.
68
+ */
69
+ mkdir(path: string, recursive?: boolean): Promise<CommandResult>;
70
+ /**
71
+ * Creates file (empty).
72
+ *
73
+ * @param path File path.
74
+ * @returns Result from touch command.
75
+ */
76
+ touch(path: string): Promise<CommandResult>;
77
+ /**
78
+ * Removes file or directory.
79
+ *
80
+ * @param path Target path.
81
+ * @param recursive When true, delete directory tree.
82
+ * @returns Result from rm command.
83
+ */
84
+ rm(path: string, recursive?: boolean): Promise<CommandResult>;
85
+ /**
86
+ * Writes file content.
87
+ *
88
+ * @param path Target file path.
89
+ * @param content Text to write.
90
+ * @returns Result from touch/write simulation.
91
+ */
92
+ writeFile(path: string, content: string): Promise<CommandResult>;
93
+ /**
94
+ * Reads file content programmatically.
95
+ *
96
+ * @param path Target file path.
97
+ * @returns File content as string or error in result.
98
+ */
99
+ readFile(path: string): Promise<CommandResult>;
100
+ /**
101
+ * Gets current working directory.
102
+ *
103
+ * @returns Normalized cwd path.
104
+ */
105
+ getCwd(): string;
106
+ /**
107
+ * Gets logged-in username.
108
+ *
109
+ * @returns Associated username.
110
+ */
111
+ getUsername(): string;
112
+ /**
113
+ * Renders tree view of directory.
114
+ *
115
+ * @param path Target directory, defaults to cwd.
116
+ * @returns Result with ASCII tree in stdout.
117
+ */
118
+ tree(path?: string): Promise<CommandResult>;
119
+ /**
120
+ * Shows current user.
121
+ *
122
+ * @returns Result from whoami command.
123
+ */
124
+ whoami(): Promise<CommandResult>;
125
+ /**
126
+ * Shows hostname.
127
+ *
128
+ * @returns Result from hostname command.
129
+ */
130
+ hostname(): Promise<CommandResult>;
131
+ /**
132
+ * Lists active users/sessions.
133
+ *
134
+ * @returns Result from who command.
135
+ */
136
+ who(): Promise<CommandResult>;
137
+ }
138
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SSHClient/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;;;;;;;GAaG;AACH,qBAAa,SAAS;IAUpB,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,QAAQ;IAVjB,OAAO,CAAC,UAAU,CAAO;IAEzB;;;;;OAKG;gBAEM,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM;IAGzB;;;;;OAKG;IACG,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IA0BnD;;;;;OAKG;IACG,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAK/C;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;IAInC;;;;;OAKG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAQ9C;;;;;OAKG;IACG,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAI/C;;;;;;OAMG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAKpE;;;;;OAKG;IACG,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIjD;;;;;;OAMG;IACG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjE;;;;;;OAMG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAiBtE;;;;;OAKG;IACG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAiBpD;;;;OAIG;IACH,MAAM,IAAI,MAAM;IAIhB;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAIrB;;;;;OAKG;IACG,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAKjD;;;;OAIG;IACG,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IAItC;;;;OAIG;IACG,QAAQ,IAAI,OAAO,CAAC,aAAa,CAAC;IAIxC;;;;OAIG;IACG,GAAG,IAAI,OAAO,CAAC,aAAa,CAAC;CAGnC"}
@@ -0,0 +1,216 @@
1
+ import { runCommand } from "../commands";
2
+ /**
3
+ * Programmatic client for executing shell commands against a virtual shell.
4
+ *
5
+ * Maintains working-directory state across invocations and runs commands as a
6
+ * single authenticated user without SSH transport overhead.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const shell = new VirtualShell("typescript-vm");
11
+ * const client = new SshClient(shell, "alice");
12
+ * const result = await client.cd("/tmp");
13
+ * const list = await client.ls();
14
+ * ```
15
+ */
16
+ export class SshClient {
17
+ shell;
18
+ username;
19
+ currentCwd = "/";
20
+ /**
21
+ * Creates a programmatic client bound to a virtual shell and user.
22
+ *
23
+ * @param shell Parent virtual shell instance.
24
+ * @param username Login user for all commands.
25
+ */
26
+ constructor(shell, username) {
27
+ this.shell = shell;
28
+ this.username = username;
29
+ }
30
+ /**
31
+ * Executes raw shell command.
32
+ *
33
+ * @param command Unparsed command line.
34
+ * @returns Command result with stdout/stderr/exitCode.
35
+ */
36
+ async exec(command) {
37
+ const vfs = this.shell.getVfs();
38
+ const users = this.shell.getUsers();
39
+ const hostname = this.shell.getHostname();
40
+ if (!vfs || !users) {
41
+ throw new Error("SSH client not started");
42
+ }
43
+ const result = runCommand(command, this.username, hostname, "exec", this.currentCwd, this.shell);
44
+ // Handle async results
45
+ if (result instanceof Promise) {
46
+ return await result;
47
+ }
48
+ return result;
49
+ }
50
+ /**
51
+ * Lists directory contents.
52
+ *
53
+ * @param path Target directory, defaults to cwd.
54
+ * @returns Result with directory listing in stdout.
55
+ */
56
+ async ls(path) {
57
+ const target = path ?? ".";
58
+ return this.exec(`ls ${target}`);
59
+ }
60
+ /**
61
+ * Prints current working directory.
62
+ *
63
+ * @returns Result with cwd path in stdout.
64
+ */
65
+ async pwd() {
66
+ return this.exec("pwd");
67
+ }
68
+ /**
69
+ * Changes working directory.
70
+ *
71
+ * @param path Target directory path.
72
+ * @returns Result; updates internal cwd on success.
73
+ */
74
+ async cd(path) {
75
+ const result = await this.exec(`cd ${path}`);
76
+ if (result.nextCwd && result.exitCode !== 1) {
77
+ this.currentCwd = result.nextCwd;
78
+ }
79
+ return result;
80
+ }
81
+ /**
82
+ * Reads file content.
83
+ *
84
+ * @param path File path.
85
+ * @returns Result with file content in stdout.
86
+ */
87
+ async cat(path) {
88
+ return this.exec(`cat ${path}`);
89
+ }
90
+ /**
91
+ * Creates directory.
92
+ *
93
+ * @param path Directory path.
94
+ * @param recursive When true, create parents.
95
+ * @returns Result from mkdir command.
96
+ */
97
+ async mkdir(path, recursive = false) {
98
+ const flag = recursive ? "-p " : "";
99
+ return this.exec(`mkdir ${flag}${path}`);
100
+ }
101
+ /**
102
+ * Creates file (empty).
103
+ *
104
+ * @param path File path.
105
+ * @returns Result from touch command.
106
+ */
107
+ async touch(path) {
108
+ return this.exec(`touch ${path}`);
109
+ }
110
+ /**
111
+ * Removes file or directory.
112
+ *
113
+ * @param path Target path.
114
+ * @param recursive When true, delete directory tree.
115
+ * @returns Result from rm command.
116
+ */
117
+ async rm(path, recursive = false) {
118
+ const flag = recursive ? "-r " : "";
119
+ return this.exec(`rm ${flag}${path}`);
120
+ }
121
+ /**
122
+ * Writes file content.
123
+ *
124
+ * @param path Target file path.
125
+ * @param content Text to write.
126
+ * @returns Result from touch/write simulation.
127
+ */
128
+ async writeFile(path, content) {
129
+ const vfs = this.shell.getVfs();
130
+ if (!vfs) {
131
+ throw new Error("SSH client not started");
132
+ }
133
+ try {
134
+ this.shell.writeFileAsUser(this.username, path, content);
135
+ return { stdout: `File '${path}' written`, exitCode: 0 };
136
+ }
137
+ catch (error) {
138
+ return {
139
+ stderr: `Failed to write '${path}': ${error instanceof Error ? error.message : String(error)}`,
140
+ exitCode: 1,
141
+ };
142
+ }
143
+ }
144
+ /**
145
+ * Reads file content programmatically.
146
+ *
147
+ * @param path Target file path.
148
+ * @returns File content as string or error in result.
149
+ */
150
+ async readFile(path) {
151
+ const vfs = this.shell.getVfs();
152
+ if (!vfs) {
153
+ throw new Error("SSH client not started");
154
+ }
155
+ try {
156
+ const content = vfs.readFile(path);
157
+ return { stdout: content, exitCode: 0 };
158
+ }
159
+ catch (error) {
160
+ return {
161
+ stderr: `Failed to read '${path}': ${error instanceof Error ? error.message : String(error)}`,
162
+ exitCode: 1,
163
+ };
164
+ }
165
+ }
166
+ /**
167
+ * Gets current working directory.
168
+ *
169
+ * @returns Normalized cwd path.
170
+ */
171
+ getCwd() {
172
+ return this.currentCwd;
173
+ }
174
+ /**
175
+ * Gets logged-in username.
176
+ *
177
+ * @returns Associated username.
178
+ */
179
+ getUsername() {
180
+ return this.username;
181
+ }
182
+ /**
183
+ * Renders tree view of directory.
184
+ *
185
+ * @param path Target directory, defaults to cwd.
186
+ * @returns Result with ASCII tree in stdout.
187
+ */
188
+ async tree(path) {
189
+ const target = path ?? ".";
190
+ return this.exec(`tree ${target}`);
191
+ }
192
+ /**
193
+ * Shows current user.
194
+ *
195
+ * @returns Result from whoami command.
196
+ */
197
+ async whoami() {
198
+ return this.exec("whoami");
199
+ }
200
+ /**
201
+ * Shows hostname.
202
+ *
203
+ * @returns Result from hostname command.
204
+ */
205
+ async hostname() {
206
+ return this.exec("hostname");
207
+ }
208
+ /**
209
+ * Lists active users/sessions.
210
+ *
211
+ * @returns Result from who command.
212
+ */
213
+ async who() {
214
+ return this.exec("who");
215
+ }
216
+ }
@@ -0,0 +1,4 @@
1
+ import type { ExecStream } from "../types/streams";
2
+ import type { VirtualShell } from "../VirtualShell";
3
+ export declare function runExec(stream: ExecStream, cmd: string, authUser: string, hostname: string, shell: VirtualShell): void;
4
+ //# sourceMappingURL=exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AASpD,wBAAgB,OAAO,CACtB,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,YAAY,GACjB,IAAI,CAiBN"}
@@ -0,0 +1,21 @@
1
+ import { runCommand } from "../commands";
2
+ function toTtyLines(text) {
3
+ return text
4
+ .replace(/\r\n/g, "\n")
5
+ .replace(/\r/g, "\n")
6
+ .replace(/\n/g, "\r\n");
7
+ }
8
+ export function runExec(stream, cmd, authUser, hostname, shell) {
9
+ Promise.resolve(runCommand(cmd, authUser, hostname, "exec", `/home/${authUser}`, shell)).then((result) => {
10
+ if (result.stdout) {
11
+ stream.write(`${toTtyLines(result.stdout)}\r\n`);
12
+ }
13
+ if (result.stderr) {
14
+ stream.stderr.write(`${toTtyLines(result.stderr)}\r\n`);
15
+ }
16
+ stream.exit(result.exitCode ?? 0);
17
+ console.log(shell.vfs);
18
+ void shell.vfs.flushMirror();
19
+ stream.end();
20
+ });
21
+ }
@@ -0,0 +1,9 @@
1
+ import type { CommandMode, CommandResult } from "../types/commands";
2
+ import type { Pipeline } from "../types/pipeline";
3
+ import type { VirtualShell } from "../VirtualShell";
4
+ /**
5
+ * Execute a parsed pipeline, chaining commands and handling redirections.
6
+ * Manages stdout/stderr flow between commands and file I/O.
7
+ */
8
+ export declare function executePipeline(pipeline: Pipeline, authUser: string, hostname: string, mode: CommandMode, cwd: string, shell: VirtualShell): Promise<CommandResult>;
9
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/executor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,KAAK,EAAE,QAAQ,EAAmB,MAAM,mBAAmB,CAAC;AACnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;GAGG;AACH,wBAAsB,eAAe,CACpC,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,GACjB,OAAO,CAAC,aAAa,CAAC,CA0BxB"}
@@ -0,0 +1,131 @@
1
+ import { runCommand as runSingleCommand } from "../commands";
2
+ import { resolvePath } from "../commands/helpers";
3
+ /**
4
+ * Execute a parsed pipeline, chaining commands and handling redirections.
5
+ * Manages stdout/stderr flow between commands and file I/O.
6
+ */
7
+ export async function executePipeline(pipeline, authUser, hostname, mode, cwd, shell) {
8
+ if (pipeline.commands.length === 0) {
9
+ return { exitCode: 0 };
10
+ }
11
+ if (pipeline.commands.length === 1) {
12
+ // Single command with possible redirections
13
+ return executeSingleCommandWithRedirections(pipeline.commands[0], authUser, hostname, mode, cwd, shell);
14
+ }
15
+ // Multiple commands in a pipeline
16
+ return executePipelineChain(pipeline.commands, authUser, hostname, mode, cwd, shell);
17
+ }
18
+ /**
19
+ * Execute a single command with input/output redirections
20
+ */
21
+ async function executeSingleCommandWithRedirections(cmd, authUser, hostname, mode, cwd, shell) {
22
+ // Prepare input if input file specified
23
+ let stdin;
24
+ if (cmd.inputFile) {
25
+ const inputPath = resolvePath(cwd, cmd.inputFile);
26
+ try {
27
+ stdin = shell.vfs.readFile(inputPath);
28
+ }
29
+ catch {
30
+ return {
31
+ stderr: `cat: ${cmd.inputFile}: No such file or directory`,
32
+ exitCode: 1,
33
+ };
34
+ }
35
+ }
36
+ // Build raw input for the command
37
+ const rawInput = [cmd.name, ...cmd.args].join(" ");
38
+ // Run the command with potential input
39
+ const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, stdin);
40
+ // Handle output redirection
41
+ if (cmd.outputFile) {
42
+ const outputPath = resolvePath(cwd, cmd.outputFile);
43
+ const output = result.stdout || "";
44
+ try {
45
+ if (cmd.appendOutput) {
46
+ try {
47
+ const existing = shell.vfs.readFile(outputPath);
48
+ shell.writeFileAsUser(authUser, outputPath, existing + output);
49
+ }
50
+ catch {
51
+ shell.writeFileAsUser(authUser, outputPath, output);
52
+ }
53
+ }
54
+ else {
55
+ shell.writeFileAsUser(authUser, outputPath, output);
56
+ }
57
+ return { ...result, stdout: "" };
58
+ }
59
+ catch {
60
+ return {
61
+ ...result,
62
+ stderr: `Failed to write to ${cmd.outputFile}`,
63
+ exitCode: 1,
64
+ };
65
+ }
66
+ }
67
+ return result;
68
+ }
69
+ /**
70
+ * Execute a chain of commands connected by pipes
71
+ */
72
+ async function executePipelineChain(commands, authUser, hostname, mode, cwd, shell) {
73
+ let currentOutput = "";
74
+ let exitCode = 0;
75
+ for (let i = 0; i < commands.length; i++) {
76
+ const cmd = commands[i];
77
+ // Handle input file for first command
78
+ if (i === 0 && cmd.inputFile) {
79
+ const inputPath = resolvePath(cwd, cmd.inputFile);
80
+ try {
81
+ currentOutput = shell.vfs.readFile(inputPath);
82
+ }
83
+ catch {
84
+ return {
85
+ stderr: `cat: ${cmd.inputFile}: No such file or directory`,
86
+ exitCode: 1,
87
+ };
88
+ }
89
+ }
90
+ // Build raw input
91
+ const rawInput = [cmd.name, ...cmd.args].join(" ");
92
+ // Create a modified context that might accept stdin
93
+ // For now, we'll append input as an additional arg for commands that support it
94
+ const result = await runSingleCommand(rawInput, authUser, hostname, mode, cwd, shell, currentOutput);
95
+ exitCode = result.exitCode ?? 0;
96
+ // Handle output redirection (only for last command)
97
+ if (i === commands.length - 1 && cmd.outputFile) {
98
+ const outputPath = resolvePath(cwd, cmd.outputFile);
99
+ const output = result.stdout || "";
100
+ try {
101
+ if (cmd.appendOutput) {
102
+ try {
103
+ const existing = shell.vfs.readFile(outputPath);
104
+ shell.writeFileAsUser(authUser, outputPath, existing + output);
105
+ }
106
+ catch {
107
+ shell.writeFileAsUser(authUser, outputPath, output);
108
+ }
109
+ }
110
+ else {
111
+ shell.writeFileAsUser(authUser, outputPath, output);
112
+ }
113
+ currentOutput = "";
114
+ }
115
+ catch {
116
+ return {
117
+ stderr: `Failed to write to ${cmd.outputFile}`,
118
+ exitCode: 1,
119
+ };
120
+ }
121
+ }
122
+ else {
123
+ // Pass output to next command
124
+ currentOutput = result.stdout || "";
125
+ }
126
+ if (result.stderr && exitCode !== 0) {
127
+ return { stderr: result.stderr, exitCode };
128
+ }
129
+ }
130
+ return { stdout: currentOutput, exitCode };
131
+ }
@@ -0,0 +1,2 @@
1
+ export declare function loadOrCreateHostKey(baseDir?: string): string;
2
+ //# sourceMappingURL=hostKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hostKey.d.ts","sourceRoot":"","sources":["../../src/SSHMimic/hostKey.ts"],"names":[],"mappings":"AAIA,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,MAAsB,GAAG,MAAM,CAgB3E"}
@@ -0,0 +1,17 @@
1
+ import { generateKeyPairSync } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
4
+ export function loadOrCreateHostKey(baseDir = process.cwd()) {
5
+ const hostKeyPath = resolve(baseDir, ".ssh-mimic", "host_rsa");
6
+ if (existsSync(hostKeyPath)) {
7
+ return readFileSync(hostKeyPath, "utf8");
8
+ }
9
+ const privateKey = generateKeyPairSync("rsa", {
10
+ modulusLength: 2048,
11
+ privateKeyEncoding: { type: "pkcs1", format: "pem" },
12
+ publicKeyEncoding: { type: "pkcs1", format: "pem" },
13
+ }).privateKey;
14
+ mkdirSync(dirname(hostKeyPath), { recursive: true });
15
+ writeFileSync(hostKeyPath, privateKey, { mode: 0o600 });
16
+ return privateKey;
17
+ }
@@ -0,0 +1,39 @@
1
+ import { Server as SshServer } from "ssh2";
2
+ import { VirtualShell } from "../VirtualShell";
3
+ /**
4
+ * SSH server facade that wires the virtual shell runtime into ssh2 sessions.
5
+ *
6
+ * This class is exported as `VirtualSshServer` for public API compatibility.
7
+ * Create an instance, call {@link SshMimic.start}, and stop it with
8
+ * {@link SshMimic.stop} when your process exits.
9
+ */
10
+ declare class SshMimic {
11
+ port: number;
12
+ server: SshServer | null;
13
+ private shell;
14
+ private shellHostname;
15
+ /**
16
+ * Creates a new SSH mimic server instance.
17
+ *
18
+ * @param port TCP port to bind on localhost.
19
+ * @param hostname Virtual hostname used for the SSH ident and default shell label.
20
+ * @param shell Optional preconfigured virtual shell instance to reuse.
21
+ */
22
+ constructor({ port, hostname, shell, }: {
23
+ port: number;
24
+ hostname?: string;
25
+ shell?: VirtualShell;
26
+ });
27
+ /**
28
+ * Starts server and initializes virtual filesystem, users, and handlers.
29
+ *
30
+ * @returns Promise resolved with bound listening port.
31
+ */
32
+ start(): Promise<number>;
33
+ /**
34
+ * Stops server if running.
35
+ */
36
+ stop(): void;
37
+ }
38
+ export { SshMimic };
39
+ //# sourceMappingURL=index.d.ts.map