@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 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.
@@ -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';
@@ -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
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "resolveJsonModule": true
7
+ },
8
+ "include": [
9
+ "src"
10
+ ]
11
+ }