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 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
- # Temporary Holding Version
1
+ # dsh-plugin-discourse
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/dsh-plugin-discourse.svg)](https://www.npmjs.com/package/dsh-plugin-discourse)
4
+ [![Publish to npm](https://github.com/AkarinLiu/dsh-plugin-discourse/actions/workflows/publish.yml/badge.svg)](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).
@@ -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>