@openacp/cli 2026.331.1 → 2026.331.2
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 +2 -1
- package/dist/cli.js +24932 -270
- package/dist/cli.js.map +1 -1
- package/dist/data/registry-snapshot.json +1 -1
- package/dist/index.js +17626 -409
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/adapter-ELG3VRZ3.js +0 -14
- package/dist/adapter-ELG3VRZ3.js.map +0 -1
- package/dist/agent-catalog-UYD26QDK.js +0 -10
- package/dist/agent-catalog-UYD26QDK.js.map +0 -1
- package/dist/agent-dependencies-ED2ZTUHG.js +0 -23
- package/dist/agent-dependencies-ED2ZTUHG.js.map +0 -1
- package/dist/agent-registry-YOGP656W.js +0 -8
- package/dist/agent-registry-YOGP656W.js.map +0 -1
- package/dist/agent-store-5UHZH2XI.js +0 -8
- package/dist/agent-store-5UHZH2XI.js.map +0 -1
- package/dist/api-client-PEMHYL5U.js +0 -13
- package/dist/api-client-PEMHYL5U.js.map +0 -1
- package/dist/api-server-DATG2KBR.js +0 -10
- package/dist/api-server-DATG2KBR.js.map +0 -1
- package/dist/api-server-L5Z7XACW.js +0 -7
- package/dist/api-server-L5Z7XACW.js.map +0 -1
- package/dist/autostart-CUPZMKKC.js +0 -22
- package/dist/autostart-CUPZMKKC.js.map +0 -1
- package/dist/chunk-23SRIVG4.js +0 -50
- package/dist/chunk-23SRIVG4.js.map +0 -1
- package/dist/chunk-2KT6TROD.js +0 -129
- package/dist/chunk-2KT6TROD.js.map +0 -1
- package/dist/chunk-2R5XM3ES.js +0 -154
- package/dist/chunk-2R5XM3ES.js.map +0 -1
- package/dist/chunk-3EWTPOF7.js +0 -51
- package/dist/chunk-3EWTPOF7.js.map +0 -1
- package/dist/chunk-566W6INH.js +0 -83
- package/dist/chunk-566W6INH.js.map +0 -1
- package/dist/chunk-5WGVYX3C.js +0 -55
- package/dist/chunk-5WGVYX3C.js.map +0 -1
- package/dist/chunk-7GXEMMEV.js +0 -44
- package/dist/chunk-7GXEMMEV.js.map +0 -1
- package/dist/chunk-7U6IZIJP.js +0 -186
- package/dist/chunk-7U6IZIJP.js.map +0 -1
- package/dist/chunk-7YIKTRSM.js +0 -105
- package/dist/chunk-7YIKTRSM.js.map +0 -1
- package/dist/chunk-7ZCQF6QM.js +0 -27
- package/dist/chunk-7ZCQF6QM.js.map +0 -1
- package/dist/chunk-AFKX424Q.js +0 -92
- package/dist/chunk-AFKX424Q.js.map +0 -1
- package/dist/chunk-BYCJQPMN.js +0 -543
- package/dist/chunk-BYCJQPMN.js.map +0 -1
- package/dist/chunk-CDAUYTVP.js +0 -41
- package/dist/chunk-CDAUYTVP.js.map +0 -1
- package/dist/chunk-EWVXSTQK.js +0 -6544
- package/dist/chunk-EWVXSTQK.js.map +0 -1
- package/dist/chunk-FNRSWA2K.js +0 -1
- package/dist/chunk-FNRSWA2K.js.map +0 -1
- package/dist/chunk-FPKQYCQS.js +0 -776
- package/dist/chunk-FPKQYCQS.js.map +0 -1
- package/dist/chunk-IZ5UEZF7.js +0 -138
- package/dist/chunk-IZ5UEZF7.js.map +0 -1
- package/dist/chunk-K6UY5M75.js +0 -653
- package/dist/chunk-K6UY5M75.js.map +0 -1
- package/dist/chunk-KGAQW6F4.js +0 -106
- package/dist/chunk-KGAQW6F4.js.map +0 -1
- package/dist/chunk-LGFWH3AE.js +0 -26
- package/dist/chunk-LGFWH3AE.js.map +0 -1
- package/dist/chunk-LRV56K2M.js +0 -4106
- package/dist/chunk-LRV56K2M.js.map +0 -1
- package/dist/chunk-MDJHCCFS.js +0 -485
- package/dist/chunk-MDJHCCFS.js.map +0 -1
- package/dist/chunk-MLF4W5R6.js +0 -101
- package/dist/chunk-MLF4W5R6.js.map +0 -1
- package/dist/chunk-NHD5XDD2.js +0 -686
- package/dist/chunk-NHD5XDD2.js.map +0 -1
- package/dist/chunk-NJX75BLK.js +0 -259
- package/dist/chunk-NJX75BLK.js.map +0 -1
- package/dist/chunk-NOEAJNTK.js +0 -156
- package/dist/chunk-NOEAJNTK.js.map +0 -1
- package/dist/chunk-ON7HB5O7.js +0 -58
- package/dist/chunk-ON7HB5O7.js.map +0 -1
- package/dist/chunk-OSBZXY2W.js +0 -126
- package/dist/chunk-OSBZXY2W.js.map +0 -1
- package/dist/chunk-OYSAN7UX.js +0 -15
- package/dist/chunk-OYSAN7UX.js.map +0 -1
- package/dist/chunk-P3HHJANC.js +0 -209
- package/dist/chunk-P3HHJANC.js.map +0 -1
- package/dist/chunk-R2YLDQLI.js +0 -1115
- package/dist/chunk-R2YLDQLI.js.map +0 -1
- package/dist/chunk-R6KZYF7D.js +0 -231
- package/dist/chunk-R6KZYF7D.js.map +0 -1
- package/dist/chunk-S64CB6J3.js +0 -98
- package/dist/chunk-S64CB6J3.js.map +0 -1
- package/dist/chunk-SSLVNCEA.js +0 -236
- package/dist/chunk-SSLVNCEA.js.map +0 -1
- package/dist/chunk-TGP34LQN.js +0 -681
- package/dist/chunk-TGP34LQN.js.map +0 -1
- package/dist/chunk-VUSCVRJL.js +0 -229
- package/dist/chunk-VUSCVRJL.js.map +0 -1
- package/dist/chunk-W26AUH5B.js +0 -61
- package/dist/chunk-W26AUH5B.js.map +0 -1
- package/dist/chunk-WQCJTU2C.js +0 -84
- package/dist/chunk-WQCJTU2C.js.map +0 -1
- package/dist/chunk-XRJUS6FE.js +0 -53
- package/dist/chunk-XRJUS6FE.js.map +0 -1
- package/dist/chunk-YZCKSNRN.js +0 -453
- package/dist/chunk-YZCKSNRN.js.map +0 -1
- package/dist/chunk-ZIRH6QWW.js +0 -69
- package/dist/chunk-ZIRH6QWW.js.map +0 -1
- package/dist/chunk-ZSLHHQPQ.js +0 -282
- package/dist/chunk-ZSLHHQPQ.js.map +0 -1
- package/dist/config-X4UP7H6R.js +0 -13
- package/dist/config-X4UP7H6R.js.map +0 -1
- package/dist/config-editor-7BENRVG5.js +0 -11
- package/dist/config-editor-7BENRVG5.js.map +0 -1
- package/dist/config-registry-M3FFWEVM.js +0 -18
- package/dist/config-registry-M3FFWEVM.js.map +0 -1
- package/dist/context-FVGCU5TI.js +0 -9
- package/dist/context-FVGCU5TI.js.map +0 -1
- package/dist/core-plugins-JSY2I44L.js +0 -25
- package/dist/core-plugins-JSY2I44L.js.map +0 -1
- package/dist/daemon-UOSRDEXW.js +0 -34
- package/dist/daemon-UOSRDEXW.js.map +0 -1
- package/dist/dev-loader-7P3HZCIA.js +0 -37
- package/dist/dev-loader-7P3HZCIA.js.map +0 -1
- package/dist/doctor-6DLACBR4.js +0 -10
- package/dist/doctor-6DLACBR4.js.map +0 -1
- package/dist/file-service-FQQYME7M.js +0 -8
- package/dist/file-service-FQQYME7M.js.map +0 -1
- package/dist/install-cloudflared-LNS5L5FR.js +0 -33
- package/dist/install-cloudflared-LNS5L5FR.js.map +0 -1
- package/dist/install-context-KZO5FR4D.js +0 -78
- package/dist/install-context-KZO5FR4D.js.map +0 -1
- package/dist/install-jq-SN4IA5K4.js +0 -31
- package/dist/install-jq-SN4IA5K4.js.map +0 -1
- package/dist/instance-context-FLCE7VZ4.js +0 -13
- package/dist/instance-context-FLCE7VZ4.js.map +0 -1
- package/dist/instance-registry-SW5FWKHO.js +0 -7
- package/dist/instance-registry-SW5FWKHO.js.map +0 -1
- package/dist/integrate-JIEZYDOR.js +0 -371
- package/dist/integrate-JIEZYDOR.js.map +0 -1
- package/dist/log-YZ243M5G.js +0 -25
- package/dist/log-YZ243M5G.js.map +0 -1
- package/dist/main-D7M2AKRM.js +0 -697
- package/dist/main-D7M2AKRM.js.map +0 -1
- package/dist/menu-ALFN37IR.js +0 -15
- package/dist/menu-ALFN37IR.js.map +0 -1
- package/dist/notifications-MO23S7S3.js +0 -8
- package/dist/notifications-MO23S7S3.js.map +0 -1
- package/dist/plugin-create-HFKS23JY.js +0 -968
- package/dist/plugin-create-HFKS23JY.js.map +0 -1
- package/dist/plugin-installer-VSTYZSXC.js +0 -9
- package/dist/plugin-installer-VSTYZSXC.js.map +0 -1
- package/dist/plugin-registry-6J3YSFHF.js +0 -7
- package/dist/plugin-registry-6J3YSFHF.js.map +0 -1
- package/dist/plugin-search-MGKAL5JM.js +0 -39
- package/dist/plugin-search-MGKAL5JM.js.map +0 -1
- package/dist/post-upgrade-F4YPMTUT.js +0 -79
- package/dist/post-upgrade-F4YPMTUT.js.map +0 -1
- package/dist/read-text-file-DJBTITIB.js +0 -7
- package/dist/read-text-file-DJBTITIB.js.map +0 -1
- package/dist/registry-client-GTBWLXYU.js +0 -7
- package/dist/registry-client-GTBWLXYU.js.map +0 -1
- package/dist/security-O4XGN2CM.js +0 -8
- package/dist/security-O4XGN2CM.js.map +0 -1
- package/dist/settings-manager-B4UN2LAC.js +0 -7
- package/dist/settings-manager-B4UN2LAC.js.map +0 -1
- package/dist/setup-44WLBIOT.js +0 -989
- package/dist/setup-44WLBIOT.js.map +0 -1
- package/dist/speech-GHTSWDAN.js +0 -9
- package/dist/speech-GHTSWDAN.js.map +0 -1
- package/dist/suggest-RST5VOHB.js +0 -36
- package/dist/suggest-RST5VOHB.js.map +0 -1
- package/dist/telegram-D7ASLVEB.js +0 -7
- package/dist/telegram-D7ASLVEB.js.map +0 -1
- package/dist/tunnel-ALJDPFDQ.js +0 -10
- package/dist/tunnel-ALJDPFDQ.js.map +0 -1
- package/dist/tunnel-service-TBAHDXMF.js +0 -755
- package/dist/tunnel-service-TBAHDXMF.js.map +0 -1
- package/dist/validators-GITLOFXC.js +0 -11
- package/dist/validators-GITLOFXC.js.map +0 -1
- package/dist/version-AXXV6IV2.js +0 -15
- package/dist/version-AXXV6IV2.js.map +0 -1
|
@@ -1,968 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getCurrentVersion
|
|
3
|
-
} from "./chunk-S64CB6J3.js";
|
|
4
|
-
|
|
5
|
-
// src/cli/commands/plugin-create.ts
|
|
6
|
-
import * as p from "@clack/prompts";
|
|
7
|
-
import fs from "fs";
|
|
8
|
-
import path from "path";
|
|
9
|
-
|
|
10
|
-
// src/cli/plugin-template/package-json.ts
|
|
11
|
-
function generatePackageJson(params) {
|
|
12
|
-
const packageJson = {
|
|
13
|
-
name: params.pluginName,
|
|
14
|
-
version: "0.1.0",
|
|
15
|
-
description: params.description || "",
|
|
16
|
-
type: "module",
|
|
17
|
-
main: "dist/index.js",
|
|
18
|
-
types: "dist/index.d.ts",
|
|
19
|
-
scripts: {
|
|
20
|
-
build: "tsc",
|
|
21
|
-
dev: "tsc --watch",
|
|
22
|
-
test: "vitest",
|
|
23
|
-
prepublishOnly: "npm run build"
|
|
24
|
-
},
|
|
25
|
-
author: params.author || "",
|
|
26
|
-
license: params.license,
|
|
27
|
-
keywords: ["openacp", "openacp-plugin"],
|
|
28
|
-
engines: {
|
|
29
|
-
openacp: `>=${params.cliVersion}`
|
|
30
|
-
},
|
|
31
|
-
peerDependencies: {
|
|
32
|
-
"@openacp/cli": `>=${params.cliVersion}`
|
|
33
|
-
},
|
|
34
|
-
devDependencies: {
|
|
35
|
-
"@openacp/plugin-sdk": params.cliVersion,
|
|
36
|
-
typescript: "^5.4.0",
|
|
37
|
-
vitest: "^3.0.0"
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
return JSON.stringify(packageJson, null, 2) + "\n";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// src/cli/plugin-template/tsconfig.ts
|
|
44
|
-
function generateTsconfig() {
|
|
45
|
-
const tsconfig = {
|
|
46
|
-
compilerOptions: {
|
|
47
|
-
target: "ES2022",
|
|
48
|
-
module: "NodeNext",
|
|
49
|
-
moduleResolution: "NodeNext",
|
|
50
|
-
declaration: true,
|
|
51
|
-
outDir: "dist",
|
|
52
|
-
rootDir: "src",
|
|
53
|
-
strict: true,
|
|
54
|
-
esModuleInterop: true,
|
|
55
|
-
skipLibCheck: true,
|
|
56
|
-
forceConsistentCasingInFileNames: true
|
|
57
|
-
},
|
|
58
|
-
include: ["src"],
|
|
59
|
-
exclude: ["node_modules", "dist", "src/**/__tests__"]
|
|
60
|
-
};
|
|
61
|
-
return JSON.stringify(tsconfig, null, 2) + "\n";
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// src/cli/plugin-template/dotfiles.ts
|
|
65
|
-
function generateGitignore() {
|
|
66
|
-
return ["node_modules/", "dist/", "*.tsbuildinfo", ".DS_Store", ""].join("\n");
|
|
67
|
-
}
|
|
68
|
-
function generateNpmignore() {
|
|
69
|
-
return ["src/", "tsconfig.json", ".editorconfig", ".gitignore", "*.test.ts", "__tests__/", ""].join("\n");
|
|
70
|
-
}
|
|
71
|
-
function generateEditorconfig() {
|
|
72
|
-
return [
|
|
73
|
-
"root = true",
|
|
74
|
-
"",
|
|
75
|
-
"[*]",
|
|
76
|
-
"indent_style = space",
|
|
77
|
-
"indent_size = 2",
|
|
78
|
-
"end_of_line = lf",
|
|
79
|
-
"charset = utf-8",
|
|
80
|
-
"trim_trailing_whitespace = true",
|
|
81
|
-
"insert_final_newline = true",
|
|
82
|
-
""
|
|
83
|
-
].join("\n");
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// src/cli/plugin-template/readme.ts
|
|
87
|
-
function generateReadme(params) {
|
|
88
|
-
return [
|
|
89
|
-
`# ${params.pluginName}`,
|
|
90
|
-
"",
|
|
91
|
-
params.description || "An OpenACP plugin.",
|
|
92
|
-
"",
|
|
93
|
-
"## Installation",
|
|
94
|
-
"",
|
|
95
|
-
"```bash",
|
|
96
|
-
`openacp plugin add ${params.pluginName}`,
|
|
97
|
-
"```",
|
|
98
|
-
"",
|
|
99
|
-
"## Development",
|
|
100
|
-
"",
|
|
101
|
-
"```bash",
|
|
102
|
-
"npm install",
|
|
103
|
-
"npm run build",
|
|
104
|
-
"npm test",
|
|
105
|
-
"",
|
|
106
|
-
"# Live development with hot-reload:",
|
|
107
|
-
`openacp dev .`,
|
|
108
|
-
"```",
|
|
109
|
-
"",
|
|
110
|
-
"## License",
|
|
111
|
-
"",
|
|
112
|
-
params.license,
|
|
113
|
-
""
|
|
114
|
-
].join("\n");
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/cli/plugin-template/plugin-source.ts
|
|
118
|
-
function generatePluginSource(params) {
|
|
119
|
-
const dirName = params.pluginName.replace(/^@[^/]+\//, "");
|
|
120
|
-
const escapedDescription = (params.description || "").replace(/'/g, "\\'");
|
|
121
|
-
return `import type { OpenACPPlugin, PluginContext, InstallContext, MigrateContext } from '@openacp/plugin-sdk'
|
|
122
|
-
|
|
123
|
-
const plugin: OpenACPPlugin = {
|
|
124
|
-
name: '${params.pluginName}',
|
|
125
|
-
version: '0.1.0',
|
|
126
|
-
description: '${escapedDescription}',
|
|
127
|
-
|
|
128
|
-
// Declare which permissions your plugin needs.
|
|
129
|
-
// Available: events:read, events:emit, services:register, services:use,
|
|
130
|
-
// middleware:register, commands:register, storage:read, storage:write, kernel:access
|
|
131
|
-
permissions: ['events:read', 'services:register'],
|
|
132
|
-
|
|
133
|
-
// Dependencies on other plugins (loaded before this one).
|
|
134
|
-
// pluginDependencies: { '@openacp/security': '>=1.0.0' },
|
|
135
|
-
|
|
136
|
-
// Optional dependencies (used if available, gracefully degrade if not).
|
|
137
|
-
// optionalPluginDependencies: { '@openacp/usage': '>=1.0.0' },
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Called during server startup in dependency order.
|
|
141
|
-
* Register services, middleware, commands, and event listeners here.
|
|
142
|
-
*/
|
|
143
|
-
async setup(ctx: PluginContext): Promise<void> {
|
|
144
|
-
ctx.log.info('Plugin setup started')
|
|
145
|
-
|
|
146
|
-
// Example: register a service
|
|
147
|
-
// ctx.registerService('my-service', myServiceImpl)
|
|
148
|
-
|
|
149
|
-
// Example: listen to events
|
|
150
|
-
// ctx.on('session:created', (event) => { ... })
|
|
151
|
-
|
|
152
|
-
// Example: register a slash command
|
|
153
|
-
// ctx.registerCommand({
|
|
154
|
-
// name: 'mycommand',
|
|
155
|
-
// description: 'Does something useful',
|
|
156
|
-
// category: 'plugin',
|
|
157
|
-
// async handler(args) {
|
|
158
|
-
// return { type: 'text', text: 'Hello from ${params.pluginName}!' }
|
|
159
|
-
// },
|
|
160
|
-
// })
|
|
161
|
-
|
|
162
|
-
ctx.log.info('Plugin setup complete')
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Called during server shutdown in reverse dependency order.
|
|
167
|
-
* Clean up resources, close connections, stop timers here.
|
|
168
|
-
* Has a 10-second timeout.
|
|
169
|
-
*/
|
|
170
|
-
async teardown(): Promise<void> {
|
|
171
|
-
// Clean up resources here
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Called when user runs \`openacp plugin add ${params.pluginName}\`.
|
|
176
|
-
* Use ctx.terminal for interactive prompts to gather configuration.
|
|
177
|
-
*/
|
|
178
|
-
async install(ctx: InstallContext): Promise<void> {
|
|
179
|
-
ctx.terminal.log.info('Installing ${params.pluginName}...')
|
|
180
|
-
|
|
181
|
-
// Example: prompt for configuration
|
|
182
|
-
// const apiKey = await ctx.terminal.text({
|
|
183
|
-
// message: 'Enter your API key',
|
|
184
|
-
// validate: (v) => v.length === 0 ? 'Required' : undefined,
|
|
185
|
-
// })
|
|
186
|
-
// await ctx.settings.set('apiKey', apiKey)
|
|
187
|
-
|
|
188
|
-
ctx.terminal.log.success('Installation complete!')
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Called when user runs \`openacp plugin configure ${params.pluginName}\`.
|
|
193
|
-
* Re-run configuration prompts to update settings.
|
|
194
|
-
*/
|
|
195
|
-
async configure(ctx: InstallContext): Promise<void> {
|
|
196
|
-
ctx.terminal.log.info('Configuring ${params.pluginName}...')
|
|
197
|
-
|
|
198
|
-
// Re-run configuration prompts, pre-filling with current values
|
|
199
|
-
// const current = await ctx.settings.getAll()
|
|
200
|
-
// ...
|
|
201
|
-
|
|
202
|
-
ctx.terminal.log.success('Configuration updated!')
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Called during boot when the plugin version has changed.
|
|
207
|
-
* Migrate settings from the old format to the new format.
|
|
208
|
-
*/
|
|
209
|
-
async migrate(ctx: MigrateContext, oldSettings: unknown, oldVersion: string): Promise<unknown> {
|
|
210
|
-
ctx.log.info(\`Migrating from v\${oldVersion}\`)
|
|
211
|
-
// Return the migrated settings object
|
|
212
|
-
return oldSettings
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Called when user runs \`openacp plugin remove ${params.pluginName}\`.
|
|
217
|
-
* Clean up any external resources. If opts.purge is true, delete all data.
|
|
218
|
-
*/
|
|
219
|
-
async uninstall(ctx: InstallContext, opts: { purge: boolean }): Promise<void> {
|
|
220
|
-
ctx.terminal.log.info('Uninstalling ${params.pluginName}...')
|
|
221
|
-
if (opts.purge) {
|
|
222
|
-
await ctx.settings.clear()
|
|
223
|
-
}
|
|
224
|
-
ctx.terminal.log.success('Uninstalled!')
|
|
225
|
-
},
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export default plugin
|
|
229
|
-
`;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// src/cli/plugin-template/plugin-test.ts
|
|
233
|
-
function generatePluginTest(params) {
|
|
234
|
-
return `import { describe, it, expect } from 'vitest'
|
|
235
|
-
import { createTestContext, createTestInstallContext } from '@openacp/plugin-sdk/testing'
|
|
236
|
-
import plugin from '../index.js'
|
|
237
|
-
|
|
238
|
-
describe('${params.pluginName}', () => {
|
|
239
|
-
it('has correct metadata', () => {
|
|
240
|
-
expect(plugin.name).toBe('${params.pluginName}')
|
|
241
|
-
expect(plugin.version).toBeDefined()
|
|
242
|
-
expect(plugin.setup).toBeInstanceOf(Function)
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('sets up without errors', async () => {
|
|
246
|
-
const ctx = createTestContext({
|
|
247
|
-
pluginName: '${params.pluginName}',
|
|
248
|
-
pluginConfig: { enabled: true },
|
|
249
|
-
permissions: plugin.permissions,
|
|
250
|
-
})
|
|
251
|
-
await expect(plugin.setup(ctx)).resolves.not.toThrow()
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
it('tears down without errors', async () => {
|
|
255
|
-
if (plugin.teardown) {
|
|
256
|
-
await expect(plugin.teardown()).resolves.not.toThrow()
|
|
257
|
-
}
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
it('installs without errors', async () => {
|
|
261
|
-
if (plugin.install) {
|
|
262
|
-
const ctx = createTestInstallContext({
|
|
263
|
-
pluginName: '${params.pluginName}',
|
|
264
|
-
terminalResponses: { password: [''], confirm: [true], select: ['apiKey'] },
|
|
265
|
-
})
|
|
266
|
-
await expect(plugin.install(ctx)).resolves.not.toThrow()
|
|
267
|
-
}
|
|
268
|
-
})
|
|
269
|
-
})
|
|
270
|
-
`;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// src/cli/plugin-template/claude-md.ts
|
|
274
|
-
function generateClaudeMd(params) {
|
|
275
|
-
return `# CLAUDE.md
|
|
276
|
-
|
|
277
|
-
This file provides context for AI coding agents (Claude, Cursor, etc.) working on this plugin.
|
|
278
|
-
|
|
279
|
-
## What is OpenACP?
|
|
280
|
-
|
|
281
|
-
OpenACP is an open-source platform that bridges AI coding agents (Claude Code, Codex, etc.) to messaging platforms (Telegram, Discord, Slack) and custom UIs via the Agent Client Protocol (ACP). It features a microkernel plugin architecture where all features \u2014 adapters, services, commands \u2014 are plugins.
|
|
282
|
-
|
|
283
|
-
- **Website & Docs**: https://openacp.gitbook.io/docs
|
|
284
|
-
- **GitHub**: https://github.com/Open-ACP/OpenACP
|
|
285
|
-
- **Plugin Registry**: https://github.com/Open-ACP/plugin-registry
|
|
286
|
-
|
|
287
|
-
Key documentation pages:
|
|
288
|
-
- [Getting Started](https://openacp.gitbook.io/docs/getting-started) \u2014 What is OpenACP, quickstart
|
|
289
|
-
- [Plugin Development](https://openacp.gitbook.io/docs/extending/building-plugins) \u2014 How to build plugins
|
|
290
|
-
- [Architecture](https://openacp.gitbook.io/docs/extending/architecture) \u2014 System design, plugin lifecycle
|
|
291
|
-
- [Dev Mode](https://openacp.gitbook.io/docs/extending/dev-mode) \u2014 Hot-reload development workflow
|
|
292
|
-
- [CLI Commands](https://openacp.gitbook.io/docs/api-reference/cli-commands) \u2014 Full CLI reference
|
|
293
|
-
- [Platform Setup](https://openacp.gitbook.io/docs/platform-setup) \u2014 Telegram, Discord, Slack guides
|
|
294
|
-
- [Configuration](https://openacp.gitbook.io/docs/self-hosting/configuration) \u2014 Config and settings reference
|
|
295
|
-
|
|
296
|
-
## Project Overview
|
|
297
|
-
|
|
298
|
-
This is an OpenACP plugin. Plugins extend OpenACP with new adapters, services, commands, and middleware.
|
|
299
|
-
|
|
300
|
-
- **Package**: ${params.pluginName}
|
|
301
|
-
- **SDK**: \`@openacp/plugin-sdk\` (types, base classes, testing utilities)
|
|
302
|
-
- **Entry point**: \`src/index.ts\` (default export of \`OpenACPPlugin\` object)
|
|
303
|
-
|
|
304
|
-
## Build & Run
|
|
305
|
-
|
|
306
|
-
\`\`\`bash
|
|
307
|
-
npm install # Install dependencies
|
|
308
|
-
npm run build # Compile TypeScript (tsc)
|
|
309
|
-
npm run dev # Watch mode (tsc --watch)
|
|
310
|
-
npm test # Run tests (vitest)
|
|
311
|
-
\`\`\`
|
|
312
|
-
|
|
313
|
-
### Development with hot-reload
|
|
314
|
-
|
|
315
|
-
\`\`\`bash
|
|
316
|
-
openacp dev . # Compiles, watches, and reloads plugin on changes
|
|
317
|
-
\`\`\`
|
|
318
|
-
|
|
319
|
-
## File Structure
|
|
320
|
-
|
|
321
|
-
\`\`\`
|
|
322
|
-
src/
|
|
323
|
-
index.ts \u2014 Plugin entry point (exports OpenACPPlugin)
|
|
324
|
-
__tests__/
|
|
325
|
-
index.test.ts \u2014 Tests using @openacp/plugin-sdk/testing
|
|
326
|
-
package.json \u2014 engines.openacp declares minimum CLI version
|
|
327
|
-
tsconfig.json \u2014 ES2022, NodeNext, strict mode
|
|
328
|
-
CLAUDE.md \u2014 This file (AI agent context)
|
|
329
|
-
PLUGIN_GUIDE.md \u2014 Human-readable developer guide
|
|
330
|
-
\`\`\`
|
|
331
|
-
|
|
332
|
-
## Architecture: How OpenACP Plugins Work
|
|
333
|
-
|
|
334
|
-
### Plugin Lifecycle
|
|
335
|
-
|
|
336
|
-
\`\`\`
|
|
337
|
-
install \u2500\u2500> [reboot] \u2500\u2500> migrate? \u2500\u2500> setup \u2500\u2500> [running] \u2500\u2500> teardown \u2500\u2500> uninstall
|
|
338
|
-
\`\`\`
|
|
339
|
-
|
|
340
|
-
| Hook | Trigger | Interactive? | Has Services? |
|
|
341
|
-
|------|---------|-------------|---------------|
|
|
342
|
-
| \`install(ctx)\` | \`openacp plugin add <name>\` | Yes | No |
|
|
343
|
-
| \`migrate(ctx, oldSettings, oldVersion)\` | Boot \u2014 stored version differs from plugin version | No | No |
|
|
344
|
-
| \`configure(ctx)\` | \`openacp plugin configure <name>\` | Yes | No |
|
|
345
|
-
| \`setup(ctx)\` | Every boot, after migrate | No | Yes |
|
|
346
|
-
| \`teardown()\` | Shutdown (10s timeout) | No | Yes |
|
|
347
|
-
| \`uninstall(ctx, opts)\` | \`openacp plugin remove <name>\` | Yes | No |
|
|
348
|
-
|
|
349
|
-
### OpenACPPlugin Interface
|
|
350
|
-
|
|
351
|
-
\`\`\`typescript
|
|
352
|
-
interface OpenACPPlugin {
|
|
353
|
-
name: string // unique identifier, e.g. '@myorg/my-plugin'
|
|
354
|
-
version: string // semver
|
|
355
|
-
description?: string
|
|
356
|
-
permissions?: PluginPermission[]
|
|
357
|
-
pluginDependencies?: Record<string, string> // name -> semver range
|
|
358
|
-
optionalPluginDependencies?: Record<string, string> // used if available
|
|
359
|
-
overrides?: string // replace a built-in plugin entirely
|
|
360
|
-
settingsSchema?: ZodSchema // Zod validation for settings
|
|
361
|
-
essential?: boolean // true = needs setup before system can run
|
|
362
|
-
|
|
363
|
-
setup(ctx: PluginContext): Promise<void>
|
|
364
|
-
teardown?(): Promise<void>
|
|
365
|
-
install?(ctx: InstallContext): Promise<void>
|
|
366
|
-
configure?(ctx: InstallContext): Promise<void>
|
|
367
|
-
migrate?(ctx: MigrateContext, oldSettings: unknown, oldVersion: string): Promise<unknown>
|
|
368
|
-
uninstall?(ctx: InstallContext, opts: { purge: boolean }): Promise<void>
|
|
369
|
-
}
|
|
370
|
-
\`\`\`
|
|
371
|
-
|
|
372
|
-
### PluginContext API (available in setup)
|
|
373
|
-
|
|
374
|
-
\`\`\`typescript
|
|
375
|
-
interface PluginContext {
|
|
376
|
-
pluginName: string
|
|
377
|
-
pluginConfig: Record<string, unknown> // from settings.json
|
|
378
|
-
|
|
379
|
-
// Events (requires 'events:read' / 'events:emit')
|
|
380
|
-
on(event: string, handler: (...args: unknown[]) => void): void
|
|
381
|
-
off(event: string, handler: (...args: unknown[]) => void): void
|
|
382
|
-
emit(event: string, payload: unknown): void
|
|
383
|
-
|
|
384
|
-
// Services (requires 'services:register' / 'services:use')
|
|
385
|
-
registerService<T>(name: string, implementation: T): void
|
|
386
|
-
getService<T>(name: string): T | undefined
|
|
387
|
-
|
|
388
|
-
// Middleware (requires 'middleware:register')
|
|
389
|
-
registerMiddleware<H extends MiddlewareHook>(hook: H, opts: MiddlewareOptions<MiddlewarePayloadMap[H]>): void
|
|
390
|
-
|
|
391
|
-
// Commands (requires 'commands:register')
|
|
392
|
-
registerCommand(def: CommandDef): void
|
|
393
|
-
|
|
394
|
-
// Storage (requires 'storage:read' / 'storage:write')
|
|
395
|
-
storage: PluginStorage // get, set, delete, list, getDataDir
|
|
396
|
-
|
|
397
|
-
// Messaging (requires 'services:use')
|
|
398
|
-
sendMessage(sessionId: string, content: OutgoingMessage): Promise<void>
|
|
399
|
-
|
|
400
|
-
// Kernel access (requires 'kernel:access')
|
|
401
|
-
sessions: SessionManager
|
|
402
|
-
config: ConfigManager
|
|
403
|
-
eventBus: EventBus
|
|
404
|
-
|
|
405
|
-
// Always available
|
|
406
|
-
log: Logger // trace, debug, info, warn, error, fatal, child
|
|
407
|
-
}
|
|
408
|
-
\`\`\`
|
|
409
|
-
|
|
410
|
-
### CommandDef and CommandResponse
|
|
411
|
-
|
|
412
|
-
\`\`\`typescript
|
|
413
|
-
interface CommandDef {
|
|
414
|
-
name: string // command name without slash
|
|
415
|
-
description: string // shown in /help
|
|
416
|
-
usage?: string // e.g. '<city>' or 'on|off'
|
|
417
|
-
category: 'system' | 'plugin'
|
|
418
|
-
handler(args: CommandArgs): Promise<CommandResponse | void>
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
interface CommandArgs {
|
|
422
|
-
raw: string // text after command name
|
|
423
|
-
sessionId: string | null
|
|
424
|
-
channelId: string // 'telegram', 'discord', 'slack'
|
|
425
|
-
userId: string
|
|
426
|
-
reply(content: string | CommandResponse): Promise<void>
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
type CommandResponse =
|
|
430
|
-
| { type: 'text'; text: string }
|
|
431
|
-
| { type: 'menu'; title: string; options: MenuOption[] }
|
|
432
|
-
| { type: 'list'; title: string; items: ListItem[] }
|
|
433
|
-
| { type: 'confirm'; question: string; onYes: string; onNo: string }
|
|
434
|
-
| { type: 'error'; message: string }
|
|
435
|
-
| { type: 'silent' }
|
|
436
|
-
\`\`\`
|
|
437
|
-
|
|
438
|
-
### Settings System
|
|
439
|
-
|
|
440
|
-
- \`settingsSchema\`: Zod schema for validation
|
|
441
|
-
- \`SettingsAPI\` (in InstallContext): get, set, getAll, setAll, delete, clear, has
|
|
442
|
-
- Settings stored at \`~/.openacp/plugins/@scope/name/settings.json\`
|
|
443
|
-
- \`PluginStorage\` (in PluginContext): key-value store at \`~/.openacp/plugins/data/@scope/name/kv.json\`
|
|
444
|
-
- \`storage.getDataDir()\`: returns path for large files, databases, caches
|
|
445
|
-
|
|
446
|
-
### InstallContext (for install/configure/uninstall)
|
|
447
|
-
|
|
448
|
-
\`\`\`typescript
|
|
449
|
-
interface InstallContext {
|
|
450
|
-
pluginName: string
|
|
451
|
-
terminal: TerminalIO // text, select, confirm, password, multiselect, log, spinner, note
|
|
452
|
-
settings: SettingsAPI
|
|
453
|
-
legacyConfig?: Record<string, unknown>
|
|
454
|
-
dataDir: string
|
|
455
|
-
log: Logger
|
|
456
|
-
}
|
|
457
|
-
\`\`\`
|
|
458
|
-
|
|
459
|
-
### Service Interfaces (available via ctx.getService)
|
|
460
|
-
|
|
461
|
-
| Service name | Interface | Description |
|
|
462
|
-
|---|---|---|
|
|
463
|
-
| \`security\` | \`SecurityService\` | Access control, session limits, user roles |
|
|
464
|
-
| \`file-service\` | \`FileServiceInterface\` | File saving, resolving, format conversion |
|
|
465
|
-
| \`notifications\` | \`NotificationService\` | Send notifications to users |
|
|
466
|
-
| \`usage\` | \`UsageService\` | Token/cost tracking and budget checking |
|
|
467
|
-
| \`speech\` | \`SpeechServiceInterface\` | Text-to-speech and speech-to-text |
|
|
468
|
-
| \`tunnel\` | \`TunnelServiceInterface\` | Port tunneling and public URL management |
|
|
469
|
-
| \`context\` | \`ContextService\` | Context building for agent sessions |
|
|
470
|
-
|
|
471
|
-
## Plugin Permissions
|
|
472
|
-
|
|
473
|
-
Declare in \`permissions\` array. Only request what you need.
|
|
474
|
-
|
|
475
|
-
| Permission | Allows |
|
|
476
|
-
|---|---|
|
|
477
|
-
| \`events:read\` | \`ctx.on()\` \u2014 subscribe to events |
|
|
478
|
-
| \`events:emit\` | \`ctx.emit()\` \u2014 emit custom events (must prefix with plugin name) |
|
|
479
|
-
| \`services:register\` | \`ctx.registerService()\` \u2014 provide services to other plugins |
|
|
480
|
-
| \`services:use\` | \`ctx.getService()\`, \`ctx.sendMessage()\` \u2014 consume services |
|
|
481
|
-
| \`middleware:register\` | \`ctx.registerMiddleware()\` \u2014 intercept and modify flows |
|
|
482
|
-
| \`commands:register\` | \`ctx.registerCommand()\` \u2014 add chat commands |
|
|
483
|
-
| \`storage:read\` | \`ctx.storage.get()\`, \`ctx.storage.list()\` |
|
|
484
|
-
| \`storage:write\` | \`ctx.storage.set()\`, \`ctx.storage.delete()\` |
|
|
485
|
-
| \`kernel:access\` | \`ctx.sessions\`, \`ctx.config\`, \`ctx.eventBus\`, \`ctx.core\` |
|
|
486
|
-
|
|
487
|
-
Calling a method without the required permission throws \`PluginPermissionError\`.
|
|
488
|
-
|
|
489
|
-
## Middleware Hooks (20 total)
|
|
490
|
-
|
|
491
|
-
Register with \`ctx.registerMiddleware(hook, { priority?, handler })\`. Return \`null\` to block the flow, call \`next()\` to continue.
|
|
492
|
-
|
|
493
|
-
### Message flow
|
|
494
|
-
- \`message:incoming\` \u2014 incoming user message (channelId, threadId, userId, text, attachments)
|
|
495
|
-
- \`message:outgoing\` \u2014 outgoing message to user (sessionId, message)
|
|
496
|
-
|
|
497
|
-
### Agent flow
|
|
498
|
-
- \`agent:beforePrompt\` \u2014 before prompt is sent to agent (sessionId, text, attachments)
|
|
499
|
-
- \`agent:beforeEvent\` \u2014 before agent event is processed (sessionId, event)
|
|
500
|
-
- \`agent:afterEvent\` \u2014 after agent event, before delivery (sessionId, event, outgoingMessage)
|
|
501
|
-
|
|
502
|
-
### Turn lifecycle
|
|
503
|
-
- \`turn:start\` \u2014 agent turn begins (sessionId, promptText, promptNumber)
|
|
504
|
-
- \`turn:end\` \u2014 agent turn ends (sessionId, stopReason, durationMs)
|
|
505
|
-
|
|
506
|
-
### File system
|
|
507
|
-
- \`fs:beforeRead\` \u2014 before file read (sessionId, path, line, limit)
|
|
508
|
-
- \`fs:beforeWrite\` \u2014 before file write (sessionId, path, content)
|
|
509
|
-
|
|
510
|
-
### Terminal
|
|
511
|
-
- \`terminal:beforeCreate\` \u2014 before terminal process spawned (sessionId, command, args, env, cwd)
|
|
512
|
-
- \`terminal:afterExit\` \u2014 after terminal process exits (sessionId, terminalId, command, exitCode, durationMs)
|
|
513
|
-
|
|
514
|
-
### Permission
|
|
515
|
-
- \`permission:beforeRequest\` \u2014 before permission prompt (sessionId, request, autoResolve)
|
|
516
|
-
- \`permission:afterResolve\` \u2014 after permission resolved (sessionId, requestId, decision, userId, durationMs)
|
|
517
|
-
|
|
518
|
-
### Session
|
|
519
|
-
- \`session:beforeCreate\` \u2014 before session creation (agentName, workingDir, userId, channelId, threadId)
|
|
520
|
-
- \`session:afterDestroy\` \u2014 after session destroyed (sessionId, reason, durationMs, promptCount)
|
|
521
|
-
|
|
522
|
-
### Control
|
|
523
|
-
- \`mode:beforeChange\` \u2014 before mode change (sessionId, fromMode, toMode)
|
|
524
|
-
- \`config:beforeChange\` \u2014 before config change (sessionId, configId, oldValue, newValue)
|
|
525
|
-
- \`model:beforeChange\` \u2014 before model change (sessionId, fromModel, toModel)
|
|
526
|
-
- \`agent:beforeCancel\` \u2014 before agent cancellation (sessionId, reason)
|
|
527
|
-
- \`agent:beforeSwitch\` \u2014 **blocking** before agent switch (sessionId, fromAgent, toAgent). Return null/false to block.
|
|
528
|
-
- \`agent:afterSwitch\` \u2014 **fire-and-forget** after agent switch (sessionId, fromAgent, toAgent, resumed). Observational only.
|
|
529
|
-
|
|
530
|
-
## Plugin Events (subscribe with ctx.on)
|
|
531
|
-
|
|
532
|
-
### System
|
|
533
|
-
- \`kernel:booted\`, \`system:ready\`, \`system:shutdown\`, \`system:commands-ready\`
|
|
534
|
-
|
|
535
|
-
### Plugin lifecycle
|
|
536
|
-
- \`plugin:loaded\`, \`plugin:failed\`, \`plugin:disabled\`, \`plugin:unloaded\`
|
|
537
|
-
|
|
538
|
-
### Session
|
|
539
|
-
- \`session:created\`, \`session:ended\`, \`session:named\`, \`session:updated\`
|
|
540
|
-
|
|
541
|
-
### Agent
|
|
542
|
-
- \`agent:event\`, \`agent:prompt\`
|
|
543
|
-
|
|
544
|
-
### Permission
|
|
545
|
-
- \`permission:request\`, \`permission:resolved\`
|
|
546
|
-
|
|
547
|
-
## Testing
|
|
548
|
-
|
|
549
|
-
Use \`@openacp/plugin-sdk/testing\`:
|
|
550
|
-
|
|
551
|
-
\`\`\`typescript
|
|
552
|
-
import { createTestContext, createTestInstallContext, mockServices } from '@openacp/plugin-sdk/testing'
|
|
553
|
-
\`\`\`
|
|
554
|
-
|
|
555
|
-
### createTestContext(opts)
|
|
556
|
-
|
|
557
|
-
Creates a test \`PluginContext\`. All state is in-memory.
|
|
558
|
-
|
|
559
|
-
\`\`\`typescript
|
|
560
|
-
const ctx = createTestContext({
|
|
561
|
-
pluginName: '${params.pluginName}',
|
|
562
|
-
pluginConfig: { enabled: true },
|
|
563
|
-
permissions: plugin.permissions,
|
|
564
|
-
services: { security: mockServices.security() },
|
|
565
|
-
})
|
|
566
|
-
await plugin.setup(ctx)
|
|
567
|
-
expect(ctx.registeredCommands.has('mycommand')).toBe(true)
|
|
568
|
-
const response = await ctx.executeCommand('mycommand', { raw: 'test' })
|
|
569
|
-
\`\`\`
|
|
570
|
-
|
|
571
|
-
Inspection properties: \`registeredServices\`, \`registeredCommands\`, \`registeredMiddleware\`, \`emittedEvents\`, \`sentMessages\`, \`executeCommand()\`.
|
|
572
|
-
|
|
573
|
-
### createTestInstallContext(opts)
|
|
574
|
-
|
|
575
|
-
Creates a test \`InstallContext\`. Terminal prompts auto-answered from \`terminalResponses\`.
|
|
576
|
-
|
|
577
|
-
\`\`\`typescript
|
|
578
|
-
const ctx = createTestInstallContext({
|
|
579
|
-
pluginName: '${params.pluginName}',
|
|
580
|
-
terminalResponses: { password: ['sk-test-key'], select: ['en'] },
|
|
581
|
-
})
|
|
582
|
-
await plugin.install!(ctx)
|
|
583
|
-
expect(ctx.settingsData.get('apiKey')).toBe('sk-test-key')
|
|
584
|
-
\`\`\`
|
|
585
|
-
|
|
586
|
-
### mockServices
|
|
587
|
-
|
|
588
|
-
Factory functions for mock service implementations:
|
|
589
|
-
|
|
590
|
-
\`\`\`typescript
|
|
591
|
-
mockServices.security(overrides?) // checkAccess, checkSessionLimit, getUserRole
|
|
592
|
-
mockServices.fileService(overrides?) // saveFile, resolveFile, readTextFileWithRange
|
|
593
|
-
mockServices.notifications(overrides?) // notify, notifyAll
|
|
594
|
-
mockServices.usage(overrides?) // trackUsage, checkBudget, getSummary
|
|
595
|
-
mockServices.speech(overrides?) // textToSpeech, speechToText, register*
|
|
596
|
-
mockServices.tunnel(overrides?) // getPublicUrl, start, stop, getStore, fileUrl, diffUrl
|
|
597
|
-
mockServices.context(overrides?) // buildContext, registerProvider
|
|
598
|
-
\`\`\`
|
|
599
|
-
|
|
600
|
-
## Conventions
|
|
601
|
-
|
|
602
|
-
- **ESM-only**: \`"type": "module"\` in package.json
|
|
603
|
-
- **Import extensions**: All imports must use \`.js\` extension (e.g., \`import x from './util.js'\`)
|
|
604
|
-
- **TypeScript strict mode**: \`strict: true\` in tsconfig.json
|
|
605
|
-
- **Target**: ES2022, module NodeNext
|
|
606
|
-
- **Test framework**: Vitest
|
|
607
|
-
- **Test files**: \`src/**/__tests__/*.test.ts\`
|
|
608
|
-
|
|
609
|
-
## How to Add a Command
|
|
610
|
-
|
|
611
|
-
\`\`\`typescript
|
|
612
|
-
// In setup():
|
|
613
|
-
ctx.registerCommand({
|
|
614
|
-
name: 'mycommand',
|
|
615
|
-
description: 'Does something useful',
|
|
616
|
-
usage: '<arg>',
|
|
617
|
-
category: 'plugin',
|
|
618
|
-
async handler(args) {
|
|
619
|
-
const input = args.raw.trim()
|
|
620
|
-
if (!input) return { type: 'error', message: 'Usage: /mycommand <arg>' }
|
|
621
|
-
return { type: 'text', text: \\\`Result: \\\${input}\\\` }
|
|
622
|
-
},
|
|
623
|
-
})
|
|
624
|
-
\`\`\`
|
|
625
|
-
|
|
626
|
-
Requires \`commands:register\` permission. Available as \`/mycommand\` (if no conflict) and \`/pluginscope:mycommand\` (always).
|
|
627
|
-
|
|
628
|
-
## How to Add a Service
|
|
629
|
-
|
|
630
|
-
\`\`\`typescript
|
|
631
|
-
// In setup():
|
|
632
|
-
const myService = new MyService()
|
|
633
|
-
ctx.registerService('my-service', myService)
|
|
634
|
-
\`\`\`
|
|
635
|
-
|
|
636
|
-
Requires \`services:register\` permission. Other plugins consume with \`ctx.getService<MyService>('my-service')\`.
|
|
637
|
-
|
|
638
|
-
## How to Add Middleware
|
|
639
|
-
|
|
640
|
-
\`\`\`typescript
|
|
641
|
-
// In setup():
|
|
642
|
-
ctx.registerMiddleware('message:outgoing', {
|
|
643
|
-
priority: 50, // lower = earlier execution
|
|
644
|
-
handler: async (payload, next) => {
|
|
645
|
-
payload.message.text = modifyText(payload.message.text)
|
|
646
|
-
return next() // continue chain; return null to block
|
|
647
|
-
},
|
|
648
|
-
})
|
|
649
|
-
\`\`\`
|
|
650
|
-
|
|
651
|
-
Requires \`middleware:register\` permission.
|
|
652
|
-
|
|
653
|
-
## How Settings Work
|
|
654
|
-
|
|
655
|
-
1. Define \`settingsSchema\` (Zod) on the plugin object
|
|
656
|
-
2. In \`install()\`: use \`ctx.terminal\` for interactive prompts, save with \`ctx.settings.set()\`
|
|
657
|
-
3. In \`configure()\`: re-run prompts with current values pre-filled
|
|
658
|
-
4. In \`setup()\`: read settings from \`ctx.pluginConfig\`
|
|
659
|
-
5. In \`migrate()\`: transform old settings to new format on version change
|
|
660
|
-
|
|
661
|
-
## Version Compatibility
|
|
662
|
-
|
|
663
|
-
The \`engines.openacp\` field in package.json declares the minimum CLI version. OpenACP checks this on install and warns if incompatible.
|
|
664
|
-
`;
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// src/cli/plugin-template/plugin-guide.ts
|
|
668
|
-
function generatePluginGuide(params) {
|
|
669
|
-
return `# Plugin Developer Guide
|
|
670
|
-
|
|
671
|
-
## Overview
|
|
672
|
-
|
|
673
|
-
**${params.pluginName}** is an OpenACP plugin.
|
|
674
|
-
|
|
675
|
-
> TODO: Describe what this plugin does.
|
|
676
|
-
|
|
677
|
-
## Project Structure
|
|
678
|
-
|
|
679
|
-
\`\`\`
|
|
680
|
-
src/
|
|
681
|
-
index.ts \u2014 Plugin entry point (exports OpenACPPlugin object)
|
|
682
|
-
__tests__/
|
|
683
|
-
index.test.ts \u2014 Tests using Vitest + @openacp/plugin-sdk/testing
|
|
684
|
-
package.json \u2014 npm package config with engines.openacp constraint
|
|
685
|
-
tsconfig.json \u2014 TypeScript strict mode, ES2022, NodeNext
|
|
686
|
-
CLAUDE.md \u2014 Full technical reference for AI coding agents
|
|
687
|
-
PLUGIN_GUIDE.md \u2014 This file
|
|
688
|
-
\`\`\`
|
|
689
|
-
|
|
690
|
-
## Development Workflow
|
|
691
|
-
|
|
692
|
-
1. **Edit** \`src/index.ts\` \u2014 implement your plugin logic
|
|
693
|
-
2. **Dev mode**: \`openacp dev .\` \u2014 compiles, watches, and hot-reloads your plugin
|
|
694
|
-
3. **Test**: \`npm test\` \u2014 runs Vitest with SDK testing utilities
|
|
695
|
-
4. **Build**: \`npm run build\` \u2014 compiles TypeScript to \`dist/\`
|
|
696
|
-
|
|
697
|
-
\`\`\`bash
|
|
698
|
-
npm install
|
|
699
|
-
openacp dev . # start developing with hot-reload
|
|
700
|
-
npm test # run tests
|
|
701
|
-
npm run build # compile for publishing
|
|
702
|
-
\`\`\`
|
|
703
|
-
|
|
704
|
-
## Adding a Command
|
|
705
|
-
|
|
706
|
-
Register commands in your \`setup()\` function. Requires \`commands:register\` permission.
|
|
707
|
-
|
|
708
|
-
\`\`\`typescript
|
|
709
|
-
async setup(ctx: PluginContext) {
|
|
710
|
-
ctx.registerCommand({
|
|
711
|
-
name: 'greet',
|
|
712
|
-
description: 'Send a greeting',
|
|
713
|
-
usage: '[name]',
|
|
714
|
-
category: 'plugin',
|
|
715
|
-
async handler(args) {
|
|
716
|
-
const name = args.raw.trim() || 'World'
|
|
717
|
-
return { type: 'text', text: \\\`Hello, \\\${name}!\\\` }
|
|
718
|
-
},
|
|
719
|
-
})
|
|
720
|
-
}
|
|
721
|
-
\`\`\`
|
|
722
|
-
|
|
723
|
-
The command will be available as \`/greet\` in all messaging platforms.
|
|
724
|
-
|
|
725
|
-
## Adding a Service
|
|
726
|
-
|
|
727
|
-
Provide a service that other plugins can consume. Requires \`services:register\` permission.
|
|
728
|
-
|
|
729
|
-
\`\`\`typescript
|
|
730
|
-
async setup(ctx: PluginContext) {
|
|
731
|
-
const myService = {
|
|
732
|
-
doSomething(input: string): string {
|
|
733
|
-
return input.toUpperCase()
|
|
734
|
-
},
|
|
735
|
-
}
|
|
736
|
-
ctx.registerService('my-service', myService)
|
|
737
|
-
}
|
|
738
|
-
\`\`\`
|
|
739
|
-
|
|
740
|
-
Other plugins access it with \`ctx.getService<MyServiceType>('my-service')\`.
|
|
741
|
-
|
|
742
|
-
## Adding Middleware
|
|
743
|
-
|
|
744
|
-
Intercept and modify message flows. Requires \`middleware:register\` permission.
|
|
745
|
-
|
|
746
|
-
\`\`\`typescript
|
|
747
|
-
async setup(ctx: PluginContext) {
|
|
748
|
-
ctx.registerMiddleware('message:outgoing', {
|
|
749
|
-
priority: 50,
|
|
750
|
-
handler: async (payload, next) => {
|
|
751
|
-
// Modify the message before delivery
|
|
752
|
-
payload.message.text += '\\n-- sent via ${params.pluginName}'
|
|
753
|
-
return next() // continue the chain
|
|
754
|
-
// return null to block the message entirely
|
|
755
|
-
},
|
|
756
|
-
})
|
|
757
|
-
}
|
|
758
|
-
\`\`\`
|
|
759
|
-
|
|
760
|
-
## Handling Settings
|
|
761
|
-
|
|
762
|
-
### Install flow (first-time setup)
|
|
763
|
-
|
|
764
|
-
\`\`\`typescript
|
|
765
|
-
async install(ctx: InstallContext) {
|
|
766
|
-
const apiKey = await ctx.terminal.password({
|
|
767
|
-
message: 'Enter your API key:',
|
|
768
|
-
validate: (v) => v.length > 0 ? undefined : 'Required',
|
|
769
|
-
})
|
|
770
|
-
await ctx.settings.set('apiKey', apiKey)
|
|
771
|
-
ctx.terminal.log.success('Configured!')
|
|
772
|
-
}
|
|
773
|
-
\`\`\`
|
|
774
|
-
|
|
775
|
-
### Configure flow (reconfiguration)
|
|
776
|
-
|
|
777
|
-
\`\`\`typescript
|
|
778
|
-
async configure(ctx: InstallContext) {
|
|
779
|
-
const current = await ctx.settings.getAll()
|
|
780
|
-
const apiKey = await ctx.terminal.password({
|
|
781
|
-
message: \\\`API key (current: \\\${current.apiKey ? '***' : 'not set'}):\\\`,
|
|
782
|
-
})
|
|
783
|
-
if (apiKey) await ctx.settings.set('apiKey', apiKey)
|
|
784
|
-
ctx.terminal.log.success('Updated!')
|
|
785
|
-
}
|
|
786
|
-
\`\`\`
|
|
787
|
-
|
|
788
|
-
### Reading settings at runtime
|
|
789
|
-
|
|
790
|
-
\`\`\`typescript
|
|
791
|
-
async setup(ctx: PluginContext) {
|
|
792
|
-
const apiKey = ctx.pluginConfig.apiKey as string
|
|
793
|
-
if (!apiKey) {
|
|
794
|
-
ctx.log.warn('Not configured \u2014 run: openacp plugin configure ${params.pluginName}')
|
|
795
|
-
return
|
|
796
|
-
}
|
|
797
|
-
// Use apiKey...
|
|
798
|
-
}
|
|
799
|
-
\`\`\`
|
|
800
|
-
|
|
801
|
-
## Testing
|
|
802
|
-
|
|
803
|
-
Tests use Vitest and \`@openacp/plugin-sdk/testing\`.
|
|
804
|
-
|
|
805
|
-
\`\`\`typescript
|
|
806
|
-
import { describe, it, expect } from 'vitest'
|
|
807
|
-
import { createTestContext, createTestInstallContext, mockServices } from '@openacp/plugin-sdk/testing'
|
|
808
|
-
import plugin from '../index.js'
|
|
809
|
-
|
|
810
|
-
describe('${params.pluginName}', () => {
|
|
811
|
-
it('registers commands on setup', async () => {
|
|
812
|
-
const ctx = createTestContext({ pluginName: '${params.pluginName}' })
|
|
813
|
-
await plugin.setup(ctx)
|
|
814
|
-
expect(ctx.registeredCommands.has('greet')).toBe(true)
|
|
815
|
-
})
|
|
816
|
-
|
|
817
|
-
it('command returns expected response', async () => {
|
|
818
|
-
const ctx = createTestContext({ pluginName: '${params.pluginName}' })
|
|
819
|
-
await plugin.setup(ctx)
|
|
820
|
-
const res = await ctx.executeCommand('greet', { raw: 'Alice' })
|
|
821
|
-
expect(res).toEqual({ type: 'text', text: 'Hello, Alice!' })
|
|
822
|
-
})
|
|
823
|
-
|
|
824
|
-
it('install saves settings', async () => {
|
|
825
|
-
const ctx = createTestInstallContext({
|
|
826
|
-
pluginName: '${params.pluginName}',
|
|
827
|
-
terminalResponses: { password: ['sk-test-key'] },
|
|
828
|
-
})
|
|
829
|
-
await plugin.install!(ctx)
|
|
830
|
-
expect(ctx.settingsData.get('apiKey')).toBe('sk-test-key')
|
|
831
|
-
})
|
|
832
|
-
})
|
|
833
|
-
\`\`\`
|
|
834
|
-
|
|
835
|
-
### Available mock services
|
|
836
|
-
|
|
837
|
-
\`\`\`typescript
|
|
838
|
-
const ctx = createTestContext({
|
|
839
|
-
pluginName: '${params.pluginName}',
|
|
840
|
-
services: {
|
|
841
|
-
security: mockServices.security(),
|
|
842
|
-
usage: mockServices.usage({ async checkBudget() { return { ok: false, percent: 100 } } }),
|
|
843
|
-
},
|
|
844
|
-
})
|
|
845
|
-
\`\`\`
|
|
846
|
-
|
|
847
|
-
## Publishing
|
|
848
|
-
|
|
849
|
-
1. Update \`version\` in both \`package.json\` and \`src/index.ts\`
|
|
850
|
-
2. Build and test:
|
|
851
|
-
\`\`\`bash
|
|
852
|
-
npm run build
|
|
853
|
-
npm test
|
|
854
|
-
\`\`\`
|
|
855
|
-
3. Publish:
|
|
856
|
-
\`\`\`bash
|
|
857
|
-
npm publish --access public
|
|
858
|
-
\`\`\`
|
|
859
|
-
4. Users install with:
|
|
860
|
-
\`\`\`bash
|
|
861
|
-
openacp plugin install ${params.pluginName}
|
|
862
|
-
\`\`\`
|
|
863
|
-
5. Submit to the [OpenACP Plugin Registry](https://github.com/Open-ACP/plugin-registry) for discoverability.
|
|
864
|
-
|
|
865
|
-
## Useful Links
|
|
866
|
-
|
|
867
|
-
- [Architecture: Plugin System](https://docs.openacp.dev/architecture/plugin-system)
|
|
868
|
-
- [Architecture: Writing Plugins](https://docs.openacp.dev/architecture/writing-plugins)
|
|
869
|
-
- [Architecture: Command System](https://docs.openacp.dev/architecture/command-system)
|
|
870
|
-
- [Plugin SDK Reference](https://docs.openacp.dev/extending/plugin-sdk-reference)
|
|
871
|
-
- [Getting Started: Your First Plugin](https://docs.openacp.dev/extending/getting-started-plugin)
|
|
872
|
-
- [Dev Mode](https://docs.openacp.dev/extending/dev-mode)
|
|
873
|
-
- [Contributing](https://github.com/Open-ACP/OpenACP/blob/main/CONTRIBUTING.md)
|
|
874
|
-
`;
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
// src/cli/commands/plugin-create.ts
|
|
878
|
-
async function cmdPluginCreate() {
|
|
879
|
-
p.intro("Create a new OpenACP plugin");
|
|
880
|
-
const result = await p.group(
|
|
881
|
-
{
|
|
882
|
-
name: () => p.text({
|
|
883
|
-
message: "Plugin name (e.g., @myorg/adapter-matrix)",
|
|
884
|
-
placeholder: "@myorg/my-plugin",
|
|
885
|
-
validate: (value) => {
|
|
886
|
-
if (!value || !value.trim()) return "Plugin name is required";
|
|
887
|
-
if (!/^(@[a-z0-9-]+\/)?[a-z0-9-]+$/.test(value.trim())) {
|
|
888
|
-
return "Must be a valid npm package name (lowercase, hyphens, optional @scope/)";
|
|
889
|
-
}
|
|
890
|
-
return void 0;
|
|
891
|
-
}
|
|
892
|
-
}),
|
|
893
|
-
description: () => p.text({
|
|
894
|
-
message: "Description",
|
|
895
|
-
placeholder: "A short description of your plugin"
|
|
896
|
-
}),
|
|
897
|
-
author: () => p.text({
|
|
898
|
-
message: "Author",
|
|
899
|
-
placeholder: "Your Name <email@example.com>"
|
|
900
|
-
}),
|
|
901
|
-
license: () => p.select({
|
|
902
|
-
message: "License",
|
|
903
|
-
options: [
|
|
904
|
-
{ value: "MIT", label: "MIT" },
|
|
905
|
-
{ value: "Apache-2.0", label: "Apache 2.0" },
|
|
906
|
-
{ value: "ISC", label: "ISC" },
|
|
907
|
-
{ value: "UNLICENSED", label: "Unlicensed (private)" }
|
|
908
|
-
]
|
|
909
|
-
})
|
|
910
|
-
},
|
|
911
|
-
{
|
|
912
|
-
onCancel: () => {
|
|
913
|
-
p.cancel("Plugin creation cancelled.");
|
|
914
|
-
process.exit(0);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
);
|
|
918
|
-
const pluginName = result.name.trim();
|
|
919
|
-
const dirName = pluginName.replace(/^@[^/]+\//, "");
|
|
920
|
-
const targetDir = path.resolve(process.cwd(), dirName);
|
|
921
|
-
if (fs.existsSync(targetDir)) {
|
|
922
|
-
p.cancel(`Directory "${dirName}" already exists.`);
|
|
923
|
-
process.exit(1);
|
|
924
|
-
}
|
|
925
|
-
const spinner2 = p.spinner();
|
|
926
|
-
spinner2.start("Scaffolding plugin...");
|
|
927
|
-
fs.mkdirSync(path.join(targetDir, "src", "__tests__"), { recursive: true });
|
|
928
|
-
const params = {
|
|
929
|
-
pluginName,
|
|
930
|
-
description: result.description || "",
|
|
931
|
-
author: result.author || "",
|
|
932
|
-
license: result.license,
|
|
933
|
-
cliVersion: getCurrentVersion()
|
|
934
|
-
};
|
|
935
|
-
const files = [
|
|
936
|
-
{ relativePath: "package.json", content: generatePackageJson(params) },
|
|
937
|
-
{ relativePath: "tsconfig.json", content: generateTsconfig() },
|
|
938
|
-
{ relativePath: ".gitignore", content: generateGitignore() },
|
|
939
|
-
{ relativePath: ".npmignore", content: generateNpmignore() },
|
|
940
|
-
{ relativePath: ".editorconfig", content: generateEditorconfig() },
|
|
941
|
-
{ relativePath: "README.md", content: generateReadme(params) },
|
|
942
|
-
{ relativePath: "CLAUDE.md", content: generateClaudeMd(params) },
|
|
943
|
-
{ relativePath: "PLUGIN_GUIDE.md", content: generatePluginGuide(params) },
|
|
944
|
-
{ relativePath: path.join("src", "index.ts"), content: generatePluginSource(params) },
|
|
945
|
-
{ relativePath: path.join("src", "__tests__", "index.test.ts"), content: generatePluginTest(params) }
|
|
946
|
-
];
|
|
947
|
-
for (const file of files) {
|
|
948
|
-
fs.writeFileSync(path.join(targetDir, file.relativePath), file.content);
|
|
949
|
-
}
|
|
950
|
-
spinner2.stop("Plugin scaffolded!");
|
|
951
|
-
p.note(
|
|
952
|
-
[
|
|
953
|
-
`cd ${dirName}`,
|
|
954
|
-
"npm install",
|
|
955
|
-
"npm run build",
|
|
956
|
-
"npm test",
|
|
957
|
-
"",
|
|
958
|
-
"# Start development with hot-reload:",
|
|
959
|
-
`openacp dev .`
|
|
960
|
-
].join("\n"),
|
|
961
|
-
"Next steps"
|
|
962
|
-
);
|
|
963
|
-
p.outro(`Plugin ${pluginName} created in ./${dirName}`);
|
|
964
|
-
}
|
|
965
|
-
export {
|
|
966
|
-
cmdPluginCreate
|
|
967
|
-
};
|
|
968
|
-
//# sourceMappingURL=plugin-create-HFKS23JY.js.map
|