@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.
Files changed (92) hide show
  1. package/dist/index.js +5312 -0
  2. package/package.json +7 -4
  3. package/scripts/smoke_providers.ts +0 -176
  4. package/src/brain/index.ts +0 -936
  5. package/src/brain/manager.ts +0 -144
  6. package/src/brain/memory.ts +0 -99
  7. package/src/brain/messageHistory.ts +0 -26
  8. package/src/brain/schedule.ts +0 -11
  9. package/src/brain/types.ts +0 -26
  10. package/src/channel/base.ts +0 -527
  11. package/src/channel/discord.ts +0 -227
  12. package/src/channel/telegram.ts +0 -150
  13. package/src/commands/auth.tsx +0 -306
  14. package/src/commands/brain.ts +0 -119
  15. package/src/commands/daemon/commands.ts +0 -56
  16. package/src/commands/daemon/pairingCommand.ts +0 -15
  17. package/src/commands/daemon/restartCommand.ts +0 -17
  18. package/src/commands/daemon.ts +0 -168
  19. package/src/commands/index.ts +0 -16
  20. package/src/commands/model.tsx +0 -138
  21. package/src/commands/onboard.tsx +0 -473
  22. package/src/commands/pairing.ts +0 -32
  23. package/src/commands/restart.ts +0 -24
  24. package/src/config/file/auth.ts +0 -105
  25. package/src/config/file/root.ts +0 -37
  26. package/src/config/index.ts +0 -21
  27. package/src/config/loader.ts +0 -115
  28. package/src/index.ts +0 -61
  29. package/src/provider/index.ts +0 -122
  30. package/src/provider/llm.ts +0 -191
  31. package/src/provider/promptLoader.ts +0 -36
  32. package/src/provider/providers/302ai.ts +0 -17
  33. package/src/provider/providers/MiniMax.ts +0 -17
  34. package/src/provider/providers/anthropic.ts +0 -257
  35. package/src/provider/providers/azure_cognitive.ts +0 -40
  36. package/src/provider/providers/azure_openai.ts +0 -49
  37. package/src/provider/providers/baseten.ts +0 -17
  38. package/src/provider/providers/bedrock.ts +0 -312
  39. package/src/provider/providers/cerebras.ts +0 -17
  40. package/src/provider/providers/cloudflare_gateway.ts +0 -34
  41. package/src/provider/providers/cloudflare_workers.ts +0 -178
  42. package/src/provider/providers/copilot.ts +0 -22
  43. package/src/provider/providers/cortecs.ts +0 -17
  44. package/src/provider/providers/deepinfra.ts +0 -17
  45. package/src/provider/providers/deepseek.ts +0 -17
  46. package/src/provider/providers/digitalocean.ts +0 -17
  47. package/src/provider/providers/fireworks.ts +0 -17
  48. package/src/provider/providers/gitlab_duo.ts +0 -184
  49. package/src/provider/providers/gmi.ts +0 -17
  50. package/src/provider/providers/groq.ts +0 -17
  51. package/src/provider/providers/helicone.ts +0 -17
  52. package/src/provider/providers/huggingface.ts +0 -17
  53. package/src/provider/providers/ionet.ts +0 -17
  54. package/src/provider/providers/llamacpp.ts +0 -17
  55. package/src/provider/providers/llmgateway.ts +0 -17
  56. package/src/provider/providers/lmstudio.ts +0 -17
  57. package/src/provider/providers/mistral.ts +0 -17
  58. package/src/provider/providers/moonshot.ts +0 -17
  59. package/src/provider/providers/nebius.ts +0 -17
  60. package/src/provider/providers/nvidia.ts +0 -17
  61. package/src/provider/providers/ollama.ts +0 -17
  62. package/src/provider/providers/ollama_cloud.ts +0 -17
  63. package/src/provider/providers/openai.ts +0 -17
  64. package/src/provider/providers/openai_compatible.ts +0 -293
  65. package/src/provider/providers/openrouter.ts +0 -175
  66. package/src/provider/providers/ovhcloud.ts +0 -17
  67. package/src/provider/providers/sap_aicore.ts +0 -22
  68. package/src/provider/providers/scaleway.ts +0 -17
  69. package/src/provider/providers/snowflake_cortex.ts +0 -207
  70. package/src/provider/providers/stackit.ts +0 -17
  71. package/src/provider/providers/together.ts +0 -17
  72. package/src/provider/providers/venice.ts +0 -17
  73. package/src/provider/providers/vercel.ts +0 -17
  74. package/src/provider/providers/vertex.ts +0 -229
  75. package/src/provider/providers/xai.ts +0 -17
  76. package/src/provider/providers/zai.ts +0 -17
  77. package/src/provider/providers/zenmux.ts +0 -17
  78. package/src/provider/schema.ts +0 -143
  79. package/src/ui/TextInput.tsx +0 -114
  80. package/src/utils/daemonClient.ts +0 -85
  81. package/src/utils/logger.ts +0 -204
  82. package/tsconfig.json +0 -34
  83. /package/{prompts → dist/prompts}/daily_schedule.md +0 -0
  84. /package/{prompts → dist/prompts}/memoir.md +0 -0
  85. /package/{prompts → dist/prompts}/monthly_schedule.md +0 -0
  86. /package/{prompts → dist/prompts}/objectifier.md +0 -0
  87. /package/{prompts → dist/prompts}/persona_base_system_prompt.md +0 -0
  88. /package/{prompts → dist/prompts}/persona_base_system_prompt_fixed.md +0 -0
  89. /package/{prompts → dist/prompts}/persona_init.md +0 -0
  90. /package/{prompts → dist/prompts}/schedule_availability.md +0 -0
  91. /package/{prompts → dist/prompts}/send_message.md +0 -0
  92. /package/{prompts → dist/prompts}/start_conversation.md +0 -0
@@ -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
- });
@@ -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
- }
@@ -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
- }
@@ -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
- }