smart-passphrase 1.0.2 → 2.0.1
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.
- package/README.md +56 -102
- package/dist/cjs/cli.js +69 -0
- package/dist/cjs/generator.js +12 -12
- package/dist/cjs/index.js +4 -4
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +64 -0
- package/dist/generator.d.ts +1 -1
- package/dist/generator.js +95 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.js +51 -0
- package/dist/wordlist.js +110 -0
- package/package.json +19 -12
package/README.md
CHANGED
|
@@ -1,44 +1,52 @@
|
|
|
1
|
+
Human-readable, secure password generator for developers and CLI usage.
|
|
1
2
|
# smart-passphrase
|
|
2
3
|
|
|
3
4
|
A lightweight, secure, and memorable passphrase generator for Node.js and modern browsers. Built to work smoothly in React, Next.js, Vite, and plain Node projects.
|
|
4
5
|
|
|
5
6
|
## Features
|
|
6
|
-
- Human‑readable passphrases
|
|
7
|
-
-
|
|
8
|
-
- Works in React, Next.js, Vite, and Node 18
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
7
|
+
- **Memorable**: Human‑readable passphrases that are easy to type and remember.
|
|
8
|
+
- **Secure**: Uses cryptographically secure randomness (via `crypto.getRandomValues`).
|
|
9
|
+
- **Versatile**: Works in React, Next.js, Vite, and Node 18+.
|
|
10
|
+
- **Premium CLI**: Beautifully styled terminal output with colors and animations.
|
|
11
|
+
- **Clipboard Support**: Copy passphrases instantly from the CLI.
|
|
12
|
+
- **Zero Dependencies (Core)**: Only adds dependencies for the CLI tool.
|
|
13
|
+
- **Fully Typed**: Written in TypeScript with a complete API.
|
|
12
14
|
|
|
13
15
|
## Install
|
|
14
16
|
```bash
|
|
15
17
|
npm install smart-passphrase
|
|
16
18
|
```
|
|
17
19
|
|
|
18
|
-
## Quick Start
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const passphrase = generatePassphrase();
|
|
23
|
-
console.log(passphrase);
|
|
20
|
+
## Quick Start (Terminal)
|
|
21
|
+
Generate a passphrase instantly with a premium experience:
|
|
22
|
+
```bash
|
|
23
|
+
npx smart-passphrase
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
**Pro Tip (Copy to clipboard):**
|
|
27
|
+
```bash
|
|
28
|
+
npx smart-passphrase copy
|
|
29
|
+
# or
|
|
30
|
+
npx smart-passphrase -c
|
|
27
31
|
```
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
|
|
33
|
+
**Custom Strength:**
|
|
34
|
+
```bash
|
|
35
|
+
npx smart-passphrase --strength ultra
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
##
|
|
34
|
-
```
|
|
38
|
+
## Quick Start (Code)
|
|
39
|
+
```ts
|
|
35
40
|
import { generatePassphrase } from "smart-passphrase";
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
const passphrase = generatePassphrase();
|
|
43
|
+
console.log(passphrase);
|
|
44
|
+
// Output: SilentGOOSE^mark324
|
|
40
45
|
```
|
|
41
46
|
|
|
47
|
+
## Example Output
|
|
48
|
+
The CLI provides a vibrant, gradient-colored output that stands out!
|
|
49
|
+
|
|
42
50
|
## API
|
|
43
51
|
|
|
44
52
|
### `generatePassphrase(options?)`
|
|
@@ -71,102 +79,48 @@ const bits = entropyEstimate({ words: 4, symbols: true, numbers: true });
|
|
|
71
79
|
## Options
|
|
72
80
|
|
|
73
81
|
### `words`
|
|
74
|
-
Number of word tokens in the passphrase.
|
|
75
|
-
|
|
76
|
-
```ts
|
|
77
|
-
generatePassphrase({ words: 4 });
|
|
78
|
-
```
|
|
82
|
+
Number of word tokens in the passphrase. Default is `3`.
|
|
79
83
|
|
|
80
84
|
### `numbers`
|
|
81
|
-
- `true` (default)
|
|
82
|
-
- `{ digits: number }
|
|
83
|
-
|
|
84
|
-
```ts
|
|
85
|
-
generatePassphrase({ numbers: { digits: 4 } });
|
|
86
|
-
```
|
|
85
|
+
- `true` (default): Adds digits.
|
|
86
|
+
- `{ digits: number }`: Controls the number of digits.
|
|
87
|
+
- `false`: Removes digits.
|
|
87
88
|
|
|
88
89
|
### `symbols`
|
|
89
|
-
- `true` (default)
|
|
90
|
-
- `string[]
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
generatePassphrase({ symbols: ["$", "-", "_"] });
|
|
94
|
-
```
|
|
90
|
+
- `true` (default): Includes one random symbol.
|
|
91
|
+
- `string[]`: Provide your own symbol list to pick from.
|
|
92
|
+
- `false`: Removes symbols.
|
|
95
93
|
|
|
96
94
|
### `uppercaseStyle`
|
|
97
|
-
Controls casing:
|
|
98
|
-
|
|
99
|
-
```ts
|
|
100
|
-
"none" | "random" | "title" | "upper" | "lower"
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Example:
|
|
104
|
-
```ts
|
|
105
|
-
generatePassphrase({ uppercaseStyle: "title" });
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### `separator`
|
|
109
|
-
String inserted between all parts.
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
generatePassphrase({ separator: "-" });
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### `unique`
|
|
116
|
-
Avoid repeating words within the same passphrase.
|
|
117
|
-
|
|
118
|
-
```ts
|
|
119
|
-
generatePassphrase({ unique: true });
|
|
120
|
-
```
|
|
95
|
+
Controls casing style for the words:
|
|
96
|
+
`"none" | "random" | "title" | "upper" | "lower"`
|
|
121
97
|
|
|
122
98
|
### `strength`
|
|
123
|
-
Preset security tiers
|
|
124
|
-
|
|
125
|
-
```ts
|
|
126
|
-
"medium" | "strong" | "ultra"
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Each tier increases words, digits, and entropy.
|
|
99
|
+
Preset security tiers that adjust words, symbols, and digits.
|
|
130
100
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
101
|
+
| Tier | Words | Symbols | Digits | Approx. Entropy |
|
|
102
|
+
| :--- | :--- | :--- | :--- | :--- |
|
|
103
|
+
| `medium` | 3 | Yes | 3 | ~45-50 bits |
|
|
104
|
+
| `strong` | 4 | Yes | 4 | ~60-70 bits |
|
|
105
|
+
| `ultra` | 5 | Yes | 5 | ~80+ bits |
|
|
134
106
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
```ts
|
|
139
|
-
generatePassphrase({ pattern: ["adj", "noun", "verb"] });
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
Available word kinds:
|
|
143
|
-
```ts
|
|
144
|
-
"adj" | "noun" | "verb"
|
|
145
|
-
```
|
|
107
|
+
## CLI Reference
|
|
108
|
+
Run `npx smart-passphrase [command] [options]` or install globally.
|
|
146
109
|
|
|
147
|
-
###
|
|
148
|
-
|
|
110
|
+
### Commands
|
|
111
|
+
- `[default]` - Generate a passphrase.
|
|
112
|
+
- `copy` - Generate and copy to clipboard immediately.
|
|
149
113
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
```
|
|
114
|
+
### Options
|
|
115
|
+
- `-w, --words <n>` - Set the number of words (default: 3).
|
|
116
|
+
- `-s, --strength <tier>` - Set tier (medium, strong, ultra).
|
|
117
|
+
- `-c, --copy` - Copy generated passphrase to clipboard.
|
|
118
|
+
- `-n, --no-numbers` - Disable numbers.
|
|
119
|
+
- `-S, --no-symbols` - Disable symbols.
|
|
159
120
|
|
|
160
121
|
## Security Notes
|
|
161
122
|
- Uses `crypto.getRandomValues` for strong randomness.
|
|
162
123
|
- Requires Node 18+ or a modern browser runtime.
|
|
163
|
-
- For even higher entropy, increase `words`, `digits`, or use `strength: "ultra"`.
|
|
164
124
|
|
|
165
125
|
## License
|
|
166
|
-
MIT
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
If you want, I can also add:
|
|
170
|
-
1. A `COPY` helper utility example
|
|
171
|
-
2. A CLI usage snippet
|
|
172
|
-
3. A comparison table for `strength` presets
|
|
126
|
+
MIT
|
package/dist/cjs/cli.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const clipboardy_1 = __importDefault(require("clipboardy"));
|
|
11
|
+
const gradient_string_1 = __importDefault(require("gradient-string"));
|
|
12
|
+
const generator_js_1 = require("./generator.js");
|
|
13
|
+
const program = new commander_1.Command();
|
|
14
|
+
program
|
|
15
|
+
.name("smart-passphrase")
|
|
16
|
+
.description("Generate secure and memorable passphrases with style")
|
|
17
|
+
.version("2.0.0")
|
|
18
|
+
.option("-w, --words <number>", "number of words", (val) => parseInt(val), 3)
|
|
19
|
+
.option("-s, --strength <tier>", "security tier (medium, strong, ultra)", "medium")
|
|
20
|
+
.option("-n, --no-numbers", "do not include numbers")
|
|
21
|
+
.option("-S, --no-symbols", "do not include symbols")
|
|
22
|
+
.option("-c, --copy", "copy to clipboard")
|
|
23
|
+
.action(async (options) => {
|
|
24
|
+
console.log("");
|
|
25
|
+
const spinner = (0, ora_1.default)({
|
|
26
|
+
text: chalk_1.default.cyan("Locking the gears..."),
|
|
27
|
+
color: "cyan"
|
|
28
|
+
}).start();
|
|
29
|
+
// Small delay for "fancy" feel
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
31
|
+
try {
|
|
32
|
+
const password = (0, generator_js_1.generatePassword)({
|
|
33
|
+
words: options.words,
|
|
34
|
+
strength: options.strength,
|
|
35
|
+
numbers: options.numbers,
|
|
36
|
+
symbols: options.symbols,
|
|
37
|
+
});
|
|
38
|
+
spinner.succeed(chalk_1.default.green("Secure passphrase ready!"));
|
|
39
|
+
console.log("");
|
|
40
|
+
const styledPassword = gradient_string_1.default.pastel.multiline(password);
|
|
41
|
+
console.log(" " + chalk_1.default.bold(styledPassword));
|
|
42
|
+
console.log("");
|
|
43
|
+
if (options.copy) {
|
|
44
|
+
clipboardy_1.default.writeSync(password);
|
|
45
|
+
console.log(chalk_1.default.dim(" 📋 Copied to clipboard!"));
|
|
46
|
+
console.log("");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
spinner.fail(chalk_1.default.red("Generation failed"));
|
|
51
|
+
console.error(chalk_1.default.red(` Error: ${error.message}`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
// Handle the "copy" as a shorthand command too
|
|
56
|
+
program
|
|
57
|
+
.command("copy")
|
|
58
|
+
.description("Generate and copy to clipboard immediately")
|
|
59
|
+
.action(async () => {
|
|
60
|
+
const spinner = (0, ora_1.default)(chalk_1.default.cyan("Generating and copying...")).start();
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
62
|
+
const password = (0, generator_js_1.generatePassword)();
|
|
63
|
+
clipboardy_1.default.writeSync(password);
|
|
64
|
+
spinner.succeed(chalk_1.default.green("Generated and copied to clipboard!"));
|
|
65
|
+
console.log("");
|
|
66
|
+
console.log(" " + gradient_string_1.default.pastel(password));
|
|
67
|
+
console.log("");
|
|
68
|
+
});
|
|
69
|
+
program.parse();
|
package/dist/cjs/generator.js
CHANGED
|
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.generatePassphrase = generatePassphrase;
|
|
4
4
|
exports.generatePassword = generatePassword;
|
|
5
5
|
exports.entropyEstimate = entropyEstimate;
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const wordlist_js_1 = require("./wordlist.js");
|
|
7
|
+
const utils_js_1 = require("./utils.js");
|
|
8
8
|
const strengthDefaults = {
|
|
9
9
|
medium: { words: 3, symbols: true, digits: 3 },
|
|
10
10
|
strong: { words: 4, symbols: true, digits: 4 },
|
|
@@ -12,9 +12,9 @@ const strengthDefaults = {
|
|
|
12
12
|
};
|
|
13
13
|
function buildDictionary(overrides) {
|
|
14
14
|
return {
|
|
15
|
-
adjectives: overrides?.adjectives ??
|
|
16
|
-
nouns: overrides?.nouns ??
|
|
17
|
-
verbs: overrides?.verbs ??
|
|
15
|
+
adjectives: overrides?.adjectives ?? wordlist_js_1.adjectives,
|
|
16
|
+
nouns: overrides?.nouns ?? wordlist_js_1.nouns,
|
|
17
|
+
verbs: overrides?.verbs ?? wordlist_js_1.verbs
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
function chooseWords(dictionary, kinds, unique) {
|
|
@@ -25,11 +25,11 @@ function chooseWords(dictionary, kinds, unique) {
|
|
|
25
25
|
if (list.length === 0) {
|
|
26
26
|
throw new Error(`Dictionary list for ${kind} is empty`);
|
|
27
27
|
}
|
|
28
|
-
let picked = (0,
|
|
28
|
+
let picked = (0, utils_js_1.randomItem)(list);
|
|
29
29
|
if (unique) {
|
|
30
30
|
let attempts = 0;
|
|
31
31
|
while (used.has(picked) && attempts < 25) {
|
|
32
|
-
picked = (0,
|
|
32
|
+
picked = (0, utils_js_1.randomItem)(list);
|
|
33
33
|
attempts += 1;
|
|
34
34
|
}
|
|
35
35
|
}
|
|
@@ -60,17 +60,17 @@ function generatePassphrase(options = {}) {
|
|
|
60
60
|
if (pattern.length !== wordCount) {
|
|
61
61
|
throw new Error("pattern length must match words");
|
|
62
62
|
}
|
|
63
|
-
const words = chooseWords(dictionary, pattern, unique).map((word) => (0,
|
|
63
|
+
const words = chooseWords(dictionary, pattern, unique).map((word) => (0, utils_js_1.applyCase)(word, uppercaseStyle));
|
|
64
64
|
const parts = [];
|
|
65
65
|
for (const word of words) {
|
|
66
66
|
parts.push(word);
|
|
67
67
|
}
|
|
68
68
|
if (useSymbols) {
|
|
69
|
-
const symbols = Array.isArray(useSymbols) ? useSymbols :
|
|
70
|
-
parts.push((0,
|
|
69
|
+
const symbols = Array.isArray(useSymbols) ? useSymbols : utils_js_1.defaultSymbols;
|
|
70
|
+
parts.push((0, utils_js_1.randomItem)(symbols));
|
|
71
71
|
}
|
|
72
72
|
if (numbersOption) {
|
|
73
|
-
parts.push(String((0,
|
|
73
|
+
parts.push(String((0, utils_js_1.randomNumberByDigits)(digits)));
|
|
74
74
|
}
|
|
75
75
|
if (separator) {
|
|
76
76
|
return parts.join(separator);
|
|
@@ -93,7 +93,7 @@ function entropyEstimate(options = {}) {
|
|
|
93
93
|
return list.length || 1;
|
|
94
94
|
});
|
|
95
95
|
const wordSpace = sizes.reduce((acc, size) => acc * size, 1);
|
|
96
|
-
const symbolSpace = useSymbols ? (Array.isArray(useSymbols) ? useSymbols.length :
|
|
96
|
+
const symbolSpace = useSymbols ? (Array.isArray(useSymbols) ? useSymbols.length : utils_js_1.defaultSymbols.length) : 1;
|
|
97
97
|
const numberSpace = numbersOption ? Math.pow(10, digits) : 1;
|
|
98
98
|
const total = wordSpace * symbolSpace * numberSpace;
|
|
99
99
|
return Math.log2(total);
|
package/dist/cjs/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.entropyEstimate = exports.generatePassword = exports.generatePassphrase = void 0;
|
|
4
|
-
var
|
|
5
|
-
Object.defineProperty(exports, "generatePassphrase", { enumerable: true, get: function () { return
|
|
6
|
-
Object.defineProperty(exports, "generatePassword", { enumerable: true, get: function () { return
|
|
7
|
-
Object.defineProperty(exports, "entropyEstimate", { enumerable: true, get: function () { return
|
|
4
|
+
var generator_js_1 = require("./generator.js");
|
|
5
|
+
Object.defineProperty(exports, "generatePassphrase", { enumerable: true, get: function () { return generator_js_1.generatePassphrase; } });
|
|
6
|
+
Object.defineProperty(exports, "generatePassword", { enumerable: true, get: function () { return generator_js_1.generatePassword; } });
|
|
7
|
+
Object.defineProperty(exports, "entropyEstimate", { enumerable: true, get: function () { return generator_js_1.entropyEstimate; } });
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
import clipboardy from "clipboardy";
|
|
6
|
+
import gradient from "gradient-string";
|
|
7
|
+
import { generatePassword } from "./generator.js";
|
|
8
|
+
const program = new Command();
|
|
9
|
+
program
|
|
10
|
+
.name("smart-passphrase")
|
|
11
|
+
.description("Generate secure and memorable passphrases with style")
|
|
12
|
+
.version("2.0.0")
|
|
13
|
+
.option("-w, --words <number>", "number of words", (val) => parseInt(val), 3)
|
|
14
|
+
.option("-s, --strength <tier>", "security tier (medium, strong, ultra)", "medium")
|
|
15
|
+
.option("-n, --no-numbers", "do not include numbers")
|
|
16
|
+
.option("-S, --no-symbols", "do not include symbols")
|
|
17
|
+
.option("-c, --copy", "copy to clipboard")
|
|
18
|
+
.action(async (options) => {
|
|
19
|
+
console.log("");
|
|
20
|
+
const spinner = ora({
|
|
21
|
+
text: chalk.cyan("Locking the gears..."),
|
|
22
|
+
color: "cyan"
|
|
23
|
+
}).start();
|
|
24
|
+
// Small delay for "fancy" feel
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
26
|
+
try {
|
|
27
|
+
const password = generatePassword({
|
|
28
|
+
words: options.words,
|
|
29
|
+
strength: options.strength,
|
|
30
|
+
numbers: options.numbers,
|
|
31
|
+
symbols: options.symbols,
|
|
32
|
+
});
|
|
33
|
+
spinner.succeed(chalk.green("Secure passphrase ready!"));
|
|
34
|
+
console.log("");
|
|
35
|
+
const styledPassword = gradient.pastel.multiline(password);
|
|
36
|
+
console.log(" " + chalk.bold(styledPassword));
|
|
37
|
+
console.log("");
|
|
38
|
+
if (options.copy) {
|
|
39
|
+
clipboardy.writeSync(password);
|
|
40
|
+
console.log(chalk.dim(" 📋 Copied to clipboard!"));
|
|
41
|
+
console.log("");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
spinner.fail(chalk.red("Generation failed"));
|
|
46
|
+
console.error(chalk.red(` Error: ${error.message}`));
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
// Handle the "copy" as a shorthand command too
|
|
51
|
+
program
|
|
52
|
+
.command("copy")
|
|
53
|
+
.description("Generate and copy to clipboard immediately")
|
|
54
|
+
.action(async () => {
|
|
55
|
+
const spinner = ora(chalk.cyan("Generating and copying...")).start();
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 400));
|
|
57
|
+
const password = generatePassword();
|
|
58
|
+
clipboardy.writeSync(password);
|
|
59
|
+
spinner.succeed(chalk.green("Generated and copied to clipboard!"));
|
|
60
|
+
console.log("");
|
|
61
|
+
console.log(" " + gradient.pastel(password));
|
|
62
|
+
console.log("");
|
|
63
|
+
});
|
|
64
|
+
program.parse();
|
package/dist/generator.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GenerateOptions } from "./types";
|
|
1
|
+
import type { GenerateOptions } from "./types.js";
|
|
2
2
|
export declare function generatePassphrase(options?: GenerateOptions): string;
|
|
3
3
|
export declare function generatePassword(options?: GenerateOptions): string;
|
|
4
4
|
export declare function entropyEstimate(options?: GenerateOptions): number;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { adjectives, nouns, verbs } from "./wordlist.js";
|
|
2
|
+
import { applyCase, defaultSymbols, randomItem, randomNumberByDigits } from "./utils.js";
|
|
3
|
+
const strengthDefaults = {
|
|
4
|
+
medium: { words: 3, symbols: true, digits: 3 },
|
|
5
|
+
strong: { words: 4, symbols: true, digits: 4 },
|
|
6
|
+
ultra: { words: 5, symbols: true, digits: 5 }
|
|
7
|
+
};
|
|
8
|
+
function buildDictionary(overrides) {
|
|
9
|
+
return {
|
|
10
|
+
adjectives: overrides?.adjectives ?? adjectives,
|
|
11
|
+
nouns: overrides?.nouns ?? nouns,
|
|
12
|
+
verbs: overrides?.verbs ?? verbs
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function chooseWords(dictionary, kinds, unique) {
|
|
16
|
+
const used = new Set();
|
|
17
|
+
const output = [];
|
|
18
|
+
for (const kind of kinds) {
|
|
19
|
+
const list = kind === "adj" ? dictionary.adjectives : kind === "noun" ? dictionary.nouns : dictionary.verbs;
|
|
20
|
+
if (list.length === 0) {
|
|
21
|
+
throw new Error(`Dictionary list for ${kind} is empty`);
|
|
22
|
+
}
|
|
23
|
+
let picked = randomItem(list);
|
|
24
|
+
if (unique) {
|
|
25
|
+
let attempts = 0;
|
|
26
|
+
while (used.has(picked) && attempts < 25) {
|
|
27
|
+
picked = randomItem(list);
|
|
28
|
+
attempts += 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
used.add(picked);
|
|
32
|
+
output.push(picked);
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
function defaultPattern(words) {
|
|
37
|
+
const pool = ["adj", "noun", "verb"];
|
|
38
|
+
const output = [];
|
|
39
|
+
for (let i = 0; i < words; i += 1) {
|
|
40
|
+
output.push(pool[i % pool.length]);
|
|
41
|
+
}
|
|
42
|
+
return output;
|
|
43
|
+
}
|
|
44
|
+
export function generatePassphrase(options = {}) {
|
|
45
|
+
const defaults = options.strength ? strengthDefaults[options.strength] : strengthDefaults.medium;
|
|
46
|
+
const wordCount = options.words ?? defaults.words;
|
|
47
|
+
const useSymbols = options.symbols ?? defaults.symbols;
|
|
48
|
+
const numbersOption = options.numbers ?? true;
|
|
49
|
+
const digits = typeof numbersOption === "object" ? numbersOption.digits ?? defaults.digits : defaults.digits;
|
|
50
|
+
const separator = options.separator ?? "";
|
|
51
|
+
const uppercaseStyle = options.uppercaseStyle ?? "random";
|
|
52
|
+
const unique = options.unique ?? true;
|
|
53
|
+
const dictionary = buildDictionary(options.dictionary);
|
|
54
|
+
const pattern = options.pattern ?? defaultPattern(wordCount);
|
|
55
|
+
if (pattern.length !== wordCount) {
|
|
56
|
+
throw new Error("pattern length must match words");
|
|
57
|
+
}
|
|
58
|
+
const words = chooseWords(dictionary, pattern, unique).map((word) => applyCase(word, uppercaseStyle));
|
|
59
|
+
const parts = [];
|
|
60
|
+
for (const word of words) {
|
|
61
|
+
parts.push(word);
|
|
62
|
+
}
|
|
63
|
+
if (useSymbols) {
|
|
64
|
+
const symbols = Array.isArray(useSymbols) ? useSymbols : defaultSymbols;
|
|
65
|
+
parts.push(randomItem(symbols));
|
|
66
|
+
}
|
|
67
|
+
if (numbersOption) {
|
|
68
|
+
parts.push(String(randomNumberByDigits(digits)));
|
|
69
|
+
}
|
|
70
|
+
if (separator) {
|
|
71
|
+
return parts.join(separator);
|
|
72
|
+
}
|
|
73
|
+
return parts.join("");
|
|
74
|
+
}
|
|
75
|
+
export function generatePassword(options = {}) {
|
|
76
|
+
return generatePassphrase(options);
|
|
77
|
+
}
|
|
78
|
+
export function entropyEstimate(options = {}) {
|
|
79
|
+
const defaults = options.strength ? strengthDefaults[options.strength] : strengthDefaults.medium;
|
|
80
|
+
const wordCount = options.words ?? defaults.words;
|
|
81
|
+
const useSymbols = options.symbols ?? defaults.symbols;
|
|
82
|
+
const numbersOption = options.numbers ?? true;
|
|
83
|
+
const digits = typeof numbersOption === "object" ? numbersOption.digits ?? defaults.digits : defaults.digits;
|
|
84
|
+
const dictionary = buildDictionary(options.dictionary);
|
|
85
|
+
const pattern = options.pattern ?? defaultPattern(wordCount);
|
|
86
|
+
const sizes = pattern.map((kind) => {
|
|
87
|
+
const list = kind === "adj" ? dictionary.adjectives : kind === "noun" ? dictionary.nouns : dictionary.verbs;
|
|
88
|
+
return list.length || 1;
|
|
89
|
+
});
|
|
90
|
+
const wordSpace = sizes.reduce((acc, size) => acc * size, 1);
|
|
91
|
+
const symbolSpace = useSymbols ? (Array.isArray(useSymbols) ? useSymbols.length : defaultSymbols.length) : 1;
|
|
92
|
+
const numberSpace = numbersOption ? Math.pow(10, digits) : 1;
|
|
93
|
+
const total = wordSpace * symbolSpace * numberSpace;
|
|
94
|
+
return Math.log2(total);
|
|
95
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { generatePassphrase, generatePassword, entropyEstimate } from "./generator";
|
|
2
|
-
export type { GenerateOptions, Dictionary, UppercaseStyle, Strength, WordKind } from "./types";
|
|
1
|
+
export { generatePassphrase, generatePassword, entropyEstimate } from "./generator.js";
|
|
2
|
+
export type { GenerateOptions, Dictionary, UppercaseStyle, Strength, WordKind } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generatePassphrase, generatePassword, entropyEstimate } from "./generator.js";
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export const defaultSymbols = ["@", "#", "^", "=", ")", "!", "*", "%"];
|
|
2
|
+
export function secureRandomInt(maxExclusive) {
|
|
3
|
+
if (!Number.isInteger(maxExclusive) || maxExclusive <= 0) {
|
|
4
|
+
throw new Error("maxExclusive must be a positive integer");
|
|
5
|
+
}
|
|
6
|
+
const cryptoObj = globalThis.crypto;
|
|
7
|
+
if (!cryptoObj || typeof cryptoObj.getRandomValues !== "function") {
|
|
8
|
+
throw new Error("Secure crypto is not available in this environment");
|
|
9
|
+
}
|
|
10
|
+
const range = 0x100000000;
|
|
11
|
+
const limit = range - (range % maxExclusive);
|
|
12
|
+
const buf = new Uint32Array(1);
|
|
13
|
+
let value = 0;
|
|
14
|
+
do {
|
|
15
|
+
cryptoObj.getRandomValues(buf);
|
|
16
|
+
value = buf[0];
|
|
17
|
+
} while (value >= limit);
|
|
18
|
+
return value % maxExclusive;
|
|
19
|
+
}
|
|
20
|
+
export function randomItem(items) {
|
|
21
|
+
if (items.length === 0) {
|
|
22
|
+
throw new Error("Cannot choose from an empty list");
|
|
23
|
+
}
|
|
24
|
+
return items[secureRandomInt(items.length)];
|
|
25
|
+
}
|
|
26
|
+
export function randomBool() {
|
|
27
|
+
return secureRandomInt(2) === 1;
|
|
28
|
+
}
|
|
29
|
+
export function applyCase(word, style) {
|
|
30
|
+
if (style === "none")
|
|
31
|
+
return word;
|
|
32
|
+
if (style === "upper")
|
|
33
|
+
return word.toUpperCase();
|
|
34
|
+
if (style === "lower")
|
|
35
|
+
return word.toLowerCase();
|
|
36
|
+
if (style === "title")
|
|
37
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
38
|
+
return word
|
|
39
|
+
.split("")
|
|
40
|
+
.map((ch) => (randomBool() ? ch.toUpperCase() : ch.toLowerCase()))
|
|
41
|
+
.join("");
|
|
42
|
+
}
|
|
43
|
+
export function randomNumberByDigits(digits) {
|
|
44
|
+
if (!Number.isInteger(digits) || digits <= 0) {
|
|
45
|
+
throw new Error("digits must be a positive integer");
|
|
46
|
+
}
|
|
47
|
+
const min = Math.pow(10, digits - 1);
|
|
48
|
+
const max = Math.pow(10, digits) - 1;
|
|
49
|
+
const span = max - min + 1;
|
|
50
|
+
return min + secureRandomInt(span);
|
|
51
|
+
}
|
package/dist/wordlist.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export const adjectives = [
|
|
2
|
+
// Original
|
|
3
|
+
"quick", "silent", "brave", "fancy", "eager", "bright",
|
|
4
|
+
"gentle", "proud", "curious", "steady", "bold", "calm",
|
|
5
|
+
// Advanced originals
|
|
6
|
+
"resilient", "ephemeral", "luminous", "tenacious", "serene",
|
|
7
|
+
"voracious", "ethereal", "stoic", "audacious", "fleeting",
|
|
8
|
+
"mellifluous", "incisive", "unyielding", "sublime", "transient",
|
|
9
|
+
"ferocious", "cunning", "reclusive", "radiant", "somber",
|
|
10
|
+
"deft", "keen", "vigilant", "nimble", "profound",
|
|
11
|
+
// New: memorial + solemn / meaningful tone
|
|
12
|
+
"memorial",
|
|
13
|
+
"immortal",
|
|
14
|
+
"reverent",
|
|
15
|
+
"hallowed",
|
|
16
|
+
"eternal",
|
|
17
|
+
"venerable",
|
|
18
|
+
"sacred",
|
|
19
|
+
"mournful",
|
|
20
|
+
"remnant",
|
|
21
|
+
"honored",
|
|
22
|
+
"legendary",
|
|
23
|
+
"fabled",
|
|
24
|
+
"timeless",
|
|
25
|
+
"perpetual",
|
|
26
|
+
"solemn",
|
|
27
|
+
"devoted",
|
|
28
|
+
"faithful",
|
|
29
|
+
"righteous",
|
|
30
|
+
"valiant",
|
|
31
|
+
"gallant",
|
|
32
|
+
"exalted",
|
|
33
|
+
"revered",
|
|
34
|
+
"venerated",
|
|
35
|
+
"undying",
|
|
36
|
+
"everlasting"
|
|
37
|
+
];
|
|
38
|
+
export const nouns = [
|
|
39
|
+
// Original
|
|
40
|
+
"tiger", "goose", "rocket", "mask", "forest", "ocean",
|
|
41
|
+
"ember", "comet", "cipher", "garden", "river", "mountain",
|
|
42
|
+
// Advanced originals
|
|
43
|
+
"phantom", "echo", "horizon", "sentinel", "abyss", "beacon",
|
|
44
|
+
"shroud", "monolith", "specter", "harbinger", "labyrinth",
|
|
45
|
+
"oracle", "crucible", "vigil", "rift", "veil", "tempest",
|
|
46
|
+
"solstice", "requiem", "synapse", "nexus", "axiom", "enigma",
|
|
47
|
+
"relic", "citadel",
|
|
48
|
+
// New: memorial + meaningful / legacy words
|
|
49
|
+
"memorial",
|
|
50
|
+
"monument",
|
|
51
|
+
"epitaph",
|
|
52
|
+
"shrine",
|
|
53
|
+
"tomb",
|
|
54
|
+
"grave",
|
|
55
|
+
"remembrance",
|
|
56
|
+
"legacy",
|
|
57
|
+
"heritage",
|
|
58
|
+
"echo",
|
|
59
|
+
"keepsake",
|
|
60
|
+
"memento",
|
|
61
|
+
"relic",
|
|
62
|
+
"vestige",
|
|
63
|
+
"tribute",
|
|
64
|
+
"offering",
|
|
65
|
+
"vow",
|
|
66
|
+
"oath",
|
|
67
|
+
"covenant",
|
|
68
|
+
"pillar",
|
|
69
|
+
"obelisk",
|
|
70
|
+
"chapel",
|
|
71
|
+
"sanctum",
|
|
72
|
+
"archive",
|
|
73
|
+
"chronicle"
|
|
74
|
+
];
|
|
75
|
+
export const verbs = [
|
|
76
|
+
// Original
|
|
77
|
+
"run", "jump", "build", "mark", "drift", "spark",
|
|
78
|
+
"forge", "glide", "shift", "trace", "craft", "sprint",
|
|
79
|
+
// Advanced originals
|
|
80
|
+
"lunge", "flicker", "shatter", "conceal", "summon", "evade",
|
|
81
|
+
"descend", "ascend", "sunder", "kindle", "lurk", "surge",
|
|
82
|
+
"fracture", "unravel", "coalesce", "sever", "ignite", "linger",
|
|
83
|
+
"plummet", "scatter", "converge", "diverge", "emanate", "sear", "wither",
|
|
84
|
+
// New: memorial / reverent / legacy actions
|
|
85
|
+
"honor",
|
|
86
|
+
"cherish",
|
|
87
|
+
"enshrine",
|
|
88
|
+
"lament",
|
|
89
|
+
"mourn",
|
|
90
|
+
"remember",
|
|
91
|
+
"immortalize",
|
|
92
|
+
"consecrate",
|
|
93
|
+
"dedicate",
|
|
94
|
+
"sanctify",
|
|
95
|
+
"preserve",
|
|
96
|
+
"bequeath",
|
|
97
|
+
"endure",
|
|
98
|
+
"inscribe",
|
|
99
|
+
"carve",
|
|
100
|
+
"engrave",
|
|
101
|
+
"invoke",
|
|
102
|
+
"recall",
|
|
103
|
+
"revere",
|
|
104
|
+
"venerate",
|
|
105
|
+
"exalt",
|
|
106
|
+
"glorify",
|
|
107
|
+
"commemorate",
|
|
108
|
+
"memorialize",
|
|
109
|
+
"pay homage"
|
|
110
|
+
];
|
package/package.json
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smart-passphrase",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Memorable, secure passphrase generator for web and Node.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ayush Solanki <ayushsolanki2901@gmail.com> (https://github.com/ayushsolanki29)",
|
|
7
7
|
"type": "module",
|
|
8
|
-
|
|
9
8
|
"repository": {
|
|
10
9
|
"type": "git",
|
|
11
10
|
"url": "https://github.com/ayushsolanki29/smart-passphrase"
|
|
12
11
|
},
|
|
13
|
-
|
|
14
12
|
"keywords": [
|
|
15
13
|
"password",
|
|
16
14
|
"passphrase",
|
|
@@ -18,29 +16,38 @@
|
|
|
18
16
|
"security",
|
|
19
17
|
"typescript"
|
|
20
18
|
],
|
|
21
|
-
|
|
19
|
+
"bin": {
|
|
20
|
+
"smart-passphrase": "./dist/cli.js"
|
|
21
|
+
},
|
|
22
22
|
"exports": {
|
|
23
23
|
".": {
|
|
24
24
|
"types": "./dist/index.d.ts",
|
|
25
|
-
"import": "./dist/
|
|
25
|
+
"import": "./dist/index.js",
|
|
26
26
|
"require": "./dist/cjs/index.cjs"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
|
-
|
|
30
29
|
"main": "./dist/cjs/index.cjs",
|
|
31
|
-
"module": "./dist/
|
|
30
|
+
"module": "./dist/index.js",
|
|
32
31
|
"types": "./dist/index.d.ts",
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
"files": [
|
|
33
|
+
"dist"
|
|
34
|
+
],
|
|
35
35
|
"sideEffects": false,
|
|
36
|
-
|
|
37
36
|
"scripts": {
|
|
38
37
|
"build:esm": "tsc -p tsconfig.esm.json",
|
|
39
38
|
"build:cjs": "tsc -p tsconfig.cjs.json",
|
|
40
39
|
"build": "npm run build:esm && npm run build:cjs"
|
|
41
40
|
},
|
|
42
|
-
|
|
43
41
|
"devDependencies": {
|
|
42
|
+
"@types/gradient-string": "^1.1.6",
|
|
43
|
+
"@types/node": "^25.6.0",
|
|
44
44
|
"typescript": "^5.5.4"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"chalk": "^5.6.2",
|
|
48
|
+
"clipboardy": "^5.3.1",
|
|
49
|
+
"commander": "^14.0.3",
|
|
50
|
+
"gradient-string": "^3.0.0",
|
|
51
|
+
"ora": "^9.3.0"
|
|
45
52
|
}
|
|
46
|
-
}
|
|
53
|
+
}
|