autho 3.0.1 → 3.0.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/autho.js +175 -39
  2. package/package.json +1 -1
package/dist/autho.js CHANGED
@@ -374,7 +374,7 @@ var init_src2 = __esm(() => {
374
374
  });
375
375
 
376
376
  // packages/core/src/paths.ts
377
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, writeFileSync } from "fs";
377
+ import { chmodSync as chmodSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
378
378
  import { homedir } from "os";
379
379
  import { dirname as dirname2, join, resolve } from "path";
380
380
  function normalizePath(path) {
@@ -388,9 +388,51 @@ function tryChmod2(path, mode) {
388
388
  chmodSync2(path, mode);
389
389
  } catch {}
390
390
  }
391
- function authoHomeDir() {
391
+ function authoConfigDir() {
392
392
  return normalizePath(process.env.AUTHO_HOME ?? join(homedir(), ".autho"));
393
393
  }
394
+ function configFilePath() {
395
+ return normalizePath(join(authoConfigDir(), "config.json"));
396
+ }
397
+ function loadConfig() {
398
+ if (configCache.loaded)
399
+ return configCache.value ?? {};
400
+ const path = configFilePath();
401
+ if (!existsSync2(path)) {
402
+ configCache.loaded = true;
403
+ configCache.value = {};
404
+ return {};
405
+ }
406
+ try {
407
+ const raw = JSON.parse(readFileSync(path, "utf8"));
408
+ configCache.loaded = true;
409
+ configCache.value = raw;
410
+ return raw;
411
+ } catch {
412
+ configCache.loaded = true;
413
+ configCache.value = {};
414
+ return {};
415
+ }
416
+ }
417
+ function saveConfig(config) {
418
+ const path = configFilePath();
419
+ ensurePrivateParent(path);
420
+ writeFileSync(path, JSON.stringify(config, null, 2) + `
421
+ `, { encoding: "utf8", mode: 384 });
422
+ hardenFilePermissions(path);
423
+ configCache.value = config;
424
+ configCache.loaded = true;
425
+ }
426
+ function authoHomeDir() {
427
+ if (process.env.AUTHO_HOME) {
428
+ return normalizePath(process.env.AUTHO_HOME);
429
+ }
430
+ const config = loadConfig();
431
+ if (config.vaultDir) {
432
+ return normalizePath(config.vaultDir);
433
+ }
434
+ return normalizePath(join(homedir(), ".autho"));
435
+ }
394
436
  function defaultVaultPath() {
395
437
  return normalizePath(join(authoHomeDir(), "vault.db"));
396
438
  }
@@ -420,13 +462,16 @@ function writeBinaryFileSecure(path, content) {
420
462
  writeFileSync(path, content, { mode: 384 });
421
463
  hardenFilePermissions(path);
422
464
  }
423
- var init_paths = () => {};
465
+ var configCache;
466
+ var init_paths = __esm(() => {
467
+ configCache = { value: null, loaded: false };
468
+ });
424
469
 
425
470
  // packages/core/src/artifacts.ts
426
471
  import { randomBytes as randomBytes2 } from "crypto";
427
472
  import {
428
473
  readdirSync,
429
- readFileSync,
474
+ readFileSync as readFileSync2,
430
475
  statSync
431
476
  } from "fs";
432
477
  import { basename, join as join2, relative, resolve as resolve2, sep } from "path";
@@ -462,7 +507,7 @@ function defaultDecryptedFolderPath(inputPath) {
462
507
  }
463
508
  function encryptFileArtifact(inputPath, outputPath, rootKey) {
464
509
  const fileKey = randomBytes2(32);
465
- const payload = encryptWithKey(readFileSync(inputPath), fileKey, `autho:file:${basename(inputPath)}`);
510
+ const payload = encryptWithKey(readFileSync2(inputPath), fileKey, `autho:file:${basename(inputPath)}`);
466
511
  const envelope = {
467
512
  kind: "file",
468
513
  originalName: basename(inputPath),
@@ -474,7 +519,7 @@ function encryptFileArtifact(inputPath, outputPath, rootKey) {
474
519
  return { outputPath };
475
520
  }
476
521
  function decryptFileArtifact(inputPath, outputPath, rootKey) {
477
- const envelope = JSON.parse(readFileSync(inputPath, "utf8"));
522
+ const envelope = JSON.parse(readFileSync2(inputPath, "utf8"));
478
523
  if (envelope.kind !== "file" || envelope.version !== 1) {
479
524
  throw new Error(`Unsupported file artifact: ${inputPath}`);
480
525
  }
@@ -492,7 +537,7 @@ function encryptFolderArtifact(inputPath, outputPath, rootKey) {
492
537
  const relativePath = normalizeRelativePath(relative(inputPath, filePath));
493
538
  return {
494
539
  path: relativePath,
495
- payload: encryptWithKey(readFileSync(filePath), folderKey, `autho:folder:${relativePath}`)
540
+ payload: encryptWithKey(readFileSync2(filePath), folderKey, `autho:folder:${relativePath}`)
496
541
  };
497
542
  }),
498
543
  kind: "folder",
@@ -507,7 +552,7 @@ function encryptFolderArtifact(inputPath, outputPath, rootKey) {
507
552
  };
508
553
  }
509
554
  function decryptFolderArtifact(inputPath, outputPath, rootKey) {
510
- const envelope = JSON.parse(readFileSync(inputPath, "utf8"));
555
+ const envelope = JSON.parse(readFileSync2(inputPath, "utf8"));
511
556
  if (envelope.kind !== "folder" || envelope.version !== 1) {
512
557
  throw new Error(`Unsupported folder artifact: ${inputPath}`);
513
558
  }
@@ -548,8 +593,8 @@ var init_artifacts = __esm(() => {
548
593
  import { spawnSync } from "child_process";
549
594
  import { createHmac as createHmac2, randomBytes as randomBytes3 } from "crypto";
550
595
  import {
551
- existsSync as existsSync2,
552
- readFileSync as readFileSync2
596
+ existsSync as existsSync3,
597
+ readFileSync as readFileSync3
553
598
  } from "fs";
554
599
  import { basename as basename2 } from "path";
555
600
  function requireValue(value, label) {
@@ -604,7 +649,7 @@ function normalizeSecretType(type) {
604
649
  throw new Error(`Unsupported secret type: ${type}`);
605
650
  }
606
651
  function parseProjectMappings(projectFile) {
607
- const raw = JSON.parse(readFileSync2(projectFile, "utf8"));
652
+ const raw = JSON.parse(readFileSync3(projectFile, "utf8"));
608
653
  return Object.entries(raw.env ?? {}).map(([envName, secretRef]) => ({
609
654
  envName,
610
655
  secretRef
@@ -682,7 +727,7 @@ function summarizeCommand(cmd) {
682
727
  };
683
728
  }
684
729
  function projectMappingsForStatus(projectFile) {
685
- if (!projectFile || !existsSync2(projectFile)) {
730
+ if (!projectFile || !existsSync3(projectFile)) {
686
731
  return {
687
732
  mappings: [],
688
733
  path: projectFile ?? null
@@ -705,7 +750,7 @@ function resolveMappings(options) {
705
750
  };
706
751
  });
707
752
  if (options.projectFile) {
708
- if (!existsSync2(options.projectFile)) {
753
+ if (!existsSync3(options.projectFile)) {
709
754
  throw new Error(`Project mapping file not found: ${options.projectFile}`);
710
755
  }
711
756
  return [...parseProjectMappings(options.projectFile), ...fromMaps];
@@ -716,7 +761,7 @@ function writeProjectConfig(input) {
716
761
  if (input.mappings.length === 0) {
717
762
  throw new Error("Provide at least one env mapping");
718
763
  }
719
- if (!input.force && existsSync2(input.outputPath)) {
764
+ if (!input.force && existsSync3(input.outputPath)) {
720
765
  throw new Error(`Project config already exists: ${input.outputPath}`);
721
766
  }
722
767
  const env = Object.fromEntries(input.mappings.map((mapping) => [mapping.envName, mapping.secretRef]));
@@ -1157,7 +1202,7 @@ class VaultSession {
1157
1202
  };
1158
1203
  }
1159
1204
  importLegacyFile(filePath, options) {
1160
- const raw = JSON.parse(readFileSync2(filePath, "utf8"));
1205
+ const raw = JSON.parse(readFileSync3(filePath, "utf8"));
1161
1206
  let imported = 0;
1162
1207
  let skipped = 0;
1163
1208
  for (const entry of raw) {
@@ -1290,7 +1335,7 @@ class VaultSession {
1290
1335
  }
1291
1336
  syncEnvFile(input) {
1292
1337
  const env = this.buildEnv(input.mappings, input.leaseId);
1293
- if (!input.force && existsSync2(input.outputPath)) {
1338
+ if (!input.force && existsSync3(input.outputPath)) {
1294
1339
  throw new Error(`Env file already exists: ${input.outputPath}`);
1295
1340
  }
1296
1341
  const createdAt = new Date().toISOString();
@@ -1342,7 +1387,7 @@ class VaultSession {
1342
1387
  encryptFile(inputPath, outputPath, options) {
1343
1388
  assertPathIsFile(inputPath);
1344
1389
  const resolvedOutput = outputPath ?? defaultEncryptedFilePath(inputPath);
1345
- if (!options?.force && existsSync2(resolvedOutput)) {
1390
+ if (!options?.force && existsSync3(resolvedOutput)) {
1346
1391
  throw new Error(`Output file already exists: ${resolvedOutput}`);
1347
1392
  }
1348
1393
  const result = encryptFileArtifact(inputPath, resolvedOutput, this.rootKey);
@@ -1354,7 +1399,7 @@ class VaultSession {
1354
1399
  decryptFile(inputPath, outputPath, options) {
1355
1400
  assertPathIsFile(inputPath);
1356
1401
  const resolvedOutput = outputPath ?? defaultDecryptedFilePath(inputPath);
1357
- if (!options?.force && existsSync2(resolvedOutput)) {
1402
+ if (!options?.force && existsSync3(resolvedOutput)) {
1358
1403
  throw new Error(`Output file already exists: ${resolvedOutput}`);
1359
1404
  }
1360
1405
  const result = decryptFileArtifact(inputPath, resolvedOutput, this.rootKey);
@@ -1366,7 +1411,7 @@ class VaultSession {
1366
1411
  encryptFolder(inputPath, outputPath, options) {
1367
1412
  assertPathIsDirectory(inputPath);
1368
1413
  const resolvedOutput = outputPath ?? defaultEncryptedFolderPath(inputPath);
1369
- if (!options?.force && existsSync2(resolvedOutput)) {
1414
+ if (!options?.force && existsSync3(resolvedOutput)) {
1370
1415
  throw new Error(`Output file already exists: ${resolvedOutput}`);
1371
1416
  }
1372
1417
  const result = encryptFolderArtifact(inputPath, resolvedOutput, this.rootKey);
@@ -1379,7 +1424,7 @@ class VaultSession {
1379
1424
  decryptFolder(inputPath, outputPath, options) {
1380
1425
  assertPathIsFile(inputPath);
1381
1426
  const resolvedOutput = outputPath ?? defaultDecryptedFolderPath(inputPath);
1382
- if (!options?.force && existsSync2(resolvedOutput)) {
1427
+ if (!options?.force && existsSync3(resolvedOutput)) {
1383
1428
  throw new Error(`Output path already exists: ${resolvedOutput}`);
1384
1429
  }
1385
1430
  const result = decryptFolderArtifact(inputPath, resolvedOutput, this.rootKey);
@@ -1867,8 +1912,8 @@ function UnlockScreen({ onUnlock, vaultPath }) {
1867
1912
  const recoveryFile = process.env.AUTHO_RECOVERY_FILE;
1868
1913
  if (recoveryFile) {
1869
1914
  try {
1870
- const { readFileSync: readFileSync4 } = await import("fs");
1871
- const content = readFileSync4(recoveryFile, "utf8");
1915
+ const { readFileSync: readFileSync5 } = await import("fs");
1916
+ const content = readFileSync5(recoveryFile, "utf8");
1872
1917
  const lines = content.split(`
1873
1918
  `);
1874
1919
  const idx = lines.findIndex((l) => l.trim() === "RECOVERY TOKEN:");
@@ -3123,7 +3168,7 @@ var init_tui = __esm(() => {
3123
3168
 
3124
3169
  // apps/cli/src/index.ts
3125
3170
  import { spawn } from "child_process";
3126
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
3171
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync2 } from "fs";
3127
3172
  import { createInterface as createInterface2 } from "readline/promises";
3128
3173
  import { resolve as resolve4 } from "path";
3129
3174
 
@@ -3211,7 +3256,7 @@ init_src3();
3211
3256
  init_paths();
3212
3257
  init_paths();
3213
3258
  import { createHash, timingSafeEqual } from "crypto";
3214
- import { existsSync as existsSync3, readFileSync as readFileSync3, rmSync } from "fs";
3259
+ import { existsSync as existsSync4, readFileSync as readFileSync4, rmSync } from "fs";
3215
3260
  var DAEMON_TOKEN_SERVICE = "autho.daemon";
3216
3261
  function daemonTokenName(statePath) {
3217
3262
  return createHash("sha256").update(statePath).digest("hex");
@@ -3269,10 +3314,10 @@ async function deleteStoredDaemonToken(state) {
3269
3314
  } catch {}
3270
3315
  }
3271
3316
  function readDaemonState(statePath) {
3272
- if (!existsSync3(statePath)) {
3317
+ if (!existsSync4(statePath)) {
3273
3318
  return null;
3274
3319
  }
3275
- const stored = JSON.parse(readFileSync3(statePath, "utf8"));
3320
+ const stored = JSON.parse(readFileSync4(statePath, "utf8"));
3276
3321
  return {
3277
3322
  pid: stored.pid,
3278
3323
  port: stored.port,
@@ -3301,7 +3346,7 @@ async function writeDaemonState(statePath, state) {
3301
3346
  async function deleteDaemonState(statePath) {
3302
3347
  const state = readDaemonState(statePath);
3303
3348
  await deleteStoredDaemonToken(state);
3304
- if (existsSync3(statePath)) {
3349
+ if (existsSync4(statePath)) {
3305
3350
  rmSync(statePath, { force: true });
3306
3351
  }
3307
3352
  }
@@ -3488,12 +3533,12 @@ async function startDaemonServer(options) {
3488
3533
  }
3489
3534
  async function waitForDaemonStateDeletion(statePath) {
3490
3535
  for (let attempt = 0;attempt < 20; attempt += 1) {
3491
- if (!existsSync3(statePath)) {
3536
+ if (!existsSync4(statePath)) {
3492
3537
  return true;
3493
3538
  }
3494
3539
  await Bun.sleep(25);
3495
3540
  }
3496
- return !existsSync3(statePath);
3541
+ return !existsSync4(statePath);
3497
3542
  }
3498
3543
  async function daemonRequest(state, path, body) {
3499
3544
  const token = await resolveDaemonToken(state);
@@ -3922,6 +3967,10 @@ function help() {
3922
3967
  " prompt [--vault <path>]",
3923
3968
  " init [--vault <path>]",
3924
3969
  " status [--vault <path>] [--project-file <path>] [--json]",
3970
+ " config [show] Show current configuration",
3971
+ " config set <key> <value> Set a config value",
3972
+ " config unset <key> Remove a config value",
3973
+ " config path Print config file path",
3925
3974
  " project init --map <ENV=ref> [--output <path>] [--force] [--json]",
3926
3975
  " web serve [--vault <path>] [--host <value>] [--port <value>]",
3927
3976
  " daemon serve [--vault <path>] [--state-file <path>] [--host <value>] [--port <value>]",
@@ -3976,9 +4025,12 @@ function help() {
3976
4025
  "",
3977
4026
  "Notes:",
3978
4027
  " Running `autho` with no arguments opens the interactive TUI.",
3979
- " The default vault path is ~/.autho/vault.db (override with AUTHO_HOME).",
3980
- " The default project file is ~/.autho/project.json.",
3981
- " The default daemon state file is ~/.autho/daemon.json."
4028
+ " Config is stored in ~/.autho/config.json (always at ~/.autho).",
4029
+ " The vault directory can be customized via `autho init` or `autho config set vaultDir <path>`.",
4030
+ " Config keys: vaultDir, defaultLeaseTtl, editor, autoLock, autoLockTimeout",
4031
+ " The default vault path is ~/.autho/vault.db (override with config or AUTHO_HOME).",
4032
+ " The default project file is <vaultDir>/project.json.",
4033
+ " The default daemon state file is <vaultDir>/daemon.json."
3982
4034
  ].join(`
3983
4035
  `);
3984
4036
  }
@@ -3990,7 +4042,7 @@ async function main() {
3990
4042
  const statePath = absolutePath(getString(args, "state-file") ?? defaultDaemonStatePath());
3991
4043
  const explicitProjectFile = getString(args, "project-file");
3992
4044
  const fallbackProjectFile = defaultProjectFilePath();
3993
- const projectFile = explicitProjectFile ?? (existsSync4(fallbackProjectFile) ? fallbackProjectFile : undefined);
4045
+ const projectFile = explicitProjectFile ?? (existsSync5(fallbackProjectFile) ? fallbackProjectFile : undefined);
3994
4046
  let password = getString(args, "password") ?? process.env.AUTHO_MASTER_PASSWORD;
3995
4047
  if (!password) {
3996
4048
  password = await loadVaultPassword(vaultPath) ?? undefined;
@@ -4032,17 +4084,35 @@ async function main() {
4032
4084
  }
4033
4085
  }
4034
4086
  if (scope === "init") {
4035
- const existingStatus = VaultService.status(vaultPath);
4087
+ let effectiveVaultPath = vaultPath;
4088
+ if (!VaultService.status(vaultPath).initialized && process.stdin.isTTY && !jsonMode) {
4089
+ const config = loadConfig();
4090
+ const currentDir = config.vaultDir ?? authoConfigDir();
4091
+ console.log(`
4092
+ \x1B[1mVault directory\x1B[0m`);
4093
+ console.log(` Default: ${currentDir}`);
4094
+ console.log(" Tip: Use a cloud-synced folder (e.g. Google Drive) to share the vault across machines.");
4095
+ const customDir = (await readLine(` Path (Enter to keep default): `)).trim();
4096
+ if (customDir && customDir !== currentDir) {
4097
+ const resolved = resolve4(customDir).replace(/\\/g, "/");
4098
+ saveConfig({ ...config, vaultDir: resolved });
4099
+ effectiveVaultPath = resolved + "/vault.db";
4100
+ console.log(` \x1B[32m\u2713\x1B[0m Vault directory set to ${resolved}`);
4101
+ console.log(` Config saved to ${authoConfigDir()}/config.json`);
4102
+ }
4103
+ console.log("");
4104
+ }
4105
+ const existingStatus = VaultService.status(effectiveVaultPath);
4036
4106
  if (!existingStatus.initialized) {
4037
4107
  const pw = required(password, "--password");
4038
- output(VaultService.initialize(vaultPath, pw), jsonMode);
4108
+ output(VaultService.initialize(effectiveVaultPath, pw), jsonMode);
4039
4109
  if (process.stdin.isTTY && !jsonMode) {
4040
- await runInitWizard(vaultPath, pw);
4110
+ await runInitWizard(effectiveVaultPath, pw);
4041
4111
  }
4042
4112
  } else {
4043
- const creds2 = await resolveUnlockCredentials(vaultPath, args, password);
4113
+ const creds2 = await resolveUnlockCredentials(effectiveVaultPath, args, password);
4044
4114
  if (process.stdin.isTTY && !jsonMode) {
4045
- await runInitWizard(vaultPath, creds2.password, creds2);
4115
+ await runInitWizard(effectiveVaultPath, creds2.password, creds2);
4046
4116
  } else {
4047
4117
  console.log("Vault already initialized. Use interactive mode (TTY) to reconfigure.");
4048
4118
  }
@@ -4056,6 +4126,72 @@ async function main() {
4056
4126
  }), jsonMode);
4057
4127
  return;
4058
4128
  }
4129
+ if (scope === "config") {
4130
+ const config = loadConfig();
4131
+ if (!action || action === "show") {
4132
+ if (jsonMode) {
4133
+ output({ configDir: authoConfigDir(), config }, jsonMode);
4134
+ } else {
4135
+ console.log(`Config: ${authoConfigDir()}/config.json`);
4136
+ console.log(`Vault dir: ${config.vaultDir ?? authoConfigDir()} ${config.vaultDir ? "(custom)" : "(default)"}`);
4137
+ if (config.defaultLeaseTtl)
4138
+ console.log(`Default lease TTL: ${config.defaultLeaseTtl}`);
4139
+ if (config.editor)
4140
+ console.log(`Editor: ${config.editor}`);
4141
+ if (config.autoLock !== undefined)
4142
+ console.log(`Auto-lock: ${config.autoLock}`);
4143
+ if (config.autoLockTimeout)
4144
+ console.log(`Auto-lock timeout: ${config.autoLockTimeout}`);
4145
+ }
4146
+ return;
4147
+ }
4148
+ if (action === "set") {
4149
+ const key = subaction;
4150
+ const value = process.argv[process.argv.indexOf("set") + 2];
4151
+ if (!key || value === undefined) {
4152
+ console.error("Usage: autho config set <key> <value>");
4153
+ console.error("Keys: vaultDir, defaultLeaseTtl, editor, autoLock, autoLockTimeout");
4154
+ process.exit(1);
4155
+ }
4156
+ const updated = { ...config };
4157
+ if (key === "autoLock") {
4158
+ updated.autoLock = value === "true";
4159
+ } else if (key === "vaultDir") {
4160
+ updated.vaultDir = value;
4161
+ } else if (key === "defaultLeaseTtl") {
4162
+ updated.defaultLeaseTtl = value;
4163
+ } else if (key === "editor") {
4164
+ updated.editor = value;
4165
+ } else if (key === "autoLockTimeout") {
4166
+ updated.autoLockTimeout = value;
4167
+ } else {
4168
+ console.error(`Unknown config key: ${key}`);
4169
+ console.error("Keys: vaultDir, defaultLeaseTtl, editor, autoLock, autoLockTimeout");
4170
+ process.exit(1);
4171
+ }
4172
+ saveConfig(updated);
4173
+ console.log(`Set ${key} = ${value}`);
4174
+ return;
4175
+ }
4176
+ if (action === "unset") {
4177
+ const key = subaction;
4178
+ if (!key) {
4179
+ console.error("Usage: autho config unset <key>");
4180
+ process.exit(1);
4181
+ }
4182
+ const updated = { ...config };
4183
+ delete updated[key];
4184
+ saveConfig(updated);
4185
+ console.log(`Removed ${key}`);
4186
+ return;
4187
+ }
4188
+ if (action === "path") {
4189
+ console.log(authoConfigDir() + "/config.json");
4190
+ return;
4191
+ }
4192
+ console.error(`Unknown config action: ${action}. Use: show, set, unset, path`);
4193
+ process.exit(1);
4194
+ }
4059
4195
  if (scope === "project" && action === "init") {
4060
4196
  output(writeProjectConfig({
4061
4197
  force: getBoolean(args, "force"),
@@ -4167,7 +4303,7 @@ async function main() {
4167
4303
  }
4168
4304
  if (scope === "unlock" && getString(args, "recovery-file")) {
4169
4305
  const recoveryFilePath = absolutePath(getString(args, "recovery-file"));
4170
- const content = readFileSync4(recoveryFilePath, "utf8");
4306
+ const content = readFileSync5(recoveryFilePath, "utf8");
4171
4307
  const lines = content.split(`
4172
4308
  `);
4173
4309
  const tokenLineIdx = lines.findIndex((l) => l.trim() === "RECOVERY TOKEN:");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autho",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "Local-first secret manager for humans and coding agents, rebuilt on Bun",
5
5
  "type": "module",
6
6
  "bin": {