brainctl 0.1.18 → 0.1.20

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 (72) hide show
  1. package/dist/cli.d.ts +7 -7
  2. package/dist/cli.js +9 -9
  3. package/dist/commands/doctor.d.ts +1 -1
  4. package/dist/commands/mcp.js +40 -27
  5. package/dist/commands/profile.d.ts +5 -5
  6. package/dist/commands/profile.js +1 -1
  7. package/dist/commands/status.d.ts +1 -1
  8. package/dist/{mcp/server.d.ts → mcp-server.d.ts} +1 -1
  9. package/dist/{mcp/server.js → mcp-server.js} +10 -10
  10. package/dist/services/agent/agent-asset-installer.d.ts +3 -0
  11. package/dist/services/{agent-asset-installer.js → agent/agent-asset-installer.js} +51 -5
  12. package/dist/services/{agent-availability-service.d.ts → agent/agent-availability-service.d.ts} +1 -1
  13. package/dist/services/{agent-availability-service.js → agent/agent-availability-service.js} +1 -1
  14. package/dist/services/{agent-config-service.d.ts → agent/agent-config-service.d.ts} +6 -6
  15. package/dist/services/{agent-config-service.js → agent/agent-config-service.js} +6 -6
  16. package/dist/services/{credential-redaction-service.d.ts → credential/credential-redaction-service.d.ts} +1 -1
  17. package/dist/services/{credential-resolution-service.d.ts → credential/credential-resolution-service.d.ts} +1 -1
  18. package/dist/services/{doctor-service.d.ts → platform/doctor-service.d.ts} +2 -2
  19. package/dist/services/{doctor-service.js → platform/doctor-service.js} +1 -1
  20. package/dist/services/{mcp-preflight-service.d.ts → platform/mcp-preflight-service.d.ts} +2 -2
  21. package/dist/services/{mcp-preflight-service.js → platform/mcp-preflight-service.js} +1 -1
  22. package/dist/services/{runtime-detector.d.ts → platform/runtime-detector.d.ts} +1 -1
  23. package/dist/services/{status-service.d.ts → platform/status-service.d.ts} +3 -3
  24. package/dist/services/{status-service.js → platform/status-service.js} +2 -2
  25. package/dist/services/{update-check-service.js → platform/update-check-service.js} +1 -1
  26. package/dist/services/plugin/plugin-install-bundle.d.ts +20 -0
  27. package/dist/services/plugin/plugin-install-bundle.js +80 -0
  28. package/dist/services/plugin/plugin-install-compatibility.d.ts +15 -0
  29. package/dist/services/plugin/plugin-install-compatibility.js +91 -0
  30. package/dist/services/plugin/plugin-install-fs.d.ts +27 -0
  31. package/dist/services/plugin/plugin-install-fs.js +65 -0
  32. package/dist/services/{plugin-install-service.d.ts → plugin/plugin-install-service.d.ts} +4 -18
  33. package/dist/services/{plugin-install-service.js → plugin/plugin-install-service.js} +7 -308
  34. package/dist/services/plugin/plugin-install-uninstall.d.ts +12 -0
  35. package/dist/services/plugin/plugin-install-uninstall.js +76 -0
  36. package/dist/services/{skill-paths.d.ts → plugin/skill-paths.d.ts} +1 -1
  37. package/dist/services/{skill-preflight-service.d.ts → plugin/skill-preflight-service.d.ts} +1 -1
  38. package/dist/services/{portable-mcp-classifier.d.ts → profile/portable-mcp-classifier.d.ts} +3 -3
  39. package/dist/services/{portable-mcp-classifier.js → profile/portable-mcp-classifier.js} +2 -2
  40. package/dist/services/{portable-profile-pack-service.d.ts → profile/portable-profile-pack-service.d.ts} +2 -2
  41. package/dist/services/{portable-profile-pack-service.js → profile/portable-profile-pack-service.js} +9 -9
  42. package/dist/services/{profile-apply-service.d.ts → profile/profile-apply-service.d.ts} +2 -2
  43. package/dist/services/{profile-apply-service.js → profile/profile-apply-service.js} +26 -9
  44. package/dist/services/{profile-export-service.d.ts → profile/profile-export-service.d.ts} +2 -2
  45. package/dist/services/{profile-import-service.d.ts → profile/profile-import-service.d.ts} +1 -1
  46. package/dist/services/{profile-import-service.js → profile/profile-import-service.js} +5 -5
  47. package/dist/services/{profile-service.d.ts → profile/profile-service.d.ts} +6 -1
  48. package/dist/services/{profile-service.js → profile/profile-service.js} +49 -6
  49. package/dist/services/{profile-snapshot-service.d.ts → profile/profile-snapshot-service.d.ts} +1 -1
  50. package/dist/types.d.ts +1 -1
  51. package/dist/ui/routes.d.ts +1 -1
  52. package/dist/ui/routes.js +63 -10
  53. package/dist/ui/server.d.ts +1 -1
  54. package/dist/web/assets/index-BSstQoDu.js +63 -0
  55. package/dist/web/assets/index-BdziBx2s.css +2 -0
  56. package/dist/web/index.html +2 -2
  57. package/package.json +3 -1
  58. package/dist/services/agent-asset-installer.d.ts +0 -3
  59. package/dist/web/assets/index-CGmTbSgk.js +0 -63
  60. package/dist/web/assets/index-EIVU5Woh.css +0 -2
  61. /package/dist/{system/executables.d.ts → executables.d.ts} +0 -0
  62. /package/dist/{system/executables.js → executables.js} +0 -0
  63. /package/dist/services/{agent-converter-service.d.ts → agent/agent-converter-service.d.ts} +0 -0
  64. /package/dist/services/{agent-converter-service.js → agent/agent-converter-service.js} +0 -0
  65. /package/dist/services/{credential-redaction-service.js → credential/credential-redaction-service.js} +0 -0
  66. /package/dist/services/{credential-resolution-service.js → credential/credential-resolution-service.js} +0 -0
  67. /package/dist/services/{runtime-detector.js → platform/runtime-detector.js} +0 -0
  68. /package/dist/services/{update-check-service.d.ts → platform/update-check-service.d.ts} +0 -0
  69. /package/dist/services/{skill-paths.js → plugin/skill-paths.js} +0 -0
  70. /package/dist/services/{skill-preflight-service.js → plugin/skill-preflight-service.js} +0 -0
  71. /package/dist/services/{profile-export-service.js → profile/profile-export-service.js} +0 -0
  72. /package/dist/services/{profile-snapshot-service.js → profile/profile-snapshot-service.js} +0 -0
package/dist/cli.d.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { type DoctorService } from './services/doctor-service.js';
4
- import { type ProfileApplyService } from './services/profile-apply-service.js';
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 ProfileSnapshotService } from './services/profile-snapshot-service.js';
9
- import { type StatusService } from './services/status-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';
10
10
  export interface CliServices {
11
11
  statusService: StatusService;
12
12
  doctorService: DoctorService;
package/dist/cli.js CHANGED
@@ -10,15 +10,15 @@ import { registerProfileCommand } from './commands/profile.js';
10
10
  import { registerStatusCommand } from './commands/status.js';
11
11
  import { registerUiCommand } from './commands/ui.js';
12
12
  import { printError } from './output.js';
13
- import { createUpdateCheckService } from './services/update-check-service.js';
14
- import { createDoctorService } from './services/doctor-service.js';
15
- import { createProfileApplyService } from './services/profile-apply-service.js';
16
- import { createProfileExportService } from './services/profile-export-service.js';
17
- import { createProfileImportService } from './services/profile-import-service.js';
18
- import { createProfileService } from './services/profile-service.js';
19
- import { createProfileSnapshotService } from './services/profile-snapshot-service.js';
20
- import { createStatusService } from './services/status-service.js';
21
- import { createAgentAvailabilityService } from './services/agent-availability-service.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';
22
22
  const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
23
23
  export function createProgram(overrides = {}) {
24
24
  const services = createDefaultServices(overrides);
@@ -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;
@@ -1,44 +1,57 @@
1
- import { spawn } from 'node:child_process';
2
- import { startMcpServer } from '../mcp/server.js';
3
- import { createUpdateCheckService } from '../services/update-check-service.js';
1
+ import { spawnSync } from 'node:child_process';
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')
7
7
  .description('Start the brainctl MCP server (stdio transport)')
8
8
  .action(async () => {
9
+ killPriorMcpServers();
10
+ process.stdin.on('end', () => process.exit(0));
11
+ process.stdin.on('close', () => process.exit(0));
12
+ await startMcpServer({ cwd: process.cwd() });
9
13
  if (!process.env.BRAINCTL_NO_UPDATE_CHECK) {
10
- await autoUpdateIfNeeded();
14
+ // Fire-and-forget after the server is up so cold-start isn't blocked
15
+ // on a network round-trip (or `npm install brainctl@latest`).
16
+ void notifyIfOutdated();
11
17
  }
12
- await startMcpServer({ cwd: process.cwd() });
13
18
  });
14
19
  }
15
- async function autoUpdateIfNeeded() {
20
+ function killPriorMcpServers() {
21
+ const self = process.pid;
22
+ const ppid = process.ppid;
16
23
  try {
17
- const service = createUpdateCheckService();
18
- const check = await service.check();
19
- if (!check.isOutdated)
20
- return;
21
- const result = await service.selfUpdate();
22
- if (result.success) {
23
- // Re-exec with the updated binary
24
- const child = spawn(process.execPath, process.argv.slice(1), {
25
- stdio: 'inherit',
26
- });
27
- await new Promise((resolve) => {
28
- child.on('exit', (code) => {
29
- process.exit(code ?? 0);
30
- });
31
- child.on('error', () => {
32
- resolve(); // fall through to current version
33
- });
34
- });
24
+ const result = spawnSync('pgrep', ['-f', 'brainctl/dist/cli\\.js mcp'], {
25
+ encoding: 'utf8',
26
+ });
27
+ if (result.status !== 0 || !result.stdout)
35
28
  return;
29
+ const pids = result.stdout
30
+ .split('\n')
31
+ .map((line) => Number.parseInt(line.trim(), 10))
32
+ .filter((pid) => Number.isFinite(pid) && pid !== self && pid !== ppid);
33
+ for (const pid of pids) {
34
+ try {
35
+ process.kill(pid, 'SIGTERM');
36
+ }
37
+ catch {
38
+ // already gone
39
+ }
36
40
  }
37
- if (result.error) {
38
- process.stderr.write(`brainctl: auto-update failed: ${result.error}\n`);
41
+ }
42
+ catch {
43
+ // pgrep unavailable — skip
44
+ }
45
+ }
46
+ async function notifyIfOutdated() {
47
+ try {
48
+ const service = createUpdateCheckService();
49
+ const check = await service.check();
50
+ if (check.isOutdated) {
51
+ process.stderr.write(`brainctl: a newer version is available (${check.latest}). Run \`npm i -g brainctl@latest\` to update.\n`);
39
52
  }
40
53
  }
41
54
  catch {
42
- // Update check failed entirely continue silently
55
+ // Update check failed — stay silent
43
56
  }
44
57
  }
@@ -1,9 +1,9 @@
1
1
  import type { Command } from 'commander';
2
- import type { ProfileApplyService } from '../services/profile-apply-service.js';
3
- import type { ProfileExportService } from '../services/profile-export-service.js';
4
- import type { ProfileImportService } from '../services/profile-import-service.js';
5
- import type { ProfileService } from '../services/profile-service.js';
6
- import type { ProfileSnapshotService } from '../services/profile-snapshot-service.js';
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';
7
7
  export interface ProfileCommandServices {
8
8
  profileService: ProfileService;
9
9
  profileExportService: ProfileExportService;
@@ -131,7 +131,7 @@ export function registerProfileCommand(program, services) {
131
131
  throw new Error('Provide --agent <claude|codex|gemini>.');
132
132
  }
133
133
  const agent = options.agent;
134
- const { defaultBackupProfileName } = await import('../services/profile-snapshot-service.js');
134
+ const { defaultBackupProfileName } = await import('../services/profile/profile-snapshot-service.js');
135
135
  const profileName = options.as ?? defaultBackupProfileName(agent);
136
136
  const result = await profileSnapshotService.execute({
137
137
  cwd: process.cwd(),
@@ -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;
@@ -1,5 +1,5 @@
1
1
  import { FastMCP } from 'fastmcp';
2
- import { type UiServer } from '../ui/server.js';
2
+ import { type UiServer } from './ui/server.js';
3
3
  export interface UiServerState {
4
4
  current: UiServer | null;
5
5
  }
@@ -2,17 +2,17 @@ import { spawn } from 'node:child_process';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { FastMCP } from 'fastmcp';
4
4
  import { z } from 'zod';
5
- import { createAgentConfigService } from '../services/agent-config-service.js';
6
- import { createDoctorService } from '../services/doctor-service.js';
7
- import { startUiServer } from '../ui/server.js';
8
- import { createProfileExportService } from '../services/profile-export-service.js';
9
- import { createProfileImportService } from '../services/profile-import-service.js';
10
- import { createProfileApplyService } from '../services/profile-apply-service.js';
11
- import { createProfileService } from '../services/profile-service.js';
12
- import { createProfileSnapshotService, defaultBackupProfileName, } from '../services/profile-snapshot-service.js';
13
- import { createStatusService } from '../services/status-service.js';
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
14
  const ALL_AGENTS = ['claude', 'codex', 'gemini'];
15
- const packageVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
15
+ const packageVersion = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
16
16
  export function createMcpServer(options = {}) {
17
17
  const cwd = options.cwd ?? process.cwd();
18
18
  const uiState = options.uiServerState ?? { current: null };
@@ -0,0 +1,3 @@
1
+ import type { AgentName, PortablePluginSnapshot, PortableUserSkillSnapshot } from '../../types.js';
2
+ export declare function installPlugin(sourceDir: string, plugin: PortablePluginSnapshot, targetAgent?: AgentName): Promise<void>;
3
+ export declare function installUserSkill(sourceDir: string, skill: PortableUserSkillSnapshot, targetAgent?: PortableUserSkillSnapshot['agent']): Promise<void>;
@@ -1,15 +1,23 @@
1
1
  import { copyFile, cp, mkdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
3
  import path from 'node:path';
4
- import { ProfileError } from '../errors.js';
5
- import { formatTimestamp } from './sync/agent-writer.js';
6
- export async function installPlugin(sourceDir, plugin) {
4
+ import { ProfileError } from '../../errors.js';
5
+ import { defaultReadInstalledPluginBundle, isAgentInstallableOnTarget, isCommandInstallableOnTarget, } from '../plugin/plugin-install-bundle.js';
6
+ import { defaultCopySkillDirectory, defaultInstallAgent, defaultInstallCommand, } from '../plugin/plugin-install-fs.js';
7
+ import { writeManagedPluginInstall } from '../sync/managed-plugin-registry.js';
8
+ import { formatTimestamp } from '../sync/agent-writer.js';
9
+ export async function installPlugin(sourceDir, plugin, targetAgent) {
7
10
  try {
8
11
  await stat(sourceDir);
9
12
  }
10
13
  catch {
11
14
  throw new ProfileError(`Bundled plugin "${plugin.name}" source missing at ${sourceDir}.`);
12
15
  }
16
+ const target = targetAgent ?? plugin.agent;
17
+ if (target !== plugin.agent) {
18
+ await installPluginCrossAgent(sourceDir, plugin, target);
19
+ return;
20
+ }
13
21
  if (plugin.agent === 'gemini') {
14
22
  return;
15
23
  }
@@ -34,14 +42,52 @@ export async function installPlugin(sourceDir, plugin) {
34
42
  });
35
43
  }
36
44
  }
37
- export async function installUserSkill(sourceDir, skill) {
45
+ async function installPluginCrossAgent(sourceDir, plugin, targetAgent) {
46
+ const bundle = await defaultReadInstalledPluginBundle(sourceDir);
47
+ for (const skillName of bundle.skills) {
48
+ await defaultCopySkillDirectory({
49
+ sourceInstallPath: sourceDir,
50
+ skillName,
51
+ targetAgent,
52
+ });
53
+ }
54
+ const installedAgents = [];
55
+ if (isAgentInstallableOnTarget(targetAgent)) {
56
+ for (const agent of bundle.agents) {
57
+ await defaultInstallAgent({ targetAgent, agent });
58
+ installedAgents.push(agent.name);
59
+ }
60
+ }
61
+ const installedCommands = [];
62
+ if (isCommandInstallableOnTarget(targetAgent)) {
63
+ for (const command of bundle.commands) {
64
+ await defaultInstallCommand({ targetAgent, command });
65
+ installedCommands.push(command.name);
66
+ }
67
+ }
68
+ await writeManagedPluginInstall({
69
+ agent: targetAgent,
70
+ plugin: {
71
+ name: plugin.name,
72
+ kind: 'plugin',
73
+ managed: true,
74
+ source: plugin.source,
75
+ pluginSkills: bundle.skills,
76
+ pluginMcps: Object.keys(bundle.mcps),
77
+ pluginAgents: installedAgents,
78
+ pluginCommands: installedCommands,
79
+ },
80
+ });
81
+ }
82
+ export async function installUserSkill(sourceDir, skill, targetAgent) {
38
83
  try {
39
84
  await stat(sourceDir);
40
85
  }
41
86
  catch {
42
87
  throw new ProfileError(`Bundled user skill "${skill.name}" source missing at ${sourceDir}.`);
43
88
  }
44
- const targetDir = path.join(homedir(), `.${skill.agent}`, 'skills', skill.name);
89
+ const agent = targetAgent ?? skill.agent;
90
+ const targetDir = path.join(homedir(), `.${agent}`, 'skills', skill.name);
45
91
  await rm(targetDir, { recursive: true, force: true });
46
92
  await mkdir(path.dirname(targetDir), { recursive: true });
47
93
  await cp(sourceDir, targetDir, { recursive: true });
@@ -1,4 +1,4 @@
1
- import type { AgentName } from '../types.js';
1
+ import type { AgentName } from '../../types.js';
2
2
  export interface AgentAvailability {
3
3
  agent: AgentName;
4
4
  available: boolean;
@@ -1,4 +1,4 @@
1
- import { findExecutable } from '../system/executables.js';
1
+ import { findExecutable } from '../../executables.js';
2
2
  const SUPPORTED_AGENTS = ['claude', 'codex', 'gemini'];
3
3
  const AGENT_COMMANDS = {
4
4
  claude: 'claude',
@@ -1,9 +1,9 @@
1
- import type { AgentName } from '../types.js';
2
- import { type AgentLiveConfig, type AgentMcpEntry, type PortableRemoteMcpMetadata } from './sync/agent-reader.js';
3
- import { type McpPreflightService } from './mcp-preflight-service.js';
4
- import { type SkillPreflightService } from './skill-preflight-service.js';
5
- export type { AgentLiveConfig, AgentMcpEntry, AgentSkillEntry } from './sync/agent-reader.js';
6
- export type { PortableRemoteMcpMetadata } from './sync/agent-reader.js';
1
+ import type { AgentName } from '../../types.js';
2
+ import { type AgentLiveConfig, type AgentMcpEntry, type PortableRemoteMcpMetadata } from '../sync/agent-reader.js';
3
+ import { type McpPreflightService } from '../platform/mcp-preflight-service.js';
4
+ import { type SkillPreflightService } from '../plugin/skill-preflight-service.js';
5
+ export type { AgentLiveConfig, AgentMcpEntry, AgentSkillEntry } from '../sync/agent-reader.js';
6
+ export type { PortableRemoteMcpMetadata } from '../sync/agent-reader.js';
7
7
  export interface AgentConfigService {
8
8
  readAll(options: {
9
9
  cwd: string;
@@ -1,12 +1,12 @@
1
1
  import { copyFile, cp, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
3
  import path from 'node:path';
4
- import { ValidationError } from '../errors.js';
5
- import { createClaudeReader, createCodexReader, createGeminiReader, } from './sync/agent-reader.js';
6
- import { formatTimestamp } from './sync/agent-writer.js';
7
- import { createMcpPreflightService } from './mcp-preflight-service.js';
8
- import { createSkillPreflightService } from './skill-preflight-service.js';
9
- import { getSkillDir } from './skill-paths.js';
4
+ import { ValidationError } from '../../errors.js';
5
+ import { createClaudeReader, createCodexReader, createGeminiReader, } from '../sync/agent-reader.js';
6
+ import { formatTimestamp } from '../sync/agent-writer.js';
7
+ import { createMcpPreflightService } from '../platform/mcp-preflight-service.js';
8
+ import { createSkillPreflightService } from '../plugin/skill-preflight-service.js';
9
+ import { getSkillDir } from '../plugin/skill-paths.js';
10
10
  const readers = {
11
11
  claude: createClaudeReader(),
12
12
  codex: createCodexReader(),
@@ -1,4 +1,4 @@
1
- import type { McpServerConfig, PortableCredentialSpec } from '../types.js';
1
+ import type { McpServerConfig, PortableCredentialSpec } from '../../types.js';
2
2
  export interface CredentialRedactionResult<T extends McpServerConfig> {
3
3
  redacted: T;
4
4
  credentials: PortableCredentialSpec[];
@@ -1,4 +1,4 @@
1
- import type { McpServerConfig, PortableCredentialPlaceholder, PortableCredentialSpec } from '../types.js';
1
+ import type { McpServerConfig, PortableCredentialPlaceholder, PortableCredentialSpec } from '../../types.js';
2
2
  export interface CredentialResolutionResult<T extends McpServerConfig> {
3
3
  resolved: T;
4
4
  missing: PortableCredentialSpec[];
@@ -1,5 +1,5 @@
1
- import { type AgentAvailabilityService } from './agent-availability-service.js';
2
- import type { DiagnosticCheck } from '../types.js';
1
+ import { type AgentAvailabilityService } from '../agent/agent-availability-service.js';
2
+ import type { DiagnosticCheck } from '../../types.js';
3
3
  export interface DoctorResult {
4
4
  checks: DiagnosticCheck[];
5
5
  hasIssues: boolean;
@@ -1,4 +1,4 @@
1
- import { createAgentAvailabilityService, } from './agent-availability-service.js';
1
+ import { createAgentAvailabilityService, } from '../agent/agent-availability-service.js';
2
2
  export function createDoctorService(dependencies = {}) {
3
3
  const availabilityService = dependencies.availabilityService ?? createAgentAvailabilityService();
4
4
  return {
@@ -1,5 +1,5 @@
1
- import type { AgentName } from '../types.js';
2
- import type { AgentMcpEntry, PortableRemoteMcpMetadata } from './agent-config-service.js';
1
+ import type { AgentName } from '../../types.js';
2
+ import type { AgentMcpEntry, PortableRemoteMcpMetadata } from '../agent/agent-config-service.js';
3
3
  export interface McpPreflightCheck {
4
4
  label: string;
5
5
  status: 'ok' | 'warn' | 'error';
@@ -1,6 +1,6 @@
1
1
  import { stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { findExecutable } from '../system/executables.js';
3
+ import { findExecutable } from '../../executables.js';
4
4
  const ENTRYPOINT_RUNNERS = new Set(['node', 'nodejs', 'python', 'python3', 'bash', 'sh', 'zsh', 'deno', 'bun']);
5
5
  const SUPPORTED_LOCAL_LAUNCHERS = ['npx', 'uvx', 'node', 'python', 'python3', 'java -jar', 'go run', 'cargo run', 'and local binaries/scripts'];
6
6
  export function createMcpPreflightService(dependencies = {}) {
@@ -1,4 +1,4 @@
1
- import type { McpRuntime } from '../types.js';
1
+ import type { McpRuntime } from '../../types.js';
2
2
  export declare function detectMcpRuntime(command: string): McpRuntime | null;
3
3
  export declare function extractEntrypoint(command: string, args: string[]): string | null;
4
4
  export declare function findProjectRoot(entrypointPath: string, runtime: McpRuntime): {
@@ -1,6 +1,6 @@
1
- import { type AgentAvailability, type AgentAvailabilityService } from './agent-availability-service.js';
2
- import { type ProfileService } from './profile-service.js';
3
- import type { AgentName } from '../types.js';
1
+ import { type AgentAvailability, type AgentAvailabilityService } from '../agent/agent-availability-service.js';
2
+ import { type ProfileService } from '../profile/profile-service.js';
3
+ import type { AgentName } from '../../types.js';
4
4
  export interface StatusResult {
5
5
  agents: Record<AgentName, AgentAvailability>;
6
6
  profiles: {
@@ -1,5 +1,5 @@
1
- import { createAgentAvailabilityService, } from './agent-availability-service.js';
2
- import { createProfileService } from './profile-service.js';
1
+ import { createAgentAvailabilityService, } from '../agent/agent-availability-service.js';
2
+ import { createProfileService } from '../profile/profile-service.js';
3
3
  export function createStatusService(dependencies = {}) {
4
4
  const availabilityService = dependencies.availabilityService ?? createAgentAvailabilityService();
5
5
  const profileService = dependencies.profileService ?? createProfileService();
@@ -9,7 +9,7 @@ const execFileAsync = promisify(execFile);
9
9
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
10
10
  const CACHE_DIR = path.join(homedir(), '.brainctl');
11
11
  const CACHE_PATH = path.join(CACHE_DIR, 'update-check.json');
12
- const packageVersion = JSON.parse(readFileSync(new URL('../../package.json', import.meta.url), 'utf8'));
12
+ const packageVersion = JSON.parse(readFileSync(new URL('../../../package.json', import.meta.url), 'utf8'));
13
13
  export function createUpdateCheckService(dependencies = {}) {
14
14
  const currentVersion = dependencies.currentVersion ?? packageVersion.version;
15
15
  const fetchLatestVersion = dependencies.fetchLatestVersion ?? fetchFromRegistry;
@@ -0,0 +1,20 @@
1
+ import type { AgentName } from '../../types.js';
2
+ import type { AgentMcpEntry } from '../agent/agent-config-service.js';
3
+ export interface PluginBundleAgent {
4
+ name: string;
5
+ sourceFormat: 'claude-md' | 'codex-toml';
6
+ content: string;
7
+ }
8
+ export interface PluginBundleCommand {
9
+ name: string;
10
+ content: string;
11
+ }
12
+ export interface PluginBundle {
13
+ skills: string[];
14
+ mcps: Record<string, AgentMcpEntry>;
15
+ agents: PluginBundleAgent[];
16
+ commands: PluginBundleCommand[];
17
+ }
18
+ export declare function isAgentInstallableOnTarget(target: AgentName): boolean;
19
+ export declare function isCommandInstallableOnTarget(target: AgentName): boolean;
20
+ export declare function defaultReadInstalledPluginBundle(installPath: string): Promise<PluginBundle>;
@@ -0,0 +1,80 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export function isAgentInstallableOnTarget(target) {
4
+ return target === 'claude' || target === 'codex';
5
+ }
6
+ export function isCommandInstallableOnTarget(target) {
7
+ return target === 'claude' || target === 'codex';
8
+ }
9
+ export async function defaultReadInstalledPluginBundle(installPath) {
10
+ const skillsDir = path.join(installPath, 'skills');
11
+ let skills = [];
12
+ try {
13
+ const entries = await readdir(skillsDir, { withFileTypes: true });
14
+ skills = entries
15
+ .filter((entry) => !entry.name.startsWith('.') && entry.isDirectory())
16
+ .map((entry) => entry.name)
17
+ .sort((left, right) => left.localeCompare(right));
18
+ }
19
+ catch {
20
+ skills = [];
21
+ }
22
+ let mcps = {};
23
+ try {
24
+ const mcpSource = await readFile(path.join(installPath, '.mcp.json'), 'utf8');
25
+ const parsed = JSON.parse(mcpSource);
26
+ mcps = Object.fromEntries(Object.entries(parsed)
27
+ .filter(([, value]) => typeof value?.command === 'string')
28
+ .map(([key, value]) => [
29
+ key,
30
+ {
31
+ command: String(value.command),
32
+ args: Array.isArray(value.args) ? value.args.map(String) : undefined,
33
+ env: value.env && typeof value.env === 'object' && !Array.isArray(value.env)
34
+ ? Object.fromEntries(Object.entries(value.env).map(([envKey, envValue]) => [
35
+ envKey,
36
+ String(envValue),
37
+ ]))
38
+ : undefined,
39
+ },
40
+ ]));
41
+ }
42
+ catch {
43
+ mcps = {};
44
+ }
45
+ const agents = [];
46
+ try {
47
+ const entries = await readdir(path.join(installPath, 'agents'), { withFileTypes: true });
48
+ for (const entry of entries) {
49
+ if (!entry.isFile())
50
+ continue;
51
+ if (entry.name.endsWith('.md')) {
52
+ const content = await readFile(path.join(installPath, 'agents', entry.name), 'utf8');
53
+ agents.push({ name: entry.name.replace(/\.md$/, ''), sourceFormat: 'claude-md', content });
54
+ }
55
+ else if (entry.name.endsWith('.toml')) {
56
+ const content = await readFile(path.join(installPath, 'agents', entry.name), 'utf8');
57
+ agents.push({ name: entry.name.replace(/\.toml$/, ''), sourceFormat: 'codex-toml', content });
58
+ }
59
+ }
60
+ agents.sort((left, right) => left.name.localeCompare(right.name));
61
+ }
62
+ catch {
63
+ // no agents dir
64
+ }
65
+ const commands = [];
66
+ try {
67
+ const entries = await readdir(path.join(installPath, 'commands'), { withFileTypes: true });
68
+ for (const entry of entries) {
69
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
70
+ continue;
71
+ const content = await readFile(path.join(installPath, 'commands', entry.name), 'utf8');
72
+ commands.push({ name: entry.name.replace(/\.md$/, ''), content });
73
+ }
74
+ commands.sort((left, right) => left.name.localeCompare(right.name));
75
+ }
76
+ catch {
77
+ // no commands dir
78
+ }
79
+ return { skills, mcps, agents, commands };
80
+ }
@@ -0,0 +1,15 @@
1
+ import type { AgentName } from '../../types.js';
2
+ import type { PluginInstallCheck } from './plugin-install-service.js';
3
+ export interface IncompatibleArtifacts {
4
+ hasAppConnector: boolean;
5
+ hasHooks: boolean;
6
+ hasCommands: boolean;
7
+ codexAgentSkills: string[];
8
+ claudeAgents: string[];
9
+ }
10
+ export declare function pathExists(target: string): Promise<boolean>;
11
+ export declare function detectIncompatibleArtifacts(installPath: string): Promise<IncompatibleArtifacts>;
12
+ export declare function formatCompatibilityWarnings(artifacts: IncompatibleArtifacts, context: {
13
+ sourceAgent: AgentName;
14
+ targetAgent: AgentName;
15
+ }): PluginInstallCheck[];