brains-cli 0.1.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -0
- package/README.md +64 -81
- package/bin/brains.js +23 -1
- package/package.json +3 -1
- package/src/api.js +115 -0
- package/src/commands/config.js +236 -0
- package/src/commands/install.js +3 -1
- package/src/commands/run.js +477 -317
- package/src/config.js +237 -0
- package/src/providers/anthropic.js +83 -0
- package/src/providers/openai-compat.js +96 -0
- package/src/providers/registry.js +125 -0
- package/src/utils/files.js +239 -0
package/.env.example
ADDED
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#
|
|
1
|
+
# brains-cli
|
|
2
2
|
|
|
3
3
|
> **Install the world's greatest brains.** AI-powered dev agents you can browse, install, and run from your terminal.
|
|
4
4
|
|
|
5
|
-
Brains are intelligent, conversational AI agents
|
|
5
|
+
Brains are intelligent, conversational AI agents powered by Claude. Unlike dumb templates or scaffolders, Brains **talk to you**, understand your context, and generate production-grade output tailored to your specific needs.
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -10,29 +10,38 @@ Brains are intelligent, conversational AI agents that act as specialized team me
|
|
|
10
10
|
# Install globally
|
|
11
11
|
npm install -g brains-cli
|
|
12
12
|
|
|
13
|
+
# Set your Anthropic API key
|
|
14
|
+
brains config set api_key sk-ant-your-key-here
|
|
15
|
+
|
|
13
16
|
# Explore available brains
|
|
14
17
|
brains explore
|
|
15
18
|
|
|
16
|
-
# Install a brain
|
|
19
|
+
# Install and run a brain
|
|
17
20
|
brains install resume
|
|
18
|
-
|
|
19
|
-
# Run it
|
|
20
21
|
brains run resume
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
##
|
|
24
|
+
## How It Works
|
|
25
|
+
|
|
26
|
+
1. **Browse** the registry of 11 specialized AI agent Brains
|
|
27
|
+
2. **Install** the ones you need to `~/.brains/`
|
|
28
|
+
3. **Run** them — they interview you, then generate real output using Claude
|
|
29
|
+
|
|
30
|
+
Every Brain has a unique personality, methodology, and expertise defined by a detailed system prompt. When you run a Brain, it calls the Claude API with that system prompt and your context to generate real, production-grade results.
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
## Brain Categories
|
|
26
33
|
|
|
27
|
-
###
|
|
34
|
+
### Builder Brains — Generate entire projects
|
|
28
35
|
|
|
29
36
|
| Brain | Description | Price |
|
|
30
37
|
|-------|-------------|-------|
|
|
31
|
-
| `resume` | Build a
|
|
38
|
+
| `resume` | Build a complete Next.js resume/portfolio site | Free |
|
|
32
39
|
| `mvp` | Turn a product idea into a full-stack MVP | $9.99 |
|
|
33
40
|
| `landing` | Generate high-converting landing pages | Free |
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
Builder brains ask you questions, then call Claude to generate a complete project with all files written to disk.
|
|
43
|
+
|
|
44
|
+
### Role Brains — Act as team members
|
|
36
45
|
|
|
37
46
|
| Brain | Description | Price |
|
|
38
47
|
|-------|-------------|-------|
|
|
@@ -40,14 +49,18 @@ A Brain is an AI agent with a specific personality, methodology, and expertise.
|
|
|
40
49
|
| `backend` | Backend architect (APIs, databases, infra) | $4.99 |
|
|
41
50
|
| `architect` | System design, diagrams, and technical decisions | $9.99 |
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
Role brains enter a conversational REPL — chat back and forth with a specialist who maintains full context.
|
|
53
|
+
|
|
54
|
+
### Reviewer Brains — Analyze & improve code
|
|
44
55
|
|
|
45
56
|
| Brain | Description | Price |
|
|
46
57
|
|-------|-------------|-------|
|
|
47
58
|
| `reviewer` | Deep, senior-level code review | Free |
|
|
48
59
|
| `security` | Find vulnerabilities and harden your app | $4.99 |
|
|
49
60
|
|
|
50
|
-
|
|
61
|
+
Reviewer brains scan your project files, send them to Claude, and output a structured review report.
|
|
62
|
+
|
|
63
|
+
### Domain Brains — Deep tech expertise
|
|
51
64
|
|
|
52
65
|
| Brain | Description | Price |
|
|
53
66
|
|-------|-------------|-------|
|
|
@@ -55,11 +68,13 @@ A Brain is an AI agent with a specific personality, methodology, and expertise.
|
|
|
55
68
|
| `supabase` | Supabase expert (auth, RLS, edge functions) | $4.99 |
|
|
56
69
|
| `stripe` | Payments, subscriptions, webhooks | $4.99 |
|
|
57
70
|
|
|
71
|
+
Domain brains are conversational experts in a specific technology.
|
|
72
|
+
|
|
58
73
|
## Commands
|
|
59
74
|
|
|
60
75
|
### `brains explore`
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
Interactive terminal browser for discovering Brains.
|
|
63
78
|
|
|
64
79
|
```bash
|
|
65
80
|
brains explore # Browse all
|
|
@@ -69,27 +84,29 @@ brains explore -s "nextjs" # Search
|
|
|
69
84
|
|
|
70
85
|
### `brains install <brain>`
|
|
71
86
|
|
|
72
|
-
Download and
|
|
87
|
+
Download and save a Brain locally.
|
|
73
88
|
|
|
74
89
|
```bash
|
|
75
|
-
brains install resume
|
|
76
|
-
brains install mvp # Install MVP generator
|
|
90
|
+
brains install resume
|
|
77
91
|
brains i reviewer # Short alias
|
|
78
92
|
```
|
|
79
93
|
|
|
80
94
|
### `brains run <brain>`
|
|
81
95
|
|
|
82
|
-
|
|
96
|
+
The core feature. Activates a Brain and connects it to Claude.
|
|
83
97
|
|
|
84
98
|
```bash
|
|
85
|
-
brains run resume #
|
|
86
|
-
brains run reviewer #
|
|
99
|
+
brains run resume # Builder: generates a project
|
|
100
|
+
brains run reviewer # Reviewer: scans and reviews your codebase
|
|
101
|
+
brains run frontend # Role: conversational REPL with a specialist
|
|
87
102
|
brains run mvp --with stripe supabase # Stack multiple brains
|
|
88
103
|
```
|
|
89
104
|
|
|
105
|
+
**Brain stacking** merges system prompts so a single agent has combined expertise.
|
|
106
|
+
|
|
90
107
|
### `brains list`
|
|
91
108
|
|
|
92
|
-
Show
|
|
109
|
+
Show installed Brains.
|
|
93
110
|
|
|
94
111
|
```bash
|
|
95
112
|
brains list # or: brains ls
|
|
@@ -97,94 +114,60 @@ brains list # or: brains ls
|
|
|
97
114
|
|
|
98
115
|
### `brains create [name]`
|
|
99
116
|
|
|
100
|
-
Author a new Brain
|
|
117
|
+
Author a new Brain with an interactive wizard.
|
|
101
118
|
|
|
102
119
|
```bash
|
|
103
120
|
brains create my-brain
|
|
104
|
-
brains create --template builder # Start from a template
|
|
105
121
|
```
|
|
106
122
|
|
|
107
123
|
### `brains publish`
|
|
108
124
|
|
|
109
|
-
Publish your Brain to the
|
|
125
|
+
Publish your Brain to the registry.
|
|
110
126
|
|
|
111
127
|
```bash
|
|
112
128
|
cd my-brain/
|
|
113
|
-
brains publish
|
|
114
|
-
brains publish --private # Private (team only)
|
|
129
|
+
brains publish
|
|
115
130
|
```
|
|
116
131
|
|
|
117
|
-
|
|
132
|
+
### `brains config`
|
|
118
133
|
|
|
119
|
-
|
|
134
|
+
Manage your API key and settings.
|
|
120
135
|
|
|
121
136
|
```bash
|
|
122
|
-
#
|
|
123
|
-
brains
|
|
124
|
-
|
|
125
|
-
#
|
|
126
|
-
brains
|
|
137
|
+
brains config # Show all settings
|
|
138
|
+
brains config set api_key sk-ant-... # Save API key
|
|
139
|
+
brains config set model claude-sonnet-4-20250514
|
|
140
|
+
brains config get api_key # Show current key (masked)
|
|
141
|
+
brains config reset # Reset to defaults
|
|
127
142
|
```
|
|
128
143
|
|
|
129
|
-
##
|
|
144
|
+
## Configuration
|
|
130
145
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
├── brain.json ← Configuration (name, category, capabilities, price)
|
|
136
|
-
├── BRAIN.md ← System prompt & documentation
|
|
137
|
-
└── README.md ← Public readme for the registry
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
The **system prompt** in `BRAIN.md` is the core of your Brain. It defines:
|
|
141
|
-
- **Personality** — How the brain communicates
|
|
142
|
-
- **Methodology** — Step-by-step workflow
|
|
143
|
-
- **Principles** — Rules and best practices
|
|
144
|
-
- **Output format** — How results are structured
|
|
145
|
-
|
|
146
|
-
```bash
|
|
147
|
-
# Scaffold a new brain
|
|
148
|
-
brains create my-brain
|
|
149
|
-
|
|
150
|
-
# Edit the system prompt
|
|
151
|
-
$EDITOR my-brain/BRAIN.md
|
|
152
|
-
|
|
153
|
-
# Test locally
|
|
154
|
-
brains run my-brain
|
|
155
|
-
|
|
156
|
-
# Publish to registry
|
|
157
|
-
cd my-brain && brains publish
|
|
158
|
-
```
|
|
146
|
+
API key resolution order:
|
|
147
|
+
1. `ANTHROPIC_API_KEY` environment variable
|
|
148
|
+
2. `~/.brains/config.json` (set via `brains config set api_key`)
|
|
149
|
+
3. Interactive prompt on first run
|
|
159
150
|
|
|
160
151
|
## Architecture
|
|
161
152
|
|
|
162
153
|
```
|
|
163
|
-
~/.brains/
|
|
164
|
-
├──
|
|
154
|
+
~/.brains/
|
|
155
|
+
├── config.json # API key, model, preferences
|
|
156
|
+
├── installed.json # Registry of installed brains
|
|
157
|
+
├── sessions/ # Saved conversation transcripts
|
|
165
158
|
├── resume/
|
|
166
|
-
│ ├── brain.json
|
|
167
|
-
│ └── BRAIN.md
|
|
168
|
-
├── reviewer/
|
|
169
|
-
│ ├── brain.json
|
|
170
|
-
│ └── BRAIN.md
|
|
159
|
+
│ ├── brain.json # Brain config + system prompt
|
|
160
|
+
│ └── BRAIN.md # Human-readable docs
|
|
171
161
|
└── ...
|
|
172
162
|
```
|
|
173
163
|
|
|
174
|
-
##
|
|
175
|
-
|
|
176
|
-
| Tier | Price | Includes |
|
|
177
|
-
|------|-------|----------|
|
|
178
|
-
| Free | $0 | 4 free brains (resume, landing, reviewer, nextjs) |
|
|
179
|
-
| Pro | $20/mo | All brains + brain stacking + priority |
|
|
180
|
-
| Enterprise | Custom | Private registry + team management |
|
|
181
|
-
|
|
182
|
-
Brain creators set their own prices. Platform takes 20%.
|
|
183
|
-
|
|
184
|
-
## Contributing
|
|
164
|
+
## Tech Stack
|
|
185
165
|
|
|
186
|
-
|
|
166
|
+
- **Runtime**: Node.js 16+ (CommonJS)
|
|
167
|
+
- **AI**: Claude via `@anthropic-ai/sdk` (streaming)
|
|
168
|
+
- **CLI**: Commander.js, Inquirer.js
|
|
169
|
+
- **UI**: Chalk, Ora, Boxen, Figlet, gradient-string, cli-table3
|
|
187
170
|
|
|
188
171
|
## License
|
|
189
172
|
|
|
190
|
-
MIT
|
|
173
|
+
MIT
|
package/bin/brains.js
CHANGED
|
@@ -10,8 +10,24 @@ const { run } = require('../src/commands/run');
|
|
|
10
10
|
const { list } = require('../src/commands/list');
|
|
11
11
|
const { create } = require('../src/commands/create');
|
|
12
12
|
const { publish } = require('../src/commands/publish');
|
|
13
|
+
const { config } = require('../src/commands/config');
|
|
13
14
|
const { showBanner } = require('../src/utils/ui');
|
|
14
15
|
|
|
16
|
+
// Graceful shutdown on Ctrl+C
|
|
17
|
+
process.on('SIGINT', () => {
|
|
18
|
+
console.log('\n');
|
|
19
|
+
process.exit(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Suppress unhandled rejection noise — errors are handled in commands
|
|
23
|
+
process.on('unhandledRejection', (err) => {
|
|
24
|
+
if (err && err.message !== 'NO_API_KEY') {
|
|
25
|
+
const chalk = require('chalk');
|
|
26
|
+
console.error(`\n ${chalk.red('✗')} Unexpected error: ${err.message || err}\n`);
|
|
27
|
+
}
|
|
28
|
+
process.exit(1);
|
|
29
|
+
});
|
|
30
|
+
|
|
15
31
|
showBanner();
|
|
16
32
|
|
|
17
33
|
program
|
|
@@ -39,7 +55,7 @@ program
|
|
|
39
55
|
program
|
|
40
56
|
.command('run <brain>')
|
|
41
57
|
.alias('r')
|
|
42
|
-
.description('Run an installed Brain
|
|
58
|
+
.description('Run an installed Brain — the core AI experience')
|
|
43
59
|
.option('-w, --with <brains...>', 'Stack multiple brains together')
|
|
44
60
|
.option('-d, --dir <directory>', 'Target directory (default: current)')
|
|
45
61
|
.option('--no-interactive', 'Run without interactive prompts')
|
|
@@ -66,6 +82,12 @@ program
|
|
|
66
82
|
.option('--private', 'Publish as a private Brain')
|
|
67
83
|
.action(publish);
|
|
68
84
|
|
|
85
|
+
// ─── Config: Manage settings ───
|
|
86
|
+
program
|
|
87
|
+
.command('config [action] [args...]')
|
|
88
|
+
.description('Manage settings (set/get api_key, model, etc.)')
|
|
89
|
+
.action(config);
|
|
90
|
+
|
|
69
91
|
program.parse(process.argv);
|
|
70
92
|
|
|
71
93
|
// If no args, show help
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brains-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "🧠 Brains.io — Install AI-powered dev agents. Build, review, architect, and ship with one command.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
"author": "Brains.io",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@anthropic-ai/sdk": "^0.39.0",
|
|
28
|
+
"openai": "^4.78.0",
|
|
27
29
|
"chalk": "^4.1.2",
|
|
28
30
|
"commander": "^11.1.0",
|
|
29
31
|
"inquirer": "^8.2.6",
|
package/src/api.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* API Router — delegates to the active provider.
|
|
5
|
+
*
|
|
6
|
+
* All brain commands call streamMessage/sendMessage from here.
|
|
7
|
+
* This module resolves which provider to use and routes accordingly.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { getActiveProvider, getProviderApiKey, getProviderModel, getProviderBaseUrl } = require('./config');
|
|
11
|
+
const { getProviderDef } = require('./providers/registry');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Stream a response from the active AI provider.
|
|
15
|
+
* Returns the full assistant message text.
|
|
16
|
+
*
|
|
17
|
+
* @param {Object} opts
|
|
18
|
+
* @param {string} opts.systemPrompt
|
|
19
|
+
* @param {Array} opts.messages - [{role, content}]
|
|
20
|
+
* @param {number} [opts.maxTokens]
|
|
21
|
+
* @param {string} [opts.model] - Override model
|
|
22
|
+
* @param {Function} [opts.onText]
|
|
23
|
+
* @param {Function} [opts.onStart]
|
|
24
|
+
* @param {Function} [opts.onEnd]
|
|
25
|
+
* @returns {Promise<string>}
|
|
26
|
+
*/
|
|
27
|
+
async function streamMessage(opts) {
|
|
28
|
+
const {
|
|
29
|
+
systemPrompt,
|
|
30
|
+
messages,
|
|
31
|
+
maxTokens = 8096,
|
|
32
|
+
model,
|
|
33
|
+
onText = (text) => process.stdout.write(text),
|
|
34
|
+
onStart = () => {},
|
|
35
|
+
onEnd = () => {},
|
|
36
|
+
} = opts;
|
|
37
|
+
|
|
38
|
+
const providerName = getActiveProvider();
|
|
39
|
+
const providerDef = getProviderDef(providerName);
|
|
40
|
+
|
|
41
|
+
if (!providerDef) {
|
|
42
|
+
throw new Error(`Unknown provider "${providerName}". Run: brains config set provider <name>`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const apiKey = getProviderApiKey(providerName);
|
|
46
|
+
const modelId = model || getProviderModel(providerName);
|
|
47
|
+
const baseURL = getProviderBaseUrl(providerName);
|
|
48
|
+
|
|
49
|
+
if (!apiKey && providerDef.requires_key !== false) {
|
|
50
|
+
throw new Error('NO_API_KEY');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const callOpts = {
|
|
54
|
+
apiKey,
|
|
55
|
+
systemPrompt,
|
|
56
|
+
messages,
|
|
57
|
+
maxTokens,
|
|
58
|
+
model: modelId,
|
|
59
|
+
onText,
|
|
60
|
+
onStart,
|
|
61
|
+
onEnd,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
if (providerDef.type === 'anthropic') {
|
|
65
|
+
const provider = require('./providers/anthropic');
|
|
66
|
+
return provider.streamMessage(callOpts);
|
|
67
|
+
} else {
|
|
68
|
+
const provider = require('./providers/openai-compat');
|
|
69
|
+
callOpts.baseURL = baseURL;
|
|
70
|
+
return provider.streamMessage(callOpts);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Send a single message (non-streaming).
|
|
76
|
+
*/
|
|
77
|
+
async function sendMessage(opts) {
|
|
78
|
+
const {
|
|
79
|
+
systemPrompt,
|
|
80
|
+
messages,
|
|
81
|
+
maxTokens = 8096,
|
|
82
|
+
model,
|
|
83
|
+
} = opts;
|
|
84
|
+
|
|
85
|
+
const providerName = getActiveProvider();
|
|
86
|
+
const providerDef = getProviderDef(providerName);
|
|
87
|
+
|
|
88
|
+
if (!providerDef) {
|
|
89
|
+
throw new Error(`Unknown provider "${providerName}".`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const apiKey = getProviderApiKey(providerName);
|
|
93
|
+
const modelId = model || getProviderModel(providerName);
|
|
94
|
+
const baseURL = getProviderBaseUrl(providerName);
|
|
95
|
+
|
|
96
|
+
if (!apiKey && providerDef.requires_key !== false) {
|
|
97
|
+
throw new Error('NO_API_KEY');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const callOpts = { apiKey, systemPrompt, messages, maxTokens, model: modelId };
|
|
101
|
+
|
|
102
|
+
if (providerDef.type === 'anthropic') {
|
|
103
|
+
const provider = require('./providers/anthropic');
|
|
104
|
+
return provider.sendMessage(callOpts);
|
|
105
|
+
} else {
|
|
106
|
+
const provider = require('./providers/openai-compat');
|
|
107
|
+
callOpts.baseURL = baseURL;
|
|
108
|
+
return provider.sendMessage(callOpts);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
streamMessage,
|
|
114
|
+
sendMessage,
|
|
115
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const Table = require('cli-table3');
|
|
5
|
+
const {
|
|
6
|
+
loadConfig, saveConfig, setConfigValue,
|
|
7
|
+
getActiveProvider, getProviderApiKey, getProviderModel, getProviderBaseUrl,
|
|
8
|
+
setProviderApiKey, setProviderModel, setActiveProvider,
|
|
9
|
+
maskApiKey, DEFAULTS,
|
|
10
|
+
} = require('../config');
|
|
11
|
+
const { getAllProviders, getProviderDef, getAllProviderNames } = require('../providers/registry');
|
|
12
|
+
const { showSuccess, showError, showInfo, showWarning, ACCENT, DIM, SUCCESS } = require('../utils/ui');
|
|
13
|
+
|
|
14
|
+
async function configCommand(action, args) {
|
|
15
|
+
if (!action) {
|
|
16
|
+
showCurrentConfig();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── brains config set <key> <value> ───
|
|
21
|
+
if (action === 'set') {
|
|
22
|
+
if (args.length < 2) {
|
|
23
|
+
showError('Usage: brains config set <key> <value>');
|
|
24
|
+
console.log('');
|
|
25
|
+
showInfo(`${ACCENT('provider')} — Switch AI provider (groq, anthropic, gemini, openrouter, ollama)`);
|
|
26
|
+
showInfo(`${ACCENT('api_key')} — Set API key for current provider`);
|
|
27
|
+
showInfo(`${ACCENT('model')} — Set model for current provider`);
|
|
28
|
+
showInfo(`${ACCENT('max_tokens')} — Set max tokens`);
|
|
29
|
+
console.log('');
|
|
30
|
+
showInfo(`Example: ${ACCENT('brains config set provider groq')}`);
|
|
31
|
+
showInfo(`Example: ${ACCENT('brains config set api_key gsk_...')}`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const key = args[0];
|
|
36
|
+
let value = args.slice(1).join(' ');
|
|
37
|
+
|
|
38
|
+
// ── Switch provider ──
|
|
39
|
+
if (key === 'provider') {
|
|
40
|
+
const providerDef = getProviderDef(value);
|
|
41
|
+
if (!providerDef) {
|
|
42
|
+
showError(`Unknown provider "${value}".`);
|
|
43
|
+
showInfo(`Available: ${getAllProviderNames().map(n => ACCENT(n)).join(', ')}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
setActiveProvider(value);
|
|
48
|
+
showSuccess(`Provider switched to ${chalk.bold(providerDef.name)}`);
|
|
49
|
+
|
|
50
|
+
// Check if key is configured
|
|
51
|
+
const apiKey = getProviderApiKey(value);
|
|
52
|
+
if (!apiKey && providerDef.requires_key !== false) {
|
|
53
|
+
console.log('');
|
|
54
|
+
showWarning(`No API key set for ${providerDef.name}.`);
|
|
55
|
+
showInfo(`Get one at: ${ACCENT(providerDef.key_url)}`);
|
|
56
|
+
showInfo(`Then run: ${ACCENT(`brains config set api_key <your-key>`)}`);
|
|
57
|
+
} else {
|
|
58
|
+
showInfo(`Model: ${DIM(getProviderModel(value))}`);
|
|
59
|
+
}
|
|
60
|
+
console.log('');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Set API key (for current provider) ──
|
|
65
|
+
if (key === 'api_key') {
|
|
66
|
+
const currentProvider = getActiveProvider();
|
|
67
|
+
setProviderApiKey(currentProvider, value);
|
|
68
|
+
showSuccess(`API key saved for ${getProviderDef(currentProvider).name}: ${maskApiKey(value)}`);
|
|
69
|
+
console.log('');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Set model (for current provider) ──
|
|
74
|
+
if (key === 'model') {
|
|
75
|
+
const currentProvider = getActiveProvider();
|
|
76
|
+
setProviderModel(currentProvider, value);
|
|
77
|
+
showSuccess(`Model set for ${getProviderDef(currentProvider).name}: ${value}`);
|
|
78
|
+
console.log('');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Generic config keys ──
|
|
83
|
+
const validKeys = ['max_tokens', 'theme', 'telemetry'];
|
|
84
|
+
if (!validKeys.includes(key)) {
|
|
85
|
+
showError(`Unknown config key "${key}".`);
|
|
86
|
+
showInfo(`Valid keys: provider, api_key, model, ${validKeys.map(k => ACCENT(k)).join(', ')}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (key === 'max_tokens') value = parseInt(value, 10);
|
|
91
|
+
if (key === 'telemetry') value = value === 'true';
|
|
92
|
+
|
|
93
|
+
setConfigValue(key, value);
|
|
94
|
+
showSuccess(`${key} = ${value}`);
|
|
95
|
+
console.log('');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── brains config get <key> ───
|
|
100
|
+
if (action === 'get') {
|
|
101
|
+
if (args.length < 1) {
|
|
102
|
+
showError('Usage: brains config get <key>');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const key = args[0];
|
|
107
|
+
|
|
108
|
+
if (key === 'provider') {
|
|
109
|
+
const p = getActiveProvider();
|
|
110
|
+
const def = getProviderDef(p);
|
|
111
|
+
console.log(`\n ${chalk.bold('provider')} = ${p} (${def ? def.name : 'unknown'})\n`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (key === 'api_key') {
|
|
116
|
+
const p = getActiveProvider();
|
|
117
|
+
const k = getProviderApiKey(p);
|
|
118
|
+
console.log(`\n ${chalk.bold('api_key')} [${p}] = ${k ? maskApiKey(k) : chalk.red('(not set)')}\n`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (key === 'model') {
|
|
123
|
+
const p = getActiveProvider();
|
|
124
|
+
console.log(`\n ${chalk.bold('model')} [${p}] = ${getProviderModel(p)}\n`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const config = loadConfig();
|
|
129
|
+
const value = config[key];
|
|
130
|
+
if (value === undefined) {
|
|
131
|
+
showInfo(`${key} is not set (default: ${DEFAULTS[key] || 'none'})`);
|
|
132
|
+
} else {
|
|
133
|
+
console.log(`\n ${chalk.bold(key)} = ${value}\n`);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── brains config providers ───
|
|
139
|
+
if (action === 'providers') {
|
|
140
|
+
showProviderList();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ─── brains config reset ───
|
|
145
|
+
if (action === 'reset') {
|
|
146
|
+
saveConfig({ ...DEFAULTS, providers: {} });
|
|
147
|
+
showSuccess('Config reset to defaults (provider: groq).');
|
|
148
|
+
console.log('');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
showError(`Unknown action "${action}". Use: set, get, providers, reset, or no action to view all.`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ═══════════════════════════════════════════
|
|
156
|
+
// Display helpers
|
|
157
|
+
// ═══════════════════════════════════════════
|
|
158
|
+
|
|
159
|
+
function showCurrentConfig() {
|
|
160
|
+
const config = loadConfig();
|
|
161
|
+
const currentProvider = getActiveProvider();
|
|
162
|
+
const providerDef = getProviderDef(currentProvider);
|
|
163
|
+
const apiKey = getProviderApiKey(currentProvider);
|
|
164
|
+
const model = getProviderModel(currentProvider);
|
|
165
|
+
|
|
166
|
+
console.log(`\n ${chalk.bold('Brains Configuration')}\n`);
|
|
167
|
+
|
|
168
|
+
const table = new Table({
|
|
169
|
+
style: { head: [], border: ['dim'] },
|
|
170
|
+
colWidths: [20, 46],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
table.push(
|
|
174
|
+
[chalk.bold('provider'), `${currentProvider} ${DIM(`(${providerDef ? providerDef.name : 'unknown'})`)}`],
|
|
175
|
+
[chalk.bold('api_key'), apiKey ? maskApiKey(apiKey) : (providerDef && providerDef.requires_key === false ? DIM('(not required)') : chalk.red('(not set)'))],
|
|
176
|
+
[chalk.bold('model'), model],
|
|
177
|
+
[chalk.bold('max_tokens'), String(config.max_tokens || DEFAULTS.max_tokens)],
|
|
178
|
+
[chalk.bold('telemetry'), String(config.telemetry ?? DEFAULTS.telemetry)]
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
console.log(table.toString());
|
|
182
|
+
|
|
183
|
+
if (!apiKey && providerDef && providerDef.requires_key !== false) {
|
|
184
|
+
console.log('');
|
|
185
|
+
showInfo(`Set API key: ${ACCENT(`brains config set api_key <your-${currentProvider}-key>`)}`);
|
|
186
|
+
if (providerDef.key_url) {
|
|
187
|
+
showInfo(`Get one at: ${ACCENT(providerDef.key_url)}`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log('');
|
|
192
|
+
showInfo(`Switch provider: ${ACCENT('brains config set provider <name>')}`);
|
|
193
|
+
showInfo(`See all providers: ${ACCENT('brains config providers')}`);
|
|
194
|
+
console.log('');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function showProviderList() {
|
|
198
|
+
const currentProvider = getActiveProvider();
|
|
199
|
+
const allProviders = getAllProviders();
|
|
200
|
+
|
|
201
|
+
console.log(`\n ${chalk.bold('Available Providers')}\n`);
|
|
202
|
+
|
|
203
|
+
const table = new Table({
|
|
204
|
+
head: [
|
|
205
|
+
chalk.white.bold('Provider'),
|
|
206
|
+
chalk.white.bold('Type'),
|
|
207
|
+
chalk.white.bold('Default Model'),
|
|
208
|
+
chalk.white.bold('Free?'),
|
|
209
|
+
chalk.white.bold('Status'),
|
|
210
|
+
],
|
|
211
|
+
style: { head: [], border: ['dim'] },
|
|
212
|
+
colWidths: [16, 12, 36, 8, 14],
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
for (const [name, def] of Object.entries(allProviders)) {
|
|
216
|
+
const isActive = name === currentProvider;
|
|
217
|
+
const hasKey = !!getProviderApiKey(name) || def.requires_key === false;
|
|
218
|
+
|
|
219
|
+
table.push([
|
|
220
|
+
isActive ? chalk.hex('#7B2FFF').bold(`▸ ${name}`) : ` ${name}`,
|
|
221
|
+
DIM(def.type),
|
|
222
|
+
DIM(def.default_model),
|
|
223
|
+
def.free ? SUCCESS('Yes') : DIM('Paid'),
|
|
224
|
+
hasKey ? SUCCESS('Ready') : chalk.yellow('Need key'),
|
|
225
|
+
]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(table.toString());
|
|
229
|
+
|
|
230
|
+
console.log('');
|
|
231
|
+
showInfo(`Active: ${ACCENT(currentProvider)}`);
|
|
232
|
+
showInfo(`Switch: ${ACCENT('brains config set provider <name>')}`);
|
|
233
|
+
console.log('');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = { config: configCommand };
|
package/src/commands/install.js
CHANGED