@aiwayds/dsh-tui-pi 2.18.2 → 2.20.0

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.
Files changed (106) hide show
  1. package/README.md +12 -43
  2. package/README.zh-CN.md +11 -42
  3. package/lib/agents.js +67 -52
  4. package/lib/agents.js.map +1 -1
  5. package/lib/ask-user.d.ts +122 -3
  6. package/lib/ask-user.js +335 -41
  7. package/lib/ask-user.js.map +1 -1
  8. package/lib/attachments.js +8 -3
  9. package/lib/attachments.js.map +1 -1
  10. package/lib/btw.d.ts +4 -2
  11. package/lib/btw.js +22 -16
  12. package/lib/btw.js.map +1 -1
  13. package/lib/custom-provider.js +34 -28
  14. package/lib/custom-provider.js.map +1 -1
  15. package/lib/footer.d.ts +9 -8
  16. package/lib/footer.js +36 -18
  17. package/lib/footer.js.map +1 -1
  18. package/lib/hard-exit.d.ts +36 -0
  19. package/lib/hard-exit.js +64 -0
  20. package/lib/hard-exit.js.map +1 -0
  21. package/lib/history-fork.d.ts +2 -2
  22. package/lib/history-fork.js +12 -9
  23. package/lib/history-fork.js.map +1 -1
  24. package/lib/history-turns.d.ts +4 -2
  25. package/lib/history-turns.js +7 -3
  26. package/lib/history-turns.js.map +1 -1
  27. package/lib/history.js +57 -45
  28. package/lib/history.js.map +1 -1
  29. package/lib/hotkeys.js +44 -33
  30. package/lib/hotkeys.js.map +1 -1
  31. package/lib/i18n/index.d.ts +54 -0
  32. package/lib/i18n/index.js +98 -0
  33. package/lib/i18n/index.js.map +1 -0
  34. package/lib/i18n/registry.d.ts +65 -0
  35. package/lib/i18n/registry.js +129 -0
  36. package/lib/i18n/registry.js.map +1 -0
  37. package/lib/index.js +219 -109
  38. package/lib/index.js.map +1 -1
  39. package/lib/language-ask.d.ts +98 -0
  40. package/lib/language-ask.js +118 -0
  41. package/lib/language-ask.js.map +1 -0
  42. package/lib/live-widgets.js +9 -8
  43. package/lib/live-widgets.js.map +1 -1
  44. package/lib/login.js +5 -4
  45. package/lib/login.js.map +1 -1
  46. package/lib/messages.d.ts +0 -4
  47. package/lib/messages.js +14 -15
  48. package/lib/messages.js.map +1 -1
  49. package/lib/model-profiles.js +32 -20
  50. package/lib/model-profiles.js.map +1 -1
  51. package/lib/panels.js +18 -17
  52. package/lib/panels.js.map +1 -1
  53. package/lib/preflight-projcache.js +9 -3
  54. package/lib/preflight-projcache.js.map +1 -1
  55. package/lib/preset-dialog.d.ts +3 -3
  56. package/lib/preset-dialog.js +20 -17
  57. package/lib/preset-dialog.js.map +1 -1
  58. package/lib/provider-catalog.d.ts +23 -7
  59. package/lib/provider-catalog.js +90 -68
  60. package/lib/provider-catalog.js.map +1 -1
  61. package/lib/queue-panel.d.ts +2 -2
  62. package/lib/queue-panel.js +13 -9
  63. package/lib/queue-panel.js.map +1 -1
  64. package/lib/repair-dialog.d.ts +4 -4
  65. package/lib/repair-dialog.js +23 -16
  66. package/lib/repair-dialog.js.map +1 -1
  67. package/lib/route-dialog.d.ts +3 -3
  68. package/lib/route-dialog.js +20 -14
  69. package/lib/route-dialog.js.map +1 -1
  70. package/lib/selectors.d.ts +15 -1
  71. package/lib/selectors.js +57 -23
  72. package/lib/selectors.js.map +1 -1
  73. package/lib/sessions.d.ts +47 -14
  74. package/lib/sessions.js +162 -53
  75. package/lib/sessions.js.map +1 -1
  76. package/lib/settings.d.ts +68 -9
  77. package/lib/settings.js +327 -78
  78. package/lib/settings.js.map +1 -1
  79. package/lib/skills-manager.js +27 -21
  80. package/lib/skills-manager.js.map +1 -1
  81. package/lib/skills.js +5 -4
  82. package/lib/skills.js.map +1 -1
  83. package/lib/startup-info.js +9 -3
  84. package/lib/startup-info.js.map +1 -1
  85. package/lib/steer-flow.d.ts +14 -4
  86. package/lib/steer-flow.js +24 -9
  87. package/lib/steer-flow.js.map +1 -1
  88. package/lib/stop-dialog.d.ts +2 -2
  89. package/lib/stop-dialog.js +16 -12
  90. package/lib/stop-dialog.js.map +1 -1
  91. package/lib/subagent-viewer.d.ts +3 -8
  92. package/lib/subagent-viewer.js +38 -62
  93. package/lib/subagent-viewer.js.map +1 -1
  94. package/lib/theme-preview.js +12 -11
  95. package/lib/theme-preview.js.map +1 -1
  96. package/lib/theme-settings.d.ts +44 -2
  97. package/lib/theme-settings.js +97 -54
  98. package/lib/theme-settings.js.map +1 -1
  99. package/lib/tui.js +2 -1
  100. package/lib/tui.js.map +1 -1
  101. package/locales/en.json +646 -0
  102. package/locales/ja.json +646 -0
  103. package/locales/ko.json +646 -0
  104. package/locales/zh-CN.json +646 -0
  105. package/package.json +2 -1
  106. package/skills/dsh-tui-pi-config/SKILL.md +14 -4
@@ -0,0 +1,646 @@
1
+ {
2
+ "name": "English",
3
+ "command.language.description": "Switch the UI language (bare /language asks you to pick one; /language <code> switches directly)",
4
+ "command.language.unknown": "Unknown language \"{id}\". Installed: {list}",
5
+ "command.language.switched": "Language switched to {name} ({id}). The next repaint uses it; the transcript backlog keeps the old language until it rebuilds.",
6
+ "command.language.persistFailed": "Switched for this session, but saving the preference failed: {error}",
7
+ "command.language.unchanged": "Language unchanged.",
8
+ "command.language.unavailable": "No ask-user answerer is available to pick a language. Installed: {list} — use /language <code>.",
9
+ "command.language.askFailed": "Could not ask for the language: {error}",
10
+ "settings.language.description": "UI language: a locale id from the bundled locales/ directory or ~/.dsh/locales (e.g. 'en', 'zh-CN'); unknown ids fall back to 'en'. Applies on the next repaint — the /language command switches live",
11
+ "settings.theme.description": "Terminal color scheme ('auto' follows the terminal, 'light'/'dark' are the GitHub palettes, any other value names a registered custom theme)",
12
+ "settings.panelHeight.description": "Think/tool panel height ('1' = one row: identifier + elapsed + last line, right-truncated; '5'/'7'/'10' = boxed header + content rows, borders add 2 more; 'all' = full content — streaming reasoning shows a 200-line live tail, tool results cap at 2000 lines)",
13
+ "settings.maxAgents.description": "Max concurrently running subagents (0 = unlimited)",
14
+ "settings.maxRounds.description": "Max assistant messages per subagent before the TUI sends a summary request (0 = unlimited)",
15
+ "settings.maxRoundsGrace.description": "Rounds a subagent may keep running after the summary request before the TUI force-stops it (0 = warn only, never force-stop)",
16
+ "settings.disableSubagent.description": "Disable the native subagent tool (delegation goes through registered agents, ~/.dsh/agents/*.md via use_agent); subagent_fork/workflow/ralph stay available",
17
+ "settings.registeredOnly.description": "Fence EVERY spawn tool except use_agent: delegation may only create subagents backed by a registered agent definition (~/.dsh/agents/*.md); the ad-hoc paths (subagent, subagent_fork, workflow, ralph) are denied at the guard",
18
+ "settings.footerHints.description": "Footer shortcut hints to display (toggle each one on/off)",
19
+ "settings.footerHints.send.description": "Show \"Enter: send\" in the footer hint bar",
20
+ "settings.footerHints.stop.description": "Show \"Esc ×2: stop\" in the footer hint bar",
21
+ "settings.footerHints.quit.description": "Show \"Ctrl+C ×2: quit\" in the footer hint bar",
22
+ "settings.footerHints.quitEmpty.description": "Show \"Ctrl+D: quit (empty)\" in the footer hint bar",
23
+ "settings.footerHints.subagents.description": "Show \"Ctrl+G: subagents\" in the footer hint bar",
24
+ "settings.footerHints.search.description": "Show \"Ctrl+Shift+F: search\" in the footer hint bar",
25
+ "settings.footerHints.history.description": "Show \"↑↓: history\" in the footer hint bar",
26
+ "settings.cacheHitMode.description": "Footer CH segment scope: 'lastMessage' = hit rate of the latest assistant message (matches the pi-tui footer); 'session' = cumulative over the whole session's input traffic. Applies on the next footer repaint",
27
+ "settings.iconSet.description": "Icon set for the risky glyphs ('auto' = pick nerdfont when a Nerd Font is detected at startup, plain otherwise; 'nerdfont' = powerline PUA glyphs (U+E0B0 separator, stop, heavy circle); plain = safe Unicode stand-ins (▸ ■ ●) — auto is the recommended default)",
28
+ "settings.rememberPreset.description": "Remember the last /preset selection per workspace and start the next launch in the same directory on it (instead of the server default). false always starts on the server-side default preset",
29
+ "settings.favoriteModels.description": "Favorite models (provider/id keys) pinned to the top of the /model picker",
30
+ "settings.hiddenModels.description": "Hidden models (provider/id keys) moved to the Hidden section of the /model picker",
31
+ "settings.retention.description": "Startup session-log janitor for ~/.dsh/sessions (explicit values here outrank the DSH_TUI_RETENTION_* env vars)",
32
+ "settings.retention.maxCount.description": "Session log retention: keep at most this many sessions (<= 0 disables the janitor); outranks DSH_TUI_RETENTION_MAX_COUNT; applies at next startup",
33
+ "settings.retention.maxAgeDays.description": "Session log retention: delete logs untouched for more than this many days (> 0); outranks DSH_TUI_RETENTION_MAX_AGE_DAYS; applies at next startup",
34
+ "settings.retention.minIdleHours.description": "Session log retention: count-rule-only idle guard in hours (>= 0); outranks DSH_TUI_RETENTION_MIN_IDLE_HOURS; applies at next startup",
35
+ "settings.resume.description": "Resume picker display filter (explicit values here outrank the DSH_TUI_RESUME_* env vars)",
36
+ "settings.resume.maxAgeDays.description": "Resume picker: only sessions with log activity inside this window get a row (> 0); outranks DSH_TUI_RESUME_MAX_AGE_DAYS",
37
+ "settings.resume.minBytes.description": "Resume picker: minimum compressed log size for a row (>= 0); outranks DSH_TUI_RESUME_MIN_BYTES",
38
+ "settings.askUser.description": "Ask User question panel timeouts (explicit values here outrank the DSH_TUI_ASK_USER_* env vars)",
39
+ "settings.askUser.idleMinutes.description": "Ask User panel: auto-answer the focused question after this many minutes without a single keypress — unanswered questions take the recommended option (first in the list), plans are never auto-approved; <= 0 disables the no-input rule; outranks DSH_TUI_ASK_USER_IDLE_MINUTES",
40
+ "settings.askUser.absoluteMinutes.description": "Ask User panel: hard cap per question — after this many minutes the question is auto-answered even if the user keeps interacting (the panel must never hold a run hostage); <= 0 disables the cap; outranks DSH_TUI_ASK_USER_ABSOLUTE_MINUTES",
41
+ "ask.language.header": "Language",
42
+ "ask.language.question": "Which UI language should the TUI use?",
43
+ "ask.language.detail": "Currently {current}. Picking a language applies it live — the next repaint speaks it (the transcript backlog keeps the old language until it rebuilds) — and persists it to dsh-tui.language.",
44
+ "ask.language.current": "current",
45
+ "ask.sentinel": "Type something.",
46
+ "ask.questionFallback": "Question {n}",
47
+ "ask.confirmRow": "⏎ Confirm answers",
48
+ "ask.col.selection": "Selection",
49
+ "ask.title.single": "● Question",
50
+ "ask.title.multi": "● Questions ({index}/{total})",
51
+ "ask.empty": "(no questions)",
52
+ "ask.escHint": "Press Esc again to decline",
53
+ "ask.footer.editing": "Type free text · Enter keep · ↑↓ move · Ctrl+T fold · Esc abandon",
54
+ "ask.footer.tabs": "←→ tabs · ",
55
+ "ask.footer.navSelect": "↑↓ move · Enter select · 1-9 pick · Ctrl+T fold · Esc decline",
56
+ "ask.footer.navToggle": "↑↓ move · Enter toggle · 1-9 pick · Ctrl+T fold · Esc decline",
57
+ "ask.review.title": "● Review answers",
58
+ "ask.review.colQuestion": "Question",
59
+ "ask.review.colAnswer": "Your answer",
60
+ "ask.review.submit": "Submit answers",
61
+ "ask.review.ready": "✓ ready",
62
+ "ask.review.incomplete": "— incomplete",
63
+ "ask.review.footer": "↑↓ select · Enter return to edit / submit · Ctrl+T fold",
64
+ "ask.review.noAnswer": "(no answer)",
65
+ "ask.collapsed.esc": "Press Esc again to decline · Ctrl+T expand",
66
+ "ask.collapsed.review": "● Review answers pending · Ctrl+T expand",
67
+ "ask.collapsed.question": "● Question pending",
68
+ "ask.collapsed.questions": "● Questions ({index}/{total} · {answered} answered)",
69
+ "ask.collapsed.expand": "Ctrl+T expand",
70
+ "ask.autoIn": "auto in {time}",
71
+ "ask.incomplete": "Answer every question first",
72
+ "ask.copied": "Copied",
73
+ "ask.notice.timeoutReview": "ask_user_question: timed out — submitted the answers already given",
74
+ "ask.notice.timeoutAuto": "ask_user_question: timed out — unanswered questions took the recommended options (noted in the answers)",
75
+ "ask.settings.invalid": "settings dsh-tui.askUser.{key}: invalid value {value} — falling back to environment/default",
76
+ "viewer.footer.running": "↑↓ scroll · Esc close · x ×2 stop · Enter steer",
77
+ "viewer.footer.settled": "↑↓ scroll · Esc close · x ×2 close · Enter steer",
78
+ "viewer.steer.footer": "Enter send · Shift+Enter newline · Esc cancel",
79
+ "viewer.steer.sent": "Steer message sent",
80
+ "viewer.steer.ended": "This subagent has ended — steering unavailable",
81
+ "viewer.steer.title": "Steer {label}",
82
+ "viewer.steer.hint": "Delivered as a plugin-sourced user message.",
83
+ "viewer.title": "● Sub-agents",
84
+ "viewer.col.subagent": "Sub-agent",
85
+ "viewer.col.stats": "Stats",
86
+ "viewer.pickerFooter": "↑↓ navigate · Enter open · s sort · K stop all · Esc close",
87
+ "viewer.truncated": "…history truncated ({cap} event cap)",
88
+ "viewer.hardStopped": "⏻ hard-stopped by maxRounds policy (round {round} + grace exhausted, cap {cap})",
89
+ "viewer.noEvents": " — no events yet —",
90
+ "viewer.canceling": "⏹ canceling this subagent…",
91
+ "viewer.stopAllOne": "⏹ stop-all: 1 subagent canceled",
92
+ "viewer.stopAllMany": "⏹ stop-all: {n} subagents canceled",
93
+ "viewer.header.child": "ⓘ subagent {id}",
94
+ "viewer.status.running": "running",
95
+ "viewer.status.completed": "completed",
96
+ "viewer.status.failed": "failed",
97
+ "viewer.status.cancelled": "cancelled",
98
+ "viewer.route.think": "think {effort}",
99
+ "viewer.rounds": "rounds {count}{cap}",
100
+ "viewer.hardStoppedAt": "⏻ hard-stopped @{round}",
101
+ "viewer.injected": "⚡ injected",
102
+ "viewer.stopped": "⏻ stopped",
103
+ "viewer.event.turnStart": "– turn {n} start",
104
+ "viewer.event.turnEnd": "– turn {n} end · {detail}",
105
+ "viewer.event.error": "error",
106
+ "viewer.event.errorDetail": "error: {message}",
107
+ "viewer.event.subagent": "subagent",
108
+ "viewer.event.todos": "☑ todos {done}/{total}: {summary}",
109
+ "agentsmg.error.integer": "expected a non-negative integer, got \"{text}\"",
110
+ "agentsmg.inherit": "(inherit)",
111
+ "agentsmg.inheritWord": "inherit",
112
+ "agentsmg.model.title": "● Model",
113
+ "agentsmg.picker.colModel": "Model",
114
+ "agentsmg.picker.colProvider": "Provider",
115
+ "agentsmg.openFailed": "✘ failed to open the agents view: {message}",
116
+ "agentsmg.title": "● Agents",
117
+ "agentsmg.col.name": "name",
118
+ "agentsmg.col.model": "model",
119
+ "agentsmg.col.deep": "deep",
120
+ "agentsmg.col.description": "description",
121
+ "agentsmg.tableFooter": "↑↓ navigate · Enter open · Esc back · l limits",
122
+ "agentsmg.seededNote": " (seeded {n} from {dir})",
123
+ "agentsmg.updated": "Agents updated: {list}{note}",
124
+ "agentsmg.seededOnly": "Seeded {n} agent(s) from {dir} — no changes.",
125
+ "agentsmg.noDescription": "(no description)",
126
+ "agentsmg.target.profile": "model/think edits apply to profile \"{name}\" (workspace-scoped — the frontmatter baseline stays untouched)",
127
+ "agentsmg.target.frontmatter": "no workspace profile pin — edits write the frontmatter baseline (global)",
128
+ "agentsmg.inheritDefault": "(inherit — default model)",
129
+ "agentsmg.fieldsFooter": "↑↓ field · Enter edit (m model · t think · d deep) · v full prompt · Esc back",
130
+ "agentsmg.savedModel": "saved {name} → {model}{think}{scope}",
131
+ "agentsmg.thinkSuffix": " · think {effort}",
132
+ "agentsmg.scopeNote": " (profile \"{name}\")",
133
+ "agentsmg.error.noModel": "✘ no model set — pick a model first (m)",
134
+ "agentsmg.error.noLlm": "✘ llm service unavailable",
135
+ "agentsmg.error.noThinkLevels": "✘ cannot resolve think levels for {model}",
136
+ "agentsmg.error.modelNoThink": "✘ {model} exposes no think levels",
137
+ "agentsmg.savedThink": "saved {name} → think {effort}{scope}",
138
+ "agentsmg.deep.title": "Max spawn depth — {name}",
139
+ "agentsmg.deep.subtitle": "0 = this agent never spawns subagents · no unlimited",
140
+ "agentsmg.deep.saved": "max depth {n} saved — takes effect when this agent is spawned",
141
+ "agentsmg.deep.status": "saved {name} → max depth {n}",
142
+ "agentsmg.body.title": "ⓘ {name} · system prompt",
143
+ "agentsmg.body.footer": " Esc to close",
144
+ "agentsmg.limits.maxAgents": "{n} · concurrent live children (0 = unlimited)",
145
+ "agentsmg.limits.maxRounds": "{n} · assistant messages before wrap-up (0 = unlimited)",
146
+ "agentsmg.limits.maxRoundsGrace": "{n} · wrap-up rounds before force-stop (0 = warn only)",
147
+ "agentsmg.limits.disableSubagent": "{value} · native subagent tool",
148
+ "agentsmg.limits.registeredOnly": "{value} · only use_agent may spawn (subagent/fork/workflow/ralph fenced)",
149
+ "agentsmg.on": "on",
150
+ "agentsmg.off": "off",
151
+ "agentsmg.limits.title": "⚙ subagent",
152
+ "agentsmg.limits.footer": "↑↓ field · Enter edit (0 = unlimited) · d toggle subagent · r registered-only · Esc back",
153
+ "agentsmg.limits.intro": "subagent delegation knobs — read live at every spawn / turn decision",
154
+ "agentsmg.limits.runtime": "runtime: {live} live · {allowed} admitted · {denied} denied · {pruned} pruned · {inFlight} in-flight (since TUI start)",
155
+ "agentsmg.limits.noAgents": "no agents in {dir} yet — drop a markdown file to define one",
156
+ "agentsmg.limits.broken": "{n} broken file(s) ignored",
157
+ "agentsmg.limit.title": "{key} — subagent delegation limit (current {current})",
158
+ "agentsmg.limit.subtitle": "non-negative integer · 0 = unlimited · empty keeps the current value",
159
+ "agentsmg.limit.saved": "{key} → {n} saved — applies to future subagent spawns",
160
+ "agentsmg.subagent.disabled": "subagent tool disabled — applies to future subagent spawns",
161
+ "agentsmg.subagent.enabled": "subagent tool enabled — applies to future subagent spawns",
162
+ "agentsmg.fence.on": "registered-only fence ON — only use_agent may spawn",
163
+ "agentsmg.fence.off": "registered-only fence off — ad-hoc spawn tools available",
164
+ "queue.footer": "↑↓ select · d remove · s steer now · Esc close",
165
+ "queue.refreshFailed": "Queue status refresh failed — the list below may be stale.",
166
+ "queue.target.steer": "↪ steer",
167
+ "queue.target.queued": "⏳ queued",
168
+ "queue.title": "Pending messages",
169
+ "queue.empty": "— no pending messages —",
170
+ "skillsmgr.reason.notSymlink": "not a symlink",
171
+ "skillsmgr.reason.differentSource": "different source",
172
+ "skillsmgr.reason.sourceMissing": "source missing",
173
+ "skillsmgr.reason.destMissing": "dest missing",
174
+ "skillsmgr.reason.failed": "failed",
175
+ "skillsmgr.summary.more": " +{n} more",
176
+ "skillsmgr.summary.ok": "ok: {list}",
177
+ "skillsmgr.summary.applied": "Applied {ok}/{total} — {okPart}failed: {failed}",
178
+ "skillsmgr.title": "⚙ Skills Manager",
179
+ "skillsmgr.unsaved": " ({n} unsaved)",
180
+ "skillsmgr.noMatches": "No matches for '{query}'",
181
+ "skillsmgr.col.on": "On",
182
+ "skillsmgr.col.skill": "Skill",
183
+ "skillsmgr.footer.confirm": "Discard unsaved changes? Y/N",
184
+ "skillsmgr.footer.filter": "Filter: {query} · Backspace clear · Esc clear filter",
185
+ "skillsmgr.footer.dirty": "↑↓ nav · Space toggle · Enter apply · Esc discard",
186
+ "skillsmgr.footer.idle": "↑↓ nav · PgUp/PgDn page · Home/End jump · Space toggle · Enter apply · Esc back",
187
+ "skillsmgr.scanning": "Scanning public skills…",
188
+ "skillsmgr.empty": "No public skills found in ~/.agents/skills/.",
189
+ "skillsmgr.readError": "Could not read ~/.agents/skills/.",
190
+ "skills.error.cannotRead": "cannot read skill file: {path}",
191
+ "skills.error.missingFrontmatter": "missing frontmatter (file must start with `---`)",
192
+ "skills.error.missingCloseFence": "missing closing frontmatter fence",
193
+ "skills.error.cannotWrite": "cannot write skill file: {message}",
194
+ "btw.error.noQuestion": "No question after /btw --model.",
195
+ "btw.error.invalidModel": "Invalid --model \"{model}\" — expected provider/model.",
196
+ "btw.queueFull": "the btw queue is full ({cap})",
197
+ "btw.error.unknownStream": "Unknown model stream error.",
198
+ "btw.error.maxTokens": "The answer hit the output token cap.",
199
+ "btw.error.toolCalls": "The side model unexpectedly requested tools.",
200
+ "btw.error.unsupportedFinish": "Unsupported stream finish: {kind}",
201
+ "btw.error.noFinish": "The model stream ended without a finish chunk.",
202
+ "btw.error.noModel": "No model selected — btw cannot run.",
203
+ "btw.error.modelUnavailable": "btw model \"{model}\" is not available.",
204
+ "btw.background": "btw running in the background — /btw to view the answer.",
205
+ "btw.failed": "btw failed: {message}",
206
+ "btw.notice.idle": "/btw answers alongside a running turn — the main line is idle, so just ask directly.",
207
+ "btw.notice.usage": "Usage: /btw <question> — ask a side question while the main task runs. The answer streams into a temporary overlay and is not kept in the session.",
208
+ "misc.attachment.loading": "🖼 {name} — loading ({width}×{height})",
209
+ "misc.attachment.more": "🖼 +{n} more images not shown",
210
+ "misc.attachment.unavailable": "🖼 {name} — unavailable{code}",
211
+ "misc.todos.title": "● Todos ",
212
+ "misc.todos.showing": " · showing {ranges}",
213
+ "misc.todos.task": "Task",
214
+ "misc.agent.rounds": "round {n}{cap}",
215
+ "misc.agent.fallbackName": "subagent",
216
+ "misc.pending.queued": "⏳ queued",
217
+ "misc.pending.steer": "↪ steer",
218
+ "misc.pending.canceled": "✕ canceled",
219
+ "misc.pending.notDelivered": "✘ not delivered",
220
+ "misc.turnFailed": "turn failed",
221
+ "misc.interrupted": "interrupted",
222
+ "misc.tokenLimit": "⚠ output token limit reached",
223
+ "stop.confirm.title": "● Stop all LLM work?",
224
+ "stop.confirm.mainRunning": "the main turn is generating",
225
+ "stop.confirm.childrenOne": "{count} subagent is running",
226
+ "stop.confirm.childrenMany": "{count} subagents are running",
227
+ "stop.confirm.joiner": " and ",
228
+ "stop.confirm.situation": "Right now {parts}.",
229
+ "stop.confirm.idle": "Nothing is running right now.",
230
+ "stop.confirm.reassure": "Confirming cancels the main turn and every running subagent. Queued messages are kept and the session stays resumable.",
231
+ "stop.option.stop": "Stop everything — cancel the main turn and all subagents",
232
+ "stop.option.cancel": "Cancel — keep everything running",
233
+ "stop.footer": "↑↓ select · 1/2 pick · Enter confirm · Esc keep running",
234
+ "repair.confirm.title": "● Corrupted resume log",
235
+ "repair.confirm.body": "Resume log is corrupted (likely from a historical double-writer). Repair it in place? The original is kept as .corrupt-bak.",
236
+ "repair.option.repair": "Repair & resume",
237
+ "repair.option.repairHint": "rebuild the log in place, then enter the session",
238
+ "repair.option.cancel": "Cancel",
239
+ "repair.option.cancelHint": "leave the log untouched",
240
+ "repair.footer": "↑↓ select · 1/2 pick · Enter confirm · Esc cancel",
241
+ "presetdlg.title.switch": "● Switch preset to {name}?",
242
+ "presetdlg.title.restart": "● Restart session on {name}?",
243
+ "presetdlg.fork.switch": "Fork & switch — new session on {name}, carrying this conversation",
244
+ "presetdlg.fork.restart": "Fork & restart — new session on {name}, carrying this conversation",
245
+ "presetdlg.fresh.switch": "Fresh start — new empty session on {name}",
246
+ "presetdlg.fresh.restart": "Restart now — new empty session on {name}",
247
+ "presetdlg.point.switch": "Switching starts a NEW session on {name}.",
248
+ "presetdlg.point.restart": "Restarting starts a NEW session on {name}.",
249
+ "presetdlg.option.cancel": "Cancel — stay on the current session",
250
+ "presetdlg.carry": "Fork carries this conversation into the new session (compacted context included) — fresh starts empty.",
251
+ "presetdlg.footer": "↑↓ select · 1/2/3 pick · Enter confirm · Esc cancel",
252
+ "presetdlg.echo.unchanged": "Preset unchanged — still on the current session.",
253
+ "presetdlg.echo.forked": "Preset → {name} — new session forked from this conversation.",
254
+ "presetdlg.echo.fresh": "Preset → {name} — new empty session started on it.",
255
+ "presetdlg.echo.direct": "Preset → {name} — new session started on it.",
256
+ "route.title": "Agent is running — route your message",
257
+ "route.preview.empty": "(empty message)",
258
+ "route.option.followup": "Queue as follow-up",
259
+ "route.option.followupHint": "delivered when the current turn ends",
260
+ "route.option.steer": "Steer now",
261
+ "route.option.steerHint": "injected at the next step boundary",
262
+ "route.footer": "↑↓ select · 1/2 pick · Enter confirm · Esc cancel",
263
+ "fork.title": "● Fork at turn {turn}?",
264
+ "fork.body.carry": "The new session carries turns through {turn} (of {total}); later turns stay in the current session.",
265
+ "fork.body.resume": "The current session stays resumable via /resume.",
266
+ "fork.body.detach": "Your live session {id} will be detached — it stays resumable via /resume.",
267
+ "fork.option.fork": "Fork now — new session through turn {turn}",
268
+ "fork.option.cancel": "Cancel",
269
+ "fork.footer": "↑↓ select · 1/2 pick · Enter confirm · Esc cancel",
270
+ "steer.notice.unavailable": "Steering was unavailable — the message stayed queued as a follow-up.",
271
+ "steer.notice.turnEnded": "The turn ended before the steer could land — queued as a follow-up instead.",
272
+ "steer.notice.removed": "Removed from the pending queue.",
273
+ "steer.notice.promoted": "Promoted — steering the current turn now.",
274
+ "steer.notice.notFound": "Already claimed or removed — list refreshed.",
275
+ "steer.notice.error": "✘ {error}",
276
+ "steer.notice.orphaned": "{error} — the message left the queue but was NOT delivered; submit it again.",
277
+ "history.footer": "↑↓ navigate · Enter/c copy · f fork · → detail · s session · / filter · Esc close",
278
+ "history.noText": "(no text)",
279
+ "history.listTitle": "● History · {id}",
280
+ "history.liveTag": "(live)",
281
+ "history.moreLines": "… +{count} more lines",
282
+ "history.turnFailed": "turn failed",
283
+ "history.interrupted": "interrupted",
284
+ "history.maxTokens": "output token limit reached",
285
+ "history.truncated": "… {count} more lines truncated — {pointer}for the full turn",
286
+ "history.emptyTurn": "(this turn rendered no prompt or reply text)",
287
+ "history.title": "History",
288
+ "history.turnTitle": "Turn {turn}",
289
+ "history.liveSnapshot": "live snapshot",
290
+ "history.selectTurn": "Select a turn on the left.",
291
+ "history.lineOne": "{count} line",
292
+ "history.lineMany": "{count} lines",
293
+ "history.linesRange": "{start}–{end}/{total} lines",
294
+ "history.detailFooterFocused": "← list · ↑↓ scroll · [ / ] page · {window}",
295
+ "history.detailFooterIdle": "[ / ] page · {window}",
296
+ "history.corruptLog": "⚠ {id}: corrupt session log — /resume {id} offers a repair.",
297
+ "history.cannotRead": "Cannot read {id}: {message}",
298
+ "history.noPersistence": "Session persistence is not configured in this profile.",
299
+ "history.noCwd": "no cwd",
300
+ "history.col.turn": "Turn",
301
+ "history.col.prompt": "Prompt",
302
+ "history.col.updated": "Updated",
303
+ "history.col.dir": "Dir",
304
+ "history.col.session": "Session",
305
+ "history.empty": "No completed turns",
306
+ "history.closed": "History closed.",
307
+ "history.nothingToCopy": "Nothing to copy — the turn has no user prompt.",
308
+ "history.copied": "Prompt copied to the editor.",
309
+ "history.pickerTitle": "● Browse session",
310
+ "history.pickerFooter": "↑↓ navigate · Enter browse · / filter · Esc back",
311
+ "history.noMatching": "No matching sessions",
312
+ "history.forked": "Forked at turn {turn} — new session opened.",
313
+ "history.forkFailed": "Fork failed: {message}",
314
+ "history.forkAtTurnFailed": "Fork at turn {turn} failed: {message}",
315
+ "history.noStored": "No stored sessions to browse.",
316
+ "history.noActive": "No active session — use /history <sessionId> to browse a stored one.",
317
+ "history.openFailed": "Failed to open the history viewer.",
318
+ "turns.toolCallOne": "tool call",
319
+ "turns.toolCallMany": "tool calls",
320
+ "resume.title": "● Resume session",
321
+ "resume.headingTitle": "ⓘ {title} · tokens: current route",
322
+ "resume.headingPlain": "ⓘ session · tokens: current route",
323
+ "resume.empty": "no active session — send a prompt or /resume one",
324
+ "resume.field.session": "session",
325
+ "resume.field.cwd": "cwd",
326
+ "resume.field.created": "created",
327
+ "resume.field.model": "model",
328
+ "resume.field.think": "think",
329
+ "resume.field.status": "status",
330
+ "resume.field.messages": "messages",
331
+ "resume.field.toolcalls": "tool calls",
332
+ "resume.field.tokensin": "tokens in",
333
+ "resume.field.tokensout": "tokens out",
334
+ "resume.field.cacheread": "cache read",
335
+ "resume.field.cachewrite": "cache write",
336
+ "resume.field.events": "events",
337
+ "resume.field.parent": "parent",
338
+ "resume.col.field": "Field",
339
+ "resume.col.value": "Value",
340
+ "resume.col.updated": "Updated",
341
+ "resume.col.dir": "Dir",
342
+ "resume.col.session": "Session",
343
+ "resume.hintEdit": "Enter save · Esc cancel",
344
+ "resume.renameEmpty": "rename ▸ {cursor} (type a title)",
345
+ "resume.renameEdit": "rename ▸ {buffer}{cursor}",
346
+ "resume.hintRename": "r rename · Esc to close",
347
+ "resume.hintClose": "Esc to close",
348
+ "resume.titleEmpty": "title is empty",
349
+ "resume.noBackend": "Session persistence exposes neither open() nor inspect().",
350
+ "resume.noPersistence": "Session persistence is not configured in this profile.",
351
+ "resume.settingInvalid": "settings dsh-tui.resume.{key}: invalid value {value} — falling back to environment/default",
352
+ "resume.listFailed": "Failed to list persisted sessions: {message}",
353
+ "resume.noCwd": "no cwd",
354
+ "tui.esc.armStop": "Press Esc again to stop all LLM work",
355
+ "tui.esc.quitArmed": "{icon} Ctrl+C ×2 — quitting…",
356
+ "tui.esc.quitAborted": "quit aborted — Ctrl+C was held, not double-pressed",
357
+ "tui.stop.stopping": "{icon} stopping all LLM work…",
358
+ "tui.stop.nothingRunning": "Nothing running to cancel.",
359
+ "tui.keys.warningPrefix": "keybindings: {warning}",
360
+ "tui.status.working": "working…",
361
+ "tui.takeover.writeLock": "Write lock acquired — follow-ups sent.",
362
+ "tui.session.none": "No active session.",
363
+ "tui.session.infoShown": "Session info shown.",
364
+ "tui.session.renamed": "renamed to \"{title}\"",
365
+ "tui.session.titleEmpty": "title normalizes to empty",
366
+ "tui.session.effortDefault": "provider default",
367
+ "tui.btw.noLlm": "LLM service is not available.",
368
+ "tui.btw.backOnScreen": "btw — the running answer is back on screen.",
369
+ "tui.btw.lastShown": "Last btw exchange shown.",
370
+ "tui.btw.started": "btw — answering alongside the main task.",
371
+ "tui.btw.queued": "btw queued (position {position}).",
372
+ "tui.btw.rejected": "btw rejected — {reason}.",
373
+ "tui.model.unchanged": "Model unchanged.",
374
+ "tui.agents.unchanged": "Agents unchanged.",
375
+ "tui.subagents.viewerClosed": "Subagent viewer closed.",
376
+ "tui.model.selected": "Model: {id}",
377
+ "tui.model.selectedThink": "Model: {id} · think {effort}",
378
+ "tui.persist.failed": "{text} · ⚠ not persisted: {error}",
379
+ "tui.think.noModel": "No model selected — pick one with /model first.",
380
+ "tui.think.unsupported": "{id} exposes no selectable think levels.",
381
+ "tui.think.unchanged": "Think level unchanged.",
382
+ "tui.think.providerDefault": "Think: provider default ({id}).",
383
+ "tui.think.set": "Think: {effort} ({id}).",
384
+ "tui.preset.memorySaveFailed": "preset memory not saved: {error}",
385
+ "tui.preset.none": "No agent presets available.",
386
+ "tui.preset.unknown": "Unknown preset: {name}",
387
+ "tui.preset.unchanged": "Preset unchanged.",
388
+ "tui.resume.noneOther": "No other persisted sessions to resume.",
389
+ "tui.resume.windowEmpty": "No sessions within the resume window ({days}d, ≥{floor}) — adjust dsh-tui.resume.* to see more.",
390
+ "tui.resume.cancelled": "Resume cancelled.",
391
+ "tui.resume.watching": "Watching {id} (read-only · driven by another process). /resume or /new to switch.",
392
+ "tui.resume.watchFailed": "Cannot watch {id} read-only: {error}",
393
+ "tui.resume.failed": "Resume failed: {error} — the previous session was closed; the next prompt starts a new one.",
394
+ "tui.resume.done": "Resumed {id} · {count} events.",
395
+ "tui.resume.cannot": "Cannot resume {id}: {error}",
396
+ "tui.repair.logMissing": "repair failed: cannot locate the log of {id} on disk — log untouched",
397
+ "tui.repair.failed": "repair failed: {error} — log untouched",
398
+ "tui.boot.resumeFailed": "--resume {id}: {error} — starting fresh.",
399
+ "tui.export.noSession": "No active session to export.",
400
+ "tui.export.done": "Exported {count} events → {path}",
401
+ "tui.new.done": "New session started.",
402
+ "tui.settings.unavailable": "Settings service is not available.",
403
+ "tui.settings.noNamespaces": "No settings namespaces are registered.",
404
+ "tui.settings.noChanges": "Settings: no changes.",
405
+ "tui.settings.appliedOne": "Settings: {count} change applied.",
406
+ "tui.settings.appliedMany": "Settings: {count} changes applied.",
407
+ "tui.skills.opened": "Skills manager opened.",
408
+ "tui.theme.unchanged": "Theme unchanged.",
409
+ "tui.theme.pinned": "Theme preference saved — display is pinned by DSH_TUI_THEME={value}",
410
+ "tui.theme.applied": "Theme: {name} — applied.",
411
+ "tui.hotkeys.unchanged": "Keybindings unchanged.",
412
+ "tui.login.unknownProvider": "Unknown provider '{name}' — run /login to pick from the directory.",
413
+ "tui.login.cancelled": "Login cancelled.",
414
+ "tui.login.configured": "Provider {name} configured.",
415
+ "tui.logout.none": "No stored credentials to remove.",
416
+ "tui.logout.cancelled": "Logout cancelled.",
417
+ "tui.logout.failed": "Failed to remove stored API key for {name}{cause}.",
418
+ "tui.logout.incomplete": "Removed the API key for {name}, but its provider configuration stays: {error} — remove the provider in /settings to finish the logout.",
419
+ "tui.logout.keyOnly": "Removed the API key for {name}. Hand-declared route — its configuration stays; remove the provider in /settings to also drop its models.",
420
+ "tui.logout.done": "Logged out {name} — API key and provider configuration removed.",
421
+ "tui.permission.unchanged": "Permission unchanged.",
422
+ "tui.steer.queuedFollowup": "Queued follow-up — sends automatically once the write lock frees up.",
423
+ "tui.steer.noSession": "No active session — the message was not delivered.",
424
+ "tui.exit.resumeHint": "To resume this session: {command}",
425
+ "tui.cmd.btw.description": "Ask a side question while the main task runs (temp overlay, not kept)",
426
+ "tui.cmd.model.description": "Select the model (and think level) for this conversation",
427
+ "tui.cmd.agents.description": "Manage agent definitions (model, think level, spawn depth) from markdown files",
428
+ "tui.cmd.subagents.description": "Browse subagents and inspect their live transcript",
429
+ "tui.cmd.think.description": "Switch the current model's think (reasoning) level",
430
+ "tui.cmd.preset.description": "Switch the agent preset (starts a new session on it; /preset next cycles)",
431
+ "tui.cmd.session.description": "Show the current session's info (id, title, stats) and rename it (r)",
432
+ "tui.cmd.resume.description": "Resume a persisted session",
433
+ "tui.cmd.history.description": "Browse past turns of a session (read-only; /history <id> for a stored one)",
434
+ "tui.cmd.export.description": "Export this session log as JSONL",
435
+ "tui.cmd.new.description": "Start a new session",
436
+ "tui.cmd.settings.description": "Browse and edit configuration (namespaces, values, resets)",
437
+ "tui.cmd.skills.description": "Manage user skills (installed and available)",
438
+ "tui.cmd.theme.description": "Set the terminal color scheme (applies immediately)",
439
+ "tui.cmd.reload.description": "Reload the TUI from the current source (apply code changes without restarting dsh)",
440
+ "tui.cmd.hotkeys.description": "Show the current keybindings (custom file: ~/.dsh/keybindings.json)",
441
+ "tui.cmd.login.description": "Register provider credentials (API key) through the provider directory",
442
+ "tui.cmd.logout.description": "Log out a provider (removes its API key and provider configuration)",
443
+ "settingsui.group.subagent": "Subagent",
444
+ "settingsui.title": "⚙ settings",
445
+ "settingsui.category.dsh-tui": "TUI",
446
+ "settingsui.category.general": "General",
447
+ "settingsui.category.models": "Models",
448
+ "settingsui.category.plugins": "Plugins",
449
+ "settingsui.category.agent": "Agent Presets",
450
+ "settingsui.category.other": "Other",
451
+ "settingsui.summary.namespaces": "{count} namespaces",
452
+ "settingsui.fields.count": "{count} fields",
453
+ "settingsui.ns.applies": "applies: {applies}",
454
+ "settingsui.value.unset": "(unset)",
455
+ "settingsui.value.items": "[{count} items]",
456
+ "settingsui.value.keys": "{count} keys",
457
+ "settingsui.dict.one": "dict · {count} entry",
458
+ "settingsui.dict.many": "dict · {count} entries",
459
+ "settingsui.meta.required": "required",
460
+ "settingsui.meta.default": "default: {value}",
461
+ "settingsui.meta.min": "min: {value}",
462
+ "settingsui.meta.max": "max: {value}",
463
+ "settingsui.meta.step": "step: {value}",
464
+ "settingsui.meta.pattern": "pattern: {value}",
465
+ "settingsui.meta.secret": "secret",
466
+ "settingsui.meta.disabled": "disabled",
467
+ "settingsui.meta.userSet": "user-set",
468
+ "settingsui.edit.footer": "Enter save · Esc back",
469
+ "settingsui.edit.secret": "secret — leave empty to keep the current value",
470
+ "settingsui.edit.current": "current: {value}",
471
+ "settingsui.parse.number": "expected a number, got \"{text}\"",
472
+ "settingsui.parse.json": "expected a JSON value (number, boolean, string, …)",
473
+ "settingsui.parse.type": "cannot edit {type} value",
474
+ "settingsui.reset.working": "resetting…",
475
+ "settingsui.reset.hints": "Enter: reset to defaults · Esc: cancel",
476
+ "settingsui.reset.namespace": "Reset this namespace to defaults",
477
+ "settingsui.reset.path": "Reset {path} to defaults",
478
+ "settingsui.reset.ns": "Reset \"{ns}\" to defaults",
479
+ "settingsui.addKey.label": "+ Add key…",
480
+ "settingsui.addKey.title": "+ key in {path}",
481
+ "settingsui.addKey.subtitle": "default: {default} · Enter to add · Esc to cancel",
482
+ "settingsui.addKey.empty": "key must not be empty",
483
+ "settingsui.addKey.exists": "key \"{key}\" already exists",
484
+ "settingsui.addProvider.title": "⚙ Add provider",
485
+ "settingsui.addProvider.empty": "All built-in providers are already configured.",
486
+ "settingsui.escClose": "Esc to close",
487
+ "settingsui.addProvider.keyTitle": "API key for {name}",
488
+ "settingsui.addProvider.keySubtitle": "stored as {ref} — never written to settings.yaml",
489
+ "settingsui.addProvider.keyRequired": "API key must not be empty",
490
+ "settingsui.commit.noCredentials": "provider added — no credentials service in this process: export {ref} to use it",
491
+ "settingsui.commit.keyFailed": "API key not stored: {cause} — provider added; export {ref}=<key> to use it",
492
+ "settingsui.models.title": "⚙ Models",
493
+ "settingsui.models.readonly": "read-only in the TUI — edit the settings document to change it",
494
+ "settingsui.models.deepseek": "DeepSeek (official)",
495
+ "settingsui.models.defaultModel": "Default model",
496
+ "settingsui.models.add": "+ Add provider…",
497
+ "settingsui.models.addDesc": "configure a built-in provider with its API key",
498
+ "settingsui.route.think": "{provider}/{model} · think {effort}",
499
+ "themepv.title": "● Theme",
500
+ "themepv.col.theme": "Theme",
501
+ "themepv.footer": "↑↓ navigate · Enter select · Esc back",
502
+ "themepv.desc.auto": "follow the terminal light/dark signal",
503
+ "themepv.desc.githubLight": "GitHub light palette",
504
+ "themepv.desc.githubDark": "GitHub dark palette",
505
+ "themepv.origin.user": "user theme",
506
+ "themepv.origin.builtin": "built-in",
507
+ "themepv.auto.follows": "Theme follows the terminal.",
508
+ "themepv.auto.pick": "Pick light/dark or a named theme to preview it.",
509
+ "themepv.input.placeholder": "Type a message…",
510
+ "sel.effort.default": "(provider default)",
511
+ "sel.effort.defaultDesc": "adapter default behavior — clears the effort override",
512
+ "sel.col.description": "Description",
513
+ "sel.col.effort": "Effort",
514
+ "sel.col.language": "Language",
515
+ "sel.col.model": "Model",
516
+ "sel.col.provider": "Provider",
517
+ "sel.col.preset": "Preset",
518
+ "sel.title.effort": "● Reasoning effort",
519
+ "sel.title.language": "● Language",
520
+ "sel.title.model": "● Model",
521
+ "sel.title.permission": "● Permission preset",
522
+ "sel.title.preset": "● Agent preset",
523
+ "sel.hidden.header": "─ Hidden ({count}) ─",
524
+ "sel.footer.model": "↑↓ navigate · Enter select · f favorite · h hide · / filter · Esc back",
525
+ "sel.status.saveFailed": "✘ save failed: {message}",
526
+ "sel.preset.shipped": "shipped preset",
527
+ "sel.preset.user": "user preset",
528
+ "modelprof.error.nameEmpty": "profile name must not be empty",
529
+ "modelprof.error.exists": "a profile named \"{name}\" already exists",
530
+ "modelprof.error.notFound": "no profile named \"{name}\"",
531
+ "modelprof.error.lastProfile": "the last profile cannot be deleted",
532
+ "modelprof.error.pinEmpty": "no profile name in file",
533
+ "modelprof.error.pinOverwrite": "refusing to overwrite {file} — edited by hand (switch applied live only)",
534
+ "modelprof.error.pinRemove": "refusing to remove {file} — edited by hand (remove it yourself)",
535
+ "modelprof.route.notSet": "(not set)",
536
+ "modelprof.route.plain": "{provider}/{model}",
537
+ "modelprof.route.think": "{provider}/{model} · think {effort}",
538
+ "modelprof.review.profile": "Profile: {name}",
539
+ "modelprof.review.current": "current",
540
+ "modelprof.review.defaultModel": "Default model: {route}",
541
+ "modelprof.review.none": "Agents: none recorded — pick models under /profile-cfg, or press s to save the current configuration",
542
+ "modelprof.review.agents": "Agents ({count}):",
543
+ "modelprof.review.notSaved": "(not saved — apply leaves untouched)",
544
+ "modelprof.review.inherit": "(inherit)",
545
+ "modelprof.review.inheritWord": "inherit",
546
+ "modelprof.review.think": "think {effort}",
547
+ "modelprof.review.fileMissing": "file missing",
548
+ "custprov.entry.name": "Custom provider…",
549
+ "custprov.entry.hint": "base URL + API key + models — any OpenAI/Anthropic-compatible gateway",
550
+ "custprov.hint.declared": "hand-declared route",
551
+ "custprov.step.title": "Custom provider {current}/{total} · {label}",
552
+ "custprov.step.id": "Provider id",
553
+ "custprov.step.idHint": "route key for settings.yaml — also derives the credential ref (e.g. acme-gateway → ACME_GATEWAY_API_KEY)",
554
+ "custprov.step.displayName": "Display name (optional)",
555
+ "custprov.step.displayNameHint": "shown in /model and Models — empty uses the route id",
556
+ "custprov.step.protocol": "API protocol",
557
+ "custprov.step.protocolHint": "one of {list} — empty defaults to {default}",
558
+ "custprov.step.baseUrl": "Base URL",
559
+ "custprov.step.baseUrlHint": "the gateway endpoint, e.g. https://gateway.internal.example/v1",
560
+ "custprov.step.models": "Models",
561
+ "custprov.step.modelsHint": "comma-separated wire model ids, e.g. acme-large, acme-think",
562
+ "custprov.step.apiKey": "API key",
563
+ "custprov.step.apiKeyHint": "stored as the derived ref — never written to settings.yaml",
564
+ "custprov.error.idEmpty": "Provider id must not be empty",
565
+ "custprov.error.idLength": "Provider id must be 2–40 characters",
566
+ "custprov.error.idChars": "Use lowercase letters, digits and dashes only (no dots, no spaces)",
567
+ "custprov.error.idTaken": "\"{id}\" already exists — pick another route id",
568
+ "custprov.error.nameLength": "Display name must be at most 40 characters",
569
+ "custprov.error.protocol": "Protocol must be one of: {list}",
570
+ "custprov.error.baseUrlEmpty": "Base URL must not be empty",
571
+ "custprov.error.baseUrlFormat": "Base URL must start with http:// or https:// and contain no spaces",
572
+ "custprov.error.modelsEmpty": "List at least one model id (comma-separated)",
573
+ "custprov.error.modelLength": "Each model id must be at most 80 characters",
574
+ "custprov.error.modelsMax": "At most 50 models in the initial list",
575
+ "custprov.error.keyEmpty": "API key must not be empty",
576
+ "provcatalog.hint.apiKey": "API key",
577
+ "provcatalog.hint.aws": "AWS credentials or bearer token",
578
+ "provcatalog.hint.copilot": "GitHub Copilot token",
579
+ "provcatalog.hint.googleCloud": "Google Cloud credentials",
580
+ "provcatalog.hint.huggingface": "Hugging Face token",
581
+ "provcatalog.hint.apiKey.gateway": "API key · OpenAI-compatible gateway",
582
+ "provcatalog.status.set": "API key set",
583
+ "provcatalog.status.missing": "API key missing",
584
+ "provcatalog.status.notConfigured": "API key not configured",
585
+ "login.title.logout": "● Log out",
586
+ "login.col.provider": "Provider",
587
+ "login.col.keyRef": "Key ref",
588
+ "login.error.removeFailed": "Failed to remove stored key for {name}{cause}",
589
+ "login.error.removeIncomplete": "Removed the stored key for {name}, but its provider configuration was not removed: {error}",
590
+ "footer.hint.default": "⌨ Enter: send · Esc ×2: stop · Ctrl+C ×2: quit · Ctrl+D: quit (empty) · Ctrl+G: subagents · Ctrl+Shift+F: search · /preset: switch · ↑↓: history",
591
+ "footer.hint.send": "Enter: send",
592
+ "footer.hint.stop": "Esc ×2: stop",
593
+ "footer.hint.quit": "Ctrl+C ×2: quit",
594
+ "footer.hint.quitEmpty": "Ctrl+D: quit (empty)",
595
+ "footer.hint.subagents": "Ctrl+G: subagents",
596
+ "footer.hint.search": "Ctrl+Shift+F: search",
597
+ "footer.hint.preset": "/preset: switch",
598
+ "footer.hint.history": "↑↓: history",
599
+ "footer.seg.msgs": "💬 {count} msgs",
600
+ "footer.seg.tools": "🔧 {count} tools",
601
+ "panel.empty.models": "No matching models",
602
+ "panel.empty.settings": "No matching settings",
603
+ "panel.filter.input": "Filter: {query}_",
604
+ "panel.filter.active": "Filter: {query}",
605
+ "panel.filter.hint": "Filter: {query} · Backspace clear · Esc clear filter",
606
+ "panel.footer.filtering": "Enter apply · Esc clear filter",
607
+ "panel.footer.navigate": "↑↓ navigate · Enter select · Esc back",
608
+ "panel.footer.search": "↑↓ navigate · Enter select · Type to search · Esc back",
609
+ "panel.footer.field": "↑↓ field · Enter edit · Esc back",
610
+ "panel.col.field": "Field",
611
+ "panel.col.value": "Value",
612
+ "panel.col.setting": "Setting",
613
+ "panel.viewer.more": "… {count} more line(s)",
614
+ "panel.viewer.close": "Esc to close",
615
+ "startup.plugin.disabled": "{label} (disabled)",
616
+ "startup.tree.base": "└─ dsh-base ({count})",
617
+ "startup.summary": "mcp {mcp} · skills {installed}/{total} · plugins {plugins}",
618
+ "hotkeys.title": "⚙ hotkeys",
619
+ "hotkeys.updated": "Keybindings updated.",
620
+ "hotkeys.openFailed": "✘ failed to open the hotkeys view: {message}",
621
+ "hotkeys.footer": "↑↓ key · Enter change · Esc close",
622
+ "hotkeys.action.escape": "stop the current task — requires two presses (1st arms, 2nd within 500ms fires)",
623
+ "hotkeys.action.ctrlC": "running: cancel turn · idle: clear editor · second press quits",
624
+ "hotkeys.action.ctrlD": "quit — only on an empty editor",
625
+ "hotkeys.action.modelPicker": "open the model / think picker",
626
+ "hotkeys.action.subagentViewer": "open the subagent picker / viewer",
627
+ "hotkeys.action.queuePanel": "open the pending-message queue (d remove · s steer now)",
628
+ "hotkeys.custom.many": "custom: {path} ({count} overrides)",
629
+ "hotkeys.custom.one": "custom: {path} (1 override)",
630
+ "hotkeys.custom.missing": "custom: {path} (not found — defaults in use)",
631
+ "hotkeys.formatHint": "format: ctrl/shift/alt/super+key · Enter edits · empty input resets to the default",
632
+ "hotkeys.editorHint": "editor: pi-tui defaults — arrows · Ctrl+B/F · Alt+←→ word · Home/End · Ctrl+W/U/K delete · Ctrl+- undo",
633
+ "hotkeys.warn.invalidJson": "{label}: invalid JSON — {error}",
634
+ "hotkeys.warn.notObject": "{label}: expected a JSON object of app-key → key id",
635
+ "hotkeys.warn.invalidKey": "{label}: \"{field}\" = {value} is not a valid key id — keeping the default",
636
+ "hotkeys.warn.unknownField": "{label}: unknown field \"{field}\" — ignored",
637
+ "hotkeys.file.notObject": "{label}: expected a JSON object — fix or delete the file, then retry",
638
+ "hotkeys.file.parseError": "{label}: {error} — fix or delete the file, then retry",
639
+ "hotkeys.file.writeError": "{label}: cannot write — {error}",
640
+ "hotkeys.parse.notKeyId": "not a key id — ctrl/shift/alt/super + key (e.g. ctrl+x, f5, escape)",
641
+ "hotkeys.edit.title": "Key for {field} — default {defaultValue}",
642
+ "hotkeys.edit.titleCustom": "Key for {field} — default {defaultValue} · custom {custom}",
643
+ "hotkeys.edit.subtitle": "ctrl/shift/alt/super + key · empty resets to the default",
644
+ "hotkeys.status.reset": "{field} → default ({defaultValue}) — applied",
645
+ "hotkeys.status.set": "{field} → {id} — applied"
646
+ }