dsh-vault 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README-zh.md CHANGED
@@ -41,13 +41,18 @@ dsh-vault 是一个面向 DeepSeek Harness 的安全加密插件:把你在使
41
41
  |---|---|
42
42
  | `vault_add` | 新增条目(上述字段任意组合;空字符串/空数组字段会被忽略) |
43
43
  | `vault_get` | 按 id 读取完整条目(含全部密钥) |
44
- | `vault_search` | 跨标题/分类/用户名/邮箱/手机/主机/端口/URL/备注/标签/自定义字段(含数字/布尔/嵌套值)检索,返回无密摘要;`limit` 须为 1–100 的整数 |
44
+ | `vault_search` | 跨标题/分类/用户名/邮箱/手机/主机/端口/URL/备注/标签/自定义字段(含数字/布尔/嵌套值;空格分隔多词 OR 命中)检索,返回无密摘要;`limit` 须为 1–100 的整数 |
45
45
  | `vault_update` | 按 id 更新字段(未提供的字段保留;空字符串清除该字段;`title` 可改名) |
46
46
  | `vault_delete` | 软删除条目(移入回收站,磁盘上仍加密保留) |
47
47
  | `vault_restore` / `vault_purge` | 从回收站恢复 / 从磁盘永久移除 |
48
48
  | `vault_lock` / `vault_unlock` | 显式锁定保险库(清空内存密钥)/ 重新解锁 |
49
49
  | `vault_totp` | 为存储的 otpSecret(或直接传入的 Base32/otpauth URI)生成当前 6 位动态验证码 |
50
50
  | `vault_generate_password` | 生成强随机密码(长度/字符集/去歧义/分组可选;`group` 须为 ≥2 的整数) |
51
+ | `vault_strength` | 零依赖密码强度评估(0–100 分,weak/fair/strong/very strong) |
52
+ | `vault_rekey` | 原地升级 scrypt KDF 参数并重加密 |
53
+ | `vault_import_csv` | 从 CSV 批量导入凭据(自定义列变为 fields) |
54
+ | `vault_totp_uri` | 为存储的或裸 TOTP 密钥生成 otpauth:// 配置 URI |
55
+ | `vault_switch` / `vault_list` | 按名称切换当前保险库 / 列出可用保险库 |
51
56
  | `vault_rotation` | 报告已过期 / 待轮换 / 即将过期的凭据(不含密钥) |
52
57
  | `vault_health` | 扫描弱密码与跨条目复用凭据(不含密钥) |
53
58
  | `vault_export` / `vault_import` | 整库加密备份/迁移(独立导出密码) |
@@ -152,7 +157,7 @@ tarball 自带预构建 `lib/` 产物,无需构建或 allowBuilds。
152
157
  autoCapture: true
153
158
  ```
154
159
 
155
- `autoCapture: true` 时,当你在对话中分享凭据(如 "我的 npm token 是 npm_…"),助手会提议存入;你同意后立即调用 `vault_add`。`autoCapture` 关闭时,只有你明确要求才保存。设置页显示当前模式(只读 / 写入前询问 / 自动读写),可用下拉切换,空库提示会引导你开始存入密钥。
160
+ `autoCapture: true` 时,当你在对话中分享凭据(如 "我的 npm token 是 npm_…"),助手会提议存入;你同意后立即调用 `vault_add`。`autoCapture` 关闭时,只有你明确要求才保存。设置页显示当前模式(只读 / 写入前询问 / 自动读写)可用下拉切换,还有**自动捕获开关**(检测对话中的凭据 → 提议保存)、类型筛选、健康与轮换摘要、回收站视图、以及默认遮罩的密钥字段(显示/隐藏切换)。
156
161
 
157
162
  首次调用任一工具时自动创建保险库;之后每次启动用主密码重新解锁。**忘记主密码 = 数据永久丢失**(无后门,这是设计使然)。
158
163
 
package/README.md CHANGED
@@ -43,13 +43,18 @@ 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); returns secret-free summaries; `limit` must be an integer 1–100 |
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
47
  | `vault_update` | Update fields by id (unprovided fields kept; empty string clears a field; `title` is renamable) |
48
48
  | `vault_delete` | Soft-delete an entry (moves it to the trash, still encrypted on disk) |
49
49
  | `vault_restore` / `vault_purge` | Bring a trashed entry back / permanently remove it from disk |
50
50
  | `vault_lock` / `vault_unlock` | Explicitly lock the vault (wipe the in-memory key) / re-unlock it |
51
51
  | `vault_totp` | Generate the current 6-digit code for a stored otpSecret (or a bare Base32 / otpauth URI) |
52
52
  | `vault_generate_password` | Generate a strong random password (length/character classes/ambiguity exclusion/grouping; `group` must be an integer ≥ 2) |
53
+ | `vault_strength` | Zero-dependency password strength estimate (score 0–100, weak/fair/strong/very strong) |
54
+ | `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) |
56
+ | `vault_totp_uri` | Build an otpauth:// provisioning URI for a stored or bare TOTP secret |
57
+ | `vault_switch` / `vault_list` | Switch the active vault by name / list available vaults |
53
58
  | `vault_rotation` | Report expired / due-for-rotation / expiring-soon credentials (no secrets) |
54
59
  | `vault_health` | Scan for weak passwords and credentials reused across entries (no secrets) |
55
60
  | `vault_export` / `vault_import` | Portable encrypted backup/migration of the whole vault (separate export password) |
@@ -154,7 +159,7 @@ Example:
154
159
  autoCapture: true
155
160
  ```
156
161
 
157
- With `autoCapture: true`, when you share a credential in chat (e.g. "my npm token is npm_…"), the assistant offers to store it; on your consent it calls `vault_add` immediately. With `autoCapture` off, credentials are only saved when you explicitly ask. The Settings UI shows the current mode (read-only / ask-before-write / automatic read-write) with a dropdown to switch it, and the empty-vault hint tells you how to get started.
162
+ With `autoCapture: true`, when you share a credential in chat (e.g. "my npm token is npm_…"), the assistant offers to store it; on your consent it calls `vault_add` immediately. With `autoCapture` off, credentials are only saved when you explicitly ask. The Settings UI shows the current mode (read-only / ask-before-write / automatic read-write) with a dropdown to switch it, an **auto-capture toggle** (detect credentials shared in chat → offer to save), a kind filter, a health & rotation summary, a trash view, and masked secret fields with a Show/Hide toggle.
158
163
 
159
164
  The vault is created automatically on first tool use; every launch re-unlocks with the master password. **Forgetting the master password = permanent data loss** (no backdoor — by design).
160
165
 
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}";
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}";
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,39 +17,43 @@ window.__ModuleLoader__.load({
17
17
  document.head.appendChild(tag);
18
18
  }
19
19
  var VaultSection_module_css_default = {
20
- "field": "t_j5va_field",
21
- "deleteButton": "t_j5va_deleteButton",
22
- "totp": "t_j5va_totp",
23
- "trashButton": "t_j5va_trashButton",
24
- "empty": "t_j5va_empty",
20
+ "reportTitle": "t_j5va_reportTitle",
21
+ "kindFilter": "t_j5va_kindFilter",
22
+ "modeAuto": "t_j5va_modeAuto",
25
23
  "saveButton": "t_j5va_saveButton",
26
- "copied": "t_j5va_copied",
27
- "addButton": "t_j5va_addButton",
24
+ "title": "t_j5va_title",
25
+ "emptyBox": "t_j5va_emptyBox",
26
+ "trashButton": "t_j5va_trashButton",
27
+ "highBadge": "t_j5va_highBadge",
28
+ "editor": "t_j5va_editor",
29
+ "intro": "t_j5va_intro",
28
30
  "identity": "t_j5va_identity",
29
31
  "row": "t_j5va_row",
30
- "emptyHint": "t_j5va_emptyHint",
32
+ "error": "t_j5va_error",
33
+ "modeReadonly": "t_j5va_modeReadonly",
34
+ "editorActions": "t_j5va_editorActions",
35
+ "deleteButton": "t_j5va_deleteButton",
36
+ "reportBox": "t_j5va_reportBox",
37
+ "reportLine": "t_j5va_reportLine",
38
+ "editorBody": "t_j5va_editorBody",
39
+ "secretField": "t_j5va_secretField",
40
+ "copied": "t_j5va_copied",
41
+ "revealButton": "t_j5va_revealButton",
31
42
  "modeAsk": "t_j5va_modeAsk",
43
+ "list": "t_j5va_list",
44
+ "policyField": "t_j5va_policyField",
45
+ "searchBox": "t_j5va_searchBox",
46
+ "emptyHint": "t_j5va_emptyHint",
32
47
  "policyBar": "t_j5va_policyBar",
33
- "status": "t_j5va_status",
34
- "modeAuto": "t_j5va_modeAuto",
35
- "editor": "t_j5va_editor",
36
- "editorBody": "t_j5va_editorBody",
37
- "reportTitle": "t_j5va_reportTitle",
38
- "rowActions": "t_j5va_rowActions",
39
- "reportLine": "t_j5va_reportLine",
48
+ "field": "t_j5va_field",
49
+ "totp": "t_j5va_totp",
40
50
  "section": "t_j5va_section",
41
- "reportBox": "t_j5va_reportBox",
42
- "error": "t_j5va_error",
43
- "searchBox": "t_j5va_searchBox",
44
- "list": "t_j5va_list",
51
+ "addButton": "t_j5va_addButton",
52
+ "rowActions": "t_j5va_rowActions",
45
53
  "rowMain": "t_j5va_rowMain",
46
- "title": "t_j5va_title",
47
- "modeReadonly": "t_j5va_modeReadonly",
48
- "emptyBox": "t_j5va_emptyBox",
49
- "intro": "t_j5va_intro",
50
- "editorActions": "t_j5va_editorActions",
51
54
  "toolbar": "t_j5va_toolbar",
52
- "policyField": "t_j5va_policyField"
55
+ "empty": "t_j5va_empty",
56
+ "status": "t_j5va_status"
53
57
  };
54
58
  //#endregion
55
59
  //#region src/client/VaultSection.tsx
@@ -142,7 +146,7 @@ window.__ModuleLoader__.load({
142
146
  }
143
147
  /** Render the Vault settings section. */
144
148
  function VaultSection(props) {
145
- const { t, config, setAccessMode, list, search, get, add, update, remove, trash, rotation, health, restore, totp } = props;
149
+ const { t, config, setAccessMode, setAutoCapture, list, search, get, add, update, remove, trash, rotation, health, restore, totp } = props;
146
150
  const searchId = (0, react.useId)();
147
151
  const [query, setQuery] = (0, react.useState)("");
148
152
  const [state, setState] = (0, react.useState)({ status: "loading" });
@@ -153,6 +157,8 @@ window.__ModuleLoader__.load({
153
157
  const [copiedId, setCopiedId] = (0, react.useState)(null);
154
158
  const [codeMap, setCodeMap] = (0, react.useState)({});
155
159
  const [tagsDraft, setTagsDraft] = (0, react.useState)("");
160
+ const [revealed, setRevealed] = (0, react.useState)({});
161
+ const [kindFilter, setKindFilter] = (0, react.useState)("");
156
162
  const [policy, setPolicy] = (0, react.useState)(null);
157
163
  const [showTrash, setShowTrash] = (0, react.useState)(false);
158
164
  const [trashEntries, setTrashEntries] = (0, react.useState)([]);
@@ -348,64 +354,103 @@ window.__ModuleLoader__.load({
348
354
  }),
349
355
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
350
356
  className: VaultSection_module_css_default.toolbar,
351
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
352
- className: VaultSection_module_css_default.searchBox,
353
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
354
- className: "sr-only",
355
- children: t("searchPlaceholder")
356
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
357
- id: searchId,
358
- type: "search",
359
- placeholder: t("searchPlaceholder"),
360
- value: query,
361
- onChange: (event) => setQuery(event.target.value)
362
- })]
363
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
364
- type: "button",
365
- className: VaultSection_module_css_default.addButton,
366
- onClick: startCreate,
367
- disabled: busy || readonly,
368
- children: ["+ ", t("add")]
369
- })]
357
+ children: [
358
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
359
+ className: VaultSection_module_css_default.searchBox,
360
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
361
+ className: "sr-only",
362
+ children: t("searchPlaceholder")
363
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
364
+ id: searchId,
365
+ type: "search",
366
+ placeholder: t("searchPlaceholder"),
367
+ value: query,
368
+ onChange: (event) => setQuery(event.target.value)
369
+ })]
370
+ }),
371
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
372
+ className: VaultSection_module_css_default.kindFilter,
373
+ value: kindFilter,
374
+ onChange: (e) => setKindFilter(e.target.value),
375
+ "aria-label": t("fieldKind"),
376
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
377
+ value: "",
378
+ children: t("allKinds")
379
+ }), Object.entries(KIND_KEYS).map(([value, key]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
380
+ value,
381
+ children: t(key)
382
+ }, value))]
383
+ }),
384
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
385
+ type: "button",
386
+ className: VaultSection_module_css_default.addButton,
387
+ onClick: startCreate,
388
+ disabled: busy || readonly,
389
+ children: ["+ ", t("add")]
390
+ })
391
+ ]
370
392
  }),
371
393
  policy !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
372
394
  className: VaultSection_module_css_default.policyBar,
373
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
374
- className: VaultSection_module_css_default.policyField,
375
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modeLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
376
- value: policy.accessMode,
377
- disabled: busy,
378
- onChange: (event) => {
379
- const next = event.target.value;
380
- setBusy(true);
381
- setMessage(null);
382
- setAccessMode(next).then((value) => {
383
- setPolicy(value);
384
- setBusy(false);
385
- }, () => {
386
- setMessage(t("error"));
387
- setBusy(false);
388
- });
389
- },
390
- children: [
391
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
392
- value: "readonly",
393
- children: t("modeReadonly")
394
- }),
395
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
396
- value: "ask",
397
- children: t("modeAsk")
398
- }),
399
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
400
- value: "auto",
401
- children: t("modeAuto")
402
- })
403
- ]
404
- })]
405
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
406
- className: policy.accessMode === "readonly" ? VaultSection_module_css_default.modeReadonly : policy.accessMode === "ask" ? VaultSection_module_css_default.modeAsk : VaultSection_module_css_default.modeAuto,
407
- children: [policy.accessMode === "readonly" ? t("modeReadonlyHint") : policy.accessMode === "ask" ? t("modeAskHint") : t("modeAutoHint"), policy.autoCapture ? ` · ${t("autoCaptureOn")}` : ` · ${t("autoCaptureOff")}`]
408
- })]
395
+ children: [
396
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
397
+ className: VaultSection_module_css_default.policyField,
398
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("modeLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
399
+ value: policy.accessMode,
400
+ disabled: busy,
401
+ onChange: (event) => {
402
+ const next = event.target.value;
403
+ setBusy(true);
404
+ setMessage(null);
405
+ setAccessMode(next).then((value) => {
406
+ setPolicy(value);
407
+ setBusy(false);
408
+ }, () => {
409
+ setMessage(t("error"));
410
+ setBusy(false);
411
+ });
412
+ },
413
+ children: [
414
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
415
+ value: "readonly",
416
+ children: t("modeReadonly")
417
+ }),
418
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
419
+ value: "ask",
420
+ children: t("modeAsk")
421
+ }),
422
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
423
+ value: "auto",
424
+ children: t("modeAuto")
425
+ })
426
+ ]
427
+ })]
428
+ }),
429
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
430
+ className: VaultSection_module_css_default.policyField,
431
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("autoCaptureLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
432
+ type: "checkbox",
433
+ checked: policy.autoCapture,
434
+ disabled: busy,
435
+ onChange: (event) => {
436
+ const next = event.target.checked;
437
+ setBusy(true);
438
+ setMessage(null);
439
+ setAutoCapture(next).then((value) => {
440
+ setPolicy(value);
441
+ setBusy(false);
442
+ }, () => {
443
+ setMessage(t("error"));
444
+ setBusy(false);
445
+ });
446
+ }
447
+ })]
448
+ }),
449
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
450
+ className: policy.accessMode === "readonly" ? VaultSection_module_css_default.modeReadonly : policy.accessMode === "ask" ? VaultSection_module_css_default.modeAsk : VaultSection_module_css_default.modeAuto,
451
+ children: [policy.accessMode === "readonly" ? t("modeReadonlyHint") : policy.accessMode === "ask" ? t("modeAskHint") : t("modeAutoHint"), policy.autoCapture ? ` · ${t("autoCaptureOn")}` : ` · ${t("autoCaptureOff")}`]
452
+ })
453
+ ]
409
454
  }),
410
455
  message !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
411
456
  role: "alert",
@@ -507,16 +552,19 @@ window.__ModuleLoader__.load({
507
552
  }),
508
553
  state.status === "ready" && state.entries.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
509
554
  className: VaultSection_module_css_default.list,
510
- children: state.entries.map((entry) => {
555
+ children: state.entries.filter((entry) => kindFilter === "" || entry.kind === kindFilter).map((entry) => {
511
556
  const code = codeMap[entry.id];
512
557
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
513
558
  className: VaultSection_module_css_default.row,
514
559
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
515
560
  className: VaultSection_module_css_default.rowMain,
516
561
  children: [
517
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
562
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
518
563
  className: VaultSection_module_css_default.title,
519
- children: entry.title
564
+ children: [entry.title, entry.sensitivity === "high" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
565
+ className: VaultSection_module_css_default.highBadge,
566
+ children: t("highSensitivity")
567
+ })]
520
568
  }),
521
569
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
522
570
  className: VaultSection_module_css_default.identity,
@@ -588,8 +636,26 @@ window.__ModuleLoader__.load({
588
636
  ...previous,
589
637
  title: event.target.value
590
638
  }))
639
+ }) : field.key === "password" || field.key === "otpSecret" || field.key === "apiKey" || field.key === "secret" || field.key === "accessToken" || field.key === "refreshToken" || field.key === "privateKey" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
640
+ className: VaultSection_module_css_default.secretField,
641
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
642
+ type: revealed[field.key] ? "text" : "password",
643
+ value: form[field.key] ?? "",
644
+ onChange: (event) => setForm((previous) => ({
645
+ ...previous,
646
+ [field.key]: event.target.value
647
+ }))
648
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
649
+ type: "button",
650
+ className: VaultSection_module_css_default.revealButton,
651
+ onClick: () => setRevealed((previous) => ({
652
+ ...previous,
653
+ [field.key]: !previous[field.key]
654
+ })),
655
+ children: revealed[field.key] ? t("hide") : t("show")
656
+ })]
591
657
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
592
- type: field.key === "password" || field.key === "otpSecret" ? "password" : "text",
658
+ type: "text",
593
659
  value: form[field.key] ?? "",
594
660
  onChange: (event) => setForm((previous) => ({
595
661
  ...previous,
@@ -637,6 +703,7 @@ window.__ModuleLoader__.load({
637
703
  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.",
638
704
  emptyHintReadonly: "The vault is in readonly mode — credentials can be read but not added here. Switch accessMode to \"readwrite\" to enable writes.",
639
705
  modeLabel: "Access mode",
706
+ autoCaptureLabel: "Auto-capture credentials from chat",
640
707
  modeReadonly: "Read-only",
641
708
  modeAsk: "Ask before writing",
642
709
  modeAuto: "Automatic read-write",
@@ -654,6 +721,10 @@ window.__ModuleLoader__.load({
654
721
  trashed: "in trash",
655
722
  trashEmpty: "Trash is empty",
656
723
  restore: "Restore",
724
+ show: "Show",
725
+ hide: "Hide",
726
+ allKinds: "All kinds",
727
+ highSensitivity: "HIGH",
657
728
  add: "Add credential",
658
729
  cancel: "Cancel",
659
730
  save: "Save",
@@ -698,6 +769,7 @@ window.__ModuleLoader__.load({
698
769
  emptyHint: "提示:你可以在对话中把 API Key 或 token 交给助手保存——开启自动捕获后,它会在你同意时帮你存入本库。",
699
770
  emptyHintReadonly: "当前为只读模式——只能读取凭据,不能在此添加。将 accessMode 改为 \"readwrite\" 即可启用写入。",
700
771
  modeLabel: "访问模式",
772
+ autoCaptureLabel: "自动捕获对话中的凭据",
701
773
  modeReadonly: "只读",
702
774
  modeAsk: "写入前询问",
703
775
  modeAuto: "自动读写",
@@ -715,6 +787,10 @@ window.__ModuleLoader__.load({
715
787
  trashed: "已删除",
716
788
  trashEmpty: "回收站为空",
717
789
  restore: "恢复",
790
+ show: "显示",
791
+ hide: "隐藏",
792
+ allKinds: "全部类型",
793
+ highSensitivity: "高敏",
718
794
  add: "新增凭据",
719
795
  cancel: "取消",
720
796
  save: "保存",
@@ -780,6 +856,7 @@ window.__ModuleLoader__.load({
780
856
  t,
781
857
  config: () => invoke("config"),
782
858
  setAccessMode: (mode) => invoke("setAccessMode", { mode }),
859
+ setAutoCapture: (enabled) => invoke("setAutoCapture", { enabled }),
783
860
  list: () => invoke("list").then((r) => r.entries),
784
861
  search: (query, limit) => invoke("search", {
785
862
  query,
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 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 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 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 health: () => Promise<{ weak: unknown[]; reused: unknown[] }>\n restore: (id: string) => Promise<{ restored: boolean }>\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>\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]\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: '' }\n}\n\n/** Render the Vault settings section. */\nexport function VaultSection(props: VaultSectionProps): ReactNode {\n const { t, config, setAccessMode, list, search, get, add, update, remove, trash, rotation, health, restore, 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 [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\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 void Promise.resolve().then(refresh).then(\n () => { /* state already set inside refresh */ },\n () => { if (current) setState({ status: 'error' }) },\n )\n return () => { current = false }\n // refresh is memoized on query; list/search are stable inject faces.\n }, [refresh])\n\n /** Open the editor for a new entry. */\n function startCreate(): void {\n setForm(emptyForm())\n setTagsDraft('')\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 })\n setTagsDraft((entry.tags ?? []).join(', '))\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 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 // 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 }\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 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 </label>\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 <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 </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 <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 {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\n {state.status === 'ready' && state.entries.length > 0 && (\n <ul className={css.list}>\n {state.entries.map(entry => {\n const code = codeMap[entry.id]\n return (\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}>{identityLine(entry)}</span>\n {code !== undefined && <span className={css.totp}>{code}</span>}\n </div>\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 type=\"button\" onClick={() => void showTotp(entry.id)} disabled={busy}>{t('totp')}</button>\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 {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 ) : (\n <input\n type={field.key === 'password' || field.key === 'otpSecret' ? 'password' : '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 </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.',\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 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 reportTitle: 'Health & rotation',\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 add: 'Add credential',\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 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 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 交给助手保存——开启自动捕获后,它会在你同意时帮你存入本库。',\n emptyHintReadonly: '当前为只读模式——只能读取凭据,不能在此添加。将 accessMode 改为 \"readwrite\" 即可启用写入。',\n modeLabel: '访问模式',\n modeReadonly: '只读',\n modeAsk: '写入前询问',\n modeAuto: '自动读写',\n modeReadonlyHint: '只读 —— 变更已禁用',\n modeAskHint: '写入前询问 —— 每次增/改/删都会征求你的确认',\n modeAutoHint: '自动读写 —— 无需逐次确认',\n autoCaptureOn: '自动捕获已开启',\n autoCaptureOff: '自动捕获已关闭',\n reportTitle: '健康与轮换',\n reportRotation: '已过期 / 待轮换',\n reportWeak: '弱密码',\n reportReused: '跨条目复用的凭据',\n showTrash: '回收站',\n hideTrash: '隐藏回收站',\n trashed: '已删除',\n trashEmpty: '回收站为空',\n restore: '恢复',\n add: '新增凭据',\n cancel: '取消',\n save: '保存',\n edit: '编辑',\n delete: '删除',\n deleteConfirm: '确定删除该条目?此操作不可撤销。',\n copy: '复制',\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 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 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 health: () => invoke<{ weak: unknown[]; reused: unknown[] }>('health'),\n restore: (id) => invoke<{ restored: boolean }>('restore', { id }),\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 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 health: () => Promise<{ weak: unknown[]; reused: unknown[] }>\n restore: (id: string) => Promise<{ restored: boolean }>\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>\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]\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: '' }\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, restore, 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 [revealed, setRevealed] = useState<Record<string, boolean>>({})\n const [kindFilter, setKindFilter] = useState('')\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\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 void Promise.resolve().then(refresh).then(\n () => { /* state already set inside refresh */ },\n () => { if (current) setState({ status: 'error' }) },\n )\n return () => { current = false }\n // refresh is memoized on query; list/search are stable inject faces.\n }, [refresh])\n\n /** Open the editor for a new entry. */\n function startCreate(): void {\n setForm(emptyForm())\n setTagsDraft('')\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 })\n setTagsDraft((entry.tags ?? []).join(', '))\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 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 // 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 }\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 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 </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 <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 </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 <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 {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\n {state.status === 'ready' && state.entries.length > 0 && (\n <ul className={css.list}>\n {state.entries.filter(entry => kindFilter === '' || entry.kind === kindFilter).map(entry => {\n const code = codeMap[entry.id]\n return (\n <li key={entry.id} className={css.row}>\n <div className={css.rowMain}>\n <span className={css.title}>\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 <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 type=\"button\" onClick={() => void showTotp(entry.id)} disabled={busy}>{t('totp')}</button>\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 {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 </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.',\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 reportTitle: 'Health & rotation',\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 show: 'Show',\n hide: 'Hide',\n allKinds: 'All kinds',\n highSensitivity: 'HIGH',\n add: 'Add credential',\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 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 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 交给助手保存——开启自动捕获后,它会在你同意时帮你存入本库。',\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 reportTitle: '健康与轮换',\n reportRotation: '已过期 / 待轮换',\n reportWeak: '弱密码',\n reportReused: '跨条目复用的凭据',\n showTrash: '回收站',\n hideTrash: '隐藏回收站',\n trashed: '已删除',\n trashEmpty: '回收站为空',\n restore: '恢复',\n show: '显示',\n hide: '隐藏',\n allKinds: '全部类型',\n highSensitivity: '高敏',\n add: '新增凭据',\n cancel: '取消',\n save: '保存',\n edit: '编辑',\n delete: '删除',\n deleteConfirm: '确定删除该条目?此操作不可撤销。',\n copy: '复制',\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 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 health: () => invoke<{ weak: unknown[]; reused: unknown[] }>('health'),\n restore: (id) => invoke<{ restored: boolean }>('restore', { id }),\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
@@ -53,7 +53,8 @@ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn,
53
53
  import { defineTool } from '@deepseek-ai/dsh-tools';
54
54
  import Schema from '@deepseek-ai/schemastery';
55
55
  import { Remote, TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
56
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
56
+ import { readFile, writeFile, mkdir, readdir } from 'node:fs/promises';
57
+ import { randomUUID } from 'node:crypto';
57
58
  import { dirname, join } from 'node:path';
58
59
  import { openVault, defaultVaultPath } from "./store.js";
59
60
  import { totp } from "./totp.js";
@@ -80,6 +81,17 @@ export async function apply(ctx, config) {
80
81
  const lockTimeoutSeconds = config.lockTimeoutSeconds ?? 0;
81
82
  /** Shared access policy; resolved once, mutated by the UI via setAccessMode. */
82
83
  const policy = await sharedAccessPolicy(config);
84
+ /** Audit events: other plugins / session logging can subscribe. Payload is
85
+ * non-secret (tool name + entry id/title only). */
86
+ const emitAudit = (kind, tool, entryId, title) => {
87
+ try {
88
+ ;
89
+ ctx.emit(`vault/${kind}`, { tool, entryId, title, at: Date.now() });
90
+ }
91
+ catch {
92
+ // A throwing listener must never break the vault operation.
93
+ }
94
+ };
83
95
  /** Reject mutations when the vault is in readonly mode. */
84
96
  function assertWritable(action) {
85
97
  if (policy.mode === 'readonly') {
@@ -93,9 +105,28 @@ export async function apply(ctx, config) {
93
105
  * call `next()` (waterfall event).
94
106
  */
95
107
  ctx.on('tools/pre-execute', async (exec, next) => {
96
- if (policy.mode === 'ask' && exec?.name !== undefined && WRITE_TOOLS.has(exec.name)) {
108
+ if (exec?.name === undefined)
109
+ return next();
110
+ if (policy.mode === 'ask' && WRITE_TOOLS.has(exec.name)) {
97
111
  return { kind: 'ask', reason: `dsh-vault: ${exec.name} requires your confirmation in "ask" (prompt-before-write) mode` };
98
112
  }
113
+ // High-sensitivity reads: in ask mode, reading a `high` entry's secrets
114
+ // (vault_get by id) also requires confirmation.
115
+ if (policy.mode === 'ask' && exec.name === 'vault_get') {
116
+ const id = exec.arguments?.id;
117
+ if (id !== undefined) {
118
+ try {
119
+ const store = await ensureStore();
120
+ const entry = store.get(id);
121
+ if (entry?.sensitivity === 'high') {
122
+ return { kind: 'ask', reason: `dsh-vault: reading high-sensitivity entry "${entry.title}" requires your confirmation` };
123
+ }
124
+ }
125
+ catch {
126
+ // If the vault is locked etc., let the tool itself report it.
127
+ }
128
+ }
129
+ }
99
130
  return next();
100
131
  });
101
132
  /** Ensure the shared store is open (lazily on first use, so a missing
@@ -229,6 +260,7 @@ export async function apply(ctx, config) {
229
260
  ...(args.tags !== undefined ? { tags: args.tags } : {}),
230
261
  ...(args.fields !== undefined ? { fields: args.fields } : {}),
231
262
  });
263
+ emitAudit('write', 'vault_add', entry.id, entry.title);
232
264
  return { id: entry.id, title: entry.title, message: 'added credential entry' };
233
265
  },
234
266
  }));
@@ -254,6 +286,7 @@ export async function apply(ctx, config) {
254
286
  const entry = await readEntry(args.id);
255
287
  if (!entry)
256
288
  return { found: false };
289
+ emitAudit('read', 'vault_get', entry.id, entry.title);
257
290
  return { found: true, entry: stripTimestamps(entry) };
258
291
  },
259
292
  }));
@@ -357,6 +390,7 @@ export async function apply(ctx, config) {
357
390
  const updated = await s.update(args.id, patch);
358
391
  if (!updated)
359
392
  return { found: false };
393
+ emitAudit('write', 'vault_update', updated.id, updated.title);
360
394
  return { found: true, entry: toSummaryJson(updated) };
361
395
  },
362
396
  }));
@@ -381,6 +415,8 @@ export async function apply(ctx, config) {
381
415
  assertWritable('vault_delete');
382
416
  const s = await guardStore();
383
417
  const deleted = await s.delete(args.id);
418
+ if (deleted)
419
+ emitAudit('write', 'vault_delete', args.id);
384
420
  return { deleted, message: deleted ? 'entry deleted' : 'entry not found' };
385
421
  },
386
422
  }));
@@ -497,6 +533,41 @@ export async function apply(ctx, config) {
497
533
  return { unlocked: true };
498
534
  },
499
535
  }));
536
+ // ── vault_totp_uri: build an otpauth:// provisioning URI ────────────────────
537
+ ctx.tools.register(defineTool({
538
+ name: 'vault_totp_uri',
539
+ description: 'Build an otpauth://totp/ provisioning URI for a stored otpSecret (or a bare secret), '
540
+ + 'so the user can scan it into an authenticator app. Returns the URI string.',
541
+ parameters: {
542
+ id: { type: 'string', description: 'Vault entry id whose otpSecret to use. Provide exactly one of id or secret.' },
543
+ secret: { type: 'string', description: 'Bare Base32 secret. Provide exactly one of id or secret.' },
544
+ label: { type: 'string', description: 'Account label in the URI (default: entry title or "dsh-vault").' },
545
+ issuer: { type: 'string', description: 'Issuer name (default: "dsh-vault").' },
546
+ },
547
+ output: { schema: { type: 'object', additionalProperties: false, properties: { uri: { type: 'string', required: true } } }, render: (_a, v) => [{ type: 'text', text: v.uri }] },
548
+ async execute(args) {
549
+ if ((args.id === undefined) === (args.secret === undefined)) {
550
+ throw new Error('vault_totp_uri: provide exactly one of id or secret');
551
+ }
552
+ let secret;
553
+ let label = args.label;
554
+ if (args.id !== undefined) {
555
+ const entry = await readEntry(args.id);
556
+ if (!entry?.otpSecret)
557
+ throw new Error(`vault_totp_uri: entry ${args.id} has no otpSecret`);
558
+ secret = entry.otpSecret;
559
+ label = label ?? entry.title;
560
+ }
561
+ else {
562
+ secret = args.secret;
563
+ label = label ?? 'dsh-vault';
564
+ }
565
+ const issuer = args.issuer ?? 'dsh-vault';
566
+ const encodedLabel = encodeURIComponent(`${issuer}:${label ?? ''}`);
567
+ const params = new URLSearchParams({ secret, issuer, algorithm: 'SHA1', digits: '6', period: '30' });
568
+ return { uri: `otpauth://totp/${encodedLabel}?${params.toString()}` };
569
+ },
570
+ }));
500
571
  // ── vault_rotation: expiry / rotation report ───────────────────────────────
501
572
  ctx.tools.register(defineTool({
502
573
  name: 'vault_rotation',
@@ -657,6 +728,149 @@ export async function apply(ctx, config) {
657
728
  return { kind, fields: TEMPLATES[kind] ?? TEMPLATES.login };
658
729
  },
659
730
  }));
731
+ // ── vault_strength: zero-dependency password strength estimation ────────────
732
+ ctx.tools.register(defineTool({
733
+ name: 'vault_strength',
734
+ description: 'Estimate the strength of a password with a zero-dependency heuristic '
735
+ + '(length, character-class diversity, common-pattern penalties). Returns a score 0–100 '
736
+ + 'and a verdict: weak / fair / strong / very strong. Use before choosing or storing a password.',
737
+ parameters: { password: { type: 'string', required: true, description: 'The password to evaluate.' } },
738
+ output: {
739
+ schema: { type: 'object', additionalProperties: false, properties: { score: { type: 'integer', required: true }, verdict: { type: 'string', required: true }, feedback: { type: 'string', required: true } } },
740
+ render: (_a, v) => [{ type: 'text', text: `${v.verdict} (${v.score}/100) — ${v.feedback}` }],
741
+ },
742
+ async execute(args) {
743
+ const r = estimateStrength(args.password);
744
+ return r;
745
+ },
746
+ }));
747
+ // ── vault_import_csv: bulk import from a CSV file ───────────────────────────
748
+ ctx.tools.register(defineTool({
749
+ name: 'vault_import_csv',
750
+ description: 'Bulk-import credentials from a CSV file. Expected columns (header row): '
751
+ + 'title,username,password,url,email,phone,host,port,apiKey,secret,notes,tags,kind. '
752
+ + 'Unknown columns become custom fields. Returns how many entries were added and skipped.',
753
+ parameters: {
754
+ path: { type: 'string', required: true, description: 'Absolute path of the CSV file.' },
755
+ delimiter: { type: 'string', description: 'CSV delimiter (default ",").' },
756
+ overwrite: { type: 'boolean', description: 'Replace entries with the same title (default false).' },
757
+ },
758
+ 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}` }] },
759
+ async execute(args) {
760
+ assertWritable('vault_import_csv');
761
+ const s = await guardStore();
762
+ const raw = await readFile(args.path, 'utf8');
763
+ const rows = parseCsv(raw, args.delimiter ?? ',');
764
+ if (rows.length === 0)
765
+ return { added: 0, skipped: 0 };
766
+ const headers = rows[0].map(h => h.trim());
767
+ const known = new Set(['title', 'username', 'password', 'url', 'email', 'phone', 'host', 'port',
768
+ 'apiKey', 'secret', 'notes', 'tags', 'kind', 'sensitivity']);
769
+ let added = 0;
770
+ let skipped = 0;
771
+ const now = Date.now();
772
+ for (let i = 1; i < rows.length; i++) {
773
+ const row = rows[i];
774
+ if (row.length === 1 && row[0].trim() === '')
775
+ continue; // blank line
776
+ const record = { title: row[0] ?? '' };
777
+ const fields = {};
778
+ for (let c = 1; c < headers.length && c < row.length; c++) {
779
+ const header = headers[c];
780
+ const value = row[c] ?? '';
781
+ if (value === '')
782
+ continue;
783
+ if (known.has(header)) {
784
+ if (header === 'tags')
785
+ record[header] = value.split(/[;,]/).map(t => t.trim()).filter(Boolean);
786
+ else
787
+ record[header] = value;
788
+ }
789
+ else {
790
+ fields[header] = value;
791
+ }
792
+ }
793
+ if (record.title === '') {
794
+ skipped++;
795
+ continue;
796
+ }
797
+ if (Object.keys(fields).length > 0)
798
+ record.fields = fields;
799
+ const title = record.title;
800
+ if (s.list().some(e => e.title === title) && !args.overwrite) {
801
+ skipped++;
802
+ continue;
803
+ }
804
+ const entry = {
805
+ id: randomUUID(),
806
+ title,
807
+ createdAt: now,
808
+ updatedAt: now,
809
+ ...pickDefinedFromRecord(record),
810
+ };
811
+ s.insertDirect(entry);
812
+ added++;
813
+ }
814
+ await s.persist();
815
+ return { added, skipped };
816
+ },
817
+ }));
818
+ // ── vault_switch / vault_list: multi-vault navigation ───────────────────────
819
+ ctx.tools.register(defineTool({
820
+ name: 'vault_list',
821
+ description: 'List available vaults in the vault directory (one .json file per vault, excluding '
822
+ + 'access/meta/export files). Marks the currently active one.',
823
+ parameters: {},
824
+ output: { schema: { type: 'object', additionalProperties: false, properties: { vaults: { type: 'array', required: true, items: { type: 'json' } } } }, render: (_a, v) => [{ type: 'text', text: JSON.stringify(v.vaults) }] },
825
+ async execute() {
826
+ const dir = dirname(resolveVaultPath(config));
827
+ const names = [];
828
+ try {
829
+ const entries = await readdir(dir);
830
+ for (const entry of entries) {
831
+ const m = /^(.*)\.json$/.exec(entry);
832
+ if (!m)
833
+ continue;
834
+ if (['access', 'meta'].includes(m[1]) || m[1].startsWith('vault-export-'))
835
+ continue;
836
+ names.push(m[1]);
837
+ }
838
+ }
839
+ catch { /* dir may not exist yet */ }
840
+ const active = currentVaultName ?? config.name ?? 'default';
841
+ return { vaults: names.sort().map(name => ({ name, active: name === active })) };
842
+ },
843
+ }));
844
+ ctx.tools.register(defineTool({
845
+ name: 'vault_switch',
846
+ description: 'Switch the active vault for this session. Future vault_* calls operate on the named '
847
+ + 'vault (created on first use). Returns the newly active vault name.',
848
+ parameters: { name: { type: 'string', required: true, description: 'Vault name (e.g. "work" or "personal").' } },
849
+ output: { schema: { type: 'object', additionalProperties: false, properties: { active: { type: 'string', required: true } } }, render: (_a, v) => [{ type: 'text', text: `switched to vault "${v.active}"` }] },
850
+ async execute(args) {
851
+ const name = args.name.trim();
852
+ if (name.length === 0)
853
+ throw new Error('vault_switch: name must not be empty');
854
+ if (!/^[a-zA-Z0-9._-]+$/.test(name))
855
+ throw new Error('vault_switch: name may contain only letters, digits, . _ -');
856
+ currentVaultName = name;
857
+ return { active: name };
858
+ },
859
+ }));
860
+ // ── vault_rekey: upgrade the scrypt KDF parameters in place ────────────────
861
+ ctx.tools.register(defineTool({
862
+ name: 'vault_rekey',
863
+ description: 'Upgrade the vault encryption to fresh scrypt KDF parameters (higher cost) and '
864
+ + 're-encrypt every entry in place. Safe to run periodically or after raising the vault '
865
+ + 'cost expectations; the old document is replaced atomically. Returns the new cost parameter n.',
866
+ parameters: {},
867
+ output: { schema: { type: 'object', additionalProperties: false, properties: { n: { type: 'integer', required: true } } }, render: (_a, v) => [{ type: 'text', text: `vault re-keyed with scrypt N=${v.n}` }] },
868
+ async execute() {
869
+ assertWritable('vault_rekey');
870
+ const s = await guardStore();
871
+ return await s.rekey();
872
+ },
873
+ }));
660
874
  // UI-facing Remote gateway: the browser Settings Vault page talks to these
661
875
  // methods through the /api RPC channel (loopback-trusted), bypassing the
662
876
  // model-tool layer entirely. Secrets are returned because the UI is the
@@ -680,6 +894,7 @@ let VaultGateway = (() => {
680
894
  let _instanceExtraInitializers = [];
681
895
  let _config_decorators;
682
896
  let _setAccessMode_decorators;
897
+ let _setAutoCapture_decorators;
683
898
  let _list_decorators;
684
899
  let _trash_decorators;
685
900
  let _restore_decorators;
@@ -696,6 +911,7 @@ let VaultGateway = (() => {
696
911
  const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
697
912
  _config_decorators = [Remote('config')];
698
913
  _setAccessMode_decorators = [Remote('setAccessMode')];
914
+ _setAutoCapture_decorators = [Remote('setAutoCapture')];
699
915
  _list_decorators = [Remote('list')];
700
916
  _trash_decorators = [Remote('trash')];
701
917
  _restore_decorators = [Remote('restore')];
@@ -709,6 +925,7 @@ let VaultGateway = (() => {
709
925
  _totp_decorators = [Remote('totp')];
710
926
  __esDecorate(this, null, _config_decorators, { kind: "method", name: "config", static: false, private: false, access: { has: obj => "config" in obj, get: obj => obj.config }, metadata: _metadata }, null, _instanceExtraInitializers);
711
927
  __esDecorate(this, null, _setAccessMode_decorators, { kind: "method", name: "setAccessMode", static: false, private: false, access: { has: obj => "setAccessMode" in obj, get: obj => obj.setAccessMode }, metadata: _metadata }, null, _instanceExtraInitializers);
928
+ __esDecorate(this, null, _setAutoCapture_decorators, { kind: "method", name: "setAutoCapture", static: false, private: false, access: { has: obj => "setAutoCapture" in obj, get: obj => obj.setAutoCapture }, metadata: _metadata }, null, _instanceExtraInitializers);
712
929
  __esDecorate(this, null, _list_decorators, { kind: "method", name: "list", static: false, private: false, access: { has: obj => "list" in obj, get: obj => obj.list }, metadata: _metadata }, null, _instanceExtraInitializers);
713
930
  __esDecorate(this, null, _trash_decorators, { kind: "method", name: "trash", static: false, private: false, access: { has: obj => "trash" in obj, get: obj => obj.trash }, metadata: _metadata }, null, _instanceExtraInitializers);
714
931
  __esDecorate(this, null, _restore_decorators, { kind: "method", name: "restore", static: false, private: false, access: { has: obj => "restore" in obj, get: obj => obj.restore }, metadata: _metadata }, null, _instanceExtraInitializers);
@@ -759,6 +976,13 @@ let VaultGateway = (() => {
759
976
  await this.persistPolicy();
760
977
  return { accessMode: this.accessPolicy.mode, autoCapture: this.accessPolicy.autoCapture };
761
978
  }
979
+ /** Toggle auto-capture (detect credentials in chat → offer to save) from
980
+ * the Settings UI and persist it. */
981
+ async setAutoCapture(enabled) {
982
+ this.accessPolicy.autoCapture = Boolean(enabled);
983
+ await this.persistPolicy();
984
+ return { accessMode: this.accessPolicy.mode, autoCapture: this.accessPolicy.autoCapture };
985
+ }
762
986
  /** Persist the current policy to `<vault dir>/access.json`. */
763
987
  async persistPolicy() {
764
988
  const file = accessPolicyFile({
@@ -864,6 +1088,7 @@ function toSummary(entry) {
864
1088
  return {
865
1089
  id: entry.id,
866
1090
  title: entry.title,
1091
+ ...(entry.sensitivity !== undefined ? { sensitivity: entry.sensitivity } : {}),
867
1092
  ...(entry.kind !== undefined ? { kind: entry.kind } : {}),
868
1093
  ...(entry.username !== undefined ? { username: entry.username } : {}),
869
1094
  ...(entry.email !== undefined ? { email: entry.email } : {}),
@@ -916,11 +1141,17 @@ function resolveExportPassword(config) {
916
1141
  */
917
1142
  const sharedVaultStores = new Map();
918
1143
  const sharedAccessPolicies = new Map();
1144
+ /** Current vault-name override (vault_switch); undefined = use config name. */
1145
+ let currentVaultName;
1146
+ /** Reset the session vault-switch override (tests). */
1147
+ export function resetVaultSwitch() {
1148
+ currentVaultName = undefined;
1149
+ }
919
1150
  /** Resolve the canonical vault file path for a config (path override or name). */
920
1151
  function resolveVaultPath(config) {
921
1152
  if (config.path !== undefined)
922
1153
  return config.path;
923
- return defaultVaultPath(config.name);
1154
+ return defaultVaultPath(currentVaultName ?? config.name);
924
1155
  }
925
1156
  /** The `<vault dir>/access.json` path holding the persisted access policy. */
926
1157
  function accessPolicyFile(config) {
@@ -1039,6 +1270,113 @@ function toSummaryJson(entry) {
1039
1270
  }
1040
1271
  /** Strip timestamps from an entry for model-visible output (keeps secrets
1041
1272
  * when the caller asked for the full entry via vault_get). */
1273
+ /** Minimal RFC-4180-ish CSV parser: handles quoted fields with embedded
1274
+ * delimiters/newlines and escaped double quotes. Returns rows of fields. */
1275
+ function parseCsv(input, delimiter = ',') {
1276
+ const rows = [];
1277
+ let row = [];
1278
+ let field = '';
1279
+ let inQuotes = false;
1280
+ let i = 0;
1281
+ const pushField = () => { row.push(field); field = ''; };
1282
+ const pushRow = () => { pushField(); rows.push(row); row = []; };
1283
+ while (i < input.length) {
1284
+ const ch = input[i];
1285
+ if (inQuotes) {
1286
+ if (ch === '"') {
1287
+ if (input[i + 1] === '"') {
1288
+ field += '"';
1289
+ i++;
1290
+ }
1291
+ else
1292
+ inQuotes = false;
1293
+ }
1294
+ else {
1295
+ field += ch;
1296
+ }
1297
+ }
1298
+ else if (ch === '"') {
1299
+ inQuotes = true;
1300
+ }
1301
+ else if (ch === delimiter) {
1302
+ pushField();
1303
+ }
1304
+ else if (ch === '\n') {
1305
+ pushRow();
1306
+ }
1307
+ else if (ch === '\r') {
1308
+ if (input[i + 1] === '\n')
1309
+ i++;
1310
+ pushRow();
1311
+ }
1312
+ else {
1313
+ field += ch;
1314
+ }
1315
+ i++;
1316
+ }
1317
+ if (field.length > 0 || row.length > 0)
1318
+ pushRow();
1319
+ return rows;
1320
+ }
1321
+ /** Copy only defined non-empty record fields into a VaultEntry-shaped patch,
1322
+ * skipping the identity fields (title handled separately). */
1323
+ function pickDefinedFromRecord(record) {
1324
+ const result = {};
1325
+ const identity = new Set(['id', 'createdAt', 'updatedAt', 'deletedAt']);
1326
+ for (const [key, value] of Object.entries(record)) {
1327
+ if (key === 'title' || identity.has(key))
1328
+ continue;
1329
+ if (value === undefined || value === '')
1330
+ continue;
1331
+ if (Array.isArray(value) && value.length === 0)
1332
+ continue;
1333
+ result[key] = value;
1334
+ }
1335
+ return result;
1336
+ }
1337
+ /** Zero-dependency password strength estimator: score 0–100 from length,
1338
+ * character-class coverage, and penalties for common weak patterns. */
1339
+ function estimateStrength(password) {
1340
+ let score = 0;
1341
+ const length = password.length;
1342
+ // Length is the dominant factor.
1343
+ score += Math.min(45, length * 3);
1344
+ const classes = [
1345
+ /[a-z]/.test(password),
1346
+ /[A-Z]/.test(password),
1347
+ /[0-9]/.test(password),
1348
+ /[^A-Za-z0-9]/.test(password),
1349
+ ].filter(Boolean).length;
1350
+ score += classes * 8;
1351
+ // Diversity bonus for longer unique characters.
1352
+ const unique = new Set(password).size;
1353
+ score += Math.min(15, unique);
1354
+ // Penalties for common weak patterns.
1355
+ let feedback = [];
1356
+ if (length < 8)
1357
+ feedback.push('too short (aim ≥ 12)');
1358
+ if (/^(password|123456|qwerty|letmein|admin|welcome|abc123)$/i.test(password)) {
1359
+ score -= 40;
1360
+ feedback.push('common password');
1361
+ }
1362
+ if (/(.)\1{2,}/.test(password)) {
1363
+ score -= 8;
1364
+ feedback.push('repeated characters');
1365
+ }
1366
+ if (/^\d+$/.test(password)) {
1367
+ score -= 15;
1368
+ feedback.push('digits only');
1369
+ }
1370
+ if (/^[a-z]+$/i.test(password)) {
1371
+ score -= 10;
1372
+ feedback.push('letters only');
1373
+ }
1374
+ if (password.length > 0 && new Set(password).size <= Math.max(3, Math.floor(length / 2)))
1375
+ feedback.push('low diversity');
1376
+ score = Math.max(0, Math.min(100, Math.round(score)));
1377
+ const verdict = score >= 80 ? 'very strong' : score >= 60 ? 'strong' : score >= 40 ? 'fair' : 'weak';
1378
+ return { score, verdict, feedback: feedback.length > 0 ? feedback.join('; ') : 'no obvious weaknesses' };
1379
+ }
1042
1380
  function stripTimestamps(entry) {
1043
1381
  const { createdAt, updatedAt, ...rest } = entry;
1044
1382
  return rest;
package/lib/store.js CHANGED
@@ -162,16 +162,17 @@ export class VaultStore {
162
162
  getIncludingTrash(id) {
163
163
  return this.entries.get(id);
164
164
  }
165
- /** Search entries across text fields; returns summaries without secrets. */
165
+ /** Search entries across text fields; returns summaries without secrets.
166
+ * Multiple whitespace-separated terms match when ANY term hits (OR). */
166
167
  search(query, limit = 20) {
167
- const needle = query.trim().toLowerCase();
168
- if (needle.length === 0)
168
+ const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
169
+ if (terms.length === 0)
169
170
  return [];
170
171
  const results = [];
171
172
  for (const entry of this.list()) {
172
173
  if (results.length >= limit)
173
174
  break;
174
- if (matches(entry, needle))
175
+ if (terms.some(term => matches(entry, term)))
175
176
  results.push(toSummary(entry));
176
177
  }
177
178
  return results;
@@ -192,6 +193,10 @@ export class VaultStore {
192
193
  await this.persist();
193
194
  return entry;
194
195
  }
196
+ /** Insert an entry directly (used by bulk import); caller owns timestamps. */
197
+ insertDirect(entry) {
198
+ this.entries.set(entry.id, entry);
199
+ }
195
200
  /** Update an existing entry's fields; returns the updated entry or undefined.
196
201
  * Every defined field in `patch` replaces the stored value; an empty string
197
202
  * clears (removes) that field. `id`/`createdAt` can never change. */
@@ -386,7 +391,40 @@ export class VaultStore {
386
391
  * The derived key is cached after load and reused, so a mutation does not
387
392
  * re-run scrypt; only a fresh vault (no cached key yet) derives once.
388
393
  */
389
- persist() {
394
+ /**
395
+ * Re-key the vault with fresh scrypt KDF parameters (cost upgrade): derive a
396
+ * new key from the master password under `newKdfParams()` and re-encrypt
397
+ * every entry under it, atomically. The old key buffer is wiped. Returns the
398
+ * new cost parameter `n` for reporting.
399
+ */
400
+ async rekey() {
401
+ if (this.key === undefined) {
402
+ await this.load();
403
+ }
404
+ const newKdf = newKdfParams();
405
+ const newKey = await deriveKey(this.masterPassword, newKdf);
406
+ // Encrypt every entry under the new key first (in memory), so a failure
407
+ // mid-encrypt leaves the on-disk document untouched.
408
+ const reEncrypted = [...this.entries.values()].map(entry => ({
409
+ id: entry.id,
410
+ ...encrypt(Buffer.from(JSON.stringify(entry), 'utf8'), newKey),
411
+ }));
412
+ await mkdir(dirname(this.path), { recursive: true, mode: 0o700 });
413
+ await withFileLock(this.path, async () => {
414
+ const file = {
415
+ version: VAULT_FORMAT_VERSION,
416
+ kdf: newKdf,
417
+ verify: encrypt(Buffer.from(VERIFY_PLAINTEXT, 'utf8'), newKey),
418
+ entries: reEncrypted,
419
+ };
420
+ await writeFileAtomic(this.path, JSON.stringify(file), { mode: 0o600, dirMode: 0o700 });
421
+ });
422
+ this.kdf = newKdf;
423
+ this.key?.fill(0);
424
+ this.key = newKey;
425
+ return { n: newKdf.n };
426
+ }
427
+ async persist() {
390
428
  const run = async () => {
391
429
  const kdf = this.kdf ?? newKdfParams();
392
430
  this.kdf = kdf;
@@ -488,6 +526,7 @@ function toSummary(entry) {
488
526
  return {
489
527
  id: entry.id,
490
528
  title: entry.title,
529
+ ...(entry.sensitivity !== undefined ? { sensitivity: entry.sensitivity } : {}),
491
530
  ...(entry.kind !== undefined ? { kind: entry.kind } : {}),
492
531
  ...(entry.username !== undefined ? { username: entry.username } : {}),
493
532
  ...(entry.email !== undefined ? { email: entry.email } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-vault",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
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",