obol-ai 0.2.1 → 0.2.2
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 +20 -11
- package/package.json +3 -2
- package/src/claude.js +44 -3
- package/src/defaults/AGENTS.md +16 -0
- package/src/telegram.js +10 -0
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ OBOL is an AI agent that evolves its own personality, rewrites its own code, tes
|
|
|
30
30
|
|
|
31
31
|
It starts as a blank slate. Through conversation it learns who you are, develops a personality shaped by your interactions, and builds operational knowledge about how to work with you. Every 100 exchanges it reflects on who it's becoming, refactors its own scripts, writes tests, fixes regressions, and builds you new tools based on patterns it spots in your conversations — scripts, commands, or full web apps deployed to Vercel. Over months it becomes an agent that's uniquely yours. No two OBOL instances are alike.
|
|
32
32
|
|
|
33
|
-
One bot, multiple users. Each allowed Telegram user gets a fully isolated context — their own personality, memory, evolution cycle,
|
|
33
|
+
One bot, multiple users. Each allowed Telegram user gets a fully isolated context — their own personality, memory, evolution cycle, and workspace. User A's personality drift, scripts, and memories never leak into User B's. Everything runs in a single process with shared API credentials.
|
|
34
34
|
|
|
35
35
|
Under the hood: Node.js + Telegram + Claude + Supabase pgvector. No framework, no plugins, no config to maintain. It backs up its brain to GitHub and hardens your server automatically.
|
|
36
36
|
|
|
@@ -156,7 +156,7 @@ Refined voice, updated your project list, cleaned up 2 unused scripts.
|
|
|
156
156
|
|
|
157
157
|
```
|
|
158
158
|
Day 1: obol init → obol start → first conversation
|
|
159
|
-
→ OBOL
|
|
159
|
+
→ OBOL responds naturally from message one
|
|
160
160
|
→ post-setup hardens your VPS automatically
|
|
161
161
|
|
|
162
162
|
Day 2: Every 5 messages → Haiku extracts facts to vector memory
|
|
@@ -224,8 +224,7 @@ Router: ctx.from.id → tenant context
|
|
|
224
224
|
| GitHub token | Evolution cycle + state |
|
|
225
225
|
| Vercel token | Scripts, tests, commands, apps |
|
|
226
226
|
| VPS hardening | Workspace directory (`~/.obol/users/{id}/`) |
|
|
227
|
-
| Process manager (pm2) |
|
|
228
|
-
| | GitHub backup (per-user repo dir) |
|
|
227
|
+
| Process manager (pm2) | GitHub backup (per-user repo dir) |
|
|
229
228
|
|
|
230
229
|
### Tenant routing
|
|
231
230
|
|
|
@@ -244,11 +243,13 @@ When users store secrets via the `pass` encrypted store, each user gets their ow
|
|
|
244
243
|
| Shared bot credentials | `obol/` | `obol/anthropic-key` |
|
|
245
244
|
| User secrets | `obol/users/{id}/` | `obol/users/206639616/gmail-key` |
|
|
246
245
|
|
|
246
|
+
Users manage their own secrets via Telegram: `/secret set <key> <value>` (message auto-deleted for safety), `/secret list`, `/secret remove <key>`. The agent can also read/write secrets via tools for scripts that need API keys at runtime.
|
|
247
|
+
|
|
247
248
|
### Adding users
|
|
248
249
|
|
|
249
250
|
1. Add their Telegram user ID to `allowedUsers` in `~/.obol/config.json` (or run `obol config`)
|
|
250
251
|
2. Restart the bot
|
|
251
|
-
3. They message the bot → OBOL creates their workspace
|
|
252
|
+
3. They message the bot → OBOL creates their workspace and starts responding immediately. Personality files are created during their first evolution cycle.
|
|
252
253
|
|
|
253
254
|
Each new user starts fresh. Their bot evolves independently from every other user's.
|
|
254
255
|
|
|
@@ -339,7 +340,7 @@ For Telegram user IDs, OBOL auto-detects by checking who messaged the bot. Just
|
|
|
339
340
|
|
|
340
341
|
### First Conversation
|
|
341
342
|
|
|
342
|
-
Send your first message. OBOL
|
|
343
|
+
Send your first message. OBOL responds naturally — no onboarding flow, it works from message one. Personality files (SOUL.md, USER.md) are created during the first evolution cycle. After first boot, it hardens your VPS and reports progress directly in the Telegram chat (Linux only — skipped on macOS/Windows):
|
|
343
344
|
|
|
344
345
|
| Task | What |
|
|
345
346
|
|------|------|
|
|
@@ -445,11 +446,18 @@ Or edit `~/.obol/config.json` directly:
|
|
|
445
446
|
## Telegram Commands
|
|
446
447
|
|
|
447
448
|
```
|
|
448
|
-
/new
|
|
449
|
-
/
|
|
450
|
-
/
|
|
451
|
-
/
|
|
452
|
-
/
|
|
449
|
+
/new — Fresh conversation
|
|
450
|
+
/memory — Search or view memory stats
|
|
451
|
+
/recent — Last 10 memories
|
|
452
|
+
/today — Today's memories
|
|
453
|
+
/tasks — Running background tasks
|
|
454
|
+
/status — Bot status, uptime, evolution progress, traits
|
|
455
|
+
/backup — Trigger GitHub backup
|
|
456
|
+
/clean — Audit workspace, remove rogue files, fix misplaced items
|
|
457
|
+
/traits — View or adjust personality traits (0-100)
|
|
458
|
+
/secret — Manage per-user encrypted secrets
|
|
459
|
+
/evolution — Evolution progress
|
|
460
|
+
/help — Show available commands
|
|
453
461
|
```
|
|
454
462
|
|
|
455
463
|
Everything else is natural conversation.
|
|
@@ -468,6 +476,7 @@ obol logs # Tail logs (pm2 or log file fallback)
|
|
|
468
476
|
obol status # Status
|
|
469
477
|
obol backup # Manual backup
|
|
470
478
|
obol upgrade # Update to latest version
|
|
479
|
+
obol delete # Full VPS cleanup (removes all OBOL data)
|
|
471
480
|
```
|
|
472
481
|
|
|
473
482
|
## Directory Structure
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Self-evolving AI assistant that learns, remembers, and acts on its own. Persistent vector memory, self-rewriting personality, proactive heartbeats.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"grammy": "^1.35.0",
|
|
30
30
|
"inquirer": "^8.2.6",
|
|
31
31
|
"node-cron": "^3.0.3",
|
|
32
|
-
"open": "^8.4.2"
|
|
32
|
+
"open": "^8.4.2",
|
|
33
|
+
"pdfkit": "^0.17.2"
|
|
33
34
|
},
|
|
34
35
|
"engines": {
|
|
35
36
|
"node": ">=18"
|
package/src/claude.js
CHANGED
|
@@ -215,8 +215,17 @@ Model: Use "sonnet" for most things (chat, simple questions, quick tasks, single
|
|
|
215
215
|
history.shift();
|
|
216
216
|
history.shift();
|
|
217
217
|
}
|
|
218
|
-
|
|
219
|
-
history
|
|
218
|
+
while (history.length > 0) {
|
|
219
|
+
const first = history[0];
|
|
220
|
+
if (first.role !== 'user') {
|
|
221
|
+
history.shift();
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(first.content) && first.content.some(b => b.type === 'tool_result')) {
|
|
225
|
+
history.shift();
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
220
229
|
}
|
|
221
230
|
|
|
222
231
|
// Add user message with memory context
|
|
@@ -310,7 +319,13 @@ Model: Use "sonnet" for most things (chat, simple questions, quick tasks, single
|
|
|
310
319
|
}
|
|
311
320
|
}
|
|
312
321
|
|
|
313
|
-
|
|
322
|
+
function injectHistory(chatId, role, content) {
|
|
323
|
+
if (!histories.has(chatId)) histories.set(chatId, []);
|
|
324
|
+
const history = histories.get(chatId);
|
|
325
|
+
history.push({ role, content });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return { chat, client, reloadPersonality, clearHistory, injectHistory };
|
|
314
329
|
}
|
|
315
330
|
|
|
316
331
|
function buildSystemPrompt(personality, userDir, opts = {}) {
|
|
@@ -392,6 +407,7 @@ Use the \`store_secret\`, \`read_secret\`, and \`list_secrets\` tools for all us
|
|
|
392
407
|
These store secrets under the prefix \`${passPrefix}/\` in pass (or JSON fallback).
|
|
393
408
|
|
|
394
409
|
Users can also manage secrets via Telegram: \`/secret set <key> <value>\` (message auto-deleted), \`/secret list\`, \`/secret remove <key>\`.
|
|
410
|
+
Since users can store secrets via /secret outside your conversation, ALWAYS call \`list_secrets\` to check what's available before telling the user their credentials aren't stored.
|
|
395
411
|
|
|
396
412
|
Shared bot credentials live under \`obol/\` — do NOT touch or re-create these:
|
|
397
413
|
\`obol/anthropic-key\`, \`obol/telegram-token\`, \`obol/supabase-url\`, \`obol/supabase-key\`, \`obol/github-token\`, \`obol/vercel-token\`
|
|
@@ -589,6 +605,19 @@ function buildTools(memory, opts = {}) {
|
|
|
589
605
|
},
|
|
590
606
|
});
|
|
591
607
|
|
|
608
|
+
tools.push({
|
|
609
|
+
name: 'send_file',
|
|
610
|
+
description: 'Send a file to the user via Telegram (PDF, image, document, etc). Use after generating files the user requested.',
|
|
611
|
+
input_schema: {
|
|
612
|
+
type: 'object',
|
|
613
|
+
properties: {
|
|
614
|
+
path: { type: 'string', description: 'Path to the file to send' },
|
|
615
|
+
caption: { type: 'string', description: 'Optional caption for the file' },
|
|
616
|
+
},
|
|
617
|
+
required: ['path'],
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
|
|
592
621
|
if (opts.bridgeEnabled) {
|
|
593
622
|
const { buildBridgeTool, buildBridgeTellTool } = require('./bridge');
|
|
594
623
|
tools.push(buildBridgeTool());
|
|
@@ -791,6 +820,18 @@ async function executeToolCall(toolUse, memory, context = {}) {
|
|
|
791
820
|
return keys.join('\n');
|
|
792
821
|
}
|
|
793
822
|
|
|
823
|
+
case 'send_file': {
|
|
824
|
+
const filePath = userDir ? resolveUserPath(input.path, userDir) : input.path;
|
|
825
|
+
if (!fs.existsSync(filePath)) return `File not found: ${filePath}`;
|
|
826
|
+
const telegramCtx = context.ctx;
|
|
827
|
+
if (!telegramCtx) return 'Cannot send files in this context.';
|
|
828
|
+
const { InputFile } = require('grammy');
|
|
829
|
+
await telegramCtx.replyWithDocument(new InputFile(filePath), {
|
|
830
|
+
caption: input.caption || undefined,
|
|
831
|
+
});
|
|
832
|
+
return `Sent: ${path.basename(filePath)}`;
|
|
833
|
+
}
|
|
834
|
+
|
|
794
835
|
case 'bridge_ask': {
|
|
795
836
|
const { bridgeAsk } = require('./bridge');
|
|
796
837
|
return await bridgeAsk(input.question, context.userId, context.config, context._notifyFn, input.partner_id);
|
package/src/defaults/AGENTS.md
CHANGED
|
@@ -41,6 +41,9 @@ Use these tools instead of `exec` for storing/reading secrets — they bypass th
|
|
|
41
41
|
|
|
42
42
|
Users can also manage secrets via Telegram: `/secret set <key> <value>` (message auto-deleted), `/secret list`, `/secret remove <key>`.
|
|
43
43
|
|
|
44
|
+
### Send File (`send_file`)
|
|
45
|
+
Send a file back to the user via Telegram. Use after generating PDFs, images, documents, or any file the user requested.
|
|
46
|
+
|
|
44
47
|
### Bridge (`bridge_ask`, `bridge_tell`)
|
|
45
48
|
Only available if bridge is enabled. Communicate with partner's AI agent.
|
|
46
49
|
|
|
@@ -98,6 +101,19 @@ Rules:
|
|
|
98
101
|
- Temporary files go in /tmp
|
|
99
102
|
- If unsure where something belongs, ask
|
|
100
103
|
|
|
104
|
+
## Self-Extending
|
|
105
|
+
|
|
106
|
+
You can give yourself new capabilities by writing scripts and running them. If the user asks for something you don't have a dedicated tool for (PDF generation, image manipulation, data processing, etc.):
|
|
107
|
+
|
|
108
|
+
1. **Write a script** to `scripts/` using `write_file`
|
|
109
|
+
2. **Install dependencies** if needed via `exec`: `pip3 install fpdf2` or `npm install --prefix . pdfkit`
|
|
110
|
+
3. **Run the script** via `exec`: `python3 scripts/generate-pdf.py` or `node scripts/generate-pdf.js`
|
|
111
|
+
4. **Send the result** to the user with `send_file`
|
|
112
|
+
|
|
113
|
+
Don't tell the user you can't do something — create the capability, then use it.
|
|
114
|
+
|
|
115
|
+
Pre-installed npm packages you can `require()` in Node scripts: `pdfkit`
|
|
116
|
+
|
|
101
117
|
## Scripts & Service Integrations
|
|
102
118
|
|
|
103
119
|
When building scripts (Gmail, Notion, APIs, etc.), prefer **Python**:
|
package/src/telegram.js
CHANGED
|
@@ -254,6 +254,11 @@ function createBot(telegramConfig, config) {
|
|
|
254
254
|
ctx.api.deleteMessage(ctx.chat.id, ctx.message.message_id).catch(() => {});
|
|
255
255
|
credentials.storeSecret(userId, key, value);
|
|
256
256
|
await ctx.reply(`🔑 Secret "${key}" stored securely.`);
|
|
257
|
+
const tenant = await getTenant(userId, config);
|
|
258
|
+
if (tenant.claude?.injectHistory) {
|
|
259
|
+
tenant.claude.injectHistory(ctx.chat.id, 'user', `[System: user stored secret "${key}" via /secret set]`);
|
|
260
|
+
tenant.claude.injectHistory(ctx.chat.id, 'assistant', `Noted — secret "${key}" is now stored.`);
|
|
261
|
+
}
|
|
257
262
|
} catch (e) {
|
|
258
263
|
await ctx.reply(`⚠️ ${e.message}`);
|
|
259
264
|
}
|
|
@@ -264,6 +269,11 @@ function createBot(telegramConfig, config) {
|
|
|
264
269
|
try {
|
|
265
270
|
credentials.removeSecret(userId, args[1]);
|
|
266
271
|
await ctx.reply(`🗑️ Secret "${args[1]}" removed.`);
|
|
272
|
+
const tenant = await getTenant(userId, config);
|
|
273
|
+
if (tenant.claude?.injectHistory) {
|
|
274
|
+
tenant.claude.injectHistory(ctx.chat.id, 'user', `[System: user removed secret "${args[1]}" via /secret remove]`);
|
|
275
|
+
tenant.claude.injectHistory(ctx.chat.id, 'assistant', `Noted — secret "${args[1]}" has been removed.`);
|
|
276
|
+
}
|
|
267
277
|
} catch (e) {
|
|
268
278
|
await ctx.reply(`⚠️ ${e.message}`);
|
|
269
279
|
}
|