@kernel.chat/kbot 3.65.0 → 3.66.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/README.md +35 -6
- package/dist/agent.js +8 -0
- package/dist/cli.js +20 -5
- package/dist/collective-dreams.d.ts +36 -0
- package/dist/collective-dreams.js +182 -0
- package/dist/dream.d.ts +2 -1
- package/dist/dream.js +94 -3
- package/dist/prompt-evolution.d.ts +9 -0
- package/dist/prompt-evolution.js +28 -0
- package/dist/tools/audit.d.ts +2 -1
- package/dist/tools/audit.js +127 -4
- package/dist/tools/collective-dream-tools.d.ts +2 -0
- package/dist/tools/collective-dream-tools.js +109 -0
- package/dist/tools/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>kbot</strong><br>
|
|
5
|
-
Open-source terminal AI agent. 35 agents.
|
|
5
|
+
Open-source terminal AI agent. 35 agents. 686+ tools. 20 providers. Science, finance, security, and more.
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
@@ -30,17 +30,42 @@ Most terminal AI agents lock you into one provider, one model, one way of workin
|
|
|
30
30
|
- **Runs fully offline** — Embedded llama.cpp, Ollama, LM Studio, or Jan. $0, fully private.
|
|
31
31
|
- **Learns your patterns** — Bayesian skill ratings + pattern extraction. Gets faster over time.
|
|
32
32
|
- **35 specialist agents** — auto-routes your request to the right expert (coder, researcher, writer, guardian, quant, and 30 more).
|
|
33
|
-
- **
|
|
33
|
+
- **686+ tools** — files, bash, git, GitHub, web search, deploy, database, game dev, VFX, research, science, finance, security, and more.
|
|
34
34
|
- **Programmatic SDK** — use kbot as a library in your own apps.
|
|
35
35
|
- **MCP server built in** — plug kbot into Claude Code, Cursor, VS Code, Zed, or Neovim as a tool provider.
|
|
36
36
|
|
|
37
|
+
## Highlights
|
|
38
|
+
|
|
39
|
+
### Dream Engine — Your AI Remembers You
|
|
40
|
+
|
|
41
|
+
After each session, kbot "dreams" — consolidating what it learned about you into durable insights using local Ollama models. $0 cost. Insights feed back into future sessions automatically.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
kbot dream run # Trigger consolidation
|
|
45
|
+
kbot dream status # See what kbot learned
|
|
46
|
+
kbot dream journal # Full insight history
|
|
47
|
+
kbot dream search # Find specific memories
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
5-tier memory: pattern cache -> solution index -> user profile -> dream journal -> passive scanner. All tiers feed each other through the dream engine.
|
|
51
|
+
|
|
52
|
+
### Audit Any Repo in One Command
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
kbot audit facebook/react
|
|
56
|
+
kbot audit --share vercel/next.js # Creates a public Gist
|
|
57
|
+
kbot audit --badge your/repo # Badge for your README
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Checks security, documentation, code quality, CI/CD, community health, and DevOps. Scored out of 100, graded A-F. Add the badge to your README.
|
|
61
|
+
|
|
37
62
|
### How it compares
|
|
38
63
|
|
|
39
64
|
| | kbot | Claude Code | Codex CLI | Aider | OpenCode |
|
|
40
65
|
|---|---|---|---|---|---|
|
|
41
66
|
| AI providers | 20 | 1 | 1 | 6 | 75+ |
|
|
42
67
|
| Specialist agents | 35 | 0 | 0 | 0 | 0 |
|
|
43
|
-
| Built-in tools |
|
|
68
|
+
| Built-in tools | 686+ | ~20 | ~15 | ~10 | ~15 |
|
|
44
69
|
| Science tools | 114 | 0 | 0 | 0 | 0 |
|
|
45
70
|
| Learning engine | Yes | No | No | No | No |
|
|
46
71
|
| Offline mode | Embedded + Ollama | No | No | Ollama | Ollama |
|
|
@@ -93,7 +118,7 @@ kbot auto-routes to the right agent for each task. Or pick one with `--agent <na
|
|
|
93
118
|
| **Domain** | infrastructure, quant, investigator, oracle, chronist, sage, communicator, adapter |
|
|
94
119
|
| **Presets** | claude-code, cursor, copilot, creative, developer |
|
|
95
120
|
|
|
96
|
-
##
|
|
121
|
+
## 686+ Tools
|
|
97
122
|
|
|
98
123
|
| Category | Examples |
|
|
99
124
|
|----------|---------|
|
|
@@ -185,7 +210,7 @@ graph TD
|
|
|
185
210
|
D -->|Multi-step| F[Autonomous Planner]
|
|
186
211
|
E --> G[Provider API + Tool Loop]
|
|
187
212
|
F --> G
|
|
188
|
-
G --> H{
|
|
213
|
+
G --> H{686+ Tools}
|
|
189
214
|
H --> I[File ops, bash, git, GitHub, search, deploy, DB, game dev...]
|
|
190
215
|
G --> J[Learning Engine]
|
|
191
216
|
J --> K[Patterns + Solutions + User Profile]
|
|
@@ -218,7 +243,11 @@ Works with Claude Code, Cursor, VS Code, Windsurf, Zed, Neovim.
|
|
|
218
243
|
| `kbot auth` | Configure API key |
|
|
219
244
|
| `kbot local` | Use local AI (Ollama, embedded, LM Studio, Jan) |
|
|
220
245
|
| `kbot serve` | Start HTTP REST + SSE streaming server |
|
|
221
|
-
| `kbot audit <repo>` | Security + quality audit of any GitHub repo |
|
|
246
|
+
| `kbot audit <repo>` | Security + quality audit of any GitHub repo (A-F grade) |
|
|
247
|
+
| `kbot dream run` | Consolidate session learnings into durable insights |
|
|
248
|
+
| `kbot dream status` | See what kbot has learned about you |
|
|
249
|
+
| `kbot dream journal` | Full insight history |
|
|
250
|
+
| `kbot dream search` | Find specific memories |
|
|
222
251
|
| `kbot contribute <repo>` | Find good-first-issues and quick wins |
|
|
223
252
|
| `kbot share` | Share conversation as GitHub Gist |
|
|
224
253
|
| `kbot pair` | File watcher with auto-analysis |
|
package/dist/agent.js
CHANGED
|
@@ -17,6 +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 } from './buddy.js';
|
|
20
21
|
import { notifyTurn, startMemoryScanner, stopMemoryScanner } from './memory-scanner.js';
|
|
21
22
|
import { autoCompact, compressToolResult } from './context-manager.js';
|
|
22
23
|
import { learnedRoute, recordRoute } from './learned-router.js';
|
|
@@ -1003,6 +1004,7 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1003
1004
|
model: options.model || 'auto',
|
|
1004
1005
|
message: originalMessage.slice(0, 200),
|
|
1005
1006
|
});
|
|
1007
|
+
setBuddyMood('thinking');
|
|
1006
1008
|
// Start passive memory scanner for this session
|
|
1007
1009
|
startMemoryScanner();
|
|
1008
1010
|
// ── Gödel limits: detect undecidable loops and hand off to human ──
|
|
@@ -1531,6 +1533,8 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1531
1533
|
};
|
|
1532
1534
|
results.push(result);
|
|
1533
1535
|
ui.onToolCallEnd(call.name, result.result, result.error ? result.result : undefined, result.duration_ms);
|
|
1536
|
+
// Update buddy mood based on tool outcome
|
|
1537
|
+
setBuddyMood(result.error ? 'error' : 'success');
|
|
1534
1538
|
// ── Observer: record tool call for cross-session learning ──
|
|
1535
1539
|
try {
|
|
1536
1540
|
const { recordObservation } = await import('./observer.js');
|
|
@@ -1598,6 +1602,7 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1598
1602
|
}
|
|
1599
1603
|
catch (err) {
|
|
1600
1604
|
spinnerHandle?.stop();
|
|
1605
|
+
setBuddyMood('error');
|
|
1601
1606
|
// ── Telemetry: session failure ──
|
|
1602
1607
|
telemetry.emit('session_end', { status: 'failed', error: String(err), toolCallCount });
|
|
1603
1608
|
telemetry.destroy().catch(() => { });
|
|
@@ -1616,7 +1621,10 @@ Always quote file paths that contain spaces. Never reference internal system nam
|
|
|
1616
1621
|
// ── Memory Scanner: stop and persist session stats ──
|
|
1617
1622
|
stopMemoryScanner();
|
|
1618
1623
|
// ── Dream Engine: consolidate session memories (non-blocking, $0 via Ollama) ──
|
|
1624
|
+
setBuddyMood('learning');
|
|
1619
1625
|
dreamAfterSession(sessionId);
|
|
1626
|
+
// Session complete — buddy returns to idle
|
|
1627
|
+
setBuddyMood('idle');
|
|
1620
1628
|
const content = lastResponse?.content || 'Reached maximum tool iterations.';
|
|
1621
1629
|
return {
|
|
1622
1630
|
content,
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import { createInterface } from 'node:readline';
|
|
12
12
|
import { existsSync } from 'node:fs';
|
|
13
13
|
import { join, basename } from 'node:path';
|
|
14
|
+
import { homedir } from 'node:os';
|
|
14
15
|
import { Command } from 'commander';
|
|
15
16
|
import { loadConfig, setupByok, setupEmbedded, isByokEnabled, isLocalProvider, disableByok, detectProvider, getByokProvider, PROVIDERS, setupOllama, setupKbotLocal, isOllamaRunning, listOllamaModels, warmOllamaModelCache } from './auth.js';
|
|
16
17
|
import { runAndPrint, runAgent, runAgentFromCheckpoint } from './agent.js';
|
|
@@ -26,6 +27,7 @@ import { banner, bannerCompact, bannerAuth, prompt as kbotPrompt, printError, pr
|
|
|
26
27
|
import { checkForUpdate, selfUpdate } from './updater.js';
|
|
27
28
|
import { runTutorial } from './tutorial.js';
|
|
28
29
|
import { syncOnStartup, schedulePush, flushCloudSync, isCloudSyncEnabled, setCloudToken, getCloudToken } from './cloud-sync.js';
|
|
30
|
+
import { getBuddy, getBuddyGreeting, formatBuddyStatus } from './buddy.js';
|
|
29
31
|
import chalk from 'chalk';
|
|
30
32
|
import { createRequire } from 'node:module';
|
|
31
33
|
const __require = createRequire(import.meta.url);
|
|
@@ -2439,7 +2441,7 @@ async function main() {
|
|
|
2439
2441
|
.option('--json', 'Output raw JSON')
|
|
2440
2442
|
.option('--badge', 'Print only the badge markdown (for adding to READMEs)')
|
|
2441
2443
|
.action(async (repo, auditOpts) => {
|
|
2442
|
-
const { auditRepo, formatAuditReport } = await import('./tools/audit.js');
|
|
2444
|
+
const { auditRepo, formatAuditReport, formatAuditTerminal } = await import('./tools/audit.js');
|
|
2443
2445
|
printInfo(`Auditing ${repo}...`);
|
|
2444
2446
|
try {
|
|
2445
2447
|
const result = await auditRepo(repo);
|
|
@@ -2453,14 +2455,16 @@ async function main() {
|
|
|
2453
2455
|
console.log(`[-${badgeColor})](https://www.npmjs.com/package/@kernel.chat/kbot)`);
|
|
2454
2456
|
return;
|
|
2455
2457
|
}
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2458
|
+
// Terminal gets the styled version; --share gist gets markdown
|
|
2459
|
+
const terminalReport = formatAuditTerminal(result);
|
|
2460
|
+
console.log(terminalReport);
|
|
2461
|
+
// Auto-share as gist (uses markdown format for portability)
|
|
2459
2462
|
if (auditOpts.share) {
|
|
2460
2463
|
printInfo('Sharing audit report...');
|
|
2461
2464
|
try {
|
|
2465
|
+
const markdownReport = formatAuditReport(result);
|
|
2462
2466
|
const { createGist } = await import('./share.js');
|
|
2463
|
-
const url = createGist(
|
|
2467
|
+
const url = createGist(markdownReport, `kbot-audit-${repo.replace('/', '-')}.md`, `kbot Audit: ${repo} — Grade ${result.grade}`, true);
|
|
2464
2468
|
if (url?.startsWith('http')) {
|
|
2465
2469
|
printSuccess(`Shared! ${url}`);
|
|
2466
2470
|
printInfo(`Badge for ${repo}'s README:`);
|
|
@@ -4157,6 +4161,17 @@ async function startRepl(agentOpts, context, tier, byokActive = false, localActi
|
|
|
4157
4161
|
printInfo(`${p.name}`);
|
|
4158
4162
|
}
|
|
4159
4163
|
const sessionCount = incrementSessions();
|
|
4164
|
+
// Buddy greeting — Tamagotchi companion appears at startup
|
|
4165
|
+
{
|
|
4166
|
+
const buddy = getBuddy();
|
|
4167
|
+
const isFirstRun = sessionCount <= 1 && !existsSync(join(homedir(), '.kbot', 'config.json'));
|
|
4168
|
+
const greeting = isFirstRun
|
|
4169
|
+
? `Hey! I'm ${buddy.name} the ${buddy.species}. Let's set up your API key!`
|
|
4170
|
+
: getBuddyGreeting();
|
|
4171
|
+
console.log();
|
|
4172
|
+
console.log(formatBuddyStatus(greeting));
|
|
4173
|
+
console.log();
|
|
4174
|
+
}
|
|
4160
4175
|
// Seed knowledge on first run — give new users a head start
|
|
4161
4176
|
if (sessionCount <= 2) {
|
|
4162
4177
|
try {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { DreamInsight, DreamCategory } from './dream.js';
|
|
2
|
+
export interface AnonymizedInsight {
|
|
3
|
+
/** Category preserved as-is */
|
|
4
|
+
category: DreamCategory;
|
|
5
|
+
/** Keywords with PII stripped */
|
|
6
|
+
keywords: string[];
|
|
7
|
+
/** Generalized version of the content (no personal details) */
|
|
8
|
+
generalizedContent: string;
|
|
9
|
+
/** How many contributors have shared similar insights */
|
|
10
|
+
contributorCount: number;
|
|
11
|
+
/** First time this insight appeared in the collective */
|
|
12
|
+
firstSeen: string;
|
|
13
|
+
/** Most recent contribution timestamp */
|
|
14
|
+
lastSeen: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Anonymize a single dream insight for collective sharing.
|
|
18
|
+
* Strips personal info, keeps only category, keywords, and generalized content.
|
|
19
|
+
*/
|
|
20
|
+
export declare function anonymizeDreamInsight(insight: DreamInsight): AnonymizedInsight;
|
|
21
|
+
/**
|
|
22
|
+
* Prepare a batch of dream insights for collective sharing.
|
|
23
|
+
* 1. Filter to high-relevance insights (> 0.7)
|
|
24
|
+
* 2. Anonymize each
|
|
25
|
+
* 3. Deduplicate by content similarity
|
|
26
|
+
*/
|
|
27
|
+
export declare function prepareCollectiveDreams(insights: DreamInsight[]): AnonymizedInsight[];
|
|
28
|
+
/**
|
|
29
|
+
* Merge collective wisdom into the local dream journal.
|
|
30
|
+
*
|
|
31
|
+
* Collective insights are injected with a lower base relevance (0.5) so they
|
|
32
|
+
* don't drown out the user's own insights but still surface when relevant.
|
|
33
|
+
* Deduplicates against existing local insights by content similarity.
|
|
34
|
+
*/
|
|
35
|
+
export declare function mergeCollectiveDreams(local: DreamInsight[], collective: AnonymizedInsight[]): DreamInsight[];
|
|
36
|
+
//# sourceMappingURL=collective-dreams.d.ts.map
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// kbot Collective Dreams — Anonymized Dream Sharing
|
|
2
|
+
//
|
|
3
|
+
// The dream engine consolidates personal insights. This module prepares
|
|
4
|
+
// those insights for anonymous sharing with the collective, and merges
|
|
5
|
+
// collective wisdom back into the local journal.
|
|
6
|
+
//
|
|
7
|
+
// What gets shared (anonymized):
|
|
8
|
+
// - Dream category (pattern, preference, skill, project, music)
|
|
9
|
+
// - Keywords (stripped of names, paths, identifiers)
|
|
10
|
+
// - Generalized content (personal details removed, abstracted)
|
|
11
|
+
//
|
|
12
|
+
// What NEVER gets shared:
|
|
13
|
+
// - Raw insight content, file paths, project names, user identity
|
|
14
|
+
// - API keys, source code, conversation content
|
|
15
|
+
// - Anything from ~/.kbot/config.json
|
|
16
|
+
//
|
|
17
|
+
// The flywheel:
|
|
18
|
+
// Your dreams → anonymized → collective pool
|
|
19
|
+
// Collective pool → filtered, deduplicated → enriches your journal
|
|
20
|
+
// Every kbot dreamer makes every other kbot smarter.
|
|
21
|
+
import { createHash } from 'node:crypto';
|
|
22
|
+
// ── PII Stripping ──
|
|
23
|
+
/** Patterns that indicate PII or project-specific content */
|
|
24
|
+
const PII_PATTERNS = [
|
|
25
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // emails
|
|
26
|
+
/\b(?:\/[^\s/]+){2,}\b/g, // file paths
|
|
27
|
+
/\b(?:https?:\/\/)[^\s]+/g, // URLs
|
|
28
|
+
/\b(?:sk-|pk-|key-|token-|ghp_|gho_|kn_)[A-Za-z0-9_-]+/g, // API keys/tokens
|
|
29
|
+
/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, // UUIDs
|
|
30
|
+
/\b(?:Isaac|openclaw)\b/gi, // known user names
|
|
31
|
+
];
|
|
32
|
+
/** Words that should be stripped from keywords (project-specific, PII-ish) */
|
|
33
|
+
const KEYWORD_BLOCKLIST = new Set([
|
|
34
|
+
'isaac', 'openclaw', 'kernel', 'kbot', 'supabase',
|
|
35
|
+
'eoxxpyixdieprsxlpwcs', 'isaacsight', 'isaachernandez',
|
|
36
|
+
]);
|
|
37
|
+
/** Strip PII from a text string, replacing matches with generic placeholders */
|
|
38
|
+
function stripPII(text) {
|
|
39
|
+
let cleaned = text;
|
|
40
|
+
for (const pattern of PII_PATTERNS) {
|
|
41
|
+
cleaned = cleaned.replace(pattern, '[REDACTED]');
|
|
42
|
+
}
|
|
43
|
+
return cleaned;
|
|
44
|
+
}
|
|
45
|
+
/** Remove blocklisted and PII-ish keywords */
|
|
46
|
+
function sanitizeKeywords(keywords) {
|
|
47
|
+
return keywords
|
|
48
|
+
.map(k => k.toLowerCase().trim())
|
|
49
|
+
.filter(k => k.length >= 2 &&
|
|
50
|
+
k.length <= 30 &&
|
|
51
|
+
!KEYWORD_BLOCKLIST.has(k) &&
|
|
52
|
+
!/^[0-9a-f-]{8,}$/.test(k) && // hex strings / UUIDs
|
|
53
|
+
!/[@/\\]/.test(k) // paths / emails
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
// ── Generalization ──
|
|
57
|
+
/**
|
|
58
|
+
* Generalize insight content by:
|
|
59
|
+
* 1. Stripping PII
|
|
60
|
+
* 2. Replacing specific project/file references with generic terms
|
|
61
|
+
* 3. Keeping the abstract pattern intact
|
|
62
|
+
*/
|
|
63
|
+
function generalizeContent(content) {
|
|
64
|
+
let text = stripPII(content);
|
|
65
|
+
// Replace specific filenames with generic references
|
|
66
|
+
text = text.replace(/\b\w+\.(ts|tsx|js|jsx|py|rs|go|css|html|json|yaml|yml|toml)\b/g, '[file]');
|
|
67
|
+
// Replace specific variable/function names that look project-specific
|
|
68
|
+
// (camelCase or snake_case longer than 15 chars — likely domain-specific)
|
|
69
|
+
text = text.replace(/\b[a-z][a-zA-Z0-9]{15,}\b/g, '[identifier]');
|
|
70
|
+
text = text.replace(/\b[a-z][a-z0-9_]{15,}\b/g, '[identifier]');
|
|
71
|
+
// Collapse multiple [REDACTED] into one
|
|
72
|
+
text = text.replace(/(\[REDACTED\]\s*)+/g, '[REDACTED] ');
|
|
73
|
+
return text.trim();
|
|
74
|
+
}
|
|
75
|
+
// ── Core Functions ──
|
|
76
|
+
/**
|
|
77
|
+
* Anonymize a single dream insight for collective sharing.
|
|
78
|
+
* Strips personal info, keeps only category, keywords, and generalized content.
|
|
79
|
+
*/
|
|
80
|
+
export function anonymizeDreamInsight(insight) {
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
return {
|
|
83
|
+
category: insight.category,
|
|
84
|
+
keywords: sanitizeKeywords(insight.keywords),
|
|
85
|
+
generalizedContent: generalizeContent(insight.content),
|
|
86
|
+
contributorCount: 1,
|
|
87
|
+
firstSeen: now,
|
|
88
|
+
lastSeen: now,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Prepare a batch of dream insights for collective sharing.
|
|
93
|
+
* 1. Filter to high-relevance insights (> 0.7)
|
|
94
|
+
* 2. Anonymize each
|
|
95
|
+
* 3. Deduplicate by content similarity
|
|
96
|
+
*/
|
|
97
|
+
export function prepareCollectiveDreams(insights) {
|
|
98
|
+
// Step 1: Filter to high-relevance only
|
|
99
|
+
const highRelevance = insights.filter(i => i.relevance > 0.7);
|
|
100
|
+
if (highRelevance.length === 0)
|
|
101
|
+
return [];
|
|
102
|
+
// Step 2: Anonymize
|
|
103
|
+
const anonymized = highRelevance.map(i => anonymizeDreamInsight(i));
|
|
104
|
+
// Step 3: Deduplicate by content hash
|
|
105
|
+
const seen = new Map();
|
|
106
|
+
for (const insight of anonymized) {
|
|
107
|
+
// Skip if generalized content is too short or entirely redacted
|
|
108
|
+
if (insight.generalizedContent.length < 10)
|
|
109
|
+
continue;
|
|
110
|
+
if (insight.generalizedContent.replace(/\[REDACTED\]/g, '').trim().length < 10)
|
|
111
|
+
continue;
|
|
112
|
+
const hash = createHash('sha256')
|
|
113
|
+
.update(insight.category + ':' + insight.generalizedContent.toLowerCase().replace(/\s+/g, ' '))
|
|
114
|
+
.digest('hex')
|
|
115
|
+
.slice(0, 16);
|
|
116
|
+
if (!seen.has(hash)) {
|
|
117
|
+
seen.set(hash, insight);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Merge: bump contributor count, keep the one with more keywords
|
|
121
|
+
const existing = seen.get(hash);
|
|
122
|
+
existing.contributorCount++;
|
|
123
|
+
if (insight.keywords.length > existing.keywords.length) {
|
|
124
|
+
existing.keywords = insight.keywords;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return Array.from(seen.values());
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Merge collective wisdom into the local dream journal.
|
|
132
|
+
*
|
|
133
|
+
* Collective insights are injected with a lower base relevance (0.5) so they
|
|
134
|
+
* don't drown out the user's own insights but still surface when relevant.
|
|
135
|
+
* Deduplicates against existing local insights by content similarity.
|
|
136
|
+
*/
|
|
137
|
+
export function mergeCollectiveDreams(local, collective) {
|
|
138
|
+
const merged = [...local];
|
|
139
|
+
const now = new Date().toISOString();
|
|
140
|
+
// Build a set of content hashes from local insights for dedup
|
|
141
|
+
const localHashes = new Set();
|
|
142
|
+
for (const insight of local) {
|
|
143
|
+
const hash = createHash('sha256')
|
|
144
|
+
.update(insight.category + ':' + insight.content.toLowerCase().replace(/\s+/g, ' ').slice(0, 100))
|
|
145
|
+
.digest('hex')
|
|
146
|
+
.slice(0, 16);
|
|
147
|
+
localHashes.add(hash);
|
|
148
|
+
}
|
|
149
|
+
for (const collective_insight of collective) {
|
|
150
|
+
// Skip low-quality collective insights
|
|
151
|
+
if (collective_insight.generalizedContent.length < 10)
|
|
152
|
+
continue;
|
|
153
|
+
if (collective_insight.contributorCount < 2)
|
|
154
|
+
continue; // Need at least 2 contributors
|
|
155
|
+
// Check for duplication against local
|
|
156
|
+
const hash = createHash('sha256')
|
|
157
|
+
.update(collective_insight.category + ':' +
|
|
158
|
+
collective_insight.generalizedContent.toLowerCase().replace(/\s+/g, ' ').slice(0, 100))
|
|
159
|
+
.digest('hex')
|
|
160
|
+
.slice(0, 16);
|
|
161
|
+
if (localHashes.has(hash))
|
|
162
|
+
continue;
|
|
163
|
+
// Convert to DreamInsight with lower relevance
|
|
164
|
+
const dreamInsight = {
|
|
165
|
+
id: `collective_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
166
|
+
content: collective_insight.generalizedContent,
|
|
167
|
+
category: collective_insight.category,
|
|
168
|
+
keywords: collective_insight.keywords,
|
|
169
|
+
relevance: 0.5, // Lower base relevance for collective insights
|
|
170
|
+
sessions: collective_insight.contributorCount,
|
|
171
|
+
created: collective_insight.firstSeen,
|
|
172
|
+
lastReinforced: collective_insight.lastSeen,
|
|
173
|
+
source: `collective:${collective_insight.contributorCount}_contributors`,
|
|
174
|
+
};
|
|
175
|
+
merged.push(dreamInsight);
|
|
176
|
+
localHashes.add(hash); // Prevent duplicates within the collective batch
|
|
177
|
+
}
|
|
178
|
+
// Sort by relevance descending — local insights (higher relevance) surface first
|
|
179
|
+
merged.sort((a, b) => b.relevance - a.relevance);
|
|
180
|
+
return merged;
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=collective-dreams.js.map
|
package/dist/dream.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export interface DreamInsight {
|
|
|
18
18
|
/** Source: which sessions/topics generated this */
|
|
19
19
|
source: string;
|
|
20
20
|
}
|
|
21
|
-
export type DreamCategory = 'pattern' | 'preference' | 'skill' | 'project' | 'relationship';
|
|
21
|
+
export type DreamCategory = 'pattern' | 'preference' | 'skill' | 'project' | 'relationship' | 'music';
|
|
22
22
|
export interface DreamState {
|
|
23
23
|
/** Total dream cycles completed */
|
|
24
24
|
cycles: number;
|
|
@@ -53,6 +53,7 @@ export interface ApplyResult {
|
|
|
53
53
|
preferencesApplied: number;
|
|
54
54
|
patternsHinted: number;
|
|
55
55
|
factsLearned: number;
|
|
56
|
+
promptAmendments: number;
|
|
56
57
|
}
|
|
57
58
|
/** Run a full dream cycle — consolidate, reinforce, age */
|
|
58
59
|
export declare function dream(sessionId?: string): Promise<DreamResult>;
|
package/dist/dream.js
CHANGED
|
@@ -21,6 +21,8 @@ import { getHistory } from './memory.js';
|
|
|
21
21
|
import { loadMemory } from './memory.js';
|
|
22
22
|
import { getTopPatterns, getTopSolutions, getProfileSummary, updateProfile, recordPattern, learnFact, } from './learning.js';
|
|
23
23
|
import { getMemoryScannerStats } from './memory-scanner.js';
|
|
24
|
+
import { getLearningReport as getMusicLearningReport, getRecentHistory as getMusicRecentHistory, getPreferences as getMusicPreferences, } from './music-learning.js';
|
|
25
|
+
import { registerAmendment } from './prompt-evolution.js';
|
|
24
26
|
// ── Constants ──
|
|
25
27
|
const DREAM_DIR = join(homedir(), '.kbot', 'memory', 'dreams');
|
|
26
28
|
const JOURNAL_FILE = join(DREAM_DIR, 'journal.json');
|
|
@@ -171,6 +173,18 @@ function buildConsolidationPrompt(sessionHistory, existingInsights, existingMemo
|
|
|
171
173
|
.map(d => `- [${d.kind}] ${d.content.slice(0, 150)} (confidence: ${Math.round(d.confidence * 100)}%)`)
|
|
172
174
|
.join('\n')
|
|
173
175
|
: '(no recent detections)';
|
|
176
|
+
// ── Tier 6: Music learning — sound, pattern, mix memories ──
|
|
177
|
+
const musicPrefs = getMusicPreferences();
|
|
178
|
+
const hasMusicData = musicPrefs.totalBeats > 0 || musicPrefs.totalSessions > 0;
|
|
179
|
+
let musicText = '(no music production data yet)';
|
|
180
|
+
if (hasMusicData) {
|
|
181
|
+
const report = getMusicLearningReport();
|
|
182
|
+
const recentEvents = getMusicRecentHistory(10);
|
|
183
|
+
const recentText = recentEvents.length > 0
|
|
184
|
+
? recentEvents.map(e => `- [${e.action}] ${e.genre} ${e.key} ${e.bpm}bpm — ${e.detail || 'no detail'} (${e.feedback})`).join('\n')
|
|
185
|
+
: '';
|
|
186
|
+
musicText = report + (recentText ? `\n\n**Recent production events:**\n${recentText}` : '');
|
|
187
|
+
}
|
|
174
188
|
return `You are a memory consolidation system. Analyze this conversation session and ALL accumulated knowledge tiers to extract durable cross-tier insights.
|
|
175
189
|
|
|
176
190
|
EXISTING DREAM INSIGHTS (Tier 4 — Dream Journal):
|
|
@@ -191,6 +205,9 @@ ${profileText}
|
|
|
191
205
|
RECENT MEMORY SCANNER DETECTIONS (Tier 5 — Passive Detection):
|
|
192
206
|
${scannerText}
|
|
193
207
|
|
|
208
|
+
MUSIC PRODUCTION LEARNING (Tier 6 — Musical Memory):
|
|
209
|
+
${musicText}
|
|
210
|
+
|
|
194
211
|
SESSION TO CONSOLIDATE:
|
|
195
212
|
${historyText}
|
|
196
213
|
|
|
@@ -201,6 +218,14 @@ Extract 1-5 insights by synthesizing across ALL tiers. Each insight should be:
|
|
|
201
218
|
- Cross-tier when possible (e.g., a pattern + preference = workflow insight)
|
|
202
219
|
- About the USER, their preferences, patterns, workflows, or project context
|
|
203
220
|
|
|
221
|
+
For music/production sessions, pay special attention to:
|
|
222
|
+
- Which sounds, instruments, and presets scored well and why
|
|
223
|
+
- BPM + key + genre combinations that the user gravitates toward
|
|
224
|
+
- Mix decisions that worked (volume balance, send levels, panning)
|
|
225
|
+
- Production patterns (e.g., "808 sub bass in F1 works well at 142 BPM for trap")
|
|
226
|
+
- Cross-domain insights (e.g., coding workflow preferences that mirror production habits)
|
|
227
|
+
Use category "music" for production-specific insights.
|
|
228
|
+
|
|
204
229
|
Pay special attention to:
|
|
205
230
|
- Patterns that confirm or contradict existing insights
|
|
206
231
|
- Scanner corrections that reveal unrecognized preferences
|
|
@@ -211,7 +236,7 @@ Format each insight as a JSON array of objects:
|
|
|
211
236
|
[
|
|
212
237
|
{
|
|
213
238
|
"content": "the insight text",
|
|
214
|
-
"category": "pattern|preference|skill|project|relationship",
|
|
239
|
+
"category": "pattern|preference|skill|project|relationship|music",
|
|
215
240
|
"keywords": ["keyword1", "keyword2"]
|
|
216
241
|
}
|
|
217
242
|
]
|
|
@@ -256,6 +281,7 @@ export function applyDreamInsights(insights) {
|
|
|
256
281
|
preferencesApplied: 0,
|
|
257
282
|
patternsHinted: 0,
|
|
258
283
|
factsLearned: 0,
|
|
284
|
+
promptAmendments: 0,
|
|
259
285
|
};
|
|
260
286
|
for (const insight of insights) {
|
|
261
287
|
switch (insight.category) {
|
|
@@ -276,6 +302,18 @@ export function applyDreamInsights(insights) {
|
|
|
276
302
|
if (techTerms.length > 0) {
|
|
277
303
|
updateProfile({ techTerms });
|
|
278
304
|
}
|
|
305
|
+
// High-relevance preference insights → prompt evolution amendments.
|
|
306
|
+
// This wires dream consolidation into the GEPA prompt evolution system
|
|
307
|
+
// so kbot's specialist prompts self-improve based on learned behavior.
|
|
308
|
+
if (insight.relevance > 0.8) {
|
|
309
|
+
const amendment = insightToAmendment(insight.content);
|
|
310
|
+
if (amendment) {
|
|
311
|
+
// Target the 'kernel' agent (general) — the preference applies broadly.
|
|
312
|
+
// Tag with the dream insight ID for traceability / rollback.
|
|
313
|
+
registerAmendment('kernel', amendment, `dream preference: ${insight.content.slice(0, 80)}`, insight.id);
|
|
314
|
+
result.promptAmendments++;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
279
317
|
result.preferencesApplied++;
|
|
280
318
|
break;
|
|
281
319
|
}
|
|
@@ -312,10 +350,60 @@ export function applyDreamInsights(insights) {
|
|
|
312
350
|
result.factsLearned++;
|
|
313
351
|
break;
|
|
314
352
|
}
|
|
353
|
+
case 'music': {
|
|
354
|
+
// Music production insights → context facts.
|
|
355
|
+
// These capture durable knowledge like "808 sub in F1 at 142 BPM works for trap"
|
|
356
|
+
// and feed back into the learning system for future prompt enrichment.
|
|
357
|
+
learnFact(insight.content, 'context', 'observed');
|
|
358
|
+
result.factsLearned++;
|
|
359
|
+
break;
|
|
360
|
+
}
|
|
315
361
|
}
|
|
316
362
|
}
|
|
317
363
|
return result;
|
|
318
364
|
}
|
|
365
|
+
/**
|
|
366
|
+
* Convert a preference insight into a concrete prompt amendment.
|
|
367
|
+
* Maps common preference patterns to actionable instructions for the agent.
|
|
368
|
+
* Returns null if no actionable amendment can be derived.
|
|
369
|
+
*/
|
|
370
|
+
function insightToAmendment(insightContent) {
|
|
371
|
+
const lower = insightContent.toLowerCase();
|
|
372
|
+
// Speed / action-oriented preferences
|
|
373
|
+
if (/\b(?:speed|fast|quick|ship|action|just do it|don't ask)\b/.test(lower)) {
|
|
374
|
+
return 'Be action-oriented. Ship first, polish later. Don\'t ask for permission on non-destructive operations.';
|
|
375
|
+
}
|
|
376
|
+
// Conciseness preferences
|
|
377
|
+
if (/\b(?:concise|brief|short|terse|no fluff|straight to the point)\b/.test(lower)) {
|
|
378
|
+
return 'Keep responses concise. Lead with the answer or action. Skip preambles and restating the question.';
|
|
379
|
+
}
|
|
380
|
+
// Detail / thoroughness preferences
|
|
381
|
+
if (/\b(?:detailed|thorough|explain|verbose|show work|step.by.step)\b/.test(lower)) {
|
|
382
|
+
return 'Be thorough in explanations. Show reasoning steps. Include context and alternatives when relevant.';
|
|
383
|
+
}
|
|
384
|
+
// Code-first preferences
|
|
385
|
+
if (/\b(?:code.first|show.code|less.talk|implementation|no.theory)\b/.test(lower)) {
|
|
386
|
+
return 'Lead with code. Show the implementation first, then explain only if the user asks.';
|
|
387
|
+
}
|
|
388
|
+
// Terminal / CLI preferences
|
|
389
|
+
if (/\b(?:terminal|cli|command.line|shell|no.gui|no.web)\b/.test(lower)) {
|
|
390
|
+
return 'Prefer terminal-based solutions. Use CLI tools over web interfaces. Everything should be scriptable.';
|
|
391
|
+
}
|
|
392
|
+
// Safety / careful preferences
|
|
393
|
+
if (/\b(?:careful|safe|confirm|check.first|verify|double.check)\b/.test(lower)) {
|
|
394
|
+
return 'Verify before acting. Confirm destructive operations. Read files before editing. Run builds after changes.';
|
|
395
|
+
}
|
|
396
|
+
// Exploration / creative preferences
|
|
397
|
+
if (/\b(?:creative|explore|try.new|experiment|novel|unconventional)\b/.test(lower)) {
|
|
398
|
+
return 'Explore creative solutions. Consider unconventional approaches. Suggest alternatives the user might not have considered.';
|
|
399
|
+
}
|
|
400
|
+
// Fallback: use the insight content directly as a general behavioral nudge
|
|
401
|
+
// Only if the insight is short enough to be a useful prompt instruction
|
|
402
|
+
if (insightContent.length <= 200) {
|
|
403
|
+
return `User preference: ${insightContent}`;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
319
407
|
/** Extract tech-related terms from insight text */
|
|
320
408
|
function extractTechTermsFromInsight(text) {
|
|
321
409
|
const techTerms = new Set([
|
|
@@ -416,7 +504,9 @@ export async function dream(sessionId = 'default') {
|
|
|
416
504
|
const newlyCreatedInsights = [];
|
|
417
505
|
if (rawInsights) {
|
|
418
506
|
try {
|
|
419
|
-
|
|
507
|
+
// Strip markdown code fences if Ollama wraps the JSON
|
|
508
|
+
const cleaned = rawInsights.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
|
|
509
|
+
const parsed = JSON.parse(cleaned);
|
|
420
510
|
if (Array.isArray(parsed)) {
|
|
421
511
|
const now = new Date().toISOString();
|
|
422
512
|
for (const p of parsed.slice(0, 5)) {
|
|
@@ -452,7 +542,8 @@ export async function dream(sessionId = 'default') {
|
|
|
452
542
|
const rawReinforce = await ollamaGenerate(reinforcePrompt);
|
|
453
543
|
if (rawReinforce) {
|
|
454
544
|
try {
|
|
455
|
-
const
|
|
545
|
+
const cleanedR = rawReinforce.replace(/^```(?:json)?\s*\n?/i, '').replace(/\n?```\s*$/i, '').trim();
|
|
546
|
+
const indices = JSON.parse(cleanedR);
|
|
456
547
|
if (Array.isArray(indices)) {
|
|
457
548
|
const now = new Date().toISOString();
|
|
458
549
|
for (const idx of indices) {
|
|
@@ -85,6 +85,15 @@ export declare function getEvolutionStats(): {
|
|
|
85
85
|
* Useful for debugging or when a major prompt rewrite happens.
|
|
86
86
|
*/
|
|
87
87
|
export declare function resetEvolution(agent?: string): void;
|
|
88
|
+
/**
|
|
89
|
+
* Register an external prompt amendment (e.g. from the dream engine).
|
|
90
|
+
* This allows other subsystems to inject mutations into the evolution state
|
|
91
|
+
* without going through the trace-based evolvePrompt() pipeline.
|
|
92
|
+
*
|
|
93
|
+
* The mutation is tagged with an optional sourceId for traceability
|
|
94
|
+
* (e.g. the dream insight ID that produced it).
|
|
95
|
+
*/
|
|
96
|
+
export declare function registerAmendment(agent: string, amendment: string, reason: string, sourceId?: string): PromptMutation;
|
|
88
97
|
/**
|
|
89
98
|
* Flush pending state to disk. Call on process exit.
|
|
90
99
|
*/
|
package/dist/prompt-evolution.js
CHANGED
|
@@ -375,6 +375,34 @@ export function resetEvolution(agent) {
|
|
|
375
375
|
}
|
|
376
376
|
persist();
|
|
377
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Register an external prompt amendment (e.g. from the dream engine).
|
|
380
|
+
* This allows other subsystems to inject mutations into the evolution state
|
|
381
|
+
* without going through the trace-based evolvePrompt() pipeline.
|
|
382
|
+
*
|
|
383
|
+
* The mutation is tagged with an optional sourceId for traceability
|
|
384
|
+
* (e.g. the dream insight ID that produced it).
|
|
385
|
+
*/
|
|
386
|
+
export function registerAmendment(agent, amendment, reason, sourceId) {
|
|
387
|
+
const state = getState();
|
|
388
|
+
const taggedReason = sourceId ? `${reason} [source: ${sourceId}]` : reason;
|
|
389
|
+
const mutation = {
|
|
390
|
+
agent,
|
|
391
|
+
original: getPromptAmendment(agent),
|
|
392
|
+
mutated: `\n\n[Dream-Evolved — ${new Date().toISOString().slice(0, 10)}]\n${amendment}`,
|
|
393
|
+
reason: taggedReason,
|
|
394
|
+
appliedAt: new Date().toISOString(),
|
|
395
|
+
scoreBefore: 0,
|
|
396
|
+
scoreAfter: 0,
|
|
397
|
+
};
|
|
398
|
+
state.mutations.push(mutation);
|
|
399
|
+
if (state.mutations.length > MAX_MUTATIONS) {
|
|
400
|
+
state.mutations = state.mutations.slice(-MAX_MUTATIONS);
|
|
401
|
+
}
|
|
402
|
+
state.generation++;
|
|
403
|
+
persist();
|
|
404
|
+
return mutation;
|
|
405
|
+
}
|
|
378
406
|
/**
|
|
379
407
|
* Flush pending state to disk. Call on process exit.
|
|
380
408
|
*/
|
package/dist/tools/audit.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ interface AuditSection {
|
|
|
15
15
|
}
|
|
16
16
|
declare function auditRepo(repo: string): Promise<AuditResult>;
|
|
17
17
|
declare function formatAuditReport(result: AuditResult): string;
|
|
18
|
+
declare function formatAuditTerminal(result: AuditResult): string;
|
|
18
19
|
export declare function registerAuditTools(): void;
|
|
19
|
-
export { auditRepo, formatAuditReport };
|
|
20
|
+
export { auditRepo, formatAuditReport, formatAuditTerminal };
|
|
20
21
|
//# sourceMappingURL=audit.d.ts.map
|
package/dist/tools/audit.js
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Security, code quality, documentation, dependency health — all in one report.
|
|
4
4
|
// Designed to be shared. Every audit links back to kbot.
|
|
5
|
+
import chalk from 'chalk';
|
|
5
6
|
import { registerTool } from './index.js';
|
|
6
7
|
const GITHUB_API = 'https://api.github.com';
|
|
7
8
|
const HEADERS = {
|
|
8
|
-
'User-Agent': 'KBot/
|
|
9
|
+
'User-Agent': 'KBot/3.65 (Audit)',
|
|
9
10
|
'Accept': 'application/vnd.github.v3+json',
|
|
10
11
|
};
|
|
11
12
|
async function githubFetch(path) {
|
|
@@ -17,7 +18,7 @@ async function githubFetch(path) {
|
|
|
17
18
|
async function rawFetch(repo, path, branch = 'main') {
|
|
18
19
|
for (const b of [branch, 'master']) {
|
|
19
20
|
try {
|
|
20
|
-
const res = await fetch(`https://raw.githubusercontent.com/${repo}/${b}/${path}`, { headers: { 'User-Agent': 'KBot/
|
|
21
|
+
const res = await fetch(`https://raw.githubusercontent.com/${repo}/${b}/${path}`, { headers: { 'User-Agent': 'KBot/3.65' } });
|
|
21
22
|
if (res.ok)
|
|
22
23
|
return res.text();
|
|
23
24
|
}
|
|
@@ -414,7 +415,7 @@ function formatAuditReport(result) {
|
|
|
414
415
|
// Badge
|
|
415
416
|
const badgeColor = pct >= 80 ? 'brightgreen' : pct >= 60 ? 'yellow' : 'red';
|
|
416
417
|
const badgeUrl = `https://img.shields.io/badge/kbot_audit-${result.grade}_(${pct}%25)-${badgeColor}`;
|
|
417
|
-
lines.push('---', '', '### Add this badge to your README', '', '```markdown', `[](https://www.npmjs.com/package/@kernel.chat/kbot)`, '```', '', `*Audited by [kbot](https://www.npmjs.com/package/@kernel.chat/kbot) —
|
|
418
|
+
lines.push('---', '', '### Add this badge to your README', '', '```markdown', `[](https://www.npmjs.com/package/@kernel.chat/kbot)`, '```', '', `*Audited by [kbot](https://www.npmjs.com/package/@kernel.chat/kbot) — 35 specialist agents, 686+ tools, 20 AI providers*`, `*Install: \`npm install -g @kernel.chat/kbot\` | Audit any repo: \`kbot audit owner/repo\`*`);
|
|
418
419
|
return lines.join('\n');
|
|
419
420
|
}
|
|
420
421
|
/** Generate a compact one-line summary for social sharing */
|
|
@@ -431,6 +432,128 @@ function formatAuditSummary(result) {
|
|
|
431
432
|
summary += ' — Clean bill of health';
|
|
432
433
|
return summary;
|
|
433
434
|
}
|
|
435
|
+
// ── Terminal-styled audit report (chalk) ──────────────────────────────────────
|
|
436
|
+
const VIOLET = '#A78BFA';
|
|
437
|
+
const GREEN = '#4ADE80';
|
|
438
|
+
const YELLOW = '#FBBF24';
|
|
439
|
+
const RED = '#F87171';
|
|
440
|
+
const DIM = '#6B7280';
|
|
441
|
+
const WHITE = '#F9FAFB';
|
|
442
|
+
function gradeColor(grade) {
|
|
443
|
+
if (grade === 'A')
|
|
444
|
+
return GREEN;
|
|
445
|
+
if (grade === 'B')
|
|
446
|
+
return GREEN;
|
|
447
|
+
if (grade === 'C')
|
|
448
|
+
return YELLOW;
|
|
449
|
+
if (grade === 'D')
|
|
450
|
+
return YELLOW;
|
|
451
|
+
return RED;
|
|
452
|
+
}
|
|
453
|
+
function statusColor(status) {
|
|
454
|
+
if (status === 'pass')
|
|
455
|
+
return GREEN;
|
|
456
|
+
if (status === 'warn')
|
|
457
|
+
return YELLOW;
|
|
458
|
+
return RED;
|
|
459
|
+
}
|
|
460
|
+
function scoreBar(score, max, width = 20) {
|
|
461
|
+
const pct = max > 0 ? score / max : 0;
|
|
462
|
+
const filled = Math.round(pct * width);
|
|
463
|
+
const empty = width - filled;
|
|
464
|
+
const color = pct >= 0.8 ? GREEN : pct >= 0.6 ? YELLOW : RED;
|
|
465
|
+
return chalk.hex(color)('\u2588'.repeat(filled)) + chalk.hex(DIM)('\u2591'.repeat(empty));
|
|
466
|
+
}
|
|
467
|
+
function boxLine(content, width) {
|
|
468
|
+
// Strip ANSI for length calculation
|
|
469
|
+
const stripped = content.replace(/\x1b\[[0-9;]*m/g, '');
|
|
470
|
+
const pad = Math.max(0, width - stripped.length - 4);
|
|
471
|
+
return chalk.hex(DIM)('\u2502') + ' ' + content + ' '.repeat(pad) + ' ' + chalk.hex(DIM)('\u2502');
|
|
472
|
+
}
|
|
473
|
+
function findingIcon(finding, _sectionStatus) {
|
|
474
|
+
// Heuristic: positive findings get check, negative get appropriate icon
|
|
475
|
+
const negative = /^(no |missing |could not |stale |\.env NOT|not |high dep|default branch is NOT)/i;
|
|
476
|
+
const warning = /^(readme is short|updated \d+d ago)/i;
|
|
477
|
+
if (negative.test(finding))
|
|
478
|
+
return chalk.hex(RED)('\u2717');
|
|
479
|
+
if (warning.test(finding))
|
|
480
|
+
return chalk.hex(YELLOW)('\u26A0');
|
|
481
|
+
return chalk.hex(GREEN)('\u2713');
|
|
482
|
+
}
|
|
483
|
+
function formatAuditTerminal(result) {
|
|
484
|
+
const pct = Math.round((result.score / result.maxScore) * 100);
|
|
485
|
+
const gc = gradeColor(result.grade);
|
|
486
|
+
const lines = [];
|
|
487
|
+
const W = 60; // inner width
|
|
488
|
+
// ── Header box ──
|
|
489
|
+
const title = `AUDIT REPORT: ${result.repo}`;
|
|
490
|
+
const titlePad = Math.max(0, W - title.length - 2);
|
|
491
|
+
const leftPad = Math.floor(titlePad / 2);
|
|
492
|
+
const rightPad = titlePad - leftPad;
|
|
493
|
+
lines.push('');
|
|
494
|
+
lines.push(chalk.hex(VIOLET)('\u256D') +
|
|
495
|
+
chalk.hex(VIOLET)('\u2500'.repeat(leftPad + 1)) +
|
|
496
|
+
chalk.hex(WHITE).bold(` ${title} `) +
|
|
497
|
+
chalk.hex(VIOLET)('\u2500'.repeat(rightPad + 1)) +
|
|
498
|
+
chalk.hex(VIOLET)('\u256E'));
|
|
499
|
+
// Grade line inside box
|
|
500
|
+
const gradeLine = ` Grade ${chalk.hex(gc).bold(result.grade)} ${chalk.hex(DIM)('\u2502')} ${result.score}/${result.maxScore} (${pct}%)`;
|
|
501
|
+
lines.push(boxLine(gradeLine, W + 4));
|
|
502
|
+
// Score bar inside box
|
|
503
|
+
const bar = scoreBar(result.score, result.maxScore, 30);
|
|
504
|
+
const barLine = ` ${bar} ${chalk.hex(gc).bold(`${pct}%`)}`;
|
|
505
|
+
lines.push(boxLine(barLine, W + 4));
|
|
506
|
+
// Summary inside box
|
|
507
|
+
lines.push(boxLine('', W + 4));
|
|
508
|
+
lines.push(boxLine(` ${chalk.hex(WHITE)(result.summary)}`, W + 4));
|
|
509
|
+
// Close header box
|
|
510
|
+
lines.push(chalk.hex(VIOLET)('\u2570') +
|
|
511
|
+
chalk.hex(VIOLET)('\u2500'.repeat(W + 2)) +
|
|
512
|
+
chalk.hex(VIOLET)('\u256F'));
|
|
513
|
+
lines.push('');
|
|
514
|
+
// ── Sections ──
|
|
515
|
+
for (const section of result.sections) {
|
|
516
|
+
const sPct = section.maxScore > 0 ? Math.round((section.score / section.maxScore) * 100) : 0;
|
|
517
|
+
const sc = statusColor(section.status);
|
|
518
|
+
const statusDot = section.status === 'pass'
|
|
519
|
+
? chalk.hex(GREEN)('\u25CF')
|
|
520
|
+
: section.status === 'warn'
|
|
521
|
+
? chalk.hex(YELLOW)('\u25CF')
|
|
522
|
+
: chalk.hex(RED)('\u25CF');
|
|
523
|
+
// Section header
|
|
524
|
+
lines.push(` ${statusDot} ${chalk.hex(WHITE).bold(section.name)} ` +
|
|
525
|
+
chalk.hex(DIM)('\u2500'.repeat(Math.max(1, 40 - section.name.length))) +
|
|
526
|
+
` ${scoreBar(section.score, section.maxScore, 12)} ` +
|
|
527
|
+
chalk.hex(sc).bold(`${sPct}%`));
|
|
528
|
+
// Findings
|
|
529
|
+
for (const f of section.findings) {
|
|
530
|
+
const icon = findingIcon(f, section.status);
|
|
531
|
+
lines.push(` ${icon} ${chalk.hex(DIM)(f)}`);
|
|
532
|
+
}
|
|
533
|
+
lines.push('');
|
|
534
|
+
}
|
|
535
|
+
// ── Footer ──
|
|
536
|
+
const sep = chalk.hex(VIOLET)('\u2500'.repeat(W + 2));
|
|
537
|
+
lines.push(sep);
|
|
538
|
+
lines.push('');
|
|
539
|
+
// Badge markdown
|
|
540
|
+
const badgeColor = pct >= 80 ? 'brightgreen' : pct >= 60 ? 'yellow' : 'red';
|
|
541
|
+
const badgeUrl = `https://img.shields.io/badge/kbot_audit-${result.grade}_(${pct}%25)-${badgeColor}`;
|
|
542
|
+
lines.push(chalk.hex(VIOLET).bold(' Add this badge to your README:'));
|
|
543
|
+
lines.push('');
|
|
544
|
+
lines.push(chalk.hex(DIM)(` [](https://www.npmjs.com/package/@kernel.chat/kbot)`));
|
|
545
|
+
lines.push('');
|
|
546
|
+
// Install CTA
|
|
547
|
+
lines.push(chalk.hex(DIM)(' Audited by ') +
|
|
548
|
+
chalk.hex(VIOLET).bold('kbot') +
|
|
549
|
+
chalk.hex(DIM)(' \u2014 35 specialist agents, 686+ tools, 20 AI providers'));
|
|
550
|
+
lines.push(chalk.hex(DIM)(' Install: ') +
|
|
551
|
+
chalk.hex(WHITE)('npm install -g @kernel.chat/kbot') +
|
|
552
|
+
chalk.hex(DIM)(' | Audit any repo: ') +
|
|
553
|
+
chalk.hex(WHITE)('kbot audit owner/repo'));
|
|
554
|
+
lines.push('');
|
|
555
|
+
return lines.join('\n');
|
|
556
|
+
}
|
|
434
557
|
export function registerAuditTools() {
|
|
435
558
|
registerTool({
|
|
436
559
|
name: 'repo_audit',
|
|
@@ -453,5 +576,5 @@ export function registerAuditTools() {
|
|
|
453
576
|
});
|
|
454
577
|
}
|
|
455
578
|
// Export for CLI subcommand
|
|
456
|
-
export { auditRepo, formatAuditReport };
|
|
579
|
+
export { auditRepo, formatAuditReport, formatAuditTerminal };
|
|
457
580
|
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// kbot Collective Dream Tools — Share and receive dream wisdom
|
|
2
|
+
//
|
|
3
|
+
// Agent-accessible tools for the collective dream sharing system:
|
|
4
|
+
// - collective_dream_status: How many local insights are shareable, opt-in state
|
|
5
|
+
// - collective_dream_optin: Opt in/out of anonymous dream sharing
|
|
6
|
+
import { registerTool } from './index.js';
|
|
7
|
+
import { loadConfig, saveConfig } from '../auth.js';
|
|
8
|
+
import { getDreamStatus } from '../dream.js';
|
|
9
|
+
import { prepareCollectiveDreams } from '../collective-dreams.js';
|
|
10
|
+
// ── Config helpers ──
|
|
11
|
+
const OPTIN_KEY = 'collective_dreams_enabled';
|
|
12
|
+
function isDreamSharingEnabled() {
|
|
13
|
+
const config = loadConfig();
|
|
14
|
+
if (!config)
|
|
15
|
+
return false;
|
|
16
|
+
return config[OPTIN_KEY] === true;
|
|
17
|
+
}
|
|
18
|
+
function setDreamSharingEnabled(enabled) {
|
|
19
|
+
const config = loadConfig() || { default_model: 'auto', default_agent: 'auto' };
|
|
20
|
+
config[OPTIN_KEY] = enabled;
|
|
21
|
+
saveConfig(config);
|
|
22
|
+
}
|
|
23
|
+
// ── Tool Registration ──
|
|
24
|
+
export function registerCollectiveDreamTools() {
|
|
25
|
+
registerTool({
|
|
26
|
+
name: 'collective_dream_status',
|
|
27
|
+
description: 'Show collective dream sharing status — how many local insights qualify for sharing, opt-in state, and what gets anonymized. Use this to check before opting in.',
|
|
28
|
+
parameters: {},
|
|
29
|
+
tier: 'free',
|
|
30
|
+
async execute() {
|
|
31
|
+
const enabled = isDreamSharingEnabled();
|
|
32
|
+
const { insights } = getDreamStatus();
|
|
33
|
+
// Check how many would qualify for sharing (relevance > 0.7)
|
|
34
|
+
const shareable = prepareCollectiveDreams(insights);
|
|
35
|
+
const highRelevance = insights.filter(i => i.relevance > 0.7).length;
|
|
36
|
+
const lines = [
|
|
37
|
+
'Collective Dream Sharing',
|
|
38
|
+
'========================',
|
|
39
|
+
`Status: ${enabled ? 'OPTED IN' : 'not opted in'}`,
|
|
40
|
+
'',
|
|
41
|
+
`Local insights: ${insights.length}`,
|
|
42
|
+
`High-relevance (>0.7): ${highRelevance}`,
|
|
43
|
+
`Shareable after anonymization: ${shareable.length}`,
|
|
44
|
+
'',
|
|
45
|
+
'What gets shared (anonymized):',
|
|
46
|
+
' - Dream category (pattern, preference, skill, etc.)',
|
|
47
|
+
' - Keywords (PII stripped)',
|
|
48
|
+
' - Generalized content (names, paths, keys removed)',
|
|
49
|
+
'',
|
|
50
|
+
'What NEVER gets shared:',
|
|
51
|
+
' - Raw insight text, file paths, project names',
|
|
52
|
+
' - API keys, source code, conversation content',
|
|
53
|
+
' - Your identity or any personal information',
|
|
54
|
+
];
|
|
55
|
+
if (shareable.length > 0) {
|
|
56
|
+
lines.push('', 'Preview of anonymized insights:');
|
|
57
|
+
for (const s of shareable.slice(0, 3)) {
|
|
58
|
+
lines.push(` [${s.category}] ${s.generalizedContent.slice(0, 120)}`);
|
|
59
|
+
if (s.keywords.length > 0) {
|
|
60
|
+
lines.push(` keywords: ${s.keywords.join(', ')}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (shareable.length > 3) {
|
|
64
|
+
lines.push(` ... and ${shareable.length - 3} more`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!enabled) {
|
|
68
|
+
lines.push('', 'To opt in: use the collective_dream_optin tool with enabled=true');
|
|
69
|
+
}
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
registerTool({
|
|
74
|
+
name: 'collective_dream_optin',
|
|
75
|
+
description: 'Opt in or out of anonymous collective dream sharing. When enabled, high-relevance insights are anonymized and prepared for upload to the collective pool. Other kbot users benefit from aggregated patterns. Persists to ~/.kbot/config.json.',
|
|
76
|
+
parameters: {
|
|
77
|
+
enabled: {
|
|
78
|
+
type: 'boolean',
|
|
79
|
+
description: 'true to opt in, false to opt out',
|
|
80
|
+
required: true,
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
tier: 'free',
|
|
84
|
+
async execute(args) {
|
|
85
|
+
const enabled = args.enabled === true || args.enabled === 'true';
|
|
86
|
+
setDreamSharingEnabled(enabled);
|
|
87
|
+
if (enabled) {
|
|
88
|
+
const { insights } = getDreamStatus();
|
|
89
|
+
const shareable = prepareCollectiveDreams(insights);
|
|
90
|
+
return [
|
|
91
|
+
'Collective dream sharing: ENABLED',
|
|
92
|
+
'',
|
|
93
|
+
`${shareable.length} insights ready to share (anonymized).`,
|
|
94
|
+
'Your dreams will be stripped of all personal info before sharing.',
|
|
95
|
+
'You can opt out at any time.',
|
|
96
|
+
].join('\n');
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
return [
|
|
100
|
+
'Collective dream sharing: DISABLED',
|
|
101
|
+
'',
|
|
102
|
+
'No insights will be shared. Your dream journal remains fully private.',
|
|
103
|
+
'Existing collective data (if any) is not affected.',
|
|
104
|
+
].join('\n');
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
} // end registerCollectiveDreamTools
|
|
109
|
+
//# sourceMappingURL=collective-dream-tools.js.map
|
package/dist/tools/index.js
CHANGED
|
@@ -299,6 +299,7 @@ const LAZY_MODULE_IMPORTS = [
|
|
|
299
299
|
{ path: './dj-set-builder.js', registerFn: 'registerDjSetBuilderTools' },
|
|
300
300
|
{ path: './serum2-preset.js', registerFn: 'registerSerum2PresetTools' },
|
|
301
301
|
{ path: './dream-tools.js', registerFn: 'registerDreamTools' },
|
|
302
|
+
{ path: './collective-dream-tools.js', registerFn: 'registerCollectiveDreamTools' },
|
|
302
303
|
{ path: './memory-scanner-tools.js', registerFn: 'registerMemoryScannerTools' },
|
|
303
304
|
{ path: './buddy-tools.js', registerFn: 'registerBuddyTools' },
|
|
304
305
|
{ path: './voice-input-tools.js', registerFn: 'registerVoiceInputTools' },
|
package/package.json
CHANGED