@ecmaos/kernel 0.6.4 → 0.6.6

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.
@@ -25369,39 +25369,101 @@ const _Auth = class _Auth {
25369
25369
  // Create a new credential
25370
25370
  create: /* @__PURE__ */ __name(async (options) => {
25371
25371
  try {
25372
- return await navigator.credentials.create({ publicKey: options });
25372
+ if (!this.passkey.isSupported()) {
25373
+ throw new Error("WebAuthn is not supported in this browser");
25374
+ }
25375
+ if (!window.isSecureContext) {
25376
+ throw new Error("WebAuthn requires a secure context (HTTPS or localhost)");
25377
+ }
25378
+ const credential = await navigator.credentials.create({ publicKey: options });
25379
+ if (!credential || !(credential instanceof PublicKeyCredential)) {
25380
+ throw new Error("Failed to create credential");
25381
+ }
25382
+ return credential;
25373
25383
  } catch (error) {
25374
- console.error("Error creating credential:", error);
25375
- return null;
25384
+ if (error instanceof Error) {
25385
+ if (error.name === "NotAllowedError") {
25386
+ throw new Error("User cancelled or denied the operation");
25387
+ } else if (error.name === "InvalidStateError") {
25388
+ throw new Error("Credential already exists or operation is invalid");
25389
+ } else if (error.name === "NotSupportedError") {
25390
+ throw new Error("The operation is not supported");
25391
+ } else if (error.name === "SecurityError") {
25392
+ throw new Error("Security error: operation not allowed");
25393
+ }
25394
+ throw error;
25395
+ }
25396
+ throw new Error(`Error creating credential: ${String(error)}`);
25376
25397
  }
25377
25398
  }, "create"),
25378
25399
  // Get an existing credential
25379
25400
  get: /* @__PURE__ */ __name(async (options) => {
25380
25401
  try {
25381
- return await navigator.credentials.get({ publicKey: options });
25402
+ if (!this.passkey.isSupported()) {
25403
+ throw new Error("WebAuthn is not supported in this browser");
25404
+ }
25405
+ if (!window.isSecureContext) {
25406
+ throw new Error("WebAuthn requires a secure context (HTTPS or localhost)");
25407
+ }
25408
+ const credential = await navigator.credentials.get({ publicKey: options });
25409
+ if (!credential || !(credential instanceof PublicKeyCredential)) {
25410
+ return null;
25411
+ }
25412
+ return credential;
25382
25413
  } catch (error) {
25383
- console.error("Error getting credential:", error);
25384
- return null;
25414
+ if (error instanceof Error) {
25415
+ if (error.name === "NotAllowedError") {
25416
+ throw new Error("User cancelled or denied the operation");
25417
+ } else if (error.name === "InvalidStateError") {
25418
+ throw new Error("No matching credential found");
25419
+ } else if (error.name === "NotSupportedError") {
25420
+ throw new Error("The operation is not supported");
25421
+ } else if (error.name === "SecurityError") {
25422
+ throw new Error("Security error: operation not allowed");
25423
+ }
25424
+ throw error;
25425
+ }
25426
+ throw new Error(`Error getting credential: ${String(error)}`);
25385
25427
  }
25386
25428
  }, "get"),
25387
25429
  // Check if WebAuthn is supported in the current browser
25388
25430
  isSupported: /* @__PURE__ */ __name(() => {
25389
- return !!window.PublicKeyCredential;
25431
+ return !!window.PublicKeyCredential && !!navigator.credentials;
25390
25432
  }, "isSupported"),
25391
- // Register a new credential
25392
- register: /* @__PURE__ */ __name(async (credential) => {
25393
- await navigator.credentials.store(credential);
25394
- }, "register"),
25395
- // Full test of passkey functionality
25396
- fullTest: /* @__PURE__ */ __name(async () => {
25397
- const credential = await this.passkey.create({
25398
- challenge: new Uint8Array(32),
25399
- rp: { name: "Example RP" },
25400
- user: { id: new Uint8Array(16), name: "example@example.com", displayName: "Example User" },
25401
- pubKeyCredParams: [{ type: "public-key", alg: -7 }]
25402
- });
25403
- console.log(credential);
25404
- }, "fullTest")
25433
+ // Verify a passkey signature
25434
+ verify: /* @__PURE__ */ __name(async (credential, challenge, publicKey) => {
25435
+ try {
25436
+ const response = credential.response;
25437
+ const signature = response.signature;
25438
+ const clientDataJSON = response.clientDataJSON;
25439
+ const authenticatorData = response.authenticatorData;
25440
+ const clientData = JSON.parse(new TextDecoder().decode(clientDataJSON));
25441
+ const receivedChallenge = Uint8Array.from(atob(clientData.challenge), (c) => c.charCodeAt(0));
25442
+ const expectedChallenge = challenge;
25443
+ if (receivedChallenge.length !== expectedChallenge.length) {
25444
+ return false;
25445
+ }
25446
+ for (let i = 0; i < expectedChallenge.length; i++) {
25447
+ if (receivedChallenge[i] !== expectedChallenge[i]) {
25448
+ return false;
25449
+ }
25450
+ }
25451
+ const clientDataHash = await crypto.subtle.digest("SHA-256", clientDataJSON);
25452
+ const signedData = new Uint8Array(authenticatorData.byteLength + clientDataHash.byteLength);
25453
+ signedData.set(new Uint8Array(authenticatorData), 0);
25454
+ signedData.set(new Uint8Array(clientDataHash), authenticatorData.byteLength);
25455
+ const isValid2 = await crypto.subtle.verify(
25456
+ { name: "ECDSA", hash: "SHA-256" },
25457
+ publicKey,
25458
+ signature,
25459
+ signedData
25460
+ );
25461
+ return isValid2;
25462
+ } catch (error) {
25463
+ console.error("Error verifying passkey:", error);
25464
+ return false;
25465
+ }
25466
+ }, "verify")
25405
25467
  });
25406
25468
  __publicField(this, "password", {
25407
25469
  create: /* @__PURE__ */ __name(async (options) => {
@@ -28559,7 +28621,7 @@ const _Dom = class _Dom {
28559
28621
  }
28560
28622
  async topbar(show) {
28561
28623
  if (!this._topbar) return;
28562
- const { default: topbar } = await import("./topbar.min-BK1fdCNA.js").then((n) => n.t);
28624
+ const { default: topbar } = await import("./topbar.min-BAV19NIs.js").then((n) => n.t);
28563
28625
  this._topbarShow = show ?? !this._topbarShow;
28564
28626
  if (this._topbarShow) topbar.show();
28565
28627
  else topbar.hide();
@@ -59629,7 +59691,7 @@ const _TerminalCommand = class _TerminalCommand {
59629
59691
  };
59630
59692
  __name(_TerminalCommand, "TerminalCommand");
59631
59693
  let TerminalCommand = _TerminalCommand;
59632
- function createCommand$h(kernel, shell, terminal) {
59694
+ function createCommand$l(kernel, shell, terminal) {
59633
59695
  return new TerminalCommand({
59634
59696
  command: "cat",
59635
59697
  description: "Concatenate files and print on the standard output",
@@ -59644,6 +59706,8 @@ function createCommand$h(kernel, shell, terminal) {
59644
59706
  run: /* @__PURE__ */ __name(async (argv, process2) => {
59645
59707
  if (!process2) return 1;
59646
59708
  const writer = process2.stdout.getWriter();
59709
+ const isTTY = process2.stdoutIsTTY ?? false;
59710
+ let lastByte;
59647
59711
  try {
59648
59712
  if (!argv.path || !argv.path[0]) {
59649
59713
  const reader = process2.stdin.getReader();
@@ -59651,11 +59715,17 @@ function createCommand$h(kernel, shell, terminal) {
59651
59715
  while (true) {
59652
59716
  const { done, value } = await reader.read();
59653
59717
  if (done) break;
59718
+ if (value.length > 0) {
59719
+ lastByte = value[value.length - 1];
59720
+ }
59654
59721
  await writer.write(value);
59655
59722
  }
59656
59723
  } finally {
59657
59724
  reader.releaseLock();
59658
59725
  }
59726
+ if (isTTY && lastByte !== void 0 && lastByte !== 10) {
59727
+ await writer.write(new Uint8Array([10]));
59728
+ }
59659
59729
  return 0;
59660
59730
  }
59661
59731
  const files = argv.path || [];
@@ -59678,7 +59748,11 @@ function createCommand$h(kernel, shell, terminal) {
59678
59748
  const data = new Uint8Array(chunkSize);
59679
59749
  const readSize = Math.min(chunkSize, stat2.size - bytesRead);
59680
59750
  await handle.read(data, 0, readSize, bytesRead);
59681
- await writer.write(data.subarray(0, readSize));
59751
+ const chunk = data.subarray(0, readSize);
59752
+ if (chunk.length > 0) {
59753
+ lastByte = chunk[chunk.length - 1];
59754
+ }
59755
+ await writer.write(chunk);
59682
59756
  bytesRead += readSize;
59683
59757
  }
59684
59758
  } else {
@@ -59695,7 +59769,11 @@ function createCommand$h(kernel, shell, terminal) {
59695
59769
  if (bytesRead > 0) {
59696
59770
  const bytesToWrite = maxBytes ? Math.min(bytesRead, maxBytes - totalBytesRead) : bytesRead;
59697
59771
  if (bytesToWrite > 0) {
59698
- await writer.write(data.subarray(0, bytesToWrite));
59772
+ const chunk = data.subarray(0, bytesToWrite);
59773
+ if (chunk.length > 0) {
59774
+ lastByte = chunk[chunk.length - 1];
59775
+ }
59776
+ await writer.write(chunk);
59699
59777
  totalBytesRead += bytesToWrite;
59700
59778
  }
59701
59779
  }
@@ -59705,16 +59783,18 @@ function createCommand$h(kernel, shell, terminal) {
59705
59783
  kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler);
59706
59784
  }
59707
59785
  }
59786
+ if (isTTY && lastByte !== void 0 && lastByte !== 10) {
59787
+ await writer.write(new Uint8Array([10]));
59788
+ }
59708
59789
  return 0;
59709
59790
  } finally {
59710
59791
  writer.releaseLock();
59711
- await writeStdout(process2, terminal, "\n");
59712
59792
  }
59713
59793
  }, "run")
59714
59794
  });
59715
59795
  }
59716
- __name(createCommand$h, "createCommand$h");
59717
- function createCommand$g(kernel, shell, terminal) {
59796
+ __name(createCommand$l, "createCommand$l");
59797
+ function createCommand$k(kernel, shell, terminal) {
59718
59798
  return new TerminalCommand({
59719
59799
  command: "cd",
59720
59800
  description: "Change the shell working directory",
@@ -59735,8 +59815,8 @@ function createCommand$g(kernel, shell, terminal) {
59735
59815
  }, "run")
59736
59816
  });
59737
59817
  }
59738
- __name(createCommand$g, "createCommand$g");
59739
- function createCommand$f(kernel, shell, terminal) {
59818
+ __name(createCommand$k, "createCommand$k");
59819
+ function createCommand$j(kernel, shell, terminal) {
59740
59820
  return new TerminalCommand({
59741
59821
  command: "chmod",
59742
59822
  description: "Change file mode bits",
@@ -59762,8 +59842,8 @@ function createCommand$f(kernel, shell, terminal) {
59762
59842
  }, "run")
59763
59843
  });
59764
59844
  }
59765
- __name(createCommand$f, "createCommand$f");
59766
- function createCommand$e(kernel, shell, terminal) {
59845
+ __name(createCommand$j, "createCommand$j");
59846
+ function createCommand$i(kernel, shell, terminal) {
59767
59847
  return new TerminalCommand({
59768
59848
  command: "cp",
59769
59849
  description: "Copy files",
@@ -59785,8 +59865,8 @@ function createCommand$e(kernel, shell, terminal) {
59785
59865
  }, "run")
59786
59866
  });
59787
59867
  }
59788
- __name(createCommand$e, "createCommand$e");
59789
- function createCommand$d(kernel, shell, terminal) {
59868
+ __name(createCommand$i, "createCommand$i");
59869
+ function createCommand$h(kernel, shell, terminal) {
59790
59870
  return new TerminalCommand({
59791
59871
  command: "echo",
59792
59872
  description: "Print arguments to the standard output",
@@ -59795,11 +59875,14 @@ function createCommand$d(kernel, shell, terminal) {
59795
59875
  terminal,
59796
59876
  options: [
59797
59877
  { name: "help", type: Boolean, description: kernel.i18n.t("Display help") },
59878
+ { name: "n", type: Boolean, alias: "n", description: "Do not output the trailing newline" },
59798
59879
  { name: "text", type: String, typeLabel: "{underline text}", defaultOption: true, multiple: true, description: "The text to print" }
59799
59880
  ],
59800
59881
  run: /* @__PURE__ */ __name(async (argv, process2) => {
59882
+ const noNewline = argv.n || false;
59801
59883
  const text = (argv.text || []).join(" ");
59802
- const data = new TextEncoder().encode(text + "\n");
59884
+ const output2 = noNewline ? text : text + "\n";
59885
+ const data = new TextEncoder().encode(output2);
59803
59886
  if (process2) {
59804
59887
  const writer = process2.stdout.getWriter();
59805
59888
  try {
@@ -59808,14 +59891,298 @@ function createCommand$d(kernel, shell, terminal) {
59808
59891
  writer.releaseLock();
59809
59892
  }
59810
59893
  } else {
59811
- terminal.write(text + "\n");
59894
+ terminal.write(output2);
59812
59895
  }
59813
59896
  return 0;
59814
59897
  }, "run")
59815
59898
  });
59816
59899
  }
59817
- __name(createCommand$d, "createCommand$d");
59818
- function createCommand$c(kernel, shell, terminal) {
59900
+ __name(createCommand$h, "createCommand$h");
59901
+ function createCommand$g(kernel, shell, terminal) {
59902
+ return new TerminalCommand({
59903
+ command: "grep",
59904
+ description: "Search for patterns in files or standard input",
59905
+ kernel,
59906
+ shell,
59907
+ terminal,
59908
+ options: [
59909
+ { name: "help", type: Boolean, description: kernel.i18n.t("Display help") },
59910
+ { name: "ignore-case", type: Boolean, alias: "i", description: "Ignore case distinctions" },
59911
+ { name: "line-number", type: Boolean, alias: "n", description: "Print line number with output lines" },
59912
+ { name: "args", type: String, defaultOption: true, multiple: true, description: "Pattern and file(s) to search" }
59913
+ ],
59914
+ run: /* @__PURE__ */ __name(async (argv, process2) => {
59915
+ if (!process2) return 1;
59916
+ const args = argv.args || [];
59917
+ if (args.length === 0 || !args[0]) {
59918
+ await writelnStderr(process2, terminal, "grep: pattern is required");
59919
+ return 1;
59920
+ }
59921
+ const pattern2 = args[0];
59922
+ const files = args.slice(1);
59923
+ const ignoreCase = argv["ignore-case"] || false;
59924
+ const showLineNumbers = argv["line-number"] || false;
59925
+ const flags = ignoreCase ? "i" : "";
59926
+ let regex;
59927
+ try {
59928
+ regex = new RegExp(pattern2, flags);
59929
+ } catch (error) {
59930
+ await writelnStderr(process2, terminal, `grep: invalid pattern: ${error instanceof Error ? error.message : "Unknown error"}`);
59931
+ return 1;
59932
+ }
59933
+ const writer = process2.stdout.getWriter();
59934
+ let exitCode = 0;
59935
+ try {
59936
+ if (files.length === 0) {
59937
+ if (!process2.stdin) {
59938
+ await writelnStderr(process2, terminal, "grep: No input provided");
59939
+ return 1;
59940
+ }
59941
+ const reader = process2.stdin.getReader();
59942
+ let currentLineNumber = 1;
59943
+ let buffer2 = "";
59944
+ try {
59945
+ while (true) {
59946
+ const { done, value } = await reader.read();
59947
+ if (done) break;
59948
+ const chunk = new TextDecoder().decode(value, { stream: true });
59949
+ buffer2 += chunk;
59950
+ const lines = buffer2.split("\n");
59951
+ buffer2 = lines.pop() || "";
59952
+ for (const line3 of lines) {
59953
+ if (regex.test(line3)) {
59954
+ const output2 = showLineNumbers ? `${currentLineNumber}:${line3}
59955
+ ` : `${line3}
59956
+ `;
59957
+ await writer.write(new TextEncoder().encode(output2));
59958
+ }
59959
+ currentLineNumber++;
59960
+ }
59961
+ }
59962
+ if (buffer2 && regex.test(buffer2)) {
59963
+ const output2 = showLineNumbers ? `${currentLineNumber}:${buffer2}
59964
+ ` : `${buffer2}
59965
+ `;
59966
+ await writer.write(new TextEncoder().encode(output2));
59967
+ }
59968
+ } finally {
59969
+ reader.releaseLock();
59970
+ }
59971
+ } else {
59972
+ for (const file of files) {
59973
+ const fullPath = path$1.resolve(shell.cwd, file);
59974
+ let interrupted = false;
59975
+ const interruptHandler = /* @__PURE__ */ __name(() => {
59976
+ interrupted = true;
59977
+ }, "interruptHandler");
59978
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler);
59979
+ try {
59980
+ if (fullPath.startsWith("/dev")) {
59981
+ await writelnStderr(process2, terminal, `grep: ${file}: cannot search device files`);
59982
+ exitCode = 1;
59983
+ continue;
59984
+ }
59985
+ const handle = await shell.context.fs.promises.open(fullPath, "r");
59986
+ const stat2 = await shell.context.fs.promises.stat(fullPath);
59987
+ let bytesRead = 0;
59988
+ const chunkSize = 1024;
59989
+ let buffer2 = "";
59990
+ let currentLineNumber = 1;
59991
+ while (bytesRead < stat2.size) {
59992
+ if (interrupted) break;
59993
+ const data = new Uint8Array(chunkSize);
59994
+ const readSize = Math.min(chunkSize, stat2.size - bytesRead);
59995
+ await handle.read(data, 0, readSize, bytesRead);
59996
+ const chunk = data.subarray(0, readSize);
59997
+ const text = new TextDecoder().decode(chunk, { stream: true });
59998
+ buffer2 += text;
59999
+ const lines = buffer2.split("\n");
60000
+ buffer2 = lines.pop() || "";
60001
+ for (const line3 of lines) {
60002
+ if (regex.test(line3)) {
60003
+ const prefix = files.length > 1 ? `${file}:` : "";
60004
+ const lineNumPrefix = showLineNumbers ? `${currentLineNumber}:` : "";
60005
+ const output2 = `${prefix}${lineNumPrefix}${line3}
60006
+ `;
60007
+ await writer.write(new TextEncoder().encode(output2));
60008
+ }
60009
+ currentLineNumber++;
60010
+ }
60011
+ bytesRead += readSize;
60012
+ }
60013
+ if (buffer2 && regex.test(buffer2)) {
60014
+ const prefix = files.length > 1 ? `${file}:` : "";
60015
+ const lineNumPrefix = showLineNumbers ? `${currentLineNumber}:` : "";
60016
+ const output2 = `${prefix}${lineNumPrefix}${buffer2}
60017
+ `;
60018
+ await writer.write(new TextEncoder().encode(output2));
60019
+ }
60020
+ } catch (error) {
60021
+ await writelnStderr(process2, terminal, `grep: ${file}: ${error instanceof Error ? error.message : "Unknown error"}`);
60022
+ exitCode = 1;
60023
+ } finally {
60024
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler);
60025
+ }
60026
+ }
60027
+ }
60028
+ return exitCode;
60029
+ } finally {
60030
+ writer.releaseLock();
60031
+ }
60032
+ }, "run")
60033
+ });
60034
+ }
60035
+ __name(createCommand$g, "createCommand$g");
60036
+ function createCommand$f(kernel, shell, terminal) {
60037
+ return new TerminalCommand({
60038
+ command: "head",
60039
+ description: "Print the first lines of files",
60040
+ kernel,
60041
+ shell,
60042
+ terminal,
60043
+ options: [
60044
+ { name: "help", type: Boolean, description: kernel.i18n.t("Display help") },
60045
+ { name: "lines", type: Number, alias: "n", description: "Print the first NUM lines instead of the first 10" },
60046
+ { name: "path", type: String, typeLabel: "{underline path}", defaultOption: true, multiple: true, description: "The path(s) to the file(s) to read" }
60047
+ ],
60048
+ run: /* @__PURE__ */ __name(async (argv, process2) => {
60049
+ if (!process2) return 1;
60050
+ const writer = process2.stdout.getWriter();
60051
+ const numLines = argv.lines ?? 10;
60052
+ try {
60053
+ if (!argv.path || !argv.path[0]) {
60054
+ if (!process2.stdin) {
60055
+ return 0;
60056
+ }
60057
+ const reader = process2.stdin.getReader();
60058
+ const decoder2 = new TextDecoder();
60059
+ const lines = [];
60060
+ let buffer2 = "";
60061
+ try {
60062
+ while (true) {
60063
+ let readResult;
60064
+ try {
60065
+ readResult = await reader.read();
60066
+ } catch (error) {
60067
+ if (error instanceof Error) {
60068
+ throw error;
60069
+ }
60070
+ break;
60071
+ }
60072
+ const { done, value } = readResult;
60073
+ if (done) {
60074
+ buffer2 += decoder2.decode();
60075
+ break;
60076
+ }
60077
+ if (value) {
60078
+ buffer2 += decoder2.decode(value, { stream: true });
60079
+ const newLines = buffer2.split("\n");
60080
+ buffer2 = newLines.pop() || "";
60081
+ lines.push(...newLines);
60082
+ if (lines.length >= numLines) break;
60083
+ }
60084
+ }
60085
+ if (buffer2 && lines.length < numLines) {
60086
+ lines.push(buffer2);
60087
+ }
60088
+ } finally {
60089
+ try {
60090
+ reader.releaseLock();
60091
+ } catch {
60092
+ }
60093
+ }
60094
+ const output2 = lines.slice(0, numLines).join("\n");
60095
+ if (output2) {
60096
+ await writer.write(new TextEncoder().encode(output2 + "\n"));
60097
+ }
60098
+ return 0;
60099
+ }
60100
+ const files = argv.path || [];
60101
+ const isMultipleFiles = files.length > 1;
60102
+ for (let i = 0; i < files.length; i++) {
60103
+ const file = files[i];
60104
+ if (!file) continue;
60105
+ const fullPath = path$1.resolve(shell.cwd, file);
60106
+ if (isMultipleFiles) {
60107
+ const header = i > 0 ? "\n" : "";
60108
+ await writer.write(new TextEncoder().encode(`${header}==> ${file} <==
60109
+ `));
60110
+ }
60111
+ let interrupted = false;
60112
+ const interruptHandler = /* @__PURE__ */ __name(() => {
60113
+ interrupted = true;
60114
+ }, "interruptHandler");
60115
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler);
60116
+ try {
60117
+ if (!fullPath.startsWith("/dev")) {
60118
+ const handle = await shell.context.fs.promises.open(fullPath, "r");
60119
+ const stat2 = await shell.context.fs.promises.stat(fullPath);
60120
+ const decoder2 = new TextDecoder();
60121
+ const lines = [];
60122
+ let buffer2 = "";
60123
+ let bytesRead = 0;
60124
+ const chunkSize = 1024;
60125
+ while (bytesRead < stat2.size && lines.length < numLines) {
60126
+ if (interrupted) break;
60127
+ const data = new Uint8Array(chunkSize);
60128
+ const readSize = Math.min(chunkSize, stat2.size - bytesRead);
60129
+ await handle.read(data, 0, readSize, bytesRead);
60130
+ const chunk = data.subarray(0, readSize);
60131
+ buffer2 += decoder2.decode(chunk, { stream: true });
60132
+ const newLines = buffer2.split("\n");
60133
+ buffer2 = newLines.pop() || "";
60134
+ lines.push(...newLines);
60135
+ bytesRead += readSize;
60136
+ if (lines.length >= numLines) break;
60137
+ }
60138
+ if (buffer2 && lines.length < numLines) {
60139
+ lines.push(buffer2);
60140
+ }
60141
+ const output2 = lines.slice(0, numLines).join("\n");
60142
+ if (output2) {
60143
+ await writer.write(new TextEncoder().encode(output2 + "\n"));
60144
+ }
60145
+ } else {
60146
+ const device = await shell.context.fs.promises.open(fullPath);
60147
+ const decoder2 = new TextDecoder();
60148
+ const lines = [];
60149
+ let buffer2 = "";
60150
+ const chunkSize = 1024;
60151
+ const data = new Uint8Array(chunkSize);
60152
+ let bytesRead = 0;
60153
+ do {
60154
+ if (interrupted) break;
60155
+ const result = await device.read(data);
60156
+ bytesRead = result.bytesRead;
60157
+ if (bytesRead > 0) {
60158
+ buffer2 += decoder2.decode(data.subarray(0, bytesRead), { stream: true });
60159
+ const newLines = buffer2.split("\n");
60160
+ buffer2 = newLines.pop() || "";
60161
+ lines.push(...newLines);
60162
+ if (lines.length >= numLines) break;
60163
+ }
60164
+ } while (bytesRead > 0 && lines.length < numLines);
60165
+ if (buffer2 && lines.length < numLines) {
60166
+ lines.push(buffer2);
60167
+ }
60168
+ const output2 = lines.slice(0, numLines).join("\n");
60169
+ if (output2) {
60170
+ await writer.write(new TextEncoder().encode(output2 + "\n"));
60171
+ }
60172
+ }
60173
+ } finally {
60174
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler);
60175
+ }
60176
+ }
60177
+ return 0;
60178
+ } finally {
60179
+ writer.releaseLock();
60180
+ }
60181
+ }, "run")
60182
+ });
60183
+ }
60184
+ __name(createCommand$f, "createCommand$f");
60185
+ function createCommand$e(kernel, shell, terminal) {
59819
60186
  return new TerminalCommand({
59820
60187
  command: "ln",
59821
60188
  description: "Create links between files",
@@ -59907,7 +60274,7 @@ function createCommand$c(kernel, shell, terminal) {
59907
60274
  }, "run")
59908
60275
  });
59909
60276
  }
59910
- __name(createCommand$c, "createCommand$c");
60277
+ __name(createCommand$e, "createCommand$e");
59911
60278
  var humanFormat$2 = { exports: {} };
59912
60279
  var humanFormat$1 = humanFormat$2.exports;
59913
60280
  var hasRequiredHumanFormat;
@@ -60188,7 +60555,7 @@ function requireHumanFormat() {
60188
60555
  __name(requireHumanFormat, "requireHumanFormat");
60189
60556
  var humanFormatExports = requireHumanFormat();
60190
60557
  const humanFormat = /* @__PURE__ */ getDefaultExportFromCjs(humanFormatExports);
60191
- function createCommand$b(kernel, shell, terminal) {
60558
+ function createCommand$d(kernel, shell, terminal) {
60192
60559
  return new TerminalCommand({
60193
60560
  command: "ls",
60194
60561
  description: "List directory contents",
@@ -60324,8 +60691,10 @@ function createCommand$b(kernel, shell, terminal) {
60324
60691
  const linkInfo = getLinkInfo(file.linkTarget, file.linkStats, file.stats);
60325
60692
  if (linkInfo) return linkInfo;
60326
60693
  if (descriptions.has(path$1.resolve(fullPath, file.name))) return descriptions.get(path$1.resolve(fullPath, file.name));
60327
- const ext = file.name.split(".").pop();
60328
- if (ext && descriptions.has("." + ext)) return descriptions.get("." + ext);
60694
+ if (file.name.includes(".")) {
60695
+ const ext = file.name.split(".").pop();
60696
+ if (ext && descriptions.has("." + ext)) return descriptions.get("." + ext);
60697
+ }
60329
60698
  if (!file.stats) return "";
60330
60699
  if (file.stats.isBlockDevice() || file.stats.isCharacterDevice()) ;
60331
60700
  return "";
@@ -60353,8 +60722,8 @@ function createCommand$b(kernel, shell, terminal) {
60353
60722
  }, "run")
60354
60723
  });
60355
60724
  }
60356
- __name(createCommand$b, "createCommand$b");
60357
- function createCommand$a(kernel, shell, terminal) {
60725
+ __name(createCommand$d, "createCommand$d");
60726
+ function createCommand$c(kernel, shell, terminal) {
60358
60727
  return new TerminalCommand({
60359
60728
  command: "mkdir",
60360
60729
  description: "Create a directory",
@@ -60373,8 +60742,8 @@ function createCommand$a(kernel, shell, terminal) {
60373
60742
  }, "run")
60374
60743
  });
60375
60744
  }
60376
- __name(createCommand$a, "createCommand$a");
60377
- function createCommand$9(kernel, shell, terminal) {
60745
+ __name(createCommand$c, "createCommand$c");
60746
+ function createCommand$b(kernel, shell, terminal) {
60378
60747
  return new TerminalCommand({
60379
60748
  command: "mv",
60380
60749
  description: "Move or rename files",
@@ -60413,8 +60782,8 @@ function createCommand$9(kernel, shell, terminal) {
60413
60782
  }, "run")
60414
60783
  });
60415
60784
  }
60416
- __name(createCommand$9, "createCommand$9");
60417
- function createCommand$8(kernel, shell, terminal) {
60785
+ __name(createCommand$b, "createCommand$b");
60786
+ function createCommand$a(kernel, shell, terminal) {
60418
60787
  return new TerminalCommand({
60419
60788
  command: "pwd",
60420
60789
  description: "Print the shell working directory",
@@ -60430,8 +60799,8 @@ function createCommand$8(kernel, shell, terminal) {
60430
60799
  }, "run")
60431
60800
  });
60432
60801
  }
60433
- __name(createCommand$8, "createCommand$8");
60434
- function createCommand$7(kernel, shell, terminal) {
60802
+ __name(createCommand$a, "createCommand$a");
60803
+ function createCommand$9(kernel, shell, terminal) {
60435
60804
  return new TerminalCommand({
60436
60805
  command: "rm",
60437
60806
  description: "Remove files or directories",
@@ -60451,8 +60820,8 @@ function createCommand$7(kernel, shell, terminal) {
60451
60820
  }, "run")
60452
60821
  });
60453
60822
  }
60454
- __name(createCommand$7, "createCommand$7");
60455
- function createCommand$6(kernel, shell, terminal) {
60823
+ __name(createCommand$9, "createCommand$9");
60824
+ function createCommand$8(kernel, shell, terminal) {
60456
60825
  return new TerminalCommand({
60457
60826
  command: "rmdir",
60458
60827
  description: "Remove a directory",
@@ -60471,7 +60840,7 @@ function createCommand$6(kernel, shell, terminal) {
60471
60840
  }, "run")
60472
60841
  });
60473
60842
  }
60474
- __name(createCommand$6, "createCommand$6");
60843
+ __name(createCommand$8, "createCommand$8");
60475
60844
  const MAX_32_BITS = 4294967295;
60476
60845
  const MAX_16_BITS = 65535;
60477
60846
  const MAX_8_BITS = 255;
@@ -65266,7 +65635,7 @@ const table = {
65266
65635
  return mimeTypes;
65267
65636
  })();
65268
65637
  t(configure);
65269
- function createCommand$5(kernel, shell, terminal) {
65638
+ function createCommand$7(kernel, shell, terminal) {
65270
65639
  return new TerminalCommand({
65271
65640
  command: "stat",
65272
65641
  description: "Display information about a file or directory",
@@ -65296,8 +65665,8 @@ function createCommand$5(kernel, shell, terminal) {
65296
65665
  }, "run")
65297
65666
  });
65298
65667
  }
65299
- __name(createCommand$5, "createCommand$5");
65300
- function createCommand$4(kernel, shell, terminal) {
65668
+ __name(createCommand$7, "createCommand$7");
65669
+ function createCommand$6(kernel, shell, terminal) {
65301
65670
  return new TerminalCommand({
65302
65671
  command: "touch",
65303
65672
  description: "Create an empty file",
@@ -65316,37 +65685,84 @@ function createCommand$4(kernel, shell, terminal) {
65316
65685
  }, "run")
65317
65686
  });
65318
65687
  }
65319
- __name(createCommand$4, "createCommand$4");
65320
- function createCommand$3(kernel, shell, terminal) {
65688
+ __name(createCommand$6, "createCommand$6");
65689
+ function createCommand$5(kernel, shell, terminal) {
65321
65690
  return new TerminalCommand({
65322
65691
  command: "hex",
65323
- description: "Display file contents in hexadecimal format",
65692
+ description: "Display file contents or stdin in hexadecimal format",
65324
65693
  kernel,
65325
65694
  shell,
65326
65695
  terminal,
65327
65696
  options: [
65328
65697
  { name: "help", type: Boolean, description: kernel.i18n.t("Display help") },
65329
- { name: "path", type: String, typeLabel: "{underline path}", defaultOption: true, description: "The path to the file to display" }
65698
+ { name: "path", type: String, typeLabel: "{underline path}", defaultOption: true, description: "The path to the file to display (if omitted, reads from stdin)" }
65330
65699
  ],
65331
65700
  run: /* @__PURE__ */ __name(async (argv, process2) => {
65701
+ if (!process2) return 1;
65332
65702
  const filePath = argv.path;
65333
- if (!filePath) {
65334
- await writelnStderr(process2, terminal, "Usage: hex <file>");
65335
- return 1;
65336
- }
65337
- const fullPath = path$1.resolve(shell.cwd, filePath);
65703
+ let data;
65338
65704
  try {
65339
- const exists2 = await shell.context.fs.promises.exists(fullPath);
65340
- if (!exists2) {
65341
- await writelnStderr(process2, terminal, `hex: ${filePath}: No such file or directory`);
65342
- return 1;
65343
- }
65344
- const stats = await shell.context.fs.promises.stat(fullPath);
65345
- if (stats.isDirectory()) {
65346
- await writelnStderr(process2, terminal, `hex: ${filePath}: Is a directory`);
65347
- return 1;
65705
+ if (!filePath) {
65706
+ if (!process2.stdin) {
65707
+ await writelnStderr(process2, terminal, "Usage: hex <file>");
65708
+ await writelnStderr(process2, terminal, " or: <command> | hex");
65709
+ return 1;
65710
+ }
65711
+ if (process2.stdinIsTTY) {
65712
+ await writelnStderr(process2, terminal, "Usage: hex <file>");
65713
+ await writelnStderr(process2, terminal, " or: <command> | hex");
65714
+ return 1;
65715
+ }
65716
+ const reader = process2.stdin.getReader();
65717
+ const chunks = [];
65718
+ try {
65719
+ const first = await reader.read();
65720
+ if (first.done && !first.value) {
65721
+ await writelnStderr(process2, terminal, "Usage: hex <file>");
65722
+ await writelnStderr(process2, terminal, " or: <command> | hex");
65723
+ return 1;
65724
+ }
65725
+ if (first.value) {
65726
+ chunks.push(first.value);
65727
+ }
65728
+ if (!first.done) {
65729
+ while (true) {
65730
+ const { done, value } = await reader.read();
65731
+ if (done) break;
65732
+ if (value) {
65733
+ chunks.push(value);
65734
+ }
65735
+ }
65736
+ }
65737
+ } finally {
65738
+ reader.releaseLock();
65739
+ }
65740
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
65741
+ if (totalLength === 0) {
65742
+ await writelnStderr(process2, terminal, "Usage: hex <file>");
65743
+ await writelnStderr(process2, terminal, " or: <command> | hex");
65744
+ return 1;
65745
+ }
65746
+ data = new Uint8Array(totalLength);
65747
+ let offset = 0;
65748
+ for (const chunk of chunks) {
65749
+ data.set(chunk, offset);
65750
+ offset += chunk.length;
65751
+ }
65752
+ } else {
65753
+ const fullPath = path$1.resolve(shell.cwd, filePath);
65754
+ const exists2 = await shell.context.fs.promises.exists(fullPath);
65755
+ if (!exists2) {
65756
+ await writelnStderr(process2, terminal, `hex: ${filePath}: No such file or directory`);
65757
+ return 1;
65758
+ }
65759
+ const stats = await shell.context.fs.promises.stat(fullPath);
65760
+ if (stats.isDirectory()) {
65761
+ await writelnStderr(process2, terminal, `hex: ${filePath}: Is a directory`);
65762
+ return 1;
65763
+ }
65764
+ data = await shell.context.fs.promises.readFile(fullPath);
65348
65765
  }
65349
- const data = await shell.context.fs.promises.readFile(fullPath);
65350
65766
  const bytesPerLine = 16;
65351
65767
  for (let offset = 0; offset < data.length; offset += bytesPerLine) {
65352
65768
  const lineBytes = data.slice(offset, offset + bytesPerLine);
@@ -65383,13 +65799,14 @@ function createCommand$3(kernel, shell, terminal) {
65383
65799
  }
65384
65800
  return 0;
65385
65801
  } catch (error) {
65386
- await writelnStderr(process2, terminal, `hex: ${filePath}: ${error instanceof Error ? error.message : "Unknown error"}`);
65802
+ const errorPath = filePath || "stdin";
65803
+ await writelnStderr(process2, terminal, `hex: ${errorPath}: ${error instanceof Error ? error.message : "Unknown error"}`);
65387
65804
  return 1;
65388
65805
  }
65389
65806
  }, "run")
65390
65807
  });
65391
65808
  }
65392
- __name(createCommand$3, "createCommand$3");
65809
+ __name(createCommand$5, "createCommand$5");
65393
65810
  const csi = "\x1B[";
65394
65811
  const ansi = {};
65395
65812
  ansi.style = {
@@ -65567,7 +65984,7 @@ ansi.erase = {
65567
65984
  return csi + (n || 0) + "K";
65568
65985
  }, "inLine")
65569
65986
  };
65570
- function createCommand$2(kernel, shell, terminal) {
65987
+ function createCommand$4(kernel, shell, terminal) {
65571
65988
  return new TerminalCommand({
65572
65989
  command: "less",
65573
65990
  description: "View file contents interactively",
@@ -65730,7 +66147,182 @@ function createCommand$2(kernel, shell, terminal) {
65730
66147
  }, "run")
65731
66148
  });
65732
66149
  }
65733
- __name(createCommand$2, "createCommand$2");
66150
+ __name(createCommand$4, "createCommand$4");
66151
+ function createCommand$3(kernel, shell, terminal) {
66152
+ return new TerminalCommand({
66153
+ command: "passkey",
66154
+ description: "Manage passkey credentials for WebAuthn authentication",
66155
+ kernel,
66156
+ shell,
66157
+ terminal,
66158
+ options: [
66159
+ { name: "help", type: Boolean, description: kernel.i18n.t("Display help") },
66160
+ { name: "subcommand", type: String, defaultOption: true, description: "Subcommand: register, list, remove, remove-all" },
66161
+ { name: "name", type: String, description: "Name/description for the passkey (used with register)" },
66162
+ { name: "id", type: String, description: "Passkey ID to remove (used with remove)" }
66163
+ ],
66164
+ run: /* @__PURE__ */ __name(async (argv, process2) => {
66165
+ if (!process2) return 1;
66166
+ const currentUid = shell.credentials.uid;
66167
+ const user2 = kernel.users.get(currentUid);
66168
+ if (!user2) {
66169
+ await writelnStderr(process2, terminal, chalk$1.red("Error: Current user not found"));
66170
+ return 1;
66171
+ }
66172
+ const subcommand = argv.subcommand?.toLowerCase();
66173
+ if (!subcommand || subcommand === "help" || argv.help) {
66174
+ await writelnStdout(process2, terminal, "Usage: passkey <subcommand> [options]");
66175
+ await writelnStdout(process2, terminal, "");
66176
+ await writelnStdout(process2, terminal, "Subcommands:");
66177
+ await writelnStdout(process2, terminal, " register [--name <name>] Register a new passkey");
66178
+ await writelnStdout(process2, terminal, " list List all registered passkeys");
66179
+ await writelnStdout(process2, terminal, " remove --id <id> Remove a specific passkey");
66180
+ await writelnStdout(process2, terminal, " remove-all Remove all passkeys");
66181
+ return 0;
66182
+ }
66183
+ try {
66184
+ switch (subcommand) {
66185
+ case "register": {
66186
+ if (!kernel.auth.passkey.isSupported()) {
66187
+ await writelnStderr(process2, terminal, chalk$1.red("Error: WebAuthn is not supported in this browser"));
66188
+ return 1;
66189
+ }
66190
+ const name = argv.name || void 0;
66191
+ const username = user2.username;
66192
+ const userId = new TextEncoder().encode(username);
66193
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
66194
+ const rpId = globalThis.location.hostname || "localhost";
66195
+ const createOptions = {
66196
+ challenge,
66197
+ rp: {
66198
+ name: kernel.name || "ecmaOS",
66199
+ id: rpId
66200
+ },
66201
+ user: {
66202
+ id: userId,
66203
+ name: username,
66204
+ displayName: username
66205
+ },
66206
+ pubKeyCredParams: [
66207
+ { type: "public-key", alg: -7 },
66208
+ { type: "public-key", alg: -257 }
66209
+ ],
66210
+ authenticatorSelection: {
66211
+ userVerification: "preferred"
66212
+ },
66213
+ timeout: 6e4
66214
+ };
66215
+ await writelnStdout(process2, terminal, chalk$1.yellow("Please interact with your authenticator to register a passkey..."));
66216
+ const credential = await kernel.auth.passkey.create(createOptions);
66217
+ if (!credential || !(credential instanceof PublicKeyCredential)) {
66218
+ await writelnStderr(process2, terminal, chalk$1.red("Error: Failed to create passkey. Registration cancelled or failed."));
66219
+ return 1;
66220
+ }
66221
+ const publicKeyCredential = credential;
66222
+ const response = publicKeyCredential.response;
66223
+ const credentialId = btoa(String.fromCharCode(...new Uint8Array(publicKeyCredential.rawId)));
66224
+ let publicKeyArray;
66225
+ try {
66226
+ if (typeof response.getPublicKey === "function") {
66227
+ try {
66228
+ const publicKeyCrypto = response.getPublicKey();
66229
+ if (publicKeyCrypto && publicKeyCrypto instanceof CryptoKey) {
66230
+ const publicKeyJwk = await crypto.subtle.exportKey("jwk", publicKeyCrypto);
66231
+ publicKeyArray = new TextEncoder().encode(JSON.stringify(publicKeyJwk));
66232
+ } else {
66233
+ throw new Error("getPublicKey() did not return a valid CryptoKey");
66234
+ }
66235
+ } catch (exportError) {
66236
+ await writelnStderr(process2, terminal, chalk$1.yellow(`Warning: Could not extract public key via getPublicKey(): ${exportError instanceof Error ? exportError.message : String(exportError)}. Using attestationObject instead.`));
66237
+ const attestationObject = response.attestationObject;
66238
+ publicKeyArray = new Uint8Array(attestationObject);
66239
+ }
66240
+ } else {
66241
+ const attestationObject = response.attestationObject;
66242
+ publicKeyArray = new Uint8Array(attestationObject);
66243
+ }
66244
+ } catch (error) {
66245
+ await writelnStderr(process2, terminal, chalk$1.red(`Error processing credential data: ${error instanceof Error ? error.message : String(error)}`));
66246
+ return 1;
66247
+ }
66248
+ const passkey = {
66249
+ id: crypto.randomUUID(),
66250
+ credentialId,
66251
+ publicKey: publicKeyArray,
66252
+ createdAt: Date.now(),
66253
+ name
66254
+ };
66255
+ await kernel.users.addPasskey(currentUid, passkey);
66256
+ await writelnStdout(process2, terminal, chalk$1.green(`Passkey registered successfully${name ? `: ${name}` : ""}`));
66257
+ await writelnStdout(process2, terminal, `Passkey ID: ${passkey.id}`);
66258
+ return 0;
66259
+ }
66260
+ case "list": {
66261
+ const passkeys = await kernel.users.getPasskeys(currentUid);
66262
+ if (passkeys.length === 0) {
66263
+ await writelnStdout(process2, terminal, "No passkeys registered for this user.");
66264
+ return 0;
66265
+ }
66266
+ await writelnStdout(process2, terminal, `Registered passkeys (${passkeys.length}):`);
66267
+ await writelnStdout(process2, terminal, "");
66268
+ for (const pk of passkeys) {
66269
+ const createdDate = new Date(pk.createdAt).toLocaleString();
66270
+ const lastUsedDate = pk.lastUsed ? new Date(pk.lastUsed).toLocaleString() : "Never";
66271
+ await writelnStdout(process2, terminal, ` ID: ${pk.id}`);
66272
+ if (pk.name) {
66273
+ await writelnStdout(process2, terminal, ` Name: ${pk.name}`);
66274
+ }
66275
+ await writelnStdout(process2, terminal, ` Created: ${createdDate}`);
66276
+ await writelnStdout(process2, terminal, ` Last used: ${lastUsedDate}`);
66277
+ await writelnStdout(process2, terminal, "");
66278
+ }
66279
+ return 0;
66280
+ }
66281
+ case "remove": {
66282
+ const id = argv.id;
66283
+ if (!id) {
66284
+ await writelnStderr(process2, terminal, chalk$1.red("Error: --id is required for remove command"));
66285
+ await writelnStdout(process2, terminal, "Usage: passkey remove --id <id>");
66286
+ return 1;
66287
+ }
66288
+ const passkeys = await kernel.users.getPasskeys(currentUid);
66289
+ const passkey = passkeys.find((pk) => pk.id === id);
66290
+ if (!passkey) {
66291
+ await writelnStderr(process2, terminal, chalk$1.red(`Error: Passkey with ID ${id} not found`));
66292
+ return 1;
66293
+ }
66294
+ await kernel.users.removePasskey(currentUid, id);
66295
+ await writelnStdout(process2, terminal, chalk$1.green(`Passkey removed successfully${passkey.name ? `: ${passkey.name}` : ""}`));
66296
+ return 0;
66297
+ }
66298
+ case "remove-all": {
66299
+ const passkeys = await kernel.users.getPasskeys(currentUid);
66300
+ if (passkeys.length === 0) {
66301
+ await writelnStdout(process2, terminal, "No passkeys to remove.");
66302
+ return 0;
66303
+ }
66304
+ await kernel.users.savePasskeys(currentUid, []);
66305
+ const passkeysPath = `${user2.home}/.passkeys`;
66306
+ try {
66307
+ await kernel.filesystem.fs.unlink(passkeysPath);
66308
+ } catch {
66309
+ }
66310
+ await writelnStdout(process2, terminal, chalk$1.green(`Removed ${passkeys.length} passkey(s)`));
66311
+ return 0;
66312
+ }
66313
+ default:
66314
+ await writelnStderr(process2, terminal, chalk$1.red(`Error: Unknown subcommand: ${subcommand}`));
66315
+ await writelnStdout(process2, terminal, 'Run "passkey help" for usage information');
66316
+ return 1;
66317
+ }
66318
+ } catch (error) {
66319
+ await writelnStderr(process2, terminal, chalk$1.red(`Error: ${error instanceof Error ? error.message : String(error)}`));
66320
+ return 1;
66321
+ }
66322
+ }, "run")
66323
+ });
66324
+ }
66325
+ __name(createCommand$3, "createCommand$3");
65734
66326
  function parseSedExpression(expr) {
65735
66327
  expr = expr.trim();
65736
66328
  const substituteMatch = expr.match(/^(\d+)?(,(\d+|\$))?s\/(.+?)\/(.*?)\/([gip]*\d*)$/);
@@ -65893,7 +66485,7 @@ function applySedCommand(line3, lineNum, totalLines, command) {
65893
66485
  return { result: line3, shouldPrint: false };
65894
66486
  }
65895
66487
  __name(applySedCommand, "applySedCommand");
65896
- function createCommand$1(kernel, shell, terminal) {
66488
+ function createCommand$2(kernel, shell, terminal) {
65897
66489
  return new TerminalCommand({
65898
66490
  command: "sed",
65899
66491
  description: "Stream editor for filtering and transforming text",
@@ -66088,8 +66680,8 @@ function createCommand$1(kernel, shell, terminal) {
66088
66680
  }, "run")
66089
66681
  });
66090
66682
  }
66091
- __name(createCommand$1, "createCommand$1");
66092
- function createCommand(kernel, shell, terminal) {
66683
+ __name(createCommand$2, "createCommand$2");
66684
+ function createCommand$1(kernel, shell, terminal) {
66093
66685
  return new TerminalCommand({
66094
66686
  command: "tee",
66095
66687
  description: "Read from standard input and write to standard output and files",
@@ -66165,27 +66757,173 @@ function createCommand(kernel, shell, terminal) {
66165
66757
  }, "run")
66166
66758
  });
66167
66759
  }
66760
+ __name(createCommand$1, "createCommand$1");
66761
+ function createCommand(kernel, shell, terminal) {
66762
+ return new TerminalCommand({
66763
+ command: "tail",
66764
+ description: "Print the last lines of files",
66765
+ kernel,
66766
+ shell,
66767
+ terminal,
66768
+ options: [
66769
+ { name: "help", type: Boolean, description: kernel.i18n.t("Display help") },
66770
+ { name: "lines", type: Number, alias: "n", description: "Print the last NUM lines instead of the last 10" },
66771
+ { name: "path", type: String, typeLabel: "{underline path}", defaultOption: true, multiple: true, description: "The path(s) to the file(s) to read" }
66772
+ ],
66773
+ run: /* @__PURE__ */ __name(async (argv, process2) => {
66774
+ if (!process2) return 1;
66775
+ const writer = process2.stdout.getWriter();
66776
+ const numLines = argv.lines ?? 10;
66777
+ try {
66778
+ if (!argv.path || !argv.path[0]) {
66779
+ if (!process2.stdin) {
66780
+ return 0;
66781
+ }
66782
+ const reader = process2.stdin.getReader();
66783
+ const decoder2 = new TextDecoder();
66784
+ const lines = [];
66785
+ let buffer2 = "";
66786
+ try {
66787
+ while (true) {
66788
+ let readResult;
66789
+ try {
66790
+ readResult = await reader.read();
66791
+ } catch (error) {
66792
+ if (error instanceof Error) {
66793
+ throw error;
66794
+ }
66795
+ break;
66796
+ }
66797
+ const { done, value } = readResult;
66798
+ if (done) {
66799
+ buffer2 += decoder2.decode();
66800
+ break;
66801
+ }
66802
+ if (value) {
66803
+ buffer2 += decoder2.decode(value, { stream: true });
66804
+ const newLines = buffer2.split("\n");
66805
+ buffer2 = newLines.pop() || "";
66806
+ lines.push(...newLines);
66807
+ }
66808
+ }
66809
+ if (buffer2) {
66810
+ lines.push(buffer2);
66811
+ }
66812
+ } finally {
66813
+ try {
66814
+ reader.releaseLock();
66815
+ } catch {
66816
+ }
66817
+ }
66818
+ const output2 = lines.slice(-numLines).join("\n");
66819
+ if (output2) {
66820
+ await writer.write(new TextEncoder().encode(output2 + "\n"));
66821
+ }
66822
+ return 0;
66823
+ }
66824
+ const files = argv.path || [];
66825
+ const isMultipleFiles = files.length > 1;
66826
+ for (let i = 0; i < files.length; i++) {
66827
+ const file = files[i];
66828
+ if (!file) continue;
66829
+ const fullPath = path$1.resolve(shell.cwd, file);
66830
+ if (isMultipleFiles) {
66831
+ const header = i > 0 ? "\n" : "";
66832
+ await writer.write(new TextEncoder().encode(`${header}==> ${file} <==
66833
+ `));
66834
+ }
66835
+ let interrupted = false;
66836
+ const interruptHandler = /* @__PURE__ */ __name(() => {
66837
+ interrupted = true;
66838
+ }, "interruptHandler");
66839
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler);
66840
+ try {
66841
+ if (!fullPath.startsWith("/dev")) {
66842
+ const handle = await shell.context.fs.promises.open(fullPath, "r");
66843
+ const stat2 = await shell.context.fs.promises.stat(fullPath);
66844
+ const decoder2 = new TextDecoder();
66845
+ let buffer2 = "";
66846
+ let bytesRead = 0;
66847
+ const chunkSize = 1024;
66848
+ while (bytesRead < stat2.size) {
66849
+ if (interrupted) break;
66850
+ const data = new Uint8Array(chunkSize);
66851
+ const readSize = Math.min(chunkSize, stat2.size - bytesRead);
66852
+ await handle.read(data, 0, readSize, bytesRead);
66853
+ const chunk = data.subarray(0, readSize);
66854
+ buffer2 += decoder2.decode(chunk, { stream: true });
66855
+ bytesRead += readSize;
66856
+ }
66857
+ const lines = buffer2.split("\n");
66858
+ if (lines[lines.length - 1] === "") {
66859
+ lines.pop();
66860
+ }
66861
+ const output2 = lines.slice(-numLines).join("\n");
66862
+ if (output2) {
66863
+ await writer.write(new TextEncoder().encode(output2 + "\n"));
66864
+ }
66865
+ } else {
66866
+ const device = await shell.context.fs.promises.open(fullPath);
66867
+ const decoder2 = new TextDecoder();
66868
+ const lines = [];
66869
+ let buffer2 = "";
66870
+ const chunkSize = 1024;
66871
+ const data = new Uint8Array(chunkSize);
66872
+ let bytesRead = 0;
66873
+ do {
66874
+ if (interrupted) break;
66875
+ const result = await device.read(data);
66876
+ bytesRead = result.bytesRead;
66877
+ if (bytesRead > 0) {
66878
+ buffer2 += decoder2.decode(data.subarray(0, bytesRead), { stream: true });
66879
+ }
66880
+ } while (bytesRead > 0);
66881
+ const allLines = buffer2.split("\n");
66882
+ if (allLines[allLines.length - 1] === "") {
66883
+ allLines.pop();
66884
+ }
66885
+ lines.push(...allLines);
66886
+ const output2 = lines.slice(-numLines).join("\n");
66887
+ if (output2) {
66888
+ await writer.write(new TextEncoder().encode(output2 + "\n"));
66889
+ }
66890
+ }
66891
+ } finally {
66892
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler);
66893
+ }
66894
+ }
66895
+ return 0;
66896
+ } finally {
66897
+ writer.releaseLock();
66898
+ }
66899
+ }, "run")
66900
+ });
66901
+ }
66168
66902
  __name(createCommand, "createCommand");
66169
66903
  function createAllCommands(kernel, shell, terminal) {
66170
66904
  return {
66171
- cat: createCommand$h(kernel, shell, terminal),
66172
- cd: createCommand$g(kernel, shell, terminal),
66173
- chmod: createCommand$f(kernel, shell, terminal),
66174
- cp: createCommand$e(kernel, shell, terminal),
66175
- echo: createCommand$d(kernel, shell, terminal),
66176
- ln: createCommand$c(kernel, shell, terminal),
66177
- ls: createCommand$b(kernel, shell, terminal),
66178
- mkdir: createCommand$a(kernel, shell, terminal),
66179
- mv: createCommand$9(kernel, shell, terminal),
66180
- pwd: createCommand$8(kernel, shell, terminal),
66181
- rm: createCommand$7(kernel, shell, terminal),
66182
- rmdir: createCommand$6(kernel, shell, terminal),
66183
- stat: createCommand$5(kernel, shell, terminal),
66184
- touch: createCommand$4(kernel, shell, terminal),
66185
- hex: createCommand$3(kernel, shell, terminal),
66186
- less: createCommand$2(kernel, shell, terminal),
66187
- sed: createCommand$1(kernel, shell, terminal),
66188
- tee: createCommand(kernel, shell, terminal)
66905
+ cat: createCommand$l(kernel, shell, terminal),
66906
+ cd: createCommand$k(kernel, shell, terminal),
66907
+ chmod: createCommand$j(kernel, shell, terminal),
66908
+ cp: createCommand$i(kernel, shell, terminal),
66909
+ echo: createCommand$h(kernel, shell, terminal),
66910
+ grep: createCommand$g(kernel, shell, terminal),
66911
+ head: createCommand$f(kernel, shell, terminal),
66912
+ ln: createCommand$e(kernel, shell, terminal),
66913
+ ls: createCommand$d(kernel, shell, terminal),
66914
+ mkdir: createCommand$c(kernel, shell, terminal),
66915
+ mv: createCommand$b(kernel, shell, terminal),
66916
+ pwd: createCommand$a(kernel, shell, terminal),
66917
+ rm: createCommand$9(kernel, shell, terminal),
66918
+ rmdir: createCommand$8(kernel, shell, terminal),
66919
+ stat: createCommand$7(kernel, shell, terminal),
66920
+ touch: createCommand$6(kernel, shell, terminal),
66921
+ hex: createCommand$5(kernel, shell, terminal),
66922
+ less: createCommand$4(kernel, shell, terminal),
66923
+ passkey: createCommand$3(kernel, shell, terminal),
66924
+ sed: createCommand$2(kernel, shell, terminal),
66925
+ tail: createCommand(kernel, shell, terminal),
66926
+ tee: createCommand$1(kernel, shell, terminal)
66189
66927
  };
66190
66928
  }
66191
66929
  __name(createAllCommands, "createAllCommands");
@@ -66289,7 +67027,7 @@ const TerminalCommands = /* @__PURE__ */ __name((kernel, shell, terminal) => {
66289
67027
  { name: "reinstall", type: Boolean, description: "Reinstall the package if it is already installed" }
66290
67028
  ],
66291
67029
  run: /* @__PURE__ */ __name(async (argv) => {
66292
- const { default: install } = await import("./install-DIY-HQ9n.js");
67030
+ const { default: install } = await import("./install-vHqmAl7t.js");
66293
67031
  return await install({ kernel, shell, terminal, args: [argv.package, argv.registry, argv.reinstall] });
66294
67032
  }, "run")
66295
67033
  }),
@@ -66304,7 +67042,7 @@ const TerminalCommands = /* @__PURE__ */ __name((kernel, shell, terminal) => {
66304
67042
  { name: "package", type: String, typeLabel: "{underline package}", defaultOption: true, description: "The package name and optional version (e.g. package@1.0.0). If no version is specified, all versions will be uninstalled." }
66305
67043
  ],
66306
67044
  run: /* @__PURE__ */ __name(async (argv) => {
66307
- const { default: uninstall } = await import("./uninstall-BOyXf2bF.js");
67045
+ const { default: uninstall } = await import("./uninstall-Ds11Scu8.js");
66308
67046
  return await uninstall({ kernel, shell, terminal, args: [argv.package] });
66309
67047
  }, "run")
66310
67048
  }),
@@ -66675,7 +67413,6 @@ const fetch$1 = /* @__PURE__ */ __name(async ({ shell, terminal, process: proces
66675
67413
  } finally {
66676
67414
  reader.releaseLock();
66677
67415
  writer?.releaseLock();
66678
- await writeStdout(process2, terminal, "\n");
66679
67416
  }
66680
67417
  return 0;
66681
67418
  } catch (error) {
@@ -67545,11 +68282,14 @@ const _Terminal = class _Terminal extends xtermExports.Terminal {
67545
68282
  async readline(prompt = "", hide = false, noListen = false) {
67546
68283
  let input = "";
67547
68284
  let cursor = 0;
67548
- if (!noListen) this.unlisten();
68285
+ const wasListening = this._keyListener !== void 0;
68286
+ this.unlisten();
67549
68287
  this.write(prompt);
67550
68288
  this.focus();
67551
68289
  const result = await new Promise((resolve2) => {
67552
68290
  const disposable = this.onKey(({ domEvent }) => {
68291
+ domEvent.preventDefault();
68292
+ domEvent.stopPropagation();
67553
68293
  switch (domEvent.key) {
67554
68294
  case "Enter":
67555
68295
  disposable.dispose();
@@ -67557,18 +68297,24 @@ const _Terminal = class _Terminal extends xtermExports.Terminal {
67557
68297
  resolve2(input);
67558
68298
  break;
67559
68299
  case "ArrowLeft":
67560
- this.write(ansi$7.cursor.back());
67561
- cursor--;
68300
+ if (cursor > 0) {
68301
+ this.write(ansi$7.cursor.back());
68302
+ cursor--;
68303
+ }
67562
68304
  break;
67563
68305
  case "ArrowRight":
67564
- this.write(ansi$7.cursor.forward());
67565
- cursor++;
68306
+ if (cursor < input.length) {
68307
+ this.write(ansi$7.cursor.forward());
68308
+ cursor++;
68309
+ }
67566
68310
  break;
67567
68311
  case "Home":
67568
- this.write(ansi$7.cursor.horizontalAbsolute(0));
68312
+ this.write(ansi$7.cursor.horizontalAbsolute(prompt.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length + 1));
68313
+ cursor = 0;
67569
68314
  break;
67570
68315
  case "End":
67571
- this.write(ansi$7.cursor.horizontalAbsolute(input.length));
68316
+ this.write(ansi$7.cursor.horizontalAbsolute(prompt.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length + input.length + 1));
68317
+ cursor = input.length;
67572
68318
  break;
67573
68319
  case "Escape":
67574
68320
  disposable.dispose();
@@ -67577,19 +68323,35 @@ const _Terminal = class _Terminal extends xtermExports.Terminal {
67577
68323
  case "Backspace":
67578
68324
  if (cursor > 0) {
67579
68325
  input = input.slice(0, cursor - 1) + input.slice(cursor);
67580
- this.write(ansi$7.cursor.horizontalAbsolute(0) + ansi$7.erase.inLine(2) + ":" + input);
68326
+ const promptLen = prompt.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length;
68327
+ this.write(ansi$7.cursor.horizontalAbsolute(promptLen + 1) + ansi$7.erase.inLine(0) + input);
68328
+ if (cursor < input.length + 1) {
68329
+ this.write(`\x1B[${input.length + 1 - cursor}D`);
68330
+ }
67581
68331
  cursor--;
67582
68332
  } else this.write("\x07");
67583
68333
  break;
67584
68334
  case "Delete":
67585
- if (cursor < input.length) input = input.slice(0, cursor) + input.slice(cursor + 1);
68335
+ if (cursor < input.length) {
68336
+ input = input.slice(0, cursor) + input.slice(cursor + 1);
68337
+ this.write(ansi$7.erase.inLine(0) + input.slice(cursor));
68338
+ if (input.length - cursor > 0) {
68339
+ this.write(`\x1B[${input.length - cursor}D`);
68340
+ }
68341
+ }
67586
68342
  break;
67587
68343
  default:
67588
68344
  if (domEvent.key.length === 1 && !domEvent.ctrlKey && !domEvent.metaKey && !domEvent.altKey) {
67589
68345
  const charCode = domEvent.key.charCodeAt(0);
67590
68346
  if (charCode >= 32 && charCode <= 126) {
67591
68347
  input = input.slice(0, cursor) + domEvent.key + input.slice(cursor);
67592
- if (!hide) this.write(ansi$7.cursor.horizontalAbsolute(0) + ansi$7.erase.inLine(2) + prompt + input);
68348
+ if (!hide) {
68349
+ const promptLen = prompt.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length;
68350
+ this.write(ansi$7.cursor.horizontalAbsolute(promptLen + 1) + ansi$7.erase.inLine(0) + input);
68351
+ if (cursor < input.length - 1) {
68352
+ this.write(`\x1B[${input.length - cursor - 1}D`);
68353
+ }
68354
+ }
67593
68355
  cursor++;
67594
68356
  }
67595
68357
  }
@@ -67598,7 +68360,7 @@ const _Terminal = class _Terminal extends xtermExports.Terminal {
67598
68360
  if (cursor > input.length) cursor = input.length;
67599
68361
  });
67600
68362
  });
67601
- if (!noListen) this.listen();
68363
+ if (wasListening && !noListen) this.listen();
67602
68364
  return result;
67603
68365
  }
67604
68366
  spinner(spinner, prefix, suffix) {
@@ -67804,33 +68566,23 @@ const _Terminal = class _Terminal extends xtermExports.Terminal {
67804
68566
  break;
67805
68567
  case "Backspace":
67806
68568
  if (this._cursorPosition > 0) {
67807
- this._cmd = this._cmd.slice(0, this._cursorPosition - 1) + this._cmd.slice(this._cursorPosition);
68569
+ const tail = this._cmd.slice(this._cursorPosition);
68570
+ this._cmd = this._cmd.slice(0, this._cursorPosition - 1) + tail;
67808
68571
  this._cursorPosition--;
67809
- this.write("\b");
67810
- this.write(this._cmd.slice(this._cursorPosition) + " ");
67811
- this.write(`\x1B[${this._cmd.length - this._cursorPosition + 1}D`);
68572
+ this.write("\b" + ansi$7.erase.inLine(0) + tail);
68573
+ if (tail.length > 0) {
68574
+ this.write(`\x1B[${tail.length}D`);
68575
+ }
67812
68576
  } else this.write("\x07");
67813
68577
  break;
67814
68578
  case "Delete":
67815
68579
  if (this._cursorPosition < this._cmd.length) {
67816
- this._cmd = this._cmd.slice(0, this._cursorPosition) + this._cmd.slice(this._cursorPosition + 1);
67817
- this.write(ansi$7.erase.inLine(2) + ansi$7.cursor.horizontalAbsolute(0));
67818
- if (this._multiLineMode) {
67819
- const parts = this._cmd.split("#");
67820
- if (parts.length > 1) {
67821
- this.write("> " + parts[0] + chalk$2.gray("#" + parts.slice(1).join("#")));
67822
- } else {
67823
- this.write("> " + this._cmd);
67824
- }
67825
- } else {
67826
- const parts = this._cmd.split("#");
67827
- if (parts.length > 1) {
67828
- this.write(this.prompt() + parts[0] + chalk$2.gray("#" + parts.slice(1).join("#")));
67829
- } else {
67830
- this.write(this.prompt() + this._cmd);
67831
- }
68580
+ const tail = this._cmd.slice(this._cursorPosition + 1);
68581
+ this._cmd = this._cmd.slice(0, this._cursorPosition) + tail;
68582
+ this.write(ansi$7.erase.inLine(0) + tail);
68583
+ if (tail.length > 0) {
68584
+ this.write(`\x1B[${tail.length}D`);
67832
68585
  }
67833
- if (this._cursorPosition < this._cmd.length) this.write(`\x1B[${this._cmd.length - this._cursorPosition}D`);
67834
68586
  }
67835
68587
  break;
67836
68588
  case "ArrowUp":
@@ -67933,25 +68685,18 @@ const _Terminal = class _Terminal extends xtermExports.Terminal {
67933
68685
  default:
67934
68686
  this._isTabCycling = false;
67935
68687
  if (key.length === 1) {
68688
+ const wasAtEnd = this._cursorPosition === this._cmd.length;
67936
68689
  this._cmd = this._cmd.slice(0, this._cursorPosition) + key + this._cmd.slice(this._cursorPosition);
67937
68690
  this._cursorPosition++;
67938
- this.write(ansi$7.erase.inLine(2) + ansi$7.cursor.horizontalAbsolute(0));
67939
- if (this._multiLineMode) {
67940
- const parts = this._cmd.split("#");
67941
- if (parts.length > 1) {
67942
- this.write("> " + parts[0] + chalk$2.gray("#" + parts.slice(1).join("#")));
67943
- } else {
67944
- this.write("> " + this._cmd);
67945
- }
68691
+ if (wasAtEnd) {
68692
+ this.write(key);
67946
68693
  } else {
67947
- const parts = this._cmd.split("#");
67948
- if (parts.length > 1) {
67949
- this.write(this.prompt() + parts[0] + chalk$2.gray("#" + parts.slice(1).join("#")));
67950
- } else {
67951
- this.write(this.prompt() + this._cmd);
68694
+ const tail = this._cmd.slice(this._cursorPosition);
68695
+ this.write(key + tail);
68696
+ if (tail.length > 0) {
68697
+ this.write(`\x1B[${tail.length}D`);
67952
68698
  }
67953
68699
  }
67954
- if (this._cursorPosition < this._cmd.length) this.write(`\x1B[${this._cmd.length - this._cursorPosition}D`);
67955
68700
  }
67956
68701
  }
67957
68702
  }
@@ -70801,7 +71546,9 @@ const _Process = class _Process {
70801
71546
  __publicField(this, "_status", "stopped");
70802
71547
  __publicField(this, "_stderr");
70803
71548
  __publicField(this, "_stdin");
71549
+ __publicField(this, "_stdinIsTTY");
70804
71550
  __publicField(this, "_stdout");
71551
+ __publicField(this, "_stdoutIsTTY");
70805
71552
  __publicField(this, "_terminal");
70806
71553
  __publicField(this, "_uid");
70807
71554
  __publicField(this, "_keepAlive", false);
@@ -70822,7 +71569,9 @@ const _Process = class _Process {
70822
71569
  this._terminal = options.terminal || this.kernel.terminal;
70823
71570
  this._uid = options.uid;
70824
71571
  this._stdin = options.stdin || this.terminal.getInputStream();
71572
+ this._stdinIsTTY = options.stdinIsTTY ?? (options.stdin ? false : true);
70825
71573
  this._stdout = options.stdout || this.terminal.stdout || new WritableStream();
71574
+ this._stdoutIsTTY = options.stdoutIsTTY ?? (options.stdout ? false : true);
70826
71575
  this._stderr = options.stderr || this.terminal.stderr || new WritableStream();
70827
71576
  this._fdtable = new FDTable(this._stdin, this._stdout, this._stderr);
70828
71577
  this.kernel.processes.add(this);
@@ -70869,9 +71618,15 @@ const _Process = class _Process {
70869
71618
  get stdin() {
70870
71619
  return this._stdin;
70871
71620
  }
71621
+ get stdinIsTTY() {
71622
+ return this._stdinIsTTY;
71623
+ }
70872
71624
  get stdout() {
70873
71625
  return this._stdout;
70874
71626
  }
71627
+ get stdoutIsTTY() {
71628
+ return this._stdoutIsTTY;
71629
+ }
70875
71630
  get terminal() {
70876
71631
  return this._terminal;
70877
71632
  }
@@ -70958,6 +71713,8 @@ const _Process = class _Process {
70958
71713
  shell: this.shell,
70959
71714
  terminal: this.terminal,
70960
71715
  stdin: this._stdin,
71716
+ stdinIsTTY: this._stdinIsTTY,
71717
+ stdoutIsTTY: this._stdoutIsTTY,
70961
71718
  stdout: this._stdout,
70962
71719
  stderr: this._stderr,
70963
71720
  uid: this.uid
@@ -71742,6 +72499,7 @@ const _Shell = class _Shell {
71742
72499
  const isFirstCommand = i === 0;
71743
72500
  const isLastCommand = i === commands.length - 1;
71744
72501
  let inputStream;
72502
+ let stdinIsTTY = false;
71745
72503
  if (isFirstCommand) {
71746
72504
  const inputRedirect = redirections.find((r) => r.type === "<");
71747
72505
  if (inputRedirect) {
@@ -71750,14 +72508,17 @@ const _Shell = class _Shell {
71750
72508
  throw new Error(`File not found: ${sourcePath}`);
71751
72509
  }
71752
72510
  inputStream = this.createFileReadStream(sourcePath, env2, kernel);
72511
+ stdinIsTTY = false;
71753
72512
  } else {
71754
72513
  inputStream = this._terminal.getInputStream();
72514
+ stdinIsTTY = true;
71755
72515
  }
71756
72516
  } else {
71757
72517
  if (!prevReadable) {
71758
72518
  throw new Error("Pipeline error: missing previous stream");
71759
72519
  }
71760
72520
  inputStream = prevReadable;
72521
+ stdinIsTTY = false;
71761
72522
  }
71762
72523
  let outputStream;
71763
72524
  let errorStream;
@@ -71827,17 +72588,20 @@ const _Shell = class _Shell {
71827
72588
  errorStream = this.createTerminalErrorStream();
71828
72589
  }
71829
72590
  }
71830
- pipelineSetup.push({ finalCommand, args, inputStream, outputStream, errorStream });
72591
+ const stdoutIsTTY = !stdoutRedirect && isLastCommand;
72592
+ pipelineSetup.push({ finalCommand, args, inputStream, stdinIsTTY, outputStream, errorStream, stdoutIsTTY });
71831
72593
  }
71832
72594
  const commandPromises = pipelineSetup.map(
71833
- ({ finalCommand, args, inputStream, outputStream, errorStream }) => this._kernel.execute({
72595
+ ({ finalCommand, args, inputStream, stdinIsTTY, outputStream, errorStream, stdoutIsTTY }) => this._kernel.execute({
71834
72596
  command: finalCommand,
71835
72597
  args,
71836
72598
  kernel: this._kernel,
71837
72599
  shell: this,
71838
72600
  terminal: this._terminal,
71839
72601
  stdin: inputStream,
72602
+ stdinIsTTY,
71840
72603
  stdout: outputStream,
72604
+ stdoutIsTTY,
71841
72605
  stderr: errorStream
71842
72606
  })
71843
72607
  );
@@ -72157,10 +72921,27 @@ const _Users = class _Users {
72157
72921
  /**
72158
72922
  * Login a user
72159
72923
  */
72160
- async login(username, password) {
72924
+ async login(username, password, passkeyCredential) {
72161
72925
  const user2 = Array.from(this._users.values()).find((u) => u.username === username);
72162
- const hashedPassword = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(password.trim()));
72163
- if (!user2 || user2.password !== Array.from(new Uint8Array(hashedPassword)).map((b) => b.toString(16).padStart(2, "0")).join("")) throw new Error("Invalid username or password");
72926
+ if (!user2) throw new Error("Invalid username or password");
72927
+ if (passkeyCredential) {
72928
+ const passkeys = await this.getPasskeys(user2.uid);
72929
+ const credential = passkeyCredential;
72930
+ const credentialId = btoa(String.fromCharCode(...new Uint8Array(credential.rawId)));
72931
+ const matchingPasskey = passkeys.find((pk) => pk.credentialId === credentialId);
72932
+ if (!matchingPasskey) {
72933
+ throw new Error("Passkey not found for this user");
72934
+ }
72935
+ matchingPasskey.lastUsed = Date.now();
72936
+ await this.savePasskeys(user2.uid, passkeys);
72937
+ } else if (password) {
72938
+ const hashedPassword = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(password.trim()));
72939
+ if (user2.password !== Array.from(new Uint8Array(hashedPassword)).map((b) => b.toString(16).padStart(2, "0")).join("")) {
72940
+ throw new Error("Invalid username or password");
72941
+ }
72942
+ } else {
72943
+ throw new Error("Password or passkey required");
72944
+ }
72164
72945
  const cred = createCredentials({
72165
72946
  uid: user2.uid,
72166
72947
  gid: user2.gid,
@@ -72204,6 +72985,77 @@ const _Users = class _Users {
72204
72985
  throw new Error(`User with UID ${uid} not found`);
72205
72986
  }
72206
72987
  }
72988
+ /**
72989
+ * Get all passkeys for a user
72990
+ */
72991
+ async getPasskeys(uid) {
72992
+ const user2 = this._users.get(uid);
72993
+ if (!user2) return [];
72994
+ const passkeysPath = `${user2.home}/.passkeys`;
72995
+ try {
72996
+ const exists2 = await this._options.kernel.filesystem.fs.exists(passkeysPath);
72997
+ if (!exists2) return [];
72998
+ const content = await this._options.kernel.filesystem.fs.readFile(passkeysPath, "utf-8");
72999
+ const parsed = JSON.parse(content);
73000
+ return parsed.map((pk) => {
73001
+ const publicKeyArray = JSON.parse(pk.publicKey);
73002
+ return {
73003
+ ...pk,
73004
+ publicKey: new Uint8Array(publicKeyArray)
73005
+ };
73006
+ });
73007
+ } catch (error) {
73008
+ this._options.kernel.log.warn(`Failed to read passkeys for user ${uid}: ${error}`);
73009
+ return [];
73010
+ }
73011
+ }
73012
+ /**
73013
+ * Save passkeys for a user
73014
+ */
73015
+ async savePasskeys(uid, passkeys) {
73016
+ const user2 = this._users.get(uid);
73017
+ if (!user2) throw new Error(`User with UID ${uid} not found`);
73018
+ const passkeysPath = `${user2.home}/.passkeys`;
73019
+ const serialized = passkeys.map((pk) => {
73020
+ const publicKeyArray = pk.publicKey instanceof ArrayBuffer ? Array.from(new Uint8Array(pk.publicKey)) : Array.from(pk.publicKey);
73021
+ return {
73022
+ ...pk,
73023
+ publicKey: JSON.stringify(publicKeyArray)
73024
+ };
73025
+ });
73026
+ await this._options.kernel.filesystem.fs.writeFile(
73027
+ passkeysPath,
73028
+ JSON.stringify(serialized, null, 2),
73029
+ { encoding: "utf-8", mode: 384 }
73030
+ );
73031
+ try {
73032
+ await this._options.kernel.filesystem.fs.chown(passkeysPath, uid, user2.gid);
73033
+ } catch {
73034
+ }
73035
+ }
73036
+ /**
73037
+ * Add a passkey to a user's collection
73038
+ */
73039
+ async addPasskey(uid, passkey) {
73040
+ const existing = await this.getPasskeys(uid);
73041
+ existing.push(passkey);
73042
+ await this.savePasskeys(uid, existing);
73043
+ }
73044
+ /**
73045
+ * Remove a passkey by ID
73046
+ */
73047
+ async removePasskey(uid, passkeyId) {
73048
+ const existing = await this.getPasskeys(uid);
73049
+ const filtered = existing.filter((pk) => pk.id !== passkeyId);
73050
+ await this.savePasskeys(uid, filtered);
73051
+ }
73052
+ /**
73053
+ * Check if a user has any registered passkeys
73054
+ */
73055
+ async hasPasskeys(uid) {
73056
+ const passkeys = await this.getPasskeys(uid);
73057
+ return passkeys.length > 0;
73058
+ }
72207
73059
  };
72208
73060
  __name(_Users, "Users");
72209
73061
  let Users = _Users;
@@ -73568,10 +74420,10 @@ const _Workers = class _Workers {
73568
74420
  };
73569
74421
  __name(_Workers, "Workers");
73570
74422
  let Workers = _Workers;
73571
- const __vite_import_meta_env__ = { "AUTHOR": { "name": "Jay Mathis", "email": "code@mathis.network", "url": "https://github.com/mathiscode" }, "BASE_URL": "/", "DESCRIPTION": "ecmaOS: Micro-kernel and framework for web technologies", "DEV": false, "HOMEPAGE": "https://ecmaos.sh", "KNOWN_ISSUES": ["It's best to stick to Chromium-based browsers for the most features", "Keyboard is broken on mobile; ecmaOS is not mobile-friendly at this time", "Don't expect any sort of POSIX compliance at this stage", "Most commands/devices are very basic implementations, not complete reproductions", "stdin/stdout/stderr streams and redirection can be wonky and don't work everywhere, but are coming along", "CTRL-C will return you to a prompt, but doesn't currently interrupt a process", "Lots of unfinished work; watch your step"], "MODE": "production", "NAME": "@ecmaos/kernel", "PROD": true, "REPOSITORY": "https://github.com/ecmaos/ecmaos", "SSR": false, "TIPS": ["If it ever fails to boot, check your logs or try clearing all data", "You can run some devices that offer a CLI - e.g. '/dev/battery --help'", "Use the 'install' command to install packages - e.g. 'install @ecmaos-apps/news'", "You can install any NPM package - e.g. 'install jquery'", "Use the 'news' command to see the latest news about ecmaOS", "Type 'ls /bin' to see all built-in commands", "Type 'ls /usr/bin' to see all installed commands", "You can set your environment variables in ~/.env (try setting PROMPT to a PS1-like format)", "Try 'fetch /xkcd-os.sixel'"], "VERSION": "0.6.4", "VITE_APP_SHOW_DEFAULT_LOGIN": "true", "VITE_AUTOLOGIN_PASSWORD": "root", "VITE_AUTOLOGIN_USERNAME": "root", "VITE_BOOT_DISABLE_ISSUES": "true", "VITE_BOOT_DISABLE_LOGO_CONSOLE": "false", "VITE_BOOT_DISABLE_LOGO_FIGLET": "false", "VITE_BOOT_DISABLE_TIPS": "false", "VITE_INITFS": "/initfs.tar.gz", "VITE_KERNEL_INTERVALS_PROC": "1000", "VITE_KERNEL_MODULES": "@ecmaos-modules/boilerplate@0.1.0", "VITE_METAL_SOCKET": "ws://localhost:30445/socket", "VITE_PORT": "30443", "VITE_RECOMMENDED_APPS": "@ecmaos-apps/code,@ecmaos-apps/edit,@ecmaos-apps/ai,@ecmaos-apps/webamp,@ecmaos-apps/news", "XTERM_VERSION": "5.5.0", "ZENFS_VERSION": "2.4.2" };
74423
+ const __vite_import_meta_env__ = { "AUTHOR": { "name": "Jay Mathis", "email": "code@mathis.network", "url": "https://github.com/mathiscode" }, "BASE_URL": "/", "DESCRIPTION": "ecmaOS: Micro-kernel and framework for web technologies", "DEV": false, "HOMEPAGE": "https://ecmaos.sh", "KNOWN_ISSUES": ["It's best to stick to Chromium-based browsers for the most features", "Keyboard is broken on mobile; ecmaOS is not mobile-friendly at this time", "Don't expect any sort of POSIX compliance at this stage", "Most commands/devices are very basic implementations, not complete reproductions", "stdin/stdout/stderr streams and redirection can be wonky and don't work everywhere, but are coming along", "CTRL-C will return you to a prompt, but doesn't currently interrupt a process", "Lots of unfinished work; watch your step"], "MODE": "production", "NAME": "@ecmaos/kernel", "PROD": true, "REPOSITORY": "https://github.com/ecmaos/ecmaos", "SSR": false, "TIPS": ["If it ever fails to boot, check your logs, try clearing all data, try incognito mode, or try another browser", "You can run some devices that offer a CLI - e.g. '/dev/battery --help'", "Use the 'install' command to install packages - e.g. 'install @ecmaos-apps/news'", "You can install any NPM package - e.g. 'install jquery'", "Use the 'news' command to see the latest news about ecmaOS", "Type 'ls /bin' to see all built-in commands", "Type 'ls /usr/bin' to see all installed commands", "You can set your environment variables in ~/.env (try setting PROMPT to a PS1-like format)", "You can register and login with a passkey: 'passkey register' and 'passkey list'", "Try 'fetch /xkcd-os.sixel'"], "VERSION": "0.6.6", "VITE_APP_SHOW_DEFAULT_LOGIN": "true", "VITE_AUTOLOGIN_PASSWORD": "root", "VITE_AUTOLOGIN_USERNAME": "root", "VITE_BOOT_DISABLE_ISSUES": "true", "VITE_BOOT_DISABLE_LOGO_CONSOLE": "false", "VITE_BOOT_DISABLE_LOGO_FIGLET": "false", "VITE_BOOT_DISABLE_TIPS": "false", "VITE_INITFS": "/initfs.tar.gz", "VITE_KERNEL_INTERVALS_PROC": "1000", "VITE_KERNEL_MODULES": "@ecmaos-modules/boilerplate@0.1.0", "VITE_METAL_SOCKET": "ws://localhost:30445/socket", "VITE_PORT": "30443", "VITE_RECOMMENDED_APPS": "@ecmaos-apps/code,@ecmaos-apps/edit,@ecmaos-apps/ai,@ecmaos-apps/webamp,@ecmaos-apps/news", "XTERM_VERSION": "5.5.0", "ZENFS_VERSION": "2.4.2" };
73572
74424
  var define_import_meta_env_AUTHOR_default = { name: "Jay Mathis", email: "code@mathis.network", url: "https://github.com/mathiscode" };
73573
74425
  var define_import_meta_env_KNOWN_ISSUES_default = ["It's best to stick to Chromium-based browsers for the most features", "Keyboard is broken on mobile; ecmaOS is not mobile-friendly at this time", "Don't expect any sort of POSIX compliance at this stage", "Most commands/devices are very basic implementations, not complete reproductions", "stdin/stdout/stderr streams and redirection can be wonky and don't work everywhere, but are coming along", "CTRL-C will return you to a prompt, but doesn't currently interrupt a process", "Lots of unfinished work; watch your step"];
73574
- var define_import_meta_env_TIPS_default = ["If it ever fails to boot, check your logs or try clearing all data", "You can run some devices that offer a CLI - e.g. '/dev/battery --help'", "Use the 'install' command to install packages - e.g. 'install @ecmaos-apps/news'", "You can install any NPM package - e.g. 'install jquery'", "Use the 'news' command to see the latest news about ecmaOS", "Type 'ls /bin' to see all built-in commands", "Type 'ls /usr/bin' to see all installed commands", "You can set your environment variables in ~/.env (try setting PROMPT to a PS1-like format)", "Try 'fetch /xkcd-os.sixel'"];
74426
+ var define_import_meta_env_TIPS_default = ["If it ever fails to boot, check your logs, try clearing all data, try incognito mode, or try another browser", "You can run some devices that offer a CLI - e.g. '/dev/battery --help'", "Use the 'install' command to install packages - e.g. 'install @ecmaos-apps/news'", "You can install any NPM package - e.g. 'install jquery'", "Use the 'news' command to see the latest news about ecmaOS", "Type 'ls /bin' to see all built-in commands", "Type 'ls /usr/bin' to see all installed commands", "You can set your environment variables in ~/.env (try setting PROMPT to a PS1-like format)", "You can register and login with a passkey: 'passkey register' and 'passkey list'", "Try 'fetch /xkcd-os.sixel'"];
73575
74427
  const DefaultKernelOptions = {
73576
74428
  devices: DefaultDevices,
73577
74429
  dom: DefaultDomOptions,
@@ -73613,7 +74465,7 @@ const _Kernel = class _Kernel {
73613
74465
  /** Name of the kernel */
73614
74466
  __publicField(this, "name", "@ecmaos/kernel");
73615
74467
  /** Version string of the kernel */
73616
- __publicField(this, "version", "0.6.4");
74468
+ __publicField(this, "version", "0.6.6");
73617
74469
  /** Authentication and authorization service */
73618
74470
  __publicField(this, "auth");
73619
74471
  /** BIOS module providing low-level functionality */
@@ -73771,7 +74623,7 @@ const _Kernel = class _Kernel {
73771
74623
  ];
73772
74624
  this.terminal.writeln(chalk$2.red.bold(`🐉 ${t2("kernel.experimental", "EXPERIMENTAL")} 🐉`));
73773
74625
  this.terminal.writeln(
73774
- `${this.terminal.createSpecialLink("https://ecmaos.sh", "@ecmaos/kernel")}@${"0.6.4"}` + chalk$2.cyan(` [${dependencyLinks.map((link2) => link2.link).join(", ")}]`)
74626
+ `${this.terminal.createSpecialLink("https://ecmaos.sh", "@ecmaos/kernel")}@${"0.6.6"}` + chalk$2.cyan(` [${dependencyLinks.map((link2) => link2.link).join(", ")}]`)
73775
74627
  );
73776
74628
  this.terminal.writeln(`${t2("kernel.madeBy", "Made with ❤️ by Jay Mathis")} ${this.terminal.createSpecialLink(
73777
74629
  define_import_meta_env_AUTHOR_default?.url || "https://github.com/mathiscode",
@@ -73788,14 +74640,14 @@ const _Kernel = class _Kernel {
73788
74640
  if (logoFiglet && true) {
73789
74641
  console.log(`%c${logoFiglet}`, "color: green");
73790
74642
  console.log(`%c${"https://github.com/ecmaos/ecmaos"}`, "color: blue; text-decoration: underline; font-size: 16px");
73791
- this.log.info(`${"@ecmaos/kernel"} v${"0.6.4"}`);
74643
+ this.log.info(`${"@ecmaos/kernel"} v${"0.6.6"}`);
73792
74644
  }
73793
74645
  if (Notification?.permission === "default") Notification.requestPermission();
73794
74646
  if (Notification?.permission === "denied") this.log.warn(t2("kernel.permissionNotificationDenied", "Notification permission denied"));
73795
74647
  this.intervals.set("title-blink", () => {
73796
74648
  globalThis.document.title = globalThis.document.title.includes("_") ? "ecmaos# " : "ecmaos# _";
73797
74649
  }, 600);
73798
- this.toast.success(`${"@ecmaos/kernel"} v${"0.6.4"}`);
74650
+ this.toast.success(`${"@ecmaos/kernel"} v${"0.6.6"}`);
73799
74651
  }
73800
74652
  await this.configure({ devices: this.options.devices || DefaultDevices, filesystem: Filesystem.options() });
73801
74653
  const requiredPaths = [
@@ -73956,20 +74808,62 @@ const _Kernel = class _Kernel {
73956
74808
  const icon = isSecure ? "🔒" : "🔓";
73957
74809
  this.terminal.writeln(`${icon} ${protocolStr}//${hostname}${port}`);
73958
74810
  const username = await this.terminal.readline(`👤 ${this.i18n.t("Username")}: `);
73959
- const password = await this.terminal.readline(`🔑 ${this.i18n.t("Password")}: `, true);
73960
- const { user: user22, cred } = await this.users.login(username, password);
73961
- this.shell.credentials = cred;
73962
- this.shell.context = bindContext({ root: "/", pwd: "/", credentials: cred });
74811
+ const user22 = Array.from(this.users.all.values()).find((u) => u.username === username);
74812
+ let loginSuccess = false;
74813
+ let userCred = null;
74814
+ if (user22) {
74815
+ const passkeys = await this.users.getPasskeys(user22.uid);
74816
+ if (passkeys.length > 0 && this.auth.passkey.isSupported()) {
74817
+ try {
74818
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
74819
+ const rpId = globalThis.location.hostname || "localhost";
74820
+ const allowCredentials = passkeys.map((pk) => {
74821
+ const credentialIdBytes = Uint8Array.from(atob(pk.credentialId), (c) => c.charCodeAt(0));
74822
+ return {
74823
+ id: credentialIdBytes.buffer,
74824
+ type: "public-key",
74825
+ transports: ["usb", "nfc", "ble", "internal"]
74826
+ };
74827
+ });
74828
+ const requestOptions = {
74829
+ challenge,
74830
+ allowCredentials,
74831
+ rpId,
74832
+ userVerification: "preferred",
74833
+ timeout: 6e4
74834
+ };
74835
+ this.terminal.writeln(chalk$2.yellow("🔐 Please use your passkey to authenticate..."));
74836
+ const credential = await this.auth.passkey.get(requestOptions);
74837
+ if (credential && credential instanceof PublicKeyCredential) {
74838
+ userCred = await this.users.login(username, void 0, credential);
74839
+ loginSuccess = true;
74840
+ } else {
74841
+ this.terminal.writeln(chalk$2.yellow("Passkey authentication cancelled or failed. Falling back to password..."));
74842
+ }
74843
+ } catch (err2) {
74844
+ this.terminal.writeln(chalk$2.yellow(`Passkey authentication error: ${err2.message}. Falling back to password...`));
74845
+ }
74846
+ }
74847
+ }
74848
+ if (!loginSuccess) {
74849
+ const password = await this.terminal.readline(`🔑 ${this.i18n.t("Password")}: `, true);
74850
+ userCred = await this.users.login(username, password);
74851
+ }
74852
+ if (!userCred) {
74853
+ throw new Error("Login failed");
74854
+ }
74855
+ this.shell.credentials = userCred.cred;
74856
+ this.shell.context = bindContext({ root: "/", pwd: "/", credentials: userCred.cred });
73963
74857
  await this.shell.loadEnvFile();
73964
- this.shell.env.set("UID", user22.uid.toString());
73965
- this.shell.env.set("GID", user22.gid.toString());
73966
- this.shell.env.set("SUID", cred.suid.toString());
73967
- this.shell.env.set("SGID", cred.sgid.toString());
73968
- this.shell.env.set("EUID", cred.euid.toString());
73969
- this.shell.env.set("EGID", cred.egid.toString());
73970
- this.shell.env.set("SHELL", user22.shell || "ecmaos");
73971
- this.shell.env.set("HOME", user22.home || "/root");
73972
- this.shell.env.set("USER", user22.username);
74858
+ this.shell.env.set("UID", userCred.user.uid.toString());
74859
+ this.shell.env.set("GID", userCred.user.gid.toString());
74860
+ this.shell.env.set("SUID", userCred.cred.suid.toString());
74861
+ this.shell.env.set("SGID", userCred.cred.sgid.toString());
74862
+ this.shell.env.set("EUID", userCred.cred.euid.toString());
74863
+ this.shell.env.set("EGID", userCred.cred.egid.toString());
74864
+ this.shell.env.set("SHELL", userCred.user.shell || "ecmaos");
74865
+ this.shell.env.set("HOME", userCred.user.home || "/root");
74866
+ this.shell.env.set("USER", userCred.user.username);
73973
74867
  process$1.env = Object.fromEntries(this.shell.env);
73974
74868
  break;
73975
74869
  } catch (err2) {
@@ -74222,7 +75116,9 @@ const _Kernel = class _Kernel {
74222
75116
  terminal: options.terminal || this.terminal,
74223
75117
  entry: /* @__PURE__ */ __name(async (params) => await command.run.call(params, params.pid, params.args), "entry"),
74224
75118
  stdin: options.stdin,
75119
+ stdinIsTTY: options.stdinIsTTY,
74225
75120
  stdout: options.stdout,
75121
+ stdoutIsTTY: options.stdoutIsTTY,
74226
75122
  stderr: options.stderr
74227
75123
  });
74228
75124
  const exitCode = await process2.start();
@@ -74499,7 +75395,7 @@ const _Kernel = class _Kernel {
74499
75395
  memory: "?",
74500
75396
  platform: navigator.userAgentData?.platform || navigator?.platform || navigator.userAgent,
74501
75397
  querystring: location.search,
74502
- version: `${"@ecmaos/kernel"} ${"0.6.4"}`,
75398
+ version: `${"@ecmaos/kernel"} ${"0.6.6"}`,
74503
75399
  language: navigator.language,
74504
75400
  host: location.host,
74505
75401
  userAgent: navigator.userAgent,
@@ -74676,4 +75572,4 @@ export {
74676
75572
  path$1 as p,
74677
75573
  semver as s
74678
75574
  };
74679
- //# sourceMappingURL=kernel-DtEbpoeV.js.map
75575
+ //# sourceMappingURL=kernel-DZB_DlxI.js.map