@memoryblock/plugin-installer 0.1.0-beta
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/LICENSE +21 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/installer.d.ts +79 -0
- package/dist/installer.d.ts.map +1 -0
- package/dist/installer.js +203 -0
- package/dist/installer.js.map +1 -0
- package/package.json +17 -0
- package/registry/plugins.json +74 -0
- package/src/index.ts +8 -0
- package/src/installer.ts +246 -0
- package/tsconfig.json +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 memoryblock
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @memoryblock/plugin-installer — single-command plugin management.
|
|
3
|
+
*
|
|
4
|
+
* `mblk add web-search` — installs the plugin + adds tools to block config
|
|
5
|
+
* `mblk remove web-search` — uninstalls the plugin + removes tools from config
|
|
6
|
+
*/
|
|
7
|
+
export { PluginInstaller, type PluginEntry, type SettingsField } from './installer.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAE,KAAK,WAAW,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @memoryblock/plugin-installer — single-command plugin management.
|
|
3
|
+
*
|
|
4
|
+
* `mblk add web-search` — installs the plugin + adds tools to block config
|
|
5
|
+
* `mblk remove web-search` — uninstalls the plugin + removes tools from config
|
|
6
|
+
*/
|
|
7
|
+
export { PluginInstaller } from './installer.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,eAAe,EAAwC,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/** Settings field types supported in plugin manifests. */
|
|
2
|
+
export interface SettingsField {
|
|
3
|
+
type: 'text' | 'password' | 'number' | 'select' | 'toggle';
|
|
4
|
+
label: string;
|
|
5
|
+
default?: string | number | boolean;
|
|
6
|
+
placeholder?: string;
|
|
7
|
+
options?: string[];
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface PluginEntry {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
package: string;
|
|
16
|
+
version: string;
|
|
17
|
+
toolNames: string[];
|
|
18
|
+
requiresAuth: string[];
|
|
19
|
+
category: string;
|
|
20
|
+
status?: string;
|
|
21
|
+
core?: boolean;
|
|
22
|
+
blockSpecific?: boolean;
|
|
23
|
+
settings?: Record<string, SettingsField>;
|
|
24
|
+
}
|
|
25
|
+
interface PluginRegistry {
|
|
26
|
+
plugins: PluginEntry[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Plugin installer — reads from the official registry and manages
|
|
30
|
+
* installation of npm packages + block config updates.
|
|
31
|
+
* Extended with settings storage and core plugin support.
|
|
32
|
+
*/
|
|
33
|
+
export declare class PluginInstaller {
|
|
34
|
+
private registry;
|
|
35
|
+
private registryPath;
|
|
36
|
+
constructor();
|
|
37
|
+
/** Load the plugin registry. */
|
|
38
|
+
loadRegistry(): Promise<PluginRegistry>;
|
|
39
|
+
/** List all available plugins. */
|
|
40
|
+
listPlugins(): Promise<PluginEntry[]>;
|
|
41
|
+
/** Find a plugin by ID. */
|
|
42
|
+
findPlugin(id: string): Promise<PluginEntry | undefined>;
|
|
43
|
+
/**
|
|
44
|
+
* Install a plugin:
|
|
45
|
+
* 1. npm install the package
|
|
46
|
+
* 2. Add tool names to the block's config.json if provided
|
|
47
|
+
*/
|
|
48
|
+
install(pluginId: string, options?: {
|
|
49
|
+
blockConfigPath?: string;
|
|
50
|
+
cwd?: string;
|
|
51
|
+
onLog?: (chunk: string) => void;
|
|
52
|
+
}): Promise<{
|
|
53
|
+
success: boolean;
|
|
54
|
+
message: string;
|
|
55
|
+
plugin?: PluginEntry;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Remove a plugin:
|
|
59
|
+
* 1. Check if it's a core plugin (can't remove)
|
|
60
|
+
* 2. npm uninstall the package
|
|
61
|
+
* 3. Remove tool names from block config if provided
|
|
62
|
+
*/
|
|
63
|
+
remove(pluginId: string, options?: {
|
|
64
|
+
blockConfigPath?: string;
|
|
65
|
+
cwd?: string;
|
|
66
|
+
onLog?: (chunk: string) => void;
|
|
67
|
+
}): Promise<{
|
|
68
|
+
success: boolean;
|
|
69
|
+
message: string;
|
|
70
|
+
}>;
|
|
71
|
+
/** Get the workspace-level settings file path for a plugin. */
|
|
72
|
+
private pluginSettingsPath;
|
|
73
|
+
/** Load settings for a plugin. Returns defaults merged with saved values. */
|
|
74
|
+
getPluginSettings(pluginId: string, workspacePath?: string): Promise<Record<string, unknown>>;
|
|
75
|
+
/** Save settings for a plugin. */
|
|
76
|
+
savePluginSettings(pluginId: string, values: Record<string, unknown>, workspacePath?: string): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
export {};
|
|
79
|
+
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"AAKA,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC1B,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3D,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;CAC5C;AAED,UAAU,cAAc;IACpB,OAAO,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED;;;;GAIG;AACH,qBAAa,eAAe;IACxB,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,YAAY,CAAS;;IAQ7B,gCAAgC;IAC1B,YAAY,IAAI,OAAO,CAAC,cAAc,CAAC;IAO7C,kCAAkC;IAC5B,WAAW,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAK3C,2BAA2B;IACrB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAK9D;;;;OAIG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;QAC5H,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,WAAW,CAAC;KACxB,CAAC;IA6DF;;;;;OAKG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;QAC3H,OAAO,EAAE,OAAO,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;KACnB,CAAC;IA+DF,+DAA+D;IAC/D,OAAO,CAAC,kBAAkB;IAK1B,6EAA6E;IACvE,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAoBnG,kCAAkC;IAC5B,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAKrH"}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
/**
|
|
6
|
+
* Plugin installer — reads from the official registry and manages
|
|
7
|
+
* installation of npm packages + block config updates.
|
|
8
|
+
* Extended with settings storage and core plugin support.
|
|
9
|
+
*/
|
|
10
|
+
export class PluginInstaller {
|
|
11
|
+
registry = null;
|
|
12
|
+
registryPath;
|
|
13
|
+
constructor() {
|
|
14
|
+
// Registry lives alongside this package's compiled output
|
|
15
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
this.registryPath = join(thisDir, '..', 'registry', 'plugins.json');
|
|
17
|
+
}
|
|
18
|
+
/** Load the plugin registry. */
|
|
19
|
+
async loadRegistry() {
|
|
20
|
+
if (this.registry)
|
|
21
|
+
return this.registry;
|
|
22
|
+
const raw = await readFile(this.registryPath, 'utf-8');
|
|
23
|
+
this.registry = JSON.parse(raw);
|
|
24
|
+
return this.registry;
|
|
25
|
+
}
|
|
26
|
+
/** List all available plugins. */
|
|
27
|
+
async listPlugins() {
|
|
28
|
+
const reg = await this.loadRegistry();
|
|
29
|
+
return reg.plugins;
|
|
30
|
+
}
|
|
31
|
+
/** Find a plugin by ID. */
|
|
32
|
+
async findPlugin(id) {
|
|
33
|
+
const reg = await this.loadRegistry();
|
|
34
|
+
return reg.plugins.find(p => p.id === id);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Install a plugin:
|
|
38
|
+
* 1. npm install the package
|
|
39
|
+
* 2. Add tool names to the block's config.json if provided
|
|
40
|
+
*/
|
|
41
|
+
async install(pluginId, options) {
|
|
42
|
+
const plugin = await this.findPlugin(pluginId);
|
|
43
|
+
if (!plugin) {
|
|
44
|
+
return { success: false, message: `Plugin "${pluginId}" not found in registry.` };
|
|
45
|
+
}
|
|
46
|
+
if (plugin.status === 'upcoming') {
|
|
47
|
+
return { success: false, message: `Plugin "${plugin.name}" is not yet available.` };
|
|
48
|
+
}
|
|
49
|
+
// Install npm package
|
|
50
|
+
const cwd = options?.cwd || process.cwd();
|
|
51
|
+
try {
|
|
52
|
+
await new Promise((resolve, reject) => {
|
|
53
|
+
const child = spawn('npm', ['install', `${plugin.package}@${plugin.version}`], { cwd, shell: true });
|
|
54
|
+
child.stdout.on('data', data => options?.onLog?.(data.toString()));
|
|
55
|
+
child.stderr.on('data', data => options?.onLog?.(data.toString()));
|
|
56
|
+
let done = false;
|
|
57
|
+
child.on('error', err => { if (!done) {
|
|
58
|
+
done = true;
|
|
59
|
+
reject(err);
|
|
60
|
+
} });
|
|
61
|
+
child.on('close', code => {
|
|
62
|
+
if (done)
|
|
63
|
+
return;
|
|
64
|
+
done = true;
|
|
65
|
+
if (code === 0)
|
|
66
|
+
resolve();
|
|
67
|
+
else
|
|
68
|
+
reject(new Error(`Exit code ${code}`));
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
return {
|
|
74
|
+
success: false,
|
|
75
|
+
message: `Failed to install ${plugin.package}: ${err.message}`,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
// Auto-update block config if path provided
|
|
79
|
+
if (options?.blockConfigPath && plugin.toolNames.length > 0) {
|
|
80
|
+
try {
|
|
81
|
+
const configRaw = await readFile(options.blockConfigPath, 'utf-8');
|
|
82
|
+
const config = JSON.parse(configRaw);
|
|
83
|
+
const enabled = config.tools?.enabled || [];
|
|
84
|
+
for (const tool of plugin.toolNames) {
|
|
85
|
+
if (!enabled.includes(tool)) {
|
|
86
|
+
enabled.push(tool);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
config.tools = { ...config.tools, enabled };
|
|
90
|
+
await writeFile(options.blockConfigPath, JSON.stringify(config, null, 4), 'utf-8');
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Config update is best-effort
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
success: true,
|
|
98
|
+
message: `Installed ${plugin.name} (${plugin.package}@${plugin.version})`,
|
|
99
|
+
plugin,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Remove a plugin:
|
|
104
|
+
* 1. Check if it's a core plugin (can't remove)
|
|
105
|
+
* 2. npm uninstall the package
|
|
106
|
+
* 3. Remove tool names from block config if provided
|
|
107
|
+
*/
|
|
108
|
+
async remove(pluginId, options) {
|
|
109
|
+
const plugin = await this.findPlugin(pluginId);
|
|
110
|
+
if (!plugin) {
|
|
111
|
+
return { success: false, message: `Plugin "${pluginId}" not found in registry.` };
|
|
112
|
+
}
|
|
113
|
+
if (plugin.core) {
|
|
114
|
+
return { success: false, message: `Plugin "${plugin.name}" is a core plugin and cannot be removed.` };
|
|
115
|
+
}
|
|
116
|
+
const cwd = options?.cwd || process.cwd();
|
|
117
|
+
try {
|
|
118
|
+
await new Promise((resolve, reject) => {
|
|
119
|
+
const child = spawn('npm', ['uninstall', plugin.package], { cwd, shell: true });
|
|
120
|
+
child.stdout.on('data', data => options?.onLog?.(data.toString()));
|
|
121
|
+
child.stderr.on('data', data => options?.onLog?.(data.toString()));
|
|
122
|
+
let done = false;
|
|
123
|
+
child.on('error', err => { if (!done) {
|
|
124
|
+
done = true;
|
|
125
|
+
reject(err);
|
|
126
|
+
} });
|
|
127
|
+
child.on('close', code => {
|
|
128
|
+
if (done)
|
|
129
|
+
return;
|
|
130
|
+
done = true;
|
|
131
|
+
if (code === 0)
|
|
132
|
+
resolve();
|
|
133
|
+
else
|
|
134
|
+
reject(new Error(`Exit code ${code}`));
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Uninstall failure is non-critical
|
|
140
|
+
options?.onLog?.('\nNote: npm uninstall encountered an error, but cleanup will proceed.\n');
|
|
141
|
+
}
|
|
142
|
+
// Clean up block config
|
|
143
|
+
if (options?.blockConfigPath && plugin.toolNames.length > 0) {
|
|
144
|
+
try {
|
|
145
|
+
const configRaw = await readFile(options.blockConfigPath, 'utf-8');
|
|
146
|
+
const config = JSON.parse(configRaw);
|
|
147
|
+
const enabled = config.tools?.enabled || [];
|
|
148
|
+
config.tools = {
|
|
149
|
+
...config.tools,
|
|
150
|
+
enabled: enabled.filter(t => !plugin.toolNames.includes(t)),
|
|
151
|
+
};
|
|
152
|
+
await writeFile(options.blockConfigPath, JSON.stringify(config, null, 4), 'utf-8');
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
// Config cleanup is best-effort
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Clean up settings
|
|
159
|
+
try {
|
|
160
|
+
const settingsPath = this.pluginSettingsPath(pluginId);
|
|
161
|
+
const { unlink } = await import('node:fs/promises');
|
|
162
|
+
await unlink(settingsPath);
|
|
163
|
+
}
|
|
164
|
+
catch { /* ignore */ }
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
message: `Removed ${plugin.name} (${plugin.package})`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// ===== Settings Storage =====
|
|
171
|
+
/** Get the workspace-level settings file path for a plugin. */
|
|
172
|
+
pluginSettingsPath(pluginId, workspacePath) {
|
|
173
|
+
const base = workspacePath || process.cwd();
|
|
174
|
+
return join(base, 'plugin-settings', `${pluginId}.json`);
|
|
175
|
+
}
|
|
176
|
+
/** Load settings for a plugin. Returns defaults merged with saved values. */
|
|
177
|
+
async getPluginSettings(pluginId, workspacePath) {
|
|
178
|
+
const plugin = await this.findPlugin(pluginId);
|
|
179
|
+
if (!plugin?.settings)
|
|
180
|
+
return {};
|
|
181
|
+
// Start with defaults
|
|
182
|
+
const defaults = {};
|
|
183
|
+
for (const [key, field] of Object.entries(plugin.settings)) {
|
|
184
|
+
defaults[key] = field.default ?? '';
|
|
185
|
+
}
|
|
186
|
+
// Merge saved values
|
|
187
|
+
try {
|
|
188
|
+
const raw = await readFile(this.pluginSettingsPath(pluginId, workspacePath), 'utf-8');
|
|
189
|
+
const saved = JSON.parse(raw);
|
|
190
|
+
return { ...defaults, ...saved };
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return defaults;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Save settings for a plugin. */
|
|
197
|
+
async savePluginSettings(pluginId, values, workspacePath) {
|
|
198
|
+
const settingsPath = this.pluginSettingsPath(pluginId, workspacePath);
|
|
199
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
200
|
+
await writeFile(settingsPath, JSON.stringify(values, null, 4), 'utf-8');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
//# sourceMappingURL=installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"installer.js","sourceRoot":"","sources":["../src/installer.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memoryblock/plugin-installer",
|
|
3
|
+
"version": "0.1.0-beta",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"memoryblock": "0.1.0-beta"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc -p tsconfig.json"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": [
|
|
3
|
+
{
|
|
4
|
+
"id": "agents",
|
|
5
|
+
"name": "Multi-Agent Orchestration",
|
|
6
|
+
"description": "Spawn and manage sub-agents from within a block",
|
|
7
|
+
"package": "@memoryblock/plugin-agents",
|
|
8
|
+
"version": "0.1.0",
|
|
9
|
+
"toolNames": ["create_agent", "list_agents", "query_agent"],
|
|
10
|
+
"requiresAuth": [],
|
|
11
|
+
"category": "core",
|
|
12
|
+
"core": true,
|
|
13
|
+
"blockSpecific": true,
|
|
14
|
+
"settings": {
|
|
15
|
+
"maxSubAgents": { "type": "number", "label": "Max sub-agents per block", "default": 3, "min": 1, "max": 10 },
|
|
16
|
+
"subAgentTimeout": { "type": "number", "label": "Sub-agent timeout (seconds)", "default": 120, "min": 10, "max": 600 }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "web-search",
|
|
21
|
+
"name": "Web Search",
|
|
22
|
+
"description": "Search the web using your configured provider",
|
|
23
|
+
"package": "@memoryblock/plugin-web-search",
|
|
24
|
+
"version": "0.1.0",
|
|
25
|
+
"toolNames": ["web_search"],
|
|
26
|
+
"requiresAuth": ["brave"],
|
|
27
|
+
"category": "search",
|
|
28
|
+
"settings": {
|
|
29
|
+
"maxResults": { "type": "number", "label": "Max results per search", "default": 5, "min": 1, "max": 20 }
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "aws",
|
|
34
|
+
"name": "AWS Tools",
|
|
35
|
+
"description": "AWS SDK client factory for service integration",
|
|
36
|
+
"package": "@memoryblock/plugin-aws",
|
|
37
|
+
"version": "0.1.0",
|
|
38
|
+
"toolNames": [],
|
|
39
|
+
"requiresAuth": ["aws"],
|
|
40
|
+
"category": "cloud",
|
|
41
|
+
"settings": {
|
|
42
|
+
"defaultRegion": { "type": "text", "label": "Default AWS Region", "default": "us-east-1", "placeholder": "us-east-1" }
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "fetch-webpage",
|
|
47
|
+
"name": "Fetch Webpage",
|
|
48
|
+
"description": "Extract and summarize content from web pages",
|
|
49
|
+
"package": "@memoryblock/plugin-fetch-webpage",
|
|
50
|
+
"version": "0.1.0",
|
|
51
|
+
"toolNames": ["fetch_webpage"],
|
|
52
|
+
"requiresAuth": [],
|
|
53
|
+
"category": "web",
|
|
54
|
+
"status": "upcoming"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "containers",
|
|
58
|
+
"name": "Container Isolation",
|
|
59
|
+
"description": "Run tools inside Podman containers for OS-level isolation",
|
|
60
|
+
"package": "@memoryblock/plugin-containers",
|
|
61
|
+
"version": "0.1.0",
|
|
62
|
+
"toolNames": [],
|
|
63
|
+
"requiresAuth": [],
|
|
64
|
+
"category": "security",
|
|
65
|
+
"status": "upcoming",
|
|
66
|
+
"settings": {
|
|
67
|
+
"runtime": { "type": "select", "label": "Container Runtime", "default": "podman", "options": ["podman"] },
|
|
68
|
+
"defaultImage": { "type": "text", "label": "Default Image", "default": "memoryblock/sandbox:latest", "placeholder": "memoryblock/sandbox:latest" },
|
|
69
|
+
"memoryLimit": { "type": "text", "label": "Memory Limit", "default": "256m", "placeholder": "256m" },
|
|
70
|
+
"networkMode": { "type": "select", "label": "Network Mode", "default": "none", "options": ["none", "host", "bridge"] }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @memoryblock/plugin-installer — single-command plugin management.
|
|
3
|
+
*
|
|
4
|
+
* `mblk add web-search` — installs the plugin + adds tools to block config
|
|
5
|
+
* `mblk remove web-search` — uninstalls the plugin + removes tools from config
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export { PluginInstaller, type PluginEntry, type SettingsField } from './installer.js';
|
package/src/installer.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
|
|
6
|
+
/** Settings field types supported in plugin manifests. */
|
|
7
|
+
export interface SettingsField {
|
|
8
|
+
type: 'text' | 'password' | 'number' | 'select' | 'toggle';
|
|
9
|
+
label: string;
|
|
10
|
+
default?: string | number | boolean;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
options?: string[]; // For 'select' type
|
|
13
|
+
min?: number; // For 'number' type
|
|
14
|
+
max?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PluginEntry {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
package: string;
|
|
22
|
+
version: string;
|
|
23
|
+
toolNames: string[];
|
|
24
|
+
requiresAuth: string[];
|
|
25
|
+
category: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
core?: boolean; // Core plugins can't be uninstalled
|
|
28
|
+
blockSpecific?: boolean; // Plugin can have per-block settings
|
|
29
|
+
settings?: Record<string, SettingsField>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface PluginRegistry {
|
|
33
|
+
plugins: PluginEntry[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Plugin installer — reads from the official registry and manages
|
|
38
|
+
* installation of npm packages + block config updates.
|
|
39
|
+
* Extended with settings storage and core plugin support.
|
|
40
|
+
*/
|
|
41
|
+
export class PluginInstaller {
|
|
42
|
+
private registry: PluginRegistry | null = null;
|
|
43
|
+
private registryPath: string;
|
|
44
|
+
|
|
45
|
+
constructor() {
|
|
46
|
+
// Registry lives alongside this package's compiled output
|
|
47
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
48
|
+
this.registryPath = join(thisDir, '..', 'registry', 'plugins.json');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Load the plugin registry. */
|
|
52
|
+
async loadRegistry(): Promise<PluginRegistry> {
|
|
53
|
+
if (this.registry) return this.registry;
|
|
54
|
+
const raw = await readFile(this.registryPath, 'utf-8');
|
|
55
|
+
this.registry = JSON.parse(raw) as PluginRegistry;
|
|
56
|
+
return this.registry;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** List all available plugins. */
|
|
60
|
+
async listPlugins(): Promise<PluginEntry[]> {
|
|
61
|
+
const reg = await this.loadRegistry();
|
|
62
|
+
return reg.plugins;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Find a plugin by ID. */
|
|
66
|
+
async findPlugin(id: string): Promise<PluginEntry | undefined> {
|
|
67
|
+
const reg = await this.loadRegistry();
|
|
68
|
+
return reg.plugins.find(p => p.id === id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Install a plugin:
|
|
73
|
+
* 1. npm install the package
|
|
74
|
+
* 2. Add tool names to the block's config.json if provided
|
|
75
|
+
*/
|
|
76
|
+
async install(pluginId: string, options?: { blockConfigPath?: string; cwd?: string; onLog?: (chunk: string) => void }): Promise<{
|
|
77
|
+
success: boolean;
|
|
78
|
+
message: string;
|
|
79
|
+
plugin?: PluginEntry;
|
|
80
|
+
}> {
|
|
81
|
+
const plugin = await this.findPlugin(pluginId);
|
|
82
|
+
if (!plugin) {
|
|
83
|
+
return { success: false, message: `Plugin "${pluginId}" not found in registry.` };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (plugin.status === 'upcoming') {
|
|
87
|
+
return { success: false, message: `Plugin "${plugin.name}" is not yet available.` };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Install npm package
|
|
91
|
+
const cwd = options?.cwd || process.cwd();
|
|
92
|
+
try {
|
|
93
|
+
await new Promise<void>((resolve, reject) => {
|
|
94
|
+
const child = spawn('npm', ['install', `${plugin.package}@${plugin.version}`], { cwd, shell: true });
|
|
95
|
+
child.stdout.on('data', data => options?.onLog?.(data.toString()));
|
|
96
|
+
child.stderr.on('data', data => options?.onLog?.(data.toString()));
|
|
97
|
+
|
|
98
|
+
let done = false;
|
|
99
|
+
child.on('error', err => { if (!done) { done = true; reject(err); } });
|
|
100
|
+
child.on('close', code => {
|
|
101
|
+
if (done) return;
|
|
102
|
+
done = true;
|
|
103
|
+
if (code === 0) resolve();
|
|
104
|
+
else reject(new Error(`Exit code ${code}`));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
return {
|
|
109
|
+
success: false,
|
|
110
|
+
message: `Failed to install ${plugin.package}: ${(err as Error).message}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Auto-update block config if path provided
|
|
115
|
+
if (options?.blockConfigPath && plugin.toolNames.length > 0) {
|
|
116
|
+
try {
|
|
117
|
+
const configRaw = await readFile(options.blockConfigPath, 'utf-8');
|
|
118
|
+
const config = JSON.parse(configRaw);
|
|
119
|
+
const enabled = config.tools?.enabled || [];
|
|
120
|
+
|
|
121
|
+
for (const tool of plugin.toolNames) {
|
|
122
|
+
if (!enabled.includes(tool)) {
|
|
123
|
+
enabled.push(tool);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
config.tools = { ...config.tools, enabled };
|
|
128
|
+
await writeFile(options.blockConfigPath, JSON.stringify(config, null, 4), 'utf-8');
|
|
129
|
+
} catch {
|
|
130
|
+
// Config update is best-effort
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
message: `Installed ${plugin.name} (${plugin.package}@${plugin.version})`,
|
|
137
|
+
plugin,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove a plugin:
|
|
143
|
+
* 1. Check if it's a core plugin (can't remove)
|
|
144
|
+
* 2. npm uninstall the package
|
|
145
|
+
* 3. Remove tool names from block config if provided
|
|
146
|
+
*/
|
|
147
|
+
async remove(pluginId: string, options?: { blockConfigPath?: string; cwd?: string; onLog?: (chunk: string) => void }): Promise<{
|
|
148
|
+
success: boolean;
|
|
149
|
+
message: string;
|
|
150
|
+
}> {
|
|
151
|
+
const plugin = await this.findPlugin(pluginId);
|
|
152
|
+
if (!plugin) {
|
|
153
|
+
return { success: false, message: `Plugin "${pluginId}" not found in registry.` };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (plugin.core) {
|
|
157
|
+
return { success: false, message: `Plugin "${plugin.name}" is a core plugin and cannot be removed.` };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const cwd = options?.cwd || process.cwd();
|
|
161
|
+
try {
|
|
162
|
+
await new Promise<void>((resolve, reject) => {
|
|
163
|
+
const child = spawn('npm', ['uninstall', plugin.package], { cwd, shell: true });
|
|
164
|
+
child.stdout.on('data', data => options?.onLog?.(data.toString()));
|
|
165
|
+
child.stderr.on('data', data => options?.onLog?.(data.toString()));
|
|
166
|
+
|
|
167
|
+
let done = false;
|
|
168
|
+
child.on('error', err => { if (!done) { done = true; reject(err); } });
|
|
169
|
+
child.on('close', code => {
|
|
170
|
+
if (done) return;
|
|
171
|
+
done = true;
|
|
172
|
+
if (code === 0) resolve();
|
|
173
|
+
else reject(new Error(`Exit code ${code}`));
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
} catch {
|
|
177
|
+
// Uninstall failure is non-critical
|
|
178
|
+
options?.onLog?.('\nNote: npm uninstall encountered an error, but cleanup will proceed.\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Clean up block config
|
|
182
|
+
if (options?.blockConfigPath && plugin.toolNames.length > 0) {
|
|
183
|
+
try {
|
|
184
|
+
const configRaw = await readFile(options.blockConfigPath, 'utf-8');
|
|
185
|
+
const config = JSON.parse(configRaw);
|
|
186
|
+
const enabled: string[] = config.tools?.enabled || [];
|
|
187
|
+
|
|
188
|
+
config.tools = {
|
|
189
|
+
...config.tools,
|
|
190
|
+
enabled: enabled.filter(t => !plugin.toolNames.includes(t)),
|
|
191
|
+
};
|
|
192
|
+
await writeFile(options.blockConfigPath, JSON.stringify(config, null, 4), 'utf-8');
|
|
193
|
+
} catch {
|
|
194
|
+
// Config cleanup is best-effort
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Clean up settings
|
|
199
|
+
try {
|
|
200
|
+
const settingsPath = this.pluginSettingsPath(pluginId);
|
|
201
|
+
const { unlink } = await import('node:fs/promises');
|
|
202
|
+
await unlink(settingsPath);
|
|
203
|
+
} catch { /* ignore */ }
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
success: true,
|
|
207
|
+
message: `Removed ${plugin.name} (${plugin.package})`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ===== Settings Storage =====
|
|
212
|
+
|
|
213
|
+
/** Get the workspace-level settings file path for a plugin. */
|
|
214
|
+
private pluginSettingsPath(pluginId: string, workspacePath?: string): string {
|
|
215
|
+
const base = workspacePath || process.cwd();
|
|
216
|
+
return join(base, 'plugin-settings', `${pluginId}.json`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Load settings for a plugin. Returns defaults merged with saved values. */
|
|
220
|
+
async getPluginSettings(pluginId: string, workspacePath?: string): Promise<Record<string, unknown>> {
|
|
221
|
+
const plugin = await this.findPlugin(pluginId);
|
|
222
|
+
if (!plugin?.settings) return {};
|
|
223
|
+
|
|
224
|
+
// Start with defaults
|
|
225
|
+
const defaults: Record<string, unknown> = {};
|
|
226
|
+
for (const [key, field] of Object.entries(plugin.settings)) {
|
|
227
|
+
defaults[key] = field.default ?? '';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Merge saved values
|
|
231
|
+
try {
|
|
232
|
+
const raw = await readFile(this.pluginSettingsPath(pluginId, workspacePath), 'utf-8');
|
|
233
|
+
const saved = JSON.parse(raw);
|
|
234
|
+
return { ...defaults, ...saved };
|
|
235
|
+
} catch {
|
|
236
|
+
return defaults;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Save settings for a plugin. */
|
|
241
|
+
async savePluginSettings(pluginId: string, values: Record<string, unknown>, workspacePath?: string): Promise<void> {
|
|
242
|
+
const settingsPath = this.pluginSettingsPath(pluginId, workspacePath);
|
|
243
|
+
await mkdir(dirname(settingsPath), { recursive: true });
|
|
244
|
+
await writeFile(settingsPath, JSON.stringify(values, null, 4), 'utf-8');
|
|
245
|
+
}
|
|
246
|
+
}
|