openbot 0.1.20 → 0.1.23

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 (40) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/browser-agent.js +31 -0
  3. package/dist/agents/os-agent.js +31 -0
  4. package/dist/cli.js +13 -3
  5. package/dist/handlers/init.js +14 -2
  6. package/dist/handlers/session-change.js +21 -0
  7. package/dist/handlers/tab-change.js +14 -0
  8. package/dist/models.js +53 -0
  9. package/dist/open-bot.js +166 -0
  10. package/dist/plugins/agent/index.js +81 -0
  11. package/dist/plugins/brain/identity.js +76 -0
  12. package/dist/plugins/brain/index.js +269 -0
  13. package/dist/plugins/brain/memory.js +120 -0
  14. package/dist/plugins/brain/prompt.js +64 -0
  15. package/dist/plugins/brain/types.js +60 -0
  16. package/dist/plugins/brain/ui.js +7 -0
  17. package/dist/plugins/browser/index.js +629 -0
  18. package/dist/plugins/browser/ui.js +13 -0
  19. package/dist/plugins/file-system/index.js +166 -0
  20. package/dist/plugins/file-system/ui.js +6 -0
  21. package/dist/plugins/llm/index.js +81 -0
  22. package/dist/plugins/meta-agent/index.js +570 -0
  23. package/dist/plugins/meta-agent/ui.js +11 -0
  24. package/dist/plugins/shell/index.js +95 -0
  25. package/dist/plugins/shell/ui.js +6 -0
  26. package/dist/plugins/skills/index.js +275 -0
  27. package/dist/plugins/skills/types.js +50 -0
  28. package/dist/plugins/skills/ui.js +12 -0
  29. package/dist/registry/agent-registry.js +35 -0
  30. package/dist/registry/index.js +3 -0
  31. package/dist/registry/plugin-registry.js +27 -0
  32. package/dist/registry/yaml-agent-loader.js +100 -0
  33. package/dist/server.js +30 -31
  34. package/dist/ui/header.js +4 -7
  35. package/dist/ui/layout.js +7 -7
  36. package/dist/ui/navigation.js +4 -2
  37. package/dist/ui/sidebar.js +42 -11
  38. package/dist/ui/thread.js +10 -8
  39. package/package.json +12 -13
  40. package/dist/agent.js +0 -110
package/README.md CHANGED
@@ -13,7 +13,7 @@ npm i -g openbot
13
13
  To start your assistant's server, just run:
14
14
 
15
15
  ```bash
16
- openbot-server
16
+ openbot server
17
17
  ```
18
18
 
19
19
  OpenBot will start listening for you at `http://localhost:4001`.
@@ -0,0 +1,31 @@
1
+ import { llmPlugin } from "../plugins/llm/index.js";
2
+ import { browserPlugin, browserToolDefinitions, } from "../plugins/browser/index.js";
3
+ const DEFAULT_SYSTEM_PROMPT = `You are a Browser Agent.
4
+ Your job is to help the user browse the web, extract information, and perform actions.
5
+
6
+ Tools:
7
+ - browser_act: Give a natural language instruction to interact with the page (e.g. "click the login button").
8
+ - browser_observe: See what actions are available on the current page.
9
+ - browser_extract: Pull structured data from the page.
10
+ - browser_state_update: Get a fresh screenshot and URL.
11
+
12
+ Always describe what you see and what you are doing.`;
13
+ /**
14
+ * High-level Browser Agent plugin for Melony.
15
+ * Composes the low-level browserPlugin with an llmPlugin.
16
+ */
17
+ export const browserAgent = (options) => (builder) => {
18
+ const { model, systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
19
+ builder
20
+ .use(browserPlugin({ ...options, model }))
21
+ .use(llmPlugin({
22
+ model,
23
+ system: systemPrompt,
24
+ toolDefinitions: browserToolDefinitions,
25
+ promptInputType: "agent:browser:input",
26
+ actionResultInputType: "agent:browser:result",
27
+ completionEventType: "agent:browser:output",
28
+ }));
29
+ // NOTE: Bridge-back to the manager is handled generically by open-bot.ts.
30
+ // No per-agent boilerplate needed.
31
+ };
@@ -0,0 +1,31 @@
1
+ import { llmPlugin } from "../plugins/llm/index.js";
2
+ import { shellPlugin, shellToolDefinitions } from "../plugins/shell/index.js";
3
+ import { shellUIPlugin } from "../plugins/shell/ui.js";
4
+ import { fileSystemPlugin, fileSystemToolDefinitions } from "../plugins/file-system/index.js";
5
+ import { fileSystemUIPlugin } from "../plugins/file-system/ui.js";
6
+ const DEFAULT_SYSTEM_PROMPT = `You are an OS Agent with access to the shell and file system.
7
+ Your job is to help the user with file operations and command execution.
8
+ You can read, write, list, and delete files, as well as execute shell commands.
9
+ Always be careful with destructive operations.
10
+ When you are done with the task, summarize what you did.`;
11
+ export const osAgent = (options) => (builder) => {
12
+ const { model, cwd = process.cwd(), systemPrompt = DEFAULT_SYSTEM_PROMPT } = options;
13
+ builder
14
+ .use(shellPlugin({ cwd }))
15
+ .use(shellUIPlugin())
16
+ .use(fileSystemPlugin({ baseDir: "/" }))
17
+ .use(fileSystemUIPlugin())
18
+ .use(llmPlugin({
19
+ model,
20
+ system: systemPrompt,
21
+ toolDefinitions: {
22
+ ...shellToolDefinitions,
23
+ ...fileSystemToolDefinitions,
24
+ },
25
+ promptInputType: "agent:os:input",
26
+ actionResultInputType: "agent:os:result",
27
+ completionEventType: "agent:os:output",
28
+ }));
29
+ // NOTE: Bridge-back to the manager is handled generically by open-bot.ts.
30
+ // No per-agent boilerplate needed.
31
+ };
package/dist/cli.js CHANGED
@@ -2,11 +2,12 @@
2
2
  import { Command } from "commander";
3
3
  import * as readline from "node:readline/promises";
4
4
  import { saveConfig } from "./config.js";
5
+ import { startServer } from "./server.js";
5
6
  const program = new Command();
6
7
  program
7
8
  .name("openbot")
8
9
  .description("OpenBot CLI - Secure and easy configuration")
9
- .version("0.1.20");
10
+ .version("0.1.23");
10
11
  program
11
12
  .command("configure")
12
13
  .description("Configure OpenBot model and settings")
@@ -46,15 +47,24 @@ program
46
47
  console.log("------------------------------------------");
47
48
  if (provider === "openai") {
48
49
  console.log("To start the server with your OpenAI key, use:");
49
- console.log(`\n openbot-server --openai-api-key YOUR_OPENAI_KEY\n`);
50
+ console.log(`\n openbot server --openai-api-key YOUR_OPENAI_KEY\n`);
50
51
  }
51
52
  else {
52
53
  console.log("To start the server with your Anthropic key, use:");
53
- console.log(`\n openbot-server --anthropic-api-key YOUR_ANTHROPIC_KEY\n`);
54
+ console.log(`\n openbot server --anthropic-api-key YOUR_ANTHROPIC_KEY\n`);
54
55
  }
55
56
  console.log("Alternatively, you can set the environment variable:");
56
57
  console.log(provider === "openai" ? " export OPENAI_API_KEY=your-key" : " export ANTHROPIC_API_KEY=your-key");
57
58
  console.log("------------------------------------------");
58
59
  rl.close();
59
60
  });
61
+ program
62
+ .command("server")
63
+ .description("Start the OpenBot server")
64
+ .option("-p, --port <number>", "Port to listen on")
65
+ .option("--openai-api-key <key>", "OpenAI API Key")
66
+ .option("--anthropic-api-key <key>", "Anthropic API Key")
67
+ .action(async (options) => {
68
+ await startServer(options);
69
+ });
60
70
  program.parse();
@@ -1,4 +1,4 @@
1
- import { layoutUI } from "../ui/layout.js";
1
+ import { sidebarOnlyUI, tabOnlyUI } from "../ui/layout.js";
2
2
  /**
3
3
  * Initial application layout handler
4
4
  */
@@ -10,8 +10,20 @@ export async function* initHandler(event, { state }) {
10
10
  if (!state.workspaceRoot) {
11
11
  state.workspaceRoot = process.cwd();
12
12
  }
13
+ const tab = event.data?.tab || "chat";
14
+ // For init, return both sidebar and content
13
15
  yield {
14
16
  type: "ui",
15
- data: await layoutUI({ tab: event.data?.tab || "chat", sessionId: state.sessionId })
17
+ meta: {
18
+ type: "sidebar",
19
+ },
20
+ data: await sidebarOnlyUI({ sessionId: state.sessionId })
21
+ };
22
+ yield {
23
+ type: "ui",
24
+ meta: {
25
+ type: "content",
26
+ },
27
+ data: await tabOnlyUI({ tab })
16
28
  };
17
29
  }
@@ -0,0 +1,21 @@
1
+ import { sidebarOnlyUI, tabOnlyUI } from "../ui/layout.js";
2
+ /**
3
+ * Session change handler
4
+ */
5
+ export async function* sessionChangeHandler(event, { state }) {
6
+ const tab = event.data?.tab || "chat";
7
+ yield {
8
+ type: "ui",
9
+ meta: {
10
+ type: "sidebar",
11
+ },
12
+ data: await sidebarOnlyUI({ sessionId: state.sessionId })
13
+ };
14
+ yield {
15
+ type: "ui",
16
+ meta: {
17
+ type: "content",
18
+ },
19
+ data: await tabOnlyUI({ tab })
20
+ };
21
+ }
@@ -0,0 +1,14 @@
1
+ import { tabOnlyUI } from "../ui/layout.js";
2
+ /**
3
+ * Tab change handler
4
+ */
5
+ export async function* tabChangeHandler(event, { state }) {
6
+ const tab = event.data?.tab || "chat";
7
+ yield {
8
+ type: "ui",
9
+ meta: {
10
+ type: "content",
11
+ },
12
+ data: await tabOnlyUI({ tab })
13
+ };
14
+ }
package/dist/models.js ADDED
@@ -0,0 +1,53 @@
1
+ import { openai } from "@ai-sdk/openai";
2
+ import { anthropic } from "@ai-sdk/anthropic";
3
+ import { loadConfig } from "./config.js";
4
+ /**
5
+ * Parse model string to extract provider and model ID
6
+ * Supports formats: "provider:model", "provider/model", or just "model" (defaults to openai)
7
+ */
8
+ export function parseModelString(modelString) {
9
+ // Check for provider:model format
10
+ if (modelString.includes(":")) {
11
+ const [provider, modelId] = modelString.split(":");
12
+ if (provider === "openai" || provider === "anthropic") {
13
+ return { provider: provider, modelId };
14
+ }
15
+ }
16
+ // Check for provider/model format
17
+ if (modelString.includes("/")) {
18
+ const [provider, modelId] = modelString.split("/");
19
+ if (provider === "openai" || provider === "anthropic") {
20
+ return { provider: provider, modelId };
21
+ }
22
+ }
23
+ // Auto-detect provider based on model name
24
+ if (modelString.startsWith("claude") || modelString.startsWith("claude-")) {
25
+ return { provider: "anthropic", modelId: modelString };
26
+ }
27
+ // Default to OpenAI
28
+ return { provider: "openai", modelId: modelString };
29
+ }
30
+ export function createModel(options) {
31
+ const config = loadConfig();
32
+ const openaiKey = options?.openaiApiKey || config.openaiApiKey || process.env.OPENAI_API_KEY;
33
+ const anthropicKey = options?.anthropicApiKey || config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
34
+ if (openaiKey) {
35
+ process.env.OPENAI_API_KEY = openaiKey;
36
+ }
37
+ if (anthropicKey) {
38
+ process.env.ANTHROPIC_API_KEY = anthropicKey;
39
+ }
40
+ const { provider, modelId } = parseModelString(options?.model || config.model || "gpt-4o-mini");
41
+ if (provider === "anthropic") {
42
+ if (!anthropicKey) {
43
+ console.warn("Warning: Anthropic model selected but ANTHROPIC_API_KEY is not set");
44
+ }
45
+ return anthropic(modelId);
46
+ }
47
+ else {
48
+ if (!openaiKey) {
49
+ console.warn("Warning: OpenAI model selected but OPENAI_API_KEY is not set");
50
+ }
51
+ return openai(modelId);
52
+ }
53
+ }
@@ -0,0 +1,166 @@
1
+ import { melony } from "melony";
2
+ import { osAgent } from "./agents/os-agent.js";
3
+ import { browserAgent } from "./agents/browser-agent.js";
4
+ import { browserUIPlugin } from "./plugins/browser/ui.js";
5
+ import { brainPlugin, brainToolDefinitions, createBrainPromptBuilder } from "./plugins/brain/index.js";
6
+ import { brainUIPlugin } from "./plugins/brain/ui.js";
7
+ import { llmPlugin } from "./plugins/llm/index.js";
8
+ import { initHandler } from "./handlers/init.js";
9
+ import { sessionChangeHandler } from "./handlers/session-change.js";
10
+ import { tabChangeHandler } from "./handlers/tab-change.js";
11
+ import { loadConfig, resolvePath, DEFAULT_BASE_DIR } from "./config.js";
12
+ import { createModel } from "./models.js";
13
+ import path from "node:path";
14
+ import { z } from "zod";
15
+ // Plugin imports for the registry
16
+ import { shellPlugin, shellToolDefinitions } from "./plugins/shell/index.js";
17
+ import { shellUIPlugin } from "./plugins/shell/ui.js";
18
+ import { fileSystemPlugin, fileSystemToolDefinitions } from "./plugins/file-system/index.js";
19
+ import { fileSystemUIPlugin } from "./plugins/file-system/ui.js";
20
+ import { browserPlugin, browserToolDefinitions } from "./plugins/browser/index.js";
21
+ // Registry
22
+ import { PluginRegistry, AgentRegistry, discoverYamlAgents } from "./registry/index.js";
23
+ /**
24
+ * Create the OpenBot manager agent.
25
+ *
26
+ * Architecture:
27
+ * 1. Built-in plugins are registered in the Plugin Registry so YAML agents can reference them by name.
28
+ * 2. Built-in agents + YAML agents discovered from ~/.openbot/agents/ are registered in the Agent Registry.
29
+ * 3. The manager LLM gets a dynamically-built `delegateTask` tool listing all registered agents.
30
+ * 4. A generic delegation handler + bridge-back wiring replaces per-agent boilerplate.
31
+ */
32
+ export async function createOpenBot(options) {
33
+ const config = loadConfig();
34
+ const baseDir = config.baseDir || DEFAULT_BASE_DIR;
35
+ const resolvedBaseDir = resolvePath(baseDir);
36
+ const model = createModel(options);
37
+ const userDataDir = path.join(resolvedBaseDir, "browser-data");
38
+ // ─── Plugin Registry ─────────────────────────────────────────────
39
+ // Register built-in plugins so YAML agents can reference them by name.
40
+ const pluginRegistry = new PluginRegistry();
41
+ pluginRegistry.register({
42
+ name: "shell",
43
+ description: "Execute shell commands",
44
+ toolDefinitions: shellToolDefinitions,
45
+ factory: () => shellPlugin({ cwd: process.cwd() }),
46
+ uiFactory: () => shellUIPlugin(),
47
+ });
48
+ pluginRegistry.register({
49
+ name: "file-system",
50
+ description: "Read, write, list, and delete files",
51
+ toolDefinitions: fileSystemToolDefinitions,
52
+ factory: () => fileSystemPlugin({ baseDir: "/" }),
53
+ uiFactory: () => fileSystemUIPlugin(),
54
+ });
55
+ pluginRegistry.register({
56
+ name: "browser",
57
+ description: "Browse the web and interact with pages",
58
+ toolDefinitions: browserToolDefinitions,
59
+ factory: () => browserPlugin({
60
+ headless: true,
61
+ userDataDir,
62
+ channel: "chrome",
63
+ model: model,
64
+ }),
65
+ uiFactory: () => browserUIPlugin(),
66
+ });
67
+ // ─── Agent Registry ──────────────────────────────────────────────
68
+ // Register built-in agents, then discover YAML agents from ~/.openbot/agents/.
69
+ const agentRegistry = new AgentRegistry();
70
+ agentRegistry.register({
71
+ name: "os",
72
+ description: "Handles shell commands and file system operations",
73
+ plugin: osAgent({ model: model }),
74
+ });
75
+ agentRegistry.register({
76
+ name: "browser",
77
+ description: "Browses the web, extracts data, and interacts with pages",
78
+ plugin: browserAgent({
79
+ model: model,
80
+ headless: true,
81
+ userDataDir,
82
+ channel: "chrome",
83
+ }),
84
+ });
85
+ // Discover community / user agents from ~/.openbot/agents/
86
+ const yamlAgents = await discoverYamlAgents(path.join(resolvedBaseDir, "agents"), pluginRegistry, model, options);
87
+ for (const agent of yamlAgents) {
88
+ agentRegistry.register(agent);
89
+ }
90
+ // ─── Compose the Melony App ──────────────────────────────────────
91
+ const allAgents = agentRegistry.getAll();
92
+ const agentNames = agentRegistry.getNames();
93
+ const app = melony();
94
+ // 1. Register all agent plugins
95
+ for (const agent of allAgents) {
96
+ app.use(agent.plugin);
97
+ }
98
+ // 2. Register global plugins (brain, UI, etc.)
99
+ const buildBrainPrompt = createBrainPromptBuilder(baseDir);
100
+ app
101
+ .use(browserUIPlugin())
102
+ .use(brainPlugin({
103
+ baseDir: resolvedBaseDir,
104
+ allowSoulModification: false,
105
+ }))
106
+ .use(brainUIPlugin());
107
+ // 3. Build dynamic delegation tool from the agent registry
108
+ const agentDescriptions = allAgents
109
+ .map((a) => `- ${a.name}: ${a.description}`)
110
+ .join("\n");
111
+ app.use(llmPlugin({
112
+ model: model,
113
+ system: async (context) => {
114
+ const [brainPrompt] = await Promise.all([
115
+ buildBrainPrompt(context),
116
+ ]);
117
+ return `${brainPrompt}`;
118
+ },
119
+ toolDefinitions: {
120
+ ...brainToolDefinitions,
121
+ delegateTask: {
122
+ description: `Delegate a task to a specialized agent.\n\nAvailable agents:\n${agentDescriptions}`,
123
+ inputSchema: z.object({
124
+ agent: z.enum(agentNames).describe("The specialized agent to use"),
125
+ task: z.string().describe("The task description"),
126
+ }),
127
+ },
128
+ },
129
+ }));
130
+ // 4. Generic delegation handler — works for any registered agent
131
+ app.on("action:delegateTask", async function* (event, { state }) {
132
+ const { agent, task, toolCallId } = event.data;
133
+ const s = state;
134
+ if (!s.pendingAgentTasks)
135
+ s.pendingAgentTasks = {};
136
+ s.pendingAgentTasks[agent] = { toolCallId };
137
+ yield {
138
+ type: `agent:${agent}:input`,
139
+ data: { content: task },
140
+ };
141
+ });
142
+ // 5. Generic bridge-back handlers — auto-wired for every registered agent
143
+ for (const agent of allAgents) {
144
+ app.on(`agent:${agent.name}:output`, async function* (event, { state }) {
145
+ const s = state;
146
+ const pending = s.pendingAgentTasks?.[agent.name];
147
+ if (pending) {
148
+ delete s.pendingAgentTasks[agent.name];
149
+ yield {
150
+ type: "action:taskResult",
151
+ data: {
152
+ action: "delegateTask",
153
+ toolCallId: pending.toolCallId,
154
+ result: event.data.content,
155
+ },
156
+ };
157
+ }
158
+ });
159
+ }
160
+ // 6. Init handlers
161
+ app
162
+ .on("init", initHandler)
163
+ .on("sessionChange", sessionChangeHandler)
164
+ .on("tabChange", tabChangeHandler);
165
+ return app;
166
+ }
@@ -0,0 +1,81 @@
1
+ import { streamText } from "ai";
2
+ import { ui } from "@melony/ui-kit";
3
+ /**
4
+ * Builds a simple history summary from recent messages.
5
+ * Keeps the last N messages as simple role/content pairs.
6
+ */
7
+ function getRecentHistory(messages, maxMessages) {
8
+ return messages.slice(-maxMessages);
9
+ }
10
+ /**
11
+ * AI SDK Plugin for Melony.
12
+ * Automatically handles text events and routes them through an LLM using Vercel AI SDK.
13
+ * It can also automatically trigger events based on tool calls.
14
+ */
15
+ export const agentPlugin = (options) => (builder) => {
16
+ const { model, system, toolDefinitions = {}, actionEventPrefix = "action:", promptInputType = "user:text", actionResultInputType = "action:result", completionEventType } = options;
17
+ async function* routeToLLM(newMessage, context) {
18
+ const state = context.state;
19
+ if (!state.messages) {
20
+ state.messages = [];
21
+ }
22
+ // Add new message to history
23
+ state.messages.push(newMessage);
24
+ // Evaluate dynamic system prompt if it's a function
25
+ const systemPrompt = typeof system === "function" ? await system(context) : system;
26
+ const result = streamText({
27
+ model,
28
+ system: systemPrompt,
29
+ messages: getRecentHistory(state.messages, 20).map(m => m.role === "system" ? { role: "user", content: `System: ${m.content}` } : m),
30
+ tools: toolDefinitions,
31
+ });
32
+ let assistantText = "";
33
+ for await (const delta of result.textStream) {
34
+ assistantText += delta;
35
+ yield {
36
+ type: "assistant:text-delta",
37
+ data: { delta },
38
+ };
39
+ }
40
+ // Wait for tool calls to complete
41
+ const toolCalls = await result.toolCalls;
42
+ // Store assistant response as simple text
43
+ if (assistantText) {
44
+ state.messages.push({
45
+ role: "assistant",
46
+ content: assistantText,
47
+ });
48
+ if (completionEventType) {
49
+ yield {
50
+ type: completionEventType,
51
+ data: { content: assistantText },
52
+ };
53
+ }
54
+ }
55
+ const usage = await result.usage;
56
+ yield ui.event(ui.status(`Usage: ${usage.totalTokens} tokens`, "info"));
57
+ // Emit tool call events
58
+ for (const call of toolCalls) {
59
+ yield {
60
+ type: `${actionEventPrefix}${call.toolName}`,
61
+ data: {
62
+ ...call.input,
63
+ toolCallId: call.toolCallId,
64
+ },
65
+ };
66
+ }
67
+ }
68
+ // Handle user text input
69
+ builder.on(promptInputType, async function* (event, context) {
70
+ const content = event.data.content;
71
+ yield* routeToLLM({ role: "user", content }, context);
72
+ });
73
+ // Feed action results back to the LLM as user messages (with a System prefix)
74
+ // We use "user" role instead of "system" to avoid errors with providers like Anthropic
75
+ // that don't support multiple system messages or system messages after the first turn.
76
+ builder.on(actionResultInputType, async function* (event, context) {
77
+ const { action, result } = event.data;
78
+ const summary = typeof result === "string" ? result : JSON.stringify(result);
79
+ yield* routeToLLM({ role: "user", content: `System: Action "${action}" completed: ${summary}` }, context);
80
+ });
81
+ };
@@ -0,0 +1,76 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+ // --- Defaults ---
4
+ const DEFAULT_SOUL = `# Soul
5
+
6
+ ## Core Values
7
+ - Be helpful, honest, and harmless
8
+ - Respect user privacy and data
9
+ - Learn and improve continuously
10
+ - Be transparent about capabilities and limitations
11
+
12
+ ## Ethical Guidelines
13
+ - Never assist with harmful or illegal activities
14
+ - Protect sensitive information
15
+ - Acknowledge uncertainty when unsure
16
+ - Prioritize user well-being
17
+ `;
18
+ const DEFAULT_IDENTITY = `# Identity
19
+
20
+ I am OpenBot, a self-evolving AI assistant built on the OpenBot framework.
21
+
22
+ ## Personality
23
+ - Friendly and approachable
24
+ - Technically competent
25
+ - Eager to learn and adapt
26
+
27
+ ## Capabilities
28
+ - Shell command execution
29
+ - File system operations
30
+ - Skill-based task execution
31
+ - Self-modification and learning
32
+ `;
33
+ // --- Factory ---
34
+ export function createIdentityModule(baseDir) {
35
+ const soulPath = path.join(baseDir, "SOUL.md");
36
+ const identityPath = path.join(baseDir, "IDENTITY.md");
37
+ return {
38
+ async initialize() {
39
+ try {
40
+ await fs.access(soulPath);
41
+ }
42
+ catch {
43
+ await fs.writeFile(soulPath, DEFAULT_SOUL, "utf-8");
44
+ }
45
+ try {
46
+ await fs.access(identityPath);
47
+ }
48
+ catch {
49
+ await fs.writeFile(identityPath, DEFAULT_IDENTITY, "utf-8");
50
+ }
51
+ },
52
+ async getSoul() {
53
+ try {
54
+ return (await fs.readFile(soulPath, "utf-8")).trim();
55
+ }
56
+ catch {
57
+ return "";
58
+ }
59
+ },
60
+ async getIdentity() {
61
+ try {
62
+ return (await fs.readFile(identityPath, "utf-8")).trim();
63
+ }
64
+ catch {
65
+ return "";
66
+ }
67
+ },
68
+ async updateIdentity(content) {
69
+ await fs.writeFile(identityPath, content, "utf-8");
70
+ },
71
+ async readFile(file) {
72
+ const filePath = file === "SOUL.md" ? soulPath : identityPath;
73
+ return await fs.readFile(filePath, "utf-8");
74
+ },
75
+ };
76
+ }