@thomas-elliott/paperclip-plugin-matrix 0.0.0-stage → 0.1.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/README.md +150 -2
- package/dist/manifest.js +151 -0
- package/dist/manifest.js.map +7 -0
- package/dist/ui/index.js +172 -0
- package/dist/ui/index.js.map +7 -0
- package/dist/worker.js +46527 -0
- package/dist/worker.js.map +7 -0
- package/package.json +50 -4
package/README.md
CHANGED
|
@@ -1,3 +1,151 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Matrix for Paperclip
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This plugin connects [Paperclip](https://paperclip.ing) to a [Matrix](https://matrix.org) homeserver.
|
|
4
|
+
A bot account posts Paperclip's news in your rooms, and people can reply, decide approvals and ask
|
|
5
|
+
agents for work from Matrix.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Each issue gets one thread in its project's room, or in the default room. The bot posts there when
|
|
10
|
+
the issue is created, when an agent comments on it, when an agent's run on it fails, and when it is
|
|
11
|
+
done.
|
|
12
|
+
- A linked person's reply in an issue's thread becomes their comment on the issue.
|
|
13
|
+
- Approvals appear in the default room. The bot adds a tick and a cross reaction to each one: a
|
|
14
|
+
linked person selects the tick to approve or the cross to reject. The message then shows the
|
|
15
|
+
decision and who made it, including decisions made in Paperclip.
|
|
16
|
+
- When an agent asks for confirmation, the bot posts in the issue's thread with the same tick and
|
|
17
|
+
cross, to accept or reject. Answer an agent's question by replying in the thread.
|
|
18
|
+
- Mention the bot, or send it a direct message, and it creates an issue from your message. The
|
|
19
|
+
issue goes to an agent mentioned as `@url-key` in the message, otherwise the project's lead agent
|
|
20
|
+
in a project's room, otherwise the default agent. The conversation carries on in your message's
|
|
21
|
+
thread.
|
|
22
|
+
- `!clip status` shows open work, working agents and waiting approvals. `!clip issues` lists open
|
|
23
|
+
issues, or only the project's issues in a project's room.
|
|
24
|
+
- Budget warnings appear in the default room.
|
|
25
|
+
- A dashboard widget shows whether the bot is connected and reading rooms, and lets you link your
|
|
26
|
+
Matrix account.
|
|
27
|
+
- A Matrix tab on each project picks the project's room from the rooms the bot is in.
|
|
28
|
+
|
|
29
|
+
Only linked people can reply, decide, ask for work or use commands. The bot ignores everyone else.
|
|
30
|
+
|
|
31
|
+
## Limitations
|
|
32
|
+
|
|
33
|
+
- The bot cannot read encrypted rooms. It says so once in any encrypted room it is in. Matrix cannot
|
|
34
|
+
turn encryption off in a room, so create rooms for the bot with encryption turned off. Element
|
|
35
|
+
encrypts direct messages, so for a direct conversation create a private room with encryption off
|
|
36
|
+
and mention the bot there.
|
|
37
|
+
- Everything is posted by one bot account, with the agent's name in the message. Agents do not have
|
|
38
|
+
their own Matrix accounts.
|
|
39
|
+
- Editing or deleting a message in Matrix does not change the comment or issue made from it.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
You need a Matrix homeserver where you can create an account, a Paperclip instance, and the right to
|
|
44
|
+
install plugins in Paperclip.
|
|
45
|
+
|
|
46
|
+
### 1. Create the bot account
|
|
47
|
+
|
|
48
|
+
On [Tuwunel](https://github.com/matrix-construct/tuwunel), send this in the admin room, choosing a
|
|
49
|
+
username and a long random password:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
!admin users create-user paperclip <password>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
On another homeserver, register the account as you would any user.
|
|
56
|
+
|
|
57
|
+
Sign in once to get the bot's access token:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
curl -X POST https://matrix.example.com/_matrix/client/v3/login \
|
|
61
|
+
-H 'Content-Type: application/json' \
|
|
62
|
+
-d '{"type": "m.login.password", "identifier": {"type": "m.id.user", "user": "paperclip"}, "password": "<password>", "initial_device_display_name": "Paperclip"}'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Copy `access_token` from the answer. The token stops working if this session is signed out, for
|
|
66
|
+
example from the bot's session list in Element.
|
|
67
|
+
|
|
68
|
+
Optionally, give the bot a display name, such as Paperclip, so people can mention it by name. Sign in
|
|
69
|
+
as the bot in Element and set it in the profile settings.
|
|
70
|
+
|
|
71
|
+
### 2. Install the plugin
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx paperclipai plugin install @thomas-elliott/paperclip-plugin-matrix --api-base https://your-paperclip.example.com
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3. Fill in the settings
|
|
78
|
+
|
|
79
|
+
Open the Matrix plugin's settings in Paperclip:
|
|
80
|
+
|
|
81
|
+
| Setting | What to enter |
|
|
82
|
+
| --- | --- |
|
|
83
|
+
| Homeserver address | The homeserver's address, such as `https://matrix.example.com` |
|
|
84
|
+
| Bot access token | The access token from step 1, added as a Paperclip secret |
|
|
85
|
+
| Room | The default room, as a room ID (`!abc…`) or alias (`#name:example.com`). Approvals, budget warnings and issues outside a project's room go here |
|
|
86
|
+
| Default agent | Optional. The URL key or name of the agent that gets work asked for outside a project's room |
|
|
87
|
+
| Homeserver is on a private network | Turn on if Paperclip reaches the homeserver at a private address, such as `192.168.x.x` or `10.x.x.x`. Paperclip blocks plugins from private addresses unless this is on |
|
|
88
|
+
| Paperclip address | Optional. Paperclip's web address, such as `https://paperclip.example.com`, so messages link to it |
|
|
89
|
+
|
|
90
|
+
Invite the bot to the default room, then save. The bot joins the room when the settings are saved.
|
|
91
|
+
The dashboard widget should show "Connected" and the bot's Matrix ID.
|
|
92
|
+
|
|
93
|
+
### 4. Give projects their own rooms
|
|
94
|
+
|
|
95
|
+
Optional. Invite the bot to the project's room, open the project in Paperclip, go to its Matrix tab,
|
|
96
|
+
and pick the room. A project without a room uses the default room.
|
|
97
|
+
|
|
98
|
+
The bot accepts invites only from linked people, so link your account first.
|
|
99
|
+
|
|
100
|
+
### 5. Link your account
|
|
101
|
+
|
|
102
|
+
Each person links their own Matrix account:
|
|
103
|
+
|
|
104
|
+
1. In the Matrix widget on the Paperclip dashboard, enter your Matrix ID, such as
|
|
105
|
+
`@alice:example.com`, and select Link.
|
|
106
|
+
2. Within 15 minutes, send the `!link` message the widget shows, from that Matrix account, in a room
|
|
107
|
+
the bot is in.
|
|
108
|
+
3. The bot reacts with a tick when you are linked.
|
|
109
|
+
|
|
110
|
+
Your replies, decisions and requests in Matrix then count as yours in Paperclip.
|
|
111
|
+
|
|
112
|
+
## Upgrade
|
|
113
|
+
|
|
114
|
+
If a new version of the plugin needs more permissions, Paperclip refuses the upgrade. Uninstall the
|
|
115
|
+
plugin and install it again; your settings are kept. Do not add `--force`, which deletes them.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npx paperclipai plugin uninstall paperclip-plugin-matrix --api-base https://your-paperclip.example.com
|
|
119
|
+
npx paperclipai plugin install @thomas-elliott/paperclip-plugin-matrix --api-base https://your-paperclip.example.com
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Development
|
|
123
|
+
|
|
124
|
+
Needs Node.js 24.11 or later and pnpm.
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pnpm install
|
|
128
|
+
pnpm dev # rebuilds dist/ on every save
|
|
129
|
+
pnpm test
|
|
130
|
+
pnpm typecheck
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Install into a running Paperclip from this folder. Paperclip reloads the worker when `dist/`
|
|
134
|
+
changes, so keep `pnpm dev` running:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx paperclipai plugin install "$PWD"
|
|
138
|
+
npx paperclipai plugin inspect paperclip-plugin-matrix # the plugin ID, not the npm name
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Paperclip only rereads the manifest on an upgrade, so run
|
|
142
|
+
`npx paperclipai plugin upgrade paperclip-plugin-matrix` after changing it.
|
|
143
|
+
|
|
144
|
+
To publish, set the version in `package.json` and create a GitHub release tagged `v` and that
|
|
145
|
+
version, such as `v0.1.0`. A workflow then publishes the package to npm.
|
|
146
|
+
|
|
147
|
+
## References
|
|
148
|
+
|
|
149
|
+
- [Plugin authoring guide](https://github.com/paperclipai/paperclip/blob/master/doc/plugins/PLUGIN_AUTHORING_GUIDE.md)
|
|
150
|
+
- [Plugin specification](https://github.com/paperclipai/paperclip/blob/master/doc/plugins/PLUGIN_SPEC.md)
|
|
151
|
+
- [Matrix client-server API](https://spec.matrix.org/latest/client-server-api/)
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "@thomas-elliott/paperclip-plugin-matrix",
|
|
4
|
+
version: "0.1.1",
|
|
5
|
+
type: "module",
|
|
6
|
+
description: "Connect a Paperclip instance to Matrix",
|
|
7
|
+
scripts: {
|
|
8
|
+
build: "node ./esbuild.config.mjs",
|
|
9
|
+
dev: "node ./esbuild.config.mjs --watch",
|
|
10
|
+
test: "vitest run",
|
|
11
|
+
typecheck: "tsc --noEmit",
|
|
12
|
+
prepublishOnly: "pnpm typecheck && pnpm test && pnpm build"
|
|
13
|
+
},
|
|
14
|
+
files: [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
paperclipPlugin: {
|
|
18
|
+
manifest: "./dist/manifest.js",
|
|
19
|
+
worker: "./dist/worker.js",
|
|
20
|
+
ui: "./dist/ui/"
|
|
21
|
+
},
|
|
22
|
+
keywords: [
|
|
23
|
+
"paperclip",
|
|
24
|
+
"plugin",
|
|
25
|
+
"connector",
|
|
26
|
+
"matrix"
|
|
27
|
+
],
|
|
28
|
+
author: "Thomas Elliott",
|
|
29
|
+
license: "MIT",
|
|
30
|
+
repository: {
|
|
31
|
+
type: "git",
|
|
32
|
+
url: "git+https://github.com/thomas-elliott/paperclip-plugin-matrix.git"
|
|
33
|
+
},
|
|
34
|
+
packageManager: "pnpm@11.25.0",
|
|
35
|
+
engines: {
|
|
36
|
+
node: ">=24.11.0"
|
|
37
|
+
},
|
|
38
|
+
devDependencies: {
|
|
39
|
+
"@paperclipai/plugin-sdk": "2026.1023.0",
|
|
40
|
+
"@types/node": "^24.0.0",
|
|
41
|
+
"@types/react": "^19.0.8",
|
|
42
|
+
esbuild: "^0.27.3",
|
|
43
|
+
marked: "^18.1.2",
|
|
44
|
+
typescript: "^5.7.3",
|
|
45
|
+
vitest: "^3.0.5"
|
|
46
|
+
},
|
|
47
|
+
peerDependencies: {
|
|
48
|
+
react: ">=18"
|
|
49
|
+
},
|
|
50
|
+
publishConfig: {
|
|
51
|
+
access: "public"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// src/manifest.ts
|
|
56
|
+
var PLUGIN_ID = "paperclip-plugin-matrix";
|
|
57
|
+
var manifest = {
|
|
58
|
+
id: PLUGIN_ID,
|
|
59
|
+
apiVersion: 1,
|
|
60
|
+
version: package_default.version,
|
|
61
|
+
displayName: "Matrix",
|
|
62
|
+
description: "Connect a Paperclip instance to a Matrix homeserver",
|
|
63
|
+
author: "Thomas Elliott",
|
|
64
|
+
categories: ["connector"],
|
|
65
|
+
capabilities: [
|
|
66
|
+
"http.outbound",
|
|
67
|
+
"secrets.read-ref",
|
|
68
|
+
"plugin.state.read",
|
|
69
|
+
"plugin.state.write",
|
|
70
|
+
"events.subscribe",
|
|
71
|
+
"companies.read",
|
|
72
|
+
"projects.read",
|
|
73
|
+
"issues.read",
|
|
74
|
+
"issues.create",
|
|
75
|
+
"agents.read",
|
|
76
|
+
"approvals.read",
|
|
77
|
+
"approvals.respond",
|
|
78
|
+
"issue.comments.read",
|
|
79
|
+
"issue.comments.create",
|
|
80
|
+
"issue.comments.create_human_attributed",
|
|
81
|
+
"issue.interactions.read",
|
|
82
|
+
"issue.interactions.respond",
|
|
83
|
+
"ui.dashboardWidget.register",
|
|
84
|
+
"ui.detailTab.register"
|
|
85
|
+
],
|
|
86
|
+
entrypoints: {
|
|
87
|
+
worker: "./dist/worker.js",
|
|
88
|
+
ui: "./dist/ui"
|
|
89
|
+
},
|
|
90
|
+
instanceConfigSchema: {
|
|
91
|
+
type: "object",
|
|
92
|
+
required: ["homeserverUrl", "accessToken"],
|
|
93
|
+
properties: {
|
|
94
|
+
homeserverUrl: {
|
|
95
|
+
type: "string",
|
|
96
|
+
title: "Homeserver address",
|
|
97
|
+
description: "The address of your Matrix homeserver, for example https://matrix.example.com"
|
|
98
|
+
},
|
|
99
|
+
accessToken: {
|
|
100
|
+
// The secret picker stores { type: "secret_ref", secretId, version? }, not the token itself.
|
|
101
|
+
type: "object",
|
|
102
|
+
format: "secret-ref",
|
|
103
|
+
title: "Bot access token",
|
|
104
|
+
description: "The Matrix bot account's access token, stored as a Paperclip secret"
|
|
105
|
+
},
|
|
106
|
+
defaultRoom: {
|
|
107
|
+
type: "string",
|
|
108
|
+
title: "Room",
|
|
109
|
+
description: "Where the bot posts, as a room ID (!abc\u2026) or alias (#name:server). Invite the bot first. Encrypted rooms are not supported. A project can have its own room, set in the project's Matrix tab."
|
|
110
|
+
},
|
|
111
|
+
defaultAgent: {
|
|
112
|
+
type: "string",
|
|
113
|
+
title: "Default agent",
|
|
114
|
+
description: "Optional. The agent that gets work people ask for by mentioning the bot or messaging it directly, by its URL key or name. In a project's room the project's lead agent gets it instead. Mentioning an agent as @url-key in the message picks that agent."
|
|
115
|
+
},
|
|
116
|
+
privateNetwork: {
|
|
117
|
+
type: "boolean",
|
|
118
|
+
default: false,
|
|
119
|
+
title: "Homeserver is on a private network",
|
|
120
|
+
description: "Turn on if Paperclip reaches the homeserver at a private address, such as 192.168.x.x or 10.x.x.x. Paperclip blocks plugins from private addresses unless this is on."
|
|
121
|
+
},
|
|
122
|
+
paperclipUrl: {
|
|
123
|
+
type: "string",
|
|
124
|
+
title: "Paperclip address",
|
|
125
|
+
description: "Optional. Your Paperclip's web address, for example https://paperclip.example.com, so messages link to it"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
ui: {
|
|
130
|
+
slots: [
|
|
131
|
+
{
|
|
132
|
+
type: "dashboardWidget",
|
|
133
|
+
id: "matrix-connection",
|
|
134
|
+
displayName: "Matrix connection",
|
|
135
|
+
exportName: "DashboardWidget"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: "detailTab",
|
|
139
|
+
id: "matrix-project-room",
|
|
140
|
+
displayName: "Matrix",
|
|
141
|
+
exportName: "ProjectRoomTab",
|
|
142
|
+
entityTypes: ["project"]
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
var manifest_default = manifest;
|
|
148
|
+
export {
|
|
149
|
+
manifest_default as default
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../package.json", "../src/manifest.ts"],
|
|
4
|
+
"sourcesContent": ["{\n \"name\": \"@thomas-elliott/paperclip-plugin-matrix\",\n \"version\": \"0.1.1\",\n \"type\": \"module\",\n \"description\": \"Connect a Paperclip instance to Matrix\",\n \"scripts\": {\n \"build\": \"node ./esbuild.config.mjs\",\n \"dev\": \"node ./esbuild.config.mjs --watch\",\n \"test\": \"vitest run\",\n \"typecheck\": \"tsc --noEmit\",\n \"prepublishOnly\": \"pnpm typecheck && pnpm test && pnpm build\"\n },\n \"files\": [\n \"dist\"\n ],\n \"paperclipPlugin\": {\n \"manifest\": \"./dist/manifest.js\",\n \"worker\": \"./dist/worker.js\",\n \"ui\": \"./dist/ui/\"\n },\n \"keywords\": [\n \"paperclip\",\n \"plugin\",\n \"connector\",\n \"matrix\"\n ],\n \"author\": \"Thomas Elliott\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/thomas-elliott/paperclip-plugin-matrix.git\"\n },\n \"packageManager\": \"pnpm@11.25.0\",\n \"engines\": {\n \"node\": \">=24.11.0\"\n },\n \"devDependencies\": {\n \"@paperclipai/plugin-sdk\": \"2026.1023.0\",\n \"@types/node\": \"^24.0.0\",\n \"@types/react\": \"^19.0.8\",\n \"esbuild\": \"^0.27.3\",\n \"marked\": \"^18.1.2\",\n \"typescript\": \"^5.7.3\",\n \"vitest\": \"^3.0.5\"\n },\n \"peerDependencies\": {\n \"react\": \">=18\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n }\n}\n", "import type { PaperclipPluginManifestV1 } from \"@paperclipai/plugin-sdk\";\n// Paperclip shows the manifest version, so take it from package.json rather than keeping two copies.\nimport packageJson from \"../package.json\" with { type: \"json\" };\n\n// Installed settings and state are keyed on this ID, so it must never change.\nconst PLUGIN_ID = \"paperclip-plugin-matrix\";\n\nconst manifest: PaperclipPluginManifestV1 = {\n id: PLUGIN_ID,\n apiVersion: 1,\n version: packageJson.version,\n displayName: \"Matrix\",\n description: \"Connect a Paperclip instance to a Matrix homeserver\",\n author: \"Thomas Elliott\",\n categories: [\"connector\"],\n capabilities: [\n \"http.outbound\",\n \"secrets.read-ref\",\n \"plugin.state.read\",\n \"plugin.state.write\",\n \"events.subscribe\",\n \"companies.read\",\n \"projects.read\",\n \"issues.read\",\n \"issues.create\",\n \"agents.read\",\n \"approvals.read\",\n \"approvals.respond\",\n \"issue.comments.read\",\n \"issue.comments.create\",\n \"issue.comments.create_human_attributed\",\n \"issue.interactions.read\",\n \"issue.interactions.respond\",\n \"ui.dashboardWidget.register\",\n \"ui.detailTab.register\",\n ],\n entrypoints: {\n worker: \"./dist/worker.js\",\n ui: \"./dist/ui\",\n },\n instanceConfigSchema: {\n type: \"object\",\n required: [\"homeserverUrl\", \"accessToken\"],\n properties: {\n homeserverUrl: {\n type: \"string\",\n title: \"Homeserver address\",\n description: \"The address of your Matrix homeserver, for example https://matrix.example.com\",\n },\n accessToken: {\n // The secret picker stores { type: \"secret_ref\", secretId, version? }, not the token itself.\n type: \"object\",\n format: \"secret-ref\",\n title: \"Bot access token\",\n description: \"The Matrix bot account's access token, stored as a Paperclip secret\",\n },\n defaultRoom: {\n type: \"string\",\n title: \"Room\",\n description:\n \"Where the bot posts, as a room ID (!abc\u2026) or alias (#name:server). Invite the bot first. Encrypted rooms are not supported. A project can have its own room, set in the project's Matrix tab.\",\n },\n defaultAgent: {\n type: \"string\",\n title: \"Default agent\",\n description:\n \"Optional. The agent that gets work people ask for by mentioning the bot or messaging it directly, by its URL key or name. In a project's room the project's lead agent gets it instead. Mentioning an agent as @url-key in the message picks that agent.\",\n },\n privateNetwork: {\n type: \"boolean\",\n default: false,\n title: \"Homeserver is on a private network\",\n description:\n \"Turn on if Paperclip reaches the homeserver at a private address, such as 192.168.x.x or 10.x.x.x. Paperclip blocks plugins from private addresses unless this is on.\",\n },\n paperclipUrl: {\n type: \"string\",\n title: \"Paperclip address\",\n description: \"Optional. Your Paperclip's web address, for example https://paperclip.example.com, so messages link to it\",\n },\n },\n },\n ui: {\n slots: [\n {\n type: \"dashboardWidget\",\n id: \"matrix-connection\",\n displayName: \"Matrix connection\",\n exportName: \"DashboardWidget\",\n },\n {\n type: \"detailTab\",\n id: \"matrix-project-room\",\n displayName: \"Matrix\",\n exportName: \"ProjectRoomTab\",\n entityTypes: [\"project\"],\n },\n ],\n },\n};\n\nexport default manifest;\n"],
|
|
5
|
+
"mappings": ";AAAA;AAAA,EACE,MAAQ;AAAA,EACR,SAAW;AAAA,EACX,MAAQ;AAAA,EACR,aAAe;AAAA,EACf,SAAW;AAAA,IACT,OAAS;AAAA,IACT,KAAO;AAAA,IACP,MAAQ;AAAA,IACR,WAAa;AAAA,IACb,gBAAkB;AAAA,EACpB;AAAA,EACA,OAAS;AAAA,IACP;AAAA,EACF;AAAA,EACA,iBAAmB;AAAA,IACjB,UAAY;AAAA,IACZ,QAAU;AAAA,IACV,IAAM;AAAA,EACR;AAAA,EACA,UAAY;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,QAAU;AAAA,EACV,SAAW;AAAA,EACX,YAAc;AAAA,IACZ,MAAQ;AAAA,IACR,KAAO;AAAA,EACT;AAAA,EACA,gBAAkB;AAAA,EAClB,SAAW;AAAA,IACT,MAAQ;AAAA,EACV;AAAA,EACA,iBAAmB;AAAA,IACjB,2BAA2B;AAAA,IAC3B,eAAe;AAAA,IACf,gBAAgB;AAAA,IAChB,SAAW;AAAA,IACX,QAAU;AAAA,IACV,YAAc;AAAA,IACd,QAAU;AAAA,EACZ;AAAA,EACA,kBAAoB;AAAA,IAClB,OAAS;AAAA,EACX;AAAA,EACA,eAAiB;AAAA,IACf,QAAU;AAAA,EACZ;AACF;;;AC9CA,IAAM,YAAY;AAElB,IAAM,WAAsC;AAAA,EAC1C,IAAI;AAAA,EACJ,YAAY;AAAA,EACZ,SAAS,gBAAY;AAAA,EACrB,aAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,YAAY,CAAC,WAAW;AAAA,EACxB,cAAc;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EACA,aAAa;AAAA,IACX,QAAQ;AAAA,IACR,IAAI;AAAA,EACN;AAAA,EACA,sBAAsB;AAAA,IACpB,MAAM;AAAA,IACN,UAAU,CAAC,iBAAiB,aAAa;AAAA,IACzC,YAAY;AAAA,MACV,eAAe;AAAA,QACb,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,MACA,aAAa;AAAA;AAAA,QAEX,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,MACA,aAAa;AAAA,QACX,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aACE;AAAA,MACJ;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aACE;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,QACd,MAAM;AAAA,QACN,SAAS;AAAA,QACT,OAAO;AAAA,QACP,aACE;AAAA,MACJ;AAAA,MACA,cAAc;AAAA,QACZ,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAAA,EACA,IAAI;AAAA,IACF,OAAO;AAAA,MACL;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,MACd;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,IAAI;AAAA,QACJ,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,aAAa,CAAC,SAAS;AAAA,MACzB;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,mBAAQ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
// src/ui/index.tsx
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import {
|
|
4
|
+
KeyValueList,
|
|
5
|
+
Spinner,
|
|
6
|
+
StatusBadge,
|
|
7
|
+
usePluginAction,
|
|
8
|
+
usePluginData
|
|
9
|
+
} from "@paperclipai/plugin-sdk/ui";
|
|
10
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
var space = (steps) => `calc(var(--spacing) * ${steps})`;
|
|
12
|
+
var styles = {
|
|
13
|
+
stack: { display: "grid", gap: space(3) },
|
|
14
|
+
heading: { margin: 0, fontSize: "var(--text-sm)", fontWeight: "var(--font-weight-semibold)" },
|
|
15
|
+
text: { margin: 0, fontSize: "var(--text-sm)" },
|
|
16
|
+
muted: { margin: 0, fontSize: "var(--text-sm)", color: "var(--muted-foreground)" },
|
|
17
|
+
row: { display: "flex", gap: space(2) },
|
|
18
|
+
input: {
|
|
19
|
+
flex: 1,
|
|
20
|
+
padding: `${space(2)} ${space(3)}`,
|
|
21
|
+
fontSize: "var(--text-sm)",
|
|
22
|
+
color: "var(--foreground)",
|
|
23
|
+
background: "var(--background)",
|
|
24
|
+
border: "1px solid var(--input)",
|
|
25
|
+
borderRadius: "var(--radius-md)"
|
|
26
|
+
},
|
|
27
|
+
button: {
|
|
28
|
+
padding: `${space(2)} ${space(3)}`,
|
|
29
|
+
fontSize: "var(--text-sm)",
|
|
30
|
+
fontWeight: "var(--font-weight-medium)",
|
|
31
|
+
color: "var(--foreground)",
|
|
32
|
+
background: "var(--background)",
|
|
33
|
+
border: "1px solid var(--border)",
|
|
34
|
+
borderRadius: "var(--radius-md)",
|
|
35
|
+
cursor: "pointer"
|
|
36
|
+
},
|
|
37
|
+
code: { fontFamily: "var(--font-mono)", fontSize: "var(--text-sm)" }
|
|
38
|
+
};
|
|
39
|
+
function errorText(error) {
|
|
40
|
+
return error instanceof Error ? error.message : String(error);
|
|
41
|
+
}
|
|
42
|
+
function MatrixAccount({ companyId }) {
|
|
43
|
+
const startLink = usePluginAction("startLink");
|
|
44
|
+
const linkStatus = usePluginAction("linkStatus");
|
|
45
|
+
const unlink = usePluginAction("unlink");
|
|
46
|
+
const [linked, setLinked] = useState(void 0);
|
|
47
|
+
const [matrixUserId, setMatrixUserId] = useState("");
|
|
48
|
+
const [pending, setPending] = useState(null);
|
|
49
|
+
const [error, setError] = useState(null);
|
|
50
|
+
async function run(action) {
|
|
51
|
+
setError(null);
|
|
52
|
+
try {
|
|
53
|
+
await action();
|
|
54
|
+
} catch (caught) {
|
|
55
|
+
setError(errorText(caught));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const refresh = () => run(async () => {
|
|
59
|
+
const status = await linkStatus({ companyId });
|
|
60
|
+
setLinked(status.matrixUserId);
|
|
61
|
+
if (status.matrixUserId) setPending(null);
|
|
62
|
+
});
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
void refresh();
|
|
65
|
+
}, [companyId]);
|
|
66
|
+
const start = () => run(async () => {
|
|
67
|
+
const { code } = await startLink({ companyId, matrixUserId });
|
|
68
|
+
setPending({ matrixUserId: matrixUserId.trim(), code });
|
|
69
|
+
});
|
|
70
|
+
const remove = () => run(async () => {
|
|
71
|
+
await unlink({ companyId });
|
|
72
|
+
setLinked(null);
|
|
73
|
+
});
|
|
74
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.stack, children: [
|
|
75
|
+
/* @__PURE__ */ jsx("h3", { style: styles.heading, children: "Your Matrix account" }),
|
|
76
|
+
linked === void 0 && !error && /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "Checking your Matrix account" }),
|
|
77
|
+
linked && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
78
|
+
/* @__PURE__ */ jsxs("p", { style: styles.text, children: [
|
|
79
|
+
"Linked to ",
|
|
80
|
+
/* @__PURE__ */ jsx("span", { style: styles.code, children: linked }),
|
|
81
|
+
". Your replies in issue threads become your comments."
|
|
82
|
+
] }),
|
|
83
|
+
/* @__PURE__ */ jsx("div", { style: styles.row, children: /* @__PURE__ */ jsx("button", { type: "button", style: styles.button, onClick: remove, children: "Unlink" }) })
|
|
84
|
+
] }),
|
|
85
|
+
linked === null && !pending && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
86
|
+
/* @__PURE__ */ jsx("p", { style: styles.muted, children: "Link your Matrix account so your replies in issue threads become your comments." }),
|
|
87
|
+
/* @__PURE__ */ jsxs("div", { style: styles.row, children: [
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
"input",
|
|
90
|
+
{
|
|
91
|
+
style: styles.input,
|
|
92
|
+
"aria-label": "Your Matrix ID",
|
|
93
|
+
placeholder: "@you:example.com",
|
|
94
|
+
value: matrixUserId,
|
|
95
|
+
onChange: (event) => setMatrixUserId(event.target.value)
|
|
96
|
+
}
|
|
97
|
+
),
|
|
98
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: styles.button, onClick: start, children: "Link" })
|
|
99
|
+
] })
|
|
100
|
+
] }),
|
|
101
|
+
linked === null && pending && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
102
|
+
/* @__PURE__ */ jsxs("p", { style: styles.text, children: [
|
|
103
|
+
"From ",
|
|
104
|
+
/* @__PURE__ */ jsx("span", { style: styles.code, children: pending.matrixUserId }),
|
|
105
|
+
", send",
|
|
106
|
+
" ",
|
|
107
|
+
/* @__PURE__ */ jsxs("span", { style: styles.code, children: [
|
|
108
|
+
"!link ",
|
|
109
|
+
pending.code
|
|
110
|
+
] }),
|
|
111
|
+
" in a room the bot is in, within 15 minutes. The bot reacts with a tick when it has linked you."
|
|
112
|
+
] }),
|
|
113
|
+
/* @__PURE__ */ jsxs("div", { style: styles.row, children: [
|
|
114
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: styles.button, onClick: refresh, children: "Check" }),
|
|
115
|
+
/* @__PURE__ */ jsx("button", { type: "button", style: styles.button, onClick: () => setPending(null), children: "Cancel" })
|
|
116
|
+
] })
|
|
117
|
+
] }),
|
|
118
|
+
error && /* @__PURE__ */ jsx("p", { style: { ...styles.muted, color: "var(--destructive)" }, children: error })
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
function DashboardWidget({ context }) {
|
|
122
|
+
const { data, loading, error } = usePluginData("connection", { companyId: context.companyId });
|
|
123
|
+
if (loading) return /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "Checking Matrix connection" });
|
|
124
|
+
if (error) return /* @__PURE__ */ jsx(StatusBadge, { status: "error", label: `Could not check Matrix: ${error.message}` });
|
|
125
|
+
if (!data?.connected) return /* @__PURE__ */ jsx(StatusBadge, { status: "error", label: data?.reason ?? "Not connected" });
|
|
126
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.stack, children: [
|
|
127
|
+
/* @__PURE__ */ jsx(
|
|
128
|
+
KeyValueList,
|
|
129
|
+
{
|
|
130
|
+
pairs: [
|
|
131
|
+
{ label: "Status", value: /* @__PURE__ */ jsx(StatusBadge, { status: "ok", label: "Connected" }) },
|
|
132
|
+
{ label: "Signed in as", value: data.userId },
|
|
133
|
+
// The loop keeps running while it retries a failing sync, so its error comes first.
|
|
134
|
+
{ label: "Reading rooms", value: data.sync?.lastError ?? (data.sync?.running ? "Yes" : "No") }
|
|
135
|
+
]
|
|
136
|
+
}
|
|
137
|
+
),
|
|
138
|
+
context.companyId && /* @__PURE__ */ jsx(MatrixAccount, { companyId: context.companyId })
|
|
139
|
+
] });
|
|
140
|
+
}
|
|
141
|
+
function ProjectRoomTab({ context }) {
|
|
142
|
+
const projectId = context.entityId;
|
|
143
|
+
const { data, loading, error, refresh } = usePluginData("projectRoom", { companyId: context.companyId, projectId });
|
|
144
|
+
const setProjectRoom = usePluginAction("setProjectRoom");
|
|
145
|
+
const [saveError, setSaveError] = useState(null);
|
|
146
|
+
if (loading) return /* @__PURE__ */ jsx(Spinner, { size: "sm", label: "Loading Matrix rooms" });
|
|
147
|
+
if (error) return /* @__PURE__ */ jsx(StatusBadge, { status: "error", label: `Could not load Matrix rooms: ${error.message}` });
|
|
148
|
+
if (!data?.connected) return /* @__PURE__ */ jsx(StatusBadge, { status: "error", label: data?.reason ?? "Not connected" });
|
|
149
|
+
async function choose(roomId) {
|
|
150
|
+
setSaveError(null);
|
|
151
|
+
try {
|
|
152
|
+
await setProjectRoom({ companyId: context.companyId, projectId, roomId });
|
|
153
|
+
refresh();
|
|
154
|
+
} catch (caught) {
|
|
155
|
+
setSaveError(errorText(caught));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return /* @__PURE__ */ jsxs("div", { style: styles.stack, children: [
|
|
159
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "matrix-project-room", style: styles.heading, children: "Matrix room for this project" }),
|
|
160
|
+
/* @__PURE__ */ jsx("p", { style: styles.muted, children: "News about this project's issues goes here, and people can ask for work by mentioning the bot here. To offer another room, invite the bot to it first." }),
|
|
161
|
+
/* @__PURE__ */ jsxs("select", { id: "matrix-project-room", style: styles.input, value: data.selected ?? "", onChange: (event) => void choose(event.target.value), children: [
|
|
162
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "The default room" }),
|
|
163
|
+
data.rooms.map((room) => /* @__PURE__ */ jsx("option", { value: room.roomId, children: room.name }, room.roomId))
|
|
164
|
+
] }),
|
|
165
|
+
saveError && /* @__PURE__ */ jsx("p", { style: { ...styles.muted, color: "var(--destructive)" }, children: saveError })
|
|
166
|
+
] });
|
|
167
|
+
}
|
|
168
|
+
export {
|
|
169
|
+
DashboardWidget,
|
|
170
|
+
ProjectRoomTab
|
|
171
|
+
};
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/ui/index.tsx"],
|
|
4
|
+
"sourcesContent": ["import { useEffect, useState, type CSSProperties } from \"react\";\nimport {\n KeyValueList,\n Spinner,\n StatusBadge,\n usePluginAction,\n usePluginData,\n type PluginDetailTabProps,\n type PluginWidgetProps,\n} from \"@paperclipai/plugin-sdk/ui\";\nimport type { ProjectRoomChoices } from \"../project-rooms.js\";\nimport type { ConnectionStatus } from \"../worker.js\";\n\n// Paperclip's theme is Tailwind v4 CSS variables on the host page. Plugins read the variables\n// rather than Tailwind classes, because the host stylesheet only contains classes it uses itself.\nconst space = (steps: number) => `calc(var(--spacing) * ${steps})`;\n\nconst styles = {\n stack: { display: \"grid\", gap: space(3) },\n heading: { margin: 0, fontSize: \"var(--text-sm)\", fontWeight: \"var(--font-weight-semibold)\" },\n text: { margin: 0, fontSize: \"var(--text-sm)\" },\n muted: { margin: 0, fontSize: \"var(--text-sm)\", color: \"var(--muted-foreground)\" },\n row: { display: \"flex\", gap: space(2) },\n input: {\n flex: 1,\n padding: `${space(2)} ${space(3)}`,\n fontSize: \"var(--text-sm)\",\n color: \"var(--foreground)\",\n background: \"var(--background)\",\n border: \"1px solid var(--input)\",\n borderRadius: \"var(--radius-md)\",\n },\n button: {\n padding: `${space(2)} ${space(3)}`,\n fontSize: \"var(--text-sm)\",\n fontWeight: \"var(--font-weight-medium)\",\n color: \"var(--foreground)\",\n background: \"var(--background)\",\n border: \"1px solid var(--border)\",\n borderRadius: \"var(--radius-md)\",\n cursor: \"pointer\",\n },\n code: { fontFamily: \"var(--font-mono)\", fontSize: \"var(--text-sm)\" },\n} satisfies Record<string, CSSProperties>;\n\ntype PendingLink = { matrixUserId: string; code: string };\n\nfunction errorText(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n// Links the signed-in Paperclip user to their Matrix account, so their replies in issue threads\n// become their comments. The worker learns who is signed in from Paperclip, not from this page.\nfunction MatrixAccount({ companyId }: { companyId: string }) {\n const startLink = usePluginAction(\"startLink\");\n const linkStatus = usePluginAction(\"linkStatus\");\n const unlink = usePluginAction(\"unlink\");\n // undefined while loading, null when not linked.\n const [linked, setLinked] = useState<string | null | undefined>(undefined);\n const [matrixUserId, setMatrixUserId] = useState(\"\");\n const [pending, setPending] = useState<PendingLink | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n async function run(action: () => Promise<void>) {\n setError(null);\n try {\n await action();\n } catch (caught) {\n setError(errorText(caught));\n }\n }\n\n const refresh = () =>\n run(async () => {\n const status = (await linkStatus({ companyId })) as { matrixUserId: string | null };\n setLinked(status.matrixUserId);\n if (status.matrixUserId) setPending(null);\n });\n\n useEffect(() => {\n void refresh();\n }, [companyId]);\n\n const start = () =>\n run(async () => {\n const { code } = (await startLink({ companyId, matrixUserId })) as { code: string };\n setPending({ matrixUserId: matrixUserId.trim(), code });\n });\n\n const remove = () =>\n run(async () => {\n await unlink({ companyId });\n setLinked(null);\n });\n\n return (\n <div style={styles.stack}>\n <h3 style={styles.heading}>Your Matrix account</h3>\n {linked === undefined && !error && <Spinner size=\"sm\" label=\"Checking your Matrix account\" />}\n {linked && (\n <>\n <p style={styles.text}>\n Linked to <span style={styles.code}>{linked}</span>. Your replies in issue threads become your comments.\n </p>\n <div style={styles.row}>\n <button type=\"button\" style={styles.button} onClick={remove}>\n Unlink\n </button>\n </div>\n </>\n )}\n {linked === null && !pending && (\n <>\n <p style={styles.muted}>Link your Matrix account so your replies in issue threads become your comments.</p>\n <div style={styles.row}>\n <input\n style={styles.input}\n aria-label=\"Your Matrix ID\"\n placeholder=\"@you:example.com\"\n value={matrixUserId}\n onChange={(event) => setMatrixUserId(event.target.value)}\n />\n <button type=\"button\" style={styles.button} onClick={start}>\n Link\n </button>\n </div>\n </>\n )}\n {linked === null && pending && (\n <>\n <p style={styles.text}>\n From <span style={styles.code}>{pending.matrixUserId}</span>, send{\" \"}\n <span style={styles.code}>!link {pending.code}</span> in a room the bot is in, within 15 minutes. The bot\n reacts with a tick when it has linked you.\n </p>\n <div style={styles.row}>\n <button type=\"button\" style={styles.button} onClick={refresh}>\n Check\n </button>\n <button type=\"button\" style={styles.button} onClick={() => setPending(null)}>\n Cancel\n </button>\n </div>\n </>\n )}\n {error && <p style={{ ...styles.muted, color: \"var(--destructive)\" }}>{error}</p>}\n </div>\n );\n}\n\nexport function DashboardWidget({ context }: PluginWidgetProps) {\n const { data, loading, error } = usePluginData<ConnectionStatus>(\"connection\", { companyId: context.companyId });\n\n if (loading) return <Spinner size=\"sm\" label=\"Checking Matrix connection\" />;\n if (error) return <StatusBadge status=\"error\" label={`Could not check Matrix: ${error.message}`} />;\n if (!data?.connected) return <StatusBadge status=\"error\" label={data?.reason ?? \"Not connected\"} />;\n\n return (\n <div style={styles.stack}>\n <KeyValueList\n pairs={[\n { label: \"Status\", value: <StatusBadge status=\"ok\" label=\"Connected\" /> },\n { label: \"Signed in as\", value: data.userId },\n // The loop keeps running while it retries a failing sync, so its error comes first.\n { label: \"Reading rooms\", value: data.sync?.lastError ?? (data.sync?.running ? \"Yes\" : \"No\") },\n ]}\n />\n {context.companyId && <MatrixAccount companyId={context.companyId} />}\n </div>\n );\n}\n\n// Picks the room where posts about this project go. Only rooms the bot is in are offered, because it\n// can only post in those.\nexport function ProjectRoomTab({ context }: PluginDetailTabProps) {\n const projectId = context.entityId;\n const { data, loading, error, refresh } = usePluginData<ProjectRoomChoices>(\"projectRoom\", { companyId: context.companyId, projectId });\n const setProjectRoom = usePluginAction(\"setProjectRoom\");\n const [saveError, setSaveError] = useState<string | null>(null);\n\n if (loading) return <Spinner size=\"sm\" label=\"Loading Matrix rooms\" />;\n if (error) return <StatusBadge status=\"error\" label={`Could not load Matrix rooms: ${error.message}`} />;\n if (!data?.connected) return <StatusBadge status=\"error\" label={data?.reason ?? \"Not connected\"} />;\n\n async function choose(roomId: string) {\n setSaveError(null);\n try {\n await setProjectRoom({ companyId: context.companyId, projectId, roomId });\n refresh();\n } catch (caught) {\n setSaveError(errorText(caught));\n }\n }\n\n return (\n <div style={styles.stack}>\n <label htmlFor=\"matrix-project-room\" style={styles.heading}>\n Matrix room for this project\n </label>\n <p style={styles.muted}>\n News about this project's issues goes here, and people can ask for work by mentioning the bot here. To offer\n another room, invite the bot to it first.\n </p>\n <select id=\"matrix-project-room\" style={styles.input} value={data.selected ?? \"\"} onChange={(event) => void choose(event.target.value)}>\n <option value=\"\">The default room</option>\n {data.rooms.map((room) => (\n <option key={room.roomId} value={room.roomId}>\n {room.name}\n </option>\n ))}\n </select>\n {saveError && <p style={{ ...styles.muted, color: \"var(--destructive)\" }}>{saveError}</p>}\n </div>\n );\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|