mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-28 08:22:03 +00:00
feat(cli): .seedpass backup naming (phase 1 -- convention only, zero format change)
A portable backup is an encrypted, checksummed, cross-impl-verified file; its only sin was landing on disk as an anonymous .json. Exports now self-identify: point `vault export` (or the TUI database export) at a directory and the file is named seedpass-<fingerprint>-<date>.seedpass -- tied to the profile it restores and sortable by date. The TUI also appends .seedpass to an extensionless destination, since interactive input has no script-compatibility concerns. The zero-risk boundary, kept deliberately: an explicit file path is used exactly as typed -- a script that exports to backup.json still gets backup.json -- and imports sniff content, never extensions, so any old export restores unchanged. No byte of the format moved, which is what makes this safe to ship ahead of the review gate while format_version 2 (magic header) and the OS icon association stay queued behind it. Tests pin all three behaviours in both surfaces: directory target gets the generated name, extensionless TUI input gets the suffix, explicit paths pass through untouched. 158 CLI tests pass. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Tkj4rzKBA3uhfgk7kqjsb8
This commit is contained in:
1 parent
7748d6b5e9
commit
0482bc3dfd
8 files changed
+109
-16
No files matched your search
@@ -150,7 +150,7 @@ async function readSecretInput(
|
||||
function resolveHome(p: string): string {
|
||||
return p.startsWith("~") ? join(homedir(), p.slice(1)) : p;
|
||||
}
|
||||
import { AppDir, resolveAppDir, INDEX_FILENAME } from "./appDir.js";
|
||||
import { AppDir, resolveAppDir, INDEX_FILENAME, BACKUP_EXTENSION, defaultBackupFilename } from "./appDir.js";
|
||||
import { loadConfig, saveConfig, mutateConfig } from "./configFile.js";
|
||||
import { AgentClient, AgentDaemon, agentSocketPath, DEFAULT_TTL_SECONDS } from "./agent.js";
|
||||
import { AuditLog } from "./audit.js";
|
||||
@@ -1478,14 +1478,25 @@ export function buildProgram(io: ProgramIo = defaultIo): Command {
|
||||
|
||||
vaultCmd
|
||||
.command("export <destFile>")
|
||||
.description("write a portable backup (encrypted by default)")
|
||||
.description(
|
||||
"write a portable backup (encrypted by default); give a directory to " +
|
||||
`get a default seedpass-<fingerprint>-<date>${BACKUP_EXTENSION} name`,
|
||||
)
|
||||
.option("--plaintext", "HIGH RISK: export without encryption")
|
||||
.option("--overwrite", "replace an existing file")
|
||||
.action(async (destFile: string, o: { plaintext?: boolean; overwrite?: boolean }) => {
|
||||
.action(async (rawDest: string, o: { plaintext?: boolean; overwrite?: boolean }) => {
|
||||
const vault = await openFromOptions(program.opts());
|
||||
const fingerprint = generateFingerprint(vault.mnemonic);
|
||||
// An explicit file path is used exactly as typed — scripts depend on
|
||||
// that. Only a directory target gets the generated name.
|
||||
const resolved = resolveHome(rawDest);
|
||||
const destFile =
|
||||
existsSync(resolved) && statSync(resolved).isDirectory()
|
||||
? join(resolved, defaultBackupFilename(fingerprint))
|
||||
: resolved;
|
||||
const wrapper = await exportBackup(vault.index as Record<string, unknown>, {
|
||||
mnemonic: vault.mnemonic,
|
||||
fingerprint: generateFingerprint(vault.mnemonic),
|
||||
fingerprint,
|
||||
encrypt: !o.plaintext,
|
||||
});
|
||||
if (existsSync(destFile) && !o.overwrite) {
|
||||
|
||||
Reference in new issue
Block a user