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,75 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import path from 'node:path';
4
+ function getRegistryPath(homeDir) {
5
+ return path.join(homeDir, '.brainctl', 'managed-plugins.json');
6
+ }
7
+ export async function readManagedPlugins(options) {
8
+ const homeDir = options.homeDir ?? homedir();
9
+ const registryPath = getRegistryPath(homeDir);
10
+ try {
11
+ const source = await readFile(registryPath, 'utf8');
12
+ const parsed = JSON.parse(source);
13
+ return (parsed.agents?.[options.agent] ?? []).map((entry) => ({
14
+ ...entry,
15
+ kind: 'plugin',
16
+ managed: true,
17
+ }));
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ }
23
+ export async function writeManagedPluginInstall(options) {
24
+ const homeDir = options.homeDir ?? homedir();
25
+ const registryPath = getRegistryPath(homeDir);
26
+ const existing = await readRegistryFile(homeDir);
27
+ const currentEntries = existing.agents?.[options.agent] ?? [];
28
+ const nextEntries = [
29
+ ...currentEntries.filter((entry) => entry.name !== options.plugin.name),
30
+ {
31
+ ...options.plugin,
32
+ kind: 'plugin',
33
+ managed: true,
34
+ },
35
+ ].sort((left, right) => left.name.localeCompare(right.name));
36
+ const next = {
37
+ version: 1,
38
+ agents: {
39
+ ...existing.agents,
40
+ [options.agent]: nextEntries,
41
+ },
42
+ };
43
+ await mkdir(path.dirname(registryPath), { recursive: true });
44
+ await writeFile(registryPath, JSON.stringify(next, null, 2) + '\n', 'utf8');
45
+ }
46
+ export async function removeManagedPluginInstall(options) {
47
+ const homeDir = options.homeDir ?? homedir();
48
+ const registryPath = getRegistryPath(homeDir);
49
+ const existing = await readRegistryFile(homeDir);
50
+ const currentEntries = existing.agents?.[options.agent] ?? [];
51
+ const nextEntries = currentEntries.filter((entry) => entry.name !== options.pluginName);
52
+ const next = {
53
+ version: 1,
54
+ agents: {
55
+ ...existing.agents,
56
+ [options.agent]: nextEntries,
57
+ },
58
+ };
59
+ await mkdir(path.dirname(registryPath), { recursive: true });
60
+ await writeFile(registryPath, JSON.stringify(next, null, 2) + '\n', 'utf8');
61
+ }
62
+ export function mergeManagedPluginsIntoSkills(localSkills, managedPlugins) {
63
+ const pluginOwnedSkills = new Set(managedPlugins.flatMap((plugin) => plugin.pluginSkills ?? []));
64
+ const filteredLocalSkills = localSkills.filter((skill) => !pluginOwnedSkills.has(skill.name));
65
+ return [...managedPlugins, ...filteredLocalSkills];
66
+ }
67
+ async function readRegistryFile(homeDir) {
68
+ try {
69
+ const source = await readFile(getRegistryPath(homeDir), 'utf8');
70
+ return JSON.parse(source);
71
+ }
72
+ catch {
73
+ return { version: 1, agents: {} };
74
+ }
75
+ }
@@ -0,0 +1,7 @@
1
+ import type { AgentSkillEntry } from './agent-reader.js';
2
+ export declare function readInstalledPlugins(installedPluginsPath: string): Promise<AgentSkillEntry[]>;
3
+ export declare function readCodexPlugins(options: {
4
+ configTomlPath: string;
5
+ pluginsCacheDir: string;
6
+ }): Promise<AgentSkillEntry[]>;
7
+ export declare function readPluginMcpKeys(installPath: string): Promise<string[]>;
@@ -0,0 +1,174 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function readInstalledPlugins(installedPluginsPath) {
4
+ const source = await readFile(installedPluginsPath, 'utf8');
5
+ const data = JSON.parse(source);
6
+ const results = [];
7
+ for (const [key, records] of Object.entries(data.plugins ?? {})) {
8
+ const [name, pluginSource] = key.split('@');
9
+ const installPath = records[0]?.installPath;
10
+ const pluginSkills = installPath ? await readPluginSkills(installPath) : [];
11
+ const pluginMcps = installPath ? await readPluginMcpKeys(installPath) : [];
12
+ const pluginAgents = installPath ? await readPluginAgentNames(installPath) : [];
13
+ const pluginCommands = installPath ? await readPluginCommandNames(installPath) : [];
14
+ results.push({
15
+ name,
16
+ source: pluginSource,
17
+ kind: 'plugin',
18
+ installPath,
19
+ pluginSkills,
20
+ ...(pluginMcps.length > 0 ? { pluginMcps } : {}),
21
+ ...(pluginAgents.length > 0 ? { pluginAgents } : {}),
22
+ ...(pluginCommands.length > 0 ? { pluginCommands } : {}),
23
+ });
24
+ }
25
+ return results;
26
+ }
27
+ async function readPluginSkills(installPath) {
28
+ const skillsDir = path.join(installPath, 'skills');
29
+ try {
30
+ const entries = await readdir(skillsDir, { withFileTypes: true });
31
+ return entries
32
+ .filter((entry) => !entry.name.startsWith('.') && entry.isDirectory())
33
+ .map((entry) => entry.name)
34
+ .sort((left, right) => left.localeCompare(right));
35
+ }
36
+ catch {
37
+ return [];
38
+ }
39
+ }
40
+ export async function readCodexPlugins(options) {
41
+ let tomlSource;
42
+ try {
43
+ tomlSource = await readFile(options.configTomlPath, 'utf8');
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ const entries = [];
49
+ for (const { name, marketplace, enabled } of parseCodexPluginDeclarations(tomlSource)) {
50
+ if (!enabled)
51
+ continue;
52
+ const installPath = await resolveCodexPluginInstallPath({
53
+ pluginsCacheDir: options.pluginsCacheDir,
54
+ marketplace,
55
+ name,
56
+ });
57
+ if (!installPath)
58
+ continue;
59
+ const pluginSkills = await readPluginSkills(installPath);
60
+ const pluginMcps = await readPluginMcpKeys(installPath);
61
+ const pluginAgents = await readPluginAgentNames(installPath);
62
+ const pluginCommands = await readPluginCommandNames(installPath);
63
+ entries.push({
64
+ name,
65
+ source: marketplace,
66
+ kind: 'plugin',
67
+ installPath,
68
+ pluginSkills,
69
+ ...(pluginMcps.length > 0 ? { pluginMcps } : {}),
70
+ ...(pluginAgents.length > 0 ? { pluginAgents } : {}),
71
+ ...(pluginCommands.length > 0 ? { pluginCommands } : {}),
72
+ });
73
+ }
74
+ return entries.sort((left, right) => left.name.localeCompare(right.name));
75
+ }
76
+ function parseCodexPluginDeclarations(source) {
77
+ const results = [];
78
+ const lines = source.split('\n');
79
+ let current = null;
80
+ const flush = () => {
81
+ if (current) {
82
+ results.push(current);
83
+ current = null;
84
+ }
85
+ };
86
+ for (const line of lines) {
87
+ const trimmed = line.trim();
88
+ const sectionMatch = trimmed.match(/^\[plugins\."([^"@]+)@([^"]+)"\]$/);
89
+ if (sectionMatch) {
90
+ flush();
91
+ current = {
92
+ name: sectionMatch[1],
93
+ marketplace: sectionMatch[2],
94
+ enabled: false,
95
+ };
96
+ continue;
97
+ }
98
+ if (/^\[/.test(trimmed)) {
99
+ flush();
100
+ continue;
101
+ }
102
+ if (!current)
103
+ continue;
104
+ const kv = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
105
+ if (kv && kv[1] === 'enabled') {
106
+ current.enabled = kv[2].trim() === 'true';
107
+ }
108
+ }
109
+ flush();
110
+ return results;
111
+ }
112
+ async function resolveCodexPluginInstallPath(options) {
113
+ const pluginRoot = path.join(options.pluginsCacheDir, options.marketplace, options.name);
114
+ let versionDirs;
115
+ try {
116
+ const entries = await readdir(pluginRoot, { withFileTypes: true });
117
+ versionDirs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ if (versionDirs.length === 0)
123
+ return null;
124
+ if (versionDirs.length === 1)
125
+ return path.join(pluginRoot, versionDirs[0]);
126
+ const stats = await Promise.all(versionDirs.map(async (dir) => {
127
+ try {
128
+ const info = await stat(path.join(pluginRoot, dir));
129
+ return { dir, mtime: info.mtimeMs };
130
+ }
131
+ catch {
132
+ return { dir, mtime: 0 };
133
+ }
134
+ }));
135
+ stats.sort((left, right) => right.mtime - left.mtime);
136
+ return path.join(pluginRoot, stats[0].dir);
137
+ }
138
+ async function readPluginAgentNames(installPath) {
139
+ try {
140
+ const entries = await readdir(path.join(installPath, 'agents'), { withFileTypes: true });
141
+ return entries
142
+ .filter((entry) => entry.isFile() && (entry.name.endsWith('.md') || entry.name.endsWith('.toml')))
143
+ .map((entry) => entry.name.replace(/\.(md|toml)$/, ''))
144
+ .sort((left, right) => left.localeCompare(right));
145
+ }
146
+ catch {
147
+ return [];
148
+ }
149
+ }
150
+ async function readPluginCommandNames(installPath) {
151
+ try {
152
+ const entries = await readdir(path.join(installPath, 'commands'), { withFileTypes: true });
153
+ return entries
154
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
155
+ .map((entry) => entry.name.replace(/\.md$/, ''))
156
+ .sort((left, right) => left.localeCompare(right));
157
+ }
158
+ catch {
159
+ return [];
160
+ }
161
+ }
162
+ export async function readPluginMcpKeys(installPath) {
163
+ try {
164
+ const source = await readFile(path.join(installPath, '.mcp.json'), 'utf8');
165
+ const parsed = JSON.parse(source);
166
+ const servers = parsed.mcpServers && typeof parsed.mcpServers === 'object' && !Array.isArray(parsed.mcpServers)
167
+ ? parsed.mcpServers
168
+ : parsed;
169
+ return Object.keys(servers).sort((left, right) => left.localeCompare(right));
170
+ }
171
+ catch {
172
+ return [];
173
+ }
174
+ }
@@ -1,3 +1,4 @@
1
+ import { ProfileError } from '../errors.js';
1
2
  import { createClaudeWriter } from './sync/claude-writer.js';
2
3
  import { createCodexWriter } from './sync/codex-writer.js';
3
4
  import { createGeminiWriter } from './sync/gemini-writer.js';
@@ -21,6 +22,10 @@ export function createSyncService(dependencies = {}) {
21
22
  throw new Error('No active profile set. Run "brainctl profile use <name>" first.');
22
23
  }
23
24
  const profile = await profileService.get({ cwd, name: meta.active_profile });
25
+ const remoteMcpName = Object.entries(profile.mcps).find(([, config]) => config.kind === 'remote')?.[0];
26
+ if (remoteMcpName) {
27
+ throw new ProfileError(`Profile "${profile.name}" includes remote MCP "${remoteMcpName}". Remote MCP sync is not supported yet.`);
28
+ }
24
29
  const results = [];
25
30
  for (const agent of meta.agents) {
26
31
  const writer = writers[agent];
@@ -35,7 +40,7 @@ export function createSyncService(dependencies = {}) {
35
40
  agent,
36
41
  configPath: result.configPath,
37
42
  backedUpTo: result.backedUpTo,
38
- mcpCount: Object.keys(profile.mcps).length + 1, // +1 for brainctl itself
43
+ mcpCount: Object.keys(profile.mcps).length,
39
44
  });
40
45
  }
41
46
  return results;
@@ -0,0 +1,33 @@
1
+ export interface UpdateCheckResult {
2
+ current: string;
3
+ latest: string;
4
+ isOutdated: boolean;
5
+ fromCache: boolean;
6
+ }
7
+ export interface SelfUpdateResult {
8
+ success: boolean;
9
+ fromVersion: string;
10
+ toVersion: string;
11
+ error?: string;
12
+ }
13
+ export interface UpdateCheckService {
14
+ check(): Promise<UpdateCheckResult>;
15
+ selfUpdate(): Promise<SelfUpdateResult>;
16
+ }
17
+ interface UpdateCheckCache {
18
+ lastCheck: string;
19
+ latestVersion: string;
20
+ }
21
+ interface UpdateCheckDependencies {
22
+ currentVersion?: string;
23
+ fetchLatestVersion?: () => Promise<string>;
24
+ readCache?: () => Promise<UpdateCheckCache | null>;
25
+ writeCache?: (cache: UpdateCheckCache) => Promise<void>;
26
+ runInstall?: () => Promise<{
27
+ success: boolean;
28
+ error?: string;
29
+ }>;
30
+ }
31
+ export declare function createUpdateCheckService(dependencies?: UpdateCheckDependencies): UpdateCheckService;
32
+ export declare function isNewer(candidate: string, baseline: string): boolean;
33
+ export {};
@@ -0,0 +1,128 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import path from 'node:path';
5
+ import https from 'node:https';
6
+ import { execFile } from 'node:child_process';
7
+ import { promisify } from 'node:util';
8
+ const execFileAsync = promisify(execFile);
9
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
10
+ const CACHE_DIR = path.join(homedir(), '.brainctl');
11
+ const CACHE_PATH = path.join(CACHE_DIR, 'update-check.json');
12
+ const packageVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
13
+ export function createUpdateCheckService(dependencies = {}) {
14
+ const currentVersion = dependencies.currentVersion ?? packageVersion.version;
15
+ const fetchLatestVersion = dependencies.fetchLatestVersion ?? fetchFromRegistry;
16
+ const readCacheFn = dependencies.readCache ?? readCacheFile;
17
+ const writeCacheFn = dependencies.writeCache ?? writeCacheFile;
18
+ const runInstall = dependencies.runInstall ?? runNpmInstall;
19
+ return {
20
+ async check() {
21
+ const cached = await readCacheFn();
22
+ if (cached && isCacheValid(cached)) {
23
+ return {
24
+ current: currentVersion,
25
+ latest: cached.latestVersion,
26
+ isOutdated: isNewer(cached.latestVersion, currentVersion),
27
+ fromCache: true,
28
+ };
29
+ }
30
+ let latest;
31
+ try {
32
+ latest = await fetchLatestVersion();
33
+ }
34
+ catch {
35
+ return {
36
+ current: currentVersion,
37
+ latest: currentVersion,
38
+ isOutdated: false,
39
+ fromCache: false,
40
+ };
41
+ }
42
+ await writeCacheFn({
43
+ lastCheck: new Date().toISOString(),
44
+ latestVersion: latest,
45
+ }).catch(() => { });
46
+ return {
47
+ current: currentVersion,
48
+ latest,
49
+ isOutdated: isNewer(latest, currentVersion),
50
+ fromCache: false,
51
+ };
52
+ },
53
+ async selfUpdate() {
54
+ const { success, error } = await runInstall();
55
+ return {
56
+ success,
57
+ fromVersion: currentVersion,
58
+ toVersion: success ? 'latest' : currentVersion,
59
+ error,
60
+ };
61
+ },
62
+ };
63
+ }
64
+ function isCacheValid(cache) {
65
+ const lastCheck = new Date(cache.lastCheck).getTime();
66
+ return Date.now() - lastCheck < CACHE_TTL_MS;
67
+ }
68
+ export function isNewer(candidate, baseline) {
69
+ const parse = (v) => v.split('.').map(Number);
70
+ const [aMaj, aMin, aPat] = parse(candidate);
71
+ const [bMaj, bMin, bPat] = parse(baseline);
72
+ if (aMaj !== bMaj)
73
+ return aMaj > bMaj;
74
+ if (aMin !== bMin)
75
+ return aMin > bMin;
76
+ return aPat > bPat;
77
+ }
78
+ function fetchFromRegistry() {
79
+ return new Promise((resolve, reject) => {
80
+ const req = https.get('https://registry.npmjs.org/brainctl/latest', { timeout: 3000 }, (res) => {
81
+ let data = '';
82
+ res.on('data', (chunk) => {
83
+ data += chunk.toString();
84
+ });
85
+ res.on('end', () => {
86
+ try {
87
+ const pkg = JSON.parse(data);
88
+ resolve(pkg.version);
89
+ }
90
+ catch (e) {
91
+ reject(e);
92
+ }
93
+ });
94
+ });
95
+ req.on('error', reject);
96
+ req.on('timeout', () => {
97
+ req.destroy();
98
+ reject(new Error('timeout'));
99
+ });
100
+ });
101
+ }
102
+ async function readCacheFile() {
103
+ try {
104
+ const content = await readFile(CACHE_PATH, 'utf8');
105
+ return JSON.parse(content);
106
+ }
107
+ catch {
108
+ return null;
109
+ }
110
+ }
111
+ async function writeCacheFile(cache) {
112
+ await mkdir(CACHE_DIR, { recursive: true });
113
+ await writeFile(CACHE_PATH, JSON.stringify(cache, null, 2) + '\n', 'utf8');
114
+ }
115
+ async function runNpmInstall() {
116
+ try {
117
+ await execFileAsync('npm', ['install', '-g', 'brainctl@latest'], {
118
+ timeout: 60_000,
119
+ });
120
+ return { success: true };
121
+ }
122
+ catch (e) {
123
+ return {
124
+ success: false,
125
+ error: e instanceof Error ? e.message : String(e),
126
+ };
127
+ }
128
+ }
@@ -0,0 +1 @@
1
+ export declare function findExecutable(command: string): Promise<string | null>;
@@ -0,0 +1,38 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import path from 'node:path';
4
+ export async function findExecutable(command) {
5
+ if (command.includes(path.sep)) {
6
+ return (await isExecutable(command)) ? command : null;
7
+ }
8
+ const pathEntries = (process.env.PATH ?? '')
9
+ .split(path.delimiter)
10
+ .filter((entry) => entry.length > 0);
11
+ const extensions = process.platform === 'win32'
12
+ ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM')
13
+ .split(';')
14
+ .filter((entry) => entry.length > 0)
15
+ : [''];
16
+ for (const pathEntry of pathEntries) {
17
+ for (const extension of extensions) {
18
+ const candidate = process.platform === 'win32' &&
19
+ extension.length > 0 &&
20
+ !command.toLowerCase().endsWith(extension.toLowerCase())
21
+ ? path.join(pathEntry, `${command}${extension}`)
22
+ : path.join(pathEntry, command);
23
+ if (await isExecutable(candidate)) {
24
+ return candidate;
25
+ }
26
+ }
27
+ }
28
+ return null;
29
+ }
30
+ async function isExecutable(filePath) {
31
+ try {
32
+ await access(filePath, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
package/dist/types.d.ts CHANGED
@@ -55,20 +55,77 @@ export interface DiagnosticCheck {
55
55
  status: DiagnosticStatus;
56
56
  message: string;
57
57
  }
58
- export interface NpmMcpServerConfig {
59
- type: 'npm';
58
+ export interface PortableCredentialSpec {
59
+ key: string;
60
+ required: boolean;
61
+ description?: string;
62
+ }
63
+ export type PortableCredentialPlaceholder = `\${credentials.${string}}`;
64
+ export type PortableCredentialPreservedValue = PortableCredentialPlaceholder | `Bearer ${PortableCredentialPlaceholder}` | `Token ${PortableCredentialPlaceholder}`;
65
+ export type PortableProfileSource = {
66
+ kind: 'profile';
67
+ profileName: string;
68
+ } | {
69
+ kind: 'agent';
70
+ agent: AgentName;
71
+ };
72
+ export interface PortablePluginSnapshot {
73
+ agent: AgentName;
74
+ name: string;
75
+ source: string;
76
+ marketplace?: string;
77
+ version?: string;
78
+ archivePath: string;
79
+ managed?: boolean;
80
+ pluginSkills?: string[];
81
+ pluginMcps?: string[];
82
+ pluginAgents?: string[];
83
+ pluginCommands?: string[];
84
+ }
85
+ export interface PortableUserSkillSnapshot {
86
+ agent: AgentName;
87
+ name: string;
88
+ archivePath: string;
89
+ }
90
+ export interface PortableProfileManifest {
91
+ schemaVersion: 1 | 2;
92
+ profileName: string;
93
+ createdBy?: {
94
+ tool: string;
95
+ version: string;
96
+ };
97
+ source?: PortableProfileSource;
98
+ credentials?: PortableCredentialSpec[];
99
+ plugins?: PortablePluginSnapshot[];
100
+ userSkills?: PortableUserSkillSnapshot[];
101
+ }
102
+ export interface LocalNpmMcpServerConfig {
103
+ kind: 'local';
104
+ source: 'npm';
60
105
  package: string;
61
106
  env?: Record<string, string>;
62
107
  }
63
- export interface BundledMcpServerConfig {
64
- type: 'bundled';
108
+ export type McpRuntime = 'node' | 'python' | 'java' | 'go' | 'rust' | 'binary';
109
+ export interface LocalBundledMcpServerConfig {
110
+ kind: 'local';
111
+ source: 'bundled';
112
+ runtime: McpRuntime;
65
113
  path: string;
66
114
  install?: string;
67
115
  command: string;
68
116
  args?: string[];
117
+ exclude?: string[];
118
+ env?: Record<string, string>;
119
+ }
120
+ export interface RemoteMcpServerConfig {
121
+ kind: 'remote';
122
+ transport: 'http' | 'sse';
123
+ url: string;
124
+ headers?: Record<string, string>;
69
125
  env?: Record<string, string>;
70
126
  }
71
- export type McpServerConfig = NpmMcpServerConfig | BundledMcpServerConfig;
127
+ export type LocalMcpServerConfig = LocalNpmMcpServerConfig | LocalBundledMcpServerConfig;
128
+ export type McpServerConfig = LocalMcpServerConfig | RemoteMcpServerConfig;
72
129
  export interface ProfileConfig {
73
130
  name: string;
74
131
  description?: string;