UNPKG

gmana

Version:

A sleek, interactive, and secure CLI tool for generating and managing passwords with modern UX, strong encryption, and smart features.

768 lines (758 loc) 27.9 kB
#!/usr/bin/env node // src/index.ts import { Command as Command4 } from "commander"; import consola4 from "consola"; // src/commands/gen.ts import { confirm, intro, multiselect, outro, spinner, text } from "@clack/prompts"; import clipboardy from "clipboardy"; import { bgBlue, bold, cyan, dim, green, red, white, yellow } from "colorette"; import { Command } from "commander"; import consola from "consola"; // src/lib/config.ts import fs from "fs-extra"; import os from "os"; import path from "path"; import { z } from "zod"; var ConfigSchema = z.object({ defaultLength: z.number().int().min(4).max(128).default(12), defaultIncludeUppercase: z.boolean().default(true), defaultIncludeLowercase: z.boolean().default(true), defaultIncludeNumbers: z.boolean().default(true), defaultIncludeSymbols: z.boolean().default(true), autoCopy: z.boolean().default(true), saveHistory: z.boolean().default(false), historyLimit: z.number().int().min(0).max(1e3).default(100) }); var CONFIG_DIR = path.join(os.homedir(), ".gmana"); var CONFIG_FILE = path.join(CONFIG_DIR, "config.json"); async function loadConfig() { try { if (await fs.pathExists(CONFIG_FILE)) { const rawConfig = await fs.readJson(CONFIG_FILE); return ConfigSchema.parse(rawConfig); } } catch { } return ConfigSchema.parse({}); } async function saveConfig(config) { await fs.ensureDir(CONFIG_DIR); const currentConfig = await loadConfig(); const newConfig = { ...currentConfig, ...config }; const validatedConfig = ConfigSchema.parse(newConfig); await fs.writeJson(CONFIG_FILE, validatedConfig, { spaces: 2 }); } // src/lib/history.ts import fs2 from "fs-extra"; import os2 from "os"; import path2 from "path"; import { z as z2 } from "zod"; var HistoryEntrySchema = z2.object({ id: z2.string(), password: z2.string(), options: z2.object({ length: z2.number(), includeUppercase: z2.boolean(), includeLowercase: z2.boolean(), includeNumbers: z2.boolean(), includeSymbols: z2.boolean(), includeExtraSymbols: z2.boolean() }), createdAt: z2.string() }); var HistorySchema = z2.array(HistoryEntrySchema); var CONFIG_DIR2 = path2.join(os2.homedir(), ".gmana"); var HISTORY_FILE = path2.join(CONFIG_DIR2, "history.json"); async function saveToHistory(password, options) { await fs2.ensureDir(CONFIG_DIR2); const history = await loadHistory(); const entry = { id: crypto.randomUUID(), password, options: { length: options.length, includeUppercase: options.includeUppercase, includeLowercase: options.includeLowercase, includeNumbers: options.includeNumbers, includeSymbols: options.includeSymbols, includeExtraSymbols: options.includeExtraSymbols }, createdAt: (/* @__PURE__ */ new Date()).toISOString() }; history.unshift(entry); const limitedHistory = history.slice(0, 100); await fs2.writeJson(HISTORY_FILE, limitedHistory, { spaces: 2 }); } async function loadHistory() { try { if (await fs2.pathExists(HISTORY_FILE)) { const rawHistory = await fs2.readJson(HISTORY_FILE); return HistorySchema.parse(rawHistory); } } catch { } return []; } async function clearHistory() { if (await fs2.pathExists(HISTORY_FILE)) { await fs2.remove(HISTORY_FILE); } } // src/lib/password-generator.ts import crypto2 from "crypto"; import { z as z3 } from "zod"; var PasswordOptionsSchema = z3.object({ length: z3.number().int().min(4).max(128).default(12), includeUppercase: z3.boolean().default(true), includeLowercase: z3.boolean().default(true), includeNumbers: z3.boolean().default(true), includeSymbols: z3.boolean().default(true), includeExtraSymbols: z3.boolean().default(false), excludeSimilar: z3.boolean().default(false), excludeAmbiguous: z3.boolean().default(false), customChars: z3.string().optional(), pattern: z3.string().optional() }); var PasswordGenerator = class { static LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; static UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static NUMBERS = "0123456789"; static SYMBOLS = "!@#$%^&*"; static EXTRA_SYMBOLS = "()_+-=[]{}|;:,.<>?"; static SIMILAR_CHARS = "il1Lo0O"; static AMBIGUOUS_CHARS = "{}[]()/\\'\"`~,;.<>"; static generate(options) { const validatedOptions = PasswordOptionsSchema.parse(options); if (validatedOptions.customChars) { return this.generateFromCustomChars(validatedOptions.customChars, validatedOptions.length); } const charset = this.buildCharset(validatedOptions); if (charset.length === 0) { throw new Error("No character types selected for password generation"); } return this.generateSecurePassword(charset, validatedOptions.length); } static buildCharset(options) { let charset = ""; if (options.includeLowercase) charset += this.LOWERCASE; if (options.includeUppercase) charset += this.UPPERCASE; if (options.includeNumbers) charset += this.NUMBERS; if (options.includeSymbols) charset += this.SYMBOLS; if (options.includeExtraSymbols) charset += this.EXTRA_SYMBOLS; if (options.excludeSimilar) { charset = charset.split("").filter((char) => !this.SIMILAR_CHARS.includes(char)).join(""); } if (options.excludeAmbiguous) { charset = charset.split("").filter((char) => !this.AMBIGUOUS_CHARS.includes(char)).join(""); } return charset; } static generateSecurePassword(charset, length) { const password = new Array(length); const charsetLength = charset.length; for (let i = 0; i < length; i++) { const randomIndex = crypto2.randomInt(0, charsetLength); password[i] = charset[randomIndex]; } return password.join(""); } static generateFromCustomChars(customChars, length) { return this.generateSecurePassword(customChars, length); } static calculateStrength(password) { let score = 0; const feedback = []; if (password.length >= 12) score += 25; else if (password.length >= 8) score += 15; else if (password.length >= 6) score += 10; else feedback.push("Password should be at least 8 characters long"); if (/[a-z]/.test(password)) score += 15; else feedback.push("Add lowercase letters"); if (/[A-Z]/.test(password)) score += 15; else feedback.push("Add uppercase letters"); if (/\d/.test(password)) score += 15; else feedback.push("Add numbers"); if (/[!@#$%^&*()_+\-=[\]{}|;:,.<>?]/.test(password)) score += 20; else feedback.push("Add special characters"); if (/(.)\1{2,}/.test(password)) { score -= 10; feedback.push("Avoid repeating characters"); } if (/123|abc|qwe/i.test(password)) { score -= 15; feedback.push("Avoid common sequences"); } const level = score >= 90 ? "Very Strong" : score >= 75 ? "Strong" : score >= 60 ? "Good" : score >= 40 ? "Fair" : score >= 20 ? "Weak" : "Very Weak"; return { score: Math.max(0, score), level, feedback }; } }; // src/commands/gen.ts var genCommand = new Command().name("gen").alias("g").description("\u{1F3B2} Generate a secure password").option("-l, --length <number>", "password length", "12").option("-i, --interactive", "interactive mode", false).option("--no-uppercase", "exclude uppercase letters").option("--no-lowercase", "exclude lowercase letters").option("--no-numbers", "exclude numbers").option("--no-symbols", "exclude symbols").option("--extra-symbols", "include extra symbols").option("--exclude-similar", "exclude similar characters (il1Lo0O)").option("--exclude-ambiguous", "exclude ambiguous characters").option("-c, --copy", "copy to clipboard", true).option("-s, --save", "save to history", false).option("--show-strength", "show password strength", true).action(async (options) => { try { if (options.interactive) { await runInteractiveMode(); } else { await runCommandMode(options); } } catch (error) { if (error && typeof error === "object" && "message" in error) { consola.error("Generation failed:", error.message); } else { consola.error("An unexpected error occurred"); } process.exit(1); } }); async function runInteractiveMode() { intro(cyan("\u{1F510} Password Generator")); const length = await text({ message: "Password length?", placeholder: "12", validate: (value) => { const num = parseInt(value || "12"); if (isNaN(num) || num < 4 || num > 128) { return "Length must be between 4 and 128"; } } }); const charTypes = await multiselect({ message: "Select character types:", options: [ { value: "uppercase", label: "Uppercase (A-Z)", hint: "recommended" }, { value: "lowercase", label: "Lowercase (a-z)", hint: "recommended" }, { value: "numbers", label: "Numbers (0-9)", hint: "recommended" }, { value: "symbols", label: "Symbols (!@#$)", hint: "recommended" }, { value: "extraSymbols", label: "Extra Symbols ([]{}|)", hint: "optional" } ], initialValues: ["uppercase", "lowercase", "numbers", "symbols"] }); const excludeOptions = await multiselect({ message: "Exclude characters? (optional)", options: [ { value: "similar", label: "Similar chars (il1Lo0O)" }, { value: "ambiguous", label: "Ambiguous chars ({}[]/\\)" } ], required: false }); const copyToClipboard = await confirm({ message: "Copy to clipboard?", initialValue: true }); const saveToHistoryConfirm = await confirm({ message: "Save to history?", initialValue: false }); const passwordOptions = { length: parseInt(length) || 12, includeUppercase: charTypes.includes("uppercase"), includeLowercase: charTypes.includes("lowercase"), includeNumbers: charTypes.includes("numbers"), includeSymbols: charTypes.includes("symbols"), includeExtraSymbols: charTypes.includes("extraSymbols"), excludeSimilar: excludeOptions.includes("similar"), excludeAmbiguous: excludeOptions.includes("ambiguous") }; await generateAndDisplay(passwordOptions, { copy: copyToClipboard, save: saveToHistoryConfirm, showStrength: true }); outro(green("\u2728 Done!")); } async function runCommandMode(options) { const config = await loadConfig(); const passwordOptions = { length: parseInt(options.length || "") || config.defaultLength || 12, includeUppercase: options.uppercase !== false, includeLowercase: options.lowercase !== false, includeNumbers: options.numbers !== false, includeSymbols: options.symbols !== false, includeExtraSymbols: options.extraSymbols ?? false, excludeSimilar: options.excludeSimilar ?? false, excludeAmbiguous: options.excludeAmbiguous ?? false }; await generateAndDisplay(passwordOptions, { copy: options.copy ?? false, save: options.save ?? false, showStrength: options.showStrength ?? false }); } async function generateAndDisplay(options, actions) { const s = spinner(); s.start("Generating secure password..."); await new Promise((resolve) => setTimeout(resolve, 500)); const password = PasswordGenerator.generate(options); s.stop("Password generated!"); console.log("\n" + bgBlue(white(" Generated Password "))); console.log(bold(white(password))); if (actions.showStrength) { const strength = PasswordGenerator.calculateStrength(password); const strengthColor = strength.score >= 75 ? green : strength.score >= 50 ? yellow : red; console.log(` ${bold("Strength:")} ${strengthColor(strength.level)} (${strength.score}/100)`); if (strength.feedback.length > 0) { console.log(dim("Suggestions: " + strength.feedback.join(", "))); } } if (actions.copy) { try { await clipboardy.write(password); consola.success("\u{1F4CB} Copied to clipboard!"); } catch { consola.warn("Failed to copy to clipboard"); } } if (actions.save) { try { await saveToHistory(password, options); consola.success("\u{1F4BE} Saved to history!"); } catch { consola.warn("Failed to save to history"); } } } // package.json var package_default = { name: "gmana", version: "1.0.6", description: "A sleek, interactive, and secure CLI tool for generating and managing passwords with modern UX, strong encryption, and smart features.", keywords: [ "cli", "password-generator", "secure-password", "interactive-cli", "typescript", "encryption", "security", "password-manager", "gmana" ], homepage: "https://github.com/sun-sreng/gmana-cli#readme", bugs: { url: "https://github.com/sun-sreng/gmana-cli/issues" }, repository: { type: "git", url: "https://github.com/sun-sreng/gmana-cli.git" }, license: "MIT", author: { name: "Sun Sreng", email: "sun.sreng123@gmail.com" }, type: "module", bin: { gmana: "dist/index.js" }, files: [ "dist", "README.md", "LICENSE" ], scripts: { build: "tsup", clean: "rimraf dist node_modules bun.lock package-lock.json", dev: "tsup --watch", format: 'prettier --write "**/*.{ts,tsx,mdx}" --cache', "format:check": 'prettier --check "**/*.{ts,tsx,mdx}" --cache', lint: "eslint src --ext .ts,.tsx", "lint:fix": "eslint src --ext .ts,.tsx --fix", prepublishOnly: "npm run build", pub: "npm publish --access public", start: "node dist/index.js", test: "vitest", "test:ui": "vitest --ui", "type-check": "tsc --noEmit" }, dependencies: { "@clack/prompts": "^0.7.0", clipboardy: "^4.0.0", colorette: "^2.0.20", commander: "^11.1.0", consola: "^3.2.3", execa: "^8.0.1", "fs-extra": "^11.2.0", ora: "^8.0.1", picocolors: "^1.0.0", zod: "^3.22.4" }, devDependencies: { "@eslint/css": "^0.8.1", "@eslint/js": "^9.28.0", "@eslint/json": "^0.12.0", "@eslint/markdown": "^6.4.0", "@types/fs-extra": "^11.0.4", "@types/node": "^20.10.5", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", esbuild: "^0.25.6", eslint: "^9.28.0", globals: "^16.2.0", prettier: "^3.1.1", rimraf: "^6.0.1", tsup: "^8.0.1", typescript: "^5.3.3", "typescript-eslint": "^8.33.0", vitest: "^3.2.4" }, engines: { node: ">=18.0.0" }, preferGlobal: true }; // src/commands/config.ts import { confirm as confirm2, intro as intro2, outro as outro2, select, text as text2 } from "@clack/prompts"; import { bold as bold2, cyan as cyan2, green as green2 } from "colorette"; import { Command as Command2 } from "commander"; import consola2 from "consola"; var configCommand = new Command2().name("config").alias("c").description("\u2699\uFE0F Manage configuration settings").option("-s, --show", "show current configuration").option("-r, --reset", "reset to default configuration").option("--set <key=value>", "set a configuration value").action(async (options) => { try { if (options.show) { await showConfig(); } else if (options.reset) { await resetConfig(); } else if (options.set) { await setConfigValue(options.set); } else { await interactiveConfig(); } } catch (error) { if (error && typeof error === "object" && "message" in error) { consola2.error("Configuration failed:", error.message); } else { consola2.error("An unexpected error occurred"); } process.exit(1); } }); async function showConfig() { const config = await loadConfig(); console.log(cyan2("\n\u{1F4CB} Current Configuration:")); console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"); const configEntries = [ ["Default Length", config.defaultLength], ["Include Uppercase", config.defaultIncludeUppercase ? "\u2705" : "\u274C"], ["Include Lowercase", config.defaultIncludeLowercase ? "\u2705" : "\u274C"], ["Include Numbers", config.defaultIncludeNumbers ? "\u2705" : "\u274C"], ["Include Symbols", config.defaultIncludeSymbols ? "\u2705" : "\u274C"], ["Auto Copy", config.autoCopy ? "\u2705" : "\u274C"], ["Save History", config.saveHistory ? "\u2705" : "\u274C"], ["History Limit", config.historyLimit] ]; configEntries.forEach(([key, value]) => { console.log(`${bold2(key.toString().padEnd(20))}: ${value}`); }); console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"); } async function resetConfig() { const confirmed = await confirm2({ message: "Are you sure you want to reset all settings to defaults?" }); if (confirmed) { await saveConfig({ defaultLength: 12, defaultIncludeUppercase: true, defaultIncludeLowercase: true, defaultIncludeNumbers: true, defaultIncludeSymbols: true, autoCopy: true, saveHistory: false, historyLimit: 100 }); consola2.success("\u{1F504} Configuration reset to defaults"); } else { consola2.info("Operation cancelled"); } } async function setConfigValue(keyValue) { const [rawKey, rawValue] = keyValue.split("="); const key = rawKey?.trim().toLowerCase(); const value = rawValue?.trim(); if (!key || value === void 0) { consola2.error("\u274C Invalid format. Use: --set key=value"); return; } const updates = {}; const parseAndValidateNumber = (val, min, max, errorMsg) => { const num = parseInt(val, 10); if (isNaN(num) || num < min || num > max) { consola2.error(`\u274C ${errorMsg}`); return null; } return num; }; switch (key) { case "defaultlength": case "length": { const length = parseAndValidateNumber(value, 4, 128, "Length must be between 4 and 128"); if (length === null) return; updates.defaultLength = length; break; } case "autocopy": updates.autoCopy = value.toLowerCase() === "true"; break; case "savehistory": updates.saveHistory = value.toLowerCase() === "true"; break; case "historylimit": { const limit = parseAndValidateNumber(value, 0, 1e3, "History limit must be between 0 and 1000"); if (limit === null) return; updates.historyLimit = limit; break; } default: consola2.error(`\u274C Unknown configuration key: ${key}`); return; } await saveConfig(updates); consola2.success(`\u2705 Updated ${key} = ${value}`); } async function interactiveConfig() { intro2(cyan2("\u2699\uFE0F Configuration Settings")); const config = await loadConfig(); const action = await select({ message: "What would you like to configure?", options: [ { value: "defaults", label: "\u{1F3AF} Default Password Settings" }, { value: "behavior", label: "\u26A1 Behavior Settings" }, { value: "history", label: "\u{1F4DA} History Settings" }, { value: "show", label: "\u{1F440} Show Current Config" }, { value: "reset", label: "\u{1F504} Reset to Defaults" } ] }); switch (action) { case "defaults": await configureDefaults(config); break; case "behavior": await configureBehavior(config); break; case "history": await configureHistory(config); break; case "show": await showConfig(); break; case "reset": await resetConfig(); break; } outro2(green2("\u2728 Configuration updated!")); } async function configureDefaults(config) { const length = await text2({ message: "Default password length?", placeholder: config.defaultLength.toString(), validate: (value) => { const num = parseInt(value || config.defaultLength.toString()); if (isNaN(num) || num < 4 || num > 128) { return "Length must be between 4 and 128"; } } }); const uppercase = await confirm2({ message: "Include uppercase letters by default?", initialValue: config.defaultIncludeUppercase }); const lowercase = await confirm2({ message: "Include lowercase letters by default?", initialValue: config.defaultIncludeLowercase }); const numbers = await confirm2({ message: "Include numbers by default?", initialValue: config.defaultIncludeNumbers }); const symbols = await confirm2({ message: "Include symbols by default?", initialValue: config.defaultIncludeSymbols }); await saveConfig({ defaultLength: parseInt(length) || config.defaultLength, defaultIncludeUppercase: uppercase, defaultIncludeLowercase: lowercase, defaultIncludeNumbers: numbers, defaultIncludeSymbols: symbols }); } async function configureBehavior(config) { const autoCopy = await confirm2({ message: "Auto-copy passwords to clipboard?", initialValue: config.autoCopy }); await saveConfig({ autoCopy }); } async function configureHistory(config) { const saveHistory = await confirm2({ message: "Save password history?", initialValue: config.saveHistory }); let historyLimit = config.historyLimit; if (saveHistory) { const limitText = await text2({ message: "Maximum history entries?", placeholder: config.historyLimit.toString(), validate: (value) => { const num = parseInt(value || config.historyLimit.toString()); if (isNaN(num) || num < 0 || num > 1e3) { return "Limit must be between 0 and 1000"; } } }); historyLimit = parseInt(limitText) || config.historyLimit; } await saveConfig({ saveHistory, historyLimit }); } // src/commands/history.ts import { confirm as confirm3, intro as intro3, outro as outro3, select as select2 } from "@clack/prompts"; import clipboardy2 from "clipboardy"; import { bold as bold3, cyan as cyan3, dim as dim2, green as green3, yellow as yellow2 } from "colorette"; import { Command as Command3 } from "commander"; import consola3 from "consola"; var historyCommand = new Command3().name("history").alias("h").description("\u{1F4DA} Manage password history").option("-l, --list", "list password history").option("-c, --clear", "clear password history").option("--limit <number>", "limit number of entries to show", "10").action(async (options) => { try { if (options.clear) { await clearHistoryCommand(); } else if (options.list) { await listHistory(parseInt(options.limit)); } else { await interactiveHistory(); } } catch (error) { if (error && typeof error === "object" && "message" in error) { consola3.error("History operation failed:", error.message); } else { consola3.error("An unexpected error occurred"); } process.exit(1); } }); async function listHistory(limit = 10) { const history = await loadHistory(); if (history.length === 0) { consola3.info("\u{1F4ED} No password history found"); return; } console.log(cyan3(` \u{1F4DA} Password History (${Math.min(limit, history.length)} of ${history.length}):`)); console.log("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"); const entries = history.slice(0, limit); entries.forEach((entry, index) => { const date = new Date(entry.createdAt).toLocaleString(); const maskedPassword = maskPassword(entry.password); const options = formatOptions(entry.options); console.log(` ${bold3(`${index + 1}.`)} ${dim2(date)}`); console.log(` Password: ${yellow2(maskedPassword)}`); console.log(` Settings: ${dim2(options)}`); }); console.log("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"); } async function clearHistoryCommand() { const confirmed = await confirm3({ message: "Are you sure you want to clear all password history?" }); if (confirmed) { await clearHistory(); consola3.success("\u{1F5D1}\uFE0F Password history cleared"); } else { consola3.info("Operation cancelled"); } } async function interactiveHistory() { intro3(cyan3("\u{1F4DA} Password History")); const history = await loadHistory(); if (history.length === 0) { consola3.info("\u{1F4ED} No password history found"); outro3("Generate some passwords first!"); return; } const action = await select2({ message: "What would you like to do?", options: [ { value: "view", label: "\u{1F440} View History" }, { value: "copy", label: "\u{1F4CB} Copy Password" }, { value: "clear", label: "\u{1F5D1}\uFE0F Clear History" } ] }); switch (action) { case "view": await listHistory(20); break; case "copy": await copyFromHistory(history); break; case "clear": await clearHistoryCommand(); break; } outro3(green3("\u2728 Done!")); } async function copyFromHistory(history) { const options = history.slice(0, 10).map((entry) => { const date = new Date(entry.createdAt).toLocaleDateString(); const maskedPassword = maskPassword(entry.password); const settings = formatOptions(entry.options); return { value: entry.id, label: `${maskedPassword} (${date})`, hint: settings }; }); const selectedId = await select2({ message: "Select password to copy:", options }); const selectedEntry = history.find((entry) => entry.id === selectedId); if (selectedEntry) { try { await clipboardy2.write(selectedEntry.password); consola3.success("\u{1F4CB} Password copied to clipboard!"); } catch { consola3.error("Failed to copy password to clipboard"); } } } function maskPassword(password) { if (password.length <= 4) { return "\u2022\u2022\u2022\u2022"; } const start = password.substring(0, 2); const end = password.substring(password.length - 2); const middle = "\u2022".repeat(password.length - 4); return `${start}${middle}${end}`; } function formatOptions(options) { const parts = []; parts.push(`L:${options.length}`); if (options.includeUppercase) parts.push("A-Z"); if (options.includeLowercase) parts.push("a-z"); if (options.includeNumbers) parts.push("0-9"); if (options.includeSymbols) parts.push("!@#"); if (options.includeExtraSymbols) parts.push("[]{}"); return parts.join(" "); } // src/index.ts async function main() { const program = new Command4().name("gmana").description("\u{1F510} A modern password generator CLI").version(package_default.version, "-v, --version", "display version number").helpOption("-h, --help", "display help for command"); program.addCommand(genCommand).addCommand(configCommand).addCommand(historyCommand); program.exitOverride(); try { await program.parseAsync(); } catch (error) { if (error instanceof Error && error.name === "CommanderError") { process.exit(0); } consola4.error("Unexpected error:", error instanceof Error ? error.message : error); process.exit(1); } } main(); //# sourceMappingURL=index.js.map