chainlesschain 0.45.70 → 0.45.74
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/package.json +1 -1
- package/src/assets/web-panel/.build-hash +1 -1
- package/src/assets/web-panel/assets/Analytics-B4OM8S8X.css +1 -0
- package/src/assets/web-panel/assets/Analytics-sBrYoc3A.js +3 -0
- package/src/assets/web-panel/assets/AppLayout-BhJ3YFWt.js +1 -0
- package/src/assets/web-panel/assets/AppLayout-Cr2lWhF-.css +1 -0
- package/src/assets/web-panel/assets/Backup-D68fenbD.js +1 -0
- package/src/assets/web-panel/assets/Backup-fZqtfC1m.css +1 -0
- package/src/assets/web-panel/assets/{Chat-DXtvKoM0.js → Chat-DaxTP3x8.js} +1 -1
- package/src/assets/web-panel/assets/{Cron-BJ4ODHOy.js → Cron-CNs03iHJ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BZd4wDPQ.js → Dashboard-CjlX4CrX.js} +2 -2
- package/src/assets/web-panel/assets/Git-CCMVr3Y8.js +2 -0
- package/src/assets/web-panel/assets/Git-DGcuBXST.css +1 -0
- package/src/assets/web-panel/assets/{Logs-CSeKZEG_.js → Logs-BY6A0UNG.js} +2 -2
- package/src/assets/web-panel/assets/{McpTools-BYQAK11r.js → McpTools-CrBVYlg6.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-gkUAPyuZ.js → Memory-CWx3SpUt.js} +2 -2
- package/src/assets/web-panel/assets/{Notes-bjNrQgAo.js → Notes-1LcGD49x.js} +2 -2
- package/src/assets/web-panel/assets/Organization-DdOOM4ic.css +1 -0
- package/src/assets/web-panel/assets/Organization-Dx2DhbkM.js +4 -0
- package/src/assets/web-panel/assets/P2P-B16fjqfJ.js +2 -0
- package/src/assets/web-panel/assets/P2P-OEzOeMZX.css +1 -0
- package/src/assets/web-panel/assets/Permissions-BQbC9FzG.js +4 -0
- package/src/assets/web-panel/assets/Permissions-C9WlkGl-.css +1 -0
- package/src/assets/web-panel/assets/Projects-CjhZbNYm.js +2 -0
- package/src/assets/web-panel/assets/Projects-DxKelI5h.css +1 -0
- package/src/assets/web-panel/assets/Providers-BEakqcO5.css +1 -0
- package/src/assets/web-panel/assets/Providers-ivOAQtHM.js +2 -0
- package/src/assets/web-panel/assets/RssFeed-BlFC20eg.css +1 -0
- package/src/assets/web-panel/assets/RssFeed-BrsErdrU.js +3 -0
- package/src/assets/web-panel/assets/Security-DnEvJU5h.js +4 -0
- package/src/assets/web-panel/assets/Security-Dwxw7rfP.css +1 -0
- package/src/assets/web-panel/assets/{Services-CS0oMdxh.js → Services-7jQywNbl.js} +2 -2
- package/src/assets/web-panel/assets/Skills-BCvgBkD3.js +1 -0
- package/src/assets/web-panel/assets/{Tasks-qULws8pc.js → Tasks-CmJBC1cf.js} +1 -1
- package/src/assets/web-panel/assets/Templates-DOY_oZnm.css +1 -0
- package/src/assets/web-panel/assets/Templates-RXT8-DNk.js +1 -0
- package/src/assets/web-panel/assets/Wallet-3iYASEx_.js +4 -0
- package/src/assets/web-panel/assets/Wallet-DnIumafl.css +1 -0
- package/src/assets/web-panel/assets/WebAuthn-CNPl2VQR.css +1 -0
- package/src/assets/web-panel/assets/WebAuthn-s3Hzd9db.js +5 -0
- package/src/assets/web-panel/assets/{antd-CJSBocer.js → antd-gZyc63Qr.js} +114 -114
- package/src/assets/web-panel/assets/chat-BmwHBi9M.js +1 -0
- package/src/assets/web-panel/assets/index-DrmEk9S3.js +2 -0
- package/src/assets/web-panel/assets/{markdown-Bo5cVN4u.js → markdown-Bv7nG63L.js} +1 -1
- package/src/assets/web-panel/assets/ws-CU7Gvoom.js +1 -0
- package/src/assets/web-panel/index.html +2 -2
- package/src/commands/doctor.js +33 -151
- package/src/commands/mcp.js +1 -1
- package/src/commands/plugin.js +1 -1
- package/src/commands/session.js +106 -7
- package/src/commands/status.js +39 -69
- package/src/gateways/ws/session-protocol.js +1 -1
- package/src/gateways/ws/ws-agent-handler.js +484 -0
- package/src/gateways/ws/ws-server.js +758 -4
- package/src/gateways/ws/ws-session-gateway.js +1432 -1
- package/src/harness/mcp-client.js +417 -0
- package/src/harness/mock-llm-provider.js +167 -0
- package/src/harness/plugin-manager.js +434 -0
- package/src/lib/agent-core.js +25 -1902
- package/src/lib/hashline.js +208 -0
- package/src/lib/jsonl-session-store.js +11 -0
- package/src/lib/mcp-client.js +14 -412
- package/src/lib/plugin-manager.js +29 -428
- package/src/lib/prompt-compressor.js +11 -0
- package/src/lib/session-hooks.js +61 -0
- package/src/lib/skill-loader.js +4 -0
- package/src/lib/skill-mcp.js +190 -0
- package/src/lib/workflow-state-reader.js +94 -0
- package/src/lib/ws-agent-handler.js +8 -472
- package/src/lib/ws-server.js +12 -756
- package/src/lib/ws-session-manager.js +8 -1417
- package/src/repl/agent-repl.js +27 -3
- package/src/runtime/agent-core.js +1760 -0
- package/src/runtime/agent-runtime.js +3 -1
- package/src/runtime/coding-agent-contract-shared.cjs +496 -0
- package/src/runtime/coding-agent-contract.js +49 -229
- package/src/runtime/coding-agent-policy.cjs +54 -5
- package/src/runtime/diagnostics.js +317 -0
- package/src/runtime/index.js +3 -0
- package/src/tools/index.js +3 -0
- package/src/tools/legacy-agent-tools.js +5 -0
- package/src/assets/web-panel/assets/AppLayout-B_tkw3Pn.js +0 -1
- package/src/assets/web-panel/assets/AppLayout-CFP4dGIJ.css +0 -1
- package/src/assets/web-panel/assets/Providers-Brm-S_hS.css +0 -1
- package/src/assets/web-panel/assets/Providers-Dbf57Tbv.js +0 -1
- package/src/assets/web-panel/assets/Skills-B2fgruv8.js +0 -1
- package/src/assets/web-panel/assets/chat-DnH09sSR.js +0 -1
- package/src/assets/web-panel/assets/index-IK-oro0g.js +0 -2
- package/src/assets/web-panel/assets/ws-DjelKkD6.js +0 -1
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin Manager — Plugin installation, management, and marketplace for CLI.
|
|
3
|
+
* Manages plugin lifecycle: install, enable, disable, remove, update.
|
|
4
|
+
*
|
|
5
|
+
* Canonical location (moved from src/lib/plugin-manager.js as part of the
|
|
6
|
+
* CLI Runtime Convergence roadmap, Phase 4). src/lib/plugin-manager.js is
|
|
7
|
+
* now a thin re-export shim for backwards compatibility.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import crypto from "crypto";
|
|
11
|
+
import fs from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import { getElectronUserDataDir } from "../lib/paths.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Ensure plugin tables exist.
|
|
17
|
+
*/
|
|
18
|
+
export function ensurePluginTables(db) {
|
|
19
|
+
db.exec(`
|
|
20
|
+
CREATE TABLE IF NOT EXISTS plugins (
|
|
21
|
+
id TEXT PRIMARY KEY,
|
|
22
|
+
name TEXT NOT NULL UNIQUE,
|
|
23
|
+
version TEXT NOT NULL,
|
|
24
|
+
description TEXT,
|
|
25
|
+
author TEXT,
|
|
26
|
+
homepage TEXT,
|
|
27
|
+
entry_point TEXT,
|
|
28
|
+
permissions TEXT,
|
|
29
|
+
status TEXT DEFAULT 'installed',
|
|
30
|
+
enabled INTEGER DEFAULT 1,
|
|
31
|
+
install_path TEXT,
|
|
32
|
+
installed_at TEXT DEFAULT (datetime('now')),
|
|
33
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
34
|
+
)
|
|
35
|
+
`);
|
|
36
|
+
db.exec(`
|
|
37
|
+
CREATE TABLE IF NOT EXISTS plugin_settings (
|
|
38
|
+
id TEXT PRIMARY KEY,
|
|
39
|
+
plugin_id TEXT NOT NULL,
|
|
40
|
+
key TEXT NOT NULL,
|
|
41
|
+
value TEXT
|
|
42
|
+
)
|
|
43
|
+
`);
|
|
44
|
+
db.exec(`
|
|
45
|
+
CREATE TABLE IF NOT EXISTS plugin_skills (
|
|
46
|
+
id TEXT PRIMARY KEY,
|
|
47
|
+
plugin_name TEXT NOT NULL,
|
|
48
|
+
skill_name TEXT NOT NULL,
|
|
49
|
+
skill_path TEXT NOT NULL,
|
|
50
|
+
installed_at TEXT DEFAULT (datetime('now'))
|
|
51
|
+
)
|
|
52
|
+
`);
|
|
53
|
+
db.exec(`
|
|
54
|
+
CREATE TABLE IF NOT EXISTS plugin_registry (
|
|
55
|
+
name TEXT PRIMARY KEY,
|
|
56
|
+
latest_version TEXT,
|
|
57
|
+
description TEXT,
|
|
58
|
+
author TEXT,
|
|
59
|
+
downloads INTEGER DEFAULT 0,
|
|
60
|
+
rating REAL DEFAULT 0,
|
|
61
|
+
tags TEXT,
|
|
62
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
63
|
+
)
|
|
64
|
+
`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Install a plugin (record in DB).
|
|
69
|
+
*/
|
|
70
|
+
export function installPlugin(db, pluginInfo) {
|
|
71
|
+
ensurePluginTables(db);
|
|
72
|
+
const {
|
|
73
|
+
name,
|
|
74
|
+
version,
|
|
75
|
+
description,
|
|
76
|
+
author,
|
|
77
|
+
homepage,
|
|
78
|
+
entryPoint,
|
|
79
|
+
permissions,
|
|
80
|
+
installPath,
|
|
81
|
+
} = pluginInfo;
|
|
82
|
+
|
|
83
|
+
if (!name || !version) {
|
|
84
|
+
throw new Error("Plugin name and version are required");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if already installed
|
|
88
|
+
const existing = getPlugin(db, name);
|
|
89
|
+
if (existing) {
|
|
90
|
+
throw new Error(`Plugin already installed: ${name}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const id = `plugin-${crypto.randomBytes(8).toString("hex")}`;
|
|
94
|
+
|
|
95
|
+
db.prepare(
|
|
96
|
+
`INSERT INTO plugins (id, name, version, description, author, homepage, entry_point, permissions, status, enabled, install_path)
|
|
97
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
98
|
+
).run(
|
|
99
|
+
id,
|
|
100
|
+
name,
|
|
101
|
+
version,
|
|
102
|
+
description || null,
|
|
103
|
+
author || null,
|
|
104
|
+
homepage || null,
|
|
105
|
+
entryPoint || null,
|
|
106
|
+
permissions ? JSON.stringify(permissions) : null,
|
|
107
|
+
"installed",
|
|
108
|
+
1,
|
|
109
|
+
installPath || null,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
id,
|
|
114
|
+
name,
|
|
115
|
+
version,
|
|
116
|
+
description,
|
|
117
|
+
author,
|
|
118
|
+
status: "installed",
|
|
119
|
+
enabled: true,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Get a plugin by name.
|
|
125
|
+
*/
|
|
126
|
+
export function getPlugin(db, name) {
|
|
127
|
+
ensurePluginTables(db);
|
|
128
|
+
return db.prepare("SELECT * FROM plugins WHERE name = ?").get(name);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Get a plugin by ID.
|
|
133
|
+
*/
|
|
134
|
+
export function getPluginById(db, pluginId) {
|
|
135
|
+
ensurePluginTables(db);
|
|
136
|
+
return db.prepare("SELECT * FROM plugins WHERE id = ?").get(pluginId);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* List all installed plugins.
|
|
141
|
+
*/
|
|
142
|
+
export function listPlugins(db, options = {}) {
|
|
143
|
+
ensurePluginTables(db);
|
|
144
|
+
const { enabledOnly = false } = options;
|
|
145
|
+
|
|
146
|
+
if (enabledOnly) {
|
|
147
|
+
return db
|
|
148
|
+
.prepare("SELECT * FROM plugins WHERE enabled = 1 ORDER BY name ASC")
|
|
149
|
+
.all();
|
|
150
|
+
}
|
|
151
|
+
return db.prepare("SELECT * FROM plugins ORDER BY name ASC").all();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Enable a plugin.
|
|
156
|
+
*/
|
|
157
|
+
export function enablePlugin(db, name) {
|
|
158
|
+
ensurePluginTables(db);
|
|
159
|
+
const result = db
|
|
160
|
+
.prepare("UPDATE plugins SET enabled = ?, status = ? WHERE name = ?")
|
|
161
|
+
.run(1, "installed", name);
|
|
162
|
+
return result.changes > 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Disable a plugin.
|
|
167
|
+
*/
|
|
168
|
+
export function disablePlugin(db, name) {
|
|
169
|
+
ensurePluginTables(db);
|
|
170
|
+
const result = db
|
|
171
|
+
.prepare("UPDATE plugins SET enabled = ?, status = ? WHERE name = ?")
|
|
172
|
+
.run(0, "disabled", name);
|
|
173
|
+
return result.changes > 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Remove (uninstall) a plugin.
|
|
178
|
+
*/
|
|
179
|
+
export function removePlugin(db, name) {
|
|
180
|
+
ensurePluginTables(db);
|
|
181
|
+
// Remove settings first
|
|
182
|
+
const plugin = getPlugin(db, name);
|
|
183
|
+
if (!plugin) return false;
|
|
184
|
+
|
|
185
|
+
db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ?").run(plugin.id);
|
|
186
|
+
const result = db.prepare("DELETE FROM plugins WHERE name = ?").run(name);
|
|
187
|
+
return result.changes > 0;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Update a plugin version.
|
|
192
|
+
*/
|
|
193
|
+
export function updatePlugin(db, name, newVersion) {
|
|
194
|
+
ensurePluginTables(db);
|
|
195
|
+
const result = db
|
|
196
|
+
.prepare(
|
|
197
|
+
"UPDATE plugins SET version = ?, updated_at = datetime('now') WHERE name = ?",
|
|
198
|
+
)
|
|
199
|
+
.run(newVersion, name);
|
|
200
|
+
return result.changes > 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Set a plugin setting.
|
|
205
|
+
*/
|
|
206
|
+
export function setPluginSetting(db, pluginName, key, value) {
|
|
207
|
+
ensurePluginTables(db);
|
|
208
|
+
const plugin = getPlugin(db, pluginName);
|
|
209
|
+
if (!plugin) throw new Error(`Plugin not found: ${pluginName}`);
|
|
210
|
+
|
|
211
|
+
// Remove existing setting for this key
|
|
212
|
+
db.prepare("DELETE FROM plugin_settings WHERE plugin_id = ? AND key = ?").run(
|
|
213
|
+
plugin.id,
|
|
214
|
+
key,
|
|
215
|
+
);
|
|
216
|
+
const settingId = `ps-${crypto.randomBytes(4).toString("hex")}`;
|
|
217
|
+
db.prepare(
|
|
218
|
+
`INSERT INTO plugin_settings (id, plugin_id, key, value) VALUES (?, ?, ?, ?)`,
|
|
219
|
+
).run(
|
|
220
|
+
settingId,
|
|
221
|
+
plugin.id,
|
|
222
|
+
key,
|
|
223
|
+
typeof value === "string" ? value : JSON.stringify(value),
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get a plugin setting.
|
|
231
|
+
*/
|
|
232
|
+
export function getPluginSetting(db, pluginName, key) {
|
|
233
|
+
ensurePluginTables(db);
|
|
234
|
+
const plugin = getPlugin(db, pluginName);
|
|
235
|
+
if (!plugin) return null;
|
|
236
|
+
|
|
237
|
+
const row = db
|
|
238
|
+
.prepare(
|
|
239
|
+
"SELECT value FROM plugin_settings WHERE plugin_id = ? AND key = ?",
|
|
240
|
+
)
|
|
241
|
+
.get(plugin.id, key);
|
|
242
|
+
return row ? row.value : null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Get all settings for a plugin.
|
|
247
|
+
*/
|
|
248
|
+
export function getPluginSettings(db, pluginName) {
|
|
249
|
+
ensurePluginTables(db);
|
|
250
|
+
const plugin = getPlugin(db, pluginName);
|
|
251
|
+
if (!plugin) return {};
|
|
252
|
+
|
|
253
|
+
const rows = db
|
|
254
|
+
.prepare("SELECT key, value FROM plugin_settings WHERE plugin_id = ?")
|
|
255
|
+
.all(plugin.id);
|
|
256
|
+
const settings = {};
|
|
257
|
+
for (const row of rows) {
|
|
258
|
+
settings[row.key] = row.value;
|
|
259
|
+
}
|
|
260
|
+
return settings;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ─── Registry / Marketplace ─────────────────────────────
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Add/update a plugin in the registry.
|
|
267
|
+
*/
|
|
268
|
+
export function registerInMarketplace(db, pluginInfo) {
|
|
269
|
+
ensurePluginTables(db);
|
|
270
|
+
const { name, latestVersion, description, author, tags } = pluginInfo;
|
|
271
|
+
|
|
272
|
+
db.prepare(
|
|
273
|
+
`INSERT OR REPLACE INTO plugin_registry (name, latest_version, description, author, tags)
|
|
274
|
+
VALUES (?, ?, ?, ?, ?)`,
|
|
275
|
+
).run(
|
|
276
|
+
name,
|
|
277
|
+
latestVersion,
|
|
278
|
+
description || null,
|
|
279
|
+
author || null,
|
|
280
|
+
tags ? JSON.stringify(tags) : null,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
return { name, latestVersion, description, author };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Search plugins in registry.
|
|
288
|
+
*/
|
|
289
|
+
export function searchRegistry(db, query) {
|
|
290
|
+
ensurePluginTables(db);
|
|
291
|
+
return db
|
|
292
|
+
.prepare(
|
|
293
|
+
"SELECT * FROM plugin_registry WHERE name LIKE ? OR description LIKE ? ORDER BY downloads DESC",
|
|
294
|
+
)
|
|
295
|
+
.all(`%${query}%`, `%${query}%`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* List all registry plugins.
|
|
300
|
+
*/
|
|
301
|
+
export function listRegistry(db) {
|
|
302
|
+
ensurePluginTables(db);
|
|
303
|
+
return db
|
|
304
|
+
.prepare("SELECT * FROM plugin_registry ORDER BY downloads DESC")
|
|
305
|
+
.all();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get plugin summary.
|
|
310
|
+
*/
|
|
311
|
+
export function getPluginSummary(db) {
|
|
312
|
+
ensurePluginTables(db);
|
|
313
|
+
const total = db.prepare("SELECT COUNT(*) as c FROM plugins").get();
|
|
314
|
+
const enabled = db
|
|
315
|
+
.prepare("SELECT COUNT(*) as c FROM plugins WHERE enabled = ?")
|
|
316
|
+
.get(1);
|
|
317
|
+
const registry = db
|
|
318
|
+
.prepare("SELECT COUNT(*) as c FROM plugin_registry")
|
|
319
|
+
.get();
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
installed: total?.c || 0,
|
|
323
|
+
enabled: enabled?.c || 0,
|
|
324
|
+
registryCount: registry?.c || 0,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ─── Plugin Skills ──────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get the marketplace skills directory
|
|
332
|
+
*/
|
|
333
|
+
function getMarketplaceSkillsDir() {
|
|
334
|
+
return path.join(getElectronUserDataDir(), "marketplace", "skills");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Install skills from a plugin manifest.
|
|
339
|
+
* Copies skill directories to the marketplace/skills/ layer.
|
|
340
|
+
*
|
|
341
|
+
* @param {object} db - Database instance
|
|
342
|
+
* @param {string} pluginName - Plugin name
|
|
343
|
+
* @param {string} pluginPath - Root path of the plugin package
|
|
344
|
+
* @param {{ name: string, path: string }[]} skills - Skills declared in manifest
|
|
345
|
+
* @returns {{ installed: string[] }} Names of installed skills
|
|
346
|
+
*/
|
|
347
|
+
export function installPluginSkills(db, pluginName, pluginPath, skills) {
|
|
348
|
+
ensurePluginTables(db);
|
|
349
|
+
if (!skills || skills.length === 0) return { installed: [] };
|
|
350
|
+
|
|
351
|
+
const marketplaceDir = getMarketplaceSkillsDir();
|
|
352
|
+
const installed = [];
|
|
353
|
+
|
|
354
|
+
for (const skill of skills) {
|
|
355
|
+
const srcDir = path.resolve(pluginPath, skill.path);
|
|
356
|
+
if (!fs.existsSync(srcDir)) continue;
|
|
357
|
+
|
|
358
|
+
const destDir = path.join(marketplaceDir, skill.name);
|
|
359
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
360
|
+
|
|
361
|
+
// Copy skill files
|
|
362
|
+
_copyDirSync(srcDir, destDir);
|
|
363
|
+
|
|
364
|
+
// Record in DB
|
|
365
|
+
const id = `ps-${crypto.randomBytes(6).toString("hex")}`;
|
|
366
|
+
db.prepare(
|
|
367
|
+
`INSERT OR REPLACE INTO plugin_skills (id, plugin_name, skill_name, skill_path)
|
|
368
|
+
VALUES (?, ?, ?, ?)`,
|
|
369
|
+
).run(id, pluginName, skill.name, destDir);
|
|
370
|
+
|
|
371
|
+
installed.push(skill.name);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return { installed };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Remove all skills installed by a plugin.
|
|
379
|
+
*
|
|
380
|
+
* @param {object} db - Database instance
|
|
381
|
+
* @param {string} pluginName - Plugin name
|
|
382
|
+
* @returns {{ removed: string[] }} Names of removed skills
|
|
383
|
+
*/
|
|
384
|
+
export function removePluginSkills(db, pluginName) {
|
|
385
|
+
ensurePluginTables(db);
|
|
386
|
+
const rows = db
|
|
387
|
+
.prepare("SELECT * FROM plugin_skills WHERE plugin_name = ?")
|
|
388
|
+
.all(pluginName);
|
|
389
|
+
|
|
390
|
+
const removed = [];
|
|
391
|
+
for (const row of rows) {
|
|
392
|
+
// Remove the skill directory
|
|
393
|
+
if (fs.existsSync(row.skill_path)) {
|
|
394
|
+
fs.rmSync(row.skill_path, { recursive: true, force: true });
|
|
395
|
+
}
|
|
396
|
+
removed.push(row.skill_name);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
db.prepare("DELETE FROM plugin_skills WHERE plugin_name = ?").run(pluginName);
|
|
400
|
+
return { removed };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* List skills installed by a specific plugin.
|
|
405
|
+
*
|
|
406
|
+
* @param {object} db - Database instance
|
|
407
|
+
* @param {string} pluginName - Plugin name
|
|
408
|
+
* @returns {{ skill_name: string, skill_path: string }[]}
|
|
409
|
+
*/
|
|
410
|
+
export function getPluginSkills(db, pluginName) {
|
|
411
|
+
ensurePluginTables(db);
|
|
412
|
+
return db
|
|
413
|
+
.prepare(
|
|
414
|
+
"SELECT skill_name, skill_path FROM plugin_skills WHERE plugin_name = ?",
|
|
415
|
+
)
|
|
416
|
+
.all(pluginName);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Recursively copy a directory
|
|
421
|
+
*/
|
|
422
|
+
function _copyDirSync(src, dest) {
|
|
423
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
424
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
const srcPath = path.join(src, entry.name);
|
|
427
|
+
const destPath = path.join(dest, entry.name);
|
|
428
|
+
if (entry.isDirectory()) {
|
|
429
|
+
_copyDirSync(srcPath, destPath);
|
|
430
|
+
} else {
|
|
431
|
+
fs.copyFileSync(srcPath, destPath);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|