@bopen-io/tortuga-plugin 0.0.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/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1
4
+
5
+ ### Added
6
+
7
+ - Initial scaffold of Tortuga Paperclip plugin
8
+ - Health dashboard widget with ping action
9
+ - Event subscription for issue.created
10
+ - Plugin state tracking
11
+ - esbuild and rollup build configs
12
+ - Test harness setup
package/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Tortuga
2
+
3
+ bOpen agent fleet bridge: syncs agents from ClawNet registry, exposes skills as tools, fleet monitoring, webhook integrations
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
+ This scaffold snapshots `@paperclipai/plugin-sdk` and `@paperclipai/shared` from a local Paperclip checkout at:
15
+
16
+ `/Users/satchmo/code/paperclip/packages/plugins/sdk`
17
+
18
+ The packed tarballs live in `.paperclip-sdk/` for local development. Before publishing this plugin, switch those dependencies to published package versions once they are available on npm.
19
+
20
+
21
+
22
+ ## Install Into Paperclip
23
+
24
+ ```bash
25
+ curl -X POST http://127.0.0.1:3100/api/plugins/install \
26
+ -H "Content-Type: application/json" \
27
+ -d '{"packageName":"/Users/satchmo/code/tortuga-plugin","isLocalPath":true}'
28
+ ```
29
+
30
+ ## Build Options
31
+
32
+ - `pnpm build` uses esbuild presets from `@paperclipai/plugin-sdk/bundlers`.
33
+ - `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,48 @@
1
+ {
2
+ "name": "@bopen-io/tortuga-plugin",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "private": false,
6
+ "description": "bOpen agent fleet bridge: syncs agents from ClawNet registry, exposes skills as tools, fleet monitoring, webhook integrations",
7
+ "scripts": {
8
+ "build": "node ./esbuild.config.mjs",
9
+ "build:rollup": "rollup -c",
10
+ "dev": "node ./esbuild.config.mjs --watch",
11
+ "dev:ui": "paperclip-plugin-dev-server --root . --ui-dir dist/ui --port 4177",
12
+ "test": "vitest run --config ./vitest.config.ts",
13
+ "typecheck": "tsc --noEmit"
14
+ },
15
+ "paperclipPlugin": {
16
+ "manifest": "./dist/manifest.js",
17
+ "worker": "./dist/worker.js",
18
+ "ui": "./dist/ui/"
19
+ },
20
+ "keywords": [
21
+ "paperclip",
22
+ "plugin",
23
+ "connector"
24
+ ],
25
+ "author": "bOpen",
26
+ "license": "MIT",
27
+ "pnpm": {
28
+ "overrides": {
29
+ "@paperclipai/shared": "file:.paperclip-sdk/paperclipai-shared-0.3.1.tgz"
30
+ }
31
+ },
32
+ "devDependencies": {
33
+ "@paperclipai/shared": "file:.paperclip-sdk/paperclipai-shared-0.3.1.tgz",
34
+ "@paperclipai/plugin-sdk": "file:.paperclip-sdk/paperclipai-plugin-sdk-1.0.0.tgz",
35
+ "@rollup/plugin-node-resolve": "^16.0.1",
36
+ "@rollup/plugin-typescript": "^12.1.2",
37
+ "@types/node": "^24.6.0",
38
+ "@types/react": "^19.0.8",
39
+ "esbuild": "^0.27.3",
40
+ "rollup": "^4.38.0",
41
+ "tslib": "^2.8.1",
42
+ "typescript": "^5.7.3",
43
+ "vitest": "^3.0.5"
44
+ },
45
+ "peerDependencies": {
46
+ "react": ">=18"
47
+ }
48
+ }
@@ -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,32 @@
1
+ import type { PaperclipPluginManifestV1 } from "@paperclipai/plugin-sdk";
2
+
3
+ const manifest: PaperclipPluginManifestV1 = {
4
+ id: "bopen-io.tortuga-plugin",
5
+ apiVersion: 1,
6
+ version: "0.1.0",
7
+ displayName: "Tortuga",
8
+ description: "bOpen agent fleet bridge: syncs agents from ClawNet registry, exposes skills as tools, fleet monitoring, webhook integrations",
9
+ author: "bOpen",
10
+ categories: ["connector"],
11
+ capabilities: [
12
+ "events.subscribe",
13
+ "plugin.state.read",
14
+ "plugin.state.write"
15
+ ],
16
+ entrypoints: {
17
+ worker: "./dist/worker.js",
18
+ ui: "./dist/ui"
19
+ },
20
+ ui: {
21
+ slots: [
22
+ {
23
+ type: "dashboardWidget",
24
+ id: "health-widget",
25
+ displayName: "Tortuga Health",
26
+ exportName: "DashboardWidget"
27
+ }
28
+ ]
29
+ }
30
+ };
31
+
32
+ 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>Tortuga</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.events.on("issue.created", async (event) => {
6
+ const issueId = event.entityId ?? "unknown";
7
+ await ctx.state.set({ scopeKind: "issue", scopeId: issueId, stateKey: "seen" }, true);
8
+ ctx.logger.info("Observed issue.created", { issueId });
9
+ });
10
+
11
+ ctx.data.register("health", async () => {
12
+ return { status: "ok", checkedAt: new Date().toISOString() };
13
+ });
14
+
15
+ ctx.actions.register("ping", async () => {
16
+ ctx.logger.info("Ping action invoked");
17
+ return { pong: true, at: new Date().toISOString() };
18
+ });
19
+ },
20
+
21
+ async onHealth() {
22
+ return { status: "ok", message: "Plugin worker is running" };
23
+ }
24
+ });
25
+
26
+ export default plugin;
27
+ runWorker(plugin, import.meta.url);
@@ -0,0 +1,20 @@
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("registers data + actions and handles events", async () => {
8
+ const harness = createTestHarness({ manifest, capabilities: [...manifest.capabilities, "events.emit"] });
9
+ await plugin.definition.setup(harness.ctx);
10
+
11
+ await harness.emit("issue.created", { issueId: "iss_1" }, { entityId: "iss_1", entityType: "issue" });
12
+ expect(harness.getState({ scopeKind: "issue", scopeId: "iss_1", stateKey: "seen" })).toBe(true);
13
+
14
+ const data = await harness.getData<{ status: string }>("health");
15
+ expect(data.status).toBe("ok");
16
+
17
+ const action = await harness.performAction<{ pong: boolean }>("ping");
18
+ expect(action.pong).toBe(true);
19
+ });
20
+ });
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
+ });