@qodalis/cli 2.0.0-beta.19 → 2.0.0-beta.20

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.
package/README.md CHANGED
@@ -136,7 +136,7 @@ const options = {
136
136
  | `help` | `man` | Show all commands or help for a specific command |
137
137
  | `version` | `ver` | Display CLI version and documentation link |
138
138
  | `hotkeys` | `shortcuts`, `keys` | Show keyboard shortcuts |
139
- | `history` | `hist` | Browse and clear command history |
139
+ | `history` | `hist` | Browse and clear your command history (per user with the users plugin) |
140
140
  | `theme` | `themes` | Apply, customize, and save terminal themes (interactive selection with live preview) |
141
141
  | `feedback` | `support` | Report bugs or request features on GitHub |
142
142
  | `pkg` | `packages` | Install, update, remove, and browse packages |
@@ -796,8 +796,8 @@ await engine.start();
796
796
  - **Multi-framework** — Angular, React, Vue, or vanilla JS
797
797
  - **38 official plugins** — 24 utility, 6 games, 10 language packs
798
798
  - **Command chaining** with `&&`, `||`, `|`, and `>>` operators
799
- - **Command history** with arrow key navigation
800
- - **Tab-like completions** and keyboard shortcuts (`Ctrl+C`, `Ctrl+L`, `Escape`)
799
+ - **Command history** with arrow key navigation, per user when the users plugin is used (like `~/.bash_history`)
800
+ - **Tab-like completions** and readline keyboard shortcuts (`Ctrl+C`, `Ctrl+L`, `Escape`, `Ctrl+A/E`, `Ctrl+U/K/W/Y`, Home/End; `hotkeys` lists them)
801
801
  - **29 built-in themes** with custom color support
802
802
  - **Tabs & split panes** — multiple terminals in tabs with horizontal splitting
803
803
  - **User sessions** with multi-user support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qodalis/cli",
3
- "version": "2.0.0-beta.19",
3
+ "version": "2.0.0-beta.20",
4
4
  "description": "Framework-agnostic CLI engine for @qodalis terminal applications.",
5
5
  "author": "Nicolae Lupei, Qodalis Solutions",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "./assets/*": "./assets/*"
31
31
  },
32
32
  "dependencies": {
33
- "@qodalis/cli-core": "2.0.0-beta.19",
33
+ "@qodalis/cli-core": "2.0.0-beta.20",
34
34
  "@xterm/xterm": "^5.5.0",
35
35
  "@xterm/addon-fit": "^0.10.0",
36
36
  "@xterm/addon-web-links": "^0.11.0",
package/public-api.d.mts CHANGED
@@ -165,7 +165,7 @@ declare const CliKeyValueStore_TOKEN: any;
165
165
  */
166
166
  declare const SyntaxHighlighterRegistry_TOKEN: any;
167
167
 
168
- declare const LIBRARY_VERSION = "2.0.0-beta.19";
168
+ declare const LIBRARY_VERSION = "2.0.0-beta.20";
169
169
  declare const API_VERSION = 2;
170
170
 
171
171
  declare class CliAliasCommandProcessor implements ICliCommandProcessor {
@@ -1025,11 +1025,29 @@ declare class CliCommandExecutionContext implements ICliExecutionContext {
1025
1025
  constructor(context: ICliExecutionContext, processor: ICliCommandProcessor);
1026
1026
  }
1027
1027
 
1028
+ /**
1029
+ * Command history, kept per user like a shell's `~/.bash_history`: each
1030
+ * signed-in user (by id) has their own, and the shared one is used while
1031
+ * nobody is signed in.
1032
+ */
1028
1033
  declare class CliCommandHistory {
1029
1034
  private readonly store;
1030
- private readonly storageKey;
1031
1035
  private commandHistory;
1036
+ /** Id of the user whose history is loaded; undefined: nobody signed in */
1037
+ private userId;
1038
+ private loading;
1039
+ /** Counts user switches (a load for an older one is dropped) */
1040
+ private switches;
1041
+ private _generation;
1032
1042
  constructor(store: ICliKeyValueStore);
1043
+ /** Changes whenever the history is replaced: switched or loaded (positions into it are stale) */
1044
+ get generation(): number;
1045
+ private get storageKey();
1046
+ /**
1047
+ * Switch to the history of `userId` (undefined: nobody signed in).
1048
+ * Resolves once it is loaded; a no-op for the user already loaded.
1049
+ */
1050
+ useUser(userId: string | undefined): Promise<void>;
1033
1051
  addCommand(command: string): Promise<void>;
1034
1052
  getLastIndex(): number;
1035
1053
  getHistory(): string[];
@@ -1173,6 +1191,12 @@ declare class CliLineBuffer {
1173
1191
  moveEnd(): void;
1174
1192
  clear(): void;
1175
1193
  setText(str: string): void;
1194
+ /** Remove the text before the cursor (Ctrl+U); returns it */
1195
+ deleteToStart(): string;
1196
+ /** Remove the text from the cursor to the end (Ctrl+K); returns it */
1197
+ deleteToEnd(): string;
1198
+ /** Remove the whitespace-delimited word before the cursor (Ctrl+W); returns it */
1199
+ deleteWordBefore(): string;
1176
1200
  }
1177
1201
 
1178
1202
  /**
package/public-api.d.ts CHANGED
@@ -165,7 +165,7 @@ declare const CliKeyValueStore_TOKEN: any;
165
165
  */
166
166
  declare const SyntaxHighlighterRegistry_TOKEN: any;
167
167
 
168
- declare const LIBRARY_VERSION = "2.0.0-beta.19";
168
+ declare const LIBRARY_VERSION = "2.0.0-beta.20";
169
169
  declare const API_VERSION = 2;
170
170
 
171
171
  declare class CliAliasCommandProcessor implements ICliCommandProcessor {
@@ -1025,11 +1025,29 @@ declare class CliCommandExecutionContext implements ICliExecutionContext {
1025
1025
  constructor(context: ICliExecutionContext, processor: ICliCommandProcessor);
1026
1026
  }
1027
1027
 
1028
+ /**
1029
+ * Command history, kept per user like a shell's `~/.bash_history`: each
1030
+ * signed-in user (by id) has their own, and the shared one is used while
1031
+ * nobody is signed in.
1032
+ */
1028
1033
  declare class CliCommandHistory {
1029
1034
  private readonly store;
1030
- private readonly storageKey;
1031
1035
  private commandHistory;
1036
+ /** Id of the user whose history is loaded; undefined: nobody signed in */
1037
+ private userId;
1038
+ private loading;
1039
+ /** Counts user switches (a load for an older one is dropped) */
1040
+ private switches;
1041
+ private _generation;
1032
1042
  constructor(store: ICliKeyValueStore);
1043
+ /** Changes whenever the history is replaced: switched or loaded (positions into it are stale) */
1044
+ get generation(): number;
1045
+ private get storageKey();
1046
+ /**
1047
+ * Switch to the history of `userId` (undefined: nobody signed in).
1048
+ * Resolves once it is loaded; a no-op for the user already loaded.
1049
+ */
1050
+ useUser(userId: string | undefined): Promise<void>;
1033
1051
  addCommand(command: string): Promise<void>;
1034
1052
  getLastIndex(): number;
1035
1053
  getHistory(): string[];
@@ -1173,6 +1191,12 @@ declare class CliLineBuffer {
1173
1191
  moveEnd(): void;
1174
1192
  clear(): void;
1175
1193
  setText(str: string): void;
1194
+ /** Remove the text before the cursor (Ctrl+U); returns it */
1195
+ deleteToStart(): string;
1196
+ /** Remove the text from the cursor to the end (Ctrl+K); returns it */
1197
+ deleteToEnd(): string;
1198
+ /** Remove the whitespace-delimited word before the cursor (Ctrl+W); returns it */
1199
+ deleteWordBefore(): string;
1176
1200
  }
1177
1201
 
1178
1202
  /**
package/public-api.js CHANGED
@@ -512,7 +512,7 @@ var CliKeyValueStore_TOKEN = cliCore.createCliToken("cli-key-value-store");
512
512
  var SyntaxHighlighterRegistry_TOKEN = cliCore.createCliToken("syntax-highlighter-registry");
513
513
 
514
514
  // src/lib/version.ts
515
- var LIBRARY_VERSION = "2.0.0-beta.19";
515
+ var LIBRARY_VERSION = "2.0.0-beta.20";
516
516
  var API_VERSION = 2;
517
517
  var ANONYMOUS = "\0anonymous";
518
518
  function aliasOwner(context2) {
@@ -5686,13 +5686,45 @@ var hotkeysInfo = [
5686
5686
  key: "Ctrl + L",
5687
5687
  description: "Clear the terminal screen"
5688
5688
  },
5689
+ {
5690
+ key: "Escape",
5691
+ description: "Cancel and start a new line"
5692
+ },
5689
5693
  {
5690
5694
  key: "\u2191 / \u2193",
5691
- description: "Navigate command history"
5695
+ description: "Navigate command history (each user has their own)"
5692
5696
  },
5693
5697
  {
5694
5698
  key: "Tab",
5695
5699
  description: "Autocomplete command"
5700
+ },
5701
+ {
5702
+ key: "Ctrl + A / Home",
5703
+ description: "Move to the start of the line"
5704
+ },
5705
+ {
5706
+ key: "Ctrl + E / End",
5707
+ description: "Move to the end of the line"
5708
+ },
5709
+ {
5710
+ key: "Ctrl + U",
5711
+ description: "Cut from the start of the line to the cursor"
5712
+ },
5713
+ {
5714
+ key: "Ctrl + K",
5715
+ description: "Cut from the cursor to the end of the line"
5716
+ },
5717
+ {
5718
+ key: "Ctrl + W",
5719
+ description: "Cut the previous word (also Alt + Backspace)"
5720
+ },
5721
+ {
5722
+ key: "Ctrl + Y",
5723
+ description: "Paste the last cut text"
5724
+ },
5725
+ {
5726
+ key: "Ctrl + D / Del",
5727
+ description: "Delete the character under the cursor"
5696
5728
  }
5697
5729
  ];
5698
5730
 
@@ -6136,7 +6168,7 @@ var CliHotKeysCommandProcessor = class {
6136
6168
  const { writer } = context2;
6137
6169
  hotkeysInfo.forEach((hotkey) => {
6138
6170
  writer.writeln(
6139
- ` ${writer.wrapInColor(hotkey.key.padEnd(12), cliCore.CliForegroundColor.Cyan)} ${DIM3}${hotkey.description}${RESET3}`
6171
+ ` ${writer.wrapInColor(hotkey.key.padEnd(16), cliCore.CliForegroundColor.Cyan)} ${DIM3}${hotkey.description}${RESET3}`
6140
6172
  );
6141
6173
  });
6142
6174
  }
@@ -14912,6 +14944,111 @@ ${this.fitLine(` \x1B[31m\u2718 ${message}\x1B[0m`)}\x1B[${this.extraLines}A`
14912
14944
  }
14913
14945
  };
14914
14946
 
14947
+ // src/lib/input/cli-line-buffer.ts
14948
+ var CliLineBuffer = class {
14949
+ constructor() {
14950
+ this._text = "";
14951
+ this._cursorPosition = 0;
14952
+ }
14953
+ get text() {
14954
+ return this._text;
14955
+ }
14956
+ get cursorPosition() {
14957
+ return this._cursorPosition;
14958
+ }
14959
+ set cursorPosition(value) {
14960
+ this._cursorPosition = Math.max(0, Math.min(value, this._text.length));
14961
+ }
14962
+ insert(str) {
14963
+ this._text = this._text.slice(0, this._cursorPosition) + str + this._text.slice(this._cursorPosition);
14964
+ this._cursorPosition += str.length;
14965
+ }
14966
+ deleteCharBefore() {
14967
+ if (this._cursorPosition > 0) {
14968
+ this._text = this._text.slice(0, this._cursorPosition - 1) + this._text.slice(this._cursorPosition);
14969
+ this._cursorPosition--;
14970
+ }
14971
+ }
14972
+ deleteCharAt() {
14973
+ if (this._cursorPosition < this._text.length) {
14974
+ this._text = this._text.slice(0, this._cursorPosition) + this._text.slice(this._cursorPosition + 1);
14975
+ }
14976
+ }
14977
+ moveCursorLeft() {
14978
+ if (this._cursorPosition > 0) {
14979
+ this._cursorPosition--;
14980
+ }
14981
+ }
14982
+ moveCursorRight() {
14983
+ if (this._cursorPosition < this._text.length) {
14984
+ this._cursorPosition++;
14985
+ }
14986
+ }
14987
+ moveHome() {
14988
+ this._cursorPosition = 0;
14989
+ }
14990
+ moveEnd() {
14991
+ this._cursorPosition = this._text.length;
14992
+ }
14993
+ clear() {
14994
+ this._text = "";
14995
+ this._cursorPosition = 0;
14996
+ }
14997
+ setText(str) {
14998
+ this._text = str;
14999
+ this._cursorPosition = str.length;
15000
+ }
15001
+ /** Remove the text before the cursor (Ctrl+U); returns it */
15002
+ deleteToStart() {
15003
+ const removed = this._text.slice(0, this._cursorPosition);
15004
+ this._text = this._text.slice(this._cursorPosition);
15005
+ this._cursorPosition = 0;
15006
+ return removed;
15007
+ }
15008
+ /** Remove the text from the cursor to the end (Ctrl+K); returns it */
15009
+ deleteToEnd() {
15010
+ const removed = this._text.slice(this._cursorPosition);
15011
+ this._text = this._text.slice(0, this._cursorPosition);
15012
+ return removed;
15013
+ }
15014
+ /** Remove the whitespace-delimited word before the cursor (Ctrl+W); returns it */
15015
+ deleteWordBefore() {
15016
+ let start = this._cursorPosition;
15017
+ while (start > 0 && /\s/.test(this._text[start - 1])) start--;
15018
+ while (start > 0 && !/\s/.test(this._text[start - 1])) start--;
15019
+ const removed = this._text.slice(start, this._cursorPosition);
15020
+ this._text = this._text.slice(0, start) + this._text.slice(this._cursorPosition);
15021
+ this._cursorPosition = start;
15022
+ return removed;
15023
+ }
15024
+ };
15025
+ var HOME_KEYS = ["", "\x1B[H", "\x1BOH", "\x1B[1~", "\x1B[7~"];
15026
+ var END_KEYS = ["", "\x1B[F", "\x1BOF", "\x1B[4~", "\x1B[8~"];
15027
+ var DELETE_KEYS = ["\x1B[3~", ""];
15028
+ function applyEditingKey(buffer, data, killRing) {
15029
+ if (HOME_KEYS.includes(data)) {
15030
+ buffer.moveHome();
15031
+ } else if (END_KEYS.includes(data)) {
15032
+ buffer.moveEnd();
15033
+ } else if (DELETE_KEYS.includes(data)) {
15034
+ buffer.deleteCharAt();
15035
+ } else if (data === "") {
15036
+ killRing.text = buffer.deleteToStart() || killRing.text;
15037
+ } else if (data === "\v") {
15038
+ killRing.text = buffer.deleteToEnd() || killRing.text;
15039
+ } else if (data === "" || data === "\x1B\x7F") {
15040
+ killRing.text = buffer.deleteWordBefore() || killRing.text;
15041
+ } else if (data === "") {
15042
+ if (killRing.text) buffer.insert(killRing.text);
15043
+ } else {
15044
+ return false;
15045
+ }
15046
+ return true;
15047
+ }
15048
+ function isEscapeSequence(data) {
15049
+ return /^\u001b(\[[0-9;?]*[~A-Za-z]|O[A-Za-z])$/.test(data);
15050
+ }
15051
+
14915
15052
  // src/lib/input/modes/line-input-mode.ts
14916
15053
  var LineInputMode = class extends InputModeBase {
14917
15054
  constructor(host, resolve, promptText, options) {
@@ -14919,6 +15056,7 @@ var LineInputMode = class extends InputModeBase {
14919
15056
  this.promptText = promptText;
14920
15057
  this.options = options;
14921
15058
  this.hasError = false;
15059
+ this.killRing = { text: "" };
14922
15060
  this.buffer = options?.default ?? "";
14923
15061
  this.cursorPosition = this.buffer.length;
14924
15062
  }
@@ -14963,7 +15101,7 @@ var LineInputMode = class extends InputModeBase {
14963
15101
  this.cursorPosition++;
14964
15102
  this.host.terminal.write(data);
14965
15103
  }
14966
- } else if (data.startsWith("\x1B")) ; else {
15104
+ } else if (this.applyEditingKey(data)) ; else if (data.startsWith("\x1B")) ; else {
14967
15105
  const text = data.replace(/[\r\n]+/g, "");
14968
15106
  this.buffer = this.buffer.slice(0, this.cursorPosition) + text + this.buffer.slice(this.cursorPosition);
14969
15107
  this.cursorPosition += text.length;
@@ -14974,6 +15112,16 @@ var LineInputMode = class extends InputModeBase {
14974
15112
  }
14975
15113
  }
14976
15114
  }
15115
+ applyEditingKey(data) {
15116
+ const line2 = new CliLineBuffer();
15117
+ line2.setText(this.buffer);
15118
+ line2.cursorPosition = this.cursorPosition;
15119
+ if (!applyEditingKey(line2, data, this.killRing)) return false;
15120
+ this.buffer = line2.text;
15121
+ this.cursorPosition = line2.cursorPosition;
15122
+ this.renderLine();
15123
+ return true;
15124
+ }
14977
15125
  onResize() {
14978
15126
  this.renderLine();
14979
15127
  }
@@ -16709,67 +16857,13 @@ var CliCompletionEngine = class {
16709
16857
  }
16710
16858
  };
16711
16859
 
16712
- // src/lib/input/cli-line-buffer.ts
16713
- var CliLineBuffer = class {
16714
- constructor() {
16715
- this._text = "";
16716
- this._cursorPosition = 0;
16717
- }
16718
- get text() {
16719
- return this._text;
16720
- }
16721
- get cursorPosition() {
16722
- return this._cursorPosition;
16723
- }
16724
- set cursorPosition(value) {
16725
- this._cursorPosition = Math.max(0, Math.min(value, this._text.length));
16726
- }
16727
- insert(str) {
16728
- this._text = this._text.slice(0, this._cursorPosition) + str + this._text.slice(this._cursorPosition);
16729
- this._cursorPosition += str.length;
16730
- }
16731
- deleteCharBefore() {
16732
- if (this._cursorPosition > 0) {
16733
- this._text = this._text.slice(0, this._cursorPosition - 1) + this._text.slice(this._cursorPosition);
16734
- this._cursorPosition--;
16735
- }
16736
- }
16737
- deleteCharAt() {
16738
- if (this._cursorPosition < this._text.length) {
16739
- this._text = this._text.slice(0, this._cursorPosition) + this._text.slice(this._cursorPosition + 1);
16740
- }
16741
- }
16742
- moveCursorLeft() {
16743
- if (this._cursorPosition > 0) {
16744
- this._cursorPosition--;
16745
- }
16746
- }
16747
- moveCursorRight() {
16748
- if (this._cursorPosition < this._text.length) {
16749
- this._cursorPosition++;
16750
- }
16751
- }
16752
- moveHome() {
16753
- this._cursorPosition = 0;
16754
- }
16755
- moveEnd() {
16756
- this._cursorPosition = this._text.length;
16757
- }
16758
- clear() {
16759
- this._text = "";
16760
- this._cursorPosition = 0;
16761
- }
16762
- setText(str) {
16763
- this._text = str;
16764
- this._cursorPosition = str.length;
16765
- }
16766
- };
16767
-
16768
16860
  // src/lib/input/command-line-mode.ts
16769
16861
  var CommandLineMode = class {
16770
16862
  constructor(host) {
16771
16863
  this.host = host;
16772
16864
  this.historyIndex = 0;
16865
+ /** History generation `historyIndex` points into (another user's history may be loaded since) */
16866
+ this.historyGeneration = 0;
16773
16867
  this.isExecutingCommand = false;
16774
16868
  this.selectionStart = null;
16775
16869
  this.selectionEnd = null;
@@ -16785,6 +16879,7 @@ var CommandLineMode = class {
16785
16879
  * into the command's output and appended to the line just submitted.
16786
16880
  */
16787
16881
  this.typeAhead = [];
16882
+ this.killRing = { text: "" };
16788
16883
  }
16789
16884
  activate() {
16790
16885
  this.host.commandHistory.initialize().then(() => {
@@ -16840,7 +16935,7 @@ var CommandLineMode = class {
16840
16935
  }
16841
16936
  } else if (data === "\x7F") {
16842
16937
  this.handleBackspace();
16843
- } else {
16938
+ } else if (this.applyEditingKey(data)) ; else if (isEscapeSequence(data)) ; else {
16844
16939
  this.handleInputText(data);
16845
16940
  }
16846
16941
  }
@@ -16938,6 +17033,16 @@ var CommandLineMode = class {
16938
17033
  } catch {
16939
17034
  }
16940
17035
  }
17036
+ applyEditingKey(data) {
17037
+ const buffer = this.host.lineBuffer;
17038
+ const before = buffer.text;
17039
+ const previousContentLength = this.host.getPromptLength() + before.length;
17040
+ if (!applyEditingKey(buffer, data, this.killRing)) return false;
17041
+ this.clearGhostText();
17042
+ if (buffer.text !== before) this.clearHistorySearch();
17043
+ this.refreshLine(previousContentLength);
17044
+ return true;
17045
+ }
16941
17046
  handleBackspace() {
16942
17047
  const buffer = this.host.lineBuffer;
16943
17048
  this.clearGhostText();
@@ -17003,7 +17108,17 @@ var CommandLineMode = class {
17003
17108
  }
17004
17109
  }
17005
17110
  }
17111
+ /** Start over at the end when another history was loaded (`su`, `exit`, login) */
17112
+ syncHistory() {
17113
+ const generation = this.host.commandHistory.generation;
17114
+ if (generation === this.historyGeneration) return;
17115
+ this.historyGeneration = generation;
17116
+ this.historySearchPrefix = null;
17117
+ this.bufferSetByHistory = false;
17118
+ this.historyIndex = this.host.commandHistory.getLastIndex();
17119
+ }
17006
17120
  showPreviousCommand() {
17121
+ this.syncHistory();
17007
17122
  this.clearGhostText();
17008
17123
  const buffer = this.host.lineBuffer;
17009
17124
  if (buffer.text && !this.bufferSetByHistory && this.historySearchPrefix === null) {
@@ -17027,6 +17142,7 @@ var CommandLineMode = class {
17027
17142
  }
17028
17143
  }
17029
17144
  showNextCommand() {
17145
+ this.syncHistory();
17030
17146
  this.clearGhostText();
17031
17147
  const buffer = this.host.lineBuffer;
17032
17148
  if (this.historySearchPrefix !== null) {
@@ -17290,6 +17406,7 @@ var CliExecutionContext = class {
17290
17406
  set userSession(session) {
17291
17407
  const previous = this._userSession?.user?.name;
17292
17408
  this._userSession = session;
17409
+ void this.commandHistory?.useUser(session?.user?.id);
17293
17410
  if (session?.user?.name !== previous) {
17294
17411
  this.events?.emit("session:changed", { user: session?.user?.name });
17295
17412
  }
@@ -18862,15 +18979,41 @@ var CliLogger = class {
18862
18979
  };
18863
18980
 
18864
18981
  // src/lib/services/cli-command-history.ts
18982
+ var SHARED_KEY = "cli-command-history";
18983
+ var MIGRATED_KEY = "cli-command-history:migrated";
18865
18984
  var CliCommandHistory = class {
18866
18985
  constructor(store) {
18867
18986
  this.store = store;
18868
- this.storageKey = "cli-command-history";
18869
18987
  this.commandHistory = [];
18988
+ this.loading = Promise.resolve();
18989
+ /** Counts user switches (a load for an older one is dropped) */
18990
+ this.switches = 0;
18991
+ this._generation = 0;
18992
+ }
18993
+ /** Changes whenever the history is replaced: switched or loaded (positions into it are stale) */
18994
+ get generation() {
18995
+ return this._generation;
18996
+ }
18997
+ get storageKey() {
18998
+ return this.userId === void 0 ? SHARED_KEY : `${SHARED_KEY}:user:${this.userId}`;
18999
+ }
19000
+ /**
19001
+ * Switch to the history of `userId` (undefined: nobody signed in).
19002
+ * Resolves once it is loaded; a no-op for the user already loaded.
19003
+ */
19004
+ useUser(userId) {
19005
+ if (userId === this.userId) return this.loading;
19006
+ this.userId = userId;
19007
+ this.commandHistory = [];
19008
+ this.switches++;
19009
+ this._generation++;
19010
+ this.loading = this.loadHistory();
19011
+ return this.loading;
18870
19012
  }
18871
19013
  async addCommand(command2) {
18872
19014
  const normalizedCommand = command2.trim();
18873
19015
  if (!normalizedCommand) return;
19016
+ await this.loading;
18874
19017
  const existingIndex = this.commandHistory.indexOf(normalizedCommand);
18875
19018
  if (existingIndex !== -1) {
18876
19019
  this.commandHistory.splice(existingIndex, 1);
@@ -18937,18 +19080,32 @@ var CliCommandHistory = class {
18937
19080
  return await this.store.set(this.storageKey, trimmedHistory);
18938
19081
  }
18939
19082
  async loadHistory() {
18940
- const savedOldHistory = localStorage.getItem("cliCommandHistory");
19083
+ const key = this.storageKey;
19084
+ const switches = this.switches;
19085
+ const savedOldHistory = typeof localStorage !== "undefined" ? localStorage.getItem("cliCommandHistory") : null;
18941
19086
  if (savedOldHistory) {
18942
19087
  localStorage.removeItem("cliCommandHistory");
18943
- await this.store.set(this.storageKey, JSON.parse(savedOldHistory));
19088
+ await this.store.set(SHARED_KEY, JSON.parse(savedOldHistory));
19089
+ }
19090
+ let savedHistory = await this.store.get(key);
19091
+ if (!savedHistory && key !== SHARED_KEY && !await this.store.get(MIGRATED_KEY)) {
19092
+ await this.store.set(MIGRATED_KEY, true);
19093
+ const shared = await this.store.get(SHARED_KEY);
19094
+ if (shared?.length) {
19095
+ await this.store.set(key, shared);
19096
+ await this.store.remove(SHARED_KEY);
19097
+ savedHistory = shared;
19098
+ }
18944
19099
  }
18945
- const savedHistory = await this.store.get(this.storageKey);
19100
+ if (switches !== this.switches) return;
18946
19101
  if (savedHistory) {
18947
19102
  this.commandHistory = savedHistory;
19103
+ this._generation++;
18948
19104
  }
18949
19105
  }
18950
19106
  async initialize() {
18951
- await this.loadHistory();
19107
+ this.loading = this.loadHistory();
19108
+ await this.loading;
18952
19109
  }
18953
19110
  };
18954
19111
  var welcomeModule = {
@@ -22990,6 +23147,9 @@ var CliTestHarness = class {
22990
23147
  createContext(writer) {
22991
23148
  const process = new CliExecutionProcess(null);
22992
23149
  const reader = createQueuedReader(this.readerResponses);
23150
+ const provider = this.userSessionProvider;
23151
+ const fallback = this.userSession;
23152
+ const assigned = { set: false };
22993
23153
  const context2 = {
22994
23154
  writer,
22995
23155
  services: this.services,
@@ -23005,7 +23165,14 @@ var CliTestHarness = class {
23005
23165
  options: void 0,
23006
23166
  logger: createNoopLogger(),
23007
23167
  process,
23008
- userSession: this.userSessionProvider ? this.userSessionProvider() : this.userSession,
23168
+ // Live, like the engine's root context (commands run by a command see changes)
23169
+ get userSession() {
23170
+ return assigned.set ? assigned.value : provider ? provider() : fallback;
23171
+ },
23172
+ set userSession(value) {
23173
+ assigned.set = true;
23174
+ assigned.value = value;
23175
+ },
23009
23176
  state: this.stateStoreManager.getStateStore("shared"),
23010
23177
  backgroundServices: createNoopBackgroundServices(),
23011
23178
  http: new CliHttpClient(),