obol-ai 0.2.36 → 0.2.38
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 +7 -0
- package/README.md +2 -0
- package/docs/obol-upgrade.png +0 -0
- package/package.json +1 -1
- package/src/backup.js +3 -2
- package/src/claude/chat.js +3 -3
- package/src/claude/prompt.js +2 -1
- package/src/cli/config.js +75 -1
- package/src/tenant.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## 0.2.38
|
|
2
|
+
- use bot name from config in system prompt, backup, and personality files
|
|
3
|
+
|
|
4
|
+
## 0.2.37
|
|
5
|
+
- add rename user option to cli config
|
|
6
|
+
- add upgrade screenshot to readme
|
|
7
|
+
|
|
1
8
|
## 0.2.36
|
|
2
9
|
- changelog and issues updates
|
|
3
10
|
- auto-send tts voice summary when tts is enabled
|
package/README.md
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obol-ai",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.38",
|
|
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(
|
|
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'];
|
package/src/claude/chat.js
CHANGED
|
@@ -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) {
|
package/src/claude/prompt.js
CHANGED
|
@@ -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(
|
|
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
|
{
|
|
@@ -162,6 +163,7 @@ async function manageUsers(cfg) {
|
|
|
162
163
|
choices: [
|
|
163
164
|
{ name: 'Add user (detect from bot messages)', value: 'detect' },
|
|
164
165
|
{ name: 'Add user (enter ID manually)', value: 'manual' },
|
|
166
|
+
...(currentUsers.length > 0 ? [{ name: 'Rename user', value: 'rename' }] : []),
|
|
165
167
|
...(currentUsers.length > 0 ? [{ name: 'Remove user', value: 'remove' }] : []),
|
|
166
168
|
new inquirer.Separator(),
|
|
167
169
|
{ name: 'Back', value: 'back' },
|
|
@@ -232,6 +234,35 @@ async function manageUsers(cfg) {
|
|
|
232
234
|
}
|
|
233
235
|
}
|
|
234
236
|
|
|
237
|
+
if (action === 'rename') {
|
|
238
|
+
const { renameId } = await inquirer.prompt([{
|
|
239
|
+
type: 'list',
|
|
240
|
+
name: 'renameId',
|
|
241
|
+
message: 'Rename which user?',
|
|
242
|
+
choices: [
|
|
243
|
+
...currentUsers.map(id => {
|
|
244
|
+
const name = cfg.users?.[String(id)]?.name;
|
|
245
|
+
return { name: name ? `${id} — ${name}` : String(id), value: id };
|
|
246
|
+
}),
|
|
247
|
+
new inquirer.Separator(),
|
|
248
|
+
{ name: 'Cancel', value: null },
|
|
249
|
+
],
|
|
250
|
+
}]);
|
|
251
|
+
if (renameId !== null) {
|
|
252
|
+
const currentName = cfg.users?.[String(renameId)]?.name || '';
|
|
253
|
+
const { newName } = await inquirer.prompt([{
|
|
254
|
+
type: 'input',
|
|
255
|
+
name: 'newName',
|
|
256
|
+
message: `Name for user ${renameId}:`,
|
|
257
|
+
default: currentName,
|
|
258
|
+
validate: (v) => v.trim().length > 0 ? true : 'Required',
|
|
259
|
+
}]);
|
|
260
|
+
if (!cfg.users) cfg.users = {};
|
|
261
|
+
if (!cfg.users[String(renameId)]) cfg.users[String(renameId)] = {};
|
|
262
|
+
cfg.users[String(renameId)].name = newName.trim();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
235
266
|
if (action === 'remove') {
|
|
236
267
|
const { removeId } = await inquirer.prompt([{
|
|
237
268
|
type: 'list',
|
|
@@ -321,6 +352,42 @@ async function runOAuthFlow(cfg) {
|
|
|
321
352
|
}
|
|
322
353
|
}
|
|
323
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
|
+
|
|
324
391
|
async function config() {
|
|
325
392
|
const cfg = loadConfig({ resolve: false });
|
|
326
393
|
if (!cfg) {
|
|
@@ -392,6 +459,8 @@ async function config() {
|
|
|
392
459
|
}
|
|
393
460
|
|
|
394
461
|
const currentVal = getNestedValue(cfg, field.key);
|
|
462
|
+
const oldBotName = cfg.bot?.name;
|
|
463
|
+
const oldOwnerName = cfg.owner?.name;
|
|
395
464
|
|
|
396
465
|
if (field.type === 'boolean') {
|
|
397
466
|
const { newVal } = await inquirer.prompt([{
|
|
@@ -440,6 +509,11 @@ async function config() {
|
|
|
440
509
|
}
|
|
441
510
|
|
|
442
511
|
saveConfig(cfg);
|
|
512
|
+
|
|
513
|
+
if (section === 'Identity') {
|
|
514
|
+
updatePersonalityNames(oldBotName, cfg.bot?.name, oldOwnerName, cfg.owner?.name);
|
|
515
|
+
}
|
|
516
|
+
|
|
443
517
|
console.log(' ✅ Saved\n');
|
|
444
518
|
}
|
|
445
519
|
|
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;
|