@kernel.chat/kbot 2.23.2 → 2.25.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/dist/pair.d.ts +81 -0
- package/dist/pair.d.ts.map +1 -0
- package/dist/pair.js +993 -0
- package/dist/pair.js.map +1 -0
- package/dist/plugin-sdk.d.ts +136 -0
- package/dist/plugin-sdk.d.ts.map +1 -0
- package/dist/plugin-sdk.js +946 -0
- package/dist/plugin-sdk.js.map +1 -0
- package/dist/record.d.ts +174 -0
- package/dist/record.d.ts.map +1 -0
- package/dist/record.js +1182 -0
- package/dist/record.js.map +1 -0
- package/dist/team.d.ts +106 -0
- package/dist/team.d.ts.map +1 -0
- package/dist/team.js +917 -0
- package/dist/team.js.map +1 -0
- package/dist/tools/database.d.ts +2 -0
- package/dist/tools/database.d.ts.map +1 -0
- package/dist/tools/database.js +751 -0
- package/dist/tools/database.js.map +1 -0
- package/dist/tools/deploy.d.ts +2 -0
- package/dist/tools/deploy.d.ts.map +1 -0
- package/dist/tools/deploy.js +824 -0
- package/dist/tools/deploy.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +13 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp-marketplace.d.ts +2 -0
- package/dist/tools/mcp-marketplace.d.ts.map +1 -0
- package/dist/tools/mcp-marketplace.js +759 -0
- package/dist/tools/mcp-marketplace.js.map +1 -0
- package/dist/tools/training.d.ts +2 -0
- package/dist/tools/training.d.ts.map +1 -0
- package/dist/tools/training.js +2313 -0
- package/dist/tools/training.js.map +1 -0
- package/package.json +35 -3
|
@@ -0,0 +1,946 @@
|
|
|
1
|
+
// K:BOT Plugin SDK — One-file plugin authoring for kbot
|
|
2
|
+
//
|
|
3
|
+
// Extends the existing plugins.ts drop-in system with a full SDK:
|
|
4
|
+
// - Structured plugin interface (tools, hooks, commands, lifecycle)
|
|
5
|
+
// - Plugin discovery from ~/.kbot/plugins/<name>/ directories and npm packages
|
|
6
|
+
// - Scaffold, enable, disable, install, uninstall management
|
|
7
|
+
// - TypeScript compilation on-the-fly via tsx/esbuild
|
|
8
|
+
//
|
|
9
|
+
// Usage:
|
|
10
|
+
// import type { KBotPlugin } from '@kernel.chat/kbot'
|
|
11
|
+
//
|
|
12
|
+
// const plugin: KBotPlugin = {
|
|
13
|
+
// name: 'my-tool',
|
|
14
|
+
// version: '1.0.0',
|
|
15
|
+
// description: 'Does something',
|
|
16
|
+
// tools: [{ name: 'my_tool', ... }],
|
|
17
|
+
// }
|
|
18
|
+
// export default plugin
|
|
19
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, } from 'node:fs';
|
|
20
|
+
import { join, basename, resolve } from 'node:path';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { pathToFileURL } from 'node:url';
|
|
23
|
+
import { execSync } from 'node:child_process';
|
|
24
|
+
import { registerTool } from './tools/index.js';
|
|
25
|
+
// ── Constants ────────────────────────────────────────────────────────────
|
|
26
|
+
const KBOT_DIR = join(homedir(), '.kbot');
|
|
27
|
+
const PLUGINS_DIR = join(KBOT_DIR, 'plugins');
|
|
28
|
+
const PLUGINS_CONFIG = join(KBOT_DIR, 'plugins.json');
|
|
29
|
+
// ── State ────────────────────────────────────────────────────────────────
|
|
30
|
+
const loadedSDKPlugins = new Map();
|
|
31
|
+
const registeredCommands = new Map();
|
|
32
|
+
const registeredHooks = [];
|
|
33
|
+
// ── Config ───────────────────────────────────────────────────────────────
|
|
34
|
+
function ensureDir(dir) {
|
|
35
|
+
if (!existsSync(dir)) {
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function readPluginConfig() {
|
|
40
|
+
if (!existsSync(PLUGINS_CONFIG)) {
|
|
41
|
+
return { enabled: [], disabled: [] };
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(readFileSync(PLUGINS_CONFIG, 'utf-8'));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { enabled: [], disabled: [] };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function writePluginConfig(config) {
|
|
51
|
+
ensureDir(KBOT_DIR);
|
|
52
|
+
writeFileSync(PLUGINS_CONFIG, JSON.stringify(config, null, 2), 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
function isPluginEnabled(name) {
|
|
55
|
+
const config = readPluginConfig();
|
|
56
|
+
// If explicitly disabled, it's disabled
|
|
57
|
+
if (config.disabled.includes(name))
|
|
58
|
+
return false;
|
|
59
|
+
// If explicitly enabled or config is empty (auto-enable), it's enabled
|
|
60
|
+
if (config.enabled.includes(name))
|
|
61
|
+
return true;
|
|
62
|
+
// Default: enabled unless explicitly disabled
|
|
63
|
+
return !config.disabled.includes(name);
|
|
64
|
+
}
|
|
65
|
+
// ── TypeScript Compilation ───────────────────────────────────────────────
|
|
66
|
+
/**
|
|
67
|
+
* Compile a TypeScript plugin file on-the-fly.
|
|
68
|
+
* Tries tsx first (already a dev dependency), falls back to esbuild, then tsc.
|
|
69
|
+
* Returns the path to the compiled JS file.
|
|
70
|
+
*/
|
|
71
|
+
function compileTypeScript(tsPath) {
|
|
72
|
+
const dir = join(tsPath, '..');
|
|
73
|
+
const jsPath = tsPath.replace(/\.ts$/, '.js');
|
|
74
|
+
// If a compiled .js already exists and is newer than .ts, reuse it
|
|
75
|
+
if (existsSync(jsPath)) {
|
|
76
|
+
try {
|
|
77
|
+
const tsStat = statSync(tsPath);
|
|
78
|
+
const jsStat = statSync(jsPath);
|
|
79
|
+
if (jsStat.mtimeMs >= tsStat.mtimeMs) {
|
|
80
|
+
return jsPath;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Fall through to recompile
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Strategy 1: esbuild (fastest, single-file transform)
|
|
88
|
+
try {
|
|
89
|
+
execSync(`npx --yes esbuild "${tsPath}" --outfile="${jsPath}" --format=esm --platform=node --target=node20 2>/dev/null`, { cwd: dir, timeout: 30_000, stdio: 'pipe' });
|
|
90
|
+
if (existsSync(jsPath))
|
|
91
|
+
return jsPath;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Fall through
|
|
95
|
+
}
|
|
96
|
+
// Strategy 2: tsx register — we can import .ts files directly if tsx is available
|
|
97
|
+
// tsx supports ESM .ts imports natively; check if it's loadable
|
|
98
|
+
try {
|
|
99
|
+
execSync('npx --yes tsx --version', { timeout: 10_000, stdio: 'pipe' });
|
|
100
|
+
// tsx is available — return the .ts path directly and let the loader handle it
|
|
101
|
+
return tsPath;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Fall through
|
|
105
|
+
}
|
|
106
|
+
// Strategy 3: tsc single-file compilation
|
|
107
|
+
try {
|
|
108
|
+
execSync(`npx tsc "${tsPath}" --outDir "${dir}" --module nodenext --moduleResolution nodenext --target es2022 --esModuleInterop --skipLibCheck 2>/dev/null`, { cwd: dir, timeout: 30_000, stdio: 'pipe' });
|
|
109
|
+
if (existsSync(jsPath))
|
|
110
|
+
return jsPath;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Fall through
|
|
114
|
+
}
|
|
115
|
+
throw new Error(`Failed to compile TypeScript plugin: ${tsPath}. Install esbuild or tsx.`);
|
|
116
|
+
}
|
|
117
|
+
// ── Plugin Loading ───────────────────────────────────────────────────────
|
|
118
|
+
/**
|
|
119
|
+
* Import a plugin module from a file path.
|
|
120
|
+
* Handles both .ts and .js files.
|
|
121
|
+
*/
|
|
122
|
+
async function importPlugin(entryPath) {
|
|
123
|
+
let loadPath = entryPath;
|
|
124
|
+
if (entryPath.endsWith('.ts')) {
|
|
125
|
+
loadPath = compileTypeScript(entryPath);
|
|
126
|
+
}
|
|
127
|
+
const fileUrl = pathToFileURL(resolve(loadPath)).href;
|
|
128
|
+
const mod = await import(fileUrl);
|
|
129
|
+
const plugin = mod.default || mod;
|
|
130
|
+
// Validate required fields
|
|
131
|
+
if (!plugin.name || typeof plugin.name !== 'string') {
|
|
132
|
+
throw new Error('Plugin must export a "name" string');
|
|
133
|
+
}
|
|
134
|
+
if (!plugin.version || typeof plugin.version !== 'string') {
|
|
135
|
+
throw new Error('Plugin must export a "version" string');
|
|
136
|
+
}
|
|
137
|
+
if (!plugin.description || typeof plugin.description !== 'string') {
|
|
138
|
+
throw new Error('Plugin must export a "description" string');
|
|
139
|
+
}
|
|
140
|
+
return plugin;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Register a KBotPlugin's tools into the kbot tool registry.
|
|
144
|
+
* Converts the SDK input_schema format to the ToolDefinition parameters format.
|
|
145
|
+
*/
|
|
146
|
+
function registerPluginTools(plugin) {
|
|
147
|
+
if (!plugin.tools || plugin.tools.length === 0)
|
|
148
|
+
return 0;
|
|
149
|
+
let count = 0;
|
|
150
|
+
for (const tool of plugin.tools) {
|
|
151
|
+
if (!tool.name || !tool.execute)
|
|
152
|
+
continue;
|
|
153
|
+
// Convert input_schema to ToolDefinition parameters
|
|
154
|
+
const parameters = {};
|
|
155
|
+
const schema = tool.input_schema || {};
|
|
156
|
+
const properties = schema.properties;
|
|
157
|
+
const required = (schema.required || []);
|
|
158
|
+
if (properties) {
|
|
159
|
+
for (const [key, prop] of Object.entries(properties)) {
|
|
160
|
+
parameters[key] = {
|
|
161
|
+
type: String(prop.type || 'string'),
|
|
162
|
+
description: String(prop.description || key),
|
|
163
|
+
required: required.includes(key),
|
|
164
|
+
};
|
|
165
|
+
if (prop.default !== undefined) {
|
|
166
|
+
parameters[key].default = prop.default;
|
|
167
|
+
}
|
|
168
|
+
if (prop.items) {
|
|
169
|
+
parameters[key].items = prop.items;
|
|
170
|
+
}
|
|
171
|
+
if (prop.properties) {
|
|
172
|
+
parameters[key].properties = prop.properties;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const toolDef = {
|
|
177
|
+
name: `plugin_${plugin.name}_${tool.name}`,
|
|
178
|
+
description: `[Plugin: ${plugin.name}] ${tool.description}`,
|
|
179
|
+
parameters,
|
|
180
|
+
tier: 'free',
|
|
181
|
+
execute: tool.execute,
|
|
182
|
+
};
|
|
183
|
+
registerTool(toolDef);
|
|
184
|
+
count++;
|
|
185
|
+
}
|
|
186
|
+
return count;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Register a plugin's slash commands.
|
|
190
|
+
*/
|
|
191
|
+
function registerPluginCommands(plugin) {
|
|
192
|
+
if (!plugin.commands || plugin.commands.length === 0)
|
|
193
|
+
return 0;
|
|
194
|
+
let count = 0;
|
|
195
|
+
for (const cmd of plugin.commands) {
|
|
196
|
+
if (!cmd.name || !cmd.execute)
|
|
197
|
+
continue;
|
|
198
|
+
registeredCommands.set(cmd.name, { ...cmd, pluginName: plugin.name });
|
|
199
|
+
count++;
|
|
200
|
+
}
|
|
201
|
+
return count;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Register a plugin's hooks into the agent lifecycle.
|
|
205
|
+
*/
|
|
206
|
+
function registerPluginHooks(plugin) {
|
|
207
|
+
if (!plugin.hooks)
|
|
208
|
+
return false;
|
|
209
|
+
const hasAnyHook = !!(plugin.hooks.beforeMessage ||
|
|
210
|
+
plugin.hooks.afterResponse ||
|
|
211
|
+
plugin.hooks.beforeToolCall ||
|
|
212
|
+
plugin.hooks.afterToolCall);
|
|
213
|
+
if (hasAnyHook) {
|
|
214
|
+
registeredHooks.push({ pluginName: plugin.name, hooks: plugin.hooks });
|
|
215
|
+
}
|
|
216
|
+
return hasAnyHook;
|
|
217
|
+
}
|
|
218
|
+
// ── Discovery ────────────────────────────────────────────────────────────
|
|
219
|
+
/**
|
|
220
|
+
* Discover local plugins in ~/.kbot/plugins/<name>/ directories.
|
|
221
|
+
* Each directory should contain an index.ts or index.js file.
|
|
222
|
+
*/
|
|
223
|
+
function discoverLocalPlugins() {
|
|
224
|
+
ensureDir(PLUGINS_DIR);
|
|
225
|
+
const results = [];
|
|
226
|
+
let entries;
|
|
227
|
+
try {
|
|
228
|
+
entries = readdirSync(PLUGINS_DIR);
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
for (const entry of entries) {
|
|
234
|
+
const dirPath = join(PLUGINS_DIR, entry);
|
|
235
|
+
try {
|
|
236
|
+
const stat = statSync(dirPath);
|
|
237
|
+
if (!stat.isDirectory())
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
// Security: reject directories writable by others
|
|
244
|
+
try {
|
|
245
|
+
const dirStat = statSync(dirPath);
|
|
246
|
+
if ((dirStat.mode & 0o022) !== 0)
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
// Look for entry point: index.ts, index.js, or main from package.json
|
|
253
|
+
let entryPath = null;
|
|
254
|
+
// Check package.json for custom main field
|
|
255
|
+
const pkgPath = join(dirPath, 'package.json');
|
|
256
|
+
if (existsSync(pkgPath)) {
|
|
257
|
+
try {
|
|
258
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
259
|
+
if (pkg.main) {
|
|
260
|
+
const mainPath = join(dirPath, pkg.main);
|
|
261
|
+
if (existsSync(mainPath)) {
|
|
262
|
+
entryPath = mainPath;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
// Fall through to default entry points
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (!entryPath) {
|
|
271
|
+
for (const candidate of ['index.ts', 'index.js', 'index.mjs']) {
|
|
272
|
+
const candidatePath = join(dirPath, candidate);
|
|
273
|
+
if (existsSync(candidatePath)) {
|
|
274
|
+
entryPath = candidatePath;
|
|
275
|
+
break;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (entryPath) {
|
|
280
|
+
results.push({ name: entry, entryPath });
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return results;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Discover npm-installed plugins (packages starting with kbot-plugin-).
|
|
287
|
+
* Scans both local node_modules and global node_modules.
|
|
288
|
+
*/
|
|
289
|
+
function discoverNpmPlugins() {
|
|
290
|
+
const results = [];
|
|
291
|
+
// Find global node_modules path
|
|
292
|
+
let globalPrefix;
|
|
293
|
+
try {
|
|
294
|
+
globalPrefix = execSync('npm root -g', { encoding: 'utf-8', timeout: 10_000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return results;
|
|
298
|
+
}
|
|
299
|
+
if (!existsSync(globalPrefix))
|
|
300
|
+
return results;
|
|
301
|
+
let entries;
|
|
302
|
+
try {
|
|
303
|
+
entries = readdirSync(globalPrefix);
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return results;
|
|
307
|
+
}
|
|
308
|
+
for (const entry of entries) {
|
|
309
|
+
if (!entry.startsWith('kbot-plugin-'))
|
|
310
|
+
continue;
|
|
311
|
+
const pkgDir = join(globalPrefix, entry);
|
|
312
|
+
const pkgPath = join(pkgDir, 'package.json');
|
|
313
|
+
if (!existsSync(pkgPath))
|
|
314
|
+
continue;
|
|
315
|
+
try {
|
|
316
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
317
|
+
const main = pkg.main || 'index.js';
|
|
318
|
+
const entryPath = join(pkgDir, main);
|
|
319
|
+
if (existsSync(entryPath)) {
|
|
320
|
+
// Extract plugin name: kbot-plugin-foo -> foo
|
|
321
|
+
const pluginName = entry.replace(/^kbot-plugin-/, '');
|
|
322
|
+
results.push({ name: pluginName, entryPath });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return results;
|
|
330
|
+
}
|
|
331
|
+
// ── Public API ───────────────────────────────────────────────────────────
|
|
332
|
+
/**
|
|
333
|
+
* Load all installed and enabled SDK plugins.
|
|
334
|
+
* Called at startup after the legacy plugins.ts loadPlugins() runs.
|
|
335
|
+
*/
|
|
336
|
+
export async function loadPlugins(verbose = false) {
|
|
337
|
+
const manifests = [];
|
|
338
|
+
// Discover from both sources
|
|
339
|
+
const localPlugins = discoverLocalPlugins();
|
|
340
|
+
const npmPlugins = discoverNpmPlugins();
|
|
341
|
+
const allDiscovered = [
|
|
342
|
+
...localPlugins.map(p => ({ ...p, source: 'local' })),
|
|
343
|
+
...npmPlugins.map(p => ({ ...p, source: 'npm' })),
|
|
344
|
+
];
|
|
345
|
+
for (const { name, entryPath, source } of allDiscovered) {
|
|
346
|
+
const manifest = {
|
|
347
|
+
name,
|
|
348
|
+
version: '0.0.0',
|
|
349
|
+
description: '',
|
|
350
|
+
source,
|
|
351
|
+
path: entryPath,
|
|
352
|
+
enabled: isPluginEnabled(name),
|
|
353
|
+
loaded: false,
|
|
354
|
+
toolCount: 0,
|
|
355
|
+
commandCount: 0,
|
|
356
|
+
hasHooks: false,
|
|
357
|
+
};
|
|
358
|
+
if (!manifest.enabled) {
|
|
359
|
+
if (verbose)
|
|
360
|
+
console.log(` [SDK] Skipping disabled plugin: ${name}`);
|
|
361
|
+
manifests.push(manifest);
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
try {
|
|
365
|
+
const plugin = await importPlugin(entryPath);
|
|
366
|
+
manifest.version = plugin.version;
|
|
367
|
+
manifest.description = plugin.description;
|
|
368
|
+
manifest.author = plugin.author;
|
|
369
|
+
// Activate lifecycle hook
|
|
370
|
+
if (plugin.activate) {
|
|
371
|
+
await plugin.activate();
|
|
372
|
+
}
|
|
373
|
+
// Register tools, commands, hooks
|
|
374
|
+
manifest.toolCount = registerPluginTools(plugin);
|
|
375
|
+
manifest.commandCount = registerPluginCommands(plugin);
|
|
376
|
+
manifest.hasHooks = registerPluginHooks(plugin);
|
|
377
|
+
manifest.loaded = true;
|
|
378
|
+
manifest.loadedAt = new Date().toISOString();
|
|
379
|
+
loadedSDKPlugins.set(name, { plugin, manifest });
|
|
380
|
+
if (verbose) {
|
|
381
|
+
console.log(` [SDK] Loaded plugin: ${name} v${plugin.version} (${manifest.toolCount} tools, ${manifest.commandCount} commands)`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
catch (err) {
|
|
385
|
+
manifest.error = err instanceof Error ? err.message : String(err);
|
|
386
|
+
if (verbose) {
|
|
387
|
+
console.error(` [SDK] Failed to load plugin ${name}: ${manifest.error}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
manifests.push(manifest);
|
|
391
|
+
}
|
|
392
|
+
return manifests;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Scaffold a new plugin with a full project structure.
|
|
396
|
+
* Creates ~/.kbot/plugins/<name>/ with index.ts and package.json.
|
|
397
|
+
*/
|
|
398
|
+
export function createPlugin(name) {
|
|
399
|
+
// Validate name
|
|
400
|
+
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(name)) {
|
|
401
|
+
return `Invalid plugin name: "${name}". Use lowercase letters, numbers, and hyphens only.`;
|
|
402
|
+
}
|
|
403
|
+
const pluginDir = join(PLUGINS_DIR, name);
|
|
404
|
+
if (existsSync(pluginDir)) {
|
|
405
|
+
return `Plugin already exists: ${pluginDir}\nEdit ${join(pluginDir, 'index.ts')} to modify it.`;
|
|
406
|
+
}
|
|
407
|
+
ensureDir(pluginDir);
|
|
408
|
+
// Generate index.ts from template
|
|
409
|
+
const indexContent = `import type { KBotPlugin } from '@kernel.chat/kbot'
|
|
410
|
+
|
|
411
|
+
const plugin: KBotPlugin = {
|
|
412
|
+
name: '${name}',
|
|
413
|
+
version: '1.0.0',
|
|
414
|
+
description: 'A custom kbot plugin',
|
|
415
|
+
|
|
416
|
+
tools: [{
|
|
417
|
+
name: '${name.replace(/-/g, '_')}',
|
|
418
|
+
description: 'Does something useful',
|
|
419
|
+
input_schema: {
|
|
420
|
+
type: 'object',
|
|
421
|
+
properties: {
|
|
422
|
+
input: { type: 'string', description: 'The input' }
|
|
423
|
+
},
|
|
424
|
+
required: ['input']
|
|
425
|
+
},
|
|
426
|
+
execute: async (args) => {
|
|
427
|
+
return \`Processed: \${args.input}\`
|
|
428
|
+
}
|
|
429
|
+
}],
|
|
430
|
+
|
|
431
|
+
commands: [{
|
|
432
|
+
name: '${name}',
|
|
433
|
+
description: 'Run the ${name} plugin',
|
|
434
|
+
execute: async (args) => {
|
|
435
|
+
return \`${name} command executed with: \${args}\`
|
|
436
|
+
}
|
|
437
|
+
}],
|
|
438
|
+
|
|
439
|
+
activate: async () => {
|
|
440
|
+
console.log('${name} plugin activated')
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
deactivate: async () => {
|
|
444
|
+
console.log('${name} plugin deactivated')
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
export default plugin
|
|
449
|
+
`;
|
|
450
|
+
// Generate package.json
|
|
451
|
+
const packageContent = {
|
|
452
|
+
name: `kbot-plugin-${name}`,
|
|
453
|
+
version: '1.0.0',
|
|
454
|
+
description: `kbot plugin: ${name}`,
|
|
455
|
+
type: 'module',
|
|
456
|
+
main: 'index.ts',
|
|
457
|
+
keywords: ['kbot-plugin', 'kbot', 'ai-agent', 'plugin'],
|
|
458
|
+
license: 'MIT',
|
|
459
|
+
peerDependencies: {
|
|
460
|
+
'@kernel.chat/kbot': '>=2.0.0',
|
|
461
|
+
},
|
|
462
|
+
};
|
|
463
|
+
writeFileSync(join(pluginDir, 'index.ts'), indexContent, 'utf-8');
|
|
464
|
+
writeFileSync(join(pluginDir, 'package.json'), JSON.stringify(packageContent, null, 2), 'utf-8');
|
|
465
|
+
// Auto-enable the new plugin
|
|
466
|
+
const config = readPluginConfig();
|
|
467
|
+
if (!config.enabled.includes(name)) {
|
|
468
|
+
config.enabled.push(name);
|
|
469
|
+
}
|
|
470
|
+
// Remove from disabled if it was there
|
|
471
|
+
config.disabled = config.disabled.filter(d => d !== name);
|
|
472
|
+
writePluginConfig(config);
|
|
473
|
+
return [
|
|
474
|
+
`Plugin created: ${pluginDir}`,
|
|
475
|
+
'',
|
|
476
|
+
'Files:',
|
|
477
|
+
` ${join(pluginDir, 'index.ts')} — plugin code`,
|
|
478
|
+
` ${join(pluginDir, 'package.json')} — metadata`,
|
|
479
|
+
'',
|
|
480
|
+
'Next steps:',
|
|
481
|
+
` 1. Edit ${join(pluginDir, 'index.ts')} to add your tools, commands, and hooks`,
|
|
482
|
+
' 2. Restart kbot to load the plugin',
|
|
483
|
+
' 3. Your tools will appear as plugin_<name>_<tool_name>',
|
|
484
|
+
` 4. Your commands will be available as /${name}`,
|
|
485
|
+
'',
|
|
486
|
+
'To publish to npm:',
|
|
487
|
+
` cd ${pluginDir}`,
|
|
488
|
+
' npm publish --access public',
|
|
489
|
+
'',
|
|
490
|
+
'Others can install it with:',
|
|
491
|
+
` kbot plugin install ${name}`,
|
|
492
|
+
].join('\n');
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Enable a plugin by name.
|
|
496
|
+
*/
|
|
497
|
+
export function enablePlugin(name) {
|
|
498
|
+
const config = readPluginConfig();
|
|
499
|
+
config.disabled = config.disabled.filter(d => d !== name);
|
|
500
|
+
if (!config.enabled.includes(name)) {
|
|
501
|
+
config.enabled.push(name);
|
|
502
|
+
}
|
|
503
|
+
writePluginConfig(config);
|
|
504
|
+
return `Plugin "${name}" enabled. Restart kbot to load it.`;
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Disable a plugin by name. Calls deactivate() if loaded.
|
|
508
|
+
*/
|
|
509
|
+
export async function disablePlugin(name) {
|
|
510
|
+
const config = readPluginConfig();
|
|
511
|
+
config.enabled = config.enabled.filter(e => e !== name);
|
|
512
|
+
if (!config.disabled.includes(name)) {
|
|
513
|
+
config.disabled.push(name);
|
|
514
|
+
}
|
|
515
|
+
writePluginConfig(config);
|
|
516
|
+
// Deactivate if currently loaded
|
|
517
|
+
const loaded = loadedSDKPlugins.get(name);
|
|
518
|
+
if (loaded) {
|
|
519
|
+
try {
|
|
520
|
+
if (loaded.plugin.deactivate) {
|
|
521
|
+
await loaded.plugin.deactivate();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
// Best effort
|
|
526
|
+
}
|
|
527
|
+
loadedSDKPlugins.delete(name);
|
|
528
|
+
}
|
|
529
|
+
return `Plugin "${name}" disabled.`;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Install a plugin from npm or a git URL.
|
|
533
|
+
*/
|
|
534
|
+
export function installPlugin(source) {
|
|
535
|
+
// Determine if it's an npm package name or git URL
|
|
536
|
+
const isGitUrl = source.startsWith('http://') || source.startsWith('https://') || source.startsWith('git@');
|
|
537
|
+
const isNpmPackage = !isGitUrl;
|
|
538
|
+
if (isNpmPackage) {
|
|
539
|
+
// Normalize: if user says "foo", install "kbot-plugin-foo"
|
|
540
|
+
const npmName = source.startsWith('kbot-plugin-') ? source : `kbot-plugin-${source}`;
|
|
541
|
+
const pluginName = source.startsWith('kbot-plugin-') ? source.replace(/^kbot-plugin-/, '') : source;
|
|
542
|
+
try {
|
|
543
|
+
execSync(`npm install -g ${npmName}`, {
|
|
544
|
+
encoding: 'utf-8',
|
|
545
|
+
timeout: 120_000,
|
|
546
|
+
stdio: 'pipe',
|
|
547
|
+
});
|
|
548
|
+
// Auto-enable
|
|
549
|
+
const config = readPluginConfig();
|
|
550
|
+
if (!config.enabled.includes(pluginName)) {
|
|
551
|
+
config.enabled.push(pluginName);
|
|
552
|
+
}
|
|
553
|
+
config.disabled = config.disabled.filter(d => d !== pluginName);
|
|
554
|
+
writePluginConfig(config);
|
|
555
|
+
return [
|
|
556
|
+
`Installed npm plugin: ${npmName}`,
|
|
557
|
+
`Plugin name: ${pluginName}`,
|
|
558
|
+
'',
|
|
559
|
+
'Restart kbot to load the plugin.',
|
|
560
|
+
].join('\n');
|
|
561
|
+
}
|
|
562
|
+
catch (err) {
|
|
563
|
+
return `Failed to install ${npmName}: ${err instanceof Error ? err.message : String(err)}`;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
// Git URL: clone into plugins directory
|
|
568
|
+
const repoName = basename(source, '.git').replace(/^kbot-plugin-/, '');
|
|
569
|
+
const pluginDir = join(PLUGINS_DIR, repoName);
|
|
570
|
+
if (existsSync(pluginDir)) {
|
|
571
|
+
return `Plugin directory already exists: ${pluginDir}. Remove it first or use a different name.`;
|
|
572
|
+
}
|
|
573
|
+
ensureDir(PLUGINS_DIR);
|
|
574
|
+
try {
|
|
575
|
+
execSync(`git clone "${source}" "${pluginDir}"`, {
|
|
576
|
+
encoding: 'utf-8',
|
|
577
|
+
timeout: 60_000,
|
|
578
|
+
stdio: 'pipe',
|
|
579
|
+
});
|
|
580
|
+
// Install dependencies if package.json exists
|
|
581
|
+
const pkgPath = join(pluginDir, 'package.json');
|
|
582
|
+
if (existsSync(pkgPath)) {
|
|
583
|
+
try {
|
|
584
|
+
execSync('npm install --production', {
|
|
585
|
+
cwd: pluginDir,
|
|
586
|
+
encoding: 'utf-8',
|
|
587
|
+
timeout: 60_000,
|
|
588
|
+
stdio: 'pipe',
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
catch {
|
|
592
|
+
// Non-fatal — plugin may not have dependencies
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Auto-enable
|
|
596
|
+
const config = readPluginConfig();
|
|
597
|
+
if (!config.enabled.includes(repoName)) {
|
|
598
|
+
config.enabled.push(repoName);
|
|
599
|
+
}
|
|
600
|
+
config.disabled = config.disabled.filter(d => d !== repoName);
|
|
601
|
+
writePluginConfig(config);
|
|
602
|
+
return [
|
|
603
|
+
`Cloned plugin from: ${source}`,
|
|
604
|
+
`Plugin name: ${repoName}`,
|
|
605
|
+
`Location: ${pluginDir}`,
|
|
606
|
+
'',
|
|
607
|
+
'Restart kbot to load the plugin.',
|
|
608
|
+
].join('\n');
|
|
609
|
+
}
|
|
610
|
+
catch (err) {
|
|
611
|
+
return `Failed to clone ${source}: ${err instanceof Error ? err.message : String(err)}`;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Uninstall a plugin by name.
|
|
617
|
+
*/
|
|
618
|
+
export async function uninstallPlugin(name) {
|
|
619
|
+
// Deactivate first
|
|
620
|
+
const loaded = loadedSDKPlugins.get(name);
|
|
621
|
+
if (loaded) {
|
|
622
|
+
try {
|
|
623
|
+
if (loaded.plugin.deactivate) {
|
|
624
|
+
await loaded.plugin.deactivate();
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
// Best effort
|
|
629
|
+
}
|
|
630
|
+
loadedSDKPlugins.delete(name);
|
|
631
|
+
}
|
|
632
|
+
const lines = [];
|
|
633
|
+
// Remove local plugin directory
|
|
634
|
+
const pluginDir = join(PLUGINS_DIR, name);
|
|
635
|
+
if (existsSync(pluginDir)) {
|
|
636
|
+
try {
|
|
637
|
+
rmSync(pluginDir, { recursive: true, force: true });
|
|
638
|
+
lines.push(`Removed local plugin: ${pluginDir}`);
|
|
639
|
+
}
|
|
640
|
+
catch (err) {
|
|
641
|
+
lines.push(`Failed to remove ${pluginDir}: ${err instanceof Error ? err.message : String(err)}`);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
// Try to uninstall npm package
|
|
645
|
+
const npmName = `kbot-plugin-${name}`;
|
|
646
|
+
try {
|
|
647
|
+
execSync(`npm uninstall -g ${npmName}`, {
|
|
648
|
+
encoding: 'utf-8',
|
|
649
|
+
timeout: 30_000,
|
|
650
|
+
stdio: 'pipe',
|
|
651
|
+
});
|
|
652
|
+
lines.push(`Uninstalled npm package: ${npmName}`);
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
// Not an npm plugin — that's fine
|
|
656
|
+
}
|
|
657
|
+
// Remove from config
|
|
658
|
+
const config = readPluginConfig();
|
|
659
|
+
config.enabled = config.enabled.filter(e => e !== name);
|
|
660
|
+
config.disabled = config.disabled.filter(d => d !== name);
|
|
661
|
+
writePluginConfig(config);
|
|
662
|
+
if (lines.length === 0) {
|
|
663
|
+
return `Plugin "${name}" not found locally or in npm global packages.`;
|
|
664
|
+
}
|
|
665
|
+
lines.push('', `Plugin "${name}" uninstalled.`);
|
|
666
|
+
return lines.join('\n');
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* List all discovered plugins with their status.
|
|
670
|
+
*/
|
|
671
|
+
export function listPlugins() {
|
|
672
|
+
const localPlugins = discoverLocalPlugins();
|
|
673
|
+
const npmPlugins = discoverNpmPlugins();
|
|
674
|
+
const config = readPluginConfig();
|
|
675
|
+
const allPlugins = [
|
|
676
|
+
...localPlugins.map(p => ({ ...p, source: 'local' })),
|
|
677
|
+
...npmPlugins.map(p => ({ ...p, source: 'npm' })),
|
|
678
|
+
];
|
|
679
|
+
if (allPlugins.length === 0 && config.enabled.length === 0) {
|
|
680
|
+
return [
|
|
681
|
+
'No plugins installed.',
|
|
682
|
+
'',
|
|
683
|
+
`Plugin directory: ${PLUGINS_DIR}`,
|
|
684
|
+
'',
|
|
685
|
+
'Create a plugin:',
|
|
686
|
+
' kbot plugin create my-tool',
|
|
687
|
+
'',
|
|
688
|
+
'Install from npm:',
|
|
689
|
+
' kbot plugin install <name>',
|
|
690
|
+
].join('\n');
|
|
691
|
+
}
|
|
692
|
+
const lines = [
|
|
693
|
+
`Plugins (${allPlugins.length} discovered)`,
|
|
694
|
+
'',
|
|
695
|
+
];
|
|
696
|
+
for (const { name, entryPath, source } of allPlugins) {
|
|
697
|
+
const enabled = isPluginEnabled(name);
|
|
698
|
+
const loaded = loadedSDKPlugins.get(name);
|
|
699
|
+
const status = loaded?.manifest.error
|
|
700
|
+
? `ERROR: ${loaded.manifest.error}`
|
|
701
|
+
: loaded?.manifest.loaded
|
|
702
|
+
? `loaded (${loaded.manifest.toolCount} tools, ${loaded.manifest.commandCount} commands)`
|
|
703
|
+
: enabled
|
|
704
|
+
? 'enabled (not yet loaded)'
|
|
705
|
+
: 'disabled';
|
|
706
|
+
const icon = loaded?.manifest.loaded ? '+' : loaded?.manifest.error ? '!' : enabled ? '-' : 'x';
|
|
707
|
+
lines.push(` [${icon}] ${name} (${source}) — ${status}`);
|
|
708
|
+
if (loaded?.manifest.loaded) {
|
|
709
|
+
lines.push(` v${loaded.manifest.version}: ${loaded.manifest.description}`);
|
|
710
|
+
}
|
|
711
|
+
lines.push(` ${entryPath}`);
|
|
712
|
+
}
|
|
713
|
+
lines.push('');
|
|
714
|
+
lines.push(`Config: ${PLUGINS_CONFIG}`);
|
|
715
|
+
return lines.join('\n');
|
|
716
|
+
}
|
|
717
|
+
// ── Hook Execution API ───────────────────────────────────────────────────
|
|
718
|
+
/**
|
|
719
|
+
* Run all registered beforeMessage hooks in sequence.
|
|
720
|
+
* Returns the (possibly transformed) message.
|
|
721
|
+
*/
|
|
722
|
+
export async function runBeforeMessageHooks(message) {
|
|
723
|
+
let result = message;
|
|
724
|
+
for (const { hooks } of registeredHooks) {
|
|
725
|
+
if (hooks.beforeMessage) {
|
|
726
|
+
try {
|
|
727
|
+
result = await hooks.beforeMessage(result);
|
|
728
|
+
}
|
|
729
|
+
catch {
|
|
730
|
+
// Non-fatal: skip this hook
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
return result;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Run all registered afterResponse hooks in sequence.
|
|
738
|
+
* Returns the (possibly transformed) response.
|
|
739
|
+
*/
|
|
740
|
+
export async function runAfterResponseHooks(response) {
|
|
741
|
+
let result = response;
|
|
742
|
+
for (const { hooks } of registeredHooks) {
|
|
743
|
+
if (hooks.afterResponse) {
|
|
744
|
+
try {
|
|
745
|
+
result = await hooks.afterResponse(result);
|
|
746
|
+
}
|
|
747
|
+
catch {
|
|
748
|
+
// Non-fatal
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return result;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Run all registered beforeToolCall hooks in sequence.
|
|
756
|
+
* Returns the (possibly modified) args.
|
|
757
|
+
*/
|
|
758
|
+
export async function runBeforeToolCallHooks(tool, args) {
|
|
759
|
+
let result = args;
|
|
760
|
+
for (const { hooks } of registeredHooks) {
|
|
761
|
+
if (hooks.beforeToolCall) {
|
|
762
|
+
try {
|
|
763
|
+
result = await hooks.beforeToolCall(tool, result);
|
|
764
|
+
}
|
|
765
|
+
catch {
|
|
766
|
+
// Non-fatal
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return result;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Run all registered afterToolCall hooks in sequence.
|
|
774
|
+
* Returns the (possibly transformed) result.
|
|
775
|
+
*/
|
|
776
|
+
export async function runAfterToolCallHooks(tool, result) {
|
|
777
|
+
let current = result;
|
|
778
|
+
for (const { hooks } of registeredHooks) {
|
|
779
|
+
if (hooks.afterToolCall) {
|
|
780
|
+
try {
|
|
781
|
+
current = await hooks.afterToolCall(tool, current);
|
|
782
|
+
}
|
|
783
|
+
catch {
|
|
784
|
+
// Non-fatal
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return current;
|
|
789
|
+
}
|
|
790
|
+
// ── Command Execution API ────────────────────────────────────────────────
|
|
791
|
+
/**
|
|
792
|
+
* Check if a slash command is provided by a plugin.
|
|
793
|
+
*/
|
|
794
|
+
export function hasPluginCommand(commandName) {
|
|
795
|
+
return registeredCommands.has(commandName);
|
|
796
|
+
}
|
|
797
|
+
/**
|
|
798
|
+
* Execute a plugin slash command.
|
|
799
|
+
*/
|
|
800
|
+
export async function executePluginCommand(commandName, args) {
|
|
801
|
+
const cmd = registeredCommands.get(commandName);
|
|
802
|
+
if (!cmd) {
|
|
803
|
+
return `Unknown plugin command: /${commandName}`;
|
|
804
|
+
}
|
|
805
|
+
try {
|
|
806
|
+
return await cmd.execute(args);
|
|
807
|
+
}
|
|
808
|
+
catch (err) {
|
|
809
|
+
return `Plugin command error (/${commandName}): ${err instanceof Error ? err.message : String(err)}`;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Get all registered plugin commands.
|
|
814
|
+
*/
|
|
815
|
+
export function getPluginCommands() {
|
|
816
|
+
return Array.from(registeredCommands.values()).map(cmd => ({
|
|
817
|
+
name: cmd.name,
|
|
818
|
+
description: cmd.description,
|
|
819
|
+
pluginName: cmd.pluginName,
|
|
820
|
+
}));
|
|
821
|
+
}
|
|
822
|
+
// ── Loaded Plugin Access ─────────────────────────────────────────────────
|
|
823
|
+
/**
|
|
824
|
+
* Get a loaded plugin by name.
|
|
825
|
+
*/
|
|
826
|
+
export function getLoadedPlugin(name) {
|
|
827
|
+
return loadedSDKPlugins.get(name)?.plugin;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Get all loaded SDK plugins.
|
|
831
|
+
*/
|
|
832
|
+
export function getLoadedSDKPlugins() {
|
|
833
|
+
return Array.from(loadedSDKPlugins.values()).map(p => p.manifest);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Deactivate all loaded plugins (called on shutdown).
|
|
837
|
+
*/
|
|
838
|
+
export async function deactivateAll() {
|
|
839
|
+
for (const [, { plugin }] of Array.from(loadedSDKPlugins.entries())) {
|
|
840
|
+
try {
|
|
841
|
+
if (plugin.deactivate) {
|
|
842
|
+
await plugin.deactivate();
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
catch {
|
|
846
|
+
// Best effort on shutdown
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
loadedSDKPlugins.clear();
|
|
850
|
+
registeredCommands.clear();
|
|
851
|
+
registeredHooks.length = 0;
|
|
852
|
+
}
|
|
853
|
+
// ── Tool Registration ────────────────────────────────────────────────────
|
|
854
|
+
/**
|
|
855
|
+
* Register plugin management tools into the kbot tool registry.
|
|
856
|
+
* These tools let the user manage plugins via natural language.
|
|
857
|
+
*/
|
|
858
|
+
export function registerPluginSDKTools() {
|
|
859
|
+
registerTool({
|
|
860
|
+
name: 'plugin_create',
|
|
861
|
+
description: 'Scaffold a new kbot plugin with index.ts and package.json. Creates a ready-to-edit plugin directory in ~/.kbot/plugins/.',
|
|
862
|
+
parameters: {
|
|
863
|
+
name: {
|
|
864
|
+
type: 'string',
|
|
865
|
+
description: 'Plugin name (lowercase, hyphens allowed, e.g., "my-tool")',
|
|
866
|
+
required: true,
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
tier: 'free',
|
|
870
|
+
async execute(args) {
|
|
871
|
+
const name = String(args.name).toLowerCase().trim();
|
|
872
|
+
return createPlugin(name);
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
registerTool({
|
|
876
|
+
name: 'plugin_list',
|
|
877
|
+
description: 'List all installed kbot plugins with their status (enabled/disabled/loaded/error).',
|
|
878
|
+
parameters: {},
|
|
879
|
+
tier: 'free',
|
|
880
|
+
async execute() {
|
|
881
|
+
return listPlugins();
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
registerTool({
|
|
885
|
+
name: 'plugin_enable',
|
|
886
|
+
description: 'Enable a disabled kbot plugin. The plugin will be loaded on next startup.',
|
|
887
|
+
parameters: {
|
|
888
|
+
name: {
|
|
889
|
+
type: 'string',
|
|
890
|
+
description: 'Plugin name to enable',
|
|
891
|
+
required: true,
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
tier: 'free',
|
|
895
|
+
async execute(args) {
|
|
896
|
+
return enablePlugin(String(args.name).trim());
|
|
897
|
+
},
|
|
898
|
+
});
|
|
899
|
+
registerTool({
|
|
900
|
+
name: 'plugin_disable',
|
|
901
|
+
description: 'Disable an active kbot plugin. Deactivates it immediately if loaded.',
|
|
902
|
+
parameters: {
|
|
903
|
+
name: {
|
|
904
|
+
type: 'string',
|
|
905
|
+
description: 'Plugin name to disable',
|
|
906
|
+
required: true,
|
|
907
|
+
},
|
|
908
|
+
},
|
|
909
|
+
tier: 'free',
|
|
910
|
+
async execute(args) {
|
|
911
|
+
return disablePlugin(String(args.name).trim());
|
|
912
|
+
},
|
|
913
|
+
});
|
|
914
|
+
registerTool({
|
|
915
|
+
name: 'plugin_install',
|
|
916
|
+
description: 'Install a kbot plugin from npm (kbot-plugin-<name>) or a git URL. Automatically enables the plugin.',
|
|
917
|
+
parameters: {
|
|
918
|
+
source: {
|
|
919
|
+
type: 'string',
|
|
920
|
+
description: 'npm package name (e.g., "my-tool" or "kbot-plugin-my-tool") or git URL',
|
|
921
|
+
required: true,
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
tier: 'free',
|
|
925
|
+
timeout: 120_000,
|
|
926
|
+
async execute(args) {
|
|
927
|
+
return installPlugin(String(args.source).trim());
|
|
928
|
+
},
|
|
929
|
+
});
|
|
930
|
+
registerTool({
|
|
931
|
+
name: 'plugin_uninstall',
|
|
932
|
+
description: 'Remove a kbot plugin completely. Removes local files and/or npm global package.',
|
|
933
|
+
parameters: {
|
|
934
|
+
name: {
|
|
935
|
+
type: 'string',
|
|
936
|
+
description: 'Plugin name to uninstall',
|
|
937
|
+
required: true,
|
|
938
|
+
},
|
|
939
|
+
},
|
|
940
|
+
tier: 'free',
|
|
941
|
+
async execute(args) {
|
|
942
|
+
return uninstallPlugin(String(args.name).trim());
|
|
943
|
+
},
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
//# sourceMappingURL=plugin-sdk.js.map
|