otplib-cli 1.0.1 → 2.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/otplibx/index.ts","../src/shared/stdin.ts","../package.json","../src/shared/version.ts","../src/otplib/commands/encode.ts","../src/shared/types.ts","../src/shared/parse.ts","../src/otplibx/storage/errors.ts","../src/otplibx/storage/native-aes.ts","../src/otplibx/storage/env-parser.ts","../src/otplibx/commands/add.ts","../src/shared/guardrails.ts","../src/otplib/commands/guard.ts","../src/otplibx/commands/guard.ts","../src/otplib/commands/hotp.ts","../src/otplibx/commands/hotp.ts","../src/otplibx/commands/init.ts","../src/shared/fuzzy.ts","../src/otplib/commands/list.ts","../src/otplibx/commands/list.ts","../src/shared/otp.ts","../src/otplib/commands/token.ts","../src/otplibx/commands/token.ts","../src/otplib/commands/type.ts","../src/otplibx/commands/type.ts","../src/otplib/commands/verify.ts","../src/otplibx/commands/verify.ts","../src/otplibx/cli.ts"],"sourcesContent":["import { Command } from \"commander\";\n\nimport { readStdin } from \"../shared/stdin.js\";\nimport { VERSION } from \"../shared/version.js\";\nimport { registerAddCommand } from \"./commands/add.js\";\nimport { registerGuardCommands } from \"./commands/guard.js\";\nimport { registerHotpCommands } from \"./commands/hotp.js\";\nimport { registerInitCommand } from \"./commands/init.js\";\nimport { registerListCommand } from \"./commands/list.js\";\nimport { registerTokenCommand } from \"./commands/token.js\";\nimport { registerTypeCommand } from \"./commands/type.js\";\nimport { registerVerifyCommand } from \"./commands/verify.js\";\n\nimport type { ReadStdinFn } from \"../shared/stdin.js\";\n\nexport type { ReadStdinFn } from \"../shared/stdin.js\";\nexport { readStdin } from \"../shared/stdin.js\";\n\nconst DEFAULT_FILE = \".env.otplibx\";\n\nexport function createOtplibxCli(readStdinFn: ReadStdinFn = readStdin): Command {\n const program = new Command();\n\n program\n .name(\"otplibx\")\n .description(\"otplib with native AES-256-GCM encrypted storage\")\n .version(VERSION)\n .option(\n \"-f, --file <path>\",\n \"encrypted secrets file\",\n process.env.OTPLIBX_FILE ?? DEFAULT_FILE,\n );\n\n registerInitCommand(program);\n registerAddCommand(program, readStdinFn);\n registerTokenCommand(program, readStdinFn);\n registerTypeCommand(program, readStdinFn);\n registerVerifyCommand(program);\n registerListCommand(program);\n registerGuardCommands(program);\n registerHotpCommands(program);\n\n return program;\n}\n","export type ReadStdinFn = () => Promise<string>;\n\nexport async function readStdin(): Promise<string> {\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(chunk);\n }\n return Buffer.concat(chunks).toString(\"utf-8\").trim();\n}\n","{\n \"name\": \"otplib-cli\",\n \"version\": \"2.0.0\",\n \"description\": \"CLI tool for managing encrypted OTP vaults\",\n \"license\": \"MIT\",\n \"author\": \"Gerald Yeo <support@yeojz.dev>\",\n \"type\": \"module\",\n \"bin\": {\n \"otplib\": \"./dist/index.cjs\",\n \"otplibx\": \"./dist/otplibx.cjs\"\n },\n \"main\": \"./dist/index.cjs\",\n \"files\": [\n \"dist\",\n \"bin\"\n ],\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/yeojz/otplib.git\",\n \"directory\": \"apps/otplib-cli\"\n },\n \"keywords\": [\n \"otp\",\n \"totp\",\n \"hotp\",\n \"2fa\",\n \"cli\",\n \"vault\"\n ],\n \"scripts\": {\n \"build\": \"tsup\",\n \"dev\": \"tsup --watch\",\n \"test\": \"vitest\",\n \"test:ci\": \"vitest run --coverage\",\n \"typecheck\": \"tsc --noEmit\",\n \"lint\": \"eslint src/\"\n },\n \"dependencies\": {\n \"commander\": \"^14.0.3\",\n \"otplib\": \"workspace:*\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^25.0.10\",\n \"tsup\": \"^8.0.1\",\n \"typescript\": \"^5.3.3\",\n \"vitest\": \"^4.0.18\"\n }\n}\n","import packageJson from \"../../package.json\" with { type: \"json\" };\n\nexport const VERSION = packageJson.version;\n","import fs from \"node:fs\";\n\nimport { parseAddInput } from \"../../shared/parse.js\";\nimport { encodePayload, generateUid } from \"../../shared/types.js\";\n\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { OtpPayload } from \"../../shared/types.js\";\nimport type { Command } from \"commander\";\n\nexport type EncodeResult = {\n id: string;\n encoded: string;\n};\n\nexport function encode(input: string, bytes = 4): EncodeResult {\n if (!input) {\n throw new Error(\"expected otpauth URI or JSON from stdin\");\n }\n\n if (bytes < 1 || bytes > 32) {\n throw new Error(\"bytes must be between 1 and 32\");\n }\n\n const data = parseAddInput(input);\n const id = generateUid(bytes);\n const payload: OtpPayload = { data };\n const encoded = encodePayload(payload);\n\n return { id, encoded };\n}\n\nexport function registerEncodeCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"encode\")\n .description(\"Encode otpauth URI or JSON into internal format with UID\")\n .option(\"--save-uid <file>\", \"Append generated UID to file\")\n .option(\"-b, --bytes <n>\", \"Byte length for UID entropy (default: 4)\", \"4\")\n .action(async (options: { saveUid?: string; bytes: string }) => {\n const raw = await readStdinFn();\n if (!raw) {\n console.error(\"Error: Expected otpauth URI or JSON from stdin\");\n console.error(\"Usage: cat otp-uri.txt | otplib encode\");\n console.error(\" pbpaste | otplib encode\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const bytes = parseInt(options.bytes, 10);\n if (isNaN(bytes)) {\n console.error(\"Error: --bytes must be between 1 and 32\");\n process.exitCode = 1;\n return;\n }\n\n const { id, encoded } = encode(raw, bytes);\n const output = `${id.toUpperCase()}=${encoded}`;\n\n process.stdout.write(output + \"\\n\");\n\n if (options.saveUid) {\n try {\n const fd = fs.openSync(options.saveUid, \"a\", 0o600);\n fs.writeSync(fd, id + \"\\n\");\n fs.closeSync(fd);\n } catch (err) {\n console.error(\n `\\nWarning: Could not save UID to ${options.saveUid}: ${(err as Error).message}`,\n );\n process.exitCode = 1;\n }\n }\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import crypto from \"node:crypto\";\n\nexport type OtpAlgorithm = \"SHA1\" | \"SHA256\" | \"SHA512\";\nexport type OtpDigits = 6 | 7 | 8;\n\nexport type TotpData = {\n type: \"totp\";\n secret: string;\n issuer?: string;\n account?: string;\n algorithm: OtpAlgorithm;\n digits: OtpDigits;\n period: number;\n};\n\nexport type HotpData = {\n type: \"hotp\";\n secret: string;\n issuer?: string;\n account?: string;\n algorithm: OtpAlgorithm;\n digits: OtpDigits;\n counter: number;\n};\n\nexport type OtpData = TotpData | HotpData;\n\nexport type OtpPayload = {\n data: OtpData;\n};\n\nexport type ParsedEntry = {\n id: string;\n payload: OtpPayload;\n};\n\nexport function generateUid(bytes = 4): string {\n return \"A\" + crypto.randomBytes(bytes).toString(\"hex\").toUpperCase();\n}\n\nexport function encodePayload(payload: OtpPayload): string {\n return Buffer.from(JSON.stringify(payload), \"utf-8\").toString(\"base64\");\n}\n\nexport function decodePayload(encoded: string): OtpPayload {\n const json = Buffer.from(encoded, \"base64\").toString(\"utf-8\");\n return JSON.parse(json) as OtpPayload;\n}\n\nexport function formatOutput(id: string, payload: OtpPayload): string {\n return `${id.toUpperCase()}=${encodePayload(payload)}`;\n}\n\nexport function getLabel(data: OtpData): string {\n if (data.issuer && data.account) {\n return `${data.issuer}:${data.account}`;\n }\n return data.account || data.issuer || \"Unknown\";\n}\n","import { decodePayload } from \"./types.js\";\n\nimport type { HotpData, OtpAlgorithm, OtpData, OtpDigits, ParsedEntry } from \"./types.js\";\nimport type { OTPGuardrailsConfig } from \"otplib\";\n\ntype JsonInput = {\n secret: string;\n type?: \"totp\" | \"hotp\";\n issuer?: string;\n account?: string;\n algorithm?: string;\n digits?: number;\n period?: number;\n counter?: number;\n};\n\nexport type ParsedEnv = {\n entries: ParsedEntry[];\n guardrails?: Partial<OTPGuardrailsConfig>;\n};\n\nfunction normalizeAlgorithm(alg?: string): OtpAlgorithm {\n if (!alg) return \"SHA1\";\n const upper = alg.toUpperCase().replace(\"-\", \"\");\n if (upper === \"SHA1\") return \"SHA1\";\n if (upper === \"SHA256\") return \"SHA256\";\n if (upper === \"SHA512\") return \"SHA512\";\n throw new Error(`Invalid algorithm: ${alg}, expected SHA1, SHA256, or SHA512`);\n}\n\nfunction normalizeDigits(digits?: number): OtpDigits {\n if (digits === undefined) return 6;\n if (digits === 6 || digits === 7 || digits === 8) return digits;\n throw new Error(`Invalid digits: ${digits}, expected 6, 7, or 8`);\n}\n\nexport function parseOtpauthUri(uri: string): OtpData {\n let url: URL;\n try {\n url = new URL(uri);\n } catch {\n throw new Error(\"Invalid URI: must start with otpauth://\");\n }\n\n if (url.protocol !== \"otpauth:\") {\n throw new Error(\"Invalid URI: must start with otpauth://\");\n }\n\n if (url.hostname !== \"totp\" && url.hostname !== \"hotp\") {\n throw new Error(`Invalid type: ${url.hostname}, expected totp or hotp`);\n }\n\n if (url.pathname === \"\") {\n throw new Error(\"Invalid URI format: missing path\");\n }\n\n const type = url.hostname as \"totp\" | \"hotp\";\n const label = decodeURIComponent(url.pathname.slice(1));\n\n const secret = url.searchParams.get(\"secret\");\n if (!secret) {\n throw new Error(\"Missing required parameter: secret\");\n }\n\n let issuer = url.searchParams.get(\"issuer\") || undefined;\n let account = label;\n\n const colonIndex = label.indexOf(\":\");\n if (colonIndex !== -1) {\n issuer = issuer || label.slice(0, colonIndex);\n account = label.slice(colonIndex + 1);\n }\n\n const algorithm = normalizeAlgorithm(url.searchParams.get(\"algorithm\") ?? undefined);\n const digitsParam = url.searchParams.get(\"digits\");\n const digits = normalizeDigits(digitsParam !== null ? parseInt(digitsParam, 10) : undefined);\n\n if (type === \"totp\") {\n const periodParam = url.searchParams.get(\"period\");\n const period = periodParam !== null ? parseInt(periodParam, 10) : 30;\n if (period <= 0) throw new Error(\"Invalid period: must be positive\");\n return { type: \"totp\", secret, issuer, account, algorithm, digits, period };\n } else {\n const counterParam = url.searchParams.get(\"counter\");\n const counter = counterParam !== null ? parseInt(counterParam, 10) : 0;\n if (counter < 0) throw new Error(\"Invalid counter: must be non-negative\");\n return { type: \"hotp\", secret, issuer, account, algorithm, digits, counter };\n }\n}\n\nexport function parseJsonInput(raw: string): OtpData {\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch {\n throw new Error(\"Invalid JSON input\");\n }\n\n if (typeof json !== \"object\" || json === null || Array.isArray(json)) {\n throw new Error(\"Invalid JSON input: expected an object\");\n }\n\n const input = json as JsonInput;\n\n if (typeof input.secret !== \"string\" || !input.secret) {\n throw new Error(\"Missing required field: secret\");\n }\n\n const type = input.type ?? \"totp\";\n if (type !== \"totp\" && type !== \"hotp\") {\n throw new Error('Invalid type: expected \"totp\" or \"hotp\"');\n }\n\n const algorithm = normalizeAlgorithm(input.algorithm);\n const digits = normalizeDigits(input.digits);\n\n if (type === \"totp\") {\n const period = input.period ?? 30;\n if (typeof period !== \"number\" || period <= 0) {\n throw new Error(\"Invalid period: must be a positive number\");\n }\n return {\n type: \"totp\",\n secret: input.secret,\n issuer: input.issuer,\n account: input.account,\n algorithm,\n digits,\n period,\n };\n } else {\n const counter = input.counter ?? 0;\n if (typeof counter !== \"number\" || counter < 0) {\n throw new Error(\"Invalid counter: must be a non-negative number\");\n }\n return {\n type: \"hotp\",\n secret: input.secret,\n issuer: input.issuer,\n account: input.account,\n algorithm,\n digits,\n counter,\n };\n }\n}\n\nexport function parseAddInput(raw: string): OtpData {\n const trimmed = raw.trim();\n if (trimmed.startsWith(\"otpauth://\")) {\n return parseOtpauthUri(trimmed);\n }\n return parseJsonInput(trimmed);\n}\n\nexport function parseEnvInput(raw: string): ParsedEnv {\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch {\n throw new Error(\"Invalid JSON input\");\n }\n\n if (typeof json !== \"object\" || json === null || Array.isArray(json)) {\n throw new Error(\"Invalid input: expected JSON object\");\n }\n\n const entries: ParsedEntry[] = [];\n const guardrails: Partial<OTPGuardrailsConfig> = {};\n\n for (const [key, value] of Object.entries(json as Record<string, unknown>)) {\n if (typeof value !== \"string\") continue;\n\n if (key === \"OTPLIB_MIN_SECRET_BYTES\") {\n const parsed = parseInt(value, 10);\n if (!isNaN(parsed) && parsed >= 1) {\n guardrails.MIN_SECRET_BYTES = parsed;\n }\n } else if (key === \"OTPLIB_MAX_SECRET_BYTES\") {\n const parsed = parseInt(value, 10);\n if (!isNaN(parsed) && parsed >= 1) {\n guardrails.MAX_SECRET_BYTES = parsed;\n }\n } else if (key === \"OTPLIB_MIN_PERIOD\") {\n const parsed = parseInt(value, 10);\n if (!isNaN(parsed) && parsed >= 1) {\n guardrails.MIN_PERIOD = parsed;\n }\n } else if (key === \"OTPLIB_MAX_PERIOD\") {\n const parsed = parseInt(value, 10);\n if (!isNaN(parsed) && parsed >= 1) {\n guardrails.MAX_PERIOD = parsed;\n }\n } else {\n try {\n const payload = decodePayload(value);\n entries.push({ id: key, payload });\n } catch {\n // Skip malformed entries\n }\n }\n }\n\n return {\n entries,\n guardrails: Object.keys(guardrails).length > 0 ? guardrails : undefined,\n };\n}\n\n/** @deprecated Use parseEnvInput instead */\nexport const parseDotenvxInput = parseEnvInput;\n\nexport function findEntry(entries: ParsedEntry[], id: string): ParsedEntry | undefined {\n return entries.find((e) => e.id === id);\n}\n\nexport function updateHotpCounter(data: HotpData, newCounter?: number): HotpData {\n return {\n ...data,\n counter: newCounter ?? data.counter + 1,\n };\n}\n","export const ErrorCodes = {\n NOT_INITIALIZED: \"NOT_INITIALIZED\",\n INVALID_KEY: \"INVALID_KEY\",\n DECRYPT_FAILED: \"DECRYPT_FAILED\",\n FILE_NOT_FOUND: \"FILE_NOT_FOUND\",\n ALREADY_INITIALIZED: \"ALREADY_INITIALIZED\",\n} as const;\n\nexport type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];\n\nexport class OtplibxStorageError extends Error {\n public readonly code: ErrorCode;\n\n constructor(message: string, code: ErrorCode) {\n super(message);\n this.name = \"OtplibxStorageError\";\n this.code = code;\n }\n}\n","import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport { parseEnvFile, serializeEnvFile } from \"./env-parser.js\";\nimport { ErrorCodes, OtplibxStorageError } from \"./errors.js\";\n\nimport type { OtplibxStorage, StorageStatus } from \"./types.js\";\n\nconst ENCRYPTED_PREFIX = \"encrypted:\";\nconst IV_LENGTH = 12; // GCM recommended IV length\nconst AUTH_TAG_LENGTH = 16; // GCM auth tag length\nconst KEY_LENGTH = 32; // 256 bits\n\nconst ENV_VAR_NAME = \"OTPLIBX_ENCRYPTION_KEY\";\nconst KEYS_FILE_NAME = \".env.keys\";\n\n/**\n * Encrypt a plaintext value using AES-256-GCM\n * Returns: encrypted:BASE64(IV || AUTH_TAG || CIPHERTEXT)\n */\nfunction encrypt(plaintext: string, key: Buffer): string {\n const iv = crypto.randomBytes(IV_LENGTH);\n const cipher = crypto.createCipheriv(\"aes-256-gcm\", key, iv);\n\n const encrypted = Buffer.concat([cipher.update(plaintext, \"utf8\"), cipher.final()]);\n const authTag = cipher.getAuthTag();\n\n // Combine: IV || AUTH_TAG || CIPHERTEXT\n const combined = Buffer.concat([iv, authTag, encrypted]);\n return `${ENCRYPTED_PREFIX}${combined.toString(\"base64\")}`;\n}\n\n/**\n * Decrypt an encrypted value\n * Input format: encrypted:BASE64(IV || AUTH_TAG || CIPHERTEXT)\n */\nfunction decrypt(encryptedValue: string, key: Buffer): string {\n if (!encryptedValue.startsWith(ENCRYPTED_PREFIX)) {\n // Not encrypted, return as-is\n return encryptedValue;\n }\n\n const base64Data = encryptedValue.slice(ENCRYPTED_PREFIX.length);\n\n let combined: Buffer;\n try {\n combined = Buffer.from(base64Data, \"base64\");\n } catch {\n throw new OtplibxStorageError(\"Invalid encrypted value format\", ErrorCodes.DECRYPT_FAILED);\n }\n\n if (combined.length < IV_LENGTH + AUTH_TAG_LENGTH) {\n throw new OtplibxStorageError(\"Encrypted value too short\", ErrorCodes.DECRYPT_FAILED);\n }\n\n const iv = combined.subarray(0, IV_LENGTH);\n const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);\n const ciphertext = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);\n\n try {\n const decipher = crypto.createDecipheriv(\"aes-256-gcm\", key, iv);\n decipher.setAuthTag(authTag);\n\n const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);\n\n return decrypted.toString(\"utf8\");\n } catch {\n throw new OtplibxStorageError(\n \"Decryption failed - invalid key or corrupted data\",\n ErrorCodes.DECRYPT_FAILED,\n );\n }\n}\n\n/**\n * Generate a new 256-bit encryption key\n */\nfunction generateKey(): string {\n return crypto.randomBytes(KEY_LENGTH).toString(\"hex\");\n}\n\n/**\n * Parse a hex-encoded key string into a Buffer\n */\nfunction parseKey(keyHex: string): Buffer {\n if (!/^[0-9a-fA-F]{64}$/.test(keyHex)) {\n throw new OtplibxStorageError(\n \"Invalid key format - expected 64 hex characters\",\n ErrorCodes.INVALID_KEY,\n );\n }\n return Buffer.from(keyHex, \"hex\");\n}\n\n/**\n * Get the path to the .env.keys file for a given secrets file\n */\nfunction getKeysFilePath(envFilePath: string): string {\n return path.join(path.dirname(envFilePath), KEYS_FILE_NAME);\n}\n\n/**\n * Find the encryption key from environment variable or .env.keys file\n */\nfunction findKey(envFilePath: string): { key: Buffer; source: \"env\" | \"file\" } | null {\n // First, check environment variable\n const envKey = process.env[ENV_VAR_NAME];\n if (envKey) {\n return { key: parseKey(envKey), source: \"env\" };\n }\n\n // Then, check .env.keys file\n const keysPath = getKeysFilePath(envFilePath);\n if (fs.existsSync(keysPath)) {\n const content = fs.readFileSync(keysPath, \"utf8\");\n const { entries } = parseEnvFile(content);\n const fileKey = entries.get(ENV_VAR_NAME);\n if (fileKey) {\n return { key: parseKey(fileKey), source: \"file\" };\n }\n }\n\n return null;\n}\n\n/**\n * Native AES-256-GCM storage implementation\n */\nexport const nativeAesStorage: OtplibxStorage = {\n async status(filePath: string): Promise<StorageStatus> {\n const envExists = fs.existsSync(filePath);\n const keysPath = getKeysFilePath(filePath);\n const keysExists = fs.existsSync(keysPath);\n const envKeyExists = !!process.env[ENV_VAR_NAME];\n\n let keySource: \"env\" | \"file\" | null = null;\n if (envKeyExists) {\n keySource = \"env\";\n } else if (keysExists) {\n const content = fs.readFileSync(keysPath, \"utf8\");\n const { entries } = parseEnvFile(content);\n if (entries.has(ENV_VAR_NAME)) {\n keySource = \"file\";\n }\n }\n\n return {\n initialized: envExists && keySource !== null,\n keySource,\n envPath: envExists ? filePath : null,\n keysPath: keysExists ? keysPath : null,\n };\n },\n\n async init(filePath: string): Promise<void> {\n // Check if already initialized\n if (fs.existsSync(filePath)) {\n throw new OtplibxStorageError(\n `File already exists: ${filePath}`,\n ErrorCodes.ALREADY_INITIALIZED,\n );\n }\n\n // Generate a new encryption key\n const key = generateKey();\n const keysPath = getKeysFilePath(filePath);\n\n // Write the keys file with restricted permissions\n let keysContent = \"\";\n if (fs.existsSync(keysPath)) {\n keysContent = fs.readFileSync(keysPath, \"utf8\");\n }\n\n const existingEntries = parseEnvFile(keysContent);\n existingEntries.entries.set(ENV_VAR_NAME, key);\n const newKeysContent = serializeEnvFile(keysContent, existingEntries.entries);\n\n fs.writeFileSync(keysPath, newKeysContent + \"\\n\", { mode: 0o600 });\n\n // Create the empty env file with restricted permissions\n fs.writeFileSync(filePath, \"\", { mode: 0o600 });\n },\n\n async load(filePath: string): Promise<Record<string, string>> {\n if (!fs.existsSync(filePath)) {\n throw new OtplibxStorageError(`File not found: ${filePath}`, ErrorCodes.FILE_NOT_FOUND);\n }\n\n const keyResult = findKey(filePath);\n if (!keyResult) {\n throw new OtplibxStorageError(\n `No encryption key found. Set ${ENV_VAR_NAME} environment variable or run 'otplibx init'`,\n ErrorCodes.NOT_INITIALIZED,\n );\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const { entries } = parseEnvFile(content);\n\n const result: Record<string, string> = {};\n for (const [key, value] of entries) {\n result[key] = decrypt(value, keyResult.key);\n }\n\n return result;\n },\n\n async set(filePath: string, key: string, value: string): Promise<void> {\n if (!fs.existsSync(filePath)) {\n throw new OtplibxStorageError(`File not found: ${filePath}`, ErrorCodes.FILE_NOT_FOUND);\n }\n\n const keyResult = findKey(filePath);\n if (!keyResult) {\n throw new OtplibxStorageError(\n `No encryption key found. Set ${ENV_VAR_NAME} environment variable or run 'otplibx init'`,\n ErrorCodes.NOT_INITIALIZED,\n );\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const { entries } = parseEnvFile(content);\n\n // Encrypt the value (empty values are used for \"remove\" operations)\n if (value === \"\") {\n entries.delete(key);\n } else {\n const encryptedValue = encrypt(value, keyResult.key);\n entries.set(key, encryptedValue);\n }\n\n const newContent = serializeEnvFile(content, entries);\n fs.writeFileSync(filePath, newContent, { mode: 0o600 });\n },\n\n async remove(filePath: string, key: string): Promise<void> {\n if (!fs.existsSync(filePath)) {\n throw new OtplibxStorageError(`File not found: ${filePath}`, ErrorCodes.FILE_NOT_FOUND);\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const { entries } = parseEnvFile(content);\n entries.delete(key);\n\n const newContent = serializeEnvFile(content, entries);\n fs.writeFileSync(filePath, newContent, { mode: 0o600 });\n },\n};\n\nexport default nativeAesStorage;\n","/**\n * Simple .env file parser that handles KEY=\"value\" format\n * Preserves comments and whitespace\n */\n\nexport interface ParsedEnvFile {\n entries: Map<string, string>;\n lines: string[];\n}\n\n/**\n * Parse a .env file content into key-value pairs\n * Returns both the entries and the original lines for preservation\n */\nexport function parseEnvFile(content: string): ParsedEnvFile {\n const entries = new Map<string, string>();\n const lines = content.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Skip empty lines and comments\n if (!trimmed || trimmed.startsWith(\"#\")) {\n continue;\n }\n\n // Match KEY=value or KEY=\"value\" patterns\n const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/);\n if (match) {\n const [, key, rawValue] = match;\n // Remove surrounding quotes if present\n let value = rawValue;\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n entries.set(key, value);\n }\n }\n\n return { entries, lines };\n}\n\n/**\n * Serialize entries back to .env format\n * Updates existing keys in place and appends new keys at the end\n */\nexport function serializeEnvFile(originalContent: string, entries: Map<string, string>): string {\n // Handle empty original content - just serialize the entries\n if (!originalContent) {\n const resultLines: string[] = [];\n for (const [key, value] of entries) {\n if (needsQuotes(value)) {\n resultLines.push(`${key}=\"${escapeValue(value)}\"`);\n } else {\n resultLines.push(`${key}=${value}`);\n }\n }\n return resultLines.join(\"\\n\");\n }\n\n const lines = originalContent.split(\"\\n\");\n const processedKeys = new Set<string>();\n const resultLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Preserve empty lines and comments\n if (!trimmed || trimmed.startsWith(\"#\")) {\n resultLines.push(line);\n continue;\n }\n\n // Check if this line has a key we need to update\n const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)=/);\n if (match) {\n const key = match[1];\n processedKeys.add(key);\n\n const value = entries.get(key);\n if (value !== undefined) {\n // Use double quotes for values containing special characters\n if (needsQuotes(value)) {\n resultLines.push(`${key}=\"${escapeValue(value)}\"`);\n } else {\n resultLines.push(`${key}=${value}`);\n }\n }\n // If key is not in entries, it was removed - skip it\n } else {\n resultLines.push(line);\n }\n }\n\n // Append new keys that weren't in the original file\n for (const [key, value] of entries) {\n if (!processedKeys.has(key)) {\n if (needsQuotes(value)) {\n resultLines.push(`${key}=\"${escapeValue(value)}\"`);\n } else {\n resultLines.push(`${key}=${value}`);\n }\n }\n }\n\n return resultLines.join(\"\\n\");\n}\n\n/**\n * Check if a value needs to be quoted\n */\nfunction needsQuotes(value: string): boolean {\n // Quote if contains spaces, special chars, or starts/ends with whitespace\n return (\n value.includes(\" \") ||\n value.includes('\"') ||\n value.includes(\"'\") ||\n value.includes(\"\\n\") ||\n value.includes(\"=\") ||\n value.includes(\"#\") ||\n value !== value.trim()\n );\n}\n\n/**\n * Escape special characters in a value for .env format\n */\nfunction escapeValue(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"').replace(/\\n/g, \"\\\\n\");\n}\n","import { encode } from \"../../otplib/commands/encode.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport type AddOptions = {\n file: string;\n bytes?: number;\n};\n\nexport async function add(input: string, options: AddOptions): Promise<string> {\n const { file, bytes } = options;\n\n const { id, encoded } = encode(input, bytes);\n\n await storage.set(file, id, encoded);\n\n return id;\n}\n\nexport function registerAddCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"add\")\n .description(\"Add OTP entry (reads otpauth URI or JSON from stdin)\")\n .option(\"-b, --bytes <n>\", \"Byte length for UID entropy (default: 4)\")\n .action(async (cmdOpts: { bytes?: string }) => {\n const opts = program.opts<{ file: string }>();\n const input = await readStdinFn();\n const bytes = cmdOpts.bytes ? parseInt(cmdOpts.bytes, 10) : undefined;\n\n if (bytes !== undefined && (isNaN(bytes) || bytes < 1 || bytes > 32)) {\n console.error(\"error: --bytes must be between 1 and 32\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const key = await add(input, { file: opts.file, bytes });\n console.log(key);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","// Default guardrail values from @otplib/core\n// These match RFC recommendations and library defaults\nexport const GUARDRAIL_DEFAULTS = {\n MIN_SECRET_BYTES: 16, // RFC 4226: 128 bits minimum\n MAX_SECRET_BYTES: 64, // 512 bits maximum\n MIN_PERIOD: 1, // 1 second minimum\n MAX_PERIOD: 3600, // 1 hour maximum\n} as const;\n\nexport type GuardrailKey = keyof typeof GUARDRAIL_DEFAULTS;\n\nexport const GUARDRAIL_KEYS = Object.keys(GUARDRAIL_DEFAULTS) as GuardrailKey[];\n\nexport function formatGuardrailsTable(configured: Record<string, number>): string {\n const maxKeyLen = Math.max(...GUARDRAIL_KEYS.map((k) => k.length));\n\n const lines: string[] = [];\n lines.push(\"Guardrail\".padEnd(maxKeyLen + 2) + \"Configured Default\");\n lines.push(\"-\".repeat(maxKeyLen + 2) + \"-\".repeat(22));\n\n for (const key of GUARDRAIL_KEYS) {\n const configuredValue = configured[key];\n const defaultValue = GUARDRAIL_DEFAULTS[key];\n const configuredStr = configuredValue !== undefined ? String(configuredValue) : \"-\";\n lines.push(`${key.padEnd(maxKeyLen + 2)}${configuredStr.padEnd(12)}${defaultValue}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nexport function normalizeGuardrailKey(key: string): string {\n const normalizedKey = key.replace(/^OTPLIB_/, \"\") as GuardrailKey;\n\n if (!GUARDRAIL_KEYS.includes(normalizedKey)) {\n throw new Error(`invalid guardrail key: ${key} (valid: ${GUARDRAIL_KEYS.join(\", \")})`);\n }\n\n return `OTPLIB_${normalizedKey}`;\n}\n\nexport function validateGuardrailValue(value: string): number {\n const numValue = parseInt(value, 10);\n if (isNaN(numValue) || numValue < 1) {\n throw new Error(\"value must be a positive integer\");\n }\n return numValue;\n}\n","import { formatGuardrailsTable } from \"../../shared/guardrails.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport function guardShow(env: ParsedEnv): string {\n const configured = env.guardrails ? (env.guardrails as Record<string, number>) : {};\n return formatGuardrailsTable(configured);\n}\n\nexport function registerGuardCommands(program: Command, readStdinFn: ReadStdinFn): void {\n const guardCmd = program.command(\"guard\").description(\"Guardrail commands\");\n\n guardCmd\n .command(\"show\")\n .description(\"Show guardrail configuration\")\n .action(async () => {\n const raw = await readStdinFn();\n\n let env: ParsedEnv = { entries: [] };\n if (raw) {\n try {\n env = parseEnvInput(raw);\n } catch {\n // Ignore parse errors, show defaults only\n }\n }\n\n const output = guardShow(env);\n process.stdout.write(output + \"\\n\");\n });\n}\n","import { guardShow as guardShowCore } from \"../../otplib/commands/guard.js\";\nimport { normalizeGuardrailKey, validateGuardrailValue } from \"../../shared/guardrails.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { Command } from \"commander\";\n\nexport type GuardUpdateOptions = {\n file: string;\n};\n\nexport async function guardUpdate(\n key: string,\n value: string,\n options: GuardUpdateOptions,\n): Promise<string> {\n const { file } = options;\n\n if (!key) {\n throw new Error(\"missing required argument: <key>\");\n }\n if (!value) {\n throw new Error(\"missing required argument: <value>\");\n }\n\n validateGuardrailValue(value);\n const normalizedKey = normalizeGuardrailKey(key);\n\n await storage.set(file, normalizedKey, value);\n\n return `${normalizedKey}=${value}`;\n}\n\nexport type GuardRmOptions = {\n file: string;\n};\n\nexport async function guardRm(key: string, options: GuardRmOptions): Promise<string> {\n const { file } = options;\n\n if (!key) {\n throw new Error(\"missing required argument: <key>\");\n }\n\n const normalizedKey = normalizeGuardrailKey(key);\n\n await storage.remove(file, normalizedKey);\n\n return `Removed: ${normalizedKey}`;\n}\n\nexport type GuardShowOptions = {\n file: string;\n};\n\nexport async function guardShow(options: GuardShowOptions): Promise<string> {\n const { file } = options;\n\n const decrypted = await storage.load(file);\n const raw = JSON.stringify(decrypted);\n\n let env: ParsedEnv = { entries: [], guardrails: undefined };\n try {\n env = parseEnvInput(raw);\n } catch {\n // Ignore parse errors, show defaults only\n }\n\n return guardShowCore(env);\n}\n\nexport function registerGuardCommands(program: Command): void {\n const guardCmd = program.command(\"guard\").description(\"Guardrail commands\");\n\n guardCmd\n .command(\"update\")\n .description(\"Add or update a guardrail value\")\n .argument(\"<key>\", \"Guardrail key\")\n .argument(\"<value>\", \"Guardrail value (positive integer)\")\n .action(async (key: string, value: string) => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const result = await guardUpdate(key, value, { file: opts.file });\n console.log(result);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n\n guardCmd\n .command(\"rm\")\n .description(\"Remove a guardrail\")\n .argument(\"<key>\", \"Guardrail key\")\n .action(async (key: string) => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const result = await guardRm(key, { file: opts.file });\n console.log(result);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n\n guardCmd\n .command(\"show\")\n .description(\"Show guardrail configuration\")\n .action(async () => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const result = await guardShow({ file: opts.file });\n console.log(result);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { findEntry, parseEnvInput, updateHotpCounter } from \"../../shared/parse.js\";\nimport { encodePayload, formatOutput } from \"../../shared/types.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { HotpData, OtpPayload } from \"../../shared/types.js\";\nimport type { Command } from \"commander\";\n\nexport type UpdateCounterResult = {\n id: string;\n encoded: string;\n};\n\nexport function updateCounter(env: ParsedEnv, id: string, counter?: number): UpdateCounterResult {\n const entry = findEntry(env.entries, id);\n if (!entry) {\n throw new Error(`entry not found: ${id}`);\n }\n\n if (entry.payload.data.type !== \"hotp\") {\n throw new Error(`Entry ${id} is TOTP, not HOTP`);\n }\n\n const updatedData = updateHotpCounter(entry.payload.data as HotpData, counter);\n const payload: OtpPayload = { data: updatedData };\n const encoded = encodePayload(payload);\n\n return { id, encoded };\n}\n\nexport function registerHotpCommands(program: Command, readStdinFn: ReadStdinFn): void {\n const hotpCmd = program.command(\"hotp\").description(\"HOTP commands\");\n\n hotpCmd\n .command(\"update-counter\")\n .description(\"Output updated HOTP entry with new counter\")\n .argument(\"<id>\", \"Entry ID\")\n .argument(\"[n]\", \"New counter value (default: current + 1)\")\n .action(async (id: string, n?: string) => {\n const raw = await readStdinFn();\n if (!raw) {\n console.error(\"Error: Expected JSON from stdin\");\n console.error(\"Usage: cat storage.json | otplib hotp update-counter <id>\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const { entries } = parseEnvInput(raw);\n const entry = findEntry(entries, id);\n\n if (!entry) {\n console.error(`Error: entry not found: ${id}`);\n process.exitCode = 1;\n return;\n }\n\n if (entry.payload.data.type !== \"hotp\") {\n console.error(`Error: Entry ${id} is TOTP, not HOTP`);\n process.exitCode = 1;\n return;\n }\n\n let newCounter: number | undefined;\n if (n !== undefined) {\n newCounter = parseInt(n, 10);\n if (isNaN(newCounter) || newCounter < 0) {\n console.error(\"Error: Counter must be a non-negative integer\");\n process.exitCode = 1;\n return;\n }\n }\n\n const updatedData = updateHotpCounter(entry.payload.data as HotpData, newCounter);\n const payload: OtpPayload = { data: updatedData };\n const output = formatOutput(id, payload);\n process.stdout.write(output + \"\\n\");\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { updateCounter as updateCounterCore } from \"../../otplib/commands/hotp.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { Command } from \"commander\";\n\nexport type UpdateCounterOptions = {\n file: string;\n counter?: number;\n};\n\nexport async function updateCounter(id: string, options: UpdateCounterOptions): Promise<string> {\n const { file, counter } = options;\n\n if (!id) {\n throw new Error(\"missing required argument: <id>\");\n }\n\n const decrypted = await storage.load(file);\n const raw = JSON.stringify(decrypted);\n const env = parseEnvInput(raw);\n\n const { id: resultId, encoded } = updateCounterCore(env, id, counter);\n\n await storage.set(file, resultId, encoded);\n\n return resultId;\n}\n\nexport function registerHotpCommands(program: Command): void {\n const hotpCmd = program.command(\"hotp\").description(\"HOTP commands\");\n\n hotpCmd\n .command(\"update-counter\")\n .description(\"Update HOTP counter\")\n .argument(\"<id>\", \"Entry ID\")\n .argument(\"[counter]\", \"New counter value (default: current + 1)\")\n .action(async (id: string, counterArg?: string) => {\n const opts = program.opts<{ file: string }>();\n let counter: number | undefined;\n\n if (counterArg !== undefined) {\n counter = parseInt(counterArg, 10);\n if (isNaN(counter) || counter < 0) {\n console.error(\"error: counter must be a non-negative integer\");\n process.exitCode = 1;\n return;\n }\n }\n\n try {\n const result = await updateCounter(id, { file: opts.file, counter });\n console.log(result);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import storage from \"../storage/index.js\";\n\nimport type { Command } from \"commander\";\n\nexport type InitOptions = {\n file: string;\n};\n\nexport async function init(options: InitOptions): Promise<void> {\n const { file } = options;\n\n await storage.init(file);\n\n console.log(`Initialized: ${file}`);\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Initialize a new encrypted secrets file\")\n .argument(\"[file]\", \"File path (overrides --file option)\")\n .action(async (fileArg: string | undefined) => {\n const opts = program.opts<{ file: string }>();\n const file = fileArg ?? opts.file;\n\n try {\n await init({ file });\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","/**\n * Fuzzy match: checks if all characters of query appear in target in order.\n * e.g. \"ghub\" matches \"GitHub\", \"gml\" matches \"gmail.com\"\n */\nexport function fuzzyMatch(query: string, target: string): boolean {\n const q = query.toLowerCase();\n const t = target.toLowerCase();\n let qi = 0;\n for (let i = 0; i < t.length && qi < q.length; i++) {\n if (t[i] === q[qi]) qi++;\n }\n return qi === q.length;\n}\n","import { fuzzyMatch } from \"../../shared/fuzzy.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport { getLabel } from \"../../shared/types.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport function list(env: ParsedEnv, filter?: string): string {\n if (env.entries.length === 0) {\n return \"No entries\";\n }\n\n const filtered = filter\n ? env.entries.filter((entry) => {\n const label = getLabel(entry.payload.data);\n return fuzzyMatch(filter, entry.id) || fuzzyMatch(filter, label);\n })\n : env.entries;\n\n if (filtered.length === 0) {\n return \"No matches\";\n }\n\n const lines: string[] = [];\n for (const entry of filtered) {\n const label = getLabel(entry.payload.data);\n const entryType = entry.payload.data.type;\n lines.push(`${label}\\t${entry.id}\\t${entryType}`);\n }\n\n return lines.join(\"\\n\");\n}\n\nexport function registerListCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"list\")\n .description(\"List OTP entries (reads JSON from stdin)\")\n .option(\"-f, --filter <query>\", \"Fuzzy filter by ID or label\")\n .action(async (options: { filter?: string }) => {\n const raw = await readStdinFn();\n if (!raw) {\n console.error(\"Error: Expected JSON from stdin\");\n console.error(\"Usage: cat storage.json | otplib list\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const env = parseEnvInput(raw);\n const result = list(env, options.filter);\n\n if (result === \"No entries\" || result === \"No matches\") {\n console.log(result);\n } else {\n for (const line of result.split(\"\\n\")) {\n process.stdout.write(line + \"\\n\");\n }\n }\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { list as listCore } from \"../../otplib/commands/list.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { Command } from \"commander\";\n\nexport type ListOptions = {\n file: string;\n filter?: string;\n};\n\nexport async function list(options: ListOptions): Promise<string> {\n const { file, filter } = options;\n\n const decrypted = await storage.load(file);\n const raw = JSON.stringify(decrypted);\n const env = parseEnvInput(raw);\n\n return listCore(env, filter);\n}\n\nexport function registerListCommand(program: Command): void {\n program\n .command(\"list\")\n .description(\"List all OTP entries\")\n .option(\"--filter <query>\", \"Fuzzy filter by ID or label\")\n .action(async (cmdOpts: { filter?: string }) => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const result = await list({ file: opts.file, filter: cmdOpts.filter });\n process.stdout.write(result + \"\\n\");\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { generate, verify, createGuardrails } from \"otplib\";\n\nimport type { OtpData } from \"./types.js\";\nimport type { OTPGuardrailsConfig } from \"otplib\";\n\nexport async function generateOtp(\n data: OtpData,\n customGuardrails?: Partial<OTPGuardrailsConfig>,\n): Promise<string> {\n const guardrails = customGuardrails ? createGuardrails(customGuardrails) : undefined;\n\n if (data.type === \"totp\") {\n return generate({\n strategy: \"totp\",\n secret: data.secret,\n digits: data.digits,\n algorithm: data.algorithm.toLowerCase() as \"sha1\" | \"sha256\" | \"sha512\",\n period: data.period,\n guardrails,\n });\n }\n\n return generate({\n strategy: \"hotp\",\n secret: data.secret,\n digits: data.digits,\n algorithm: data.algorithm.toLowerCase() as \"sha1\" | \"sha256\" | \"sha512\",\n counter: data.counter,\n guardrails,\n });\n}\n\nexport async function verifyOtp(\n data: OtpData,\n token: string,\n customGuardrails?: Partial<OTPGuardrailsConfig>,\n): Promise<boolean> {\n const guardrails = customGuardrails ? createGuardrails(customGuardrails) : undefined;\n\n if (data.type === \"totp\") {\n const result = await verify({\n strategy: \"totp\",\n secret: data.secret,\n token,\n digits: data.digits,\n algorithm: data.algorithm.toLowerCase() as \"sha1\" | \"sha256\" | \"sha512\",\n period: data.period,\n guardrails,\n });\n return result.valid;\n }\n\n const result = await verify({\n strategy: \"hotp\",\n secret: data.secret,\n token,\n digits: data.digits,\n algorithm: data.algorithm.toLowerCase() as \"sha1\" | \"sha256\" | \"sha512\",\n counter: data.counter,\n guardrails,\n });\n return result.valid;\n}\n","import { generateOtp } from \"../../shared/otp.js\";\nimport { findEntry, parseEnvInput } from \"../../shared/parse.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport async function token(env: ParsedEnv, id: string): Promise<string> {\n const entry = findEntry(env.entries, id);\n if (!entry) {\n throw new Error(`entry not found: ${id}`);\n }\n return generateOtp(entry.payload.data, env.guardrails);\n}\n\nexport function registerTokenCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"token\")\n .description(\"Generate OTP token (auto-detects TOTP/HOTP)\")\n .argument(\"<id>\", \"Entry ID\")\n .option(\"-n, --no-newline\", \"Omit trailing newline\")\n .action(async (id: string, options: { newline: boolean }) => {\n const raw = await readStdinFn();\n if (!raw) {\n console.error(\"Error: Expected JSON from stdin\");\n console.error(\"Usage: cat storage.json | otplib token <id>\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const env = parseEnvInput(raw);\n const code = await token(env, id);\n process.stdout.write(options.newline ? code + \"\\n\" : code);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { token as tokenCore } from \"../../otplib/commands/token.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport type TokenOptions = {\n file: string;\n};\n\nexport async function token(id: string, options: TokenOptions): Promise<string> {\n const { file } = options;\n\n if (!id) {\n throw new Error(\"missing required argument: <id>\");\n }\n\n const decrypted = await storage.load(file);\n const raw = JSON.stringify(decrypted);\n const env = parseEnvInput(raw);\n\n return tokenCore(env, id);\n}\n\nexport function registerTokenCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"token\")\n .description(\"Generate OTP token for an entry\")\n .argument(\"[id]\", \"Entry ID (or read from stdin)\")\n .option(\"-n, --no-newline\", \"Omit trailing newline\")\n .action(async (idArg: string | undefined, cmdOpts: { newline: boolean }) => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const id = idArg || (await readStdinFn()).trim();\n if (!id) {\n console.error(\"error: missing entry ID\");\n process.exitCode = 1;\n return;\n }\n\n const result = await token(id, { file: opts.file });\n process.stdout.write(cmdOpts.newline ? result + \"\\n\" : result);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { findEntry, parseEnvInput } from \"../../shared/parse.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport function type(env: ParsedEnv, id: string): string {\n const entry = findEntry(env.entries, id);\n if (!entry) {\n throw new Error(`entry not found: ${id}`);\n }\n return entry.payload.data.type;\n}\n\nexport function registerTypeCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"type\")\n .description(\"Output entry type (totp or hotp)\")\n .argument(\"<id>\", \"Entry ID\")\n .option(\"-n, --no-newline\", \"Omit trailing newline\")\n .action(async (id: string, options: { newline: boolean }) => {\n const raw = await readStdinFn();\n if (!raw) {\n console.error(\"Error: Expected JSON from stdin\");\n console.error(\"Usage: cat storage.json | otplib type <id>\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const env = parseEnvInput(raw);\n const result = type(env, id);\n process.stdout.write(options.newline ? result + \"\\n\" : result);\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { type as typeCore } from \"../../otplib/commands/type.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport type TypeOptions = {\n file: string;\n};\n\nexport async function type(id: string, options: TypeOptions): Promise<string> {\n const { file } = options;\n\n if (!id) {\n throw new Error(\"missing required argument: <id>\");\n }\n\n const decrypted = await storage.load(file);\n const raw = JSON.stringify(decrypted);\n const env = parseEnvInput(raw);\n\n return typeCore(env, id);\n}\n\nexport function registerTypeCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"type\")\n .description(\"Output entry type (totp or hotp)\")\n .argument(\"[id]\", \"Entry ID (or read from stdin)\")\n .option(\"-n, --no-newline\", \"Omit trailing newline\")\n .action(async (idArg: string | undefined, cmdOpts: { newline: boolean }) => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const id = idArg || (await readStdinFn()).trim();\n if (!id) {\n console.error(\"error: missing entry ID\");\n process.exitCode = 1;\n return;\n }\n\n const result = await type(id, { file: opts.file });\n process.stdout.write(cmdOpts.newline ? result + \"\\n\" : result);\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { verifyOtp } from \"../../shared/otp.js\";\nimport { findEntry, parseEnvInput } from \"../../shared/parse.js\";\n\nimport type { ParsedEnv } from \"../../shared/parse.js\";\nimport type { ReadStdinFn } from \"../../shared/stdin.js\";\nimport type { Command } from \"commander\";\n\nexport async function verify(env: ParsedEnv, id: string, token: string): Promise<boolean> {\n const entry = findEntry(env.entries, id);\n if (!entry) {\n throw new Error(`entry not found: ${id}`);\n }\n return verifyOtp(entry.payload.data, token, env.guardrails);\n}\n\nexport function registerVerifyCommand(program: Command, readStdinFn: ReadStdinFn): void {\n program\n .command(\"verify\")\n .description(\"Verify a token against an entry\")\n .argument(\"<id>\", \"Entry ID\")\n .argument(\"<token>\", \"Token to verify (6-8 digits)\")\n .action(async (id: string, tokenArg: string) => {\n const raw = await readStdinFn();\n if (!raw) {\n console.error(\"Error: Expected JSON from stdin\");\n console.error(\"Usage: cat storage.json | otplib verify <id> <token>\");\n process.exitCode = 1;\n return;\n }\n\n try {\n const env = parseEnvInput(raw);\n const valid = await verify(env, id, tokenArg);\n if (!valid) {\n process.exitCode = 1;\n }\n } catch (err) {\n console.error(`Error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { verify as verifyCore } from \"../../otplib/commands/verify.js\";\nimport { parseEnvInput } from \"../../shared/parse.js\";\nimport storage from \"../storage/index.js\";\n\nimport type { Command } from \"commander\";\n\nexport type VerifyOptions = {\n file: string;\n};\n\nexport async function verify(id: string, token: string, options: VerifyOptions): Promise<boolean> {\n const { file } = options;\n\n if (!id) {\n throw new Error(\"missing required argument: <id>\");\n }\n if (!token) {\n throw new Error(\"missing required argument: <token>\");\n }\n\n const decrypted = await storage.load(file);\n const raw = JSON.stringify(decrypted);\n const env = parseEnvInput(raw);\n\n return verifyCore(env, id, token);\n}\n\nexport function registerVerifyCommand(program: Command): void {\n program\n .command(\"verify\")\n .description(\"Verify a token against an entry\")\n .argument(\"<id>\", \"Entry ID\")\n .argument(\"<token>\", \"Token to verify (6-8 digits)\")\n .action(async (id: string, token: string) => {\n const opts = program.opts<{ file: string }>();\n\n try {\n const valid = await verify(id, token, { file: opts.file });\n if (!valid) {\n process.exitCode = 1;\n }\n } catch (err) {\n console.error(`error: ${(err as Error).message}`);\n process.exitCode = 1;\n }\n });\n}\n","import { createOtplibxCli } from \"./index.js\";\n\ncreateOtplibxCli()\n .parseAsync(process.argv)\n .catch((err) => {\n console.error(`error: ${err.message}`);\n process.exitCode = 1;\n });\n"],"mappings":"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","names":["crypto","type","import_node_crypto","import_node_fs","resultLines","crypto","path","fs","guardShow","updateCounter","list","token","result","token","type","verify","token","verify","token"]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/package.json CHANGED
@@ -1,52 +1,48 @@
1
1
  {
2
2
  "name": "otplib-cli",
3
- "version": "1.0.1",
4
- "description": "Command-line tool for OTP token generation and verification",
5
- "main": "src/index.js",
3
+ "version": "2.0.0",
4
+ "description": "CLI tool for managing encrypted OTP vaults",
5
+ "license": "MIT",
6
+ "author": "Gerald Yeo <support@yeojz.dev>",
7
+ "type": "module",
6
8
  "bin": {
7
- "otplib": "bin/otplib.js"
8
- },
9
- "scripts": {
10
- "lint": "eslint --ext js src tests",
11
- "test": "mocha tests/**/*.spec.js"
9
+ "otplib": "./dist/index.cjs",
10
+ "otplibx": "./dist/otplibx.cjs"
12
11
  },
12
+ "main": "./dist/index.cjs",
13
+ "files": [
14
+ "dist",
15
+ "bin"
16
+ ],
13
17
  "repository": {
14
18
  "type": "git",
15
- "url": "https://github.com/yeojz/otplib-cli.git"
19
+ "url": "https://github.com/yeojz/otplib.git",
20
+ "directory": "apps/otplib-cli"
16
21
  },
17
22
  "keywords": [
18
- "cli",
19
- "command-line",
20
23
  "otp",
21
24
  "totp",
22
25
  "hotp",
23
- "one time password",
24
- "google authenticator",
25
- "authenticator",
26
- "authentication",
27
- "2FA",
28
- "2 factor",
29
- "node",
30
- "browser"
26
+ "2fa",
27
+ "cli",
28
+ "vault"
31
29
  ],
32
- "author": "Gerald Yeo <contact@fusedthought.com>",
33
- "license": "MIT",
34
- "bugs": {
35
- "url": "https://github.com/yeojz/otplib-cli/issues"
36
- },
37
- "homepage": "https://github.com/yeojz/otplib-cli",
38
30
  "dependencies": {
39
- "commander": "^2.9.0",
40
- "execa": "^0.6.3",
41
- "lodash.omit": "^4.5.0",
42
- "lodash.pick": "^4.4.0",
43
- "ora": "^1.2.0",
44
- "otplib": "^4.0.1",
45
- "qrcode": "^0.8.1"
31
+ "commander": "^14.0.3",
32
+ "otplib": "13.2.1"
46
33
  },
47
34
  "devDependencies": {
48
- "chai": "^3.5.0",
49
- "eslint": "^3.19.0",
50
- "mocha": "^3.2.0"
35
+ "@types/node": "^25.0.10",
36
+ "tsup": "^8.0.1",
37
+ "typescript": "^5.3.3",
38
+ "vitest": "^4.0.18"
39
+ },
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "dev": "tsup --watch",
43
+ "test": "vitest",
44
+ "test:ci": "vitest run --coverage",
45
+ "typecheck": "tsc --noEmit",
46
+ "lint": "eslint src/"
51
47
  }
52
- }
48
+ }
package/.npmignore DELETED
@@ -1,20 +0,0 @@
1
- .DS_Store
2
- coverage
3
- tests
4
- site
5
-
6
- /docs
7
- /buildtools
8
- .babelrc
9
- .editorconfig
10
- .eslintignore
11
- .eslintrc.json
12
- .nycrc
13
- .travis.yml
14
- circle.yml
15
- jsdoc.json
16
- webpack.config.js
17
-
18
- /otplib.json
19
- /otplib-*.json
20
- /otplib-*.png
package/bin/otplib.js DELETED
@@ -1 +0,0 @@
1
- require('../src/cli');
package/src/cli.js DELETED
@@ -1,89 +0,0 @@
1
- /* eslint-disable no-console */
2
- const program = require('commander');
3
- const ora = require('ora');
4
-
5
- const constants = require('./constants');
6
- const endec = require('./endec');
7
- const generate = require('./generate');
8
- const initialise = require('./initialise');
9
- const qrcode = require('./qrcode');
10
- const verify = require('./verify');
11
- const pkg = require('../package.json');
12
-
13
- const cwd = process.cwd();
14
-
15
- program
16
- .version(pkg.version)
17
- .option('-p, --password [password]', 'password to encrypt/decrypt the secret')
18
- .option('-k, --secret [key]', 'provide a secret key')
19
- .option('-c, --config [path]', 'path to configuration file', constants.DEFAULT_FILENAME)
20
- .option('-m, --mode [mode]', 'operation mode. (hotp | totp | authenticator)')
21
- .option('-d, --digits [number]', 'number of digits in token', 6)
22
- .option('-a, --algorithm [type]', 'algorithm to generate for totp (' + constants.SUPPORT_ALGORITHM.join(' | ') + ')', 'sha1')
23
- .option('-s, --step [number]', 'time step (totp / authenticator)', 30);
24
-
25
- program
26
- .command('init')
27
- .description('initialise a new configuration')
28
- .option('-l, --keylength [len]', 'length of secret key', 20)
29
- .option('-o, --output [filename]', 'output file. default: otplib.json', constants.DEFAULT_FILENAME)
30
- .action((opts) => (
31
- initialise(cwd, program, opts)
32
- ));
33
-
34
- program
35
- .command('generate')
36
- .description('generate new tokens')
37
- .option('-e, --epoch [number]', 'time since UNIX epoch (totp / authenticator)')
38
- .option('--counter [number]', 'current counter for HOTP', 0)
39
- .action((opts) => (
40
- generate(cwd, program, opts)
41
- ));
42
-
43
- program
44
- .command('verify [token]')
45
- .description('validate a token against the setting or configuration')
46
- .option('-e, --epoch [number]', 'time since UNIX epoch (totp / authenticator)')
47
- .option('--counter [number]', 'current counter for HOTP', 0)
48
- .action((token, opts) => (
49
- verify(cwd, program, token, opts)
50
- ));
51
-
52
- program
53
- .command('qrcode')
54
- .option('--user [name]', 'user to associate with this', 'my token')
55
- .option('--issuer [name]', 'your service name', 'otplib')
56
- .option('--keyuri', 'show the key uri instead of QR Code')
57
- .option('-o, --output [filename]', 'output file. default: otplib-qrcode.png', constants.DEFAULT_QRCODE_FILENAME)
58
- .description('generate a QR Code from configuration')
59
- .action((opts) => (
60
- qrcode(cwd, program, opts)
61
- ));
62
-
63
- program
64
- .command('encrypt [secret]')
65
- .description('encrypt secret to store in config')
66
- .action((secret) => {
67
- if (!program.password) {
68
- ora().fail('No password provided');
69
- return;
70
- }
71
- ora().succeed(endec.encrypt(program.password, secret));
72
- });
73
-
74
- program
75
- .command('decrypt [secret]')
76
- .description('decrypt secret from config')
77
- .action((secret) => {
78
- if (!program.password) {
79
- ora().fail('No password provided');
80
- return;
81
- }
82
- ora().succeed(endec.decrypt(program.password, secret));
83
- });
84
-
85
- program.parse(process.argv);
86
-
87
- if (!program.args.length) {
88
- program.help()
89
- }
package/src/constants.js DELETED
@@ -1,26 +0,0 @@
1
- const DEFAULT_FILENAME = 'otplib.json';
2
- const DEFAULT_QRCODE_FILENAME = 'otplib-qrcode.png';
3
-
4
- const CLI_OPTIONS = [
5
- 'config',
6
- 'epoch',
7
- 'keylength',
8
- 'mode',
9
- 'output',
10
- 'secret',
11
- 'service',
12
- 'user'
13
- ];
14
-
15
- const SUPPORT_ALGORITHM = [
16
- 'sha1',
17
- 'sha256',
18
- 'sha512'
19
- ];
20
-
21
- module.exports = {
22
- CLI_OPTIONS,
23
- DEFAULT_FILENAME,
24
- DEFAULT_QRCODE_FILENAME,
25
- SUPPORT_ALGORITHM
26
- }
package/src/endec.js DELETED
@@ -1,19 +0,0 @@
1
- const crypto = require('crypto');
2
- const algorithm = 'aes-256-ctr';
3
-
4
- function encrypt(password, text){
5
- const cipher = crypto.createCipher(algorithm, password)
6
- const value = cipher.update(text, 'utf8', 'hex')
7
- return value + cipher.final('hex');
8
- }
9
-
10
- function decrypt(password, text){
11
- const decipher = crypto.createDecipher(algorithm, password)
12
- const value = decipher.update(text, 'hex', 'utf8')
13
- return value + decipher.final('utf8');
14
- }
15
-
16
- module.exports = {
17
- decrypt,
18
- encrypt
19
- }
package/src/generate.js DELETED
@@ -1,61 +0,0 @@
1
- /* eslint-disable no-console */
2
-
3
- const otplib = require('otplib').default;
4
- const ora = require('ora');
5
- const getConfig = require('./getConfig');
6
-
7
- function createGenerator(otp, config, display) {
8
- display.color = 'green';
9
-
10
- let token = otp.generate(config.cli.secret);
11
- const yellow = Math.ceil(config.options.step / 2);
12
-
13
- return function generator() {
14
- const epoch = Math.floor(new Date().getTime() / 1000.0);
15
- const count = epoch % config.options.step;
16
- const timeLeft = config.options.step - count;
17
-
18
- if (timeLeft < 5) {
19
- display.color = 'red';
20
- } else if (timeLeft < yellow) {
21
- display.color = 'yellow';
22
- }
23
-
24
- if (count === 0){
25
- token = otp.generate(config.cli.secret);
26
- display.color = 'green';
27
- }
28
- display.text = '[' + timeLeft + 's] ' + token;
29
- }
30
- }
31
-
32
- function generate(cwd, program, opts) {
33
- const config = getConfig(cwd, program, opts);
34
-
35
- if (config == null) {
36
- return;
37
- }
38
-
39
- const otp = otplib[config.cli.mode];
40
- otp.options = config.options;
41
-
42
- console.log('');
43
- ora().succeed('[mode] ' + config.cli.mode);
44
-
45
- let display = ora('[code] generating').start();
46
-
47
- setTimeout(() => {
48
- if (config.cli.mode === 'hotp') {
49
- const counter = Number(config.options.counter);
50
- const token = otp.generate(config.cli.secret, counter);
51
- display.succeed('[code] ' + token);
52
- return;
53
- }
54
-
55
- // Start TOTP / Authenticator
56
- setInterval(createGenerator(otp, config, display), 1000);
57
-
58
- }, 2000);
59
- }
60
-
61
- module.exports = generate;
package/src/getConfig.js DELETED
@@ -1,45 +0,0 @@
1
- /* eslint-disable no-console */
2
-
3
- const path = require('path');
4
- const omit = require('lodash.omit');
5
- const pick = require('lodash.pick');
6
-
7
- const constants = require('./constants');
8
- const endec = require('./endec');
9
-
10
- function getConfig(cwd, program, options = {}) {
11
- var config = {}
12
-
13
- if (program.config) {
14
- try {
15
- config = require(path.join(cwd, program.config));
16
- } catch (e) {
17
- console.error('No config file provided');
18
- return null;
19
- }
20
- }
21
-
22
- config = Object.assign({}, config, {
23
- algorithm: program.algorithm || config.algorithm,
24
- digits: program.digits || config.digits,
25
- mode: program.mode || config.mode,
26
- secret: program.secret || config.secret,
27
- step: options.step || config.step
28
- });
29
-
30
- if (!config.secret) {
31
- console.error('No secret provided');
32
- return null;
33
- }
34
-
35
- if (program.password) {
36
- config.secret = endec.decrypt(program.password, config.secret);
37
- }
38
-
39
- return {
40
- cli: pick(config, constants.CLI_OPTIONS),
41
- options: omit(config, constants.CLI_OPTIONS)
42
- };
43
- }
44
-
45
- module.exports = getConfig;
package/src/index.js DELETED
@@ -1,2 +0,0 @@
1
- var otplib = require('otplib').default;
2
- module.exports = otplib;
package/src/initialise.js DELETED
@@ -1,74 +0,0 @@
1
- /* eslint-disable no-console */
2
-
3
- const otplib = require('otplib').default;
4
- const ora = require('ora');
5
- const path = require('path');
6
- const fs = require('fs');
7
-
8
- const constants = require('./constants');
9
- const endec = require('./endec');
10
-
11
- function initialise(cwd, program, opts){
12
- let spinner = ora('Generating config').start();
13
- const outputPath = path.join(cwd, opts.output);
14
-
15
- let config = {}
16
- try {
17
- require(outputPath);
18
- spinner.fail('A configuration file of the same name already exists');
19
-
20
- } catch (e) {
21
- config.secret = otplib.authenticator
22
- .generateSecret(Number(opts.keylength));
23
-
24
- if (program.password) {
25
- config.secret = endec.encrypt(program.password, config.secret);
26
- }
27
-
28
- config.mode = program.mode
29
- ? program.mode.toLowerCase()
30
- : 'hotp';
31
-
32
- config.digits = program.digits;
33
-
34
- if (config.mode === 'totp') {
35
- config.algorithm = program.algorithm;
36
-
37
- if (constants.SUPPORT_ALGORITHM.indexOf(config.algorithm) < 0) {
38
- spinner.fail('Unsupported algorithm. Accepted values: ' + constants.SUPPORT_ALGORITHM.join(' | '));
39
- return;
40
- }
41
- }
42
-
43
- if (config.mode === 'totp' || config.mode === 'authenticator') {
44
- config.step = Number(program.step);
45
- }
46
-
47
- spinner.succeed('Done');
48
-
49
- // Sort keys
50
- let ordered = {};
51
- Object.keys(config)
52
- .sort()
53
- .forEach((key) => {
54
- ordered[key] = config[key];
55
- })
56
-
57
- spinner = ora('Writing to file').start();
58
-
59
- fs.writeFile(outputPath, JSON.stringify(ordered, null, 2), function(err) {
60
- if (err) {
61
- spinner.fail('Error writing to file');
62
- console.error(err);
63
- return;
64
- }
65
-
66
- spinner.succeed('Finished');
67
- ordered.secret = '**************';
68
- console.log(JSON.stringify(ordered, null, 2));
69
- return;
70
- });
71
- }
72
- }
73
-
74
- module.exports = initialise;