dotenvx-ui 0.1.0 → 0.1.1

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.
Files changed (2) hide show
  1. package/dist/cli.js +460 -123
  2. package/package.json +15 -8
package/dist/cli.js CHANGED
@@ -92,20 +92,20 @@ function scan(cwd) {
92
92
  }
93
93
 
94
94
  // src/tui/App.tsx
95
- import { useState as useState3 } from "react";
95
+ import { useState as useState4 } from "react";
96
96
  import { Box as Box7, Text as Text7, useApp, useInput as useInput6, useStdin as useStdin6 } from "ink";
97
97
  import clipboard from "clipboardy";
98
98
 
99
99
  // src/tui/FileList.tsx
100
100
  import { Box, Text, useInput, useStdin } from "ink";
101
101
  import { jsx, jsxs } from "react/jsx-runtime";
102
- function FileList({ files, selectedIndex, focused, onSelect }) {
102
+ function FileList({ files, selectedIndex, focused, interactive, onSelect }) {
103
103
  const { isRawModeSupported } = useStdin();
104
104
  useInput((_, key) => {
105
105
  if (!focused) return;
106
106
  if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
107
107
  if (key.downArrow) onSelect(Math.min(files.length - 1, selectedIndex + 1));
108
- }, { isActive: isRawModeSupported });
108
+ }, { isActive: isRawModeSupported && interactive });
109
109
  const byPkg = Map.groupBy(files, (f) => f.package);
110
110
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", width: 24, borderStyle: "single", borderRight: true, borderTop: false, borderBottom: false, borderLeft: false, children: Array.from(byPkg.entries()).map(([pkg, pkgFiles]) => /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
111
111
  /* @__PURE__ */ jsxs(Text, { bold: true, dimColor: true, children: [
@@ -132,21 +132,71 @@ function FileList({ files, selectedIndex, focused, onSelect }) {
132
132
 
133
133
  // src/tui/KeyTable.tsx
134
134
  import { Box as Box2, Text as Text2, useInput as useInput2, useStdin as useStdin2 } from "ink";
135
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
135
+
136
+ // src/tui/useTerminalRows.ts
137
+ import { useEffect, useState } from "react";
138
+ import { useStdout } from "ink";
139
+ function useTerminalRows() {
140
+ const { stdout } = useStdout();
141
+ const [rows, setRows] = useState(stdout?.rows ?? 24);
142
+ useEffect(() => {
143
+ if (!stdout) return;
144
+ const onResize = () => setRows(stdout.rows);
145
+ stdout.on("resize", onResize);
146
+ return () => {
147
+ stdout.off("resize", onResize);
148
+ };
149
+ }, [stdout]);
150
+ return rows;
151
+ }
152
+ function useTerminalCols() {
153
+ const { stdout } = useStdout();
154
+ const [cols, setCols] = useState(stdout?.columns ?? 80);
155
+ useEffect(() => {
156
+ if (!stdout) return;
157
+ const onResize = () => setCols(stdout.columns);
158
+ stdout.on("resize", onResize);
159
+ return () => {
160
+ stdout.off("resize", onResize);
161
+ };
162
+ }, [stdout]);
163
+ return cols;
164
+ }
165
+ function scrollWindow(length, selectedIndex, maxVisible) {
166
+ if (length <= maxVisible) return { start: 0, end: length, above: 0, below: 0 };
167
+ const start = Math.min(
168
+ Math.max(0, selectedIndex - Math.floor(maxVisible / 2)),
169
+ length - maxVisible
170
+ );
171
+ const end = start + maxVisible;
172
+ return { start, end, above: start, below: length - end };
173
+ }
174
+
175
+ // src/tui/KeyTable.tsx
176
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
136
177
  var SECRET_PATTERN = /secret|password|token|key|private|api_?key/i;
178
+ var MAX_INLINE = 48;
179
+ function truncate(s) {
180
+ const first = s.split("\n")[0];
181
+ return first.length > MAX_INLINE ? first.slice(0, MAX_INLINE - 1) + "\u2026" : first;
182
+ }
137
183
  function maskValue(k, revealed) {
138
- if (revealed.has(k.key)) return revealed.get(k.key);
184
+ if (revealed.has(k.key)) return truncate(revealed.get(k.key));
139
185
  if (k.encrypted) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
140
186
  if (SECRET_PATTERN.test(k.key)) return "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022";
141
- return k.value.length > 48 ? k.value.slice(0, 48) + "\u2026" : k.value;
187
+ return truncate(k.value);
142
188
  }
143
- function KeyTable({ file, keys, selectedIndex, focused, revealed, onSelect }) {
189
+ function KeyTable({ file, keys, selectedIndex, focused, interactive, revealed, onSelect, maxRows }) {
144
190
  const { isRawModeSupported } = useStdin2();
145
191
  useInput2((_, key) => {
146
192
  if (!focused) return;
147
193
  if (key.upArrow) onSelect(Math.max(0, selectedIndex - 1));
148
194
  if (key.downArrow) onSelect(Math.min(keys.length - 1, selectedIndex + 1));
149
- }, { isActive: isRawModeSupported });
195
+ }, { isActive: isRawModeSupported && interactive });
196
+ const maxVisible = Math.max(3, maxRows - 3);
197
+ const { start, end, above, below } = scrollWindow(keys.length, selectedIndex, maxVisible);
198
+ const visibleKeys = keys.slice(start, end);
199
+ const keyColWidth = Math.min(48, Math.max(16, ...keys.map((k) => k.key.length))) + 2;
150
200
  const encBadge = file.encrypted ? /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: " encrypted" }) : /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " plain" });
151
201
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, children: [
152
202
  /* @__PURE__ */ jsxs2(Box2, { paddingX: 1, children: [
@@ -163,37 +213,72 @@ function KeyTable({ file, keys, selectedIndex, focused, revealed, onSelect }) {
163
213
  "No keys found. Press ",
164
214
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: "a" }),
165
215
  " to add one."
166
- ] }) }) : keys.map((k, idx) => {
167
- const selected = idx === selectedIndex;
168
- const value = maskValue(k, revealed);
169
- const lockIcon = k.encrypted && !revealed.has(k.key) ? " \u{1F512}" : "";
170
- return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(
171
- Text2,
172
- {
173
- backgroundColor: selected && focused ? "blue" : void 0,
174
- color: selected && focused ? "white" : selected ? "cyan" : void 0,
175
- children: [
176
- k.key.padEnd(24),
177
- /* @__PURE__ */ jsx2(Text2, { dimColor: !selected, children: value }),
178
- lockIcon
179
- ]
180
- }
181
- ) }, k.key);
182
- })
216
+ ] }) }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
217
+ above > 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
218
+ "\u2191 ",
219
+ above,
220
+ " more"
221
+ ] }) }),
222
+ visibleKeys.map((k, i) => {
223
+ const idx = start + i;
224
+ const selected = idx === selectedIndex;
225
+ const value = maskValue(k, revealed);
226
+ const lockIcon = k.encrypted && !revealed.has(k.key) ? " \u{1F512}" : "";
227
+ return /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(
228
+ Text2,
229
+ {
230
+ backgroundColor: selected && focused ? "blue" : void 0,
231
+ color: selected && focused ? "white" : selected ? "cyan" : void 0,
232
+ children: [
233
+ k.key.padEnd(keyColWidth),
234
+ /* @__PURE__ */ jsx2(Text2, { dimColor: !selected, children: value }),
235
+ lockIcon
236
+ ]
237
+ }
238
+ ) }, k.key);
239
+ }),
240
+ below > 0 && /* @__PURE__ */ jsx2(Box2, { paddingX: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
241
+ "\u2193 ",
242
+ below,
243
+ " more"
244
+ ] }) })
245
+ ] })
183
246
  ] });
184
247
  }
185
248
 
186
249
  // src/tui/StatusBar.tsx
187
250
  import { Box as Box3, Text as Text3 } from "ink";
188
- import { jsx as jsx3 } from "react/jsx-runtime";
189
- var FILE_HINTS = "\u2191\u2193 navigate tab switch panel ? help q quit";
190
- var KEY_HINTS = "\u2191\u2193 navigate tab switch enter edit y copy r reveal a add D del d diff ? help q quit";
251
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
252
+ var FILE_HINTS = [
253
+ { key: "\u2191\u2193", action: "navigate" },
254
+ { key: "tab", action: "switch panel" },
255
+ { key: "?", action: "help" },
256
+ { key: "q", action: "quit" }
257
+ ];
258
+ var KEY_HINTS = [
259
+ { key: "\u2191\u2193", action: "navigate" },
260
+ { key: "tab", action: "switch" },
261
+ { key: "enter", action: "edit" },
262
+ { key: "y", action: "copy" },
263
+ { key: "r", action: "reveal" },
264
+ { key: "a", action: "add" },
265
+ { key: "D", action: "delete" },
266
+ { key: "d", action: "diff" },
267
+ { key: "?", action: "help" },
268
+ { key: "q", action: "quit" }
269
+ ];
270
+ function Hints({ hints }) {
271
+ return /* @__PURE__ */ jsx3(Box3, { gap: 2, children: hints.map(({ key, action }) => /* @__PURE__ */ jsxs3(Box3, { gap: 1, children: [
272
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: "cyan", children: key }),
273
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: action })
274
+ ] }, key)) });
275
+ }
191
276
  function StatusBar({ message, focus }) {
192
- return /* @__PURE__ */ jsx3(Box3, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: message ?? (focus === "files" ? FILE_HINTS : KEY_HINTS) }) });
277
+ return /* @__PURE__ */ jsx3(Box3, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: message ? /* @__PURE__ */ jsx3(Text3, { children: message }) : /* @__PURE__ */ jsx3(Hints, { hints: focus === "files" ? FILE_HINTS : KEY_HINTS }) });
193
278
  }
194
279
 
195
280
  // src/tui/DiffView.tsx
196
- import { useState } from "react";
281
+ import { useEffect as useEffect2, useMemo, useRef, useState as useState2 } from "react";
197
282
  import { Box as Box4, Text as Text4, useInput as useInput3, useStdin as useStdin3 } from "ink";
198
283
 
199
284
  // src/core/parser/io.ts
@@ -406,16 +491,48 @@ function decryptValue(encryptedValue, envFilePath) {
406
491
  const keyName = findKeyForValue(encryptedValue, envFilePath);
407
492
  if (!keyName) return null;
408
493
  const keysFile = findKeysFile(envFilePath);
409
- try {
494
+ return silenced(() => {
410
495
  const result = dotenvx.get(keyName, {
411
496
  path: envFilePath,
412
497
  ...keysFile ? { envKeysFile: keysFile } : {},
413
498
  logLevel: "error"
414
499
  });
415
500
  return result ?? null;
501
+ });
502
+ }
503
+ function decryptAllValues(envFilePath) {
504
+ let raw;
505
+ try {
506
+ raw = readFileSync3(envFilePath, "utf8");
416
507
  } catch {
417
- return null;
508
+ return {};
418
509
  }
510
+ let privateKey = process.env["DOTENV_PRIVATE_KEY"] ?? null;
511
+ if (!privateKey) {
512
+ const keysFile = findKeysFile(envFilePath);
513
+ if (keysFile) {
514
+ try {
515
+ const keypairs = dotenvx.keypair(envFilePath, void 0, keysFile);
516
+ for (const [name, value] of Object.entries(keypairs)) {
517
+ if (name.startsWith("DOTENV_PRIVATE_KEY") && value) {
518
+ privateKey = value;
519
+ break;
520
+ }
521
+ }
522
+ } catch {
523
+ }
524
+ }
525
+ }
526
+ return silenced(() => {
527
+ try {
528
+ return dotenvx.parse(raw, {
529
+ ...privateKey ? { privateKey } : {},
530
+ processEnv: {}
531
+ });
532
+ } catch {
533
+ return {};
534
+ }
535
+ });
419
536
  }
420
537
  var DOTENVX_INTERNAL_KEYS = /* @__PURE__ */ new Set(["DOTENV_PUBLIC_KEY", "DOTENV_PRIVATE_KEY"]);
421
538
  function encryptFile(envFilePath) {
@@ -439,13 +556,31 @@ function encryptKey(envFilePath, keyName, plainValue) {
439
556
  }
440
557
  function decryptFile(envFilePath) {
441
558
  const keys = readEnvFile(envFilePath);
559
+ const decrypted = decryptAllValues(envFilePath);
442
560
  for (const k of keys) {
443
561
  if (isEncryptedValue(k.value)) {
444
- const plain = decryptValue(k.value, envFilePath);
445
- if (plain !== null) updateKey(envFilePath, k.key, plain);
562
+ const plain = decrypted[k.key];
563
+ if (plain !== void 0 && !isEncryptedValue(plain)) updateKey(envFilePath, k.key, plain);
446
564
  }
447
565
  }
448
566
  }
567
+ function silenced(fn) {
568
+ const origWrite = process.stderr.write.bind(process.stderr);
569
+ const origOut = process.stdout.write.bind(process.stdout);
570
+ const mute = (chunk) => {
571
+ const s = String(chunk);
572
+ if (s.includes("[MISSING_PRIVATE_KEY]") || s.includes("could not decrypt") || s.includes("\u2620")) return true;
573
+ return false;
574
+ };
575
+ process.stderr.write = ((chunk, ...args) => mute(chunk) ? true : origWrite(chunk, ...args));
576
+ process.stdout.write = ((chunk, ...args) => mute(chunk) ? true : origOut(chunk, ...args));
577
+ try {
578
+ return fn();
579
+ } finally {
580
+ process.stderr.write = origWrite;
581
+ process.stdout.write = origOut;
582
+ }
583
+ }
449
584
  function findKeysFile(envFilePath) {
450
585
  let dir = dirname3(envFilePath);
451
586
  while (true) {
@@ -474,14 +609,16 @@ function findKeyForValue(encryptedValue, envFilePath) {
474
609
  }
475
610
 
476
611
  // src/tui/DiffView.tsx
477
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
612
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
478
613
  var COL_VAL = 26;
614
+ var PICKER_MAX = 5;
479
615
  function buildDisplayMap(keys, filePath) {
616
+ const decrypted = keys.some((k) => k.encrypted) ? decryptAllValues(filePath) : {};
480
617
  const out = /* @__PURE__ */ new Map();
481
618
  for (const k of keys) {
482
619
  if (k.encrypted) {
483
- const plain = decryptValue(k.value, filePath);
484
- out.set(k.key, plain !== null ? plain : "\u{1F512}");
620
+ const plain = decrypted[k.key];
621
+ out.set(k.key, plain !== void 0 && !isEncryptedValue(plain) ? plain : "\u{1F512}");
485
622
  } else {
486
623
  out.set(k.key, k.value);
487
624
  }
@@ -519,35 +656,81 @@ function safeRead(file) {
519
656
  }
520
657
  function DiffView({ left, files, onClose }) {
521
658
  const { isRawModeSupported } = useStdin3();
659
+ const termRows = useTerminalRows();
522
660
  const others = files.filter((f) => f.path !== left.path);
523
- const [pickerIndex, setPickerIndex] = useState(0);
661
+ const [pickerIndex, setPickerIndex] = useState2(0);
662
+ const [rowScroll, setRowScroll] = useState2(0);
524
663
  const rightFile = others[pickerIndex] ?? null;
525
- const leftKeys = safeRead(left);
526
- const rightKeys = rightFile ? safeRead(rightFile) : [];
527
- const leftMap = buildDisplayMap(leftKeys, left.path);
528
- const rightMap = rightFile ? buildDisplayMap(rightKeys, rightFile.path) : /* @__PURE__ */ new Map();
529
- const rows = rightFile ? buildRows(leftMap, rightMap) : [];
664
+ const mapsCache = useRef(/* @__PURE__ */ new Map());
665
+ const [cacheVersion, setCacheVersion] = useState2(0);
666
+ function getMap(file) {
667
+ return mapsCache.current.get(file.path) ?? null;
668
+ }
669
+ function buildAndCache(file) {
670
+ if (!mapsCache.current.has(file.path)) {
671
+ mapsCache.current.set(file.path, buildDisplayMap(safeRead(file), file.path));
672
+ setCacheVersion((v) => v + 1);
673
+ }
674
+ }
675
+ useEffect2(() => {
676
+ buildAndCache(left);
677
+ if (others[0]) buildAndCache(others[0]);
678
+ }, []);
679
+ useEffect2(() => {
680
+ const next = others[pickerIndex + 1];
681
+ const prev = others[pickerIndex - 1];
682
+ if (next) buildAndCache(next);
683
+ if (prev) buildAndCache(prev);
684
+ }, [pickerIndex]);
685
+ const rows = useMemo(() => {
686
+ if (!rightFile) return [];
687
+ const leftMap = getMap(left);
688
+ const rightMap = getMap(rightFile);
689
+ if (!leftMap || !rightMap) return [];
690
+ return buildRows(leftMap, rightMap);
691
+ }, [left.path, rightFile?.path, cacheVersion]);
692
+ const pickerVisible = Math.min(others.length, PICKER_MAX);
693
+ const picker = scrollWindow(others.length, pickerIndex, PICKER_MAX);
694
+ const chrome = 9 + pickerVisible + (picker.above > 0 || picker.below > 0 ? 2 : 0);
695
+ const maxRows = Math.max(3, termRows - chrome);
696
+ const maxScroll = Math.max(0, rows.length - maxRows);
697
+ const scroll = Math.min(rowScroll, maxScroll);
698
+ const visibleRows = rows.slice(scroll, scroll + maxRows);
699
+ const rowsAbove = scroll;
700
+ const rowsBelow = rows.length - (scroll + visibleRows.length);
530
701
  useInput3((input, key) => {
531
702
  if (key.escape || input === "q") {
532
703
  onClose();
533
704
  return;
534
705
  }
535
- if (key.upArrow) setPickerIndex((i) => Math.max(0, i - 1));
536
- if (key.downArrow) setPickerIndex((i) => Math.min(others.length - 1, i + 1));
706
+ if (key.upArrow) {
707
+ setPickerIndex((i) => Math.max(0, i - 1));
708
+ setRowScroll(0);
709
+ return;
710
+ }
711
+ if (key.downArrow) {
712
+ setPickerIndex((i) => Math.min(others.length - 1, i + 1));
713
+ setRowScroll(0);
714
+ return;
715
+ }
716
+ if (input === "k") setRowScroll(Math.max(0, scroll - 1));
717
+ if (input === "j") setRowScroll(Math.min(maxScroll, scroll + 1));
718
+ if (key.pageUp) setRowScroll(Math.max(0, scroll - maxRows));
719
+ if (key.pageDown) setRowScroll(Math.min(maxScroll, scroll + maxRows));
537
720
  }, { isActive: isRawModeSupported });
538
721
  const leftName = trunc(left.relativePath, COL_VAL);
539
722
  const rightName = rightFile ? trunc(rightFile.relativePath, COL_VAL) : "\u2014";
540
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
541
- /* @__PURE__ */ jsxs3(Box4, { paddingX: 1, children: [
723
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
724
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
542
725
  /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "dotenvx-ui " }),
543
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
726
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
544
727
  "diff ",
545
728
  left.relativePath,
546
729
  " \u2194 ",
547
730
  rightFile?.relativePath ?? "\u2014"
548
731
  ] })
549
732
  ] }),
550
- /* @__PURE__ */ jsxs3(
733
+ /* @__PURE__ */ jsxs4(
551
734
  Box4,
552
735
  {
553
736
  flexDirection: "column",
@@ -559,9 +742,14 @@ function DiffView({ left, files, onClose }) {
559
742
  borderRight: false,
560
743
  children: [
561
744
  /* @__PURE__ */ jsx4(Text4, { bold: true, dimColor: true, children: "compare with" }),
562
- others.map((f, i) => {
563
- const selected = i === pickerIndex;
564
- return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs3(
745
+ picker.above > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
746
+ " \u2191 ",
747
+ picker.above,
748
+ " more"
749
+ ] }),
750
+ others.slice(picker.start, picker.end).map((f, i) => {
751
+ const selected = picker.start + i === pickerIndex;
752
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(
565
753
  Text4,
566
754
  {
567
755
  backgroundColor: selected ? "blue" : void 0,
@@ -573,24 +761,39 @@ function DiffView({ left, files, onClose }) {
573
761
  }
574
762
  ) }, f.path);
575
763
  }),
764
+ picker.below > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
765
+ " \u2193 ",
766
+ picker.below,
767
+ " more"
768
+ ] }),
576
769
  others.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " no other files" })
577
770
  ]
578
771
  }
579
772
  ),
580
- /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
581
- /* @__PURE__ */ jsxs3(Box4, { borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, children: [
773
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
774
+ /* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, children: [
582
775
  /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "KEY".padEnd(22) }),
583
- /* @__PURE__ */ jsxs3(Text4, { bold: true, children: [
776
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
584
777
  " ",
585
778
  leftName.padEnd(COL_VAL + 2)
586
779
  ] }),
587
780
  /* @__PURE__ */ jsx4(Text4, { bold: true, children: rightName })
588
781
  ] }),
589
- rows.map((row) => /* @__PURE__ */ jsx4(DiffRow, { row }, row.key)),
782
+ rowsAbove > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
783
+ "\u2191 ",
784
+ rowsAbove,
785
+ " more"
786
+ ] }),
787
+ visibleRows.map((row) => /* @__PURE__ */ jsx4(DiffRow, { row }, row.key)),
788
+ rowsBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
789
+ "\u2193 ",
790
+ rowsBelow,
791
+ " more"
792
+ ] }),
590
793
  rows.length === 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Select a file to compare." }) })
591
794
  ] }),
592
- /* @__PURE__ */ jsxs3(Box4, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: [
593
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file esc close " }),
795
+ /* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: [
796
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file j/k scroll esc close " }),
594
797
  /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u25CF same" })
595
798
  ] })
596
799
  ] });
@@ -598,9 +801,9 @@ function DiffView({ left, files, onClose }) {
598
801
  function DiffRow({ row }) {
599
802
  const { key, leftDisplay, rightDisplay, status } = row;
600
803
  const color = status === "same" ? "green" : void 0;
601
- return /* @__PURE__ */ jsxs3(Box4, { children: [
804
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
602
805
  /* @__PURE__ */ jsx4(Text4, { color, children: key.padEnd(22) }),
603
- /* @__PURE__ */ jsxs3(Text4, { color, children: [
806
+ /* @__PURE__ */ jsxs4(Text4, { color, children: [
604
807
  " ",
605
808
  (leftDisplay || "\u2014").padEnd(COL_VAL + 2)
606
809
  ] }),
@@ -610,7 +813,7 @@ function DiffRow({ row }) {
610
813
 
611
814
  // src/tui/HelpOverlay.tsx
612
815
  import { Box as Box5, Text as Text5, useInput as useInput4, useStdin as useStdin4 } from "ink";
613
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
816
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
614
817
  var SECTIONS = [
615
818
  {
616
819
  title: "Navigation",
@@ -652,14 +855,14 @@ function HelpOverlay({ onClose }) {
652
855
  useInput4((input, key) => {
653
856
  if (input === "?" || input === "q" || key.escape) onClose();
654
857
  }, { isActive: isRawModeSupported });
655
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
656
- /* @__PURE__ */ jsxs4(Box5, { marginBottom: 1, children: [
858
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
859
+ /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
657
860
  /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "dotenvx-ui " }),
658
861
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "keyboard shortcuts" })
659
862
  ] }),
660
- SECTIONS.map((section) => /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
863
+ SECTIONS.map((section) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
661
864
  /* @__PURE__ */ jsx5(Text5, { bold: true, children: section.title }),
662
- section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs4(Box5, { children: [
865
+ section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs5(Box5, { children: [
663
866
  /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: key.padEnd(KEY_WIDTH) }),
664
867
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: desc })
665
868
  ] }, key))
@@ -669,89 +872,177 @@ function HelpOverlay({ onClose }) {
669
872
  }
670
873
 
671
874
  // src/tui/InlineForm.tsx
672
- import { useState as useState2 } from "react";
673
- import { Box as Box6, Text as Text6, useInput as useInput5, useStdin as useStdin5 } from "ink";
674
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
875
+ import { useState as useState3, useRef as useRef2 } from "react";
876
+ import { Box as Box6, Text as Text6, useInput as useInput5, useStdin as useStdin5, useStdout as useStdout2 } from "ink";
877
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
878
+ function makeInitialState(value) {
879
+ const lines = value.split("\n");
880
+ return {
881
+ lines,
882
+ row: lines.length - 1,
883
+ col: lines[lines.length - 1].length
884
+ };
885
+ }
886
+ var CHROME = 5;
675
887
  function InlineForm({ label, initialValue = "", onSubmit, onCancel }) {
676
888
  const { isRawModeSupported } = useStdin5();
677
- const [value, setValue] = useState2(initialValue);
678
- const [cursor, setCursor] = useState2(initialValue.length);
889
+ const { stdout } = useStdout2();
890
+ const [editor, setEditor] = useState3(() => makeInitialState(initialValue));
891
+ const editorRef = useRef2(editor);
892
+ editorRef.current = editor;
679
893
  useInput5((input, key) => {
680
894
  if (key.escape) {
681
895
  onCancel();
682
896
  return;
683
897
  }
898
+ const { lines: lines2, row: row2, col: col2 } = editorRef.current;
899
+ const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
684
900
  if (key.return) {
685
- onSubmit(value);
901
+ onSubmit(lines2.join("\n"));
902
+ return;
903
+ }
904
+ if (key.upArrow) {
905
+ const line = lines2[row2];
906
+ const visualRow = Math.floor(col2 / lineWidth);
907
+ if (visualRow > 0) {
908
+ const targetVisualRow = visualRow - 1;
909
+ const colInVisualRow = col2 % lineWidth;
910
+ const newCol = Math.min(targetVisualRow * lineWidth + colInVisualRow, line.length);
911
+ setEditor({ lines: lines2, row: row2, col: newCol });
912
+ } else if (row2 > 0) {
913
+ const prevLine = lines2[row2 - 1];
914
+ const prevVisualRows = Math.floor(prevLine.length / lineWidth);
915
+ const colInVisualRow = col2 % lineWidth;
916
+ const newCol = Math.min(prevVisualRows * lineWidth + colInVisualRow, prevLine.length);
917
+ setEditor({ lines: lines2, row: row2 - 1, col: newCol });
918
+ }
919
+ return;
920
+ }
921
+ if (key.downArrow) {
922
+ const line = lines2[row2];
923
+ const visualRow = Math.floor(col2 / lineWidth);
924
+ const lastVisualRow = Math.floor(line.length / lineWidth);
925
+ if (visualRow < lastVisualRow) {
926
+ const colInVisualRow = col2 % lineWidth;
927
+ const newCol = Math.min((visualRow + 1) * lineWidth + colInVisualRow, line.length);
928
+ setEditor({ lines: lines2, row: row2, col: newCol });
929
+ } else if (row2 < lines2.length - 1) {
930
+ const colInVisualRow = col2 % lineWidth;
931
+ const newCol = Math.min(colInVisualRow, lines2[row2 + 1].length);
932
+ setEditor({ lines: lines2, row: row2 + 1, col: newCol });
933
+ }
686
934
  return;
687
935
  }
688
936
  if (key.leftArrow) {
689
- setCursor((c) => Math.max(0, c - 1));
937
+ if (col2 > 0) {
938
+ setEditor({ lines: lines2, row: row2, col: col2 - 1 });
939
+ } else if (row2 > 0) {
940
+ const newRow = row2 - 1;
941
+ setEditor({ lines: lines2, row: newRow, col: lines2[newRow].length });
942
+ }
690
943
  return;
691
944
  }
692
945
  if (key.rightArrow) {
693
- setCursor((c) => Math.min(value.length, c + 1));
946
+ if (col2 < lines2[row2].length) {
947
+ setEditor({ lines: lines2, row: row2, col: col2 + 1 });
948
+ } else if (row2 < lines2.length - 1) {
949
+ setEditor({ lines: lines2, row: row2 + 1, col: 0 });
950
+ }
694
951
  return;
695
952
  }
696
953
  if (key.ctrl && input === "a" || key.home) {
697
- setCursor(0);
954
+ setEditor({ lines: lines2, row: row2, col: 0 });
698
955
  return;
699
956
  }
700
957
  if (key.ctrl && input === "e" || key.end) {
701
- setCursor(value.length);
958
+ setEditor({ lines: lines2, row: row2, col: lines2[row2].length });
702
959
  return;
703
960
  }
704
961
  if (key.ctrl && input === "k") {
705
- setValue((v) => v.slice(0, cursor));
962
+ const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
963
+ setEditor({ lines: newLines, row: row2, col: col2 });
706
964
  return;
707
965
  }
708
966
  if (key.ctrl && input === "u") {
709
- setValue((v) => v.slice(cursor));
710
- setCursor(0);
967
+ const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
968
+ setEditor({ lines: newLines, row: row2, col: 0 });
711
969
  return;
712
970
  }
713
971
  if (key.backspace) {
714
- if (cursor === 0) return;
715
- setValue((v) => v.slice(0, cursor - 1) + v.slice(cursor));
716
- setCursor((c) => c - 1);
972
+ if (col2 > 0) {
973
+ const cur = lines2[row2];
974
+ const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2 - 1) + cur.slice(col2) : l);
975
+ setEditor({ lines: newLines, row: row2, col: col2 - 1 });
976
+ } else if (row2 > 0) {
977
+ const prevLen = lines2[row2 - 1].length;
978
+ const merged = lines2[row2 - 1] + lines2[row2];
979
+ const newLines = [...lines2.slice(0, row2 - 1), merged, ...lines2.slice(row2 + 1)];
980
+ setEditor({ lines: newLines, row: row2 - 1, col: prevLen });
981
+ }
717
982
  return;
718
983
  }
719
984
  if (key.delete) {
720
- setValue((v) => v.slice(0, cursor) + v.slice(cursor + 1));
985
+ const cur = lines2[row2];
986
+ if (col2 < cur.length) {
987
+ const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + cur.slice(col2 + 1) : l);
988
+ setEditor({ lines: newLines, row: row2, col: col2 });
989
+ } else if (row2 < lines2.length - 1) {
990
+ const merged = cur + lines2[row2 + 1];
991
+ const newLines = [...lines2.slice(0, row2), merged, ...lines2.slice(row2 + 2)];
992
+ setEditor({ lines: newLines, row: row2, col: col2 });
993
+ }
721
994
  return;
722
995
  }
723
996
  if (input && !key.ctrl && !key.meta) {
724
- setValue((v) => v.slice(0, cursor) + input + v.slice(cursor));
725
- setCursor((c) => c + input.length);
997
+ const cur = lines2[row2];
998
+ const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + input + cur.slice(col2) : l);
999
+ setEditor({ lines: newLines, row: row2, col: col2 + input.length });
726
1000
  }
727
1001
  }, { isActive: isRawModeSupported });
728
- const before = value.slice(0, cursor);
729
- const at = value[cursor] ?? " ";
730
- const after = value.slice(cursor + 1);
731
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, paddingTop: 0, children: /* @__PURE__ */ jsxs5(Box6, { children: [
732
- /* @__PURE__ */ jsxs5(Text6, { bold: true, color: "cyan", children: [
733
- label,
734
- " "
1002
+ const { lines, row, col } = editor;
1003
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, paddingTop: 0, children: [
1004
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1005
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "cyan", children: [
1006
+ label,
1007
+ " "
1008
+ ] }),
1009
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u21B5 confirm esc cancel" })
735
1010
  ] }),
736
- /* @__PURE__ */ jsx6(Text6, { children: before }),
737
- /* @__PURE__ */ jsx6(Text6, { inverse: true, children: at }),
738
- /* @__PURE__ */ jsx6(Text6, { children: after }),
739
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " \u21B5 confirm esc cancel" })
740
- ] }) });
1011
+ lines.map((line, r) => {
1012
+ const isActive = r === row;
1013
+ if (!isActive) {
1014
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1015
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
1016
+ /* @__PURE__ */ jsx6(Text6, { children: line || " " })
1017
+ ] }, r);
1018
+ }
1019
+ const before = line.slice(0, col);
1020
+ const cursor = line[col] ?? " ";
1021
+ const after = line.slice(col + 1);
1022
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1023
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
1024
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1025
+ before,
1026
+ /* @__PURE__ */ jsx6(Text6, { inverse: true, children: cursor }),
1027
+ after
1028
+ ] })
1029
+ ] }, r);
1030
+ })
1031
+ ] });
741
1032
  }
742
1033
 
743
1034
  // src/tui/App.tsx
744
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1035
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
745
1036
  function App({ files }) {
746
1037
  const { exit } = useApp();
747
1038
  const { isRawModeSupported } = useStdin6();
748
- const [fileIndex, setFileIndex] = useState3(0);
749
- const [keyIndex, setKeyIndex] = useState3(0);
750
- const [focus, setFocus] = useState3("files");
751
- const [revealed, setRevealed] = useState3(/* @__PURE__ */ new Map());
752
- const [mode, setMode] = useState3({ type: "normal" });
753
- const [statusMsg, setStatusMsg] = useState3();
754
- const [keys, setKeys] = useState3(() => loadKeys(files[0]));
1039
+ const [fileIndex, setFileIndex] = useState4(0);
1040
+ const [keyIndex, setKeyIndex] = useState4(0);
1041
+ const [focus, setFocus] = useState4("files");
1042
+ const [revealed, setRevealed] = useState4(/* @__PURE__ */ new Map());
1043
+ const [mode, setMode] = useState4({ type: "normal" });
1044
+ const [statusMsg, setStatusMsg] = useState4();
1045
+ const [keys, setKeys] = useState4(() => loadKeys(files[0]));
755
1046
  const selectedFile = files[fileIndex];
756
1047
  function loadKeys(file) {
757
1048
  try {
@@ -774,7 +1065,6 @@ function App({ files }) {
774
1065
  setTimeout(() => setStatusMsg(void 0), ms);
775
1066
  }
776
1067
  useInput6((input, key) => {
777
- if (mode.type !== "normal") return;
778
1068
  if (input === "q" || key.escape) {
779
1069
  exit();
780
1070
  return;
@@ -816,11 +1106,12 @@ function App({ files }) {
816
1106
  setRevealed(/* @__PURE__ */ new Map());
817
1107
  return;
818
1108
  }
1109
+ const decrypted = keys.some((e) => e.encrypted) ? decryptAllValues(selectedFile.path) : {};
819
1110
  const next = /* @__PURE__ */ new Map();
820
1111
  for (const entry of keys) {
821
1112
  if (entry.encrypted) {
822
- const plain = decryptValue(entry.value, selectedFile.path);
823
- if (plain === null) {
1113
+ const plain = decrypted[entry.key];
1114
+ if (plain === void 0 || isEncryptedValue(plain)) {
824
1115
  flash("\u{1F512} Private key not found \u2014 cannot reveal all");
825
1116
  return;
826
1117
  }
@@ -890,6 +1181,7 @@ function App({ files }) {
890
1181
  onSelectKey: setKeyIndex,
891
1182
  statusMsg,
892
1183
  focus2: focus,
1184
+ interactive: false,
893
1185
  extra: /* @__PURE__ */ jsx7(
894
1186
  InlineForm,
895
1187
  {
@@ -930,6 +1222,7 @@ function App({ files }) {
930
1222
  onSelectKey: setKeyIndex,
931
1223
  statusMsg,
932
1224
  focus2: focus,
1225
+ interactive: false,
933
1226
  extra: /* @__PURE__ */ jsx7(
934
1227
  InlineForm,
935
1228
  {
@@ -963,6 +1256,7 @@ function App({ files }) {
963
1256
  onSelectKey: setKeyIndex,
964
1257
  statusMsg,
965
1258
  focus2: focus,
1259
+ interactive: false,
966
1260
  extra: /* @__PURE__ */ jsx7(
967
1261
  InlineForm,
968
1262
  {
@@ -1008,6 +1302,7 @@ function App({ files }) {
1008
1302
  onSelectKey: setKeyIndex,
1009
1303
  statusMsg,
1010
1304
  focus2: focus,
1305
+ interactive: false,
1011
1306
  extra: /* @__PURE__ */ jsx7(ConfirmAddEncrypt, { keyName, onEncrypt: () => commit(true), onPlain: () => commit(false), onCancel: () => setMode({ type: "normal" }) })
1012
1307
  }
1013
1308
  );
@@ -1027,6 +1322,7 @@ function App({ files }) {
1027
1322
  onSelectKey: setKeyIndex,
1028
1323
  statusMsg,
1029
1324
  focus2: focus,
1325
+ interactive: false,
1030
1326
  extra: /* @__PURE__ */ jsx7(
1031
1327
  ConfirmDelete,
1032
1328
  {
@@ -1059,6 +1355,7 @@ function App({ files }) {
1059
1355
  onSelectKey: setKeyIndex,
1060
1356
  statusMsg,
1061
1357
  focus2: focus,
1358
+ interactive: false,
1062
1359
  extra: /* @__PURE__ */ jsx7(
1063
1360
  ConfirmEncrypt,
1064
1361
  {
@@ -1111,7 +1408,8 @@ function App({ files }) {
1111
1408
  onSelectFile: selectFile,
1112
1409
  onSelectKey: setKeyIndex,
1113
1410
  statusMsg,
1114
- focus2: focus
1411
+ focus2: focus,
1412
+ interactive: true
1115
1413
  }
1116
1414
  );
1117
1415
  }
@@ -1121,6 +1419,7 @@ function Layout({
1121
1419
  keys,
1122
1420
  keyIndex,
1123
1421
  focus,
1422
+ interactive,
1124
1423
  revealed,
1125
1424
  onSelectFile,
1126
1425
  onSelectKey,
@@ -1129,10 +1428,13 @@ function Layout({
1129
1428
  }) {
1130
1429
  const selectedFile = files[fileIndex];
1131
1430
  const encCount = files.filter((f) => f.encrypted).length;
1132
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", height: "100%", children: [
1133
- /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1431
+ const termRows = useTerminalRows();
1432
+ const termCols = useTerminalCols();
1433
+ const listRows = Math.max(3, termRows - (extra ? 3 : 5));
1434
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: termRows, children: [
1435
+ /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1134
1436
  /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "dotenvx-ui" }),
1135
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
1437
+ /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1136
1438
  " ",
1137
1439
  selectedFile.relativePath,
1138
1440
  " \xB7 ",
@@ -1142,8 +1444,8 @@ function Layout({
1142
1444
  " enc"
1143
1445
  ] })
1144
1446
  ] }),
1145
- /* @__PURE__ */ jsxs6(Box7, { flexGrow: 1, children: [
1146
- /* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", onSelect: onSelectFile }),
1447
+ /* @__PURE__ */ jsxs7(Box7, { height: listRows, children: [
1448
+ /* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", interactive, onSelect: onSelectFile }),
1147
1449
  /* @__PURE__ */ jsx7(
1148
1450
  KeyTable,
1149
1451
  {
@@ -1151,23 +1453,58 @@ function Layout({
1151
1453
  keys,
1152
1454
  selectedIndex: keyIndex,
1153
1455
  focused: focus === "keys",
1456
+ interactive,
1154
1457
  revealed,
1155
- onSelect: onSelectKey
1458
+ onSelect: onSelectKey,
1459
+ maxRows: listRows
1156
1460
  }
1157
1461
  )
1158
1462
  ] }),
1159
1463
  extra,
1464
+ !extra && /* @__PURE__ */ jsx7(ValuePreview, { keys, keyIndex, focus, revealed, width: termCols }),
1160
1465
  /* @__PURE__ */ jsx7(StatusBar, { focus, message: statusMsg })
1161
1466
  ] });
1162
1467
  }
1468
+ function ValuePreview({ keys, keyIndex, focus, revealed, width }) {
1469
+ if (focus !== "keys") return null;
1470
+ const k = keys[keyIndex];
1471
+ if (!k) return null;
1472
+ let value;
1473
+ if (k.encrypted) {
1474
+ value = revealed.has(k.key) ? revealed.get(k.key) : "\u2022\u2022\u2022\u2022 (press r to reveal)";
1475
+ } else {
1476
+ value = revealed.has(k.key) ? revealed.get(k.key) : k.value;
1477
+ }
1478
+ const flat = value.replace(/\n/g, "\u21B5 ");
1479
+ return /* @__PURE__ */ jsxs7(
1480
+ Box7,
1481
+ {
1482
+ borderStyle: "single",
1483
+ borderTop: true,
1484
+ borderBottom: false,
1485
+ borderLeft: false,
1486
+ borderRight: false,
1487
+ paddingX: 1,
1488
+ width,
1489
+ children: [
1490
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
1491
+ k.key,
1492
+ " "
1493
+ ] }),
1494
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7 " }),
1495
+ /* @__PURE__ */ jsx7(Text7, { truncate: true, children: flat })
1496
+ ]
1497
+ }
1498
+ );
1499
+ }
1163
1500
  function ConfirmDelete({ keyName, onConfirm, onCancel }) {
1164
1501
  const { isRawModeSupported } = useStdin6();
1165
1502
  useInput6((input) => {
1166
1503
  if (input === "y" || input === "Y") onConfirm();
1167
1504
  else onCancel();
1168
1505
  }, { isActive: isRawModeSupported });
1169
- return /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1170
- /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
1506
+ return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1507
+ /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
1171
1508
  "Delete ",
1172
1509
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
1173
1510
  "? "
@@ -1183,8 +1520,8 @@ function ConfirmEncrypt({ decrypt, fileName, onConfirm, onCancel }) {
1183
1520
  }, { isActive: isRawModeSupported });
1184
1521
  const action = decrypt ? "Decrypt" : "Encrypt";
1185
1522
  const color = decrypt ? "yellow" : "green";
1186
- return /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1187
- /* @__PURE__ */ jsxs6(Text7, { color, children: [
1523
+ return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1524
+ /* @__PURE__ */ jsxs7(Text7, { color, children: [
1188
1525
  action,
1189
1526
  " ",
1190
1527
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: fileName }),
@@ -1209,8 +1546,8 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
1209
1546
  return;
1210
1547
  }
1211
1548
  }, { isActive: isRawModeSupported });
1212
- return /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1213
- /* @__PURE__ */ jsxs6(Text7, { children: [
1549
+ return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1550
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1214
1551
  "Encrypt ",
1215
1552
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
1216
1553
  "? "
@@ -1222,7 +1559,7 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
1222
1559
  // src/tui/ErrorBoundary.tsx
1223
1560
  import React4 from "react";
1224
1561
  import { Box as Box8, Text as Text8 } from "ink";
1225
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1562
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1226
1563
  var ErrorBoundary = class extends React4.Component {
1227
1564
  state = { error: null };
1228
1565
  static getDerivedStateFromError(error) {
@@ -1230,7 +1567,7 @@ var ErrorBoundary = class extends React4.Component {
1230
1567
  }
1231
1568
  render() {
1232
1569
  if (this.state.error) {
1233
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1570
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1234
1571
  /* @__PURE__ */ jsx8(Text8, { bold: true, color: "red", children: "dotenvx-ui crashed" }),
1235
1572
  /* @__PURE__ */ jsx8(Text8, { children: this.state.error.message }),
1236
1573
  /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Please report this at https://github.com/alxbrla/dotenvx-ui/issues" }) })
@@ -1287,7 +1624,7 @@ function runTUI() {
1287
1624
  console.error("No .env files found in this directory.");
1288
1625
  process.exit(1);
1289
1626
  }
1290
- render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }));
1627
+ render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }), { alternateScreen: true });
1291
1628
  }
1292
1629
  function runWebUI() {
1293
1630
  console.log("Web UI \u2014 coming soon");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dotenvx-ui",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Terminal and web UI for managing dotenvx environment files",
5
5
  "author": "Alexandru Burla <alexandru.burla@gmail.com>",
6
6
  "license": "MIT",
@@ -18,6 +18,13 @@
18
18
  "files": [
19
19
  "dist"
20
20
  ],
21
+ "scripts": {
22
+ "build": "tsup",
23
+ "dev": "tsup --watch",
24
+ "typecheck": "tsc --noEmit",
25
+ "test": "pnpm build && tsc --noEmit && node --import tsx/esm --test 'src/test/**/*.test.ts'",
26
+ "prepublishOnly": "pnpm build"
27
+ },
21
28
  "keywords": [
22
29
  "dotenvx",
23
30
  "env",
@@ -25,6 +32,7 @@
25
32
  "cli",
26
33
  "dotenv"
27
34
  ],
35
+ "packageManager": "pnpm@10.30.0",
28
36
  "engines": {
29
37
  "node": ">=22"
30
38
  },
@@ -35,6 +43,11 @@
35
43
  "ink": "^7.0.5",
36
44
  "react": "^19.2.7"
37
45
  },
46
+ "pnpm": {
47
+ "onlyBuiltDependencies": [
48
+ "esbuild"
49
+ ]
50
+ },
38
51
  "devDependencies": {
39
52
  "@types/node": "^25.9.3",
40
53
  "@types/react": "^19.2.17",
@@ -42,11 +55,5 @@
42
55
  "tsup": "^8.5.1",
43
56
  "tsx": "^4.22.4",
44
57
  "typescript": "^6.0.3"
45
- },
46
- "scripts": {
47
- "build": "tsup",
48
- "dev": "tsup --watch",
49
- "typecheck": "tsc --noEmit",
50
- "test": "pnpm build && tsc --noEmit && node --import tsx/esm --test 'src/test/**/*.test.ts'"
51
58
  }
52
- }
59
+ }