feat(rkd)!: run as the owner's host for the Claude Code App (#917) #3

Merged
dozken merged 3 commits from feat/app-hosts into main 2026-09-20 14:40:00 +02:00
Owner

Summary

The plugin side of #917 plan B: this machine stops being a shared bot account and becomes its owner's host for the Claude Code integration in RKD. The server routes only that person's requests here, so pairing codes, allowlists and channel lists are gone from the plugin — RKD decides who may reach you. Also brings the easier approvals from the first commit: ✅ / ❌ reactions, a bare yes / no, "typing…" and shorter prompts.

Needs the server work in nanoya-biz/rkd#971. Until that is deployed, 0.2.0 cannot sign in.

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup (no user-visible change)
  • UI / UX
  • Build, CI, or tooling
  • Docs

Test plan

Automated:

  • pnpm test: 83 unit tests — the classifier (only the owner's messages count, coded and bare verdicts, reactions), the REST client and the device-code sign-in (polls until approved, shows the code once, gives up when it expires, reports a consumed code), and the bridge (threading, chunking, edit throttle and edit-window fallback, credential-upload refusal, prompt routing to the owner's DM before any trigger, typing)
  • pnpm typecheck, pnpm check:dist, claude plugin validate --strict, and the Node 20 handshake smoke
  • pnpm test:integration against a real rkd-server built from #971: 7 cases — only the owner's mention is forwarded while a stranger gets the not-connected reply, replies post as the App with for_user_id, edits and reactions land, only the owner's ✅ approves and a bare no denies, and a DM round-trips

Manual, full loop on a local server:

  • /rkd:configure <server> started the sign-in itself and showed the code; approving it in RKD connected the machine
  • @claude_code how many files are in this repo? → answered in the thread
  • Asked for an rm outside the project: prompt in the thread mentioning me, ✅ reaction approved it, prompt edited to "✅ Allowed", file gone

Review checklist

  • No console.log / commented-out code left behind (the login CLI prints to the terminal on purpose)
  • No secrets, tokens, or personal URLs added
  • New dependencies — none; @modelcontextprotocol/sdk, ws and zod unchanged and still bundled into dist/server.js
  • README updated (setup, what reaches Claude, threat model, development)

Migrations and breaking changes

BREAKING: 0.2.0 needs a server with App hosts, and the old bot-account credentials in .env are ignored — sign in again with login. /rkd:access is gone; /rkd:configure covers sign-in and status.

Notes for the reviewer

  • Names. rkd@rkd repeated itself, so the marketplace is now rkd-plugins: claude plugin install rkd@rkd-plugins, channel plugin:rkd@rkd-plugins. The plugin stays rkd (skills /rkd:configure, tools mcp__plugin_rkd_rkd__*), like telegram@claude-plugins-official. Anyone who installed the old marketplace name needs to re-add it.

  • Sign-in is a skill now. No password is involved, so /rkd:configure <server> runs the login in the background and shows the code instead of asking the user to run a version-pinned path in another terminal.

  • src/triggers.ts is now small on purpose: the server is the boundary, and this is the second line of defence (anything not written by the owner is dropped).

  • src/cli.ts — the device-code flow. The plugin never sees a password.

  • The bridge still holds the prompt bookkeeping, since Claude Code's request_id is what an answer has to name.

  • Worth knowing for anyone testing: don't run --plugin-dir while a plugin of the same name is installed. Claude Code then reports plugin:rkd@inline · plugin not installed and the channel silently never registers — that cost an hour. Uninstall first, or add the checkout as a local marketplace (claude plugin marketplace add <path>).

https://claude.ai/code/session_01GdkP2Y3vcxcGz5XCFFWW16

## Summary The plugin side of #917 plan B: this machine stops being a shared bot account and becomes **its owner's host** for the Claude Code integration in RKD. The server routes only that person's requests here, so pairing codes, allowlists and channel lists are gone from the plugin — RKD decides who may reach you. Also brings the easier approvals from the first commit: ✅ / ❌ reactions, a bare yes / no, "typing…" and shorter prompts. Needs the server work in nanoya-biz/rkd#971. Until that is deployed, 0.2.0 cannot sign in. ## Type of change - [ ] Bug fix - [x] New feature - [x] Refactor / cleanup (no user-visible change) - [ ] UI / UX - [ ] Build, CI, or tooling - [x] Docs ## Test plan Automated: - [x] `pnpm test`: 83 unit tests — the classifier (only the owner's messages count, coded and bare verdicts, reactions), the REST client and the device-code sign-in (polls until approved, shows the code once, gives up when it expires, reports a consumed code), and the bridge (threading, chunking, edit throttle and edit-window fallback, credential-upload refusal, prompt routing to the owner's DM before any trigger, typing) - [x] `pnpm typecheck`, `pnpm check:dist`, `claude plugin validate --strict`, and the Node 20 handshake smoke - [x] `pnpm test:integration` against a real rkd-server built from #971: 7 cases — only the owner's mention is forwarded while a stranger gets the not-connected reply, replies post as the App with `for_user_id`, edits and reactions land, only the owner's ✅ approves and a bare `no` denies, and a DM round-trips Manual, full loop on a local server: - [x] `/rkd:configure <server>` started the sign-in itself and showed the code; approving it in RKD connected the machine - [x] `@claude_code how many files are in this repo?` → answered in the thread - [x] Asked for an `rm` outside the project: prompt in the thread mentioning me, ✅ reaction approved it, prompt edited to "✅ Allowed", file gone ## Review checklist - [x] No `console.log` / commented-out code left behind (the `login` CLI prints to the terminal on purpose) - [x] No secrets, tokens, or personal URLs added - [x] New dependencies — none; `@modelcontextprotocol/sdk`, `ws` and `zod` unchanged and still bundled into `dist/server.js` - [x] README updated (setup, what reaches Claude, threat model, development) ## Migrations and breaking changes BREAKING: 0.2.0 needs a server with App hosts, and the old bot-account credentials in `.env` are ignored — sign in again with `login`. `/rkd:access` is gone; `/rkd:configure` covers sign-in and status. ## Notes for the reviewer - **Names.** `rkd@rkd` repeated itself, so the marketplace is now `rkd-plugins`: `claude plugin install rkd@rkd-plugins`, channel `plugin:rkd@rkd-plugins`. The plugin stays `rkd` (skills `/rkd:configure`, tools `mcp__plugin_rkd_rkd__*`), like `telegram@claude-plugins-official`. Anyone who installed the old marketplace name needs to re-add it. - **Sign-in is a skill now.** No password is involved, so `/rkd:configure <server>` runs the login in the background and shows the code instead of asking the user to run a version-pinned path in another terminal. - `src/triggers.ts` is now small on purpose: the server is the boundary, and this is the second line of defence (anything not written by the owner is dropped). - `src/cli.ts` — the device-code flow. The plugin never sees a password. - The bridge still holds the prompt bookkeeping, since Claude Code's `request_id` is what an answer has to name. - Worth knowing for anyone testing: don't run `--plugin-dir` while a plugin of the same name is installed. Claude Code then reports `plugin:rkd@inline · plugin not installed` and the channel silently never registers — that cost an hour. Uninstall first, or add the checkout as a local marketplace (`claude plugin marketplace add <path>`). https://claude.ai/code/session_01GdkP2Y3vcxcGz5XCFFWW16
A permission prompt now mentions the requester (so their phone gets a
push), names the tool briefly with a short input preview, and accepts
a ✅/❌ reaction or a bare yes/no replying to it (or the only open prompt
in that chat) from a paired user; `yes <id>` still works. The prompt is
edited to show the outcome. The bot shows "typing…" from a delivered
trigger until it next posts, and again after an approval.

/rkd:configure recommends --permission-mode auto and offers to allow
the plugin's own tools, so answering in chat no longer needs approval.

Claude-Session: https://claude.ai/code/session_01GdkP2Y3vcxcGz5XCFFWW16
feat(rkd)!: run as the owner's host for the Claude Code App (#917)
All checks were successful
CI / rkd-plugin (pull_request) Successful in 31s
c8162b1f08
Plan B: Claude Code is one App in RKD, installed per server, and each
person connects their own machine as their host for it. The server
routes a request only to the host of whoever asked, so this plugin no
longer keeps a bot account, pairing codes, an allowlist or channel
lists.

- Sign-in is a device code: `login <server>` shows a code, the owner
  approves it in RKD → Settings → Apps → Claude Code, and the machine
  gets an `rkdh_` host token. No password, no refresh rotation.
- The socket receives HostReady and then only AppChannelTrigger,
  AppDmTrigger and AppReaction; triggers.ts still drops anything not
  from the owner.
- Replies post as the App "for" the owner; permission prompts mention
  the owner and go to the owner's DM before any trigger.
- /rkd:access is gone; /rkd:configure covers sign-in and status.

BREAKING CHANGE: needs an RKD server with App hosts; bot-account
credentials in .env are ignored, sign in again with `login`.

Claude-Session: https://claude.ai/code/session_01GdkP2Y3vcxcGz5XCFFWW16
fix(rkd): name the marketplace rkd-plugins; /rkd:configure runs the sign-in
All checks were successful
CI / rkd-plugin (pull_request) Successful in 42s
068c73a04f
`rkd@rkd` repeated itself. Claude Code's own plugins name the marketplace
after the publisher and the plugin after the product (telegram@claude-
plugins-official), so the marketplace is now `rkd-plugins`: install with
`claude plugin install rkd@rkd-plugins`, launch the channel with
`plugin:rkd@rkd-plugins`.

The sign-in no longer needs a version-pinned path in a separate terminal.
It is a device code with no password to protect, so `/rkd:configure
<server>` starts it in the background itself and shows the code to
enter in RKD. (The allowed-tools glob has to tolerate the quoted
`server.js" login` form, otherwise the command needs a manual approval.)

Also fixes copy left over from the bot-account design: the repo README
and plugin.json (what /plugin shows) still described a bot account,
pairing and /rkd:access.

Claude-Session: https://claude.ai/code/session_01GdkP2Y3vcxcGz5XCFFWW16
dozken merged commit 39de31ee58 into main 2026-09-20 14:40:00 +02:00
dozken deleted branch feat/app-hosts 2026-09-20 14:40:00 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rkd/claude-plugins!3
No description provided.