@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/public-api.mjs CHANGED
@@ -511,7 +511,7 @@ var CliKeyValueStore_TOKEN = createCliToken("cli-key-value-store");
511
511
  var SyntaxHighlighterRegistry_TOKEN = createCliToken("syntax-highlighter-registry");
512
512
 
513
513
  // src/lib/version.ts
514
- var LIBRARY_VERSION = "2.0.0-beta.19";
514
+ var LIBRARY_VERSION = "2.0.0-beta.20";
515
515
  var API_VERSION = 2;
516
516
  var ANONYMOUS = "\0anonymous";
517
517
  function aliasOwner(context2) {
@@ -5685,13 +5685,45 @@ var hotkeysInfo = [
5685
5685
  key: "Ctrl + L",
5686
5686
  description: "Clear the terminal screen"
5687
5687
  },
5688
+ {
5689
+ key: "Escape",
5690
+ description: "Cancel and start a new line"
5691
+ },
5688
5692
  {
5689
5693
  key: "\u2191 / \u2193",
5690
- description: "Navigate command history"
5694
+ description: "Navigate command history (each user has their own)"
5691
5695
  },
5692
5696
  {
5693
5697
  key: "Tab",
5694
5698
  description: "Autocomplete command"
5699
+ },
5700
+ {
5701
+ key: "Ctrl + A / Home",
5702
+ description: "Move to the start of the line"
5703
+ },
5704
+ {
5705
+ key: "Ctrl + E / End",
5706
+ description: "Move to the end of the line"
5707
+ },
5708
+ {
5709
+ key: "Ctrl + U",
5710
+ description: "Cut from the start of the line to the cursor"
5711
+ },
5712
+ {
5713
+ key: "Ctrl + K",
5714
+ description: "Cut from the cursor to the end of the line"
5715
+ },
5716
+ {
5717
+ key: "Ctrl + W",
5718
+ description: "Cut the previous word (also Alt + Backspace)"
5719
+ },
5720
+ {
5721
+ key: "Ctrl + Y",
5722
+ description: "Paste the last cut text"
5723
+ },
5724
+ {
5725
+ key: "Ctrl + D / Del",
5726
+ description: "Delete the character under the cursor"
5695
5727
  }
5696
5728
  ];
5697
5729
 
@@ -6135,7 +6167,7 @@ var CliHotKeysCommandProcessor = class {
6135
6167
  const { writer } = context2;
6136
6168
  hotkeysInfo.forEach((hotkey) => {
6137
6169
  writer.writeln(
6138
- ` ${writer.wrapInColor(hotkey.key.padEnd(12), CliForegroundColor.Cyan)} ${DIM3}${hotkey.description}${RESET3}`
6170
+ ` ${writer.wrapInColor(hotkey.key.padEnd(16), CliForegroundColor.Cyan)} ${DIM3}${hotkey.description}${RESET3}`
6139
6171
  );
6140
6172
  });
6141
6173
  }
@@ -14911,6 +14943,111 @@ ${this.fitLine(` \x1B[31m\u2718 ${message}\x1B[0m`)}\x1B[${this.extraLines}A`
14911
14943
  }
14912
14944
  };
14913
14945
 
14946
+ // src/lib/input/cli-line-buffer.ts
14947
+ var CliLineBuffer = class {
14948
+ constructor() {
14949
+ this._text = "";
14950
+ this._cursorPosition = 0;
14951
+ }
14952
+ get text() {
14953
+ return this._text;
14954
+ }
14955
+ get cursorPosition() {
14956
+ return this._cursorPosition;
14957
+ }
14958
+ set cursorPosition(value) {
14959
+ this._cursorPosition = Math.max(0, Math.min(value, this._text.length));
14960
+ }
14961
+ insert(str) {
14962
+ this._text = this._text.slice(0, this._cursorPosition) + str + this._text.slice(this._cursorPosition);
14963
+ this._cursorPosition += str.length;
14964
+ }
14965
+ deleteCharBefore() {
14966
+ if (this._cursorPosition > 0) {
14967
+ this._text = this._text.slice(0, this._cursorPosition - 1) + this._text.slice(this._cursorPosition);
14968
+ this._cursorPosition--;
14969
+ }
14970
+ }
14971
+ deleteCharAt() {
14972
+ if (this._cursorPosition < this._text.length) {
14973
+ this._text = this._text.slice(0, this._cursorPosition) + this._text.slice(this._cursorPosition + 1);
14974
+ }
14975
+ }
14976
+ moveCursorLeft() {
14977
+ if (this._cursorPosition > 0) {
14978
+ this._cursorPosition--;
14979
+ }
14980
+ }
14981
+ moveCursorRight() {
14982
+ if (this._cursorPosition < this._text.length) {
14983
+ this._cursorPosition++;
14984
+ }
14985
+ }
14986
+ moveHome() {
14987
+ this._cursorPosition = 0;
14988
+ }
14989
+ moveEnd() {
14990
+ this._cursorPosition = this._text.length;
14991
+ }
14992
+ clear() {
14993
+ this._text = "";
14994
+ this._cursorPosition = 0;
14995
+ }
14996
+ setText(str) {
14997
+ this._text = str;
14998
+ this._cursorPosition = str.length;
14999
+ }
15000
+ /** Remove the text before the cursor (Ctrl+U); returns it */
15001
+ deleteToStart() {
15002
+ const removed = this._text.slice(0, this._cursorPosition);
15003
+ this._text = this._text.slice(this._cursorPosition);
15004
+ this._cursorPosition = 0;
15005
+ return removed;
15006
+ }
15007
+ /** Remove the text from the cursor to the end (Ctrl+K); returns it */
15008
+ deleteToEnd() {
15009
+ const removed = this._text.slice(this._cursorPosition);
15010
+ this._text = this._text.slice(0, this._cursorPosition);
15011
+ return removed;
15012
+ }
15013
+ /** Remove the whitespace-delimited word before the cursor (Ctrl+W); returns it */
15014
+ deleteWordBefore() {
15015
+ let start = this._cursorPosition;
15016
+ while (start > 0 && /\s/.test(this._text[start - 1])) start--;
15017
+ while (start > 0 && !/\s/.test(this._text[start - 1])) start--;
15018
+ const removed = this._text.slice(start, this._cursorPosition);
15019
+ this._text = this._text.slice(0, start) + this._text.slice(this._cursorPosition);
15020
+ this._cursorPosition = start;
15021
+ return removed;
15022
+ }
15023
+ };
15024
+ var HOME_KEYS = ["", "\x1B[H", "\x1BOH", "\x1B[1~", "\x1B[7~"];
15025
+ var END_KEYS = ["", "\x1B[F", "\x1BOF", "\x1B[4~", "\x1B[8~"];
15026
+ var DELETE_KEYS = ["\x1B[3~", ""];
15027
+ function applyEditingKey(buffer, data, killRing) {
15028
+ if (HOME_KEYS.includes(data)) {
15029
+ buffer.moveHome();
15030
+ } else if (END_KEYS.includes(data)) {
15031
+ buffer.moveEnd();
15032
+ } else if (DELETE_KEYS.includes(data)) {
15033
+ buffer.deleteCharAt();
15034
+ } else if (data === "") {
15035
+ killRing.text = buffer.deleteToStart() || killRing.text;
15036
+ } else if (data === "\v") {
15037
+ killRing.text = buffer.deleteToEnd() || killRing.text;
15038
+ } else if (data === "" || data === "\x1B\x7F") {
15039
+ killRing.text = buffer.deleteWordBefore() || killRing.text;
15040
+ } else if (data === "") {
15041
+ if (killRing.text) buffer.insert(killRing.text);
15042
+ } else {
15043
+ return false;
15044
+ }
15045
+ return true;
15046
+ }
15047
+ function isEscapeSequence(data) {
15048
+ return /^\u001b(\[[0-9;?]*[~A-Za-z]|O[A-Za-z])$/.test(data);
15049
+ }
15050
+
14914
15051
  // src/lib/input/modes/line-input-mode.ts
14915
15052
  var LineInputMode = class extends InputModeBase {
14916
15053
  constructor(host, resolve, promptText, options) {
@@ -14918,6 +15055,7 @@ var LineInputMode = class extends InputModeBase {
14918
15055
  this.promptText = promptText;
14919
15056
  this.options = options;
14920
15057
  this.hasError = false;
15058
+ this.killRing = { text: "" };
14921
15059
  this.buffer = options?.default ?? "";
14922
15060
  this.cursorPosition = this.buffer.length;
14923
15061
  }
@@ -14962,7 +15100,7 @@ var LineInputMode = class extends InputModeBase {
14962
15100
  this.cursorPosition++;
14963
15101
  this.host.terminal.write(data);
14964
15102
  }
14965
- } else if (data.startsWith("\x1B")) ; else {
15103
+ } else if (this.applyEditingKey(data)) ; else if (data.startsWith("\x1B")) ; else {
14966
15104
  const text = data.replace(/[\r\n]+/g, "");
14967
15105
  this.buffer = this.buffer.slice(0, this.cursorPosition) + text + this.buffer.slice(this.cursorPosition);
14968
15106
  this.cursorPosition += text.length;
@@ -14973,6 +15111,16 @@ var LineInputMode = class extends InputModeBase {
14973
15111
  }
14974
15112
  }
14975
15113
  }
15114
+ applyEditingKey(data) {
15115
+ const line2 = new CliLineBuffer();
15116
+ line2.setText(this.buffer);
15117
+ line2.cursorPosition = this.cursorPosition;
15118
+ if (!applyEditingKey(line2, data, this.killRing)) return false;
15119
+ this.buffer = line2.text;
15120
+ this.cursorPosition = line2.cursorPosition;
15121
+ this.renderLine();
15122
+ return true;
15123
+ }
14976
15124
  onResize() {
14977
15125
  this.renderLine();
14978
15126
  }
@@ -16708,67 +16856,13 @@ var CliCompletionEngine = class {
16708
16856
  }
16709
16857
  };
16710
16858
 
16711
- // src/lib/input/cli-line-buffer.ts
16712
- var CliLineBuffer = class {
16713
- constructor() {
16714
- this._text = "";
16715
- this._cursorPosition = 0;
16716
- }
16717
- get text() {
16718
- return this._text;
16719
- }
16720
- get cursorPosition() {
16721
- return this._cursorPosition;
16722
- }
16723
- set cursorPosition(value) {
16724
- this._cursorPosition = Math.max(0, Math.min(value, this._text.length));
16725
- }
16726
- insert(str) {
16727
- this._text = this._text.slice(0, this._cursorPosition) + str + this._text.slice(this._cursorPosition);
16728
- this._cursorPosition += str.length;
16729
- }
16730
- deleteCharBefore() {
16731
- if (this._cursorPosition > 0) {
16732
- this._text = this._text.slice(0, this._cursorPosition - 1) + this._text.slice(this._cursorPosition);
16733
- this._cursorPosition--;
16734
- }
16735
- }
16736
- deleteCharAt() {
16737
- if (this._cursorPosition < this._text.length) {
16738
- this._text = this._text.slice(0, this._cursorPosition) + this._text.slice(this._cursorPosition + 1);
16739
- }
16740
- }
16741
- moveCursorLeft() {
16742
- if (this._cursorPosition > 0) {
16743
- this._cursorPosition--;
16744
- }
16745
- }
16746
- moveCursorRight() {
16747
- if (this._cursorPosition < this._text.length) {
16748
- this._cursorPosition++;
16749
- }
16750
- }
16751
- moveHome() {
16752
- this._cursorPosition = 0;
16753
- }
16754
- moveEnd() {
16755
- this._cursorPosition = this._text.length;
16756
- }
16757
- clear() {
16758
- this._text = "";
16759
- this._cursorPosition = 0;
16760
- }
16761
- setText(str) {
16762
- this._text = str;
16763
- this._cursorPosition = str.length;
16764
- }
16765
- };
16766
-
16767
16859
  // src/lib/input/command-line-mode.ts
16768
16860
  var CommandLineMode = class {
16769
16861
  constructor(host) {
16770
16862
  this.host = host;
16771
16863
  this.historyIndex = 0;
16864
+ /** History generation `historyIndex` points into (another user's history may be loaded since) */
16865
+ this.historyGeneration = 0;
16772
16866
  this.isExecutingCommand = false;
16773
16867
  this.selectionStart = null;
16774
16868
  this.selectionEnd = null;
@@ -16784,6 +16878,7 @@ var CommandLineMode = class {
16784
16878
  * into the command's output and appended to the line just submitted.
16785
16879
  */
16786
16880
  this.typeAhead = [];
16881
+ this.killRing = { text: "" };
16787
16882
  }
16788
16883
  activate() {
16789
16884
  this.host.commandHistory.initialize().then(() => {
@@ -16839,7 +16934,7 @@ var CommandLineMode = class {
16839
16934
  }
16840
16935
  } else if (data === "\x7F") {
16841
16936
  this.handleBackspace();
16842
- } else {
16937
+ } else if (this.applyEditingKey(data)) ; else if (isEscapeSequence(data)) ; else {
16843
16938
  this.handleInputText(data);
16844
16939
  }
16845
16940
  }
@@ -16937,6 +17032,16 @@ var CommandLineMode = class {
16937
17032
  } catch {
16938
17033
  }
16939
17034
  }
17035
+ applyEditingKey(data) {
17036
+ const buffer = this.host.lineBuffer;
17037
+ const before = buffer.text;
17038
+ const previousContentLength = this.host.getPromptLength() + before.length;
17039
+ if (!applyEditingKey(buffer, data, this.killRing)) return false;
17040
+ this.clearGhostText();
17041
+ if (buffer.text !== before) this.clearHistorySearch();
17042
+ this.refreshLine(previousContentLength);
17043
+ return true;
17044
+ }
16940
17045
  handleBackspace() {
16941
17046
  const buffer = this.host.lineBuffer;
16942
17047
  this.clearGhostText();
@@ -17002,7 +17107,17 @@ var CommandLineMode = class {
17002
17107
  }
17003
17108
  }
17004
17109
  }
17110
+ /** Start over at the end when another history was loaded (`su`, `exit`, login) */
17111
+ syncHistory() {
17112
+ const generation = this.host.commandHistory.generation;
17113
+ if (generation === this.historyGeneration) return;
17114
+ this.historyGeneration = generation;
17115
+ this.historySearchPrefix = null;
17116
+ this.bufferSetByHistory = false;
17117
+ this.historyIndex = this.host.commandHistory.getLastIndex();
17118
+ }
17005
17119
  showPreviousCommand() {
17120
+ this.syncHistory();
17006
17121
  this.clearGhostText();
17007
17122
  const buffer = this.host.lineBuffer;
17008
17123
  if (buffer.text && !this.bufferSetByHistory && this.historySearchPrefix === null) {
@@ -17026,6 +17141,7 @@ var CommandLineMode = class {
17026
17141
  }
17027
17142
  }
17028
17143
  showNextCommand() {
17144
+ this.syncHistory();
17029
17145
  this.clearGhostText();
17030
17146
  const buffer = this.host.lineBuffer;
17031
17147
  if (this.historySearchPrefix !== null) {
@@ -17289,6 +17405,7 @@ var CliExecutionContext = class {
17289
17405
  set userSession(session) {
17290
17406
  const previous = this._userSession?.user?.name;
17291
17407
  this._userSession = session;
17408
+ void this.commandHistory?.useUser(session?.user?.id);
17292
17409
  if (session?.user?.name !== previous) {
17293
17410
  this.events?.emit("session:changed", { user: session?.user?.name });
17294
17411
  }
@@ -18861,15 +18978,41 @@ var CliLogger = class {
18861
18978
  };
18862
18979
 
18863
18980
  // src/lib/services/cli-command-history.ts
18981
+ var SHARED_KEY = "cli-command-history";
18982
+ var MIGRATED_KEY = "cli-command-history:migrated";
18864
18983
  var CliCommandHistory = class {
18865
18984
  constructor(store) {
18866
18985
  this.store = store;
18867
- this.storageKey = "cli-command-history";
18868
18986
  this.commandHistory = [];
18987
+ this.loading = Promise.resolve();
18988
+ /** Counts user switches (a load for an older one is dropped) */
18989
+ this.switches = 0;
18990
+ this._generation = 0;
18991
+ }
18992
+ /** Changes whenever the history is replaced: switched or loaded (positions into it are stale) */
18993
+ get generation() {
18994
+ return this._generation;
18995
+ }
18996
+ get storageKey() {
18997
+ return this.userId === void 0 ? SHARED_KEY : `${SHARED_KEY}:user:${this.userId}`;
18998
+ }
18999
+ /**
19000
+ * Switch to the history of `userId` (undefined: nobody signed in).
19001
+ * Resolves once it is loaded; a no-op for the user already loaded.
19002
+ */
19003
+ useUser(userId) {
19004
+ if (userId === this.userId) return this.loading;
19005
+ this.userId = userId;
19006
+ this.commandHistory = [];
19007
+ this.switches++;
19008
+ this._generation++;
19009
+ this.loading = this.loadHistory();
19010
+ return this.loading;
18869
19011
  }
18870
19012
  async addCommand(command2) {
18871
19013
  const normalizedCommand = command2.trim();
18872
19014
  if (!normalizedCommand) return;
19015
+ await this.loading;
18873
19016
  const existingIndex = this.commandHistory.indexOf(normalizedCommand);
18874
19017
  if (existingIndex !== -1) {
18875
19018
  this.commandHistory.splice(existingIndex, 1);
@@ -18936,18 +19079,32 @@ var CliCommandHistory = class {
18936
19079
  return await this.store.set(this.storageKey, trimmedHistory);
18937
19080
  }
18938
19081
  async loadHistory() {
18939
- const savedOldHistory = localStorage.getItem("cliCommandHistory");
19082
+ const key = this.storageKey;
19083
+ const switches = this.switches;
19084
+ const savedOldHistory = typeof localStorage !== "undefined" ? localStorage.getItem("cliCommandHistory") : null;
18940
19085
  if (savedOldHistory) {
18941
19086
  localStorage.removeItem("cliCommandHistory");
18942
- await this.store.set(this.storageKey, JSON.parse(savedOldHistory));
19087
+ await this.store.set(SHARED_KEY, JSON.parse(savedOldHistory));
19088
+ }
19089
+ let savedHistory = await this.store.get(key);
19090
+ if (!savedHistory && key !== SHARED_KEY && !await this.store.get(MIGRATED_KEY)) {
19091
+ await this.store.set(MIGRATED_KEY, true);
19092
+ const shared = await this.store.get(SHARED_KEY);
19093
+ if (shared?.length) {
19094
+ await this.store.set(key, shared);
19095
+ await this.store.remove(SHARED_KEY);
19096
+ savedHistory = shared;
19097
+ }
18943
19098
  }
18944
- const savedHistory = await this.store.get(this.storageKey);
19099
+ if (switches !== this.switches) return;
18945
19100
  if (savedHistory) {
18946
19101
  this.commandHistory = savedHistory;
19102
+ this._generation++;
18947
19103
  }
18948
19104
  }
18949
19105
  async initialize() {
18950
- await this.loadHistory();
19106
+ this.loading = this.loadHistory();
19107
+ await this.loading;
18951
19108
  }
18952
19109
  };
18953
19110
  var welcomeModule = {
@@ -22989,6 +23146,9 @@ var CliTestHarness = class {
22989
23146
  createContext(writer) {
22990
23147
  const process = new CliExecutionProcess(null);
22991
23148
  const reader = createQueuedReader(this.readerResponses);
23149
+ const provider = this.userSessionProvider;
23150
+ const fallback = this.userSession;
23151
+ const assigned = { set: false };
22992
23152
  const context2 = {
22993
23153
  writer,
22994
23154
  services: this.services,
@@ -23004,7 +23164,14 @@ var CliTestHarness = class {
23004
23164
  options: void 0,
23005
23165
  logger: createNoopLogger(),
23006
23166
  process,
23007
- userSession: this.userSessionProvider ? this.userSessionProvider() : this.userSession,
23167
+ // Live, like the engine's root context (commands run by a command see changes)
23168
+ get userSession() {
23169
+ return assigned.set ? assigned.value : provider ? provider() : fallback;
23170
+ },
23171
+ set userSession(value) {
23172
+ assigned.set = true;
23173
+ assigned.value = value;
23174
+ },
23008
23175
  state: this.stateStoreManager.getStateStore("shared"),
23009
23176
  backgroundServices: createNoopBackgroundServices(),
23010
23177
  http: new CliHttpClient(),