obol-ai 0.2.37 → 0.2.39

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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.2.39
2
+ - use bot name from config in all telegram status messages
3
+ - update changelog
4
+
5
+ ## 0.2.38
6
+ - use bot name from config in system prompt, backup, and personality files
7
+
1
8
  ## 0.2.37
2
9
  - add rename user option to cli config
3
10
  - add upgrade screenshot to readme
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "obol-ai",
3
- "version": "0.2.37",
3
+ "version": "0.2.39",
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": {
package/src/backup.js CHANGED
@@ -2,7 +2,7 @@ const cron = require('node-cron');
2
2
  const { execSync } = require('child_process');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
- const { OBOL_DIR } = require('./config');
5
+ const { OBOL_DIR, loadConfig } = require('./config');
6
6
 
7
7
  function setupBackup(githubConfig) {
8
8
  const { listUsers } = require('./config');
@@ -30,6 +30,7 @@ async function runBackup(githubConfig, commitMessage, userDir) {
30
30
  const baseDir = userDir || OBOL_DIR;
31
31
  const backupDir = path.join(baseDir, '.backup-repo');
32
32
  const repoUrl = `https://${token}@github.com/${username}/${repo}.git`;
33
+ const botName = loadConfig({ resolve: false })?.bot?.name || 'OBOL';
33
34
 
34
35
  if (!fs.existsSync(path.join(backupDir, '.git'))) {
35
36
  execSync(`git clone ${repoUrl} "${backupDir}"`, { stdio: 'pipe' });
@@ -37,7 +38,7 @@ async function runBackup(githubConfig, commitMessage, userDir) {
37
38
  execSync('git pull', { cwd: backupDir, stdio: 'pipe' });
38
39
  }
39
40
 
40
- execSync('git config user.name "OBOL"', { cwd: backupDir });
41
+ execSync(`git config user.name "${botName}"`, { cwd: backupDir });
41
42
  execSync('git config user.email "obol@backup"', { cwd: backupDir });
42
43
 
43
44
  const syncDirs = ['personality', 'scripts', 'tests', 'commands', 'apps'];
@@ -8,10 +8,10 @@ const { buildTools, buildRunnableTools } = require('./tool-registry');
8
8
  const { withCacheBreakpoints, sanitizeMessages } = require('./cache');
9
9
  const { getMaxToolIterations } = require('./constants');
10
10
 
11
- function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR, bridgeEnabled }) {
11
+ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR, bridgeEnabled, botName }) {
12
12
  let client = createAnthropicClient(anthropicConfig);
13
13
 
14
- let baseSystemPrompt = buildSystemPrompt(personality, userDir, { bridgeEnabled });
14
+ let baseSystemPrompt = buildSystemPrompt(personality, userDir, { bridgeEnabled, botName });
15
15
 
16
16
  const histories = new ChatHistory(50);
17
17
  const chatLocks = new Map();
@@ -266,7 +266,7 @@ function createClaude(anthropicConfig, { personality, memory, userDir = OBOL_DIR
266
266
  const newPersonality = require('../personality').loadPersonality(pDir);
267
267
  for (const key of Object.keys(personality)) delete personality[key];
268
268
  Object.assign(personality, newPersonality);
269
- baseSystemPrompt = buildSystemPrompt(personality, userDir, { bridgeEnabled });
269
+ baseSystemPrompt = buildSystemPrompt(personality, userDir, { bridgeEnabled, botName });
270
270
  }
271
271
 
272
272
  function clearHistory(chatId) {
@@ -3,8 +3,9 @@ const path = require('path');
3
3
 
4
4
  function buildSystemPrompt(personality, userDir, opts = {}) {
5
5
  const parts = [];
6
+ const botName = opts.botName || 'OBOL';
6
7
 
7
- parts.push('You are OBOL, a personal AI agent running 24/7 on a server. You have persistent memory, can execute shell commands, deploy websites, and learn over time. You are not a generic chatbot — you are a dedicated agent for one person.');
8
+ parts.push(`You are ${botName}, a personal AI agent running 24/7 on a server. You have persistent memory, can execute shell commands, deploy websites, and learn over time. You are not a generic chatbot — you are a dedicated agent for one person.`);
8
9
 
9
10
  if (personality.soul) {
10
11
  parts.push(`\n## Personality\n${personality.soul}`);
package/src/cli/config.js CHANGED
@@ -1,8 +1,9 @@
1
1
  const inquirer = require('inquirer');
2
- const { loadConfig, saveConfig, CONFIG_FILE, ensureUserDir, getUserDir } = require('../config');
2
+ const { loadConfig, saveConfig, CONFIG_FILE, ensureUserDir, getUserDir, USERS_DIR } = require('../config');
3
3
  const { generatePKCE, buildAuthorizationUrl, exchangeCodeForTokens } = require('../oauth');
4
4
  const { spawnSync } = require('child_process');
5
5
  const fs = require('fs');
6
+ const path = require('path');
6
7
 
7
8
  const SECTIONS = [
8
9
  {
@@ -351,6 +352,42 @@ async function runOAuthFlow(cfg) {
351
352
  }
352
353
  }
353
354
 
355
+ function updatePersonalityNames(oldBotName, newBotName, oldOwnerName, newOwnerName) {
356
+ if (!fs.existsSync(USERS_DIR)) return;
357
+ const users = fs.readdirSync(USERS_DIR).filter(u => {
358
+ try { return fs.statSync(path.join(USERS_DIR, u)).isDirectory(); } catch { return false; }
359
+ });
360
+ for (const userId of users) {
361
+ const personalityDir = path.join(USERS_DIR, userId, 'personality');
362
+ if (!fs.existsSync(personalityDir)) continue;
363
+
364
+ if (oldBotName !== newBotName) {
365
+ const soulPath = path.join(personalityDir, 'SOUL.md');
366
+ if (fs.existsSync(soulPath)) {
367
+ let content = fs.readFileSync(soulPath, 'utf-8');
368
+ content = content.replace(new RegExp(`# SOUL\\.md — Who is ${oldBotName}\\?`, 'g'), `# SOUL.md — Who is ${newBotName}?`);
369
+ content = content.replace(new RegExp(`\\*\\*Name:\\*\\* ${oldBotName}`, 'g'), `**Name:** ${newBotName}`);
370
+ fs.writeFileSync(soulPath, content, 'utf-8');
371
+ }
372
+ const agentsPath = path.join(personalityDir, 'AGENTS.md');
373
+ if (fs.existsSync(agentsPath)) {
374
+ let content = fs.readFileSync(agentsPath, 'utf-8');
375
+ content = content.replace(new RegExp(`# AGENTS\\.md — How ${oldBotName} Works`, 'g'), `# AGENTS.md — How ${newBotName} Works`);
376
+ fs.writeFileSync(agentsPath, content, 'utf-8');
377
+ }
378
+ }
379
+
380
+ if (oldOwnerName !== newOwnerName) {
381
+ const soulPath = path.join(personalityDir, 'SOUL.md');
382
+ if (fs.existsSync(soulPath)) {
383
+ let content = fs.readFileSync(soulPath, 'utf-8');
384
+ content = content.replace(new RegExp(`\\*\\*Created by:\\*\\* ${oldOwnerName}`, 'g'), `**Created by:** ${newOwnerName}`);
385
+ fs.writeFileSync(soulPath, content, 'utf-8');
386
+ }
387
+ }
388
+ }
389
+ }
390
+
354
391
  async function config() {
355
392
  const cfg = loadConfig({ resolve: false });
356
393
  if (!cfg) {
@@ -422,6 +459,8 @@ async function config() {
422
459
  }
423
460
 
424
461
  const currentVal = getNestedValue(cfg, field.key);
462
+ const oldBotName = cfg.bot?.name;
463
+ const oldOwnerName = cfg.owner?.name;
425
464
 
426
465
  if (field.type === 'boolean') {
427
466
  const { newVal } = await inquirer.prompt([{
@@ -470,6 +509,11 @@ async function config() {
470
509
  }
471
510
 
472
511
  saveConfig(cfg);
512
+
513
+ if (section === 'Identity') {
514
+ updatePersonalityNames(oldBotName, cfg.bot?.name, oldOwnerName, cfg.owner?.name);
515
+ }
516
+
473
517
  console.log(' ✅ Saved\n');
474
518
  }
475
519
 
@@ -9,6 +9,7 @@ const { sendHtml, splitMessage, startTyping } = require('../utils');
9
9
  const pkg = require('../../../package.json');
10
10
 
11
11
  function register(bot, config, createAsk) {
12
+ const botName = config.bot?.name || 'OBOL';
12
13
  bot.command('backup', async (ctx) => {
13
14
  if (!ctx.from) return;
14
15
  try {
@@ -173,7 +174,7 @@ Summarize what was cleaned and secrets migrated.`);
173
174
  const current = getMaxToolIterations();
174
175
 
175
176
  if (!args[0]) {
176
- await ctx.reply(`🔧 Max tool iterations: ${current}\n\nThis limits how many tool calls OBOL can make per message. Higher = more complex tasks, but slower responses.\n\nSet: /toolimit <number>\nExample: /toolimit 50`);
177
+ await ctx.reply(`🔧 Max tool iterations: ${current}\n\nThis limits how many tool calls ${botName} can make per message. Higher = more complex tasks, but slower responses.\n\nSet: /toolimit <number>\nExample: /toolimit 50`);
177
178
  return;
178
179
  }
179
180
 
@@ -3,8 +3,9 @@ const { TERM_SEP } = require('../constants');
3
3
  const pkg = require('../../../package.json');
4
4
 
5
5
  function register(bot, config) {
6
+ const botName = config.bot?.name || 'OBOL';
6
7
  bot.command('start', async (ctx) => {
7
- await ctx.reply(`<pre>◈ OBOL v${pkg.version}\n${TERM_SEP}\nSYSTEM ONLINE\n${TERM_SEP}</pre>`, { parse_mode: 'HTML' });
8
+ await ctx.reply(`<pre>◈ ${botName} v${pkg.version}\n${TERM_SEP}\nSYSTEM ONLINE\n${TERM_SEP}</pre>`, { parse_mode: 'HTML' });
8
9
  });
9
10
 
10
11
  bot.command('new', async (ctx) => {
@@ -7,6 +7,7 @@ const { termBar, formatTraits } = require('../utils');
7
7
  const { TERM_SEP } = require('../constants');
8
8
 
9
9
  function register(bot, config) {
10
+ const botName = config.bot?.name || 'OBOL';
10
11
  bot.command('status', async (ctx) => {
11
12
  if (!ctx.from) return;
12
13
  const tenant = await getTenant(ctx.from.id, config);
@@ -18,7 +19,7 @@ function register(bot, config) {
18
19
  const pkg = require('../../../package.json');
19
20
 
20
21
  const lines = [
21
- `◈ OBOL SYSTEM STATUS`,
22
+ `◈ ${botName} SYSTEM STATUS`,
22
23
  TERM_SEP,
23
24
  ``,
24
25
  `RUNTIME`,
@@ -66,7 +67,7 @@ function register(bot, config) {
66
67
  const state = loadEvolutionState(tenant.userDir);
67
68
 
68
69
  const lines = [
69
- `◈ OBOL EVOLUTION CYCLE`,
70
+ `◈ ${botName} EVOLUTION CYCLE`,
70
71
  TERM_SEP,
71
72
  ``,
72
73
  ` ${state.evolutionCount || 0} completed`,
@@ -108,7 +109,7 @@ function register(bot, config) {
108
109
  if (!ctx.from) return;
109
110
  const tenant = await getTenant(ctx.from.id, config);
110
111
  const running = tenant.bg.getStatus();
111
- const lines = [`◈ OBOL ACTIVE TASKS`, TERM_SEP];
112
+ const lines = [`◈ ${botName} ACTIVE TASKS`, TERM_SEP];
112
113
  if (running.length === 0) {
113
114
  lines.push(``, ` (none)`);
114
115
  } else {
@@ -5,6 +5,7 @@ const { formatTraits } = require('../utils');
5
5
  const { TERM_SEP } = require('../constants');
6
6
 
7
7
  function register(bot, config) {
8
+ const botName = config.bot?.name || 'OBOL';
8
9
  bot.command('traits', async (ctx) => {
9
10
  if (!ctx.from) return;
10
11
  const tenant = await getTenant(ctx.from.id, config);
@@ -15,7 +16,7 @@ function register(bot, config) {
15
16
  saveTraits(personalityDir, { ...DEFAULT_TRAITS });
16
17
  tenant.claude.reloadPersonality();
17
18
  const traits = { ...DEFAULT_TRAITS };
18
- const lines = [`◈ OBOL PERSONALITY MATRIX`, TERM_SEP, `RESET TO DEFAULTS`, ``, formatTraits(traits), TERM_SEP];
19
+ const lines = [`◈ ${botName} PERSONALITY MATRIX`, TERM_SEP, `RESET TO DEFAULTS`, ``, formatTraits(traits), TERM_SEP];
19
20
  await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
20
21
  return;
21
22
  }
@@ -35,13 +36,13 @@ function register(bot, config) {
35
36
  traits[traitName] = value;
36
37
  saveTraits(personalityDir, traits);
37
38
  tenant.claude.reloadPersonality();
38
- const lines = [`◈ OBOL PERSONALITY MATRIX`, TERM_SEP, `UPDATED ${traitName} → ${value}`, ``, formatTraits(traits), TERM_SEP];
39
+ const lines = [`◈ ${botName} PERSONALITY MATRIX`, TERM_SEP, `UPDATED ${traitName} → ${value}`, ``, formatTraits(traits), TERM_SEP];
39
40
  await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
40
41
  return;
41
42
  }
42
43
 
43
44
  const traits = loadTraits(personalityDir);
44
- const lines = [`◈ OBOL PERSONALITY MATRIX`, TERM_SEP, ``, formatTraits(traits), ``, `/traits &lt;name&gt; &lt;0-100&gt;`, `/traits reset`, TERM_SEP];
45
+ const lines = [`◈ ${botName} PERSONALITY MATRIX`, TERM_SEP, ``, formatTraits(traits), ``, `/traits &lt;name&gt; &lt;0-100&gt;`, `/traits reset`, TERM_SEP];
45
46
  await ctx.reply(`<pre>${lines.join('\n')}</pre>`, { parse_mode: 'HTML' });
46
47
  });
47
48
  }
package/src/tenant.js CHANGED
@@ -34,7 +34,7 @@ async function createTenant(userId, config) {
34
34
  const personality = loadPersonality(personalityDir);
35
35
  const memory = config.supabase ? await createMemory(config.supabase, userId) : null;
36
36
  const bridgeEnabled = isBridgeEnabled(config) && (config.telegram?.allowedUsers?.length || 0) >= 2;
37
- const claude = createClaude(config.anthropic, { personality, memory, userDir, bridgeEnabled });
37
+ const claude = createClaude(config.anthropic, { personality, memory, userDir, bridgeEnabled, botName: config.bot?.name });
38
38
  const messageLog = config.supabase ? createMessageLog(config.supabase, memory, config.anthropic, userId, userDir) : null;
39
39
  const scheduler = config.supabase ? createScheduler(config.supabase, userId) : null;
40
40
  const toolPrefsApi = config.supabase ? createToolPrefs(config.supabase, userId) : null;