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.
Files changed (3) hide show
  1. package/README.md +146 -48
  2. package/index.js +263 -106
  3. 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 workspace wired to Donobus plugin API, pins dependencies to whatever versions your local Studio installation already uses, and ships with helper utilities so you can focus on describing new tools instead of wiring.
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
- - Donobu watches a plugins directory inside its working data folder (macOS: `~/Library/Application Support/Donobu Studio/plugins`, Windows: `%APPDATA%/Donobu Studio/plugins`, Linux: `~/.config/Donobu Studio/plugins`).
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
- Keep those three rules in mind and your plugin will load reliably.
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 18+ and npm 8+
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
- ## Installation
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
- Provide the plugin name as the first argument. Names may include lowercase letters, numbers, hyphens, and underscores. The CLI prints usage information if the argument is missing or invalid.
43
+ ### Persistence plugin
27
44
 
28
- ## Quick start
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
- 1. Scaffold a plugin: `npx create-donobu-plugin my-support-tools`
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 ESM package whose `main`/`types` fields point to `dist/`. |
42
- | `tsconfig.json` | NodeNext compiler settings that emit JS + declarations into `dist/`. |
43
- | `esbuild.config.mjs` | Bundles the compiled JS into `dist/index.mjs`, keeping Donobu and Playwright as external dependencies. |
44
- | `src/index.ts` | Entry point that must export `loadCustomTools(dependencies)` and return an array of tools. |
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
- Because the scaffold reuses the Donobu versions already on disk, regenerating the project after a Donobu upgrade is the fastest way to stay in lockstep.
66
+ ## The deps pattern
48
67
 
49
- ## Authoring tools
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
- Every plugin's `index.ts` exports `loadCustomTools(dependencies)` and returns an array of `Tool` instances.
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
- ## Working with injected dependencies
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
- - `deps.donobu` exposes the public SDK: the base `Tool` class, `PlaywrightUtils`, logging helpers, type definitions, etc.
105
- - `deps.playwright` points to the same Playwright build Donobu uses, so your plugin and the core runtime stay aligned.
106
- - Feel free to add your own dependencies; `esbuild` bundles everything except the packages listed in `external`.
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/`, reinstalls dependencies (to ensure version parity), runs TypeScript, and bundles the result. `npm exec install-donobu-plugin` validates the current folder, infers the plugin name (prefers `package.json`, falls back to the git repo name or `$USER`), and copies `dist/` into the Donobu plugins directory. Restart Donobu after installing so the new bundle is loaded.
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
- - **Schema errors:** Zod throws runtime errors when inputs don’t match your schema. Log the error message to quickly see which field failed.
130
- - **Version mismatch:** If Donobu upgrades Playwright or its SDK, re-run the scaffold (or update the plugin’s dev dependencies) so you stay compatible.
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, readFile } from 'fs/promises';
4
- import { join, dirname } from 'path';
5
- import { createRequire } from 'module';
3
+ import { mkdir, writeFile } from 'fs/promises';
4
+ import { join } from 'path';
6
5
 
7
- async function getDonobuVersions() {
8
- try {
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
- const packageJsonContent = await readFile(packageJsonPath, 'utf8');
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
- // Get current donobu and playwright versions
52
- const versions = await getDonobuVersions();
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
- await createTemplateFiles(pluginDir, pluginName, versions);
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
- console.log(` # Edit src/index.ts to add your custom tools`);
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
- async function createTemplateFiles(pluginDir, pluginName, versions) {
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: './dist/index.mjs',
77
- types: './dist/index.d.ts',
77
+ main: 'dist/index.js',
78
+ types: 'dist/index.d.ts',
78
79
  exports: {
79
- '.': './dist/index.mjs',
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: `>=${versions.donobuVersion}`,
97
- playwright: `>=${versions.playwrightVersion}`,
98
- '@playwright/test': `>=${versions.playwrightTestVersion}`,
91
+ donobu: `>=${donobuVersion}`,
99
92
  },
100
- peerDependenciesMeta: {
101
- playwright: {
102
- optional: false,
103
- },
104
- '@playwright/test': {
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
- dependencies: {
109
- zod: '^3.25.76',
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(tsConfig, null, 2)
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: ['dist/index.js'],
118
+ entryPoints: ['src/index.ts'],
145
119
  outfile: 'dist/index.mjs',
146
120
  bundle: true,
147
121
  format: 'esm',
148
122
  platform: 'node',
149
- external: ['donobu', 'playwright'],
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: ${parameters.webpageTitle}\`,
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 with tools for browser automation.
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 see your tools
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[2];
367
+ const { pluginName, pluginType } = parseArgs(process.argv);
227
368
 
228
369
  if (!pluginName) {
229
- console.error('Usage: create-donobu-plugin <plugin-name>');
370
+ console.error(
371
+ 'Usage: create-donobu-plugin <plugin-name> [--type <tool|persistence>]',
372
+ );
230
373
  console.error('');
231
- console.error('Example:');
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
- "name": "create-donobu-plugin",
3
- "version": "1.3.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
- "dependencies": {
16
- "donobu": "^3.4.0"
17
- },
18
- "keywords": [
19
- "donobu",
20
- "plugin",
21
- "create"
22
- ],
23
- "license": "UNLICENSED"
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
  }