autho 2.0.0 → 3.0.0

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/autho.js +1580 -66
  2. package/package.json +8 -2
package/dist/autho.js CHANGED
@@ -1,15 +1,20 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
-
4
- // apps/cli/src/index.ts
5
- import { spawn } from "child_process";
6
- import { existsSync as existsSync4 } from "fs";
7
- import { createInterface } from "readline/promises";
8
- import { resolve as resolve3 } from "path";
9
-
10
- // packages/core/src/daemon.ts
11
- import { createHash, timingSafeEqual } from "crypto";
12
- import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
3
+ var __defProp = Object.defineProperty;
4
+ var __returnValue = (v) => v;
5
+ function __exportSetter(name, newValue) {
6
+ this[name] = __returnValue.bind(null, newValue);
7
+ }
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: true,
13
+ configurable: true,
14
+ set: __exportSetter.bind(all, name)
15
+ });
16
+ };
17
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
13
18
 
14
19
  // packages/storage/src/index.ts
15
20
  import { Database } from "bun:sqlite";
@@ -144,6 +149,31 @@ class AuthoDatabase {
144
149
  LIMIT 1`).get(ref);
145
150
  return row ?? null;
146
151
  }
152
+ updateSecret(id, updates) {
153
+ const parts = [];
154
+ const values = [];
155
+ let idx = 1;
156
+ if (updates.name !== undefined) {
157
+ parts.push(`name = ?${idx}`);
158
+ values.push(updates.name);
159
+ idx++;
160
+ }
161
+ if (updates.type !== undefined) {
162
+ parts.push(`type = ?${idx}`);
163
+ values.push(updates.type);
164
+ idx++;
165
+ }
166
+ if (updates.payload !== undefined) {
167
+ parts.push(`payload = ?${idx}`);
168
+ values.push(updates.payload);
169
+ idx++;
170
+ }
171
+ parts.push(`updated_at = ?${idx}`);
172
+ values.push(updates.updatedAt);
173
+ idx++;
174
+ values.push(id);
175
+ this.db.query(`UPDATE secrets SET ${parts.join(", ")} WHERE id = ?${idx}`).run(...values);
176
+ }
147
177
  deleteSecret(id) {
148
178
  this.db.query("DELETE FROM secrets WHERE id = ?1").run(id);
149
179
  }
@@ -185,6 +215,7 @@ class AuthoDatabase {
185
215
  LIMIT ?1`).all(limit);
186
216
  }
187
217
  }
218
+ var init_src = () => {};
188
219
 
189
220
  // packages/crypto/src/index.ts
190
221
  import {
@@ -193,14 +224,6 @@ import {
193
224
  randomBytes,
194
225
  scryptSync
195
226
  } from "crypto";
196
- var DEFAULT_KDF = {
197
- keyLength: 32,
198
- name: "scrypt",
199
- salt: "",
200
- N: 1 << 17,
201
- p: 1,
202
- r: 8
203
- };
204
227
  function toBuffer(value) {
205
228
  return Buffer.isBuffer(value) ? value : Buffer.from(value, "utf8");
206
229
  }
@@ -257,24 +280,17 @@ function unlockRootKey(password, config) {
257
280
  const key = deriveKeyFromPassword(password, config.kdf);
258
281
  return decryptWithKey(config.wrappedRootKey, key, "autho:vault-root");
259
282
  }
260
-
261
- // packages/core/src/index.ts
262
- import { spawnSync } from "child_process";
263
- import { createHmac, randomBytes as randomBytes3 } from "crypto";
264
- import {
265
- existsSync as existsSync2,
266
- readFileSync as readFileSync2
267
- } from "fs";
268
- import { basename as basename2 } from "path";
269
-
270
- // packages/core/src/artifacts.ts
271
- import { randomBytes as randomBytes2 } from "crypto";
272
- import {
273
- readdirSync,
274
- readFileSync,
275
- statSync
276
- } from "fs";
277
- import { basename, join as join2, relative, resolve as resolve2, sep } from "path";
283
+ var DEFAULT_KDF;
284
+ var init_src2 = __esm(() => {
285
+ DEFAULT_KDF = {
286
+ keyLength: 32,
287
+ name: "scrypt",
288
+ salt: "",
289
+ N: 1 << 17,
290
+ p: 1,
291
+ r: 8
292
+ };
293
+ });
278
294
 
279
295
  // packages/core/src/paths.ts
280
296
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
@@ -323,8 +339,16 @@ function writeBinaryFileSecure(path, content) {
323
339
  writeFileSync(path, content, { mode: 384 });
324
340
  hardenFilePermissions(path);
325
341
  }
342
+ var init_paths = () => {};
326
343
 
327
344
  // packages/core/src/artifacts.ts
345
+ import { randomBytes as randomBytes2 } from "crypto";
346
+ import {
347
+ readdirSync,
348
+ readFileSync,
349
+ statSync
350
+ } from "fs";
351
+ import { basename, join as join2, relative, resolve as resolve2, sep } from "path";
328
352
  function normalizeRelativePath(input) {
329
353
  return input.replace(/\\/g, "/");
330
354
  }
@@ -434,8 +458,19 @@ function assertPathIsFile(path) {
434
458
  throw new Error(`Expected file: ${path}`);
435
459
  }
436
460
  }
461
+ var init_artifacts = __esm(() => {
462
+ init_src2();
463
+ init_paths();
464
+ });
437
465
 
438
466
  // packages/core/src/index.ts
467
+ import { spawnSync } from "child_process";
468
+ import { createHmac, randomBytes as randomBytes3 } from "crypto";
469
+ import {
470
+ existsSync as existsSync2,
471
+ readFileSync as readFileSync2
472
+ } from "fs";
473
+ import { basename as basename2 } from "path";
439
474
  function requireValue(value, label) {
440
475
  if (!value) {
441
476
  throw new Error(`Missing required option: ${label}`);
@@ -808,6 +843,44 @@ class VaultSession {
808
843
  updatedAt: now
809
844
  };
810
845
  }
846
+ updateSecret(ref, updates) {
847
+ const existing = this.getSecretOrThrow(ref);
848
+ if (updates.name && updates.name !== existing.name) {
849
+ const conflict = this.db.findSecret(updates.name);
850
+ if (conflict && conflict.id !== existing.id) {
851
+ throw new Error(`Secret already exists: ${updates.name}`);
852
+ }
853
+ }
854
+ const newName = updates.name ?? existing.name;
855
+ const newType = updates.type ? normalizeSecretType(updates.type) : existing.type;
856
+ const newValue = updates.value ?? existing.value;
857
+ const newUsername = updates.username !== undefined ? updates.username || null : existing.username;
858
+ const newMetadata = updates.metadata ? normalizeMetadata({ ...existing.metadata, ...updates.metadata }) : existing.metadata;
859
+ const now = new Date().toISOString();
860
+ const wrappedKeyBlob = JSON.parse(this.db.findSecret(existing.id).wrappedKey);
861
+ const dek = decryptWithKey(wrappedKeyBlob, this.rootKey, `autho:secret:${existing.id}:dek`);
862
+ const payload = encryptWithKey(JSON.stringify({
863
+ metadata: newMetadata,
864
+ username: newUsername,
865
+ value: newValue
866
+ }), dek, `autho:secret:${existing.id}:payload`);
867
+ this.db.updateSecret(existing.id, {
868
+ name: newName,
869
+ payload: JSON.stringify(payload),
870
+ type: newType,
871
+ updatedAt: now
872
+ });
873
+ this.audit("secret.updated", "secret", existing.id, "Secret updated", {
874
+ fields: Object.keys(updates).filter((k) => updates[k] !== undefined)
875
+ });
876
+ return {
877
+ createdAt: existing.createdAt,
878
+ id: existing.id,
879
+ name: newName,
880
+ type: newType,
881
+ updatedAt: now
882
+ };
883
+ }
811
884
  importLegacyFile(filePath, options) {
812
885
  const raw = JSON.parse(readFileSync2(filePath, "utf8"));
813
886
  let imported = 0;
@@ -1045,8 +1118,1393 @@ class VaultSession {
1045
1118
  return this.db.listAudit(limit).map(toAuditEvent);
1046
1119
  }
1047
1120
  }
1121
+ var init_src3 = __esm(() => {
1122
+ init_src2();
1123
+ init_artifacts();
1124
+ init_src();
1125
+ init_paths();
1126
+ init_paths();
1127
+ });
1128
+
1129
+ // apps/cli/src/tui.tsx
1130
+ var exports_tui = {};
1131
+ __export(exports_tui, {
1132
+ runTui: () => runTui
1133
+ });
1134
+ import { createCliRenderer } from "@opentui/core";
1135
+ import { createRoot, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react";
1136
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
1137
+ import { jsxDEV } from "@opentui/react/jsx-dev-runtime";
1138
+ function typeLabel(type) {
1139
+ switch (type) {
1140
+ case "password":
1141
+ return "Login";
1142
+ case "note":
1143
+ return "Secure Note";
1144
+ case "otp":
1145
+ return "OTP Secret";
1146
+ default:
1147
+ return type;
1148
+ }
1149
+ }
1150
+ function Header({ title, subtitle }) {
1151
+ return /* @__PURE__ */ jsxDEV("box", {
1152
+ width: "100%",
1153
+ paddingLeft: 2,
1154
+ paddingTop: 1,
1155
+ paddingBottom: 1,
1156
+ borderBottom: true,
1157
+ borderColor: "#333333",
1158
+ children: [
1159
+ /* @__PURE__ */ jsxDEV("text", {
1160
+ fg: "#FFD700",
1161
+ children: /* @__PURE__ */ jsxDEV("strong", {
1162
+ children: title
1163
+ }, undefined, false, undefined, this)
1164
+ }, undefined, false, undefined, this),
1165
+ subtitle ? /* @__PURE__ */ jsxDEV("text", {
1166
+ fg: "#555555",
1167
+ children: [
1168
+ " ",
1169
+ subtitle
1170
+ ]
1171
+ }, undefined, true, undefined, this) : null
1172
+ ]
1173
+ }, undefined, true, undefined, this);
1174
+ }
1175
+ function StatusBar({ message }) {
1176
+ return /* @__PURE__ */ jsxDEV("box", {
1177
+ width: "100%",
1178
+ height: 1,
1179
+ backgroundColor: "#1a1a1a",
1180
+ paddingLeft: 1,
1181
+ paddingRight: 1,
1182
+ children: /* @__PURE__ */ jsxDEV("text", {
1183
+ fg: "#666666",
1184
+ children: message
1185
+ }, undefined, false, undefined, this)
1186
+ }, undefined, false, undefined, this);
1187
+ }
1188
+ function ToastBar({ toast }) {
1189
+ if (!toast)
1190
+ return null;
1191
+ const color = toast.type === "success" ? "#00CC66" : "#FF4444";
1192
+ const icon = toast.type === "success" ? "+" : "!";
1193
+ return /* @__PURE__ */ jsxDEV("box", {
1194
+ width: "100%",
1195
+ height: 1,
1196
+ backgroundColor: color,
1197
+ paddingLeft: 1,
1198
+ children: /* @__PURE__ */ jsxDEV("text", {
1199
+ fg: "#000000",
1200
+ children: /* @__PURE__ */ jsxDEV("strong", {
1201
+ children: [
1202
+ " ",
1203
+ icon,
1204
+ " ",
1205
+ toast.message
1206
+ ]
1207
+ }, undefined, true, undefined, this)
1208
+ }, undefined, false, undefined, this)
1209
+ }, undefined, false, undefined, this);
1210
+ }
1211
+ function KeyValue({ label, value, labelColor = "#888888", valueColor = "#FFFFFF" }) {
1212
+ return /* @__PURE__ */ jsxDEV("box", {
1213
+ flexDirection: "row",
1214
+ gap: 1,
1215
+ children: [
1216
+ /* @__PURE__ */ jsxDEV("text", {
1217
+ fg: labelColor,
1218
+ width: 14,
1219
+ children: label
1220
+ }, undefined, false, undefined, this),
1221
+ /* @__PURE__ */ jsxDEV("text", {
1222
+ fg: valueColor,
1223
+ children: value
1224
+ }, undefined, false, undefined, this)
1225
+ ]
1226
+ }, undefined, true, undefined, this);
1227
+ }
1228
+ function useToast() {
1229
+ const [toast, setToast] = useState(null);
1230
+ const timerRef = useRef(null);
1231
+ const show = useCallback((message, type = "success") => {
1232
+ if (timerRef.current)
1233
+ clearTimeout(timerRef.current);
1234
+ setToast({ message, type });
1235
+ timerRef.current = setTimeout(() => setToast(null), 3000);
1236
+ }, []);
1237
+ useEffect(() => () => {
1238
+ if (timerRef.current)
1239
+ clearTimeout(timerRef.current);
1240
+ }, []);
1241
+ return { toast, show };
1242
+ }
1243
+ function PasswordScreen({ onUnlock, vaultPath }) {
1244
+ const [password, setPassword] = useState("");
1245
+ const [error, setError] = useState("");
1246
+ const [unlocking, setUnlocking] = useState(false);
1247
+ useKeyboard((key) => {
1248
+ if (key.eventType !== "press" && key.eventType !== "repeat")
1249
+ return;
1250
+ if (unlocking)
1251
+ return;
1252
+ if (key.name === "enter" || key.name === "return") {
1253
+ if (!password)
1254
+ return;
1255
+ setUnlocking(true);
1256
+ setError("");
1257
+ try {
1258
+ const session = VaultService.unlock(vaultPath, password);
1259
+ onUnlock(session);
1260
+ } catch {
1261
+ setError("Wrong password");
1262
+ setPassword("");
1263
+ setUnlocking(false);
1264
+ }
1265
+ return;
1266
+ }
1267
+ if (key.name === "backspace") {
1268
+ setPassword((p) => p.slice(0, -1));
1269
+ return;
1270
+ }
1271
+ if (key.ctrl || key.meta || key.name === "escape" || key.name === "tab" || key.name === "up" || key.name === "down" || key.name === "left" || key.name === "right" || key.name === "home" || key.name === "end" || key.name === "delete" || key.name === "insert" || key.name === "pageup" || key.name === "pagedown" || key.name.startsWith("f") && /^f\d+$/.test(key.name))
1272
+ return;
1273
+ const char = key.sequence;
1274
+ if (char && char.length === 1 && char.charCodeAt(0) >= 32)
1275
+ setPassword((p) => p + char);
1276
+ });
1277
+ return /* @__PURE__ */ jsxDEV("box", {
1278
+ flexDirection: "column",
1279
+ alignItems: "center",
1280
+ justifyContent: "center",
1281
+ width: "100%",
1282
+ height: "100%",
1283
+ children: /* @__PURE__ */ jsxDEV("box", {
1284
+ flexDirection: "column",
1285
+ border: true,
1286
+ borderStyle: "rounded",
1287
+ padding: 2,
1288
+ width: 52,
1289
+ gap: 1,
1290
+ children: [
1291
+ /* @__PURE__ */ jsxDEV("ascii-font", {
1292
+ text: "autho",
1293
+ font: "tiny",
1294
+ color: "#FFD700"
1295
+ }, undefined, false, undefined, this),
1296
+ /* @__PURE__ */ jsxDEV("text", {
1297
+ fg: "#888888",
1298
+ children: "Unlock your vault"
1299
+ }, undefined, false, undefined, this),
1300
+ error ? /* @__PURE__ */ jsxDEV("box", {
1301
+ backgroundColor: "#331111",
1302
+ paddingX: 1,
1303
+ width: "100%",
1304
+ children: /* @__PURE__ */ jsxDEV("text", {
1305
+ fg: "#FF4444",
1306
+ children: error
1307
+ }, undefined, false, undefined, this)
1308
+ }, undefined, false, undefined, this) : null,
1309
+ /* @__PURE__ */ jsxDEV("box", {
1310
+ flexDirection: "row",
1311
+ gap: 1,
1312
+ marginTop: 1,
1313
+ children: [
1314
+ /* @__PURE__ */ jsxDEV("text", {
1315
+ fg: "#AAAAAA",
1316
+ children: "Password "
1317
+ }, undefined, false, undefined, this),
1318
+ /* @__PURE__ */ jsxDEV("box", {
1319
+ backgroundColor: "#111111",
1320
+ flexGrow: 1,
1321
+ paddingX: 1,
1322
+ height: 1,
1323
+ children: /* @__PURE__ */ jsxDEV("text", {
1324
+ fg: "#FFD700",
1325
+ children: password ? "*".repeat(password.length) : ""
1326
+ }, undefined, false, undefined, this)
1327
+ }, undefined, false, undefined, this)
1328
+ ]
1329
+ }, undefined, true, undefined, this),
1330
+ unlocking ? /* @__PURE__ */ jsxDEV("text", {
1331
+ fg: "#FFD700",
1332
+ children: "Unlocking..."
1333
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("text", {
1334
+ fg: "#444444",
1335
+ children: "Enter to unlock | Ctrl+C to exit"
1336
+ }, undefined, false, undefined, this)
1337
+ ]
1338
+ }, undefined, true, undefined, this)
1339
+ }, undefined, false, undefined, this);
1340
+ }
1341
+ function buildDescription(s, availableWidth) {
1342
+ const label = typeLabel(s.type);
1343
+ let desc = label;
1344
+ if (s.username) {
1345
+ const withUser = `${label} \xB7 ${s.username}`;
1346
+ if (withUser.length <= availableWidth) {
1347
+ desc = withUser;
1348
+ } else {
1349
+ return desc;
1350
+ }
1351
+ }
1352
+ const url = s.metadata?.url ? String(s.metadata.url) : "";
1353
+ if (url) {
1354
+ const shortUrl = url.replace(/^https?:\/\//, "");
1355
+ const withUrl = `${desc} \xB7 ${shortUrl}`;
1356
+ if (withUrl.length <= availableWidth) {
1357
+ desc = withUrl;
1358
+ } else {
1359
+ return desc;
1360
+ }
1361
+ }
1362
+ const note = s.metadata?.description ? String(s.metadata.description) : "";
1363
+ if (note) {
1364
+ const withNote = `${desc} \xB7 ${note}`;
1365
+ if (withNote.length <= availableWidth) {
1366
+ desc = withNote;
1367
+ } else {
1368
+ const budget = availableWidth - desc.length - 5;
1369
+ if (budget > 5) {
1370
+ desc = `${desc} \u2014 ${note.slice(0, budget)}\u2026`;
1371
+ }
1372
+ }
1373
+ }
1374
+ return desc;
1375
+ }
1376
+ function HomeScreen({ session, onSelect, onCreate, toast }) {
1377
+ const renderer = useRenderer();
1378
+ const { width: termWidth } = useTerminalDimensions();
1379
+ const [secrets, setSecrets] = useState([]);
1380
+ const [query, setQuery] = useState("");
1381
+ const [searchFocused, setSearchFocused] = useState(false);
1382
+ const reload = useCallback(() => {
1383
+ try {
1384
+ const summaries = session.listSecrets();
1385
+ const enriched = summaries.map((s) => {
1386
+ try {
1387
+ return session.getSecret(s.id);
1388
+ } catch {
1389
+ return s;
1390
+ }
1391
+ });
1392
+ setSecrets(enriched);
1393
+ } catch {
1394
+ setSecrets([]);
1395
+ }
1396
+ }, [session]);
1397
+ useEffect(reload, [reload]);
1398
+ const filtered = useMemo(() => {
1399
+ if (!query)
1400
+ return secrets;
1401
+ const q = query.toLowerCase();
1402
+ return secrets.filter((s) => s.name.toLowerCase().includes(q) || s.type.toLowerCase().includes(q) || s.username && s.username.toLowerCase().includes(q) || s.metadata?.url && String(s.metadata.url).toLowerCase().includes(q) || s.metadata?.description && String(s.metadata.description).toLowerCase().includes(q));
1403
+ }, [secrets, query]);
1404
+ const descWidth = Math.max(20, termWidth - 30);
1405
+ const options = useMemo(() => filtered.map((s) => ({
1406
+ name: s.name,
1407
+ description: buildDescription(s, descWidth),
1408
+ value: s.id
1409
+ })), [filtered, descWidth]);
1410
+ useKeyboard((key) => {
1411
+ if (key.eventType !== "press")
1412
+ return;
1413
+ if (key.name === "q" && !searchFocused) {
1414
+ session.close();
1415
+ renderer.destroy();
1416
+ return;
1417
+ }
1418
+ if (key.name === "n" && !searchFocused) {
1419
+ onCreate();
1420
+ return;
1421
+ }
1422
+ if (key.name === "/" && !searchFocused) {
1423
+ setSearchFocused(true);
1424
+ return;
1425
+ }
1426
+ if (key.name === "escape" && searchFocused) {
1427
+ setSearchFocused(false);
1428
+ setQuery("");
1429
+ return;
1430
+ }
1431
+ if (key.name === "escape" && !searchFocused) {
1432
+ session.close();
1433
+ renderer.destroy();
1434
+ return;
1435
+ }
1436
+ if (key.name === "tab") {
1437
+ setSearchFocused((f) => !f);
1438
+ return;
1439
+ }
1440
+ });
1441
+ return /* @__PURE__ */ jsxDEV("box", {
1442
+ flexDirection: "column",
1443
+ width: "100%",
1444
+ height: "100%",
1445
+ children: [
1446
+ /* @__PURE__ */ jsxDEV(Header, {
1447
+ title: "Autho",
1448
+ subtitle: `${secrets.length} secrets`
1449
+ }, undefined, false, undefined, this),
1450
+ /* @__PURE__ */ jsxDEV("box", {
1451
+ flexDirection: "row",
1452
+ paddingX: 2,
1453
+ paddingTop: 1,
1454
+ gap: 2,
1455
+ alignItems: "center",
1456
+ children: [
1457
+ /* @__PURE__ */ jsxDEV("input", {
1458
+ value: query,
1459
+ onChange: setQuery,
1460
+ placeholder: "/ search...",
1461
+ focused: searchFocused,
1462
+ flexGrow: 1,
1463
+ backgroundColor: "#111111",
1464
+ focusedBackgroundColor: "#1a1a1a",
1465
+ textColor: "#FFFFFF",
1466
+ placeholderColor: "#444444"
1467
+ }, undefined, false, undefined, this),
1468
+ /* @__PURE__ */ jsxDEV("text", {
1469
+ fg: "#555555",
1470
+ children: "[n] new"
1471
+ }, undefined, false, undefined, this)
1472
+ ]
1473
+ }, undefined, true, undefined, this),
1474
+ /* @__PURE__ */ jsxDEV("box", {
1475
+ flexGrow: 1,
1476
+ paddingX: 2,
1477
+ paddingTop: 1,
1478
+ children: filtered.length === 0 ? /* @__PURE__ */ jsxDEV("box", {
1479
+ padding: 1,
1480
+ children: /* @__PURE__ */ jsxDEV("text", {
1481
+ fg: "#555555",
1482
+ children: secrets.length === 0 ? "No secrets yet. Press [n] to create one." : "No matches."
1483
+ }, undefined, false, undefined, this)
1484
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("select", {
1485
+ options,
1486
+ onSelect: (index) => onSelect(filtered[index]),
1487
+ focused: !searchFocused,
1488
+ height: Math.min(filtered.length * 2 + 1, 18),
1489
+ selectedBackgroundColor: "#222222",
1490
+ selectedTextColor: "#FFD700"
1491
+ }, undefined, false, undefined, this)
1492
+ }, undefined, false, undefined, this),
1493
+ /* @__PURE__ */ jsxDEV(ToastBar, {
1494
+ toast
1495
+ }, undefined, false, undefined, this),
1496
+ /* @__PURE__ */ jsxDEV(StatusBar, {
1497
+ message: "\u2191\u2193 navigate Enter open / search n new q quit"
1498
+ }, undefined, false, undefined, this)
1499
+ ]
1500
+ }, undefined, true, undefined, this);
1501
+ }
1502
+ function DetailScreen({ session, secret, onBack, onDeleted, onEdit }) {
1503
+ const renderer = useRenderer();
1504
+ const [revealing, setRevealing] = useState(false);
1505
+ const [copied, setCopied] = useState("");
1506
+ const [confirmDelete, setConfirmDelete] = useState(false);
1507
+ const [otpResult, setOtpResult] = useState(null);
1508
+ const [otpCountdown, setOtpCountdown] = useState(0);
1509
+ const [error, setError] = useState("");
1510
+ const hideTimerRef = useRef(null);
1511
+ const countdownRef = useRef(null);
1512
+ let detail;
1513
+ try {
1514
+ detail = session.getSecret(secret.name ?? secret.id);
1515
+ } catch {
1516
+ detail = secret;
1517
+ }
1518
+ const isOtp = detail.type === "otp";
1519
+ useEffect(() => () => {
1520
+ if (hideTimerRef.current)
1521
+ clearTimeout(hideTimerRef.current);
1522
+ if (countdownRef.current)
1523
+ clearInterval(countdownRef.current);
1524
+ }, []);
1525
+ useEffect(() => {
1526
+ if (!otpResult) {
1527
+ if (countdownRef.current)
1528
+ clearInterval(countdownRef.current);
1529
+ return;
1530
+ }
1531
+ const update = () => {
1532
+ const remaining = Math.max(0, Math.ceil((new Date(otpResult.expiresAt).getTime() - Date.now()) / 1000));
1533
+ setOtpCountdown(remaining);
1534
+ if (remaining <= 0 && countdownRef.current) {
1535
+ clearInterval(countdownRef.current);
1536
+ }
1537
+ };
1538
+ update();
1539
+ countdownRef.current = setInterval(update, 1000);
1540
+ return () => {
1541
+ if (countdownRef.current)
1542
+ clearInterval(countdownRef.current);
1543
+ };
1544
+ }, [otpResult]);
1545
+ useKeyboard((key) => {
1546
+ if (key.name === "s" && !confirmDelete && (key.eventType === "press" || key.eventType === "repeat")) {
1547
+ setRevealing(true);
1548
+ if (hideTimerRef.current)
1549
+ clearTimeout(hideTimerRef.current);
1550
+ hideTimerRef.current = setTimeout(() => setRevealing(false), 600);
1551
+ return;
1552
+ }
1553
+ if (key.eventType !== "press")
1554
+ return;
1555
+ if (key.name === "escape") {
1556
+ if (confirmDelete) {
1557
+ setConfirmDelete(false);
1558
+ return;
1559
+ }
1560
+ if (otpResult) {
1561
+ setOtpResult(null);
1562
+ return;
1563
+ }
1564
+ onBack();
1565
+ return;
1566
+ }
1567
+ if (key.name === "c" && !confirmDelete) {
1568
+ const toCopy = otpResult ? otpResult.code : detail.value ?? "";
1569
+ if (toCopy) {
1570
+ renderer.copyToClipboardOSC52(toCopy);
1571
+ setCopied(otpResult ? "OTP code" : "Value");
1572
+ setTimeout(() => setCopied(""), 2000);
1573
+ }
1574
+ return;
1575
+ }
1576
+ if (key.name === "e" && !confirmDelete && !otpResult) {
1577
+ onEdit();
1578
+ return;
1579
+ }
1580
+ if (key.name === "d" && !confirmDelete && !otpResult) {
1581
+ setConfirmDelete(true);
1582
+ return;
1583
+ }
1584
+ if (key.name === "o" && isOtp && !confirmDelete) {
1585
+ try {
1586
+ const result = session.generateOtp(detail.name);
1587
+ setOtpResult(result);
1588
+ setError("");
1589
+ } catch (e) {
1590
+ setError(e instanceof Error ? e.message : String(e));
1591
+ }
1592
+ return;
1593
+ }
1594
+ if (key.name === "r" && otpResult) {
1595
+ try {
1596
+ const result = session.generateOtp(detail.name);
1597
+ setOtpResult(result);
1598
+ setError("");
1599
+ } catch (e) {
1600
+ setError(e instanceof Error ? e.message : String(e));
1601
+ }
1602
+ return;
1603
+ }
1604
+ });
1605
+ if (confirmDelete) {
1606
+ return /* @__PURE__ */ jsxDEV("box", {
1607
+ flexDirection: "column",
1608
+ width: "100%",
1609
+ height: "100%",
1610
+ children: [
1611
+ /* @__PURE__ */ jsxDEV(Header, {
1612
+ title: "Delete",
1613
+ subtitle: detail.name
1614
+ }, undefined, false, undefined, this),
1615
+ /* @__PURE__ */ jsxDEV("box", {
1616
+ padding: 2,
1617
+ flexDirection: "column",
1618
+ gap: 1,
1619
+ children: [
1620
+ /* @__PURE__ */ jsxDEV("box", {
1621
+ border: true,
1622
+ borderStyle: "rounded",
1623
+ borderColor: "#FF4444",
1624
+ padding: 1,
1625
+ flexDirection: "column",
1626
+ gap: 0,
1627
+ children: [
1628
+ /* @__PURE__ */ jsxDEV("text", {
1629
+ fg: "#FF4444",
1630
+ children: /* @__PURE__ */ jsxDEV("strong", {
1631
+ children: "Permanently delete this secret?"
1632
+ }, undefined, false, undefined, this)
1633
+ }, undefined, false, undefined, this),
1634
+ /* @__PURE__ */ jsxDEV(KeyValue, {
1635
+ label: "Name",
1636
+ value: detail.name,
1637
+ valueColor: "#FFD700"
1638
+ }, undefined, false, undefined, this),
1639
+ /* @__PURE__ */ jsxDEV(KeyValue, {
1640
+ label: "Type",
1641
+ value: detail.type
1642
+ }, undefined, false, undefined, this)
1643
+ ]
1644
+ }, undefined, true, undefined, this),
1645
+ /* @__PURE__ */ jsxDEV("select", {
1646
+ options: [
1647
+ { name: "Cancel", description: "Keep the secret", value: "cancel" },
1648
+ { name: "Delete forever", description: "Cannot be undone", value: "delete" }
1649
+ ],
1650
+ onSelect: (_i, opt) => {
1651
+ if (opt.value === "delete") {
1652
+ try {
1653
+ session.removeSecret(detail.name ?? detail.id);
1654
+ onDeleted(`"${detail.name}" deleted`);
1655
+ } catch (e) {
1656
+ onDeleted(`Error: ${e instanceof Error ? e.message : String(e)}`);
1657
+ }
1658
+ } else {
1659
+ setConfirmDelete(false);
1660
+ }
1661
+ },
1662
+ focused: true,
1663
+ height: 4,
1664
+ selectedBackgroundColor: "#222222",
1665
+ selectedTextColor: "#FF4444"
1666
+ }, undefined, false, undefined, this)
1667
+ ]
1668
+ }, undefined, true, undefined, this),
1669
+ /* @__PURE__ */ jsxDEV(StatusBar, {
1670
+ message: "\u2191\u2193 navigate Enter confirm Esc cancel"
1671
+ }, undefined, false, undefined, this)
1672
+ ]
1673
+ }, undefined, true, undefined, this);
1674
+ }
1675
+ let maskedValue = "\u2014";
1676
+ if (detail.value) {
1677
+ maskedValue = revealing ? detail.value : "*".repeat(Math.min(detail.value.length, 32));
1678
+ }
1679
+ const hints = ["Hold s reveal", "c copy", "e edit", "d delete"];
1680
+ if (isOtp)
1681
+ hints.push(otpResult ? "r regen" : "o OTP");
1682
+ hints.push("Esc back");
1683
+ return /* @__PURE__ */ jsxDEV("box", {
1684
+ flexDirection: "column",
1685
+ width: "100%",
1686
+ height: "100%",
1687
+ children: [
1688
+ /* @__PURE__ */ jsxDEV(Header, {
1689
+ title: detail.name,
1690
+ subtitle: typeLabel(detail.type)
1691
+ }, undefined, false, undefined, this),
1692
+ /* @__PURE__ */ jsxDEV("box", {
1693
+ flexDirection: "column",
1694
+ flexGrow: 1,
1695
+ padding: 2,
1696
+ gap: 1,
1697
+ children: [
1698
+ /* @__PURE__ */ jsxDEV("box", {
1699
+ flexDirection: "column",
1700
+ border: true,
1701
+ borderStyle: "rounded",
1702
+ borderColor: "#333333",
1703
+ padding: 1,
1704
+ gap: 0,
1705
+ width: "100%",
1706
+ children: [
1707
+ /* @__PURE__ */ jsxDEV(KeyValue, {
1708
+ label: "Name",
1709
+ value: detail.name,
1710
+ valueColor: "#FFD700"
1711
+ }, undefined, false, undefined, this),
1712
+ /* @__PURE__ */ jsxDEV(KeyValue, {
1713
+ label: "Type",
1714
+ value: typeLabel(detail.type)
1715
+ }, undefined, false, undefined, this),
1716
+ /* @__PURE__ */ jsxDEV(KeyValue, {
1717
+ label: "ID",
1718
+ value: detail.id,
1719
+ valueColor: "#555555"
1720
+ }, undefined, false, undefined, this),
1721
+ detail.username ? /* @__PURE__ */ jsxDEV(KeyValue, {
1722
+ label: "Username",
1723
+ value: detail.username
1724
+ }, undefined, false, undefined, this) : null,
1725
+ /* @__PURE__ */ jsxDEV(KeyValue, {
1726
+ label: "Value",
1727
+ value: maskedValue,
1728
+ valueColor: revealing ? "#00FF00" : "#FF4444"
1729
+ }, undefined, false, undefined, this),
1730
+ detail.metadata?.url ? /* @__PURE__ */ jsxDEV(KeyValue, {
1731
+ label: "URL",
1732
+ value: String(detail.metadata.url)
1733
+ }, undefined, false, undefined, this) : null,
1734
+ detail.metadata?.description ? /* @__PURE__ */ jsxDEV(KeyValue, {
1735
+ label: "Description",
1736
+ value: String(detail.metadata.description)
1737
+ }, undefined, false, undefined, this) : null
1738
+ ]
1739
+ }, undefined, true, undefined, this),
1740
+ /* @__PURE__ */ jsxDEV("text", {
1741
+ fg: revealing ? "#00FF00" : "#666666",
1742
+ children: revealing ? "Revealing \u2014 release [s] to hide" : "Hold [s] to reveal secret value"
1743
+ }, undefined, false, undefined, this),
1744
+ otpResult ? /* @__PURE__ */ jsxDEV("box", {
1745
+ border: true,
1746
+ borderStyle: "rounded",
1747
+ borderColor: "#00CC66",
1748
+ paddingX: 2,
1749
+ height: 3,
1750
+ flexDirection: "row",
1751
+ alignItems: "center",
1752
+ gap: 2,
1753
+ children: [
1754
+ /* @__PURE__ */ jsxDEV("text", {
1755
+ fg: "#888888",
1756
+ children: "OTP"
1757
+ }, undefined, false, undefined, this),
1758
+ /* @__PURE__ */ jsxDEV("text", {
1759
+ fg: "#00FF00",
1760
+ children: /* @__PURE__ */ jsxDEV("strong", {
1761
+ children: otpResult.code
1762
+ }, undefined, false, undefined, this)
1763
+ }, undefined, false, undefined, this),
1764
+ /* @__PURE__ */ jsxDEV("text", {
1765
+ fg: otpCountdown <= 5 ? "#FF4444" : "#888888",
1766
+ children: otpCountdown > 0 ? `${otpCountdown}s` : "expired"
1767
+ }, undefined, false, undefined, this),
1768
+ /* @__PURE__ */ jsxDEV("text", {
1769
+ fg: "#555555",
1770
+ children: "[r] refresh"
1771
+ }, undefined, false, undefined, this)
1772
+ ]
1773
+ }, undefined, true, undefined, this) : null,
1774
+ error ? /* @__PURE__ */ jsxDEV("text", {
1775
+ fg: "#FF4444",
1776
+ children: error
1777
+ }, undefined, false, undefined, this) : null,
1778
+ copied ? /* @__PURE__ */ jsxDEV("text", {
1779
+ fg: "#00CC66",
1780
+ children: [
1781
+ copied,
1782
+ " copied to clipboard"
1783
+ ]
1784
+ }, undefined, true, undefined, this) : null,
1785
+ /* @__PURE__ */ jsxDEV("box", {
1786
+ flexDirection: "row",
1787
+ gap: 3,
1788
+ marginTop: 1,
1789
+ children: [
1790
+ /* @__PURE__ */ jsxDEV("text", {
1791
+ fg: "#00CC66",
1792
+ children: "[c] Copy"
1793
+ }, undefined, false, undefined, this),
1794
+ /* @__PURE__ */ jsxDEV("text", {
1795
+ fg: "#FFD700",
1796
+ children: "[e] Edit"
1797
+ }, undefined, false, undefined, this),
1798
+ /* @__PURE__ */ jsxDEV("text", {
1799
+ fg: "#FF4444",
1800
+ children: "[d] Delete"
1801
+ }, undefined, false, undefined, this),
1802
+ isOtp ? /* @__PURE__ */ jsxDEV("text", {
1803
+ fg: "#00CCFF",
1804
+ children: "[o] Generate OTP"
1805
+ }, undefined, false, undefined, this) : null
1806
+ ]
1807
+ }, undefined, true, undefined, this)
1808
+ ]
1809
+ }, undefined, true, undefined, this),
1810
+ /* @__PURE__ */ jsxDEV(StatusBar, {
1811
+ message: hints.join(" ")
1812
+ }, undefined, false, undefined, this)
1813
+ ]
1814
+ }, undefined, true, undefined, this);
1815
+ }
1816
+ function CreateScreen({ session, onDone, onBack }) {
1817
+ const [step, setStep] = useState(0);
1818
+ const [secretType, setSecretType] = useState("password");
1819
+ const [name, setName] = useState("");
1820
+ const [value, setValue] = useState("");
1821
+ const [username, setUsername] = useState("");
1822
+ const [url, setUrl] = useState("");
1823
+ const [description, setDescription] = useState("");
1824
+ const typeOptions = [
1825
+ { name: "Login", description: "Username, password, and URL", value: "password" },
1826
+ { name: "Secure Note", description: "Encrypted text note", value: "note" },
1827
+ { name: "OTP Secret", description: "TOTP key for 2FA codes", value: "otp" }
1828
+ ];
1829
+ const steps = (() => {
1830
+ switch (secretType) {
1831
+ case "password":
1832
+ return ["type", "name", "value", "username", "url", "description"];
1833
+ case "note":
1834
+ return ["type", "name", "value", "description"];
1835
+ case "otp":
1836
+ return ["type", "name", "value", "username", "description"];
1837
+ default:
1838
+ return ["type", "name", "value", "description"];
1839
+ }
1840
+ })();
1841
+ const currentField = steps[step] ?? "description";
1842
+ const totalSteps = steps.length;
1843
+ const label = typeLabel(secretType);
1844
+ const doCreate = useCallback(() => {
1845
+ try {
1846
+ const metadata = Object.fromEntries(Object.entries({
1847
+ description: description || undefined,
1848
+ url: url || undefined
1849
+ }).filter(([, v]) => v !== undefined));
1850
+ session.addSecret({
1851
+ metadata,
1852
+ name,
1853
+ type: secretType,
1854
+ username: username || undefined,
1855
+ value
1856
+ });
1857
+ onDone(`"${name}" created`);
1858
+ } catch (e) {
1859
+ onDone(`Error: ${e instanceof Error ? e.message : String(e)}`);
1860
+ }
1861
+ }, [session, name, value, secretType, username, url, description, onDone]);
1862
+ useKeyboard((key) => {
1863
+ if (key.eventType !== "press")
1864
+ return;
1865
+ if (key.name === "escape") {
1866
+ if (step > 0)
1867
+ setStep((s) => s - 1);
1868
+ else
1869
+ onBack();
1870
+ }
1871
+ });
1872
+ const progress = `${step + 1}/${totalSteps}`;
1873
+ const nextStep = () => setStep((s) => s + 1);
1874
+ const inputRow = (label2, val, onChange, onSubmit, placeholder) => /* @__PURE__ */ jsxDEV("box", {
1875
+ flexDirection: "row",
1876
+ gap: 1,
1877
+ children: [
1878
+ /* @__PURE__ */ jsxDEV("text", {
1879
+ fg: "#AAAAAA",
1880
+ width: 12,
1881
+ children: label2
1882
+ }, undefined, false, undefined, this),
1883
+ /* @__PURE__ */ jsxDEV("input", {
1884
+ value: val,
1885
+ onChange,
1886
+ onSubmit,
1887
+ placeholder,
1888
+ focused: true,
1889
+ flexGrow: 1,
1890
+ backgroundColor: "#111111",
1891
+ focusedBackgroundColor: "#1a1a1a",
1892
+ textColor: "#FFFFFF"
1893
+ }, undefined, false, undefined, this)
1894
+ ]
1895
+ }, undefined, true, undefined, this);
1896
+ if (step === 0) {
1897
+ return /* @__PURE__ */ jsxDEV("box", {
1898
+ flexDirection: "column",
1899
+ width: "100%",
1900
+ height: "100%",
1901
+ children: [
1902
+ /* @__PURE__ */ jsxDEV(Header, {
1903
+ title: "New Secret",
1904
+ subtitle: progress
1905
+ }, undefined, false, undefined, this),
1906
+ /* @__PURE__ */ jsxDEV("box", {
1907
+ paddingLeft: 2,
1908
+ paddingTop: 1,
1909
+ children: /* @__PURE__ */ jsxDEV("text", {
1910
+ fg: "#888888",
1911
+ children: "What type of secret?"
1912
+ }, undefined, false, undefined, this)
1913
+ }, undefined, false, undefined, this),
1914
+ /* @__PURE__ */ jsxDEV("box", {
1915
+ flexGrow: 1,
1916
+ paddingLeft: 2,
1917
+ paddingTop: 1,
1918
+ children: /* @__PURE__ */ jsxDEV("select", {
1919
+ options: typeOptions,
1920
+ onSelect: (_i, opt) => {
1921
+ setSecretType(opt.value ?? "password");
1922
+ nextStep();
1923
+ },
1924
+ focused: true,
1925
+ height: 6,
1926
+ selectedBackgroundColor: "#222222",
1927
+ selectedTextColor: "#FFD700"
1928
+ }, undefined, false, undefined, this)
1929
+ }, undefined, false, undefined, this),
1930
+ /* @__PURE__ */ jsxDEV(StatusBar, {
1931
+ message: "Enter select Esc cancel"
1932
+ }, undefined, false, undefined, this)
1933
+ ]
1934
+ }, undefined, true, undefined, this);
1935
+ }
1936
+ if (currentField === "name") {
1937
+ return /* @__PURE__ */ jsxDEV("box", {
1938
+ flexDirection: "column",
1939
+ width: "100%",
1940
+ height: "100%",
1941
+ children: [
1942
+ /* @__PURE__ */ jsxDEV(Header, {
1943
+ title: "New Secret",
1944
+ subtitle: `${progress} \xB7 ${label}`
1945
+ }, undefined, false, undefined, this),
1946
+ /* @__PURE__ */ jsxDEV("box", {
1947
+ padding: 2,
1948
+ flexDirection: "column",
1949
+ gap: 1,
1950
+ children: [
1951
+ /* @__PURE__ */ jsxDEV("text", {
1952
+ fg: "#888888",
1953
+ children: "Give it a name"
1954
+ }, undefined, false, undefined, this),
1955
+ inputRow("Name", name, setName, () => {
1956
+ if (name.trim())
1957
+ nextStep();
1958
+ }, "e.g. github-login")
1959
+ ]
1960
+ }, undefined, true, undefined, this),
1961
+ /* @__PURE__ */ jsxDEV(StatusBar, {
1962
+ message: "Enter next Esc back"
1963
+ }, undefined, false, undefined, this)
1964
+ ]
1965
+ }, undefined, true, undefined, this);
1966
+ }
1967
+ if (currentField === "value") {
1968
+ const hint = secretType === "password" ? "Enter the password" : secretType === "otp" ? "Enter the TOTP base32 secret key" : "Enter the note content";
1969
+ const placeholder = secretType === "password" ? "password..." : secretType === "otp" ? "e.g. JBSWY3DPEHPK3PXP" : "note content...";
1970
+ return /* @__PURE__ */ jsxDEV("box", {
1971
+ flexDirection: "column",
1972
+ width: "100%",
1973
+ height: "100%",
1974
+ children: [
1975
+ /* @__PURE__ */ jsxDEV(Header, {
1976
+ title: "New Secret",
1977
+ subtitle: `${progress} \xB7 ${name}`
1978
+ }, undefined, false, undefined, this),
1979
+ /* @__PURE__ */ jsxDEV("box", {
1980
+ padding: 2,
1981
+ flexDirection: "column",
1982
+ gap: 1,
1983
+ children: [
1984
+ /* @__PURE__ */ jsxDEV("text", {
1985
+ fg: "#888888",
1986
+ children: hint
1987
+ }, undefined, false, undefined, this),
1988
+ inputRow(secretType === "note" ? "Note" : "Value", value, setValue, () => {
1989
+ if (value.trim())
1990
+ nextStep();
1991
+ }, placeholder)
1992
+ ]
1993
+ }, undefined, true, undefined, this),
1994
+ /* @__PURE__ */ jsxDEV(StatusBar, {
1995
+ message: "Enter next Esc back"
1996
+ }, undefined, false, undefined, this)
1997
+ ]
1998
+ }, undefined, true, undefined, this);
1999
+ }
2000
+ if (currentField === "username") {
2001
+ return /* @__PURE__ */ jsxDEV("box", {
2002
+ flexDirection: "column",
2003
+ width: "100%",
2004
+ height: "100%",
2005
+ children: [
2006
+ /* @__PURE__ */ jsxDEV(Header, {
2007
+ title: "New Secret",
2008
+ subtitle: `${progress} \xB7 optional`
2009
+ }, undefined, false, undefined, this),
2010
+ /* @__PURE__ */ jsxDEV("box", {
2011
+ padding: 2,
2012
+ flexDirection: "column",
2013
+ gap: 1,
2014
+ children: [
2015
+ /* @__PURE__ */ jsxDEV("text", {
2016
+ fg: "#888888",
2017
+ children: "Username or email (optional)"
2018
+ }, undefined, false, undefined, this),
2019
+ inputRow("Username", username, setUsername, nextStep, "skip with Enter")
2020
+ ]
2021
+ }, undefined, true, undefined, this),
2022
+ /* @__PURE__ */ jsxDEV(StatusBar, {
2023
+ message: "Enter next (empty = skip) Esc back"
2024
+ }, undefined, false, undefined, this)
2025
+ ]
2026
+ }, undefined, true, undefined, this);
2027
+ }
2028
+ if (currentField === "url") {
2029
+ return /* @__PURE__ */ jsxDEV("box", {
2030
+ flexDirection: "column",
2031
+ width: "100%",
2032
+ height: "100%",
2033
+ children: [
2034
+ /* @__PURE__ */ jsxDEV(Header, {
2035
+ title: "New Secret",
2036
+ subtitle: `${progress} \xB7 optional`
2037
+ }, undefined, false, undefined, this),
2038
+ /* @__PURE__ */ jsxDEV("box", {
2039
+ padding: 2,
2040
+ flexDirection: "column",
2041
+ gap: 1,
2042
+ children: [
2043
+ /* @__PURE__ */ jsxDEV("text", {
2044
+ fg: "#888888",
2045
+ children: "Website URL (optional)"
2046
+ }, undefined, false, undefined, this),
2047
+ inputRow("URL", url, setUrl, nextStep, "e.g. https://github.com")
2048
+ ]
2049
+ }, undefined, true, undefined, this),
2050
+ /* @__PURE__ */ jsxDEV(StatusBar, {
2051
+ message: "Enter next (empty = skip) Esc back"
2052
+ }, undefined, false, undefined, this)
2053
+ ]
2054
+ }, undefined, true, undefined, this);
2055
+ }
2056
+ if (currentField === "description") {
2057
+ return /* @__PURE__ */ jsxDEV("box", {
2058
+ flexDirection: "column",
2059
+ width: "100%",
2060
+ height: "100%",
2061
+ children: [
2062
+ /* @__PURE__ */ jsxDEV(Header, {
2063
+ title: "New Secret",
2064
+ subtitle: `${progress} \xB7 save`
2065
+ }, undefined, false, undefined, this),
2066
+ /* @__PURE__ */ jsxDEV("box", {
2067
+ padding: 2,
2068
+ flexDirection: "column",
2069
+ gap: 1,
2070
+ children: [
2071
+ /* @__PURE__ */ jsxDEV("text", {
2072
+ fg: "#888888",
2073
+ children: "Description (optional) \u2014 Enter to save"
2074
+ }, undefined, false, undefined, this),
2075
+ inputRow("Description", description, setDescription, doCreate, "skip with Enter"),
2076
+ /* @__PURE__ */ jsxDEV("box", {
2077
+ flexDirection: "column",
2078
+ border: true,
2079
+ borderStyle: "rounded",
2080
+ borderColor: "#333333",
2081
+ padding: 1,
2082
+ marginTop: 1,
2083
+ gap: 0,
2084
+ children: [
2085
+ /* @__PURE__ */ jsxDEV("text", {
2086
+ fg: "#555555",
2087
+ children: /* @__PURE__ */ jsxDEV("strong", {
2088
+ children: "Summary"
2089
+ }, undefined, false, undefined, this)
2090
+ }, undefined, false, undefined, this),
2091
+ /* @__PURE__ */ jsxDEV(KeyValue, {
2092
+ label: "Type",
2093
+ value: label
2094
+ }, undefined, false, undefined, this),
2095
+ /* @__PURE__ */ jsxDEV(KeyValue, {
2096
+ label: "Name",
2097
+ value: name,
2098
+ valueColor: "#FFD700"
2099
+ }, undefined, false, undefined, this),
2100
+ /* @__PURE__ */ jsxDEV(KeyValue, {
2101
+ label: "Value",
2102
+ value: "*".repeat(Math.min(value.length, 20)),
2103
+ valueColor: "#FF4444"
2104
+ }, undefined, false, undefined, this),
2105
+ username ? /* @__PURE__ */ jsxDEV(KeyValue, {
2106
+ label: "Username",
2107
+ value: username
2108
+ }, undefined, false, undefined, this) : null,
2109
+ url ? /* @__PURE__ */ jsxDEV(KeyValue, {
2110
+ label: "URL",
2111
+ value: url
2112
+ }, undefined, false, undefined, this) : null
2113
+ ]
2114
+ }, undefined, true, undefined, this)
2115
+ ]
2116
+ }, undefined, true, undefined, this),
2117
+ /* @__PURE__ */ jsxDEV(StatusBar, {
2118
+ message: "Enter save Esc back"
2119
+ }, undefined, false, undefined, this)
2120
+ ]
2121
+ }, undefined, true, undefined, this);
2122
+ }
2123
+ return null;
2124
+ }
2125
+ function EditScreen({ session, secret, onDone, onBack }) {
2126
+ let detail;
2127
+ try {
2128
+ detail = session.getSecret(secret.id);
2129
+ } catch {
2130
+ detail = secret;
2131
+ }
2132
+ const secretType = detail.type;
2133
+ const fields = EDIT_FIELDS.filter((f) => f.forTypes.includes(secretType));
2134
+ const [editing, setEditing] = useState(null);
2135
+ const [values, setValues] = useState({
2136
+ name: detail.name,
2137
+ value: detail.value ?? "",
2138
+ username: detail.username ?? "",
2139
+ url: detail.metadata?.url ? String(detail.metadata.url) : "",
2140
+ description: detail.metadata?.description ? String(detail.metadata.description) : ""
2141
+ });
2142
+ const [inputVal, setInputVal] = useState("");
2143
+ const inputValRef = useRef("");
2144
+ const [editGeneration, setEditGeneration] = useState(0);
2145
+ const doSave = useCallback(() => {
2146
+ try {
2147
+ const updates = {};
2148
+ if (values.name !== detail.name)
2149
+ updates.name = values.name;
2150
+ if (values.value !== (detail.value ?? ""))
2151
+ updates.value = values.value;
2152
+ if (values.username !== (detail.username ?? ""))
2153
+ updates.username = values.username;
2154
+ const metaUpdates = {};
2155
+ const oldUrl = detail.metadata?.url ? String(detail.metadata.url) : "";
2156
+ const oldDesc = detail.metadata?.description ? String(detail.metadata.description) : "";
2157
+ if (values.url !== oldUrl)
2158
+ metaUpdates.url = values.url || undefined;
2159
+ if (values.description !== oldDesc)
2160
+ metaUpdates.description = values.description || undefined;
2161
+ if (Object.keys(metaUpdates).length > 0)
2162
+ updates.metadata = metaUpdates;
2163
+ if (Object.keys(updates).length === 0) {
2164
+ onDone("No changes");
2165
+ return;
2166
+ }
2167
+ session.updateSecret(detail.id, updates);
2168
+ onDone(`"${values.name}" updated`);
2169
+ } catch (e) {
2170
+ onDone(`Error: ${e instanceof Error ? e.message : String(e)}`);
2171
+ }
2172
+ }, [values, detail, session, onDone]);
2173
+ useKeyboard((key) => {
2174
+ if (key.eventType !== "press")
2175
+ return;
2176
+ if (key.name === "escape") {
2177
+ if (editing) {
2178
+ setEditing(null);
2179
+ } else {
2180
+ onBack();
2181
+ }
2182
+ }
2183
+ if (key.ctrl && key.name === "s" && !editing) {
2184
+ doSave();
2185
+ }
2186
+ });
2187
+ if (editing) {
2188
+ const fieldDef = fields.find((f) => f.key === editing);
2189
+ return /* @__PURE__ */ jsxDEV("box", {
2190
+ flexDirection: "column",
2191
+ width: "100%",
2192
+ height: "100%",
2193
+ children: [
2194
+ /* @__PURE__ */ jsxDEV(Header, {
2195
+ title: `Edit ${fieldDef?.label ?? editing}`,
2196
+ subtitle: detail.name
2197
+ }, undefined, false, undefined, this),
2198
+ /* @__PURE__ */ jsxDEV("box", {
2199
+ padding: 2,
2200
+ flexDirection: "column",
2201
+ gap: 1,
2202
+ children: [
2203
+ /* @__PURE__ */ jsxDEV("text", {
2204
+ fg: "#888888",
2205
+ children: [
2206
+ "Current: ",
2207
+ /* @__PURE__ */ jsxDEV("span", {
2208
+ fg: "#555555",
2209
+ children: values[editing] || "(empty)"
2210
+ }, undefined, false, undefined, this)
2211
+ ]
2212
+ }, undefined, true, undefined, this),
2213
+ /* @__PURE__ */ jsxDEV("box", {
2214
+ flexDirection: "row",
2215
+ gap: 1,
2216
+ children: [
2217
+ /* @__PURE__ */ jsxDEV("text", {
2218
+ fg: "#AAAAAA",
2219
+ width: 12,
2220
+ children: "New value"
2221
+ }, undefined, false, undefined, this),
2222
+ /* @__PURE__ */ jsxDEV("input", {
2223
+ value: inputVal,
2224
+ onChange: (v) => {
2225
+ setInputVal(v);
2226
+ inputValRef.current = v;
2227
+ },
2228
+ onSubmit: () => {
2229
+ const saved = inputValRef.current;
2230
+ const field = editing;
2231
+ if (field) {
2232
+ setValues((prev) => ({ ...prev, [field]: saved }));
2233
+ }
2234
+ setEditing(null);
2235
+ setInputVal("");
2236
+ inputValRef.current = "";
2237
+ setEditGeneration((g) => g + 1);
2238
+ },
2239
+ placeholder: values[editing] || "enter new value...",
2240
+ focused: true,
2241
+ flexGrow: 1,
2242
+ backgroundColor: "#111111",
2243
+ focusedBackgroundColor: "#1a1a1a",
2244
+ textColor: "#FFFFFF"
2245
+ }, undefined, false, undefined, this)
2246
+ ]
2247
+ }, undefined, true, undefined, this)
2248
+ ]
2249
+ }, undefined, true, undefined, this),
2250
+ /* @__PURE__ */ jsxDEV(StatusBar, {
2251
+ message: "Enter save field Esc cancel"
2252
+ }, undefined, false, undefined, this)
2253
+ ]
2254
+ }, undefined, true, undefined, this);
2255
+ }
2256
+ const options = fields.map((f) => {
2257
+ let display;
2258
+ if (f.key === "value") {
2259
+ display = values[f.key] ? "***" : "(empty)";
2260
+ } else {
2261
+ display = values[f.key] || "(empty)";
2262
+ }
2263
+ return {
2264
+ name: `${f.label}: ${display}`,
2265
+ description: "Enter to edit",
2266
+ value: f.key
2267
+ };
2268
+ });
2269
+ return /* @__PURE__ */ jsxDEV("box", {
2270
+ flexDirection: "column",
2271
+ width: "100%",
2272
+ height: "100%",
2273
+ children: [
2274
+ /* @__PURE__ */ jsxDEV(Header, {
2275
+ title: "Edit Secret",
2276
+ subtitle: detail.name
2277
+ }, undefined, false, undefined, this),
2278
+ /* @__PURE__ */ jsxDEV("box", {
2279
+ flexGrow: 1,
2280
+ paddingX: 2,
2281
+ paddingTop: 1,
2282
+ children: /* @__PURE__ */ jsxDEV("select", {
2283
+ options,
2284
+ onSelect: (_i, opt) => {
2285
+ const val = opt.value ?? "";
2286
+ const current = values[val] ?? "";
2287
+ setEditing(val);
2288
+ setInputVal(current);
2289
+ inputValRef.current = current;
2290
+ },
2291
+ focused: true,
2292
+ height: Math.min(options.length * 2 + 1, 14),
2293
+ selectedBackgroundColor: "#222222",
2294
+ selectedTextColor: "#FFD700"
2295
+ }, editGeneration, false, undefined, this)
2296
+ }, undefined, false, undefined, this),
2297
+ /* @__PURE__ */ jsxDEV("box", {
2298
+ paddingX: 2,
2299
+ paddingBottom: 1,
2300
+ children: /* @__PURE__ */ jsxDEV("box", {
2301
+ border: true,
2302
+ borderStyle: "rounded",
2303
+ borderColor: "#FFD700",
2304
+ paddingX: 2,
2305
+ height: 3,
2306
+ flexDirection: "row",
2307
+ alignItems: "center",
2308
+ gap: 2,
2309
+ onMouseDown: doSave,
2310
+ children: /* @__PURE__ */ jsxDEV("text", {
2311
+ fg: "#FFD700",
2312
+ children: /* @__PURE__ */ jsxDEV("strong", {
2313
+ children: "Ctrl+S Save"
2314
+ }, undefined, false, undefined, this)
2315
+ }, undefined, false, undefined, this)
2316
+ }, undefined, false, undefined, this)
2317
+ }, undefined, false, undefined, this),
2318
+ /* @__PURE__ */ jsxDEV(StatusBar, {
2319
+ message: "Enter edit field Ctrl+S save Esc back"
2320
+ }, undefined, false, undefined, this)
2321
+ ]
2322
+ }, undefined, true, undefined, this);
2323
+ }
2324
+ function App({ vaultPath }) {
2325
+ const [screen, setScreen] = useState("password");
2326
+ const [session, setSession] = useState(null);
2327
+ const [selectedSecret, setSelectedSecret] = useState(null);
2328
+ const { toast, show: showToast } = useToast();
2329
+ const [refreshKey, setRefreshKey] = useState(0);
2330
+ const goHome = useCallback(() => {
2331
+ setScreen("home");
2332
+ setSelectedSecret(null);
2333
+ setRefreshKey((k) => k + 1);
2334
+ }, []);
2335
+ const handleDone = useCallback((msg) => {
2336
+ goHome();
2337
+ showToast(msg, msg.startsWith("Error:") ? "error" : "success");
2338
+ }, [goHome, showToast]);
2339
+ if (screen === "password") {
2340
+ return /* @__PURE__ */ jsxDEV(PasswordScreen, {
2341
+ vaultPath,
2342
+ onUnlock: (s) => {
2343
+ setSession(s);
2344
+ setScreen("home");
2345
+ }
2346
+ }, undefined, false, undefined, this);
2347
+ }
2348
+ if (!session)
2349
+ return /* @__PURE__ */ jsxDEV("box", {
2350
+ padding: 2,
2351
+ children: /* @__PURE__ */ jsxDEV("text", {
2352
+ fg: "#FF4444",
2353
+ children: "No session."
2354
+ }, undefined, false, undefined, this)
2355
+ }, undefined, false, undefined, this);
2356
+ switch (screen) {
2357
+ case "home":
2358
+ return /* @__PURE__ */ jsxDEV(HomeScreen, {
2359
+ session,
2360
+ onSelect: (s) => {
2361
+ setSelectedSecret(s);
2362
+ setScreen("detail");
2363
+ },
2364
+ onCreate: () => setScreen("create"),
2365
+ toast
2366
+ }, refreshKey, false, undefined, this);
2367
+ case "detail":
2368
+ return selectedSecret ? /* @__PURE__ */ jsxDEV(DetailScreen, {
2369
+ session,
2370
+ secret: selectedSecret,
2371
+ onBack: goHome,
2372
+ onDeleted: handleDone,
2373
+ onEdit: () => setScreen("edit")
2374
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(HomeScreen, {
2375
+ session,
2376
+ onSelect: (s) => {
2377
+ setSelectedSecret(s);
2378
+ setScreen("detail");
2379
+ },
2380
+ onCreate: () => setScreen("create"),
2381
+ toast
2382
+ }, refreshKey, false, undefined, this);
2383
+ case "edit":
2384
+ return selectedSecret ? /* @__PURE__ */ jsxDEV(EditScreen, {
2385
+ session,
2386
+ secret: selectedSecret,
2387
+ onDone: handleDone,
2388
+ onBack: () => setScreen("detail")
2389
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(HomeScreen, {
2390
+ session,
2391
+ onSelect: (s) => {
2392
+ setSelectedSecret(s);
2393
+ setScreen("detail");
2394
+ },
2395
+ onCreate: () => setScreen("create"),
2396
+ toast
2397
+ }, refreshKey, false, undefined, this);
2398
+ case "create":
2399
+ return /* @__PURE__ */ jsxDEV(CreateScreen, {
2400
+ session,
2401
+ onDone: handleDone,
2402
+ onBack: goHome
2403
+ }, undefined, false, undefined, this);
2404
+ default:
2405
+ return /* @__PURE__ */ jsxDEV(HomeScreen, {
2406
+ session,
2407
+ onSelect: (s) => {
2408
+ setSelectedSecret(s);
2409
+ setScreen("detail");
2410
+ },
2411
+ onCreate: () => setScreen("create"),
2412
+ toast
2413
+ }, refreshKey, false, undefined, this);
2414
+ }
2415
+ }
2416
+ async function runTui(vaultPath) {
2417
+ const vault = vaultPath ?? defaultVaultPath();
2418
+ const renderer = await createCliRenderer({ exitOnCtrlC: true });
2419
+ createRoot(renderer).render(/* @__PURE__ */ jsxDEV(App, {
2420
+ vaultPath: vault
2421
+ }, undefined, false, undefined, this));
2422
+ }
2423
+ var EDIT_FIELDS;
2424
+ var init_tui = __esm(() => {
2425
+ init_src3();
2426
+ EDIT_FIELDS = [
2427
+ { key: "name", label: "Name", forTypes: ["password", "note", "otp"] },
2428
+ { key: "value", label: "Value", forTypes: ["password", "note", "otp"] },
2429
+ { key: "username", label: "Username", forTypes: ["password", "otp"] },
2430
+ { key: "url", label: "URL", forTypes: ["password"] },
2431
+ { key: "description", label: "Description", forTypes: ["password", "note", "otp"] }
2432
+ ];
2433
+ });
2434
+
2435
+ // apps/cli/src/index.ts
2436
+ import { spawn } from "child_process";
2437
+ import { existsSync as existsSync4 } from "fs";
2438
+ import { createInterface } from "readline/promises";
2439
+ import { resolve as resolve3 } from "path";
2440
+
2441
+ // apps/cli/src/password.ts
2442
+ async function readPasswordMasked(prompt = "Master password: ") {
2443
+ if (!process.stdin.isTTY) {
2444
+ throw new Error("Cannot read password: stdin is not a TTY");
2445
+ }
2446
+ process.stdout.write(prompt);
2447
+ process.stdin.setRawMode(true);
2448
+ process.stdin.resume();
2449
+ process.stdin.setEncoding("utf8");
2450
+ let password = "";
2451
+ return new Promise((resolve, reject) => {
2452
+ const onData = (char) => {
2453
+ const code = char.charCodeAt(0);
2454
+ if (char === "\r" || char === `
2455
+ `) {
2456
+ cleanup();
2457
+ process.stdout.write(`
2458
+ `);
2459
+ resolve(password);
2460
+ return;
2461
+ }
2462
+ if (code === 3) {
2463
+ cleanup();
2464
+ process.stdout.write(`
2465
+ `);
2466
+ reject(new Error("Password input cancelled"));
2467
+ return;
2468
+ }
2469
+ if (code === 4) {
2470
+ cleanup();
2471
+ process.stdout.write(`
2472
+ `);
2473
+ resolve(password);
2474
+ return;
2475
+ }
2476
+ if (code === 127 || code === 8) {
2477
+ if (password.length > 0) {
2478
+ password = password.slice(0, -1);
2479
+ process.stdout.write("\b \b");
2480
+ }
2481
+ return;
2482
+ }
2483
+ if (code === 27) {
2484
+ return;
2485
+ }
2486
+ if (code >= 32) {
2487
+ password += char;
2488
+ process.stdout.write("*");
2489
+ }
2490
+ };
2491
+ const cleanup = () => {
2492
+ process.stdin.setRawMode(false);
2493
+ process.stdin.pause();
2494
+ process.stdin.removeListener("data", onData);
2495
+ };
2496
+ process.stdin.on("data", onData);
2497
+ });
2498
+ }
1048
2499
 
1049
2500
  // packages/core/src/daemon.ts
2501
+ init_src();
2502
+ init_src2();
2503
+ init_src3();
2504
+ init_paths();
2505
+ init_paths();
2506
+ import { createHash, timingSafeEqual } from "crypto";
2507
+ import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
1050
2508
  var DAEMON_TOKEN_SERVICE = "autho.daemon";
1051
2509
  function daemonTokenName(statePath) {
1052
2510
  return createHash("sha256").update(statePath).digest("hex");
@@ -1402,6 +2860,7 @@ async function daemonStop(options) {
1402
2860
  }
1403
2861
 
1404
2862
  // apps/cli/src/index.ts
2863
+ init_src3();
1405
2864
  function parseArgs(argv) {
1406
2865
  const dashDashIndex = argv.indexOf("--");
1407
2866
  const main = dashDashIndex === -1 ? argv : argv.slice(0, dashDashIndex);
@@ -1525,6 +2984,9 @@ async function askPassword(prompt, initial) {
1525
2984
  if (initial) {
1526
2985
  return initial;
1527
2986
  }
2987
+ if (process.stdin.isTTY) {
2988
+ return readPasswordMasked("Master password: ");
2989
+ }
1528
2990
  return prompt.ask("Master password: ");
1529
2991
  }
1530
2992
  async function runPromptMode(vaultPath, initialPassword) {
@@ -1620,44 +3082,56 @@ async function runWebServer(vaultPath, args) {
1620
3082
  }
1621
3083
  function help() {
1622
3084
  return [
1623
- "Autho Bun CLI",
3085
+ "Autho \u2013 Local-first secret manager for humans and coding agents",
3086
+ "",
3087
+ "Usage:",
3088
+ " autho Open interactive TUI (terminal UI)",
3089
+ " autho <command> Run a CLI command (see below)",
1624
3090
  "",
1625
3091
  "Commands:",
1626
- " prompt [--password <value>] [--vault <path>]",
1627
- " init --password <value> [--vault <path>]",
1628
- " status [--password <value>] [--vault <path>] [--project-file <path>] [--json]",
1629
- " project init --map <ENV_NAME=secretRef> [--map <ENV_NAME=secretRef>] [--output <path>] [--force] [--json]",
3092
+ " prompt [--vault <path>]",
3093
+ " init [--vault <path>]",
3094
+ " status [--vault <path>] [--project-file <path>] [--json]",
3095
+ " project init --map <ENV=ref> [--output <path>] [--force] [--json]",
1630
3096
  " web serve [--vault <path>] [--host <value>] [--port <value>]",
1631
3097
  " daemon serve [--vault <path>] [--state-file <path>] [--host <value>] [--port <value>]",
1632
3098
  " daemon status [--state-file <path>] [--json]",
1633
- " daemon unlock --password <value> [--ttl <seconds>] [--state-file <path>] [--json]",
3099
+ " daemon unlock [--ttl <seconds>] [--state-file <path>] [--json]",
1634
3100
  " daemon lock --session <id> [--state-file <path>] [--json]",
1635
3101
  " daemon stop [--state-file <path>] [--json]",
1636
- " daemon env render --session <id> --map <ENV_NAME=secretRef> [--project-file <path>] [--lease <lease-id>] [--state-file <path>] [--json]",
1637
- " daemon exec --session <id> --map <ENV_NAME=secretRef> [--project-file <path>] [--lease <lease-id>] [--state-file <path>] -- <command>",
1638
- " import legacy --password <value> --file <path> [--skip-existing] [--vault <path>] [--json]",
1639
- " secrets add --password <value> --name <name> --type <password|note|otp> --value <value> [--username <value>] [--url <value>] [--description <value>] [--digits <value>] [--algorithm <value>] [--vault <path>]",
1640
- " secrets list --password <value> [--vault <path>] [--json]",
1641
- " secrets get --password <value> --ref <name-or-id> [--vault <path>] [--json]",
1642
- " secrets rm --password <value> --ref <name-or-id> [--vault <path>] [--json]",
1643
- " otp code --password <value> --ref <name-or-id> [--vault <path>] [--json]",
1644
- " lease create --password <value> --secret <name-or-id> [--secret <name-or-id>] --ttl <seconds> [--name <value>] [--vault <path>] [--json]",
1645
- " lease revoke --password <value> --lease <lease-id> [--vault <path>] [--json]",
1646
- " env render --password <value> --map <ENV_NAME=secretRef> [--map <ENV_NAME=secretRef>] [--project-file <path>] [--lease <lease-id>] [--vault <path>] [--json]",
1647
- " env sync --password <value> --map <ENV_NAME=secretRef> [--project-file <path>] [--lease <lease-id>] [--ttl <seconds>] [--output <path>] [--force] [--vault <path>] [--json]",
1648
- " exec --password <value> --map <ENV_NAME=secretRef> [--project-file <path>] [--lease <lease-id>] [--vault <path>] -- <command>",
1649
- " file encrypt --password <value> --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
1650
- " file decrypt --password <value> --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
1651
- " files encrypt --password <value> --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
1652
- " files decrypt --password <value> --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
1653
- " audit list --password <value> [--limit <number>] [--vault <path>] [--json]",
3102
+ " daemon env render --session <id> --map <ENV=ref> [--project-file <path>] [--lease <id>] [--state-file <path>] [--json]",
3103
+ " daemon exec --session <id> --map <ENV=ref> [--project-file <path>] [--lease <id>] [--state-file <path>] -- <cmd>",
3104
+ " import legacy --file <path> [--skip-existing] [--vault <path>] [--json]",
3105
+ " secrets add --name <name> --type <password|note|otp> --value <value> [--username <v>] [--url <v>] [--description <v>] [--digits <v>] [--algorithm <v>] [--vault <path>]",
3106
+ " secrets list [--vault <path>] [--json]",
3107
+ " secrets get --ref <name-or-id> [--vault <path>] [--json]",
3108
+ " secrets rm --ref <name-or-id> [--vault <path>] [--json]",
3109
+ " secrets edit --ref <name-or-id> [--new-name <v>] [--value <v>] [--username <v>] [--url <v>] [--description <v>] [--vault <path>] [--json]",
3110
+ " otp code --ref <name-or-id> [--vault <path>] [--json]",
3111
+ " lease create --secret <ref> [--secret <ref>] --ttl <seconds> [--name <v>] [--vault <path>] [--json]",
3112
+ " lease revoke --lease <id> [--vault <path>] [--json]",
3113
+ " env render --map <ENV=ref> [--project-file <path>] [--lease <id>] [--vault <path>] [--json]",
3114
+ " env sync --map <ENV=ref> [--project-file <path>] [--lease <id>] [--ttl <seconds>] [--output <path>] [--force] [--vault <path>] [--json]",
3115
+ " exec --map <ENV=ref> [--project-file <path>] [--lease <id>] [--vault <path>] -- <cmd>",
3116
+ " file encrypt --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
3117
+ " file decrypt --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
3118
+ " files encrypt --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
3119
+ " files decrypt --input <path> [--output <path>] [--force] [--vault <path>] [--json]",
3120
+ " audit list [--limit <number>] [--vault <path>] [--json]",
3121
+ "",
3122
+ "Authentication:",
3123
+ " When running interactively (TTY), you will be securely prompted for your",
3124
+ " master password with masked input (no --password flag needed).",
3125
+ "",
3126
+ " For automation and coding agents, use one of:",
3127
+ " AUTHO_MASTER_PASSWORD=<value> Environment variable (recommended for agents)",
3128
+ " --password <value> CLI flag (visible in shell history - avoid!)",
1654
3129
  "",
1655
3130
  "Notes:",
1656
- " Running `autho` with no command enters interactive prompt mode.",
1657
- " The default vault path is ~/.autho/vault.db (or AUTHO_HOME/vault.db).",
1658
- " The default project file is ~/.autho/project.json when it exists (or AUTHO_HOME/project.json).",
1659
- " The default daemon state file is ~/.autho/daemon.json (or AUTHO_HOME/daemon.json).",
1660
- " AUTHO_MASTER_PASSWORD can be used instead of --password."
3131
+ " Running `autho` with no arguments opens the interactive TUI.",
3132
+ " The default vault path is ~/.autho/vault.db (override with AUTHO_HOME).",
3133
+ " The default project file is ~/.autho/project.json.",
3134
+ " The default daemon state file is ~/.autho/daemon.json."
1661
3135
  ].join(`
1662
3136
  `);
1663
3137
  }
@@ -1670,8 +3144,13 @@ async function main() {
1670
3144
  const explicitProjectFile = getString(args, "project-file");
1671
3145
  const fallbackProjectFile = defaultProjectFilePath();
1672
3146
  const projectFile = explicitProjectFile ?? (existsSync4(fallbackProjectFile) ? fallbackProjectFile : undefined);
1673
- const password = getString(args, "password") ?? process.env.AUTHO_MASTER_PASSWORD;
3147
+ let password = getString(args, "password") ?? process.env.AUTHO_MASTER_PASSWORD;
1674
3148
  if (!scope) {
3149
+ if (process.stdin.isTTY) {
3150
+ const { runTui: runTui2 } = await Promise.resolve().then(() => (init_tui(), exports_tui));
3151
+ await runTui2(vaultPath);
3152
+ return;
3153
+ }
1675
3154
  await runPromptMode(vaultPath, password);
1676
3155
  return;
1677
3156
  }
@@ -1683,6 +3162,24 @@ async function main() {
1683
3162
  await runPromptMode(vaultPath, password);
1684
3163
  return;
1685
3164
  }
3165
+ if (!password && process.stdin.isTTY) {
3166
+ const needsPassword = [
3167
+ "init",
3168
+ "secrets",
3169
+ "otp",
3170
+ "lease",
3171
+ "env",
3172
+ "exec",
3173
+ "file",
3174
+ "files",
3175
+ "audit",
3176
+ "import"
3177
+ ].includes(scope);
3178
+ const daemonNeedsPassword = scope === "daemon" && action === "unlock";
3179
+ if (needsPassword || daemonNeedsPassword) {
3180
+ password = await readPasswordMasked("Master password: ");
3181
+ }
3182
+ }
1686
3183
  if (scope === "init") {
1687
3184
  output(VaultService.initialize(vaultPath, required(password, "--password")), jsonMode);
1688
3185
  return;
@@ -1791,6 +3288,23 @@ async function main() {
1791
3288
  output(session.removeSecret(required(ref, "--ref")), jsonMode);
1792
3289
  return;
1793
3290
  }
3291
+ if (scope === "secrets" && action === "edit") {
3292
+ const ref = getString(args, "ref") ?? getString(args, "name") ?? getString(args, "id");
3293
+ const updates = {};
3294
+ if (getString(args, "new-name"))
3295
+ updates.name = getString(args, "new-name");
3296
+ if (getString(args, "value"))
3297
+ updates.value = getString(args, "value");
3298
+ if (getString(args, "username"))
3299
+ updates.username = getString(args, "username");
3300
+ if (getString(args, "type"))
3301
+ updates.type = getString(args, "type");
3302
+ const meta = buildSecretMetadata(args);
3303
+ if (Object.keys(meta).length > 0)
3304
+ updates.metadata = meta;
3305
+ output(session.updateSecret(required(ref, "--ref"), updates), jsonMode);
3306
+ return;
3307
+ }
1794
3308
  if (scope === "otp" && action === "code") {
1795
3309
  const ref = getString(args, "ref") ?? getString(args, "name") ?? getString(args, "id");
1796
3310
  output(session.generateOtp(required(ref, "--ref")), jsonMode);