@p-sw/brainbox 0.1.0 → 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/dist/index.js +5312 -0
- package/package.json +7 -4
- package/scripts/smoke_providers.ts +0 -176
- package/src/brain/index.ts +0 -936
- package/src/brain/manager.ts +0 -144
- package/src/brain/memory.ts +0 -99
- package/src/brain/messageHistory.ts +0 -26
- package/src/brain/schedule.ts +0 -11
- package/src/brain/types.ts +0 -26
- package/src/channel/base.ts +0 -527
- package/src/channel/discord.ts +0 -227
- package/src/channel/telegram.ts +0 -150
- package/src/commands/auth.tsx +0 -306
- package/src/commands/brain.ts +0 -119
- package/src/commands/daemon/commands.ts +0 -56
- package/src/commands/daemon/pairingCommand.ts +0 -15
- package/src/commands/daemon/restartCommand.ts +0 -17
- package/src/commands/daemon.ts +0 -168
- package/src/commands/index.ts +0 -16
- package/src/commands/model.tsx +0 -138
- package/src/commands/onboard.tsx +0 -473
- package/src/commands/pairing.ts +0 -32
- package/src/commands/restart.ts +0 -24
- package/src/config/file/auth.ts +0 -105
- package/src/config/file/root.ts +0 -37
- package/src/config/index.ts +0 -21
- package/src/config/loader.ts +0 -115
- package/src/index.ts +0 -61
- package/src/provider/index.ts +0 -122
- package/src/provider/llm.ts +0 -191
- package/src/provider/promptLoader.ts +0 -36
- package/src/provider/providers/302ai.ts +0 -17
- package/src/provider/providers/MiniMax.ts +0 -17
- package/src/provider/providers/anthropic.ts +0 -257
- package/src/provider/providers/azure_cognitive.ts +0 -40
- package/src/provider/providers/azure_openai.ts +0 -49
- package/src/provider/providers/baseten.ts +0 -17
- package/src/provider/providers/bedrock.ts +0 -312
- package/src/provider/providers/cerebras.ts +0 -17
- package/src/provider/providers/cloudflare_gateway.ts +0 -34
- package/src/provider/providers/cloudflare_workers.ts +0 -178
- package/src/provider/providers/copilot.ts +0 -22
- package/src/provider/providers/cortecs.ts +0 -17
- package/src/provider/providers/deepinfra.ts +0 -17
- package/src/provider/providers/deepseek.ts +0 -17
- package/src/provider/providers/digitalocean.ts +0 -17
- package/src/provider/providers/fireworks.ts +0 -17
- package/src/provider/providers/gitlab_duo.ts +0 -184
- package/src/provider/providers/gmi.ts +0 -17
- package/src/provider/providers/groq.ts +0 -17
- package/src/provider/providers/helicone.ts +0 -17
- package/src/provider/providers/huggingface.ts +0 -17
- package/src/provider/providers/ionet.ts +0 -17
- package/src/provider/providers/llamacpp.ts +0 -17
- package/src/provider/providers/llmgateway.ts +0 -17
- package/src/provider/providers/lmstudio.ts +0 -17
- package/src/provider/providers/mistral.ts +0 -17
- package/src/provider/providers/moonshot.ts +0 -17
- package/src/provider/providers/nebius.ts +0 -17
- package/src/provider/providers/nvidia.ts +0 -17
- package/src/provider/providers/ollama.ts +0 -17
- package/src/provider/providers/ollama_cloud.ts +0 -17
- package/src/provider/providers/openai.ts +0 -17
- package/src/provider/providers/openai_compatible.ts +0 -293
- package/src/provider/providers/openrouter.ts +0 -175
- package/src/provider/providers/ovhcloud.ts +0 -17
- package/src/provider/providers/sap_aicore.ts +0 -22
- package/src/provider/providers/scaleway.ts +0 -17
- package/src/provider/providers/snowflake_cortex.ts +0 -207
- package/src/provider/providers/stackit.ts +0 -17
- package/src/provider/providers/together.ts +0 -17
- package/src/provider/providers/venice.ts +0 -17
- package/src/provider/providers/vercel.ts +0 -17
- package/src/provider/providers/vertex.ts +0 -229
- package/src/provider/providers/xai.ts +0 -17
- package/src/provider/providers/zai.ts +0 -17
- package/src/provider/providers/zenmux.ts +0 -17
- package/src/provider/schema.ts +0 -143
- package/src/ui/TextInput.tsx +0 -114
- package/src/utils/daemonClient.ts +0 -85
- package/src/utils/logger.ts +0 -204
- package/tsconfig.json +0 -34
- /package/{prompts → dist/prompts}/daily_schedule.md +0 -0
- /package/{prompts → dist/prompts}/memoir.md +0 -0
- /package/{prompts → dist/prompts}/monthly_schedule.md +0 -0
- /package/{prompts → dist/prompts}/objectifier.md +0 -0
- /package/{prompts → dist/prompts}/persona_base_system_prompt.md +0 -0
- /package/{prompts → dist/prompts}/persona_base_system_prompt_fixed.md +0 -0
- /package/{prompts → dist/prompts}/persona_init.md +0 -0
- /package/{prompts → dist/prompts}/schedule_availability.md +0 -0
- /package/{prompts → dist/prompts}/send_message.md +0 -0
- /package/{prompts → dist/prompts}/start_conversation.md +0 -0
package/src/commands/brain.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import { registerCommand } from "@/commands";
|
|
4
|
-
import { brainManager } from "@/brain/manager";
|
|
5
|
-
import { Brain } from "@/brain";
|
|
6
|
-
import { logger } from "@/utils/logger";
|
|
7
|
-
|
|
8
|
-
export async function listBrains(): Promise<void> {
|
|
9
|
-
const brains = await brainManager.listBrains();
|
|
10
|
-
logger.debug(`listBrains: ${brains.length} brain(s)`);
|
|
11
|
-
if (brains.length === 0) {
|
|
12
|
-
logger.info("No brains found.");
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
for (const b of brains) {
|
|
16
|
-
const status = b.activated ? chalk.green("active") : chalk.gray("inactive");
|
|
17
|
-
const channel = b.channel ? chalk.cyan(b.channel) : chalk.gray("-");
|
|
18
|
-
const name = chalk.bold(b.displayName);
|
|
19
|
-
console.log(`${b.brainId} ${name} ${status} ${channel}`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function setActivated(
|
|
24
|
-
brainId: string,
|
|
25
|
-
activated: boolean,
|
|
26
|
-
): Promise<void> {
|
|
27
|
-
logger.debug(`setActivated: id=${brainId} → ${activated}`);
|
|
28
|
-
const brain = await brainManager.loadBrain(brainId);
|
|
29
|
-
if (!brain) {
|
|
30
|
-
logger.error(`Brain not found: ${brainId}`);
|
|
31
|
-
process.exitCode = 1;
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (brain.activated === activated) {
|
|
35
|
-
const verb = activated ? "activated" : "deactivated";
|
|
36
|
-
logger.info(`Brain "${brain.displayName}" is already ${verb}.`);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
await brainManager.saveBrain(brainId, { ...brain, activated });
|
|
40
|
-
const verb = activated ? "Activated" : "Deactivated";
|
|
41
|
-
logger.success(`${verb} brain "${brain.displayName}" (${brain.brainId})`);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export async function activateBrain(brainId: string): Promise<void> {
|
|
45
|
-
await setActivated(brainId, true);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function deactivateBrain(brainId: string): Promise<void> {
|
|
49
|
-
await setActivated(brainId, false);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function createBrain(
|
|
53
|
-
displayName: string,
|
|
54
|
-
seed: string,
|
|
55
|
-
options: { schedule: boolean },
|
|
56
|
-
): Promise<void> {
|
|
57
|
-
logger.debug(
|
|
58
|
-
`createBrain: name="${displayName}" seed length=${seed.length} schedule=${options.schedule}`,
|
|
59
|
-
);
|
|
60
|
-
const result = await Brain.create(displayName, seed);
|
|
61
|
-
if (!result) {
|
|
62
|
-
process.exitCode = 1;
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (options.schedule) {
|
|
66
|
-
await result.brain.regenerateSchedules();
|
|
67
|
-
logger.success(
|
|
68
|
-
`Created brain "${displayName}" (${chalk.cyan(result.brainId)}) with initial schedule`,
|
|
69
|
-
);
|
|
70
|
-
} else {
|
|
71
|
-
logger.success(
|
|
72
|
-
`Created brain "${displayName}" (${chalk.cyan(result.brainId)})`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export async function removeBrain(brainId: string): Promise<void> {
|
|
78
|
-
logger.debug(`removeBrain: id=${brainId}`);
|
|
79
|
-
const brain = await brainManager.loadBrain(brainId);
|
|
80
|
-
if (!brain) {
|
|
81
|
-
logger.error(`Brain not found: ${brainId}`);
|
|
82
|
-
process.exitCode = 1;
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const removed = await Brain.delete(brainId);
|
|
86
|
-
if (!removed) {
|
|
87
|
-
process.exitCode = 1;
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
logger.success(
|
|
91
|
-
`Removed brain "${brain.displayName}" (${chalk.cyan(brainId)})`,
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function register(program: Command): Command {
|
|
96
|
-
const cmd = registerCommand(program, {
|
|
97
|
-
name: "brain",
|
|
98
|
-
description: "Manage brains",
|
|
99
|
-
});
|
|
100
|
-
cmd.command("list").description("List all brains").action(listBrains);
|
|
101
|
-
cmd
|
|
102
|
-
.command("create <name> [seed]")
|
|
103
|
-
.description("Create a new brain from a free-form seed")
|
|
104
|
-
.option("--no-schedule", "Skip generating the initial schedule")
|
|
105
|
-
.action(createBrain);
|
|
106
|
-
cmd
|
|
107
|
-
.command("remove <brainId>")
|
|
108
|
-
.description("Remove a brain and its memory")
|
|
109
|
-
.action(removeBrain);
|
|
110
|
-
cmd
|
|
111
|
-
.command("activate <brainId>")
|
|
112
|
-
.description("Activate a brain")
|
|
113
|
-
.action(activateBrain);
|
|
114
|
-
cmd
|
|
115
|
-
.command("deactivate <brainId>")
|
|
116
|
-
.description("Deactivate a brain")
|
|
117
|
-
.action(deactivateBrain);
|
|
118
|
-
return cmd;
|
|
119
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
// ponytail: registry for remote commands received over the daemon's unix socket.
|
|
2
|
-
// Wire a command by calling defineCommand() from the command module's top level.
|
|
3
|
-
|
|
4
|
-
import { logger } from "@/utils/logger";
|
|
5
|
-
|
|
6
|
-
const log = logger.child("daemon-cmd");
|
|
7
|
-
|
|
8
|
-
export interface CommandResult<T = unknown> {
|
|
9
|
-
ok: boolean;
|
|
10
|
-
result?: T;
|
|
11
|
-
error?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export type CommandHandler<TArgs = unknown> = (
|
|
15
|
-
args: TArgs,
|
|
16
|
-
) => Promise<CommandResult> | CommandResult;
|
|
17
|
-
|
|
18
|
-
interface CommandEntry<TArgs = unknown> {
|
|
19
|
-
name: string;
|
|
20
|
-
handler: CommandHandler<TArgs>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const registry = new Map<string, CommandEntry<unknown>>();
|
|
24
|
-
|
|
25
|
-
export function defineCommand<TArgs>(config: {
|
|
26
|
-
name: string;
|
|
27
|
-
handler: CommandHandler<TArgs>;
|
|
28
|
-
}): void {
|
|
29
|
-
log.debug(`defineCommand: registered "${config.name}"`);
|
|
30
|
-
registry.set(config.name, config as CommandEntry<unknown>);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export async function dispatch(payload: unknown): Promise<CommandResult> {
|
|
34
|
-
if (typeof payload !== "object" || payload === null) {
|
|
35
|
-
log.debug(`dispatch: payload not an object`);
|
|
36
|
-
return { ok: false, error: "expected object" };
|
|
37
|
-
}
|
|
38
|
-
const { command, args } = payload as { command?: unknown; args?: unknown };
|
|
39
|
-
if (typeof command !== "string" || command.length === 0) {
|
|
40
|
-
log.debug(`dispatch: missing/empty command field`);
|
|
41
|
-
return { ok: false, error: "missing command" };
|
|
42
|
-
}
|
|
43
|
-
const entry = registry.get(command);
|
|
44
|
-
if (!entry) {
|
|
45
|
-
log.debug(`dispatch: unknown command "${command}"`);
|
|
46
|
-
return { ok: false, error: `unknown command: ${command}` };
|
|
47
|
-
}
|
|
48
|
-
log.debug(`dispatch: "${command}" → handler (args type=${typeof args})`);
|
|
49
|
-
try {
|
|
50
|
-
return await entry.handler(args);
|
|
51
|
-
} catch (error) {
|
|
52
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
53
|
-
log.debug(`dispatch: "${command}" threw: ${reason}`);
|
|
54
|
-
return { ok: false, error: reason };
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "@/commands/daemon/commands";
|
|
2
|
-
import { BaseChannel } from "@/channel/base";
|
|
3
|
-
import { logger } from "@/utils/logger";
|
|
4
|
-
|
|
5
|
-
defineCommand<{ code: string }>({
|
|
6
|
-
name: "pairing",
|
|
7
|
-
handler: async ({ code }) => {
|
|
8
|
-
logger.debug(`pairing handler: code="${code}"`);
|
|
9
|
-
if (typeof code !== "string" || code.trim().length === 0) {
|
|
10
|
-
logger.debug(`pairing handler: missing code argument`);
|
|
11
|
-
return { ok: false, error: "missing pairing code" };
|
|
12
|
-
}
|
|
13
|
-
return await BaseChannel.completePairingByCode(code);
|
|
14
|
-
},
|
|
15
|
-
});
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { defineCommand } from "@/commands/daemon/commands";
|
|
2
|
-
import { BaseChannel } from "@/channel/base";
|
|
3
|
-
import { startChannels } from "@/commands/daemon";
|
|
4
|
-
import { logger } from "@/utils/logger";
|
|
5
|
-
|
|
6
|
-
defineCommand({
|
|
7
|
-
name: "restart",
|
|
8
|
-
handler: async () => {
|
|
9
|
-
const before = BaseChannel.all().length;
|
|
10
|
-
logger.info(`Restart: shutting down ${before} active channel(s)`);
|
|
11
|
-
logger.debug(`restart handler: tearing down ${before} active channel(s)`);
|
|
12
|
-
await BaseChannel.shutdownAll();
|
|
13
|
-
const started = await startChannels();
|
|
14
|
-
logger.success(`Restart: ${started} channel(s) back up`);
|
|
15
|
-
return { ok: true, result: { restarted: started } };
|
|
16
|
-
},
|
|
17
|
-
});
|
package/src/commands/daemon.ts
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
import { registerCommand } from "@/commands";
|
|
3
|
-
import {
|
|
4
|
-
brainManager,
|
|
5
|
-
type BrainItemDiscord,
|
|
6
|
-
type BrainItemTelegram,
|
|
7
|
-
} from "@/brain/manager";
|
|
8
|
-
import { Brain } from "@/brain";
|
|
9
|
-
import { DiscordChannel } from "@/channel/discord";
|
|
10
|
-
import { TelegramChannel } from "@/channel/telegram";
|
|
11
|
-
import { logger } from "@/utils/logger";
|
|
12
|
-
import { DAEMON_SOCKET_PATH } from "@/utils/daemonClient";
|
|
13
|
-
import { createServer, type Socket } from "node:net";
|
|
14
|
-
import { chmodSync, unlinkSync } from "node:fs";
|
|
15
|
-
import { dispatch } from "./daemon/commands";
|
|
16
|
-
|
|
17
|
-
import "./daemon/pairingCommand";
|
|
18
|
-
import "./daemon/restartCommand";
|
|
19
|
-
|
|
20
|
-
export async function startChannels(): Promise<number> {
|
|
21
|
-
const items = await brainManager.listAvailableBrain();
|
|
22
|
-
logger.debug(`startChannels: ${items.length} candidate(s)`);
|
|
23
|
-
let started = 0;
|
|
24
|
-
for (const item of items) {
|
|
25
|
-
const brain = await Brain.load(item.brainId);
|
|
26
|
-
if (!brain) {
|
|
27
|
-
logger.debug(
|
|
28
|
-
`startChannels: skip ${item.brainId} (Brain.load returned null)`,
|
|
29
|
-
);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
if (item.channel === "discord") {
|
|
34
|
-
const channel = new DiscordChannel(brain as Brain<BrainItemDiscord>);
|
|
35
|
-
await channel.init();
|
|
36
|
-
logger.success(
|
|
37
|
-
`Discord channel started: ${brain.brainbase.displayName}`,
|
|
38
|
-
);
|
|
39
|
-
started += 1;
|
|
40
|
-
} else if (item.channel === "telegram") {
|
|
41
|
-
const channel = new TelegramChannel(brain as Brain<BrainItemTelegram>);
|
|
42
|
-
await channel.init();
|
|
43
|
-
logger.success(
|
|
44
|
-
`Telegram channel started: ${brain.brainbase.displayName}`,
|
|
45
|
-
);
|
|
46
|
-
started += 1;
|
|
47
|
-
} else {
|
|
48
|
-
logger.debug(
|
|
49
|
-
`startChannels: unknown channel type "${(item as { channel: string }).channel}" for ${(item as { brainId: string }).brainId}`,
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
} catch (error) {
|
|
53
|
-
const reason = error instanceof Error ? error.message : String(error);
|
|
54
|
-
logger.error(
|
|
55
|
-
`Failed to start channel for "${brain.brainbase.displayName}" (${item.channel}): ${reason}`,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
logger.debug(`startChannels: ${started}/${items.length} started`);
|
|
60
|
-
return started;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export async function daemon(): Promise<void> {
|
|
64
|
-
logger.debug(`daemon: boot`);
|
|
65
|
-
const started = await startChannels();
|
|
66
|
-
if (started === 0) {
|
|
67
|
-
logger.info("No activated brains with channels. Daemon idling.");
|
|
68
|
-
}
|
|
69
|
-
await listenOnSocket();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const SOCKET_PATH = DAEMON_SOCKET_PATH;
|
|
73
|
-
|
|
74
|
-
async function listenOnSocket(): Promise<void> {
|
|
75
|
-
logger.debug(`listenOnSocket: unlinking stale socket at ${SOCKET_PATH}`);
|
|
76
|
-
try {
|
|
77
|
-
unlinkSync(SOCKET_PATH);
|
|
78
|
-
} catch {
|
|
79
|
-
// ponytail: stale socket from a prior crash — best-effort unlink
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const sockets = new Set<Socket>();
|
|
83
|
-
const server = createServer((conn) => {
|
|
84
|
-
logger.debug(`listenOnSocket: new connection`);
|
|
85
|
-
sockets.add(conn);
|
|
86
|
-
conn.on("close", () => {
|
|
87
|
-
logger.debug(`listenOnSocket: connection closed`);
|
|
88
|
-
sockets.delete(conn);
|
|
89
|
-
});
|
|
90
|
-
handleConnection(conn);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
await new Promise<void>((resolve, reject) => {
|
|
94
|
-
const onError = (err: Error) => {
|
|
95
|
-
server.off("listening", onListening);
|
|
96
|
-
reject(err);
|
|
97
|
-
};
|
|
98
|
-
const onListening = () => {
|
|
99
|
-
server.off("error", onError);
|
|
100
|
-
chmodSync(SOCKET_PATH, 0o600);
|
|
101
|
-
logger.success(`Daemon listening on unix://${SOCKET_PATH}`);
|
|
102
|
-
resolve();
|
|
103
|
-
};
|
|
104
|
-
server.once("error", onError);
|
|
105
|
-
server.once("listening", onListening);
|
|
106
|
-
server.listen(SOCKET_PATH);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
await new Promise<void>((resolve) => {
|
|
110
|
-
const shutdown = () => {
|
|
111
|
-
logger.debug(`listenOnSocket: shutdown signal received`);
|
|
112
|
-
for (const s of sockets) s.destroy();
|
|
113
|
-
server.close(() => {
|
|
114
|
-
try {
|
|
115
|
-
unlinkSync(SOCKET_PATH);
|
|
116
|
-
} catch {
|
|
117
|
-
// ignore
|
|
118
|
-
}
|
|
119
|
-
resolve();
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
process.once("SIGINT", shutdown);
|
|
123
|
-
process.once("SIGTERM", shutdown);
|
|
124
|
-
});
|
|
125
|
-
logger.info("Daemon shutting down.");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function handleConnection(conn: Socket): void {
|
|
129
|
-
let buf = "";
|
|
130
|
-
conn.setEncoding("utf8");
|
|
131
|
-
conn.on("data", (chunk) => {
|
|
132
|
-
buf += chunk;
|
|
133
|
-
let idx: number;
|
|
134
|
-
while ((idx = buf.indexOf("\n")) >= 0) {
|
|
135
|
-
const line = buf.slice(0, idx).trim();
|
|
136
|
-
buf = buf.slice(idx + 1);
|
|
137
|
-
if (line.length === 0) continue;
|
|
138
|
-
logger.debug(`handleConnection: received line (${line.length} chars)`);
|
|
139
|
-
void handleLine(conn, line);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
conn.on("error", (err) => {
|
|
143
|
-
logger.debug(`Socket connection error: ${err.message}`);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function handleLine(conn: Socket, line: string): Promise<void> {
|
|
148
|
-
let payload: unknown;
|
|
149
|
-
try {
|
|
150
|
-
payload = JSON.parse(line);
|
|
151
|
-
} catch {
|
|
152
|
-
logger.debug(`handleLine: invalid json, replying with error`);
|
|
153
|
-
conn.write(JSON.stringify({ ok: false, error: "invalid json" }) + "\n");
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const cmd = (payload as { command?: string }).command;
|
|
157
|
-
logger.debug(`handleLine: dispatching command="${cmd ?? "?"}"`);
|
|
158
|
-
const response = await dispatch(payload);
|
|
159
|
-
conn.write(JSON.stringify(response) + "\n");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function register(program: Command): Command {
|
|
163
|
-
return registerCommand(program, {
|
|
164
|
-
name: "daemon",
|
|
165
|
-
description:
|
|
166
|
-
"Run the BrainBox daemon (activated channels + remote commands)",
|
|
167
|
-
}).action(daemon);
|
|
168
|
-
}
|
package/src/commands/index.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { Command } from "commander";
|
|
2
|
-
|
|
3
|
-
export interface CommandConfig {
|
|
4
|
-
name: string;
|
|
5
|
-
description: string;
|
|
6
|
-
configure?: (cmd: Command) => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function registerCommand(
|
|
10
|
-
program: Command,
|
|
11
|
-
config: CommandConfig,
|
|
12
|
-
): Command {
|
|
13
|
-
const cmd = program.command(config.name).description(config.description);
|
|
14
|
-
config.configure?.(cmd);
|
|
15
|
-
return cmd;
|
|
16
|
-
}
|
package/src/commands/model.tsx
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Box, Text, render } from "ink";
|
|
3
|
-
import type { Command } from "commander";
|
|
4
|
-
import { logger } from "@/utils/logger";
|
|
5
|
-
import { TextInput } from "@/ui/TextInput";
|
|
6
|
-
import { listProviderNames } from "@/provider/llm";
|
|
7
|
-
import { readRootFile, setModelSlot, type ModelSlot } from "@/config/file/root";
|
|
8
|
-
import { registerCommand } from "@/commands";
|
|
9
|
-
|
|
10
|
-
type Slot = ModelSlot;
|
|
11
|
-
|
|
12
|
-
function ModelApp({
|
|
13
|
-
slot,
|
|
14
|
-
onDone,
|
|
15
|
-
}: {
|
|
16
|
-
slot: Slot;
|
|
17
|
-
onDone: () => void;
|
|
18
|
-
}): React.ReactElement {
|
|
19
|
-
const root = readRootFile();
|
|
20
|
-
const current = slot === "identity" ? root.identityModel : root.conversationModel;
|
|
21
|
-
const [error, setError] = useState<string | null>(null);
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<Box flexDirection="column">
|
|
25
|
-
<Text>
|
|
26
|
-
Setting <Text color="cyan">{slot}</Text> model (current:{" "}
|
|
27
|
-
<Text dimColor>{current || "(unset)"}</Text>)
|
|
28
|
-
</Text>
|
|
29
|
-
<Text dimColor>Enter as provider/model (e.g. openai/gpt-4o)</Text>
|
|
30
|
-
<TextInput
|
|
31
|
-
prompt={`${slot}> `}
|
|
32
|
-
initialValue={current}
|
|
33
|
-
onSubmit={(raw) => {
|
|
34
|
-
const value = raw.trim();
|
|
35
|
-
if (!value) {
|
|
36
|
-
setError("Model cannot be empty");
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const slash = value.indexOf("/");
|
|
40
|
-
if (slash < 0) {
|
|
41
|
-
setError('Must be in "provider/model" form');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const provider = value.slice(0, slash);
|
|
45
|
-
const known = listProviderNames();
|
|
46
|
-
if (!known.includes(provider)) {
|
|
47
|
-
setError(`Unknown provider "${provider}"`);
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
setModelSlot(slot, value);
|
|
51
|
-
logger.success(`Set ${slot} model to ${value}`);
|
|
52
|
-
onDone();
|
|
53
|
-
}}
|
|
54
|
-
/>
|
|
55
|
-
{error && <Text color="red">{error}</Text>}
|
|
56
|
-
<Text dimColor>
|
|
57
|
-
Known providers: {listProviderNames().slice(0, 12).join(", ")}…
|
|
58
|
-
</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function SlotPickerApp({
|
|
64
|
-
onPick,
|
|
65
|
-
}: {
|
|
66
|
-
onPick: (slot: Slot) => void;
|
|
67
|
-
}): React.ReactElement {
|
|
68
|
-
return (
|
|
69
|
-
<Box flexDirection="column">
|
|
70
|
-
<Text>Select which model slot to set:</Text>
|
|
71
|
-
<TextInput
|
|
72
|
-
prompt="slot [identity|conversation]> "
|
|
73
|
-
onSubmit={(v) => {
|
|
74
|
-
const s = v.trim().toLowerCase();
|
|
75
|
-
if (s !== "identity" && s !== "conversation") {
|
|
76
|
-
logger.error(`Invalid slot "${s}". Expected identity or conversation.`);
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
onPick(s);
|
|
80
|
-
}}
|
|
81
|
-
/>
|
|
82
|
-
</Box>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function runModel(slotArg?: string): Promise<void> {
|
|
87
|
-
const slot = slotArg as Slot | undefined;
|
|
88
|
-
if (slot && slot !== "identity" && slot !== "conversation") {
|
|
89
|
-
logger.error(`Invalid slot "${slot}". Expected identity or conversation.`);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (slot) {
|
|
94
|
-
const app = render(<ModelApp slot={slot} onDone={() => app.unmount()} />);
|
|
95
|
-
await app.waitUntilExit();
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ponytail: chain the two screens by re-rendering inside the same promise —
|
|
100
|
-
// one terminal render at a time keeps ink's stdin listeners sane.
|
|
101
|
-
const { promise, resolve } = Promise.withResolvers<void>();
|
|
102
|
-
let unmounted = false;
|
|
103
|
-
let active = render(
|
|
104
|
-
<SlotPickerApp
|
|
105
|
-
onPick={(picked) => {
|
|
106
|
-
active.unmount();
|
|
107
|
-
if (unmounted) return;
|
|
108
|
-
active = render(
|
|
109
|
-
<ModelApp
|
|
110
|
-
slot={picked}
|
|
111
|
-
onDone={() => {
|
|
112
|
-
active.unmount();
|
|
113
|
-
resolve();
|
|
114
|
-
}}
|
|
115
|
-
/>,
|
|
116
|
-
);
|
|
117
|
-
}}
|
|
118
|
-
/>,
|
|
119
|
-
);
|
|
120
|
-
void active.waitUntilExit().then(() => {
|
|
121
|
-
unmounted = true;
|
|
122
|
-
resolve();
|
|
123
|
-
});
|
|
124
|
-
await promise;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export function register(program: Command): Command {
|
|
128
|
-
return registerCommand(program, {
|
|
129
|
-
name: "model",
|
|
130
|
-
description: "Set identity or conversation model (interactive)",
|
|
131
|
-
configure: (cmd) =>
|
|
132
|
-
cmd
|
|
133
|
-
.argument("[slot]", "Model slot: identity or conversation")
|
|
134
|
-
.action(async (slot?: string) => {
|
|
135
|
-
await runModel(slot);
|
|
136
|
-
}),
|
|
137
|
-
});
|
|
138
|
-
}
|