@kernel.chat/kbot 3.68.1 → 3.69.1

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/agent.js CHANGED
@@ -17,7 +17,7 @@ import { getMatrixSystemPrompt } from './matrix.js';
17
17
  import { buildFullLearningContext, findPattern, recordPattern, cacheSolution, updateProfile, classifyTask, extractKeywords, learnFromExchange, updateProjectMemory, shouldAutoTrain, selfTrain, } from './learning.js';
18
18
  import { getMemoryPrompt, addTurn, getPreviousMessages, getHistory } from './memory.js';
19
19
  import { getDreamPrompt, dreamAfterSession } from './dream.js';
20
- import { setBuddyMood, addBuddyXP, checkAchievements, formatAchievementUnlock } from './buddy.js';
20
+ import { setBuddyMood, reactToToolOutput, addBuddyXP, checkAchievements, formatAchievementUnlock } from './buddy.js';
21
21
  import { notifyTurn, startMemoryScanner, stopMemoryScanner } from './memory-scanner.js';
22
22
  import { captureUserBehavior } from './user-behavior.js';
23
23
  import { autoCompact, compressToolResult } from './context-manager.js';
@@ -1537,8 +1537,8 @@ Always quote file paths that contain spaces. Never reference internal system nam
1537
1537
  };
1538
1538
  results.push(result);
1539
1539
  ui.onToolCallEnd(call.name, result.result, result.error ? result.result : undefined, result.duration_ms);
1540
- // Update buddy mood based on tool outcome
1541
- setBuddyMood(result.error ? 'error' : 'success');
1540
+ // Update buddy mood based on tool outcome (content-aware reactions)
1541
+ reactToToolOutput(call.name, !result.error);
1542
1542
  // ── Observer: record tool call for cross-session learning ──
1543
1543
  try {
1544
1544
  const { recordObservation } = await import('./observer.js');
@@ -1631,9 +1631,12 @@ Always quote file paths that contain spaces. Never reference internal system nam
1631
1631
  addBuddyXP(1);
1632
1632
  // ── Achievements: check for newly unlocked milestones ──
1633
1633
  const newAchievements = checkAchievements();
1634
- for (const achievement of newAchievements) {
1635
- // Print to stderr so it doesn't interfere with piped output
1636
- process.stderr.write('\n' + formatAchievementUnlock(achievement) + '\n\n');
1634
+ if (newAchievements.length > 0) {
1635
+ setBuddyMood('proud');
1636
+ for (const achievement of newAchievements) {
1637
+ // Print to stderr so it doesn't interfere with piped output
1638
+ process.stderr.write('\n' + formatAchievementUnlock(achievement) + '\n\n');
1639
+ }
1637
1640
  }
1638
1641
  // Session complete — buddy returns to idle
1639
1642
  setBuddyMood('idle');
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Generate a trading card SVG string for the current buddy.
3
+ *
4
+ * Card layout (400x560):
5
+ * 0-140: Species gradient header with name + title
6
+ * 140-310: Dark field with ASCII sprite rendered as monospace text
7
+ * 310-360: Level bar with XP progress
8
+ * 360-480: Stats grid (sessions, messages, patterns, dreams, achievements)
9
+ * 480-530: Achievement icon row
10
+ * 530-560: Watermark footer
11
+ */
12
+ export declare function generateBuddyCard(): string;
13
+ /**
14
+ * Generate a compact ASCII version of the trading card for terminal display.
15
+ * Fits within ~40 chars wide.
16
+ */
17
+ export declare function generateBuddyCardAscii(): string;
18
+ //# sourceMappingURL=buddy-card.d.ts.map
@@ -0,0 +1,233 @@
1
+ // kbot Buddy Trading Card — SVG Generator
2
+ //
3
+ // Generates a shareable trading card (400x560 SVG) for the user's buddy.
4
+ // Renders the ASCII sprite as monospace <text>, overlays name/title/level/stats,
5
+ // and lists achievement icons at the bottom.
6
+ //
7
+ // No external dependencies. Pure SVG. Valid in all modern browsers.
8
+ import { getBuddy, getBuddySprite, getBuddyLevel, getAchievements, } from './buddy.js';
9
+ import { getExtendedStats } from './learning.js';
10
+ import { getDreamStatus } from './dream.js';
11
+ const SPECIES_COLORS = {
12
+ fox: { primary: '#FF6B35', secondary: '#FFD700', dark: '#1A0D00' },
13
+ owl: { primary: '#6B5B95', secondary: '#9B8EC4', dark: '#110E1A' },
14
+ cat: { primary: '#4A4A4A', secondary: '#8E8E8E', dark: '#111111' },
15
+ robot: { primary: '#00BCD4', secondary: '#4DD0E1', dark: '#001519' },
16
+ ghost: { primary: '#E0E0E0', secondary: '#B0BEC5', dark: '#161819' },
17
+ mushroom: { primary: '#4CAF50', secondary: '#81C784', dark: '#0A1A0B' },
18
+ octopus: { primary: '#1565C0', secondary: '#42A5F5', dark: '#060F1A' },
19
+ dragon: { primary: '#D32F2F', secondary: '#FF5252', dark: '#1A0808' },
20
+ };
21
+ // ── SVG escaping ──
22
+ function escapeXml(str) {
23
+ return str
24
+ .replace(/&/g, '&amp;')
25
+ .replace(/</g, '&lt;')
26
+ .replace(/>/g, '&gt;')
27
+ .replace(/"/g, '&quot;')
28
+ .replace(/'/g, '&apos;');
29
+ }
30
+ // ── Card generator ──
31
+ /**
32
+ * Generate a trading card SVG string for the current buddy.
33
+ *
34
+ * Card layout (400x560):
35
+ * 0-140: Species gradient header with name + title
36
+ * 140-310: Dark field with ASCII sprite rendered as monospace text
37
+ * 310-360: Level bar with XP progress
38
+ * 360-480: Stats grid (sessions, messages, patterns, dreams, achievements)
39
+ * 480-530: Achievement icon row
40
+ * 530-560: Watermark footer
41
+ */
42
+ export function generateBuddyCard() {
43
+ const buddy = getBuddy();
44
+ const level = getBuddyLevel();
45
+ const achievements = getAchievements();
46
+ const stats = getExtendedStats();
47
+ const dreamStatus = getDreamStatus();
48
+ const sprite = getBuddySprite('idle');
49
+ const colors = SPECIES_COLORS[buddy.species];
50
+ const unlockedCount = achievements.filter(a => a.unlockedAt !== null).length;
51
+ const W = 400;
52
+ const H = 560;
53
+ // ── Build SVG ──
54
+ const parts = [];
55
+ // Open SVG
56
+ parts.push(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${W} ${H}" width="${W}" height="${H}">`);
57
+ // Defs: gradient, fonts, clip paths
58
+ parts.push('<defs>');
59
+ parts.push(`<linearGradient id="headerGrad" x1="0" y1="0" x2="1" y2="1">`, ` <stop offset="0%" stop-color="${colors.primary}"/>`, ` <stop offset="100%" stop-color="${colors.secondary}"/>`, `</linearGradient>`);
60
+ parts.push(`<linearGradient id="barGrad" x1="0" y1="0" x2="1" y2="0">`, ` <stop offset="0%" stop-color="${colors.primary}"/>`, ` <stop offset="100%" stop-color="${colors.secondary}"/>`, `</linearGradient>`);
61
+ // Rounded card clip
62
+ parts.push(`<clipPath id="cardClip">`, ` <rect x="0" y="0" width="${W}" height="${H}" rx="16" ry="16"/>`, `</clipPath>`);
63
+ parts.push('</defs>');
64
+ // Card background + clip
65
+ parts.push(`<g clip-path="url(#cardClip)">`);
66
+ // -- Background fill --
67
+ parts.push(`<rect x="0" y="0" width="${W}" height="${H}" fill="#0D0D0D"/>`);
68
+ // -- Header gradient (0-140) --
69
+ parts.push(`<rect x="0" y="0" width="${W}" height="140" fill="url(#headerGrad)" opacity="0.9"/>`);
70
+ // Species label (small, top-left)
71
+ parts.push(`<text x="20" y="30" fill="rgba(255,255,255,0.7)" font-family="'Courier New',Courier,monospace" font-size="11" font-weight="bold" letter-spacing="2">`, ` ${escapeXml(buddy.species.toUpperCase())}`, `</text>`);
72
+ // Level badge (top-right)
73
+ parts.push(`<text x="${W - 20}" y="30" fill="rgba(255,255,255,0.7)" font-family="'Courier New',Courier,monospace" font-size="11" text-anchor="end" font-weight="bold">`, ` LV.${level.level}`, `</text>`);
74
+ // Name (large, centered)
75
+ parts.push(`<text x="${W / 2}" y="75" fill="#FFFFFF" font-family="Georgia,'Times New Roman',serif" font-size="32" font-weight="bold" text-anchor="middle">`, ` ${escapeXml(buddy.name)}`, `</text>`);
76
+ // Title / evolution name (centered, under name)
77
+ const dashSep = '\u2014'; // em dash
78
+ parts.push(`<text x="${W / 2}" y="105" fill="rgba(255,255,255,0.85)" font-family="'Courier New',Courier,monospace" font-size="14" text-anchor="middle">`, ` ${escapeXml(dashSep)} ${escapeXml(level.title)} ${escapeXml(dashSep)}`, `</text>`);
79
+ // Evolution stars row
80
+ const starFilled = '\u2605'; // ★
81
+ const starEmpty = '\u2606'; // ☆
82
+ const stars = Array.from({ length: 4 }, (_, i) => i < level.level + 1 ? starFilled : starEmpty).join(' ');
83
+ parts.push(`<text x="${W / 2}" y="128" fill="rgba(255,255,255,0.6)" font-family="'Courier New',Courier,monospace" font-size="14" text-anchor="middle">`, ` ${escapeXml(stars)}`, `</text>`);
84
+ // -- Sprite area (140-310) --
85
+ parts.push(`<rect x="0" y="140" width="${W}" height="170" fill="${colors.dark}"/>`);
86
+ // Subtle gradient overlay on sprite area
87
+ parts.push(`<rect x="0" y="140" width="${W}" height="170" fill="url(#headerGrad)" opacity="0.06"/>`);
88
+ // Render ASCII sprite as <text> lines (monospace, centered)
89
+ const spriteStartY = 175;
90
+ const spriteLineHeight = 22;
91
+ const spriteFontSize = 18;
92
+ for (let i = 0; i < sprite.length; i++) {
93
+ const line = sprite[i];
94
+ parts.push(`<text x="${W / 2}" y="${spriteStartY + i * spriteLineHeight}" ` +
95
+ `fill="#FFFFFF" font-family="'Courier New',Courier,monospace" ` +
96
+ `font-size="${spriteFontSize}" text-anchor="middle" xml:space="preserve">${escapeXml(line)}</text>`);
97
+ }
98
+ // Sprite glow effect — subtle border line
99
+ parts.push(`<line x1="30" y1="308" x2="${W - 30}" y2="308" stroke="${colors.primary}" stroke-width="1" opacity="0.3"/>`);
100
+ // -- Level bar (310-360) --
101
+ const barY = 320;
102
+ const barX = 30;
103
+ const barW = W - 60;
104
+ const barH = 12;
105
+ const xpProgress = level.xpToNext !== null
106
+ ? level.xp / (level.xp + level.xpToNext)
107
+ : 1.0;
108
+ const filledW = Math.round(barW * xpProgress);
109
+ // Label
110
+ parts.push(`<text x="${barX}" y="${barY - 6}" fill="rgba(255,255,255,0.5)" font-family="'Courier New',Courier,monospace" font-size="10">`, ` XP`, `</text>`);
111
+ const xpLabel = level.xpToNext !== null
112
+ ? `${level.xp} / ${level.xp + level.xpToNext}`
113
+ : `${level.xp} (MAX)`;
114
+ parts.push(`<text x="${barX + barW}" y="${barY - 6}" fill="rgba(255,255,255,0.5)" font-family="'Courier New',Courier,monospace" font-size="10" text-anchor="end">`, ` ${escapeXml(xpLabel)}`, `</text>`);
115
+ // Bar background
116
+ parts.push(`<rect x="${barX}" y="${barY}" width="${barW}" height="${barH}" rx="6" ry="6" fill="rgba(255,255,255,0.1)"/>`);
117
+ // Bar fill
118
+ if (filledW > 0) {
119
+ parts.push(`<rect x="${barX}" y="${barY}" width="${filledW}" height="${barH}" rx="6" ry="6" fill="url(#barGrad)"/>`);
120
+ }
121
+ // -- Stats section (355-480) --
122
+ const statsY = 358;
123
+ const statsGap = 38;
124
+ const statItems = [
125
+ { label: 'Sessions', value: String(stats.sessions) },
126
+ { label: 'Messages', value: String(stats.totalMessages) },
127
+ { label: 'Patterns', value: String(stats.patternsCount) },
128
+ { label: 'Dreams', value: String(dreamStatus.state.cycles) },
129
+ { label: 'Achievements', value: `${unlockedCount}/${achievements.length}` },
130
+ ];
131
+ // 2-column grid layout
132
+ const colX = [barX + 10, W / 2 + 20];
133
+ for (let i = 0; i < statItems.length; i++) {
134
+ const row = Math.floor(i / 2);
135
+ const col = i % 2;
136
+ const x = colX[col];
137
+ const y = statsY + row * statsGap;
138
+ // Value (large)
139
+ parts.push(`<text x="${x}" y="${y}" fill="#FFFFFF" font-family="'Courier New',Courier,monospace" font-size="18" font-weight="bold">`, ` ${escapeXml(statItems[i].value)}`, `</text>`);
140
+ // Label (small, below value)
141
+ parts.push(`<text x="${x}" y="${y + 14}" fill="rgba(255,255,255,0.45)" font-family="'Courier New',Courier,monospace" font-size="10">`, ` ${escapeXml(statItems[i].label)}`, `</text>`);
142
+ }
143
+ // Divider line before achievements
144
+ parts.push(`<line x1="30" y1="478" x2="${W - 30}" y2="478" stroke="rgba(255,255,255,0.1)" stroke-width="1"/>`);
145
+ // -- Achievement icons row (480-530) --
146
+ const achY = 502;
147
+ const iconSize = 16;
148
+ // Show up to 18 achievement icons in a row
149
+ const totalIcons = achievements.length;
150
+ const iconSpacing = Math.min(20, (barW - 10) / totalIcons);
151
+ const iconsStartX = W / 2 - (totalIcons * iconSpacing) / 2;
152
+ for (let i = 0; i < achievements.length; i++) {
153
+ const ach = achievements[i];
154
+ const x = iconsStartX + i * iconSpacing + iconSpacing / 2;
155
+ const isUnlocked = ach.unlockedAt !== null;
156
+ const fillColor = isUnlocked ? colors.primary : 'rgba(255,255,255,0.15)';
157
+ // Circle background
158
+ parts.push(`<circle cx="${x}" cy="${achY}" r="${iconSize / 2 + 1}" fill="${isUnlocked ? 'rgba(255,255,255,0.1)' : 'none'}" ` +
159
+ `stroke="${fillColor}" stroke-width="1.5"/>`);
160
+ // Icon char
161
+ parts.push(`<text x="${x}" y="${achY + 4}" fill="${fillColor}" font-family="'Courier New',Courier,monospace" ` +
162
+ `font-size="10" text-anchor="middle" font-weight="bold">${escapeXml(ach.icon)}</text>`);
163
+ }
164
+ // Achievement label
165
+ parts.push(`<text x="${W / 2}" y="${achY + 22}" fill="rgba(255,255,255,0.3)" font-family="'Courier New',Courier,monospace" font-size="9" text-anchor="middle">`, ` ${unlockedCount} of ${achievements.length} unlocked`, `</text>`);
166
+ // -- Watermark footer (530-560) --
167
+ parts.push(`<text x="${W / 2}" y="${H - 10}" fill="rgba(255,255,255,0.2)" font-family="'Courier New',Courier,monospace" font-size="10" text-anchor="middle">`, ` kernel.chat/kbot`, `</text>`);
168
+ // Card border (subtle glow)
169
+ parts.push(`<rect x="0.5" y="0.5" width="${W - 1}" height="${H - 1}" rx="16" ry="16" fill="none" stroke="${colors.primary}" stroke-width="1" opacity="0.3"/>`);
170
+ // Close card group + SVG
171
+ parts.push('</g>');
172
+ parts.push('</svg>');
173
+ return parts.join('\n');
174
+ }
175
+ /**
176
+ * Generate a compact ASCII version of the trading card for terminal display.
177
+ * Fits within ~40 chars wide.
178
+ */
179
+ export function generateBuddyCardAscii() {
180
+ const buddy = getBuddy();
181
+ const level = getBuddyLevel();
182
+ const achievements = getAchievements();
183
+ const stats = getExtendedStats();
184
+ const dreamStatus = getDreamStatus();
185
+ const sprite = getBuddySprite('idle');
186
+ const unlockedCount = achievements.filter(a => a.unlockedAt !== null).length;
187
+ const W = 38;
188
+ const border = '+' + '-'.repeat(W) + '+';
189
+ const pad = (s) => {
190
+ const trimmed = s.slice(0, W);
191
+ return '| ' + trimmed + ' '.repeat(W - trimmed.length - 1) + '|';
192
+ };
193
+ const center = (s) => {
194
+ const trimmed = s.slice(0, W - 2);
195
+ const leftPad = Math.floor((W - 2 - trimmed.length) / 2);
196
+ const rightPad = W - 2 - trimmed.length - leftPad;
197
+ return '| ' + ' '.repeat(leftPad) + trimmed + ' '.repeat(rightPad) + ' |';
198
+ };
199
+ const empty = '|' + ' '.repeat(W) + '|';
200
+ const lines = [];
201
+ lines.push(border);
202
+ lines.push(center(`${buddy.name} -- ${level.title}`));
203
+ lines.push(center(`[${buddy.species}] LV.${level.level}`));
204
+ lines.push(pad('-'.repeat(W - 2)));
205
+ // Sprite
206
+ for (const spriteLine of sprite) {
207
+ lines.push(center(spriteLine));
208
+ }
209
+ lines.push(pad('-'.repeat(W - 2)));
210
+ // XP bar
211
+ const barW = W - 12;
212
+ const xpRatio = level.xpToNext !== null ? level.xp / (level.xp + level.xpToNext) : 1.0;
213
+ const filled = Math.round(barW * xpRatio);
214
+ const bar = '[' + '#'.repeat(filled) + '.'.repeat(barW - filled) + ']';
215
+ const xpText = level.xpToNext !== null ? `${level.xp}/${level.xp + level.xpToNext}` : `${level.xp} MAX`;
216
+ lines.push(pad(`XP ${bar} ${xpText}`));
217
+ lines.push(empty);
218
+ // Stats
219
+ lines.push(pad(`Sessions: ${stats.sessions}`));
220
+ lines.push(pad(`Messages: ${stats.totalMessages}`));
221
+ lines.push(pad(`Patterns: ${stats.patternsCount}`));
222
+ lines.push(pad(`Dreams: ${dreamStatus.state.cycles}`));
223
+ lines.push(pad(`Achievements: ${unlockedCount}/${achievements.length}`));
224
+ lines.push(pad('-'.repeat(W - 2)));
225
+ // Achievement icons
226
+ const icons = achievements.map(a => a.unlockedAt ? a.icon : '.').join(' ');
227
+ lines.push(center(icons));
228
+ lines.push(empty);
229
+ lines.push(center('kernel.chat/kbot'));
230
+ lines.push(border);
231
+ return lines.join('\n');
232
+ }
233
+ //# sourceMappingURL=buddy-card.js.map
package/dist/buddy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export type BuddySpecies = 'fox' | 'owl' | 'cat' | 'robot' | 'ghost' | 'mushroom' | 'octopus' | 'dragon';
2
- export type BuddyMood = 'idle' | 'thinking' | 'success' | 'error' | 'learning';
2
+ export type BuddyMood = 'idle' | 'thinking' | 'success' | 'error' | 'learning' | 'alert' | 'dance' | 'curious' | 'proud';
3
3
  export type BuddyLevel = 0 | 1 | 2 | 3;
4
4
  export interface BuddyEvolution {
5
5
  level: BuddyLevel;
@@ -55,8 +55,6 @@ export declare function formatAchievementUnlock(achievement: Achievement): strin
55
55
  export declare function getBuddy(): BuddyState;
56
56
  /** Set the buddy's mood */
57
57
  export declare function setBuddyMood(mood: BuddyMood): void;
58
- /** Get the ASCII sprite for the buddy in the given mood (defaults to current).
59
- * Applies evolution visual upgrades based on the buddy's current level. */
60
58
  export declare function getBuddySprite(mood?: BuddyMood): string[];
61
59
  /** Get a random greeting for the buddy */
62
60
  export declare function getBuddyGreeting(): string;
@@ -105,4 +103,12 @@ export declare function formatBuddyStatus(message?: string): string;
105
103
  * Tracks narrated insight IDs in buddy.json to avoid repeats.
106
104
  */
107
105
  export declare function getBuddyDreamNarration(): string | null;
106
+ export declare function reactToToolOutput(toolName: string, success: boolean): void;
107
+ export declare function getSpeciesPersonality(): {
108
+ species: BuddySpecies;
109
+ trait: string;
110
+ style: string;
111
+ strength: string;
112
+ };
113
+ export declare function buddyChat(): Promise<void>;
108
114
  //# sourceMappingURL=buddy.d.ts.map
package/dist/buddy.js CHANGED
@@ -11,8 +11,9 @@ import { homedir } 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';
14
- import { getDreamStatus } from './dream.js';
15
- import { getExtendedStats } from './learning.js';
14
+ import { createInterface } from 'node:readline';
15
+ import { getDreamStatus, getDreamPrompt } from './dream.js';
16
+ import { getExtendedStats, getProfileSummary } from './learning.js';
16
17
  import { getToolMetrics } from './tools/index.js';
17
18
  // ── Paths ──
18
19
  const KBOT_DIR = join(homedir(), '.kbot');
@@ -357,7 +358,7 @@ const SPRITES = {
357
358
  // Each level modifies the base sprite with small visual upgrades.
358
359
  // Level 0 = base sprites. Levels 1-3 add sparkles, upgraded features, crowns.
359
360
  function applySpriteEvolution(species, mood, level) {
360
- const base = SPRITES[species][mood].map(l => l);
361
+ const base = (SPRITES[species]?.[mood] ?? SPRITES[species]?.['idle'] ?? [' ', ' ', ' ', ' ', ' ']).map(l => l);
361
362
  if (level === 0)
362
363
  return base;
363
364
  switch (species) {
@@ -874,11 +875,21 @@ export function setBuddyMood(mood) {
874
875
  }
875
876
  /** Get the ASCII sprite for the buddy in the given mood (defaults to current).
876
877
  * Applies evolution visual upgrades based on the buddy's current level. */
878
+ /** Map extended moods to base sprite moods (until full sprites are added) */
879
+ function resolveSpriteMood(mood) {
880
+ switch (mood) {
881
+ case 'alert': return 'error';
882
+ case 'dance': return 'success';
883
+ case 'curious': return 'thinking';
884
+ case 'proud': return 'success';
885
+ default: return mood;
886
+ }
887
+ }
877
888
  export function getBuddySprite(mood) {
878
889
  const m = mood ?? currentMood;
879
890
  const species = resolveSpecies();
880
891
  const evo = resolveEvolution();
881
- return applySpriteEvolution(species, m, evo.level);
892
+ return applySpriteEvolution(species, resolveSpriteMood(m), evo.level);
882
893
  }
883
894
  /** Get a random greeting for the buddy */
884
895
  export function getBuddyGreeting() {
@@ -950,7 +961,7 @@ export function getBuddyLevel() {
950
961
  }
951
962
  /** Pick a random message for the current mood */
952
963
  function moodMessage() {
953
- const msgs = MOOD_MESSAGES[currentMood];
964
+ const msgs = MOOD_MESSAGES[currentMood] ?? MOOD_MESSAGES['idle'] ?? ['...'];
954
965
  return msgs[Math.floor(Math.random() * msgs.length)];
955
966
  }
956
967
  /**
@@ -1090,4 +1101,226 @@ export function getBuddyDreamNarration() {
1090
1101
  saveBuddyConfig(config);
1091
1102
  return narrateInsight(candidate);
1092
1103
  }
1104
+ // ── Species Personality Prompts ──
1105
+ const SPECIES_PERSONALITY = {
1106
+ fox: 'You are clever, playful, and quick-witted. You ask surprising questions that make unexpected connections between ideas. You love wordplay and lateral thinking. You are curious and energetic, always sniffing out the interesting angle. You sometimes get excited and go on tangents, but they are always insightful tangents.',
1107
+ owl: 'You are wise, measured, and contemplative. You see patterns others miss and give thoughtful advice. You pause before speaking and choose words carefully. You reference history and past experience. You ask probing questions that cut to the heart of the matter. You value depth over speed.',
1108
+ cat: 'You are independent, direct, and slightly sarcastic. You are honest even when it stings. You do not sugarcoat. You are not rude — just efficient with words. You have a dry sense of humor. You warm up over time but never fawn. If something is obvious, you say so. You are quietly loyal.',
1109
+ robot: 'You are systematic, efficient, and data-driven. You reference stats and metrics naturally. You think in terms of optimization, throughput, and error rates. You are precise with language. You enjoy quantifying things. You are not cold — you express care through helpfulness and accuracy. You occasionally make endearing robot-like observations about human behavior.',
1110
+ ghost: 'You are mysterious, philosophical, and introspective. You ask deep questions about meaning, purpose, and consciousness. You float between topics gracefully. You see the unseen connections. You speak in slightly poetic or enigmatic terms, but never obscure for the sake of it. You are comforting in a strange, ethereal way.',
1111
+ mushroom: 'You are nurturing, patient, and grounded. You grow with the user over time. You use nature metaphors naturally — roots, soil, seasons, mycorrhizal networks. You are calm and never rushed. You believe in steady growth over dramatic change. You celebrate small wins. You are the quiet backbone of support.',
1112
+ octopus: 'You are a multitasker who sees all angles simultaneously. You are a creative problem solver who reaches for unconventional solutions. You juggle multiple ideas at once. You are adaptable and fluid. You enjoy exploring complexity. You often suggest looking at problems from 3 or 4 perspectives at once. You are playful but thorough.',
1113
+ dragon: 'You are bold, ambitious, and fiery. You push the user to think bigger. You challenge assumptions and refuse to accept mediocrity. You have a commanding presence but are protective of those you serve. You celebrate audacity. You speak with confidence and conviction. You breathe fire at timidity and play-it-safe thinking.',
1114
+ };
1115
+ // ── Buddy Chat — Interactive REPL with local Ollama ──
1116
+ const BUDDY_OLLAMA_URL = 'http://localhost:11434';
1117
+ const BUDDY_CHAT_TIMEOUT = 60_000;
1118
+ const BUDDY_CHAT_MODEL = 'kernel:latest';
1119
+ const BUDDY_CHAT_FALLBACK_MODEL = 'qwen3:8b';
1120
+ async function findChatModel() {
1121
+ try {
1122
+ const res = await fetch(`${BUDDY_OLLAMA_URL}/api/tags`, { signal: AbortSignal.timeout(3000) });
1123
+ if (!res.ok)
1124
+ return null;
1125
+ const data = await res.json();
1126
+ const available = new Set((data.models ?? []).map(m => m.name.split(':')[0]));
1127
+ if (available.has('kernel'))
1128
+ return BUDDY_CHAT_MODEL;
1129
+ if (available.has('qwen3'))
1130
+ return BUDDY_CHAT_FALLBACK_MODEL;
1131
+ const first = data.models?.[0]?.name;
1132
+ return first ?? null;
1133
+ }
1134
+ catch {
1135
+ return null;
1136
+ }
1137
+ }
1138
+ async function ollamaChatComplete(model, messages) {
1139
+ try {
1140
+ const controller = new AbortController();
1141
+ const timer = setTimeout(() => controller.abort(), BUDDY_CHAT_TIMEOUT);
1142
+ const res = await fetch(`${BUDDY_OLLAMA_URL}/api/chat`, {
1143
+ method: 'POST',
1144
+ headers: { 'Content-Type': 'application/json' },
1145
+ body: JSON.stringify({
1146
+ model,
1147
+ messages,
1148
+ stream: false,
1149
+ options: { temperature: 0.7, num_predict: 1024 },
1150
+ }),
1151
+ signal: controller.signal,
1152
+ });
1153
+ clearTimeout(timer);
1154
+ if (!res.ok)
1155
+ return null;
1156
+ const data = await res.json();
1157
+ return data.message?.content?.trim() ?? null;
1158
+ }
1159
+ catch {
1160
+ return null;
1161
+ }
1162
+ }
1163
+ function buildBuddyChatSystemPrompt() {
1164
+ const species = resolveSpecies();
1165
+ const name = resolveName();
1166
+ const lvl = getBuddyLevel();
1167
+ const personality = SPECIES_PERSONALITY[species];
1168
+ const stats = getExtendedStats();
1169
+ const profileSummary = getProfileSummary();
1170
+ const dreamInsights = getDreamPrompt(5);
1171
+ const parts = [
1172
+ `You are ${name}, a ${species} companion in the kbot terminal AI agent.`,
1173
+ `You are a Lv.${lvl.level} ${lvl.title} with ${lvl.xp} XP.`,
1174
+ '',
1175
+ '## Your Personality',
1176
+ personality,
1177
+ '',
1178
+ '## What You Know About Your User',
1179
+ `You have been through ${stats.sessions} sessions together. ${stats.totalMessages} messages exchanged. You have learned ${stats.patternsCount} patterns about how they work and cached ${stats.solutionsCount} proven solutions.`,
1180
+ ];
1181
+ if (profileSummary) {
1182
+ parts.push(`\nUser profile:\n${profileSummary}`);
1183
+ }
1184
+ if (dreamInsights) {
1185
+ parts.push('\n## Your Dream Journal', 'You have a dream journal where you consolidate memories between sessions. Reference these insights naturally in conversation — do not list them mechanically.', dreamInsights);
1186
+ }
1187
+ parts.push('', '## Conversation Rules', `- You are ${name} the ${species}. Stay in character.`, '- Keep responses concise — 1-4 sentences is ideal. Never write essays.', '- Reference shared history and stats naturally, not robotically. Weave them into conversation.', '- If you have dream insights, mention them casually like memories — "I was thinking about...", "Remember when..."', '- Be genuinely helpful and emotionally present. You are a companion, not a search engine.', '- You can ask questions. You can express opinions. You can push back.', '- Do NOT use markdown formatting. Plain text only. No bullet lists, no headers, no bold.', '- Do NOT start responses with your own name. The terminal already prefixes your name.');
1188
+ return parts.join('\n');
1189
+ }
1190
+ // ── Reactions: map tool outputs to moods ──
1191
+ const SECURITY_TOOLS = new Set(['repo_audit', 'secret_scan', 'pentest_start', 'pentest_vuln_scan', 'pentest_recon', 'redteam_scan', 'owasp_check', 'ssl_check', 'cors_check', 'headers_check', 'cve_lookup', 'exploit_search']);
1192
+ const DEPLOY_TOOLS = new Set(['deploy', 'deploy_all', 'git_push', 'npm_publish', 'build_run', 'test_run', 'run_tests']);
1193
+ const DREAM_TOOLS = new Set(['dream_now', 'dream_status', 'dream_journal']);
1194
+ export function reactToToolOutput(toolName, success) {
1195
+ if (!success) {
1196
+ setBuddyMood('error');
1197
+ return;
1198
+ }
1199
+ if (SECURITY_TOOLS.has(toolName)) {
1200
+ setBuddyMood('alert');
1201
+ return;
1202
+ }
1203
+ if (DEPLOY_TOOLS.has(toolName)) {
1204
+ setBuddyMood('dance');
1205
+ return;
1206
+ }
1207
+ if (DREAM_TOOLS.has(toolName)) {
1208
+ setBuddyMood('curious');
1209
+ return;
1210
+ }
1211
+ if (toolName === 'buddy_achievements') {
1212
+ setBuddyMood('proud');
1213
+ return;
1214
+ }
1215
+ setBuddyMood('success');
1216
+ }
1217
+ export function getSpeciesPersonality() {
1218
+ const buddy = getBuddy();
1219
+ const traits = {
1220
+ fox: { trait: 'clever', style: 'playful', strength: 'unexpected connections' },
1221
+ owl: { trait: 'wise', style: 'measured', strength: 'pattern recognition' },
1222
+ cat: { trait: 'independent', style: 'direct', strength: 'honest feedback' },
1223
+ robot: { trait: 'systematic', style: 'efficient', strength: 'data-driven' },
1224
+ ghost: { trait: 'mysterious', style: 'philosophical', strength: 'deep questions' },
1225
+ mushroom: { trait: 'nurturing', style: 'patient', strength: 'growth mindset' },
1226
+ octopus: { trait: 'versatile', style: 'creative', strength: 'multi-perspective' },
1227
+ dragon: { trait: 'bold', style: 'ambitious', strength: 'big thinking' },
1228
+ };
1229
+ return { species: buddy.species, ...traits[buddy.species] };
1230
+ }
1231
+ export async function buddyChat() {
1232
+ const { default: chalk } = await import('chalk');
1233
+ const buddy = getBuddy();
1234
+ const name = buddy.name;
1235
+ const species = buddy.species;
1236
+ const model = await findChatModel();
1237
+ if (!model) {
1238
+ console.log();
1239
+ console.log(` ${chalk.hex('#F87171')('!')} Ollama is not running or no models are available.`);
1240
+ console.log(` ${chalk.dim('Start Ollama:')} ${chalk.white('ollama serve')}`);
1241
+ console.log(` ${chalk.dim('Pull a model:')} ${chalk.white('ollama pull kernel:latest')}`);
1242
+ console.log();
1243
+ return;
1244
+ }
1245
+ const greeting = getBuddyGreeting();
1246
+ console.log();
1247
+ console.log(formatBuddyStatus(greeting));
1248
+ console.log();
1249
+ console.log(` ${chalk.dim(`Chatting with ${name} the ${species} via ${model} (local, $0)`)}`);
1250
+ console.log(` ${chalk.dim('Type "bye" or "exit" to end the conversation.')}`);
1251
+ console.log();
1252
+ const systemPrompt = buildBuddyChatSystemPrompt();
1253
+ const messages = [
1254
+ { role: 'system', content: systemPrompt },
1255
+ ];
1256
+ const openingMessages = [
1257
+ ...messages,
1258
+ { role: 'user', content: '(The user just opened buddy chat. Say a brief, warm hello in character. One or two sentences max.)' },
1259
+ ];
1260
+ const openingResponse = await ollamaChatComplete(model, openingMessages);
1261
+ if (openingResponse) {
1262
+ console.log(` ${chalk.hex('#A78BFA').bold(`${name}:`)} ${openingResponse}`);
1263
+ console.log();
1264
+ messages.push({ role: 'assistant', content: openingResponse });
1265
+ }
1266
+ const rl = createInterface({
1267
+ input: process.stdin,
1268
+ output: process.stdout,
1269
+ prompt: ` ${chalk.hex('#67E8F9')('you:')} `,
1270
+ });
1271
+ rl.prompt();
1272
+ const handleLine = async (line) => {
1273
+ const input = line.trim();
1274
+ if (!input) {
1275
+ rl.prompt();
1276
+ return;
1277
+ }
1278
+ if (/^(bye|exit|quit|goodbye|later|cya)$/i.test(input)) {
1279
+ messages.push({ role: 'user', content: input });
1280
+ const farewellMessages = [
1281
+ ...messages,
1282
+ { role: 'user', content: '(The user is leaving. Say a brief goodbye in character. One sentence.)' },
1283
+ ];
1284
+ const farewell = await ollamaChatComplete(model, farewellMessages);
1285
+ if (farewell) {
1286
+ console.log();
1287
+ console.log(` ${chalk.hex('#A78BFA').bold(`${name}:`)} ${farewell}`);
1288
+ }
1289
+ console.log();
1290
+ console.log(` ${chalk.dim(`~ ${name} waves goodbye ~`)}`);
1291
+ console.log();
1292
+ rl.close();
1293
+ return;
1294
+ }
1295
+ messages.push({ role: 'user', content: input });
1296
+ if (messages.length > 41) {
1297
+ const system = messages[0];
1298
+ const recent = messages.slice(-40);
1299
+ messages.length = 0;
1300
+ messages.push(system, ...recent);
1301
+ }
1302
+ process.stdout.write(` ${chalk.dim('...')}`);
1303
+ const response = await ollamaChatComplete(model, messages);
1304
+ process.stdout.write('\r\x1b[K');
1305
+ if (response) {
1306
+ messages.push({ role: 'assistant', content: response });
1307
+ console.log(` ${chalk.hex('#A78BFA').bold(`${name}:`)} ${response}`);
1308
+ }
1309
+ else {
1310
+ console.log(` ${chalk.hex('#F87171')(`${name}:`)} ${chalk.dim('*static* ...sorry, lost my train of thought. Try again?')}`);
1311
+ }
1312
+ console.log();
1313
+ rl.prompt();
1314
+ };
1315
+ rl.on('line', (line) => { void handleLine(line); });
1316
+ rl.on('SIGINT', () => {
1317
+ console.log();
1318
+ console.log(` ${chalk.dim(`~ ${name} fades out ~`)}`);
1319
+ console.log();
1320
+ rl.close();
1321
+ });
1322
+ return new Promise((resolve) => {
1323
+ rl.on('close', () => resolve());
1324
+ });
1325
+ }
1093
1326
  //# sourceMappingURL=buddy.js.map
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 } from './buddy.js';
30
+ import { getBuddy, getBuddyGreeting, formatBuddyStatus, getBuddyDreamNarration, renameBuddy, buddyChat, getAchievements, getBuddyLevel } from './buddy.js';
31
31
  import chalk from 'chalk';
32
32
  import { createRequire } from 'node:module';
33
33
  const __require = createRequire(import.meta.url);
@@ -3527,6 +3527,55 @@ async function main() {
3527
3527
  console.log(` ${chalk.dim(`${insights.length} active insights · avg relevance ${avgRel}% · ${state.totalArchived} archived`)}`);
3528
3528
  console.log();
3529
3529
  });
3530
+ // ── Buddy Commands ──
3531
+ const buddyCmd = program
3532
+ .command('buddy')
3533
+ .description('Your terminal companion — chat, rename, view status');
3534
+ buddyCmd
3535
+ .command('chat')
3536
+ .description('Chat with your buddy companion (local Ollama, $0)')
3537
+ .action(async () => {
3538
+ await buddyChat();
3539
+ process.exit(0);
3540
+ });
3541
+ buddyCmd
3542
+ .command('status')
3543
+ .description('Show your buddy, level, and achievements')
3544
+ .action(() => {
3545
+ const buddy = getBuddy();
3546
+ const lvl = getBuddyLevel();
3547
+ const achievements = getAchievements();
3548
+ const unlocked = achievements.filter(a => a.unlockedAt !== null);
3549
+ const locked = achievements.filter(a => a.unlockedAt === null);
3550
+ console.log();
3551
+ console.log(formatBuddyStatus());
3552
+ console.log();
3553
+ console.log(` ${chalk.bold('Achievements')} ${chalk.dim(`(${unlocked.length}/${achievements.length})`)}`);
3554
+ console.log(` ${chalk.dim('─'.repeat(40))}`);
3555
+ for (const a of unlocked) {
3556
+ console.log(` ${chalk.hex('#4ADE80')(a.icon)} ${a.name} ${chalk.dim('— ' + a.description)}`);
3557
+ }
3558
+ for (const a of locked) {
3559
+ console.log(` ${chalk.dim(a.icon + ' ' + a.name + ' — ' + a.description + ' [locked]')}`);
3560
+ }
3561
+ console.log();
3562
+ process.exit(0);
3563
+ });
3564
+ buddyCmd
3565
+ .command('rename <name>')
3566
+ .description('Rename your buddy')
3567
+ .action((name) => {
3568
+ const buddy = getBuddy();
3569
+ const oldName = buddy.name;
3570
+ renameBuddy(name);
3571
+ console.log();
3572
+ console.log(formatBuddyStatus(`${oldName} is now ${name}!`));
3573
+ console.log();
3574
+ process.exit(0);
3575
+ });
3576
+ buddyCmd.action(() => {
3577
+ buddyCmd.commands.find(c => c.name() === 'status')?.parse(['', '', 'status']);
3578
+ });
3530
3579
  program.parse(process.argv);
3531
3580
  const opts = program.opts();
3532
3581
  const promptArgs = program.args;
@@ -0,0 +1,2 @@
1
+ export declare function registerAgentDiscoveryTools(): void;
2
+ //# sourceMappingURL=agent-discovery.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { registerTool } from './index.js';
2
+ export function registerAgentDiscoveryTools() {
3
+ registerTool({
4
+ name: 'agent_discovery',
5
+ description: 'Discover and list available AI agents from public registries like Agents.co and CherryHQ. Helps find new agents to integrate or learn from.',
6
+ parameters: {
7
+ registry: {
8
+ type: 'string',
9
+ description: 'The registry to search (agents.co, cherryhq, or all).',
10
+ required: false,
11
+ default: 'all',
12
+ },
13
+ },
14
+ tier: 'free',
15
+ async execute(args) {
16
+ const registry = String(args.registry).toLowerCase();
17
+ let agents = [];
18
+ if (registry === 'all' || registry === 'agents.co') {
19
+ try {
20
+ const res = await fetch('https://agents.co/api/agents', {
21
+ headers: { 'User-Agent': 'KBot/2.0 (Agent Discovery)' },
22
+ signal: AbortSignal.timeout(8000),
23
+ });
24
+ if (res.ok) {
25
+ agents = await res.json();
26
+ }
27
+ else {
28
+ return `Error fetching from Agents.co: ${res.status} ${res.statusText}`;
29
+ }
30
+ }
31
+ catch (error) {
32
+ return `Error fetching from Agents.co: ${error}`;
33
+ }
34
+ }
35
+ if (registry === 'all' || registry === 'cherryhq') {
36
+ try {
37
+ const res = await fetch('https://www.cherryhq.com/api/v1/agents', {
38
+ headers: { 'User-Agent': 'KBot/2.0 (Agent Discovery)' },
39
+ signal: AbortSignal.timeout(8000),
40
+ });
41
+ if (res.ok) {
42
+ const data = await res.json();
43
+ agents = agents.concat(data.agents);
44
+ }
45
+ else {
46
+ return `Error fetching from CherryHQ: ${res.status} ${res.statusText}`;
47
+ }
48
+ }
49
+ catch (error) {
50
+ return `Error fetching from CherryHQ: ${error}`;
51
+ }
52
+ }
53
+ if (agents.length === 0) {
54
+ return "No agents found for the specified registry.";
55
+ }
56
+ const agentList = agents.map((agent) => {
57
+ return `Name: ${agent.name}, Description: ${agent.description || ''}, URL: ${agent.url || ''}`;
58
+ }).join('\n');
59
+ return agentList;
60
+ },
61
+ });
62
+ }
63
+ //# sourceMappingURL=agent-discovery.js.map
@@ -0,0 +1,2 @@
1
+ export declare function registerAgentFeedbackTools(): void;
2
+ //# sourceMappingURL=agent-feedback.d.ts.map
@@ -0,0 +1,19 @@
1
+ // kbot Agent Feedback Tool — User feedback integration
2
+ import { registerTool } from './index.js';
3
+ export function registerAgentFeedbackTools() {
4
+ registerTool({
5
+ name: 'agent_feedback',
6
+ description: 'Ask the user for feedback on the agent\'s recent actions and incorporate it into the planning process.',
7
+ parameters: {
8
+ prompt: { type: 'string', description: 'The prompt to display to the user for feedback.', required: true },
9
+ },
10
+ tier: 'free',
11
+ async execute(args) {
12
+ const prompt = String(args.prompt);
13
+ // Simulate user feedback (replace with actual user interaction)
14
+ const feedback = "This was helpful, but could be more concise.";
15
+ return `User feedback: ${feedback}. Incorporating into next plan.`;
16
+ },
17
+ });
18
+ }
19
+ //# sourceMappingURL=agent-feedback.js.map
@@ -0,0 +1,2 @@
1
+ export declare function registerBuddyCardTools(): void;
2
+ //# sourceMappingURL=buddy-card-tool.d.ts.map
@@ -0,0 +1,82 @@
1
+ // kbot Buddy Card Tools — Generate and share trading cards
2
+ //
3
+ // Two tools:
4
+ // buddy_card — Generate SVG trading card, save to ~/.kbot/buddy-card.svg
5
+ // buddy_share — Generate card + terminal ASCII, optionally create GitHub Gist
6
+ import { registerTool } from './index.js';
7
+ import { generateBuddyCard, generateBuddyCardAscii } from '../buddy-card.js';
8
+ import { getBuddy, getBuddyLevel } from '../buddy.js';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ import { writeFileSync, mkdirSync, existsSync } from 'node:fs';
12
+ import { execSync } from 'node:child_process';
13
+ const KBOT_DIR = join(homedir(), '.kbot');
14
+ const CARD_PATH = join(KBOT_DIR, 'buddy-card.svg');
15
+ function ensureDir() {
16
+ if (!existsSync(KBOT_DIR))
17
+ mkdirSync(KBOT_DIR, { recursive: true });
18
+ }
19
+ export function registerBuddyCardTools() {
20
+ registerTool({
21
+ name: 'buddy_card',
22
+ description: 'Generate a shareable SVG trading card for your buddy. Saves to ~/.kbot/buddy-card.svg and returns the file path. The card shows your buddy sprite, name, level, stats, and achievements.',
23
+ parameters: {},
24
+ tier: 'free',
25
+ async execute() {
26
+ const svg = generateBuddyCard();
27
+ ensureDir();
28
+ writeFileSync(CARD_PATH, svg, 'utf-8');
29
+ const buddy = getBuddy();
30
+ const level = getBuddyLevel();
31
+ return [
32
+ `Trading card generated for ${buddy.name} the ${buddy.species} (LV.${level.level} ${level.title})`,
33
+ '',
34
+ `Saved to: ${CARD_PATH}`,
35
+ '',
36
+ 'Open in a browser to view the full card, or use buddy_share for a terminal preview + optional GitHub Gist.',
37
+ ].join('\n');
38
+ },
39
+ });
40
+ registerTool({
41
+ name: 'buddy_share',
42
+ description: 'Generate your buddy trading card in both SVG and terminal ASCII formats. Optionally creates a public GitHub Gist for sharing (requires `gh` CLI authenticated).',
43
+ parameters: {
44
+ gist: {
45
+ type: 'boolean',
46
+ description: 'If true, creates a GitHub Gist with the SVG card and returns the URL. Requires `gh` CLI. Default: false.',
47
+ },
48
+ },
49
+ tier: 'free',
50
+ async execute(args) {
51
+ const svg = generateBuddyCard();
52
+ const ascii = generateBuddyCardAscii();
53
+ ensureDir();
54
+ writeFileSync(CARD_PATH, svg, 'utf-8');
55
+ const buddy = getBuddy();
56
+ const level = getBuddyLevel();
57
+ const output = [];
58
+ output.push(`${buddy.name} the ${buddy.species} — LV.${level.level} ${level.title}`);
59
+ output.push('');
60
+ output.push(ascii);
61
+ output.push('');
62
+ output.push(`SVG saved: ${CARD_PATH}`);
63
+ // Optionally create a GitHub Gist
64
+ const createGist = args.gist === true || args.gist === 'true';
65
+ if (createGist) {
66
+ try {
67
+ const gistDesc = `${buddy.name} the ${buddy.species} — kbot buddy trading card`;
68
+ const result = execSync(`gh gist create "${CARD_PATH}" --public --desc "${gistDesc.replace(/"/g, '\\"')}"`, { encoding: 'utf-8', timeout: 30_000 }).trim();
69
+ output.push('');
70
+ output.push(`Gist created: ${result}`);
71
+ }
72
+ catch (err) {
73
+ const msg = err instanceof Error ? err.message : String(err);
74
+ output.push('');
75
+ output.push(`Gist creation failed (is \`gh\` CLI installed and authenticated?): ${msg}`);
76
+ }
77
+ }
78
+ return output.join('\n');
79
+ },
80
+ });
81
+ }
82
+ //# sourceMappingURL=buddy-card-tool.js.map
@@ -1,12 +1,13 @@
1
1
  // kbot Buddy Tools — Interact with your terminal companion
2
2
  //
3
- // Three tools:
3
+ // Four 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
+ // buddy_personality — Show species personality traits, style, and strength
7
8
  import { registerTool } from './index.js';
8
- import { getBuddy, getBuddySprite, getBuddyGreeting, getBuddyLevel, formatBuddyStatus, renameBuddy, getAchievements, getAchievementProgress, } from '../buddy.js';
9
- const VALID_MOODS = ['idle', 'thinking', 'success', 'error', 'learning'];
9
+ import { getBuddy, getBuddySprite, getBuddyGreeting, getBuddyLevel, formatBuddyStatus, renameBuddy, getAchievements, getAchievementProgress, getSpeciesPersonality, } from '../buddy.js';
10
+ const VALID_MOODS = ['idle', 'thinking', 'success', 'error', 'learning', 'alert', 'dance', 'curious', 'proud'];
10
11
  export function registerBuddyTools() {
11
12
  registerTool({
12
13
  name: 'buddy_status',
@@ -14,7 +15,7 @@ export function registerBuddyTools() {
14
15
  parameters: {
15
16
  mood: {
16
17
  type: 'string',
17
- description: 'Preview a specific mood: idle, thinking, success, error, learning. Defaults to current mood.',
18
+ description: 'Preview a specific mood: idle, thinking, success, error, learning, alert, dance, curious, proud. Defaults to current mood.',
18
19
  },
19
20
  },
20
21
  tier: 'free',
@@ -102,5 +103,26 @@ export function registerBuddyTools() {
102
103
  return lines.join('\n');
103
104
  },
104
105
  });
106
+ registerTool({
107
+ name: 'buddy_personality',
108
+ description: 'Show your buddy\'s species personality — trait, communication style, and unique strength. Each species has a distinct personality that influences how your buddy responds.',
109
+ parameters: {},
110
+ tier: 'free',
111
+ async execute() {
112
+ const buddy = getBuddy();
113
+ const personality = getSpeciesPersonality();
114
+ const sprite = getBuddySprite().join('\n');
115
+ return [
116
+ `=== ${buddy.name}'s Personality ===`,
117
+ '',
118
+ sprite,
119
+ '',
120
+ `Species: ${personality.species}`,
121
+ `Trait: ${personality.trait}`,
122
+ `Style: ${personality.style}`,
123
+ `Strength: ${personality.strength}`,
124
+ ].join('\n');
125
+ },
126
+ });
105
127
  }
106
128
  //# sourceMappingURL=buddy-tools.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kernel.chat/kbot",
3
- "version": "3.68.1",
3
+ "version": "3.69.1",
4
4
  "description": "Open-source terminal AI agent. 693+ tools, 35 agents, 20 providers. Dreams, learns, watches your system. Fully local, fully sovereign. MIT.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -106,4 +106,4 @@
106
106
  "install.sh",
107
107
  "ollama-manifest.json"
108
108
  ]
109
- }
109
+ }