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
JavaScript
#!/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