dsh-plugin-discourse 1.0.1 → 1.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/README.md CHANGED
@@ -5,9 +5,10 @@
5
5
 
6
6
  Discourse administration and agent tools for the [DeepSeek Harness](https://github.com/deepseek-ai).
7
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.
8
+ One Host plugin registers 17 model-facing tools over the Discourse REST API: an agent can search a
9
+ forum, read topics and profiles, publish posts, carry out the moderator and administrator work a
10
+ staff account would do in the web UI, and sign in through the user's browser with a scoped user API
11
+ key.
11
12
 
12
13
  ```
13
14
  User → DSH agent → discourse_* tools → Discourse REST API → forum
@@ -17,6 +18,7 @@ User → DSH agent → discourse_* tools → Discourse REST API → forum
17
18
 
18
19
  | Tool | Group | What it does | Key needed |
19
20
  |---|---|---|---|
21
+ | `discourse_login` | auth | Start browser authorization, report the session, finish an older payload page, cancel a pending attempt, or sign out | no |
20
22
  | `discourse_search` | read | Search topics, posts, and users | no |
21
23
  | `discourse_topics` | read | List a feed (`latest`/`new`/`unread`/`top`) or one category | no |
22
24
  | `discourse_topic` | read | Read a topic and a slice of its posts | no |
@@ -111,6 +113,11 @@ it at your forum, add an override to your **profile's** `cordis.patch.yml` (`$DS
111
113
  readOnly: true
112
114
  timeoutMs: 20000
113
115
  maxItems: 12
116
+ authMode: auto
117
+ loginScopes: read,write,session_info
118
+ loginClientId: dsh-plugin-discourse
119
+ loginApplicationName: DeepSeek Harness
120
+ loginTimeoutMs: 600000
114
121
  ```
115
122
 
116
123
  A loader override replaces the row's `config` wholesale, so restate every key you want to keep.
@@ -123,6 +130,11 @@ A loader override replaces the row's `config` wholesale, so restate every key yo
123
130
  | `readOnly` | `false` | Blocks every mutating tool when `true`. |
124
131
  | `timeoutMs` | `20000` | Per-request deadline, 1000–120000. |
125
132
  | `maxItems` | `12` | Default listing size, 1–50. |
133
+ | `authMode` | `auto` | `auto` uses a stored browser session for normal calls and `apiKey` for staff/admin calls; `api-key` ignores the session; `user` requires it for every call. |
134
+ | `loginScopes` | `read,write,session_info` | Comma-separated scopes requested by browser sign-in. Narrow or extend this to match the forum's `allow_user_api_key_scopes`. |
135
+ | `loginClientId` | `dsh-plugin-discourse` | Client id Discourse records with the authorized key. |
136
+ | `loginApplicationName` | `DeepSeek Harness` | Application name shown on the forum authorization page. |
137
+ | `loginTimeoutMs` | `600000` | Browser sign-in deadline, 30000–1800000. Discourse's device code lives 10 minutes. |
126
138
 
127
139
  Anonymous reads work out of the box against the default forum, so you can try `discourse_topics`
128
140
  before configuring anything.
@@ -144,11 +156,54 @@ export DISCOURSE_API_USERNAME="staff-bot"
144
156
 
145
157
  Row config wins over the environment when both are present.
146
158
 
159
+ ### Browser sign-in
160
+
161
+ `discourse_login` obtains a **user API key** for the signed-in forum account, so the plugin can act as
162
+ that user without an admin key in the profile. On current Discourse releases it uses the device
163
+ authorization flow (`Auth API` version 4):
164
+
165
+ 1. `discourse_login` with `action: "start"` returns a verification URL and an eight-character code.
166
+ 2. Open the URL, sign in to the forum if needed, and confirm the code.
167
+ 3. `discourse_login` with `action: "status"` reports the signed-in account.
168
+
169
+ The tool also accepts `waitSeconds` on `status` (0–120) when the caller wants to wait for the browser
170
+ step before reporting. `action: "cancel"` withdraws a pending attempt without touching a stored
171
+ session.
172
+
173
+ Forums older than the device flow get the classic `/user-api-key/new` page instead. The plugin asks
174
+ for the encrypted payload that page shows after approval, and
175
+ `discourse_login` with `action: "complete"` and the pasted `payload` finishes the sign-in. Neither
176
+ path needs an `auth_redirect`, a callback listener, or a change to
177
+ `allowed_user_api_auth_redirects`.
178
+
179
+ The authorized key is stored as the `discourse/session` record in the DSH credential store — never in
180
+ `cordis.patch.yml` and never in a tool result. `discourse_login` with `action: "logout"` revokes it at
181
+ the forum when possible and deletes the local record. `readOnly` still permits sign-in and sign-out:
182
+ they manage authentication, not forum content.
183
+
184
+ `authMode` decides which credential a request uses:
185
+
186
+ - `auto` (default) — normal reads and posts use the stored session; staff/admin tools keep using
187
+ `apiKey` when one is configured, so moderation and administration still work.
188
+ - `api-key` — ignore any stored session and always use `apiKey`.
189
+ - `user` — require the stored session for every call; requests fail with a sign-in hint when it is
190
+ missing or expired.
191
+
192
+ A user API key is limited to the account's own permissions and the scopes granted on the consent
193
+ page. It cannot call admin endpoints such as `/admin/users.json`; those tools need `apiKey` (or fail
194
+ with the forum's 403). A forum that does not allow `write` in `allow_user_api_key_scopes` needs
195
+ `loginScopes: read,session_info`.
196
+
147
197
  ## Safety
148
198
 
149
- - `readOnly: true` is enforced inside the plugin, before any mutating request is built.
199
+ - `readOnly: true` blocks every content-mutating tool before any forum request is built. Browser
200
+ sign-in and sign-out remain available because they manage the credential rather than content.
201
+ - `discourse_login` stores the user API key in the credential store and never prints it. Use
202
+ `action: "logout"` to revoke it at the forum and remove the local record.
150
203
  - `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.
204
+ those scopes receives the forum's 403 response, and the tool reports it instead of retrying. A
205
+ browser session does not widen a user key into an admin key; `authMode: auto` therefore prefers a
206
+ configured `apiKey` for staff and admin calls.
152
207
  - Failures carry the HTTP status and Discourse's own error text, so a wrong id or a missing scope is
153
208
  visible in the transcript.
154
209
  - Writes should be exercised with a staging forum first — `discourse_delete_post` with
@@ -160,16 +215,20 @@ The package is plain ESM with no runtime dependencies; `index.js` is the whole p
160
215
 
161
216
  ```bash
162
217
  node --check index.js # syntax
163
- npm test # register every tool and validate definitions offline
218
+ npm test # register every tool, validate definitions, and exercise browser login offline
164
219
  node scripts/smoke.mjs --live # additionally call read-only tools against the forum
220
+ node scripts/login-test.mjs # browser login flow against a stubbed forum and credential store
165
221
  npm run verify # build the npm tarball and check its file list
166
222
  node scripts/doctor.mjs --profile desktop # inspect a profile for a stale local link
167
223
  ```
168
224
 
169
225
  `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.
226
+ without starting a Harness. `scripts/login-test.mjs` drives the same `apply()` path with a stub
227
+ credential store, authorization seam, and forum: it starts the classic page, completes it with an RSA
228
+ payload, reads the status, and signs out, then repeats the device-code path. `scripts/verify-package.mjs`
229
+ packs the package with `npm pack` and fails when a runtime file is missing from `files`, when the
230
+ manifest loses `dsh.bundle`, or when a locale loses its display metadata. `prepack` runs the smoke and
231
+ login tests, so a broken package cannot be published.
173
232
 
174
233
  ## Release
175
234
 
@@ -182,7 +241,7 @@ push to `main` or a `v*` tag, and every pull request, verifies the package, and
182
241
  2. Commit, then tag and push:
183
242
 
184
243
  ```bash
185
- git tag -a v1.0.1 -m "dsh-plugin-discourse 1.0.1"
244
+ git tag -a v1.1.0 -m "dsh-plugin-discourse 1.1.0"
186
245
  git push origin main --follow-tags
187
246
  ```
188
247
 
@@ -199,22 +258,28 @@ Before the first release, add a **Trusted Publisher** on npmjs.com (package page
199
258
  | Environment | *(leave empty)* |
200
259
  | Allowed action | `npm publish` |
201
260
 
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.
261
+ With the trusted publisher in place the workflow needs no long-lived credential: it exchanges the
262
+ GitHub OIDC token (`id-token: write`) for publish rights and attaches provenance. Until that setting
263
+ exists, the publish job falls back to the repository's `NPM_TOKEN` secret, and it fails with a
264
+ diagnostic when neither credential is available. npm now requires an interactive 2FA challenge for
265
+ token and trusted-publisher management, so the one-time setup happens in the browser; after it, tagged
266
+ releases are fully automatic.
206
267
 
207
268
  ## 中文说明
208
269
 
209
- - 这是一个 **Host 插件**,向 DSH 智能体提供 16 个 Discourse 工具(读取 / 发帖 / 版主 / 管理员)。
270
+ - 这是一个 **Host 插件**,向 DSH 智能体提供 17 个 Discourse 工具(登录 / 读取 / 发帖 / 版主 / 管理员)。
271
+ - 浏览器授权登录:先调用 `discourse_login`(`action: "start"`),插件会返回论坛授权页地址和 8 位验证码;在浏览器中登录论坛并确认验证码后,再用 `action: "status"` 查看登录结果。中途放弃可用 `action: "cancel"`。新版 Discourse 使用设备授权码流程;旧版论坛会显示加密 payload,把该 payload 交给 `action: "complete"` 即可完成。两种方式都不需要配置 `auth_redirect`,也不需要修改 `allowed_user_api_auth_redirects`。
272
+ - 登录得到的用户 API Key 保存在 DSH 凭据存储的 `discourse/session` 记录中,不会写进 `cordis.patch.yml`,也不会出现在工具结果里;`action: "logout"` 会尽量在论坛撤销它并删除本地记录。
273
+ - `authMode: auto`(默认)下,普通读取和发帖使用已登录的会话,版主/管理员工具继续使用 `apiKey`;改为 `api-key` 可完全忽略会话,改为 `user` 则所有请求都必须先登录。
274
+ - 用户 API Key 只拥有该账号的权限,不能调用 `/admin/*` 管理接口;管理工具仍需 `apiKey`。如果论坛不允许 `write` scope,请把 `loginScopes` 调成 `read,session_info`。
210
275
  - 安装:优先用 Web UI 的 *设置 → 插件 → 安装 bundle*,填入 `dsh-plugin-discourse`(npm)或 `github:AkarinLiu/dsh-plugin-discourse`;安装器会先替换旧的本地链接。
211
276
  - 命令行:`dsh plugin --profile desktop add dsh-plugin-discourse` 只是把参数转交给 pnpm,安装后还需在插件页选中该 bundle。
212
277
  - Windows 从本地克隆切到 npm 时报 `ERR_PNPM_EPERM ... rename ...dsh-plugin-discourse...`:这是 profile 里遗留的目录联接;运行 `node scripts/doctor.mjs --profile desktop --fix` 后再安装(脚本只删链接,不动源码),然后重启 Harness。
213
278
  - 发布:推送 `v*` 标签会触发 `.github/workflows/publish.yml`。首次发布前在 npmjs.com 的包设置 → *Trusted Publishing* 添加 GitHub Actions(仓库 `AkarinLiu/dsh-plugin-discourse`、工作流 `publish.yml`、环境留空、允许 `npm publish`),之后不需要 `NPM_TOKEN`,由 OIDC 发布并生成 provenance。
214
279
  - 配置:在 profile 的 `cordis.patch.yml` 中加一条 `id: discourse` 的覆盖,把 `baseUrl` 改成你的论坛并填入 `apiKey`(覆盖会整体替换 `config`,请把其它键一并写全)。
215
- - 不需要 API Key 即可试用只读工具(默认指向 `https://meta.discourse.org`)。
280
+ - 不需要 API Key 即可试用只读工具(默认指向 `https://meta.discourse.org`),也可以直接用浏览器登录获得用户会话。
216
281
  - 密钥建议通过环境变量 `DISCOURSE_API_KEY` / `DISCOURSE_API_USERNAME` 提供,避免写进配置文件。
217
- - 不需要写入权限时把 `readOnly` 设为 `true`,所有写操作会被插件直接拒绝。
282
+ - 不需要写入权限时把 `readOnly` 设为 `true`,所有写操作会被插件直接拒绝;登录和退出仍可使用。
218
283
 
219
284
  ## License
220
285
 
package/cordis.patch.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  # Inserts the Discourse tool bundle into the current DSH profile.
2
2
  # Change `baseUrl` to your forum and point `apiKey` at an admin-scoped key
3
- # before enabling writes.
3
+ # before enabling writes. Browser sign-in stores a scoped user API key in the
4
+ # DSH credential store, so no key is required in this file for `discourse_login`.
4
5
  - insert:
5
6
  - id: discourse
6
7
  name: dsh-plugin-discourse
@@ -11,3 +12,8 @@
11
12
  readOnly: false
12
13
  timeoutMs: 20000
13
14
  maxItems: 12
15
+ authMode: auto
16
+ loginScopes: read,write,session_info
17
+ loginClientId: dsh-plugin-discourse
18
+ loginApplicationName: DeepSeek Harness
19
+ loginTimeoutMs: 600000