create-donobu-plugin 1.2.1 → 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 +173 -65
- package/index.js +295 -193
- package/package.json +31 -22
package/README.md
CHANGED
|
@@ -1,99 +1,191 @@
|
|
|
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
|
|
40
|
-
|
|
|
41
|
-
| `package.json`
|
|
42
|
-
| `tsconfig.json`
|
|
43
|
-
| `esbuild.config.mjs`
|
|
44
|
-
| `src/index.ts`
|
|
45
|
-
| `
|
|
46
|
-
|
|
47
|
-
|
|
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. |
|
|
65
|
+
|
|
66
|
+
## The deps pattern
|
|
67
|
+
|
|
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.
|
|
69
|
+
|
|
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.
|
|
48
90
|
|
|
49
|
-
|
|
91
|
+
Feel free to add your own npm dependencies; esbuild bundles everything except the packages listed in `external`.
|
|
50
92
|
|
|
51
|
-
## Authoring
|
|
93
|
+
## Authoring tool plugins
|
|
52
94
|
|
|
53
|
-
Every plugin exports `loadCustomTools(
|
|
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()`:
|
|
54
96
|
|
|
55
97
|
```ts
|
|
56
|
-
|
|
98
|
+
import type { Tool, PluginDependencies } from 'donobu';
|
|
57
99
|
import { z } from 'zod/v4';
|
|
58
|
-
import { createTool } from './createTool.js';
|
|
59
|
-
import type { PluginDependencies } from './PluginDependencies.js';
|
|
60
100
|
|
|
61
|
-
export async function loadCustomTools(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
export async function loadCustomTools(
|
|
102
|
+
deps: PluginDependencies,
|
|
103
|
+
): Promise<Tool<any, any>[]> {
|
|
104
|
+
return [
|
|
105
|
+
deps.donobu.createTool(deps, {
|
|
106
|
+
name: 'judgeWebpageTitle',
|
|
107
|
+
description: 'Judge the quality of a title to a webpage.',
|
|
108
|
+
schema: z.object({ webpageTitle: z.string() }),
|
|
109
|
+
requiresGpt: true,
|
|
110
|
+
call: async (context, parameters) => {
|
|
111
|
+
const resp = await context.gptClient!.getMessage([
|
|
112
|
+
{
|
|
113
|
+
type: 'user',
|
|
114
|
+
items: [
|
|
115
|
+
{
|
|
116
|
+
type: 'text',
|
|
117
|
+
text: `Judge the quality of this webpage title on a scale of 1 (worst) to 10 (best) and explain why.
|
|
118
|
+
Title: ${parameters.webpageTitle}`,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
return {
|
|
124
|
+
isSuccessful: true,
|
|
125
|
+
forLlm: resp.text,
|
|
126
|
+
metadata: null,
|
|
127
|
+
};
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
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
|
+
}
|
|
81
145
|
|
|
82
|
-
return
|
|
146
|
+
return new MyTool();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function loadCustomTools(deps: PluginDependencies) {
|
|
150
|
+
return [createMyTool(deps)];
|
|
83
151
|
}
|
|
84
152
|
```
|
|
85
153
|
|
|
86
|
-
|
|
154
|
+
## Authoring persistence plugins
|
|
87
155
|
|
|
88
|
-
|
|
89
|
-
- Use `callFromGpt` if the GPT path needs different behavior; otherwise the helper routes GPT calls through `call`.
|
|
90
|
-
- Request `requiresGpt: true` only when the tool cannot run without LLM assistance.
|
|
156
|
+
Persistence plugins export `loadPersistencePlugin(deps)`. The returned `key` must appear in the `PERSISTENCE_PRIORITY` environment variable for Donobu to use it.
|
|
91
157
|
|
|
92
|
-
|
|
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
|
+
```
|
|
93
187
|
|
|
94
|
-
|
|
95
|
-
- `dependencies.playwright` points to the same Playwright build Donobu uses, so your plugin and the core runtime stay aligned.
|
|
96
|
-
- Feel free to add your own dependencies; `esbuild` bundles everything except the packages listed in `external`.
|
|
188
|
+
If your persistence classes need SDK utilities like `appLogger` or `FlowNotFoundException`, accept `deps.donobu` as a constructor parameter rather than importing them directly.
|
|
97
189
|
|
|
98
190
|
## Build and install
|
|
99
191
|
|
|
@@ -102,7 +194,24 @@ npm run build
|
|
|
102
194
|
npm exec install-donobu-plugin
|
|
103
195
|
```
|
|
104
196
|
|
|
105
|
-
`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.
|
|
106
215
|
|
|
107
216
|
## Recommended development loop
|
|
108
217
|
|
|
@@ -111,10 +220,9 @@ npm exec install-donobu-plugin
|
|
|
111
220
|
3. Restart Donobu Studio or the backend process
|
|
112
221
|
4. Trigger your tool from the UI or API to verify behavior
|
|
113
222
|
|
|
114
|
-
Because the default build script runs `npm install`, consider splitting the script into `build` and `bundle` variants if you need faster inner-loop iterations.
|
|
115
|
-
|
|
116
223
|
## Troubleshooting
|
|
117
224
|
|
|
118
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.
|
|
119
|
-
- **
|
|
120
|
-
- **
|
|
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
|
`;
|
|
@@ -154,150 +133,273 @@ await build({
|
|
|
154
133
|
await writeFile(join(pluginDir, 'esbuild.config.mjs'), esbuildConfig);
|
|
155
134
|
|
|
156
135
|
// Create src/index.ts
|
|
157
|
-
const indexTs =
|
|
158
|
-
|
|
136
|
+
const indexTs = `// Note that non-type imports from 'donobu' should be extracted from the given
|
|
137
|
+
// 'deps.donobu' parameter in 'loadCustomTools'.
|
|
138
|
+
import type { Tool, PluginDependencies } from 'donobu';
|
|
139
|
+
import { z } from 'zod/v4';
|
|
159
140
|
|
|
160
141
|
export async function loadCustomTools(
|
|
161
|
-
|
|
142
|
+
deps: PluginDependencies
|
|
162
143
|
): Promise<Tool<any, any>[]> {
|
|
163
144
|
// Register your custom tools here.
|
|
164
|
-
return [
|
|
165
|
-
|
|
166
|
-
|
|
145
|
+
return [
|
|
146
|
+
// Here is a small example tool for reference.
|
|
147
|
+
deps.donobu.createTool(deps, {
|
|
148
|
+
// The tool name, description, and schema are shared with the Donobu agent
|
|
149
|
+
// to help it decide when, where, and how this tool will be called.
|
|
150
|
+
name: 'judgeWebpageTitle',
|
|
151
|
+
description: 'Judge the quality of a title to a webpage.',
|
|
152
|
+
schema: z.object({ webpageTitle: z.string() }),
|
|
153
|
+
// This example tool uses a GPT/LLM so we call it out here.
|
|
154
|
+
requiresGpt: true,
|
|
155
|
+
call: async (context, parameters) => {
|
|
156
|
+
// 'requiredGpt' is true, so we are guaranteed a valid gptClient handle.
|
|
157
|
+
const resp = await context.gptClient!.getMessage([
|
|
158
|
+
{
|
|
159
|
+
type: 'user',
|
|
160
|
+
items: [
|
|
161
|
+
{
|
|
162
|
+
type: 'text',
|
|
163
|
+
text: \`Judge the quality of this webpage title on a scale of 1 (worst) to 10 (best) and explain why.
|
|
164
|
+
Title: \${parameters.webpageTitle}\`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
return {
|
|
170
|
+
// Returning false or throwing an exception will signal tool call failure.
|
|
171
|
+
isSuccessful: true,
|
|
172
|
+
// This is what is shared back with the Donobu agent.
|
|
173
|
+
forLlm: resp.text,
|
|
174
|
+
// Metadata is persisted with the tool call result, but is not shared
|
|
175
|
+
// with the Donobu agent, so this can be used to store large complex data.
|
|
176
|
+
metadata: null,
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
];
|
|
181
|
+
}`;
|
|
167
182
|
|
|
168
183
|
await writeFile(join(pluginDir, 'src', 'index.ts'), indexTs);
|
|
169
184
|
|
|
170
|
-
// Create
|
|
171
|
-
const
|
|
172
|
-
import type { ToolCallContext, ToolCallResult, Tool } from "donobu";
|
|
173
|
-
import { PluginDependencies } from "./PluginDependencies.js";
|
|
185
|
+
// Create a basic README
|
|
186
|
+
const readme = `# ${pluginName}
|
|
174
187
|
|
|
175
|
-
|
|
176
|
-
* Factory function to create a Donobu Tool instance.
|
|
177
|
-
*/
|
|
178
|
-
export function createTool<
|
|
179
|
-
CallSchema extends z.ZodObject<any>,
|
|
180
|
-
CallFromGptSchema extends z.ZodObject<any> = CallSchema,
|
|
181
|
-
>(
|
|
182
|
-
dependencies: PluginDependencies,
|
|
183
|
-
options: CreateToolOptions<CallSchema, CallFromGptSchema>
|
|
184
|
-
): Tool<CallSchema, CallFromGptSchema> {
|
|
185
|
-
class AnonymousTool extends dependencies.donobu.Tool<
|
|
186
|
-
CallSchema,
|
|
187
|
-
CallFromGptSchema
|
|
188
|
-
> {
|
|
189
|
-
public constructor() {
|
|
190
|
-
super(
|
|
191
|
-
options.name,
|
|
192
|
-
options.description,
|
|
193
|
-
options.schema,
|
|
194
|
-
(options.gptSchema ?? options.schema) as CallFromGptSchema,
|
|
195
|
-
options.requiresGpt
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
public override async call(
|
|
199
|
-
ctx: ToolCallContext,
|
|
200
|
-
params: z.infer<CallSchema>
|
|
201
|
-
): Promise<ToolCallResult> {
|
|
202
|
-
return options.call(ctx, params);
|
|
203
|
-
}
|
|
204
|
-
public override async callFromGpt(
|
|
205
|
-
ctx: ToolCallContext,
|
|
206
|
-
params: z.infer<CallFromGptSchema>
|
|
207
|
-
): Promise<ToolCallResult> {
|
|
208
|
-
return options.callFromGpt
|
|
209
|
-
? options.callFromGpt(ctx, params)
|
|
210
|
-
: this.call(ctx, params);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
188
|
+
A custom Donobu tool plugin for browser automation.
|
|
213
189
|
|
|
214
|
-
|
|
215
|
-
}
|
|
190
|
+
## Development
|
|
216
191
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
* @param inputSchema This is the JSON-schema for the tool when it is invoked by a non-LLM.
|
|
225
|
-
* @param inputSchemaForGpt This is the JSON-schema that will be shared with the LLM when making
|
|
226
|
-
* requests during autonomous flows.
|
|
227
|
-
* @param requiresGpt Set to true if this tool requires the usage of a GPT.
|
|
228
|
-
* @param call This is the function that will be invoked when the tool is called by a non-LLM.
|
|
229
|
-
* @param callFromGpt This is the function that will be invoked when the tool is called by an LLM.
|
|
230
|
-
*/
|
|
231
|
-
name: string;
|
|
232
|
-
description: string;
|
|
233
|
-
requiresGpt: boolean;
|
|
234
|
-
schema: CallSchema;
|
|
235
|
-
gptSchema?: CallFromGptSchema;
|
|
236
|
-
call(
|
|
237
|
-
ctx: ToolCallContext,
|
|
238
|
-
params: z.infer<CallSchema>
|
|
239
|
-
): Promise<ToolCallResult> | ToolCallResult;
|
|
240
|
-
callFromGpt?(
|
|
241
|
-
ctx: ToolCallContext,
|
|
242
|
-
params: z.infer<CallFromGptSchema>
|
|
243
|
-
): Promise<ToolCallResult> | ToolCallResult;
|
|
244
|
-
}
|
|
192
|
+
1. Edit \`src/index.ts\` to add your custom tools
|
|
193
|
+
2. Build and install the plugin:
|
|
194
|
+
\`\`\`bash
|
|
195
|
+
npm run build
|
|
196
|
+
npm exec install-donobu-plugin
|
|
197
|
+
\`\`\`
|
|
198
|
+
3. Restart Donobu to load your plugin
|
|
245
199
|
`;
|
|
246
200
|
|
|
247
|
-
await writeFile(join(pluginDir, '
|
|
248
|
-
|
|
249
|
-
// Create src/PluginDependencies.ts
|
|
250
|
-
const pluginDepsTs = `export interface PluginDependencies {
|
|
251
|
-
donobu: typeof import("donobu");
|
|
252
|
-
playwright: typeof import("playwright");
|
|
201
|
+
await writeFile(join(pluginDir, 'README.md'), readme);
|
|
253
202
|
}
|
|
254
|
-
|
|
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
|
+
};
|
|
255
246
|
|
|
256
247
|
await writeFile(
|
|
257
|
-
join(pluginDir, '
|
|
258
|
-
|
|
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),
|
|
259
255
|
);
|
|
260
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
|
+
|
|
261
325
|
// Create a basic README
|
|
262
326
|
const readme = `# ${pluginName}
|
|
263
327
|
|
|
264
|
-
A custom Donobu plugin
|
|
328
|
+
A custom Donobu persistence plugin.
|
|
265
329
|
|
|
266
330
|
## Development
|
|
267
331
|
|
|
268
|
-
1. Edit \`src/index.ts\` to
|
|
332
|
+
1. Edit \`src/index.ts\` to implement your persistence plugin
|
|
269
333
|
2. Build and install the plugin:
|
|
270
334
|
\`\`\`bash
|
|
271
335
|
npm run build
|
|
272
336
|
npm exec install-donobu-plugin
|
|
273
337
|
\`\`\`
|
|
274
|
-
3.
|
|
338
|
+
3. Set the \`PERSISTENCE_PRIORITY\` environment variable to include your plugin key
|
|
339
|
+
4. Restart Donobu to load your plugin
|
|
275
340
|
`;
|
|
276
341
|
|
|
277
342
|
await writeFile(join(pluginDir, 'README.md'), readme);
|
|
278
343
|
}
|
|
279
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
|
+
|
|
280
366
|
async function main() {
|
|
281
|
-
const pluginName = process.argv
|
|
367
|
+
const { pluginName, pluginType } = parseArgs(process.argv);
|
|
282
368
|
|
|
283
369
|
if (!pluginName) {
|
|
284
|
-
console.error(
|
|
370
|
+
console.error(
|
|
371
|
+
'Usage: create-donobu-plugin <plugin-name> [--type <tool|persistence>]',
|
|
372
|
+
);
|
|
285
373
|
console.error('');
|
|
286
|
-
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:');
|
|
287
380
|
console.error(' npm create donobu-plugin my-awesome-plugin');
|
|
381
|
+
console.error(' npm create donobu-plugin my-storage --type persistence');
|
|
288
382
|
process.exit(1);
|
|
289
383
|
}
|
|
290
384
|
|
|
291
385
|
// Validate plugin name
|
|
292
386
|
if (!/^[a-z0-9-_]+$/.test(pluginName)) {
|
|
293
387
|
console.error(
|
|
294
|
-
'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(', ')}`,
|
|
295
397
|
);
|
|
296
398
|
process.exit(1);
|
|
297
399
|
}
|
|
298
400
|
|
|
299
401
|
try {
|
|
300
|
-
await createPluginStructure(pluginName);
|
|
402
|
+
await createPluginStructure(pluginName, pluginType);
|
|
301
403
|
} catch (error) {
|
|
302
404
|
console.error('Failed to create plugin:', error.message);
|
|
303
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
|
}
|