brainctl 0.1.16 → 0.1.18

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 (74) hide show
  1. package/dist/cli.d.ts +4 -6
  2. package/dist/cli.js +11 -16
  3. package/dist/commands/profile.d.ts +4 -0
  4. package/dist/commands/profile.js +106 -16
  5. package/dist/commands/status.js +7 -7
  6. package/dist/mcp/server.d.ts +5 -0
  7. package/dist/mcp/server.js +85 -154
  8. package/dist/services/agent-asset-installer.d.ts +3 -0
  9. package/dist/services/agent-asset-installer.js +109 -0
  10. package/dist/services/agent-availability-service.d.ts +11 -0
  11. package/dist/services/agent-availability-service.js +32 -0
  12. package/dist/services/credential-redaction-service.d.ts +1 -0
  13. package/dist/services/credential-redaction-service.js +9 -3
  14. package/dist/services/doctor-service.d.ts +2 -2
  15. package/dist/services/doctor-service.js +7 -63
  16. package/dist/services/portable-profile-pack-service.d.ts +6 -0
  17. package/dist/services/portable-profile-pack-service.js +78 -4
  18. package/dist/services/profile-apply-service.d.ts +34 -0
  19. package/dist/services/profile-apply-service.js +102 -0
  20. package/dist/services/profile-export-service.d.ts +5 -1
  21. package/dist/services/profile-export-service.js +3 -1
  22. package/dist/services/profile-import-service.js +82 -127
  23. package/dist/services/profile-service.d.ts +3 -11
  24. package/dist/services/profile-service.js +57 -102
  25. package/dist/services/profile-snapshot-service.d.ts +12 -0
  26. package/dist/services/profile-snapshot-service.js +47 -0
  27. package/dist/services/status-service.d.ts +9 -7
  28. package/dist/services/status-service.js +14 -13
  29. package/dist/types.d.ts +2 -57
  30. package/dist/ui/routes.d.ts +0 -2
  31. package/dist/ui/routes.js +71 -120
  32. package/dist/web/assets/index-CGmTbSgk.js +63 -0
  33. package/dist/web/assets/index-EIVU5Woh.css +2 -0
  34. package/dist/web/brainctl-mark.svg +13 -0
  35. package/dist/web/index.html +2 -5
  36. package/package.json +2 -1
  37. package/dist/commands/init.d.ts +0 -3
  38. package/dist/commands/init.js +0 -27
  39. package/dist/commands/run.d.ts +0 -3
  40. package/dist/commands/run.js +0 -25
  41. package/dist/commands/sync.d.ts +0 -3
  42. package/dist/commands/sync.js +0 -31
  43. package/dist/config.d.ts +0 -14
  44. package/dist/config.js +0 -96
  45. package/dist/context/builder.d.ts +0 -6
  46. package/dist/context/builder.js +0 -13
  47. package/dist/context/memory.d.ts +0 -5
  48. package/dist/context/memory.js +0 -43
  49. package/dist/context/skills.d.ts +0 -2
  50. package/dist/context/skills.js +0 -8
  51. package/dist/executor/claude.d.ts +0 -12
  52. package/dist/executor/claude.js +0 -16
  53. package/dist/executor/codex.d.ts +0 -12
  54. package/dist/executor/codex.js +0 -16
  55. package/dist/executor/process.d.ts +0 -11
  56. package/dist/executor/process.js +0 -40
  57. package/dist/executor/resolver.d.ts +0 -13
  58. package/dist/executor/resolver.js +0 -60
  59. package/dist/executor/types.d.ts +0 -14
  60. package/dist/executor/types.js +0 -1
  61. package/dist/services/config-write-service.d.ts +0 -12
  62. package/dist/services/config-write-service.js +0 -70
  63. package/dist/services/init-service.d.ts +0 -14
  64. package/dist/services/init-service.js +0 -88
  65. package/dist/services/memory-write-service.d.ts +0 -12
  66. package/dist/services/memory-write-service.js +0 -56
  67. package/dist/services/run-service.d.ts +0 -15
  68. package/dist/services/run-service.js +0 -94
  69. package/dist/services/sync-service.d.ts +0 -15
  70. package/dist/services/sync-service.js +0 -69
  71. package/dist/ui/streaming.d.ts +0 -3
  72. package/dist/ui/streaming.js +0 -16
  73. package/dist/web/assets/index-CuNIAQ7N.js +0 -65
  74. package/dist/web/assets/index-Ow6x3bQk.css +0 -2
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[];
@@ -1,10 +1,8 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
- import type { RunService } from '../services/run-service.js';
3
2
  import type { StatusService } from '../services/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 { loadMemory } from '../context/memory.js';
7
4
  import { createAgentConfigService } from '../services/agent-config-service.js';
8
- import { createConfigWriteService } from '../services/config-write-service.js';
9
5
  import { createMcpPreflightService } from '../services/mcp-preflight-service.js';
10
6
  import { createPluginInstallService } from '../services/plugin-install-service.js';
11
7
  import { createProfileExportService } from '../services/profile-export-service.js';
12
8
  import { createProfileImportService } from '../services/profile-import-service.js';
13
9
  import { createProfileService } from '../services/profile-service.js';
14
- import { createRunService } from '../services/run-service.js';
15
10
  import { createSkillPreflightService } from '../services/skill-preflight-service.js';
16
11
  import { createStatusService } from '../services/status-service.js';
17
- import { createSyncService } from '../services/sync-service.js';
18
- import { startSseStream, writeSseEvent } from './streaming.js';
12
+ import { createProfileApplyService, } from '../services/profile-apply-service.js';
13
+ import { createProfileSnapshotService, defaultBackupProfileName, } from '../services/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 syncService = createSyncService({ profileService });
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/sync': {
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 syncService.execute({ cwd: dependencies.cwd });
216
- return sendJson(response, 200, result);
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 sendJson(response, 500, {
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
- const profileMatch = url.pathname.match(/^\/api\/profiles\/([^/]+)(\/activate)?$/);
454
- if (profileMatch) {
455
- const name = decodeURIComponent(profileMatch[1]);
456
- const isActivate = profileMatch[2] === '/activate';
457
- if (isActivate) {
458
- if (request.method !== 'POST') {
459
- return sendJson(response, 405, { error: 'Method not allowed' });
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 result = await profileService.use({ cwd: dependencies.cwd, name });
463
- return sendJson(response, 200, result);
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 (error) {
466
- return sendProfileError(response, error);
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 });