dsh-plugin-discourse 0.0.0-stage → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +220 -2
- package/cordis.patch.yml +13 -0
- package/icon.svg +15 -0
- package/index.js +1240 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +56 -4
- package/scripts/doctor.mjs +159 -0
- package/scripts/smoke.mjs +118 -0
- package/scripts/verify-package.mjs +154 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AkarinLiu
|
|
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
CHANGED
|
@@ -1,3 +1,221 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-plugin-discourse
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-plugin-discourse)
|
|
4
|
+
[](https://github.com/AkarinLiu/dsh-plugin-discourse/actions/workflows/publish.yml)
|
|
5
|
+
|
|
6
|
+
Discourse administration and agent tools for the [DeepSeek Harness](https://github.com/deepseek-ai).
|
|
7
|
+
|
|
8
|
+
One Host plugin registers 16 model-facing tools over the Discourse REST API: an agent can search a
|
|
9
|
+
forum, read topics and profiles, publish posts, and carry out the moderator and administrator work a
|
|
10
|
+
staff account would do in the web UI.
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
User → DSH agent → discourse_* tools → Discourse REST API → forum
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Tools
|
|
17
|
+
|
|
18
|
+
| Tool | Group | What it does | Key needed |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| `discourse_search` | read | Search topics, posts, and users | no |
|
|
21
|
+
| `discourse_topics` | read | List a feed (`latest`/`new`/`unread`/`top`) or one category | no |
|
|
22
|
+
| `discourse_topic` | read | Read a topic and a slice of its posts | no |
|
|
23
|
+
| `discourse_categories` | read | List categories and subcategories with ids | no |
|
|
24
|
+
| `discourse_user` | read | Read one profile with roles, trust level, and activity counts | no |
|
|
25
|
+
| `discourse_create_topic` | author | Create a topic with category and tags | yes |
|
|
26
|
+
| `discourse_reply` | author | Reply to a topic, optionally to a specific post | yes |
|
|
27
|
+
| `discourse_update_post` | author | Replace a post body | yes |
|
|
28
|
+
| `discourse_flag_post` | author | Flag a post as spam, inappropriate, or off-topic | yes |
|
|
29
|
+
| `discourse_review_queue` | moderate | List flagged items awaiting a decision | moderator |
|
|
30
|
+
| `discourse_delete_post` | moderate | Delete or permanently destroy a post | moderator |
|
|
31
|
+
| `discourse_restore_post` | moderate | Restore a deleted post | moderator |
|
|
32
|
+
| `discourse_admin_users` | admin | Search the user directory by status and order | admin |
|
|
33
|
+
| `discourse_moderate_user` | admin | Suspend, silence, lift a sanction, or activate an account | admin |
|
|
34
|
+
| `discourse_site_settings` | admin | Read settings, or change one by name | admin |
|
|
35
|
+
| `discourse_admin_stats` | admin | Read a dashboard report as a time series | admin |
|
|
36
|
+
|
|
37
|
+
Every listing is bounded (12 items by default, 50 maximum) and every post body is clipped, so a
|
|
38
|
+
thread search never floods the model context. Results keep ids, so a follow-up call reads the detail.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
The bundle is a profile plugin: it is installed into one DSH profile and selected in that profile's
|
|
43
|
+
bundle list. Install it through the Web UI when you can: DSH's bundle installer replaces an existing
|
|
44
|
+
copy or local link before it writes the package, while the CLI is a thin pnpm wrapper.
|
|
45
|
+
|
|
46
|
+
**From the Web UI (recommended)** — open *Settings → Plugins → Install bundle* and enter
|
|
47
|
+
`dsh-plugin-discourse` for the npm package, or `github:AkarinLiu/dsh-plugin-discourse`, or a local
|
|
48
|
+
clone path. DSH installs the package, adds it to the profile's bundle list, and activates it.
|
|
49
|
+
|
|
50
|
+
**From npm on the CLI** — the launcher passes everything after `plugin` to pnpm, so this is a plain
|
|
51
|
+
pnpm install rather than the bundle installer:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
dsh plugin --profile desktop add dsh-plugin-discourse
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
After a CLI install, select `dsh-plugin-discourse` once in the Plugins page (or through the
|
|
58
|
+
`plugin_manager` tool) so the profile records it as a bundle.
|
|
59
|
+
|
|
60
|
+
**From a clone** — `gh repo clone AkarinLiu/dsh-plugin-discourse`, then
|
|
61
|
+
`dsh plugin --profile desktop add ./dsh-plugin-discourse`. On Windows this creates a directory
|
|
62
|
+
junction in the profile; read the next section before moving that profile to the npm package.
|
|
63
|
+
|
|
64
|
+
To remove it, uninstall the bundle from the Plugins page, which drops the dependency and the bundle
|
|
65
|
+
entry together.
|
|
66
|
+
|
|
67
|
+
> The plugin is Host-only: no build step, no dependencies, no client assets.
|
|
68
|
+
|
|
69
|
+
### Windows: EPERM when replacing a local link
|
|
70
|
+
|
|
71
|
+
A clone install leaves `node_modules\dsh-plugin-discourse` as a directory junction. pnpm's hoisted
|
|
72
|
+
linker does not always record or prune that junction, so a later npm install cannot replace it and
|
|
73
|
+
Windows refuses the rename:
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
[ERR_PNPM_EPERM] [importPackage ...\node_modules\dsh-plugin-discourse]
|
|
77
|
+
EPERM: operation not permitted, rename '...dsh-plugin-discourse_tmp_...' -> '...dsh-plugin-discourse'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The checkout is untouched; only the link is stale. From a clone of this repository, let the bundled
|
|
81
|
+
doctor remove the link and then reinstall:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
node scripts/doctor.mjs --profile desktop --fix
|
|
85
|
+
dsh plugin --profile desktop add dsh-plugin-discourse
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Or delete the junction by hand and retry:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
$link = "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-plugin-discourse"
|
|
92
|
+
if ((Get-Item $link -Force -ErrorAction SilentlyContinue).LinkType -eq 'Junction') {
|
|
93
|
+
(Get-Item $link -Force).Delete()
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Restart the Harness after the package is replaced so the new module generation loads.
|
|
98
|
+
|
|
99
|
+
## Configure
|
|
100
|
+
|
|
101
|
+
The bundle ships its defaults in `cordis.patch.yml`, which DSH applies as the bundle layer. To point
|
|
102
|
+
it at your forum, add an override to your **profile's** `cordis.patch.yml` (`$DSH_HOME/profiles/<name>/cordis.patch.yml`):
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
- id: discourse
|
|
106
|
+
name: dsh-plugin-discourse
|
|
107
|
+
config:
|
|
108
|
+
baseUrl: https://forum.example.com
|
|
109
|
+
apiKey: ''
|
|
110
|
+
apiUsername: ''
|
|
111
|
+
readOnly: true
|
|
112
|
+
timeoutMs: 20000
|
|
113
|
+
maxItems: 12
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
A loader override replaces the row's `config` wholesale, so restate every key you want to keep.
|
|
117
|
+
|
|
118
|
+
| Key | Default | Meaning |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `baseUrl` | `https://meta.discourse.org` | Forum origin. Point this at your own forum. |
|
|
121
|
+
| `apiKey` | `''` | Discourse API key. Empty means anonymous, read-only access. |
|
|
122
|
+
| `apiUsername` | `''` → `system` | Username the key acts as. |
|
|
123
|
+
| `readOnly` | `false` | Blocks every mutating tool when `true`. |
|
|
124
|
+
| `timeoutMs` | `20000` | Per-request deadline, 1000–120000. |
|
|
125
|
+
| `maxItems` | `12` | Default listing size, 1–50. |
|
|
126
|
+
|
|
127
|
+
Anonymous reads work out of the box against the default forum, so you can try `discourse_topics`
|
|
128
|
+
before configuring anything.
|
|
129
|
+
|
|
130
|
+
### Credentials
|
|
131
|
+
|
|
132
|
+
Generate a key in Discourse under *Admin → API → API keys*. Two shapes work:
|
|
133
|
+
|
|
134
|
+
- **Global key with an admin user** — full tool coverage, including `discourse_admin_*`.
|
|
135
|
+
- **Granular key** — grant only the scopes you want (`topics:write`, `posts:write`, `users:moderate`,
|
|
136
|
+
and similar). Any tool whose scope is missing fails with the forum's own 403 message.
|
|
137
|
+
|
|
138
|
+
Rather than storing the secret in YAML, the plugin also reads it from the environment:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
export DISCOURSE_API_KEY="..."
|
|
142
|
+
export DISCOURSE_API_USERNAME="staff-bot"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Row config wins over the environment when both are present.
|
|
146
|
+
|
|
147
|
+
## Safety
|
|
148
|
+
|
|
149
|
+
- `readOnly: true` is enforced inside the plugin, before any mutating request is built.
|
|
150
|
+
- `discourse_moderate_user` and the site-settings tools require an admin-scoped key; a key without
|
|
151
|
+
those scopes receives the forum's 403 response, and the tool reports it instead of retrying.
|
|
152
|
+
- Failures carry the HTTP status and Discourse's own error text, so a wrong id or a missing scope is
|
|
153
|
+
visible in the transcript.
|
|
154
|
+
- Writes should be exercised with a staging forum first — `discourse_delete_post` with
|
|
155
|
+
`permanent: true` is irreversible.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
The package is plain ESM with no runtime dependencies; `index.js` is the whole plugin.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
node --check index.js # syntax
|
|
163
|
+
npm test # register every tool and validate definitions offline
|
|
164
|
+
node scripts/smoke.mjs --live # additionally call read-only tools against the forum
|
|
165
|
+
npm run verify # build the npm tarball and check its file list
|
|
166
|
+
node scripts/doctor.mjs --profile desktop # inspect a profile for a stale local link
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`scripts/smoke.mjs` drives `apply()` with a stub context, so it exercises the real registration path
|
|
170
|
+
without starting a Harness. `scripts/verify-package.mjs` packs the package with `npm pack` and fails
|
|
171
|
+
when a runtime file is missing from `files`, when the manifest loses `dsh.bundle`, or when a locale
|
|
172
|
+
loses its display metadata. `prepack` runs the smoke test, so a broken package cannot be published.
|
|
173
|
+
|
|
174
|
+
## Release
|
|
175
|
+
|
|
176
|
+
Releases are automated by [`.github/workflows/publish.yml`](.github/workflows/publish.yml): every
|
|
177
|
+
push to `main` or a `v*` tag, and every pull request, verifies the package, and a `v*` tag publishes it to
|
|
178
|
+
[npmjs.com](https://www.npmjs.com/package/dsh-plugin-discourse) with
|
|
179
|
+
[provenance](https://docs.npmjs.com/generating-provenance-statements).
|
|
180
|
+
|
|
181
|
+
1. Bump `version` in `package.json` (the tag must match it exactly).
|
|
182
|
+
2. Commit, then tag and push:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
git tag -a v1.0.1 -m "dsh-plugin-discourse 1.0.1"
|
|
186
|
+
git push origin main --follow-tags
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
3. The workflow runs on Node 24, verifies the tarball, checks `v<version>`, and publishes with
|
|
190
|
+
`--provenance --access public`.
|
|
191
|
+
|
|
192
|
+
Before the first release, add a **Trusted Publisher** on npmjs.com (package page → *Settings* →
|
|
193
|
+
*Trusted Publishing* → *GitHub Actions*):
|
|
194
|
+
|
|
195
|
+
| Field | Value |
|
|
196
|
+
|---|---|
|
|
197
|
+
| Repository | `AkarinLiu/dsh-plugin-discourse` |
|
|
198
|
+
| Workflow file | `publish.yml` |
|
|
199
|
+
| Environment | *(leave empty)* |
|
|
200
|
+
| Allowed action | `npm publish` |
|
|
201
|
+
|
|
202
|
+
No `NPM_TOKEN` secret is needed: the workflow exchanges the GitHub OIDC token
|
|
203
|
+
(`id-token: write`) for publish rights and attaches provenance. npm now requires an interactive 2FA
|
|
204
|
+
challenge for token and trusted-publisher management, so this one-time setup happens in the browser;
|
|
205
|
+
after it, tagged releases are fully automatic.
|
|
206
|
+
|
|
207
|
+
## 中文说明
|
|
208
|
+
|
|
209
|
+
- 这是一个 **Host 插件**,向 DSH 智能体提供 16 个 Discourse 工具(读取 / 发帖 / 版主 / 管理员)。
|
|
210
|
+
- 安装:优先用 Web UI 的 *设置 → 插件 → 安装 bundle*,填入 `dsh-plugin-discourse`(npm)或 `github:AkarinLiu/dsh-plugin-discourse`;安装器会先替换旧的本地链接。
|
|
211
|
+
- 命令行:`dsh plugin --profile desktop add dsh-plugin-discourse` 只是把参数转交给 pnpm,安装后还需在插件页选中该 bundle。
|
|
212
|
+
- Windows 从本地克隆切到 npm 时报 `ERR_PNPM_EPERM ... rename ...dsh-plugin-discourse...`:这是 profile 里遗留的目录联接;运行 `node scripts/doctor.mjs --profile desktop --fix` 后再安装(脚本只删链接,不动源码),然后重启 Harness。
|
|
213
|
+
- 发布:推送 `v*` 标签会触发 `.github/workflows/publish.yml`。首次发布前在 npmjs.com 的包设置 → *Trusted Publishing* 添加 GitHub Actions(仓库 `AkarinLiu/dsh-plugin-discourse`、工作流 `publish.yml`、环境留空、允许 `npm publish`),之后不需要 `NPM_TOKEN`,由 OIDC 发布并生成 provenance。
|
|
214
|
+
- 配置:在 profile 的 `cordis.patch.yml` 中加一条 `id: discourse` 的覆盖,把 `baseUrl` 改成你的论坛并填入 `apiKey`(覆盖会整体替换 `config`,请把其它键一并写全)。
|
|
215
|
+
- 不需要 API Key 即可试用只读工具(默认指向 `https://meta.discourse.org`)。
|
|
216
|
+
- 密钥建议通过环境变量 `DISCOURSE_API_KEY` / `DISCOURSE_API_USERNAME` 提供,避免写进配置文件。
|
|
217
|
+
- 不需要写入权限时把 `readOnly` 设为 `true`,所有写操作会被插件直接拒绝。
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT © 2026 AkarinLiu. See [LICENSE](LICENSE).
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Inserts the Discourse tool bundle into the current DSH profile.
|
|
2
|
+
# Change `baseUrl` to your forum and point `apiKey` at an admin-scoped key
|
|
3
|
+
# before enabling writes.
|
|
4
|
+
- insert:
|
|
5
|
+
- id: discourse
|
|
6
|
+
name: dsh-plugin-discourse
|
|
7
|
+
config:
|
|
8
|
+
baseUrl: https://meta.discourse.org
|
|
9
|
+
apiKey: ''
|
|
10
|
+
apiUsername: ''
|
|
11
|
+
readOnly: false
|
|
12
|
+
timeoutMs: 20000
|
|
13
|
+
maxItems: 12
|
package/icon.svg
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Discourse forum tools">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="discourse-bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#3b5bdb"/>
|
|
5
|
+
<stop offset="1" stop-color="#0ea5a3"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="15" fill="url(#discourse-bg)"/>
|
|
9
|
+
<path d="M17 15h30a6 6 0 0 1 6 6v16a6 6 0 0 1-6 6H31l-10 8v-8h-4a6 6 0 0 1-6-6V21a6 6 0 0 1 6-6z" fill="#ffffff" opacity="0.96"/>
|
|
10
|
+
<circle cx="23" cy="29" r="2.6" fill="#3b5bdb"/>
|
|
11
|
+
<circle cx="32" cy="29" r="2.6" fill="#3b5bdb"/>
|
|
12
|
+
<circle cx="41" cy="29" r="2.6" fill="#3b5bdb"/>
|
|
13
|
+
<path d="M46 38l7 3v6.5c0 4.2-3 7.6-7 8.5-4-0.9-7-4.3-7-8.5V41l7-3z" fill="#ffffff" opacity="0.96"/>
|
|
14
|
+
<path d="M43.4 46.4l2.1 2.1 4-4.4" fill="none" stroke="#0ea5a3" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
15
|
+
</svg>
|