@qodalis/cli 2.0.0-beta.15 → 2.0.0-beta.17

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.js CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  var cliCore = require('@qodalis/cli-core');
4
- var rxjs = require('rxjs');
5
- var operators = require('rxjs/operators');
6
4
  var xterm = require('@xterm/xterm');
7
5
  var addonFit = require('@xterm/addon-fit');
8
6
  var addonWebLinks = require('@xterm/addon-web-links');
9
7
  var addonUnicode11 = require('@xterm/addon-unicode11');
10
8
  var addonSerialize = require('@xterm/addon-serialize');
9
+ var rxjs = require('rxjs');
10
+ var operators = require('rxjs/operators');
11
11
 
12
12
  // src/lib/parsers/command-parser.ts
13
13
  var CommandParser = class {
@@ -413,7 +413,7 @@ var CliKeyValueStore_TOKEN = "cli-key-value-store";
413
413
  var SyntaxHighlighterRegistry_TOKEN = "syntax-highlighter-registry";
414
414
 
415
415
  // src/lib/version.ts
416
- var LIBRARY_VERSION = "2.0.0-beta.15";
416
+ var LIBRARY_VERSION = "2.0.0-beta.17";
417
417
  var API_VERSION = 2;
418
418
  var ANONYMOUS = "\0anonymous";
419
419
  function aliasOwner(context2) {
@@ -2699,6 +2699,150 @@ var CliYesCommandProcessor = class {
2699
2699
  );
2700
2700
  }
2701
2701
  };
2702
+
2703
+ // src/lib/addons/overlay.ts
2704
+ var OverlayAddon = class {
2705
+ constructor() {
2706
+ this.overlayNode = document.createElement("div");
2707
+ this.overlayNode.style.cssText = `border-radius: 15px;
2708
+ font-size: xx-large;
2709
+ opacity: 0.75;
2710
+ padding: 0.2em 0.5em 0.2em 0.5em;
2711
+ position: absolute;
2712
+ -webkit-user-select: none;
2713
+ -webkit-transition: opacity 180ms ease-in;
2714
+ -moz-user-select: none;
2715
+ -moz-transition: opacity 180ms ease-in;`;
2716
+ this.overlayNode.addEventListener(
2717
+ "mousedown",
2718
+ (e) => {
2719
+ e.preventDefault();
2720
+ e.stopPropagation();
2721
+ },
2722
+ true
2723
+ );
2724
+ }
2725
+ activate(terminal) {
2726
+ this.terminal = terminal;
2727
+ }
2728
+ dispose() {
2729
+ }
2730
+ showOverlay(msg, timeout) {
2731
+ const { terminal, overlayNode } = this;
2732
+ if (!terminal.element) return;
2733
+ overlayNode.style.color = "#101010";
2734
+ overlayNode.style.backgroundColor = "#f0f0f0";
2735
+ overlayNode.textContent = msg;
2736
+ overlayNode.style.opacity = "0.75";
2737
+ if (!overlayNode.parentNode) {
2738
+ terminal.element.appendChild(overlayNode);
2739
+ }
2740
+ const divSize = terminal.element.getBoundingClientRect();
2741
+ const overlaySize = overlayNode.getBoundingClientRect();
2742
+ overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + "px";
2743
+ overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + "px";
2744
+ if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
2745
+ if (!timeout) return;
2746
+ this.overlayTimeout = window.setTimeout(() => {
2747
+ overlayNode.style.opacity = "0";
2748
+ this.overlayTimeout = window.setTimeout(() => {
2749
+ if (overlayNode.parentNode) {
2750
+ overlayNode.parentNode.removeChild(overlayNode);
2751
+ }
2752
+ this.overlayTimeout = void 0;
2753
+ overlayNode.style.opacity = "0.75";
2754
+ }, 200);
2755
+ }, timeout || 1500);
2756
+ }
2757
+ };
2758
+ var CliDragDropService = class {
2759
+ constructor(_container) {
2760
+ this._container = _container;
2761
+ this._subject = new rxjs.Subject();
2762
+ this._dragOver = (e) => e.preventDefault();
2763
+ this._drop = (e) => {
2764
+ e.preventDefault();
2765
+ const files = Array.from(e.dataTransfer?.files ?? []);
2766
+ if (files.length > 0) {
2767
+ this._subject.next(files);
2768
+ }
2769
+ };
2770
+ this._container.addEventListener("dragover", this._dragOver);
2771
+ this._container.addEventListener("drop", this._drop);
2772
+ }
2773
+ get onFileDrop() {
2774
+ return this._subject.asObservable();
2775
+ }
2776
+ destroy() {
2777
+ this._container.removeEventListener("dragover", this._dragOver);
2778
+ this._container.removeEventListener("drop", this._drop);
2779
+ this._subject.complete();
2780
+ }
2781
+ };
2782
+
2783
+ // src/lib/engine/cli-terminal-setup.ts
2784
+ var NARROW_VIEWPORT = 640;
2785
+ function defaultFontSize() {
2786
+ const width = typeof window !== "undefined" ? window.innerWidth : 0;
2787
+ return width > 0 && width <= NARROW_VIEWPORT ? 14 : 20;
2788
+ }
2789
+ function getTerminalOptions(userOptions) {
2790
+ return {
2791
+ cursorBlink: true,
2792
+ allowProposedApi: true,
2793
+ fontSize: defaultFontSize(),
2794
+ theme: cliCore.DefaultThemes.default,
2795
+ convertEol: true,
2796
+ ...userOptions ?? {}
2797
+ };
2798
+ }
2799
+ function initializeTerminal(container, opts) {
2800
+ const terminal = new xterm.Terminal(opts);
2801
+ const fitAddon = new addonFit.FitAddon();
2802
+ terminal.loadAddon(fitAddon);
2803
+ terminal.loadAddon(new addonWebLinks.WebLinksAddon());
2804
+ terminal.loadAddon(new OverlayAddon());
2805
+ terminal.loadAddon(new addonUnicode11.Unicode11Addon());
2806
+ const serializeAddon = new addonSerialize.SerializeAddon();
2807
+ terminal.loadAddon(serializeAddon);
2808
+ terminal.open(container);
2809
+ fitAddon.fit();
2810
+ requestAnimationFrame(() => fitAddon.fit());
2811
+ if (document.fonts?.ready) {
2812
+ document.fonts.ready.then(() => {
2813
+ requestAnimationFrame(() => fitAddon.fit());
2814
+ });
2815
+ }
2816
+ container.classList.add("terminal-container");
2817
+ container.style.background = opts.theme?.background ?? "#000";
2818
+ const wheelListener = (e) => e.preventDefault();
2819
+ container.addEventListener("wheel", wheelListener, { passive: false });
2820
+ terminal.focus();
2821
+ const dragDropService = new CliDragDropService(container);
2822
+ return { terminal, fitAddon, serializeAddon, dragDropService, wheelListener };
2823
+ }
2824
+ var LAYOUT_TIMEOUT_MS = 1e4;
2825
+ function waitForLayout(container) {
2826
+ return new Promise((resolve, reject) => {
2827
+ const deadline = Date.now() + LAYOUT_TIMEOUT_MS;
2828
+ const check = () => {
2829
+ if (container.offsetWidth > 0 && container.offsetHeight > 0) {
2830
+ resolve();
2831
+ return;
2832
+ }
2833
+ if (Date.now() >= deadline) {
2834
+ reject(new Error(
2835
+ `CliEngine.start(): container has zero dimensions after ${LAYOUT_TIMEOUT_MS}ms. Ensure the container element is visible and laid out before calling start().`
2836
+ ));
2837
+ return;
2838
+ }
2839
+ requestAnimationFrame(check);
2840
+ };
2841
+ check();
2842
+ });
2843
+ }
2844
+
2845
+ // src/lib/processors/configure/cli-configure-command-processor.ts
2702
2846
  var SYSTEM_OPTIONS = [
2703
2847
  {
2704
2848
  key: "logLevel",
@@ -2775,7 +2919,7 @@ var SYSTEM_OPTIONS = [
2775
2919
  label: "Font Size",
2776
2920
  description: "Terminal font size in pixels",
2777
2921
  type: "number",
2778
- defaultValue: 20,
2922
+ defaultValue: defaultFontSize(),
2779
2923
  validator: (value) => {
2780
2924
  if (value < 8 || value > 40) {
2781
2925
  return { valid: false, message: "Font size must be between 8 and 40" };
@@ -2855,7 +2999,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
2855
2999
  async initialize(context2) {
2856
3000
  const state = context2.state.getState();
2857
3001
  if (state?.system) {
2858
- this.applySystemSettings(state.system, context2);
3002
+ this.applySystemSettings(state.system, context2, true);
2859
3003
  }
2860
3004
  }
2861
3005
  writeDescription(context2) {
@@ -3090,7 +3234,11 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3090
3234
  }
3091
3235
  return result;
3092
3236
  }
3093
- applySystemSettings(settings, context2) {
3237
+ /**
3238
+ * @param onBoot at startup, terminal options still at their default are skipped so the
3239
+ * host's `terminalOptions` (e.g. its own fontSize) are not overridden.
3240
+ */
3241
+ applySystemSettings(settings, context2, onBoot = false) {
3094
3242
  if (settings["logLevel"]) {
3095
3243
  const level = LOG_LEVEL_MAP[settings["logLevel"]];
3096
3244
  if (level !== void 0) {
@@ -3104,23 +3252,29 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3104
3252
  translator?.setLocale(settings["language"]);
3105
3253
  }
3106
3254
  if (context2.terminal?.options) {
3107
- if (settings["cursorBlink"] !== void 0) {
3108
- context2.terminal.options.cursorBlink = settings["cursorBlink"];
3255
+ const terminal = onBoot ? Object.fromEntries(
3256
+ Object.entries(settings).filter(([key, value]) => {
3257
+ const option = SYSTEM_OPTIONS.find((o) => o.key === key);
3258
+ return !option || value !== option.defaultValue;
3259
+ })
3260
+ ) : settings;
3261
+ if (terminal["cursorBlink"] !== void 0) {
3262
+ context2.terminal.options.cursorBlink = terminal["cursorBlink"];
3109
3263
  }
3110
- if (settings["cursorStyle"]) {
3111
- context2.terminal.options.cursorStyle = settings["cursorStyle"];
3264
+ if (terminal["cursorStyle"]) {
3265
+ context2.terminal.options.cursorStyle = terminal["cursorStyle"];
3112
3266
  }
3113
- if (settings["scrollback"] !== void 0) {
3114
- context2.terminal.options.scrollback = settings["scrollback"];
3267
+ if (terminal["scrollback"] !== void 0) {
3268
+ context2.terminal.options.scrollback = terminal["scrollback"];
3115
3269
  }
3116
- if (settings["fontSize"] !== void 0) {
3117
- context2.terminal.options.fontSize = settings["fontSize"];
3270
+ if (terminal["fontSize"] !== void 0) {
3271
+ context2.terminal.options.fontSize = terminal["fontSize"];
3118
3272
  }
3119
- if (settings["fontFamily"]) {
3120
- context2.terminal.options.fontFamily = settings["fontFamily"];
3273
+ if (terminal["fontFamily"]) {
3274
+ context2.terminal.options.fontFamily = terminal["fontFamily"];
3121
3275
  }
3122
- if (settings["smoothScrollDuration"] !== void 0) {
3123
- context2.terminal.options.smoothScrollDuration = settings["smoothScrollDuration"];
3276
+ if (terminal["smoothScrollDuration"] !== void 0) {
3277
+ context2.terminal.options.smoothScrollDuration = terminal["smoothScrollDuration"];
3124
3278
  }
3125
3279
  }
3126
3280
  }
@@ -3718,6 +3872,7 @@ var CliHelpCommandProcessor = class {
3718
3872
  return 0;
3719
3873
  }
3720
3874
  );
3875
+ const narrow = isNarrow(context2);
3721
3876
  sortedModules.forEach(([module, processors]) => {
3722
3877
  const displayName = this.formatModuleName(module);
3723
3878
  writer.writeln(
@@ -3726,7 +3881,7 @@ var CliHelpCommandProcessor = class {
3726
3881
  processors.forEach((processor) => {
3727
3882
  const icon = processor?.metadata?.icon || cliCore.CliIcon.Extension;
3728
3883
  const name = writer.wrapInColor(
3729
- processor.command.padEnd(20),
3884
+ narrow ? processor.command : processor.command.padEnd(20),
3730
3885
  cliCore.CliForegroundColor.Cyan
3731
3886
  );
3732
3887
  const aliasText = processor.aliases?.length ? `${DIM} ${processor.aliases.join(", ")}${RESET}` : "";
@@ -3735,6 +3890,11 @@ var CliHelpCommandProcessor = class {
3735
3890
  processor.command,
3736
3891
  context2
3737
3892
  );
3893
+ if (narrow) {
3894
+ writer.writeln(` ${icon} ${name}${aliasText}`);
3895
+ writer.writeln(` ${desc}`);
3896
+ return;
3897
+ }
3738
3898
  writer.writeln(` ${icon} ${name} ${desc}${aliasText}`);
3739
3899
  });
3740
3900
  writer.writeln();
@@ -3844,9 +4004,10 @@ var CliHelpCommandProcessor = class {
3844
4004
  writer.writeln(
3845
4005
  ` ${BOLD}${writer.wrapInColor(t.t("cli.help.subcommands", "Subcommands"), cliCore.CliForegroundColor.Yellow)}${RESET}`
3846
4006
  );
4007
+ const narrow = isNarrow(context2);
3847
4008
  processor.processors.forEach((sub) => {
3848
4009
  const subName = writer.wrapInColor(
3849
- sub.command.padEnd(16),
4010
+ narrow ? sub.command : sub.command.padEnd(16),
3850
4011
  cliCore.CliForegroundColor.Cyan
3851
4012
  );
3852
4013
  const subAliases = sub.aliases?.length ? `${DIM} (${sub.aliases.join(", ")})${RESET}` : "";
@@ -3855,6 +4016,11 @@ var CliHelpCommandProcessor = class {
3855
4016
  `${processor.command}.${sub.command}`,
3856
4017
  context2
3857
4018
  );
4019
+ if (narrow) {
4020
+ writer.writeln(` ${subName}${subAliases}`);
4021
+ writer.writeln(` ${subDesc}`);
4022
+ return;
4023
+ }
3858
4024
  writer.writeln(
3859
4025
  ` ${subName} ${subDesc}${subAliases}`
3860
4026
  );
@@ -3868,7 +4034,7 @@ var CliHelpCommandProcessor = class {
3868
4034
  ` ${BOLD}${writer.wrapInColor(t.t("cli.help.options", "Options"), cliCore.CliForegroundColor.Yellow)}${RESET}`
3869
4035
  );
3870
4036
  commandParams.forEach((param) => {
3871
- this.writeParameter(param, writer, t);
4037
+ this.writeParameter(param, writer, t, isNarrow(context2));
3872
4038
  });
3873
4039
  }
3874
4040
  if (globalParams.length > 0) {
@@ -3877,7 +4043,7 @@ var CliHelpCommandProcessor = class {
3877
4043
  ` ${BOLD}${writer.wrapInColor(t.t("cli.help.global_options", "Global options"), cliCore.CliForegroundColor.Yellow)}${RESET}`
3878
4044
  );
3879
4045
  globalParams.forEach((param) => {
3880
- this.writeParameter(param, writer, t);
4046
+ this.writeParameter(param, writer, t, isNarrow(context2));
3881
4047
  });
3882
4048
  }
3883
4049
  if (processor.metadata?.requireServer) {
@@ -3899,7 +4065,7 @@ var CliHelpCommandProcessor = class {
3899
4065
  }
3900
4066
  return module.charAt(0).toUpperCase() + module.slice(1);
3901
4067
  }
3902
- writeParameter(param, writer, translator) {
4068
+ writeParameter(param, writer, translator, narrow = false) {
3903
4069
  const aliases = param.aliases?.length ? `, ${param.aliases.map((a) => `-${a}`).join(", ")}` : "";
3904
4070
  const nameStr = writer.wrapInColor(
3905
4071
  `--${param.name}${aliases}`,
@@ -3908,11 +4074,19 @@ var CliHelpCommandProcessor = class {
3908
4074
  const typeStr = `${DIM}<${param.type}>${RESET}`;
3909
4075
  const required = param.required ? writer.wrapInColor(` ${translator.t("cli.help.required", "(required)")}`, cliCore.CliForegroundColor.Red) : "";
3910
4076
  const defaultVal = param.defaultValue !== void 0 && !param.required ? `${DIM} [${param.defaultValue}]${RESET}` : "";
4077
+ if (narrow) {
4078
+ writer.writeln(` ${nameStr} ${typeStr}${required}${defaultVal}`);
4079
+ writer.writeln(` ${param.description}`);
4080
+ return;
4081
+ }
3911
4082
  writer.writeln(
3912
4083
  ` ${nameStr} ${typeStr} ${param.description}${required}${defaultVal}`
3913
4084
  );
3914
4085
  }
3915
4086
  };
4087
+ function isNarrow(context2) {
4088
+ return (context2.terminal?.cols ?? 80) < 60;
4089
+ }
3916
4090
 
3917
4091
  // src/lib/constants/index.ts
3918
4092
  var CLi_Name_Art_Full = `
@@ -9832,11 +10006,12 @@ function hexToRgb(hex) {
9832
10006
  const n = parseInt(m[1], 16);
9833
10007
  return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
9834
10008
  }
9835
- function colorSwatch(hex) {
9836
- if (!hex) return " ";
10009
+ function colorSwatch(hex, width = 2) {
10010
+ const cell = " ".repeat(width);
10011
+ if (!hex) return cell;
9837
10012
  const rgb = hexToRgb(hex);
9838
- if (!rgb) return " ";
9839
- return `\x1B[48;2;${rgb.r};${rgb.g};${rgb.b}m \x1B[0m`;
10013
+ if (!rgb) return cell;
10014
+ return `\x1B[48;2;${rgb.r};${rgb.g};${rgb.b}m${cell}\x1B[0m`;
9840
10015
  }
9841
10016
  function colorText(text, hex) {
9842
10017
  if (!hex) return text;
@@ -9903,11 +10078,20 @@ var CliThemeCommandProcessor = class {
9903
10078
  )
9904
10079
  );
9905
10080
  context2.writer.writeln();
10081
+ const narrow = (context2.terminal?.cols ?? 80) < 60;
9906
10082
  for (const [name, info] of entries) {
9907
10083
  const active = name === currentName ? " (active)" : "";
9908
10084
  const swatches = PALETTE_KEYS.map(
9909
10085
  (k) => colorSwatch(info.theme[k])
9910
10086
  ).join("");
10087
+ if (narrow) {
10088
+ const compact = PALETTE_KEYS.slice(0, 8).map((k) => colorSwatch(info.theme[k], 1)).join("");
10089
+ context2.writer.writeln(
10090
+ ` ${compact} ${context2.writer.wrapInColor(name, cliCore.CliForegroundColor.Cyan)}${active}`
10091
+ );
10092
+ context2.writer.writeln(` ${info.description}`);
10093
+ continue;
10094
+ }
9911
10095
  const nameLabel = context2.writer.wrapInColor(
9912
10096
  name.padEnd(22),
9913
10097
  cliCore.CliForegroundColor.Cyan
@@ -10609,37 +10793,106 @@ var CliPingCommandProcessor = class {
10609
10793
  var ICliEnvironment_TOKEN = "cli-environment";
10610
10794
  var CliEnvironment = class {
10611
10795
  constructor() {
10612
- this.vars = /* @__PURE__ */ new Map();
10613
- this.vars.set("SHELL", "/bin/sh");
10614
- this.vars.set("TERM", "xterm-256color");
10615
- this.vars.set("LANG", "en_US.UTF-8");
10616
- this.vars.set("PWD", "/");
10617
- this.vars.set("HOME", "/home/user");
10618
- this.vars.set("USER", "user");
10619
- this.vars.set("PATH", "/usr/local/bin:/usr/bin:/bin");
10796
+ this.defaults = /* @__PURE__ */ new Map();
10797
+ /** Variables computed on read; a user's own value wins over them. */
10798
+ this.bound = /* @__PURE__ */ new Map();
10799
+ this.profiles = /* @__PURE__ */ new Map();
10800
+ this.profileKey = () => "";
10801
+ this.defaults.set("SHELL", "/bin/sh");
10802
+ this.defaults.set("TERM", "xterm-256color");
10803
+ this.defaults.set("LANG", "en_US.UTF-8");
10804
+ this.defaults.set("PWD", "/");
10805
+ this.defaults.set("HOME", "/home/user");
10806
+ this.defaults.set("USER", "user");
10807
+ this.defaults.set("PATH", "/usr/local/bin:/usr/bin:/bin");
10808
+ }
10809
+ /** Compute a variable on every read; the seeded value is the fallback while it returns nothing. */
10810
+ bind(name, resolver) {
10811
+ this.bound.set(name, resolver);
10812
+ }
10813
+ /** Pick whose changes apply (the signed-in user's name, '' when signed out). */
10814
+ setProfileResolver(resolver) {
10815
+ this.profileKey = () => resolver() ?? "";
10816
+ }
10817
+ /** Restore saved profiles (replaces the ones in memory). */
10818
+ load(snapshot) {
10819
+ this.profiles.clear();
10820
+ for (const [key, profile] of Object.entries(snapshot ?? {})) {
10821
+ this.profiles.set(key, {
10822
+ vars: new Map(Object.entries(profile?.vars ?? {})),
10823
+ unset: new Set(profile?.unset ?? [])
10824
+ });
10825
+ }
10826
+ }
10827
+ snapshot() {
10828
+ const result = {};
10829
+ for (const [key, profile] of this.profiles) {
10830
+ if (!profile.vars.size && !profile.unset.size) continue;
10831
+ result[key] = { vars: Object.fromEntries(profile.vars), unset: [...profile.unset] };
10832
+ }
10833
+ return result;
10620
10834
  }
10621
10835
  get(name) {
10622
- return this.vars.get(name);
10836
+ const profile = this.profiles.get(this.profileKey());
10837
+ if (profile?.vars.has(name)) return profile.vars.get(name);
10838
+ if (profile?.unset.has(name)) return void 0;
10839
+ return this.bound.get(name)?.() || this.defaults.get(name);
10623
10840
  }
10624
10841
  set(name, value) {
10625
- this.vars.set(name, value);
10842
+ const profile = this.currentProfile();
10843
+ profile.unset.delete(name);
10844
+ profile.vars.set(name, value);
10845
+ this.onChange?.(this.snapshot());
10626
10846
  }
10627
10847
  unset(name) {
10628
- this.vars.delete(name);
10848
+ const profile = this.currentProfile();
10849
+ profile.vars.delete(name);
10850
+ if (this.defaults.has(name) || this.bound.has(name)) profile.unset.add(name);
10851
+ this.onChange?.(this.snapshot());
10629
10852
  }
10630
10853
  getAll() {
10854
+ const profile = this.profiles.get(this.profileKey());
10855
+ const names = /* @__PURE__ */ new Set([...this.defaults.keys(), ...this.bound.keys(), ...profile?.vars.keys() ?? []]);
10631
10856
  const result = {};
10632
- for (const [k, v] of this.vars) {
10633
- result[k] = v;
10857
+ for (const name of names) {
10858
+ const value = this.get(name);
10859
+ if (value !== void 0) result[name] = value;
10634
10860
  }
10635
10861
  return result;
10636
10862
  }
10637
10863
  has(name) {
10638
- return this.vars.has(name);
10864
+ return this.get(name) !== void 0;
10865
+ }
10866
+ currentProfile() {
10867
+ const key = this.profileKey();
10868
+ let profile = this.profiles.get(key);
10869
+ if (!profile) {
10870
+ profile = { vars: /* @__PURE__ */ new Map(), unset: /* @__PURE__ */ new Set() };
10871
+ this.profiles.set(key, profile);
10872
+ }
10873
+ return profile;
10639
10874
  }
10640
10875
  };
10641
10876
 
10642
10877
  // src/lib/processors/cli-env-command-processor.ts
10878
+ var ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|'[^']*'|\S*)/y;
10879
+ var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
10880
+ function parseAssignments(raw) {
10881
+ const result = [];
10882
+ for (const token of raw.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? []) {
10883
+ ASSIGNMENT.lastIndex = 0;
10884
+ const match = ASSIGNMENT.exec(token);
10885
+ if (match && ASSIGNMENT.lastIndex === token.length) {
10886
+ const quoted = /^(["']).*\1$/.test(match[2]);
10887
+ result.push({ name: match[1], value: quoted ? match[2].slice(1, -1) : match[2] });
10888
+ } else if (NAME.test(token)) {
10889
+ result.push({ name: token });
10890
+ } else {
10891
+ return null;
10892
+ }
10893
+ }
10894
+ return result;
10895
+ }
10643
10896
  var CliExportCommandProcessor = class {
10644
10897
  constructor() {
10645
10898
  this.command = "export";
@@ -10647,6 +10900,21 @@ var CliExportCommandProcessor = class {
10647
10900
  this.author = cliCore.DefaultLibraryAuthor;
10648
10901
  this.acceptsRawInput = true;
10649
10902
  this.metadata = { icon: "\u{1F4E6}", module: "system" };
10903
+ this.stateConfiguration = {
10904
+ storeName: "environment",
10905
+ initialState: { profiles: {} }
10906
+ };
10907
+ }
10908
+ /** Restore saved variables and save every later change (export, unset, VAR=value). */
10909
+ async initialize(context2) {
10910
+ const env = context2.services.get(ICliEnvironment_TOKEN);
10911
+ if (!(env instanceof CliEnvironment)) return;
10912
+ const store = context2.state;
10913
+ env.load(store.getState()?.profiles);
10914
+ env.onChange = (profiles) => {
10915
+ store.updateState({ profiles });
10916
+ void store.persist();
10917
+ };
10650
10918
  }
10651
10919
  async processCommand(command2, context2) {
10652
10920
  const env = context2.services.getRequired(
@@ -10663,20 +10931,21 @@ var CliExportCommandProcessor = class {
10663
10931
  }
10664
10932
  return;
10665
10933
  }
10666
- const match = raw.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
10667
- if (!match) {
10934
+ const assignments = parseAssignments(raw);
10935
+ if (!assignments?.length) {
10668
10936
  context2.writer.writeError(
10669
10937
  `export: invalid syntax: ${raw}`
10670
10938
  );
10671
10939
  context2.process.exit(1, { silent: true });
10672
10940
  return;
10673
10941
  }
10674
- const varName = match[1];
10675
- let varValue = match[2];
10676
- if (varValue.startsWith('"') && varValue.endsWith('"') || varValue.startsWith("'") && varValue.endsWith("'")) {
10677
- varValue = varValue.slice(1, -1);
10942
+ for (const { name, value } of assignments) {
10943
+ if (value !== void 0) {
10944
+ env.set(name, value);
10945
+ } else if (!env.has(name)) {
10946
+ env.set(name, "");
10947
+ }
10678
10948
  }
10679
- env.set(varName, varValue);
10680
10949
  }
10681
10950
  writeDescription(context2) {
10682
10951
  const { writer, translator: t } = context2;
@@ -10692,6 +10961,13 @@ var CliExportCommandProcessor = class {
10692
10961
  writer.writeln(
10693
10962
  ` ${writer.wrapInColor('export VAR="hello world"', cliCore.CliForegroundColor.Cyan)} ${t.t("cli.export.set_spaces", "Set with spaces")}`
10694
10963
  );
10964
+ writer.writeln(
10965
+ ` ${writer.wrapInColor("export A=1 B=2", cliCore.CliForegroundColor.Cyan)} ${t.t("cli.export.set_many", "Set several at once")}`
10966
+ );
10967
+ writer.writeln();
10968
+ writer.writeln(
10969
+ t.t("cli.export.saved", "Variables are saved for the signed-in user and used as $VAR or ${VAR} in commands.")
10970
+ );
10695
10971
  }
10696
10972
  };
10697
10973
  var CliUnsetCommandProcessor = class {
@@ -10706,16 +10982,15 @@ var CliUnsetCommandProcessor = class {
10706
10982
  const env = context2.services.getRequired(
10707
10983
  ICliEnvironment_TOKEN
10708
10984
  );
10709
- const varName = (command2.value || "").trim();
10710
- if (!varName) {
10985
+ const names = (command2.value || "").trim().split(/\s+/).filter(Boolean);
10986
+ if (!names.length) {
10711
10987
  context2.writer.writeError("unset: missing variable name");
10712
10988
  context2.process.exit(1, { silent: true });
10713
10989
  return;
10714
10990
  }
10715
- if (!env.has(varName)) {
10716
- return;
10991
+ for (const name of names) {
10992
+ if (env.has(name)) env.unset(name);
10717
10993
  }
10718
- env.unset(varName);
10719
10994
  }
10720
10995
  writeDescription(context2) {
10721
10996
  const { writer, translator: t } = context2;
@@ -10723,7 +10998,7 @@ var CliUnsetCommandProcessor = class {
10723
10998
  writer.writeln();
10724
10999
  writer.writeln(t.t("cli.common.usage", "Usage:"));
10725
11000
  writer.writeln(
10726
- ` ${writer.wrapInColor("unset VAR", cliCore.CliForegroundColor.Cyan)}`
11001
+ ` ${writer.wrapInColor("unset VAR [VAR...]", cliCore.CliForegroundColor.Cyan)}`
10727
11002
  );
10728
11003
  }
10729
11004
  };
@@ -10773,7 +11048,6 @@ var CliEnvCommandProcessor = class {
10773
11048
  );
10774
11049
  }
10775
11050
  };
10776
- var DEFAULT_FONT_SIZE = 20;
10777
11051
  var MIN_FONT_SIZE = 8;
10778
11052
  var MAX_FONT_SIZE = 40;
10779
11053
  var STEP = 2;
@@ -10786,7 +11060,7 @@ var CliFontSizeCommandProcessor = class {
10786
11060
  this.metadata = { icon: cliCore.CliIcon.Settings, module: "system" };
10787
11061
  this.stateConfiguration = {
10788
11062
  storeName: "font-size",
10789
- initialState: { fontSize: DEFAULT_FONT_SIZE }
11063
+ initialState: {}
10790
11064
  };
10791
11065
  this.processors = [
10792
11066
  {
@@ -10805,9 +11079,9 @@ var CliFontSizeCommandProcessor = class {
10805
11079
  },
10806
11080
  {
10807
11081
  command: "reset",
10808
- description: `Reset font size to default (${DEFAULT_FONT_SIZE}px)`,
11082
+ description: "Reset font size to the default",
10809
11083
  processCommand: async (_, context2) => {
10810
- await this.setFontSize(context2, DEFAULT_FONT_SIZE);
11084
+ await this.resetFontSize(context2);
10811
11085
  }
10812
11086
  },
10813
11087
  {
@@ -10831,6 +11105,7 @@ var CliFontSizeCommandProcessor = class {
10831
11105
  ];
10832
11106
  }
10833
11107
  async initialize(context2) {
11108
+ this.hostFontSize = context2.terminal?.options?.fontSize;
10834
11109
  const state = context2.state.getState();
10835
11110
  if (state?.fontSize != null && context2.terminal?.options) {
10836
11111
  context2.terminal.options.fontSize = state.fontSize;
@@ -10851,7 +11126,7 @@ var CliFontSizeCommandProcessor = class {
10851
11126
  ` ${context2.writer.wrapInColor("font-size set <n>", cliCore.CliForegroundColor.Cyan)} Set to exact size`
10852
11127
  );
10853
11128
  context2.writer.writeln(
10854
- ` ${context2.writer.wrapInColor("font-size reset", cliCore.CliForegroundColor.Cyan)} Reset to default (${DEFAULT_FONT_SIZE}px)`
11129
+ ` ${context2.writer.wrapInColor("font-size reset", cliCore.CliForegroundColor.Cyan)} Reset to default (${this.getDefault()}px)`
10855
11130
  );
10856
11131
  }
10857
11132
  getCurrent(context2) {
@@ -10860,7 +11135,19 @@ var CliFontSizeCommandProcessor = class {
10860
11135
  return fromTerminal;
10861
11136
  }
10862
11137
  const state = context2.state.getState();
10863
- return state?.fontSize ?? DEFAULT_FONT_SIZE;
11138
+ return state?.fontSize ?? this.getDefault();
11139
+ }
11140
+ getDefault() {
11141
+ return this.hostFontSize ?? defaultFontSize();
11142
+ }
11143
+ async resetFontSize(context2) {
11144
+ const size = this.getDefault();
11145
+ if (context2.terminal?.options) {
11146
+ context2.terminal.options.fontSize = size;
11147
+ }
11148
+ context2.state.updateState({ fontSize: void 0 });
11149
+ await context2.state.persist();
11150
+ context2.writer.writeSuccess(`Font size set to ${size}px`);
10864
11151
  }
10865
11152
  async setFontSize(context2, size) {
10866
11153
  const clamped = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, size));
@@ -11811,6 +12098,7 @@ var CliTimerManager = class {
11811
12098
  this.managedTimers.clear();
11812
12099
  }
11813
12100
  };
12101
+ var MIN_READABLE_COLUMN = 8;
11814
12102
  var CliTerminalWriter = class {
11815
12103
  constructor(terminal) {
11816
12104
  this.terminal = terminal;
@@ -11942,6 +12230,24 @@ var CliTerminalWriter = class {
11942
12230
  const indent = " ".repeat(level);
11943
12231
  this.writeln(`${indent}${text}`);
11944
12232
  }
12233
+ /** One block per row, header names as keys; used when a table cannot fit the terminal. */
12234
+ writeRecords(headers, rows) {
12235
+ const keyWidth = Math.max(...headers.map((h) => cliCore.visibleLength(h)));
12236
+ rows.forEach((row, index) => {
12237
+ if (index > 0) this.write("\r\n");
12238
+ headers.forEach((header, i) => {
12239
+ const value = row[i] ?? "";
12240
+ if (!header.trim()) {
12241
+ if (value.trim()) this.write(`${value}\r
12242
+ `);
12243
+ return;
12244
+ }
12245
+ const key = header + " ".repeat(keyWidth - cliCore.visibleLength(header));
12246
+ this.write(`${this.wrapInColor(key, cliCore.CliForegroundColor.Yellow)} : ${value}\r
12247
+ `);
12248
+ });
12249
+ });
12250
+ }
11945
12251
  writeTable(headers, rows, options) {
11946
12252
  const cols = this.terminal.cols;
11947
12253
  const naturalWidths = headers.map(
@@ -11979,6 +12285,11 @@ var CliTerminalWriter = class {
11979
12285
  used++;
11980
12286
  }
11981
12287
  }
12288
+ const tooNarrow = colWidths.reduce((s, w) => s + w, 0) > Math.max(available, 0) || colWidths.some((w, i) => w < Math.min(naturalWidths[i], MIN_READABLE_COLUMN));
12289
+ if (tooNarrow) {
12290
+ this.writeRecords(headers, rows);
12291
+ return;
12292
+ }
11982
12293
  const truncateText = (text, maxWidth) => {
11983
12294
  const t = text ?? "";
11984
12295
  const vis = cliCore.visibleLength(t);
@@ -12344,8 +12655,6 @@ var CliClipboard = class {
12344
12655
  }
12345
12656
  }
12346
12657
  };
12347
-
12348
- // src/lib/input/modes/input-mode-base.ts
12349
12658
  var InputModeBase = class {
12350
12659
  constructor(host, resolve) {
12351
12660
  this.host = host;
@@ -12414,6 +12723,21 @@ var InputModeBase = class {
12414
12723
  }
12415
12724
  this.host.terminal.write(buf);
12416
12725
  }
12726
+ /**
12727
+ * Cut a line to the terminal width. Lines redrawn in place must stay on one row:
12728
+ * a wrapped line takes two rows and breaks the cursor-up arithmetic of the redraw.
12729
+ */
12730
+ /** A prompt label without a trailing colon, for modes that append their own ": ". */
12731
+ label(promptText) {
12732
+ return promptText.replace(/:\s*$/, "");
12733
+ }
12734
+ fitLine(text) {
12735
+ return cliCore.truncateToWidth(text, Math.max(1, this.host.getTerminalCols() - 1));
12736
+ }
12737
+ /** Start a new row and write `text` cut to the terminal width. */
12738
+ writeRow(text) {
12739
+ this.host.terminal.write("\r\n" + this.fitLine(text));
12740
+ }
12417
12741
  /**
12418
12742
  * Render a dimmed help bar below the current line.
12419
12743
  */
@@ -12421,7 +12745,7 @@ var InputModeBase = class {
12421
12745
  this.extraLines++;
12422
12746
  this.host.terminal.write(
12423
12747
  `\r
12424
- \x1B[2m${text}\x1B[0m\x1B[${this.extraLines}A`
12748
+ ${this.fitLine(` \x1B[2m${text}\x1B[0m`)}\x1B[${this.extraLines}A`
12425
12749
  );
12426
12750
  }
12427
12751
  /**
@@ -12431,7 +12755,7 @@ var InputModeBase = class {
12431
12755
  this.extraLines++;
12432
12756
  this.host.terminal.write(
12433
12757
  `\r
12434
- \x1B[31m\u2718 ${message}\x1B[0m\x1B[${this.extraLines}A`
12758
+ ${this.fitLine(` \x1B[31m\u2718 ${message}\x1B[0m`)}\x1B[${this.extraLines}A`
12435
12759
  );
12436
12760
  }
12437
12761
  /**
@@ -12581,6 +12905,7 @@ var ConfirmInputMode = class extends InputModeBase {
12581
12905
  async handleInput(data) {
12582
12906
  if (data === "\r") {
12583
12907
  if (this.buffer === "") {
12908
+ this.host.terminal.write(this.defaultValue ? "y" : "n");
12584
12909
  this.resolveAndPop(this.defaultValue);
12585
12910
  } else {
12586
12911
  const lower = this.buffer.toLowerCase();
@@ -12762,7 +13087,7 @@ var SelectInputMode = class extends InputModeBase {
12762
13087
  const selectedOpt = this.options.find((o) => o.value === value);
12763
13088
  const label = selectedOpt ? selectedOpt.label : value;
12764
13089
  this.host.terminal.write("\x1B[2K\r");
12765
- this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${label}\x1B[0m`);
13090
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.label(this.promptText)}: \x1B[36m${label}\x1B[0m`);
12766
13091
  this.host.terminal.write("\x1B[?25h");
12767
13092
  super.resolveAndPop(value);
12768
13093
  }
@@ -12772,7 +13097,7 @@ var SelectInputMode = class extends InputModeBase {
12772
13097
  abort() {
12773
13098
  this.clearRenderedLines();
12774
13099
  this.host.terminal.write("\x1B[2K\r");
12775
- this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
13100
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.label(this.promptText)}: \x1B[2mcancelled\x1B[0m`);
12776
13101
  this.host.terminal.write("\x1B[?25h");
12777
13102
  super.abort();
12778
13103
  }
@@ -12933,15 +13258,15 @@ var SelectInputMode = class extends InputModeBase {
12933
13258
  if (this.selectOptions?.searchable) {
12934
13259
  if (this.filter.length > 0) {
12935
13260
  this.host.terminal.write(
12936
- `\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
13261
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: ${this.filter}`)
12937
13262
  );
12938
13263
  } else {
12939
13264
  this.host.terminal.write(
12940
- `\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
13265
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[2mType to filter...\x1B[0m`)
12941
13266
  );
12942
13267
  }
12943
13268
  } else {
12944
- this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
13269
+ this.host.terminal.write(this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}:`));
12945
13270
  }
12946
13271
  }
12947
13272
  /**
@@ -12956,17 +13281,15 @@ var SelectInputMode = class extends InputModeBase {
12956
13281
  this.scrollOffset + this.maxVisible
12957
13282
  );
12958
13283
  if (visibleStart > 0) {
12959
- this.host.terminal.write(`\r
12960
- \x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
13284
+ this.writeRow(`\x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
12961
13285
  this.renderedLines++;
12962
13286
  }
12963
13287
  let selectableIdx = 0;
12964
13288
  for (const item of items) {
12965
13289
  if (item.type === "group-header") {
12966
13290
  if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
12967
- this.host.terminal.write(
12968
- `\r
12969
- \x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
13291
+ this.writeRow(
13292
+ `\x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
12970
13293
  );
12971
13294
  this.renderedLines++;
12972
13295
  }
@@ -12989,8 +13312,7 @@ var SelectInputMode = class extends InputModeBase {
12989
13312
  }
12990
13313
  const remaining = selectables.length - visibleEnd;
12991
13314
  if (remaining > 0) {
12992
- this.host.terminal.write(`\r
12993
- \x1B[2m \u2193 ${remaining} more below\x1B[0m`);
13315
+ this.writeRow(`\x1B[2m \u2193 ${remaining} more below\x1B[0m`);
12994
13316
  this.renderedLines++;
12995
13317
  }
12996
13318
  }
@@ -13032,19 +13354,16 @@ var SelectInputMode = class extends InputModeBase {
13032
13354
  const label = this.highlightMatch(opt.label);
13033
13355
  const desc = opt.description ? ` \x1B[2;3m\u2014 ${opt.description}\x1B[0m` : "";
13034
13356
  if (opt.disabled) {
13035
- this.host.terminal.write(
13036
- `\r
13037
- \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
13357
+ this.writeRow(
13358
+ ` \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
13038
13359
  );
13039
13360
  } else if (isSelected) {
13040
- this.host.terminal.write(
13041
- `\r
13042
- \x1B[48;5;24m\x1B[38;5;117m\u276F ${label}${desc}\x1B[0m`
13361
+ this.writeRow(
13362
+ ` \x1B[48;5;24m\x1B[38;5;117m\u276F ${label}${desc}\x1B[0m`
13043
13363
  );
13044
13364
  } else {
13045
- this.host.terminal.write(
13046
- `\r
13047
- ${label}${desc}`
13365
+ this.writeRow(
13366
+ ` ${label}${desc}`
13048
13367
  );
13049
13368
  }
13050
13369
  }
@@ -13078,8 +13397,7 @@ var SelectInputMode = class extends InputModeBase {
13078
13397
  } else {
13079
13398
  help = "\u2191/\u2193 navigate \xB7 enter to select \xB7 esc to cancel";
13080
13399
  }
13081
- this.host.terminal.write(`\r
13082
- \x1B[2m${help}\x1B[0m`);
13400
+ this.writeRow(` \x1B[2m${help}\x1B[0m`);
13083
13401
  this.renderedLines++;
13084
13402
  }
13085
13403
  };
@@ -13120,7 +13438,7 @@ var InlineSelectInputMode = class extends InputModeBase {
13120
13438
  const selectedOpt = this.selectableOptions.find((o) => o.value === value);
13121
13439
  const label = selectedOpt ? selectedOpt.label : value;
13122
13440
  this.host.terminal.write("\x1B[2K\r");
13123
- this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText}: \x1B[36m${label}\x1B[0m`);
13441
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.label(this.promptText)}: \x1B[36m${label}\x1B[0m`);
13124
13442
  this.host.terminal.write("\x1B[?25h");
13125
13443
  super.resolveAndPop(value);
13126
13444
  }
@@ -13130,7 +13448,7 @@ var InlineSelectInputMode = class extends InputModeBase {
13130
13448
  abort() {
13131
13449
  this.clearExtraLines();
13132
13450
  this.host.terminal.write("\x1B[2K\r");
13133
- this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText}: \x1B[2mcancelled\x1B[0m`);
13451
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.label(this.promptText)}: \x1B[2mcancelled\x1B[0m`);
13134
13452
  this.host.terminal.write("\x1B[?25h");
13135
13453
  super.abort();
13136
13454
  }
@@ -13170,13 +13488,13 @@ var InlineSelectInputMode = class extends InputModeBase {
13170
13488
  * Get the prompt prefix string (without ANSI escapes for width calculation).
13171
13489
  */
13172
13490
  getPromptPrefix() {
13173
- return `? ${this.promptText}: `;
13491
+ return `? ${this.label(this.promptText)}: `;
13174
13492
  }
13175
13493
  /**
13176
13494
  * Get the rendered prompt prefix (with ANSI color codes).
13177
13495
  */
13178
13496
  getRenderedPromptPrefix() {
13179
- return `\x1B[36m?\x1B[0m ${this.promptText}: `;
13497
+ return `\x1B[36m?\x1B[0m ${this.label(this.promptText)}: `;
13180
13498
  }
13181
13499
  /**
13182
13500
  * Calculate the width needed for a single option.
@@ -13373,7 +13691,7 @@ var MultiSelectInputMode = class extends InputModeBase {
13373
13691
  });
13374
13692
  const summary = labels.length > 0 ? labels.join(", ") : "none";
13375
13693
  this.host.terminal.write("\x1B[2K\r");
13376
- this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${summary}\x1B[0m`);
13694
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.label(this.promptText)}: \x1B[36m${summary}\x1B[0m`);
13377
13695
  this.host.terminal.write("\x1B[?25h");
13378
13696
  super.resolveAndPop(value);
13379
13697
  }
@@ -13383,7 +13701,7 @@ var MultiSelectInputMode = class extends InputModeBase {
13383
13701
  abort() {
13384
13702
  this.clearRenderedLines();
13385
13703
  this.host.terminal.write("\x1B[2K\r");
13386
- this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
13704
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.label(this.promptText)}: \x1B[2mcancelled\x1B[0m`);
13387
13705
  this.host.terminal.write("\x1B[?25h");
13388
13706
  super.abort();
13389
13707
  }
@@ -13557,15 +13875,15 @@ var MultiSelectInputMode = class extends InputModeBase {
13557
13875
  if (this.selectOptions?.searchable) {
13558
13876
  if (this.filter.length > 0) {
13559
13877
  this.host.terminal.write(
13560
- `\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`
13878
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: ${this.filter}`)
13561
13879
  );
13562
13880
  } else {
13563
13881
  this.host.terminal.write(
13564
- `\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`
13882
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[2mType to filter...\x1B[0m`)
13565
13883
  );
13566
13884
  }
13567
13885
  } else {
13568
- this.host.terminal.write(`\x1B[36m?\x1B[0m ${this.promptText}:`);
13886
+ this.host.terminal.write(this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}:`));
13569
13887
  }
13570
13888
  }
13571
13889
  /**
@@ -13580,17 +13898,15 @@ var MultiSelectInputMode = class extends InputModeBase {
13580
13898
  this.scrollOffset + this.maxVisible
13581
13899
  );
13582
13900
  if (visibleStart > 0) {
13583
- this.host.terminal.write(`\r
13584
- \x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
13901
+ this.writeRow(`\x1B[2m \u2191 ${visibleStart} more above\x1B[0m`);
13585
13902
  this.renderedLines++;
13586
13903
  }
13587
13904
  let selectableIdx = 0;
13588
13905
  for (const item of items) {
13589
13906
  if (item.type === "group-header") {
13590
13907
  if (this.isGroupVisible(item.groupName, items, visibleStart, visibleEnd)) {
13591
- this.host.terminal.write(
13592
- `\r
13593
- \x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
13908
+ this.writeRow(
13909
+ `\x1B[35m\u2500\u2500 ${item.groupName} \u2500\u2500\x1B[0m`
13594
13910
  );
13595
13911
  this.renderedLines++;
13596
13912
  }
@@ -13613,8 +13929,7 @@ var MultiSelectInputMode = class extends InputModeBase {
13613
13929
  }
13614
13930
  const remaining = selectables.length - visibleEnd;
13615
13931
  if (remaining > 0) {
13616
- this.host.terminal.write(`\r
13617
- \x1B[2m \u2193 ${remaining} more below\x1B[0m`);
13932
+ this.writeRow(`\x1B[2m \u2193 ${remaining} more below\x1B[0m`);
13618
13933
  this.renderedLines++;
13619
13934
  }
13620
13935
  }
@@ -13659,19 +13974,16 @@ var MultiSelectInputMode = class extends InputModeBase {
13659
13974
  const checkbox = isChecked ? "\x1B[32m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
13660
13975
  if (opt.disabled) {
13661
13976
  const disabledCheckbox = isChecked ? "\x1B[2m\u25C9\x1B[0m" : "\x1B[2m\u25CB\x1B[0m";
13662
- this.host.terminal.write(
13663
- `\r
13664
- ${disabledCheckbox} \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
13977
+ this.writeRow(
13978
+ ` ${disabledCheckbox} \x1B[2m${opt.label}${desc} (disabled)\x1B[0m`
13665
13979
  );
13666
13980
  } else if (isSelected) {
13667
- this.host.terminal.write(
13668
- `\r
13669
- \x1B[48;5;24m\x1B[38;5;117m\u276F ${checkbox} ${label}${desc}\x1B[0m`
13981
+ this.writeRow(
13982
+ ` \x1B[48;5;24m\x1B[38;5;117m\u276F ${checkbox} ${label}${desc}\x1B[0m`
13670
13983
  );
13671
13984
  } else {
13672
- this.host.terminal.write(
13673
- `\r
13674
- ${checkbox} ${label}${desc}`
13985
+ this.writeRow(
13986
+ ` ${checkbox} ${label}${desc}`
13675
13987
  );
13676
13988
  }
13677
13989
  }
@@ -13706,8 +14018,7 @@ var MultiSelectInputMode = class extends InputModeBase {
13706
14018
  } else {
13707
14019
  help = `${checkedCount} selected \xB7 space to toggle \xB7 a to select all \xB7 enter to confirm`;
13708
14020
  }
13709
- this.host.terminal.write(`\r
13710
- \x1B[2m${help}\x1B[0m`);
14021
+ this.writeRow(` \x1B[2m${help}\x1B[0m`);
13711
14022
  this.renderedLines++;
13712
14023
  }
13713
14024
  };
@@ -13907,13 +14218,13 @@ var FileInputMode = class extends InputModeBase {
13907
14218
  this.waiting = false;
13908
14219
  if (!results || this.isResolved) {
13909
14220
  this.host.terminal.write("\x1B[2K\r");
13910
- this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2m(cancelled)\x1B[0m\r
14221
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[2m(cancelled)\x1B[0m\r
13911
14222
  `);
13912
14223
  if (!this.isResolved) this.abort();
13913
14224
  return;
13914
14225
  }
13915
14226
  this.host.terminal.write("\x1B[2K\r");
13916
- this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}:\r
14227
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}:\r
13917
14228
  `);
13918
14229
  let totalSize = 0;
13919
14230
  for (const file of results) {
@@ -13931,7 +14242,7 @@ var FileInputMode = class extends InputModeBase {
13931
14242
  this.waiting = false;
13932
14243
  if (this.isResolved) return;
13933
14244
  this.host.terminal.write("\x1B[2K\r");
13934
- this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[31m(error)\x1B[0m\r
14245
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[31m(error)\x1B[0m\r
13935
14246
  `);
13936
14247
  this.abort();
13937
14248
  }
@@ -15401,7 +15712,7 @@ function substituteVars(input, variables) {
15401
15712
  );
15402
15713
  return result;
15403
15714
  }
15404
- function expandEnvironmentVars(command2, context2) {
15715
+ function expandEnvironmentVars(command2, context2, lastExitCode) {
15405
15716
  const env = context2.services.get(ICliEnvironment_TOKEN);
15406
15717
  if (!env) {
15407
15718
  return command2;
@@ -15409,6 +15720,9 @@ function expandEnvironmentVars(command2, context2) {
15409
15720
  const parts = command2.split("'");
15410
15721
  for (let i = 0; i < parts.length; i++) {
15411
15722
  if (i % 2 === 0) {
15723
+ if (lastExitCode !== void 0) {
15724
+ parts[i] = parts[i].replace(/\$\?/g, String(lastExitCode));
15725
+ }
15412
15726
  parts[i] = parts[i].replace(
15413
15727
  /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
15414
15728
  (_, name) => env.get(name) ?? ""
@@ -15417,17 +15731,30 @@ function expandEnvironmentVars(command2, context2) {
15417
15731
  /\$([A-Za-z_][A-Za-z0-9_]*)/g,
15418
15732
  (_, name) => env.get(name) ?? ""
15419
15733
  );
15734
+ const home = env.get("HOME");
15735
+ if (home) {
15736
+ parts[i] = parts[i].replace(
15737
+ /(^|\s)~(?=\/|\s|$)/g,
15738
+ (match, before, offset) => i > 0 && offset === 0 && !before ? match : `${before}${home}`
15739
+ );
15740
+ }
15420
15741
  }
15421
15742
  }
15422
15743
  return parts.join("'");
15423
15744
  }
15424
15745
 
15425
15746
  // src/lib/executor/cli-command-executor.ts
15747
+ function toExitStatus(code) {
15748
+ if (code === void 0) return 0;
15749
+ return code < 0 ? 1 : code;
15750
+ }
15426
15751
  var CliCommandExecutor = class {
15427
15752
  constructor(registry) {
15428
15753
  this.registry = registry;
15429
15754
  this.commandParser = new CommandParser();
15430
15755
  this.globalParameters = [];
15756
+ /** Exit status of the last command, expanded as `$?` (0 = success). */
15757
+ this.lastExitCode = 0;
15431
15758
  this.registerGlobalParameter(versionGlobalParameter);
15432
15759
  this.registerGlobalParameter(helpGlobalParameter);
15433
15760
  this.registerGlobalParameter(contextGlobalParameter);
@@ -15531,6 +15858,7 @@ var CliCommandExecutor = class {
15531
15858
  rootContext
15532
15859
  );
15533
15860
  lastExitSuccess = context2.process.exitCode === void 0 || context2.process.exitCode === 0;
15861
+ this.lastExitCode = toExitStatus(context2.process.exitCode);
15534
15862
  rootContext.lastCommandResult = {
15535
15863
  command: part.value,
15536
15864
  success: lastExitSuccess
@@ -15538,6 +15866,7 @@ var CliCommandExecutor = class {
15538
15866
  pipelineData = context2.process.data;
15539
15867
  } catch (e) {
15540
15868
  lastExitSuccess = false;
15869
+ this.lastExitCode = 1;
15541
15870
  rootContext.lastCommandResult = {
15542
15871
  command: part.value,
15543
15872
  success: false
@@ -15553,7 +15882,7 @@ var CliCommandExecutor = class {
15553
15882
  }
15554
15883
  async executeSingleCommand(command2, data, context2) {
15555
15884
  const process = context2.process;
15556
- command2 = expandEnvironmentVars(command2, context2);
15885
+ command2 = expandEnvironmentVars(command2, context2, this.lastExitCode);
15557
15886
  const assignMatch = command2.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
15558
15887
  if (assignMatch) {
15559
15888
  const env = context2.services.get(ICliEnvironment_TOKEN);
@@ -16609,30 +16938,6 @@ var CliDefaultPingServerService = class {
16609
16938
  return new Promise((resolve) => setTimeout(resolve, 2e3));
16610
16939
  }
16611
16940
  };
16612
- var CliDragDropService = class {
16613
- constructor(_container) {
16614
- this._container = _container;
16615
- this._subject = new rxjs.Subject();
16616
- this._dragOver = (e) => e.preventDefault();
16617
- this._drop = (e) => {
16618
- e.preventDefault();
16619
- const files = Array.from(e.dataTransfer?.files ?? []);
16620
- if (files.length > 0) {
16621
- this._subject.next(files);
16622
- }
16623
- };
16624
- this._container.addEventListener("dragover", this._dragOver);
16625
- this._container.addEventListener("drop", this._drop);
16626
- }
16627
- get onFileDrop() {
16628
- return this._subject.asObservable();
16629
- }
16630
- destroy() {
16631
- this._container.removeEventListener("dragover", this._dragOver);
16632
- this._container.removeEventListener("drop", this._drop);
16633
- this._subject.complete();
16634
- }
16635
- };
16636
16941
 
16637
16942
  // src/lib/services/cli-permission-registry.ts
16638
16943
  var CliPermissionRegistry = class {
@@ -16657,62 +16962,6 @@ var CliPermissionRegistry = class {
16657
16962
  }
16658
16963
  };
16659
16964
 
16660
- // src/lib/addons/overlay.ts
16661
- var OverlayAddon = class {
16662
- constructor() {
16663
- this.overlayNode = document.createElement("div");
16664
- this.overlayNode.style.cssText = `border-radius: 15px;
16665
- font-size: xx-large;
16666
- opacity: 0.75;
16667
- padding: 0.2em 0.5em 0.2em 0.5em;
16668
- position: absolute;
16669
- -webkit-user-select: none;
16670
- -webkit-transition: opacity 180ms ease-in;
16671
- -moz-user-select: none;
16672
- -moz-transition: opacity 180ms ease-in;`;
16673
- this.overlayNode.addEventListener(
16674
- "mousedown",
16675
- (e) => {
16676
- e.preventDefault();
16677
- e.stopPropagation();
16678
- },
16679
- true
16680
- );
16681
- }
16682
- activate(terminal) {
16683
- this.terminal = terminal;
16684
- }
16685
- dispose() {
16686
- }
16687
- showOverlay(msg, timeout) {
16688
- const { terminal, overlayNode } = this;
16689
- if (!terminal.element) return;
16690
- overlayNode.style.color = "#101010";
16691
- overlayNode.style.backgroundColor = "#f0f0f0";
16692
- overlayNode.textContent = msg;
16693
- overlayNode.style.opacity = "0.75";
16694
- if (!overlayNode.parentNode) {
16695
- terminal.element.appendChild(overlayNode);
16696
- }
16697
- const divSize = terminal.element.getBoundingClientRect();
16698
- const overlaySize = overlayNode.getBoundingClientRect();
16699
- overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + "px";
16700
- overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + "px";
16701
- if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
16702
- if (!timeout) return;
16703
- this.overlayTimeout = window.setTimeout(() => {
16704
- overlayNode.style.opacity = "0";
16705
- this.overlayTimeout = window.setTimeout(() => {
16706
- if (overlayNode.parentNode) {
16707
- overlayNode.parentNode.removeChild(overlayNode);
16708
- }
16709
- this.overlayTimeout = void 0;
16710
- overlayNode.style.opacity = "0.75";
16711
- }, 200);
16712
- }, timeout || 1500);
16713
- }
16714
- };
16715
-
16716
16965
  // src/lib/storage/cli-key-value-store.ts
16717
16966
  var CliKeyValueStore = class {
16718
16967
  constructor() {
@@ -17887,61 +18136,6 @@ function createServerModule() {
17887
18136
  services: []
17888
18137
  };
17889
18138
  }
17890
- function getTerminalOptions(userOptions) {
17891
- return {
17892
- cursorBlink: true,
17893
- allowProposedApi: true,
17894
- fontSize: 20,
17895
- theme: cliCore.DefaultThemes.default,
17896
- convertEol: true,
17897
- ...userOptions ?? {}
17898
- };
17899
- }
17900
- function initializeTerminal(container, opts) {
17901
- const terminal = new xterm.Terminal(opts);
17902
- const fitAddon = new addonFit.FitAddon();
17903
- terminal.loadAddon(fitAddon);
17904
- terminal.loadAddon(new addonWebLinks.WebLinksAddon());
17905
- terminal.loadAddon(new OverlayAddon());
17906
- terminal.loadAddon(new addonUnicode11.Unicode11Addon());
17907
- const serializeAddon = new addonSerialize.SerializeAddon();
17908
- terminal.loadAddon(serializeAddon);
17909
- terminal.open(container);
17910
- fitAddon.fit();
17911
- requestAnimationFrame(() => fitAddon.fit());
17912
- if (document.fonts?.ready) {
17913
- document.fonts.ready.then(() => {
17914
- requestAnimationFrame(() => fitAddon.fit());
17915
- });
17916
- }
17917
- container.classList.add("terminal-container");
17918
- container.style.background = opts.theme?.background ?? "#000";
17919
- const wheelListener = (e) => e.preventDefault();
17920
- container.addEventListener("wheel", wheelListener, { passive: false });
17921
- terminal.focus();
17922
- const dragDropService = new CliDragDropService(container);
17923
- return { terminal, fitAddon, serializeAddon, dragDropService, wheelListener };
17924
- }
17925
- var LAYOUT_TIMEOUT_MS = 1e4;
17926
- function waitForLayout(container) {
17927
- return new Promise((resolve, reject) => {
17928
- const deadline = Date.now() + LAYOUT_TIMEOUT_MS;
17929
- const check = () => {
17930
- if (container.offsetWidth > 0 && container.offsetHeight > 0) {
17931
- resolve();
17932
- return;
17933
- }
17934
- if (Date.now() >= deadline) {
17935
- reject(new Error(
17936
- `CliEngine.start(): container has zero dimensions after ${LAYOUT_TIMEOUT_MS}ms. Ensure the container element is visible and laid out before calling start().`
17937
- ));
17938
- return;
17939
- }
17940
- requestAnimationFrame(check);
17941
- };
17942
- check();
17943
- });
17944
- }
17945
18139
 
17946
18140
  // src/lib/editor/syntax/registry.ts
17947
18141
  var SyntaxHighlighterRegistry = class {
@@ -18333,6 +18527,14 @@ var CliEngine = class {
18333
18527
  { ...this.options ?? {}, terminalOptions: terminalOpts }
18334
18528
  );
18335
18529
  this.executionContext.initializeTerminalListeners();
18530
+ const environment = services.get(ICliEnvironment_TOKEN);
18531
+ if (environment instanceof CliEnvironment) {
18532
+ const ctx = this.executionContext;
18533
+ environment.bind("USER", () => ctx.userSession?.user.name);
18534
+ environment.bind("HOME", () => ctx.userSession?.user.homeDir);
18535
+ environment.bind("PWD", () => ctx.promptPathProvider?.());
18536
+ environment.setProfileResolver(() => ctx.userSession?.user.name);
18537
+ }
18336
18538
  this.executionContext.backgroundServices.setProcessRegistry(processRegistry);
18337
18539
  services.set([{
18338
18540
  provide: CliBackgroundServiceRegistry_TOKEN,
@@ -19038,6 +19240,7 @@ exports.builtinProcessors = builtinProcessors;
19038
19240
  exports.computeDiff = computeDiff;
19039
19241
  exports.contextGlobalParameter = contextGlobalParameter;
19040
19242
  exports.createServerModule = createServerModule;
19243
+ exports.defaultFontSize = defaultFontSize;
19041
19244
  exports.describeShellClose = describeShellClose;
19042
19245
  exports.executeOnServer = executeOnServer;
19043
19246
  exports.expandEnvironmentVars = expandEnvironmentVars;
@@ -19055,6 +19258,7 @@ exports.initializeTerminal = initializeTerminal;
19055
19258
  exports.isWasmAccelerated = isWasmAccelerated;
19056
19259
  exports.miscProcessors = miscProcessors;
19057
19260
  exports.openLink = openLink;
19261
+ exports.parseAssignments = parseAssignments;
19058
19262
  exports.reconcileArgs = reconcileArgs;
19059
19263
  exports.renderServerResponse = renderServerResponse;
19060
19264
  exports.renderSingleOutput = renderSingleOutput;