@kernel.chat/kbot 3.72.0 → 3.73.0
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/dist/buddy.d.ts +21 -0
- package/dist/buddy.js +97 -1
- package/dist/cli.js +57 -1
- package/dist/tools/buddy-tools.js +55 -2
- package/package.json +1 -1
package/dist/buddy.d.ts
CHANGED
|
@@ -74,6 +74,27 @@ export declare function addBuddyXP(amount: number): {
|
|
|
74
74
|
levelInfo: BuddyLevelInfo;
|
|
75
75
|
leveledUp: boolean;
|
|
76
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* Sync buddy stats to the cloud leaderboard.
|
|
79
|
+
* Anonymous — uses a SHA-256 hash of hostname+homedir, not user identity.
|
|
80
|
+
* Requires a kernel.chat token (cloud sync enabled).
|
|
81
|
+
*/
|
|
82
|
+
export declare function syncBuddyToCloud(): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Fetch the buddy leaderboard from the cloud.
|
|
85
|
+
* Returns ranked entries sorted by XP descending.
|
|
86
|
+
*/
|
|
87
|
+
export declare function fetchBuddyLeaderboard(opts?: {
|
|
88
|
+
limit?: number;
|
|
89
|
+
species?: string;
|
|
90
|
+
}): Promise<Array<{
|
|
91
|
+
species: string;
|
|
92
|
+
level: number;
|
|
93
|
+
xp: number;
|
|
94
|
+
achievement_count: number;
|
|
95
|
+
sessions: number;
|
|
96
|
+
rank: number;
|
|
97
|
+
}>>;
|
|
77
98
|
/**
|
|
78
99
|
* Get the buddy's current level info without modifying state.
|
|
79
100
|
* Includes level, XP, XP to next level, and species-specific title.
|
package/dist/buddy.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// Achievements: milestones that unlock as the user uses kbot. Persisted in buddy.json.
|
|
8
8
|
//
|
|
9
9
|
// Persists buddy name + achievements to ~/.kbot/buddy.json
|
|
10
|
-
import { homedir } from 'node:os';
|
|
10
|
+
import { homedir, hostname } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
13
13
|
import { createHash } from 'node:crypto';
|
|
@@ -15,6 +15,7 @@ import { createInterface } from 'node:readline';
|
|
|
15
15
|
import { getDreamStatus, getDreamPrompt } from './dream.js';
|
|
16
16
|
import { getExtendedStats, getProfileSummary } from './learning.js';
|
|
17
17
|
import { getToolMetrics } from './tools/index.js';
|
|
18
|
+
import { getCloudToken } from './cloud-sync.js';
|
|
18
19
|
// ── Paths ──
|
|
19
20
|
const KBOT_DIR = join(homedir(), '.kbot');
|
|
20
21
|
const BUDDY_FILE = join(KBOT_DIR, 'buddy.json');
|
|
@@ -930,6 +931,8 @@ export function addBuddyXP(amount) {
|
|
|
930
931
|
config.evolution = evo;
|
|
931
932
|
saveBuddyConfig(config);
|
|
932
933
|
cachedEvolution = evo;
|
|
934
|
+
// Debounced cloud sync — leaderboard update
|
|
935
|
+
scheduleBuddySync();
|
|
933
936
|
const species = resolveSpecies();
|
|
934
937
|
const nextLevel = (newLevel < 3 ? (newLevel + 1) : null);
|
|
935
938
|
const xpToNext = nextLevel !== null ? LEVEL_THRESHOLDS[nextLevel] - evo.xp : null;
|
|
@@ -943,6 +946,99 @@ export function addBuddyXP(amount) {
|
|
|
943
946
|
leveledUp,
|
|
944
947
|
};
|
|
945
948
|
}
|
|
949
|
+
// ── Cloud Sync — Buddy Leaderboard ──
|
|
950
|
+
const ENGINE_URL = 'https://eoxxpyixdieprsxlpwcs.supabase.co/functions/v1/kbot-engine';
|
|
951
|
+
const BUDDY_SYNC_DEBOUNCE_MS = 5 * 60 * 1000; // max once per 5 minutes
|
|
952
|
+
let buddySyncTimer = null;
|
|
953
|
+
let lastBuddySync = 0;
|
|
954
|
+
/** Generate an anonymous device hash from hostname + homedir */
|
|
955
|
+
function getDeviceHash() {
|
|
956
|
+
return createHash('sha256')
|
|
957
|
+
.update(`${hostname()}:${homedir()}`)
|
|
958
|
+
.digest('hex');
|
|
959
|
+
}
|
|
960
|
+
/**
|
|
961
|
+
* Sync buddy stats to the cloud leaderboard.
|
|
962
|
+
* Anonymous — uses a SHA-256 hash of hostname+homedir, not user identity.
|
|
963
|
+
* Requires a kernel.chat token (cloud sync enabled).
|
|
964
|
+
*/
|
|
965
|
+
export async function syncBuddyToCloud() {
|
|
966
|
+
const token = getCloudToken();
|
|
967
|
+
if (!token)
|
|
968
|
+
return false;
|
|
969
|
+
try {
|
|
970
|
+
const buddy = getBuddy();
|
|
971
|
+
const lvl = getBuddyLevel();
|
|
972
|
+
const achievements = getAchievements();
|
|
973
|
+
const stats = getExtendedStats();
|
|
974
|
+
const unlockedCount = achievements.filter(a => a.unlockedAt !== null).length;
|
|
975
|
+
const res = await fetch(`${ENGINE_URL}/sync`, {
|
|
976
|
+
method: 'POST',
|
|
977
|
+
headers: {
|
|
978
|
+
'Content-Type': 'application/json',
|
|
979
|
+
'Authorization': `Bearer ${token}`,
|
|
980
|
+
},
|
|
981
|
+
body: JSON.stringify({
|
|
982
|
+
action: 'buddy_sync',
|
|
983
|
+
device_hash: getDeviceHash(),
|
|
984
|
+
species: buddy.species,
|
|
985
|
+
level: lvl.level,
|
|
986
|
+
xp: lvl.xp,
|
|
987
|
+
achievement_count: unlockedCount,
|
|
988
|
+
sessions: stats.sessions,
|
|
989
|
+
}),
|
|
990
|
+
signal: AbortSignal.timeout(10_000),
|
|
991
|
+
});
|
|
992
|
+
return res.ok;
|
|
993
|
+
}
|
|
994
|
+
catch {
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
/** Debounced buddy sync — called from addBuddyXP, max once per 5 minutes */
|
|
999
|
+
function scheduleBuddySync() {
|
|
1000
|
+
const now = Date.now();
|
|
1001
|
+
if (now - lastBuddySync < BUDDY_SYNC_DEBOUNCE_MS)
|
|
1002
|
+
return;
|
|
1003
|
+
if (buddySyncTimer)
|
|
1004
|
+
return;
|
|
1005
|
+
buddySyncTimer = setTimeout(() => {
|
|
1006
|
+
buddySyncTimer = null;
|
|
1007
|
+
lastBuddySync = Date.now();
|
|
1008
|
+
syncBuddyToCloud().catch(() => { }); // fire and forget
|
|
1009
|
+
}, 1000); // short delay to batch rapid XP gains
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Fetch the buddy leaderboard from the cloud.
|
|
1013
|
+
* Returns ranked entries sorted by XP descending.
|
|
1014
|
+
*/
|
|
1015
|
+
export async function fetchBuddyLeaderboard(opts) {
|
|
1016
|
+
const token = getCloudToken();
|
|
1017
|
+
if (!token)
|
|
1018
|
+
return [];
|
|
1019
|
+
try {
|
|
1020
|
+
const res = await fetch(`${ENGINE_URL}/sync`, {
|
|
1021
|
+
method: 'POST',
|
|
1022
|
+
headers: {
|
|
1023
|
+
'Content-Type': 'application/json',
|
|
1024
|
+
'Authorization': `Bearer ${token}`,
|
|
1025
|
+
},
|
|
1026
|
+
body: JSON.stringify({
|
|
1027
|
+
action: 'buddy_leaderboard',
|
|
1028
|
+
limit: opts?.limit ?? 50,
|
|
1029
|
+
...(opts?.species ? { species: opts.species } : {}),
|
|
1030
|
+
}),
|
|
1031
|
+
signal: AbortSignal.timeout(10_000),
|
|
1032
|
+
});
|
|
1033
|
+
if (!res.ok)
|
|
1034
|
+
return [];
|
|
1035
|
+
const data = await res.json();
|
|
1036
|
+
return data.leaderboard ?? [];
|
|
1037
|
+
}
|
|
1038
|
+
catch {
|
|
1039
|
+
return [];
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
946
1042
|
/**
|
|
947
1043
|
* Get the buddy's current level info without modifying state.
|
|
948
1044
|
* Includes level, XP, XP to next level, and species-specific title.
|
package/dist/cli.js
CHANGED
|
@@ -27,7 +27,7 @@ import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, pr
|
|
|
27
27
|
import { checkForUpdate, selfUpdate } from './updater.js';
|
|
28
28
|
import { runTutorial } from './tutorial.js';
|
|
29
29
|
import { syncOnStartup, schedulePush, flushCloudSync, isCloudSyncEnabled, setCloudToken, getCloudToken } from './cloud-sync.js';
|
|
30
|
-
import { getBuddy, getBuddyGreeting, formatBuddyStatus, getBuddyDreamNarration, renameBuddy, buddyChat, getAchievements, getBuddyLevel } from './buddy.js';
|
|
30
|
+
import { getBuddy, getBuddyGreeting, formatBuddyStatus, getBuddyDreamNarration, renameBuddy, buddyChat, getAchievements, getBuddyLevel, fetchBuddyLeaderboard } from './buddy.js';
|
|
31
31
|
import chalk from 'chalk';
|
|
32
32
|
import { createRequire } from 'node:module';
|
|
33
33
|
const __require = createRequire(import.meta.url);
|
|
@@ -3579,6 +3579,62 @@ async function main() {
|
|
|
3579
3579
|
console.log();
|
|
3580
3580
|
process.exit(0);
|
|
3581
3581
|
});
|
|
3582
|
+
buddyCmd
|
|
3583
|
+
.command('leaderboard')
|
|
3584
|
+
.description('Show the global buddy leaderboard — anonymous rankings across all kbot installs')
|
|
3585
|
+
.option('-l, --limit <n>', 'Number of entries to show', '20')
|
|
3586
|
+
.option('-s, --species <species>', 'Filter by species (fox, owl, cat, robot, ghost, mushroom, octopus, dragon)')
|
|
3587
|
+
.action(async (opts) => {
|
|
3588
|
+
const limit = Math.min(Math.max(parseInt(opts.limit, 10) || 20, 1), 200);
|
|
3589
|
+
const species = opts.species?.toLowerCase();
|
|
3590
|
+
const validSpecies = ['fox', 'owl', 'cat', 'robot', 'ghost', 'mushroom', 'octopus', 'dragon'];
|
|
3591
|
+
if (species && !validSpecies.includes(species)) {
|
|
3592
|
+
printError(`Unknown species "${species}". Valid: ${validSpecies.join(', ')}`);
|
|
3593
|
+
process.exit(1);
|
|
3594
|
+
}
|
|
3595
|
+
printInfo('Fetching leaderboard...');
|
|
3596
|
+
const entries = await fetchBuddyLeaderboard({ limit, species });
|
|
3597
|
+
if (entries.length === 0) {
|
|
3598
|
+
console.log();
|
|
3599
|
+
printWarn('No entries on the leaderboard yet.');
|
|
3600
|
+
printInfo('Use kbot to earn XP and enable cloud sync to appear on the leaderboard.');
|
|
3601
|
+
console.log();
|
|
3602
|
+
process.exit(0);
|
|
3603
|
+
}
|
|
3604
|
+
const SPECIES_ICONS = {
|
|
3605
|
+
fox: '[fox]', owl: '[owl]', cat: '[cat]', robot: '[bot]',
|
|
3606
|
+
ghost: '[gho]', mushroom: '[msh]', octopus: '[oct]', dragon: '[drg]',
|
|
3607
|
+
};
|
|
3608
|
+
const LEVEL_TITLES_SHORT = {
|
|
3609
|
+
0: 'Novice', 1: 'Adept', 2: 'Master', 3: 'Legend',
|
|
3610
|
+
};
|
|
3611
|
+
const header = species
|
|
3612
|
+
? `Buddy Leaderboard — ${species}`
|
|
3613
|
+
: 'Global Buddy Leaderboard';
|
|
3614
|
+
console.log();
|
|
3615
|
+
console.log(` ${chalk.bold(header)}`);
|
|
3616
|
+
console.log(` ${chalk.dim('─'.repeat(56))}`);
|
|
3617
|
+
console.log(` ${chalk.dim('#'.padStart(3))} ${chalk.dim('Species'.padEnd(7))} ${chalk.dim('Level'.padEnd(12))} ${chalk.dim('XP'.padStart(6))} ${chalk.dim('Achv'.padStart(4))} ${chalk.dim('Sessions'.padStart(8))}`);
|
|
3618
|
+
console.log(` ${chalk.dim('─'.repeat(56))}`);
|
|
3619
|
+
for (const entry of entries) {
|
|
3620
|
+
const icon = SPECIES_ICONS[entry.species] || entry.species.slice(0, 5);
|
|
3621
|
+
const title = LEVEL_TITLES_SHORT[entry.level] ?? `L${entry.level}`;
|
|
3622
|
+
const levelStr = `${entry.level} ${title}`;
|
|
3623
|
+
const rankStr = String(entry.rank).padStart(3);
|
|
3624
|
+
const xpStr = String(entry.xp).padStart(6);
|
|
3625
|
+
const achvStr = String(entry.achievement_count).padStart(4);
|
|
3626
|
+
const sessStr = String(entry.sessions).padStart(8);
|
|
3627
|
+
// Highlight top 3
|
|
3628
|
+
const rankColor = entry.rank === 1 ? chalk.hex('#FFD700') :
|
|
3629
|
+
entry.rank === 2 ? chalk.hex('#C0C0C0') :
|
|
3630
|
+
entry.rank === 3 ? chalk.hex('#CD7F32') : chalk.white;
|
|
3631
|
+
console.log(` ${rankColor(rankStr)} ${chalk.hex('#A78BFA')(icon.padEnd(7))} ${levelStr.padEnd(12)} ${chalk.hex('#4ADE80')(xpStr)} ${achvStr} ${chalk.dim(sessStr)}`);
|
|
3632
|
+
}
|
|
3633
|
+
console.log();
|
|
3634
|
+
console.log(` ${chalk.dim(`${entries.length} entries shown`)}`);
|
|
3635
|
+
console.log();
|
|
3636
|
+
process.exit(0);
|
|
3637
|
+
});
|
|
3582
3638
|
buddyCmd.action(() => {
|
|
3583
3639
|
buddyCmd.commands.find(c => c.name() === 'status')?.parse(['', '', 'status']);
|
|
3584
3640
|
});
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// kbot Buddy Tools — Interact with your terminal companion
|
|
2
2
|
//
|
|
3
|
-
//
|
|
3
|
+
// Five tools:
|
|
4
4
|
// buddy_status — Show buddy name, species, mood, and sprite
|
|
5
5
|
// buddy_rename — Give your buddy a custom name (persisted to ~/.kbot/buddy.json)
|
|
6
6
|
// buddy_achievements — Show all achievements with unlock status and progress
|
|
7
7
|
// buddy_personality — Show species personality traits, style, and strength
|
|
8
|
+
// buddy_leaderboard — Global anonymous leaderboard across all kbot installs
|
|
8
9
|
import { registerTool } from './index.js';
|
|
9
|
-
import { getBuddy, getBuddySprite, getBuddyGreeting, getBuddyLevel, formatBuddyStatus, renameBuddy, getAchievements, getAchievementProgress, getSpeciesPersonality, } from '../buddy.js';
|
|
10
|
+
import { getBuddy, getBuddySprite, getBuddyGreeting, getBuddyLevel, formatBuddyStatus, renameBuddy, getAchievements, getAchievementProgress, getSpeciesPersonality, fetchBuddyLeaderboard, } from '../buddy.js';
|
|
10
11
|
const VALID_MOODS = ['idle', 'thinking', 'success', 'error', 'learning', 'alert', 'dance', 'curious', 'proud'];
|
|
11
12
|
export function registerBuddyTools() {
|
|
12
13
|
registerTool({
|
|
@@ -124,5 +125,57 @@ export function registerBuddyTools() {
|
|
|
124
125
|
].join('\n');
|
|
125
126
|
},
|
|
126
127
|
});
|
|
128
|
+
registerTool({
|
|
129
|
+
name: 'buddy_leaderboard',
|
|
130
|
+
description: 'Show the global buddy leaderboard — anonymous rankings of all kbot buddies across installs, sorted by XP. Requires cloud sync (kernel.chat token).',
|
|
131
|
+
parameters: {
|
|
132
|
+
limit: {
|
|
133
|
+
type: 'number',
|
|
134
|
+
description: 'Number of entries to show (default 20, max 200)',
|
|
135
|
+
},
|
|
136
|
+
species: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
description: 'Filter by species: fox, owl, cat, robot, ghost, mushroom, octopus, dragon',
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
tier: 'free',
|
|
142
|
+
async execute(args) {
|
|
143
|
+
const SPECIES_ICONS = {
|
|
144
|
+
fox: '[fox]', owl: '[owl]', cat: '[cat]', robot: '[bot]',
|
|
145
|
+
ghost: '[gho]', mushroom: '[msh]', octopus: '[oct]', dragon: '[drg]',
|
|
146
|
+
};
|
|
147
|
+
const LEVEL_TITLES_SHORT = {
|
|
148
|
+
0: 'Novice', 1: 'Adept', 2: 'Master', 3: 'Legend',
|
|
149
|
+
};
|
|
150
|
+
const limit = Math.min(Math.max(Math.floor(Number(args.limit) || 20), 1), 200);
|
|
151
|
+
const species = args.species ? String(args.species).toLowerCase() : undefined;
|
|
152
|
+
const validSpecies = ['fox', 'owl', 'cat', 'robot', 'ghost', 'mushroom', 'octopus', 'dragon'];
|
|
153
|
+
if (species && !validSpecies.includes(species)) {
|
|
154
|
+
return `Unknown species "${species}". Valid: ${validSpecies.join(', ')}`;
|
|
155
|
+
}
|
|
156
|
+
const entries = await fetchBuddyLeaderboard({ limit, species });
|
|
157
|
+
if (entries.length === 0) {
|
|
158
|
+
return 'No entries on the leaderboard yet. Use kbot to earn XP and sync to the cloud!';
|
|
159
|
+
}
|
|
160
|
+
const lines = [];
|
|
161
|
+
const header = species
|
|
162
|
+
? `=== Buddy Leaderboard — ${species} ===`
|
|
163
|
+
: '=== Global Buddy Leaderboard ===';
|
|
164
|
+
lines.push(header);
|
|
165
|
+
lines.push('');
|
|
166
|
+
// Table header
|
|
167
|
+
lines.push(` ${'#'.padStart(3)} ${'Species'.padEnd(7)} ${'Level'.padEnd(12)} ${'XP'.padStart(6)} ${'Achv'.padStart(4)} ${'Sessions'.padStart(8)}`);
|
|
168
|
+
lines.push(` ${'─'.repeat(3)} ${'─'.repeat(7)} ${'─'.repeat(12)} ${'─'.repeat(6)} ${'─'.repeat(4)} ${'─'.repeat(8)}`);
|
|
169
|
+
for (const entry of entries) {
|
|
170
|
+
const icon = SPECIES_ICONS[entry.species] || entry.species.slice(0, 5);
|
|
171
|
+
const title = LEVEL_TITLES_SHORT[entry.level] ?? `L${entry.level}`;
|
|
172
|
+
const levelStr = `${entry.level} ${title}`;
|
|
173
|
+
lines.push(` ${String(entry.rank).padStart(3)} ${icon.padEnd(7)} ${levelStr.padEnd(12)} ${String(entry.xp).padStart(6)} ${String(entry.achievement_count).padStart(4)} ${String(entry.sessions).padStart(8)}`);
|
|
174
|
+
}
|
|
175
|
+
lines.push('');
|
|
176
|
+
lines.push(`${entries.length} entries shown`);
|
|
177
|
+
return lines.join('\n');
|
|
178
|
+
},
|
|
179
|
+
});
|
|
127
180
|
}
|
|
128
181
|
//# sourceMappingURL=buddy-tools.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernel.chat/kbot",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.73.0",
|
|
4
4
|
"description": "Open-source terminal AI agent. 764+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Controls your phone. Fully local, fully sovereign. MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|