brainctl 0.1.6 → 0.1.9

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.
Files changed (59) hide show
  1. package/README.md +215 -136
  2. package/dist/cli.js +40 -0
  3. package/dist/commands/mcp.js +35 -0
  4. package/dist/commands/profile.js +35 -2
  5. package/dist/executor/resolver.js +1 -38
  6. package/dist/mcp/server.js +82 -2
  7. package/dist/services/agent-config-service.d.ts +20 -3
  8. package/dist/services/agent-config-service.js +84 -16
  9. package/dist/services/agent-converter-service.d.ts +21 -0
  10. package/dist/services/agent-converter-service.js +182 -0
  11. package/dist/services/credential-redaction-service.d.ts +13 -0
  12. package/dist/services/credential-redaction-service.js +89 -0
  13. package/dist/services/credential-resolution-service.d.ts +11 -0
  14. package/dist/services/credential-resolution-service.js +69 -0
  15. package/dist/services/mcp-preflight-service.d.ts +26 -0
  16. package/dist/services/mcp-preflight-service.js +238 -0
  17. package/dist/services/plugin-install-service.d.ts +135 -0
  18. package/dist/services/plugin-install-service.js +601 -0
  19. package/dist/services/portable-mcp-classifier.d.ts +12 -0
  20. package/dist/services/portable-mcp-classifier.js +116 -0
  21. package/dist/services/portable-profile-pack-service.d.ts +26 -0
  22. package/dist/services/portable-profile-pack-service.js +264 -0
  23. package/dist/services/profile-export-service.d.ts +15 -3
  24. package/dist/services/profile-export-service.js +10 -57
  25. package/dist/services/profile-import-service.d.ts +9 -1
  26. package/dist/services/profile-import-service.js +266 -11
  27. package/dist/services/profile-service.d.ts +1 -0
  28. package/dist/services/profile-service.js +128 -32
  29. package/dist/services/runtime-detector.d.ts +9 -0
  30. package/dist/services/runtime-detector.js +130 -0
  31. package/dist/services/skill-paths.d.ts +4 -0
  32. package/dist/services/skill-paths.js +26 -0
  33. package/dist/services/skill-preflight-service.d.ts +23 -0
  34. package/dist/services/skill-preflight-service.js +40 -0
  35. package/dist/services/sync/agent-reader.d.ts +14 -0
  36. package/dist/services/sync/agent-reader.js +198 -45
  37. package/dist/services/sync/claude-writer.js +4 -7
  38. package/dist/services/sync/codex-writer.d.ts +1 -0
  39. package/dist/services/sync/codex-writer.js +25 -8
  40. package/dist/services/sync/gemini-writer.js +9 -8
  41. package/dist/services/sync/managed-plugin-registry.d.ts +17 -0
  42. package/dist/services/sync/managed-plugin-registry.js +75 -0
  43. package/dist/services/sync/plugin-skill-reader.d.ts +7 -0
  44. package/dist/services/sync/plugin-skill-reader.js +174 -0
  45. package/dist/services/sync-service.js +6 -1
  46. package/dist/services/update-check-service.d.ts +33 -0
  47. package/dist/services/update-check-service.js +128 -0
  48. package/dist/system/executables.d.ts +1 -0
  49. package/dist/system/executables.js +38 -0
  50. package/dist/types.d.ts +62 -5
  51. package/dist/ui/routes.js +293 -8
  52. package/dist/web/assets/index-Cdb5hbxM.css +1 -0
  53. package/dist/web/assets/index-gN83hZYA.js +65 -0
  54. package/dist/web/favicon-light.svg +13 -0
  55. package/dist/web/favicon.svg +13 -0
  56. package/dist/web/index.html +7 -2
  57. package/package.json +9 -1
  58. package/dist/web/assets/index-364NYWPA.css +0 -1
  59. package/dist/web/assets/index-BmfE7rus.js +0 -16
@@ -0,0 +1,89 @@
1
+ export function redactPortableMcpCredentials(config) {
2
+ const credentialsByKey = new Map();
3
+ const redactedEnv = redactStringMap(config.env, 'env', credentialsByKey);
4
+ if (config.kind === 'remote') {
5
+ const redactedHeaders = redactStringMap(config.headers, 'header', credentialsByKey);
6
+ return {
7
+ redacted: {
8
+ ...config,
9
+ ...(redactedEnv ? { env: redactedEnv } : {}),
10
+ ...(redactedHeaders ? { headers: redactedHeaders } : {}),
11
+ },
12
+ credentials: finalizePortableCredentialSpecs(credentialsByKey),
13
+ };
14
+ }
15
+ return {
16
+ redacted: {
17
+ ...config,
18
+ ...(redactedEnv ? { env: redactedEnv } : {}),
19
+ },
20
+ credentials: finalizePortableCredentialSpecs(credentialsByKey),
21
+ };
22
+ }
23
+ function redactStringMap(values, source, credentialsByKey) {
24
+ if (!values) {
25
+ return undefined;
26
+ }
27
+ const redacted = {};
28
+ for (const [key, value] of Object.entries(values)) {
29
+ if (!shouldRedact(key)) {
30
+ redacted[key] = value;
31
+ continue;
32
+ }
33
+ const credentialKey = normalizeCredentialKey(key);
34
+ addCredentialSpec(credentialsByKey, credentialKey, source, key);
35
+ redacted[key] = isCredentialPlaceholder(value)
36
+ ? value
37
+ : `\${credentials.${credentialKey}}`;
38
+ }
39
+ return redacted;
40
+ }
41
+ function shouldRedact(key) {
42
+ const tokens = tokenizeCredentialKey(key);
43
+ if (tokens.length === 0) {
44
+ return false;
45
+ }
46
+ return (tokens[tokens.length - 1] === 'authorization' ||
47
+ tokens[tokens.length - 1] === 'password' ||
48
+ tokens[tokens.length - 1] === 'secret' ||
49
+ tokens[tokens.length - 1] === 'token' ||
50
+ (tokens[tokens.length - 1] === 'key' &&
51
+ (tokens.includes('api') || (tokens.includes('auth') && tokens.includes('key')))));
52
+ }
53
+ function normalizeCredentialKey(key) {
54
+ return tokenizeCredentialKey(key).join('_');
55
+ }
56
+ function tokenizeCredentialKey(key) {
57
+ return key
58
+ .trim()
59
+ .replace(/([A-Z]+)([A-Z][a-z0-9])/g, '$1 $2')
60
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
61
+ .toLowerCase()
62
+ .split(/[^a-z0-9]+/g)
63
+ .filter(Boolean);
64
+ }
65
+ function isCredentialPlaceholder(value) {
66
+ return /^\$\{credentials\.[^}]+\}$/.test(value) || /^(Bearer|Token)\s+\$\{credentials\.[^}]+\}$/i.test(value);
67
+ }
68
+ function addCredentialSpec(credentialsByKey, credentialKey, source, originalKey) {
69
+ const description = source === 'env' ? `Environment variable ${originalKey}` : `Header ${originalKey}`;
70
+ const existing = credentialsByKey.get(credentialKey);
71
+ if (existing) {
72
+ existing.descriptions.add(description);
73
+ return;
74
+ }
75
+ credentialsByKey.set(credentialKey, {
76
+ key: credentialKey,
77
+ required: true,
78
+ descriptions: new Set([description]),
79
+ });
80
+ }
81
+ export function finalizePortableCredentialSpecs(credentialsByKey) {
82
+ return Array.from(credentialsByKey.values())
83
+ .sort((left, right) => left.key.localeCompare(right.key))
84
+ .map((entry) => ({
85
+ key: entry.key,
86
+ required: entry.required,
87
+ description: `${Array.from(entry.descriptions).sort().join('; ')} required for MCP access`,
88
+ }));
89
+ }
@@ -0,0 +1,11 @@
1
+ import type { McpServerConfig, PortableCredentialPlaceholder, PortableCredentialSpec } from '../types.js';
2
+ export interface CredentialResolutionResult<T extends McpServerConfig> {
3
+ resolved: T;
4
+ missing: PortableCredentialSpec[];
5
+ }
6
+ export declare function resolvePortableMcpCredentials<T extends McpServerConfig>(config: T, options?: {
7
+ credentials?: Record<string, string>;
8
+ credentialSpecs?: PortableCredentialSpec[];
9
+ environment?: Record<string, string | undefined>;
10
+ }): CredentialResolutionResult<T>;
11
+ export declare function toPortableCredentialPlaceholder(key: string): PortableCredentialPlaceholder;
@@ -0,0 +1,69 @@
1
+ export function resolvePortableMcpCredentials(config, options = {}) {
2
+ const specs = new Map((options.credentialSpecs ?? []).map((spec) => [spec.key, spec]));
3
+ const missing = new Map();
4
+ const env = resolveStringMap(config.env, specs, missing, options.credentials, options.environment);
5
+ if (config.kind === 'remote') {
6
+ const headers = resolveStringMap(config.headers, specs, missing, options.credentials, options.environment);
7
+ return {
8
+ resolved: {
9
+ ...config,
10
+ ...(env ? { env } : {}),
11
+ ...(headers ? { headers } : {}),
12
+ },
13
+ missing: Array.from(missing.values()),
14
+ };
15
+ }
16
+ return {
17
+ resolved: {
18
+ ...config,
19
+ ...(env ? { env } : {}),
20
+ },
21
+ missing: Array.from(missing.values()),
22
+ };
23
+ }
24
+ function resolveStringMap(values, specs, missing, credentials, environment) {
25
+ if (!values) {
26
+ return undefined;
27
+ }
28
+ const resolved = {};
29
+ for (const [key, value] of Object.entries(values)) {
30
+ const placeholder = parseCredentialPlaceholder(value);
31
+ if (!placeholder) {
32
+ resolved[key] = value;
33
+ continue;
34
+ }
35
+ const resolvedValue = credentials?.[placeholder.key] ?? environment?.[placeholder.key];
36
+ if (typeof resolvedValue === 'string' && resolvedValue.length > 0) {
37
+ resolved[key] = placeholder.prefix ? `${placeholder.prefix} ${resolvedValue}` : resolvedValue;
38
+ continue;
39
+ }
40
+ const spec = specs.get(placeholder.key) ?? {
41
+ key: placeholder.key,
42
+ required: true,
43
+ description: `Credential ${placeholder.key} is required`,
44
+ };
45
+ if (spec.required) {
46
+ missing.set(spec.key, spec);
47
+ }
48
+ resolved[key] = value;
49
+ }
50
+ return resolved;
51
+ }
52
+ function parseCredentialPlaceholder(value) {
53
+ const bareMatch = value.match(/^\$\{credentials\.([^}]+)\}$/);
54
+ if (bareMatch) {
55
+ return { key: bareMatch[1] };
56
+ }
57
+ const prefixedMatch = value.match(/^(Bearer|Token)\s+\$\{credentials\.([^}]+)\}$/i);
58
+ if (prefixedMatch) {
59
+ const prefix = prefixedMatch[1].toLowerCase() === 'bearer' ? 'Bearer' : 'Token';
60
+ return {
61
+ key: prefixedMatch[2],
62
+ prefix,
63
+ };
64
+ }
65
+ return null;
66
+ }
67
+ export function toPortableCredentialPlaceholder(key) {
68
+ return `\${credentials.${key}}`;
69
+ }
@@ -0,0 +1,26 @@
1
+ import type { AgentName } from '../types.js';
2
+ import type { AgentMcpEntry, PortableRemoteMcpMetadata } from './agent-config-service.js';
3
+ export interface McpPreflightCheck {
4
+ label: string;
5
+ status: 'ok' | 'warn' | 'error';
6
+ message: string;
7
+ }
8
+ export interface McpPreflightResult {
9
+ ok: boolean;
10
+ checks: McpPreflightCheck[];
11
+ }
12
+ export interface McpPreflightService {
13
+ execute(options: {
14
+ cwd: string;
15
+ agent: AgentName;
16
+ key: string;
17
+ entry?: AgentMcpEntry;
18
+ remoteEntry?: PortableRemoteMcpMetadata;
19
+ }): Promise<McpPreflightResult>;
20
+ }
21
+ interface McpPreflightDependencies {
22
+ resolveExecutable?: (command: string) => Promise<string | null>;
23
+ pathExists?: (targetPath: string) => Promise<boolean>;
24
+ }
25
+ export declare function createMcpPreflightService(dependencies?: McpPreflightDependencies): McpPreflightService;
26
+ export {};
@@ -0,0 +1,238 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { findExecutable } from '../system/executables.js';
4
+ const ENTRYPOINT_RUNNERS = new Set(['node', 'nodejs', 'python', 'python3', 'bash', 'sh', 'zsh', 'deno', 'bun']);
5
+ const SUPPORTED_LOCAL_LAUNCHERS = ['npx', 'uvx', 'node', 'python', 'python3', 'java -jar', 'go run', 'cargo run', 'and local binaries/scripts'];
6
+ export function createMcpPreflightService(dependencies = {}) {
7
+ const resolveExecutable = dependencies.resolveExecutable ?? findExecutable;
8
+ const pathExists = dependencies.pathExists ?? defaultPathExists;
9
+ return {
10
+ async execute(options) {
11
+ if (options.remoteEntry) {
12
+ return validateRemoteMcp(options.agent, options.remoteEntry);
13
+ }
14
+ const checks = [];
15
+ if (!options.entry) {
16
+ checks.push({
17
+ label: 'Launcher',
18
+ status: 'error',
19
+ message: 'Missing local MCP command metadata.',
20
+ });
21
+ return { ok: false, checks };
22
+ }
23
+ const launcherCheck = validateLocalLauncher(options.entry);
24
+ checks.push(launcherCheck);
25
+ if (launcherCheck.status === 'error') {
26
+ return { ok: false, checks };
27
+ }
28
+ if (isLocalBinaryPath(options.entry.command)) {
29
+ const commandPath = path.isAbsolute(options.entry.command)
30
+ ? options.entry.command
31
+ : path.resolve(options.cwd, options.entry.command);
32
+ const exists = await pathExists(commandPath);
33
+ checks.push({
34
+ label: 'Command',
35
+ status: exists ? 'ok' : 'error',
36
+ message: exists
37
+ ? `Local MCP command was found: ${commandPath}`
38
+ : `Local MCP command was not found: ${commandPath}`,
39
+ });
40
+ return {
41
+ ok: checks.every((check) => check.status !== 'error'),
42
+ checks,
43
+ };
44
+ }
45
+ const resolvedCommand = await resolveExecutable(options.entry.command);
46
+ if (!resolvedCommand) {
47
+ checks.push({
48
+ label: 'Command',
49
+ status: 'error',
50
+ message: `Command "${options.entry.command}" is not available on PATH.`,
51
+ });
52
+ return { ok: false, checks };
53
+ }
54
+ checks.push({
55
+ label: 'Command',
56
+ status: 'ok',
57
+ message: `Command "${options.entry.command}" resolved to ${resolvedCommand}.`,
58
+ });
59
+ if (options.entry.command === 'npx' || options.entry.command === 'uvx') {
60
+ const nonFlagArg = options.entry.args?.find((arg) => !arg.startsWith('-'));
61
+ if (!nonFlagArg) {
62
+ checks.push({
63
+ label: 'Package',
64
+ status: 'error',
65
+ message: `${options.entry.command}-based MCP entries must include a package or executable argument.`,
66
+ });
67
+ }
68
+ else {
69
+ checks.push({
70
+ label: 'Package',
71
+ status: 'ok',
72
+ message: `${options.entry.command} will attempt to launch ${nonFlagArg}.`,
73
+ });
74
+ }
75
+ }
76
+ const entrypointPath = resolveEntrypointPath(options.cwd, options.entry);
77
+ if (entrypointPath) {
78
+ const exists = await pathExists(entrypointPath);
79
+ checks.push({
80
+ label: 'Entrypoint',
81
+ status: exists ? 'ok' : 'error',
82
+ message: exists
83
+ ? `Entrypoint script was found: ${entrypointPath}`
84
+ : `Entrypoint script was not found: ${entrypointPath}`,
85
+ });
86
+ }
87
+ return {
88
+ ok: checks.every((check) => check.status !== 'error'),
89
+ checks,
90
+ };
91
+ },
92
+ };
93
+ }
94
+ function resolveEntrypointPath(cwd, entry) {
95
+ if (entry.command === 'java') {
96
+ const jarIndex = entry.args?.indexOf('-jar') ?? -1;
97
+ const jarPath = jarIndex >= 0 ? entry.args?.[jarIndex + 1] : undefined;
98
+ if (!jarPath || !looksLikeLocalPath(jarPath))
99
+ return null;
100
+ return path.isAbsolute(jarPath) ? jarPath : path.resolve(cwd, jarPath);
101
+ }
102
+ if (entry.command === 'go') {
103
+ const runIndex = entry.args?.indexOf('run') ?? -1;
104
+ const runPath = runIndex >= 0 ? entry.args?.[runIndex + 1] : undefined;
105
+ if (!runPath || !looksLikeLocalPath(runPath))
106
+ return null;
107
+ return path.isAbsolute(runPath) ? runPath : path.resolve(cwd, runPath);
108
+ }
109
+ if (!ENTRYPOINT_RUNNERS.has(entry.command)) {
110
+ return null;
111
+ }
112
+ const firstArg = entry.args?.[0];
113
+ if (!firstArg || !looksLikeLocalPath(firstArg)) {
114
+ return null;
115
+ }
116
+ return path.isAbsolute(firstArg) ? firstArg : path.resolve(cwd, firstArg);
117
+ }
118
+ function looksLikeLocalPath(value) {
119
+ return (value.startsWith('.') ||
120
+ value.startsWith('/') ||
121
+ value.includes(path.sep) ||
122
+ /\.(cjs|cts|js|json|jsx|mjs|mts|py|sh|ts|tsx)$/i.test(value));
123
+ }
124
+ function validateLocalLauncher(entry) {
125
+ if (isLocalBinaryPath(entry.command)) {
126
+ return {
127
+ label: 'Launcher',
128
+ status: 'ok',
129
+ message: `Launcher "${entry.command}" is supported for exact MCP copy.`,
130
+ };
131
+ }
132
+ if (entry.command === 'npx' || entry.command === 'uvx' || entry.command === 'node' || entry.command === 'nodejs' || entry.command === 'python' || entry.command === 'python3') {
133
+ return {
134
+ label: 'Launcher',
135
+ status: 'ok',
136
+ message: `Launcher "${entry.command}" is supported for exact MCP copy.`,
137
+ };
138
+ }
139
+ if (entry.command === 'java') {
140
+ const jarIndex = entry.args?.indexOf('-jar') ?? -1;
141
+ const jarPath = jarIndex >= 0 ? entry.args?.[jarIndex + 1] : undefined;
142
+ return jarPath
143
+ ? {
144
+ label: 'Launcher',
145
+ status: 'ok',
146
+ message: 'Launcher "java -jar" is supported for exact MCP copy.',
147
+ }
148
+ : {
149
+ label: 'Launcher',
150
+ status: 'error',
151
+ message: 'Launcher "java" is only supported for exact MCP copy when used as "java -jar <path>".',
152
+ };
153
+ }
154
+ if (entry.command === 'go') {
155
+ const runIndex = entry.args?.indexOf('run') ?? -1;
156
+ const runPath = runIndex >= 0 ? entry.args?.[runIndex + 1] : undefined;
157
+ return runPath
158
+ ? {
159
+ label: 'Launcher',
160
+ status: 'ok',
161
+ message: 'Launcher "go run" is supported for exact MCP copy.',
162
+ }
163
+ : {
164
+ label: 'Launcher',
165
+ status: 'error',
166
+ message: 'Launcher "go" is only supported for exact MCP copy when used as "go run <path>".',
167
+ };
168
+ }
169
+ if (entry.command === 'cargo') {
170
+ return entry.args?.[0] === 'run'
171
+ ? {
172
+ label: 'Launcher',
173
+ status: 'ok',
174
+ message: 'Launcher "cargo run" is supported for exact MCP copy.',
175
+ }
176
+ : {
177
+ label: 'Launcher',
178
+ status: 'error',
179
+ message: 'Launcher "cargo" is only supported for exact MCP copy when used as "cargo run".',
180
+ };
181
+ }
182
+ return {
183
+ label: 'Launcher',
184
+ status: 'error',
185
+ message: `Launcher "${entry.command}" is not supported for exact MCP copy. Supported launchers: ${SUPPORTED_LOCAL_LAUNCHERS.join(', ')}.`,
186
+ };
187
+ }
188
+ function isLocalBinaryPath(command) {
189
+ return command.startsWith('./') || command.startsWith('/') || command.startsWith('.\\');
190
+ }
191
+ function validateRemoteMcp(agent, remoteEntry) {
192
+ const checks = [];
193
+ let parsedUrl;
194
+ try {
195
+ parsedUrl = new URL(remoteEntry.url);
196
+ }
197
+ catch {
198
+ checks.push({
199
+ label: 'Remote URL',
200
+ status: 'error',
201
+ message: `Remote MCP URL is invalid: ${remoteEntry.url}`,
202
+ });
203
+ return { ok: false, checks };
204
+ }
205
+ if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
206
+ checks.push({
207
+ label: 'Remote URL',
208
+ status: 'error',
209
+ message: `Remote MCP URL must use http or https: ${remoteEntry.url}`,
210
+ });
211
+ return { ok: false, checks };
212
+ }
213
+ checks.push({
214
+ label: 'Remote URL',
215
+ status: 'ok',
216
+ message: `Remote MCP URL is valid: ${remoteEntry.url}`,
217
+ });
218
+ if (agent === 'codex' && (remoteEntry.transport !== 'http' || remoteEntry.headers || remoteEntry.env)) {
219
+ checks.push({
220
+ label: 'Remote support',
221
+ status: 'error',
222
+ message: 'Codex remote MCP entries only support a plain HTTP url today; headers and env cannot be preserved exactly.',
223
+ });
224
+ }
225
+ return {
226
+ ok: checks.every((check) => check.status !== 'error'),
227
+ checks,
228
+ };
229
+ }
230
+ async function defaultPathExists(targetPath) {
231
+ try {
232
+ await stat(targetPath);
233
+ return true;
234
+ }
235
+ catch {
236
+ return false;
237
+ }
238
+ }
@@ -0,0 +1,135 @@
1
+ import type { AgentName } from '../types.js';
2
+ import type { AgentLiveConfig, AgentMcpEntry, AgentSkillEntry } from './agent-config-service.js';
3
+ export interface PluginInstallCheck {
4
+ label: string;
5
+ status: 'ok' | 'warn' | 'error';
6
+ message: string;
7
+ }
8
+ export interface PluginBundleAgent {
9
+ name: string;
10
+ sourceFormat: 'claude-md' | 'codex-toml';
11
+ content: string;
12
+ }
13
+ export interface PluginBundleCommand {
14
+ name: string;
15
+ content: string;
16
+ }
17
+ export interface PluginInstallPlan {
18
+ ok: boolean;
19
+ checks: PluginInstallCheck[];
20
+ skills: string[];
21
+ mcps: Record<string, AgentMcpEntry>;
22
+ agents: string[];
23
+ commands: string[];
24
+ }
25
+ export interface PluginInstallResult {
26
+ installedSkills: string[];
27
+ installedMcps: string[];
28
+ installedAgents: string[];
29
+ installedCommands: string[];
30
+ }
31
+ export interface PluginUninstallPlan {
32
+ ok: boolean;
33
+ checks: PluginInstallCheck[];
34
+ skills: string[];
35
+ mcps: string[];
36
+ agents: string[];
37
+ commands: string[];
38
+ }
39
+ export interface PluginUninstallResult {
40
+ removedSkills: string[];
41
+ removedMcps: string[];
42
+ removedAgents: string[];
43
+ removedCommands: string[];
44
+ }
45
+ export interface PluginInstallService {
46
+ plan(options: {
47
+ cwd: string;
48
+ targetAgent: AgentName;
49
+ sourceAgent: AgentName;
50
+ plugin: AgentSkillEntry;
51
+ }): Promise<PluginInstallPlan>;
52
+ execute(options: {
53
+ cwd: string;
54
+ targetAgent: AgentName;
55
+ sourceAgent: AgentName;
56
+ plugin: AgentSkillEntry;
57
+ }): Promise<PluginInstallResult>;
58
+ planRemoval(options: {
59
+ cwd: string;
60
+ targetAgent: AgentName;
61
+ plugin: AgentSkillEntry;
62
+ }): Promise<PluginUninstallPlan>;
63
+ remove(options: {
64
+ cwd: string;
65
+ targetAgent: AgentName;
66
+ plugin: AgentSkillEntry;
67
+ }): Promise<PluginUninstallResult>;
68
+ }
69
+ interface PluginBundle {
70
+ skills: string[];
71
+ mcps: Record<string, AgentMcpEntry>;
72
+ agents: PluginBundleAgent[];
73
+ commands: PluginBundleCommand[];
74
+ }
75
+ interface PluginInstallDependencies {
76
+ readInstalledPluginBundle?: (installPath: string) => Promise<PluginBundle>;
77
+ readTargetState?: (options: {
78
+ cwd: string;
79
+ agent: AgentName;
80
+ }) => Promise<Pick<AgentLiveConfig, 'skills' | 'mcpServers'>>;
81
+ copySkillDirectory?: (options: {
82
+ sourceInstallPath: string;
83
+ skillName: string;
84
+ targetAgent: AgentName;
85
+ }) => Promise<void>;
86
+ installAgent?: (options: {
87
+ targetAgent: AgentName;
88
+ agent: PluginBundleAgent;
89
+ }) => Promise<void>;
90
+ installCommand?: (options: {
91
+ targetAgent: AgentName;
92
+ command: PluginBundleCommand;
93
+ }) => Promise<void>;
94
+ addMcpEntry?: (options: {
95
+ cwd: string;
96
+ agent: AgentName;
97
+ key: string;
98
+ entry: AgentMcpEntry;
99
+ }) => Promise<void>;
100
+ recordManagedPluginInstall?: (options: {
101
+ agent: AgentName;
102
+ plugin: AgentSkillEntry;
103
+ }) => Promise<void>;
104
+ removeSkillDirectory?: (options: {
105
+ targetAgent: AgentName;
106
+ skillName: string;
107
+ }) => Promise<void>;
108
+ removeAgentFile?: (options: {
109
+ targetAgent: AgentName;
110
+ agentName: string;
111
+ }) => Promise<void>;
112
+ removeCommandFile?: (options: {
113
+ targetAgent: AgentName;
114
+ commandName: string;
115
+ }) => Promise<void>;
116
+ removeMcpEntry?: (options: {
117
+ cwd: string;
118
+ agent: AgentName;
119
+ key: string;
120
+ }) => Promise<void>;
121
+ removeManagedPluginInstall?: (options: {
122
+ agent: AgentName;
123
+ pluginName: string;
124
+ }) => Promise<void>;
125
+ uninstallCodexPlugin?: (options: {
126
+ pluginKey: string;
127
+ installPath: string;
128
+ }) => Promise<void>;
129
+ uninstallClaudePlugin?: (options: {
130
+ pluginKey: string;
131
+ installPath: string;
132
+ }) => Promise<void>;
133
+ }
134
+ export declare function createPluginInstallService(dependencies?: PluginInstallDependencies): PluginInstallService;
135
+ export {};