typescript-virtual-container 1.4.1 → 1.4.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAwBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CA+nBN"}
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/VirtualShell/shell.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,GAAG,CAAC;AAMvD,OAAO,EAGN,KAAK,YAAY,EAEjB,MAAM,yBAAyB,CAAC;AAIjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAwBpD,wBAAgB,UAAU,CACzB,UAAU,EAAE,eAAe,EAC3B,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,aAAa,oBAAY,EACzB,YAAY,EAAE,YAAY,YAAyB,EACnD,KAAK,EAAE,YAAY,GACjB,IAAI,CA0nBN"}
@@ -8,7 +8,7 @@ import { buildPrompt } from "../SSHMimic/prompt";
8
8
  export function startShell(properties, stream, authUser, hostname, sessionId, remoteAddress = "unknown", terminalSize = { cols: 80, rows: 24 }, shell) {
9
9
  let lineBuffer = "";
10
10
  let cursorPos = 0;
11
- let history = loadHistory(shell.vfs);
11
+ let history = loadHistory(shell.vfs, authUser);
12
12
  let historyIndex = null;
13
13
  let historyDraft = "";
14
14
  let cwd = `/home/${authUser}`;
@@ -252,10 +252,10 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
252
252
  history = history.slice(history.length - 500);
253
253
  }
254
254
  const data = history.length > 0 ? `${history.join("\n")}\n` : "";
255
- shell.vfs.writeFile("/virtual-env-js/.bash_history", data);
255
+ shell.vfs.writeFile(`/home/${authUser}/.bash_history`, data);
256
256
  }
257
257
  function readLastLogin() {
258
- const lastlogPath = `/virtual-env-js/.lastlog/${authUser}.json`;
258
+ const lastlogPath = `/home/${authUser}/.lastlog.json`;
259
259
  if (!shell.vfs.exists(lastlogPath)) {
260
260
  return null;
261
261
  }
@@ -267,11 +267,7 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
267
267
  }
268
268
  }
269
269
  function writeLastLogin(nowIso) {
270
- const dir = "/virtual-env-js/.lastlog";
271
- if (!shell.vfs.exists(dir)) {
272
- shell.vfs.mkdir(dir, 0o700);
273
- }
274
- const lastlogPath = `${dir}/${authUser}.json`;
270
+ const lastlogPath = `/home/${authUser}/.lastlog`;
275
271
  shell.vfs.writeFile(lastlogPath, JSON.stringify({ at: nowIso, from: remoteAddress }));
276
272
  }
277
273
  function renderLoginBanner() {
@@ -492,8 +488,8 @@ export function startShell(properties, stream, authUser, hostname, sessionId, re
492
488
  }
493
489
  });
494
490
  }
495
- function loadHistory(vfs) {
496
- const historyPath = "/virtual-env-js/.bash_history";
491
+ function loadHistory(vfs, authUser) {
492
+ const historyPath = `/home/${authUser}/.bash_history`;
497
493
  if (!vfs.exists(historyPath)) {
498
494
  vfs.writeFile(historyPath, "");
499
495
  return [];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/VirtualUserManager/index.ts"],"names":[],"mappings":"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"}
@@ -18,10 +18,10 @@ export class VirtualUserManager extends EventEmitter {
18
18
  autoSudoForNewUsers;
19
19
  static recordCache = new Map();
20
20
  static fastPasswordHash = resolveFastPasswordHash();
21
- usersPath = "/virtual-env-js/.auth/htpasswd";
22
- sudoersPath = "/virtual-env-js/.auth/sudoers";
23
- quotasPath = "/virtual-env-js/.auth/quotas";
24
- authDirPath = "/virtual-env-js/.auth";
21
+ usersPath = "/etc/htpasswd";
22
+ sudoersPath = "/etc/sudoers";
23
+ quotasPath = "/etc/quotas";
24
+ authDirPath = "/.virtual-env-js/.auth";
25
25
  users = new Map();
26
26
  sudoers = new Set();
27
27
  quotas = new Map();
@@ -1,6 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import * as path from "node:path";
3
- const PROTECTED_PREFIXES = ["/virtual-env-js/.auth"];
3
+ const PROTECTED_PREFIXES = ["/.virtual-env-js/.auth", "/etc/htpasswd"];
4
4
  function normalizeFetchUrl(input) {
5
5
  if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(input)) {
6
6
  return input;
@@ -8,9 +8,9 @@ export const historyCommand = {
8
8
  description: "Display command history",
9
9
  category: "shell",
10
10
  params: ["[n]"],
11
- run: ({ args, shell }) => {
11
+ run: ({ args, shell, authUser }) => {
12
12
  // History is persisted in the VFS by the interactive shell
13
- const histPath = "/virtual-env-js/.bash_history";
13
+ const histPath = `/home/${authUser}/.bash_history`;
14
14
  if (!shell.vfs.exists(histPath)) {
15
15
  return { stdout: "", exitCode: 0 };
16
16
  }
@@ -6,8 +6,8 @@
6
6
  * Called once during VirtualShell initialization. Idempotent — skips
7
7
  * paths that already exist so FS-mode snapshots survive restarts.
8
8
  */
9
- import type { ShellProperties } from "../VirtualShell";
10
9
  import type VirtualFileSystem from "../VirtualFileSystem";
10
+ import type { ShellProperties } from "../VirtualShell";
11
11
  import type { VirtualUserManager } from "../VirtualUserManager";
12
12
  /**
13
13
  * Sync `/etc/passwd`, `/etc/group`, and `/etc/shadow` from the
@@ -1 +1 @@
1
- {"version":3,"file":"linuxRootfs.d.ts","sourceRoot":"","sources":["../../src/modules/linuxRootfs.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAgHhE;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,GACvB,IAAI,CAsCN;AAgED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,OAAO,uBAAuB,EAAE,oBAAoB,EAAE,GAC/D,IAAI,CAmJN;AAuND;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,EACtB,cAAc,EAAE,MAAM,GACpB,IAAI,CAYN"}
1
+ {"version":3,"file":"linuxRootfs.d.ts","sourceRoot":"","sources":["../../src/modules/linuxRootfs.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE;;;;;;GAMG;AAGH,OAAO,KAAK,iBAAiB,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAgHhE;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,GACvB,IAAI,CAsCN;AAgED;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAC1B,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,OAAO,uBAAuB,EAAE,oBAAoB,EAAE,GAC/D,IAAI,CAmJN;AAuND;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CACnC,GAAG,EAAE,iBAAiB,EACtB,KAAK,EAAE,kBAAkB,EACzB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,eAAe,EACtB,cAAc,EAAE,MAAM,GACpB,IAAI,CAYN"}
@@ -394,18 +394,18 @@ function bootstrapTmp(vfs) {
394
394
  // ─── /root ────────────────────────────────────────────────────────────────────
395
395
  function bootstrapRoot(vfs) {
396
396
  ensureDir(vfs, "/root", 0o700);
397
- ensureFile(vfs, "/root/.bashrc", `${[
397
+ ensureFile(vfs, "/home/root/.bashrc", `${[
398
398
  "# root .bashrc",
399
399
  "export PS1='\\[\\033[0;31m\\]\\u@\\h\\[\\033[0m\\]:\\[\\033[0;34m\\]\\w\\[\\033[0m\\]# '",
400
400
  "export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
401
401
  "alias ll='ls -la'",
402
402
  "alias la='ls -A'",
403
403
  ].join("\n")}\n`);
404
- ensureFile(vfs, "/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
404
+ ensureFile(vfs, "/home/root/.profile", "[ -f ~/.bashrc ] && . ~/.bashrc\n");
405
405
  // Fix: /home/root should map to /root for root user
406
- if (!vfs.exists("/home/root")) {
407
- vfs.symlink("/root", "/home/root");
408
- }
406
+ // if (!vfs.exists("/home/root")) {
407
+ // vfs.symlink("/root", "/home/root");
408
+ // }
409
409
  }
410
410
  // ─── /opt + /srv + /mnt + /media ─────────────────────────────────────────────
411
411
  function bootstrapMisc(vfs) {
@@ -1,14 +1,18 @@
1
1
  import { readFile, unlink, writeFile } from "node:fs/promises";
2
+ import * as path from "node:path";
2
3
  import { basename } from "node:path";
3
4
  import { stdin, stdout } from "node:process";
4
5
  import { createInterface } from "node:readline";
6
+ import { getCommandNames } from "./commands/registry";
5
7
  import { makeDefaultEnv, runCommand } from "./commands/runtime";
6
8
  import { spawnNanoEditorProcess } from "./modules/shellInteractive";
9
+ import { resolvePath } from "./modules/shellRuntime";
7
10
  import { buildLoginBanner } from "./SSHMimic/loginBanner";
8
11
  import { buildPrompt } from "./SSHMimic/prompt";
9
12
  import { VirtualShell } from "./VirtualShell";
10
13
  const hostname = process.env.SSH_MIMIC_HOSTNAME ?? "typescript-vm";
11
14
  const argv = process.argv.slice(2);
15
+ // ── CLI args ──────────────────────────────────────────────────────────────────
12
16
  function readUserArg() {
13
17
  for (let index = 0; index < argv.length; index += 1) {
14
18
  const current = argv[index];
@@ -30,11 +34,11 @@ const virtualShell = new VirtualShell(hostname, undefined, {
30
34
  mode: "fs",
31
35
  snapshotPath: ".vfs",
32
36
  });
37
+ // ── VFS helpers ───────────────────────────────────────────────────────────────
33
38
  function readLastLogin(username) {
34
- const lastlogPath = `/virtual-env-js/.lastlog/${username}.json`;
35
- if (!virtualShell.vfs.exists(lastlogPath)) {
39
+ const lastlogPath = `/home/${username}/.lastlog`;
40
+ if (!virtualShell.vfs.exists(lastlogPath))
36
41
  return null;
37
- }
38
42
  try {
39
43
  return JSON.parse(virtualShell.vfs.readFile(lastlogPath));
40
44
  }
@@ -42,6 +46,63 @@ function readLastLogin(username) {
42
46
  return null;
43
47
  }
44
48
  }
49
+ function writeLastLogin(username, from) {
50
+ virtualShell.vfs.writeFile(`/home/${username}/.lastlog`, JSON.stringify({ at: new Date().toISOString(), from }));
51
+ }
52
+ async function flushVfs() {
53
+ await virtualShell.vfs.flushMirror();
54
+ }
55
+ function loadHistory(authUser) {
56
+ const historyPath = `/home/${authUser}/.bash_history`;
57
+ if (!virtualShell.vfs.exists(historyPath)) {
58
+ virtualShell.vfs.writeFile(historyPath, "");
59
+ return [];
60
+ }
61
+ return virtualShell.vfs
62
+ .readFile(historyPath)
63
+ .split("\n")
64
+ .map((line) => line.trim())
65
+ .filter((line) => line.length > 0);
66
+ }
67
+ function saveHistory(history, authUser) {
68
+ const data = history.length > 0 ? `${history.join("\n")}\n` : "";
69
+ virtualShell.vfs.writeFile(`/home/${authUser}/.bash_history`, data);
70
+ }
71
+ // ── Tab completion ────────────────────────────────────────────────────────────
72
+ function listPathCompletions(vfs, cwd, prefix) {
73
+ const slashIndex = prefix.lastIndexOf("/");
74
+ const dirPart = slashIndex >= 0 ? prefix.slice(0, slashIndex + 1) : "";
75
+ const namePart = slashIndex >= 0 ? prefix.slice(slashIndex + 1) : prefix;
76
+ const basePath = resolvePath(cwd, dirPart || ".");
77
+ try {
78
+ return vfs
79
+ .list(basePath)
80
+ .filter((e) => !e.startsWith(".") && e.startsWith(namePart))
81
+ .map((e) => {
82
+ const fullPath = path.posix.join(basePath, e);
83
+ const st = vfs.stat(fullPath);
84
+ return `${dirPart}${e}${st.type === "directory" ? "/" : ""}`;
85
+ })
86
+ .sort();
87
+ }
88
+ catch {
89
+ return [];
90
+ }
91
+ }
92
+ function makeCompleter(getState) {
93
+ const commandNames = Array.from(new Set(getCommandNames())).sort();
94
+ return (line, cb) => {
95
+ const { cwd } = getState();
96
+ // Extract the token under/before cursor (last whitespace-separated word)
97
+ const token = line.split(/\s+/).at(-1) ?? "";
98
+ const isFirstToken = line.trimStart() === token;
99
+ const cmdHits = isFirstToken ? commandNames.filter((n) => n.startsWith(token)) : [];
100
+ const pathHits = listPathCompletions(virtualShell.vfs, cwd, token);
101
+ const hits = Array.from(new Set([...cmdHits, ...pathHits])).sort();
102
+ cb(null, [hits, token]);
103
+ };
104
+ }
105
+ // ── Hidden password input ─────────────────────────────────────────────────────
45
106
  function askHiddenQuestion(rl, promptText) {
46
107
  return new Promise((resolve) => {
47
108
  if (!stdin.isTTY || !stdout.isTTY) {
@@ -52,10 +113,8 @@ function askHiddenQuestion(rl, promptText) {
52
113
  let buffer = "";
53
114
  const cleanup = () => {
54
115
  stdin.off("data", onData);
55
- if (!wasRawMode) {
116
+ if (!wasRawMode)
56
117
  stdin.setRawMode(false);
57
- }
58
- rl.resume();
59
118
  };
60
119
  const finish = (value) => {
61
120
  cleanup();
@@ -64,8 +123,8 @@ function askHiddenQuestion(rl, promptText) {
64
123
  };
65
124
  const onData = (chunk) => {
66
125
  const input = chunk.toString("utf8");
67
- for (let index = 0; index < input.length; index += 1) {
68
- const ch = input[index];
126
+ for (let i = 0; i < input.length; i += 1) {
127
+ const ch = input[i];
69
128
  if (ch === "\r" || ch === "\n") {
70
129
  finish(buffer);
71
130
  return;
@@ -74,46 +133,20 @@ function askHiddenQuestion(rl, promptText) {
74
133
  buffer = buffer.slice(0, -1);
75
134
  continue;
76
135
  }
77
- if (ch >= " ") {
136
+ if (ch >= " ")
78
137
  buffer += ch;
79
- }
80
138
  }
81
139
  };
140
+ // Pause readline so it doesn't eat our raw keystrokes
82
141
  rl.pause();
83
142
  stdout.write(promptText);
84
- if (!wasRawMode) {
143
+ if (!wasRawMode)
85
144
  stdin.setRawMode(true);
86
- }
87
145
  stdin.resume();
88
146
  stdin.on("data", onData);
89
147
  });
90
148
  }
91
- function writeLastLogin(username, from) {
92
- const dir = "/virtual-env-js/.lastlog";
93
- if (!virtualShell.vfs.exists(dir)) {
94
- virtualShell.vfs.mkdir(dir, 0o700);
95
- }
96
- virtualShell.vfs.writeFile(`/virtual-env-js/.lastlog/${username}.json`, JSON.stringify({ at: new Date().toISOString(), from }));
97
- }
98
- async function flushVfs() {
99
- await virtualShell.vfs.flushMirror();
100
- }
101
- function loadHistory() {
102
- const historyPath = "/virtual-env-js/.bash_history";
103
- if (!virtualShell.vfs.exists(historyPath)) {
104
- virtualShell.vfs.writeFile(historyPath, "");
105
- return [];
106
- }
107
- return virtualShell.vfs
108
- .readFile(historyPath)
109
- .split("\n")
110
- .map((line) => line.trim())
111
- .filter((line) => line.length > 0);
112
- }
113
- function saveHistory(history) {
114
- const data = history.length > 0 ? `${history.join("\n")}\n` : "";
115
- virtualShell.vfs.writeFile("/virtual-env-js/.bash_history", data);
116
- }
149
+ // ── Session state helper ──────────────────────────────────────────────────────
117
150
  function applySessionState(authUserState, cwdState, result, shellEnvState) {
118
151
  let authUser = authUserState;
119
152
  let cwd = cwdState;
@@ -131,21 +164,16 @@ function applySessionState(authUserState, cwdState, result, shellEnvState) {
131
164
  }
132
165
  return { authUser, cwd };
133
166
  }
134
- virtualShell.addCommand("demo", [], () => {
135
- return {
136
- stdout: "This is a demo command. It does nothing useful.",
137
- exitCode: 0,
138
- };
139
- });
167
+ // ── Demo command ──────────────────────────────────────────────────────────────
168
+ virtualShell.addCommand("demo", [], () => ({
169
+ stdout: "This is a demo command. It does nothing useful.",
170
+ exitCode: 0,
171
+ }));
172
+ // ── Main shell ────────────────────────────────────────────────────────────────
140
173
  async function runReadlineShell() {
141
- const rl = createInterface({ input: stdin, output: stdout, terminal: true });
142
174
  await virtualShell.ensureInitialized();
143
- let history = loadHistory();
144
- const rlWithHistory = rl;
145
- rlWithHistory.history = [...history].reverse();
146
175
  const selectedUser = initialUser.trim() || "root";
147
- const userExists = virtualShell.users.getPasswordHash(selectedUser) !== null;
148
- if (!userExists) {
176
+ if (virtualShell.users.getPasswordHash(selectedUser) === null) {
149
177
  process.stderr.write(`self-standalone: user '${selectedUser}' does not exist\n`);
150
178
  process.exit(1);
151
179
  }
@@ -154,10 +182,19 @@ async function runReadlineShell() {
154
182
  let cwd = `/home/${authUser}`;
155
183
  shellEnv.vars.PWD = cwd;
156
184
  const remoteAddress = "localhost";
157
- const terminalSize = {
158
- cols: stdout.columns ?? 80,
159
- rows: stdout.rows ?? 24,
160
- };
185
+ const terminalSize = { cols: stdout.columns ?? 80, rows: stdout.rows ?? 24 };
186
+ let history = loadHistory(authUser);
187
+ // completer reads cwd via closure — always current
188
+ const rl = createInterface({
189
+ input: stdin,
190
+ output: stdout,
191
+ terminal: true,
192
+ completer: makeCompleter(() => ({ cwd })),
193
+ });
194
+ // Sync readline's internal history with our VFS history
195
+ const rlWithHistory = rl;
196
+ rlWithHistory.history = [...history].reverse();
197
+ // ── nano editor ────────────────────────────────────────────────────────────
161
198
  async function startNanoEditor(targetPath, initialContent, tempPath) {
162
199
  if (virtualShell.vfs.exists(targetPath)) {
163
200
  await writeFile(tempPath, initialContent, "utf8");
@@ -169,20 +206,16 @@ async function runReadlineShell() {
169
206
  end: () => undefined,
170
207
  });
171
208
  const wasRawMode = Boolean(stdin.isRaw);
172
- const forwardInput = (chunk) => {
173
- editor.stdin.write(chunk);
174
- };
209
+ const forwardInput = (chunk) => { editor.stdin.write(chunk); };
175
210
  stdin.resume();
176
- if (!wasRawMode) {
211
+ if (!wasRawMode)
177
212
  stdin.setRawMode(true);
178
- }
179
213
  stdin.on("data", forwardInput);
180
214
  await new Promise((resolve) => {
181
215
  const cleanup = () => {
182
216
  stdin.off("data", forwardInput);
183
- if (!wasRawMode) {
217
+ if (!wasRawMode)
184
218
  stdin.setRawMode(false);
185
- }
186
219
  rl.resume();
187
220
  };
188
221
  editor.on("error", (error) => {
@@ -199,7 +232,7 @@ async function runReadlineShell() {
199
232
  await flushVfs();
200
233
  }
201
234
  catch {
202
- // Save skipped or temp file missing.
235
+ // save skipped or temp file missing
203
236
  }
204
237
  await unlink(tempPath).catch(() => undefined);
205
238
  stdout.write("\r\n");
@@ -207,6 +240,7 @@ async function runReadlineShell() {
207
240
  });
208
241
  });
209
242
  }
243
+ // ── challenge handlers ─────────────────────────────────────────────────────
210
244
  async function handleSudoChallenge(challenge) {
211
245
  if (challenge.onPassword) {
212
246
  let promptText = challenge.prompt;
@@ -275,6 +309,7 @@ async function runReadlineShell() {
275
309
  break;
276
310
  }
277
311
  }
312
+ // handleCommandResult must be declared before the "line" handler
278
313
  async function handleCommandResult(result) {
279
314
  if (result.openEditor) {
280
315
  await startNanoEditor(result.openEditor.targetPath, result.openEditor.initialContent, result.openEditor.tempPath);
@@ -288,32 +323,26 @@ async function runReadlineShell() {
288
323
  await handlePasswordChallenge(result.passwordChallenge);
289
324
  return;
290
325
  }
326
+ if (result.clearScreen) {
327
+ stdout.write("\u001b[2J\u001b[H");
328
+ console.clear();
329
+ }
291
330
  if (result.stdout) {
292
331
  stdout.write(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}\n`);
293
332
  }
294
333
  if (result.stderr) {
295
334
  process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}\n`);
296
335
  }
297
- if (result.clearScreen) {
298
- stdout.write("\u001b[2J\u001b[H");
299
- console.clear();
300
- }
301
- const updatedState = applySessionState(authUser, cwd, result, shellEnv);
302
- authUser = updatedState.authUser;
303
- cwd = updatedState.cwd;
336
+ const updated = applySessionState(authUser, cwd, result, shellEnv);
337
+ authUser = updated.authUser;
338
+ cwd = updated.cwd;
304
339
  if (result.closeSession) {
305
340
  await flushVfs();
306
341
  rl.close();
307
342
  process.exit(result.exitCode ?? 0);
308
343
  }
309
344
  }
310
- if (process.env.USER !== "root" && virtualShell.users.hasPassword(authUser)) {
311
- const password = await askHiddenQuestion(rl, `Password for ${authUser}: `);
312
- if (!virtualShell.users.verifyPassword(authUser, password)) {
313
- process.stderr.write("self-standalone: authentication failed\n");
314
- process.exit(1);
315
- }
316
- }
345
+ // ── Prompt helper ──────────────────────────────────────────────────────────
317
346
  const renderPrompt = () => {
318
347
  const cwdLabel = cwd === `/home/${authUser}` ? "~" : basename(cwd) || "/";
319
348
  return buildPrompt(authUser, hostname, cwdLabel);
@@ -322,50 +351,68 @@ async function runReadlineShell() {
322
351
  rl.setPrompt(renderPrompt());
323
352
  rl.prompt();
324
353
  };
325
- rl.on("SIGINT", () => {
326
- stdout.write("^C\n");
327
- rl.write("", { ctrl: true, name: "u" });
328
- prompt();
329
- });
330
- rl.on("close", () => {
331
- void (async () => {
332
- await flushVfs();
333
- console.log("");
334
- process.exit(0);
335
- })();
336
- });
354
+ // ── Auth (password gate) ───────────────────────────────────────────────────
355
+ if (process.env.USER !== "root" && virtualShell.users.hasPassword(authUser)) {
356
+ const password = await askHiddenQuestion(rl, `Password for ${authUser}: `);
357
+ if (!virtualShell.users.verifyPassword(authUser, password)) {
358
+ process.stderr.write("self-standalone: authentication failed\n");
359
+ process.exit(1);
360
+ }
361
+ }
362
+ // ── Login banner ───────────────────────────────────────────────────────────
337
363
  stdout.write(buildLoginBanner(hostname, virtualShell.properties, readLastLogin(authUser)));
338
364
  writeLastLogin(authUser, remoteAddress);
339
365
  await flushVfs();
340
- prompt();
341
- while (true) {
342
- const inputLine = await new Promise((resolve) => {
343
- rl.once("line", (line) => resolve(line));
344
- });
366
+ // ── Event-driven line handler (enables completer) ──────────────────────────
367
+ //
368
+ // Key insight: readline's completer only fires when readline itself owns
369
+ // stdin (i.e. rl is not paused). We use the event-driven "line" pattern
370
+ // instead of a while(true)+rl.once("line") loop so readline stays active
371
+ // between commands. We pause only while awaiting async work, then resume
372
+ // immediately before re-prompting so the next Tab press is caught.
373
+ let busy = false;
374
+ rl.on("line", async (inputLine) => {
375
+ if (busy)
376
+ return; // shouldn't happen but guard re-entrancy
377
+ busy = true;
345
378
  rl.pause();
346
- if (inputLine.trim().length > 0) {
379
+ const trimmed = inputLine.trim();
380
+ if (trimmed.length > 0) {
347
381
  history.push(inputLine);
348
- if (history.length > 500) {
382
+ if (history.length > 500)
349
383
  history = history.slice(history.length - 500);
350
- }
351
- saveHistory(history);
384
+ saveHistory(history, authUser);
352
385
  rlWithHistory.history = [...history].reverse();
353
386
  }
354
387
  const result = await runCommand(inputLine, authUser, hostname, "shell", cwd, virtualShell, undefined, shellEnv);
355
388
  await handleCommandResult(result);
356
389
  await flushVfs();
357
- prompt();
390
+ busy = false;
391
+ // Resume before prompt so readline can handle Tab on the next input
358
392
  rl.resume();
359
- }
393
+ prompt();
394
+ });
395
+ rl.on("SIGINT", () => {
396
+ stdout.write("^C\n");
397
+ rl.write("", { ctrl: true, name: "u" });
398
+ prompt();
399
+ });
400
+ rl.on("close", () => {
401
+ void flushVfs().then(() => {
402
+ console.log("");
403
+ process.exit(0);
404
+ });
405
+ });
406
+ // Initial prompt — readline is already active, completer live from first keystroke
407
+ prompt();
360
408
  }
361
409
  runReadlineShell().catch((error) => {
362
410
  console.error("Failed to start readline SSH emulation:", error);
363
411
  process.exit(1);
364
412
  });
365
413
  process.on("uncaughtException", (error) => {
366
- console.log("Oh my god, something terrible happened: ", error);
414
+ console.error("Uncaught exception:", error);
367
415
  });
368
416
  process.on("unhandledRejection", (error, promise) => {
369
- console.log(" Oh Lord! We forgot to handle a promise rejection here: ", promise);
370
- console.log(" The error was: ", error);
417
+ console.error("Unhandled rejection at:", promise, "error:", error);
371
418
  });
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
- "version": "1.4.1",
7
+ "version": "1.4.2",
8
8
  "license": "MIT",
9
9
  "repository": {
10
10
  "type": "git",
@@ -52,7 +52,7 @@ export function startShell(
52
52
  ): void {
53
53
  let lineBuffer = "";
54
54
  let cursorPos = 0;
55
- let history = loadHistory(shell.vfs);
55
+ let history = loadHistory(shell.vfs, authUser);
56
56
  let historyIndex: number | null = null;
57
57
  let historyDraft = "";
58
58
  let cwd = `/home/${authUser}`;
@@ -388,11 +388,11 @@ export function startShell(
388
388
  }
389
389
 
390
390
  const data = history.length > 0 ? `${history.join("\n")}\n` : "";
391
- shell.vfs.writeFile("/virtual-env-js/.bash_history", data);
391
+ shell.vfs.writeFile(`/home/${authUser}/.bash_history`, data);
392
392
  }
393
393
 
394
394
  function readLastLogin(): { at: string; from: string } | null {
395
- const lastlogPath = `/virtual-env-js/.lastlog/${authUser}.json`;
395
+ const lastlogPath = `/home/${authUser}/.lastlog.json`;
396
396
  if (!shell.vfs.exists(lastlogPath)) {
397
397
  return null;
398
398
  }
@@ -408,12 +408,7 @@ export function startShell(
408
408
  }
409
409
 
410
410
  function writeLastLogin(nowIso: string): void {
411
- const dir = "/virtual-env-js/.lastlog";
412
- if (!shell.vfs.exists(dir)) {
413
- shell.vfs.mkdir(dir, 0o700);
414
- }
415
-
416
- const lastlogPath = `${dir}/${authUser}.json`;
411
+ const lastlogPath = `/home/${authUser}/.lastlog`;
417
412
  shell.vfs.writeFile(
418
413
  lastlogPath,
419
414
  JSON.stringify({ at: nowIso, from: remoteAddress }),
@@ -690,8 +685,8 @@ export function startShell(
690
685
  });
691
686
  }
692
687
 
693
- function loadHistory(vfs: VirtualFileSystem): string[] {
694
- const historyPath = "/virtual-env-js/.bash_history";
688
+ function loadHistory(vfs: VirtualFileSystem, authUser: string): string[] {
689
+ const historyPath = `/home/${authUser}/.bash_history`;
695
690
  if (!vfs.exists(historyPath)) {
696
691
  vfs.writeFile(historyPath, "");
697
692
  return [];
@@ -47,10 +47,10 @@ const perf: PerfLogger = createPerfLogger("VirtualUserManager");
47
47
  export class VirtualUserManager extends EventEmitter {
48
48
  private static readonly recordCache = new Map<string, VirtualUserRecord>();
49
49
  private static readonly fastPasswordHash = resolveFastPasswordHash();
50
- private readonly usersPath = "/virtual-env-js/.auth/htpasswd";
51
- private readonly sudoersPath = "/virtual-env-js/.auth/sudoers";
52
- private readonly quotasPath = "/virtual-env-js/.auth/quotas";
53
- private readonly authDirPath = "/virtual-env-js/.auth";
50
+ private readonly usersPath = "/etc/htpasswd";
51
+ private readonly sudoersPath = "/etc/sudoers";
52
+ private readonly quotasPath = "/etc/quotas";
53
+ private readonly authDirPath = "/.virtual-env-js/.auth";
54
54
  private readonly users = new Map<string, VirtualUserRecord>();
55
55
  private readonly sudoers = new Set<string>();
56
56
  private readonly quotas = new Map<string, number>();
@@ -4,7 +4,7 @@ import type VirtualFileSystem from "../VirtualFileSystem";
4
4
  import type { VirtualPackageManager } from "../VirtualPackageManager";
5
5
  import type { VirtualShell } from "../VirtualShell";
6
6
 
7
- const PROTECTED_PREFIXES = ["/virtual-env-js/.auth"] as const;
7
+ const PROTECTED_PREFIXES = ["/.virtual-env-js/.auth", "/etc/htpasswd"] as const;
8
8
 
9
9
  function normalizeFetchUrl(input: string): string {
10
10
  if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(input)) {