@ronakgajjar/paperclip-plugin-gchat 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 ADDED
@@ -0,0 +1,30 @@
1
+ # My Gchat Plugin
2
+
3
+ A Paperclip plugin
4
+
5
+ ## Development
6
+
7
+ ```bash
8
+ pnpm install
9
+ pnpm dev # watch builds
10
+ pnpm dev:ui # local dev server with hot-reload events
11
+ pnpm test
12
+ ```
13
+
14
+ `pnpm dev` rebuilds the worker, manifest, and UI bundles into `dist/`.
15
+ When this package is installed from a local path, Paperclip watches that rebuilt
16
+ output and reloads the plugin worker. Local installs run trusted code from this
17
+ folder on your machine.
18
+
19
+
20
+
21
+ ## Install Into Paperclip
22
+
23
+ ```bash
24
+ paperclipai plugin install /home/user2026/plugin-work/my-gchat-plugin
25
+ ```
26
+
27
+ ## Build Options
28
+
29
+ - `pnpm build` uses esbuild presets from `@paperclipai/plugin-sdk/bundlers`.
30
+ - `pnpm build:rollup` uses rollup presets from the same SDK.
@@ -0,0 +1,17 @@
1
+ import esbuild from "esbuild";
2
+ import { createPluginBundlerPresets } from "@paperclipai/plugin-sdk/bundlers";
3
+
4
+ const presets = createPluginBundlerPresets({ uiEntry: "src/ui/index.tsx" });
5
+ const watch = process.argv.includes("--watch");
6
+
7
+ const workerCtx = await esbuild.context(presets.esbuild.worker);
8
+ const manifestCtx = await esbuild.context(presets.esbuild.manifest);
9
+ const uiCtx = await esbuild.context(presets.esbuild.ui);
10
+
11
+ if (watch) {
12
+ await Promise.all([workerCtx.watch(), manifestCtx.watch(), uiCtx.watch()]);
13
+ console.log("esbuild watch mode enabled for worker, manifest, and ui");
14
+ } else {
15
+ await Promise.all([workerCtx.rebuild(), manifestCtx.rebuild(), uiCtx.rebuild()]);
16
+ await Promise.all([workerCtx.dispose(), manifestCtx.dispose(), uiCtx.dispose()]);
17
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@ronakgajjar/paperclip-plugin-gchat",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "A Paperclip plugin",
6
+ "scripts": {
7
+ "build": "node ./esbuild.config.mjs",
8
+ "build:rollup": "rollup -c",
9
+ "dev": "node ./esbuild.config.mjs --watch",
10
+ "dev:ui": "paperclip-plugin-dev-server --root . --ui-dir dist/ui --port 4177",
11
+ "test": "vitest run --config ./vitest.config.ts",
12
+ "typecheck": "tsc --noEmit"
13
+ },
14
+ "paperclipPlugin": {
15
+ "manifest": "./dist/manifest.js",
16
+ "worker": "./dist/worker.js",
17
+ "ui": "./dist/ui/"
18
+ },
19
+ "keywords": [
20
+ "paperclip",
21
+ "plugin",
22
+ "connector"
23
+ ],
24
+ "author": "Plugin Author",
25
+ "license": "MIT",
26
+ "devDependencies": {
27
+ "@paperclipai/plugin-sdk": "2026.831.1",
28
+ "@rollup/plugin-node-resolve": "^16.0.1",
29
+ "@rollup/plugin-typescript": "^12.1.2",
30
+ "@types/node": "^24.0.0",
31
+ "@types/react": "^19.0.8",
32
+ "esbuild": "^0.27.3",
33
+ "rollup": "^4.38.0",
34
+ "tslib": "^2.8.1",
35
+ "typescript": "^5.7.3",
36
+ "vitest": "^3.0.5"
37
+ },
38
+ "peerDependencies": {
39
+ "react": ">=18"
40
+ },
41
+ "allowScripts": {
42
+ "esbuild@0.27.7": true
43
+ }
44
+ }
@@ -0,0 +1,28 @@
1
+ import { nodeResolve } from "@rollup/plugin-node-resolve";
2
+ import typescript from "@rollup/plugin-typescript";
3
+ import { createPluginBundlerPresets } from "@paperclipai/plugin-sdk/bundlers";
4
+
5
+ const presets = createPluginBundlerPresets({ uiEntry: "src/ui/index.tsx" });
6
+
7
+ function withPlugins(config) {
8
+ if (!config) return null;
9
+ return {
10
+ ...config,
11
+ plugins: [
12
+ nodeResolve({
13
+ extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs"],
14
+ }),
15
+ typescript({
16
+ tsconfig: "./tsconfig.json",
17
+ declaration: false,
18
+ declarationMap: false,
19
+ }),
20
+ ],
21
+ };
22
+ }
23
+
24
+ export default [
25
+ withPlugins(presets.rollup.manifest),
26
+ withPlugins(presets.rollup.worker),
27
+ withPlugins(presets.rollup.ui),
28
+ ].filter(Boolean);
@@ -0,0 +1,33 @@
1
+ import type { PaperclipPluginManifestV1 } from "@paperclipai/plugin-sdk";
2
+
3
+ const manifest: PaperclipPluginManifestV1 = {
4
+ id: "paperclip-plugin-gchat",
5
+ apiVersion: 1,
6
+ version: "0.1.0",
7
+ displayName: "Google Chat Notifier",
8
+ description: "Sends agent output to a Google Chat space via webhook",
9
+ author: "Your Name",
10
+ categories: ["connector"],
11
+ capabilities: [
12
+ "events.subscribe",
13
+ "plugin.state.read",
14
+ "plugin.state.write",
15
+ "secrets.read",
16
+ "ui.dashboardWidget.register"
17
+ ],
18
+ entrypoints: {
19
+ worker: "./dist/worker.js",
20
+ ui: "./dist/ui"
21
+ },
22
+ ui: {
23
+ slots: [
24
+ {
25
+ type: "dashboardWidget",
26
+ id: "health-widget",
27
+ displayName: "Google Chat Plugin Health",
28
+ exportName: "DashboardWidget"
29
+ }
30
+ ]
31
+ }
32
+ };
33
+ export default manifest;
@@ -0,0 +1,23 @@
1
+ import { usePluginAction, usePluginData, type PluginWidgetProps } from "@paperclipai/plugin-sdk/ui";
2
+
3
+ type HealthData = {
4
+ status: "ok" | "degraded" | "error";
5
+ checkedAt: string;
6
+ };
7
+
8
+ export function DashboardWidget(_props: PluginWidgetProps) {
9
+ const { data, loading, error } = usePluginData<HealthData>("health");
10
+ const ping = usePluginAction("ping");
11
+
12
+ if (loading) return <div>Loading plugin health...</div>;
13
+ if (error) return <div>Plugin error: {error.message}</div>;
14
+
15
+ return (
16
+ <div style={{ display: "grid", gap: "0.5rem" }}>
17
+ <strong>My Gchat Plugin</strong>
18
+ <div>Health: {data?.status ?? "unknown"}</div>
19
+ <div>Checked: {data?.checkedAt ?? "never"}</div>
20
+ <button onClick={() => void ping()}>Ping Worker</button>
21
+ </div>
22
+ );
23
+ }
package/src/worker.ts ADDED
@@ -0,0 +1,27 @@
1
+ import { definePlugin, runWorker } from "@paperclipai/plugin-sdk";
2
+
3
+ const plugin = definePlugin({
4
+ async setup(ctx) {
5
+ ctx.actions.register("send_google_chat", async (params) => {
6
+ const { message } = params as { message: string };
7
+ const webhookUrl = await ctx.secrets.get("GOOGLE_CHAT_WEBHOOK_URL");
8
+ const res = await fetch(webhookUrl, {
9
+ method: "POST",
10
+ headers: { "Content-Type": "application/json" },
11
+ body: JSON.stringify({ text: message })
12
+ });
13
+ ctx.logger.info("Sent Google Chat message", { ok: res.ok });
14
+ return { sent: res.ok };
15
+ });
16
+
17
+ ctx.data.register("health", async () => {
18
+ return { status: "ok", checkedAt: new Date().toISOString() };
19
+ });
20
+ },
21
+ async onHealth() {
22
+ return { status: "ok", message: "Google Chat plugin worker is running" };
23
+ }
24
+ });
25
+
26
+ export default plugin;
27
+ runWorker(plugin, import.meta.url);
@@ -0,0 +1,25 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createTestHarness } from "@paperclipai/plugin-sdk/testing";
3
+ import manifest from "../src/manifest.js";
4
+ import plugin from "../src/worker.js";
5
+
6
+ describe("plugin scaffold", () => {
7
+ it("declares capabilities for its manifest features", () => {
8
+ expect(manifest.capabilities).toContain("events.subscribe");
9
+ expect(manifest.capabilities).toContain("ui.dashboardWidget.register");
10
+ });
11
+
12
+ it("registers data + actions and handles events", async () => {
13
+ const harness = createTestHarness({ manifest, capabilities: [...manifest.capabilities, "events.emit"] });
14
+ await plugin.definition.setup(harness.ctx);
15
+
16
+ await harness.emit("issue.created", { issueId: "iss_1" }, { entityId: "iss_1", entityType: "issue" });
17
+ expect(harness.getState({ scopeKind: "issue", scopeId: "iss_1", stateKey: "seen" })).toBe(true);
18
+
19
+ const data = await harness.getData<{ status: string }>("health");
20
+ expect(data.status).toBe("ok");
21
+
22
+ const action = await harness.performAction<{ pong: boolean }>("ping");
23
+ expect(action.pong).toBe(true);
24
+ });
25
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": [
7
+ "ES2022",
8
+ "DOM"
9
+ ],
10
+ "jsx": "react-jsx",
11
+ "strict": true,
12
+ "skipLibCheck": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "outDir": "dist",
17
+ "rootDir": "."
18
+ },
19
+ "include": [
20
+ "src",
21
+ "tests"
22
+ ],
23
+ "exclude": [
24
+ "dist",
25
+ "node_modules"
26
+ ]
27
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["tests/**/*.spec.ts"],
6
+ environment: "node",
7
+ },
8
+ });