brainctl 0.1.7 → 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.
- package/README.md +210 -157
- package/dist/cli.js +40 -0
- package/dist/commands/mcp.js +35 -0
- package/dist/commands/profile.js +35 -2
- package/dist/mcp/server.js +51 -5
- package/dist/services/agent-config-service.d.ts +4 -2
- package/dist/services/agent-config-service.js +50 -15
- package/dist/services/agent-converter-service.d.ts +21 -0
- package/dist/services/agent-converter-service.js +182 -0
- package/dist/services/credential-redaction-service.d.ts +13 -0
- package/dist/services/credential-redaction-service.js +89 -0
- package/dist/services/credential-resolution-service.d.ts +11 -0
- package/dist/services/credential-resolution-service.js +69 -0
- package/dist/services/mcp-preflight-service.d.ts +3 -2
- package/dist/services/mcp-preflight-service.js +159 -5
- package/dist/services/plugin-install-service.d.ts +43 -0
- package/dist/services/plugin-install-service.js +379 -21
- package/dist/services/portable-mcp-classifier.d.ts +12 -0
- package/dist/services/portable-mcp-classifier.js +116 -0
- package/dist/services/portable-profile-pack-service.d.ts +26 -0
- package/dist/services/portable-profile-pack-service.js +264 -0
- package/dist/services/profile-export-service.d.ts +15 -3
- package/dist/services/profile-export-service.js +10 -57
- package/dist/services/profile-import-service.d.ts +9 -1
- package/dist/services/profile-import-service.js +265 -10
- package/dist/services/profile-service.js +11 -0
- package/dist/services/runtime-detector.d.ts +9 -0
- package/dist/services/runtime-detector.js +130 -0
- package/dist/services/skill-paths.d.ts +2 -0
- package/dist/services/skill-paths.js +14 -0
- package/dist/services/sync/agent-reader.d.ts +9 -0
- package/dist/services/sync/agent-reader.js +177 -35
- package/dist/services/sync/claude-writer.js +0 -6
- package/dist/services/sync/codex-writer.d.ts +1 -0
- package/dist/services/sync/codex-writer.js +21 -8
- package/dist/services/sync/gemini-writer.js +5 -7
- package/dist/services/sync/plugin-skill-reader.d.ts +5 -0
- package/dist/services/sync/plugin-skill-reader.js +142 -1
- package/dist/services/sync-service.js +1 -1
- package/dist/services/update-check-service.d.ts +33 -0
- package/dist/services/update-check-service.js +128 -0
- package/dist/types.d.ts +47 -0
- package/dist/ui/routes.js +35 -8
- package/dist/web/assets/index-Cdb5hbxM.css +1 -0
- package/dist/web/assets/index-gN83hZYA.js +65 -0
- package/dist/web/favicon-light.svg +13 -0
- package/dist/web/favicon.svg +13 -0
- package/dist/web/index.html +7 -2
- package/package.json +5 -1
- package/dist/web/assets/index-BCkorugl.css +0 -1
- package/dist/web/assets/index-sGnTMhkX.js +0 -16
|
@@ -1,13 +1,47 @@
|
|
|
1
1
|
import { stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { findExecutable } from '../system/executables.js';
|
|
4
|
-
const
|
|
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'];
|
|
5
6
|
export function createMcpPreflightService(dependencies = {}) {
|
|
6
7
|
const resolveExecutable = dependencies.resolveExecutable ?? findExecutable;
|
|
7
8
|
const pathExists = dependencies.pathExists ?? defaultPathExists;
|
|
8
9
|
return {
|
|
9
10
|
async execute(options) {
|
|
11
|
+
if (options.remoteEntry) {
|
|
12
|
+
return validateRemoteMcp(options.agent, options.remoteEntry);
|
|
13
|
+
}
|
|
10
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
|
+
}
|
|
11
45
|
const resolvedCommand = await resolveExecutable(options.entry.command);
|
|
12
46
|
if (!resolvedCommand) {
|
|
13
47
|
checks.push({
|
|
@@ -22,20 +56,20 @@ export function createMcpPreflightService(dependencies = {}) {
|
|
|
22
56
|
status: 'ok',
|
|
23
57
|
message: `Command "${options.entry.command}" resolved to ${resolvedCommand}.`,
|
|
24
58
|
});
|
|
25
|
-
if (options.entry.command === 'npx') {
|
|
59
|
+
if (options.entry.command === 'npx' || options.entry.command === 'uvx') {
|
|
26
60
|
const nonFlagArg = options.entry.args?.find((arg) => !arg.startsWith('-'));
|
|
27
61
|
if (!nonFlagArg) {
|
|
28
62
|
checks.push({
|
|
29
63
|
label: 'Package',
|
|
30
64
|
status: 'error',
|
|
31
|
-
message:
|
|
65
|
+
message: `${options.entry.command}-based MCP entries must include a package or executable argument.`,
|
|
32
66
|
});
|
|
33
67
|
}
|
|
34
68
|
else {
|
|
35
69
|
checks.push({
|
|
36
70
|
label: 'Package',
|
|
37
71
|
status: 'ok',
|
|
38
|
-
message:
|
|
72
|
+
message: `${options.entry.command} will attempt to launch ${nonFlagArg}.`,
|
|
39
73
|
});
|
|
40
74
|
}
|
|
41
75
|
}
|
|
@@ -58,7 +92,21 @@ export function createMcpPreflightService(dependencies = {}) {
|
|
|
58
92
|
};
|
|
59
93
|
}
|
|
60
94
|
function resolveEntrypointPath(cwd, entry) {
|
|
61
|
-
if (
|
|
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)) {
|
|
62
110
|
return null;
|
|
63
111
|
}
|
|
64
112
|
const firstArg = entry.args?.[0];
|
|
@@ -73,6 +121,112 @@ function looksLikeLocalPath(value) {
|
|
|
73
121
|
value.includes(path.sep) ||
|
|
74
122
|
/\.(cjs|cts|js|json|jsx|mjs|mts|py|sh|ts|tsx)$/i.test(value));
|
|
75
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
|
+
}
|
|
76
230
|
async function defaultPathExists(targetPath) {
|
|
77
231
|
try {
|
|
78
232
|
await stat(targetPath);
|
|
@@ -5,25 +5,42 @@ export interface PluginInstallCheck {
|
|
|
5
5
|
status: 'ok' | 'warn' | 'error';
|
|
6
6
|
message: string;
|
|
7
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
|
+
}
|
|
8
17
|
export interface PluginInstallPlan {
|
|
9
18
|
ok: boolean;
|
|
10
19
|
checks: PluginInstallCheck[];
|
|
11
20
|
skills: string[];
|
|
12
21
|
mcps: Record<string, AgentMcpEntry>;
|
|
22
|
+
agents: string[];
|
|
23
|
+
commands: string[];
|
|
13
24
|
}
|
|
14
25
|
export interface PluginInstallResult {
|
|
15
26
|
installedSkills: string[];
|
|
16
27
|
installedMcps: string[];
|
|
28
|
+
installedAgents: string[];
|
|
29
|
+
installedCommands: string[];
|
|
17
30
|
}
|
|
18
31
|
export interface PluginUninstallPlan {
|
|
19
32
|
ok: boolean;
|
|
20
33
|
checks: PluginInstallCheck[];
|
|
21
34
|
skills: string[];
|
|
22
35
|
mcps: string[];
|
|
36
|
+
agents: string[];
|
|
37
|
+
commands: string[];
|
|
23
38
|
}
|
|
24
39
|
export interface PluginUninstallResult {
|
|
25
40
|
removedSkills: string[];
|
|
26
41
|
removedMcps: string[];
|
|
42
|
+
removedAgents: string[];
|
|
43
|
+
removedCommands: string[];
|
|
27
44
|
}
|
|
28
45
|
export interface PluginInstallService {
|
|
29
46
|
plan(options: {
|
|
@@ -52,6 +69,8 @@ export interface PluginInstallService {
|
|
|
52
69
|
interface PluginBundle {
|
|
53
70
|
skills: string[];
|
|
54
71
|
mcps: Record<string, AgentMcpEntry>;
|
|
72
|
+
agents: PluginBundleAgent[];
|
|
73
|
+
commands: PluginBundleCommand[];
|
|
55
74
|
}
|
|
56
75
|
interface PluginInstallDependencies {
|
|
57
76
|
readInstalledPluginBundle?: (installPath: string) => Promise<PluginBundle>;
|
|
@@ -64,6 +83,14 @@ interface PluginInstallDependencies {
|
|
|
64
83
|
skillName: string;
|
|
65
84
|
targetAgent: AgentName;
|
|
66
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>;
|
|
67
94
|
addMcpEntry?: (options: {
|
|
68
95
|
cwd: string;
|
|
69
96
|
agent: AgentName;
|
|
@@ -78,6 +105,14 @@ interface PluginInstallDependencies {
|
|
|
78
105
|
targetAgent: AgentName;
|
|
79
106
|
skillName: string;
|
|
80
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>;
|
|
81
116
|
removeMcpEntry?: (options: {
|
|
82
117
|
cwd: string;
|
|
83
118
|
agent: AgentName;
|
|
@@ -87,6 +122,14 @@ interface PluginInstallDependencies {
|
|
|
87
122
|
agent: AgentName;
|
|
88
123
|
pluginName: string;
|
|
89
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>;
|
|
90
133
|
}
|
|
91
134
|
export declare function createPluginInstallService(dependencies?: PluginInstallDependencies): PluginInstallService;
|
|
92
135
|
export {};
|