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 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, workspace, and first-run experience. User A's personality drift, scripts, and memories never leak into User B's. Everything runs in a single process with shared API credentials.
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 asks 2-3 questions, writes SOUL.md + USER.md
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) | First-run onboarding experience |
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, runs first-run onboarding, and writes their own SOUL.md + USER.md
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 introduces itself, asks 2-3 questions, then writes its own SOUL.md and USER.md. After that, it hardens your VPS and reports progress directly in the Telegram chat (Linux only — skipped on macOS/Windows):
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 — Fresh conversation
449
- /tasks Running background tasks
450
- /status Uptime and memory stats
451
- /backup Trigger GitHub backup
452
- /clean Audit workspace, remove rogue files, fix misplaced items
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.1",
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
- if (history.length > 0 && history[0].role !== 'user') {
219
- history.shift();
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
- return { chat, client, reloadPersonality, clearHistory };
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);
@@ -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
  }