@qodalis/cli 2.0.0-beta.16 → 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.mjs CHANGED
@@ -1,12 +1,12 @@
1
1
  import { CliLogLevel, DefaultThemes, DefaultThemeInfos, DefaultLibraryAuthor, CliIcon, CliForegroundColor, LIBRARY_VERSION as LIBRARY_VERSION$1, resolveConfigurationCategories, ICliTranslationService_TOKEN, ICliAuthorizationService_TOKEN, resolveHeaders, appendQueryParams, buildAuthenticatedWebSocketUrl, ServerVersionNegotiator, CliHttpError, formatJson, visibleLength, stripAnsi, getRightOfWord, CancellablePromise, isCliAccessDeniedError, getParameterValue, ICliCommandGuard_TOKEN, CLI_CONFIGURE_STORE_NAME, CliModuleRegistry, initializeBrowserEnvironment, ICliPermissionRegistry_TOKEN, satisfiesVersionRange, CliServerPermissions, ICliDragDropService_TOKEN, cliAccessMetadataGuards, ICliServerAuthService_TOKEN, ICliCompletionProvider_TOKEN, delay, colorFirstWord, clearTerminalLine, ICliServerAuthTokenProvider_TOKEN, truncateToWidth } from '@qodalis/cli-core';
2
2
  export { ICliUserSessionService_TOKEN, ICliUsersStoreService_TOKEN } from '@qodalis/cli-core';
3
- import { Subject, BehaviorSubject } from 'rxjs';
4
- import { map, distinctUntilChanged } from 'rxjs/operators';
5
3
  import { Terminal } from '@xterm/xterm';
6
4
  import { FitAddon } from '@xterm/addon-fit';
7
5
  import { WebLinksAddon } from '@xterm/addon-web-links';
8
6
  import { Unicode11Addon } from '@xterm/addon-unicode11';
9
7
  import { SerializeAddon } from '@xterm/addon-serialize';
8
+ import { Subject, BehaviorSubject } from 'rxjs';
9
+ import { map, distinctUntilChanged } from 'rxjs/operators';
10
10
 
11
11
  // src/lib/parsers/command-parser.ts
12
12
  var CommandParser = class {
@@ -412,7 +412,7 @@ var CliKeyValueStore_TOKEN = "cli-key-value-store";
412
412
  var SyntaxHighlighterRegistry_TOKEN = "syntax-highlighter-registry";
413
413
 
414
414
  // src/lib/version.ts
415
- var LIBRARY_VERSION = "2.0.0-beta.16";
415
+ var LIBRARY_VERSION = "2.0.0-beta.17";
416
416
  var API_VERSION = 2;
417
417
  var ANONYMOUS = "\0anonymous";
418
418
  function aliasOwner(context2) {
@@ -2698,6 +2698,150 @@ var CliYesCommandProcessor = class {
2698
2698
  );
2699
2699
  }
2700
2700
  };
2701
+
2702
+ // src/lib/addons/overlay.ts
2703
+ var OverlayAddon = class {
2704
+ constructor() {
2705
+ this.overlayNode = document.createElement("div");
2706
+ this.overlayNode.style.cssText = `border-radius: 15px;
2707
+ font-size: xx-large;
2708
+ opacity: 0.75;
2709
+ padding: 0.2em 0.5em 0.2em 0.5em;
2710
+ position: absolute;
2711
+ -webkit-user-select: none;
2712
+ -webkit-transition: opacity 180ms ease-in;
2713
+ -moz-user-select: none;
2714
+ -moz-transition: opacity 180ms ease-in;`;
2715
+ this.overlayNode.addEventListener(
2716
+ "mousedown",
2717
+ (e) => {
2718
+ e.preventDefault();
2719
+ e.stopPropagation();
2720
+ },
2721
+ true
2722
+ );
2723
+ }
2724
+ activate(terminal) {
2725
+ this.terminal = terminal;
2726
+ }
2727
+ dispose() {
2728
+ }
2729
+ showOverlay(msg, timeout) {
2730
+ const { terminal, overlayNode } = this;
2731
+ if (!terminal.element) return;
2732
+ overlayNode.style.color = "#101010";
2733
+ overlayNode.style.backgroundColor = "#f0f0f0";
2734
+ overlayNode.textContent = msg;
2735
+ overlayNode.style.opacity = "0.75";
2736
+ if (!overlayNode.parentNode) {
2737
+ terminal.element.appendChild(overlayNode);
2738
+ }
2739
+ const divSize = terminal.element.getBoundingClientRect();
2740
+ const overlaySize = overlayNode.getBoundingClientRect();
2741
+ overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + "px";
2742
+ overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + "px";
2743
+ if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
2744
+ if (!timeout) return;
2745
+ this.overlayTimeout = window.setTimeout(() => {
2746
+ overlayNode.style.opacity = "0";
2747
+ this.overlayTimeout = window.setTimeout(() => {
2748
+ if (overlayNode.parentNode) {
2749
+ overlayNode.parentNode.removeChild(overlayNode);
2750
+ }
2751
+ this.overlayTimeout = void 0;
2752
+ overlayNode.style.opacity = "0.75";
2753
+ }, 200);
2754
+ }, timeout || 1500);
2755
+ }
2756
+ };
2757
+ var CliDragDropService = class {
2758
+ constructor(_container) {
2759
+ this._container = _container;
2760
+ this._subject = new Subject();
2761
+ this._dragOver = (e) => e.preventDefault();
2762
+ this._drop = (e) => {
2763
+ e.preventDefault();
2764
+ const files = Array.from(e.dataTransfer?.files ?? []);
2765
+ if (files.length > 0) {
2766
+ this._subject.next(files);
2767
+ }
2768
+ };
2769
+ this._container.addEventListener("dragover", this._dragOver);
2770
+ this._container.addEventListener("drop", this._drop);
2771
+ }
2772
+ get onFileDrop() {
2773
+ return this._subject.asObservable();
2774
+ }
2775
+ destroy() {
2776
+ this._container.removeEventListener("dragover", this._dragOver);
2777
+ this._container.removeEventListener("drop", this._drop);
2778
+ this._subject.complete();
2779
+ }
2780
+ };
2781
+
2782
+ // src/lib/engine/cli-terminal-setup.ts
2783
+ var NARROW_VIEWPORT = 640;
2784
+ function defaultFontSize() {
2785
+ const width = typeof window !== "undefined" ? window.innerWidth : 0;
2786
+ return width > 0 && width <= NARROW_VIEWPORT ? 14 : 20;
2787
+ }
2788
+ function getTerminalOptions(userOptions) {
2789
+ return {
2790
+ cursorBlink: true,
2791
+ allowProposedApi: true,
2792
+ fontSize: defaultFontSize(),
2793
+ theme: DefaultThemes.default,
2794
+ convertEol: true,
2795
+ ...userOptions ?? {}
2796
+ };
2797
+ }
2798
+ function initializeTerminal(container, opts) {
2799
+ const terminal = new Terminal(opts);
2800
+ const fitAddon = new FitAddon();
2801
+ terminal.loadAddon(fitAddon);
2802
+ terminal.loadAddon(new WebLinksAddon());
2803
+ terminal.loadAddon(new OverlayAddon());
2804
+ terminal.loadAddon(new Unicode11Addon());
2805
+ const serializeAddon = new SerializeAddon();
2806
+ terminal.loadAddon(serializeAddon);
2807
+ terminal.open(container);
2808
+ fitAddon.fit();
2809
+ requestAnimationFrame(() => fitAddon.fit());
2810
+ if (document.fonts?.ready) {
2811
+ document.fonts.ready.then(() => {
2812
+ requestAnimationFrame(() => fitAddon.fit());
2813
+ });
2814
+ }
2815
+ container.classList.add("terminal-container");
2816
+ container.style.background = opts.theme?.background ?? "#000";
2817
+ const wheelListener = (e) => e.preventDefault();
2818
+ container.addEventListener("wheel", wheelListener, { passive: false });
2819
+ terminal.focus();
2820
+ const dragDropService = new CliDragDropService(container);
2821
+ return { terminal, fitAddon, serializeAddon, dragDropService, wheelListener };
2822
+ }
2823
+ var LAYOUT_TIMEOUT_MS = 1e4;
2824
+ function waitForLayout(container) {
2825
+ return new Promise((resolve, reject) => {
2826
+ const deadline = Date.now() + LAYOUT_TIMEOUT_MS;
2827
+ const check = () => {
2828
+ if (container.offsetWidth > 0 && container.offsetHeight > 0) {
2829
+ resolve();
2830
+ return;
2831
+ }
2832
+ if (Date.now() >= deadline) {
2833
+ reject(new Error(
2834
+ `CliEngine.start(): container has zero dimensions after ${LAYOUT_TIMEOUT_MS}ms. Ensure the container element is visible and laid out before calling start().`
2835
+ ));
2836
+ return;
2837
+ }
2838
+ requestAnimationFrame(check);
2839
+ };
2840
+ check();
2841
+ });
2842
+ }
2843
+
2844
+ // src/lib/processors/configure/cli-configure-command-processor.ts
2701
2845
  var SYSTEM_OPTIONS = [
2702
2846
  {
2703
2847
  key: "logLevel",
@@ -2774,7 +2918,7 @@ var SYSTEM_OPTIONS = [
2774
2918
  label: "Font Size",
2775
2919
  description: "Terminal font size in pixels",
2776
2920
  type: "number",
2777
- defaultValue: 20,
2921
+ defaultValue: defaultFontSize(),
2778
2922
  validator: (value) => {
2779
2923
  if (value < 8 || value > 40) {
2780
2924
  return { valid: false, message: "Font size must be between 8 and 40" };
@@ -2854,7 +2998,7 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
2854
2998
  async initialize(context2) {
2855
2999
  const state = context2.state.getState();
2856
3000
  if (state?.system) {
2857
- this.applySystemSettings(state.system, context2);
3001
+ this.applySystemSettings(state.system, context2, true);
2858
3002
  }
2859
3003
  }
2860
3004
  writeDescription(context2) {
@@ -3089,7 +3233,11 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3089
3233
  }
3090
3234
  return result;
3091
3235
  }
3092
- applySystemSettings(settings, context2) {
3236
+ /**
3237
+ * @param onBoot at startup, terminal options still at their default are skipped so the
3238
+ * host's `terminalOptions` (e.g. its own fontSize) are not overridden.
3239
+ */
3240
+ applySystemSettings(settings, context2, onBoot = false) {
3093
3241
  if (settings["logLevel"]) {
3094
3242
  const level = LOG_LEVEL_MAP[settings["logLevel"]];
3095
3243
  if (level !== void 0) {
@@ -3103,23 +3251,29 @@ var CliConfigureCommandProcessor = class _CliConfigureCommandProcessor {
3103
3251
  translator?.setLocale(settings["language"]);
3104
3252
  }
3105
3253
  if (context2.terminal?.options) {
3106
- if (settings["cursorBlink"] !== void 0) {
3107
- context2.terminal.options.cursorBlink = settings["cursorBlink"];
3254
+ const terminal = onBoot ? Object.fromEntries(
3255
+ Object.entries(settings).filter(([key, value]) => {
3256
+ const option = SYSTEM_OPTIONS.find((o) => o.key === key);
3257
+ return !option || value !== option.defaultValue;
3258
+ })
3259
+ ) : settings;
3260
+ if (terminal["cursorBlink"] !== void 0) {
3261
+ context2.terminal.options.cursorBlink = terminal["cursorBlink"];
3108
3262
  }
3109
- if (settings["cursorStyle"]) {
3110
- context2.terminal.options.cursorStyle = settings["cursorStyle"];
3263
+ if (terminal["cursorStyle"]) {
3264
+ context2.terminal.options.cursorStyle = terminal["cursorStyle"];
3111
3265
  }
3112
- if (settings["scrollback"] !== void 0) {
3113
- context2.terminal.options.scrollback = settings["scrollback"];
3266
+ if (terminal["scrollback"] !== void 0) {
3267
+ context2.terminal.options.scrollback = terminal["scrollback"];
3114
3268
  }
3115
- if (settings["fontSize"] !== void 0) {
3116
- context2.terminal.options.fontSize = settings["fontSize"];
3269
+ if (terminal["fontSize"] !== void 0) {
3270
+ context2.terminal.options.fontSize = terminal["fontSize"];
3117
3271
  }
3118
- if (settings["fontFamily"]) {
3119
- context2.terminal.options.fontFamily = settings["fontFamily"];
3272
+ if (terminal["fontFamily"]) {
3273
+ context2.terminal.options.fontFamily = terminal["fontFamily"];
3120
3274
  }
3121
- if (settings["smoothScrollDuration"] !== void 0) {
3122
- context2.terminal.options.smoothScrollDuration = settings["smoothScrollDuration"];
3275
+ if (terminal["smoothScrollDuration"] !== void 0) {
3276
+ context2.terminal.options.smoothScrollDuration = terminal["smoothScrollDuration"];
3123
3277
  }
3124
3278
  }
3125
3279
  }
@@ -3717,6 +3871,7 @@ var CliHelpCommandProcessor = class {
3717
3871
  return 0;
3718
3872
  }
3719
3873
  );
3874
+ const narrow = isNarrow(context2);
3720
3875
  sortedModules.forEach(([module, processors]) => {
3721
3876
  const displayName = this.formatModuleName(module);
3722
3877
  writer.writeln(
@@ -3725,7 +3880,7 @@ var CliHelpCommandProcessor = class {
3725
3880
  processors.forEach((processor) => {
3726
3881
  const icon = processor?.metadata?.icon || CliIcon.Extension;
3727
3882
  const name = writer.wrapInColor(
3728
- processor.command.padEnd(20),
3883
+ narrow ? processor.command : processor.command.padEnd(20),
3729
3884
  CliForegroundColor.Cyan
3730
3885
  );
3731
3886
  const aliasText = processor.aliases?.length ? `${DIM} ${processor.aliases.join(", ")}${RESET}` : "";
@@ -3734,6 +3889,11 @@ var CliHelpCommandProcessor = class {
3734
3889
  processor.command,
3735
3890
  context2
3736
3891
  );
3892
+ if (narrow) {
3893
+ writer.writeln(` ${icon} ${name}${aliasText}`);
3894
+ writer.writeln(` ${desc}`);
3895
+ return;
3896
+ }
3737
3897
  writer.writeln(` ${icon} ${name} ${desc}${aliasText}`);
3738
3898
  });
3739
3899
  writer.writeln();
@@ -3843,9 +4003,10 @@ var CliHelpCommandProcessor = class {
3843
4003
  writer.writeln(
3844
4004
  ` ${BOLD}${writer.wrapInColor(t.t("cli.help.subcommands", "Subcommands"), CliForegroundColor.Yellow)}${RESET}`
3845
4005
  );
4006
+ const narrow = isNarrow(context2);
3846
4007
  processor.processors.forEach((sub) => {
3847
4008
  const subName = writer.wrapInColor(
3848
- sub.command.padEnd(16),
4009
+ narrow ? sub.command : sub.command.padEnd(16),
3849
4010
  CliForegroundColor.Cyan
3850
4011
  );
3851
4012
  const subAliases = sub.aliases?.length ? `${DIM} (${sub.aliases.join(", ")})${RESET}` : "";
@@ -3854,6 +4015,11 @@ var CliHelpCommandProcessor = class {
3854
4015
  `${processor.command}.${sub.command}`,
3855
4016
  context2
3856
4017
  );
4018
+ if (narrow) {
4019
+ writer.writeln(` ${subName}${subAliases}`);
4020
+ writer.writeln(` ${subDesc}`);
4021
+ return;
4022
+ }
3857
4023
  writer.writeln(
3858
4024
  ` ${subName} ${subDesc}${subAliases}`
3859
4025
  );
@@ -3867,7 +4033,7 @@ var CliHelpCommandProcessor = class {
3867
4033
  ` ${BOLD}${writer.wrapInColor(t.t("cli.help.options", "Options"), CliForegroundColor.Yellow)}${RESET}`
3868
4034
  );
3869
4035
  commandParams.forEach((param) => {
3870
- this.writeParameter(param, writer, t);
4036
+ this.writeParameter(param, writer, t, isNarrow(context2));
3871
4037
  });
3872
4038
  }
3873
4039
  if (globalParams.length > 0) {
@@ -3876,7 +4042,7 @@ var CliHelpCommandProcessor = class {
3876
4042
  ` ${BOLD}${writer.wrapInColor(t.t("cli.help.global_options", "Global options"), CliForegroundColor.Yellow)}${RESET}`
3877
4043
  );
3878
4044
  globalParams.forEach((param) => {
3879
- this.writeParameter(param, writer, t);
4045
+ this.writeParameter(param, writer, t, isNarrow(context2));
3880
4046
  });
3881
4047
  }
3882
4048
  if (processor.metadata?.requireServer) {
@@ -3898,7 +4064,7 @@ var CliHelpCommandProcessor = class {
3898
4064
  }
3899
4065
  return module.charAt(0).toUpperCase() + module.slice(1);
3900
4066
  }
3901
- writeParameter(param, writer, translator) {
4067
+ writeParameter(param, writer, translator, narrow = false) {
3902
4068
  const aliases = param.aliases?.length ? `, ${param.aliases.map((a) => `-${a}`).join(", ")}` : "";
3903
4069
  const nameStr = writer.wrapInColor(
3904
4070
  `--${param.name}${aliases}`,
@@ -3907,11 +4073,19 @@ var CliHelpCommandProcessor = class {
3907
4073
  const typeStr = `${DIM}<${param.type}>${RESET}`;
3908
4074
  const required = param.required ? writer.wrapInColor(` ${translator.t("cli.help.required", "(required)")}`, CliForegroundColor.Red) : "";
3909
4075
  const defaultVal = param.defaultValue !== void 0 && !param.required ? `${DIM} [${param.defaultValue}]${RESET}` : "";
4076
+ if (narrow) {
4077
+ writer.writeln(` ${nameStr} ${typeStr}${required}${defaultVal}`);
4078
+ writer.writeln(` ${param.description}`);
4079
+ return;
4080
+ }
3910
4081
  writer.writeln(
3911
4082
  ` ${nameStr} ${typeStr} ${param.description}${required}${defaultVal}`
3912
4083
  );
3913
4084
  }
3914
4085
  };
4086
+ function isNarrow(context2) {
4087
+ return (context2.terminal?.cols ?? 80) < 60;
4088
+ }
3915
4089
 
3916
4090
  // src/lib/constants/index.ts
3917
4091
  var CLi_Name_Art_Full = `
@@ -9831,11 +10005,12 @@ function hexToRgb(hex) {
9831
10005
  const n = parseInt(m[1], 16);
9832
10006
  return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255 };
9833
10007
  }
9834
- function colorSwatch(hex) {
9835
- if (!hex) return " ";
10008
+ function colorSwatch(hex, width = 2) {
10009
+ const cell = " ".repeat(width);
10010
+ if (!hex) return cell;
9836
10011
  const rgb = hexToRgb(hex);
9837
- if (!rgb) return " ";
9838
- return `\x1B[48;2;${rgb.r};${rgb.g};${rgb.b}m \x1B[0m`;
10012
+ if (!rgb) return cell;
10013
+ return `\x1B[48;2;${rgb.r};${rgb.g};${rgb.b}m${cell}\x1B[0m`;
9839
10014
  }
9840
10015
  function colorText(text, hex) {
9841
10016
  if (!hex) return text;
@@ -9902,11 +10077,20 @@ var CliThemeCommandProcessor = class {
9902
10077
  )
9903
10078
  );
9904
10079
  context2.writer.writeln();
10080
+ const narrow = (context2.terminal?.cols ?? 80) < 60;
9905
10081
  for (const [name, info] of entries) {
9906
10082
  const active = name === currentName ? " (active)" : "";
9907
10083
  const swatches = PALETTE_KEYS.map(
9908
10084
  (k) => colorSwatch(info.theme[k])
9909
10085
  ).join("");
10086
+ if (narrow) {
10087
+ const compact = PALETTE_KEYS.slice(0, 8).map((k) => colorSwatch(info.theme[k], 1)).join("");
10088
+ context2.writer.writeln(
10089
+ ` ${compact} ${context2.writer.wrapInColor(name, CliForegroundColor.Cyan)}${active}`
10090
+ );
10091
+ context2.writer.writeln(` ${info.description}`);
10092
+ continue;
10093
+ }
9910
10094
  const nameLabel = context2.writer.wrapInColor(
9911
10095
  name.padEnd(22),
9912
10096
  CliForegroundColor.Cyan
@@ -10608,37 +10792,106 @@ var CliPingCommandProcessor = class {
10608
10792
  var ICliEnvironment_TOKEN = "cli-environment";
10609
10793
  var CliEnvironment = class {
10610
10794
  constructor() {
10611
- this.vars = /* @__PURE__ */ new Map();
10612
- this.vars.set("SHELL", "/bin/sh");
10613
- this.vars.set("TERM", "xterm-256color");
10614
- this.vars.set("LANG", "en_US.UTF-8");
10615
- this.vars.set("PWD", "/");
10616
- this.vars.set("HOME", "/home/user");
10617
- this.vars.set("USER", "user");
10618
- this.vars.set("PATH", "/usr/local/bin:/usr/bin:/bin");
10795
+ this.defaults = /* @__PURE__ */ new Map();
10796
+ /** Variables computed on read; a user's own value wins over them. */
10797
+ this.bound = /* @__PURE__ */ new Map();
10798
+ this.profiles = /* @__PURE__ */ new Map();
10799
+ this.profileKey = () => "";
10800
+ this.defaults.set("SHELL", "/bin/sh");
10801
+ this.defaults.set("TERM", "xterm-256color");
10802
+ this.defaults.set("LANG", "en_US.UTF-8");
10803
+ this.defaults.set("PWD", "/");
10804
+ this.defaults.set("HOME", "/home/user");
10805
+ this.defaults.set("USER", "user");
10806
+ this.defaults.set("PATH", "/usr/local/bin:/usr/bin:/bin");
10807
+ }
10808
+ /** Compute a variable on every read; the seeded value is the fallback while it returns nothing. */
10809
+ bind(name, resolver) {
10810
+ this.bound.set(name, resolver);
10811
+ }
10812
+ /** Pick whose changes apply (the signed-in user's name, '' when signed out). */
10813
+ setProfileResolver(resolver) {
10814
+ this.profileKey = () => resolver() ?? "";
10815
+ }
10816
+ /** Restore saved profiles (replaces the ones in memory). */
10817
+ load(snapshot) {
10818
+ this.profiles.clear();
10819
+ for (const [key, profile] of Object.entries(snapshot ?? {})) {
10820
+ this.profiles.set(key, {
10821
+ vars: new Map(Object.entries(profile?.vars ?? {})),
10822
+ unset: new Set(profile?.unset ?? [])
10823
+ });
10824
+ }
10825
+ }
10826
+ snapshot() {
10827
+ const result = {};
10828
+ for (const [key, profile] of this.profiles) {
10829
+ if (!profile.vars.size && !profile.unset.size) continue;
10830
+ result[key] = { vars: Object.fromEntries(profile.vars), unset: [...profile.unset] };
10831
+ }
10832
+ return result;
10619
10833
  }
10620
10834
  get(name) {
10621
- return this.vars.get(name);
10835
+ const profile = this.profiles.get(this.profileKey());
10836
+ if (profile?.vars.has(name)) return profile.vars.get(name);
10837
+ if (profile?.unset.has(name)) return void 0;
10838
+ return this.bound.get(name)?.() || this.defaults.get(name);
10622
10839
  }
10623
10840
  set(name, value) {
10624
- this.vars.set(name, value);
10841
+ const profile = this.currentProfile();
10842
+ profile.unset.delete(name);
10843
+ profile.vars.set(name, value);
10844
+ this.onChange?.(this.snapshot());
10625
10845
  }
10626
10846
  unset(name) {
10627
- this.vars.delete(name);
10847
+ const profile = this.currentProfile();
10848
+ profile.vars.delete(name);
10849
+ if (this.defaults.has(name) || this.bound.has(name)) profile.unset.add(name);
10850
+ this.onChange?.(this.snapshot());
10628
10851
  }
10629
10852
  getAll() {
10853
+ const profile = this.profiles.get(this.profileKey());
10854
+ const names = /* @__PURE__ */ new Set([...this.defaults.keys(), ...this.bound.keys(), ...profile?.vars.keys() ?? []]);
10630
10855
  const result = {};
10631
- for (const [k, v] of this.vars) {
10632
- result[k] = v;
10856
+ for (const name of names) {
10857
+ const value = this.get(name);
10858
+ if (value !== void 0) result[name] = value;
10633
10859
  }
10634
10860
  return result;
10635
10861
  }
10636
10862
  has(name) {
10637
- return this.vars.has(name);
10863
+ return this.get(name) !== void 0;
10864
+ }
10865
+ currentProfile() {
10866
+ const key = this.profileKey();
10867
+ let profile = this.profiles.get(key);
10868
+ if (!profile) {
10869
+ profile = { vars: /* @__PURE__ */ new Map(), unset: /* @__PURE__ */ new Set() };
10870
+ this.profiles.set(key, profile);
10871
+ }
10872
+ return profile;
10638
10873
  }
10639
10874
  };
10640
10875
 
10641
10876
  // src/lib/processors/cli-env-command-processor.ts
10877
+ var ASSIGNMENT = /([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|'[^']*'|\S*)/y;
10878
+ var NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
10879
+ function parseAssignments(raw) {
10880
+ const result = [];
10881
+ for (const token of raw.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? []) {
10882
+ ASSIGNMENT.lastIndex = 0;
10883
+ const match = ASSIGNMENT.exec(token);
10884
+ if (match && ASSIGNMENT.lastIndex === token.length) {
10885
+ const quoted = /^(["']).*\1$/.test(match[2]);
10886
+ result.push({ name: match[1], value: quoted ? match[2].slice(1, -1) : match[2] });
10887
+ } else if (NAME.test(token)) {
10888
+ result.push({ name: token });
10889
+ } else {
10890
+ return null;
10891
+ }
10892
+ }
10893
+ return result;
10894
+ }
10642
10895
  var CliExportCommandProcessor = class {
10643
10896
  constructor() {
10644
10897
  this.command = "export";
@@ -10646,6 +10899,21 @@ var CliExportCommandProcessor = class {
10646
10899
  this.author = DefaultLibraryAuthor;
10647
10900
  this.acceptsRawInput = true;
10648
10901
  this.metadata = { icon: "\u{1F4E6}", module: "system" };
10902
+ this.stateConfiguration = {
10903
+ storeName: "environment",
10904
+ initialState: { profiles: {} }
10905
+ };
10906
+ }
10907
+ /** Restore saved variables and save every later change (export, unset, VAR=value). */
10908
+ async initialize(context2) {
10909
+ const env = context2.services.get(ICliEnvironment_TOKEN);
10910
+ if (!(env instanceof CliEnvironment)) return;
10911
+ const store = context2.state;
10912
+ env.load(store.getState()?.profiles);
10913
+ env.onChange = (profiles) => {
10914
+ store.updateState({ profiles });
10915
+ void store.persist();
10916
+ };
10649
10917
  }
10650
10918
  async processCommand(command2, context2) {
10651
10919
  const env = context2.services.getRequired(
@@ -10662,20 +10930,21 @@ var CliExportCommandProcessor = class {
10662
10930
  }
10663
10931
  return;
10664
10932
  }
10665
- const match = raw.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
10666
- if (!match) {
10933
+ const assignments = parseAssignments(raw);
10934
+ if (!assignments?.length) {
10667
10935
  context2.writer.writeError(
10668
10936
  `export: invalid syntax: ${raw}`
10669
10937
  );
10670
10938
  context2.process.exit(1, { silent: true });
10671
10939
  return;
10672
10940
  }
10673
- const varName = match[1];
10674
- let varValue = match[2];
10675
- if (varValue.startsWith('"') && varValue.endsWith('"') || varValue.startsWith("'") && varValue.endsWith("'")) {
10676
- varValue = varValue.slice(1, -1);
10941
+ for (const { name, value } of assignments) {
10942
+ if (value !== void 0) {
10943
+ env.set(name, value);
10944
+ } else if (!env.has(name)) {
10945
+ env.set(name, "");
10946
+ }
10677
10947
  }
10678
- env.set(varName, varValue);
10679
10948
  }
10680
10949
  writeDescription(context2) {
10681
10950
  const { writer, translator: t } = context2;
@@ -10691,6 +10960,13 @@ var CliExportCommandProcessor = class {
10691
10960
  writer.writeln(
10692
10961
  ` ${writer.wrapInColor('export VAR="hello world"', CliForegroundColor.Cyan)} ${t.t("cli.export.set_spaces", "Set with spaces")}`
10693
10962
  );
10963
+ writer.writeln(
10964
+ ` ${writer.wrapInColor("export A=1 B=2", CliForegroundColor.Cyan)} ${t.t("cli.export.set_many", "Set several at once")}`
10965
+ );
10966
+ writer.writeln();
10967
+ writer.writeln(
10968
+ t.t("cli.export.saved", "Variables are saved for the signed-in user and used as $VAR or ${VAR} in commands.")
10969
+ );
10694
10970
  }
10695
10971
  };
10696
10972
  var CliUnsetCommandProcessor = class {
@@ -10705,16 +10981,15 @@ var CliUnsetCommandProcessor = class {
10705
10981
  const env = context2.services.getRequired(
10706
10982
  ICliEnvironment_TOKEN
10707
10983
  );
10708
- const varName = (command2.value || "").trim();
10709
- if (!varName) {
10984
+ const names = (command2.value || "").trim().split(/\s+/).filter(Boolean);
10985
+ if (!names.length) {
10710
10986
  context2.writer.writeError("unset: missing variable name");
10711
10987
  context2.process.exit(1, { silent: true });
10712
10988
  return;
10713
10989
  }
10714
- if (!env.has(varName)) {
10715
- return;
10990
+ for (const name of names) {
10991
+ if (env.has(name)) env.unset(name);
10716
10992
  }
10717
- env.unset(varName);
10718
10993
  }
10719
10994
  writeDescription(context2) {
10720
10995
  const { writer, translator: t } = context2;
@@ -10722,7 +10997,7 @@ var CliUnsetCommandProcessor = class {
10722
10997
  writer.writeln();
10723
10998
  writer.writeln(t.t("cli.common.usage", "Usage:"));
10724
10999
  writer.writeln(
10725
- ` ${writer.wrapInColor("unset VAR", CliForegroundColor.Cyan)}`
11000
+ ` ${writer.wrapInColor("unset VAR [VAR...]", CliForegroundColor.Cyan)}`
10726
11001
  );
10727
11002
  }
10728
11003
  };
@@ -10772,7 +11047,6 @@ var CliEnvCommandProcessor = class {
10772
11047
  );
10773
11048
  }
10774
11049
  };
10775
- var DEFAULT_FONT_SIZE = 20;
10776
11050
  var MIN_FONT_SIZE = 8;
10777
11051
  var MAX_FONT_SIZE = 40;
10778
11052
  var STEP = 2;
@@ -10785,7 +11059,7 @@ var CliFontSizeCommandProcessor = class {
10785
11059
  this.metadata = { icon: CliIcon.Settings, module: "system" };
10786
11060
  this.stateConfiguration = {
10787
11061
  storeName: "font-size",
10788
- initialState: { fontSize: DEFAULT_FONT_SIZE }
11062
+ initialState: {}
10789
11063
  };
10790
11064
  this.processors = [
10791
11065
  {
@@ -10804,9 +11078,9 @@ var CliFontSizeCommandProcessor = class {
10804
11078
  },
10805
11079
  {
10806
11080
  command: "reset",
10807
- description: `Reset font size to default (${DEFAULT_FONT_SIZE}px)`,
11081
+ description: "Reset font size to the default",
10808
11082
  processCommand: async (_, context2) => {
10809
- await this.setFontSize(context2, DEFAULT_FONT_SIZE);
11083
+ await this.resetFontSize(context2);
10810
11084
  }
10811
11085
  },
10812
11086
  {
@@ -10830,6 +11104,7 @@ var CliFontSizeCommandProcessor = class {
10830
11104
  ];
10831
11105
  }
10832
11106
  async initialize(context2) {
11107
+ this.hostFontSize = context2.terminal?.options?.fontSize;
10833
11108
  const state = context2.state.getState();
10834
11109
  if (state?.fontSize != null && context2.terminal?.options) {
10835
11110
  context2.terminal.options.fontSize = state.fontSize;
@@ -10850,7 +11125,7 @@ var CliFontSizeCommandProcessor = class {
10850
11125
  ` ${context2.writer.wrapInColor("font-size set <n>", CliForegroundColor.Cyan)} Set to exact size`
10851
11126
  );
10852
11127
  context2.writer.writeln(
10853
- ` ${context2.writer.wrapInColor("font-size reset", CliForegroundColor.Cyan)} Reset to default (${DEFAULT_FONT_SIZE}px)`
11128
+ ` ${context2.writer.wrapInColor("font-size reset", CliForegroundColor.Cyan)} Reset to default (${this.getDefault()}px)`
10854
11129
  );
10855
11130
  }
10856
11131
  getCurrent(context2) {
@@ -10859,7 +11134,19 @@ var CliFontSizeCommandProcessor = class {
10859
11134
  return fromTerminal;
10860
11135
  }
10861
11136
  const state = context2.state.getState();
10862
- return state?.fontSize ?? DEFAULT_FONT_SIZE;
11137
+ return state?.fontSize ?? this.getDefault();
11138
+ }
11139
+ getDefault() {
11140
+ return this.hostFontSize ?? defaultFontSize();
11141
+ }
11142
+ async resetFontSize(context2) {
11143
+ const size = this.getDefault();
11144
+ if (context2.terminal?.options) {
11145
+ context2.terminal.options.fontSize = size;
11146
+ }
11147
+ context2.state.updateState({ fontSize: void 0 });
11148
+ await context2.state.persist();
11149
+ context2.writer.writeSuccess(`Font size set to ${size}px`);
10863
11150
  }
10864
11151
  async setFontSize(context2, size) {
10865
11152
  const clamped = Math.min(MAX_FONT_SIZE, Math.max(MIN_FONT_SIZE, size));
@@ -11810,6 +12097,7 @@ var CliTimerManager = class {
11810
12097
  this.managedTimers.clear();
11811
12098
  }
11812
12099
  };
12100
+ var MIN_READABLE_COLUMN = 8;
11813
12101
  var CliTerminalWriter = class {
11814
12102
  constructor(terminal) {
11815
12103
  this.terminal = terminal;
@@ -11941,6 +12229,24 @@ var CliTerminalWriter = class {
11941
12229
  const indent = " ".repeat(level);
11942
12230
  this.writeln(`${indent}${text}`);
11943
12231
  }
12232
+ /** One block per row, header names as keys; used when a table cannot fit the terminal. */
12233
+ writeRecords(headers, rows) {
12234
+ const keyWidth = Math.max(...headers.map((h) => visibleLength(h)));
12235
+ rows.forEach((row, index) => {
12236
+ if (index > 0) this.write("\r\n");
12237
+ headers.forEach((header, i) => {
12238
+ const value = row[i] ?? "";
12239
+ if (!header.trim()) {
12240
+ if (value.trim()) this.write(`${value}\r
12241
+ `);
12242
+ return;
12243
+ }
12244
+ const key = header + " ".repeat(keyWidth - visibleLength(header));
12245
+ this.write(`${this.wrapInColor(key, CliForegroundColor.Yellow)} : ${value}\r
12246
+ `);
12247
+ });
12248
+ });
12249
+ }
11944
12250
  writeTable(headers, rows, options) {
11945
12251
  const cols = this.terminal.cols;
11946
12252
  const naturalWidths = headers.map(
@@ -11978,6 +12284,11 @@ var CliTerminalWriter = class {
11978
12284
  used++;
11979
12285
  }
11980
12286
  }
12287
+ 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));
12288
+ if (tooNarrow) {
12289
+ this.writeRecords(headers, rows);
12290
+ return;
12291
+ }
11981
12292
  const truncateText = (text, maxWidth) => {
11982
12293
  const t = text ?? "";
11983
12294
  const vis = visibleLength(t);
@@ -12415,6 +12726,10 @@ var InputModeBase = class {
12415
12726
  * Cut a line to the terminal width. Lines redrawn in place must stay on one row:
12416
12727
  * a wrapped line takes two rows and breaks the cursor-up arithmetic of the redraw.
12417
12728
  */
12729
+ /** A prompt label without a trailing colon, for modes that append their own ": ". */
12730
+ label(promptText) {
12731
+ return promptText.replace(/:\s*$/, "");
12732
+ }
12418
12733
  fitLine(text) {
12419
12734
  return truncateToWidth(text, Math.max(1, this.host.getTerminalCols() - 1));
12420
12735
  }
@@ -12589,6 +12904,7 @@ var ConfirmInputMode = class extends InputModeBase {
12589
12904
  async handleInput(data) {
12590
12905
  if (data === "\r") {
12591
12906
  if (this.buffer === "") {
12907
+ this.host.terminal.write(this.defaultValue ? "y" : "n");
12592
12908
  this.resolveAndPop(this.defaultValue);
12593
12909
  } else {
12594
12910
  const lower = this.buffer.toLowerCase();
@@ -12770,7 +13086,7 @@ var SelectInputMode = class extends InputModeBase {
12770
13086
  const selectedOpt = this.options.find((o) => o.value === value);
12771
13087
  const label = selectedOpt ? selectedOpt.label : value;
12772
13088
  this.host.terminal.write("\x1B[2K\r");
12773
- this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${label}\x1B[0m`);
13089
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.label(this.promptText)}: \x1B[36m${label}\x1B[0m`);
12774
13090
  this.host.terminal.write("\x1B[?25h");
12775
13091
  super.resolveAndPop(value);
12776
13092
  }
@@ -12780,7 +13096,7 @@ var SelectInputMode = class extends InputModeBase {
12780
13096
  abort() {
12781
13097
  this.clearRenderedLines();
12782
13098
  this.host.terminal.write("\x1B[2K\r");
12783
- this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
13099
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.label(this.promptText)}: \x1B[2mcancelled\x1B[0m`);
12784
13100
  this.host.terminal.write("\x1B[?25h");
12785
13101
  super.abort();
12786
13102
  }
@@ -12941,15 +13257,15 @@ var SelectInputMode = class extends InputModeBase {
12941
13257
  if (this.selectOptions?.searchable) {
12942
13258
  if (this.filter.length > 0) {
12943
13259
  this.host.terminal.write(
12944
- this.fitLine(`\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`)
13260
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: ${this.filter}`)
12945
13261
  );
12946
13262
  } else {
12947
13263
  this.host.terminal.write(
12948
- this.fitLine(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`)
13264
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[2mType to filter...\x1B[0m`)
12949
13265
  );
12950
13266
  }
12951
13267
  } else {
12952
- this.host.terminal.write(this.fitLine(`\x1B[36m?\x1B[0m ${this.promptText}:`));
13268
+ this.host.terminal.write(this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}:`));
12953
13269
  }
12954
13270
  }
12955
13271
  /**
@@ -13121,7 +13437,7 @@ var InlineSelectInputMode = class extends InputModeBase {
13121
13437
  const selectedOpt = this.selectableOptions.find((o) => o.value === value);
13122
13438
  const label = selectedOpt ? selectedOpt.label : value;
13123
13439
  this.host.terminal.write("\x1B[2K\r");
13124
- this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText}: \x1B[36m${label}\x1B[0m`);
13440
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.label(this.promptText)}: \x1B[36m${label}\x1B[0m`);
13125
13441
  this.host.terminal.write("\x1B[?25h");
13126
13442
  super.resolveAndPop(value);
13127
13443
  }
@@ -13131,7 +13447,7 @@ var InlineSelectInputMode = class extends InputModeBase {
13131
13447
  abort() {
13132
13448
  this.clearExtraLines();
13133
13449
  this.host.terminal.write("\x1B[2K\r");
13134
- this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText}: \x1B[2mcancelled\x1B[0m`);
13450
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.label(this.promptText)}: \x1B[2mcancelled\x1B[0m`);
13135
13451
  this.host.terminal.write("\x1B[?25h");
13136
13452
  super.abort();
13137
13453
  }
@@ -13171,13 +13487,13 @@ var InlineSelectInputMode = class extends InputModeBase {
13171
13487
  * Get the prompt prefix string (without ANSI escapes for width calculation).
13172
13488
  */
13173
13489
  getPromptPrefix() {
13174
- return `? ${this.promptText}: `;
13490
+ return `? ${this.label(this.promptText)}: `;
13175
13491
  }
13176
13492
  /**
13177
13493
  * Get the rendered prompt prefix (with ANSI color codes).
13178
13494
  */
13179
13495
  getRenderedPromptPrefix() {
13180
- return `\x1B[36m?\x1B[0m ${this.promptText}: `;
13496
+ return `\x1B[36m?\x1B[0m ${this.label(this.promptText)}: `;
13181
13497
  }
13182
13498
  /**
13183
13499
  * Calculate the width needed for a single option.
@@ -13374,7 +13690,7 @@ var MultiSelectInputMode = class extends InputModeBase {
13374
13690
  });
13375
13691
  const summary = labels.length > 0 ? labels.join(", ") : "none";
13376
13692
  this.host.terminal.write("\x1B[2K\r");
13377
- this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.promptText} \x1B[36m${summary}\x1B[0m`);
13693
+ this.host.terminal.write(`\x1B[32m\u2714\x1B[0m ${this.label(this.promptText)}: \x1B[36m${summary}\x1B[0m`);
13378
13694
  this.host.terminal.write("\x1B[?25h");
13379
13695
  super.resolveAndPop(value);
13380
13696
  }
@@ -13384,7 +13700,7 @@ var MultiSelectInputMode = class extends InputModeBase {
13384
13700
  abort() {
13385
13701
  this.clearRenderedLines();
13386
13702
  this.host.terminal.write("\x1B[2K\r");
13387
- this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.promptText} \x1B[2mcancelled\x1B[0m`);
13703
+ this.host.terminal.write(`\x1B[33m\u2718\x1B[0m ${this.label(this.promptText)}: \x1B[2mcancelled\x1B[0m`);
13388
13704
  this.host.terminal.write("\x1B[?25h");
13389
13705
  super.abort();
13390
13706
  }
@@ -13558,15 +13874,15 @@ var MultiSelectInputMode = class extends InputModeBase {
13558
13874
  if (this.selectOptions?.searchable) {
13559
13875
  if (this.filter.length > 0) {
13560
13876
  this.host.terminal.write(
13561
- this.fitLine(`\x1B[36m?\x1B[0m ${this.promptText}: ${this.filter}`)
13877
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: ${this.filter}`)
13562
13878
  );
13563
13879
  } else {
13564
13880
  this.host.terminal.write(
13565
- this.fitLine(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2mType to filter...\x1B[0m`)
13881
+ this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[2mType to filter...\x1B[0m`)
13566
13882
  );
13567
13883
  }
13568
13884
  } else {
13569
- this.host.terminal.write(this.fitLine(`\x1B[36m?\x1B[0m ${this.promptText}:`));
13885
+ this.host.terminal.write(this.fitLine(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}:`));
13570
13886
  }
13571
13887
  }
13572
13888
  /**
@@ -13901,13 +14217,13 @@ var FileInputMode = class extends InputModeBase {
13901
14217
  this.waiting = false;
13902
14218
  if (!results || this.isResolved) {
13903
14219
  this.host.terminal.write("\x1B[2K\r");
13904
- this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[2m(cancelled)\x1B[0m\r
14220
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[2m(cancelled)\x1B[0m\r
13905
14221
  `);
13906
14222
  if (!this.isResolved) this.abort();
13907
14223
  return;
13908
14224
  }
13909
14225
  this.host.terminal.write("\x1B[2K\r");
13910
- this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}:\r
14226
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}:\r
13911
14227
  `);
13912
14228
  let totalSize = 0;
13913
14229
  for (const file of results) {
@@ -13925,7 +14241,7 @@ var FileInputMode = class extends InputModeBase {
13925
14241
  this.waiting = false;
13926
14242
  if (this.isResolved) return;
13927
14243
  this.host.terminal.write("\x1B[2K\r");
13928
- this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.promptText}: \x1B[31m(error)\x1B[0m\r
14244
+ this.host.writeToTerminal(`\x1B[36m?\x1B[0m ${this.label(this.promptText)}: \x1B[31m(error)\x1B[0m\r
13929
14245
  `);
13930
14246
  this.abort();
13931
14247
  }
@@ -15395,7 +15711,7 @@ function substituteVars(input, variables) {
15395
15711
  );
15396
15712
  return result;
15397
15713
  }
15398
- function expandEnvironmentVars(command2, context2) {
15714
+ function expandEnvironmentVars(command2, context2, lastExitCode) {
15399
15715
  const env = context2.services.get(ICliEnvironment_TOKEN);
15400
15716
  if (!env) {
15401
15717
  return command2;
@@ -15403,6 +15719,9 @@ function expandEnvironmentVars(command2, context2) {
15403
15719
  const parts = command2.split("'");
15404
15720
  for (let i = 0; i < parts.length; i++) {
15405
15721
  if (i % 2 === 0) {
15722
+ if (lastExitCode !== void 0) {
15723
+ parts[i] = parts[i].replace(/\$\?/g, String(lastExitCode));
15724
+ }
15406
15725
  parts[i] = parts[i].replace(
15407
15726
  /\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g,
15408
15727
  (_, name) => env.get(name) ?? ""
@@ -15411,17 +15730,30 @@ function expandEnvironmentVars(command2, context2) {
15411
15730
  /\$([A-Za-z_][A-Za-z0-9_]*)/g,
15412
15731
  (_, name) => env.get(name) ?? ""
15413
15732
  );
15733
+ const home = env.get("HOME");
15734
+ if (home) {
15735
+ parts[i] = parts[i].replace(
15736
+ /(^|\s)~(?=\/|\s|$)/g,
15737
+ (match, before, offset) => i > 0 && offset === 0 && !before ? match : `${before}${home}`
15738
+ );
15739
+ }
15414
15740
  }
15415
15741
  }
15416
15742
  return parts.join("'");
15417
15743
  }
15418
15744
 
15419
15745
  // src/lib/executor/cli-command-executor.ts
15746
+ function toExitStatus(code) {
15747
+ if (code === void 0) return 0;
15748
+ return code < 0 ? 1 : code;
15749
+ }
15420
15750
  var CliCommandExecutor = class {
15421
15751
  constructor(registry) {
15422
15752
  this.registry = registry;
15423
15753
  this.commandParser = new CommandParser();
15424
15754
  this.globalParameters = [];
15755
+ /** Exit status of the last command, expanded as `$?` (0 = success). */
15756
+ this.lastExitCode = 0;
15425
15757
  this.registerGlobalParameter(versionGlobalParameter);
15426
15758
  this.registerGlobalParameter(helpGlobalParameter);
15427
15759
  this.registerGlobalParameter(contextGlobalParameter);
@@ -15525,6 +15857,7 @@ var CliCommandExecutor = class {
15525
15857
  rootContext
15526
15858
  );
15527
15859
  lastExitSuccess = context2.process.exitCode === void 0 || context2.process.exitCode === 0;
15860
+ this.lastExitCode = toExitStatus(context2.process.exitCode);
15528
15861
  rootContext.lastCommandResult = {
15529
15862
  command: part.value,
15530
15863
  success: lastExitSuccess
@@ -15532,6 +15865,7 @@ var CliCommandExecutor = class {
15532
15865
  pipelineData = context2.process.data;
15533
15866
  } catch (e) {
15534
15867
  lastExitSuccess = false;
15868
+ this.lastExitCode = 1;
15535
15869
  rootContext.lastCommandResult = {
15536
15870
  command: part.value,
15537
15871
  success: false
@@ -15547,7 +15881,7 @@ var CliCommandExecutor = class {
15547
15881
  }
15548
15882
  async executeSingleCommand(command2, data, context2) {
15549
15883
  const process = context2.process;
15550
- command2 = expandEnvironmentVars(command2, context2);
15884
+ command2 = expandEnvironmentVars(command2, context2, this.lastExitCode);
15551
15885
  const assignMatch = command2.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);
15552
15886
  if (assignMatch) {
15553
15887
  const env = context2.services.get(ICliEnvironment_TOKEN);
@@ -16603,30 +16937,6 @@ var CliDefaultPingServerService = class {
16603
16937
  return new Promise((resolve) => setTimeout(resolve, 2e3));
16604
16938
  }
16605
16939
  };
16606
- var CliDragDropService = class {
16607
- constructor(_container) {
16608
- this._container = _container;
16609
- this._subject = new Subject();
16610
- this._dragOver = (e) => e.preventDefault();
16611
- this._drop = (e) => {
16612
- e.preventDefault();
16613
- const files = Array.from(e.dataTransfer?.files ?? []);
16614
- if (files.length > 0) {
16615
- this._subject.next(files);
16616
- }
16617
- };
16618
- this._container.addEventListener("dragover", this._dragOver);
16619
- this._container.addEventListener("drop", this._drop);
16620
- }
16621
- get onFileDrop() {
16622
- return this._subject.asObservable();
16623
- }
16624
- destroy() {
16625
- this._container.removeEventListener("dragover", this._dragOver);
16626
- this._container.removeEventListener("drop", this._drop);
16627
- this._subject.complete();
16628
- }
16629
- };
16630
16940
 
16631
16941
  // src/lib/services/cli-permission-registry.ts
16632
16942
  var CliPermissionRegistry = class {
@@ -16651,62 +16961,6 @@ var CliPermissionRegistry = class {
16651
16961
  }
16652
16962
  };
16653
16963
 
16654
- // src/lib/addons/overlay.ts
16655
- var OverlayAddon = class {
16656
- constructor() {
16657
- this.overlayNode = document.createElement("div");
16658
- this.overlayNode.style.cssText = `border-radius: 15px;
16659
- font-size: xx-large;
16660
- opacity: 0.75;
16661
- padding: 0.2em 0.5em 0.2em 0.5em;
16662
- position: absolute;
16663
- -webkit-user-select: none;
16664
- -webkit-transition: opacity 180ms ease-in;
16665
- -moz-user-select: none;
16666
- -moz-transition: opacity 180ms ease-in;`;
16667
- this.overlayNode.addEventListener(
16668
- "mousedown",
16669
- (e) => {
16670
- e.preventDefault();
16671
- e.stopPropagation();
16672
- },
16673
- true
16674
- );
16675
- }
16676
- activate(terminal) {
16677
- this.terminal = terminal;
16678
- }
16679
- dispose() {
16680
- }
16681
- showOverlay(msg, timeout) {
16682
- const { terminal, overlayNode } = this;
16683
- if (!terminal.element) return;
16684
- overlayNode.style.color = "#101010";
16685
- overlayNode.style.backgroundColor = "#f0f0f0";
16686
- overlayNode.textContent = msg;
16687
- overlayNode.style.opacity = "0.75";
16688
- if (!overlayNode.parentNode) {
16689
- terminal.element.appendChild(overlayNode);
16690
- }
16691
- const divSize = terminal.element.getBoundingClientRect();
16692
- const overlaySize = overlayNode.getBoundingClientRect();
16693
- overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + "px";
16694
- overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + "px";
16695
- if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
16696
- if (!timeout) return;
16697
- this.overlayTimeout = window.setTimeout(() => {
16698
- overlayNode.style.opacity = "0";
16699
- this.overlayTimeout = window.setTimeout(() => {
16700
- if (overlayNode.parentNode) {
16701
- overlayNode.parentNode.removeChild(overlayNode);
16702
- }
16703
- this.overlayTimeout = void 0;
16704
- overlayNode.style.opacity = "0.75";
16705
- }, 200);
16706
- }, timeout || 1500);
16707
- }
16708
- };
16709
-
16710
16964
  // src/lib/storage/cli-key-value-store.ts
16711
16965
  var CliKeyValueStore = class {
16712
16966
  constructor() {
@@ -17881,61 +18135,6 @@ function createServerModule() {
17881
18135
  services: []
17882
18136
  };
17883
18137
  }
17884
- function getTerminalOptions(userOptions) {
17885
- return {
17886
- cursorBlink: true,
17887
- allowProposedApi: true,
17888
- fontSize: 20,
17889
- theme: DefaultThemes.default,
17890
- convertEol: true,
17891
- ...userOptions ?? {}
17892
- };
17893
- }
17894
- function initializeTerminal(container, opts) {
17895
- const terminal = new Terminal(opts);
17896
- const fitAddon = new FitAddon();
17897
- terminal.loadAddon(fitAddon);
17898
- terminal.loadAddon(new WebLinksAddon());
17899
- terminal.loadAddon(new OverlayAddon());
17900
- terminal.loadAddon(new Unicode11Addon());
17901
- const serializeAddon = new SerializeAddon();
17902
- terminal.loadAddon(serializeAddon);
17903
- terminal.open(container);
17904
- fitAddon.fit();
17905
- requestAnimationFrame(() => fitAddon.fit());
17906
- if (document.fonts?.ready) {
17907
- document.fonts.ready.then(() => {
17908
- requestAnimationFrame(() => fitAddon.fit());
17909
- });
17910
- }
17911
- container.classList.add("terminal-container");
17912
- container.style.background = opts.theme?.background ?? "#000";
17913
- const wheelListener = (e) => e.preventDefault();
17914
- container.addEventListener("wheel", wheelListener, { passive: false });
17915
- terminal.focus();
17916
- const dragDropService = new CliDragDropService(container);
17917
- return { terminal, fitAddon, serializeAddon, dragDropService, wheelListener };
17918
- }
17919
- var LAYOUT_TIMEOUT_MS = 1e4;
17920
- function waitForLayout(container) {
17921
- return new Promise((resolve, reject) => {
17922
- const deadline = Date.now() + LAYOUT_TIMEOUT_MS;
17923
- const check = () => {
17924
- if (container.offsetWidth > 0 && container.offsetHeight > 0) {
17925
- resolve();
17926
- return;
17927
- }
17928
- if (Date.now() >= deadline) {
17929
- reject(new Error(
17930
- `CliEngine.start(): container has zero dimensions after ${LAYOUT_TIMEOUT_MS}ms. Ensure the container element is visible and laid out before calling start().`
17931
- ));
17932
- return;
17933
- }
17934
- requestAnimationFrame(check);
17935
- };
17936
- check();
17937
- });
17938
- }
17939
18138
 
17940
18139
  // src/lib/editor/syntax/registry.ts
17941
18140
  var SyntaxHighlighterRegistry = class {
@@ -18327,6 +18526,14 @@ var CliEngine = class {
18327
18526
  { ...this.options ?? {}, terminalOptions: terminalOpts }
18328
18527
  );
18329
18528
  this.executionContext.initializeTerminalListeners();
18529
+ const environment = services.get(ICliEnvironment_TOKEN);
18530
+ if (environment instanceof CliEnvironment) {
18531
+ const ctx = this.executionContext;
18532
+ environment.bind("USER", () => ctx.userSession?.user.name);
18533
+ environment.bind("HOME", () => ctx.userSession?.user.homeDir);
18534
+ environment.bind("PWD", () => ctx.promptPathProvider?.());
18535
+ environment.setProfileResolver(() => ctx.userSession?.user.name);
18536
+ }
18330
18537
  this.executionContext.backgroundServices.setProcessRegistry(processRegistry);
18331
18538
  services.set([{
18332
18539
  provide: CliBackgroundServiceRegistry_TOKEN,
@@ -18917,6 +19124,6 @@ function createInMemoryKvStore() {
18917
19124
  };
18918
19125
  }
18919
19126
 
18920
- export { API_VERSION, CLi_Name_Art, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliFullScreenManager, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliHtopCommandProcessor, CliHttpClient, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKeyValueStore_TOKEN, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliNotifier, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPermissionRegistry, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimerManager, CliTimestampCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, OverlayAddon, ProcessExitedError, ScriptLoaderService, SyntaxHighlighterRegistry_TOKEN, appendOutputToFile, appendStderrToFile, builtinProcessors, computeDiff, contextGlobalParameter, createServerModule, describeShellClose, executeOnServer, expandEnvironmentVars, formatDiff, getAccelerator, getCliNameArt, getGreetingBasedOnTime, getTerminalOptions, groupBy, helpGlobalParameter, hotkeysInfo, initWasmAccelerator, initializeServices, initializeTerminal, isWasmAccelerated, miscProcessors, openLink, reconcileArgs, renderServerResponse, renderSingleOutput, substituteVars, systemProcessors, themeInfos, themes, tryExecuteScript, versionGlobalParameter, waitForLayout, welcomeModule, writeOutputToFile, writeStderrToFile };
19127
+ export { API_VERSION, CLi_Name_Art, CapturingTerminalWriter, CliAliasCommandProcessor, CliArgsParser, CliBackgroundServiceRegistry_TOKEN, CliBoot, CliCalCommandProcessor, CliCaptureCommandProcessor, CliClearCommandProcessor, CliClipboard, CliClipboardCommandProcessor, CliColorCommandProcessor, CliCommandExecutionContext, CliCommandExecutor, CliCommandHistory, CliCommandHistory_TOKEN, CliCommandProcessorRegistry, CliConfigureCommandProcessor, CliConvertCommandProcessor, CliDebugCommandProcessor, CliDefaultPingServerService, CliDiffCommandProcessor, CliDragDropService, CliEchoCommandProcessor, CliEngine, CliEnvCommandProcessor, CliEnvironment, CliEvalCommandProcessor, CliExecutionContext, CliExecutionProcess, CliExportCommandProcessor, CliFeedbackCommandProcessor, CliFontSizeCommandProcessor, CliFullScreenManager, CliHelpCommandProcessor, CliHistoryCommandProcessor, CliHotKeysCommandProcessor, CliHtopCommandProcessor, CliHttpClient, CliInputReader, CliJsonCommandProcessor, CliKeyValueStore, CliKeyValueStore_TOKEN, CliKillCommandProcessor, CliLogger, CliLoremCommandProcessor, CliModuleRegistry_TOKEN, CliNanoCommandProcessor, CliNotifier, CliOpenCommandProcessor, CliPackageManagerService, CliPackageManagerService_TOKEN, CliPackagesCommandProcessor, CliPermissionRegistry, CliPingCommandProcessor, CliProcessRegistry, CliProcessRegistry_TOKEN, CliProcessorsRegistry_TOKEN, CliPsCommandProcessor, CliRandomCommandProcessor, CliScreenCommandProcessor, CliSeqCommandProcessor, CliServerCommandProcessor, CliServerConnection, CliServerManager, CliServerManager_TOKEN, CliServerMultiProxyProcessor, CliServerProxyProcessor, CliServiceContainer, CliServicesCommandProcessor, CliSleepCommandProcessor, CliSnippetCommandProcessor, CliSshCommandProcessor, CliStateStore, CliStateStoreManager, CliStateStoreManager_TOKEN, CliTerminalProgressBar, CliTerminalSpinner, CliTerminalTextAnimator, CliTerminalWriter, CliTestHarness, CliThemeCommandProcessor, CliTimeCommandProcessor, CliTimerManager, CliTimestampCommandProcessor, CliUnAliasCommandProcessor, CliUnameCommandProcessor, CliUnsetCommandProcessor, CliUptimeCommandProcessor, CliVersionCommandProcessor, CliYesCommandProcessor, CommandParser, ICliEnvironment_TOKEN, ICliPingServerService_TOKEN, JsFallbackAccelerator, LIBRARY_VERSION, OverlayAddon, ProcessExitedError, ScriptLoaderService, SyntaxHighlighterRegistry_TOKEN, appendOutputToFile, appendStderrToFile, builtinProcessors, computeDiff, contextGlobalParameter, createServerModule, defaultFontSize, describeShellClose, executeOnServer, expandEnvironmentVars, formatDiff, getAccelerator, getCliNameArt, getGreetingBasedOnTime, getTerminalOptions, groupBy, helpGlobalParameter, hotkeysInfo, initWasmAccelerator, initializeServices, initializeTerminal, isWasmAccelerated, miscProcessors, openLink, parseAssignments, reconcileArgs, renderServerResponse, renderSingleOutput, substituteVars, systemProcessors, themeInfos, themes, tryExecuteScript, versionGlobalParameter, waitForLayout, welcomeModule, writeOutputToFile, writeStderrToFile };
18921
19128
  //# sourceMappingURL=public-api.mjs.map
18922
19129
  //# sourceMappingURL=public-api.mjs.map