dotenvx-ui 0.1.0 → 0.1.2

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 +464 -126
  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,82 @@ 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 });
721
+ const keyColWidth = Math.min(48, Math.max(16, ...rows.map((r) => r.key.length))) + 2;
538
722
  const leftName = trunc(left.relativePath, COL_VAL);
539
723
  const rightName = rightFile ? trunc(rightFile.relativePath, COL_VAL) : "\u2014";
540
- return /* @__PURE__ */ jsxs3(Box4, { flexDirection: "column", children: [
541
- /* @__PURE__ */ jsxs3(Box4, { paddingX: 1, children: [
724
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
725
+ /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, children: [
542
726
  /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "dotenvx-ui " }),
543
- /* @__PURE__ */ jsxs3(Text4, { dimColor: true, children: [
727
+ /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
544
728
  "diff ",
545
729
  left.relativePath,
546
730
  " \u2194 ",
547
731
  rightFile?.relativePath ?? "\u2014"
548
732
  ] })
549
733
  ] }),
550
- /* @__PURE__ */ jsxs3(
734
+ /* @__PURE__ */ jsxs4(
551
735
  Box4,
552
736
  {
553
737
  flexDirection: "column",
@@ -559,9 +743,14 @@ function DiffView({ left, files, onClose }) {
559
743
  borderRight: false,
560
744
  children: [
561
745
  /* @__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(
746
+ picker.above > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
747
+ " \u2191 ",
748
+ picker.above,
749
+ " more"
750
+ ] }),
751
+ others.slice(picker.start, picker.end).map((f, i) => {
752
+ const selected = picker.start + i === pickerIndex;
753
+ return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(
565
754
  Text4,
566
755
  {
567
756
  backgroundColor: selected ? "blue" : void 0,
@@ -573,34 +762,49 @@ function DiffView({ left, files, onClose }) {
573
762
  }
574
763
  ) }, f.path);
575
764
  }),
765
+ picker.below > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
766
+ " \u2193 ",
767
+ picker.below,
768
+ " more"
769
+ ] }),
576
770
  others.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " no other files" })
577
771
  ]
578
772
  }
579
773
  ),
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: [
582
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "KEY".padEnd(22) }),
583
- /* @__PURE__ */ jsxs3(Text4, { bold: true, children: [
774
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
775
+ /* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderBottom: true, borderTop: false, borderLeft: false, borderRight: false, children: [
776
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "KEY".padEnd(keyColWidth) }),
777
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, children: [
584
778
  " ",
585
779
  leftName.padEnd(COL_VAL + 2)
586
780
  ] }),
587
781
  /* @__PURE__ */ jsx4(Text4, { bold: true, children: rightName })
588
782
  ] }),
589
- rows.map((row) => /* @__PURE__ */ jsx4(DiffRow, { row }, row.key)),
783
+ rowsAbove > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
784
+ "\u2191 ",
785
+ rowsAbove,
786
+ " more"
787
+ ] }),
788
+ visibleRows.map((row) => /* @__PURE__ */ jsx4(DiffRow, { row, keyColWidth }, row.key)),
789
+ rowsBelow > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
790
+ "\u2193 ",
791
+ rowsBelow,
792
+ " more"
793
+ ] }),
590
794
  rows.length === 0 && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Select a file to compare." }) })
591
795
  ] }),
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 " }),
796
+ /* @__PURE__ */ jsxs4(Box4, { borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, children: [
797
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 pick file j/k scroll esc close " }),
594
798
  /* @__PURE__ */ jsx4(Text4, { color: "green", children: "\u25CF same" })
595
799
  ] })
596
800
  ] });
597
801
  }
598
- function DiffRow({ row }) {
802
+ function DiffRow({ row, keyColWidth }) {
599
803
  const { key, leftDisplay, rightDisplay, status } = row;
600
804
  const color = status === "same" ? "green" : void 0;
601
- return /* @__PURE__ */ jsxs3(Box4, { children: [
602
- /* @__PURE__ */ jsx4(Text4, { color, children: key.padEnd(22) }),
603
- /* @__PURE__ */ jsxs3(Text4, { color, children: [
805
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
806
+ /* @__PURE__ */ jsx4(Text4, { color, children: key.padEnd(keyColWidth) }),
807
+ /* @__PURE__ */ jsxs4(Text4, { color, children: [
604
808
  " ",
605
809
  (leftDisplay || "\u2014").padEnd(COL_VAL + 2)
606
810
  ] }),
@@ -610,7 +814,7 @@ function DiffRow({ row }) {
610
814
 
611
815
  // src/tui/HelpOverlay.tsx
612
816
  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";
817
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
614
818
  var SECTIONS = [
615
819
  {
616
820
  title: "Navigation",
@@ -652,14 +856,14 @@ function HelpOverlay({ onClose }) {
652
856
  useInput4((input, key) => {
653
857
  if (input === "?" || input === "q" || key.escape) onClose();
654
858
  }, { isActive: isRawModeSupported });
655
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
656
- /* @__PURE__ */ jsxs4(Box5, { marginBottom: 1, children: [
859
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
860
+ /* @__PURE__ */ jsxs5(Box5, { marginBottom: 1, children: [
657
861
  /* @__PURE__ */ jsx5(Text5, { bold: true, color: "cyan", children: "dotenvx-ui " }),
658
862
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "keyboard shortcuts" })
659
863
  ] }),
660
- SECTIONS.map((section) => /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", marginBottom: 1, children: [
864
+ SECTIONS.map((section) => /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginBottom: 1, children: [
661
865
  /* @__PURE__ */ jsx5(Text5, { bold: true, children: section.title }),
662
- section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs4(Box5, { children: [
866
+ section.rows.map(([key, desc]) => /* @__PURE__ */ jsxs5(Box5, { children: [
663
867
  /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: key.padEnd(KEY_WIDTH) }),
664
868
  /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: desc })
665
869
  ] }, key))
@@ -669,89 +873,177 @@ function HelpOverlay({ onClose }) {
669
873
  }
670
874
 
671
875
  // 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";
876
+ import { useState as useState3, useRef as useRef2 } from "react";
877
+ import { Box as Box6, Text as Text6, useInput as useInput5, useStdin as useStdin5, useStdout as useStdout2 } from "ink";
878
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
879
+ function makeInitialState(value) {
880
+ const lines = value.split("\n");
881
+ return {
882
+ lines,
883
+ row: lines.length - 1,
884
+ col: lines[lines.length - 1].length
885
+ };
886
+ }
887
+ var CHROME = 5;
675
888
  function InlineForm({ label, initialValue = "", onSubmit, onCancel }) {
676
889
  const { isRawModeSupported } = useStdin5();
677
- const [value, setValue] = useState2(initialValue);
678
- const [cursor, setCursor] = useState2(initialValue.length);
890
+ const { stdout } = useStdout2();
891
+ const [editor, setEditor] = useState3(() => makeInitialState(initialValue));
892
+ const editorRef = useRef2(editor);
893
+ editorRef.current = editor;
679
894
  useInput5((input, key) => {
680
895
  if (key.escape) {
681
896
  onCancel();
682
897
  return;
683
898
  }
899
+ const { lines: lines2, row: row2, col: col2 } = editorRef.current;
900
+ const lineWidth = Math.max(1, (stdout?.columns ?? 80) - CHROME);
684
901
  if (key.return) {
685
- onSubmit(value);
902
+ onSubmit(lines2.join("\n"));
903
+ return;
904
+ }
905
+ if (key.upArrow) {
906
+ const line = lines2[row2];
907
+ const visualRow = Math.floor(col2 / lineWidth);
908
+ if (visualRow > 0) {
909
+ const targetVisualRow = visualRow - 1;
910
+ const colInVisualRow = col2 % lineWidth;
911
+ const newCol = Math.min(targetVisualRow * lineWidth + colInVisualRow, line.length);
912
+ setEditor({ lines: lines2, row: row2, col: newCol });
913
+ } else if (row2 > 0) {
914
+ const prevLine = lines2[row2 - 1];
915
+ const prevVisualRows = Math.floor(prevLine.length / lineWidth);
916
+ const colInVisualRow = col2 % lineWidth;
917
+ const newCol = Math.min(prevVisualRows * lineWidth + colInVisualRow, prevLine.length);
918
+ setEditor({ lines: lines2, row: row2 - 1, col: newCol });
919
+ }
920
+ return;
921
+ }
922
+ if (key.downArrow) {
923
+ const line = lines2[row2];
924
+ const visualRow = Math.floor(col2 / lineWidth);
925
+ const lastVisualRow = Math.floor(line.length / lineWidth);
926
+ if (visualRow < lastVisualRow) {
927
+ const colInVisualRow = col2 % lineWidth;
928
+ const newCol = Math.min((visualRow + 1) * lineWidth + colInVisualRow, line.length);
929
+ setEditor({ lines: lines2, row: row2, col: newCol });
930
+ } else if (row2 < lines2.length - 1) {
931
+ const colInVisualRow = col2 % lineWidth;
932
+ const newCol = Math.min(colInVisualRow, lines2[row2 + 1].length);
933
+ setEditor({ lines: lines2, row: row2 + 1, col: newCol });
934
+ }
686
935
  return;
687
936
  }
688
937
  if (key.leftArrow) {
689
- setCursor((c) => Math.max(0, c - 1));
938
+ if (col2 > 0) {
939
+ setEditor({ lines: lines2, row: row2, col: col2 - 1 });
940
+ } else if (row2 > 0) {
941
+ const newRow = row2 - 1;
942
+ setEditor({ lines: lines2, row: newRow, col: lines2[newRow].length });
943
+ }
690
944
  return;
691
945
  }
692
946
  if (key.rightArrow) {
693
- setCursor((c) => Math.min(value.length, c + 1));
947
+ if (col2 < lines2[row2].length) {
948
+ setEditor({ lines: lines2, row: row2, col: col2 + 1 });
949
+ } else if (row2 < lines2.length - 1) {
950
+ setEditor({ lines: lines2, row: row2 + 1, col: 0 });
951
+ }
694
952
  return;
695
953
  }
696
954
  if (key.ctrl && input === "a" || key.home) {
697
- setCursor(0);
955
+ setEditor({ lines: lines2, row: row2, col: 0 });
698
956
  return;
699
957
  }
700
958
  if (key.ctrl && input === "e" || key.end) {
701
- setCursor(value.length);
959
+ setEditor({ lines: lines2, row: row2, col: lines2[row2].length });
702
960
  return;
703
961
  }
704
962
  if (key.ctrl && input === "k") {
705
- setValue((v) => v.slice(0, cursor));
963
+ const newLines = lines2.map((l, i) => i === row2 ? l.slice(0, col2) : l);
964
+ setEditor({ lines: newLines, row: row2, col: col2 });
706
965
  return;
707
966
  }
708
967
  if (key.ctrl && input === "u") {
709
- setValue((v) => v.slice(cursor));
710
- setCursor(0);
968
+ const newLines = lines2.map((l, i) => i === row2 ? l.slice(col2) : l);
969
+ setEditor({ lines: newLines, row: row2, col: 0 });
711
970
  return;
712
971
  }
713
972
  if (key.backspace) {
714
- if (cursor === 0) return;
715
- setValue((v) => v.slice(0, cursor - 1) + v.slice(cursor));
716
- setCursor((c) => c - 1);
973
+ if (col2 > 0) {
974
+ const cur = lines2[row2];
975
+ const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2 - 1) + cur.slice(col2) : l);
976
+ setEditor({ lines: newLines, row: row2, col: col2 - 1 });
977
+ } else if (row2 > 0) {
978
+ const prevLen = lines2[row2 - 1].length;
979
+ const merged = lines2[row2 - 1] + lines2[row2];
980
+ const newLines = [...lines2.slice(0, row2 - 1), merged, ...lines2.slice(row2 + 1)];
981
+ setEditor({ lines: newLines, row: row2 - 1, col: prevLen });
982
+ }
717
983
  return;
718
984
  }
719
985
  if (key.delete) {
720
- setValue((v) => v.slice(0, cursor) + v.slice(cursor + 1));
986
+ const cur = lines2[row2];
987
+ if (col2 < cur.length) {
988
+ const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + cur.slice(col2 + 1) : l);
989
+ setEditor({ lines: newLines, row: row2, col: col2 });
990
+ } else if (row2 < lines2.length - 1) {
991
+ const merged = cur + lines2[row2 + 1];
992
+ const newLines = [...lines2.slice(0, row2), merged, ...lines2.slice(row2 + 2)];
993
+ setEditor({ lines: newLines, row: row2, col: col2 });
994
+ }
721
995
  return;
722
996
  }
723
997
  if (input && !key.ctrl && !key.meta) {
724
- setValue((v) => v.slice(0, cursor) + input + v.slice(cursor));
725
- setCursor((c) => c + input.length);
998
+ const cur = lines2[row2];
999
+ const newLines = lines2.map((l, i) => i === row2 ? cur.slice(0, col2) + input + cur.slice(col2) : l);
1000
+ setEditor({ lines: newLines, row: row2, col: col2 + input.length });
726
1001
  }
727
1002
  }, { 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
- " "
1003
+ const { lines, row, col } = editor;
1004
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "single", borderTop: true, borderBottom: false, borderLeft: false, borderRight: false, paddingX: 1, paddingTop: 0, children: [
1005
+ /* @__PURE__ */ jsxs6(Box6, { children: [
1006
+ /* @__PURE__ */ jsxs6(Text6, { bold: true, color: "cyan", children: [
1007
+ label,
1008
+ " "
1009
+ ] }),
1010
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "\u21B5 confirm esc cancel" })
735
1011
  ] }),
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
- ] }) });
1012
+ lines.map((line, r) => {
1013
+ const isActive = r === row;
1014
+ if (!isActive) {
1015
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1016
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
1017
+ /* @__PURE__ */ jsx6(Text6, { children: line || " " })
1018
+ ] }, r);
1019
+ }
1020
+ const before = line.slice(0, col);
1021
+ const cursor = line[col] ?? " ";
1022
+ const after = line.slice(col + 1);
1023
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
1024
+ /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: " " }),
1025
+ /* @__PURE__ */ jsxs6(Text6, { children: [
1026
+ before,
1027
+ /* @__PURE__ */ jsx6(Text6, { inverse: true, children: cursor }),
1028
+ after
1029
+ ] })
1030
+ ] }, r);
1031
+ })
1032
+ ] });
741
1033
  }
742
1034
 
743
1035
  // src/tui/App.tsx
744
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1036
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
745
1037
  function App({ files }) {
746
1038
  const { exit } = useApp();
747
1039
  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]));
1040
+ const [fileIndex, setFileIndex] = useState4(0);
1041
+ const [keyIndex, setKeyIndex] = useState4(0);
1042
+ const [focus, setFocus] = useState4("files");
1043
+ const [revealed, setRevealed] = useState4(/* @__PURE__ */ new Map());
1044
+ const [mode, setMode] = useState4({ type: "normal" });
1045
+ const [statusMsg, setStatusMsg] = useState4();
1046
+ const [keys, setKeys] = useState4(() => loadKeys(files[0]));
755
1047
  const selectedFile = files[fileIndex];
756
1048
  function loadKeys(file) {
757
1049
  try {
@@ -774,7 +1066,6 @@ function App({ files }) {
774
1066
  setTimeout(() => setStatusMsg(void 0), ms);
775
1067
  }
776
1068
  useInput6((input, key) => {
777
- if (mode.type !== "normal") return;
778
1069
  if (input === "q" || key.escape) {
779
1070
  exit();
780
1071
  return;
@@ -816,11 +1107,12 @@ function App({ files }) {
816
1107
  setRevealed(/* @__PURE__ */ new Map());
817
1108
  return;
818
1109
  }
1110
+ const decrypted = keys.some((e) => e.encrypted) ? decryptAllValues(selectedFile.path) : {};
819
1111
  const next = /* @__PURE__ */ new Map();
820
1112
  for (const entry of keys) {
821
1113
  if (entry.encrypted) {
822
- const plain = decryptValue(entry.value, selectedFile.path);
823
- if (plain === null) {
1114
+ const plain = decrypted[entry.key];
1115
+ if (plain === void 0 || isEncryptedValue(plain)) {
824
1116
  flash("\u{1F512} Private key not found \u2014 cannot reveal all");
825
1117
  return;
826
1118
  }
@@ -890,6 +1182,7 @@ function App({ files }) {
890
1182
  onSelectKey: setKeyIndex,
891
1183
  statusMsg,
892
1184
  focus2: focus,
1185
+ interactive: false,
893
1186
  extra: /* @__PURE__ */ jsx7(
894
1187
  InlineForm,
895
1188
  {
@@ -930,6 +1223,7 @@ function App({ files }) {
930
1223
  onSelectKey: setKeyIndex,
931
1224
  statusMsg,
932
1225
  focus2: focus,
1226
+ interactive: false,
933
1227
  extra: /* @__PURE__ */ jsx7(
934
1228
  InlineForm,
935
1229
  {
@@ -963,6 +1257,7 @@ function App({ files }) {
963
1257
  onSelectKey: setKeyIndex,
964
1258
  statusMsg,
965
1259
  focus2: focus,
1260
+ interactive: false,
966
1261
  extra: /* @__PURE__ */ jsx7(
967
1262
  InlineForm,
968
1263
  {
@@ -1008,6 +1303,7 @@ function App({ files }) {
1008
1303
  onSelectKey: setKeyIndex,
1009
1304
  statusMsg,
1010
1305
  focus2: focus,
1306
+ interactive: false,
1011
1307
  extra: /* @__PURE__ */ jsx7(ConfirmAddEncrypt, { keyName, onEncrypt: () => commit(true), onPlain: () => commit(false), onCancel: () => setMode({ type: "normal" }) })
1012
1308
  }
1013
1309
  );
@@ -1027,6 +1323,7 @@ function App({ files }) {
1027
1323
  onSelectKey: setKeyIndex,
1028
1324
  statusMsg,
1029
1325
  focus2: focus,
1326
+ interactive: false,
1030
1327
  extra: /* @__PURE__ */ jsx7(
1031
1328
  ConfirmDelete,
1032
1329
  {
@@ -1059,6 +1356,7 @@ function App({ files }) {
1059
1356
  onSelectKey: setKeyIndex,
1060
1357
  statusMsg,
1061
1358
  focus2: focus,
1359
+ interactive: false,
1062
1360
  extra: /* @__PURE__ */ jsx7(
1063
1361
  ConfirmEncrypt,
1064
1362
  {
@@ -1111,7 +1409,8 @@ function App({ files }) {
1111
1409
  onSelectFile: selectFile,
1112
1410
  onSelectKey: setKeyIndex,
1113
1411
  statusMsg,
1114
- focus2: focus
1412
+ focus2: focus,
1413
+ interactive: true
1115
1414
  }
1116
1415
  );
1117
1416
  }
@@ -1121,6 +1420,7 @@ function Layout({
1121
1420
  keys,
1122
1421
  keyIndex,
1123
1422
  focus,
1423
+ interactive,
1124
1424
  revealed,
1125
1425
  onSelectFile,
1126
1426
  onSelectKey,
@@ -1129,10 +1429,13 @@ function Layout({
1129
1429
  }) {
1130
1430
  const selectedFile = files[fileIndex];
1131
1431
  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: [
1432
+ const termRows = useTerminalRows();
1433
+ const termCols = useTerminalCols();
1434
+ const listRows = Math.max(3, termRows - (extra ? 3 : 5));
1435
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: termRows, children: [
1436
+ /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1134
1437
  /* @__PURE__ */ jsx7(Text7, { bold: true, color: "cyan", children: "dotenvx-ui" }),
1135
- /* @__PURE__ */ jsxs6(Text7, { dimColor: true, children: [
1438
+ /* @__PURE__ */ jsxs7(Text7, { dimColor: true, children: [
1136
1439
  " ",
1137
1440
  selectedFile.relativePath,
1138
1441
  " \xB7 ",
@@ -1142,8 +1445,8 @@ function Layout({
1142
1445
  " enc"
1143
1446
  ] })
1144
1447
  ] }),
1145
- /* @__PURE__ */ jsxs6(Box7, { flexGrow: 1, children: [
1146
- /* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", onSelect: onSelectFile }),
1448
+ /* @__PURE__ */ jsxs7(Box7, { height: listRows, children: [
1449
+ /* @__PURE__ */ jsx7(FileList, { files, selectedIndex: fileIndex, focused: focus === "files", interactive, onSelect: onSelectFile }),
1147
1450
  /* @__PURE__ */ jsx7(
1148
1451
  KeyTable,
1149
1452
  {
@@ -1151,23 +1454,58 @@ function Layout({
1151
1454
  keys,
1152
1455
  selectedIndex: keyIndex,
1153
1456
  focused: focus === "keys",
1457
+ interactive,
1154
1458
  revealed,
1155
- onSelect: onSelectKey
1459
+ onSelect: onSelectKey,
1460
+ maxRows: listRows
1156
1461
  }
1157
1462
  )
1158
1463
  ] }),
1159
1464
  extra,
1465
+ !extra && /* @__PURE__ */ jsx7(ValuePreview, { keys, keyIndex, focus, revealed, width: termCols }),
1160
1466
  /* @__PURE__ */ jsx7(StatusBar, { focus, message: statusMsg })
1161
1467
  ] });
1162
1468
  }
1469
+ function ValuePreview({ keys, keyIndex, focus, revealed, width }) {
1470
+ if (focus !== "keys") return null;
1471
+ const k = keys[keyIndex];
1472
+ if (!k) return null;
1473
+ let value;
1474
+ if (k.encrypted) {
1475
+ value = revealed.has(k.key) ? revealed.get(k.key) : "\u2022\u2022\u2022\u2022 (press r to reveal)";
1476
+ } else {
1477
+ value = revealed.has(k.key) ? revealed.get(k.key) : k.value;
1478
+ }
1479
+ const flat = value.replace(/\n/g, "\u21B5 ");
1480
+ return /* @__PURE__ */ jsxs7(
1481
+ Box7,
1482
+ {
1483
+ borderStyle: "single",
1484
+ borderTop: true,
1485
+ borderBottom: false,
1486
+ borderLeft: false,
1487
+ borderRight: false,
1488
+ paddingX: 1,
1489
+ width,
1490
+ children: [
1491
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: "cyan", children: [
1492
+ k.key,
1493
+ " "
1494
+ ] }),
1495
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "\xB7 " }),
1496
+ /* @__PURE__ */ jsx7(Text7, { truncate: true, children: flat })
1497
+ ]
1498
+ }
1499
+ );
1500
+ }
1163
1501
  function ConfirmDelete({ keyName, onConfirm, onCancel }) {
1164
1502
  const { isRawModeSupported } = useStdin6();
1165
1503
  useInput6((input) => {
1166
1504
  if (input === "y" || input === "Y") onConfirm();
1167
1505
  else onCancel();
1168
1506
  }, { isActive: isRawModeSupported });
1169
- return /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1170
- /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
1507
+ return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1508
+ /* @__PURE__ */ jsxs7(Text7, { color: "red", children: [
1171
1509
  "Delete ",
1172
1510
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
1173
1511
  "? "
@@ -1183,8 +1521,8 @@ function ConfirmEncrypt({ decrypt, fileName, onConfirm, onCancel }) {
1183
1521
  }, { isActive: isRawModeSupported });
1184
1522
  const action = decrypt ? "Decrypt" : "Encrypt";
1185
1523
  const color = decrypt ? "yellow" : "green";
1186
- return /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1187
- /* @__PURE__ */ jsxs6(Text7, { color, children: [
1524
+ return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1525
+ /* @__PURE__ */ jsxs7(Text7, { color, children: [
1188
1526
  action,
1189
1527
  " ",
1190
1528
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: fileName }),
@@ -1209,8 +1547,8 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
1209
1547
  return;
1210
1548
  }
1211
1549
  }, { isActive: isRawModeSupported });
1212
- return /* @__PURE__ */ jsxs6(Box7, { paddingX: 1, children: [
1213
- /* @__PURE__ */ jsxs6(Text7, { children: [
1550
+ return /* @__PURE__ */ jsxs7(Box7, { paddingX: 1, children: [
1551
+ /* @__PURE__ */ jsxs7(Text7, { children: [
1214
1552
  "Encrypt ",
1215
1553
  /* @__PURE__ */ jsx7(Text7, { bold: true, children: keyName }),
1216
1554
  "? "
@@ -1222,7 +1560,7 @@ function ConfirmAddEncrypt({ keyName, onEncrypt, onPlain, onCancel }) {
1222
1560
  // src/tui/ErrorBoundary.tsx
1223
1561
  import React4 from "react";
1224
1562
  import { Box as Box8, Text as Text8 } from "ink";
1225
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1563
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1226
1564
  var ErrorBoundary = class extends React4.Component {
1227
1565
  state = { error: null };
1228
1566
  static getDerivedStateFromError(error) {
@@ -1230,7 +1568,7 @@ var ErrorBoundary = class extends React4.Component {
1230
1568
  }
1231
1569
  render() {
1232
1570
  if (this.state.error) {
1233
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1571
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
1234
1572
  /* @__PURE__ */ jsx8(Text8, { bold: true, color: "red", children: "dotenvx-ui crashed" }),
1235
1573
  /* @__PURE__ */ jsx8(Text8, { children: this.state.error.message }),
1236
1574
  /* @__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 +1625,7 @@ function runTUI() {
1287
1625
  console.error("No .env files found in this directory.");
1288
1626
  process.exit(1);
1289
1627
  }
1290
- render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }));
1628
+ render(/* @__PURE__ */ jsx9(ErrorBoundary, { children: /* @__PURE__ */ jsx9(App, { files }) }), { alternateScreen: true });
1291
1629
  }
1292
1630
  function runWebUI() {
1293
1631
  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.2",
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
+ }