@p-sw/brainbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/README.md +138 -0
  2. package/package.json +41 -0
  3. package/prompts/daily_schedule.md +55 -0
  4. package/prompts/memoir.md +26 -0
  5. package/prompts/monthly_schedule.md +53 -0
  6. package/prompts/objectifier.md +14 -0
  7. package/prompts/persona_base_system_prompt.md +173 -0
  8. package/prompts/persona_base_system_prompt_fixed.md +47 -0
  9. package/prompts/persona_init.md +107 -0
  10. package/prompts/schedule_availability.md +77 -0
  11. package/prompts/send_message.md +37 -0
  12. package/prompts/start_conversation.md +55 -0
  13. package/scripts/smoke_providers.ts +176 -0
  14. package/src/brain/index.ts +936 -0
  15. package/src/brain/manager.ts +144 -0
  16. package/src/brain/memory.ts +99 -0
  17. package/src/brain/messageHistory.ts +26 -0
  18. package/src/brain/schedule.ts +11 -0
  19. package/src/brain/types.ts +26 -0
  20. package/src/channel/base.ts +527 -0
  21. package/src/channel/discord.ts +227 -0
  22. package/src/channel/telegram.ts +150 -0
  23. package/src/commands/auth.tsx +306 -0
  24. package/src/commands/brain.ts +119 -0
  25. package/src/commands/daemon/commands.ts +56 -0
  26. package/src/commands/daemon/pairingCommand.ts +15 -0
  27. package/src/commands/daemon/restartCommand.ts +17 -0
  28. package/src/commands/daemon.ts +168 -0
  29. package/src/commands/index.ts +16 -0
  30. package/src/commands/model.tsx +138 -0
  31. package/src/commands/onboard.tsx +473 -0
  32. package/src/commands/pairing.ts +32 -0
  33. package/src/commands/restart.ts +24 -0
  34. package/src/config/file/auth.ts +105 -0
  35. package/src/config/file/root.ts +37 -0
  36. package/src/config/index.ts +21 -0
  37. package/src/config/loader.ts +115 -0
  38. package/src/index.ts +61 -0
  39. package/src/provider/index.ts +122 -0
  40. package/src/provider/llm.ts +191 -0
  41. package/src/provider/promptLoader.ts +36 -0
  42. package/src/provider/providers/302ai.ts +17 -0
  43. package/src/provider/providers/MiniMax.ts +17 -0
  44. package/src/provider/providers/anthropic.ts +257 -0
  45. package/src/provider/providers/azure_cognitive.ts +40 -0
  46. package/src/provider/providers/azure_openai.ts +49 -0
  47. package/src/provider/providers/baseten.ts +17 -0
  48. package/src/provider/providers/bedrock.ts +312 -0
  49. package/src/provider/providers/cerebras.ts +17 -0
  50. package/src/provider/providers/cloudflare_gateway.ts +34 -0
  51. package/src/provider/providers/cloudflare_workers.ts +178 -0
  52. package/src/provider/providers/copilot.ts +22 -0
  53. package/src/provider/providers/cortecs.ts +17 -0
  54. package/src/provider/providers/deepinfra.ts +17 -0
  55. package/src/provider/providers/deepseek.ts +17 -0
  56. package/src/provider/providers/digitalocean.ts +17 -0
  57. package/src/provider/providers/fireworks.ts +17 -0
  58. package/src/provider/providers/gitlab_duo.ts +184 -0
  59. package/src/provider/providers/gmi.ts +17 -0
  60. package/src/provider/providers/groq.ts +17 -0
  61. package/src/provider/providers/helicone.ts +17 -0
  62. package/src/provider/providers/huggingface.ts +17 -0
  63. package/src/provider/providers/ionet.ts +17 -0
  64. package/src/provider/providers/llamacpp.ts +17 -0
  65. package/src/provider/providers/llmgateway.ts +17 -0
  66. package/src/provider/providers/lmstudio.ts +17 -0
  67. package/src/provider/providers/mistral.ts +17 -0
  68. package/src/provider/providers/moonshot.ts +17 -0
  69. package/src/provider/providers/nebius.ts +17 -0
  70. package/src/provider/providers/nvidia.ts +17 -0
  71. package/src/provider/providers/ollama.ts +17 -0
  72. package/src/provider/providers/ollama_cloud.ts +17 -0
  73. package/src/provider/providers/openai.ts +17 -0
  74. package/src/provider/providers/openai_compatible.ts +293 -0
  75. package/src/provider/providers/openrouter.ts +175 -0
  76. package/src/provider/providers/ovhcloud.ts +17 -0
  77. package/src/provider/providers/sap_aicore.ts +22 -0
  78. package/src/provider/providers/scaleway.ts +17 -0
  79. package/src/provider/providers/snowflake_cortex.ts +207 -0
  80. package/src/provider/providers/stackit.ts +17 -0
  81. package/src/provider/providers/together.ts +17 -0
  82. package/src/provider/providers/venice.ts +17 -0
  83. package/src/provider/providers/vercel.ts +17 -0
  84. package/src/provider/providers/vertex.ts +229 -0
  85. package/src/provider/providers/xai.ts +17 -0
  86. package/src/provider/providers/zai.ts +17 -0
  87. package/src/provider/providers/zenmux.ts +17 -0
  88. package/src/provider/schema.ts +143 -0
  89. package/src/ui/TextInput.tsx +114 -0
  90. package/src/utils/daemonClient.ts +85 -0
  91. package/src/utils/logger.ts +204 -0
  92. package/tsconfig.json +34 -0
@@ -0,0 +1,144 @@
1
+ import { config } from "@/config";
2
+ import { mkdir, readFile, writeFile } from "fs/promises";
3
+ import { join } from "path";
4
+ import { logger } from "@/utils/logger";
5
+
6
+ const log = logger.child("brain-manager");
7
+
8
+ export type ChannelKeys = "discord" | "telegram";
9
+ export interface BrainDiscordConfig {
10
+ token: string;
11
+ channelId?: string;
12
+ }
13
+ export interface BrainTelegramConfig {
14
+ token: string;
15
+ chatId?: number;
16
+ }
17
+ export interface BrainItem {
18
+ brainId: string;
19
+ spaceName: string;
20
+ displayName: string;
21
+ baseSystemPrompt: string;
22
+ dndReplyProbability: number;
23
+ startConversationCountThreshold: number;
24
+ startConversationTimeThreshold: number;
25
+ activated: boolean;
26
+ channel?: ChannelKeys;
27
+ discord?: BrainDiscordConfig;
28
+ telegram?: BrainTelegramConfig;
29
+ }
30
+ export type BrainItemDiscord = Omit<BrainItem, "channel" | ChannelKeys> & {
31
+ channel: "discord";
32
+ discord: BrainDiscordConfig;
33
+ };
34
+ export type BrainItemTelegram = Omit<BrainItem, "channel" | ChannelKeys> & {
35
+ channel: "telegram";
36
+ telegram: BrainTelegramConfig;
37
+ };
38
+ export type BrainItemWithChannel = BrainItemDiscord | BrainItemTelegram;
39
+ export type BrainList = BrainItem[];
40
+
41
+ export class BrainDBManager {
42
+ constructor(private readonly root: string = config.brainboxRoot) {}
43
+
44
+ private dbFile(): string {
45
+ return join(this.root, "brains.json");
46
+ }
47
+
48
+ private async readDB(): Promise<BrainList> {
49
+ try {
50
+ const content = await readFile(this.dbFile(), { encoding: "utf-8" });
51
+ return JSON.parse(content) as BrainList;
52
+ } catch {
53
+ return [];
54
+ }
55
+ }
56
+
57
+ private async writeDB(list: BrainList): Promise<void> {
58
+ await mkdir(this.root, { recursive: true });
59
+ await writeFile(this.dbFile(), JSON.stringify(list, null, 2), {
60
+ encoding: "utf-8",
61
+ });
62
+ }
63
+
64
+ async loadBrain(brainId: string): Promise<BrainItem | undefined> {
65
+ log.debug(`loadBrain: id=${brainId}`);
66
+ const list = await this.readDB();
67
+ const found = list.find((b) => b.brainId === brainId);
68
+ log.debug(`loadBrain: ${found ? "hit" : "miss"} (db size=${list.length})`);
69
+ return found;
70
+ }
71
+
72
+ async listBrains(): Promise<BrainList> {
73
+ const list = await this.readDB();
74
+ log.debug(`listBrains: count=${list.length}`);
75
+ return list;
76
+ }
77
+
78
+ async saveBrain(brainId: string, brain: BrainItem): Promise<void> {
79
+ log.debug(`saveBrain: id=${brainId} name=${brain.displayName}`);
80
+ const list = await this.readDB();
81
+ const idx = list.findIndex((b) => b.brainId === brainId);
82
+ const op = idx >= 0 ? "update" : "insert";
83
+ if (idx >= 0) list[idx] = brain;
84
+ else list.push(brain);
85
+ await this.writeDB(list);
86
+ log.debug(`saveBrain: ${op} committed (db size=${list.length})`);
87
+ }
88
+
89
+ async listAvailableBrain(): Promise<BrainItemWithChannel[]> {
90
+ const list = await this.readDB();
91
+ const ready = list.filter((b) => this.isBrainReady(b));
92
+ log.debug(
93
+ `listAvailableBrain: ${ready.length}/${list.length} ready (channel-bound + activated)`,
94
+ );
95
+ return ready;
96
+ }
97
+
98
+ async deleteBrain(brainId: string): Promise<void> {
99
+ log.debug(`deleteBrain: id=${brainId}`);
100
+ const list = await this.readDB();
101
+ const filtered = list.filter((b) => b.brainId !== brainId);
102
+ if (filtered.length === list.length) {
103
+ log.debug(`deleteBrain: no-op (id not in db)`);
104
+ return;
105
+ }
106
+ await this.writeDB(filtered);
107
+ log.debug(`deleteBrain: removed (db size=${filtered.length})`);
108
+ }
109
+
110
+ async isBrainAvailable(brainId: string): Promise<boolean> {
111
+ const item = await this.loadBrain(brainId);
112
+ const ok = item !== undefined && this.isBrainReady(item);
113
+ log.debug(`isBrainAvailable: id=${brainId} → ${ok}`);
114
+ return ok;
115
+ }
116
+
117
+ isBrainReady(item: BrainItem): item is BrainItemWithChannel {
118
+ if (!item.activated) {
119
+ log.debug(`isBrainReady: ${item.brainId} not activated`);
120
+ return false;
121
+ }
122
+ switch (item.channel) {
123
+ case "discord":
124
+ if (!item.discord?.token) {
125
+ log.debug(`isBrainReady: ${item.brainId} missing discord.token`);
126
+ return false;
127
+ }
128
+ return true;
129
+ case "telegram":
130
+ if (!item.telegram?.token) {
131
+ log.debug(`isBrainReady: ${item.brainId} missing telegram.token`);
132
+ return false;
133
+ }
134
+ return true;
135
+ default:
136
+ log.debug(
137
+ `isBrainReady: ${item.brainId} has no channel configured`,
138
+ );
139
+ return false;
140
+ }
141
+ }
142
+ }
143
+
144
+ export const brainManager = new BrainDBManager();
@@ -0,0 +1,99 @@
1
+ import type Supermemory from "supermemory";
2
+ import { logger } from "@/utils/logger";
3
+ import type { FactInput, FactMetadata, SearchHit, Space } from "./types";
4
+
5
+ const log = logger.child("memory");
6
+
7
+ export class Memory {
8
+ constructor(
9
+ public db: Supermemory,
10
+ public space: Space,
11
+ ) {
12
+ log.debug(`Memory constructed for space=${space.name}`);
13
+ }
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Memory primitives — thin wrappers over supermemory's `documents` API.
17
+ //
18
+ // containerTag = space.name
19
+ // customId = the stable lookup key (e.g. "daily-schedule:2026-06-10")
20
+ // content = the fact text or JSON-encoded schedule
21
+ // metadata = filterable bag: { kind, source, ... }
22
+ // ---------------------------------------------------------------------------
23
+
24
+ async add(input: FactInput): Promise<{ id: string }> {
25
+ log.debug(
26
+ `add: customId=${input.customId} kind=${input.metadata?.["kind"] ?? "-"} bytes=${input.content.length}`,
27
+ );
28
+ const response = await this.db.documents.add({
29
+ content: input.content,
30
+ containerTag: this.space.name,
31
+ customId: input.customId,
32
+ metadata: input.metadata,
33
+ });
34
+ log.debug(`add: stored id=${response.id}`);
35
+ return { id: response.id };
36
+ }
37
+
38
+ async get(
39
+ customId: string,
40
+ ): Promise<{ content: string; metadata: FactMetadata | null } | null> {
41
+ log.debug(`get: customId=${customId}`);
42
+ const listed = await this.db.documents.list({
43
+ containerTags: [this.space.name],
44
+ limit: 200,
45
+ });
46
+ const match = (listed.memories ?? []).find((m) => m.customId === customId);
47
+ if (!match) {
48
+ log.debug(`get: miss for customId=${customId}`);
49
+ return null;
50
+ }
51
+ const full = await this.db.documents.get(match.id);
52
+ log.debug(`get: hit customId=${customId} bytes=${full.content?.length ?? 0}`);
53
+ return {
54
+ content: full.content ?? "",
55
+ metadata: (full.metadata ?? null) as FactMetadata | null,
56
+ };
57
+ }
58
+
59
+ async clear(): Promise<void> {
60
+ log.debug(`clear: deleting all docs in space=${this.space.name}`);
61
+ await this.db.documents.deleteBulk({ containerTags: [this.space.name] });
62
+ log.debug(`clear: done`);
63
+ }
64
+
65
+ async list(): Promise<Array<{ customId: string | null; content: string }>> {
66
+ log.debug(`list: fetching up to 200 docs in space=${this.space.name}`);
67
+ const listed = await this.db.documents.list({
68
+ containerTags: [this.space.name],
69
+ limit: 200,
70
+ });
71
+ const docs = (listed.memories ?? []).map((d) => ({
72
+ customId: d.customId,
73
+ content: d.content ?? "",
74
+ }));
75
+ log.debug(`list: got ${docs.length} docs`);
76
+ return docs;
77
+ }
78
+
79
+ async search(query: string, limit = 5): Promise<SearchHit[]> {
80
+ log.debug(
81
+ `search: q="${query}" limit=${limit} space=${this.space.name}`,
82
+ );
83
+ const response = await this.db.search.execute({
84
+ q: query,
85
+ containerTag: this.space.name,
86
+ limit,
87
+ onlyMatchingChunks: true,
88
+ });
89
+ const hits = (response.results ?? []).map((r) => {
90
+ const firstChunk = r.chunks?.[0];
91
+ return {
92
+ content: firstChunk?.content ?? "",
93
+ score: r.score,
94
+ };
95
+ });
96
+ log.debug(`search: ${hits.length} hits`);
97
+ return hits;
98
+ }
99
+ }
@@ -0,0 +1,26 @@
1
+ export type MessageSender = "persona" | "user";
2
+
3
+ export interface MessageHistoryEntry {
4
+ sender: MessageSender;
5
+ time: Date;
6
+ content: string;
7
+ }
8
+
9
+ function formatTime(time: Date): string {
10
+ const pad = (n: number) => n.toString().padStart(2, "0");
11
+ return `${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad(
12
+ time.getDate(),
13
+ )} ${pad(time.getHours())}:${pad(time.getMinutes())}:${pad(time.getSeconds())}`;
14
+ }
15
+
16
+ export function translateMessageHistory(
17
+ personaName: string,
18
+ entries: ReadonlyArray<MessageHistoryEntry>,
19
+ ): string {
20
+ return entries
21
+ .map((entry) => {
22
+ const label = entry.sender === "persona" ? personaName : "사용자";
23
+ return `${label}@${formatTime(entry.time)}: ${entry.content}`;
24
+ })
25
+ .join("\n");
26
+ }
@@ -0,0 +1,11 @@
1
+ export function pad2(n: number): string {
2
+ return n < 10 ? `0${n}` : `${n}`;
3
+ }
4
+
5
+ export function formatDateKey(d: Date): string {
6
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
7
+ }
8
+
9
+ export function formatMonthKey(d: Date): string {
10
+ return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}`;
11
+ }
@@ -0,0 +1,26 @@
1
+ /** A brain's logical namespace. Maps to supermemory's `containerTag`. */
2
+ export interface Space {
3
+ name: string;
4
+ description?: string;
5
+ }
6
+
7
+ /** Metadata bag stored alongside a document in supermemory. */
8
+ export type FactMetadata = Record<string, string | number | boolean | string[]>;
9
+
10
+ /**
11
+ * A fact/document to store in the brain's memory. Mirrors supermemory's
12
+ * `documents.add` parameter shape: `content` is the canonical text (or
13
+ * JSON-encoded schedule), `customId` is the stable lookup key, and
14
+ * `metadata` is the filterable bag.
15
+ */
16
+ export interface FactInput {
17
+ customId: string;
18
+ content: string;
19
+ metadata?: FactMetadata;
20
+ }
21
+
22
+ /** A single semantic-search hit. */
23
+ export interface SearchHit {
24
+ content: string;
25
+ score: number;
26
+ }