@thomas-elliott/paperclip-plugin-matrix 0.0.0-stage → 0.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
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
1
+ # Matrix for Paperclip
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
+ 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
+ - Paperclip blocks plugins from private network addresses, such as `192.168.x.x`, so Paperclip must
41
+ reach the homeserver at a public address.
42
+
43
+ ## Install
44
+
45
+ You need a Matrix homeserver where you can create an account, a Paperclip instance, and the right to
46
+ install plugins in Paperclip.
47
+
48
+ ### 1. Create the bot account
49
+
50
+ On [Tuwunel](https://github.com/matrix-construct/tuwunel), send this in the admin room, choosing a
51
+ username and a long random password:
52
+
53
+ ```
54
+ !admin users create-user paperclip <password>
55
+ ```
56
+
57
+ On another homeserver, register the account as you would any user.
58
+
59
+ Sign in once to get the bot's access token:
60
+
61
+ ```bash
62
+ curl -X POST https://matrix.example.com/_matrix/client/v3/login \
63
+ -H 'Content-Type: application/json' \
64
+ -d '{"type": "m.login.password", "identifier": {"type": "m.id.user", "user": "paperclip"}, "password": "<password>", "initial_device_display_name": "Paperclip"}'
65
+ ```
66
+
67
+ Copy `access_token` from the answer. The token stops working if this session is signed out, for
68
+ example from the bot's session list in Element.
69
+
70
+ Optionally, give the bot a display name, such as Paperclip, so people can mention it by name. Sign in
71
+ as the bot in Element and set it in the profile settings.
72
+
73
+ ### 2. Install the plugin
74
+
75
+ ```bash
76
+ npx paperclipai plugin install @thomas-elliott/paperclip-plugin-matrix --api-base https://your-paperclip.example.com
77
+ ```
78
+
79
+ ### 3. Fill in the settings
80
+
81
+ Open the Matrix plugin's settings in Paperclip:
82
+
83
+ | Setting | What to enter |
84
+ | --- | --- |
85
+ | Homeserver address | The homeserver's address, such as `https://matrix.example.com` |
86
+ | Bot access token | The access token from step 1, added as a Paperclip secret |
87
+ | 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 |
88
+ | Default agent | Optional. The URL key or name of the agent that gets work asked for outside a project's room |
89
+ | Paperclip address | Optional. Paperclip's web address, such as `https://paperclip.example.com`, so messages link to it |
90
+
91
+ Invite the bot to the default room, then save. The bot joins the room when the settings are saved.
92
+ The dashboard widget should show "Connected" and the bot's Matrix ID.
93
+
94
+ ### 4. Give projects their own rooms
95
+
96
+ Optional. Invite the bot to the project's room, open the project in Paperclip, go to its Matrix tab,
97
+ and pick the room. A project without a room uses the default room.
98
+
99
+ The bot accepts invites only from linked people, so link your account first.
100
+
101
+ ### 5. Link your account
102
+
103
+ Each person links their own Matrix account:
104
+
105
+ 1. In the Matrix widget on the Paperclip dashboard, enter your Matrix ID, such as
106
+ `@alice:example.com`, and select Link.
107
+ 2. Within 15 minutes, send the `!link` message the widget shows, from that Matrix account, in a room
108
+ the bot is in.
109
+ 3. The bot reacts with a tick when you are linked.
110
+
111
+ Your replies, decisions and requests in Matrix then count as yours in Paperclip.
112
+
113
+ ## Upgrade
114
+
115
+ If a new version of the plugin needs more permissions, Paperclip refuses the upgrade. Uninstall the
116
+ plugin and install it again; your settings are kept. Do not add `--force`, which deletes them.
117
+
118
+ ```bash
119
+ npx paperclipai plugin uninstall paperclip-plugin-matrix --api-base https://your-paperclip.example.com
120
+ npx paperclipai plugin install @thomas-elliott/paperclip-plugin-matrix --api-base https://your-paperclip.example.com
121
+ ```
122
+
123
+ ## Development
124
+
125
+ Needs Node.js 24.11 or later and pnpm.
126
+
127
+ ```bash
128
+ pnpm install
129
+ pnpm dev # rebuilds dist/ on every save
130
+ pnpm test
131
+ pnpm typecheck
132
+ ```
133
+
134
+ Install into a running Paperclip from this folder. Paperclip reloads the worker when `dist/`
135
+ changes, so keep `pnpm dev` running:
136
+
137
+ ```bash
138
+ npx paperclipai plugin install "$PWD"
139
+ npx paperclipai plugin inspect paperclip-plugin-matrix # the plugin ID, not the npm name
140
+ ```
141
+
142
+ Paperclip only rereads the manifest on an upgrade, so run
143
+ `npx paperclipai plugin upgrade paperclip-plugin-matrix` after changing it.
144
+
145
+ To publish, set the version in `package.json` and create a GitHub release tagged `v` and that
146
+ version, such as `v0.1.0`. A workflow then publishes the package to npm.
147
+
148
+ ## References
149
+
150
+ - [Plugin authoring guide](https://github.com/paperclipai/paperclip/blob/master/doc/plugins/PLUGIN_AUTHORING_GUIDE.md)
151
+ - [Plugin specification](https://github.com/paperclipai/paperclip/blob/master/doc/plugins/PLUGIN_SPEC.md)
152
+ - [Matrix client-server API](https://spec.matrix.org/latest/client-server-api/)
@@ -0,0 +1,145 @@
1
+ // package.json
2
+ var package_default = {
3
+ name: "@thomas-elliott/paperclip-plugin-matrix",
4
+ version: "0.1.0",
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
+ paperclipUrl: {
117
+ type: "string",
118
+ title: "Paperclip address",
119
+ description: "Optional. Your Paperclip's web address, for example https://paperclip.example.com, so messages link to it"
120
+ }
121
+ }
122
+ },
123
+ ui: {
124
+ slots: [
125
+ {
126
+ type: "dashboardWidget",
127
+ id: "matrix-connection",
128
+ displayName: "Matrix connection",
129
+ exportName: "DashboardWidget"
130
+ },
131
+ {
132
+ type: "detailTab",
133
+ id: "matrix-project-room",
134
+ displayName: "Matrix",
135
+ exportName: "ProjectRoomTab",
136
+ entityTypes: ["project"]
137
+ }
138
+ ]
139
+ }
140
+ };
141
+ var manifest_default = manifest;
142
+ export {
143
+ manifest_default as default
144
+ };
145
+ //# 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.0\",\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 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,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
+ }
@@ -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
+ }