dsh-vault 1.10.68 → 1.10.70
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-zh.md +21 -0
- package/README.md +27 -0
- package/lib/cli.js +50 -4
- package/lib/client.js +212 -163
- package/lib/client.js.map +1 -1
- package/lib/index.js +20 -0
- package/package.json +1 -1
package/README-zh.md
CHANGED
|
@@ -252,6 +252,11 @@ dsh-vault get my-entry --field apiKey # 取指定字段
|
|
|
252
252
|
dsh-vault get my-entry --mask # 只确认存在,不打印值
|
|
253
253
|
dsh-vault show my-entry # 非敏感字段的 JSON
|
|
254
254
|
dsh-vault env # 带 env 标签的条目 → KEY=VALUE
|
|
255
|
+
dsh-vault env --mask # 人类视角,两段都是 KEY=VALUE:
|
|
256
|
+
# ## exported items
|
|
257
|
+
# NPM_REGISTRY_API_KEY=npm_***
|
|
258
|
+
# ## unexported items
|
|
259
|
+
# EXAMPLE_BILLING_API_KEY=***
|
|
255
260
|
dsh-vault export-env .env # ……或写成 0600 权限的 .env 文件
|
|
256
261
|
dsh-vault verify # 校验主密码,stdout 无输出
|
|
257
262
|
```
|
|
@@ -319,6 +324,22 @@ npx dsh-vault list # 或者干脆不装
|
|
|
319
324
|
|
|
320
325
|
profile 这条路还有两个注意点:垫片可能被之后在该目录执行的 `pnpm install` 清掉;`pnpm dsh plugin --profile web add dsh-vault` 会依据包的 `dsh.bundle` 声明重整 `dsh.profile.bundles` —— 如果你自己的 patch 层**也**插入了这个插件,就会**挂两份**,要先删掉 patch 层那一行。
|
|
321
326
|
|
|
327
|
+
## 强度指示
|
|
328
|
+
|
|
329
|
+
列表里每个条目在标题后有一个**三星**强度指示 —— **☆☆☆ = 0 分,★★★ = 满分**,中间按半星分档。半星不是 Unicode 字符(大多数字体没有),而是把同样三个 `★` 复制一层、按百分比裁剪彩色层实现的:
|
|
330
|
+
|
|
331
|
+
| 分数 | 显示 | 档位 |
|
|
332
|
+
|---|---|---|
|
|
333
|
+
| 0–8 | ☆☆☆ | 弱 |
|
|
334
|
+
| 9–24 | ½☆☆ | 弱 |
|
|
335
|
+
| 25–41 | ★☆☆ | 弱 |
|
|
336
|
+
| 42–58 | ★½☆ | 中 |
|
|
337
|
+
| 59–74 | ★★☆ | 中 |
|
|
338
|
+
| 75–91 | ★★½ | 强 |
|
|
339
|
+
| 92–100 | ★★★ | 强 |
|
|
340
|
+
|
|
341
|
+
悬停可看到精确分数与评价。分数在 **host 侧**计算(列表本身永远拿不到密钥),针对条目的**密码或银行卡 PIN**;机器生成的 API Key、私钥**故意不评分**,免得所有密钥都是满分。
|
|
342
|
+
|
|
322
343
|
## 环境变量导出
|
|
323
344
|
|
|
324
345
|
打上 `env` 标签的条目可以用 `vault_env` / `vault_export_env` 导出为 `KEY=VALUE` 行,名字按各家工具链预期的字段名生成:
|
package/README.md
CHANGED
|
@@ -268,6 +268,11 @@ dsh-vault get my-entry --field apiKey # one named field
|
|
|
268
268
|
dsh-vault get my-entry --mask # confirm it exists without printing it
|
|
269
269
|
dsh-vault show my-entry # non-secret metadata as JSON
|
|
270
270
|
dsh-vault env # env-tagged entries as KEY=VALUE
|
|
271
|
+
dsh-vault env --mask # human view, both sections as KEY=VALUE:
|
|
272
|
+
# ## exported items
|
|
273
|
+
# NPM_REGISTRY_API_KEY=npm_***
|
|
274
|
+
# ## unexported items
|
|
275
|
+
# EXAMPLE_BILLING_API_KEY=***
|
|
271
276
|
dsh-vault export-env .env # …or write them to a 0600 file
|
|
272
277
|
dsh-vault verify # check the master password, nothing on stdout
|
|
273
278
|
```
|
|
@@ -354,6 +359,28 @@ Two more caveats on the profile route: its `.bin` shim can be pruned by a later
|
|
|
354
359
|
patch layer *also* inserts the plugin, that mounts it twice, so remove the patch
|
|
355
360
|
row first.
|
|
356
361
|
|
|
362
|
+
## Strength indicator
|
|
363
|
+
|
|
364
|
+
Each entry row shows a three-star indicator after its title — **☆☆☆ = 0 score,
|
|
365
|
+
★★★ = full marks** — in six half-star steps. A half star is the coloured copy of
|
|
366
|
+
the same three glyphs clipped to a percentage (Unicode has no half-star glyph
|
|
367
|
+
most fonts can render):
|
|
368
|
+
|
|
369
|
+
| score | shows | band |
|
|
370
|
+
|---|---|---|
|
|
371
|
+
| 0–8 | ☆☆☆ | weak |
|
|
372
|
+
| 9–24 | ½☆☆ | weak |
|
|
373
|
+
| 25–41 | ★☆☆ | weak |
|
|
374
|
+
| 42–58 | ★½☆ | fair |
|
|
375
|
+
| 59–74 | ★★☆ | fair |
|
|
376
|
+
| 75–91 | ★★½ | strong |
|
|
377
|
+
| 92–100 | ★★★ | strong |
|
|
378
|
+
|
|
379
|
+
Hovering it shows the exact score and verdict. The score is computed **host-side**
|
|
380
|
+
(the list never receives the secret) and covers the entry's **password or card
|
|
381
|
+
PIN**; machine-generated API keys and private keys are deliberately not scored,
|
|
382
|
+
so every key does not sit at full marks.
|
|
383
|
+
|
|
357
384
|
## Environment export
|
|
358
385
|
|
|
359
386
|
Entries tagged `env` can be materialised as `KEY=VALUE` lines (`vault_env`,
|
package/lib/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
24
24
|
import { createInterface } from 'node:readline';
|
|
25
25
|
import { ReadStream } from 'node:tty';
|
|
26
26
|
import { openVault, defaultVaultPath } from "./store.js";
|
|
27
|
-
import { envLinesFor, envPairsForEntry, maskSecret, primarySecret } from "./env-export.js";
|
|
27
|
+
import { envLinesFor, envPairsForEntry, maskSecret, primarySecret, shellQuote } from "./env-export.js";
|
|
28
28
|
function packageVersion() {
|
|
29
29
|
try {
|
|
30
30
|
const require = createRequire(import.meta.url);
|
|
@@ -80,7 +80,9 @@ env options:
|
|
|
80
80
|
--prefix <P> Prefix for derived key names (an explicit envKey is kept verbatim)
|
|
81
81
|
--kind <k> Only entries of this kind
|
|
82
82
|
--keys-only Print key names without values
|
|
83
|
-
--mask
|
|
83
|
+
--mask Human view: masked values PLUS an "unexported items"
|
|
84
|
+
section (what the tag currently leaves out)
|
|
85
|
+
--explain Same two-section view with the real values
|
|
84
86
|
--file <path> Write to a file instead of stdout
|
|
85
87
|
|
|
86
88
|
Exported lines are shell-quoted (KEY='value'), so read them with
|
|
@@ -357,7 +359,7 @@ export async function runCli(argv, io) {
|
|
|
357
359
|
list: ['kind', 'tag'],
|
|
358
360
|
get: ['field', 'fields', 'mask', 'all'],
|
|
359
361
|
show: [],
|
|
360
|
-
env: ['prefix', 'kind', 'keys-only', 'mask', 'file'],
|
|
362
|
+
env: ['prefix', 'kind', 'keys-only', 'mask', 'explain', 'file'],
|
|
361
363
|
'export-env': ['prefix', 'kind', 'keys-only'],
|
|
362
364
|
verify: [],
|
|
363
365
|
};
|
|
@@ -479,7 +481,8 @@ export async function runCli(argv, io) {
|
|
|
479
481
|
// env / export-env
|
|
480
482
|
const prefix = stringFlag(parsed, 'prefix') ?? '';
|
|
481
483
|
const kind = stringFlag(parsed, 'kind');
|
|
482
|
-
const
|
|
484
|
+
const matching = store.list().filter(entry => kind === undefined || (entry.kind ?? 'login') === kind);
|
|
485
|
+
const lines = envLinesFor(matching, { prefix, ...(kind !== undefined ? { kind } : {}) });
|
|
483
486
|
const rendered = lines.map(line => {
|
|
484
487
|
if (keysOnly)
|
|
485
488
|
return line.split('=')[0] ?? line;
|
|
@@ -488,6 +491,27 @@ export async function runCli(argv, io) {
|
|
|
488
491
|
const eq = line.indexOf('=');
|
|
489
492
|
return `${line.slice(0, eq)}=${maskSecret(line.slice(eq + 1).replace(/^'|'$/g, ''))}`;
|
|
490
493
|
});
|
|
494
|
+
// `--mask` is the human view (masked values are useless to a script), so it
|
|
495
|
+
// also lists what is NOT exported. Without it the output stays pure
|
|
496
|
+
// `KEY=VALUE`, which is what `eval "$(…)"` and pipelines need.
|
|
497
|
+
const explain = mask || parsed.flags.get('explain') === true;
|
|
498
|
+
const unexported = matching.filter(entry => !(entry.tags ?? []).includes('env'));
|
|
499
|
+
/** `KEY=***` for entries that are not exported (names visible, values not). */
|
|
500
|
+
const envPairsForEntryPairs = (entries) => pairsOf(entries, true).map(line => `${line.slice(0, line.indexOf('='))}=***`);
|
|
501
|
+
/** KEY=VALUE pairs of the given entries, de-duplicated, first entry wins. */
|
|
502
|
+
const pairsOf = (entries, masked) => {
|
|
503
|
+
const seen = new Set();
|
|
504
|
+
const out = [];
|
|
505
|
+
for (const entry of entries) {
|
|
506
|
+
for (const pair of envPairsForEntry(entry, prefix)) {
|
|
507
|
+
if (seen.has(pair.key))
|
|
508
|
+
continue;
|
|
509
|
+
seen.add(pair.key);
|
|
510
|
+
out.push(`${pair.key}=${masked ? maskSecret(pair.value) : shellQuote(pair.value)}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return out;
|
|
514
|
+
};
|
|
491
515
|
const target = parsed.command === 'export-env' ? parsed.positional[0] : stringFlag(parsed, 'file');
|
|
492
516
|
if (parsed.command === 'export-env' && target === undefined) {
|
|
493
517
|
io.err('dsh-vault: export-env needs a file path\n');
|
|
@@ -504,6 +528,28 @@ export async function runCli(argv, io) {
|
|
|
504
528
|
: `dsh-vault: wrote ${rendered.length} lines to ${file} (mode 0600)\n`);
|
|
505
529
|
return 0;
|
|
506
530
|
}
|
|
531
|
+
if (explain) {
|
|
532
|
+
// Both sections use the same KEY=VALUE shape, so a name can be copied from
|
|
533
|
+
// either one. The unexported values stay masked even under --explain: the
|
|
534
|
+
// tag is what says "this one is meant for a shell".
|
|
535
|
+
io.out('## exported items\n');
|
|
536
|
+
if (rendered.length > 0)
|
|
537
|
+
io.out(`${rendered.join('\n')}\n`);
|
|
538
|
+
else
|
|
539
|
+
io.out(' (none — no entry carries the "env" tag)\n');
|
|
540
|
+
io.out('\n## unexported items\n');
|
|
541
|
+
// A fixed placeholder, not `maskSecret`: these did not opt into export, so
|
|
542
|
+
// not even a four-character prefix of a password should be shown.
|
|
543
|
+
const hidden = envPairsForEntryPairs(unexported);
|
|
544
|
+
if (hidden.length > 0) {
|
|
545
|
+
io.out(`${hidden.join('\n')}\n`);
|
|
546
|
+
io.out(' not exported (no "env" tag) — values hidden. `get <name>` still reads them.\n');
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
io.out(' (none — every entry is exported)\n');
|
|
550
|
+
}
|
|
551
|
+
return 0;
|
|
552
|
+
}
|
|
507
553
|
io.out(rendered.join('\n') + (rendered.length > 0 ? '\n' : ''));
|
|
508
554
|
if (rendered.length === 0) {
|
|
509
555
|
// The usual first-run wall: `env` only exports entries tagged `env`, and
|