brain-colonies 0.1.1
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/README.md +67 -0
- package/dist/commands/auth.js +43 -0
- package/dist/commands/chat.js +51 -0
- package/dist/commands/check.js +26 -0
- package/dist/commands/colonies.js +45 -0
- package/dist/commands/consult.js +33 -0
- package/dist/commands/wrap.js +84 -0
- package/dist/index.js +63 -0
- package/dist/lib/api.js +95 -0
- package/dist/lib/config.js +52 -0
- package/dist/lib/ui.js +52 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# brain-colonies — `bc` CLI
|
|
2
|
+
|
|
3
|
+
Wisdom-layer counsel for the terminal. Wrap any AI CLI for per-turn drift-protection, or talk to BC brains directly.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g brain-colonies
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 1. Authenticate (paste your bc_live_ key)
|
|
15
|
+
bc auth
|
|
16
|
+
|
|
17
|
+
# 2. See your colonies
|
|
18
|
+
bc colonies
|
|
19
|
+
bc colonies --set-default <colony-id-or-name>
|
|
20
|
+
|
|
21
|
+
# 3. Check in before doing something
|
|
22
|
+
bc check "should i delete this migration?"
|
|
23
|
+
|
|
24
|
+
# 4. Talk to a specific brain
|
|
25
|
+
bc consult @security-engineer "is JWT decode-without-verify ever fine?"
|
|
26
|
+
|
|
27
|
+
# 5. Interactive chat with Alt-me
|
|
28
|
+
bc chat
|
|
29
|
+
|
|
30
|
+
# 6. THE KILLER FEATURE — wrap any AI CLI
|
|
31
|
+
bc wrap -- claude "should i refactor this auth code?"
|
|
32
|
+
bc wrap --inject -- aichat "review my plan to migrate to Postgres"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## What `bc wrap` does
|
|
36
|
+
|
|
37
|
+
For every turn, BC fires `bc.checkIn` against the user message **before** the wrapped CLI runs. The check-in returns:
|
|
38
|
+
|
|
39
|
+
- **drift signal** — high if you're about to repeat a known pattern
|
|
40
|
+
- **missing perspectives** — brains in your colony you haven't consulted that probably apply
|
|
41
|
+
- **pending reflections** — deferred patches waiting for your review
|
|
42
|
+
- **observations** — Alt-me's read on the message
|
|
43
|
+
|
|
44
|
+
Default mode prints the check-in to stderr (you see it, the wrapped CLI doesn't). `--inject` mode prepends the check-in block into the prompt — the wrapped LLM reads it inline.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
| Command | Description |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `bc auth` | Store API key in `~/.bc/config.json` |
|
|
51
|
+
| `bc check "<msg>"` | One-off `bc.checkIn` call |
|
|
52
|
+
| `bc consult @brain "<q>"` | Single-turn brain consult |
|
|
53
|
+
| `bc colonies` | List colonies + brains; `--set-default` to choose |
|
|
54
|
+
| `bc chat` | REPL against any brain (default: Alt-me) |
|
|
55
|
+
| `bc wrap -- <cli>` | Pre-turn check-in + forward to any CLI |
|
|
56
|
+
|
|
57
|
+
## Config
|
|
58
|
+
|
|
59
|
+
Lives at `~/.bc/config.json` (0600). Contains `apiKey`, `apiBase`, `defaultColonyId`.
|
|
60
|
+
|
|
61
|
+
## Development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install
|
|
65
|
+
npm run dev -- check "test"
|
|
66
|
+
npm run build
|
|
67
|
+
```
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.authCommand = authCommand;
|
|
4
|
+
const promises_1 = require("node:readline/promises");
|
|
5
|
+
const node_process_1 = require("node:process");
|
|
6
|
+
const config_js_1 = require("../lib/config.js");
|
|
7
|
+
const api_js_1 = require("../lib/api.js");
|
|
8
|
+
const ui_js_1 = require("../lib/ui.js");
|
|
9
|
+
async function authCommand(opts) {
|
|
10
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
11
|
+
try {
|
|
12
|
+
const existing = (0, config_js_1.loadConfig)();
|
|
13
|
+
if (existing)
|
|
14
|
+
(0, ui_js_1.info)(`existing config: ${(0, config_js_1.configPath)()}`);
|
|
15
|
+
const apiBase = opts.apiBase || existing?.apiBase || (0, config_js_1.defaultApiBase)();
|
|
16
|
+
(0, ui_js_1.info)(`API base: ${apiBase}`);
|
|
17
|
+
const apiKey = (await rl.question('Paste your bc_live_ API key: ')).trim();
|
|
18
|
+
if (!apiKey.startsWith('bc_live_')) {
|
|
19
|
+
(0, ui_js_1.err)('API keys must start with bc_live_. Generate one at https://braincolonies.com/settings/developers');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
let userId;
|
|
23
|
+
try {
|
|
24
|
+
const me = await (0, api_js_1.whoami)({ apiKey, apiBase, defaultColonyId: existing?.defaultColonyId });
|
|
25
|
+
userId = me.userId;
|
|
26
|
+
if (!userId) {
|
|
27
|
+
(0, ui_js_1.err)('Server returned no userId — please report this to support.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
(0, ui_js_1.err)(`Key validation failed: ${e.message}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const cfg = { apiKey, apiBase, userId, defaultColonyId: existing?.defaultColonyId };
|
|
36
|
+
(0, config_js_1.saveConfig)(cfg);
|
|
37
|
+
(0, ui_js_1.ok)(`Authenticated as ${userId}`);
|
|
38
|
+
(0, ui_js_1.ok)(`Config saved to ${(0, config_js_1.configPath)()}`);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
rl.close();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.chatCommand = chatCommand;
|
|
7
|
+
const promises_1 = require("node:readline/promises");
|
|
8
|
+
const node_process_1 = require("node:process");
|
|
9
|
+
const config_js_1 = require("../lib/config.js");
|
|
10
|
+
const api_js_1 = require("../lib/api.js");
|
|
11
|
+
const ui_js_1 = require("../lib/ui.js");
|
|
12
|
+
const kleur_1 = __importDefault(require("kleur"));
|
|
13
|
+
async function chatCommand(opts) {
|
|
14
|
+
const cfg = (0, config_js_1.requireConfig)();
|
|
15
|
+
const brain = opts.brain || 'alt-me';
|
|
16
|
+
process.stdout.write((0, ui_js_1.banner)() + '\n');
|
|
17
|
+
process.stdout.write(kleur_1.default.dim(`talking to @${brain}. type /quit to exit.\n\n`));
|
|
18
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
19
|
+
rl.on('SIGINT', () => { rl.close(); process.exit(0); });
|
|
20
|
+
try {
|
|
21
|
+
while (true) {
|
|
22
|
+
const userMsg = (await rl.question(kleur_1.default.bold().green('you: '))).trim();
|
|
23
|
+
if (!userMsg)
|
|
24
|
+
continue;
|
|
25
|
+
if (userMsg === '/quit' || userMsg === '/exit')
|
|
26
|
+
break;
|
|
27
|
+
if (!opts.noCheckIn) {
|
|
28
|
+
try {
|
|
29
|
+
const r = await (0, api_js_1.checkIn)(cfg, userMsg, opts.colony);
|
|
30
|
+
if (r.driftSignal !== 'none' || r.observations.length || r.missingPerspectives.length || r.mustReadAloud) {
|
|
31
|
+
process.stdout.write((0, ui_js_1.renderCheckIn)(r) + '\n');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
process.stderr.write(kleur_1.default.dim(`(check-in failed: ${e.message})\n`));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const r = await (0, api_js_1.consult)(cfg, brain, userMsg, opts.colony);
|
|
40
|
+
process.stdout.write(kleur_1.default.bold().cyan(`${r.brainName || brain}: `));
|
|
41
|
+
process.stdout.write(r.text + '\n\n');
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
(0, ui_js_1.err)(e.message);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
rl.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkCommand = checkCommand;
|
|
4
|
+
const config_js_1 = require("../lib/config.js");
|
|
5
|
+
const api_js_1 = require("../lib/api.js");
|
|
6
|
+
const ui_js_1 = require("../lib/ui.js");
|
|
7
|
+
async function checkCommand(message, opts) {
|
|
8
|
+
const cfg = (0, config_js_1.requireConfig)();
|
|
9
|
+
if (!message || !message.trim()) {
|
|
10
|
+
(0, ui_js_1.err)('Usage: bc check "<message>"');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const r = await (0, api_js_1.checkIn)(cfg, message, opts.colony);
|
|
15
|
+
if (opts.json) {
|
|
16
|
+
process.stdout.write(JSON.stringify(r, null, 2) + '\n');
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
process.stdout.write((0, ui_js_1.renderCheckIn)(r) + '\n');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
(0, ui_js_1.err)(e.message);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.coloniesCommand = coloniesCommand;
|
|
7
|
+
const config_js_1 = require("../lib/config.js");
|
|
8
|
+
const api_js_1 = require("../lib/api.js");
|
|
9
|
+
const ui_js_1 = require("../lib/ui.js");
|
|
10
|
+
const kleur_1 = __importDefault(require("kleur"));
|
|
11
|
+
async function coloniesCommand(opts) {
|
|
12
|
+
const cfg = (0, config_js_1.requireConfig)();
|
|
13
|
+
try {
|
|
14
|
+
const cols = await (0, api_js_1.listColonies)(cfg);
|
|
15
|
+
if (opts.setDefault) {
|
|
16
|
+
const match = cols.find((c) => c.id === opts.setDefault || c.name === opts.setDefault);
|
|
17
|
+
if (!match) {
|
|
18
|
+
(0, ui_js_1.err)(`No colony matching '${opts.setDefault}'`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
(0, config_js_1.saveConfig)({ ...cfg, defaultColonyId: match.id });
|
|
22
|
+
(0, ui_js_1.ok)(`Default colony set to ${match.name} (${match.id})`);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (opts.json) {
|
|
26
|
+
process.stdout.write(JSON.stringify(cols, null, 2) + '\n');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (cols.length === 0) {
|
|
30
|
+
process.stdout.write(kleur_1.default.dim('No colonies yet.\n'));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const c of cols) {
|
|
34
|
+
const tag = c.id === cfg.defaultColonyId ? kleur_1.default.green(' (default)') : '';
|
|
35
|
+
process.stdout.write(`${kleur_1.default.bold(c.name)} ${kleur_1.default.dim(c.id)}${tag}\n`);
|
|
36
|
+
for (const b of c.brains ?? []) {
|
|
37
|
+
process.stdout.write(` ${kleur_1.default.cyan('@' + b.id)} ${b.name}\n`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
(0, ui_js_1.err)(e.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.consultCommand = consultCommand;
|
|
7
|
+
const config_js_1 = require("../lib/config.js");
|
|
8
|
+
const api_js_1 = require("../lib/api.js");
|
|
9
|
+
const ui_js_1 = require("../lib/ui.js");
|
|
10
|
+
const kleur_1 = __importDefault(require("kleur"));
|
|
11
|
+
async function consultCommand(brainSpec, question, opts) {
|
|
12
|
+
const cfg = (0, config_js_1.requireConfig)();
|
|
13
|
+
if (!brainSpec || !question?.trim()) {
|
|
14
|
+
(0, ui_js_1.err)('Usage: bc consult @brainId "<question>"');
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const brainId = brainSpec.replace(/^@/, '');
|
|
18
|
+
(0, ui_js_1.info)(`→ @${brainId}`);
|
|
19
|
+
try {
|
|
20
|
+
const r = await (0, api_js_1.consult)(cfg, brainId, question, opts.colony);
|
|
21
|
+
if (opts.json) {
|
|
22
|
+
process.stdout.write(JSON.stringify(r, null, 2) + '\n');
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
process.stdout.write(kleur_1.default.bold().cyan(r.brainName || brainId) + ':\n');
|
|
26
|
+
process.stdout.write(r.text + '\n');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (e) {
|
|
30
|
+
(0, ui_js_1.err)(e.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrapCommand = wrapCommand;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const config_js_1 = require("../lib/config.js");
|
|
6
|
+
const api_js_1 = require("../lib/api.js");
|
|
7
|
+
const ui_js_1 = require("../lib/ui.js");
|
|
8
|
+
async function wrapCommand(rawArgs, opts) {
|
|
9
|
+
if (rawArgs.length === 0) {
|
|
10
|
+
(0, ui_js_1.err)('Usage: bc wrap [--inject] [--message "..."] -- <cli> [args...]');
|
|
11
|
+
(0, ui_js_1.err)('Example: bc wrap -- claude "should i refactor this auth code?"');
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const cfg = (0, config_js_1.requireConfig)();
|
|
15
|
+
const target = rawArgs[0];
|
|
16
|
+
const targetArgs = rawArgs.slice(1);
|
|
17
|
+
let userMessage = opts.message;
|
|
18
|
+
let messageArgIdx = -1;
|
|
19
|
+
if (!userMessage) {
|
|
20
|
+
for (let i = targetArgs.length - 1; i >= 0; i--) {
|
|
21
|
+
const a = targetArgs[i];
|
|
22
|
+
if (a && !a.startsWith('-')) {
|
|
23
|
+
userMessage = a;
|
|
24
|
+
messageArgIdx = i;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!userMessage) {
|
|
30
|
+
(0, ui_js_1.info)('bc wrap: no user message found in args — forwarding without check-in');
|
|
31
|
+
return forward(target, targetArgs);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const r = await (0, api_js_1.checkIn)(cfg, userMessage, opts.colony);
|
|
35
|
+
if (!opts.silent) {
|
|
36
|
+
process.stderr.write((0, ui_js_1.renderCheckIn)(r) + '\n');
|
|
37
|
+
}
|
|
38
|
+
if (opts.inject && messageArgIdx >= 0) {
|
|
39
|
+
const block = formatInjectBlock(r);
|
|
40
|
+
if (block) {
|
|
41
|
+
targetArgs[messageArgIdx] = `${block}\n\n${userMessage}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
process.stderr.write(`⚠ bc.checkIn failed: ${e.message} — forwarding anyway\n`);
|
|
47
|
+
}
|
|
48
|
+
return forward(target, targetArgs);
|
|
49
|
+
}
|
|
50
|
+
function formatInjectBlock(r) {
|
|
51
|
+
const parts = [];
|
|
52
|
+
parts.push('[BrainColonies check-in — read before responding]');
|
|
53
|
+
parts.push(`drift signal: ${r.driftSignal}`);
|
|
54
|
+
if (r.missingPerspectives.length)
|
|
55
|
+
parts.push(`missing perspectives: ${r.missingPerspectives.join(', ')}`);
|
|
56
|
+
for (const o of r.observations)
|
|
57
|
+
parts.push(`observation: ${o}`);
|
|
58
|
+
if (r.message)
|
|
59
|
+
parts.push(r.mustReadAloud ? `MUST READ ALOUD: ${r.message}` : r.message);
|
|
60
|
+
parts.push('[end check-in]');
|
|
61
|
+
return parts.length > 2 ? parts.join('\n') : '';
|
|
62
|
+
}
|
|
63
|
+
function forward(target, args) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const child = (0, node_child_process_1.spawn)(target, args, { stdio: 'inherit' });
|
|
66
|
+
child.on('error', (e) => {
|
|
67
|
+
if (e.code === 'ENOENT') {
|
|
68
|
+
(0, ui_js_1.err)(`Command not found: ${target}`);
|
|
69
|
+
process.exit(127);
|
|
70
|
+
}
|
|
71
|
+
(0, ui_js_1.err)(`Failed to spawn ${target}: ${e.message}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
});
|
|
74
|
+
child.on('exit', (code, signal) => {
|
|
75
|
+
if (signal) {
|
|
76
|
+
process.kill(process.pid, signal);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
process.exit(code ?? 0);
|
|
80
|
+
}
|
|
81
|
+
resolve();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const auth_js_1 = require("./commands/auth.js");
|
|
6
|
+
const check_js_1 = require("./commands/check.js");
|
|
7
|
+
const consult_js_1 = require("./commands/consult.js");
|
|
8
|
+
const colonies_js_1 = require("./commands/colonies.js");
|
|
9
|
+
const wrap_js_1 = require("./commands/wrap.js");
|
|
10
|
+
const chat_js_1 = require("./commands/chat.js");
|
|
11
|
+
const program = new commander_1.Command();
|
|
12
|
+
program
|
|
13
|
+
.name('bc')
|
|
14
|
+
.description('BrainColonies — wisdom-layer counsel for the terminal')
|
|
15
|
+
.version('0.1.1');
|
|
16
|
+
program
|
|
17
|
+
.command('auth')
|
|
18
|
+
.description('Authenticate with a bc_live_ API key (stored in ~/.bc/config.json)')
|
|
19
|
+
.option('--api-base <url>', 'override API base URL')
|
|
20
|
+
.action(auth_js_1.authCommand);
|
|
21
|
+
program
|
|
22
|
+
.command('check <message...>')
|
|
23
|
+
.description('Call bc.checkIn for a user message — prints drift signal, missing perspectives, observations')
|
|
24
|
+
.option('-c, --colony <id>', 'colony id (defaults to your default colony)')
|
|
25
|
+
.option('--json', 'emit raw JSON instead of pretty output')
|
|
26
|
+
.action((message, opts) => (0, check_js_1.checkCommand)(message.join(' '), opts));
|
|
27
|
+
program
|
|
28
|
+
.command('consult <brain> <question...>')
|
|
29
|
+
.description('Ask a specific brain a question (single turn)')
|
|
30
|
+
.option('-c, --colony <id>', 'colony id (defaults to your default colony)')
|
|
31
|
+
.option('--json', 'emit raw JSON')
|
|
32
|
+
.action((brain, question, opts) => (0, consult_js_1.consultCommand)(brain, question.join(' '), opts));
|
|
33
|
+
program
|
|
34
|
+
.command('colonies')
|
|
35
|
+
.description('List your colonies and the brains inside each')
|
|
36
|
+
.option('--set-default <idOrName>', 'set a colony as the CLI default')
|
|
37
|
+
.option('--json', 'emit raw JSON')
|
|
38
|
+
.action(colonies_js_1.coloniesCommand);
|
|
39
|
+
program
|
|
40
|
+
.command('wrap')
|
|
41
|
+
.description('Wrap another CLI: bc.checkIn fires first, then forwards. Use -- to separate args.')
|
|
42
|
+
.option('-m, --message <text>', 'explicit user message (otherwise: last positional arg of wrapped cmd)')
|
|
43
|
+
.option('-i, --inject', "inject the check-in block INTO the wrapped CLI's prompt (default: print only)")
|
|
44
|
+
.option('-c, --colony <id>', 'colony id (defaults to your default colony)')
|
|
45
|
+
.option('-s, --silent', 'do not print the check-in block to stderr')
|
|
46
|
+
.allowUnknownOption(true)
|
|
47
|
+
.helpOption('-h, --help', 'show help')
|
|
48
|
+
.action(function () {
|
|
49
|
+
const opts = this.opts();
|
|
50
|
+
const raw = this.args ?? [];
|
|
51
|
+
(0, wrap_js_1.wrapCommand)(raw, opts);
|
|
52
|
+
});
|
|
53
|
+
program
|
|
54
|
+
.command('chat')
|
|
55
|
+
.description('Interactive chat with a BC brain — fires bc.checkIn per turn')
|
|
56
|
+
.option('-b, --brain <id>', 'brain id (default: alt-me)')
|
|
57
|
+
.option('-c, --colony <id>', 'colony id (defaults to your default colony)')
|
|
58
|
+
.option('--no-check-in', 'skip the per-turn bc.checkIn call')
|
|
59
|
+
.action(chat_js_1.chatCommand);
|
|
60
|
+
program.parseAsync(process.argv).catch((e) => {
|
|
61
|
+
process.stderr.write(`✖ ${e.message}\n`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BcApiError = void 0;
|
|
4
|
+
exports.bcFetch = bcFetch;
|
|
5
|
+
exports.checkIn = checkIn;
|
|
6
|
+
exports.consult = consult;
|
|
7
|
+
exports.listColonies = listColonies;
|
|
8
|
+
exports.whoami = whoami;
|
|
9
|
+
class BcApiError extends Error {
|
|
10
|
+
status;
|
|
11
|
+
body;
|
|
12
|
+
constructor(status, body, message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.status = status;
|
|
15
|
+
this.body = body;
|
|
16
|
+
this.name = 'BcApiError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.BcApiError = BcApiError;
|
|
20
|
+
async function bcFetch(cfg, path, opts = {}) {
|
|
21
|
+
const url = `${cfg.apiBase.replace(/\/$/, '')}${path}`;
|
|
22
|
+
const res = await fetch(url, {
|
|
23
|
+
method: opts.method ?? (opts.body ? 'POST' : 'GET'),
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${cfg.apiKey}`,
|
|
26
|
+
'Content-Type': 'application/json',
|
|
27
|
+
},
|
|
28
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined,
|
|
29
|
+
signal: opts.signal,
|
|
30
|
+
});
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
let parsed = text;
|
|
33
|
+
try {
|
|
34
|
+
parsed = text ? JSON.parse(text) : null;
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const msg = (parsed && typeof parsed === 'object' && 'error' in parsed)
|
|
39
|
+
? String(parsed.error)
|
|
40
|
+
: `HTTP ${res.status}`;
|
|
41
|
+
throw new BcApiError(res.status, parsed, msg);
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
async function checkIn(cfg, userMessage, colonyId) {
|
|
46
|
+
return bcFetch(cfg, '/mcp', {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
body: {
|
|
49
|
+
jsonrpc: '2.0',
|
|
50
|
+
id: 1,
|
|
51
|
+
method: 'tools/call',
|
|
52
|
+
params: {
|
|
53
|
+
name: 'bc.checkIn',
|
|
54
|
+
arguments: { userMessage, colonyId: colonyId ?? cfg.defaultColonyId },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}).then((raw) => {
|
|
58
|
+
const r = raw;
|
|
59
|
+
const first = r?.result?.content?.[0];
|
|
60
|
+
if (first?.type === 'text' && first.text) {
|
|
61
|
+
try {
|
|
62
|
+
return JSON.parse(first.text);
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
driftSignal: 'none',
|
|
68
|
+
pendingReflections: 0,
|
|
69
|
+
missingPerspectives: [],
|
|
70
|
+
observations: [],
|
|
71
|
+
mustReadAloud: false,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
async function consult(cfg, brainId, question, colonyId) {
|
|
76
|
+
const resolvedColony = colonyId ?? cfg.defaultColonyId;
|
|
77
|
+
if (!resolvedColony) {
|
|
78
|
+
throw new Error('No colony selected. Run `bc colonies` and set one as default, or pass --colony.');
|
|
79
|
+
}
|
|
80
|
+
return bcFetch(cfg, `/api/colony/${resolvedColony}/interact`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
body: {
|
|
83
|
+
brainId,
|
|
84
|
+
mode: 'chat',
|
|
85
|
+
messages: [{ role: 'user', content: question }],
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async function listColonies(cfg) {
|
|
90
|
+
const raw = await bcFetch(cfg, `/colonies?userId=${encodeURIComponent(cfg.userId)}`);
|
|
91
|
+
return Array.isArray(raw) ? raw : (raw?.colonies ?? []);
|
|
92
|
+
}
|
|
93
|
+
async function whoami(cfg) {
|
|
94
|
+
return bcFetch(cfg, '/api/me');
|
|
95
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadConfig = loadConfig;
|
|
4
|
+
exports.saveConfig = saveConfig;
|
|
5
|
+
exports.configPath = configPath;
|
|
6
|
+
exports.defaultApiBase = defaultApiBase;
|
|
7
|
+
exports.requireConfig = requireConfig;
|
|
8
|
+
const node_os_1 = require("node:os");
|
|
9
|
+
const node_path_1 = require("node:path");
|
|
10
|
+
const node_fs_1 = require("node:fs");
|
|
11
|
+
const CONFIG_DIR = (0, node_path_1.join)((0, node_os_1.homedir)(), '.bc');
|
|
12
|
+
const CONFIG_PATH = (0, node_path_1.join)(CONFIG_DIR, 'config.json');
|
|
13
|
+
const DEFAULT_API_BASE = 'https://ma3yuktnyh.execute-api.us-east-1.amazonaws.com';
|
|
14
|
+
function loadConfig() {
|
|
15
|
+
if (!(0, node_fs_1.existsSync)(CONFIG_PATH))
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
const raw = (0, node_fs_1.readFileSync)(CONFIG_PATH, 'utf8');
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (!parsed.apiKey || !parsed.userId)
|
|
21
|
+
return null;
|
|
22
|
+
return {
|
|
23
|
+
apiKey: parsed.apiKey,
|
|
24
|
+
apiBase: parsed.apiBase || DEFAULT_API_BASE,
|
|
25
|
+
userId: parsed.userId,
|
|
26
|
+
defaultColonyId: parsed.defaultColonyId,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function saveConfig(cfg) {
|
|
34
|
+
if (!(0, node_fs_1.existsSync)(CONFIG_DIR)) {
|
|
35
|
+
(0, node_fs_1.mkdirSync)(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
36
|
+
}
|
|
37
|
+
(0, node_fs_1.writeFileSync)(CONFIG_PATH, JSON.stringify(cfg, null, 2), { mode: 0o600 });
|
|
38
|
+
(0, node_fs_1.chmodSync)(CONFIG_PATH, 0o600);
|
|
39
|
+
}
|
|
40
|
+
function configPath() {
|
|
41
|
+
return CONFIG_PATH;
|
|
42
|
+
}
|
|
43
|
+
function defaultApiBase() {
|
|
44
|
+
return DEFAULT_API_BASE;
|
|
45
|
+
}
|
|
46
|
+
function requireConfig() {
|
|
47
|
+
const cfg = loadConfig();
|
|
48
|
+
if (!cfg) {
|
|
49
|
+
throw new Error('Not authenticated. Run `bc auth` first.');
|
|
50
|
+
}
|
|
51
|
+
return cfg;
|
|
52
|
+
}
|
package/dist/lib/ui.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.banner = banner;
|
|
7
|
+
exports.err = err;
|
|
8
|
+
exports.ok = ok;
|
|
9
|
+
exports.info = info;
|
|
10
|
+
exports.renderCheckIn = renderCheckIn;
|
|
11
|
+
const kleur_1 = __importDefault(require("kleur"));
|
|
12
|
+
function banner() {
|
|
13
|
+
return kleur_1.default.bold().cyan('🧠 BrainColonies');
|
|
14
|
+
}
|
|
15
|
+
function err(msg) {
|
|
16
|
+
process.stderr.write(kleur_1.default.red('✖ ') + msg + '\n');
|
|
17
|
+
}
|
|
18
|
+
function ok(msg) {
|
|
19
|
+
process.stdout.write(kleur_1.default.green('✓ ') + msg + '\n');
|
|
20
|
+
}
|
|
21
|
+
function info(msg) {
|
|
22
|
+
process.stdout.write(kleur_1.default.dim(msg) + '\n');
|
|
23
|
+
}
|
|
24
|
+
const DRIFT_COLORS = {
|
|
25
|
+
none: kleur_1.default.dim,
|
|
26
|
+
low: kleur_1.default.cyan,
|
|
27
|
+
medium: kleur_1.default.yellow,
|
|
28
|
+
high: kleur_1.default.red,
|
|
29
|
+
};
|
|
30
|
+
function renderCheckIn(r) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
lines.push(kleur_1.default.bold().cyan('━━━ bc.checkIn ━━━'));
|
|
33
|
+
const colorFn = DRIFT_COLORS[r.driftSignal] ?? kleur_1.default.dim;
|
|
34
|
+
lines.push(`drift: ${colorFn(r.driftSignal.toUpperCase())}`);
|
|
35
|
+
if (r.pendingReflections > 0) {
|
|
36
|
+
lines.push(`pending reflections: ${kleur_1.default.yellow(String(r.pendingReflections))}`);
|
|
37
|
+
}
|
|
38
|
+
if (r.missingPerspectives.length > 0) {
|
|
39
|
+
lines.push(`missing perspectives: ${kleur_1.default.magenta(r.missingPerspectives.join(', '))}`);
|
|
40
|
+
}
|
|
41
|
+
if (r.observations.length > 0) {
|
|
42
|
+
lines.push(kleur_1.default.bold('observations:'));
|
|
43
|
+
for (const o of r.observations)
|
|
44
|
+
lines.push(` • ${o}`);
|
|
45
|
+
}
|
|
46
|
+
if (r.message) {
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push(r.mustReadAloud ? kleur_1.default.bold().yellow(r.message) : r.message);
|
|
49
|
+
}
|
|
50
|
+
lines.push(kleur_1.default.dim('━━━━━━━━━━━━━━━━━━'));
|
|
51
|
+
return lines.join('\n');
|
|
52
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "brain-colonies",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "BrainColonies CLI — wisdom-layer counsel for the terminal. Wrap any AI CLI for per-turn drift-protection.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"bc": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"prepublishOnly": "npm run build"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=18"
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist", "README.md"],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ai",
|
|
21
|
+
"cli",
|
|
22
|
+
"brain-colonies",
|
|
23
|
+
"wisdom-layer",
|
|
24
|
+
"drift-protection",
|
|
25
|
+
"ontology",
|
|
26
|
+
"mcp"
|
|
27
|
+
],
|
|
28
|
+
"homepage": "https://braincolonies.com",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/ganbarone/brain-colony-collective.git",
|
|
32
|
+
"directory": "cli"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^12.1.0",
|
|
36
|
+
"kleur": "^4.1.5"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"typescript": "^5.4.0",
|
|
40
|
+
"@types/node": "^20.11.0",
|
|
41
|
+
"tsx": "^4.7.0"
|
|
42
|
+
}
|
|
43
|
+
}
|