dsh-oc-tui 0.1.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.
- package/LICENSE +504 -0
- package/README.md +247 -0
- package/bin/dsh-oc-tui.js +219 -0
- package/cordis.patch.yml +24 -0
- package/docs/max-thinking.gif +0 -0
- package/docs//347/224/250/346/210/267/346/211/213/345/206/214.md +324 -0
- package/lib/index.js +1771 -0
- package/lib/interrupt.js +31 -0
- package/lib/markdown.js +297 -0
- package/lib/metrics.js +70 -0
- package/lib/startup.js +42 -0
- package/lib/term.js +506 -0
- package/lib/ui.js +1646 -0
- package/lib/util.js +281 -0
- package/lib/web-settings.js +450 -0
- package/package.json +61 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# DeepSeek Harness TUI 用户手册
|
|
2
|
+
|
|
3
|
+
本手册面向**安装使用** `dsh-oc-tui` 的用户,介绍如何把终端界面(TUI)安装进 DeepSeek Harness(dsh)的 profile,并以最方便的方式启动。开发者本地开发方式见文末[开发模式启动](#开发模式启动)。
|
|
4
|
+
|
|
5
|
+
## 1. 简介
|
|
6
|
+
|
|
7
|
+
`dsh-oc-tui` 是一个运行在 dsh 进程内的终端界面插件。它通过 `ctx.agents` 创建/恢复会话,渲染持久化的 `session/event` 事件流(用户消息、流式回复、工具卡片、待办列表),并把键盘输入通过 `agent.followup()` 送回 agent;授权询问会在界面内直接回答。会话执行任务的整个过程中,输入框边框会以金色流动的“蚂蚁线”动画(亮金短划沿边框顺时针流动)提示进行中状态。
|
|
8
|
+
|
|
9
|
+
它不是一个独立的 agent 运行时。模型路由、工具执行、授权、命令、会话存储和凭据仍由 dsh 负责,插件只负责终端里的输入与显示。
|
|
10
|
+
|
|
11
|
+
## 2. 安装前准备
|
|
12
|
+
|
|
13
|
+
| 项目 | 要求 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Node.js | >= 22 |
|
|
16
|
+
| dsh CLI | 已安装 `@deepseek-ai/dsh`,例如 `npm install -g @deepseek-ai/dsh` |
|
|
17
|
+
| pnpm | 已加入 PATH(`dsh plugin` 内部转发给 pnpm) |
|
|
18
|
+
| 终端 | 交互式终端(Windows Terminal / ConPTY、iTerm2、GNOME Terminal 等) |
|
|
19
|
+
| 模型配置 | `$DSH_HOME/settings.yaml` 与 `$DSH_HOME/.credentials.yaml` 中已配置可用的模型路由(与 Web GUI 共用) |
|
|
20
|
+
|
|
21
|
+
检查 dsh 与 pnpm 是否可用:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
dsh --version
|
|
25
|
+
pnpm --version
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 3. 安装插件
|
|
29
|
+
|
|
30
|
+
### 3.1 自动安装脚本(推荐)
|
|
31
|
+
|
|
32
|
+
仓库内置了 Linux/macOS 与 Windows 的一键安装脚本,会自动检查 Node.js >= 22、确保 pnpm 可用,并从 GitHub 把插件装进 `tui` profile;加 `--launcher`/`-Launcher` 还会把 `dsh-oc-tui` 命令全局装好。
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
# Linux / macOS
|
|
36
|
+
curl -fsSL https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.sh | bash
|
|
37
|
+
|
|
38
|
+
# Windows(PowerShell)
|
|
39
|
+
powershell -ExecutionPolicy Bypass -Command "iwr https://raw.githubusercontent.com/rayafriandion/dsh-oc-tui/main/install.ps1 -OutFile install.ps1; & .\install.ps1"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
在源码目录里也可以直接运行 `./install.sh`(Linux/macOS)或 `.\install.ps1`(Windows)。加上 `--launcher` / `-Launcher` 同时安装便捷启动器:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
./install.sh --launcher # Linux/macOS
|
|
46
|
+
.\install.ps1 -Launcher # Windows
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
其他选项:`--local`(`-Local`)改为安装当前源码目录;`--source <spec>`(`-Source <spec>`)指定自定义来源(例如发布到 npm 后可用 `dsh-oc-tui`,或 tarball 路径);`--profile <name>`(`-Profile <name>`)指定非默认 profile。
|
|
50
|
+
|
|
51
|
+
### 3.2 官方安装:一条命令
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
dsh plugin --profile tui add dsh-oc-tui
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
这条命令会:
|
|
58
|
+
|
|
59
|
+
1. 首次使用时初始化 `$DSH_HOME/profiles/tui`(默认组合为 `@deepseek-ai/dsh-base`)。
|
|
60
|
+
2. 用 pnpm 把 `dsh-oc-tui` 安装进该 profile 的 `node_modules`。
|
|
61
|
+
3. 因为本包声明了 `dsh.bundle.patch`,dsh 会自动把 `dsh-oc-tui` 追加到 profile 的 `dsh.profile.bundles`。
|
|
62
|
+
|
|
63
|
+
安装完成后无需手动编辑任何配置。
|
|
64
|
+
|
|
65
|
+
### 3.3 从本地目录或 tarball 安装
|
|
66
|
+
|
|
67
|
+
未发布到 npm 之前,可以从源码目录或打包好的 tarball 安装:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
# 在包含 dsh-oc-tui 的目录下执行
|
|
71
|
+
dsh plugin --profile tui add ./dsh-oc-tui
|
|
72
|
+
|
|
73
|
+
# 或使用 pnpm pack 生成的 tarball
|
|
74
|
+
dsh plugin --profile tui add ./dsh-oc-tui-0.1.0.tgz
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`dsh plugin` 会把相对路径按你执行命令时所在的目录解析后再转发给 pnpm。
|
|
78
|
+
|
|
79
|
+
### 3.4 从 GitHub 安装
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
dsh plugin --profile tui add github:you/dsh-oc-tui
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
本包是纯 JavaScript,不需要构建步骤。若未来版本增加了 `prepare` 构建脚本,pnpm >= 10 会要求先在 profile 的 `pnpm-workspace.yaml` 中允许该包执行构建(allowBuilds),然后重试。
|
|
86
|
+
|
|
87
|
+
### 3.5 验证安装
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
dsh --profile tui --dump-config
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
输出中应能看到 `# == dsh-oc-tui` 这一层,其中包含 `tui-startup` 与 `tui-app` 两行。
|
|
94
|
+
|
|
95
|
+
## 4. 启动 TUI
|
|
96
|
+
|
|
97
|
+
### 4.1 标准启动
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
dsh --profile tui
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
启动后先进入标题界面,发送第一条消息即创建会话。常用启动参数:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
dsh --profile tui --resume <sessionId> # 恢复已保存的会话
|
|
107
|
+
dsh --profile tui --model <modelId> # 新会话默认模型
|
|
108
|
+
dsh --profile tui --provider <route> # 新会话默认 provider 路由
|
|
109
|
+
dsh --profile tui --no-sidebar # 不显示侧边栏
|
|
110
|
+
dsh --profile tui --help # 查看 TUI 自己的参数帮助
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4.2 便捷启动器 dsh-oc-tui
|
|
114
|
+
|
|
115
|
+
本包附带 `dsh-oc-tui` 命令。它等价于 `dsh --profile tui`,但会在启动前检查 `tui` profile 是否已安装本插件;如果没有,会直接打印一次性安装命令,而不是进入一个空转的 profile。
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
dsh-oc-tui # 启动 tui profile
|
|
119
|
+
dsh-oc-tui --profile mytui # 启动名为 mytui 的 profile
|
|
120
|
+
dsh-oc-tui --help # 查看启动器帮助
|
|
121
|
+
dsh-oc-tui --version # 查看启动器版本
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`dsh-oc-tui` 优先使用 PATH 中的 `dsh`;找不到时回退到 `npx --yes @deepseek-ai/dsh`。要把 `dsh-oc-tui` 安装到 PATH:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
npm install -g dsh-oc-tui
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
支持的环境变量:
|
|
131
|
+
|
|
132
|
+
| 环境变量 | 作用 |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| `DSH_TUI_PROFILE` | 未指定 `--profile` 时的默认 profile 名(默认 `tui`) |
|
|
135
|
+
| `DSH_TUI_SKIP_CHECK` | 设为 `1` 跳过 profile 预检(高级安装方式使用) |
|
|
136
|
+
|
|
137
|
+
### 4.3 想要输入 `dsh tui`?
|
|
138
|
+
|
|
139
|
+
dsh 启动器只把 `web` 硬编码为裸子命令别名,插件无法注册新的裸子命令。可以加一行 shell 别名实现:
|
|
140
|
+
|
|
141
|
+
```bat
|
|
142
|
+
:: CMD
|
|
143
|
+
doskey tui=dsh --profile tui $*
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
# PowerShell profile
|
|
148
|
+
function tui { dsh --profile tui @args }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 5. 快捷键
|
|
152
|
+
|
|
153
|
+
| 按键 | 作用 |
|
|
154
|
+
| --- | --- |
|
|
155
|
+
| Enter | 发送消息 |
|
|
156
|
+
| Ctrl+Enter / Shift+Enter / Alt+Enter | 输入换行 |
|
|
157
|
+
| Ctrl+C | 清空非空输入框 / 取消进行中的轮次;空闲时连按两次退出 |
|
|
158
|
+
| Ctrl+P | 打开设置菜单 |
|
|
159
|
+
| Ctrl+E | 打开/关闭输入框下方的思考强度滑块 |
|
|
160
|
+
| Tab | 会话页面:循环切换思考强度;设置页面:切换左侧菜单(Main/Model) |
|
|
161
|
+
| Ctrl+N | 新建会话 |
|
|
162
|
+
| Ctrl+D | 在 设置 → 管理会话 中删除当前选中的会话(连按两次确认) |
|
|
163
|
+
| Ctrl+L | 清空当前转录视图 |
|
|
164
|
+
| Up / Down | 在多行输入中上下移动光标;位于第一行/最后一行时改为浏览输入历史 |
|
|
165
|
+
| Left / Right | 在输入框中左右移动光标 |
|
|
166
|
+
| PgUp / PgDn | 滚动转录区 |
|
|
167
|
+
| Esc | 关闭上下文仪表盘 / 关闭强度滑块 / 关闭帮助 / 取消授权询问 |
|
|
168
|
+
| y / n | 回答界面内的授权询问 |
|
|
169
|
+
|
|
170
|
+
鼠标操作:滚轮用于滚动页面——在会话页滚动转录区,在设置菜单打开时滚动设置窗口(不再移动选中项焦点)。在转录区按住鼠标左键拖动可选中文字,随后按下右键即可将所选文字复制到剪贴板。
|
|
171
|
+
|
|
172
|
+
## 6. 斜杠命令
|
|
173
|
+
|
|
174
|
+
`/help` `/settings` `/new` `/resume <id>` `/model <id>` `/provider <route>` `/clear` `/cancel` `/quit`
|
|
175
|
+
|
|
176
|
+
dsh 的人类命令(如 `/compact`、`/goal`)会转发给 `ctx.commands`,不经过模型轮次执行。
|
|
177
|
+
|
|
178
|
+
## 7. 设置菜单
|
|
179
|
+
|
|
180
|
+
按 `Ctrl+P` 打开。菜单覆盖与 WebUI 相同的 Host 设置命名空间,修改通过 `ctx.settings` 持久化到 `$DSH_HOME/settings.yaml`。设置窗口左侧有一个菜单栏,按 `Tab`(或鼠标点击)在两个菜单项之间切换,`↑/↓` 移动、`Enter` 打开选项列表、`Esc` 返回,分区标题行不可选中:
|
|
181
|
+
|
|
182
|
+
- **Main**(原来的通用设置)
|
|
183
|
+
- **General**:Busy Enter 行为、默认 agent 预设、权限预设
|
|
184
|
+
- **Sessions**:新建会话、管理会话
|
|
185
|
+
- **System**:Provider API 配置提示、设置文件路径
|
|
186
|
+
- **Model**(原来的提供商设置和模型设置合并到这里)
|
|
187
|
+
- **Default**:默认 provider / model / 思考强度
|
|
188
|
+
- 每个 provider 一个分组,组内为:
|
|
189
|
+
- **Provider URL**
|
|
190
|
+
- **Provider API key**
|
|
191
|
+
- **Models**:该 provider 的模型列表
|
|
192
|
+
|
|
193
|
+
只显示你在设置文件中**手动添加过**的 provider(即用户设置层里实际存在的 provider);系统预设但从未添加的 provider 不会显示。
|
|
194
|
+
|
|
195
|
+
**Models 自动获取**:在某个 provider 的 **Models** 行按 `Enter`,TUI 会调用 `ctx.llm.discoverModels` 自动获取该 provider 的完整模型列表(已知内置路由直接取内置目录,自定义路由则请求其端点),然后弹出模型列表窗口,用 `[x]`/`[ ]` 勾选你需要的模型(`Enter` 切换、`Esc` 返回)。选中的模型会保存到该 provider 的 `models` 配置,并逐条列在 **Models** 行下方;在列出的模型上按 `Enter` 可把它设为默认模型。
|
|
196
|
+
|
|
197
|
+
默认 agent 预设来自 profile 挂载的 `agent-presets` 名单(内置预设 + 你在 `$DSH_HOME/.agent-presets` 下自建的预设)。TUI 会话始终从进程级 base 组合生成,因此该默认值只在按预设创建会话时生效。
|
|
198
|
+
|
|
199
|
+
WebUI 专属选项(`ui-theme` 外观、`locale` 语言)不在 TUI 中显示。
|
|
200
|
+
|
|
201
|
+
### 7.1 思考强度滑块
|
|
202
|
+
|
|
203
|
+
输入框右上角显示当前生效的思考强度等级名(不再带 "effort" 字样),与 `provider · model` 标签对角。在会话页面直接按 `Tab` 即可循环切换思考强度(到最高档后回到最低档,`Shift+Tab` 反向),改动即时保存;也可以按 `Ctrl+E` 打开输入框下方的滑块,用 `Tab` 或 `←`/`→` 调整并保存,`Esc` 或 `Ctrl+E` 关闭。滑块选项来自当前模型在 provider 适配器中实际声明的等级(`ctx.llm.resolveModelInfo`),因此不同模型显示的可选等级不同(例如 DeepSeek 只显示 `Off`/`High`/`Max`)。选择结果通过 `agent/request` waterfall 应用到会话请求,并保存到 `agent-default-model.reasoningEffort`。
|
|
204
|
+
|
|
205
|
+
### 7.2 上下文仪表(Context meter)
|
|
206
|
+
|
|
207
|
+
状态栏带有一个实时的上下文占用条(例如 `ctx ▓▓░░ 32K/128K 25%`),数据来自 token-meter 的 `contextPressure` 投影,与 Web 界面输入框右侧的环形仪表同源:
|
|
208
|
+
|
|
209
|
+
- **`当前上下文长度 / 上下文长度上限`**:一旦 provider 上报了已用 token 与上下文窗口容量,就会显示(例如 `32K/128K`)。分子优先使用 `projectedTokens`(最新一次采样随会话表面变化推进),因此刚压缩过的上下文会立即反映在读数里。
|
|
210
|
+
- **可视化**:占用条随百分比填充,超过 90% 时转为警示色、达到 100% 时转为错误色。
|
|
211
|
+
- **明细面板**:鼠标点击占用条会打开一个明细面板(再次点击或按 `Esc` 关闭),显示占用百分比、`当前/上限` 读数,以及按启发式拆分的占用条与图例——系统提示词、工具、对话消息——对应 Web 界面 ContextMeter 的弹出面板。
|
|
212
|
+
- 当 profile 缺少 token-meter 投影(例如没有 `@deepseek-ai/dsh-base`)时,占用条自动隐藏,不影响其他功能。
|
|
213
|
+
|
|
214
|
+
## 8. 卸载
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
dsh plugin --profile tui remove dsh-oc-tui
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
该命令会同时移除 profile 依赖和 bundle 层。profile 目录本身会保留;如需删除整个 profile,可直接删除 `$DSH_HOME/profiles/tui`。
|
|
221
|
+
|
|
222
|
+
## 9. 常见问题
|
|
223
|
+
|
|
224
|
+
### 9.1 `dsh --profile tui` 报 profile 不存在
|
|
225
|
+
|
|
226
|
+
说明 profile 还没创建。执行一次:
|
|
227
|
+
|
|
228
|
+
```sh
|
|
229
|
+
dsh plugin --profile tui add dsh-oc-tui
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### 9.2 `dsh plugin` 提示 `pnpm not found on PATH`
|
|
233
|
+
|
|
234
|
+
`dsh plugin` 依赖 pnpm。先安装 pnpm:
|
|
235
|
+
|
|
236
|
+
```sh
|
|
237
|
+
npm install -g pnpm
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
然后重试 `dsh plugin --profile tui add ...`。
|
|
241
|
+
|
|
242
|
+
### 9.3 `dsh-oc-tui` 启动前提示 profile 未安装
|
|
243
|
+
|
|
244
|
+
这是 `dsh-oc-tui` 的预检:它发现 `$DSH_HOME/profiles/tui` 不存在,或 profile 的 `dsh.profile.bundles` 里没有 `dsh-oc-tui`。按提示执行:
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
dsh plugin --profile tui add dsh-oc-tui
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
如果你使用的是开发模式(在 profile 的 `cordis.patch.yml` 里写绝对路径),可设置 `DSH_TUI_SKIP_CHECK=1` 跳过预检。
|
|
251
|
+
|
|
252
|
+
### 9.4 `--dump-config` 里没有 TUI 层
|
|
253
|
+
|
|
254
|
+
通常是安装命令没有成功完成,或包名拼写不一致。重新执行 `dsh plugin --profile tui add dsh-oc-tui`,确认 `dsh.profile.bundles` 中包含 `dsh-oc-tui`。
|
|
255
|
+
|
|
256
|
+
### 9.5 启动后立刻退出或没有界面
|
|
257
|
+
|
|
258
|
+
先确认在交互式终端中运行(不是重定向/管道环境)。TUI 需要 stdin/stdout 都是 TTY。再确认 model/provider 已在 `$DSH_HOME/settings.yaml` 中配置好,且凭据文件存在。
|
|
259
|
+
|
|
260
|
+
### 9.6 `--resume` 或 设置 → 管理会话 不可用
|
|
261
|
+
|
|
262
|
+
这两个功能需要 profile 中的共享 `sessionQuery` 服务,该服务由 `@deepseek-ai/dsh-base` 挂载。确认 profile 的 bundle 列表第一个仍是 `@deepseek-ai/dsh-base`。
|
|
263
|
+
|
|
264
|
+
### 9.7 修改插件源码不生效
|
|
265
|
+
|
|
266
|
+
profile 的热更新监视的是 profile 目录,不是插件目录。修改插件源码后重启 `dsh --profile tui` 即可。
|
|
267
|
+
|
|
268
|
+
## 10. 开发模式启动
|
|
269
|
+
|
|
270
|
+
开发本插件时,可以跳过 pnpm 安装,直接让 profile 引用源码的绝对路径:
|
|
271
|
+
|
|
272
|
+
1. 初始化基础 profile:
|
|
273
|
+
|
|
274
|
+
```sh
|
|
275
|
+
dsh --profile tui --dump-config
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
2. 编辑 `$DSH_HOME/profiles/tui/cordis.patch.yml`,加入(路径换成你的实际路径):
|
|
279
|
+
|
|
280
|
+
```yaml
|
|
281
|
+
- insert:
|
|
282
|
+
- id: tui-startup
|
|
283
|
+
name: 'file:///D:/Projects/DeepSeekHarnessPlugins/dsh-oc-tui/lib/startup.js'
|
|
284
|
+
- id: tui-app
|
|
285
|
+
name: 'file:///D:/Projects/DeepSeekHarnessPlugins/dsh-oc-tui/lib/index.js'
|
|
286
|
+
config:
|
|
287
|
+
sidebar: true
|
|
288
|
+
showReasoning: true
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
3. 启动:
|
|
292
|
+
|
|
293
|
+
```sh
|
|
294
|
+
dsh --profile tui
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
插件对 dsh 各包的 import 会通过 `$DSH_HOME/profiles/node_modules` 共享回退解析,无需在插件目录里安装依赖。
|
|
298
|
+
|
|
299
|
+
运行测试:
|
|
300
|
+
|
|
301
|
+
```sh
|
|
302
|
+
node tests/smoke.test.mjs # 不依赖 dsh 的纯模块测试
|
|
303
|
+
node --check lib/*.js # 语法检查
|
|
304
|
+
node --check bin/*.js # 启动器语法检查
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## 11. 目录结构
|
|
308
|
+
|
|
309
|
+
```
|
|
310
|
+
lib/index.js 插件入口:agents、事件、输入、命令、授权
|
|
311
|
+
lib/startup.js 命令行参数提供者(tuiStartup 服务)
|
|
312
|
+
lib/term.js 终端引擎(raw 模式、屏幕、按键解码)
|
|
313
|
+
lib/ui.js 响应式视图模型 + 渲染器
|
|
314
|
+
lib/metrics.js 持久事件遥测统计
|
|
315
|
+
lib/interrupt.js Ctrl+C 生命周期状态机
|
|
316
|
+
lib/web-settings.js WebUI 设置投影
|
|
317
|
+
lib/markdown.js Markdown -> 带样式文本行
|
|
318
|
+
lib/util.js 文本/显示工具
|
|
319
|
+
bin/dsh-oc-tui.js 便捷启动器
|
|
320
|
+
install.sh 一键安装脚本(Linux/macOS)
|
|
321
|
+
install.ps1 一键安装脚本(Windows)
|
|
322
|
+
cordis.patch.yml bundle 补丁层(TUI 行)
|
|
323
|
+
tests/smoke.test.mjs 独立冒烟测试
|
|
324
|
+
```
|