brainctl 0.1.0

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 (44) hide show
  1. package/README.md +242 -0
  2. package/dist/cli.d.ts +14 -0
  3. package/dist/cli.js +52 -0
  4. package/dist/commands/doctor.d.ts +3 -0
  5. package/dist/commands/doctor.js +13 -0
  6. package/dist/commands/init.d.ts +3 -0
  7. package/dist/commands/init.js +27 -0
  8. package/dist/commands/run.d.ts +3 -0
  9. package/dist/commands/run.js +25 -0
  10. package/dist/commands/status.d.ts +3 -0
  11. package/dist/commands/status.js +18 -0
  12. package/dist/config.d.ts +6 -0
  13. package/dist/config.js +78 -0
  14. package/dist/context/builder.d.ts +6 -0
  15. package/dist/context/builder.js +13 -0
  16. package/dist/context/memory.d.ts +5 -0
  17. package/dist/context/memory.js +38 -0
  18. package/dist/context/skills.d.ts +2 -0
  19. package/dist/context/skills.js +8 -0
  20. package/dist/errors.d.ts +27 -0
  21. package/dist/errors.js +45 -0
  22. package/dist/executor/claude.d.ts +5 -0
  23. package/dist/executor/claude.js +12 -0
  24. package/dist/executor/codex.d.ts +5 -0
  25. package/dist/executor/codex.js +12 -0
  26. package/dist/executor/process.d.ts +10 -0
  27. package/dist/executor/process.js +38 -0
  28. package/dist/executor/resolver.d.ts +13 -0
  29. package/dist/executor/resolver.js +94 -0
  30. package/dist/executor/types.d.ts +13 -0
  31. package/dist/executor/types.js +1 -0
  32. package/dist/output.d.ts +4 -0
  33. package/dist/output.js +25 -0
  34. package/dist/services/doctor-service.d.ts +14 -0
  35. package/dist/services/doctor-service.js +79 -0
  36. package/dist/services/init-service.d.ts +14 -0
  37. package/dist/services/init-service.js +88 -0
  38. package/dist/services/run-service.d.ts +11 -0
  39. package/dist/services/run-service.js +93 -0
  40. package/dist/services/status-service.d.ts +17 -0
  41. package/dist/services/status-service.js +21 -0
  42. package/dist/types.d.ts +53 -0
  43. package/dist/types.js +1 -0
  44. package/package.json +46 -0
@@ -0,0 +1,5 @@
1
+ import type { Executor, ExecutorRunOptions, ExecutorResult } from './types.js';
2
+ export declare class CodexExecutor implements Executor {
3
+ readonly agent: "codex";
4
+ run(context: string, options?: ExecutorRunOptions): Promise<ExecutorResult>;
5
+ }
@@ -0,0 +1,12 @@
1
+ import { runAgentProcess } from './process.js';
2
+ export class CodexExecutor {
3
+ agent = 'codex';
4
+ async run(context, options) {
5
+ return await runAgentProcess({
6
+ command: 'codex',
7
+ agent: this.agent,
8
+ context,
9
+ runOptions: options
10
+ });
11
+ }
12
+ }
@@ -0,0 +1,10 @@
1
+ import type { AgentName } from '../types.js';
2
+ import type { ExecutorResult, ExecutorRunOptions } from './types.js';
3
+ interface RunAgentProcessOptions {
4
+ command: string;
5
+ agent: AgentName;
6
+ context: string;
7
+ runOptions?: ExecutorRunOptions;
8
+ }
9
+ export declare function runAgentProcess(options: RunAgentProcessOptions): Promise<ExecutorResult>;
10
+ export {};
@@ -0,0 +1,38 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { ExecutionError } from '../errors.js';
3
+ export async function runAgentProcess(options) {
4
+ return await new Promise((resolve, reject) => {
5
+ const child = spawn(options.command, [], {
6
+ stdio: ['pipe', 'pipe', 'pipe']
7
+ });
8
+ let output = '';
9
+ child.stdout.on('data', (chunk) => {
10
+ const text = chunk.toString();
11
+ output += text;
12
+ if (options.runOptions?.streamOutput !== false) {
13
+ process.stdout.write(chunk);
14
+ }
15
+ });
16
+ child.stderr.on('data', (chunk) => {
17
+ const text = chunk.toString();
18
+ output += text;
19
+ if (options.runOptions?.streamOutput !== false) {
20
+ process.stderr.write(chunk);
21
+ }
22
+ });
23
+ child.on('error', (error) => {
24
+ reject(new ExecutionError(`Failed to start ${options.agent}: ${error.message}`));
25
+ });
26
+ child.on('close', (code) => {
27
+ resolve({
28
+ output,
29
+ exitCode: code ?? 1,
30
+ agent: options.agent
31
+ });
32
+ });
33
+ child.stdin.on('error', () => {
34
+ // Ignore broken-pipe behavior if the child exits before stdin completes.
35
+ });
36
+ child.stdin.end(options.context);
37
+ });
38
+ }
@@ -0,0 +1,13 @@
1
+ import type { AgentName } from '../types.js';
2
+ import type { Executor } from './types.js';
3
+ export interface AgentAvailability {
4
+ agent: AgentName;
5
+ available: boolean;
6
+ command: string;
7
+ resolvedPath?: string;
8
+ }
9
+ export interface ExecutorResolver {
10
+ resolveExecutor(agentName: AgentName): Promise<Executor>;
11
+ getAgentAvailability(): Promise<Record<AgentName, AgentAvailability>>;
12
+ }
13
+ export declare function createExecutorResolver(): ExecutorResolver;
@@ -0,0 +1,94 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { AgentNotAvailableError } from '../errors.js';
5
+ import { ClaudeExecutor } from './claude.js';
6
+ import { CodexExecutor } from './codex.js';
7
+ const SUPPORTED_AGENTS = ['claude', 'codex'];
8
+ const AGENT_COMMANDS = {
9
+ claude: 'claude',
10
+ codex: 'codex'
11
+ };
12
+ class DefaultExecutorResolver {
13
+ availabilityCache = new Map();
14
+ executorCache = new Map();
15
+ async resolveExecutor(agentName) {
16
+ const availability = await this.getAvailabilityForAgent(agentName);
17
+ if (!availability.available) {
18
+ throw new AgentNotAvailableError(`Agent "${agentName}" is not available on PATH.`);
19
+ }
20
+ if (!this.executorCache.has(agentName)) {
21
+ this.executorCache.set(agentName, createExecutor(agentName));
22
+ }
23
+ return this.executorCache.get(agentName);
24
+ }
25
+ async getAgentAvailability() {
26
+ const checks = await Promise.all(SUPPORTED_AGENTS.map(async (agentName) => [
27
+ agentName,
28
+ await this.getAvailabilityForAgent(agentName)
29
+ ]));
30
+ return Object.fromEntries(checks);
31
+ }
32
+ async getAvailabilityForAgent(agentName) {
33
+ if (!this.availabilityCache.has(agentName)) {
34
+ this.availabilityCache.set(agentName, checkAvailability(agentName));
35
+ }
36
+ return await this.availabilityCache.get(agentName);
37
+ }
38
+ }
39
+ export function createExecutorResolver() {
40
+ return new DefaultExecutorResolver();
41
+ }
42
+ function createExecutor(agentName) {
43
+ switch (agentName) {
44
+ case 'claude':
45
+ return new ClaudeExecutor();
46
+ case 'codex':
47
+ return new CodexExecutor();
48
+ }
49
+ }
50
+ async function checkAvailability(agentName) {
51
+ const command = AGENT_COMMANDS[agentName];
52
+ const resolvedPath = await findExecutable(command);
53
+ return {
54
+ agent: agentName,
55
+ command,
56
+ available: resolvedPath !== null,
57
+ resolvedPath: resolvedPath ?? undefined
58
+ };
59
+ }
60
+ async function findExecutable(command) {
61
+ if (command.includes(path.sep)) {
62
+ return (await isExecutable(command)) ? command : null;
63
+ }
64
+ const pathEntries = (process.env.PATH ?? '')
65
+ .split(path.delimiter)
66
+ .filter((entry) => entry.length > 0);
67
+ const extensions = process.platform === 'win32'
68
+ ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM')
69
+ .split(';')
70
+ .filter((entry) => entry.length > 0)
71
+ : [''];
72
+ for (const pathEntry of pathEntries) {
73
+ for (const extension of extensions) {
74
+ const candidate = process.platform === 'win32' &&
75
+ extension.length > 0 &&
76
+ !command.toLowerCase().endsWith(extension.toLowerCase())
77
+ ? path.join(pathEntry, `${command}${extension}`)
78
+ : path.join(pathEntry, command);
79
+ if (await isExecutable(candidate)) {
80
+ return candidate;
81
+ }
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ async function isExecutable(filePath) {
87
+ try {
88
+ await access(filePath, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
89
+ return true;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
@@ -0,0 +1,13 @@
1
+ import type { AgentName } from '../types.js';
2
+ export interface ExecutorRunOptions {
3
+ streamOutput?: boolean;
4
+ }
5
+ export interface ExecutorResult {
6
+ output: string;
7
+ exitCode: number;
8
+ agent: AgentName;
9
+ }
10
+ export interface Executor {
11
+ readonly agent: AgentName;
12
+ run(context: string, options?: ExecutorRunOptions): Promise<ExecutorResult>;
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type { DiagnosticStatus } from './types.js';
2
+ export declare function printError(error: unknown): void;
3
+ export declare function formatError(error: unknown): string;
4
+ export declare function formatDiagnosticStatus(status: DiagnosticStatus): string;
package/dist/output.js ADDED
@@ -0,0 +1,25 @@
1
+ import pc from 'picocolors';
2
+ import { BrainctlError } from './errors.js';
3
+ export function printError(error) {
4
+ console.error(formatError(error));
5
+ }
6
+ export function formatError(error) {
7
+ if (error instanceof BrainctlError) {
8
+ const prefix = error.category === 'user' ? 'Error' : 'System error';
9
+ return pc.red(`${prefix}: ${error.message}`);
10
+ }
11
+ if (error instanceof Error) {
12
+ return pc.red(`System error: ${error.message}`);
13
+ }
14
+ return pc.red('System error: An unknown failure occurred.');
15
+ }
16
+ export function formatDiagnosticStatus(status) {
17
+ switch (status) {
18
+ case 'ok':
19
+ return pc.green('OK');
20
+ case 'warn':
21
+ return pc.yellow('WARN');
22
+ case 'error':
23
+ return pc.red('ERROR');
24
+ }
25
+ }
@@ -0,0 +1,14 @@
1
+ import type { ExecutorResolver } from '../executor/resolver.js';
2
+ import type { DiagnosticCheck } from '../types.js';
3
+ export interface DoctorResult {
4
+ checks: DiagnosticCheck[];
5
+ hasIssues: boolean;
6
+ }
7
+ export interface DoctorService {
8
+ execute(options?: {
9
+ cwd?: string;
10
+ }): Promise<DoctorResult>;
11
+ }
12
+ export declare function createDoctorService(dependencies?: {
13
+ resolver?: ExecutorResolver;
14
+ }): DoctorService;
@@ -0,0 +1,79 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { loadConfig } from '../config.js';
4
+ import { createExecutorResolver } from '../executor/resolver.js';
5
+ export function createDoctorService(dependencies = {}) {
6
+ const resolver = dependencies.resolver ?? createExecutorResolver();
7
+ return {
8
+ async execute(options = {}) {
9
+ const cwd = options.cwd ?? process.cwd();
10
+ const checks = [];
11
+ const configPath = path.join(cwd, 'ai-stack.yaml');
12
+ const configExists = await pathExists(configPath);
13
+ if (!configExists) {
14
+ checks.push({
15
+ label: 'Config',
16
+ status: 'error',
17
+ message: 'ai-stack.yaml was not found.'
18
+ });
19
+ }
20
+ if (configExists) {
21
+ try {
22
+ const config = await loadConfig({ cwd });
23
+ checks.push({
24
+ label: 'Config',
25
+ status: 'ok',
26
+ message: `Loaded ${config.configPath}`
27
+ });
28
+ for (const memoryPath of config.memory.paths) {
29
+ const exists = await pathExists(memoryPath);
30
+ checks.push({
31
+ label: 'Memory',
32
+ status: exists ? 'ok' : 'error',
33
+ message: exists
34
+ ? `Memory path is available: ${memoryPath}`
35
+ : `Memory path is missing: ${memoryPath}`
36
+ });
37
+ }
38
+ checks.push({
39
+ label: 'Skills',
40
+ status: Object.keys(config.skills).length > 0 ? 'ok' : 'error',
41
+ message: Object.keys(config.skills).length > 0
42
+ ? `${Object.keys(config.skills).length} skills configured`
43
+ : 'No skills are configured.'
44
+ });
45
+ }
46
+ catch (error) {
47
+ checks.push({
48
+ label: 'Config',
49
+ status: 'error',
50
+ message: error instanceof Error ? error.message : 'Config validation failed.'
51
+ });
52
+ }
53
+ }
54
+ const availability = await resolver.getAgentAvailability();
55
+ for (const agent of Object.values(availability)) {
56
+ checks.push({
57
+ label: 'Agent',
58
+ status: agent.available ? 'ok' : 'warn',
59
+ message: agent.available
60
+ ? `${agent.agent} is available`
61
+ : `${agent.agent} is not available on PATH`
62
+ });
63
+ }
64
+ return {
65
+ checks,
66
+ hasIssues: checks.some((check) => check.status !== 'ok')
67
+ };
68
+ }
69
+ };
70
+ }
71
+ async function pathExists(targetPath) {
72
+ try {
73
+ await stat(targetPath);
74
+ return true;
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
@@ -0,0 +1,14 @@
1
+ export interface InitServiceRequest {
2
+ cwd?: string;
3
+ force?: boolean;
4
+ }
5
+ export interface InitServiceResult {
6
+ created: string[];
7
+ replaced: string[];
8
+ skipped: string[];
9
+ alreadyInitialized: boolean;
10
+ }
11
+ export interface InitService {
12
+ execute(request?: InitServiceRequest): Promise<InitServiceResult>;
13
+ }
14
+ export declare function createInitService(): InitService;
@@ -0,0 +1,88 @@
1
+ import { mkdir, stat, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const SAMPLE_CONFIG = `memory:
4
+ paths:
5
+ - ./memory
6
+
7
+ skills:
8
+ summarize:
9
+ description: Summarize content
10
+ prompt: |
11
+ Summarize the following content into concise bullet points.
12
+
13
+ analyze:
14
+ description: Analyze content deeply
15
+ prompt: |
16
+ Analyze the following content and extract key insights.
17
+
18
+ mcps: {}
19
+ `;
20
+ const SAMPLE_MEMORY = `# Team Notes
21
+
22
+ - Track important project context here.
23
+ - Keep prompts and references concise.
24
+ `;
25
+ export function createInitService() {
26
+ return {
27
+ async execute(request = {}) {
28
+ const cwd = request.cwd ?? process.cwd();
29
+ const force = request.force ?? false;
30
+ const configPath = path.join(cwd, 'ai-stack.yaml');
31
+ const memoryDir = path.join(cwd, 'memory');
32
+ const notesPath = path.join(memoryDir, 'notes.md');
33
+ const created = [];
34
+ const replaced = [];
35
+ const skipped = [];
36
+ await writeManagedFile({
37
+ targetPath: configPath,
38
+ content: SAMPLE_CONFIG,
39
+ force,
40
+ created,
41
+ replaced,
42
+ skipped
43
+ });
44
+ const memoryDirExists = await pathExists(memoryDir);
45
+ if (!memoryDirExists) {
46
+ await mkdir(memoryDir, { recursive: true });
47
+ created.push(memoryDir);
48
+ }
49
+ await writeManagedFile({
50
+ targetPath: notesPath,
51
+ content: SAMPLE_MEMORY,
52
+ force,
53
+ created,
54
+ replaced,
55
+ skipped
56
+ });
57
+ return {
58
+ created,
59
+ replaced,
60
+ skipped,
61
+ alreadyInitialized: created.length === 0 && replaced.length === 0
62
+ };
63
+ }
64
+ };
65
+ }
66
+ async function writeManagedFile(options) {
67
+ const exists = await pathExists(options.targetPath);
68
+ if (exists && !options.force) {
69
+ options.skipped.push(options.targetPath);
70
+ return;
71
+ }
72
+ await mkdir(path.dirname(options.targetPath), { recursive: true });
73
+ await writeFile(options.targetPath, options.content, 'utf8');
74
+ if (exists) {
75
+ options.replaced.push(options.targetPath);
76
+ return;
77
+ }
78
+ options.created.push(options.targetPath);
79
+ }
80
+ async function pathExists(targetPath) {
81
+ try {
82
+ await stat(targetPath);
83
+ return true;
84
+ }
85
+ catch {
86
+ return false;
87
+ }
88
+ }
@@ -0,0 +1,11 @@
1
+ import type { ExecutorResolver } from '../executor/resolver.js';
2
+ import type { ExecutionStep, ExecutionTrace, RunRequest } from '../types.js';
3
+ export interface RunService {
4
+ execute(request: RunRequest): Promise<ExecutionTrace>;
5
+ }
6
+ interface RunServiceDependencies {
7
+ resolver?: ExecutorResolver;
8
+ }
9
+ export declare function createRunService(dependencies?: RunServiceDependencies): RunService;
10
+ export declare function buildExecutionPlan(request: RunRequest): ExecutionStep[];
11
+ export {};
@@ -0,0 +1,93 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { AgentNotAvailableError, InputFileError } from '../errors.js';
4
+ import { loadConfig } from '../config.js';
5
+ import { buildContext } from '../context/builder.js';
6
+ import { loadMemory } from '../context/memory.js';
7
+ import { resolveSkillPrompt } from '../context/skills.js';
8
+ import { createExecutorResolver } from '../executor/resolver.js';
9
+ export function createRunService(dependencies = {}) {
10
+ const resolver = dependencies.resolver ?? createExecutorResolver();
11
+ return {
12
+ async execute(request) {
13
+ const cwd = request.cwd ?? process.cwd();
14
+ const config = await loadConfig({ cwd });
15
+ const memory = await loadMemory({ paths: config.memory.paths });
16
+ const steps = buildExecutionPlan(request);
17
+ const results = [];
18
+ let previousOutput;
19
+ for (const [stepIndex, step] of steps.entries()) {
20
+ const input = await resolveInput(step, cwd, previousOutput);
21
+ const skill = resolveSkillPrompt(config, step.skill);
22
+ const context = buildContext({
23
+ memory: memory.content,
24
+ skill,
25
+ input
26
+ });
27
+ let executor = await resolvePrimaryExecutor(resolver, step);
28
+ let fallbackUsed = false;
29
+ if (executor.fallbackRequired) {
30
+ fallbackUsed = true;
31
+ }
32
+ const result = await executor.instance.run(context, {
33
+ streamOutput: true
34
+ });
35
+ previousOutput = result.output;
36
+ results.push({
37
+ stepIndex,
38
+ requestedAgent: step.primaryAgent,
39
+ agent: result.agent,
40
+ fallbackUsed,
41
+ exitCode: result.exitCode,
42
+ output: result.output
43
+ });
44
+ }
45
+ const finalResult = results.at(-1);
46
+ return {
47
+ steps: results,
48
+ finalOutput: finalResult?.output ?? '',
49
+ finalExitCode: finalResult?.exitCode ?? 0
50
+ };
51
+ }
52
+ };
53
+ }
54
+ export function buildExecutionPlan(request) {
55
+ return [
56
+ {
57
+ skill: request.skill,
58
+ inputFile: request.inputFile,
59
+ primaryAgent: request.primaryAgent,
60
+ fallbackAgent: request.fallbackAgent,
61
+ usePreviousOutput: false
62
+ }
63
+ ];
64
+ }
65
+ async function resolveInput(step, cwd, previousOutput) {
66
+ if (step.usePreviousOutput && previousOutput !== undefined) {
67
+ return previousOutput;
68
+ }
69
+ const inputPath = path.resolve(cwd, step.inputFile);
70
+ try {
71
+ return await readFile(inputPath, 'utf8');
72
+ }
73
+ catch (error) {
74
+ throw new InputFileError(`Could not read input file: ${step.inputFile}`);
75
+ }
76
+ }
77
+ async function resolvePrimaryExecutor(resolver, step) {
78
+ try {
79
+ return {
80
+ instance: await resolver.resolveExecutor(step.primaryAgent),
81
+ fallbackRequired: false
82
+ };
83
+ }
84
+ catch (error) {
85
+ if (!(error instanceof AgentNotAvailableError) || !step.fallbackAgent) {
86
+ throw error;
87
+ }
88
+ return {
89
+ instance: await resolver.resolveExecutor(step.fallbackAgent),
90
+ fallbackRequired: true
91
+ };
92
+ }
93
+ }
@@ -0,0 +1,17 @@
1
+ import type { AgentAvailability, ExecutorResolver } from '../executor/resolver.js';
2
+ import type { AgentName, MemoryLoadResult } from '../types.js';
3
+ export interface StatusResult {
4
+ configPath: string;
5
+ memory: MemoryLoadResult;
6
+ skills: string[];
7
+ mcpCount: number;
8
+ agents: Record<AgentName, AgentAvailability>;
9
+ }
10
+ export interface StatusService {
11
+ execute(options?: {
12
+ cwd?: string;
13
+ }): Promise<StatusResult>;
14
+ }
15
+ export declare function createStatusService(dependencies?: {
16
+ resolver?: ExecutorResolver;
17
+ }): StatusService;
@@ -0,0 +1,21 @@
1
+ import { loadConfig } from '../config.js';
2
+ import { loadMemory } from '../context/memory.js';
3
+ import { createExecutorResolver } from '../executor/resolver.js';
4
+ export function createStatusService(dependencies = {}) {
5
+ const resolver = dependencies.resolver ?? createExecutorResolver();
6
+ return {
7
+ async execute(options = {}) {
8
+ const cwd = options.cwd ?? process.cwd();
9
+ const config = await loadConfig({ cwd });
10
+ const memory = await loadMemory({ paths: config.memory.paths });
11
+ const agents = await resolver.getAgentAvailability();
12
+ return {
13
+ configPath: config.configPath,
14
+ memory,
15
+ skills: Object.keys(config.skills).sort((left, right) => left.localeCompare(right)),
16
+ mcpCount: Object.keys(config.mcps).length,
17
+ agents
18
+ };
19
+ }
20
+ };
21
+ }
@@ -0,0 +1,53 @@
1
+ export type AgentName = 'claude' | 'codex';
2
+ export type ErrorCategory = 'user' | 'system';
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
+ }
22
+ export interface RunRequest {
23
+ cwd?: string;
24
+ skill: string;
25
+ inputFile: string;
26
+ primaryAgent: AgentName;
27
+ fallbackAgent?: AgentName;
28
+ }
29
+ export interface ExecutionStep {
30
+ skill: string;
31
+ inputFile: string;
32
+ primaryAgent: AgentName;
33
+ fallbackAgent?: AgentName;
34
+ usePreviousOutput?: boolean;
35
+ }
36
+ export interface ExecutionStepResult {
37
+ stepIndex: number;
38
+ requestedAgent: AgentName;
39
+ agent: AgentName;
40
+ fallbackUsed: boolean;
41
+ exitCode: number;
42
+ output: string;
43
+ }
44
+ export interface ExecutionTrace {
45
+ steps: ExecutionStepResult[];
46
+ finalOutput: string;
47
+ finalExitCode: number;
48
+ }
49
+ export interface DiagnosticCheck {
50
+ label: string;
51
+ status: DiagnosticStatus;
52
+ message: string;
53
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "brainctl",
3
+ "version": "0.1.0",
4
+ "description": "CLI environment manager for consistent AI workflows",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Rorogogogo/brainctl.git"
9
+ },
10
+ "homepage": "https://github.com/Rorogogogo/brainctl#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/Rorogogogo/brainctl/issues"
13
+ },
14
+ "bin": {
15
+ "brainctl": "./dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "engines": {
21
+ "node": ">=18.18.0"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json",
25
+ "dev": "tsx src/cli.ts",
26
+ "prepublishOnly": "npm test && npm run build",
27
+ "test": "vitest run"
28
+ },
29
+ "keywords": [
30
+ "cli",
31
+ "ai",
32
+ "developer-tools"
33
+ ],
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "commander": "^14.0.1",
37
+ "picocolors": "^1.1.1",
38
+ "yaml": "^2.8.1"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^24.6.1",
42
+ "tsx": "^4.20.6",
43
+ "typescript": "^5.9.3",
44
+ "vitest": "^3.2.4"
45
+ }
46
+ }