brainctl 0.1.17 → 0.1.19
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/dist/cli.d.ts +9 -11
- package/dist/cli.js +17 -22
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/mcp.js +2 -2
- package/dist/commands/profile.d.ts +7 -3
- package/dist/commands/profile.js +106 -16
- package/dist/commands/status.d.ts +1 -1
- package/dist/commands/status.js +7 -7
- package/dist/{mcp/server.d.ts → mcp-server.d.ts} +1 -1
- package/dist/{mcp/server.js → mcp-server.js} +56 -149
- package/dist/services/agent/agent-asset-installer.d.ts +3 -0
- package/dist/services/agent/agent-asset-installer.js +109 -0
- package/dist/services/agent/agent-availability-service.d.ts +11 -0
- package/dist/services/agent/agent-availability-service.js +32 -0
- package/dist/services/{agent-config-service.d.ts → agent/agent-config-service.d.ts} +6 -6
- package/dist/services/{agent-config-service.js → agent/agent-config-service.js} +6 -6
- package/dist/services/{credential-redaction-service.d.ts → credential/credential-redaction-service.d.ts} +2 -1
- package/dist/services/{credential-redaction-service.js → credential/credential-redaction-service.js} +9 -3
- package/dist/services/{credential-resolution-service.d.ts → credential/credential-resolution-service.d.ts} +1 -1
- package/dist/services/{doctor-service.d.ts → platform/doctor-service.d.ts} +3 -3
- package/dist/services/platform/doctor-service.js +23 -0
- package/dist/services/{mcp-preflight-service.d.ts → platform/mcp-preflight-service.d.ts} +2 -2
- package/dist/services/{mcp-preflight-service.js → platform/mcp-preflight-service.js} +1 -1
- package/dist/services/{runtime-detector.d.ts → platform/runtime-detector.d.ts} +1 -1
- package/dist/services/platform/status-service.d.ts +19 -0
- package/dist/services/platform/status-service.js +22 -0
- package/dist/services/{update-check-service.js → platform/update-check-service.js} +1 -1
- package/dist/services/plugin/plugin-install-bundle.d.ts +20 -0
- package/dist/services/plugin/plugin-install-bundle.js +80 -0
- package/dist/services/plugin/plugin-install-compatibility.d.ts +15 -0
- package/dist/services/plugin/plugin-install-compatibility.js +91 -0
- package/dist/services/plugin/plugin-install-fs.d.ts +27 -0
- package/dist/services/plugin/plugin-install-fs.js +65 -0
- package/dist/services/{plugin-install-service.d.ts → plugin/plugin-install-service.d.ts} +4 -18
- package/dist/services/{plugin-install-service.js → plugin/plugin-install-service.js} +7 -308
- package/dist/services/plugin/plugin-install-uninstall.d.ts +12 -0
- package/dist/services/plugin/plugin-install-uninstall.js +76 -0
- package/dist/services/{skill-paths.d.ts → plugin/skill-paths.d.ts} +1 -1
- package/dist/services/{skill-preflight-service.d.ts → plugin/skill-preflight-service.d.ts} +1 -1
- package/dist/services/{portable-mcp-classifier.d.ts → profile/portable-mcp-classifier.d.ts} +3 -3
- package/dist/services/{portable-mcp-classifier.js → profile/portable-mcp-classifier.js} +2 -2
- package/dist/services/{portable-profile-pack-service.d.ts → profile/portable-profile-pack-service.d.ts} +8 -2
- package/dist/services/{portable-profile-pack-service.js → profile/portable-profile-pack-service.js} +83 -9
- package/dist/services/profile/profile-apply-service.d.ts +34 -0
- package/dist/services/profile/profile-apply-service.js +102 -0
- package/dist/services/{profile-export-service.d.ts → profile/profile-export-service.d.ts} +7 -3
- package/dist/services/{profile-export-service.js → profile/profile-export-service.js} +3 -1
- package/dist/services/{profile-import-service.d.ts → profile/profile-import-service.d.ts} +1 -1
- package/dist/services/{profile-import-service.js → profile/profile-import-service.js} +85 -130
- package/dist/services/{profile-service.d.ts → profile/profile-service.d.ts} +3 -11
- package/dist/services/{profile-service.js → profile/profile-service.js} +58 -103
- package/dist/services/profile/profile-snapshot-service.d.ts +12 -0
- package/dist/services/profile/profile-snapshot-service.js +47 -0
- package/dist/types.d.ts +2 -57
- package/dist/ui/routes.d.ts +1 -3
- package/dist/ui/routes.js +79 -128
- package/dist/ui/server.d.ts +1 -1
- package/dist/web/assets/index-CGmTbSgk.js +63 -0
- package/dist/web/assets/index-EIVU5Woh.css +2 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.js +0 -27
- package/dist/commands/run.d.ts +0 -3
- package/dist/commands/run.js +0 -25
- package/dist/commands/sync.d.ts +0 -3
- package/dist/commands/sync.js +0 -31
- package/dist/config.d.ts +0 -14
- package/dist/config.js +0 -96
- package/dist/context/builder.d.ts +0 -6
- package/dist/context/builder.js +0 -13
- package/dist/context/memory.d.ts +0 -5
- package/dist/context/memory.js +0 -43
- package/dist/context/skills.d.ts +0 -2
- package/dist/context/skills.js +0 -8
- package/dist/executor/claude.d.ts +0 -12
- package/dist/executor/claude.js +0 -16
- package/dist/executor/codex.d.ts +0 -12
- package/dist/executor/codex.js +0 -16
- package/dist/executor/process.d.ts +0 -11
- package/dist/executor/process.js +0 -40
- package/dist/executor/resolver.d.ts +0 -13
- package/dist/executor/resolver.js +0 -60
- package/dist/executor/types.d.ts +0 -14
- package/dist/executor/types.js +0 -1
- package/dist/services/config-write-service.d.ts +0 -12
- package/dist/services/config-write-service.js +0 -70
- package/dist/services/doctor-service.js +0 -79
- package/dist/services/init-service.d.ts +0 -14
- package/dist/services/init-service.js +0 -88
- package/dist/services/memory-write-service.d.ts +0 -12
- package/dist/services/memory-write-service.js +0 -56
- package/dist/services/run-service.d.ts +0 -15
- package/dist/services/run-service.js +0 -94
- package/dist/services/status-service.d.ts +0 -17
- package/dist/services/status-service.js +0 -21
- package/dist/services/sync-service.d.ts +0 -15
- package/dist/services/sync-service.js +0 -69
- package/dist/ui/streaming.d.ts +0 -3
- package/dist/ui/streaming.js +0 -16
- package/dist/web/assets/index-Bbophmwh.css +0 -2
- package/dist/web/assets/index-DDG_ylui.js +0 -63
- /package/dist/{system/executables.d.ts → executables.d.ts} +0 -0
- /package/dist/{system/executables.js → executables.js} +0 -0
- /package/dist/services/{agent-converter-service.d.ts → agent/agent-converter-service.d.ts} +0 -0
- /package/dist/services/{agent-converter-service.js → agent/agent-converter-service.js} +0 -0
- /package/dist/services/{credential-resolution-service.js → credential/credential-resolution-service.js} +0 -0
- /package/dist/services/{runtime-detector.js → platform/runtime-detector.js} +0 -0
- /package/dist/services/{update-check-service.d.ts → platform/update-check-service.d.ts} +0 -0
- /package/dist/services/{skill-paths.js → plugin/skill-paths.js} +0 -0
- /package/dist/services/{skill-preflight-service.js → plugin/skill-preflight-service.js} +0 -0
package/dist/cli.d.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { type DoctorService } from './services/doctor-service.js';
|
|
4
|
-
import { type
|
|
5
|
-
import { type ProfileExportService } from './services/profile-export-service.js';
|
|
6
|
-
import { type ProfileImportService } from './services/profile-import-service.js';
|
|
7
|
-
import { type ProfileService } from './services/profile-service.js';
|
|
8
|
-
import { type
|
|
9
|
-
import { type StatusService } from './services/status-service.js';
|
|
10
|
-
import { type SyncService } from './services/sync-service.js';
|
|
3
|
+
import { type DoctorService } from './services/platform/doctor-service.js';
|
|
4
|
+
import { type ProfileApplyService } from './services/profile/profile-apply-service.js';
|
|
5
|
+
import { type ProfileExportService } from './services/profile/profile-export-service.js';
|
|
6
|
+
import { type ProfileImportService } from './services/profile/profile-import-service.js';
|
|
7
|
+
import { type ProfileService } from './services/profile/profile-service.js';
|
|
8
|
+
import { type ProfileSnapshotService } from './services/profile/profile-snapshot-service.js';
|
|
9
|
+
import { type StatusService } from './services/platform/status-service.js';
|
|
11
10
|
export interface CliServices {
|
|
12
|
-
initService: InitService;
|
|
13
|
-
runService: RunService;
|
|
14
11
|
statusService: StatusService;
|
|
15
12
|
doctorService: DoctorService;
|
|
16
13
|
profileService: ProfileService;
|
|
17
14
|
profileExportService: ProfileExportService;
|
|
18
15
|
profileImportService: ProfileImportService;
|
|
19
|
-
|
|
16
|
+
profileApplyService: ProfileApplyService;
|
|
17
|
+
profileSnapshotService: ProfileSnapshotService;
|
|
20
18
|
}
|
|
21
19
|
export declare function createProgram(overrides?: Partial<CliServices>): Command;
|
|
22
20
|
export declare function main(argv?: string[]): Promise<void>;
|
package/dist/cli.js
CHANGED
|
@@ -5,24 +5,20 @@ import { createInterface } from 'node:readline';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import { registerDoctorCommand } from './commands/doctor.js';
|
|
8
|
-
import { registerInitCommand } from './commands/init.js';
|
|
9
8
|
import { registerMcpCommand } from './commands/mcp.js';
|
|
10
9
|
import { registerProfileCommand } from './commands/profile.js';
|
|
11
|
-
import { registerRunCommand } from './commands/run.js';
|
|
12
10
|
import { registerStatusCommand } from './commands/status.js';
|
|
13
|
-
import { registerSyncCommand } from './commands/sync.js';
|
|
14
11
|
import { registerUiCommand } from './commands/ui.js';
|
|
15
12
|
import { printError } from './output.js';
|
|
16
|
-
import { createUpdateCheckService } from './services/update-check-service.js';
|
|
17
|
-
import { createDoctorService } from './services/doctor-service.js';
|
|
18
|
-
import {
|
|
19
|
-
import { createProfileExportService } from './services/profile-export-service.js';
|
|
20
|
-
import { createProfileImportService } from './services/profile-import-service.js';
|
|
21
|
-
import { createProfileService } from './services/profile-service.js';
|
|
22
|
-
import {
|
|
23
|
-
import { createStatusService } from './services/status-service.js';
|
|
24
|
-
import {
|
|
25
|
-
import { createExecutorResolver } from './executor/resolver.js';
|
|
13
|
+
import { createUpdateCheckService } from './services/platform/update-check-service.js';
|
|
14
|
+
import { createDoctorService } from './services/platform/doctor-service.js';
|
|
15
|
+
import { createProfileApplyService } from './services/profile/profile-apply-service.js';
|
|
16
|
+
import { createProfileExportService } from './services/profile/profile-export-service.js';
|
|
17
|
+
import { createProfileImportService } from './services/profile/profile-import-service.js';
|
|
18
|
+
import { createProfileService } from './services/profile/profile-service.js';
|
|
19
|
+
import { createProfileSnapshotService } from './services/profile/profile-snapshot-service.js';
|
|
20
|
+
import { createStatusService } from './services/platform/status-service.js';
|
|
21
|
+
import { createAgentAvailabilityService } from './services/agent/agent-availability-service.js';
|
|
26
22
|
const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
27
23
|
export function createProgram(overrides = {}) {
|
|
28
24
|
const services = createDefaultServices(overrides);
|
|
@@ -31,16 +27,15 @@ export function createProgram(overrides = {}) {
|
|
|
31
27
|
.name('brainctl')
|
|
32
28
|
.description('Manage repeatable AI environments for local agent workflows')
|
|
33
29
|
.version(packageVersion.version);
|
|
34
|
-
registerInitCommand(program, services.initService);
|
|
35
30
|
registerStatusCommand(program, services.statusService);
|
|
36
|
-
registerRunCommand(program, services.runService);
|
|
37
31
|
registerDoctorCommand(program, services.doctorService);
|
|
38
32
|
registerProfileCommand(program, {
|
|
39
33
|
profileService: services.profileService,
|
|
40
34
|
profileExportService: services.profileExportService,
|
|
41
35
|
profileImportService: services.profileImportService,
|
|
36
|
+
profileApplyService: services.profileApplyService,
|
|
37
|
+
profileSnapshotService: services.profileSnapshotService,
|
|
42
38
|
});
|
|
43
|
-
registerSyncCommand(program, services.syncService);
|
|
44
39
|
registerUiCommand(program);
|
|
45
40
|
registerMcpCommand(program);
|
|
46
41
|
return program;
|
|
@@ -100,17 +95,17 @@ export function shouldRunMain(entryPointPath, moduleUrl) {
|
|
|
100
95
|
return resolveRealPath(entryPointPath) === resolveRealPath(fileURLToPath(moduleUrl));
|
|
101
96
|
}
|
|
102
97
|
function createDefaultServices(overrides) {
|
|
103
|
-
const
|
|
98
|
+
const availabilityService = createAgentAvailabilityService();
|
|
104
99
|
const profileService = createProfileService();
|
|
100
|
+
const profileSnapshotService = createProfileSnapshotService();
|
|
105
101
|
return {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
statusService: createStatusService({ resolver }),
|
|
109
|
-
doctorService: createDoctorService({ resolver }),
|
|
102
|
+
statusService: createStatusService({ availabilityService }),
|
|
103
|
+
doctorService: createDoctorService({ availabilityService }),
|
|
110
104
|
profileService,
|
|
111
105
|
profileExportService: createProfileExportService({ profileService }),
|
|
112
106
|
profileImportService: createProfileImportService(),
|
|
113
|
-
|
|
107
|
+
profileApplyService: createProfileApplyService({ profileService, snapshotService: profileSnapshotService }),
|
|
108
|
+
profileSnapshotService,
|
|
114
109
|
...overrides
|
|
115
110
|
};
|
|
116
111
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import type { DoctorService } from '../services/doctor-service.js';
|
|
2
|
+
import type { DoctorService } from '../services/platform/doctor-service.js';
|
|
3
3
|
export declare function registerDoctorCommand(program: Command, doctorService: DoctorService): void;
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { startMcpServer } from '../mcp
|
|
3
|
-
import { createUpdateCheckService } from '../services/update-check-service.js';
|
|
2
|
+
import { startMcpServer } from '../mcp-server.js';
|
|
3
|
+
import { createUpdateCheckService } from '../services/platform/update-check-service.js';
|
|
4
4
|
export function registerMcpCommand(program) {
|
|
5
5
|
program
|
|
6
6
|
.command('mcp')
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
2
|
+
import type { ProfileApplyService } from '../services/profile/profile-apply-service.js';
|
|
3
|
+
import type { ProfileExportService } from '../services/profile/profile-export-service.js';
|
|
4
|
+
import type { ProfileImportService } from '../services/profile/profile-import-service.js';
|
|
5
|
+
import type { ProfileService } from '../services/profile/profile-service.js';
|
|
6
|
+
import type { ProfileSnapshotService } from '../services/profile/profile-snapshot-service.js';
|
|
5
7
|
export interface ProfileCommandServices {
|
|
6
8
|
profileService: ProfileService;
|
|
7
9
|
profileExportService: ProfileExportService;
|
|
8
10
|
profileImportService: ProfileImportService;
|
|
11
|
+
profileApplyService: ProfileApplyService;
|
|
12
|
+
profileSnapshotService: ProfileSnapshotService;
|
|
9
13
|
}
|
|
10
14
|
export declare function registerProfileCommand(program: Command, services: ProfileCommandServices): void;
|
package/dist/commands/profile.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
|
+
const ALL_AGENTS = ['claude', 'codex', 'gemini'];
|
|
2
3
|
export function registerProfileCommand(program, services) {
|
|
3
|
-
const { profileService, profileExportService, profileImportService } = services;
|
|
4
|
+
const { profileService, profileExportService, profileImportService, profileApplyService, profileSnapshotService, } = services;
|
|
4
5
|
const profileCmd = program
|
|
5
6
|
.command('profile')
|
|
6
7
|
.description('Manage brainctl profiles');
|
|
@@ -8,15 +9,14 @@ export function registerProfileCommand(program, services) {
|
|
|
8
9
|
.command('list')
|
|
9
10
|
.description('List available profiles')
|
|
10
11
|
.action(async () => {
|
|
11
|
-
const { profiles
|
|
12
|
+
const { profiles } = await profileService.list({ cwd: process.cwd() });
|
|
12
13
|
if (profiles.length === 0) {
|
|
13
14
|
console.log('No profiles found. Run "brainctl profile create <name>" to create one.');
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
console.log(pc.bold('Profiles:'));
|
|
17
18
|
for (const name of profiles) {
|
|
18
|
-
|
|
19
|
-
console.log(` ${name}${marker}`);
|
|
19
|
+
console.log(` ${name}`);
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
22
|
profileCmd
|
|
@@ -32,21 +32,14 @@ export function registerProfileCommand(program, services) {
|
|
|
32
32
|
});
|
|
33
33
|
console.log(`Created profile at ${result.profilePath}`);
|
|
34
34
|
});
|
|
35
|
-
profileCmd
|
|
36
|
-
.command('use')
|
|
37
|
-
.argument('<name>', 'Profile name to activate')
|
|
38
|
-
.description('Switch the active profile')
|
|
39
|
-
.action(async (name) => {
|
|
40
|
-
const result = await profileService.use({ cwd: process.cwd(), name });
|
|
41
|
-
const prev = result.previousProfile ? ` (was "${result.previousProfile}")` : '';
|
|
42
|
-
console.log(`Switched to profile "${name}"${prev}`);
|
|
43
|
-
});
|
|
44
35
|
profileCmd
|
|
45
36
|
.command('export')
|
|
46
37
|
.argument('[name]', 'Profile name to export')
|
|
47
38
|
.option('-a, --agent <name>', 'Pack a live agent config instead (claude, codex, gemini)')
|
|
48
|
-
.option('-o, --output <path>', 'Output file path')
|
|
49
|
-
.
|
|
39
|
+
.option('-o, --output <path>', 'Output file or directory path')
|
|
40
|
+
.option('-f, --format <format>', 'Output format: tarball (default) or folder', 'tarball')
|
|
41
|
+
.option('--credentials <mode>', 'How to handle secrets: redact (default, public-safe) or keep (writes .env with real values for self-sync)', 'redact')
|
|
42
|
+
.description('Export a profile as a portable tarball or folder')
|
|
50
43
|
.action(async (name, options) => {
|
|
51
44
|
const agent = options.agent === 'claude' || options.agent === 'codex' || options.agent === 'gemini'
|
|
52
45
|
? options.agent
|
|
@@ -54,14 +47,28 @@ export function registerProfileCommand(program, services) {
|
|
|
54
47
|
if (!agent && !name) {
|
|
55
48
|
throw new Error('Provide a profile name or --agent <name>.');
|
|
56
49
|
}
|
|
50
|
+
if (options.format && options.format !== 'tarball' && options.format !== 'folder') {
|
|
51
|
+
throw new Error(`Invalid --format "${options.format}". Use "tarball" or "folder".`);
|
|
52
|
+
}
|
|
53
|
+
if (options.credentials &&
|
|
54
|
+
options.credentials !== 'redact' &&
|
|
55
|
+
options.credentials !== 'keep') {
|
|
56
|
+
throw new Error(`Invalid --credentials "${options.credentials}". Use "redact" or "keep".`);
|
|
57
|
+
}
|
|
57
58
|
const result = await profileExportService.execute({
|
|
58
59
|
cwd: process.cwd(),
|
|
59
60
|
source: agent
|
|
60
61
|
? { source: 'agent', agent, cwd: process.cwd() }
|
|
61
62
|
: { source: 'profile', name: name },
|
|
62
63
|
outputPath: options.output,
|
|
64
|
+
format: options.format ?? 'tarball',
|
|
65
|
+
credentialsMode: options.credentials ?? 'redact',
|
|
63
66
|
});
|
|
64
|
-
|
|
67
|
+
for (const warning of result.warnings) {
|
|
68
|
+
console.warn(pc.yellow(`warning: ${warning}`));
|
|
69
|
+
}
|
|
70
|
+
const label = result.format === 'folder' ? 'profile folder' : 'profile tarball';
|
|
71
|
+
console.log(`Exported ${label} to ${result.archivePath}`);
|
|
65
72
|
});
|
|
66
73
|
profileCmd
|
|
67
74
|
.command('import')
|
|
@@ -81,6 +88,89 @@ export function registerProfileCommand(program, services) {
|
|
|
81
88
|
console.log(`Installed bundled MCPs: ${result.installedMcps.join(', ')}`);
|
|
82
89
|
}
|
|
83
90
|
});
|
|
91
|
+
profileCmd
|
|
92
|
+
.command('apply')
|
|
93
|
+
.argument('<name>', 'Profile name to apply')
|
|
94
|
+
.option('-a, --agent <list>', 'Comma-separated agents to target (claude, codex, gemini, or all)', 'all')
|
|
95
|
+
.option('-i, --items <list>', 'Comma-separated items to apply (e.g. mcp:github,plugin:demo,skill:reviewer). Default: everything matching.')
|
|
96
|
+
.option('--no-backup', 'Skip auto-backup of live agent state before applying')
|
|
97
|
+
.description('Apply a profile (MCPs + plugins + skills) to selected agents')
|
|
98
|
+
.action(async (name, options) => {
|
|
99
|
+
const agents = parseAgentList(options.agent);
|
|
100
|
+
const items = options.items ? parseItemList(options.items) : undefined;
|
|
101
|
+
const { backups, applied } = await profileApplyService.execute({
|
|
102
|
+
cwd: process.cwd(),
|
|
103
|
+
profileName: name,
|
|
104
|
+
agents,
|
|
105
|
+
items,
|
|
106
|
+
backup: options.backup,
|
|
107
|
+
});
|
|
108
|
+
if (backups.length > 0) {
|
|
109
|
+
console.log(pc.bold('Backups:'));
|
|
110
|
+
for (const b of backups) {
|
|
111
|
+
console.log(` ${b.agent} -> ${b.profileName}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
console.log(pc.bold(`Applied "${name}" to:`));
|
|
115
|
+
for (const r of applied) {
|
|
116
|
+
const extras = [`${r.mcpCount} MCPs`];
|
|
117
|
+
if (r.pluginsInstalled?.length)
|
|
118
|
+
extras.push(`plugins: ${r.pluginsInstalled.join(',')}`);
|
|
119
|
+
if (r.userSkillsInstalled?.length)
|
|
120
|
+
extras.push(`skills: ${r.userSkillsInstalled.join(',')}`);
|
|
121
|
+
console.log(` ${r.agent}: ${extras.join(' | ')}`);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
profileCmd
|
|
125
|
+
.command('snapshot')
|
|
126
|
+
.option('-a, --agent <name>', 'Agent to snapshot (claude, codex, gemini)')
|
|
127
|
+
.option('--as <name>', 'Profile name to write into (default: backup-<agent>-<timestamp>)')
|
|
128
|
+
.description("Snapshot a live agent's MCPs+plugins+skills into a new profile folder")
|
|
129
|
+
.action(async (options) => {
|
|
130
|
+
if (!options.agent || !ALL_AGENTS.includes(options.agent)) {
|
|
131
|
+
throw new Error('Provide --agent <claude|codex|gemini>.');
|
|
132
|
+
}
|
|
133
|
+
const agent = options.agent;
|
|
134
|
+
const { defaultBackupProfileName } = await import('../services/profile/profile-snapshot-service.js');
|
|
135
|
+
const profileName = options.as ?? defaultBackupProfileName(agent);
|
|
136
|
+
const result = await profileSnapshotService.execute({
|
|
137
|
+
cwd: process.cwd(),
|
|
138
|
+
agent,
|
|
139
|
+
profileName,
|
|
140
|
+
});
|
|
141
|
+
console.log(`Snapshotted ${agent} into ${result.profilePath}`);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
function parseAgentList(value) {
|
|
145
|
+
if (value === 'all')
|
|
146
|
+
return [...ALL_AGENTS];
|
|
147
|
+
const parts = value.split(',').map((s) => s.trim()).filter(Boolean);
|
|
148
|
+
for (const p of parts) {
|
|
149
|
+
if (!ALL_AGENTS.includes(p)) {
|
|
150
|
+
throw new Error(`Invalid agent "${p}". Use claude, codex, gemini, or all.`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return parts;
|
|
154
|
+
}
|
|
155
|
+
function parseItemList(value) {
|
|
156
|
+
return value
|
|
157
|
+
.split(',')
|
|
158
|
+
.map((s) => s.trim())
|
|
159
|
+
.filter(Boolean)
|
|
160
|
+
.map((entry) => {
|
|
161
|
+
const colonIdx = entry.indexOf(':');
|
|
162
|
+
if (colonIdx <= 0) {
|
|
163
|
+
throw new Error(`Invalid item "${entry}". Use type:name (e.g. mcp:github).`);
|
|
164
|
+
}
|
|
165
|
+
const type = entry.slice(0, colonIdx);
|
|
166
|
+
const name = entry.slice(colonIdx + 1);
|
|
167
|
+
if (type !== 'mcp' && type !== 'plugin' && type !== 'skill') {
|
|
168
|
+
throw new Error(`Invalid item type "${type}". Use mcp, plugin, or skill.`);
|
|
169
|
+
}
|
|
170
|
+
if (!name)
|
|
171
|
+
throw new Error(`Item "${entry}" missing name.`);
|
|
172
|
+
return { type, name };
|
|
173
|
+
});
|
|
84
174
|
}
|
|
85
175
|
function collectCredentialOption(value, previous) {
|
|
86
176
|
return [...previous, value];
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
-
import type { StatusService } from '../services/status-service.js';
|
|
2
|
+
import type { StatusService } from '../services/platform/status-service.js';
|
|
3
3
|
export declare function registerStatusCommand(program: Command, statusService: StatusService): void;
|
package/dist/commands/status.js
CHANGED
|
@@ -2,17 +2,17 @@ import pc from 'picocolors';
|
|
|
2
2
|
export function registerStatusCommand(program, statusService) {
|
|
3
3
|
program
|
|
4
4
|
.command('status')
|
|
5
|
-
.description('Show
|
|
5
|
+
.description('Show agent availability and profile inventory')
|
|
6
6
|
.action(async () => {
|
|
7
7
|
const status = await statusService.execute({ cwd: process.cwd() });
|
|
8
8
|
console.log(pc.bold('brainctl status'));
|
|
9
|
-
console.log(`
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
console.log('
|
|
9
|
+
console.log(`Profiles: ${status.profiles.count}`);
|
|
10
|
+
for (const name of status.profiles.names) {
|
|
11
|
+
console.log(` ${name}`);
|
|
12
|
+
}
|
|
13
|
+
console.log('Agents:');
|
|
14
14
|
for (const agent of Object.values(status.agents)) {
|
|
15
|
-
console.log(
|
|
15
|
+
console.log(` ${agent.agent}: ${agent.available ? pc.green('available') : pc.yellow('missing')}`);
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
}
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
3
|
import { FastMCP } from 'fastmcp';
|
|
5
4
|
import { z } from 'zod';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
import { createSyncService } from '../services/sync-service.js';
|
|
18
|
-
const packageVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
|
|
5
|
+
import { createAgentConfigService } from './services/agent/agent-config-service.js';
|
|
6
|
+
import { createDoctorService } from './services/platform/doctor-service.js';
|
|
7
|
+
import { startUiServer } from './ui/server.js';
|
|
8
|
+
import { createProfileExportService } from './services/profile/profile-export-service.js';
|
|
9
|
+
import { createProfileImportService } from './services/profile/profile-import-service.js';
|
|
10
|
+
import { createProfileApplyService } from './services/profile/profile-apply-service.js';
|
|
11
|
+
import { createProfileService } from './services/profile/profile-service.js';
|
|
12
|
+
import { createProfileSnapshotService, defaultBackupProfileName, } from './services/profile/profile-snapshot-service.js';
|
|
13
|
+
import { createStatusService } from './services/platform/status-service.js';
|
|
14
|
+
const ALL_AGENTS = ['claude', 'codex', 'gemini'];
|
|
15
|
+
const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
19
16
|
export function createMcpServer(options = {}) {
|
|
20
17
|
const cwd = options.cwd ?? process.cwd();
|
|
21
18
|
const uiState = options.uiServerState ?? { current: null };
|
|
@@ -23,53 +20,6 @@ export function createMcpServer(options = {}) {
|
|
|
23
20
|
name: 'brainctl',
|
|
24
21
|
version: packageVersion.version,
|
|
25
22
|
});
|
|
26
|
-
server.addTool({
|
|
27
|
-
name: 'brainctl_list_skills',
|
|
28
|
-
description: 'List available skills from the ai-stack.yaml config',
|
|
29
|
-
parameters: z.object({}),
|
|
30
|
-
execute: async () => {
|
|
31
|
-
const config = await loadConfig({ cwd });
|
|
32
|
-
const skills = Object.entries(config.skills).map(([name, skill]) => ({
|
|
33
|
-
name,
|
|
34
|
-
description: skill.description ?? null,
|
|
35
|
-
}));
|
|
36
|
-
return JSON.stringify(skills, null, 2);
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
server.addTool({
|
|
40
|
-
name: 'brainctl_run',
|
|
41
|
-
description: 'Execute a skill with input text. Runs the skill through the configured agent and returns the output.',
|
|
42
|
-
parameters: z.object({
|
|
43
|
-
skill: z.string().describe('Skill name as defined in ai-stack.yaml'),
|
|
44
|
-
input: z.string().describe('Input text to pass to the skill'),
|
|
45
|
-
agent: z.enum(['claude', 'codex']).default('claude').describe('Agent to use for execution'),
|
|
46
|
-
fallback_agent: z.enum(['claude', 'codex']).optional().describe('Fallback agent if primary is unavailable'),
|
|
47
|
-
}),
|
|
48
|
-
execute: async (args) => {
|
|
49
|
-
const inputPath = path.join(cwd, `.brainctl-mcp-input-${Date.now()}.tmp`);
|
|
50
|
-
const { writeFile: writeFileAsync, unlink } = await import('node:fs/promises');
|
|
51
|
-
try {
|
|
52
|
-
await writeFileAsync(inputPath, args.input, 'utf8');
|
|
53
|
-
const runService = createRunService();
|
|
54
|
-
const trace = await runService.execute({
|
|
55
|
-
cwd,
|
|
56
|
-
skill: args.skill,
|
|
57
|
-
inputFile: path.basename(inputPath),
|
|
58
|
-
primaryAgent: args.agent,
|
|
59
|
-
fallbackAgent: args.fallback_agent,
|
|
60
|
-
});
|
|
61
|
-
return trace.finalOutput;
|
|
62
|
-
}
|
|
63
|
-
finally {
|
|
64
|
-
try {
|
|
65
|
-
await unlink(inputPath);
|
|
66
|
-
}
|
|
67
|
-
catch {
|
|
68
|
-
// temp file cleanup is best-effort
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
});
|
|
73
23
|
server.addTool({
|
|
74
24
|
name: 'brainctl_status',
|
|
75
25
|
description: 'Show project status: config path, memory files, available skills, and agent availability',
|
|
@@ -91,94 +41,67 @@ export function createMcpServer(options = {}) {
|
|
|
91
41
|
},
|
|
92
42
|
});
|
|
93
43
|
server.addTool({
|
|
94
|
-
name: '
|
|
95
|
-
description: '
|
|
44
|
+
name: 'brainctl_list_profiles',
|
|
45
|
+
description: 'List available profiles and show which one is active.',
|
|
96
46
|
parameters: z.object({}),
|
|
97
47
|
execute: async () => {
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const result = {
|
|
101
|
-
count: memory.count,
|
|
102
|
-
files: memory.entries.map((entry) => ({
|
|
103
|
-
path: entry.path,
|
|
104
|
-
content: entry.content,
|
|
105
|
-
})),
|
|
106
|
-
};
|
|
48
|
+
const profileService = createProfileService();
|
|
49
|
+
const result = await profileService.list({ cwd });
|
|
107
50
|
return JSON.stringify(result, null, 2);
|
|
108
51
|
},
|
|
109
52
|
});
|
|
110
53
|
server.addTool({
|
|
111
|
-
name: '
|
|
112
|
-
description: '
|
|
54
|
+
name: 'brainctl_apply_profile',
|
|
55
|
+
description: 'Apply a profile (MCPs, plugins, user skills) to the specified agents. Selective by --agents and --items. Auto-backs up live agent state before a full apply unless backup=false.',
|
|
113
56
|
parameters: z.object({
|
|
114
|
-
|
|
115
|
-
|
|
57
|
+
name: z.string().describe('Profile name to apply'),
|
|
58
|
+
agents: z
|
|
59
|
+
.array(z.enum(['claude', 'codex', 'gemini']))
|
|
60
|
+
.optional()
|
|
61
|
+
.describe('Agents to target (default: all three)'),
|
|
62
|
+
items: z
|
|
63
|
+
.array(z.object({
|
|
64
|
+
type: z.enum(['mcp', 'plugin', 'skill']),
|
|
65
|
+
name: z.string(),
|
|
66
|
+
}))
|
|
67
|
+
.optional()
|
|
68
|
+
.describe('Specific items to apply (default: everything matching)'),
|
|
69
|
+
backup: z
|
|
70
|
+
.boolean()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe('Force backup on/off (default: on for full apply, off for partial)'),
|
|
116
73
|
}),
|
|
117
74
|
execute: async (args) => {
|
|
118
|
-
const
|
|
119
|
-
const result = await
|
|
75
|
+
const applyService = createProfileApplyService();
|
|
76
|
+
const result = await applyService.execute({
|
|
120
77
|
cwd,
|
|
121
|
-
|
|
122
|
-
|
|
78
|
+
profileName: args.name,
|
|
79
|
+
agents: args.agents ?? ALL_AGENTS,
|
|
80
|
+
items: args.items,
|
|
81
|
+
backup: args.backup,
|
|
123
82
|
});
|
|
124
|
-
return JSON.stringify({ written: result.filePath });
|
|
125
|
-
},
|
|
126
|
-
});
|
|
127
|
-
server.addTool({
|
|
128
|
-
name: 'brainctl_get_skill',
|
|
129
|
-
description: 'Get the full details of a specific skill including its prompt text and description. Use this to understand what a skill does before running it.',
|
|
130
|
-
parameters: z.object({
|
|
131
|
-
skill: z.string().describe('Skill name as defined in ai-stack.yaml'),
|
|
132
|
-
}),
|
|
133
|
-
execute: async (args) => {
|
|
134
|
-
const config = await loadConfig({ cwd });
|
|
135
|
-
const skillConfig = config.skills[args.skill];
|
|
136
|
-
if (!skillConfig) {
|
|
137
|
-
throw new Error(`Skill "${args.skill}" is not defined in ai-stack.yaml.`);
|
|
138
|
-
}
|
|
139
|
-
return JSON.stringify({
|
|
140
|
-
name: args.skill,
|
|
141
|
-
description: skillConfig.description ?? null,
|
|
142
|
-
prompt: skillConfig.prompt,
|
|
143
|
-
}, null, 2);
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
server.addTool({
|
|
147
|
-
name: 'brainctl_list_profiles',
|
|
148
|
-
description: 'List available profiles and show which one is active.',
|
|
149
|
-
parameters: z.object({}),
|
|
150
|
-
execute: async () => {
|
|
151
|
-
const profileService = createProfileService();
|
|
152
|
-
const result = await profileService.list({ cwd });
|
|
153
83
|
return JSON.stringify(result, null, 2);
|
|
154
84
|
},
|
|
155
85
|
});
|
|
156
86
|
server.addTool({
|
|
157
|
-
name: '
|
|
158
|
-
description: '
|
|
87
|
+
name: 'brainctl_snapshot_agent',
|
|
88
|
+
description: "Snapshot a live agent's MCPs+plugins+skills into a new profile folder. Useful for backups or capturing your current setup as a shareable profile.",
|
|
159
89
|
parameters: z.object({
|
|
160
|
-
|
|
90
|
+
agent: z.enum(['claude', 'codex', 'gemini']),
|
|
91
|
+
as: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe('Profile name to write into (default: backup-<agent>-<timestamp>)'),
|
|
161
95
|
}),
|
|
162
96
|
execute: async (args) => {
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}, null, 2);
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
server.addTool({
|
|
175
|
-
name: 'brainctl_sync',
|
|
176
|
-
description: 'Sync the active profile to all configured agent configs (Claude, Codex). Creates backups before overwriting.',
|
|
177
|
-
parameters: z.object({}),
|
|
178
|
-
execute: async () => {
|
|
179
|
-
const syncService = createSyncService();
|
|
180
|
-
const result = await syncService.execute({ cwd });
|
|
181
|
-
return JSON.stringify(result, null, 2);
|
|
97
|
+
const snapshotService = createProfileSnapshotService();
|
|
98
|
+
const profileName = args.as ?? defaultBackupProfileName(args.agent);
|
|
99
|
+
const result = await snapshotService.execute({
|
|
100
|
+
cwd,
|
|
101
|
+
agent: args.agent,
|
|
102
|
+
profileName,
|
|
103
|
+
});
|
|
104
|
+
return JSON.stringify({ profileName, ...result }, null, 2);
|
|
182
105
|
},
|
|
183
106
|
});
|
|
184
107
|
server.addTool({
|
|
@@ -212,20 +135,13 @@ export function createMcpServer(options = {}) {
|
|
|
212
135
|
});
|
|
213
136
|
server.addTool({
|
|
214
137
|
name: 'brainctl_update_profile',
|
|
215
|
-
description: 'Update a profile config. Pass the full profile object with
|
|
138
|
+
description: 'Update a profile config. Pass the full profile object with name, optional description, and mcps map.',
|
|
216
139
|
parameters: z.object({
|
|
217
140
|
name: z.string().describe('Profile name to update'),
|
|
218
141
|
config: z.object({
|
|
219
142
|
name: z.string(),
|
|
220
143
|
description: z.string().optional(),
|
|
221
|
-
skills: z.record(z.string(), z.object({
|
|
222
|
-
description: z.string().optional(),
|
|
223
|
-
prompt: z.string(),
|
|
224
|
-
})),
|
|
225
144
|
mcps: z.record(z.string(), z.unknown()),
|
|
226
|
-
memory: z.object({
|
|
227
|
-
paths: z.array(z.string()),
|
|
228
|
-
}),
|
|
229
145
|
}).describe('Full profile config object'),
|
|
230
146
|
}),
|
|
231
147
|
execute: async (args) => {
|
|
@@ -252,25 +168,17 @@ export function createMcpServer(options = {}) {
|
|
|
252
168
|
});
|
|
253
169
|
server.addTool({
|
|
254
170
|
name: 'brainctl_copy_profile_items',
|
|
255
|
-
description: 'Copy
|
|
171
|
+
description: 'Copy MCPs from one profile to another. Existing MCPs with the same key in the target are overwritten.',
|
|
256
172
|
parameters: z.object({
|
|
257
173
|
source: z.string().describe('Source profile name'),
|
|
258
174
|
target: z.string().describe('Target profile name'),
|
|
259
|
-
skills: z.array(z.string()).default([]).describe('Skill keys to copy'),
|
|
260
175
|
mcps: z.array(z.string()).default([]).describe('MCP keys to copy'),
|
|
261
176
|
}),
|
|
262
177
|
execute: async (args) => {
|
|
263
178
|
const profileService = createProfileService();
|
|
264
179
|
const sourceProfile = await profileService.get({ cwd, name: args.source });
|
|
265
180
|
const targetProfile = await profileService.get({ cwd, name: args.target });
|
|
266
|
-
const copiedSkills = [];
|
|
267
181
|
const copiedMcps = [];
|
|
268
|
-
for (const key of args.skills) {
|
|
269
|
-
if (sourceProfile.skills[key]) {
|
|
270
|
-
targetProfile.skills[key] = sourceProfile.skills[key];
|
|
271
|
-
copiedSkills.push(key);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
182
|
for (const key of args.mcps) {
|
|
275
183
|
if (sourceProfile.mcps[key]) {
|
|
276
184
|
targetProfile.mcps[key] = sourceProfile.mcps[key];
|
|
@@ -281,7 +189,6 @@ export function createMcpServer(options = {}) {
|
|
|
281
189
|
return JSON.stringify({
|
|
282
190
|
source: args.source,
|
|
283
191
|
target: args.target,
|
|
284
|
-
copiedSkills,
|
|
285
192
|
copiedMcps,
|
|
286
193
|
}, null, 2);
|
|
287
194
|
},
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { PortablePluginSnapshot, PortableUserSkillSnapshot } from '../../types.js';
|
|
2
|
+
export declare function installPlugin(sourceDir: string, plugin: PortablePluginSnapshot): Promise<void>;
|
|
3
|
+
export declare function installUserSkill(sourceDir: string, skill: PortableUserSkillSnapshot): Promise<void>;
|