@chance722/dsh-inbox 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 +21 -0
- package/README.md +182 -0
- package/README.zh.md +182 -0
- package/cordis.patch.yml +11 -0
- package/lib/cli.js +259 -0
- package/lib/client.js +3823 -0
- package/lib/index.js +3718 -0
- package/lib/types/cli.d.ts +49 -0
- package/lib/types/client/card.d.ts +52 -0
- package/lib/types/client/dock.d.ts +52 -0
- package/lib/types/client/heading.d.ts +54 -0
- package/lib/types/client/index.d.ts +24 -0
- package/lib/types/client/manual.d.ts +22 -0
- package/lib/types/client/scheme.d.ts +47 -0
- package/lib/types/host/capture.d.ts +117 -0
- package/lib/types/host/classify/model.d.ts +112 -0
- package/lib/types/host/classify/redact.d.ts +18 -0
- package/lib/types/host/classify/rules.d.ts +58 -0
- package/lib/types/host/command.d.ts +18 -0
- package/lib/types/host/crypto/secret-box.d.ts +60 -0
- package/lib/types/host/index.d.ts +21 -0
- package/lib/types/host/link-title.d.ts +64 -0
- package/lib/types/host/remote/auto-push.d.ts +37 -0
- package/lib/types/host/remote/merge.d.ts +80 -0
- package/lib/types/host/remote/pull.d.ts +60 -0
- package/lib/types/host/remote/push.d.ts +101 -0
- package/lib/types/host/remote/remove.d.ts +65 -0
- package/lib/types/host/remote/writer.d.ts +43 -0
- package/lib/types/host/rpc.d.ts +36 -0
- package/lib/types/host/s3/client.d.ts +216 -0
- package/lib/types/host/s3/probe.d.ts +28 -0
- package/lib/types/host/tools.d.ts +56 -0
- package/lib/types/host/ui/config.d.ts +54 -0
- package/lib/types/host/vault/lease.d.ts +54 -0
- package/lib/types/host/vault/query.d.ts +38 -0
- package/lib/types/host/vault/spec.d.ts +193 -0
- package/lib/types/host/vault/vault.d.ts +269 -0
- package/lib/types/host/webdav/client.d.ts +102 -0
- package/lib/types/host/webdav/config.d.ts +129 -0
- package/lib/types/host/webdav/probe.d.ts +22 -0
- package/lib/types/host/webdav/run.d.ts +34 -0
- package/lib/types/shared/constants.d.ts +23 -0
- package/lib/types/shared/panel-wire.d.ts +474 -0
- package/lib/types/shared/vocabulary.d.ts +39 -0
- package/package.json +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chance722
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# dsh-inbox
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
A **local inbox plugin for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)** (`dsh`): file whatever you copy into one vault, get it back when you need it — including **by asking your assistant in conversation**.
|
|
6
|
+
|
|
7
|
+
English | [中文](README.zh.md)
|
|
8
|
+
|
|
9
|
+
## The problem it solves
|
|
10
|
+
|
|
11
|
+
The things you copy during a day — a link to read later, a screenshot, a snippet of config, an account and password — end up scattered across clipboard history, bookmark folders and temporary files. When you need them you cannot find them, and you cannot remember where they went.
|
|
12
|
+
|
|
13
|
+
**dsh-inbox puts them in one local vault**: paste to file, automatic classification, browse and search from the sidebar — and instead of digging through it yourself, just ask your assistant "what was that article about caching I saved last month?".
|
|
14
|
+
|
|
15
|
+
It works for you alone: everything lands on your machine, and the model only sees a record when you ask it to look.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
| Feature | What it does |
|
|
20
|
+
|---|---|
|
|
21
|
+
| **Two ways in** | `/inbox <text or link>` in the composer (attach images to carry them along), or paste / drop / pick a file in the panel |
|
|
22
|
+
| **Automatic classification** | Links are filed by platform and media type (a Bilibili video, a WeChat article…), text by credential shape, images get a 疑似证件 tag when they have card proportions; what the rules cannot decide goes to the model, with a daily cap |
|
|
23
|
+
| **Panel** | Filter by watch-later / category / tag, search across title, text, link and note, switch between two list densities; the detail pane edits name, category, note and tags, flags watch-later, deletes and restores; it follows dsh's dark and light themes |
|
|
24
|
+
| **Everything has a name** | A link is named by the headline of the page it points at (no model tokens spent), a photo or file by the name it arrived with, and the name you type always wins. The row shows the name; the glyph on the left says what kind of thing it is (a key for a credential) |
|
|
25
|
+
| **Who judged the category** | A coloured badge next to the category: 规则判定 (grey, the local rules) / 模型判定 (violet, the capped model pass) / 手动判定 (blue, your own choice — nothing overwrites it later) |
|
|
26
|
+
| **Ask in conversation** | Ask for 收件箱 / 仓库 / inbox and the assistant searches by words, category, tag, watch-later flag or kind (ten at a time plus a count of the rest, with thumbnails right in the results), then opens one by id (text up to 1000 characters, link, note, tags, attachment facts). To look at one yourself: expand 「N 次工具调用」 above the answer and press 打开 ↗ — the 仓库 tab opens on that record |
|
|
27
|
+
| **Looking at a picture** | Image bytes stay out of the conversation by default; when you ask "look at this picture and tell me what it is", the assistant sends that one image to itself — explicitly, per call |
|
|
28
|
+
| **Credentials are safe** | A credential's body is **encrypted at rest** with a key derived from your master password; neither the password nor the key is ever written down. The list shows only the name you gave it; plain text never reaches a conversation or a model |
|
|
29
|
+
| **Two-way sync** | Point it at a WebDAV folder or an S3 bucket: changes are pushed a few seconds after you make them, 刷新 runs a full sync (push, then pull, settled by timestamp), and emptying the recycle bin deletes the cloud copies too |
|
|
30
|
+
| **Conversation cards** | Tool results render as dsh-inbox cards — links become clickable, image markers become thumbnails drawn on your machine |
|
|
31
|
+
| **Gateway quirks** | Some object-storage gateways bind each AccessKey to an "application" and identify clients by a header (refusing you with the same status a wrong password gets) — the plugin keeps **one client identity per protocol** for exactly that |
|
|
32
|
+
|
|
33
|
+
## Screenshots
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
It all lives inside dsh: the left rail gains an **Inbox** entry that opens a full-page vault, and the panel's top right has 设置 (settings) and 使用手册 (a short manual).
|
|
38
|
+
|
|
39
|
+
## Two ways to use it
|
|
40
|
+
|
|
41
|
+
**① File something from the conversation**
|
|
42
|
+
|
|
43
|
+
Type `/inbox` in the composer followed by text or a link, and attach images directly — **this command never reaches the model**, it only goes into the vault. It is the way to file credentials and throwaway links that have no business appearing in a conversation.
|
|
44
|
+
|
|
45
|
+
**② Ask for it later**
|
|
46
|
+
|
|
47
|
+
No command needed, just talk:
|
|
48
|
+
|
|
49
|
+
> which of my saved images is the mini-program code?
|
|
50
|
+
>
|
|
51
|
+
> show me that article about caching I saved last week
|
|
52
|
+
>
|
|
53
|
+
> list the links in my inbox I haven't read yet
|
|
54
|
+
|
|
55
|
+
Two questions in the same conversation, on a real machine:
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
Requires Node ≥ 22 and a working `dsh` (`@deepseek-ai/dsh`).
|
|
64
|
+
|
|
65
|
+
**Platform**: fully accepted on **Windows** only so far; macOS and Linux are **not verified yet** (no platform-specific dependency in the code — try it and tell me how it goes).
|
|
66
|
+
|
|
67
|
+
One command installs it:
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
npx @chance722/dsh-inbox init
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**On a fresh machine** (no `inbox` profile yet) either of these works:
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
# A. let init create it, using dsh's own web template
|
|
77
|
+
npx @chance722/dsh-inbox init --create-profile
|
|
78
|
+
|
|
79
|
+
# B. create it yourself, then install (--dump-config creates and exits)
|
|
80
|
+
dsh --profile inbox --from-default-profile web --dump-config
|
|
81
|
+
npx @chance722/dsh-inbox init
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
It does three things, and running it twice is safe:
|
|
85
|
+
|
|
86
|
+
1. installs the plugin into a profile (`inbox` by default; if that profile does not exist it tells you how to create one)
|
|
87
|
+
2. copies dsh's shipped `standard` preset into `~/.dsh/.agent-presets/inbox/` and adds this plugin — **this is the step that decides whether the assistant can see the inbox tools**
|
|
88
|
+
3. points the user-level default preset at it (backing up `~/.dsh/settings.yaml` first)
|
|
89
|
+
|
|
90
|
+
Then restart dsh and open a new session:
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
dsh --profile inbox --no-open --port 3102
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Open the printed URL (it carries a token). The Inbox panel is in the left rail; in a new session, ask "what links in my inbox haven't I read yet?" and the assistant will look it up. **Your daily `dsh web` profile is not touched.**
|
|
97
|
+
|
|
98
|
+
Useful flags:
|
|
99
|
+
|
|
100
|
+
```powershell
|
|
101
|
+
npx @chance722/dsh-inbox init # into the inbox profile (default; refuses if it is missing)
|
|
102
|
+
npx @chance722/dsh-inbox init --create-profile # create that profile when it is missing
|
|
103
|
+
npx @chance722/dsh-inbox init --profile web # into the profile you already use
|
|
104
|
+
npx @chance722/dsh-inbox init --no-default # install only, leave the default preset alone
|
|
105
|
+
npx @chance722/dsh-inbox init --help # every flag
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Which profile you install into only decides **where the panel runs**. The agent preset is shared by every profile (`~/.dsh/.agent-presets/inbox/`), so installing into a second profile just fills in the missing row.
|
|
109
|
+
|
|
110
|
+
### From a local checkout
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
git clone <this repo> dsh-inbox ; cd dsh-inbox
|
|
114
|
+
pnpm install ; pnpm build
|
|
115
|
+
node lib/cli.js init --package <absolute path to this repo>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Uninstall
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
# 1. remove the package from the profile (also drops it from dsh.profile.bundles)
|
|
122
|
+
dsh plugin --profile inbox remove @chance722/dsh-inbox
|
|
123
|
+
|
|
124
|
+
# 2. delete what init created
|
|
125
|
+
rm -r ~/.dsh/.agent-presets/inbox # the preset copy
|
|
126
|
+
# default preset: delete agent-presets.default in ~/.dsh/settings.yaml (falls back to the
|
|
127
|
+
# deployment default) or set it to standard; every init left a settings.yaml.bak-* backup
|
|
128
|
+
|
|
129
|
+
# 3. and the isolated profile, if you want it gone
|
|
130
|
+
rm -r ~/.dsh/profiles/inbox
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Uninstalling does not delete your vault.** To remove the records too: `rm -r ~/.dsh/storages/dsh_inbox`.
|
|
134
|
+
|
|
135
|
+
## Where things live
|
|
136
|
+
|
|
137
|
+
All of it on your machine (`%DSH_HOME%`, i.e. `C:\Users\<you>\.dsh` on Windows):
|
|
138
|
+
|
|
139
|
+
| What | Where |
|
|
140
|
+
|---|---|
|
|
141
|
+
| Records: text, links, category, note, tags, watch-later… | `storages\dsh_inbox\items\*.json`, one file each |
|
|
142
|
+
| Attachment index (mime / size / original name) | `storages\dsh_inbox\attachments\*.json` |
|
|
143
|
+
| The **bytes** of images, videos and files | `attachments\` — dsh's own content-addressed store, never auto-deleted |
|
|
144
|
+
| Remote password / S3 AccessKey Secret | dsh's credential store, `.credentials.yaml` |
|
|
145
|
+
| Remote settings (URL, bucket, client identity…) | dsh's own settings |
|
|
146
|
+
|
|
147
|
+
### What the remote looks like
|
|
148
|
+
|
|
149
|
+
With a remote configured and `/inbox` as the directory:
|
|
150
|
+
|
|
151
|
+
| Remote path | What it is |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `inbox/<whatever you drop>` | The **drop folder**: any device drops files here and this one ingests them |
|
|
154
|
+
| `inbox/sync/items/<record id>.json` | One record, **machine-readable** — the source of truth for sync |
|
|
155
|
+
| `inbox/sync/items/<record id>.txt` | The same record, **readable** (text, note, which attachments it points at) |
|
|
156
|
+
| `inbox/sync/attachments/<attachment id>.<ext>` | Attachment **bytes** (images, video, PDFs open as themselves) |
|
|
157
|
+
| `inbox/sync/attachments/<attachment id>.meta.json` | The attachment's metadata (original name, dimensions, size, digest) |
|
|
158
|
+
| A 0-byte key ending in `/` | A folder marker the cloud drive made itself, not us |
|
|
159
|
+
|
|
160
|
+
## Privacy and security
|
|
161
|
+
|
|
162
|
+
- **Credentials are encrypted at rest**: the body is stored as ciphertext (AES-256-GCM, key derived from your master password). **Neither the password nor the key is ever written down** — the vault locks again on every restart and you unlock it under 设置 → 账密加密; a forgotten password means unrecoverable ciphertext. With no master password set, a credential is **refused rather than stored in the clear**.
|
|
163
|
+
- **What that covers**: only the **body** of credential records. Notes, categories, tags, timestamps and attachment **bytes** are not encrypted — a key inside a pasted file is still a key inside a file.
|
|
164
|
+
- **Masked where it matters**: a credential is listed by the name you gave it, and its plain text never reaches a conversation or a model.
|
|
165
|
+
- **Pictures**: classification does send an image to the model (a phone photo of an ID card has the same proportions as any other photo); the conversation gets an `[attachment:id]` marker by default. **The one exception**: when you explicitly ask the assistant to look at a picture, that single image is sent to it — per call, never by default.
|
|
166
|
+
- **The model cannot see your vault** unless you ask it to look, and classification requests are redacted first.
|
|
167
|
+
- **Your remote needs access control**: only credential bodies are ciphertext up there — text, links, notes and attachment bytes are in the clear, and the master password and key never sync.
|
|
168
|
+
|
|
169
|
+
## Development
|
|
170
|
+
|
|
171
|
+
```powershell
|
|
172
|
+
pnpm install
|
|
173
|
+
pnpm build # lib/index.js (host) + lib/client.js (panel) + lib/cli.js (init) + lib/types
|
|
174
|
+
pnpm typecheck
|
|
175
|
+
pnpm test # vitest
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Client-side changes need `pnpm build` and a page reload (dsh's client-hmr reloads it for you); host-side changes need a restart. Details in [docs/help/dev-setup.md](docs/help/dev-setup.md); milestones and acceptance records in the [development bus](docs/feature/dev-bus.md).
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# dsh-inbox
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的**本地收件箱插件**:把随手复制的东西收进一个仓库,需要的时候找得回来——包括**在对话里让助手替你取**。
|
|
6
|
+
|
|
7
|
+
[English](README.md) | 中文
|
|
8
|
+
|
|
9
|
+
## 它解决什么问题
|
|
10
|
+
|
|
11
|
+
一天里复制过的东西——一条待读的链接、一张截图、一段配置、一个账号密码——散落在剪贴板历史、收藏夹和临时文件里,要用的时候找不到,也记不住放在哪。
|
|
12
|
+
|
|
13
|
+
**dsh-inbox 把它们收进一个本地仓库**:粘贴即存、自动分类、侧栏里能翻能搜;想找的时候不必自己翻,直接在对话里问助手「我上个月存的那篇讲缓存的文章呢」。
|
|
14
|
+
|
|
15
|
+
它只服务于你自己:数据全部落在本机,只有你让助手查的时候,模型才看得到内容。
|
|
16
|
+
|
|
17
|
+
## 主要功能
|
|
18
|
+
|
|
19
|
+
| 功能 | 说明 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| **两个入口** | 输入框里 `/inbox <文字或链接>`(图片附在输入框上一起收),或面板里粘贴 / 拖拽 / 选择文件 |
|
|
22
|
+
| **自动分类** | 链接按平台与类型分(B 站视频、公众号文章…)、文本按密钥形状分、图片按比例加「疑似证件」标签;规则判不出的交给模型兜底,有每日上限 |
|
|
23
|
+
| **面板** | 待看 / 类目 / 标签筛选 + 跨标题正文链接备注的搜索;两种列表密度;详情里能改名称、类目、备注、标签,能标待看,能删除与恢复;**跟着 dsh 的深色/浅色主题走** |
|
|
24
|
+
| **每条都有名字** | 链接自动抓页面标题(不花模型 token)、图片文件用原文件名;你自己起的名字永远优先。列表里显示名字本身,类型交给左侧图标(密钥就是一把钥匙) |
|
|
25
|
+
| **类目是谁判的** | 类目旁带颜色标签:规则判定(灰)/ 模型判定(紫)/ 手动判定(蓝)。你改过的类目,后续任何流程都不会覆盖 |
|
|
26
|
+
| **对话里取回** | 直接问「我的收件箱 / 仓库 / inbox 里有哪些还没看的链接」就行,助手按关键词/类目/标签/待看/类型查(最多 10 条 + 还剩几条,带图的结果直接显示缩略图),按 id 打开一条(正文最多 1000 字、链接、备注、标签、附件信息)。想直接看某一条:展开回答上方的「N 次工具调用」,记录后面的「打开 ↗」会在右侧「仓库」里停到那一条 |
|
|
27
|
+
| **看图** | 默认图片字节不进对话;你说「帮我看这张图是什么」时,助手才会把那张图发给自己看(一次性、显式要) |
|
|
28
|
+
| **密钥安全** | 账密正文**加密落盘**:主密码派生密钥,密码与密钥都不落盘;列表只显示你起的名字;明文永不进对话、永不发给模型 |
|
|
29
|
+
| **双向同步** | 配一个 WebDAV 目录或 S3 桶:入库后自动推送,点「刷新」做一次完整同步(先推后拉、按时间合并),清空回收站连云端一起删 |
|
|
30
|
+
| **对话卡片** | 工具结果渲染成 dsh-inbox 卡片:链接可点,图片标记在本机渲染成缩略图 |
|
|
31
|
+
| **网关兼容** | 有些对象存储网关把每把 AccessKey 绑在"应用"上并按客户端标识认人(状态码与"密码错"一样)——插件支持**按协议各存一份客户端标识** |
|
|
32
|
+
|
|
33
|
+
## 界面
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
界面都在 dsh 里:左侧栏多一个 **Inbox** 入口,点开是整页仓库管理;面板右上角有「设置」和「使用手册」(手册把常见场景讲了一遍)。
|
|
38
|
+
|
|
39
|
+
## 两种用法
|
|
40
|
+
|
|
41
|
+
**① 存:在对话里转存**
|
|
42
|
+
|
|
43
|
+
输入框里写 `/inbox` 再跟文字或链接,图片直接附在输入框上——**这条命令不会发给模型**,只进仓库。适合存账号密码、临时链接这类不该出现在对话里的东西。
|
|
44
|
+
|
|
45
|
+
**② 取:在对话里问**
|
|
46
|
+
|
|
47
|
+
不用记命令,正常说话就行:
|
|
48
|
+
|
|
49
|
+
> 我收件库里那个小程序码是哪张?
|
|
50
|
+
>
|
|
51
|
+
> 上周存的讲缓存的文章给我看看
|
|
52
|
+
>
|
|
53
|
+
> 帮我把还没看过的链接列一下
|
|
54
|
+
|
|
55
|
+
同一段对话里的两次提问(真机截图):
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
59
|
+

|
|
60
|
+
|
|
61
|
+
## 安装
|
|
62
|
+
|
|
63
|
+
前置:Node ≥ 22,以及一个能用的 `dsh`(`@deepseek-ai/dsh`)。
|
|
64
|
+
|
|
65
|
+
**平台**:目前只在 **Windows** 上做过完整验收;macOS / Linux **尚未验证**(代码里没有平台特定依赖,欢迎试用后反馈)。
|
|
66
|
+
|
|
67
|
+
一条命令装好:
|
|
68
|
+
|
|
69
|
+
```powershell
|
|
70
|
+
npx @chance722/dsh-inbox init
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**全新机器**(还没有 `inbox` 这个 profile)两种走法,选一个:
|
|
74
|
+
|
|
75
|
+
```powershell
|
|
76
|
+
# A. 让 init 顺手建(用 dsh 自带的 web 模板)
|
|
77
|
+
npx @chance722/dsh-inbox init --create-profile
|
|
78
|
+
|
|
79
|
+
# B. 自己建再装(--dump-config 让它创建完就退出,不启动)
|
|
80
|
+
dsh --profile inbox --from-default-profile web --dump-config
|
|
81
|
+
npx @chance722/dsh-inbox init
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
它做三件事,重复运行是安全的:
|
|
85
|
+
|
|
86
|
+
1. 把插件装进 profile(默认 `inbox`;profile 不存在会告诉你先建一个)
|
|
87
|
+
2. 复制 dsh 自带的 `standard` preset 到 `~/.dsh/.agent-presets/inbox/` 并加入本插件——**这一步决定助手能不能看到收件箱工具**
|
|
88
|
+
3. 把用户级默认 preset 指向它(会先备份 `~/.dsh/settings.yaml`)
|
|
89
|
+
|
|
90
|
+
然后重启 dsh、开一个新会话:
|
|
91
|
+
|
|
92
|
+
```powershell
|
|
93
|
+
dsh --profile inbox --no-open --port 3102
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
打开它打印的地址(带 token)。左栏出现 Inbox;新会话里问「我的收件箱里有哪些还没看的链接」,助手就会去查。**你日常的 `dsh web` profile 不受影响**。
|
|
97
|
+
|
|
98
|
+
常用选项:
|
|
99
|
+
|
|
100
|
+
```powershell
|
|
101
|
+
npx @chance722/dsh-inbox init # 装进 inbox profile(默认,不存在则报错)
|
|
102
|
+
npx @chance722/dsh-inbox init --create-profile # profile 不存在就顺手建一个
|
|
103
|
+
npx @chance722/dsh-inbox init --profile web # 装进你日常用的那个 profile
|
|
104
|
+
npx @chance722/dsh-inbox init --no-default # 只装,不动默认 preset
|
|
105
|
+
npx @chance722/dsh-inbox init --help # 全部选项
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
装进哪个 profile 只决定**面板跑在哪儿**;agent preset 全局共享(`~/.dsh/.agent-presets/inbox/`),所以装第二个 profile 时只会补上缺的那行,不会另建一份。
|
|
109
|
+
|
|
110
|
+
### 从本地仓库装
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
git clone <本仓库> dsh-inbox ; cd dsh-inbox
|
|
114
|
+
pnpm install ; pnpm build
|
|
115
|
+
node lib/cli.js init --package <本仓库的绝对路径>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 卸载
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
# 1. 从 profile 里摘掉(同时会从 dsh.profile.bundles 移除)
|
|
122
|
+
dsh plugin --profile inbox remove @chance722/dsh-inbox
|
|
123
|
+
|
|
124
|
+
# 2. 删掉 init 建的东西
|
|
125
|
+
rm -r ~/.dsh/.agent-presets/inbox # 那个 preset 副本
|
|
126
|
+
# 默认 preset:把 ~/.dsh/settings.yaml 里 agent-presets.default 删掉(继承部署默认),
|
|
127
|
+
# 或改成 standard;init 每次都留了 settings.yaml.bak-* 备份,也可以直接还原
|
|
128
|
+
|
|
129
|
+
# 3. 想连隔离 profile 一起删
|
|
130
|
+
rm -r ~/.dsh/profiles/inbox
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**卸载不会删掉你的仓库。** 连记录一起删:`rm -r ~/.dsh/storages/dsh_inbox`。
|
|
134
|
+
|
|
135
|
+
## 东西存在哪
|
|
136
|
+
|
|
137
|
+
全部在本机(`%DSH_HOME%`,Windows 上就是 `C:\Users\<你>\.dsh`):
|
|
138
|
+
|
|
139
|
+
| 什么 | 在哪 |
|
|
140
|
+
|---|---|
|
|
141
|
+
| 记录:文本、链接、类目、备注、标签、待看… | `storages\dsh_inbox\items\*.json`(一条一个文件) |
|
|
142
|
+
| 附件索引(mime / 尺寸 / 原文件名) | `storages\dsh_inbox\attachments\*.json` |
|
|
143
|
+
| 图片 / 视频 / 文件的**字节** | `attachments\`(dsh 自己的附件仓库,按内容寻址,永不自动删) |
|
|
144
|
+
| 远端密码 / S3 AccessKey Secret | dsh 的凭证库 `.credentials.yaml` |
|
|
145
|
+
| 远端设置(地址、桶、客户端标识…) | dsh 的 settings |
|
|
146
|
+
|
|
147
|
+
### 云端长什么样
|
|
148
|
+
|
|
149
|
+
配了远端(WebDAV 目录或 S3 桶)、目录填 `/inbox` 时:
|
|
150
|
+
|
|
151
|
+
| 远端路径 | 是什么 |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `inbox/<你扔的文件>` | **投放区**:任何设备往这儿扔文件,本机拉取时读它们并入库 |
|
|
154
|
+
| `inbox/sync/items/<记录 id>.json` | 一条记录的**机器可读版**(同步的真相来源) |
|
|
155
|
+
| `inbox/sync/items/<记录 id>.txt` | 同一条记录的**可读版**(云盘里直接能看:正文、备注、附件指向) |
|
|
156
|
+
| `inbox/sync/attachments/<附件 id>.<扩展名>` | 附件**字节**(图片 / 视频 / PDF 直接能打开) |
|
|
157
|
+
| `inbox/sync/attachments/<附件 id>.meta.json` | 附件元数据(原文件名、宽高、字节数、摘要) |
|
|
158
|
+
| 0 字节、以 `/` 结尾的 key | 云盘自己建的目录占位,不是插件写的 |
|
|
159
|
+
|
|
160
|
+
## 隐私与安全
|
|
161
|
+
|
|
162
|
+
- **账密加密落盘**:账密正文以密文保存(AES-256-GCM,密钥由你设的主密码派生)。**主密码和密钥都不落盘**——服务每次重启都要在「设置 → 账密加密」解锁一次;密码忘了就解不开已有密文,没有找回。没设主密码时,账密**不会被存进去**。
|
|
163
|
+
- **加密的边界**:只加密账密的**正文**。备注、类目、标签、时间与附件的**字节**不在内——附件里装着密钥,它就是明文。别把"账密加密"理解成"整个仓库加密"。
|
|
164
|
+
- **列表与对话脱敏**:密钥类记录在列表里只显示你起的名字;明文永不进对话、永不发给模型。
|
|
165
|
+
- **图片**:分类时会把图片发给模型判断(手机拍的证件照比例与普通照片无异);对话里默认只回 `[attachment:id]` 标记,字节不进对话。**唯一的例外**:你明确让助手「看这张图」时,它会把那一张发给自己看——每次都要显式要求,默认永不发。
|
|
166
|
+
- **模型看不到你的仓库**,除非你让它查(它调工具时才读得到),且分类请求先过脱敏。
|
|
167
|
+
- **云端要有访问控制**:同步上去的内容里只有账密正文是密文,其余(文本、链接、备注、附件字节)是明文;主密码与密钥从不同步。
|
|
168
|
+
|
|
169
|
+
## 开发
|
|
170
|
+
|
|
171
|
+
```powershell
|
|
172
|
+
pnpm install
|
|
173
|
+
pnpm build # lib/index.js(宿主)+ lib/client.js(面板)+ lib/cli.js(init)+ lib/types
|
|
174
|
+
pnpm typecheck
|
|
175
|
+
pnpm test # vitest
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
改客户端代码:`pnpm build` 后刷新页面即可(dsh 的 client-hmr 会自己重载);改宿主代码要重启服务。细节见 [docs/help/dev-setup.md](docs/help/dev-setup.md),阶段与验收记录在[开发总线](docs/feature/dev-bus.md)。
|
|
179
|
+
|
|
180
|
+
## 许可
|
|
181
|
+
|
|
182
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# The dsh-inbox bundle patch. Loaded when this package is named in the profile's
|
|
2
|
+
# `dsh.profile.bundles`; it inserts one host row, and the browser half arrives
|
|
3
|
+
# through the same package's `dsh.client` declaration (the client-modules node
|
|
4
|
+
# half scans enabled rows for packages that declare one).
|
|
5
|
+
#
|
|
6
|
+
# Row ids are addressed by later layers (the profile's cordis.patch.yml, --patch
|
|
7
|
+
# overlays), so `dsh-inbox` is the id other people should target to reconfigure us.
|
|
8
|
+
|
|
9
|
+
- insert:
|
|
10
|
+
- id: dsh-inbox
|
|
11
|
+
name: '@chance722/dsh-inbox'
|
package/lib/cli.js
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
var PACKAGE_NAME = "@chance722/dsh-inbox";
|
|
11
|
+
var PRESET_ROW = `
|
|
12
|
+
# dsh-inbox: the vault's conversation tools (inbox_search / inbox_get).
|
|
13
|
+
- id: dsh-inbox
|
|
14
|
+
name: '${PACKAGE_NAME}'
|
|
15
|
+
`;
|
|
16
|
+
var PRESET_ROW_PATTERN = /(^[ \t]*-[ \t]*id:[ \t]*dsh-inbox[ \t]*\r?\n[ \t]*name:[ \t]*['"]?)([^'"\r\n]+)(['"]?[ \t]*$)/m;
|
|
17
|
+
function ensurePresetRow(body) {
|
|
18
|
+
const row = PRESET_ROW_PATTERN.exec(body);
|
|
19
|
+
if (row === null) {
|
|
20
|
+
return { body: `${body.replace(/\s*$/, "")}
|
|
21
|
+
${PRESET_ROW}`, change: "added" };
|
|
22
|
+
}
|
|
23
|
+
const from = (row[2] ?? "").trim();
|
|
24
|
+
if (from === PACKAGE_NAME) return { body, change: "unchanged" };
|
|
25
|
+
return { body: body.replace(PRESET_ROW_PATTERN, `$1${PACKAGE_NAME}$3`), change: "renamed", from };
|
|
26
|
+
}
|
|
27
|
+
function usage() {
|
|
28
|
+
return `dsh-inbox init \u2014\u2014 \u628A inbox \u88C5\u8FDB\u4E00\u4E2A dsh profile\uFF0C\u5E76\u8BA9\u52A9\u624B\u770B\u5F97\u89C1\u5B83\u7684\u5DE5\u5177
|
|
29
|
+
|
|
30
|
+
\u7528\u6CD5\uFF1A
|
|
31
|
+
dsh-inbox init [\u9009\u9879]
|
|
32
|
+
|
|
33
|
+
\u9009\u9879\uFF1A
|
|
34
|
+
--profile <\u540D\u5B57> dsh profile\uFF0C\u9ED8\u8BA4 inbox\uFF08\u4E0D\u5B58\u5728\u5219\u62A5\u9519\u5E76\u544A\u8BC9\u4F60\u600E\u4E48\u5EFA\uFF09
|
|
35
|
+
--create-profile profile \u4E0D\u5B58\u5728\u65F6\u7528 dsh \u81EA\u5E26\u7684 web \u6A21\u677F\u5EFA\u4E00\u4E2A\uFF08\u9ED8\u8BA4\u4E0D\u5F00\uFF09
|
|
36
|
+
--preset <id> agent preset \u7684 id\uFF0C\u9ED8\u8BA4 inbox
|
|
37
|
+
--package <\u6765\u6E90> \u63D2\u4EF6\u6765\u6E90\uFF0C\u9ED8\u8BA4 ${PACKAGE_NAME}\uFF08\u672C\u5730\u5F00\u53D1\u4F20\u4ED3\u5E93\u8DEF\u5F84\uFF09
|
|
38
|
+
--no-default \u4E0D\u628A\u9ED8\u8BA4 preset \u6307\u8FC7\u53BB\uFF08\u53EA\u88C5\uFF0C\u4E0D\u6539 dsh \u7684\u9ED8\u8BA4\u9009\u62E9\uFF09
|
|
39
|
+
--help \u8FD9\u4EFD\u8BF4\u660E
|
|
40
|
+
|
|
41
|
+
\u505A\u4E09\u4EF6\u4E8B\uFF1A\u2460 \u628A\u63D2\u4EF6\u88C5\u8FDB profile\uFF1B\u2461 \u590D\u5236 standard preset \u5230
|
|
42
|
+
<DSH_HOME>/.agent-presets/<id> \u5E76\u8FFD\u52A0\u672C\u63D2\u4EF6\uFF1B\u2462 \u628A\u7528\u6237\u7EA7\u9ED8\u8BA4 preset \u6307\u5411\u5B83\u3002
|
|
43
|
+
\u91CD\u590D\u8FD0\u884C\u662F\u5B89\u5168\u7684\uFF1A\u5DF2\u7ECF\u505A\u8FC7\u7684\u4E0D\u4F1A\u91CD\u590D\u505A\u3002`;
|
|
44
|
+
}
|
|
45
|
+
function parse(argv) {
|
|
46
|
+
const options = {
|
|
47
|
+
profile: "inbox",
|
|
48
|
+
preset: "",
|
|
49
|
+
source: PACKAGE_NAME,
|
|
50
|
+
defaultPreset: true,
|
|
51
|
+
createProfile: false
|
|
52
|
+
};
|
|
53
|
+
const rest = argv[0] === "init" ? argv.slice(1) : argv;
|
|
54
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
55
|
+
const flag = rest[index];
|
|
56
|
+
const value = () => {
|
|
57
|
+
const next = rest[index + 1];
|
|
58
|
+
if (next === void 0 || next.startsWith("--")) {
|
|
59
|
+
throw new Error(`${String(flag)} \u9700\u8981\u4E00\u4E2A\u503C`);
|
|
60
|
+
}
|
|
61
|
+
index += 1;
|
|
62
|
+
return next;
|
|
63
|
+
};
|
|
64
|
+
if (flag === "--help" || flag === "-h") return void 0;
|
|
65
|
+
else if (flag === "--profile") options.profile = value();
|
|
66
|
+
else if (flag === "--preset") options.preset = value();
|
|
67
|
+
else if (flag === "--package") options.source = value();
|
|
68
|
+
else if (flag === "--no-default") options.defaultPreset = false;
|
|
69
|
+
else if (flag === "--create-profile") options.createProfile = true;
|
|
70
|
+
else throw new Error(`\u770B\u4E0D\u61C2\u7684\u9009\u9879\uFF1A${String(flag)}`);
|
|
71
|
+
}
|
|
72
|
+
if (options.preset.length === 0) options.preset = "inbox";
|
|
73
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(options.preset)) {
|
|
74
|
+
throw new Error(`preset id \u53EA\u80FD\u7528\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u8FDE\u5B57\u7B26\uFF1A${options.preset}`);
|
|
75
|
+
}
|
|
76
|
+
return options;
|
|
77
|
+
}
|
|
78
|
+
function dshHome() {
|
|
79
|
+
const configured = process.env.DSH_HOME;
|
|
80
|
+
return configured !== void 0 && configured.length > 0 ? configured : join(homedir(), ".dsh");
|
|
81
|
+
}
|
|
82
|
+
function shippedStandard(profileDir) {
|
|
83
|
+
const candidates = [];
|
|
84
|
+
try {
|
|
85
|
+
const require2 = createRequire(join(profileDir, "package.json"));
|
|
86
|
+
candidates.push(dirname(require2.resolve("@deepseek-ai/dsh-agent-presets/package.json")));
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
candidates.push(
|
|
90
|
+
join(profileDir, "node_modules", "@deepseek-ai", "dsh-agent-presets"),
|
|
91
|
+
join(profileDir, "node_modules", "@deepseek-ai", "dsh", "node_modules", "@deepseek-ai", "dsh-agent-presets")
|
|
92
|
+
);
|
|
93
|
+
for (const candidate of candidates) {
|
|
94
|
+
const standard = join(candidate, "presets", "standard");
|
|
95
|
+
if (existsSync(join(standard, "agent.cordis.yml"))) return standard;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
function setDefaultPreset(settingsPath, id) {
|
|
100
|
+
const original = existsSync(settingsPath) ? readFileSync(settingsPath, "utf8") : "";
|
|
101
|
+
const commit = (next, note) => {
|
|
102
|
+
if (next === original) return "\u9ED8\u8BA4 preset \u5DF2\u7ECF\u662F\u5B83\uFF0C\u6CA1\u6539";
|
|
103
|
+
if (original.length > 0) writeFileSync(`${settingsPath}.bak-${String(Date.now())}`, original, "utf8");
|
|
104
|
+
writeFileSync(settingsPath, next, "utf8");
|
|
105
|
+
return note;
|
|
106
|
+
};
|
|
107
|
+
const lines = original.split(/\r?\n/);
|
|
108
|
+
const head = lines.findIndex((line) => /^agent-presets:\s*$/.test(line));
|
|
109
|
+
if (head === -1) {
|
|
110
|
+
const block = `agent-presets:
|
|
111
|
+
default: ${id}
|
|
112
|
+
`;
|
|
113
|
+
const body = original.length === 0 ? block : `${original.replace(/\s*$/, "")}
|
|
114
|
+
${block}`;
|
|
115
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
116
|
+
return commit(body, original.length === 0 ? "\u65B0\u5EFA\u4E86 settings.yaml" : `\u8FFD\u52A0\u4E86 agent-presets.default: ${id}`);
|
|
117
|
+
}
|
|
118
|
+
let end = lines.length;
|
|
119
|
+
for (let index = head + 1; index < lines.length; index += 1) {
|
|
120
|
+
const line = lines[index] ?? "";
|
|
121
|
+
if (line.length > 0 && !/^\s/.test(line)) {
|
|
122
|
+
end = index;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const before = lines.slice(0, end);
|
|
127
|
+
const after = lines.slice(end);
|
|
128
|
+
const defaultAt = before.findIndex((line, index) => index > head && /^\s+default:/.test(line));
|
|
129
|
+
if (defaultAt !== -1) before[defaultAt] = ` default: ${id}`;
|
|
130
|
+
else before.push(` default: ${id}`);
|
|
131
|
+
return commit([...before, ...after].join("\n"), `\u628A agent-presets.default \u6539\u6210 ${id}`);
|
|
132
|
+
}
|
|
133
|
+
function main(argv) {
|
|
134
|
+
let options;
|
|
135
|
+
try {
|
|
136
|
+
const parsed = parse(argv);
|
|
137
|
+
if (parsed === void 0) {
|
|
138
|
+
process.stdout.write(`${usage()}
|
|
139
|
+
`);
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
options = parsed;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
145
|
+
|
|
146
|
+
`);
|
|
147
|
+
process.stderr.write(`${usage()}
|
|
148
|
+
`);
|
|
149
|
+
return 2;
|
|
150
|
+
}
|
|
151
|
+
const home = dshHome();
|
|
152
|
+
const profileDir = join(home, "profiles", options.profile);
|
|
153
|
+
if (!existsSync(join(profileDir, "package.json"))) {
|
|
154
|
+
const hint = `\u5148\u5EFA\u4E00\u4E2A\uFF1Adsh --profile ${options.profile} --from-default-profile web --dump-config
|
|
155
|
+
\uFF08\u6216\u8005\u91CD\u8DD1\u65F6\u52A0\u4E0A --create-profile\uFF0C\u8BA9\u8FD9\u4E00\u6B65\u81EA\u5DF1\u53D1\u751F\uFF09
|
|
156
|
+
`;
|
|
157
|
+
if (!options.createProfile) {
|
|
158
|
+
process.stderr.write(`\u627E\u4E0D\u5230 profile \u300C${options.profile}\u300D\uFF08${profileDir}\uFF09\u3002
|
|
159
|
+
${hint}`);
|
|
160
|
+
return 1;
|
|
161
|
+
}
|
|
162
|
+
process.stdout.write(`\u24EA profile \u300C${options.profile}\u300D\u4E0D\u5B58\u5728\uFF0C\u7528 dsh \u7684 web \u6A21\u677F\u5EFA\u4E00\u4E2A\u2026
|
|
163
|
+
`);
|
|
164
|
+
const created = spawnSync(
|
|
165
|
+
"dsh",
|
|
166
|
+
["--profile", options.profile, "--from-default-profile", "web", "--dump-config"],
|
|
167
|
+
{ encoding: "utf8", shell: process.platform === "win32" }
|
|
168
|
+
);
|
|
169
|
+
if (created.error !== void 0 || created.status !== 0 || !existsSync(join(profileDir, "package.json"))) {
|
|
170
|
+
for (const stream of [created.stdout, created.stderr]) {
|
|
171
|
+
if (typeof stream === "string" && stream.trim().length > 0) process.stderr.write(`${stream.trimEnd()}
|
|
172
|
+
`);
|
|
173
|
+
}
|
|
174
|
+
process.stderr.write(
|
|
175
|
+
`\u5EFA profile \u5931\u8D25${created.error === void 0 ? "" : `\uFF08${created.error.message}\uFF09`}\u3002
|
|
176
|
+
${hint}`
|
|
177
|
+
);
|
|
178
|
+
return 1;
|
|
179
|
+
}
|
|
180
|
+
process.stdout.write(` \u5EFA\u597D\u4E86 ${profileDir}
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
process.stdout.write(`\u2460 \u628A ${options.source} \u88C5\u8FDB profile ${options.profile}\u2026
|
|
184
|
+
`);
|
|
185
|
+
const added = spawnSync("dsh", ["plugin", "--profile", options.profile, "add", options.source], {
|
|
186
|
+
stdio: "inherit",
|
|
187
|
+
shell: process.platform === "win32"
|
|
188
|
+
});
|
|
189
|
+
if (added.error !== void 0 || added.status !== 0) {
|
|
190
|
+
process.stderr.write(
|
|
191
|
+
`\u88C5\u63D2\u4EF6\u5931\u8D25${added.error === void 0 ? "" : `\uFF08${added.error.message}\uFF09`}\u3002
|
|
192
|
+
\u53EF\u4EE5\u5728\u4F60\u7684\u7EC8\u7AEF\u91CC\u624B\u52A8\u8DD1\uFF1Adsh plugin --profile ${options.profile} add ${options.source}
|
|
193
|
+
`
|
|
194
|
+
);
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
const standard = shippedStandard(profileDir);
|
|
198
|
+
if (standard === void 0) {
|
|
199
|
+
process.stderr.write("\u627E\u4E0D\u5230\u968F dsh \u9644\u5E26\u7684 standard preset\uFF0C\u65E0\u6CD5\u521B\u5EFA agent preset\u3002\n");
|
|
200
|
+
return 1;
|
|
201
|
+
}
|
|
202
|
+
const presetDir = join(home, ".agent-presets", options.preset);
|
|
203
|
+
if (existsSync(join(presetDir, "agent.cordis.yml"))) {
|
|
204
|
+
process.stdout.write(`\u2461 preset \u300C${options.preset}\u300D\u5DF2\u5B58\u5728\uFF0C\u53EA\u8865\u4E0A\u7F3A\u5931\u7684\u63D2\u4EF6\u884C
|
|
205
|
+
`);
|
|
206
|
+
} else {
|
|
207
|
+
process.stdout.write(`\u2461 \u4ECE standard \u590D\u5236\u4E00\u4EFD preset \u5230 ${presetDir}
|
|
208
|
+
`);
|
|
209
|
+
mkdirSync(dirname(presetDir), { recursive: true });
|
|
210
|
+
cpSync(standard, presetDir, { recursive: true });
|
|
211
|
+
const manifest = join(presetDir, "preset.yml");
|
|
212
|
+
if (existsSync(manifest)) {
|
|
213
|
+
const text = readFileSync(manifest, "utf8");
|
|
214
|
+
writeFileSync(
|
|
215
|
+
manifest,
|
|
216
|
+
text.replace(/^name:.*$/m, "name: \u6536\u4EF6\u7BB1\uFF08\u5E26 dsh-inbox\uFF09").replace(
|
|
217
|
+
/^description:.*$/m,
|
|
218
|
+
"description: \u6807\u51C6\u6A21\u5F0F + dsh-inbox\uFF1A\u52A9\u624B\u53EF\u4EE5\u76F4\u63A5\u67E5\u4F60\u7684\u6536\u4EF6\u7BB1\uFF08\u53EB\u5B83\u4ED3\u5E93 / inbox \u4E5F\u884C\uFF09\u5E76\u628A\u5185\u5BB9\u53D6\u56DE\u6765\u3002"
|
|
219
|
+
),
|
|
220
|
+
"utf8"
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const composition = join(presetDir, "agent.cordis.yml");
|
|
225
|
+
const body = readFileSync(composition, "utf8");
|
|
226
|
+
const outcome = ensurePresetRow(body);
|
|
227
|
+
if (outcome.change === "added") {
|
|
228
|
+
writeFileSync(composition, outcome.body, "utf8");
|
|
229
|
+
process.stdout.write(` \u5DF2\u628A ${PACKAGE_NAME} \u8FFD\u52A0\u8FDB preset \u7684\u7EC4\u5408
|
|
230
|
+
`);
|
|
231
|
+
} else if (outcome.change === "renamed") {
|
|
232
|
+
writeFileSync(composition, outcome.body, "utf8");
|
|
233
|
+
process.stdout.write(` preset \u91CC\u90A3\u4E00\u884C\u7684\u6765\u6E90\u4ECE ${String(outcome.from)} \u6539\u6210 ${PACKAGE_NAME}
|
|
234
|
+
`);
|
|
235
|
+
} else {
|
|
236
|
+
process.stdout.write(" preset \u91CC\u5DF2\u7ECF\u6709\u8FD9\u4E2A\u63D2\u4EF6\uFF0C\u8DF3\u8FC7\n");
|
|
237
|
+
}
|
|
238
|
+
if (options.defaultPreset) {
|
|
239
|
+
const changed = setDefaultPreset(join(home, "settings.yaml"), options.preset);
|
|
240
|
+
process.stdout.write(`\u2462 \u9ED8\u8BA4 preset\uFF1A${changed}
|
|
241
|
+
`);
|
|
242
|
+
}
|
|
243
|
+
process.stdout.write(
|
|
244
|
+
`
|
|
245
|
+
\u5B8C\u6210\u3002\u63A5\u4E0B\u6765\uFF1A
|
|
246
|
+
\xB7 \u91CD\u542F dsh\uFF08preset \u5728\u542F\u52A8\u65F6\u626B\u63CF\uFF09
|
|
247
|
+
\xB7 \u65B0\u5EFA\u4E00\u4E2A\u4F1A\u8BDD\uFF0C\u5B83\u5C31\u4F1A\u5E26\u4E0A\u6536\u4EF6\u7BB1\u5DE5\u5177\uFF1B\u60F3\u8BA9\u52A9\u624B\u67E5\u4ED3\u5E93\uFF0C\u76F4\u63A5\u95EE\u300C\u6211\u7684\u6536\u4EF6\u7BB1\u91CC\u6709\u54EA\u4E9B\u8FD8\u6CA1\u770B\u7684\u94FE\u63A5\u300D
|
|
248
|
+
\xB7 \u9762\u677F\uFF08\u4FA7\u680F Inbox\uFF09\u4E0D\u9700\u8981 preset\uFF0C\u88C5\u5B8C\u5C31\u5728
|
|
249
|
+
`
|
|
250
|
+
);
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
254
|
+
process.exitCode = main(process.argv.slice(2));
|
|
255
|
+
}
|
|
256
|
+
export {
|
|
257
|
+
ensurePresetRow,
|
|
258
|
+
parse
|
|
259
|
+
};
|