dsh-vault 0.8.5 → 0.8.7
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 +6 -3
- package/README.md +6 -3
- package/lib/client.js +180 -36
- package/lib/client.js.map +1 -1
- package/lib/index.js +162 -14
- package/lib/store.js +11 -1
- package/package.json +2 -2
package/README-zh.md
CHANGED
|
@@ -41,8 +41,10 @@ dsh-vault 是一个面向 DeepSeek Harness 的安全加密插件:把你在使
|
|
|
41
41
|
|---|---|
|
|
42
42
|
| `vault_add` | 新增条目(上述字段任意组合;空字符串/空数组字段会被忽略) |
|
|
43
43
|
| `vault_get` | 按 id 读取完整条目(含全部密钥) |
|
|
44
|
-
| `vault_search` | 跨标题/分类/用户名/邮箱/手机/主机/端口/URL/备注/标签/自定义字段(含数字/布尔/嵌套值;空格分隔多词 OR
|
|
45
|
-
| `vault_update` | 按 id 更新字段(未提供的字段保留;空字符串清除该字段;`title`
|
|
44
|
+
| `vault_search` | 跨标题/分类/用户名/邮箱/手机/主机/端口/URL/备注/标签/自定义字段(含数字/布尔/嵌套值;空格分隔多词 OR 命中)检索;可选 `createdAfter`/`createdBefore` 毫秒时间戳过滤;返回无密摘要;`limit` 须为 1–100 的整数 |
|
|
45
|
+
| `vault_update` | 按 id 更新字段(未提供的字段保留;空字符串清除该字段;`title` 可改名;`rotationDays: 0` 清除轮换 = 永不轮换) |
|
|
46
|
+
| `vault_compare` | 逐字段比较两个条目(`onlyA`/`onlyB`/`differ`/`equal`)——只返回字段名,绝不返回密钥值 |
|
|
47
|
+
| `vault_rename` | 一次调用即可重命名条目(`vault_update` 的快捷方式) |
|
|
46
48
|
| `vault_delete` | 软删除条目(移入回收站,磁盘上仍加密保留) |
|
|
47
49
|
| `vault_restore` / `vault_purge` | 从回收站恢复 / 从磁盘永久移除 |
|
|
48
50
|
| `vault_lock` / `vault_unlock` | 显式锁定保险库(清空内存密钥)/ 重新解锁 |
|
|
@@ -50,7 +52,8 @@ dsh-vault 是一个面向 DeepSeek Harness 的安全加密插件:把你在使
|
|
|
50
52
|
| `vault_generate_password` | 生成强随机密码(长度/字符集/去歧义/分组可选;`group` 须为 ≥2 的整数) |
|
|
51
53
|
| `vault_strength` | 零依赖密码强度评估(0–100 分,weak/fair/strong/very strong) |
|
|
52
54
|
| `vault_rekey` | 原地升级 scrypt KDF 参数并重加密 |
|
|
53
|
-
| `vault_import_csv` | 从 CSV 批量导入凭据(自定义列变为 fields
|
|
55
|
+
| `vault_import_csv` | 从 CSV 批量导入凭据(自定义列变为 fields;`overwrite: true` 合并字段到已有条目,不再产生重复) |
|
|
56
|
+
| `vault_apply_tags` | 按查询批量增/删/替换条目标签(支持 dry-run,不含密钥) |
|
|
54
57
|
| `vault_totp_uri` | 为存储的或裸 TOTP 密钥生成 otpauth:// 配置 URI |
|
|
55
58
|
| `vault_switch` / `vault_list` | 按名称切换当前保险库 / 列出可用保险库 |
|
|
56
59
|
| `vault_rotation` | 报告已过期 / 待轮换 / 即将过期的凭据(不含密钥) |
|
package/README.md
CHANGED
|
@@ -43,8 +43,10 @@ Each record has a `title`, an optional `kind`, and any combination of fields:
|
|
|
43
43
|
|---|---|
|
|
44
44
|
| `vault_add` | Add an entry (any combination of fields; empty strings/arrays are ignored) |
|
|
45
45
|
| `vault_get` | Read a full entry by id (including all secrets) |
|
|
46
|
-
| `vault_search` | Search titles/categories/usernames/emails/phones/hosts/ports/URLs/notes/tags/custom fields (incl. numeric/boolean/nested values; whitespace-separated terms OR-match); returns secret-free summaries; `limit` must be an integer 1–100 |
|
|
47
|
-
| `vault_update` | Update fields by id (unprovided fields kept; empty string clears a field; `title` is renamable) |
|
|
46
|
+
| `vault_search` | Search titles/categories/usernames/emails/phones/hosts/ports/URLs/notes/tags/custom fields (incl. numeric/boolean/nested values; whitespace-separated terms OR-match); optional `createdAfter`/`createdBefore` epoch-millis filters; returns secret-free summaries; `limit` must be an integer 1–100 |
|
|
47
|
+
| `vault_update` | Update fields by id (unprovided fields kept; empty string clears a field; `title` is renamable; `rotationDays: 0` clears rotation = never rotate) |
|
|
48
|
+
| `vault_compare` | Compare two entries field by field (`onlyA`/`onlyB`/`differ`/`equal`) — field names only, never secret values |
|
|
49
|
+
| `vault_rename` | Rename an entry in one call (shortcut for `vault_update`) |
|
|
48
50
|
| `vault_delete` | Soft-delete an entry (moves it to the trash, still encrypted on disk) |
|
|
49
51
|
| `vault_restore` / `vault_purge` | Bring a trashed entry back / permanently remove it from disk |
|
|
50
52
|
| `vault_lock` / `vault_unlock` | Explicitly lock the vault (wipe the in-memory key) / re-unlock it |
|
|
@@ -52,7 +54,8 @@ Each record has a `title`, an optional `kind`, and any combination of fields:
|
|
|
52
54
|
| `vault_generate_password` | Generate a strong random password (length/character classes/ambiguity exclusion/grouping; `group` must be an integer ≥ 2) |
|
|
53
55
|
| `vault_strength` | Zero-dependency password strength estimate (score 0–100, weak/fair/strong/very strong) |
|
|
54
56
|
| `vault_rekey` | Upgrade the vault to fresh scrypt KDF parameters in place |
|
|
55
|
-
| `vault_import_csv` | Bulk-import credentials from a CSV file (custom columns become fields) |
|
|
57
|
+
| `vault_import_csv` | Bulk-import credentials from a CSV file (custom columns become fields; `overwrite: true` merges fields into existing entries instead of duplicating) |
|
|
58
|
+
| `vault_apply_tags` | Bulk add/remove/replace tags on every entry matching a query (dry-run supported, no secrets) |
|
|
56
59
|
| `vault_totp_uri` | Build an otpauth:// provisioning URI for a stored or bare TOTP secret |
|
|
57
60
|
| `vault_switch` / `vault_list` | Switch the active vault by name / list available vaults |
|
|
58
61
|
| `vault_rotation` | Report expired / due-for-rotation / expiring-soon credentials (no secrets) |
|
package/lib/client.js
CHANGED
|
@@ -7,7 +7,7 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
let react = require("react");
|
|
8
8
|
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
9
|
//#region \0dsh-css:/Users/zhipeng/Desktop/work/dsh test/dsh-vault/src/client/VaultSection.module.css.mjs
|
|
10
|
-
const css = ".t_j5va_section{flex-direction:column;gap:12px;padding:16px 0;display:flex}.t_j5va_intro{opacity:.75;margin:0;font-size:13px}.t_j5va_toolbar{align-items:center;gap:8px;display:flex}.t_j5va_searchBox{flex:1;display:flex}.t_j5va_searchBox input{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;flex:1;padding:6px 10px;font-size:13px}.t_j5va_addButton,.t_j5va_saveButton{background:var(--color-accent,#4f8cff);color:#fff;cursor:pointer;border:1px solid #0000;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_addButton:disabled,.t_j5va_saveButton:disabled,.t_j5va_rowActions button:disabled{opacity:.5;cursor:not-allowed}.t_j5va_list{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}.t_j5va_row{border:1px solid var(--color-border,#333);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 10px;display:flex}.t_j5va_rowMain{flex-direction:column;gap:2px;min-width:0;display:flex}.t_j5va_title{font-size:14px;font-weight:600}.t_j5va_identity{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.t_j5va_totp{font-family:var(--font-mono,monospace);color:var(--color-accent,#4f8cff);font-size:13px}.t_j5va_rowActions{flex-shrink:0;align-items:center;gap:6px;display:flex}.t_j5va_rowActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:4px 8px;font-size:12px}.t_j5va_deleteButton{color:#e5484d}.t_j5va_copied{color:#30a46c;font-size:12px}.t_j5va_empty,.t_j5va_status,.t_j5va_error{opacity:.8;font-size:13px}.t_j5va_modeReadonly,.t_j5va_modeAsk,.t_j5va_modeAuto{border-radius:999px;width:fit-content;margin:0;padding:4px 10px;font-size:12px;display:inline-block}.t_j5va_modeReadonly{color:#e5484d;border:1px solid #e5484d80}.t_j5va_modeAsk{color:#f5a524;border:1px solid #f5a52480}.t_j5va_modeAuto{color:#30a46c;border:1px solid #30a46c80}.t_j5va_policyBar{flex-direction:column;align-items:flex-start;gap:6px;display:flex}.t_j5va_policyField{align-items:center;gap:8px;font-size:13px;display:flex}.t_j5va_policyField select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:4px 8px;font-size:13px}.t_j5va_emptyBox{flex-direction:column;gap:4px;display:flex}.t_j5va_emptyHint{opacity:.65;margin:0;font-size:12px}.t_j5va_error{color:#e5484d}.t_j5va_editor{border:1px solid var(--color-border,#444);border-radius:8px;padding:12px}.t_j5va_editorBody{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px 12px;display:grid}.t_j5va_field{flex-direction:column;gap:4px;font-size:12px;display:flex}.t_j5va_field input,.t_j5va_field select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:5px 8px;font-size:13px}.t_j5va_editorActions{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.t_j5va_editorActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_reportBox{border:1px solid #f5a52466;border-radius:8px;flex-direction:column;gap:2px;padding:8px 10px;font-size:12px;display:flex}.t_j5va_reportTitle{margin:0;font-weight:600}.t_j5va_reportLine{opacity:.8;margin:0}.t_j5va_trashButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;align-self:flex-start;padding:4px 8px;font-size:12px}.t_j5va_secretField{gap:4px;display:flex}.t_j5va_secretField input{flex:1;min-width:0}.t_j5va_revealButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:0 8px;font-size:12px}.t_j5va_kindFilter{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:6px 8px;font-size:13px}.t_j5va_highBadge{color:#e5484d;vertical-align:middle;background:#e5484d26;border-radius:4px;margin-left:6px;padding:1px 6px;font-size:10px;font-weight:600}.t_j5va_pinStar{color:#f5a524;margin-right:4px}.t_j5va_clearButton{color:inherit;cursor:pointer;opacity:.6;background:0 0;border:none;padding:0 8px;font-size:16px}.t_j5va_field textarea{border:1px solid var(--color-border,#444);color:inherit;resize:vertical;background:0 0;border-radius:6px;padding:5px 8px;font-family:inherit;font-size:13px}.t_j5va_row{transition:background .12s}.t_j5va_row:hover{background:var(--color-hover,#80808014)}.t_j5va_footer{opacity:.6;margin:0;font-size:12px}.t_j5va_kindIcon{margin-right:4px;font-size:12px}.t_j5va_dangerButton{color:#e5484d;cursor:pointer;background:0 0;border:1px solid #e5484d80;border-radius:6px;align-self:flex-start;padding:4px 8px;font-size:12px}.t_j5va_rowMain{cursor:pointer}.t_j5va_detailBox{background:var(--color-hover,#8080800f);border-radius:6px;flex-wrap:wrap;grid-column:1/-1;gap:4px 12px;padding:6px 8px;font-size:12px;display:flex}.t_j5va_detailItem{opacity:.85}.t_j5va_sortButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:6px 10px;font-size:13px}";
|
|
10
|
+
const css = ".t_j5va_section{flex-direction:column;gap:12px;padding:16px 0;display:flex}.t_j5va_intro{opacity:.75;margin:0;font-size:13px}.t_j5va_toolbar{align-items:center;gap:8px;display:flex}.t_j5va_searchBox{flex:1;display:flex}.t_j5va_searchBox input{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;flex:1;padding:6px 10px;font-size:13px}.t_j5va_addButton,.t_j5va_saveButton{background:var(--color-accent,#4f8cff);color:#fff;cursor:pointer;border:1px solid #0000;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_addButton:disabled,.t_j5va_saveButton:disabled,.t_j5va_rowActions button:disabled{opacity:.5;cursor:not-allowed}.t_j5va_list{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}.t_j5va_row{border:1px solid var(--color-border,#333);border-radius:8px;justify-content:space-between;align-items:center;gap:12px;padding:8px 10px;display:flex}.t_j5va_rowMain{flex-direction:column;gap:2px;min-width:0;display:flex}.t_j5va_title{font-size:14px;font-weight:600}.t_j5va_identity{opacity:.7;text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.t_j5va_totp{font-family:var(--font-mono,monospace);color:var(--color-accent,#4f8cff);font-size:13px}.t_j5va_rowActions{flex-shrink:0;align-items:center;gap:6px;display:flex}.t_j5va_rowActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:4px 8px;font-size:12px}.t_j5va_deleteButton{color:#e5484d}.t_j5va_copied{color:#30a46c;font-size:12px}.t_j5va_empty,.t_j5va_status,.t_j5va_error{opacity:.8;font-size:13px}.t_j5va_modeReadonly,.t_j5va_modeAsk,.t_j5va_modeAuto{border-radius:999px;width:fit-content;margin:0;padding:4px 10px;font-size:12px;display:inline-block}.t_j5va_modeReadonly{color:#e5484d;border:1px solid #e5484d80}.t_j5va_modeAsk{color:#f5a524;border:1px solid #f5a52480}.t_j5va_modeAuto{color:#30a46c;border:1px solid #30a46c80}.t_j5va_policyBar{flex-direction:column;align-items:flex-start;gap:6px;display:flex}.t_j5va_policyField{align-items:center;gap:8px;font-size:13px;display:flex}.t_j5va_policyField select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:4px 8px;font-size:13px}.t_j5va_emptyBox{flex-direction:column;gap:4px;display:flex}.t_j5va_emptyHint{opacity:.65;margin:0;font-size:12px}.t_j5va_error{color:#e5484d}.t_j5va_editor{border:1px solid var(--color-border,#444);border-radius:8px;padding:12px}.t_j5va_editorBody{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px 12px;display:grid}.t_j5va_field{flex-direction:column;gap:4px;font-size:12px;display:flex}.t_j5va_field input,.t_j5va_field select{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:5px 8px;font-size:13px}.t_j5va_editorActions{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.t_j5va_editorActions button{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:6px 12px;font-size:13px}.t_j5va_reportBox{border:1px solid #f5a52466;border-radius:8px;flex-direction:column;gap:2px;padding:8px 10px;font-size:12px;display:flex}.t_j5va_reportTitle{margin:0;font-weight:600}.t_j5va_reportLine{opacity:.8;margin:0}.t_j5va_trashButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;align-self:flex-start;padding:4px 8px;font-size:12px}.t_j5va_secretField{gap:4px;display:flex}.t_j5va_secretField input{flex:1;min-width:0}.t_j5va_revealButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:0 8px;font-size:12px}.t_j5va_kindFilter{border:1px solid var(--color-border,#444);color:inherit;background:0 0;border-radius:6px;padding:6px 8px;font-size:13px}.t_j5va_highBadge{color:#e5484d;vertical-align:middle;background:#e5484d26;border-radius:4px;margin-left:6px;padding:1px 6px;font-size:10px;font-weight:600}.t_j5va_pinStar{color:#f5a524;margin-right:4px}.t_j5va_clearButton{color:inherit;cursor:pointer;opacity:.6;background:0 0;border:none;padding:0 8px;font-size:16px}.t_j5va_field textarea{border:1px solid var(--color-border,#444);color:inherit;resize:vertical;background:0 0;border-radius:6px;padding:5px 8px;font-family:inherit;font-size:13px}.t_j5va_row{transition:background .12s}.t_j5va_row:hover{background:var(--color-hover,#80808014)}.t_j5va_healthBar{flex-wrap:wrap;gap:6px;margin:0 0 10px;display:flex}.t_j5va_badge{border:1px solid var(--dsh-color-border,#ddd);background:var(--dsh-color-surface-2,#f7f7f8);color:var(--dsh-color-text-2,#555);border-radius:999px;padding:4px 10px;font-size:12px;line-height:1}.t_j5va_badgeOk{color:#2e9e5b;border-color:#2e9e5b}.t_j5va_badgeWarn{color:#b57f1a;border-color:#c98a1b}.t_j5va_rowDue{border-left:3px solid #c98a1b}.t_j5va_rowExpired{border-left:3px solid #cf3d3d}.t_j5va_dueBadge{border-radius:999px;margin-left:6px;padding:3px 8px;font-size:11px;font-weight:600;line-height:1}.t_j5va_badgeDanger{color:#c03a3a;border-color:#cf3d3d}.t_j5va_footer{opacity:.6;margin:0;font-size:12px}.t_j5va_kindIcon{margin-right:4px;font-size:12px}.t_j5va_dangerButton{color:#e5484d;cursor:pointer;background:0 0;border:1px solid #e5484d80;border-radius:6px;align-self:flex-start;padding:4px 8px;font-size:12px}.t_j5va_rowMain{cursor:pointer}.t_j5va_detailBox{background:var(--color-hover,#8080800f);border-radius:6px;flex-wrap:wrap;grid-column:1/-1;gap:4px 12px;padding:6px 8px;font-size:12px;display:flex}.t_j5va_detailItem{opacity:.85}.t_j5va_sortButton{border:1px solid var(--color-border,#444);color:inherit;cursor:pointer;background:0 0;border-radius:6px;padding:6px 10px;font-size:13px}";
|
|
11
11
|
const tagId = "dsh-vault/VaultSection.module.css";
|
|
12
12
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
13
13
|
const tag = document.createElement("style");
|
|
@@ -17,51 +17,59 @@ window.__ModuleLoader__.load({
|
|
|
17
17
|
document.head.appendChild(tag);
|
|
18
18
|
}
|
|
19
19
|
var VaultSection_module_css_default = {
|
|
20
|
+
"highBadge": "t_j5va_highBadge",
|
|
21
|
+
"badge": "t_j5va_badge",
|
|
22
|
+
"badgeWarn": "t_j5va_badgeWarn",
|
|
20
23
|
"sortButton": "t_j5va_sortButton",
|
|
21
|
-
"editorActions": "t_j5va_editorActions",
|
|
22
|
-
"identity": "t_j5va_identity",
|
|
23
|
-
"section": "t_j5va_section",
|
|
24
|
-
"editor": "t_j5va_editor",
|
|
25
|
-
"pinStar": "t_j5va_pinStar",
|
|
26
|
-
"trashButton": "t_j5va_trashButton",
|
|
27
|
-
"emptyBox": "t_j5va_emptyBox",
|
|
28
|
-
"saveButton": "t_j5va_saveButton",
|
|
29
|
-
"footer": "t_j5va_footer",
|
|
30
|
-
"modeReadonly": "t_j5va_modeReadonly",
|
|
31
|
-
"kindIcon": "t_j5va_kindIcon",
|
|
32
24
|
"status": "t_j5va_status",
|
|
25
|
+
"editor": "t_j5va_editor",
|
|
26
|
+
"editorBody": "t_j5va_editorBody",
|
|
27
|
+
"section": "t_j5va_section",
|
|
28
|
+
"badgeOk": "t_j5va_badgeOk",
|
|
33
29
|
"modeAuto": "t_j5va_modeAuto",
|
|
34
|
-
"
|
|
35
|
-
"copied": "t_j5va_copied",
|
|
36
|
-
"reportTitle": "t_j5va_reportTitle",
|
|
37
|
-
"policyBar": "t_j5va_policyBar",
|
|
30
|
+
"row": "t_j5va_row",
|
|
38
31
|
"searchBox": "t_j5va_searchBox",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
32
|
+
"title": "t_j5va_title",
|
|
33
|
+
"identity": "t_j5va_identity",
|
|
34
|
+
"modeAsk": "t_j5va_modeAsk",
|
|
41
35
|
"addButton": "t_j5va_addButton",
|
|
42
|
-
"
|
|
36
|
+
"rowDue": "t_j5va_rowDue",
|
|
43
37
|
"list": "t_j5va_list",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
38
|
+
"rowExpired": "t_j5va_rowExpired",
|
|
39
|
+
"emptyBox": "t_j5va_emptyBox",
|
|
40
|
+
"footer": "t_j5va_footer",
|
|
41
|
+
"revealButton": "t_j5va_revealButton",
|
|
42
|
+
"deleteButton": "t_j5va_deleteButton",
|
|
46
43
|
"empty": "t_j5va_empty",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
44
|
+
"reportBox": "t_j5va_reportBox",
|
|
45
|
+
"badgeDanger": "t_j5va_badgeDanger",
|
|
46
|
+
"rowMain": "t_j5va_rowMain",
|
|
47
|
+
"editorActions": "t_j5va_editorActions",
|
|
48
|
+
"detailItem": "t_j5va_detailItem",
|
|
49
49
|
"field": "t_j5va_field",
|
|
50
|
+
"trashButton": "t_j5va_trashButton",
|
|
51
|
+
"emptyHint": "t_j5va_emptyHint",
|
|
52
|
+
"detailBox": "t_j5va_detailBox",
|
|
53
|
+
"saveButton": "t_j5va_saveButton",
|
|
54
|
+
"pinStar": "t_j5va_pinStar",
|
|
55
|
+
"totp": "t_j5va_totp",
|
|
56
|
+
"copied": "t_j5va_copied",
|
|
57
|
+
"policyBar": "t_j5va_policyBar",
|
|
58
|
+
"kindFilter": "t_j5va_kindFilter",
|
|
50
59
|
"secretField": "t_j5va_secretField",
|
|
51
|
-
"
|
|
52
|
-
"deleteButton": "t_j5va_deleteButton",
|
|
53
|
-
"row": "t_j5va_row",
|
|
54
|
-
"error": "t_j5va_error",
|
|
55
|
-
"revealButton": "t_j5va_revealButton",
|
|
56
|
-
"intro": "t_j5va_intro",
|
|
57
|
-
"toolbar": "t_j5va_toolbar",
|
|
58
|
-
"rowMain": "t_j5va_rowMain",
|
|
60
|
+
"kindIcon": "t_j5va_kindIcon",
|
|
59
61
|
"rowActions": "t_j5va_rowActions",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
+
"clearButton": "t_j5va_clearButton",
|
|
63
|
+
"healthBar": "t_j5va_healthBar",
|
|
64
|
+
"dueBadge": "t_j5va_dueBadge",
|
|
65
|
+
"toolbar": "t_j5va_toolbar",
|
|
62
66
|
"dangerButton": "t_j5va_dangerButton",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
67
|
+
"intro": "t_j5va_intro",
|
|
68
|
+
"error": "t_j5va_error",
|
|
69
|
+
"reportTitle": "t_j5va_reportTitle",
|
|
70
|
+
"modeReadonly": "t_j5va_modeReadonly",
|
|
71
|
+
"policyField": "t_j5va_policyField",
|
|
72
|
+
"reportLine": "t_j5va_reportLine"
|
|
65
73
|
};
|
|
66
74
|
//#endregion
|
|
67
75
|
//#region src/client/VaultSection.tsx
|
|
@@ -185,6 +193,7 @@ window.__ModuleLoader__.load({
|
|
|
185
193
|
const [showTrash, setShowTrash] = (0, react.useState)(false);
|
|
186
194
|
const [trashEntries, setTrashEntries] = (0, react.useState)([]);
|
|
187
195
|
const [report, setReport] = (0, react.useState)(null);
|
|
196
|
+
const [dueMap, setDueMap] = (0, react.useState)({});
|
|
188
197
|
const [recentEvents, setRecentEvents] = (0, react.useState)([]);
|
|
189
198
|
const [vaultStats, setVaultStats] = (0, react.useState)(null);
|
|
190
199
|
const [backupInfo, setBackupInfo] = (0, react.useState)(null);
|
|
@@ -226,6 +235,50 @@ window.__ModuleLoader__.load({
|
|
|
226
235
|
window.clearTimeout(timer);
|
|
227
236
|
};
|
|
228
237
|
}, [refresh]);
|
|
238
|
+
(0, react.useEffect)(() => {
|
|
239
|
+
let current = true;
|
|
240
|
+
const load = () => {
|
|
241
|
+
Promise.all([
|
|
242
|
+
stats().catch(() => null),
|
|
243
|
+
backupStatus().catch(() => null),
|
|
244
|
+
rotation().catch(() => []),
|
|
245
|
+
health().catch(() => null),
|
|
246
|
+
recent().catch(() => [])
|
|
247
|
+
]).then(([st, bk, rot, hl, rc]) => {
|
|
248
|
+
if (!current) return;
|
|
249
|
+
if (st !== null) setVaultStats(st);
|
|
250
|
+
if (bk !== null) setBackupInfo(bk);
|
|
251
|
+
setReport({
|
|
252
|
+
rotation: rot ?? [],
|
|
253
|
+
weak: hl?.weak ?? [],
|
|
254
|
+
reused: hl?.reused ?? [],
|
|
255
|
+
strength: hl?.strength ?? null
|
|
256
|
+
});
|
|
257
|
+
const due = {};
|
|
258
|
+
for (const item of rot ?? []) if (item.id !== void 0 && item.due !== void 0) due[item.id] = {
|
|
259
|
+
due: item.due,
|
|
260
|
+
daysLeft: item.daysLeft ?? 0
|
|
261
|
+
};
|
|
262
|
+
setDueMap(due);
|
|
263
|
+
setRecentEntries(rc ?? []);
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
load();
|
|
267
|
+
const onFocus = () => {
|
|
268
|
+
load();
|
|
269
|
+
};
|
|
270
|
+
window.addEventListener("focus", onFocus);
|
|
271
|
+
return () => {
|
|
272
|
+
current = false;
|
|
273
|
+
window.removeEventListener("focus", onFocus);
|
|
274
|
+
};
|
|
275
|
+
}, [
|
|
276
|
+
stats,
|
|
277
|
+
backupStatus,
|
|
278
|
+
rotation,
|
|
279
|
+
health,
|
|
280
|
+
recent
|
|
281
|
+
]);
|
|
229
282
|
/** Open the editor for a new entry. */
|
|
230
283
|
function startCreate() {
|
|
231
284
|
setForm(emptyForm());
|
|
@@ -699,7 +752,7 @@ window.__ModuleLoader__.load({
|
|
|
699
752
|
children: state.entries.filter((entry) => (kindFilter === "" || entry.kind === kindFilter) && (tagFilter === "" || (entry.tags ?? []).includes(tagFilter))).sort((a, b) => sortBy === "alpha" ? a.title.localeCompare(b.title) : 0).map((entry) => {
|
|
700
753
|
const code = codeMap[entry.id];
|
|
701
754
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
702
|
-
className: VaultSection_module_css_default.row
|
|
755
|
+
className: `${VaultSection_module_css_default.row}${dueMap[entry.id] !== void 0 ? ` ${dueMap[entry.id].due === "expired" ? VaultSection_module_css_default.rowExpired : VaultSection_module_css_default.rowDue}` : ""}`,
|
|
703
756
|
children: [
|
|
704
757
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
705
758
|
className: VaultSection_module_css_default.rowMain,
|
|
@@ -721,6 +774,10 @@ window.__ModuleLoader__.load({
|
|
|
721
774
|
entry.sensitivity === "high" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
722
775
|
className: VaultSection_module_css_default.highBadge,
|
|
723
776
|
children: t("highSensitivity")
|
|
777
|
+
}),
|
|
778
|
+
dueMap[entry.id] !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
779
|
+
className: `${VaultSection_module_css_default.dueBadge} ${dueMap[entry.id].due === "expired" ? VaultSection_module_css_default.badgeDanger : VaultSection_module_css_default.badgeWarn}`,
|
|
780
|
+
children: dueMap[entry.id].due === "expired" ? t("dueExpired") : dueMap[entry.id].due === "soon" ? `${t("dueExpiring")} ${dueMap[entry.id].daysLeft}d` : t("dueNow")
|
|
724
781
|
})
|
|
725
782
|
]
|
|
726
783
|
}),
|
|
@@ -801,6 +858,81 @@ window.__ModuleLoader__.load({
|
|
|
801
858
|
}, entry.id);
|
|
802
859
|
})
|
|
803
860
|
}),
|
|
861
|
+
state.status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
862
|
+
className: VaultSection_module_css_default.healthBar,
|
|
863
|
+
children: [
|
|
864
|
+
vaultStats !== null && typeof vaultStats.total === "number" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
865
|
+
className: VaultSection_module_css_default.badge,
|
|
866
|
+
children: [
|
|
867
|
+
t("entryCount"),
|
|
868
|
+
": ",
|
|
869
|
+
String(vaultStats.total)
|
|
870
|
+
]
|
|
871
|
+
}),
|
|
872
|
+
vaultStats !== null && typeof vaultStats.withTotp === "number" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
873
|
+
className: VaultSection_module_css_default.badge,
|
|
874
|
+
children: ["TOTP: ", String(vaultStats.withTotp)]
|
|
875
|
+
}),
|
|
876
|
+
vaultStats !== null && typeof vaultStats.highSensitivity === "number" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
877
|
+
className: VaultSection_module_css_default.badge,
|
|
878
|
+
children: [
|
|
879
|
+
t("highSensitivity"),
|
|
880
|
+
": ",
|
|
881
|
+
String(vaultStats.highSensitivity)
|
|
882
|
+
]
|
|
883
|
+
}),
|
|
884
|
+
report !== null && report.strength !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
885
|
+
className: `${VaultSection_module_css_default.badge} ${report.strength.weak > 0 ? VaultSection_module_css_default.badgeDanger : VaultSection_module_css_default.badgeOk}`,
|
|
886
|
+
children: [
|
|
887
|
+
t("healthStrength"),
|
|
888
|
+
": ",
|
|
889
|
+
report.strength.weak,
|
|
890
|
+
"W/",
|
|
891
|
+
report.strength.fair,
|
|
892
|
+
"F/",
|
|
893
|
+
report.strength.strong,
|
|
894
|
+
"S"
|
|
895
|
+
]
|
|
896
|
+
}),
|
|
897
|
+
report !== null && report.rotation.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
898
|
+
className: `${VaultSection_module_css_default.badge} ${VaultSection_module_css_default.badgeWarn}`,
|
|
899
|
+
children: [
|
|
900
|
+
t("reportRotation"),
|
|
901
|
+
": ",
|
|
902
|
+
report.rotation.length
|
|
903
|
+
]
|
|
904
|
+
}),
|
|
905
|
+
report !== null && report.weak.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
906
|
+
className: `${VaultSection_module_css_default.badge} ${VaultSection_module_css_default.badgeDanger}`,
|
|
907
|
+
children: [
|
|
908
|
+
t("reportWeak"),
|
|
909
|
+
": ",
|
|
910
|
+
report.weak.length
|
|
911
|
+
]
|
|
912
|
+
}),
|
|
913
|
+
report !== null && report.reused.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
914
|
+
className: `${VaultSection_module_css_default.badge} ${VaultSection_module_css_default.badgeDanger}`,
|
|
915
|
+
children: [
|
|
916
|
+
t("reportReused"),
|
|
917
|
+
": ",
|
|
918
|
+
report.reused.length
|
|
919
|
+
]
|
|
920
|
+
}),
|
|
921
|
+
report !== null && report.rotation.length === 0 && report.weak.length === 0 && report.reused.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
922
|
+
className: `${VaultSection_module_css_default.badge} ${VaultSection_module_css_default.badgeOk}`,
|
|
923
|
+
children: t("healthOk")
|
|
924
|
+
}),
|
|
925
|
+
backupInfo !== null && backupInfo.daysSinceBackup >= 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
926
|
+
className: `${VaultSection_module_css_default.badge} ${backupInfo.daysSinceBackup > 14 ? VaultSection_module_css_default.badgeDanger : backupInfo.daysSinceBackup > 7 ? VaultSection_module_css_default.badgeWarn : ""}`,
|
|
927
|
+
children: [
|
|
928
|
+
t("healthBackup"),
|
|
929
|
+
": ",
|
|
930
|
+
backupInfo.daysSinceBackup,
|
|
931
|
+
"d"
|
|
932
|
+
]
|
|
933
|
+
})
|
|
934
|
+
]
|
|
935
|
+
}),
|
|
804
936
|
state.status === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
805
937
|
className: VaultSection_module_css_default.footer,
|
|
806
938
|
children: [
|
|
@@ -933,6 +1065,12 @@ window.__ModuleLoader__.load({
|
|
|
933
1065
|
reportRotation: "expired / due for rotation",
|
|
934
1066
|
reportWeak: "weak passwords",
|
|
935
1067
|
reportReused: "credentials reused across entries",
|
|
1068
|
+
healthOk: "vault healthy",
|
|
1069
|
+
healthBackup: "backup",
|
|
1070
|
+
healthStrength: "strength",
|
|
1071
|
+
dueExpired: "EXPIRED",
|
|
1072
|
+
dueExpiring: "expires in",
|
|
1073
|
+
dueNow: "rotation due",
|
|
936
1074
|
showTrash: "Trash",
|
|
937
1075
|
hideTrash: "Hide trash",
|
|
938
1076
|
trashed: "in trash",
|
|
@@ -1016,6 +1154,12 @@ window.__ModuleLoader__.load({
|
|
|
1016
1154
|
reportRotation: "已过期 / 待轮换",
|
|
1017
1155
|
reportWeak: "弱密码",
|
|
1018
1156
|
reportReused: "跨条目复用的凭据",
|
|
1157
|
+
healthOk: "凭据库健康",
|
|
1158
|
+
healthBackup: "备份",
|
|
1159
|
+
healthStrength: "强度",
|
|
1160
|
+
dueExpired: "已过期",
|
|
1161
|
+
dueExpiring: "将于",
|
|
1162
|
+
dueNow: "待轮换",
|
|
1019
1163
|
showTrash: "回收站",
|
|
1020
1164
|
hideTrash: "隐藏回收站",
|
|
1021
1165
|
trashed: "已删除",
|
package/lib/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":["css"],"sources":["../src/client/VaultSection.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Vault settings section: list, search, add, edit, delete, and copy encrypted\n * credentials. All mutations go through the host VaultGateway remote; secrets\n * are shown only inside the edit form or a copy action, never in the list.\n */\n\nimport { useEffect, useId, useMemo, useState, type ReactNode } from 'react'\nimport type { PropsLocale, PropsRuntime, InjectFace, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { VaultLocaleKey } from './locales.ts'\nimport css from './VaultSection.module.css'\n\n/** Wire shapes shared with the host gateway (mirror of src/index.ts types). */\nexport interface VaultSummaryWire {\n id: string\n title: string\n sensitivity?: string\n kind?: string\n username?: string\n email?: string\n phone?: string\n host?: string\n port?: string\n url?: string\n tags?: string[]\n}\n\nexport interface VaultFullWire {\n id: string\n title: string\n kind?: string\n username?: string\n email?: string\n phone?: string\n password?: string\n host?: string\n port?: string\n privateKey?: string\n apiKey?: string\n secret?: string\n accessToken?: string\n refreshToken?: string\n expiresAt?: number\n otpSecret?: string\n url?: string\n notes?: string\n tags?: string[]\n icon?: string\n color?: string\n fields?: Record<string, unknown>\n}\n\n/** Patch shape accepted by vault.add / vault.update. */\nexport type VaultPatch = Partial<Omit<VaultFullWire, 'id'>>\n\n/** Registration-side business face supplied by the plugin entry. */\nexport interface VaultSectionInjected {\n t: TranslateNS<'settings.vault'>\n config: () => Promise<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }>\n setAccessMode: (mode: 'readonly' | 'ask' | 'auto') => Promise<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }>\n setAutoCapture: (enabled: boolean) => Promise<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }>\n list: () => Promise<VaultSummaryWire[]>\n search: (query: string, limit?: number) => Promise<VaultSummaryWire[]>\n get: (id: string) => Promise<{ found: boolean; entry?: VaultFullWire }>\n add: (patch: VaultPatch & { title: string }) => Promise<VaultSummaryWire>\n update: (id: string, patch: VaultPatch) => Promise<{ found: boolean; entry?: VaultSummaryWire }>\n remove: (id: string) => Promise<{ deleted: boolean }>\n trash: () => Promise<VaultSummaryWire[]>\n rotation: () => Promise<unknown[]>\n history: () => Promise<unknown[]>\n stats: () => Promise<Record<string, unknown>>\n recent: () => Promise<unknown[]>\n backupStatus: () => Promise<{ daysSinceBackup: number; backups: number }>\n health: () => Promise<{ weak: unknown[]; reused: unknown[] }>\n restore: (id: string) => Promise<{ restored: boolean }>\n undeleteAll: () => Promise<{ restored: number }>\n totp: (id: string) => Promise<{ code: string; label?: string; secondsRemaining: number }>\n}\n\n/** Type-level alias so consumers can reference the wire shapes without values. */\nexport type VaultSectionTypes = {\n entries: VaultSummaryWire[]\n fullEntry: VaultFullWire\n summaryEntry: VaultSummaryWire\n config: { accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }\n accessModes: Array<'readonly' | 'ask' | 'auto'>\n}\n\n/** Full component props assembled by the Settings slot renderer. */\nexport type VaultSectionProps =\n PropsRuntime<'settings.section'>\n & PropsLocale<'settings.vault'>\n & InjectFace<VaultSectionInjected>\n\n/** The editable form fields we render (subset of the full wire). */\ntype FormFields = Pick<\n VaultFullWire, 'title' | 'kind' | 'username' | 'email' | 'phone' | 'password' | 'host' | 'port'\n | 'privateKey' | 'apiKey' | 'secret' | 'accessToken' | 'refreshToken' | 'otpSecret' | 'url' | 'notes'\n | 'icon' | 'color'\n>\n\nconst FORM_FIELDS: Array<{ key: keyof FormFields; label: VaultLocaleKey }> = [\n { key: 'title', label: 'fieldTitle' },\n { key: 'kind', label: 'fieldKind' },\n { key: 'username', label: 'fieldUsername' },\n { key: 'email', label: 'fieldEmail' },\n { key: 'phone', label: 'fieldPhone' },\n { key: 'password', label: 'fieldPassword' },\n { key: 'host', label: 'fieldHost' },\n { key: 'port', label: 'fieldPort' },\n { key: 'apiKey', label: 'fieldApiKey' },\n { key: 'secret', label: 'fieldSecret' },\n { key: 'accessToken', label: 'fieldAccessToken' },\n { key: 'refreshToken', label: 'fieldRefreshToken' },\n { key: 'otpSecret', label: 'fieldOtpSecret' },\n { key: 'url', label: 'fieldUrl' },\n { key: 'notes', label: 'fieldNotes' },\n { key: 'icon', label: 'fieldIcon' },\n { key: 'color', label: 'fieldColor' },\n]\n\nconst KIND_KEYS: Record<string, VaultLocaleKey> = {\n login: 'kindLogin',\n ssh: 'kindSsh',\n 'api-key': 'kindApiKey',\n secret: 'kindSecret',\n oauth: 'kindOauth',\n custom: 'kindCustom',\n}\n\ntype ViewState =\n | { readonly status: 'loading' }\n | { readonly status: 'error' }\n | { readonly status: 'ready'; readonly entries: VaultSummaryWire[] }\n\ntype EditorState =\n | { readonly status: 'closed' }\n | { readonly status: 'creating' }\n | { readonly status: 'editing'; readonly entry: VaultFullWire }\n\nfunction emptyForm(): FormFields {\n return { title: '', kind: 'login', username: '', email: '', phone: '', password: '', host: '', port: '', icon: '', color: '' }\n}\n\n/** Render the Vault settings section. */\nexport function VaultSection(props: VaultSectionProps): ReactNode {\n const { t, config, setAccessMode, setAutoCapture, list, search, get, add, update, remove, trash, rotation, health, history, stats, backupStatus, recent, restore, undeleteAll, totp } = props\n const searchId = useId()\n const [query, setQuery] = useState('')\n const [state, setState] = useState<ViewState>({ status: 'loading' })\n const [editor, setEditor] = useState<EditorState>({ status: 'closed' })\n const [form, setForm] = useState<FormFields>(emptyForm())\n const [busy, setBusy] = useState(false)\n const [message, setMessage] = useState<string | null>(null)\n const [copiedId, setCopiedId] = useState<string | null>(null)\n const [codeMap, setCodeMap] = useState<Record<string, string>>({})\n const [tagsDraft, setTagsDraft] = useState('')\n const [fieldsDraft, setFieldsDraft] = useState('')\n const [revealed, setRevealed] = useState<Record<string, boolean>>({})\n const [kindFilter, setKindFilter] = useState('')\n const [tagFilter, setTagFilter] = useState('')\n const [expandedId, setExpandedId] = useState<string | null>(null)\n const [sortBy, setSortBy] = useState<'alpha' | 'recent'>('alpha')\n const [policy, setPolicy] = useState<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean } | null>(null)\n const [showTrash, setShowTrash] = useState(false)\n const [trashEntries, setTrashEntries] = useState<VaultSummaryWire[]>([])\n const [report, setReport] = useState<{ rotation: unknown[]; weak: unknown[]; reused: unknown[] } | null>(null)\n const [recentEvents, setRecentEvents] = useState<Array<Record<string, unknown>>>([])\n const [vaultStats, setVaultStats] = useState<Record<string, unknown> | null>(null)\n const [backupInfo, setBackupInfo] = useState<{ daysSinceBackup: number; backups: number } | null>(null)\n const [recentEntries, setRecentEntries] = useState<Array<Record<string, unknown>>>([])\n\n const readonly = policy?.accessMode === 'readonly'\n\n useEffect(() => {\n let current = true\n void config().then(\n value => { if (current) setPolicy(value) },\n () => { /* policy is informational; ignore failures */ },\n )\n return () => { current = false }\n }, [config])\n\n const refresh = useMemo(() => async () => {\n setState({ status: 'loading' })\n try {\n const entries = query.trim().length === 0\n ? await list()\n : await search(query.trim())\n setState({ status: 'ready', entries })\n } catch {\n setState({ status: 'error' })\n }\n }, [list, search, query])\n\n useEffect(() => {\n let current = true\n const timer = window.setTimeout(() => {\n void refresh().then(\n () => { /* state already set inside refresh */ },\n () => { if (current) setState({ status: 'error' }) },\n )\n }, 250)\n return () => { current = false; window.clearTimeout(timer) }\n // refresh is memoized on query (debounced); list/search are stable.\n }, [refresh])\n\n /** Open the editor for a new entry. */\n function startCreate(): void {\n setForm(emptyForm())\n setTagsDraft('')\n setFieldsDraft('')\n setMessage(null)\n setEditor({ status: 'creating' })\n }\n\n /** Open the editor for an existing entry (fetches full secrets). */\n async function startEdit(id: string): Promise<void> {\n setBusy(true)\n setMessage(null)\n try {\n const result = await get(id)\n if (!result.found || result.entry === undefined) {\n setMessage(t('error'))\n return\n }\n const entry = result.entry\n setForm({\n title: entry.title ?? '',\n kind: entry.kind ?? 'login',\n username: entry.username ?? '',\n email: entry.email ?? '',\n phone: entry.phone ?? '',\n password: entry.password ?? '',\n host: entry.host ?? '',\n port: entry.port ?? '',\n privateKey: entry.privateKey ?? '',\n apiKey: entry.apiKey ?? '',\n secret: entry.secret ?? '',\n accessToken: entry.accessToken ?? '',\n refreshToken: entry.refreshToken ?? '',\n otpSecret: entry.otpSecret ?? '',\n url: entry.url ?? '',\n notes: entry.notes ?? '',\n icon: entry.icon ?? '',\n color: entry.color ?? '',\n })\n setTagsDraft((entry.tags ?? []).join(', '))\n setFieldsDraft(entry.fields !== undefined ? Object.entries(entry.fields).map(([k, v]) => `${k}=${String(v)}`).join('\\n') : '')\n setEditor({ status: 'editing', entry })\n } catch {\n setMessage(t('error'))\n } finally {\n setBusy(false)\n }\n }\n\n /** Save the current form (create or update). */\n async function save(): Promise<void> {\n if (!form.title.trim()) {\n setMessage(t('error'))\n return\n }\n setBusy(true)\n setMessage(null)\n try {\n const tags = tagsDraft.split(',').map(x => x.trim()).filter(x => x.length > 0)\n const fields: Record<string, string> = {}\n for (const line of fieldsDraft.split('\\n')) {\n const trimmed = line.trim()\n if (trimmed.length === 0) continue\n const eq = trimmed.indexOf('=')\n if (eq <= 0) continue\n fields[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim()\n }\n const patch: VaultPatch = {\n title: form.title.trim(),\n ...(form.kind !== undefined ? { kind: form.kind } : {}),\n ...(form.username !== undefined ? { username: form.username } : {}),\n ...(form.email !== undefined ? { email: form.email } : {}),\n ...(form.phone !== undefined ? { phone: form.phone } : {}),\n ...(form.password !== undefined ? { password: form.password } : {}),\n ...(form.host !== undefined ? { host: form.host } : {}),\n ...(form.port !== undefined ? { port: form.port } : {}),\n ...(form.privateKey !== undefined ? { privateKey: form.privateKey } : {}),\n ...(form.apiKey !== undefined ? { apiKey: form.apiKey } : {}),\n ...(form.secret !== undefined ? { secret: form.secret } : {}),\n ...(form.accessToken !== undefined ? { accessToken: form.accessToken } : {}),\n ...(form.refreshToken !== undefined ? { refreshToken: form.refreshToken } : {}),\n ...(form.otpSecret !== undefined ? { otpSecret: form.otpSecret } : {}),\n ...(form.url !== undefined ? { url: form.url } : {}),\n ...(form.notes !== undefined ? { notes: form.notes } : {}),\n ...(form.icon !== undefined ? { icon: form.icon } : {}),\n ...(form.color !== undefined ? { color: form.color } : {}),\n // Always send tags: an empty array clears them, and the host store\n // treats empty string values as \"clear this field\" too.\n tags,\n fields,\n }\n if (editor.status === 'creating') {\n await add(patch as VaultPatch & { title: string })\n } else if (editor.status === 'editing') {\n await update(editor.entry.id, patch)\n }\n setEditor({ status: 'closed' })\n await refresh()\n } catch {\n setMessage(t('error'))\n } finally {\n setBusy(false)\n }\n }\n\n /** Delete an entry after confirmation. */\n async function removeEntry(id: string): Promise<void> {\n if (!window.confirm(t('deleteConfirm'))) return\n setBusy(true)\n try {\n await remove(id)\n await refresh()\n } catch {\n setMessage(t('error'))\n } finally {\n setBusy(false)\n }\n }\n\n /** Copy a field value to the clipboard and flash the row. */\n async function copyValue(id: string, value: string): Promise<void> {\n try {\n await navigator.clipboard.writeText(value)\n } catch {\n // Clipboard may be blocked; fall back to a temporary textarea.\n const textarea = document.createElement('textarea')\n textarea.value = value\n document.body.appendChild(textarea)\n textarea.select()\n document.execCommand('copy')\n textarea.remove()\n }\n setCopiedId(id)\n window.setTimeout(() => setCopiedId(null), 1500)\n }\n\n /** Fetch and display a TOTP code for an entry with an otpSecret. */\n async function showTotp(id: string): Promise<void> {\n setBusy(true)\n try {\n const result = await totp(id)\n setCodeMap(previous => ({ ...previous, [id]: `${result.code} (${result.secondsRemaining}s)` }))\n } catch {\n setCodeMap(previous => ({ ...previous, [id]: t('error') }))\n } finally {\n setBusy(false)\n }\n }\n\n /** Field-set summary line for one entry (non-secret). */\n function kindIcon(kind?: string): string {\n switch (kind ?? 'login') {\n case 'ssh': return '🖥️'\n case 'api-key': return '🔑'\n case 'oauth': return '🔐'\n case 'secret': return '🤫'\n case 'custom': return '🧩'\n default: return '👤'\n }\n }\n\n function identityLine(entry: VaultSummaryWire): string {\n const parts = [\n entry.kind !== undefined ? t(KIND_KEYS[entry.kind] ?? 'kindCustom') : t('kindLogin'),\n entry.username,\n entry.email,\n entry.phone,\n entry.host !== undefined ? `${entry.host}${entry.port !== undefined ? `:${entry.port}` : ''}` : undefined,\n entry.url,\n ].filter((value): value is string => value !== undefined && value.length > 0)\n return parts.join(' · ')\n }\n\n return (\n <section className={css.section} aria-labelledby=\"vault-heading\">\n <h2 id=\"vault-heading\">{t('heading')}</h2>\n <p className={css.intro}>{t('intro')}</p>\n\n <div className={css.toolbar}>\n <label className={css.searchBox}>\n <span className=\"sr-only\">{t('searchPlaceholder')}</span>\n <input\n id={searchId}\n type=\"search\"\n placeholder={t('searchPlaceholder')}\n value={query}\n onChange={event => setQuery(event.target.value)}\n />\n {query.length > 0 && (\n <button type=\"button\" className={css.clearButton} onClick={() => setQuery('')} aria-label={t('clearSearch')}>×</button>\n )}\n </label>\n <select className={css.kindFilter} value={kindFilter} onChange={e => setKindFilter(e.target.value)} aria-label={t('fieldKind')}>\n <option value=\"\">{t('allKinds')}</option>\n {Object.entries(KIND_KEYS).map(([value, key]) => (\n <option key={value} value={value}>{t(key)}</option>\n ))}\n </select>\n <select className={css.kindFilter} value={tagFilter} onChange={e => setTagFilter(e.target.value)} aria-label={t('fieldTags')}>\n <option value=\"\">{t('allTags')}</option>\n {[...new Set(state.status === 'ready' ? state.entries.flatMap(e => e.tags ?? []) : [])].sort().map(tag => (\n <option key={tag} value={tag}>{tag}</option>\n ))}\n </select>\n <button\n type=\"button\"\n className={css.sortButton}\n onClick={() => setSortBy(sortBy === 'alpha' ? 'recent' : 'alpha')}\n >{sortBy === 'alpha' ? t('sortAlpha') : t('sortRecent')}</button>\n <button type=\"button\" className={css.addButton} onClick={startCreate} disabled={busy || readonly}>\n + {t('add')}\n </button>\n </div>\n\n {policy !== null && (\n <div className={css.policyBar}>\n <label className={css.policyField}>\n <span>{t('modeLabel')}</span>\n <select\n value={policy.accessMode}\n disabled={busy}\n onChange={event => {\n const next = event.target.value as 'readonly' | 'ask' | 'auto'\n setBusy(true)\n setMessage(null)\n void setAccessMode(next).then(\n value => { setPolicy(value); setBusy(false) },\n () => { setMessage(t('error')); setBusy(false) },\n )\n }}\n >\n <option value=\"readonly\">{t('modeReadonly')}</option>\n <option value=\"ask\">{t('modeAsk')}</option>\n <option value=\"auto\">{t('modeAuto')}</option>\n </select>\n </label>\n <label className={css.policyField}>\n <span>{t('autoCaptureLabel')}</span>\n <input\n type=\"checkbox\"\n checked={policy.autoCapture}\n disabled={busy}\n onChange={event => {\n const next = event.target.checked\n setBusy(true)\n setMessage(null)\n void setAutoCapture(next).then(\n value => { setPolicy(value); setBusy(false) },\n () => { setMessage(t('error')); setBusy(false) },\n )\n }}\n />\n </label>\n <p className={policy.accessMode === 'readonly' ? css.modeReadonly : policy.accessMode === 'ask' ? css.modeAsk : css.modeAuto}>\n {policy.accessMode === 'readonly'\n ? t('modeReadonlyHint')\n : policy.accessMode === 'ask'\n ? t('modeAskHint')\n : t('modeAutoHint')}\n {policy.autoCapture ? ` · ${t('autoCaptureOn')}` : ` · ${t('autoCaptureOff')}`}\n </p>\n </div>\n )}\n\n {message !== null && <p role=\"alert\" className={css.error}>{message}</p>}\n\n {state.status === 'loading' && <p className={css.status}>{t('loading')}</p>}\n {state.status === 'error' && <p role=\"alert\" className={css.error}>{t('error')}</p>}\n {state.status === 'ready' && state.entries.length === 0 && (\n <div className={css.emptyBox}>\n <p className={css.empty}>{t('empty')}</p>\n <p className={css.emptyHint}>{readonly ? t('emptyHintReadonly') : t('emptyHint')}</p>\n {!readonly && (\n <button type=\"button\" className={css.addButton} onClick={startCreate}>{t('quickAdd')}</button>\n )}\n </div>\n )}\n\n {recentEntries.length > 0 && (\n <div className={css.reportBox}>\n <p className={css.reportTitle}>{t('recentlyAdded')}</p>\n {recentEntries.map((e, i) => (\n <p key={i} className={css.reportLine}>{String(e.title ?? '')}</p>\n ))}\n </div>\n )}\n\n {recentEvents.length > 0 && (\n <div className={css.reportBox}>\n <p className={css.reportTitle}>{t('recentActivity')}</p>\n {recentEvents.map((ev, i) => (\n <p key={i} className={css.reportLine}>\n {String(ev.action ?? '')} · {String(ev.title ?? ev.id ?? '')}\n </p>\n ))}\n </div>\n )}\n\n {report !== null && (report.rotation.length > 0 || report.weak.length > 0 || report.reused.length > 0) && (\n <div className={css.reportBox}>\n <p className={css.reportTitle}>{t('reportTitle')}</p>\n {report.rotation.length > 0 && (\n <p className={css.reportLine}>{t('reportRotation')}: {report.rotation.length}</p>\n )}\n {report.weak.length > 0 && (\n <p className={css.reportLine}>{t('reportWeak')}: {report.weak.length}</p>\n )}\n {report.reused.length > 0 && (\n <p className={css.reportLine}>{t('reportReused')}: {report.reused.length}</p>\n )}\n </div>\n )}\n\n {state.status === 'ready' && (state.entries.length > 0 || trashEntries.length > 0) && (\n <button\n type=\"button\"\n className={css.trashButton}\n onClick={() => {\n if (!showTrash) void trash().then(setTrashEntries)\n setShowTrash(!showTrash)\n }}\n >{showTrash ? t('hideTrash') : t('showTrash')}{trashEntries.length > 0 ? ` (${trashEntries.length})` : ''}</button>\n )}\n\n {showTrash && (\n <ul className={css.list}>\n {trashEntries.map(entry => (\n <li key={entry.id} className={css.row}>\n <div className={css.rowMain}>\n <span className={css.title}>{entry.title}</span>\n <span className={css.identity}>{t('trashed')}</span>\n </div>\n <div className={css.rowActions}>\n <button\n type=\"button\"\n onClick={() => { void restore(entry.id).then(() => { void trash().then(setTrashEntries); void refresh() }) }}\n disabled={busy || readonly}\n >{t('restore')}</button>\n </div>\n </li>\n ))}\n {trashEntries.length === 0 && <p className={css.empty}>{t('trashEmpty')}</p>}\n </ul>\n )}\n {showTrash && trashEntries.length > 0 && (\n <>\n <button\n type=\"button\"\n className={css.trashButton}\n onClick={() => {\n setBusy(true)\n void undeleteAll().then(() => {\n void trash().then(setTrashEntries)\n void refresh()\n setBusy(false)\n }, () => setBusy(false))\n }}\n disabled={busy || readonly}\n >{t('restoreAll')}</button>\n <button\n type=\"button\"\n className={css.dangerButton}\n onClick={() => {\n if (!window.confirm(t('clearTrashConfirm'))) return\n setBusy(true)\n void Promise.all(trashEntries.map(e => remove(e.id))).then(() => {\n void trash().then(setTrashEntries)\n void refresh()\n setBusy(false)\n }, () => setBusy(false))\n }}\n disabled={busy || readonly}\n >{t('clearTrash')}</button>\n </>\n )}\n\n {state.status === 'ready' && state.entries.length > 0 && (\n <ul className={css.list}>\n {state.entries.filter(entry => (kindFilter === '' || entry.kind === kindFilter) && (tagFilter === '' || (entry.tags ?? []).includes(tagFilter))).sort((a, b) => sortBy === 'alpha' ? a.title.localeCompare(b.title) : 0).map(entry => {\n const code = codeMap[entry.id]\n return (\n <li key={entry.id} className={css.row}>\n <div className={css.rowMain} onClick={() => setExpandedId(expandedId === entry.id ? null : entry.id)}>\n <span className={css.title}>\n <span className={css.kindIcon}>{kindIcon(entry.kind)}</span>\n {(entry as VaultSummaryWire & { favorite?: boolean }).favorite && (\n <span className={css.pinStar} title={t('pinned')}>★</span>\n )}\n {entry.title}\n {(entry as VaultSummaryWire & { sensitivity?: string }).sensitivity === 'high' && (\n <span className={css.highBadge}>{t('highSensitivity')}</span>\n )}\n </span>\n <span className={css.identity}>{identityLine(entry)}</span>\n {code !== undefined && <span className={css.totp}>{code}</span>}\n </div>\n {expandedId === entry.id && (\n <div className={css.detailBox}>\n {Object.entries(entry).filter(([k]) => !['id', 'title'].includes(k) && entry[k as keyof VaultSummaryWire] !== undefined).map(([k, v]) => (\n <span key={k} className={css.detailItem}>\n <strong>{k}</strong>: {Array.isArray(v) ? v.join(', ') : String(v)}\n </span>\n ))}\n </div>\n )}\n <div className={css.rowActions}>\n {copiedId === entry.id && <span className={css.copied}>{t('copied')}</span>}\n <button\n type=\"button\"\n onClick={() => void copyValue(entry.id, entry.username ?? entry.title)}\n disabled={busy}\n >{t('copy')}</button>\n <button\n type=\"button\"\n onClick={() => {\n setBusy(true)\n void get(entry.id).then(r => {\n if (r.found && r.entry?.password) void copyValue(entry.id, r.entry.password)\n setBusy(false)\n }, () => setBusy(false))\n }}\n disabled={busy || readonly}\n >{t('copyPassword')}</button>\n <button type=\"button\" onClick={() => void showTotp(entry.id)} disabled={busy}>{t('totp')}</button>\n {code !== undefined && (\n <button type=\"button\" onClick={() => void copyValue(entry.id, code.split(' ')[0]!)} disabled={busy}>{t('copyCode')}</button>\n )}\n <button type=\"button\" onClick={() => void startEdit(entry.id)} disabled={busy || readonly}>{t('edit')}</button>\n <button\n type=\"button\"\n className={css.deleteButton}\n onClick={() => void removeEntry(entry.id)}\n disabled={busy || readonly}\n >{t('delete')}</button>\n </div>\n </li>\n )\n })}\n </ul>\n )}\n\n {state.status === 'ready' && (\n <p className={css.footer}>\n {t('entryCount')}: {state.entries.length}\n {vaultStats !== null && typeof vaultStats.withTotp === 'number' && ` · TOTP: ${String(vaultStats.withTotp)}`}\n {vaultStats !== null && typeof vaultStats.highSensitivity === 'number' && ` · ${t('highSensitivity')}: ${String(vaultStats.highSensitivity)}`}\n {backupInfo !== null && backupInfo.daysSinceBackup >= 0 && ` · backup: ${backupInfo.daysSinceBackup}d ago`}\n {vaultStats !== null && typeof vaultStats.byTag === 'object' && vaultStats.byTag !== null\n && Object.keys(vaultStats.byTag as Record<string, unknown>).length > 0\n && ` · tags: ${Object.entries(vaultStats.byTag as Record<string, unknown>).map(([k, v]) => `${k}(${String(v)})`).join(' ')}`}\n </p>\n )}\n\n {editor.status !== 'closed' && (\n <div className={css.editor} role=\"dialog\" aria-label={editor.status === 'creating' ? t('add') : t('edit')}>\n <div className={css.editorBody}>\n {FORM_FIELDS.map(field => (\n <label key={field.key} className={css.field}>\n <span>{t(field.label)}</span>\n {field.key === 'kind' ? (\n <select\n value={form.kind ?? 'login'}\n onChange={event => setForm(previous => ({ ...previous, kind: event.target.value }))}\n >\n {Object.entries(KIND_KEYS).map(([value, key]) => (\n <option key={value} value={value}>{t(key)}</option>\n ))}\n </select>\n ) : field.key === 'title' ? (\n <input\n value={form.title ?? ''}\n onChange={event => setForm(previous => ({ ...previous, title: event.target.value }))}\n />\n ) : field.key === 'password' || field.key === 'otpSecret' || field.key === 'apiKey'\n || field.key === 'secret' || field.key === 'accessToken' || field.key === 'refreshToken'\n || field.key === 'privateKey' ? (\n <span className={css.secretField}>\n <input\n type={revealed[field.key] ? 'text' : 'password'}\n value={(form[field.key] as string | undefined) ?? ''}\n onChange={event => setForm(previous => ({ ...previous, [field.key]: event.target.value }))}\n />\n <button\n type=\"button\"\n className={css.revealButton}\n onClick={() => setRevealed(previous => ({ ...previous, [field.key]: !previous[field.key] }))}\n >{revealed[field.key] ? t('hide') : t('show')}</button>\n </span>\n ) : (\n <input\n type=\"text\"\n value={(form[field.key] as string | undefined) ?? ''}\n onChange={event => setForm(previous => ({ ...previous, [field.key]: event.target.value }))}\n />\n )}\n </label>\n ))}\n <label className={css.field}>\n <span>{t('fieldTags')}</span>\n <input\n value={tagsDraft}\n onChange={event => setTagsDraft(event.target.value)}\n placeholder=\"dev, prod\"\n />\n </label>\n <label className={css.field}>\n <span>{t('fieldCustom')}</span>\n <textarea\n value={fieldsDraft}\n onChange={event => setFieldsDraft(event.target.value)}\n rows={3}\n placeholder=\"region=us-east-1 team=infra\"\n />\n </label>\n </div>\n <div className={css.editorActions}>\n <button type=\"button\" onClick={() => setEditor({ status: 'closed' })} disabled={busy}>{t('cancel')}</button>\n <button type=\"button\" className={css.saveButton} onClick={() => void save()} disabled={busy}>{t('save')}</button>\n </div>\n </div>\n )}\n </section>\n )\n}\n","/** Copy dictionaries for the Vault settings section. */\n\n/** English strings (the key-set source of truth for this pair). */\nexport const en = {\n nav: 'Vault',\n heading: 'Vault',\n intro: 'Encrypted credentials stored on this machine.',\n searchPlaceholder: 'Search title, username, email, host…',\n loading: 'Loading…',\n empty: 'No credentials yet. Add one to get started.',\n emptyHint: 'Tip: you can ask the assistant to save an API key or token you share in chat — with auto-capture on it offers to store it for you. Bulk importing? Ask for vault_import_csv with a CSV file path.',\n emptyHintReadonly: 'The vault is in readonly mode — credentials can be read but not added here. Switch accessMode to \"readwrite\" to enable writes.',\n modeLabel: 'Access mode',\n autoCaptureLabel: 'Auto-capture credentials from chat',\n modeReadonly: 'Read-only',\n modeAsk: 'Ask before writing',\n modeAuto: 'Automatic read-write',\n modeReadonlyHint: 'Read-only — mutations disabled',\n modeAskHint: 'Prompt before writing — every add/update/delete asks you',\n modeAutoHint: 'Automatic read-write — no per-call prompt',\n autoCaptureOn: 'auto-capture on',\n autoCaptureOff: 'auto-capture off',\n clearSearch: 'Clear search',\n entryCount: 'Entries',\n reportTitle: 'Health & rotation',\n recentActivity: 'Recent activity',\n recentlyAdded: 'Recently added',\n reportRotation: 'expired / due for rotation',\n reportWeak: 'weak passwords',\n reportReused: 'credentials reused across entries',\n showTrash: 'Trash',\n hideTrash: 'Hide trash',\n trashed: 'in trash',\n trashEmpty: 'Trash is empty',\n restore: 'Restore',\n clearTrash: 'Empty trash',\n restoreAll: 'Restore all',\n clearTrashConfirm: 'Permanently delete ALL trashed entries? This cannot be undone.',\n show: 'Show',\n hide: 'Hide',\n allKinds: 'All kinds',\n allTags: 'All tags',\n sortAlpha: 'Sort: A–Z',\n sortRecent: 'Sort: recent',\n highSensitivity: 'HIGH',\n pinned: 'Pinned',\n add: 'Add credential',\n quickAdd: 'Quick add',\n cancel: 'Cancel',\n save: 'Save',\n edit: 'Edit',\n delete: 'Delete',\n deleteConfirm: 'Delete this entry? This cannot be undone.',\n copy: 'Copy',\n copyPassword: 'Copy password',\n copyCode: 'Copy code',\n copied: 'Copied',\n totp: 'Code',\n fieldTitle: 'Title',\n fieldKind: 'Kind',\n fieldUsername: 'Username',\n fieldEmail: 'Email',\n fieldPhone: 'Phone',\n fieldPassword: 'Password',\n fieldHost: 'Host',\n fieldPort: 'Port',\n fieldApiKey: 'API key',\n fieldSecret: 'Secret',\n fieldAccessToken: 'Access token',\n fieldRefreshToken: 'Refresh token',\n fieldOtpSecret: 'OTP secret',\n fieldUrl: 'URL',\n fieldNotes: 'Notes',\n fieldTags: 'Tags',\n fieldIcon: 'Icon (emoji)',\n fieldColor: 'Color',\n fieldCustom: 'Custom fields (key=value per line)',\n error: 'Something went wrong.',\n kindLogin: 'Login',\n kindSsh: 'SSH',\n kindApiKey: 'API key',\n kindSecret: 'Secret',\n kindOauth: 'OAuth',\n kindCustom: 'Custom',\n} as const\n\nexport type VaultLocaleKey = keyof typeof en\n\n/** Chinese strings (same keys as English). */\nexport const zh: Record<VaultLocaleKey, string> = {\n nav: '凭据库',\n heading: 'Vault 凭据库',\n intro: '本机加密存储的凭据。',\n searchPlaceholder: '搜索标题、用户名、邮箱、主机…',\n loading: '加载中…',\n empty: '还没有凭据,添加一条开始使用。',\n emptyHint: '提示:你可以在对话中把 API Key 或 token 交给助手保存——开启自动捕获后,它会在你同意时帮你存入本库。批量导入?让助手调用 vault_import_csv 并给出 CSV 路径即可。',\n emptyHintReadonly: '当前为只读模式——只能读取凭据,不能在此添加。将 accessMode 改为 \"readwrite\" 即可启用写入。',\n modeLabel: '访问模式',\n autoCaptureLabel: '自动捕获对话中的凭据',\n modeReadonly: '只读',\n modeAsk: '写入前询问',\n modeAuto: '自动读写',\n modeReadonlyHint: '只读 —— 变更已禁用',\n modeAskHint: '写入前询问 —— 每次增/改/删都会征求你的确认',\n modeAutoHint: '自动读写 —— 无需逐次确认',\n autoCaptureOn: '自动捕获已开启',\n autoCaptureOff: '自动捕获已关闭',\n clearSearch: '清除搜索',\n entryCount: '条目数',\n reportTitle: '健康与轮换',\n recentActivity: '最近活动',\n recentlyAdded: '最近添加',\n reportRotation: '已过期 / 待轮换',\n reportWeak: '弱密码',\n reportReused: '跨条目复用的凭据',\n showTrash: '回收站',\n hideTrash: '隐藏回收站',\n trashed: '已删除',\n trashEmpty: '回收站为空',\n restore: '恢复',\n clearTrash: '清空回收站',\n restoreAll: '全部恢复',\n clearTrashConfirm: '永久删除回收站中全部条目?此操作不可撤销。',\n show: '显示',\n hide: '隐藏',\n allKinds: '全部类型',\n allTags: '全部标签',\n sortAlpha: '排序:A–Z',\n sortRecent: '排序:最近',\n highSensitivity: '高敏',\n pinned: '已置顶',\n add: '新增凭据',\n quickAdd: '快速添加',\n cancel: '取消',\n save: '保存',\n edit: '编辑',\n delete: '删除',\n deleteConfirm: '确定删除该条目?此操作不可撤销。',\n copy: '复制',\n copyPassword: '复制密码',\n copyCode: '复制验证码',\n copied: '已复制',\n totp: '验证码',\n fieldTitle: '标题',\n fieldKind: '类型',\n fieldUsername: '用户名',\n fieldEmail: '邮箱',\n fieldPhone: '手机号',\n fieldPassword: '密码',\n fieldHost: '主机',\n fieldPort: '端口',\n fieldApiKey: 'API 密钥',\n fieldSecret: 'Secret',\n fieldAccessToken: '访问令牌',\n fieldRefreshToken: '刷新令牌',\n fieldOtpSecret: 'TOTP 密钥',\n fieldUrl: 'URL',\n fieldNotes: '备注',\n fieldTags: '标签',\n fieldIcon: '图标(emoji)',\n fieldColor: '颜色',\n fieldCustom: '自定义字段(每行 key=value)',\n error: '操作失败。',\n kindLogin: '登录',\n kindSsh: 'SSH',\n kindApiKey: 'API 密钥',\n kindSecret: 'Secret',\n kindOauth: 'OAuth',\n kindCustom: '自定义',\n}\n","/**\n * dsh-vault browser half: a Vault settings page managing encrypted\n * credentials — list, search, add, edit, delete, and copy — through the host\n * VaultGateway remote over the /api RPC channel.\n *\n * @module dsh-vault/client\n */\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { VaultSection, type VaultSectionInjected } from './VaultSection.tsx'\nimport type { VaultSectionTypes } from './VaultSection.tsx'\nimport { en, zh, type VaultLocaleKey } from './locales.ts'\n\nexport type { VaultSectionInjected, VaultSectionProps } from './VaultSection.tsx'\nexport type { VaultSectionTypes } from './VaultSection.tsx'\nexport type { VaultLocaleKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Vault settings page copy. */\n 'settings.vault': VaultLocaleKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'settings.vault'\n\n/** Services required by the Settings registration and the RPC face. */\nexport const inject = ['slots', 'locale', 'connection']\n\n/**\n * Register the Vault settings section once the `settings.section` declaration\n * is on the ledger. The section talks to the host through the connection RPC\n * channel (endpoints `vault/*` served by the host VaultGateway remote).\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-vault: dictionaries')\n\n const t = ctx.locale.bind(NS)\n const connection = ctx.get('connection') as ConnectionHandle\n\n /** Invoke one host vault remote and normalize the RPC envelope. */\n async function invoke<T>(method: string, args: Record<string, unknown> = {}): Promise<T> {\n const result = await connection.rpc.call('/api', `vault/${method}`, { args })\n if (!result.ok) {\n throw new Error(`vault.${method} failed: ${result.error.code}: ${result.error.message}`)\n }\n return result.value as T\n }\n\n const injected = (): VaultSectionInjected => ({\n t,\n config: () => invoke<VaultSectionTypes['config']>('config'),\n setAccessMode: (mode) => invoke<VaultSectionTypes['config']>('setAccessMode', { mode }),\n setAutoCapture: (enabled) => invoke<VaultSectionTypes['config']>('setAutoCapture', { enabled }),\n list: () => invoke<{ entries: VaultSectionTypes['entries'] }>('list').then(r => r.entries),\n search: (query, limit) => invoke<{ entries: VaultSectionTypes['entries'] }>('search', { query, limit: limit ?? 50 }).then(r => r.entries),\n get: (id) => invoke<{ found: boolean; entry?: VaultSectionTypes['fullEntry'] }>('get', { id }),\n add: (patch) => invoke<VaultSectionTypes['summaryEntry']>('add', { patch }),\n update: (id, patch) => invoke<{ found: boolean; entry?: VaultSectionTypes['summaryEntry'] }>('update', { id, patch }),\n remove: (id) => invoke<{ deleted: boolean }>('delete', { id }),\n trash: () => invoke<{ entries: VaultSectionTypes['entries'] }>('trash').then(r => r.entries),\n rotation: () => invoke<{ entries: unknown[] }>('rotation').then(r => r.entries),\n history: () => invoke<{ events: unknown[] }>('history').then(r => r.events),\n stats: () => invoke<Record<string, unknown>>('stats'),\n recent: () => invoke<{ entries: unknown[] }>('recent').then(r => r.entries),\n backupStatus: () => invoke<{ daysSinceBackup: number; backups: number }>('backupStatus'),\n health: () => invoke<{ weak: unknown[]; reused: unknown[] }>('health'),\n restore: (id) => invoke<{ restored: boolean }>('restore', { id }),\n undeleteAll: () => invoke<{ restored: number }>('undeleteAll'),\n totp: (id) => invoke<{ code: string; label?: string; secondsRemaining: number }>('totp', { id }),\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'vault',\n order: 30,\n label: () => t('nav'),\n inject: injected,\n }, VaultSection))\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client.js","names":["css"],"sources":["../src/client/VaultSection.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * Vault settings section: list, search, add, edit, delete, and copy encrypted\n * credentials. All mutations go through the host VaultGateway remote; secrets\n * are shown only inside the edit form or a copy action, never in the list.\n */\n\nimport { useEffect, useId, useMemo, useState, type ReactNode } from 'react'\nimport type { PropsLocale, PropsRuntime, InjectFace, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots'\nimport type { VaultLocaleKey } from './locales.ts'\nimport css from './VaultSection.module.css'\n\n/** Wire shapes shared with the host gateway (mirror of src/index.ts types). */\nexport interface VaultSummaryWire {\n id: string\n title: string\n sensitivity?: string\n kind?: string\n username?: string\n email?: string\n phone?: string\n host?: string\n port?: string\n url?: string\n tags?: string[]\n}\n\nexport interface VaultFullWire {\n id: string\n title: string\n kind?: string\n username?: string\n email?: string\n phone?: string\n password?: string\n host?: string\n port?: string\n privateKey?: string\n apiKey?: string\n secret?: string\n accessToken?: string\n refreshToken?: string\n expiresAt?: number\n otpSecret?: string\n url?: string\n notes?: string\n tags?: string[]\n icon?: string\n color?: string\n fields?: Record<string, unknown>\n}\n\n/** Patch shape accepted by vault.add / vault.update. */\nexport type VaultPatch = Partial<Omit<VaultFullWire, 'id'>>\n\n/** Registration-side business face supplied by the plugin entry. */\nexport interface VaultSectionInjected {\n t: TranslateNS<'settings.vault'>\n config: () => Promise<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }>\n setAccessMode: (mode: 'readonly' | 'ask' | 'auto') => Promise<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }>\n setAutoCapture: (enabled: boolean) => Promise<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }>\n list: () => Promise<VaultSummaryWire[]>\n search: (query: string, limit?: number) => Promise<VaultSummaryWire[]>\n get: (id: string) => Promise<{ found: boolean; entry?: VaultFullWire }>\n add: (patch: VaultPatch & { title: string }) => Promise<VaultSummaryWire>\n update: (id: string, patch: VaultPatch) => Promise<{ found: boolean; entry?: VaultSummaryWire }>\n remove: (id: string) => Promise<{ deleted: boolean }>\n trash: () => Promise<VaultSummaryWire[]>\n rotation: () => Promise<unknown[]>\n history: () => Promise<unknown[]>\n stats: () => Promise<Record<string, unknown>>\n recent: () => Promise<unknown[]>\n backupStatus: () => Promise<{ daysSinceBackup: number; backups: number }>\n health: () => Promise<{ weak: unknown[]; reused: unknown[]; strength: { weak: number; fair: number; strong: number } }>\n restore: (id: string) => Promise<{ restored: boolean }>\n undeleteAll: () => Promise<{ restored: number }>\n totp: (id: string) => Promise<{ code: string; label?: string; secondsRemaining: number }>\n}\n\n/** Type-level alias so consumers can reference the wire shapes without values. */\nexport type VaultSectionTypes = {\n entries: VaultSummaryWire[]\n fullEntry: VaultFullWire\n summaryEntry: VaultSummaryWire\n config: { accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean }\n accessModes: Array<'readonly' | 'ask' | 'auto'>\n}\n\n/** Full component props assembled by the Settings slot renderer. */\nexport type VaultSectionProps =\n PropsRuntime<'settings.section'>\n & PropsLocale<'settings.vault'>\n & InjectFace<VaultSectionInjected>\n\n/** The editable form fields we render (subset of the full wire). */\ntype FormFields = Pick<\n VaultFullWire, 'title' | 'kind' | 'username' | 'email' | 'phone' | 'password' | 'host' | 'port'\n | 'privateKey' | 'apiKey' | 'secret' | 'accessToken' | 'refreshToken' | 'otpSecret' | 'url' | 'notes'\n | 'icon' | 'color'\n>\n\nconst FORM_FIELDS: Array<{ key: keyof FormFields; label: VaultLocaleKey }> = [\n { key: 'title', label: 'fieldTitle' },\n { key: 'kind', label: 'fieldKind' },\n { key: 'username', label: 'fieldUsername' },\n { key: 'email', label: 'fieldEmail' },\n { key: 'phone', label: 'fieldPhone' },\n { key: 'password', label: 'fieldPassword' },\n { key: 'host', label: 'fieldHost' },\n { key: 'port', label: 'fieldPort' },\n { key: 'apiKey', label: 'fieldApiKey' },\n { key: 'secret', label: 'fieldSecret' },\n { key: 'accessToken', label: 'fieldAccessToken' },\n { key: 'refreshToken', label: 'fieldRefreshToken' },\n { key: 'otpSecret', label: 'fieldOtpSecret' },\n { key: 'url', label: 'fieldUrl' },\n { key: 'notes', label: 'fieldNotes' },\n { key: 'icon', label: 'fieldIcon' },\n { key: 'color', label: 'fieldColor' },\n]\n\nconst KIND_KEYS: Record<string, VaultLocaleKey> = {\n login: 'kindLogin',\n ssh: 'kindSsh',\n 'api-key': 'kindApiKey',\n secret: 'kindSecret',\n oauth: 'kindOauth',\n custom: 'kindCustom',\n}\n\ntype ViewState =\n | { readonly status: 'loading' }\n | { readonly status: 'error' }\n | { readonly status: 'ready'; readonly entries: VaultSummaryWire[] }\n\ntype EditorState =\n | { readonly status: 'closed' }\n | { readonly status: 'creating' }\n | { readonly status: 'editing'; readonly entry: VaultFullWire }\n\nfunction emptyForm(): FormFields {\n return { title: '', kind: 'login', username: '', email: '', phone: '', password: '', host: '', port: '', icon: '', color: '' }\n}\n\n/** Render the Vault settings section. */\nexport function VaultSection(props: VaultSectionProps): ReactNode {\n const { t, config, setAccessMode, setAutoCapture, list, search, get, add, update, remove, trash, rotation, health, history, stats, backupStatus, recent, restore, undeleteAll, totp } = props\n const searchId = useId()\n const [query, setQuery] = useState('')\n const [state, setState] = useState<ViewState>({ status: 'loading' })\n const [editor, setEditor] = useState<EditorState>({ status: 'closed' })\n const [form, setForm] = useState<FormFields>(emptyForm())\n const [busy, setBusy] = useState(false)\n const [message, setMessage] = useState<string | null>(null)\n const [copiedId, setCopiedId] = useState<string | null>(null)\n const [codeMap, setCodeMap] = useState<Record<string, string>>({})\n const [tagsDraft, setTagsDraft] = useState('')\n const [fieldsDraft, setFieldsDraft] = useState('')\n const [revealed, setRevealed] = useState<Record<string, boolean>>({})\n const [kindFilter, setKindFilter] = useState('')\n const [tagFilter, setTagFilter] = useState('')\n const [expandedId, setExpandedId] = useState<string | null>(null)\n const [sortBy, setSortBy] = useState<'alpha' | 'recent'>('alpha')\n const [policy, setPolicy] = useState<{ accessMode: 'readonly' | 'ask' | 'auto'; autoCapture: boolean } | null>(null)\n const [showTrash, setShowTrash] = useState(false)\n const [trashEntries, setTrashEntries] = useState<VaultSummaryWire[]>([])\n const [report, setReport] = useState<{ rotation: unknown[]; weak: unknown[]; reused: unknown[]; strength: { weak: number; fair: number; strong: number } | null } | null>(null)\n const [dueMap, setDueMap] = useState<Record<string, { due: string; daysLeft: number }>>({})\n const [recentEvents, setRecentEvents] = useState<Array<Record<string, unknown>>>([])\n const [vaultStats, setVaultStats] = useState<Record<string, unknown> | null>(null)\n const [backupInfo, setBackupInfo] = useState<{ daysSinceBackup: number; backups: number } | null>(null)\n const [recentEntries, setRecentEntries] = useState<Array<Record<string, unknown>>>([])\n\n const readonly = policy?.accessMode === 'readonly'\n\n useEffect(() => {\n let current = true\n void config().then(\n value => { if (current) setPolicy(value) },\n () => { /* policy is informational; ignore failures */ },\n )\n return () => { current = false }\n }, [config])\n\n const refresh = useMemo(() => async () => {\n setState({ status: 'loading' })\n try {\n const entries = query.trim().length === 0\n ? await list()\n : await search(query.trim())\n setState({ status: 'ready', entries })\n } catch {\n setState({ status: 'error' })\n }\n }, [list, search, query])\n\n useEffect(() => {\n let current = true\n const timer = window.setTimeout(() => {\n void refresh().then(\n () => { /* state already set inside refresh */ },\n () => { if (current) setState({ status: 'error' }) },\n )\n }, 250)\n return () => { current = false; window.clearTimeout(timer) }\n // refresh is memoized on query (debounced); list/search are stable.\n }, [refresh])\n\n // Vault health & meta: load once on mount (stats, backup age, rotation,\n // weak/reused scan, recent activity) and refresh on window focus.\n useEffect(() => {\n let current = true\n const load = (): void => {\n void Promise.all([\n stats().catch(() => null),\n backupStatus().catch(() => null),\n rotation().catch(() => []),\n health().catch(() => null),\n recent().catch(() => []),\n ]).then(([st, bk, rot, hl, rc]) => {\n if (!current) return\n if (st !== null) setVaultStats(st as Record<string, unknown>)\n if (bk !== null) setBackupInfo(bk)\n setReport({ rotation: (rot ?? []) as unknown[], weak: ((hl?.weak ?? []) as unknown[]), reused: ((hl?.reused ?? []) as unknown[]), strength: (hl?.strength ?? null) as { weak: number; fair: number; strong: number } | null })\n const due: Record<string, { due: string; daysLeft: number }> = {}\n for (const item of (rot ?? []) as Array<{ id?: string; due?: string; daysLeft?: number }>) {\n if (item.id !== undefined && item.due !== undefined) due[item.id] = { due: item.due, daysLeft: item.daysLeft ?? 0 }\n }\n setDueMap(due)\n setRecentEntries((rc ?? []) as Array<Record<string, unknown>>)\n })\n }\n load()\n const onFocus = (): void => { load() }\n window.addEventListener('focus', onFocus)\n return () => { current = false; window.removeEventListener('focus', onFocus) }\n }, [stats, backupStatus, rotation, health, recent])\n\n /** Open the editor for a new entry. */\n function startCreate(): void {\n setForm(emptyForm())\n setTagsDraft('')\n setFieldsDraft('')\n setMessage(null)\n setEditor({ status: 'creating' })\n }\n\n /** Open the editor for an existing entry (fetches full secrets). */\n async function startEdit(id: string): Promise<void> {\n setBusy(true)\n setMessage(null)\n try {\n const result = await get(id)\n if (!result.found || result.entry === undefined) {\n setMessage(t('error'))\n return\n }\n const entry = result.entry\n setForm({\n title: entry.title ?? '',\n kind: entry.kind ?? 'login',\n username: entry.username ?? '',\n email: entry.email ?? '',\n phone: entry.phone ?? '',\n password: entry.password ?? '',\n host: entry.host ?? '',\n port: entry.port ?? '',\n privateKey: entry.privateKey ?? '',\n apiKey: entry.apiKey ?? '',\n secret: entry.secret ?? '',\n accessToken: entry.accessToken ?? '',\n refreshToken: entry.refreshToken ?? '',\n otpSecret: entry.otpSecret ?? '',\n url: entry.url ?? '',\n notes: entry.notes ?? '',\n icon: entry.icon ?? '',\n color: entry.color ?? '',\n })\n setTagsDraft((entry.tags ?? []).join(', '))\n setFieldsDraft(entry.fields !== undefined ? Object.entries(entry.fields).map(([k, v]) => `${k}=${String(v)}`).join('\\n') : '')\n setEditor({ status: 'editing', entry })\n } catch {\n setMessage(t('error'))\n } finally {\n setBusy(false)\n }\n }\n\n /** Save the current form (create or update). */\n async function save(): Promise<void> {\n if (!form.title.trim()) {\n setMessage(t('error'))\n return\n }\n setBusy(true)\n setMessage(null)\n try {\n const tags = tagsDraft.split(',').map(x => x.trim()).filter(x => x.length > 0)\n const fields: Record<string, string> = {}\n for (const line of fieldsDraft.split('\\n')) {\n const trimmed = line.trim()\n if (trimmed.length === 0) continue\n const eq = trimmed.indexOf('=')\n if (eq <= 0) continue\n fields[trimmed.slice(0, eq).trim()] = trimmed.slice(eq + 1).trim()\n }\n const patch: VaultPatch = {\n title: form.title.trim(),\n ...(form.kind !== undefined ? { kind: form.kind } : {}),\n ...(form.username !== undefined ? { username: form.username } : {}),\n ...(form.email !== undefined ? { email: form.email } : {}),\n ...(form.phone !== undefined ? { phone: form.phone } : {}),\n ...(form.password !== undefined ? { password: form.password } : {}),\n ...(form.host !== undefined ? { host: form.host } : {}),\n ...(form.port !== undefined ? { port: form.port } : {}),\n ...(form.privateKey !== undefined ? { privateKey: form.privateKey } : {}),\n ...(form.apiKey !== undefined ? { apiKey: form.apiKey } : {}),\n ...(form.secret !== undefined ? { secret: form.secret } : {}),\n ...(form.accessToken !== undefined ? { accessToken: form.accessToken } : {}),\n ...(form.refreshToken !== undefined ? { refreshToken: form.refreshToken } : {}),\n ...(form.otpSecret !== undefined ? { otpSecret: form.otpSecret } : {}),\n ...(form.url !== undefined ? { url: form.url } : {}),\n ...(form.notes !== undefined ? { notes: form.notes } : {}),\n ...(form.icon !== undefined ? { icon: form.icon } : {}),\n ...(form.color !== undefined ? { color: form.color } : {}),\n // Always send tags: an empty array clears them, and the host store\n // treats empty string values as \"clear this field\" too.\n tags,\n fields,\n }\n if (editor.status === 'creating') {\n await add(patch as VaultPatch & { title: string })\n } else if (editor.status === 'editing') {\n await update(editor.entry.id, patch)\n }\n setEditor({ status: 'closed' })\n await refresh()\n } catch {\n setMessage(t('error'))\n } finally {\n setBusy(false)\n }\n }\n\n /** Delete an entry after confirmation. */\n async function removeEntry(id: string): Promise<void> {\n if (!window.confirm(t('deleteConfirm'))) return\n setBusy(true)\n try {\n await remove(id)\n await refresh()\n } catch {\n setMessage(t('error'))\n } finally {\n setBusy(false)\n }\n }\n\n /** Copy a field value to the clipboard and flash the row. */\n async function copyValue(id: string, value: string): Promise<void> {\n try {\n await navigator.clipboard.writeText(value)\n } catch {\n // Clipboard may be blocked; fall back to a temporary textarea.\n const textarea = document.createElement('textarea')\n textarea.value = value\n document.body.appendChild(textarea)\n textarea.select()\n document.execCommand('copy')\n textarea.remove()\n }\n setCopiedId(id)\n window.setTimeout(() => setCopiedId(null), 1500)\n }\n\n /** Fetch and display a TOTP code for an entry with an otpSecret. */\n async function showTotp(id: string): Promise<void> {\n setBusy(true)\n try {\n const result = await totp(id)\n setCodeMap(previous => ({ ...previous, [id]: `${result.code} (${result.secondsRemaining}s)` }))\n } catch {\n setCodeMap(previous => ({ ...previous, [id]: t('error') }))\n } finally {\n setBusy(false)\n }\n }\n\n /** Field-set summary line for one entry (non-secret). */\n function kindIcon(kind?: string): string {\n switch (kind ?? 'login') {\n case 'ssh': return '🖥️'\n case 'api-key': return '🔑'\n case 'oauth': return '🔐'\n case 'secret': return '🤫'\n case 'custom': return '🧩'\n default: return '👤'\n }\n }\n\n function identityLine(entry: VaultSummaryWire): string {\n const parts = [\n entry.kind !== undefined ? t(KIND_KEYS[entry.kind] ?? 'kindCustom') : t('kindLogin'),\n entry.username,\n entry.email,\n entry.phone,\n entry.host !== undefined ? `${entry.host}${entry.port !== undefined ? `:${entry.port}` : ''}` : undefined,\n entry.url,\n ].filter((value): value is string => value !== undefined && value.length > 0)\n return parts.join(' · ')\n }\n\n return (\n <section className={css.section} aria-labelledby=\"vault-heading\">\n <h2 id=\"vault-heading\">{t('heading')}</h2>\n <p className={css.intro}>{t('intro')}</p>\n\n <div className={css.toolbar}>\n <label className={css.searchBox}>\n <span className=\"sr-only\">{t('searchPlaceholder')}</span>\n <input\n id={searchId}\n type=\"search\"\n placeholder={t('searchPlaceholder')}\n value={query}\n onChange={event => setQuery(event.target.value)}\n />\n {query.length > 0 && (\n <button type=\"button\" className={css.clearButton} onClick={() => setQuery('')} aria-label={t('clearSearch')}>×</button>\n )}\n </label>\n <select className={css.kindFilter} value={kindFilter} onChange={e => setKindFilter(e.target.value)} aria-label={t('fieldKind')}>\n <option value=\"\">{t('allKinds')}</option>\n {Object.entries(KIND_KEYS).map(([value, key]) => (\n <option key={value} value={value}>{t(key)}</option>\n ))}\n </select>\n <select className={css.kindFilter} value={tagFilter} onChange={e => setTagFilter(e.target.value)} aria-label={t('fieldTags')}>\n <option value=\"\">{t('allTags')}</option>\n {[...new Set(state.status === 'ready' ? state.entries.flatMap(e => e.tags ?? []) : [])].sort().map(tag => (\n <option key={tag} value={tag}>{tag}</option>\n ))}\n </select>\n <button\n type=\"button\"\n className={css.sortButton}\n onClick={() => setSortBy(sortBy === 'alpha' ? 'recent' : 'alpha')}\n >{sortBy === 'alpha' ? t('sortAlpha') : t('sortRecent')}</button>\n <button type=\"button\" className={css.addButton} onClick={startCreate} disabled={busy || readonly}>\n + {t('add')}\n </button>\n </div>\n\n {policy !== null && (\n <div className={css.policyBar}>\n <label className={css.policyField}>\n <span>{t('modeLabel')}</span>\n <select\n value={policy.accessMode}\n disabled={busy}\n onChange={event => {\n const next = event.target.value as 'readonly' | 'ask' | 'auto'\n setBusy(true)\n setMessage(null)\n void setAccessMode(next).then(\n value => { setPolicy(value); setBusy(false) },\n () => { setMessage(t('error')); setBusy(false) },\n )\n }}\n >\n <option value=\"readonly\">{t('modeReadonly')}</option>\n <option value=\"ask\">{t('modeAsk')}</option>\n <option value=\"auto\">{t('modeAuto')}</option>\n </select>\n </label>\n <label className={css.policyField}>\n <span>{t('autoCaptureLabel')}</span>\n <input\n type=\"checkbox\"\n checked={policy.autoCapture}\n disabled={busy}\n onChange={event => {\n const next = event.target.checked\n setBusy(true)\n setMessage(null)\n void setAutoCapture(next).then(\n value => { setPolicy(value); setBusy(false) },\n () => { setMessage(t('error')); setBusy(false) },\n )\n }}\n />\n </label>\n <p className={policy.accessMode === 'readonly' ? css.modeReadonly : policy.accessMode === 'ask' ? css.modeAsk : css.modeAuto}>\n {policy.accessMode === 'readonly'\n ? t('modeReadonlyHint')\n : policy.accessMode === 'ask'\n ? t('modeAskHint')\n : t('modeAutoHint')}\n {policy.autoCapture ? ` · ${t('autoCaptureOn')}` : ` · ${t('autoCaptureOff')}`}\n </p>\n </div>\n )}\n\n {message !== null && <p role=\"alert\" className={css.error}>{message}</p>}\n\n {state.status === 'loading' && <p className={css.status}>{t('loading')}</p>}\n {state.status === 'error' && <p role=\"alert\" className={css.error}>{t('error')}</p>}\n {state.status === 'ready' && state.entries.length === 0 && (\n <div className={css.emptyBox}>\n <p className={css.empty}>{t('empty')}</p>\n <p className={css.emptyHint}>{readonly ? t('emptyHintReadonly') : t('emptyHint')}</p>\n {!readonly && (\n <button type=\"button\" className={css.addButton} onClick={startCreate}>{t('quickAdd')}</button>\n )}\n </div>\n )}\n\n {recentEntries.length > 0 && (\n <div className={css.reportBox}>\n <p className={css.reportTitle}>{t('recentlyAdded')}</p>\n {recentEntries.map((e, i) => (\n <p key={i} className={css.reportLine}>{String(e.title ?? '')}</p>\n ))}\n </div>\n )}\n\n {recentEvents.length > 0 && (\n <div className={css.reportBox}>\n <p className={css.reportTitle}>{t('recentActivity')}</p>\n {recentEvents.map((ev, i) => (\n <p key={i} className={css.reportLine}>\n {String(ev.action ?? '')} · {String(ev.title ?? ev.id ?? '')}\n </p>\n ))}\n </div>\n )}\n\n {report !== null && (report.rotation.length > 0 || report.weak.length > 0 || report.reused.length > 0) && (\n <div className={css.reportBox}>\n <p className={css.reportTitle}>{t('reportTitle')}</p>\n {report.rotation.length > 0 && (\n <p className={css.reportLine}>{t('reportRotation')}: {report.rotation.length}</p>\n )}\n {report.weak.length > 0 && (\n <p className={css.reportLine}>{t('reportWeak')}: {report.weak.length}</p>\n )}\n {report.reused.length > 0 && (\n <p className={css.reportLine}>{t('reportReused')}: {report.reused.length}</p>\n )}\n </div>\n )}\n\n {state.status === 'ready' && (state.entries.length > 0 || trashEntries.length > 0) && (\n <button\n type=\"button\"\n className={css.trashButton}\n onClick={() => {\n if (!showTrash) void trash().then(setTrashEntries)\n setShowTrash(!showTrash)\n }}\n >{showTrash ? t('hideTrash') : t('showTrash')}{trashEntries.length > 0 ? ` (${trashEntries.length})` : ''}</button>\n )}\n\n {showTrash && (\n <ul className={css.list}>\n {trashEntries.map(entry => (\n <li key={entry.id} className={css.row}>\n <div className={css.rowMain}>\n <span className={css.title}>{entry.title}</span>\n <span className={css.identity}>{t('trashed')}</span>\n </div>\n <div className={css.rowActions}>\n <button\n type=\"button\"\n onClick={() => { void restore(entry.id).then(() => { void trash().then(setTrashEntries); void refresh() }) }}\n disabled={busy || readonly}\n >{t('restore')}</button>\n </div>\n </li>\n ))}\n {trashEntries.length === 0 && <p className={css.empty}>{t('trashEmpty')}</p>}\n </ul>\n )}\n {showTrash && trashEntries.length > 0 && (\n <>\n <button\n type=\"button\"\n className={css.trashButton}\n onClick={() => {\n setBusy(true)\n void undeleteAll().then(() => {\n void trash().then(setTrashEntries)\n void refresh()\n setBusy(false)\n }, () => setBusy(false))\n }}\n disabled={busy || readonly}\n >{t('restoreAll')}</button>\n <button\n type=\"button\"\n className={css.dangerButton}\n onClick={() => {\n if (!window.confirm(t('clearTrashConfirm'))) return\n setBusy(true)\n void Promise.all(trashEntries.map(e => remove(e.id))).then(() => {\n void trash().then(setTrashEntries)\n void refresh()\n setBusy(false)\n }, () => setBusy(false))\n }}\n disabled={busy || readonly}\n >{t('clearTrash')}</button>\n </>\n )}\n\n {state.status === 'ready' && state.entries.length > 0 && (\n <ul className={css.list}>\n {state.entries.filter(entry => (kindFilter === '' || entry.kind === kindFilter) && (tagFilter === '' || (entry.tags ?? []).includes(tagFilter))).sort((a, b) => sortBy === 'alpha' ? a.title.localeCompare(b.title) : 0).map(entry => {\n const code = codeMap[entry.id]\n return (\n <li key={entry.id} className={`${css.row}${dueMap[entry.id] !== undefined ? ` ${dueMap[entry.id]!.due === 'expired' ? css.rowExpired : css.rowDue}` : ''}`}>\n <div className={css.rowMain} onClick={() => setExpandedId(expandedId === entry.id ? null : entry.id)}>\n <span className={css.title}>\n <span className={css.kindIcon}>{kindIcon(entry.kind)}</span>\n {(entry as VaultSummaryWire & { favorite?: boolean }).favorite && (\n <span className={css.pinStar} title={t('pinned')}>★</span>\n )}\n {entry.title}\n {(entry as VaultSummaryWire & { sensitivity?: string }).sensitivity === 'high' && (\n <span className={css.highBadge}>{t('highSensitivity')}</span>\n )}\n {dueMap[entry.id] !== undefined && (\n <span className={`${css.dueBadge} ${dueMap[entry.id]!.due === 'expired' ? css.badgeDanger : css.badgeWarn}`}>\n {dueMap[entry.id]!.due === 'expired' ? t('dueExpired') : dueMap[entry.id]!.due === 'soon' ? `${t('dueExpiring')} ${dueMap[entry.id]!.daysLeft}d` : t('dueNow')}\n </span>\n )}\n </span>\n <span className={css.identity}>{identityLine(entry)}</span>\n {code !== undefined && <span className={css.totp}>{code}</span>}\n </div>\n {expandedId === entry.id && (\n <div className={css.detailBox}>\n {Object.entries(entry).filter(([k]) => !['id', 'title'].includes(k) && entry[k as keyof VaultSummaryWire] !== undefined).map(([k, v]) => (\n <span key={k} className={css.detailItem}>\n <strong>{k}</strong>: {Array.isArray(v) ? v.join(', ') : String(v)}\n </span>\n ))}\n </div>\n )}\n <div className={css.rowActions}>\n {copiedId === entry.id && <span className={css.copied}>{t('copied')}</span>}\n <button\n type=\"button\"\n onClick={() => void copyValue(entry.id, entry.username ?? entry.title)}\n disabled={busy}\n >{t('copy')}</button>\n <button\n type=\"button\"\n onClick={() => {\n setBusy(true)\n void get(entry.id).then(r => {\n if (r.found && r.entry?.password) void copyValue(entry.id, r.entry.password)\n setBusy(false)\n }, () => setBusy(false))\n }}\n disabled={busy || readonly}\n >{t('copyPassword')}</button>\n <button type=\"button\" onClick={() => void showTotp(entry.id)} disabled={busy}>{t('totp')}</button>\n {code !== undefined && (\n <button type=\"button\" onClick={() => void copyValue(entry.id, code.split(' ')[0]!)} disabled={busy}>{t('copyCode')}</button>\n )}\n <button type=\"button\" onClick={() => void startEdit(entry.id)} disabled={busy || readonly}>{t('edit')}</button>\n <button\n type=\"button\"\n className={css.deleteButton}\n onClick={() => void removeEntry(entry.id)}\n disabled={busy || readonly}\n >{t('delete')}</button>\n </div>\n </li>\n )\n })}\n </ul>\n )}\n\n {state.status === 'ready' && (\n <div className={css.healthBar}>\n {vaultStats !== null && typeof vaultStats.total === 'number' && (\n <span className={css.badge}>{t('entryCount')}: {String(vaultStats.total)}</span>\n )}\n {vaultStats !== null && typeof vaultStats.withTotp === 'number' && (\n <span className={css.badge}>TOTP: {String(vaultStats.withTotp)}</span>\n )}\n {vaultStats !== null && typeof vaultStats.highSensitivity === 'number' && (\n <span className={css.badge}>{t('highSensitivity')}: {String(vaultStats.highSensitivity)}</span>\n )}\n {report !== null && report.strength !== null && (\n <span className={`${css.badge} ${report.strength.weak > 0 ? css.badgeDanger : css.badgeOk}`}>\n {t('healthStrength')}: {report.strength.weak}W/{report.strength.fair}F/{report.strength.strong}S\n </span>\n )}\n {report !== null && report.rotation.length > 0 && (\n <span className={`${css.badge} ${css.badgeWarn}`}>{t('reportRotation')}: {report.rotation.length}</span>\n )}\n {report !== null && report.weak.length > 0 && (\n <span className={`${css.badge} ${css.badgeDanger}`}>{t('reportWeak')}: {report.weak.length}</span>\n )}\n {report !== null && report.reused.length > 0 && (\n <span className={`${css.badge} ${css.badgeDanger}`}>{t('reportReused')}: {report.reused.length}</span>\n )}\n {report !== null && report.rotation.length === 0 && report.weak.length === 0 && report.reused.length === 0 && (\n <span className={`${css.badge} ${css.badgeOk}`}>{t('healthOk')}</span>\n )}\n {backupInfo !== null && backupInfo.daysSinceBackup >= 0 && (\n <span className={`${css.badge} ${backupInfo.daysSinceBackup > 14 ? css.badgeDanger : backupInfo.daysSinceBackup > 7 ? css.badgeWarn : ''}`}>\n {t('healthBackup')}: {backupInfo.daysSinceBackup}d\n </span>\n )}\n </div>\n )}\n {state.status === 'ready' && (\n <p className={css.footer}>\n {t('entryCount')}: {state.entries.length}\n {vaultStats !== null && typeof vaultStats.withTotp === 'number' && ` · TOTP: ${String(vaultStats.withTotp)}`}\n {vaultStats !== null && typeof vaultStats.highSensitivity === 'number' && ` · ${t('highSensitivity')}: ${String(vaultStats.highSensitivity)}`}\n {backupInfo !== null && backupInfo.daysSinceBackup >= 0 && ` · backup: ${backupInfo.daysSinceBackup}d ago`}\n {vaultStats !== null && typeof vaultStats.byTag === 'object' && vaultStats.byTag !== null\n && Object.keys(vaultStats.byTag as Record<string, unknown>).length > 0\n && ` · tags: ${Object.entries(vaultStats.byTag as Record<string, unknown>).map(([k, v]) => `${k}(${String(v)})`).join(' ')}`}\n </p>\n )}\n\n {editor.status !== 'closed' && (\n <div className={css.editor} role=\"dialog\" aria-label={editor.status === 'creating' ? t('add') : t('edit')}>\n <div className={css.editorBody}>\n {FORM_FIELDS.map(field => (\n <label key={field.key} className={css.field}>\n <span>{t(field.label)}</span>\n {field.key === 'kind' ? (\n <select\n value={form.kind ?? 'login'}\n onChange={event => setForm(previous => ({ ...previous, kind: event.target.value }))}\n >\n {Object.entries(KIND_KEYS).map(([value, key]) => (\n <option key={value} value={value}>{t(key)}</option>\n ))}\n </select>\n ) : field.key === 'title' ? (\n <input\n value={form.title ?? ''}\n onChange={event => setForm(previous => ({ ...previous, title: event.target.value }))}\n />\n ) : field.key === 'password' || field.key === 'otpSecret' || field.key === 'apiKey'\n || field.key === 'secret' || field.key === 'accessToken' || field.key === 'refreshToken'\n || field.key === 'privateKey' ? (\n <span className={css.secretField}>\n <input\n type={revealed[field.key] ? 'text' : 'password'}\n value={(form[field.key] as string | undefined) ?? ''}\n onChange={event => setForm(previous => ({ ...previous, [field.key]: event.target.value }))}\n />\n <button\n type=\"button\"\n className={css.revealButton}\n onClick={() => setRevealed(previous => ({ ...previous, [field.key]: !previous[field.key] }))}\n >{revealed[field.key] ? t('hide') : t('show')}</button>\n </span>\n ) : (\n <input\n type=\"text\"\n value={(form[field.key] as string | undefined) ?? ''}\n onChange={event => setForm(previous => ({ ...previous, [field.key]: event.target.value }))}\n />\n )}\n </label>\n ))}\n <label className={css.field}>\n <span>{t('fieldTags')}</span>\n <input\n value={tagsDraft}\n onChange={event => setTagsDraft(event.target.value)}\n placeholder=\"dev, prod\"\n />\n </label>\n <label className={css.field}>\n <span>{t('fieldCustom')}</span>\n <textarea\n value={fieldsDraft}\n onChange={event => setFieldsDraft(event.target.value)}\n rows={3}\n placeholder=\"region=us-east-1 team=infra\"\n />\n </label>\n </div>\n <div className={css.editorActions}>\n <button type=\"button\" onClick={() => setEditor({ status: 'closed' })} disabled={busy}>{t('cancel')}</button>\n <button type=\"button\" className={css.saveButton} onClick={() => void save()} disabled={busy}>{t('save')}</button>\n </div>\n </div>\n )}\n </section>\n )\n}\n","/** Copy dictionaries for the Vault settings section. */\n\n/** English strings (the key-set source of truth for this pair). */\nexport const en = {\n nav: 'Vault',\n heading: 'Vault',\n intro: 'Encrypted credentials stored on this machine.',\n searchPlaceholder: 'Search title, username, email, host…',\n loading: 'Loading…',\n empty: 'No credentials yet. Add one to get started.',\n emptyHint: 'Tip: you can ask the assistant to save an API key or token you share in chat — with auto-capture on it offers to store it for you. Bulk importing? Ask for vault_import_csv with a CSV file path.',\n emptyHintReadonly: 'The vault is in readonly mode — credentials can be read but not added here. Switch accessMode to \"readwrite\" to enable writes.',\n modeLabel: 'Access mode',\n autoCaptureLabel: 'Auto-capture credentials from chat',\n modeReadonly: 'Read-only',\n modeAsk: 'Ask before writing',\n modeAuto: 'Automatic read-write',\n modeReadonlyHint: 'Read-only — mutations disabled',\n modeAskHint: 'Prompt before writing — every add/update/delete asks you',\n modeAutoHint: 'Automatic read-write — no per-call prompt',\n autoCaptureOn: 'auto-capture on',\n autoCaptureOff: 'auto-capture off',\n clearSearch: 'Clear search',\n entryCount: 'Entries',\n reportTitle: 'Health & rotation',\n recentActivity: 'Recent activity',\n recentlyAdded: 'Recently added',\n reportRotation: 'expired / due for rotation',\n reportWeak: 'weak passwords',\n reportReused: 'credentials reused across entries',\n healthOk: 'vault healthy',\n healthBackup: 'backup',\n healthStrength: 'strength',\n dueExpired: 'EXPIRED',\n dueExpiring: 'expires in',\n dueNow: 'rotation due',\n showTrash: 'Trash',\n hideTrash: 'Hide trash',\n trashed: 'in trash',\n trashEmpty: 'Trash is empty',\n restore: 'Restore',\n clearTrash: 'Empty trash',\n restoreAll: 'Restore all',\n clearTrashConfirm: 'Permanently delete ALL trashed entries? This cannot be undone.',\n show: 'Show',\n hide: 'Hide',\n allKinds: 'All kinds',\n allTags: 'All tags',\n sortAlpha: 'Sort: A–Z',\n sortRecent: 'Sort: recent',\n highSensitivity: 'HIGH',\n pinned: 'Pinned',\n add: 'Add credential',\n quickAdd: 'Quick add',\n cancel: 'Cancel',\n save: 'Save',\n edit: 'Edit',\n delete: 'Delete',\n deleteConfirm: 'Delete this entry? This cannot be undone.',\n copy: 'Copy',\n copyPassword: 'Copy password',\n copyCode: 'Copy code',\n copied: 'Copied',\n totp: 'Code',\n fieldTitle: 'Title',\n fieldKind: 'Kind',\n fieldUsername: 'Username',\n fieldEmail: 'Email',\n fieldPhone: 'Phone',\n fieldPassword: 'Password',\n fieldHost: 'Host',\n fieldPort: 'Port',\n fieldApiKey: 'API key',\n fieldSecret: 'Secret',\n fieldAccessToken: 'Access token',\n fieldRefreshToken: 'Refresh token',\n fieldOtpSecret: 'OTP secret',\n fieldUrl: 'URL',\n fieldNotes: 'Notes',\n fieldTags: 'Tags',\n fieldIcon: 'Icon (emoji)',\n fieldColor: 'Color',\n fieldCustom: 'Custom fields (key=value per line)',\n error: 'Something went wrong.',\n kindLogin: 'Login',\n kindSsh: 'SSH',\n kindApiKey: 'API key',\n kindSecret: 'Secret',\n kindOauth: 'OAuth',\n kindCustom: 'Custom',\n} as const\n\nexport type VaultLocaleKey = keyof typeof en\n\n/** Chinese strings (same keys as English). */\nexport const zh: Record<VaultLocaleKey, string> = {\n nav: '凭据库',\n heading: 'Vault 凭据库',\n intro: '本机加密存储的凭据。',\n searchPlaceholder: '搜索标题、用户名、邮箱、主机…',\n loading: '加载中…',\n empty: '还没有凭据,添加一条开始使用。',\n emptyHint: '提示:你可以在对话中把 API Key 或 token 交给助手保存——开启自动捕获后,它会在你同意时帮你存入本库。批量导入?让助手调用 vault_import_csv 并给出 CSV 路径即可。',\n emptyHintReadonly: '当前为只读模式——只能读取凭据,不能在此添加。将 accessMode 改为 \"readwrite\" 即可启用写入。',\n modeLabel: '访问模式',\n autoCaptureLabel: '自动捕获对话中的凭据',\n modeReadonly: '只读',\n modeAsk: '写入前询问',\n modeAuto: '自动读写',\n modeReadonlyHint: '只读 —— 变更已禁用',\n modeAskHint: '写入前询问 —— 每次增/改/删都会征求你的确认',\n modeAutoHint: '自动读写 —— 无需逐次确认',\n autoCaptureOn: '自动捕获已开启',\n autoCaptureOff: '自动捕获已关闭',\n clearSearch: '清除搜索',\n entryCount: '条目数',\n reportTitle: '健康与轮换',\n recentActivity: '最近活动',\n recentlyAdded: '最近添加',\n reportRotation: '已过期 / 待轮换',\n reportWeak: '弱密码',\n reportReused: '跨条目复用的凭据',\n healthOk: '凭据库健康',\n healthBackup: '备份',\n healthStrength: '强度',\n dueExpired: '已过期',\n dueExpiring: '将于',\n dueNow: '待轮换',\n showTrash: '回收站',\n hideTrash: '隐藏回收站',\n trashed: '已删除',\n trashEmpty: '回收站为空',\n restore: '恢复',\n clearTrash: '清空回收站',\n restoreAll: '全部恢复',\n clearTrashConfirm: '永久删除回收站中全部条目?此操作不可撤销。',\n show: '显示',\n hide: '隐藏',\n allKinds: '全部类型',\n allTags: '全部标签',\n sortAlpha: '排序:A–Z',\n sortRecent: '排序:最近',\n highSensitivity: '高敏',\n pinned: '已置顶',\n add: '新增凭据',\n quickAdd: '快速添加',\n cancel: '取消',\n save: '保存',\n edit: '编辑',\n delete: '删除',\n deleteConfirm: '确定删除该条目?此操作不可撤销。',\n copy: '复制',\n copyPassword: '复制密码',\n copyCode: '复制验证码',\n copied: '已复制',\n totp: '验证码',\n fieldTitle: '标题',\n fieldKind: '类型',\n fieldUsername: '用户名',\n fieldEmail: '邮箱',\n fieldPhone: '手机号',\n fieldPassword: '密码',\n fieldHost: '主机',\n fieldPort: '端口',\n fieldApiKey: 'API 密钥',\n fieldSecret: 'Secret',\n fieldAccessToken: '访问令牌',\n fieldRefreshToken: '刷新令牌',\n fieldOtpSecret: 'TOTP 密钥',\n fieldUrl: 'URL',\n fieldNotes: '备注',\n fieldTags: '标签',\n fieldIcon: '图标(emoji)',\n fieldColor: '颜色',\n fieldCustom: '自定义字段(每行 key=value)',\n error: '操作失败。',\n kindLogin: '登录',\n kindSsh: 'SSH',\n kindApiKey: 'API 密钥',\n kindSecret: 'Secret',\n kindOauth: 'OAuth',\n kindCustom: '自定义',\n}\n","/**\n * dsh-vault browser half: a Vault settings page managing encrypted\n * credentials — list, search, add, edit, delete, and copy — through the host\n * VaultGateway remote over the /api RPC channel.\n *\n * @module dsh-vault/client\n */\n\nimport type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'\nimport type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client'\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { VaultSection, type VaultSectionInjected } from './VaultSection.tsx'\nimport type { VaultSectionTypes } from './VaultSection.tsx'\nimport { en, zh, type VaultLocaleKey } from './locales.ts'\n\nexport type { VaultSectionInjected, VaultSectionProps } from './VaultSection.tsx'\nexport type { VaultSectionTypes } from './VaultSection.tsx'\nexport type { VaultLocaleKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n /** Vault settings page copy. */\n 'settings.vault': VaultLocaleKey\n }\n}\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'settings.vault'\n\n/** Services required by the Settings registration and the RPC face. */\nexport const inject = ['slots', 'locale', 'connection']\n\n/**\n * Register the Vault settings section once the `settings.section` declaration\n * is on the ledger. The section talks to the host through the connection RPC\n * channel (endpoints `vault/*` served by the host VaultGateway remote).\n */\nexport function apply(ctx: ClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-vault: dictionaries')\n\n const t = ctx.locale.bind(NS)\n const connection = ctx.get('connection') as ConnectionHandle\n\n /** Invoke one host vault remote and normalize the RPC envelope. */\n async function invoke<T>(method: string, args: Record<string, unknown> = {}): Promise<T> {\n const result = await connection.rpc.call('/api', `vault/${method}`, { args })\n if (!result.ok) {\n throw new Error(`vault.${method} failed: ${result.error.code}: ${result.error.message}`)\n }\n return result.value as T\n }\n\n const injected = (): VaultSectionInjected => ({\n t,\n config: () => invoke<VaultSectionTypes['config']>('config'),\n setAccessMode: (mode) => invoke<VaultSectionTypes['config']>('setAccessMode', { mode }),\n setAutoCapture: (enabled) => invoke<VaultSectionTypes['config']>('setAutoCapture', { enabled }),\n list: () => invoke<{ entries: VaultSectionTypes['entries'] }>('list').then(r => r.entries),\n search: (query, limit) => invoke<{ entries: VaultSectionTypes['entries'] }>('search', { query, limit: limit ?? 50 }).then(r => r.entries),\n get: (id) => invoke<{ found: boolean; entry?: VaultSectionTypes['fullEntry'] }>('get', { id }),\n add: (patch) => invoke<VaultSectionTypes['summaryEntry']>('add', { patch }),\n update: (id, patch) => invoke<{ found: boolean; entry?: VaultSectionTypes['summaryEntry'] }>('update', { id, patch }),\n remove: (id) => invoke<{ deleted: boolean }>('delete', { id }),\n trash: () => invoke<{ entries: VaultSectionTypes['entries'] }>('trash').then(r => r.entries),\n rotation: () => invoke<{ entries: unknown[] }>('rotation').then(r => r.entries),\n history: () => invoke<{ events: unknown[] }>('history').then(r => r.events),\n stats: () => invoke<Record<string, unknown>>('stats'),\n recent: () => invoke<{ entries: unknown[] }>('recent').then(r => r.entries),\n backupStatus: () => invoke<{ daysSinceBackup: number; backups: number }>('backupStatus'),\n health: () => invoke<{ weak: unknown[]; reused: unknown[]; strength: { weak: number; fair: number; strong: number } }>('health'),\n restore: (id) => invoke<{ restored: boolean }>('restore', { id }),\n undeleteAll: () => invoke<{ restored: number }>('undeleteAll'),\n totp: (id) => invoke<{ code: string; label?: string; secondsRemaining: number }>('totp', { id }),\n })\n\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'vault',\n order: 30,\n label: () => t('nav'),\n inject: injected,\n }, VaultSection))\n}\n"],"mappings":"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"}
|
package/lib/index.js
CHANGED
|
@@ -345,6 +345,8 @@ export async function apply(ctx, config) {
|
|
|
345
345
|
favoriteOnly: { type: 'boolean', description: 'Only return pinned (favorite) entries.' },
|
|
346
346
|
regex: { type: 'boolean', description: 'Treat query as a regular expression (case-insensitive).' },
|
|
347
347
|
sortBy: { type: 'string', enum: ['alpha', 'recent'], description: 'Sort results alphabetically (default) or by updatedAt desc.' },
|
|
348
|
+
createdAfter: { type: 'integer', description: 'Only entries created after this epoch millis.' },
|
|
349
|
+
createdBefore: { type: 'integer', description: 'Only entries created before this epoch millis.' },
|
|
348
350
|
limit: { type: 'number', description: 'Maximum results (default 20).' },
|
|
349
351
|
},
|
|
350
352
|
output: {
|
|
@@ -377,6 +379,16 @@ export async function apply(ctx, config) {
|
|
|
377
379
|
let filtered = kind === undefined ? results : results.filter(r => (r.kind ?? 'login') === kind);
|
|
378
380
|
if (args.favoriteOnly === true)
|
|
379
381
|
filtered = filtered.filter(r => r.favorite);
|
|
382
|
+
if (args.createdAfter !== undefined) {
|
|
383
|
+
const store2 = await guardStore();
|
|
384
|
+
const ids = new Set(store2.list().filter(e => e.createdAt > args.createdAfter).map(e => e.id));
|
|
385
|
+
filtered = filtered.filter(r => ids.has(r.id));
|
|
386
|
+
}
|
|
387
|
+
if (args.createdBefore !== undefined) {
|
|
388
|
+
const store2 = await guardStore();
|
|
389
|
+
const ids = new Set(store2.list().filter(e => e.createdAt < args.createdBefore).map(e => e.id));
|
|
390
|
+
filtered = filtered.filter(r => ids.has(r.id));
|
|
391
|
+
}
|
|
380
392
|
if (args.sortBy === 'recent') {
|
|
381
393
|
filtered = [...filtered].sort((a, b) => {
|
|
382
394
|
const au = a.updatedAt ?? 0;
|
|
@@ -952,6 +964,64 @@ export async function apply(ctx, config) {
|
|
|
952
964
|
return { tags };
|
|
953
965
|
},
|
|
954
966
|
}));
|
|
967
|
+
// ── vault_apply_tags: bulk tag management across matching entries ──────────
|
|
968
|
+
ctx.tools.register(defineTool({
|
|
969
|
+
name: 'vault_apply_tags',
|
|
970
|
+
description: 'Bulk add, remove, or replace tags on every entry matching a search query. '
|
|
971
|
+
+ 'Give at least one of add/remove/replace. Matches titles, usernames, emails, hosts, urls, notes, '
|
|
972
|
+
+ 'tags and custom-field values (case-insensitive); empty query = every active entry. No secrets.',
|
|
973
|
+
parameters: {
|
|
974
|
+
query: { type: 'string', description: 'Search text selecting which entries to update. Omit to update all active entries.' },
|
|
975
|
+
add: { type: 'array', items: { type: 'string' }, description: 'Tags to add (union).' },
|
|
976
|
+
remove: { type: 'array', items: { type: 'string' }, description: 'Tags to remove.' },
|
|
977
|
+
replace: { type: 'array', items: { type: 'string' }, description: 'Replace the whole tag list with these tags.' },
|
|
978
|
+
dryRun: { type: 'boolean', description: 'Only report how many entries would change, without writing.' },
|
|
979
|
+
},
|
|
980
|
+
output: { schema: { type: 'object', additionalProperties: false, properties: { matched: { type: 'integer', required: true }, updated: { type: 'integer', required: true }, entries: { type: 'array', required: true, items: { type: 'json' } } } }, render: (_a, v) => [{ type: 'text', text: `matched ${v.matched}, updated ${v.updated} entries` }] },
|
|
981
|
+
async execute(args) {
|
|
982
|
+
assertWritable('vault_apply_tags');
|
|
983
|
+
const add = normalizeTags(args.add ?? []);
|
|
984
|
+
const remove = normalizeTags(args.remove ?? []);
|
|
985
|
+
const replace = args.replace !== undefined ? normalizeTags(args.replace) : undefined;
|
|
986
|
+
if (add.length === 0 && remove.length === 0 && replace === undefined) {
|
|
987
|
+
throw new Error('vault_apply_tags: provide at least one of add, remove, or replace');
|
|
988
|
+
}
|
|
989
|
+
const s = await guardStore();
|
|
990
|
+
const all = s.list();
|
|
991
|
+
const query = typeof args.query === 'string' ? args.query.trim() : '';
|
|
992
|
+
const matched = query.length === 0
|
|
993
|
+
? all
|
|
994
|
+
: all.filter(e => {
|
|
995
|
+
const haystack = [e.title, e.username, e.email, e.phone, e.host, e.url, ...(e.tags ?? []), ...Object.values(e.fields ?? {})]
|
|
996
|
+
.filter((v) => v !== undefined)
|
|
997
|
+
.join('\n').toLowerCase();
|
|
998
|
+
return haystack.includes(query.toLowerCase());
|
|
999
|
+
});
|
|
1000
|
+
const changed = [];
|
|
1001
|
+
let updatedCount = 0;
|
|
1002
|
+
for (const e of matched) {
|
|
1003
|
+
const current = [...(e.tags ?? [])];
|
|
1004
|
+
let next;
|
|
1005
|
+
if (replace !== undefined)
|
|
1006
|
+
next = [...replace];
|
|
1007
|
+
else {
|
|
1008
|
+
next = [...current];
|
|
1009
|
+
for (const t of add)
|
|
1010
|
+
if (!next.includes(t))
|
|
1011
|
+
next.push(t);
|
|
1012
|
+
next = next.filter(t => !remove.includes(t));
|
|
1013
|
+
}
|
|
1014
|
+
if (next.length === current.length && next.every((t, i) => t === current[i]))
|
|
1015
|
+
continue;
|
|
1016
|
+
updatedCount++;
|
|
1017
|
+
if (!args.dryRun) {
|
|
1018
|
+
await s.update(e.id, { tags: next });
|
|
1019
|
+
changed.push({ id: e.id, title: e.title, tags: next });
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
return { matched: matched.length, updated: updatedCount, entries: changed };
|
|
1023
|
+
},
|
|
1024
|
+
}));
|
|
955
1025
|
// ── vault_generate_username: random username/email suggestion ───────────────
|
|
956
1026
|
ctx.tools.register(defineTool({
|
|
957
1027
|
name: 'vault_generate_username',
|
|
@@ -1227,14 +1297,24 @@ export async function apply(ctx, config) {
|
|
|
1227
1297
|
// ── vault_count: lightweight entry count ────────────────────────────────────
|
|
1228
1298
|
ctx.tools.register(defineTool({
|
|
1229
1299
|
name: 'vault_count',
|
|
1230
|
-
description: 'Return the number of active entries (optionally filtered by kind). Lightweight '
|
|
1300
|
+
description: 'Return the number of active entries (optionally filtered by kind and/or tag). Lightweight '
|
|
1231
1301
|
+ 'alternative to vault_stats when you only need a count.',
|
|
1232
|
-
parameters: {
|
|
1302
|
+
parameters: {
|
|
1303
|
+
kind: { type: 'string', enum: ['login', 'ssh', 'api-key', 'secret', 'oauth', 'custom'], description: 'Count only this kind.' },
|
|
1304
|
+
tag: { type: 'string', description: 'Count only entries carrying this tag.' },
|
|
1305
|
+
},
|
|
1233
1306
|
output: { schema: { type: 'object', additionalProperties: false, properties: { count: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `${v.count} entries` }] },
|
|
1234
1307
|
async execute(args) {
|
|
1235
1308
|
const s = await guardStore();
|
|
1236
1309
|
const kind = args.kind;
|
|
1237
|
-
const
|
|
1310
|
+
const tag = typeof args.tag === 'string' ? args.tag.trim() : '';
|
|
1311
|
+
const count = s.list().filter(e => {
|
|
1312
|
+
if (kind !== undefined && (e.kind ?? 'login') !== kind)
|
|
1313
|
+
return false;
|
|
1314
|
+
if (tag.length > 0 && !(e.tags ?? []).includes(tag))
|
|
1315
|
+
return false;
|
|
1316
|
+
return true;
|
|
1317
|
+
}).length;
|
|
1238
1318
|
return { count };
|
|
1239
1319
|
},
|
|
1240
1320
|
}));
|
|
@@ -1380,7 +1460,7 @@ export async function apply(ctx, config) {
|
|
|
1380
1460
|
description: 'Import a browser password-manager CSV (name,url,username,password — Chrome/Firefox/'
|
|
1381
1461
|
+ 'Edge export format). Rows without a name are skipped; returns added/skipped counts.',
|
|
1382
1462
|
parameters: { path: { type: 'string', required: true, description: 'Absolute path of the browser CSV.' } },
|
|
1383
|
-
output: { schema: { type: 'object', additionalProperties: false, properties: { added: { type: 'integer', required: true }, skipped: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `imported ${v.added}, skipped ${v.skipped}` }] },
|
|
1463
|
+
output: { schema: { type: 'object', additionalProperties: false, properties: { added: { type: 'integer', required: true }, skipped: { type: 'integer', required: true }, updated: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `imported ${v.added}, updated ${v.updated}, skipped ${v.skipped}` }] },
|
|
1384
1464
|
async execute(args) {
|
|
1385
1465
|
assertWritable('vault_import_browser');
|
|
1386
1466
|
const s = await guardStore();
|
|
@@ -1388,7 +1468,7 @@ export async function apply(ctx, config) {
|
|
|
1388
1468
|
const cleaned = raw.charCodeAt(0) === 0xFEFF ? raw.slice(1) : raw;
|
|
1389
1469
|
const rows = parseCsv(cleaned);
|
|
1390
1470
|
if (rows.length === 0)
|
|
1391
|
-
return { added: 0, skipped: 0 };
|
|
1471
|
+
return { added: 0, skipped: 0, updated: 0 };
|
|
1392
1472
|
// Header may be name,url,username,password or url,name,username,password.
|
|
1393
1473
|
const header = rows[0].map(h => h.trim().toLowerCase());
|
|
1394
1474
|
const idx = {
|
|
@@ -1398,9 +1478,10 @@ export async function apply(ctx, config) {
|
|
|
1398
1478
|
password: header.indexOf('password'),
|
|
1399
1479
|
};
|
|
1400
1480
|
if (idx.name < 0 || idx.password < 0)
|
|
1401
|
-
return { added: 0, skipped: rows.length - 1 };
|
|
1481
|
+
return { added: 0, skipped: rows.length - 1, updated: 0 };
|
|
1402
1482
|
let added = 0;
|
|
1403
1483
|
let skipped = 0;
|
|
1484
|
+
let updated = 0;
|
|
1404
1485
|
const now = Date.now();
|
|
1405
1486
|
for (let i = 1; i < rows.length; i++) {
|
|
1406
1487
|
const row = rows[i];
|
|
@@ -1426,7 +1507,7 @@ export async function apply(ctx, config) {
|
|
|
1426
1507
|
added++;
|
|
1427
1508
|
}
|
|
1428
1509
|
await s.persist();
|
|
1429
|
-
return { added, skipped };
|
|
1510
|
+
return { added, skipped, updated: 0 };
|
|
1430
1511
|
},
|
|
1431
1512
|
}));
|
|
1432
1513
|
// ── vault_autofill_check: does a target have usable credentials? ────────────
|
|
@@ -1595,7 +1676,7 @@ export async function apply(ctx, config) {
|
|
|
1595
1676
|
+ 'an entry titled by its filename; the first line is the password, remaining lines are parsed as '
|
|
1596
1677
|
+ 'login:/email:/url: metadata. Returns added/skipped.',
|
|
1597
1678
|
parameters: { dir: { type: 'string', required: true, description: 'Absolute pass directory.' } },
|
|
1598
|
-
output: { schema: { type: 'object', additionalProperties: false, properties: { added: { type: 'integer', required: true }, skipped: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `imported ${v.added}, skipped ${v.skipped}` }] },
|
|
1679
|
+
output: { schema: { type: 'object', additionalProperties: false, properties: { added: { type: 'integer', required: true }, skipped: { type: 'integer', required: true }, updated: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `imported ${v.added}, updated ${v.updated}, skipped ${v.skipped}` }] },
|
|
1599
1680
|
async execute(args) {
|
|
1600
1681
|
assertWritable('vault_import_wallet');
|
|
1601
1682
|
const s = await guardStore();
|
|
@@ -1637,7 +1718,63 @@ export async function apply(ctx, config) {
|
|
|
1637
1718
|
added++;
|
|
1638
1719
|
}
|
|
1639
1720
|
await s.persist();
|
|
1640
|
-
return { added, skipped };
|
|
1721
|
+
return { added, skipped, updated: 0 };
|
|
1722
|
+
},
|
|
1723
|
+
}));
|
|
1724
|
+
// ── vault_compare: field-by-field diff of two entries ───────────────────────
|
|
1725
|
+
ctx.tools.register(defineTool({
|
|
1726
|
+
name: 'vault_compare',
|
|
1727
|
+
description: 'Compare two entries field by field and report which fields differ, are only in one, '
|
|
1728
|
+
+ 'or are equal. Secret VALUES are not shown — only the field names and a difference summary.',
|
|
1729
|
+
parameters: { idA: { type: 'string', required: true, description: 'First entry id.' }, idB: { type: 'string', required: true, description: 'Second entry id.' } },
|
|
1730
|
+
output: { schema: { type: 'object', additionalProperties: false, properties: { onlyA: { type: 'array', required: true, items: { type: 'string' } }, onlyB: { type: 'array', required: true, items: { type: 'string' } }, differ: { type: 'array', required: true, items: { type: 'string' } }, equal: { type: 'array', required: true, items: { type: 'string' } } } }, render: (_a, v) => [{ type: 'text', text: `only in A: ${v.onlyA.join(',') || '-'} | only in B: ${v.onlyB.join(',') || '-'} | differ: ${v.differ.join(',') || '-'}` }] },
|
|
1731
|
+
async execute(args) {
|
|
1732
|
+
const s = await guardStore();
|
|
1733
|
+
const a = s.get(args.idA);
|
|
1734
|
+
const b = s.get(args.idB);
|
|
1735
|
+
if (!a || !b)
|
|
1736
|
+
throw new Error('vault_compare: both entries must exist and be active');
|
|
1737
|
+
const norm = (e) => {
|
|
1738
|
+
const { createdAt, updatedAt, ...rest } = e;
|
|
1739
|
+
return rest;
|
|
1740
|
+
};
|
|
1741
|
+
const na = norm(a);
|
|
1742
|
+
const nb = norm(b);
|
|
1743
|
+
const keys = new Set([...Object.keys(na), ...Object.keys(nb)]);
|
|
1744
|
+
const onlyA = [];
|
|
1745
|
+
const onlyB = [];
|
|
1746
|
+
const differ = [];
|
|
1747
|
+
const equal = [];
|
|
1748
|
+
for (const k of keys) {
|
|
1749
|
+
const va = na[k];
|
|
1750
|
+
const vb = nb[k];
|
|
1751
|
+
const sa = JSON.stringify(va);
|
|
1752
|
+
const sb = JSON.stringify(vb);
|
|
1753
|
+
if (va === undefined)
|
|
1754
|
+
onlyB.push(k);
|
|
1755
|
+
else if (vb === undefined)
|
|
1756
|
+
onlyA.push(k);
|
|
1757
|
+
else if (sa !== sb)
|
|
1758
|
+
differ.push(k);
|
|
1759
|
+
else
|
|
1760
|
+
equal.push(k);
|
|
1761
|
+
}
|
|
1762
|
+
return { onlyA, onlyB, differ, equal };
|
|
1763
|
+
},
|
|
1764
|
+
}));
|
|
1765
|
+
// ── vault_rename: rename an entry quickly ───────────────────────────────────
|
|
1766
|
+
ctx.tools.register(defineTool({
|
|
1767
|
+
name: 'vault_rename',
|
|
1768
|
+
description: 'Rename an entry (set a new title). Convenience shortcut for vault_update.',
|
|
1769
|
+
parameters: { id: { type: 'string', required: true, description: 'Entry id.' }, title: { type: 'string', required: true, description: 'New title.' } },
|
|
1770
|
+
output: { schema: { type: 'object', additionalProperties: false, properties: { renamed: { type: 'boolean', required: true } } }, render: (_a, v) => [{ type: 'text', text: v.renamed ? 'entry renamed' : 'entry not found' }] },
|
|
1771
|
+
async execute(args) {
|
|
1772
|
+
assertWritable('vault_rename');
|
|
1773
|
+
if (!args.title.trim())
|
|
1774
|
+
throw new Error('vault_rename: title must not be empty');
|
|
1775
|
+
const s = await guardStore();
|
|
1776
|
+
const updated = await s.update(args.id, { title: args.title.trim() });
|
|
1777
|
+
return { renamed: updated !== undefined };
|
|
1641
1778
|
},
|
|
1642
1779
|
}));
|
|
1643
1780
|
// ── vault_rotation: expiry / rotation report ───────────────────────────────
|
|
@@ -1896,7 +2033,7 @@ export async function apply(ctx, config) {
|
|
|
1896
2033
|
delimiter: { type: 'string', description: 'CSV delimiter (default ",").' },
|
|
1897
2034
|
overwrite: { type: 'boolean', description: 'Replace entries with the same title (default false).' },
|
|
1898
2035
|
},
|
|
1899
|
-
output: { schema: { type: 'object', additionalProperties: false, properties: { added: { type: 'integer', required: true }, skipped: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `imported ${v.added}, skipped ${v.skipped}` }] },
|
|
2036
|
+
output: { schema: { type: 'object', additionalProperties: false, properties: { added: { type: 'integer', required: true }, skipped: { type: 'integer', required: true }, updated: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `imported ${v.added}, updated ${v.updated}, skipped ${v.skipped}` }] },
|
|
1900
2037
|
async execute(args) {
|
|
1901
2038
|
assertWritable('vault_import_csv');
|
|
1902
2039
|
const s = await guardStore();
|
|
@@ -1904,13 +2041,14 @@ export async function apply(ctx, config) {
|
|
|
1904
2041
|
const cleaned = raw.charCodeAt(0) === 0xFEFF ? raw.slice(1) : raw;
|
|
1905
2042
|
const rows = parseCsv(cleaned, args.delimiter ?? ',');
|
|
1906
2043
|
if (rows.length === 0)
|
|
1907
|
-
return { added: 0, skipped: 0 };
|
|
2044
|
+
return { added: 0, skipped: 0, updated: 0 };
|
|
1908
2045
|
const headers = rows[0].map(h => h.trim());
|
|
1909
2046
|
const known = new Set(['title', 'username', 'password', 'url', 'email', 'phone', 'host', 'port',
|
|
1910
2047
|
'apiKey', 'secret', 'accessToken', 'refreshToken', 'expiresAt', 'otpSecret', 'notes', 'tags',
|
|
1911
2048
|
'kind', 'sensitivity', 'favorite', 'rotationDays']);
|
|
1912
2049
|
let added = 0;
|
|
1913
2050
|
let skipped = 0;
|
|
2051
|
+
let updated = 0;
|
|
1914
2052
|
const now = Date.now();
|
|
1915
2053
|
for (let i = 1; i < rows.length; i++) {
|
|
1916
2054
|
const row = rows[i];
|
|
@@ -1964,11 +2102,21 @@ export async function apply(ctx, config) {
|
|
|
1964
2102
|
const recordKind = typeof record.kind === 'string' ? record.kind : 'login';
|
|
1965
2103
|
// Dedupe on title + kind: the same title with a different kind is a
|
|
1966
2104
|
// distinct entry (e.g. "prod" as ssh vs api-key).
|
|
1967
|
-
const
|
|
1968
|
-
if (
|
|
2105
|
+
const existing = s.list().find(e => e.title === title && (e.kind ?? 'login') === recordKind);
|
|
2106
|
+
if (existing && !args.overwrite) {
|
|
1969
2107
|
skipped++;
|
|
1970
2108
|
continue;
|
|
1971
2109
|
}
|
|
2110
|
+
if (existing && args.overwrite) {
|
|
2111
|
+
// overwrite: merge CSV fields into the existing entry instead of
|
|
2112
|
+
// inserting a duplicate. Empty CSV cells are ignored so a partial
|
|
2113
|
+
// row can never blank a credential.
|
|
2114
|
+
const patch = pickDefinedFromRecord(record);
|
|
2115
|
+
delete patch.title;
|
|
2116
|
+
await s.update(existing.id, patch);
|
|
2117
|
+
updated++;
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
1972
2120
|
const entry = {
|
|
1973
2121
|
id: randomUUID(),
|
|
1974
2122
|
title,
|
|
@@ -1980,7 +2128,7 @@ export async function apply(ctx, config) {
|
|
|
1980
2128
|
added++;
|
|
1981
2129
|
}
|
|
1982
2130
|
await s.persist();
|
|
1983
|
-
return { added, skipped };
|
|
2131
|
+
return { added, skipped, updated };
|
|
1984
2132
|
},
|
|
1985
2133
|
}));
|
|
1986
2134
|
// ── vault_switch / vault_list: multi-vault navigation ───────────────────────
|
package/lib/store.js
CHANGED
|
@@ -211,6 +211,9 @@ export class VaultStore {
|
|
|
211
211
|
createdAt: now,
|
|
212
212
|
updatedAt: now,
|
|
213
213
|
};
|
|
214
|
+
if (patch.rotationDays === 0) {
|
|
215
|
+
delete entry.rotationDays;
|
|
216
|
+
}
|
|
214
217
|
this.entries.set(entry.id, entry);
|
|
215
218
|
this.recordHistory('add', entry.id, entry.title);
|
|
216
219
|
await this.persist();
|
|
@@ -276,6 +279,12 @@ export class VaultStore {
|
|
|
276
279
|
}
|
|
277
280
|
}
|
|
278
281
|
this.entries.set(id, updated);
|
|
282
|
+
// rotationDays: 0 means "never rotate" — clear the field entirely instead
|
|
283
|
+
// of keeping a zero interval that would report due forever.
|
|
284
|
+
if ('rotationDays' in patch && (patch.rotationDays ?? 0) === 0) {
|
|
285
|
+
delete updated.rotationDays;
|
|
286
|
+
this.entries.set(id, updated);
|
|
287
|
+
}
|
|
279
288
|
this.recordHistory('update', id, updated.title);
|
|
280
289
|
await this.persist();
|
|
281
290
|
return updated;
|
|
@@ -327,7 +336,8 @@ export class VaultStore {
|
|
|
327
336
|
const report = [];
|
|
328
337
|
for (const entry of this.list()) {
|
|
329
338
|
const base = entry.updatedAt ?? entry.createdAt;
|
|
330
|
-
const
|
|
339
|
+
const rotationDays = entry.rotationDays !== undefined && entry.rotationDays > 0 ? entry.rotationDays : undefined;
|
|
340
|
+
const rotationAt = rotationDays !== undefined ? base + rotationDays * 86_400_000 : undefined;
|
|
331
341
|
const expiresAt = entry.expiresAt;
|
|
332
342
|
let due;
|
|
333
343
|
let daysLeft;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vault",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "Encrypted credential vault for DeepSeek Harness: store and retrieve usernames, emails, phone numbers, passwords, TOTP secrets, SSH/API-key/OAuth developer credentials through model tools and a Settings UI page.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -79,4 +79,4 @@
|
|
|
79
79
|
"test": "vitest run",
|
|
80
80
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
81
81
|
}
|
|
82
|
-
}
|
|
82
|
+
}
|