hexpass 1.0.0 → 1.0.2

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 CHANGED
@@ -21,15 +21,20 @@ npx hexpass
21
21
  ```
22
22
  hexpass [length]
23
23
  hexpass --bytes <n>
24
+ hexpass --count <n>
25
+ hexpass --env <NAME>
26
+ hexpass --json
24
27
  hexpass --help
25
28
  hexpass --version
26
- hexpass --copy
27
29
  ```
28
30
 
29
31
  - Default: `hexpass` outputs a 32-character hex string.
30
32
  - `hexpass 64` outputs a 64-character string.
31
- - `hexpass 33` supports odd lengths (generates extra byte and truncates).
32
- - `hexpass --bytes 16` uses 16 random bytes (32 characters of hex).
33
+ - `hexpass 33` supports odd lengths (generates the necessary extra byte and truncates to the requested number of hex characters).
34
+ - `hexpass --bytes 16` uses 16 random bytes (32 hex characters).
35
+ - `hexpass 32 --count 5` generates 5 secrets, each on its own line.
36
+ - `hexpass 48 --env JWT_SECRET` outputs `JWT_SECRET=<secret>` format.
37
+ - `hexpass 32 --json` outputs a JSON object with `length`, `bytes`, and `hex` fields (single secret only).
33
38
 
34
39
  ## Options
35
40
 
@@ -37,30 +42,64 @@ hexpass --copy
37
42
  | --- | --- |
38
43
  | `length` | Desired hex string length (default 32, max 1024) |
39
44
  | `--bytes <n>` | Generate from n bytes (output has n×2 characters) |
40
- | `--copy`, `-c` | Copy the generated hex string to the clipboard |
45
+ | `--count <n>`, `-n` | Generate n secrets (default 1, max 100) |
46
+ | `--env <NAME>`, `-e` | Output as `NAME=secret` instead of raw secret (only valid for single secret) |
47
+ | `--json`, `-j` | Output as JSON object (single secret only; incompatible with `--env` and `--copy`) |
48
+ | `--copy`, `-c` | Copy the generated hex string to the clipboard (single secret only) |
41
49
  | `--help`, `-h` | Show usage information |
42
50
  | `--version`, `-v` | Show version number |
43
51
 
52
+ ### Notes on compatibility
53
+
54
+ - `--json` is only supported for single-secret output (`--count` must be 1).
55
+ - `--env` and `--json` are incompatible — use one or the other depending on whether you want an assignment string or structured data.
56
+ - `--copy` works only for single-secret output and is incompatible with `--json`.
57
+
44
58
  ## Examples
45
59
 
46
60
  ```
47
- # Quickly generate a secret
48
- hexpass 64
61
+ # Quickly generate a secret (default 32 chars)
62
+ hexpass
49
63
 
50
- # Populate .env
51
- JWT_SECRET=$(hexpass 48)
64
+ # Generate a longer secret
65
+ hexpass 64
52
66
 
53
- # Create API key
67
+ # Use explicit byte count (generates n * 2 hex chars)
54
68
  hexpass --bytes 32 > api-key.txt
55
69
 
56
- # Generate and copy instantly
70
+ # Generate and copy a secret (single secret)
57
71
  hexpass 48 --copy
72
+
73
+ # Generate multiple secrets in one go
74
+ hexpass 32 --count 5
75
+
76
+ # Output as environment variable format
77
+ hexpass 48 --env JWT_SECRET
78
+ # Output: JWT_SECRET=a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f
79
+
80
+ # Output as JSON for automation (single secret)
81
+ hexpass 32 --json
82
+ # Output: {"length":32,"bytes":16,"hex":"a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"}
58
83
  ```
59
84
 
60
85
  ## Security
61
86
 
62
87
  `hexpass` always uses `crypto.randomBytes()` from Node.js for cryptographically strong randomness. No weak sources like `Math.random()` are used.
63
88
 
89
+ ## Testing
90
+
91
+ The repository includes a small CLI test script used during development. You can exercise common invocations with:
92
+
93
+ ```
94
+ npm test
95
+ ```
96
+
97
+ This runs several `hexpass` variants to validate behavior (lengths, bytes, `--count`, `--env`, and `--json`).
98
+
99
+ ## Version
100
+
101
+ This README reflects the changes introduced in version 1.0.1 (modular refactor and new CLI options).
102
+
64
103
  ## License
65
104
 
66
105
  MIT
package/bin/hexpass.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { run } = require("../src/cli");
4
+
5
+ run(process.argv.slice(2), process.stdout, process.stderr, process.exit);
package/index.js CHANGED
@@ -1,201 +1,7 @@
1
- #!/usr/bin/env node
1
+ const { run } = require("./src/cli");
2
2
 
3
- const crypto = require("crypto");
4
- const { spawnSync } = require("child_process");
5
- const { version } = require("./package.json");
6
-
7
- const DEFAULT_LENGTH = 32;
8
- const MAX_LENGTH = 1024;
9
- const MAX_BYTES = MAX_LENGTH / 2;
10
-
11
- const args = process.argv.slice(2);
12
-
13
- const parsed = parseArgs(args);
14
-
15
- if (parsed.action === "help") {
16
- printHelp();
17
- process.exit(0);
18
- }
19
-
20
- if (parsed.action === "version") {
21
- console.log(version);
22
- process.exit(0);
23
- }
24
-
25
- if (parsed.bytes != null && parsed.lengthValue != null) {
26
- exitWithError("Provide either a length or --bytes, not both.");
27
- }
28
-
29
- let charLength;
30
-
31
- if (parsed.bytes != null) {
32
- const byteLength = parseByteLength(parsed.bytes);
33
- if (byteLength * 2 > MAX_LENGTH) {
34
- exitWithError(
35
- `Byte length exceeds maximum of ${MAX_BYTES} bytes (${MAX_LENGTH} characters).`
36
- );
37
- }
38
- charLength = byteLength * 2;
39
- } else if (parsed.lengthValue != null) {
40
- const length = parseCharacterLength(parsed.lengthValue);
41
- if (length > MAX_LENGTH) {
42
- exitWithError(`Length exceeds maximum of ${MAX_LENGTH} characters.`);
43
- }
44
- charLength = length;
45
- } else {
46
- charLength = DEFAULT_LENGTH;
3
+ if (require.main === module) {
4
+ run(process.argv.slice(2), process.stdout, process.stderr, process.exit);
47
5
  }
48
6
 
49
- const password = generateHex(charLength);
50
- process.stdout.write(`${password}\n`);
51
-
52
- if (parsed.copy) {
53
- try {
54
- copyToClipboard(password);
55
- } catch (error) {
56
- exitWithError(error.message || "Failed to copy to clipboard.");
57
- }
58
- }
59
-
60
- function parseArgs(argv) {
61
- let lengthValue = null;
62
- let bytes = null;
63
- let copy = false;
64
-
65
- for (let i = 0; i < argv.length; i += 1) {
66
- const arg = argv[i];
67
-
68
- if (arg === "--help" || arg === "-h") {
69
- return { action: "help" };
70
- }
71
-
72
- if (arg === "--version" || arg === "-v") {
73
- return { action: "version" };
74
- }
75
-
76
- if (arg === "--bytes") {
77
- if (bytes != null) {
78
- exitWithError("The --bytes option can only be specified once.");
79
- }
80
- const next = argv[i + 1];
81
- if (!next) {
82
- exitWithError("Missing value for --bytes.");
83
- }
84
- bytes = next;
85
- i += 1;
86
- continue;
87
- }
88
-
89
- if (arg === "--copy" || arg === "-c") {
90
- copy = true;
91
- continue;
92
- }
93
-
94
- if (arg.startsWith("--")) {
95
- exitWithError(`Unknown option: ${arg}`);
96
- }
97
-
98
- if (lengthValue != null) {
99
- exitWithError("Multiple length values provided.");
100
- }
101
-
102
- lengthValue = arg;
103
- }
104
-
105
- return { action: "generate", lengthValue, bytes, copy };
106
- }
107
-
108
- function parseCharacterLength(value) {
109
- const num = Number(value);
110
- if (!Number.isFinite(num) || !Number.isInteger(num)) {
111
- exitWithError(`Invalid length: '${value}'. Must be a positive integer.`);
112
- }
113
- if (num <= 0) {
114
- exitWithError("Length must be a positive integer.");
115
- }
116
- return num;
117
- }
118
-
119
- function parseByteLength(value) {
120
- const num = Number(value);
121
- if (!Number.isFinite(num) || !Number.isInteger(num)) {
122
- exitWithError(
123
- `Invalid byte length: '${value}'. Must be a positive integer.`
124
- );
125
- }
126
- if (num <= 0) {
127
- exitWithError("Byte length must be a positive integer.");
128
- }
129
- return num;
130
- }
131
-
132
- function generateHex(charLength) {
133
- const bytesNeeded = Math.ceil(charLength / 2);
134
- const hex = crypto.randomBytes(bytesNeeded).toString("hex");
135
- return hex.slice(0, charLength);
136
- }
137
-
138
- function printHelp() {
139
- console.log(
140
- `hexpass - Generate cryptographically secure hex passwords\n\n` +
141
- `Usage:\n` +
142
- ` hexpass [length] Generate hex string with specified length (default: ${DEFAULT_LENGTH})\n` +
143
- ` hexpass --bytes <n> Generate from n random bytes (output has n*2 characters)\n\n` +
144
- `Options:\n` +
145
- ` -h, --help Show this help message\n` +
146
- ` -v, --version Show version number\n` +
147
- ` -c, --copy Copy the generated hex string to clipboard\n` +
148
- ` --bytes <n> Specify length in bytes instead of characters\n\n` +
149
- `Examples:\n` +
150
- ` hexpass # 32-character hex string\n` +
151
- ` hexpass 64 # 64-character hex string\n` +
152
- ` hexpass --bytes 32 # 64-character hex string (32 bytes)\n` +
153
- ` hexpass 48 --copy # Generate 48 chars and copy to clipboard\n\n` +
154
- `Limits:\n` +
155
- ` Maximum output length is ${MAX_LENGTH} characters (${MAX_BYTES} bytes).\n\n` +
156
- `Security:\n` +
157
- ` All passwords are generated using Node.js crypto.randomBytes()`
158
- );
159
- }
160
-
161
- function exitWithError(message) {
162
- console.error(`Error: ${message}`);
163
- process.exit(1);
164
- }
165
-
166
- function copyToClipboard(text) {
167
- const platform = process.platform;
168
-
169
- if (platform === "darwin") {
170
- runClipboardCommand("pbcopy", text);
171
- return;
172
- }
173
-
174
- if (platform === "win32") {
175
- runClipboardCommand("clip", text);
176
- return;
177
- }
178
-
179
- const linuxCommands = [
180
- ["xclip", ["-selection", "clipboard"]],
181
- ["xsel", ["--clipboard", "--input"]],
182
- ];
183
-
184
- for (const [cmd, args] of linuxCommands) {
185
- const result = spawnSync(cmd, args, { input: text, encoding: "utf8" });
186
- if (!result.error && result.status === 0) {
187
- return;
188
- }
189
- }
190
-
191
- throw new Error(
192
- "Clipboard copy not supported on this system. Install xclip or xsel."
193
- );
194
- }
195
-
196
- function runClipboardCommand(command, input) {
197
- const result = spawnSync(command, [], { input, encoding: "utf8" });
198
- if (result.error || result.status !== 0) {
199
- throw new Error(`Failed to copy to clipboard using ${command}.`);
200
- }
201
- }
7
+ module.exports = { run };
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "hexpass",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Generate cryptographically secure hexadecimal passwords and secrets",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "hexpass": "./index.js"
7
+ "hexpass": "bin/hexpass.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "node index.js && node index.js 64 && node index.js --bytes 16"
10
+ "test": "node test.js"
11
11
  },
12
12
  "keywords": [
13
13
  "password",
@@ -19,12 +19,22 @@
19
19
  "jwt",
20
20
  "api-key"
21
21
  ],
22
- "author": "",
22
+ "author": "Tejaswan Kalluri",
23
23
  "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/tejaswankalluri/hexpass.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/tejaswankalluri/hexpass/issues"
30
+ },
31
+ "homepage": "https://github.com/tejaswankalluri/hexpass#readme",
24
32
  "engines": {
25
33
  "node": ">=14.0.0"
26
34
  },
27
35
  "files": [
36
+ "bin/",
37
+ "src/",
28
38
  "index.js",
29
39
  "LICENSE",
30
40
  "README.md"
package/src/args.js ADDED
@@ -0,0 +1,153 @@
1
+ const DEFAULT_LENGTH = 32;
2
+ const MAX_LENGTH = 1024;
3
+ const MAX_BYTES = MAX_LENGTH / 2;
4
+ const MAX_COUNT = 100;
5
+
6
+ function parseArgs(argv) {
7
+ let lengthValue = null;
8
+ let bytes = null;
9
+ let copy = false;
10
+ let count = 1;
11
+ let envName = null;
12
+ let json = false;
13
+
14
+ for (let i = 0; i < argv.length; i += 1) {
15
+ const arg = argv[i];
16
+
17
+ if (arg === "--help" || arg === "-h") {
18
+ return { action: "help" };
19
+ }
20
+
21
+ if (arg === "--version" || arg === "-v") {
22
+ return { action: "version" };
23
+ }
24
+
25
+ if (arg === "--bytes") {
26
+ if (bytes != null) {
27
+ throw new Error("The --bytes option can only be specified once.");
28
+ }
29
+ const next = argv[i + 1];
30
+ if (!next || next.startsWith("-")) {
31
+ throw new Error("Missing value for --bytes.");
32
+ }
33
+ bytes = next;
34
+ i += 1;
35
+ continue;
36
+ }
37
+
38
+ if (arg === "--copy" || arg === "-c") {
39
+ copy = true;
40
+ continue;
41
+ }
42
+
43
+ if (arg === "--count" || arg === "-n") {
44
+ if (count !== 1) {
45
+ throw new Error("The --count option can only be specified once.");
46
+ }
47
+ const next = argv[i + 1];
48
+ if (!next || next.startsWith("-")) {
49
+ throw new Error("Missing value for --count.");
50
+ }
51
+ const parsedCount = parseCount(next);
52
+ count = parsedCount;
53
+ i += 1;
54
+ continue;
55
+ }
56
+
57
+ if (arg === "--env" || arg === "-e") {
58
+ if (envName != null) {
59
+ throw new Error("The --env option can only be specified once.");
60
+ }
61
+ const next = argv[i + 1];
62
+ if (!next || next.startsWith("-")) {
63
+ throw new Error("Missing value for --env.");
64
+ }
65
+ envName = parseEnvName(next);
66
+ i += 1;
67
+ continue;
68
+ }
69
+
70
+ if (arg === "--json" || arg === "-j") {
71
+ json = true;
72
+ continue;
73
+ }
74
+
75
+ if (arg.startsWith("--")) {
76
+ throw new Error(`Unknown option: ${arg}`);
77
+ }
78
+
79
+ if (arg.startsWith("-")) {
80
+ throw new Error(`Unknown option: ${arg}`);
81
+ }
82
+
83
+ if (lengthValue != null) {
84
+ throw new Error("Multiple length values provided.");
85
+ }
86
+
87
+ lengthValue = arg;
88
+ }
89
+
90
+ return { action: "generate", lengthValue, bytes, copy, count, envName, json };
91
+ }
92
+
93
+ function parseCharacterLength(value) {
94
+ const num = Number(value);
95
+ if (!Number.isFinite(num) || !Number.isInteger(num)) {
96
+ throw new Error(`Invalid length: '${value}'. Must be a positive integer.`);
97
+ }
98
+ if (num <= 0) {
99
+ throw new Error("Length must be a positive integer.");
100
+ }
101
+ return num;
102
+ }
103
+
104
+ function parseByteLength(value) {
105
+ const num = Number(value);
106
+ if (!Number.isFinite(num) || !Number.isInteger(num)) {
107
+ throw new Error(
108
+ `Invalid byte length: '${value}'. Must be a positive integer.`
109
+ );
110
+ }
111
+ if (num <= 0) {
112
+ throw new Error("Byte length must be a positive integer.");
113
+ }
114
+ return num;
115
+ }
116
+
117
+ function parseCount(value) {
118
+ const num = Number(value);
119
+ if (!Number.isFinite(num) || !Number.isInteger(num)) {
120
+ throw new Error(
121
+ `Invalid count: '${value}'. Must be a positive integer.`
122
+ );
123
+ }
124
+ if (num <= 0) {
125
+ throw new Error("Count must be a positive integer.");
126
+ }
127
+ if (num > MAX_COUNT) {
128
+ throw new Error(`Count exceeds maximum of ${MAX_COUNT}.`);
129
+ }
130
+ return num;
131
+ }
132
+
133
+ function parseEnvName(value) {
134
+ if (!value || value.trim() === "") {
135
+ throw new Error("Environment variable name cannot be empty.");
136
+ }
137
+ if (value.includes(" ")) {
138
+ throw new Error("Environment variable name cannot contain spaces.");
139
+ }
140
+ return value;
141
+ }
142
+
143
+ module.exports = {
144
+ DEFAULT_LENGTH,
145
+ MAX_LENGTH,
146
+ MAX_BYTES,
147
+ MAX_COUNT,
148
+ parseArgs,
149
+ parseCharacterLength,
150
+ parseByteLength,
151
+ parseCount,
152
+ parseEnvName
153
+ };
package/src/cli.js ADDED
@@ -0,0 +1,103 @@
1
+ const {
2
+ DEFAULT_LENGTH,
3
+ MAX_LENGTH,
4
+ MAX_BYTES,
5
+ parseArgs,
6
+ parseCharacterLength,
7
+ parseByteLength
8
+ } = require("./args");
9
+ const { generateSecrets, bytesUsed } = require("./generator");
10
+ const { formatPlain, formatJson, copyToClipboard } = require("./output");
11
+ const { getHelp } = require("./help");
12
+ const { version } = require("../package.json");
13
+
14
+ function run(argv, stdout, stderr, exit) {
15
+ try {
16
+ const parsed = parseArgs(argv);
17
+
18
+ if (parsed.action === "help") {
19
+ stdout.write(getHelp() + "\n");
20
+ exit(0);
21
+ return;
22
+ }
23
+
24
+ if (parsed.action === "version") {
25
+ stdout.write(version + "\n");
26
+ exit(0);
27
+ return;
28
+ }
29
+
30
+ if (parsed.bytes != null && parsed.lengthValue != null) {
31
+ throw new Error("Provide either a length or --bytes, not both.");
32
+ }
33
+
34
+ if (parsed.count > 1 && parsed.envName != null) {
35
+ throw new Error("The --env option is only supported for single secret generation (count=1).");
36
+ }
37
+
38
+ if (parsed.count > 1 && parsed.copy) {
39
+ throw new Error("Copy is only supported for single secret.");
40
+ }
41
+
42
+ if (parsed.json && parsed.count > 1) {
43
+ throw new Error("JSON mode is only supported for single secret generation (count=1).");
44
+ }
45
+
46
+ if (parsed.json && parsed.envName != null) {
47
+ throw new Error("JSON mode is not compatible with --env. Use JSON output directly for structured data.");
48
+ }
49
+
50
+ if (parsed.json && parsed.copy) {
51
+ throw new Error("JSON mode is not compatible with --copy. Use JSON output for automation.");
52
+ }
53
+
54
+ let charLength;
55
+ let cachedByteLength = null;
56
+
57
+ if (parsed.bytes != null) {
58
+ cachedByteLength = parseByteLength(parsed.bytes);
59
+ if (cachedByteLength * 2 > MAX_LENGTH) {
60
+ throw new Error(
61
+ `Byte length exceeds maximum of ${MAX_BYTES} bytes (${MAX_LENGTH} characters).`
62
+ );
63
+ }
64
+ charLength = cachedByteLength * 2;
65
+ } else if (parsed.lengthValue != null) {
66
+ const length = parseCharacterLength(parsed.lengthValue);
67
+ if (length > MAX_LENGTH) {
68
+ throw new Error(`Length exceeds maximum of ${MAX_LENGTH} characters.`);
69
+ }
70
+ charLength = length;
71
+ } else {
72
+ charLength = DEFAULT_LENGTH;
73
+ }
74
+
75
+ const secrets = generateSecrets(charLength, parsed.count);
76
+
77
+ if (parsed.json) {
78
+ const bytes = bytesUsed(charLength, cachedByteLength);
79
+ const jsonOutput = formatJson(secrets[0], charLength, bytes);
80
+ stdout.write(jsonOutput + "\n");
81
+ } else {
82
+ const output = formatPlain(secrets, parsed.envName);
83
+ stdout.write(output + "\n");
84
+
85
+ if (parsed.copy) {
86
+ try {
87
+ copyToClipboard(secrets[0]);
88
+ } catch (error) {
89
+ throw new Error(error.message || "Failed to copy to clipboard.");
90
+ }
91
+ }
92
+ }
93
+
94
+ exit(0);
95
+ } catch (error) {
96
+ stderr.write(`Error: ${error.message}\n`);
97
+ exit(1);
98
+ }
99
+ }
100
+
101
+ module.exports = {
102
+ run
103
+ };
@@ -0,0 +1,28 @@
1
+ const crypto = require("crypto");
2
+
3
+ function generateSecrets(charLength, count) {
4
+ const secrets = [];
5
+ for (let i = 0; i < count; i += 1) {
6
+ const secret = generateHex(charLength);
7
+ secrets.push(secret);
8
+ }
9
+ return secrets;
10
+ }
11
+
12
+ function generateHex(charLength) {
13
+ const bytesNeeded = Math.ceil(charLength / 2);
14
+ const hex = crypto.randomBytes(bytesNeeded).toString("hex");
15
+ return hex.slice(0, charLength);
16
+ }
17
+
18
+ function bytesUsed(charLength, inputBytes) {
19
+ if (inputBytes != null) {
20
+ return inputBytes;
21
+ }
22
+ return Math.ceil(charLength / 2);
23
+ }
24
+
25
+ module.exports = {
26
+ generateSecrets,
27
+ bytesUsed
28
+ };
package/src/help.js ADDED
@@ -0,0 +1,38 @@
1
+ const { DEFAULT_LENGTH, MAX_LENGTH, MAX_BYTES, MAX_COUNT } = require("./args");
2
+
3
+ function getHelp() {
4
+ return (
5
+ `hexpass - Generate cryptographically secure hex passwords\n\n` +
6
+ `Usage:\n` +
7
+ ` hexpass [length] Generate hex string with specified length (default: ${DEFAULT_LENGTH})\n` +
8
+ ` hexpass --bytes <n> Generate from n random bytes (output has n*2 characters)\n` +
9
+ ` hexpass --count <n> Generate n secrets (default: 1, max: ${MAX_COUNT})\n` +
10
+ ` hexpass --env <NAME> Output as NAME=secret instead of raw secret\n` +
11
+ ` hexpass --json Output as JSON object (single secret only)\n\n` +
12
+ `Options:\n` +
13
+ ` -h, --help Show this help message\n` +
14
+ ` -v, --version Show version number\n` +
15
+ ` -c, --copy Copy the generated hex string to clipboard (single secret only)\n` +
16
+ ` -n, --count <n> Generate n secrets (default: 1, max: ${MAX_COUNT})\n` +
17
+ ` -e, --env <NAME> Output as NAME=secret (single secret only)\n` +
18
+ ` -j, --json Output as JSON object (single secret only, incompatible with --env and --copy)\n` +
19
+ ` --bytes <n> Specify length in bytes instead of characters\n\n` +
20
+ `Examples:\n` +
21
+ ` hexpass # 32-character hex string\n` +
22
+ ` hexpass 64 # 64-character hex string\n` +
23
+ ` hexpass --bytes 32 # 64-character hex string (32 bytes)\n` +
24
+ ` hexpass 48 --copy # Generate 48 chars and copy to clipboard\n` +
25
+ ` hexpass 32 --count 5 # Generate 5 secrets, each 32 characters\n` +
26
+ ` hexpass 48 --env JWT_SECRET # Output as JWT_SECRET=<secret>\n` +
27
+ ` hexpass 32 --json # Output as JSON with length, bytes, and hex fields\n\n` +
28
+ `Limits:\n` +
29
+ ` Maximum output length is ${MAX_LENGTH} characters (${MAX_BYTES} bytes).\n` +
30
+ ` Maximum count is ${MAX_COUNT} secrets.\n\n` +
31
+ `Security:\n` +
32
+ ` All passwords are generated using Node.js crypto.randomBytes()`
33
+ );
34
+ }
35
+
36
+ module.exports = {
37
+ getHelp
38
+ };
package/src/output.js ADDED
@@ -0,0 +1,62 @@
1
+ const { spawnSync } = require("child_process");
2
+
3
+ function formatPlain(secrets, envName) {
4
+ const output = secrets.map(secret => {
5
+ if (envName != null) {
6
+ return `${envName}=${secret}`;
7
+ }
8
+ return secret;
9
+ }).join("\n");
10
+ return output;
11
+ }
12
+
13
+ function formatJson(secret, charLength, bytesValue) {
14
+ return JSON.stringify({
15
+ length: charLength,
16
+ bytes: bytesValue,
17
+ hex: secret
18
+ });
19
+ }
20
+
21
+ function copyToClipboard(text) {
22
+ const platform = process.platform;
23
+
24
+ if (platform === "darwin") {
25
+ runClipboardCommand("pbcopy", text);
26
+ return;
27
+ }
28
+
29
+ if (platform === "win32") {
30
+ runClipboardCommand("clip", text);
31
+ return;
32
+ }
33
+
34
+ const linuxCommands = [
35
+ ["xclip", ["-selection", "clipboard"]],
36
+ ["xsel", ["--clipboard", "--input"]],
37
+ ];
38
+
39
+ for (const [cmd, args] of linuxCommands) {
40
+ const result = spawnSync(cmd, args, { input: text, encoding: "utf8" });
41
+ if (!result.error && result.status === 0) {
42
+ return;
43
+ }
44
+ }
45
+
46
+ throw new Error(
47
+ "Clipboard copy not supported on this system. Install xclip or xsel."
48
+ );
49
+ }
50
+
51
+ function runClipboardCommand(command, input) {
52
+ const result = spawnSync(command, [], { input, encoding: "utf8" });
53
+ if (result.error || result.status !== 0) {
54
+ throw new Error(`Failed to copy to clipboard using ${command}.`);
55
+ }
56
+ }
57
+
58
+ module.exports = {
59
+ formatPlain,
60
+ formatJson,
61
+ copyToClipboard
62
+ };