create-donobu-plugin 1.3.0 → 1.7.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 +146 -48
- package/index.js +263 -106
- package/package.json +31 -22
package/README.md
CHANGED
|
@@ -1,77 +1,113 @@
|
|
|
1
1
|
# create-donobu-plugin
|
|
2
2
|
|
|
3
|
-
`create-donobu-plugin` is the official scaffolding CLI for Donobu Studio plugins. It generates a TypeScript
|
|
3
|
+
`create-donobu-plugin` is the official scaffolding CLI for Donobu Studio plugins. It generates a TypeScript project wired to Donobu's plugin API and pins dependencies to whatever versions your local Donobu installation already uses.
|
|
4
4
|
|
|
5
5
|
## How Donobu loads plugins
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- Each plugin ships a bundled `dist/index.mjs` that exports one async function named `loadCustomTools(dependencies)`. When Donobu starts it imports every plugin bundle and calls that function to collect tools.
|
|
9
|
-
- `npm exec install-donobu-plugin` builds your project and copies the resulting `dist/` folder into `<workingDir>/plugins/<plugin-name>`, so restarting Donobu is enough to pick up changes.
|
|
7
|
+
Donobu watches a plugins directory inside its working data folder:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
| Platform | Path |
|
|
10
|
+
| -------- | ------------------------------------------------------ |
|
|
11
|
+
| macOS | `~/Library/Application Support/Donobu Studio/plugins/` |
|
|
12
|
+
| Windows | `%APPDATA%/Donobu Studio/plugins/` |
|
|
13
|
+
| Linux | `~/.config/Donobu Studio/plugins/` |
|
|
14
|
+
|
|
15
|
+
Each plugin ships a bundled `dist/index.mjs`. On startup Donobu imports every plugin and calls the exported functions it finds:
|
|
16
|
+
|
|
17
|
+
- **`loadCustomTools(deps)`** — returns an array of `Tool` instances.
|
|
18
|
+
- **`loadPersistencePlugin(deps)`** — returns `{ key, plugin }` to register a persistence backend, or `null` to skip.
|
|
19
|
+
|
|
20
|
+
A single plugin may export both functions. Scoped package names (e.g., `@myorg/my-plugin`) are supported.
|
|
21
|
+
|
|
22
|
+
`npm exec install-donobu-plugin` builds your project and copies `dist/` into the plugins directory. Restarting Donobu is enough to pick up changes.
|
|
12
23
|
|
|
13
24
|
## Prerequisites
|
|
14
25
|
|
|
15
|
-
- Node.js
|
|
26
|
+
- Node.js 20+ and a package manager (npm 8+, pnpm 10+, or yarn)
|
|
16
27
|
- A local Donobu Studio installation (desktop or backend) so the installer has somewhere to copy the plugin bundle
|
|
17
|
-
- Playwright browsers installed (Donobu will prompt you if they are missing)
|
|
18
28
|
- Write access to the Donobu Studio working directory
|
|
19
29
|
|
|
20
|
-
##
|
|
30
|
+
## Quick start
|
|
31
|
+
|
|
32
|
+
### Tool plugin (default)
|
|
21
33
|
|
|
22
34
|
```bash
|
|
23
35
|
npx create-donobu-plugin my-support-tools
|
|
36
|
+
cd my-support-tools && npm install
|
|
37
|
+
# Edit src/index.ts to add your custom tools
|
|
38
|
+
npm run build
|
|
39
|
+
npm exec install-donobu-plugin
|
|
40
|
+
# Restart Donobu Studio
|
|
24
41
|
```
|
|
25
42
|
|
|
26
|
-
|
|
43
|
+
### Persistence plugin
|
|
27
44
|
|
|
28
|
-
|
|
45
|
+
```bash
|
|
46
|
+
npx create-donobu-plugin my-storage --type persistence
|
|
47
|
+
cd my-storage && npm install
|
|
48
|
+
# Edit src/index.ts to implement your persistence plugin
|
|
49
|
+
npm run build
|
|
50
|
+
npm exec install-donobu-plugin
|
|
51
|
+
# Set PERSISTENCE_PRIORITY to include your plugin key, then restart Donobu
|
|
52
|
+
```
|
|
29
53
|
|
|
30
|
-
|
|
31
|
-
2. `cd my-support-tools && npm install`
|
|
32
|
-
3. Implement your tools in `src/index.ts`
|
|
33
|
-
4. `npm run build`
|
|
34
|
-
5. `npm exec install-donobu-plugin`
|
|
35
|
-
6. Restart Donobu Studio and verify the tools appear in the UI/logs
|
|
54
|
+
Plugin names may include lowercase letters, numbers, hyphens, and underscores. The CLI prints usage information if the argument is missing or invalid.
|
|
36
55
|
|
|
37
56
|
## Generated project layout
|
|
38
57
|
|
|
39
|
-
| Item | Description
|
|
40
|
-
| -------------------- |
|
|
41
|
-
| `package.json` | Private
|
|
42
|
-
| `tsconfig.json` | NodeNext compiler settings that emit
|
|
43
|
-
| `esbuild.config.mjs` | Bundles
|
|
44
|
-
| `src/index.ts` | Entry point
|
|
45
|
-
| `README.md` | Template instructions for the team that owns the generated plugin.
|
|
58
|
+
| Item | Description |
|
|
59
|
+
| -------------------- | ----------------------------------------------------------------------------------------------- |
|
|
60
|
+
| `package.json` | Private package with `"donobu-plugin": true`. `donobu` is a peer/dev dependency, never bundled. |
|
|
61
|
+
| `tsconfig.json` | ES2021 + NodeNext compiler settings that emit declarations into `dist/`. |
|
|
62
|
+
| `esbuild.config.mjs` | Bundles TypeScript source into `dist/index.mjs`, keeping `donobu` external. |
|
|
63
|
+
| `src/index.ts` | Entry point. Exports `loadCustomTools(deps)` and/or `loadPersistencePlugin(deps)`. |
|
|
64
|
+
| `README.md` | Template instructions for the team that owns the generated plugin. |
|
|
46
65
|
|
|
47
|
-
|
|
66
|
+
## The deps pattern
|
|
48
67
|
|
|
49
|
-
|
|
68
|
+
Plugins receive all runtime values through the `deps` parameter — **never use value imports from `donobu`**. Only `import type` is allowed because type imports are erased at compile time, while value imports would produce an `import ... from "donobu"` statement in the bundle that Node cannot resolve from the plugins directory.
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
```ts
|
|
71
|
+
// CORRECT — type imports are erased, runtime values come from deps
|
|
72
|
+
import type { Tool, PluginDependencies } from 'donobu';
|
|
73
|
+
|
|
74
|
+
export async function loadCustomTools(
|
|
75
|
+
deps: PluginDependencies,
|
|
76
|
+
): Promise<Tool<any, any>[]> {
|
|
77
|
+
const logger = deps.donobu.appLogger; // runtime access via deps
|
|
78
|
+
// ...
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
// WRONG — this creates a runtime import that will fail at load time
|
|
84
|
+
import { appLogger } from 'donobu';
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### What is available on `deps`
|
|
88
|
+
|
|
89
|
+
- **`deps.donobu`** — the full public SDK: `createTool`, `Tool`, `ReplayableInteraction`, `appLogger`, `MiscUtils`, `PlaywrightUtils`, schemas, exception classes, etc.
|
|
90
|
+
|
|
91
|
+
Feel free to add your own npm dependencies; esbuild bundles everything except the packages listed in `external`.
|
|
92
|
+
|
|
93
|
+
## Authoring tool plugins
|
|
94
|
+
|
|
95
|
+
Every tool plugin exports `loadCustomTools(deps)` and returns an array of `Tool` instances. The simplest way to create a tool is `deps.donobu.createTool()`:
|
|
52
96
|
|
|
53
97
|
```ts
|
|
54
|
-
// Note that non-type imports from 'donobu' should be extracted from the given
|
|
55
|
-
// 'deps.donobu' parameter in 'loadCustomTools'.
|
|
56
98
|
import type { Tool, PluginDependencies } from 'donobu';
|
|
57
99
|
import { z } from 'zod/v4';
|
|
58
100
|
|
|
59
101
|
export async function loadCustomTools(
|
|
60
|
-
deps: PluginDependencies
|
|
102
|
+
deps: PluginDependencies,
|
|
61
103
|
): Promise<Tool<any, any>[]> {
|
|
62
|
-
// Register your custom tools here.
|
|
63
104
|
return [
|
|
64
|
-
// Here is a small example tool for reference.
|
|
65
105
|
deps.donobu.createTool(deps, {
|
|
66
|
-
// The tool name, description, and schema are shared with the Donobu agent
|
|
67
|
-
// to help it decide when, where, and how this tool will be called.
|
|
68
106
|
name: 'judgeWebpageTitle',
|
|
69
107
|
description: 'Judge the quality of a title to a webpage.',
|
|
70
108
|
schema: z.object({ webpageTitle: z.string() }),
|
|
71
|
-
// This example tool uses a GPT/LLM so we call it out here.
|
|
72
109
|
requiresGpt: true,
|
|
73
110
|
call: async (context, parameters) => {
|
|
74
|
-
// 'requiredGpt' is true, so we are guaranteed a valid gptClient handle.
|
|
75
111
|
const resp = await context.gptClient!.getMessage([
|
|
76
112
|
{
|
|
77
113
|
type: 'user',
|
|
@@ -85,12 +121,8 @@ export async function loadCustomTools(
|
|
|
85
121
|
},
|
|
86
122
|
]);
|
|
87
123
|
return {
|
|
88
|
-
// Returning false or throwing an exception will signal tool call failure.
|
|
89
124
|
isSuccessful: true,
|
|
90
|
-
// This is what is shared back with the Donobu agent.
|
|
91
125
|
forLlm: resp.text,
|
|
92
|
-
// Metadata is persisted with the tool call result, but is not shared
|
|
93
|
-
// with the Donobu agent, so this can be used to store large complex data.
|
|
94
126
|
metadata: null,
|
|
95
127
|
};
|
|
96
128
|
},
|
|
@@ -99,11 +131,61 @@ export async function loadCustomTools(
|
|
|
99
131
|
}
|
|
100
132
|
```
|
|
101
133
|
|
|
102
|
-
|
|
134
|
+
For tools that need to extend `ReplayableInteraction` or another base class, use a factory function so the base class comes from `deps.donobu` rather than a value import:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import type { Tool, PluginDependencies } from 'donobu';
|
|
138
|
+
|
|
139
|
+
function createMyTool(deps: PluginDependencies): Tool<any, any> {
|
|
140
|
+
const { ReplayableInteraction } = deps.donobu;
|
|
141
|
+
|
|
142
|
+
class MyTool extends ReplayableInteraction</* ... */> {
|
|
143
|
+
// ...
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return new MyTool();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function loadCustomTools(deps: PluginDependencies) {
|
|
150
|
+
return [createMyTool(deps)];
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Authoring persistence plugins
|
|
155
|
+
|
|
156
|
+
Persistence plugins export `loadPersistencePlugin(deps)`. The returned `key` must appear in the `PERSISTENCE_PRIORITY` environment variable for Donobu to use it.
|
|
103
157
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
158
|
+
```ts
|
|
159
|
+
import type {
|
|
160
|
+
EnvPersistence,
|
|
161
|
+
FlowsPersistence,
|
|
162
|
+
PersistencePlugin,
|
|
163
|
+
PluginDependencies,
|
|
164
|
+
} from 'donobu';
|
|
165
|
+
|
|
166
|
+
export async function loadPersistencePlugin(
|
|
167
|
+
deps: PluginDependencies,
|
|
168
|
+
): Promise<{ key: string; plugin: PersistencePlugin } | null> {
|
|
169
|
+
const bucket = process.env.MY_STORAGE_BUCKET;
|
|
170
|
+
if (!bucket) return null; // skip registration if config is missing
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
key: 'MY_CUSTOM',
|
|
174
|
+
plugin: {
|
|
175
|
+
async createFlowsPersistence(): Promise<FlowsPersistence | null> {
|
|
176
|
+
// Return your FlowsPersistence implementation, or null.
|
|
177
|
+
return null;
|
|
178
|
+
},
|
|
179
|
+
async createEnvPersistence(): Promise<EnvPersistence | null> {
|
|
180
|
+
// Return your EnvPersistence implementation, or null.
|
|
181
|
+
return null;
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
If your persistence classes need SDK utilities like `appLogger` or `FlowNotFoundException`, accept `deps.donobu` as a constructor parameter rather than importing them directly.
|
|
107
189
|
|
|
108
190
|
## Build and install
|
|
109
191
|
|
|
@@ -112,7 +194,24 @@ npm run build
|
|
|
112
194
|
npm exec install-donobu-plugin
|
|
113
195
|
```
|
|
114
196
|
|
|
115
|
-
`npm run build` cleans `dist/`,
|
|
197
|
+
`npm run build` cleans `dist/`, runs TypeScript (for declarations), and bundles the source with esbuild. `npm exec install-donobu-plugin` infers the plugin name from `package.json` and copies `dist/` into the Donobu plugins directory. Restart Donobu after installing so the new bundle is loaded.
|
|
198
|
+
|
|
199
|
+
## esbuild considerations
|
|
200
|
+
|
|
201
|
+
The generated `esbuild.config.mjs` bundles your TypeScript source and all third-party dependencies into a single `dist/index.mjs`, while keeping `donobu` external (it is injected at runtime by the plugin loader). The `mainFields: ['module', 'main']` option ensures esbuild prefers ESM entry points from third-party packages for cleaner output.
|
|
202
|
+
|
|
203
|
+
If you add dependencies that include CJS modules using `require()` for Node builtins (common in Google Cloud, AWS, or gRPC libraries), the bundle may fail at runtime with `"Dynamic require of X is not supported"`. Fix this by adding a `banner` that provides a real `require()`:
|
|
204
|
+
|
|
205
|
+
```js
|
|
206
|
+
await build({
|
|
207
|
+
// ... your existing options ...
|
|
208
|
+
banner: {
|
|
209
|
+
js: 'import { createRequire } from "module"; const require = createRequire(import.meta.url);',
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This injects a real `require()` via Node's `createRequire`, allowing bundled CJS code to resolve Node builtins normally in the ESM context.
|
|
116
215
|
|
|
117
216
|
## Recommended development loop
|
|
118
217
|
|
|
@@ -121,10 +220,9 @@ npm exec install-donobu-plugin
|
|
|
121
220
|
3. Restart Donobu Studio or the backend process
|
|
122
221
|
4. Trigger your tool from the UI or API to verify behavior
|
|
123
222
|
|
|
124
|
-
Because the default build script runs `npm install`, consider splitting the script into `build` and `bundle` variants if you need faster inner-loop iterations.
|
|
125
|
-
|
|
126
223
|
## Troubleshooting
|
|
127
224
|
|
|
128
225
|
- **Plugin not appearing:** Ensure `npm exec install-donobu-plugin` ran successfully and that `dist/index.mjs` exists. Restart Donobu and watch the logs for plugin loading messages.
|
|
129
|
-
- **
|
|
130
|
-
- **
|
|
226
|
+
- **"Cannot find package 'donobu'":** Your bundle contains a runtime `import ... from "donobu"`. Ensure all `donobu` imports use `import type` and access runtime values through `deps.donobu`.
|
|
227
|
+
- **"Dynamic require of X is not supported":** A bundled CJS dependency calls `require()` for a Node builtin. Add the `createRequire` banner to your esbuild config (see above).
|
|
228
|
+
- **Schema errors:** Zod throws runtime errors when inputs don't match your schema. Log the error message to quickly see which field failed.
|
package/index.js
CHANGED
|
@@ -1,152 +1,131 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { mkdir, writeFile
|
|
4
|
-
import { join
|
|
5
|
-
import { createRequire } from 'module';
|
|
3
|
+
import { mkdir, writeFile } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
// Find the donobu package.json by resolving the module path
|
|
10
|
-
const require = createRequire(import.meta.url);
|
|
11
|
-
const donobuPath = require.resolve('donobu');
|
|
12
|
-
// Navigate up to find package.json (donobu path might point to dist/main.js)
|
|
13
|
-
const donobuDir = dirname(donobuPath);
|
|
14
|
-
let packageJsonPath = join(donobuDir, 'package.json');
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
await readFile(packageJsonPath, 'utf8');
|
|
18
|
-
} catch {
|
|
19
|
-
// If not found, try going up one more directory
|
|
20
|
-
packageJsonPath = join(dirname(donobuDir), 'package.json');
|
|
21
|
-
}
|
|
6
|
+
const DONOBU_VERSION = '5.17.2';
|
|
7
|
+
const VALID_PLUGIN_TYPES = ['tool', 'persistence'];
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
const donobuPackageJson = JSON.parse(packageJsonContent);
|
|
25
|
-
|
|
26
|
-
return {
|
|
27
|
-
donobuVersion: donobuPackageJson.version,
|
|
28
|
-
playwrightVersion:
|
|
29
|
-
donobuPackageJson.devDependencies?.playwright ||
|
|
30
|
-
donobuPackageJson.dependencies?.playwright ||
|
|
31
|
-
donobuPackageJson.peerDependencies?.playwright?.replace(/^>=/, '') ||
|
|
32
|
-
'1.53.2',
|
|
33
|
-
playwrightTestVersion:
|
|
34
|
-
donobuPackageJson.devDependencies?.['@playwright/test'] ||
|
|
35
|
-
donobuPackageJson.dependencies?.['@playwright/test'] ||
|
|
36
|
-
donobuPackageJson.peerDependencies?.['@playwright/test']?.replace(
|
|
37
|
-
/^>=/,
|
|
38
|
-
''
|
|
39
|
-
) ||
|
|
40
|
-
'1.53.2',
|
|
41
|
-
};
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.error('Could not read the Donobu package.json file!');
|
|
44
|
-
throw error;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function createPluginStructure(pluginName) {
|
|
9
|
+
async function createPluginStructure(pluginName, pluginType) {
|
|
49
10
|
const pluginDir = join(process.cwd(), pluginName);
|
|
50
|
-
console.log(`Creating plugin directory: ${pluginDir}`);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
console.log(`Using Donobu version: ${versions.donobuVersion}`);
|
|
54
|
-
console.log(`Using Playwright version: ${versions.playwrightVersion}`);
|
|
11
|
+
console.log(`Creating ${pluginType} plugin directory: ${pluginDir}`);
|
|
12
|
+
const donobuVersion = DONOBU_VERSION;
|
|
13
|
+
console.log(`Using Donobu version: ${donobuVersion}`);
|
|
55
14
|
// Create directory structure
|
|
56
15
|
await mkdir(pluginDir, { recursive: true });
|
|
57
16
|
await mkdir(join(pluginDir, 'src'), { recursive: true });
|
|
58
17
|
console.log('Creating files...');
|
|
59
|
-
|
|
18
|
+
|
|
19
|
+
if (pluginType === 'persistence') {
|
|
20
|
+
await createPersistenceTemplateFiles(pluginDir, pluginName, donobuVersion);
|
|
21
|
+
} else {
|
|
22
|
+
await createToolTemplateFiles(pluginDir, pluginName, donobuVersion);
|
|
23
|
+
}
|
|
24
|
+
|
|
60
25
|
console.log(`\nPlugin "${pluginName}" created successfully!`);
|
|
61
26
|
console.log(`\nNext steps:`);
|
|
62
27
|
console.log(` cd ${pluginName}`);
|
|
63
28
|
console.log(` npm install`);
|
|
64
|
-
|
|
29
|
+
if (pluginType === 'persistence') {
|
|
30
|
+
console.log(` # Edit src/index.ts to implement your persistence plugin`);
|
|
31
|
+
} else {
|
|
32
|
+
console.log(` # Edit src/index.ts to add your custom tools`);
|
|
33
|
+
}
|
|
65
34
|
console.log(` npx install-donobu-plugin`);
|
|
66
35
|
}
|
|
67
36
|
|
|
68
|
-
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Shared template helpers
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
function createTsConfig() {
|
|
42
|
+
return {
|
|
43
|
+
compilerOptions: {
|
|
44
|
+
target: 'ES2021',
|
|
45
|
+
lib: ['ES2021', 'DOM'],
|
|
46
|
+
module: 'NodeNext',
|
|
47
|
+
moduleResolution: 'NodeNext',
|
|
48
|
+
rootDir: './src',
|
|
49
|
+
outDir: './dist',
|
|
50
|
+
declaration: true,
|
|
51
|
+
declarationMap: true,
|
|
52
|
+
sourceMap: true,
|
|
53
|
+
esModuleInterop: true,
|
|
54
|
+
forceConsistentCasingInFileNames: true,
|
|
55
|
+
strict: true,
|
|
56
|
+
skipLibCheck: true,
|
|
57
|
+
allowUnusedLabels: false,
|
|
58
|
+
allowUnreachableCode: false,
|
|
59
|
+
},
|
|
60
|
+
include: ['src'],
|
|
61
|
+
exclude: ['node_modules', 'dist'],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Tool plugin templates
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
async function createToolTemplateFiles(pluginDir, pluginName, donobuVersion) {
|
|
69
70
|
// Create package.json
|
|
70
71
|
const packageJson = {
|
|
71
72
|
name: pluginName,
|
|
73
|
+
'donobu-plugin': true,
|
|
72
74
|
version: '1.0.0',
|
|
73
75
|
private: true,
|
|
74
|
-
type: 'module',
|
|
75
76
|
description: 'Custom tools for use by Donobu.',
|
|
76
|
-
main: '
|
|
77
|
-
types: '
|
|
77
|
+
main: 'dist/index.js',
|
|
78
|
+
types: 'dist/index.d.ts',
|
|
78
79
|
exports: {
|
|
79
|
-
|
|
80
|
+
types: './dist/index.d.ts',
|
|
81
|
+
import: './dist/index.mjs',
|
|
82
|
+
require: './dist/index.js',
|
|
80
83
|
},
|
|
81
84
|
files: ['dist'],
|
|
82
85
|
scripts: {
|
|
83
86
|
clean: 'rm -rf dist',
|
|
84
|
-
build:
|
|
85
|
-
'npm run clean && npm install && tsc -p tsconfig.json && node esbuild.config.mjs',
|
|
87
|
+
build: 'npm run clean && tsc && node esbuild.config.mjs',
|
|
86
88
|
},
|
|
87
89
|
license: 'UNLICENSED',
|
|
88
|
-
devDependencies: {
|
|
89
|
-
donobu: `^${versions.donobuVersion}`,
|
|
90
|
-
playwright: versions.playwrightVersion,
|
|
91
|
-
'@playwright/test': versions.playwrightTestVersion,
|
|
92
|
-
typescript: '^5.8.3',
|
|
93
|
-
esbuild: '^0.25.6',
|
|
94
|
-
},
|
|
95
90
|
peerDependencies: {
|
|
96
|
-
donobu: `>=${
|
|
97
|
-
playwright: `>=${versions.playwrightVersion}`,
|
|
98
|
-
'@playwright/test': `>=${versions.playwrightTestVersion}`,
|
|
91
|
+
donobu: `>=${donobuVersion}`,
|
|
99
92
|
},
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
'
|
|
105
|
-
optional: false,
|
|
106
|
-
},
|
|
93
|
+
devDependencies: {
|
|
94
|
+
'@types/node': '^22.19.13',
|
|
95
|
+
donobu: `^${donobuVersion}`,
|
|
96
|
+
esbuild: '^0.25.12',
|
|
97
|
+
typescript: '^5.8.3',
|
|
107
98
|
},
|
|
108
|
-
|
|
109
|
-
|
|
99
|
+
pnpm: {
|
|
100
|
+
onlyBuiltDependencies: ['better-sqlite3', 'esbuild'],
|
|
110
101
|
},
|
|
111
102
|
};
|
|
112
103
|
|
|
113
104
|
await writeFile(
|
|
114
105
|
join(pluginDir, 'package.json'),
|
|
115
|
-
JSON.stringify(packageJson, null, 2)
|
|
106
|
+
JSON.stringify(packageJson, null, 2),
|
|
116
107
|
);
|
|
117
108
|
|
|
118
|
-
// Create tsconfig.json
|
|
119
|
-
const tsConfig = {
|
|
120
|
-
compilerOptions: {
|
|
121
|
-
target: 'ES2020',
|
|
122
|
-
module: 'NodeNext',
|
|
123
|
-
moduleResolution: 'NodeNext',
|
|
124
|
-
rootDir: './src',
|
|
125
|
-
outDir: './dist',
|
|
126
|
-
declaration: true,
|
|
127
|
-
strict: true,
|
|
128
|
-
esModuleInterop: true,
|
|
129
|
-
skipLibCheck: true,
|
|
130
|
-
forceConsistentCasingInFileNames: true,
|
|
131
|
-
},
|
|
132
|
-
include: ['src/**/*'],
|
|
133
|
-
};
|
|
134
|
-
|
|
135
109
|
await writeFile(
|
|
136
110
|
join(pluginDir, 'tsconfig.json'),
|
|
137
|
-
JSON.stringify(
|
|
111
|
+
JSON.stringify(createTsConfig(), null, 2),
|
|
138
112
|
);
|
|
139
113
|
|
|
140
114
|
// Create esbuild.config.mjs
|
|
141
115
|
const esbuildConfig = `import { build } from 'esbuild';
|
|
142
116
|
|
|
143
117
|
await build({
|
|
144
|
-
entryPoints: ['
|
|
118
|
+
entryPoints: ['src/index.ts'],
|
|
145
119
|
outfile: 'dist/index.mjs',
|
|
146
120
|
bundle: true,
|
|
147
121
|
format: 'esm',
|
|
148
122
|
platform: 'node',
|
|
149
|
-
|
|
123
|
+
|
|
124
|
+
// 'donobu' is provided at runtime by the plugin loader via deps.donobu.
|
|
125
|
+
external: ['donobu'],
|
|
126
|
+
|
|
127
|
+
mainFields: ['module', 'main'],
|
|
128
|
+
|
|
150
129
|
minify: true,
|
|
151
130
|
});
|
|
152
131
|
`;
|
|
@@ -182,7 +161,7 @@ export async function loadCustomTools(
|
|
|
182
161
|
{
|
|
183
162
|
type: 'text',
|
|
184
163
|
text: \`Judge the quality of this webpage title on a scale of 1 (worst) to 10 (best) and explain why.
|
|
185
|
-
Title:
|
|
164
|
+
Title: \${parameters.webpageTitle}\`,
|
|
186
165
|
},
|
|
187
166
|
],
|
|
188
167
|
},
|
|
@@ -206,7 +185,7 @@ export async function loadCustomTools(
|
|
|
206
185
|
// Create a basic README
|
|
207
186
|
const readme = `# ${pluginName}
|
|
208
187
|
|
|
209
|
-
A custom Donobu plugin
|
|
188
|
+
A custom Donobu tool plugin for browser automation.
|
|
210
189
|
|
|
211
190
|
## Development
|
|
212
191
|
|
|
@@ -216,33 +195,211 @@ A custom Donobu plugin with tools for browser automation.
|
|
|
216
195
|
npm run build
|
|
217
196
|
npm exec install-donobu-plugin
|
|
218
197
|
\`\`\`
|
|
219
|
-
3. Restart Donobu to
|
|
198
|
+
3. Restart Donobu to load your plugin
|
|
220
199
|
`;
|
|
221
200
|
|
|
222
201
|
await writeFile(join(pluginDir, 'README.md'), readme);
|
|
223
202
|
}
|
|
224
203
|
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Persistence plugin templates
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
async function createPersistenceTemplateFiles(
|
|
209
|
+
pluginDir,
|
|
210
|
+
pluginName,
|
|
211
|
+
donobuVersion,
|
|
212
|
+
) {
|
|
213
|
+
// Create package.json — no playwright dependency needed
|
|
214
|
+
const packageJson = {
|
|
215
|
+
name: pluginName,
|
|
216
|
+
'donobu-plugin': true,
|
|
217
|
+
version: '1.0.0',
|
|
218
|
+
private: true,
|
|
219
|
+
description: 'Custom persistence plugin for Donobu.',
|
|
220
|
+
main: 'dist/index.js',
|
|
221
|
+
types: 'dist/index.d.ts',
|
|
222
|
+
exports: {
|
|
223
|
+
types: './dist/index.d.ts',
|
|
224
|
+
import: './dist/index.mjs',
|
|
225
|
+
require: './dist/index.js',
|
|
226
|
+
},
|
|
227
|
+
files: ['dist'],
|
|
228
|
+
scripts: {
|
|
229
|
+
clean: 'rm -rf dist',
|
|
230
|
+
build: 'npm run clean && tsc && node esbuild.config.mjs',
|
|
231
|
+
},
|
|
232
|
+
license: 'UNLICENSED',
|
|
233
|
+
peerDependencies: {
|
|
234
|
+
donobu: `>=${donobuVersion}`,
|
|
235
|
+
},
|
|
236
|
+
devDependencies: {
|
|
237
|
+
'@types/node': '^22.19.13',
|
|
238
|
+
donobu: `^${donobuVersion}`,
|
|
239
|
+
esbuild: '^0.25.12',
|
|
240
|
+
typescript: '^5.8.3',
|
|
241
|
+
},
|
|
242
|
+
pnpm: {
|
|
243
|
+
onlyBuiltDependencies: ['better-sqlite3', 'esbuild'],
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
await writeFile(
|
|
248
|
+
join(pluginDir, 'package.json'),
|
|
249
|
+
JSON.stringify(packageJson, null, 2),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
await writeFile(
|
|
253
|
+
join(pluginDir, 'tsconfig.json'),
|
|
254
|
+
JSON.stringify(createTsConfig(), null, 2),
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Create esbuild.config.mjs — only externalize donobu
|
|
258
|
+
const esbuildConfig = `import { build } from 'esbuild';
|
|
259
|
+
|
|
260
|
+
await build({
|
|
261
|
+
entryPoints: ['src/index.ts'],
|
|
262
|
+
outfile: 'dist/index.mjs',
|
|
263
|
+
bundle: true,
|
|
264
|
+
format: 'esm',
|
|
265
|
+
platform: 'node',
|
|
266
|
+
|
|
267
|
+
// 'donobu' is provided at runtime by the plugin loader via deps.donobu.
|
|
268
|
+
external: ['donobu'],
|
|
269
|
+
|
|
270
|
+
mainFields: ['module', 'main'],
|
|
271
|
+
|
|
272
|
+
minify: true,
|
|
273
|
+
});
|
|
274
|
+
`;
|
|
275
|
+
|
|
276
|
+
await writeFile(join(pluginDir, 'esbuild.config.mjs'), esbuildConfig);
|
|
277
|
+
|
|
278
|
+
// Create src/index.ts
|
|
279
|
+
const indexTs = `import type {
|
|
280
|
+
EnvPersistence,
|
|
281
|
+
FlowsPersistence,
|
|
282
|
+
PersistencePlugin,
|
|
283
|
+
PluginDependencies,
|
|
284
|
+
} from 'donobu';
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Called by Donobu on startup when this plugin is installed.
|
|
288
|
+
*
|
|
289
|
+
* Return an object with a \`key\` (matching an entry in the
|
|
290
|
+
* PERSISTENCE_PRIORITY environment variable) and a \`plugin\`
|
|
291
|
+
* implementing the PersistencePlugin interface.
|
|
292
|
+
*
|
|
293
|
+
* Return \`null\` to skip registration (e.g., if required
|
|
294
|
+
* environment variables are missing).
|
|
295
|
+
*/
|
|
296
|
+
export async function loadPersistencePlugin(
|
|
297
|
+
deps: PluginDependencies,
|
|
298
|
+
): Promise<{ key: string; plugin: PersistencePlugin } | null> {
|
|
299
|
+
// Example: skip registration if required config is not set.
|
|
300
|
+
// const bucket = process.env.MY_STORAGE_BUCKET;
|
|
301
|
+
// if (!bucket) return null;
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
// This key must appear in the PERSISTENCE_PRIORITY env variable
|
|
305
|
+
// for Donobu to use this plugin. For example:
|
|
306
|
+
// PERSISTENCE_PRIORITY=MY_CUSTOM,LOCAL,RAM
|
|
307
|
+
key: 'MY_CUSTOM',
|
|
308
|
+
plugin: {
|
|
309
|
+
async createFlowsPersistence(): Promise<FlowsPersistence | null> {
|
|
310
|
+
// Return your FlowsPersistence implementation here.
|
|
311
|
+
// Return null if the required configuration is missing.
|
|
312
|
+
return null;
|
|
313
|
+
},
|
|
314
|
+
async createEnvPersistence(): Promise<EnvPersistence | null> {
|
|
315
|
+
// Return your EnvPersistence implementation here.
|
|
316
|
+
// Return null if the required configuration is missing.
|
|
317
|
+
return null;
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}`;
|
|
322
|
+
|
|
323
|
+
await writeFile(join(pluginDir, 'src', 'index.ts'), indexTs);
|
|
324
|
+
|
|
325
|
+
// Create a basic README
|
|
326
|
+
const readme = `# ${pluginName}
|
|
327
|
+
|
|
328
|
+
A custom Donobu persistence plugin.
|
|
329
|
+
|
|
330
|
+
## Development
|
|
331
|
+
|
|
332
|
+
1. Edit \`src/index.ts\` to implement your persistence plugin
|
|
333
|
+
2. Build and install the plugin:
|
|
334
|
+
\`\`\`bash
|
|
335
|
+
npm run build
|
|
336
|
+
npm exec install-donobu-plugin
|
|
337
|
+
\`\`\`
|
|
338
|
+
3. Set the \`PERSISTENCE_PRIORITY\` environment variable to include your plugin key
|
|
339
|
+
4. Restart Donobu to load your plugin
|
|
340
|
+
`;
|
|
341
|
+
|
|
342
|
+
await writeFile(join(pluginDir, 'README.md'), readme);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
// CLI
|
|
347
|
+
// ---------------------------------------------------------------------------
|
|
348
|
+
|
|
349
|
+
function parseArgs(argv) {
|
|
350
|
+
const args = argv.slice(2);
|
|
351
|
+
let pluginName = null;
|
|
352
|
+
let pluginType = 'tool';
|
|
353
|
+
|
|
354
|
+
for (let i = 0; i < args.length; i++) {
|
|
355
|
+
if (args[i] === '--type' && i + 1 < args.length) {
|
|
356
|
+
pluginType = args[i + 1];
|
|
357
|
+
i++; // skip value
|
|
358
|
+
} else if (!args[i].startsWith('-')) {
|
|
359
|
+
pluginName = args[i];
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { pluginName, pluginType };
|
|
364
|
+
}
|
|
365
|
+
|
|
225
366
|
async function main() {
|
|
226
|
-
const pluginName = process.argv
|
|
367
|
+
const { pluginName, pluginType } = parseArgs(process.argv);
|
|
227
368
|
|
|
228
369
|
if (!pluginName) {
|
|
229
|
-
console.error(
|
|
370
|
+
console.error(
|
|
371
|
+
'Usage: create-donobu-plugin <plugin-name> [--type <tool|persistence>]',
|
|
372
|
+
);
|
|
230
373
|
console.error('');
|
|
231
|
-
console.error('
|
|
374
|
+
console.error('Options:');
|
|
375
|
+
console.error(
|
|
376
|
+
' --type <type> Plugin type: "tool" (default) or "persistence"',
|
|
377
|
+
);
|
|
378
|
+
console.error('');
|
|
379
|
+
console.error('Examples:');
|
|
232
380
|
console.error(' npm create donobu-plugin my-awesome-plugin');
|
|
381
|
+
console.error(' npm create donobu-plugin my-storage --type persistence');
|
|
233
382
|
process.exit(1);
|
|
234
383
|
}
|
|
235
384
|
|
|
236
385
|
// Validate plugin name
|
|
237
386
|
if (!/^[a-z0-9-_]+$/.test(pluginName)) {
|
|
238
387
|
console.error(
|
|
239
|
-
'Plugin name must contain only lowercase letters, numbers, hyphens, and underscores'
|
|
388
|
+
'Plugin name must contain only lowercase letters, numbers, hyphens, and underscores',
|
|
389
|
+
);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Validate plugin type
|
|
394
|
+
if (!VALID_PLUGIN_TYPES.includes(pluginType)) {
|
|
395
|
+
console.error(
|
|
396
|
+
`Invalid plugin type "${pluginType}". Must be one of: ${VALID_PLUGIN_TYPES.join(', ')}`,
|
|
240
397
|
);
|
|
241
398
|
process.exit(1);
|
|
242
399
|
}
|
|
243
400
|
|
|
244
401
|
try {
|
|
245
|
-
await createPluginStructure(pluginName);
|
|
402
|
+
await createPluginStructure(pluginName, pluginType);
|
|
246
403
|
} catch (error) {
|
|
247
404
|
console.error('Failed to create plugin:', error.message);
|
|
248
405
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,24 +1,33 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
2
|
+
"name": "create-donobu-plugin",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Create a new Donobu plugin",
|
|
6
|
+
"author": "Donobu",
|
|
7
|
+
"homepage": "https://donobu.com",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"bin": {
|
|
10
|
+
"create-donobu-plugin": "./index.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"index.js"
|
|
14
|
+
],
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@eslint/js": "^10.0.1",
|
|
17
|
+
"eslint": "^10.0.2",
|
|
18
|
+
"eslint-plugin-simple-import-sort": "^12.1.1",
|
|
19
|
+
"globals": "^16.0.0",
|
|
20
|
+
"prettier": "^3.8.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"donobu",
|
|
24
|
+
"plugin",
|
|
25
|
+
"create"
|
|
26
|
+
],
|
|
27
|
+
"license": "UNLICENSED",
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "pnpm install --frozen-lockfile && pnpm run lint --fix && pnpm run format && pnpm run lint",
|
|
30
|
+
"lint": "eslint .",
|
|
31
|
+
"format": "prettier --write ."
|
|
32
|
+
}
|
|
24
33
|
}
|