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/cli.d.ts CHANGED
@@ -1,22 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import { type DoctorService } from './services/doctor-service.js';
4
- import { type InitService } from './services/init-service.js';
4
+ import { type ProfileApplyService } from './services/profile-apply-service.js';
5
5
  import { type ProfileExportService } from './services/profile-export-service.js';
6
6
  import { type ProfileImportService } from './services/profile-import-service.js';
7
7
  import { type ProfileService } from './services/profile-service.js';
8
- import { type RunService } from './services/run-service.js';
8
+ import { type ProfileSnapshotService } from './services/profile-snapshot-service.js';
9
9
  import { type StatusService } from './services/status-service.js';
10
- import { type SyncService } from './services/sync-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
- syncService: SyncService;
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
13
  import { createUpdateCheckService } from './services/update-check-service.js';
17
14
  import { createDoctorService } from './services/doctor-service.js';
18
- import { createInitService } from './services/init-service.js';
15
+ import { createProfileApplyService } from './services/profile-apply-service.js';
19
16
  import { createProfileExportService } from './services/profile-export-service.js';
20
17
  import { createProfileImportService } from './services/profile-import-service.js';
21
18
  import { createProfileService } from './services/profile-service.js';
22
- import { createRunService } from './services/run-service.js';
19
+ import { createProfileSnapshotService } from './services/profile-snapshot-service.js';
23
20
  import { createStatusService } from './services/status-service.js';
24
- import { createSyncService } from './services/sync-service.js';
25
- import { createExecutorResolver } from './executor/resolver.js';
21
+ import { createAgentAvailabilityService } from './services/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 resolver = createExecutorResolver();
98
+ const availabilityService = createAgentAvailabilityService();
104
99
  const profileService = createProfileService();
100
+ const profileSnapshotService = createProfileSnapshotService();
105
101
  return {
106
- initService: createInitService(),
107
- runService: createRunService({ resolver }),
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
- syncService: createSyncService({ profileService }),
107
+ profileApplyService: createProfileApplyService({ profileService, snapshotService: profileSnapshotService }),
108
+ profileSnapshotService,
114
109
  ...overrides
115
110
  };
116
111
  }
@@ -1,10 +1,14 @@
1
1
  import type { Command } from 'commander';
2
+ import type { ProfileApplyService } from '../services/profile-apply-service.js';
2
3
  import type { ProfileExportService } from '../services/profile-export-service.js';
3
4
  import type { ProfileImportService } from '../services/profile-import-service.js';
4
5
  import type { ProfileService } from '../services/profile-service.js';
6
+ import type { ProfileSnapshotService } from '../services/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;
@@ -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, activeProfile } = await profileService.list({ cwd: process.cwd() });
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
- const marker = name === activeProfile ? pc.green(' (active)') : '';
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
- .description('Export a profile as a portable tarball')
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
- console.log(`Exported profile to ${result.archivePath}`);
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-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];
@@ -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 current brainctl configuration status')
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(`Config: ${status.configPath}`);
10
- console.log(`Memory files loaded: ${status.memory.count}`);
11
- console.log(`Available skills: ${status.skills.length > 0 ? status.skills.join(', ') : 'none'}`);
12
- console.log(`MCP count: ${status.mcpCount}`);
13
- console.log('Available agents:');
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(`- ${agent.agent}: ${agent.available ? pc.green('available') : pc.yellow('missing')}`);
15
+ console.log(` ${agent.agent}: ${agent.available ? pc.green('available') : pc.yellow('missing')}`);
16
16
  }
17
17
  });
18
18
  }
@@ -1,6 +1,11 @@
1
1
  import { FastMCP } from 'fastmcp';
2
+ import { type UiServer } from '../ui/server.js';
3
+ export interface UiServerState {
4
+ current: UiServer | null;
5
+ }
2
6
  export declare function createMcpServer(options?: {
3
7
  cwd?: string;
8
+ uiServerState?: UiServerState;
4
9
  }): FastMCP;
5
10
  export declare function startMcpServer(options?: {
6
11
  cwd?: string;