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
|
@@ -1,133 +1,115 @@
|
|
|
1
|
-
import { readdir, readFile, writeFile, mkdir,
|
|
1
|
+
import { readdir, readFile, writeFile, mkdir, rename, rm, stat } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
|
-
import { ProfileError, ProfileNotFoundError } from '
|
|
4
|
+
import { ProfileError, ProfileNotFoundError } from '../../errors.js';
|
|
5
5
|
const VALID_RUNTIMES = new Set(['node', 'python', 'java', 'go', 'rust', 'binary']);
|
|
6
|
-
const BRAINCTL_DIR = '.brainctl';
|
|
7
6
|
const PROFILES_DIR = '.brainctl/profiles';
|
|
8
|
-
const
|
|
7
|
+
const PROFILE_FILE = 'profile.yaml';
|
|
8
|
+
export function profileDir(cwd, name) {
|
|
9
|
+
return path.join(cwd, PROFILES_DIR, name);
|
|
10
|
+
}
|
|
11
|
+
export function profileFile(cwd, name) {
|
|
12
|
+
return path.join(profileDir(cwd, name), PROFILE_FILE);
|
|
13
|
+
}
|
|
14
|
+
function legacyProfileFile(cwd, name) {
|
|
15
|
+
return path.join(cwd, PROFILES_DIR, `${name}.yaml`);
|
|
16
|
+
}
|
|
17
|
+
async function migrateLegacyProfile(cwd, name) {
|
|
18
|
+
const legacy = legacyProfileFile(cwd, name);
|
|
19
|
+
const folder = profileDir(cwd, name);
|
|
20
|
+
const newFile = profileFile(cwd, name);
|
|
21
|
+
if (!(await pathExists(legacy)))
|
|
22
|
+
return;
|
|
23
|
+
if (await pathExists(newFile))
|
|
24
|
+
return;
|
|
25
|
+
await mkdir(folder, { recursive: true });
|
|
26
|
+
await rename(legacy, newFile);
|
|
27
|
+
}
|
|
9
28
|
export function createProfileService() {
|
|
10
29
|
return {
|
|
11
30
|
async list(options = {}) {
|
|
12
31
|
const cwd = options.cwd ?? process.cwd();
|
|
13
32
|
const profilesDir = path.join(cwd, PROFILES_DIR);
|
|
14
|
-
|
|
33
|
+
const names = new Set();
|
|
15
34
|
try {
|
|
16
|
-
const entries = await readdir(profilesDir);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
35
|
+
const entries = await readdir(profilesDir, { withFileTypes: true });
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
if (await pathExists(path.join(profilesDir, entry.name, PROFILE_FILE))) {
|
|
39
|
+
names.add(entry.name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (entry.isFile() && entry.name.endsWith('.yaml')) {
|
|
43
|
+
const bare = entry.name.replace(/\.yaml$/, '');
|
|
44
|
+
await migrateLegacyProfile(cwd, bare);
|
|
45
|
+
names.add(bare);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
21
48
|
}
|
|
22
49
|
catch {
|
|
23
50
|
// No profiles directory yet
|
|
24
51
|
}
|
|
25
|
-
const meta = await loadMetaConfig(cwd);
|
|
26
52
|
return {
|
|
27
|
-
profiles:
|
|
28
|
-
activeProfile: meta.active_profile || null,
|
|
53
|
+
profiles: Array.from(names).sort(),
|
|
29
54
|
};
|
|
30
55
|
},
|
|
31
56
|
async get(options) {
|
|
32
57
|
const cwd = options.cwd ?? process.cwd();
|
|
33
|
-
|
|
58
|
+
await migrateLegacyProfile(cwd, options.name);
|
|
59
|
+
const filePath = profileFile(cwd, options.name);
|
|
34
60
|
let source;
|
|
35
61
|
try {
|
|
36
|
-
source = await readFile(
|
|
62
|
+
source = await readFile(filePath, 'utf8');
|
|
37
63
|
}
|
|
38
64
|
catch {
|
|
39
|
-
throw new ProfileNotFoundError(`Profile "${options.name}" not found at ${
|
|
65
|
+
throw new ProfileNotFoundError(`Profile "${options.name}" not found at ${filePath}`);
|
|
40
66
|
}
|
|
41
67
|
return parseProfile(source, options.name);
|
|
42
68
|
},
|
|
43
69
|
async create(options) {
|
|
44
70
|
const cwd = options.cwd ?? process.cwd();
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
if (await pathExists(
|
|
71
|
+
const folder = profileDir(cwd, options.name);
|
|
72
|
+
const filePath = profileFile(cwd, options.name);
|
|
73
|
+
if ((await pathExists(filePath)) ||
|
|
74
|
+
(await pathExists(legacyProfileFile(cwd, options.name)))) {
|
|
48
75
|
throw new ProfileError(`Profile "${options.name}" already exists.`);
|
|
49
76
|
}
|
|
50
77
|
const scaffold = {
|
|
51
78
|
name: options.name,
|
|
52
79
|
description: options.description ?? '',
|
|
53
|
-
skills: {
|
|
54
|
-
example: {
|
|
55
|
-
description: 'Example skill',
|
|
56
|
-
prompt: 'Describe what this skill does...',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
80
|
mcps: {},
|
|
60
|
-
memory: {
|
|
61
|
-
paths: ['./memory'],
|
|
62
|
-
},
|
|
63
81
|
};
|
|
64
|
-
await mkdir(
|
|
65
|
-
await writeFile(
|
|
66
|
-
return { profilePath };
|
|
82
|
+
await mkdir(folder, { recursive: true });
|
|
83
|
+
await writeFile(filePath, YAML.stringify(scaffold), 'utf8');
|
|
84
|
+
return { profilePath: filePath };
|
|
67
85
|
},
|
|
68
86
|
async update(options) {
|
|
69
87
|
const cwd = options.cwd ?? process.cwd();
|
|
70
|
-
|
|
71
|
-
|
|
88
|
+
await migrateLegacyProfile(cwd, options.name);
|
|
89
|
+
const filePath = profileFile(cwd, options.name);
|
|
90
|
+
if (!(await pathExists(filePath))) {
|
|
72
91
|
throw new ProfileNotFoundError(`Profile "${options.name}" not found.`);
|
|
73
92
|
}
|
|
74
93
|
const normalized = normalizeProfileConfig(options.config, options.name);
|
|
75
94
|
const data = {
|
|
76
95
|
name: normalized.name,
|
|
77
96
|
...(normalized.description ? { description: normalized.description } : {}),
|
|
78
|
-
skills: normalized.skills,
|
|
79
97
|
mcps: normalized.mcps,
|
|
80
|
-
memory: normalized.memory,
|
|
81
98
|
};
|
|
82
|
-
await writeFile(
|
|
99
|
+
await writeFile(filePath, YAML.stringify(data), 'utf8');
|
|
83
100
|
},
|
|
84
101
|
async delete(options) {
|
|
85
102
|
const cwd = options.cwd ?? process.cwd();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const meta = await loadMetaConfig(cwd);
|
|
91
|
-
if (meta.active_profile === options.name) {
|
|
92
|
-
throw new ProfileError('Cannot delete the active profile.');
|
|
93
|
-
}
|
|
94
|
-
await unlink(profilePath);
|
|
95
|
-
},
|
|
96
|
-
async use(options) {
|
|
97
|
-
const cwd = options.cwd ?? process.cwd();
|
|
98
|
-
// Validate profile exists
|
|
99
|
-
const profilePath = path.join(cwd, PROFILES_DIR, `${options.name}.yaml`);
|
|
100
|
-
if (!(await pathExists(profilePath))) {
|
|
103
|
+
await migrateLegacyProfile(cwd, options.name);
|
|
104
|
+
const folder = profileDir(cwd, options.name);
|
|
105
|
+
const filePath = profileFile(cwd, options.name);
|
|
106
|
+
if (!(await pathExists(filePath))) {
|
|
101
107
|
throw new ProfileNotFoundError(`Profile "${options.name}" not found.`);
|
|
102
108
|
}
|
|
103
|
-
|
|
104
|
-
const previousProfile = meta.active_profile || null;
|
|
105
|
-
meta.active_profile = options.name;
|
|
106
|
-
const metaPath = path.join(cwd, META_CONFIG);
|
|
107
|
-
await mkdir(path.dirname(metaPath), { recursive: true });
|
|
108
|
-
await writeFile(metaPath, YAML.stringify(meta), 'utf8');
|
|
109
|
-
return { previousProfile };
|
|
110
|
-
},
|
|
111
|
-
async getMetaConfig(options = {}) {
|
|
112
|
-
const cwd = options.cwd ?? process.cwd();
|
|
113
|
-
return loadMetaConfig(cwd);
|
|
109
|
+
await rm(folder, { recursive: true, force: true });
|
|
114
110
|
},
|
|
115
111
|
};
|
|
116
112
|
}
|
|
117
|
-
async function loadMetaConfig(cwd) {
|
|
118
|
-
const metaPath = path.join(cwd, META_CONFIG);
|
|
119
|
-
try {
|
|
120
|
-
const source = await readFile(metaPath, 'utf8');
|
|
121
|
-
const parsed = YAML.parse(source) ?? {};
|
|
122
|
-
return {
|
|
123
|
-
active_profile: typeof parsed.active_profile === 'string' ? parsed.active_profile : '',
|
|
124
|
-
agents: Array.isArray(parsed.agents) ? parsed.agents : ['claude', 'codex'],
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
return { active_profile: '', agents: ['claude', 'codex', 'gemini'] };
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
113
|
export function parseProfile(source, name) {
|
|
132
114
|
let parsed;
|
|
133
115
|
try {
|
|
@@ -146,38 +128,11 @@ export function normalizeProfileConfig(value, name) {
|
|
|
146
128
|
throw new ProfileError(`Profile "${name}" has invalid structure.`);
|
|
147
129
|
}
|
|
148
130
|
const data = value;
|
|
149
|
-
const skills = {};
|
|
150
|
-
if (data.skills && typeof data.skills === 'object' && !Array.isArray(data.skills)) {
|
|
151
|
-
for (const [key, value] of Object.entries(data.skills)) {
|
|
152
|
-
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
153
|
-
const s = value;
|
|
154
|
-
if (typeof s.prompt === 'string') {
|
|
155
|
-
skills[key] = {
|
|
156
|
-
prompt: s.prompt,
|
|
157
|
-
description: typeof s.description === 'string' ? s.description : undefined,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
131
|
const mcps = normalizeMcps(data.mcps, name);
|
|
164
|
-
const memoryPaths = [];
|
|
165
|
-
if (data.memory && typeof data.memory === 'object' && !Array.isArray(data.memory)) {
|
|
166
|
-
const mem = data.memory;
|
|
167
|
-
if (Array.isArray(mem.paths)) {
|
|
168
|
-
for (const p of mem.paths) {
|
|
169
|
-
if (typeof p === 'string') {
|
|
170
|
-
memoryPaths.push(p);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
132
|
return {
|
|
176
133
|
name: typeof data.name === 'string' ? data.name : name,
|
|
177
134
|
description: typeof data.description === 'string' ? data.description : undefined,
|
|
178
|
-
skills,
|
|
179
135
|
mcps,
|
|
180
|
-
memory: { paths: memoryPaths },
|
|
181
136
|
};
|
|
182
137
|
}
|
|
183
138
|
function normalizeMcps(value, profileName) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AgentName } from '../../types.js';
|
|
2
|
+
export interface ProfileSnapshotService {
|
|
3
|
+
execute(options: {
|
|
4
|
+
cwd: string;
|
|
5
|
+
agent: AgentName;
|
|
6
|
+
profileName: string;
|
|
7
|
+
}): Promise<{
|
|
8
|
+
profilePath: string;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export declare function createProfileSnapshotService(): ProfileSnapshotService;
|
|
12
|
+
export declare function defaultBackupProfileName(agent: AgentName): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import YAML from 'yaml';
|
|
4
|
+
import { profileDir } from './profile-service.js';
|
|
5
|
+
import { createPortableProfilePackService } from './portable-profile-pack-service.js';
|
|
6
|
+
export function createProfileSnapshotService() {
|
|
7
|
+
const packService = createPortableProfilePackService();
|
|
8
|
+
return {
|
|
9
|
+
async execute(options) {
|
|
10
|
+
const outputPath = profileDir(options.cwd, options.profileName);
|
|
11
|
+
const result = await packService.execute({
|
|
12
|
+
cwd: options.cwd,
|
|
13
|
+
source: { source: 'agent', agent: options.agent, cwd: options.cwd },
|
|
14
|
+
outputPath,
|
|
15
|
+
format: 'folder',
|
|
16
|
+
credentialsMode: 'keep',
|
|
17
|
+
});
|
|
18
|
+
await renameInsideProfile(outputPath, options.profileName);
|
|
19
|
+
return { profilePath: result.archivePath };
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function renameInsideProfile(profilePath, name) {
|
|
24
|
+
const profileFile = path.join(profilePath, 'profile.yaml');
|
|
25
|
+
const manifestFile = path.join(profilePath, 'manifest.yaml');
|
|
26
|
+
for (const file of [profileFile, manifestFile]) {
|
|
27
|
+
try {
|
|
28
|
+
const source = await readFile(file, 'utf8');
|
|
29
|
+
const parsed = YAML.parse(source);
|
|
30
|
+
if (file === profileFile)
|
|
31
|
+
parsed.name = name;
|
|
32
|
+
else
|
|
33
|
+
parsed.profileName = name;
|
|
34
|
+
await writeFile(file, YAML.stringify(parsed), 'utf8');
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// best-effort
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function defaultBackupProfileName(agent) {
|
|
42
|
+
const ts = new Date()
|
|
43
|
+
.toISOString()
|
|
44
|
+
.replace(/[-:T]/g, '')
|
|
45
|
+
.replace(/\..+$/, '');
|
|
46
|
+
return `backup-${agent}-${ts}`;
|
|
47
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,55 +1,6 @@
|
|
|
1
1
|
export type AgentName = 'claude' | 'codex' | 'gemini';
|
|
2
2
|
export type ErrorCategory = 'user' | 'system';
|
|
3
3
|
export type DiagnosticStatus = 'ok' | 'warn' | 'error';
|
|
4
|
-
export interface SkillConfig {
|
|
5
|
-
description?: string;
|
|
6
|
-
prompt: string;
|
|
7
|
-
}
|
|
8
|
-
export interface BrainctlConfig {
|
|
9
|
-
configPath: string;
|
|
10
|
-
rootDir: string;
|
|
11
|
-
memory: {
|
|
12
|
-
paths: string[];
|
|
13
|
-
};
|
|
14
|
-
skills: Record<string, SkillConfig>;
|
|
15
|
-
mcps: Record<string, unknown>;
|
|
16
|
-
}
|
|
17
|
-
export interface MemoryLoadResult {
|
|
18
|
-
content: string;
|
|
19
|
-
files: string[];
|
|
20
|
-
count: number;
|
|
21
|
-
entries: Array<{
|
|
22
|
-
path: string;
|
|
23
|
-
content: string;
|
|
24
|
-
}>;
|
|
25
|
-
}
|
|
26
|
-
export interface RunRequest {
|
|
27
|
-
cwd?: string;
|
|
28
|
-
skill: string;
|
|
29
|
-
inputFile: string;
|
|
30
|
-
primaryAgent: AgentName;
|
|
31
|
-
fallbackAgent?: AgentName;
|
|
32
|
-
}
|
|
33
|
-
export interface ExecutionStep {
|
|
34
|
-
skill: string;
|
|
35
|
-
inputFile: string;
|
|
36
|
-
primaryAgent: AgentName;
|
|
37
|
-
fallbackAgent?: AgentName;
|
|
38
|
-
usePreviousOutput?: boolean;
|
|
39
|
-
}
|
|
40
|
-
export interface ExecutionStepResult {
|
|
41
|
-
stepIndex: number;
|
|
42
|
-
requestedAgent: AgentName;
|
|
43
|
-
agent: AgentName;
|
|
44
|
-
fallbackUsed: boolean;
|
|
45
|
-
exitCode: number;
|
|
46
|
-
output: string;
|
|
47
|
-
}
|
|
48
|
-
export interface ExecutionTrace {
|
|
49
|
-
steps: ExecutionStepResult[];
|
|
50
|
-
finalOutput: string;
|
|
51
|
-
finalExitCode: number;
|
|
52
|
-
}
|
|
53
4
|
export interface DiagnosticCheck {
|
|
54
5
|
label: string;
|
|
55
6
|
status: DiagnosticStatus;
|
|
@@ -129,20 +80,14 @@ export type McpServerConfig = LocalMcpServerConfig | RemoteMcpServerConfig;
|
|
|
129
80
|
export interface ProfileConfig {
|
|
130
81
|
name: string;
|
|
131
82
|
description?: string;
|
|
132
|
-
skills: Record<string, SkillConfig>;
|
|
133
83
|
mcps: Record<string, McpServerConfig>;
|
|
134
|
-
memory: {
|
|
135
|
-
paths: string[];
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
export interface BrainctlMetaConfig {
|
|
139
|
-
active_profile: string;
|
|
140
|
-
agents: AgentName[];
|
|
141
84
|
}
|
|
142
85
|
export interface SyncAgentResult {
|
|
143
86
|
agent: AgentName;
|
|
144
87
|
configPath: string;
|
|
145
88
|
backedUpTo: string | null;
|
|
146
89
|
mcpCount: number;
|
|
90
|
+
pluginsInstalled?: string[];
|
|
91
|
+
userSkillsInstalled?: string[];
|
|
147
92
|
}
|
|
148
93
|
export type SyncResult = SyncAgentResult[];
|
package/dist/ui/routes.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
-
import type {
|
|
3
|
-
import type { StatusService } from '../services/status-service.js';
|
|
2
|
+
import type { StatusService } from '../services/platform/status-service.js';
|
|
4
3
|
export interface UiRouteDependencies {
|
|
5
4
|
cwd: string;
|
|
6
5
|
statusService?: StatusService;
|
|
7
|
-
runService?: RunService;
|
|
8
6
|
}
|
|
9
7
|
export type UiRouteHandler = (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
10
8
|
export declare function createUiRouteHandler(dependencies: UiRouteDependencies): UiRouteHandler;
|
package/dist/ui/routes.js
CHANGED
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { loadConfig } from '../config.js';
|
|
4
|
-
import { parseConfigPayload } from '../config.js';
|
|
5
3
|
import { BrainctlError, ProfileError, ProfileNotFoundError, ValidationError } from '../errors.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { createStatusService } from '../services/status-service.js';
|
|
17
|
-
import { createSyncService } from '../services/sync-service.js';
|
|
18
|
-
import { startSseStream, writeSseEvent } from './streaming.js';
|
|
4
|
+
import { createAgentConfigService } from '../services/agent/agent-config-service.js';
|
|
5
|
+
import { createMcpPreflightService } from '../services/platform/mcp-preflight-service.js';
|
|
6
|
+
import { createPluginInstallService } from '../services/plugin/plugin-install-service.js';
|
|
7
|
+
import { createProfileExportService } from '../services/profile/profile-export-service.js';
|
|
8
|
+
import { createProfileImportService } from '../services/profile/profile-import-service.js';
|
|
9
|
+
import { createProfileService } from '../services/profile/profile-service.js';
|
|
10
|
+
import { createSkillPreflightService } from '../services/plugin/skill-preflight-service.js';
|
|
11
|
+
import { createStatusService } from '../services/platform/status-service.js';
|
|
12
|
+
import { createProfileApplyService, } from '../services/profile/profile-apply-service.js';
|
|
13
|
+
import { createProfileSnapshotService, defaultBackupProfileName, } from '../services/profile/profile-snapshot-service.js';
|
|
19
14
|
import path from 'node:path';
|
|
20
15
|
import { fileURLToPath } from 'node:url';
|
|
21
16
|
const uiAssetRoot = resolveUiAssetRoot();
|
|
22
17
|
export function createUiRouteHandler(dependencies) {
|
|
23
18
|
const statusService = dependencies.statusService ?? createStatusService();
|
|
24
|
-
const runService = dependencies.runService ?? createRunService();
|
|
25
|
-
const configWriteService = createConfigWriteService();
|
|
26
19
|
const profileService = createProfileService();
|
|
27
20
|
const profileExportService = createProfileExportService({ profileService });
|
|
28
21
|
const profileImportService = createProfileImportService();
|
|
29
|
-
const
|
|
22
|
+
const profileApplyService = createProfileApplyService({ profileService });
|
|
23
|
+
const profileSnapshotService = createProfileSnapshotService();
|
|
30
24
|
const agentConfigService = createAgentConfigService();
|
|
31
25
|
const mcpPreflightService = createMcpPreflightService();
|
|
32
26
|
const pluginInstallService = createPluginInstallService();
|
|
@@ -41,71 +35,6 @@ export function createUiRouteHandler(dependencies) {
|
|
|
41
35
|
const overview = await statusService.execute({ cwd: dependencies.cwd });
|
|
42
36
|
return sendJson(response, 200, overview);
|
|
43
37
|
}
|
|
44
|
-
case '/api/run/stream': {
|
|
45
|
-
if (request.method !== 'GET') {
|
|
46
|
-
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
47
|
-
}
|
|
48
|
-
const runRequest = parseRunRequest(url);
|
|
49
|
-
if (runRequest === null) {
|
|
50
|
-
return sendJson(response, 400, {
|
|
51
|
-
error: 'Missing skill, inputFile, or primaryAgent'
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if ('error' in runRequest) {
|
|
55
|
-
return sendJson(response, 400, {
|
|
56
|
-
error: runRequest.error
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
startSseStream(response);
|
|
60
|
-
try {
|
|
61
|
-
const trace = await runService.execute({
|
|
62
|
-
...runRequest.request,
|
|
63
|
-
cwd: dependencies.cwd
|
|
64
|
-
}, {
|
|
65
|
-
onOutputChunk: (chunk) => {
|
|
66
|
-
writeSseEvent(response, 'output', chunk);
|
|
67
|
-
},
|
|
68
|
-
streamOutput: false
|
|
69
|
-
});
|
|
70
|
-
writeSseEvent(response, 'result', trace);
|
|
71
|
-
response.end();
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
writeSseEvent(response, 'run-error', {
|
|
75
|
-
error: error instanceof Error ? error.message : 'Unexpected server error'
|
|
76
|
-
});
|
|
77
|
-
response.end();
|
|
78
|
-
}
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
case '/api/config': {
|
|
82
|
-
if (request.method === 'PUT') {
|
|
83
|
-
const body = await readJsonBody(request);
|
|
84
|
-
if (!body.ok) {
|
|
85
|
-
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
86
|
-
}
|
|
87
|
-
const config = parseConfigPayload(body.value);
|
|
88
|
-
await configWriteService.execute({
|
|
89
|
-
cwd: dependencies.cwd,
|
|
90
|
-
config
|
|
91
|
-
});
|
|
92
|
-
const savedConfig = await loadConfig({ cwd: dependencies.cwd });
|
|
93
|
-
return sendJson(response, 200, savedConfig);
|
|
94
|
-
}
|
|
95
|
-
if (request.method !== 'GET') {
|
|
96
|
-
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
97
|
-
}
|
|
98
|
-
const config = await loadConfig({ cwd: dependencies.cwd });
|
|
99
|
-
return sendJson(response, 200, config);
|
|
100
|
-
}
|
|
101
|
-
case '/api/memory': {
|
|
102
|
-
if (request.method !== 'GET') {
|
|
103
|
-
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
104
|
-
}
|
|
105
|
-
const config = await loadConfig({ cwd: dependencies.cwd });
|
|
106
|
-
const memory = await loadMemory({ paths: config.memory.paths });
|
|
107
|
-
return sendJson(response, 200, memory);
|
|
108
|
-
}
|
|
109
38
|
case '/api/agents': {
|
|
110
39
|
if (request.method !== 'GET') {
|
|
111
40
|
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
@@ -207,21 +136,58 @@ export function createUiRouteHandler(dependencies) {
|
|
|
207
136
|
return sendProfileError(response, error);
|
|
208
137
|
}
|
|
209
138
|
}
|
|
210
|
-
case '/api/
|
|
139
|
+
case '/api/profiles/snapshot': {
|
|
211
140
|
if (request.method !== 'POST') {
|
|
212
141
|
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
213
142
|
}
|
|
143
|
+
const body = await readJsonBody(request);
|
|
144
|
+
if (!body.ok) {
|
|
145
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
146
|
+
}
|
|
147
|
+
const data = (body.value ?? {});
|
|
148
|
+
if (data.agent !== 'claude' && data.agent !== 'codex' && data.agent !== 'gemini') {
|
|
149
|
+
return sendJson(response, 400, { error: 'Invalid agent' });
|
|
150
|
+
}
|
|
151
|
+
const profileName = data.as ?? defaultBackupProfileName(data.agent);
|
|
214
152
|
try {
|
|
215
|
-
const result = await
|
|
216
|
-
|
|
153
|
+
const result = await profileSnapshotService.execute({
|
|
154
|
+
cwd: dependencies.cwd,
|
|
155
|
+
agent: data.agent,
|
|
156
|
+
profileName,
|
|
157
|
+
});
|
|
158
|
+
return sendJson(response, 200, { profileName, ...result });
|
|
217
159
|
}
|
|
218
160
|
catch (error) {
|
|
219
|
-
return
|
|
220
|
-
error: error instanceof Error ? error.message : 'Sync failed',
|
|
221
|
-
});
|
|
161
|
+
return sendProfileError(response, error);
|
|
222
162
|
}
|
|
223
163
|
}
|
|
224
164
|
default: {
|
|
165
|
+
// Profile apply: POST /api/profiles/:name/apply
|
|
166
|
+
const applyMatch = url.pathname.match(/^\/api\/profiles\/([^/]+)\/apply$/);
|
|
167
|
+
if (applyMatch) {
|
|
168
|
+
if (request.method !== 'POST') {
|
|
169
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
170
|
+
}
|
|
171
|
+
const profileName = decodeURIComponent(applyMatch[1]);
|
|
172
|
+
const body = await readJsonBody(request);
|
|
173
|
+
if (!body.ok) {
|
|
174
|
+
return sendJson(response, 400, { error: 'Invalid JSON body' });
|
|
175
|
+
}
|
|
176
|
+
const data = (body.value ?? {});
|
|
177
|
+
try {
|
|
178
|
+
const result = await profileApplyService.execute({
|
|
179
|
+
cwd: dependencies.cwd,
|
|
180
|
+
profileName,
|
|
181
|
+
agents: data.agents ?? ['claude', 'codex', 'gemini'],
|
|
182
|
+
items: data.items,
|
|
183
|
+
backup: data.backup,
|
|
184
|
+
});
|
|
185
|
+
return sendJson(response, 200, result);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return sendProfileError(response, error);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
225
191
|
// Agent MCP routes: /api/agents/:name/mcps(/:key)
|
|
226
192
|
const agentMcpCheckMatch = url.pathname.match(/^\/api\/agents\/(claude|codex|gemini)\/mcps\/check$/);
|
|
227
193
|
if (agentMcpCheckMatch) {
|
|
@@ -450,22 +416,34 @@ export function createUiRouteHandler(dependencies) {
|
|
|
450
416
|
}
|
|
451
417
|
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
452
418
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
419
|
+
// Profile contents: GET /api/profiles/:name/contents
|
|
420
|
+
const contentsMatch = url.pathname.match(/^\/api\/profiles\/([^/]+)\/contents$/);
|
|
421
|
+
if (contentsMatch) {
|
|
422
|
+
if (request.method !== 'GET') {
|
|
423
|
+
return sendJson(response, 405, { error: 'Method not allowed' });
|
|
424
|
+
}
|
|
425
|
+
const profileName = decodeURIComponent(contentsMatch[1]);
|
|
426
|
+
try {
|
|
427
|
+
const profile = await profileService.get({ cwd: dependencies.cwd, name: profileName });
|
|
428
|
+
const manifestPath = path.join(dependencies.cwd, '.brainctl', 'profiles', profileName, 'manifest.yaml');
|
|
429
|
+
let manifest = null;
|
|
461
430
|
try {
|
|
462
|
-
const
|
|
463
|
-
|
|
431
|
+
const { readFile: readManifest } = await import('node:fs/promises');
|
|
432
|
+
const yamlMod = await import('yaml');
|
|
433
|
+
manifest = yamlMod.default.parse(await readManifest(manifestPath, 'utf8'));
|
|
464
434
|
}
|
|
465
|
-
catch
|
|
466
|
-
|
|
435
|
+
catch {
|
|
436
|
+
manifest = null;
|
|
467
437
|
}
|
|
438
|
+
return sendJson(response, 200, { profile, manifest });
|
|
468
439
|
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
return sendProfileError(response, error);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
const profileMatch = url.pathname.match(/^\/api\/profiles\/([^/]+)$/);
|
|
445
|
+
if (profileMatch) {
|
|
446
|
+
const name = decodeURIComponent(profileMatch[1]);
|
|
469
447
|
if (request.method === 'GET') {
|
|
470
448
|
try {
|
|
471
449
|
const profile = await profileService.get({ cwd: dependencies.cwd, name });
|
|
@@ -548,33 +526,6 @@ async function readJsonBody(request) {
|
|
|
548
526
|
return { ok: false };
|
|
549
527
|
}
|
|
550
528
|
}
|
|
551
|
-
function parseRunRequest(url) {
|
|
552
|
-
const skill = url.searchParams.get('skill');
|
|
553
|
-
const inputFile = url.searchParams.get('inputFile');
|
|
554
|
-
const primaryAgent = parseAgentName(url.searchParams.get('primaryAgent'));
|
|
555
|
-
const fallbackAgentParam = url.searchParams.get('fallbackAgent');
|
|
556
|
-
const fallbackAgent = fallbackAgentParam === null ? null : parseAgentName(fallbackAgentParam);
|
|
557
|
-
if (!skill || !inputFile || !primaryAgent || fallbackAgentParam !== null && !fallbackAgent) {
|
|
558
|
-
return null;
|
|
559
|
-
}
|
|
560
|
-
if (fallbackAgent !== null && fallbackAgent === primaryAgent) {
|
|
561
|
-
return { error: 'fallbackAgent must differ from primaryAgent' };
|
|
562
|
-
}
|
|
563
|
-
return {
|
|
564
|
-
request: {
|
|
565
|
-
skill,
|
|
566
|
-
inputFile,
|
|
567
|
-
primaryAgent,
|
|
568
|
-
fallbackAgent: fallbackAgent ?? undefined
|
|
569
|
-
}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
function parseAgentName(value) {
|
|
573
|
-
if (value === 'claude' || value === 'codex') {
|
|
574
|
-
return value;
|
|
575
|
-
}
|
|
576
|
-
return null;
|
|
577
|
-
}
|
|
578
529
|
function sendProfileError(response, error) {
|
|
579
530
|
if (error instanceof ProfileNotFoundError) {
|
|
580
531
|
return sendJson(response, 404, { error: error.message });
|
package/dist/ui/server.d.ts
CHANGED