@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 +12 -0
- package/README.md +33 -0
- package/esbuild.config.mjs +17 -0
- package/package.json +48 -0
- package/rollup.config.mjs +28 -0
- package/src/manifest.ts +32 -0
- package/src/ui/index.tsx +23 -0
- package/src/worker.ts +27 -0
- package/tests/plugin.spec.ts +20 -0
- package/tsconfig.json +27 -0
- package/vitest.config.ts +8 -0
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);
|
package/src/manifest.ts
ADDED
|
@@ -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;
|
package/src/ui/index.tsx
ADDED
|
@@ -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
|
+
}
|