0xkobold 0.7.2 → 0.8.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/HEARTBEAT.md +239 -0
- package/IDENTITY.md +12 -0
- package/README.md +138 -4
- package/SOUL.md +21 -0
- package/dist/package.json +10 -5
- package/dist/src/agent/bootstrap-loader.js +295 -66
- package/dist/src/agent/bootstrap-loader.js.map +1 -1
- package/dist/src/agent/context-pruning.js +10 -5
- package/dist/src/agent/context-pruning.js.map +1 -1
- package/dist/src/agent/embedded-runner.js +29 -15
- package/dist/src/agent/embedded-runner.js.map +1 -1
- package/dist/src/agent/index.js +5 -2
- package/dist/src/agent/index.js.map +1 -1
- package/dist/src/agent/system-prompt.js +167 -20
- package/dist/src/agent/system-prompt.js.map +1 -1
- package/dist/src/agent/tools/spawn-agent.js +72 -5
- package/dist/src/agent/tools/spawn-agent.js.map +1 -1
- package/dist/src/channels/slack/webhook.js +2 -2
- package/dist/src/channels/slack/webhook.js.map +1 -1
- package/dist/src/channels/telegram/bot.js +4 -4
- package/dist/src/channels/telegram/bot.js.map +1 -1
- package/dist/src/channels/whatsapp/integration.js +4 -4
- package/dist/src/channels/whatsapp/integration.js.map +1 -1
- package/dist/src/cli/commands/gateway.js +9 -10
- package/dist/src/cli/commands/gateway.js.map +1 -1
- package/dist/src/cli/commands/init.js +90 -0
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/setup.js +53 -0
- package/dist/src/cli/commands/setup.js.map +1 -1
- package/dist/src/cli/index.js +0 -0
- package/dist/src/config/unified-config.js +5 -0
- package/dist/src/config/unified-config.js.map +1 -1
- package/dist/src/cron/index.js +2 -0
- package/dist/src/cron/index.js.map +1 -1
- package/dist/src/cron/nl-parser.js +522 -0
- package/dist/src/cron/nl-parser.js.map +1 -0
- package/dist/src/cron/runner.js +6 -31
- package/dist/src/cron/runner.js.map +1 -1
- package/dist/src/event-bus/index.js.map +1 -1
- package/dist/src/extensions/core/agent-orchestrator-extension.js +200 -148
- package/dist/src/extensions/core/agent-orchestrator-extension.js.map +1 -1
- package/dist/src/extensions/core/diagnostics-extension.js +93 -56
- package/dist/src/extensions/core/diagnostics-extension.js.map +1 -1
- package/dist/src/extensions/core/draconic-safety-extension.js +256 -3
- package/dist/src/extensions/core/draconic-safety-extension.js.map +1 -1
- package/dist/src/extensions/core/heartbeat-extension.js +416 -150
- package/dist/src/extensions/core/heartbeat-extension.js.map +1 -1
- package/dist/src/extensions/core/{generative-agents-extension.js → learning-extension.js} +90 -128
- package/dist/src/extensions/core/learning-extension.js.map +1 -0
- package/dist/src/extensions/core/perennial-memory-extension.js +884 -87
- package/dist/src/extensions/core/perennial-memory-extension.js.map +1 -1
- package/dist/src/extensions/core/routed-ollama-extension.js +345 -0
- package/dist/src/extensions/core/routed-ollama-extension.js.map +1 -0
- package/dist/src/extensions/core/task-manager-extension.js +25 -2
- package/dist/src/extensions/core/task-manager-extension.js.map +1 -1
- package/dist/src/extensions/core/websearch-enhanced-extension.js +81 -23
- package/dist/src/extensions/core/websearch-enhanced-extension.js.map +1 -1
- package/dist/src/extensions/core/workspace-footer-extension.js +40 -63
- package/dist/src/extensions/core/workspace-footer-extension.js.map +1 -1
- package/dist/src/extensions/loader.js +5 -1
- package/dist/src/extensions/loader.js.map +1 -1
- package/dist/src/gateway/gateway-server.js +74 -3
- package/dist/src/gateway/gateway-server.js.map +1 -1
- package/dist/src/gateway/index.js +2 -2
- package/dist/src/gateway/index.js.map +1 -1
- package/dist/src/gateway/methods/agent.js +1 -1
- package/dist/src/gateway/methods/agent.js.map +1 -1
- package/dist/src/gateway/methods/index.js +4 -0
- package/dist/src/gateway/methods/index.js.map +1 -1
- package/dist/src/gateway/methods/node.js +261 -0
- package/dist/src/gateway/methods/node.js.map +1 -0
- package/dist/src/gateway/queue-modes.js +356 -0
- package/dist/src/gateway/queue-modes.js.map +1 -0
- package/dist/src/index.js +47 -6
- package/dist/src/index.js.map +1 -1
- package/dist/src/llm/community-analytics.js +569 -0
- package/dist/src/llm/community-analytics.js.map +1 -0
- package/dist/src/llm/index.js +28 -4
- package/dist/src/llm/index.js.map +1 -1
- package/dist/src/llm/model-discovery.js +335 -0
- package/dist/src/llm/model-discovery.js.map +1 -0
- package/dist/src/llm/model-popularity.js +566 -0
- package/dist/src/llm/model-popularity.js.map +1 -0
- package/dist/src/llm/model-scoring-db.js +553 -0
- package/dist/src/llm/model-scoring-db.js.map +1 -0
- package/dist/src/llm/multi-provider.js +258 -0
- package/dist/src/llm/multi-provider.js.map +1 -0
- package/dist/src/llm/ollama.js +133 -187
- package/dist/src/llm/ollama.js.map +1 -1
- package/dist/src/llm/router-commands.js +773 -0
- package/dist/src/llm/router-commands.js.map +1 -0
- package/dist/src/llm/router-core.js +600 -0
- package/dist/src/llm/router-core.js.map +1 -0
- package/dist/src/memory/checkpoint-manager.js +278 -0
- package/dist/src/memory/checkpoint-manager.js.map +1 -0
- package/dist/src/memory/conflict-detector.js +279 -0
- package/dist/src/memory/conflict-detector.js.map +1 -0
- package/dist/src/memory/context-graph.js +360 -0
- package/dist/src/memory/context-graph.js.map +1 -0
- package/dist/src/memory/dialectic/benchmark-cli.js +200 -0
- package/dist/src/memory/dialectic/benchmark-cli.js.map +1 -0
- package/dist/src/memory/dialectic/debug-reasoning.js +37 -0
- package/dist/src/memory/dialectic/debug-reasoning.js.map +1 -0
- package/dist/src/memory/dialectic/index.js +135 -0
- package/dist/src/memory/dialectic/index.js.map +1 -0
- package/dist/src/memory/dialectic/nudges.js +380 -0
- package/dist/src/memory/dialectic/nudges.js.map +1 -0
- package/dist/src/memory/dialectic/reasoning-engine.js +607 -0
- package/dist/src/memory/dialectic/reasoning-engine.js.map +1 -0
- package/dist/src/memory/dialectic/reasoning.js +390 -0
- package/dist/src/memory/dialectic/reasoning.js.map +1 -0
- package/dist/src/memory/dialectic/skill-creation.js +481 -0
- package/dist/src/memory/dialectic/skill-creation.js.map +1 -0
- package/dist/src/memory/dialectic/store.js +663 -0
- package/dist/src/memory/dialectic/store.js.map +1 -0
- package/dist/src/memory/dialectic/types.js +11 -0
- package/dist/src/memory/dialectic/types.js.map +1 -0
- package/dist/src/memory/index.js +24 -2
- package/dist/src/memory/index.js.map +1 -1
- package/dist/src/memory/memory-decay.js +350 -0
- package/dist/src/memory/memory-decay.js.map +1 -0
- package/dist/src/memory/memory-integration.js +5 -5
- package/dist/src/memory/memory-integration.js.map +1 -1
- package/dist/src/memory/migrate-memory-stream.js +97 -0
- package/dist/src/memory/migrate-memory-stream.js.map +1 -0
- package/dist/src/memory/session-memory-bridge.js +49 -5
- package/dist/src/memory/session-memory-bridge.js.map +1 -1
- package/dist/src/memory/session-store.js +123 -0
- package/dist/src/memory/session-store.js.map +1 -1
- package/dist/src/memory/smart-write-rules.js +164 -0
- package/dist/src/memory/smart-write-rules.js.map +1 -0
- package/dist/src/memory/tiered-memory.js +436 -0
- package/dist/src/memory/tiered-memory.js.map +1 -0
- package/dist/src/memory/types.js +6 -0
- package/dist/src/memory/types.js.map +1 -0
- package/dist/src/memory/verify-migration.js +99 -0
- package/dist/src/memory/verify-migration.js.map +1 -0
- package/dist/src/pi-config.js +11 -9
- package/dist/src/pi-config.js.map +1 -1
- package/dist/src/skills/conditional-skills.js +464 -0
- package/dist/src/skills/conditional-skills.js.map +1 -0
- package/dist/src/skills/index.js +5 -0
- package/dist/src/skills/index.js.map +1 -1
- package/dist/src/skills/loader.js +56 -0
- package/dist/src/skills/loader.js.map +1 -1
- package/dist/src/skills/skill-manage.js +417 -0
- package/dist/src/skills/skill-manage.js.map +1 -0
- package/dist/src/tui/commands/orchestration-commands.js +62 -0
- package/dist/src/tui/commands/orchestration-commands.js.map +1 -1
- package/package.json +10 -5
- package/skills/model-router-test.ts +65 -0
- package/dist/src/extensions/core/auto-security-scan-extension.js +0 -41
- package/dist/src/extensions/core/auto-security-scan-extension.js.map +0 -1
- package/dist/src/extensions/core/cloudflare-browser-extension.js +0 -389
- package/dist/src/extensions/core/cloudflare-browser-extension.js.map +0 -1
- package/dist/src/extensions/core/compaction-safeguard.js +0 -223
- package/dist/src/extensions/core/compaction-safeguard.js.map +0 -1
- package/dist/src/extensions/core/generative-agents-extension.js.map +0 -1
- package/dist/src/extensions/core/obsidian-bridge-extension.js +0 -488
- package/dist/src/extensions/core/obsidian-bridge-extension.js.map +0 -1
- package/dist/src/llm/router.js +0 -145
- package/dist/src/llm/router.js.map +0 -1
- package/skills/kobold-scan-skill/node_modules/.package-lock.json +0 -172
- package/skills/kobold-scan-skill/node_modules/ansi-styles/index.d.ts +0 -345
- package/skills/kobold-scan-skill/node_modules/ansi-styles/index.js +0 -163
- package/skills/kobold-scan-skill/node_modules/ansi-styles/license +0 -9
- package/skills/kobold-scan-skill/node_modules/ansi-styles/package.json +0 -56
- package/skills/kobold-scan-skill/node_modules/ansi-styles/readme.md +0 -152
- package/skills/kobold-scan-skill/node_modules/balanced-match/.github/FUNDING.yml +0 -2
- package/skills/kobold-scan-skill/node_modules/balanced-match/LICENSE.md +0 -21
- package/skills/kobold-scan-skill/node_modules/balanced-match/README.md +0 -97
- package/skills/kobold-scan-skill/node_modules/balanced-match/index.js +0 -62
- package/skills/kobold-scan-skill/node_modules/balanced-match/package.json +0 -48
- package/skills/kobold-scan-skill/node_modules/brace-expansion/.github/FUNDING.yml +0 -2
- package/skills/kobold-scan-skill/node_modules/brace-expansion/LICENSE +0 -21
- package/skills/kobold-scan-skill/node_modules/brace-expansion/README.md +0 -135
- package/skills/kobold-scan-skill/node_modules/brace-expansion/index.js +0 -203
- package/skills/kobold-scan-skill/node_modules/brace-expansion/package.json +0 -49
- package/skills/kobold-scan-skill/node_modules/chalk/index.d.ts +0 -415
- package/skills/kobold-scan-skill/node_modules/chalk/license +0 -9
- package/skills/kobold-scan-skill/node_modules/chalk/package.json +0 -68
- package/skills/kobold-scan-skill/node_modules/chalk/readme.md +0 -341
- package/skills/kobold-scan-skill/node_modules/chalk/source/index.js +0 -229
- package/skills/kobold-scan-skill/node_modules/chalk/source/templates.js +0 -134
- package/skills/kobold-scan-skill/node_modules/chalk/source/util.js +0 -39
- package/skills/kobold-scan-skill/node_modules/color-convert/CHANGELOG.md +0 -54
- package/skills/kobold-scan-skill/node_modules/color-convert/LICENSE +0 -21
- package/skills/kobold-scan-skill/node_modules/color-convert/README.md +0 -68
- package/skills/kobold-scan-skill/node_modules/color-convert/conversions.js +0 -839
- package/skills/kobold-scan-skill/node_modules/color-convert/index.js +0 -81
- package/skills/kobold-scan-skill/node_modules/color-convert/package.json +0 -48
- package/skills/kobold-scan-skill/node_modules/color-convert/route.js +0 -97
- package/skills/kobold-scan-skill/node_modules/color-name/LICENSE +0 -8
- package/skills/kobold-scan-skill/node_modules/color-name/README.md +0 -11
- package/skills/kobold-scan-skill/node_modules/color-name/index.js +0 -152
- package/skills/kobold-scan-skill/node_modules/color-name/package.json +0 -28
- package/skills/kobold-scan-skill/node_modules/commander/LICENSE +0 -22
- package/skills/kobold-scan-skill/node_modules/commander/Readme.md +0 -1129
- package/skills/kobold-scan-skill/node_modules/commander/esm.mjs +0 -16
- package/skills/kobold-scan-skill/node_modules/commander/index.js +0 -27
- package/skills/kobold-scan-skill/node_modules/commander/lib/argument.js +0 -147
- package/skills/kobold-scan-skill/node_modules/commander/lib/command.js +0 -2179
- package/skills/kobold-scan-skill/node_modules/commander/lib/error.js +0 -45
- package/skills/kobold-scan-skill/node_modules/commander/lib/help.js +0 -461
- package/skills/kobold-scan-skill/node_modules/commander/lib/option.js +0 -326
- package/skills/kobold-scan-skill/node_modules/commander/lib/suggestSimilar.js +0 -100
- package/skills/kobold-scan-skill/node_modules/commander/package-support.json +0 -16
- package/skills/kobold-scan-skill/node_modules/commander/package.json +0 -80
- package/skills/kobold-scan-skill/node_modules/commander/typings/index.d.ts +0 -891
- package/skills/kobold-scan-skill/node_modules/fs.realpath/LICENSE +0 -43
- package/skills/kobold-scan-skill/node_modules/fs.realpath/README.md +0 -33
- package/skills/kobold-scan-skill/node_modules/fs.realpath/index.js +0 -66
- package/skills/kobold-scan-skill/node_modules/fs.realpath/old.js +0 -303
- package/skills/kobold-scan-skill/node_modules/fs.realpath/package.json +0 -26
- package/skills/kobold-scan-skill/node_modules/glob/LICENSE +0 -15
- package/skills/kobold-scan-skill/node_modules/glob/README.md +0 -399
- package/skills/kobold-scan-skill/node_modules/glob/common.js +0 -244
- package/skills/kobold-scan-skill/node_modules/glob/glob.js +0 -790
- package/skills/kobold-scan-skill/node_modules/glob/package.json +0 -55
- package/skills/kobold-scan-skill/node_modules/glob/sync.js +0 -486
- package/skills/kobold-scan-skill/node_modules/has-flag/index.d.ts +0 -39
- package/skills/kobold-scan-skill/node_modules/has-flag/index.js +0 -8
- package/skills/kobold-scan-skill/node_modules/has-flag/license +0 -9
- package/skills/kobold-scan-skill/node_modules/has-flag/package.json +0 -46
- package/skills/kobold-scan-skill/node_modules/has-flag/readme.md +0 -89
- package/skills/kobold-scan-skill/node_modules/inflight/LICENSE +0 -15
- package/skills/kobold-scan-skill/node_modules/inflight/README.md +0 -37
- package/skills/kobold-scan-skill/node_modules/inflight/inflight.js +0 -54
- package/skills/kobold-scan-skill/node_modules/inflight/package.json +0 -29
- package/skills/kobold-scan-skill/node_modules/inherits/LICENSE +0 -16
- package/skills/kobold-scan-skill/node_modules/inherits/README.md +0 -42
- package/skills/kobold-scan-skill/node_modules/inherits/inherits.js +0 -9
- package/skills/kobold-scan-skill/node_modules/inherits/inherits_browser.js +0 -27
- package/skills/kobold-scan-skill/node_modules/inherits/package.json +0 -29
- package/skills/kobold-scan-skill/node_modules/minimatch/LICENSE +0 -15
- package/skills/kobold-scan-skill/node_modules/minimatch/README.md +0 -259
- package/skills/kobold-scan-skill/node_modules/minimatch/lib/path.js +0 -4
- package/skills/kobold-scan-skill/node_modules/minimatch/minimatch.js +0 -944
- package/skills/kobold-scan-skill/node_modules/minimatch/package.json +0 -35
- package/skills/kobold-scan-skill/node_modules/once/LICENSE +0 -15
- package/skills/kobold-scan-skill/node_modules/once/README.md +0 -79
- package/skills/kobold-scan-skill/node_modules/once/once.js +0 -42
- package/skills/kobold-scan-skill/node_modules/once/package.json +0 -33
- package/skills/kobold-scan-skill/node_modules/supports-color/browser.js +0 -5
- package/skills/kobold-scan-skill/node_modules/supports-color/index.js +0 -135
- package/skills/kobold-scan-skill/node_modules/supports-color/license +0 -9
- package/skills/kobold-scan-skill/node_modules/supports-color/package.json +0 -53
- package/skills/kobold-scan-skill/node_modules/supports-color/readme.md +0 -76
- package/skills/kobold-scan-skill/node_modules/wrappy/LICENSE +0 -15
- package/skills/kobold-scan-skill/node_modules/wrappy/README.md +0 -36
- package/skills/kobold-scan-skill/node_modules/wrappy/package.json +0 -29
- package/skills/kobold-scan-skill/node_modules/wrappy/wrappy.js +0 -33
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Commands - Singleton, Init, CLI Handlers
|
|
3
|
+
*
|
|
4
|
+
* The plumbing: singleton state, initialization, command handlers,
|
|
5
|
+
* and model status tracking for display.
|
|
6
|
+
*
|
|
7
|
+
* Consolidated from:
|
|
8
|
+
* - unified-router.ts (singleton, commands)
|
|
9
|
+
* - model-status.ts (current model tracking)
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from 'path';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { existsSync } from 'fs';
|
|
14
|
+
import { AdaptiveModelRouter, createRouter } from './router-core';
|
|
15
|
+
import { getOllamaProvider } from './ollama';
|
|
16
|
+
import { getModelPopularityService } from './model-popularity';
|
|
17
|
+
let currentModel = null;
|
|
18
|
+
export function setCurrentModel(modelName, reason) {
|
|
19
|
+
currentModel = {
|
|
20
|
+
name: modelName,
|
|
21
|
+
reason,
|
|
22
|
+
timestamp: Date.now(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export function getCurrentModel() {
|
|
26
|
+
return currentModel;
|
|
27
|
+
}
|
|
28
|
+
export function clearCurrentModel() {
|
|
29
|
+
currentModel = null;
|
|
30
|
+
}
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Singleton Router State
|
|
33
|
+
// ============================================================================
|
|
34
|
+
let routerInstance = null;
|
|
35
|
+
let routerInitializing = false;
|
|
36
|
+
let routerInitPromise = null;
|
|
37
|
+
let favoriteModels = [];
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Initialization
|
|
40
|
+
// ============================================================================
|
|
41
|
+
export async function getRouter() {
|
|
42
|
+
if (routerInstance)
|
|
43
|
+
return routerInstance;
|
|
44
|
+
if (routerInitializing && routerInitPromise) {
|
|
45
|
+
return routerInitPromise;
|
|
46
|
+
}
|
|
47
|
+
routerInitializing = true;
|
|
48
|
+
routerInitPromise = initializeRouter();
|
|
49
|
+
try {
|
|
50
|
+
routerInstance = await routerInitPromise;
|
|
51
|
+
return routerInstance;
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
routerInitPromise = null;
|
|
55
|
+
throw err;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function initializeRouter() {
|
|
59
|
+
console.log('[Router] Initializing...');
|
|
60
|
+
const provider = await getOllamaProvider();
|
|
61
|
+
const router = await createRouter(provider, 'kimi-k2.5:cloud');
|
|
62
|
+
await loadFavoriteModels(router);
|
|
63
|
+
router.setLearningEnabled(true);
|
|
64
|
+
console.log('[Router] Ready with adaptive routing');
|
|
65
|
+
return router;
|
|
66
|
+
}
|
|
67
|
+
async function loadFavoriteModels(router) {
|
|
68
|
+
try {
|
|
69
|
+
const configPath = resolve(homedir(), '.0xkobold/config.json');
|
|
70
|
+
if (!existsSync(configPath))
|
|
71
|
+
return;
|
|
72
|
+
const config = JSON.parse(await Bun.file(configPath).text());
|
|
73
|
+
if (config.favoriteModels && Array.isArray(config.favoriteModels)) {
|
|
74
|
+
favoriteModels = config.favoriteModels;
|
|
75
|
+
router.setFavoriteModels(favoriteModels);
|
|
76
|
+
console.log(`[Router] Favorites: ${favoriteModels.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Silent fail - favorites are optional
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// ============================================================================
|
|
84
|
+
// Status Checks
|
|
85
|
+
// ============================================================================
|
|
86
|
+
export function isRouterReady() {
|
|
87
|
+
return routerInstance !== null;
|
|
88
|
+
}
|
|
89
|
+
export function getRouterStatus() {
|
|
90
|
+
return {
|
|
91
|
+
ready: routerInstance !== null,
|
|
92
|
+
initializing: routerInitializing,
|
|
93
|
+
favorites: [...favoriteModels],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
// ============================================================================
|
|
97
|
+
// Routed Provider Wrapper
|
|
98
|
+
// ============================================================================
|
|
99
|
+
export async function createRoutedOllamaProvider() {
|
|
100
|
+
const router = await getRouter();
|
|
101
|
+
return {
|
|
102
|
+
name: 'routed-ollama',
|
|
103
|
+
async chat(options) {
|
|
104
|
+
const lastUserMsg = options.messages?.findLast?.((m) => m.role === 'user')?.content || '';
|
|
105
|
+
const selectedModel = await router.selectModel(lastUserMsg);
|
|
106
|
+
setCurrentModel(selectedModel, 'adaptive routing');
|
|
107
|
+
const baseProvider = await getOllamaProvider();
|
|
108
|
+
return baseProvider.chat({ ...options, model: selectedModel });
|
|
109
|
+
},
|
|
110
|
+
// Note: Streaming not fully implemented - falls back to non-streaming
|
|
111
|
+
async *chatStream(options) {
|
|
112
|
+
const baseProvider = await getOllamaProvider();
|
|
113
|
+
const router = await getRouter();
|
|
114
|
+
const lastUserMsg = options.messages?.findLast?.((m) => m.role === 'user')?.content || '';
|
|
115
|
+
const selectedModel = await router.selectModel(lastUserMsg);
|
|
116
|
+
const result = await baseProvider.chat({ ...options, model: selectedModel });
|
|
117
|
+
yield result;
|
|
118
|
+
},
|
|
119
|
+
async listModels() {
|
|
120
|
+
const models = await router.listModels();
|
|
121
|
+
return models.map(m => ({
|
|
122
|
+
id: m.name,
|
|
123
|
+
name: m.name,
|
|
124
|
+
provider: 'routed-ollama',
|
|
125
|
+
contextWindow: m.contextWindow,
|
|
126
|
+
}));
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// ============================================================================
|
|
131
|
+
// Command Handlers
|
|
132
|
+
// ============================================================================
|
|
133
|
+
export async function handleRouterCommand(args) {
|
|
134
|
+
const subcommand = args.trim().toLowerCase();
|
|
135
|
+
if (!subcommand) {
|
|
136
|
+
return `🧠 Adaptive Model Router
|
|
137
|
+
|
|
138
|
+
Commands:
|
|
139
|
+
/router auto - Enable adaptive routing
|
|
140
|
+
/router manual - Use static model
|
|
141
|
+
/router info - Show current model info
|
|
142
|
+
/router favorites - List favorite models
|
|
143
|
+
/router fav MODEL - Add to favorites
|
|
144
|
+
/router unfav MODEL - Remove from favorites
|
|
145
|
+
/router stats MODEL - Show model performance stats
|
|
146
|
+
/router history - Show recent performance history
|
|
147
|
+
/router MODEL - Force specific model
|
|
148
|
+
/models - List all models
|
|
149
|
+
/model-rankings - Show model leaderboard
|
|
150
|
+
/tier-list - Show AI-generated tier list
|
|
151
|
+
/rate <1-5> - Rate last model response`;
|
|
152
|
+
}
|
|
153
|
+
const router = await getRouter();
|
|
154
|
+
switch (subcommand) {
|
|
155
|
+
case 'auto':
|
|
156
|
+
router.setLearningEnabled(true);
|
|
157
|
+
return '🧠 Adaptive routing enabled. Model will be selected automatically.';
|
|
158
|
+
case 'manual':
|
|
159
|
+
case 'static':
|
|
160
|
+
router.setLearningEnabled(false);
|
|
161
|
+
return '🎯 Static model selection. Use /router MODEL to select specific model.';
|
|
162
|
+
case 'info':
|
|
163
|
+
return getRouterInfo(router);
|
|
164
|
+
case 'favorites':
|
|
165
|
+
case 'favs':
|
|
166
|
+
return getFavoritesOutput(router);
|
|
167
|
+
case 'history':
|
|
168
|
+
return getHistoryOutput(router);
|
|
169
|
+
default:
|
|
170
|
+
if (subcommand.startsWith('stats ')) {
|
|
171
|
+
const model = args.replace(/^stats\s+/, '').trim();
|
|
172
|
+
return getModelStatsOutput(router, model);
|
|
173
|
+
}
|
|
174
|
+
if (subcommand.startsWith('fav ')) {
|
|
175
|
+
const model = args.replace(/^fav\s+/, '').trim();
|
|
176
|
+
router.addFavoriteModel(model);
|
|
177
|
+
return `⭐ Added ${model} to favorites`;
|
|
178
|
+
}
|
|
179
|
+
if (subcommand.startsWith('unfav ')) {
|
|
180
|
+
const model = args.replace(/^unfav\s+/, '').trim();
|
|
181
|
+
router.removeFavoriteModel(model);
|
|
182
|
+
return `⭐ Removed ${model} from favorites`;
|
|
183
|
+
}
|
|
184
|
+
return `🎯 Model set to: ${args.trim()}\nUse /router auto to re-enable adaptive routing.`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
export async function handleModelsCommand(args) {
|
|
188
|
+
const router = await getRouter();
|
|
189
|
+
const models = await router.listModels();
|
|
190
|
+
if (args.includes('--refresh')) {
|
|
191
|
+
await router.refreshModels();
|
|
192
|
+
return '✅ Model cache refreshed';
|
|
193
|
+
}
|
|
194
|
+
if (args.includes('--recommend')) {
|
|
195
|
+
return getRecommendationsOutput(models);
|
|
196
|
+
}
|
|
197
|
+
const lines = ['🧠 Available Models:\n'];
|
|
198
|
+
const byType = {
|
|
199
|
+
chat: models.filter(m => m.capabilities.chat),
|
|
200
|
+
code: models.filter(m => m.capabilities.code && m.specializations.includes('coding')),
|
|
201
|
+
reasoning: models.filter(m => m.capabilities.reasoning),
|
|
202
|
+
vision: models.filter(m => m.capabilities.vision),
|
|
203
|
+
};
|
|
204
|
+
for (const [type, list] of Object.entries(byType)) {
|
|
205
|
+
if (list.length > 0) {
|
|
206
|
+
lines.push(`${type.toUpperCase()}:`);
|
|
207
|
+
for (const m of list) {
|
|
208
|
+
const params = m.parameterCount ? ` ${m.parameterCount}B` : '';
|
|
209
|
+
const cloud = m.isCloud ? ' ☁️' : '';
|
|
210
|
+
const isFav = favoriteModels.includes(m.name) ? ' ⭐' : '';
|
|
211
|
+
lines.push(` • ${m.name}${params}${cloud}${isFav} - ${m.speedTier}/${m.qualityTier}`);
|
|
212
|
+
}
|
|
213
|
+
lines.push('');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
lines.push('Commands: /router auto|manual|info|favorites');
|
|
217
|
+
return lines.join('\n');
|
|
218
|
+
}
|
|
219
|
+
export async function handleRateCommand(args) {
|
|
220
|
+
const rating = parseInt(args.trim(), 10);
|
|
221
|
+
if (isNaN(rating) || rating < 1 || rating > 5) {
|
|
222
|
+
return 'Usage: /rate <1-5> (rate the last model response)';
|
|
223
|
+
}
|
|
224
|
+
const currentModelStatus = getCurrentModel();
|
|
225
|
+
if (!currentModelStatus) {
|
|
226
|
+
return '❌ No model has been used yet. Use a model first, then rate it.';
|
|
227
|
+
}
|
|
228
|
+
const router = await getRouter();
|
|
229
|
+
router.addFeedback(currentModelStatus.name, 'chat', rating);
|
|
230
|
+
return `✅ Rated ${currentModelStatus.name} ${rating}/5. This improves model selection.`;
|
|
231
|
+
}
|
|
232
|
+
// ============================================================================
|
|
233
|
+
// Model Rankings & Tier List Commands
|
|
234
|
+
// ============================================================================
|
|
235
|
+
export async function handleModelRankingsCommand(args) {
|
|
236
|
+
const router = await getRouter();
|
|
237
|
+
const period = args.trim() || 'all';
|
|
238
|
+
let periodLabel = 'all time';
|
|
239
|
+
if (period === 'day')
|
|
240
|
+
periodLabel = 'last 24 hours';
|
|
241
|
+
else if (period === 'week')
|
|
242
|
+
periodLabel = 'last week';
|
|
243
|
+
else if (period === 'month')
|
|
244
|
+
periodLabel = 'last month';
|
|
245
|
+
const rankings = router.getModelRankings();
|
|
246
|
+
if (rankings.length === 0) {
|
|
247
|
+
return `📊 No model rankings data yet for ${periodLabel}.\n\nUse models and rate them with /rate <1-5> to build rankings.`;
|
|
248
|
+
}
|
|
249
|
+
const lines = [`📊 Model Rankings (${periodLabel})\n`];
|
|
250
|
+
lines.push('Rank | Model | Score | Quality | Latency | Uses');
|
|
251
|
+
lines.push('-----|----------------------|-------|---------|---------|------');
|
|
252
|
+
// Sort by score descending
|
|
253
|
+
const sorted = [...rankings].sort((a, b) => b.score - a.score);
|
|
254
|
+
sorted.slice(0, 15).forEach((model, i) => {
|
|
255
|
+
const rank = (i + 1).toString().padEnd(4);
|
|
256
|
+
const name = model.modelName.substring(0, 20).padEnd(20);
|
|
257
|
+
const score = model.score.toFixed(2).padStart(5);
|
|
258
|
+
const quality = ((model.avgQuality || 0) / 5 * 100).toFixed(0).padStart(3) + '%';
|
|
259
|
+
const latency = ((model.avgLatency || 0) / 1000).toFixed(1) + 's';
|
|
260
|
+
const uses = model.usageCount.toString().padStart(5);
|
|
261
|
+
lines.push(`${rank} | ${name} | ${score} | ${quality} | ${latency.padStart(7)} | ${uses}`);
|
|
262
|
+
});
|
|
263
|
+
lines.push('\nCommands: /model-rankings day|week|month|all | /tier-list | /popularity');
|
|
264
|
+
return lines.join('\n');
|
|
265
|
+
}
|
|
266
|
+
export async function handleTierListCommand(args) {
|
|
267
|
+
const router = await getRouter();
|
|
268
|
+
const period = args.trim() || 'all';
|
|
269
|
+
const tierList = router.generateTierList(period);
|
|
270
|
+
if (tierList.length === 0) {
|
|
271
|
+
return `🏆 No tier list data yet.\n\nModels need at least 2 uses to appear in rankings.\nUse models and rate them with /rate <1-5>.`;
|
|
272
|
+
}
|
|
273
|
+
// Group by tier
|
|
274
|
+
const tiers = {
|
|
275
|
+
S: [],
|
|
276
|
+
A: [],
|
|
277
|
+
B: [],
|
|
278
|
+
C: [],
|
|
279
|
+
D: [],
|
|
280
|
+
};
|
|
281
|
+
tierList.forEach(model => {
|
|
282
|
+
tiers[model.tier].push(model);
|
|
283
|
+
});
|
|
284
|
+
const lines = [`🏆 AI-Generated Model Tier List\n`];
|
|
285
|
+
const tierEmojis = { S: '🥇', A: '🥈', B: '🥉', C: '⭐', D: '·' };
|
|
286
|
+
const tierDescriptions = {
|
|
287
|
+
S: 'Excellent - Top performers (score ≥ 0.85)',
|
|
288
|
+
A: 'Great - Reliable choices (score ≥ 0.70)',
|
|
289
|
+
B: 'Good - Solid performers (score ≥ 0.55)',
|
|
290
|
+
C: 'Fair - Acceptable (score ≥ 0.40)',
|
|
291
|
+
D: 'Needs improvement (score < 0.40)',
|
|
292
|
+
};
|
|
293
|
+
for (const [tier, models] of Object.entries(tiers)) {
|
|
294
|
+
if (models.length === 0)
|
|
295
|
+
continue;
|
|
296
|
+
const emoji = tierEmojis[tier];
|
|
297
|
+
lines.push(`${emoji} **Tier ${tier}** - ${tierDescriptions[tier]}`);
|
|
298
|
+
models.forEach(model => {
|
|
299
|
+
const scoreBar = '█'.repeat(Math.round(model.score * 10));
|
|
300
|
+
const strengths = model.strengths.length > 0 ? ` [${model.strengths.join(', ')}]` : '';
|
|
301
|
+
lines.push(` ${model.modelName} - ${model.score.toFixed(2)} ${scoreBar}${strengths}`);
|
|
302
|
+
});
|
|
303
|
+
lines.push('');
|
|
304
|
+
}
|
|
305
|
+
const totalSamples = tierList.reduce((sum, m) => sum + m.usageCount, 0);
|
|
306
|
+
lines.push(`_Based on ${tierList.length} models, ${totalSamples} total samples_`);
|
|
307
|
+
lines.push('\nCommands: /tier-list day|week|month|all | /model-rankings | /popularity');
|
|
308
|
+
return lines.join('\n');
|
|
309
|
+
}
|
|
310
|
+
export async function handlePopularityCommand(args) {
|
|
311
|
+
const popularity = getModelPopularityService();
|
|
312
|
+
// Refresh if needed
|
|
313
|
+
if (popularity.needsRefresh()) {
|
|
314
|
+
await popularity.refreshFromOllama();
|
|
315
|
+
}
|
|
316
|
+
const lines = ['📈 Model Popularity (Community Usage)\n'];
|
|
317
|
+
// Get trending from Ollama
|
|
318
|
+
const trending = popularity.getTrending(10);
|
|
319
|
+
if (trending.length === 0) {
|
|
320
|
+
lines.push('⏳ Fetching popularity data from Ollama...');
|
|
321
|
+
lines.push('Run /popularity again in a few seconds.');
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
lines.push('🔥 Most Popular (Ollama Pulls):');
|
|
325
|
+
lines.push('');
|
|
326
|
+
trending.forEach((model, i) => {
|
|
327
|
+
const pullStr = model.pullCount >= 1_000_000
|
|
328
|
+
? (model.pullCount / 1_000_000).toFixed(1) + 'M'
|
|
329
|
+
: model.pullCount >= 1_000
|
|
330
|
+
? (model.pullCount / 1_000).toFixed(1) + 'K'
|
|
331
|
+
: model.pullCount.toString();
|
|
332
|
+
const local = model.localUsageCount > 0
|
|
333
|
+
? ` (you: ${model.localUsageCount} uses)`
|
|
334
|
+
: '';
|
|
335
|
+
lines.push(` ${i + 1}. ${model.modelName} - ${pullStr} pulls${local}`);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
// Show most used locally
|
|
339
|
+
const mostUsedLocally = popularity.getMostUsedLocally(5);
|
|
340
|
+
if (mostUsedLocally.length > 0) {
|
|
341
|
+
lines.push('');
|
|
342
|
+
lines.push('👤 Your Most Used Models:');
|
|
343
|
+
mostUsedLocally.forEach((model, i) => {
|
|
344
|
+
const popScore = popularity.calculatePopularityScore(model.modelName);
|
|
345
|
+
lines.push(` ${i + 1}. ${model.modelName} - ${model.localUsageCount} uses (pop: ${(popScore * 100).toFixed(0)}%)`);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
lines.push('\nCommands: /popularity --refresh | /model-rankings | /tier-list');
|
|
349
|
+
return lines.join('\n');
|
|
350
|
+
}
|
|
351
|
+
export async function handleRefreshPopularity() {
|
|
352
|
+
const popularity = getModelPopularityService();
|
|
353
|
+
const count = await popularity.refreshFromOllama();
|
|
354
|
+
return `✅ Refreshed popularity data for ${count} models from Ollama library.`;
|
|
355
|
+
}
|
|
356
|
+
export async function handleModelStatsCommand(modelName) {
|
|
357
|
+
const router = await getRouter();
|
|
358
|
+
const popularity = getModelPopularityService();
|
|
359
|
+
const stats = router.getModelStats(modelName);
|
|
360
|
+
const pop = popularity.getPopularity(modelName);
|
|
361
|
+
if (!stats && !pop) {
|
|
362
|
+
return `❌ No statistics found for model: ${modelName}\n\nUse this model to generate statistics.`;
|
|
363
|
+
}
|
|
364
|
+
const lines = [`📊 Statistics for ${modelName}\n`];
|
|
365
|
+
if (stats) {
|
|
366
|
+
lines.push('🏁 Performance:');
|
|
367
|
+
lines.push(` Score: ${stats.score.toFixed(2)}`);
|
|
368
|
+
lines.push(` Quality: ${((stats.avgQuality || 0) / 5 * 100).toFixed(0)}%`);
|
|
369
|
+
lines.push(` Latency: ${((stats.avgLatency || 0) / 1000).toFixed(2)}s avg`);
|
|
370
|
+
lines.push(` Success Rate: ${((stats.successRate || 0) * 100).toFixed(0)}%`);
|
|
371
|
+
lines.push(` Total Uses: ${stats.usageCount}`);
|
|
372
|
+
lines.push('');
|
|
373
|
+
}
|
|
374
|
+
if (pop) {
|
|
375
|
+
lines.push('📈 Popularity:');
|
|
376
|
+
lines.push(` Ollama Pulls: ${pop.pullCount.toLocaleString()}`);
|
|
377
|
+
lines.push(` Pull Rank: #${pop.pullCountRank}`);
|
|
378
|
+
if (pop.communitySampleSize > 0) {
|
|
379
|
+
lines.push(` Community Rating: ${(pop.communityScore * 100).toFixed(0)}% (${pop.communitySampleSize} samples)`);
|
|
380
|
+
}
|
|
381
|
+
lines.push(` Your Uses: ${pop.localUsageCount}`);
|
|
382
|
+
}
|
|
383
|
+
return lines.join('\n');
|
|
384
|
+
}
|
|
385
|
+
// ============================================================================
|
|
386
|
+
// Output Helpers
|
|
387
|
+
// ============================================================================
|
|
388
|
+
async function getRouterInfo(router) {
|
|
389
|
+
const currentModel = router.getDefaultModel();
|
|
390
|
+
const modelInfo = await router.getModelInfo(currentModel);
|
|
391
|
+
const lines = ['🧠 Model Info'];
|
|
392
|
+
lines.push('Mode: 🧠 Adaptive (auto-select)');
|
|
393
|
+
lines.push(`Current: ${currentModel}`);
|
|
394
|
+
if (modelInfo) {
|
|
395
|
+
lines.push(`Speed: ${modelInfo.speedTier} | Quality: ${modelInfo.qualityTier}`);
|
|
396
|
+
lines.push(`Context: ${modelInfo.contextWindow.toLocaleString()} tokens`);
|
|
397
|
+
}
|
|
398
|
+
if (favoriteModels.length > 0) {
|
|
399
|
+
lines.push(`Favorites: ${favoriteModels.join(', ')}`);
|
|
400
|
+
}
|
|
401
|
+
return lines.join('\n');
|
|
402
|
+
}
|
|
403
|
+
function getFavoritesOutput(router) {
|
|
404
|
+
const favs = router.getFavoriteModels();
|
|
405
|
+
if (favs.length === 0) {
|
|
406
|
+
return '⭐ No favorites set.\nAdd to ~/.0xkobold/config.json:\n "favoriteModels": ["kimi-k2.5:cloud"]';
|
|
407
|
+
}
|
|
408
|
+
return '⭐ Favorite models:\n' + favs.map(f => ` • ${f}`).join('\n');
|
|
409
|
+
}
|
|
410
|
+
function getRecommendationsOutput(models) {
|
|
411
|
+
const lines = ['🎯 Model Recommendations:\n'];
|
|
412
|
+
const bySize = {
|
|
413
|
+
fast: models.filter(m => m.speedTier === 'fast'),
|
|
414
|
+
balanced: models.filter(m => m.speedTier === 'medium' && m.qualityTier === 'good'),
|
|
415
|
+
powerful: models.filter(m => m.qualityTier === 'excellent'),
|
|
416
|
+
};
|
|
417
|
+
if (bySize.fast.length > 0) {
|
|
418
|
+
lines.push('Fast (simple tasks):');
|
|
419
|
+
bySize.fast.slice(0, 3).forEach(m => lines.push(` • ${m.name}`));
|
|
420
|
+
lines.push('');
|
|
421
|
+
}
|
|
422
|
+
if (bySize.balanced.length > 0) {
|
|
423
|
+
lines.push('Balanced (most tasks):');
|
|
424
|
+
bySize.balanced.slice(0, 3).forEach(m => lines.push(` • ${m.name}`));
|
|
425
|
+
lines.push('');
|
|
426
|
+
}
|
|
427
|
+
if (bySize.powerful.length > 0) {
|
|
428
|
+
lines.push('Powerful (complex tasks):');
|
|
429
|
+
bySize.powerful.slice(0, 3).forEach(m => lines.push(` • ${m.name}`));
|
|
430
|
+
}
|
|
431
|
+
return lines.join('\n');
|
|
432
|
+
}
|
|
433
|
+
// ============================================================================
|
|
434
|
+
// Footer Status (for TUI)
|
|
435
|
+
// ============================================================================
|
|
436
|
+
export async function getFooterStatus() {
|
|
437
|
+
try {
|
|
438
|
+
if (!routerInstance) {
|
|
439
|
+
return { text: '🧠 init...', tooltip: 'Router initializing...' };
|
|
440
|
+
}
|
|
441
|
+
const currentModel = routerInstance.getDefaultModel();
|
|
442
|
+
const modelInfo = await routerInstance.getModelInfo(currentModel);
|
|
443
|
+
if (!modelInfo) {
|
|
444
|
+
return {
|
|
445
|
+
text: `🧠 ${currentModel}`,
|
|
446
|
+
tooltip: 'Model selected (no info available)',
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
const speedEmoji = modelInfo.speedTier === 'fast' ? '⚡' : modelInfo.speedTier === 'medium' ? '🔄' : '🐢';
|
|
450
|
+
const qualityEmoji = modelInfo.qualityTier === 'excellent' ? '⭐' : modelInfo.qualityTier === 'good' ? '✅' : '·';
|
|
451
|
+
return {
|
|
452
|
+
text: `🧠 ${currentModel}`,
|
|
453
|
+
tooltip: `${speedEmoji} ${modelInfo.speedTier} | ${qualityEmoji} ${modelInfo.qualityTier}`,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// ============================================================================
|
|
461
|
+
// Multi-Provider Commands
|
|
462
|
+
// ============================================================================
|
|
463
|
+
/**
|
|
464
|
+
* Handle /providers command - show all provider statuses
|
|
465
|
+
*/
|
|
466
|
+
export async function handleProvidersCommand() {
|
|
467
|
+
const { getProviderStatuses } = await import('./multi-provider');
|
|
468
|
+
const statuses = await getProviderStatuses();
|
|
469
|
+
const lines = ['🔌 Provider Status:\n'];
|
|
470
|
+
for (const status of statuses) {
|
|
471
|
+
const emoji = status.available ? '✅' : '❌';
|
|
472
|
+
lines.push(`${emoji} ${status.name}: ${status.available ? `${status.modelCount} models` : 'unavailable'}`);
|
|
473
|
+
}
|
|
474
|
+
lines.push('\n📌 Model prefixes:');
|
|
475
|
+
lines.push(' • ollama/<model> - Ollama (local or cloud)');
|
|
476
|
+
lines.push(' • claude/<model> - Anthropic Claude');
|
|
477
|
+
lines.push(' • anthropic/<model> - Anthropic Claude (alias)');
|
|
478
|
+
return lines.join('\n');
|
|
479
|
+
}
|
|
480
|
+
// ============================================================================
|
|
481
|
+
// History Output Helper
|
|
482
|
+
// ============================================================================
|
|
483
|
+
function getHistoryOutput(router) {
|
|
484
|
+
const history = router.getPerformanceHistory(20);
|
|
485
|
+
if (history.length === 0) {
|
|
486
|
+
return '📋 No performance history yet.\n\nUse models to build history. Ratings are persisted.';
|
|
487
|
+
}
|
|
488
|
+
const lines = ['📋 Recent Model Usage History\n'];
|
|
489
|
+
lines.push('Time | Model | Task | Latency | Result');
|
|
490
|
+
lines.push('---------------------|----------------------|-----------|---------|--------');
|
|
491
|
+
history.forEach(entry => {
|
|
492
|
+
const time = new Date(entry.timestamp).toLocaleTimeString().substring(0, 5);
|
|
493
|
+
const name = entry.modelName.substring(0, 20).padEnd(20);
|
|
494
|
+
const task = (entry.taskType || 'chat').substring(0, 7).padEnd(7);
|
|
495
|
+
const latency = ((entry.latencyMs || 0) / 1000).toFixed(1) + 's';
|
|
496
|
+
const result = entry.success ? '✅' : '❌';
|
|
497
|
+
const rating = entry.userRating ? ` (${entry.userRating}/5)` : '';
|
|
498
|
+
lines.push(`${time.padEnd(20)} | ${name} | ${task} | ${latency.padStart(6)} | ${result}${rating}`);
|
|
499
|
+
});
|
|
500
|
+
lines.push('\nCommands: /model-rankings | /tier-list | /popularity');
|
|
501
|
+
return lines.join('\n');
|
|
502
|
+
}
|
|
503
|
+
function getModelStatsOutput(router, modelName) {
|
|
504
|
+
const stats = router.getModelStats(modelName);
|
|
505
|
+
if (!stats) {
|
|
506
|
+
return `📊 No statistics found for: ${modelName}\n\nUse this model to generate statistics.`;
|
|
507
|
+
}
|
|
508
|
+
const lines = [`📊 Statistics for ${modelName}\n`];
|
|
509
|
+
lines.push('🏁 Performance:');
|
|
510
|
+
lines.push(` Score: ${stats.score.toFixed(2)}`);
|
|
511
|
+
lines.push(` Quality: ${((stats.avgQuality || 0) / 5 * 100).toFixed(0)}%`);
|
|
512
|
+
lines.push(` Latency: ${((stats.avgLatency || 0) / 1000).toFixed(2)}s avg`);
|
|
513
|
+
lines.push(` Success Rate: ${((stats.successRate || 0) * 100).toFixed(0)}%`);
|
|
514
|
+
lines.push(` Total Uses: ${stats.usageCount}`);
|
|
515
|
+
return lines.join('\n');
|
|
516
|
+
}
|
|
517
|
+
// ============================================================================
|
|
518
|
+
// Community Analytics Commands
|
|
519
|
+
// ============================================================================
|
|
520
|
+
export async function handleCommunityCommand(args) {
|
|
521
|
+
const subcommand = args.trim().toLowerCase();
|
|
522
|
+
if (!subcommand) {
|
|
523
|
+
return `🌐 Community Stats Sharing
|
|
524
|
+
|
|
525
|
+
Share anonymized model performance data to improve recommendations for everyone.
|
|
526
|
+
|
|
527
|
+
Commands:
|
|
528
|
+
/community status - Check sharing status
|
|
529
|
+
/community enable - Enable anonymous stats sharing
|
|
530
|
+
/community disable - Disable sharing
|
|
531
|
+
/community export - Export your stats for sharing
|
|
532
|
+
/community publish - Publish to Nostr network
|
|
533
|
+
/community fetch-nostr - Fetch community stats from Nostr
|
|
534
|
+
/community fetch - Fetch community stats from GitHub
|
|
535
|
+
/community merge - Show merged local + community stats
|
|
536
|
+
/community tier-list - Show community-enhanced tier list
|
|
537
|
+
|
|
538
|
+
Privacy:
|
|
539
|
+
✅ Only model names + aggregate stats shared
|
|
540
|
+
✅ NO prompts, responses, or personal data
|
|
541
|
+
✅ NO user identity stored
|
|
542
|
+
✅ Opt-in only (disabled by default)
|
|
543
|
+
|
|
544
|
+
Share your data:
|
|
545
|
+
1. Run /community enable
|
|
546
|
+
2. Use models and rate them with /rate
|
|
547
|
+
3. Run /community publish (Nostr) OR /community export (GitHub)
|
|
548
|
+
4. Your data helps everyone choose the best models!`;
|
|
549
|
+
}
|
|
550
|
+
const community = require('./community-analytics').getCommunityAnalytics();
|
|
551
|
+
switch (subcommand) {
|
|
552
|
+
case 'status': {
|
|
553
|
+
const enabled = community.isEnabled();
|
|
554
|
+
const cached = community.getCachedData();
|
|
555
|
+
return `🌐 Community Stats Status
|
|
556
|
+
|
|
557
|
+
Sharing: ${enabled ? '✅ Enabled' : '❌ Disabled'}
|
|
558
|
+
Community endpoint: ${community.config.endpoint}
|
|
559
|
+
Your user ID: ${community.userId}
|
|
560
|
+
Last fetched: ${community.config.lastFetched ? new Date(community.config.lastFetched).toLocaleString() : 'Never'}
|
|
561
|
+
|
|
562
|
+
Run /community enable to start sharing.`;
|
|
563
|
+
}
|
|
564
|
+
case 'enable': {
|
|
565
|
+
community.enable();
|
|
566
|
+
return `✅ Community stats sharing enabled!
|
|
567
|
+
|
|
568
|
+
Your anonymized performance data will be shared with the community.
|
|
569
|
+
|
|
570
|
+
Commands:
|
|
571
|
+
/community export - Generate submission file
|
|
572
|
+
/community publish - Publish via Nostr (coming soon)
|
|
573
|
+
/community fetch-nostr - Fetch community data from Nostr
|
|
574
|
+
|
|
575
|
+
Your user ID: ${community.userId}`;
|
|
576
|
+
}
|
|
577
|
+
case 'disable':
|
|
578
|
+
community.disable();
|
|
579
|
+
return '❌ Community stats sharing disabled.\n\nYour data will no longer be shared.';
|
|
580
|
+
case 'publish': {
|
|
581
|
+
if (!community.isEnabled()) {
|
|
582
|
+
return '❌ Community sharing not enabled.\n\nRun /community enable first.';
|
|
583
|
+
}
|
|
584
|
+
const publishResult = await community.publishToNostr();
|
|
585
|
+
if (publishResult.success) {
|
|
586
|
+
return `✅ Published to Nostr!
|
|
587
|
+
|
|
588
|
+
Event ID: ${publishResult.eventId}
|
|
589
|
+
Your stats are now available to the community.
|
|
590
|
+
|
|
591
|
+
View with: /community fetch-nostr
|
|
592
|
+
Merge with local: /community merge`;
|
|
593
|
+
}
|
|
594
|
+
return `❌ Failed to publish: ${publishResult.error}\n\nYou can still use /community export to save locally.`;
|
|
595
|
+
}
|
|
596
|
+
case 'fetch-nostr': {
|
|
597
|
+
const nostrData = await community.fetchFromNostr();
|
|
598
|
+
if (!nostrData) {
|
|
599
|
+
return '❌ Failed to fetch from Nostr relays.\n\nThis could mean:\n- No stats have been published yet\n- Network connectivity issues\n\nBe the first to publish with /community publish!';
|
|
600
|
+
}
|
|
601
|
+
return `✅ Fetched community data from Nostr!
|
|
602
|
+
|
|
603
|
+
Models: ${nostrData.models.length}
|
|
604
|
+
Contributors: ${nostrData.totalContributors}
|
|
605
|
+
Updated: ${new Date(nostrData.updatedAt).toLocaleString()}
|
|
606
|
+
|
|
607
|
+
Top models:
|
|
608
|
+
${nostrData.models
|
|
609
|
+
.sort((a, b) => b.avgRating - a.avgRating)
|
|
610
|
+
.slice(0, 5)
|
|
611
|
+
.map((m) => ` ${m.modelName}: ${m.avgRating.toFixed(1)}★ (${m.contributorCount} contributors)`)
|
|
612
|
+
.join('\n')}
|
|
613
|
+
|
|
614
|
+
Run /community merge to combine with your local data.`;
|
|
615
|
+
}
|
|
616
|
+
case 'export': {
|
|
617
|
+
const outputPath = community.saveSubmissionLocally();
|
|
618
|
+
const shareText = community.createShareableText();
|
|
619
|
+
const lines = shareText.split('\n');
|
|
620
|
+
// Show first 20 lines
|
|
621
|
+
return `📤 Community Submission Ready!
|
|
622
|
+
|
|
623
|
+
File saved to: ${outputPath}
|
|
624
|
+
|
|
625
|
+
Preview:
|
|
626
|
+
${lines.slice(0, 20).join('\n')}
|
|
627
|
+
...
|
|
628
|
+
|
|
629
|
+
To submit:
|
|
630
|
+
1. Copy the file content
|
|
631
|
+
2. Go to: https://github.com/kobolds/0xKobolds/tree/main/community
|
|
632
|
+
3. Create a PR or issue with your submission
|
|
633
|
+
|
|
634
|
+
Or share the raw JSON in GitHub Discussions.`;
|
|
635
|
+
}
|
|
636
|
+
case 'fetch': {
|
|
637
|
+
const data = await community.fetchCommunityStats();
|
|
638
|
+
if (!data) {
|
|
639
|
+
return '❌ Failed to fetch community data.\n\nCheck your internet connection and try again.';
|
|
640
|
+
}
|
|
641
|
+
return `✅ Fetched community data!
|
|
642
|
+
|
|
643
|
+
Models: ${data.models.length}
|
|
644
|
+
Contributors: ${data.totalContributors || 'unknown'}
|
|
645
|
+
Updated: ${new Date(data.updatedAt).toLocaleString()}
|
|
646
|
+
|
|
647
|
+
Top models by rating:
|
|
648
|
+
${data.models
|
|
649
|
+
.sort((a, b) => b.avgRating - a.avgRating)
|
|
650
|
+
.slice(0, 5)
|
|
651
|
+
.map((m) => ` ${m.modelName}: ${m.avgRating.toFixed(1)}/5 (${m.contributorCount} contributors)`)
|
|
652
|
+
.join('\n')}
|
|
653
|
+
|
|
654
|
+
Run /community merge to combine with your local data.`;
|
|
655
|
+
}
|
|
656
|
+
case 'merge': {
|
|
657
|
+
const merged = community.mergeWithLocal();
|
|
658
|
+
const sorted = Array.from(merged.values())
|
|
659
|
+
.sort((a, b) => b.avgRating - a.avgRating);
|
|
660
|
+
const mergeLines = ['🌐 Merged Community + Local Stats\n'];
|
|
661
|
+
mergeLines.push('Rank | Model | Rating | Latency | Success | Users');
|
|
662
|
+
mergeLines.push('-----|------------------------|--------|---------|---------|------');
|
|
663
|
+
sorted.slice(0, 15).forEach((m, i) => {
|
|
664
|
+
mergeLines.push(`${(i + 1).toString().padStart(4)} | ${m.modelName.substring(0, 22).padEnd(22)} | ${m.avgRating.toFixed(1)} | ${(m.avgLatency / 1000).toFixed(1)}s | ${(m.successRate * 100).toFixed(0)}% | ${m.contributorCount}`);
|
|
665
|
+
});
|
|
666
|
+
mergeLines.push(`\n_Showing ${Math.min(15, sorted.length)} of ${sorted.length} models_`);
|
|
667
|
+
mergeLines.push('\nCommands: /community tier-list | /community fetch');
|
|
668
|
+
return mergeLines.join('\n');
|
|
669
|
+
}
|
|
670
|
+
case 'tier-list': {
|
|
671
|
+
const tiers = community.getCommunityTierList();
|
|
672
|
+
const tierLines = ['🌐 Community Tier List\n'];
|
|
673
|
+
const tierEmojis = { S: '🥇', A: '🥈', B: '🥉', C: '⭐', D: '·' };
|
|
674
|
+
const tierDescs = {
|
|
675
|
+
S: 'Excellent (≥0.85)',
|
|
676
|
+
A: 'Great (≥0.70)',
|
|
677
|
+
B: 'Good (≥0.55)',
|
|
678
|
+
C: 'Fair (≥0.40)',
|
|
679
|
+
D: 'Needs improvement',
|
|
680
|
+
};
|
|
681
|
+
for (const { tier, models } of tiers) {
|
|
682
|
+
if (models.length === 0)
|
|
683
|
+
continue;
|
|
684
|
+
tierLines.push(`${tierEmojis[tier]} **Tier ${tier}** - ${tierDescs[tier]}`);
|
|
685
|
+
models.slice(0, 5).forEach(m => {
|
|
686
|
+
const rating = m.avgRating.toFixed(1);
|
|
687
|
+
const users = m.contributorCount;
|
|
688
|
+
tierLines.push(` ${m.modelName} - ${rating}/5 (${users} users)`);
|
|
689
|
+
});
|
|
690
|
+
tierLines.push('');
|
|
691
|
+
}
|
|
692
|
+
const totalModels = tiers.reduce((sum, t) => sum + t.models.length, 0);
|
|
693
|
+
tierLines.push(`_Based on ${totalModels} models from community data_`);
|
|
694
|
+
return tierLines.join('\n');
|
|
695
|
+
}
|
|
696
|
+
default:
|
|
697
|
+
return `Unknown command: ${subcommand}\n\nUse /community to see available commands.`;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
export async function handleBestForCommand(taskType) {
|
|
701
|
+
const community = require('./community-analytics').getCommunityAnalytics();
|
|
702
|
+
const merged = community.mergeWithLocal();
|
|
703
|
+
const task = taskType.toLowerCase().trim() || 'all';
|
|
704
|
+
const validTasks = ['chat', 'code', 'vision', 'reasoning', 'all'];
|
|
705
|
+
if (!validTasks.includes(task)) {
|
|
706
|
+
return `Unknown task: ${task}\n\nValid tasks: ${validTasks.join(', ')}`;
|
|
707
|
+
}
|
|
708
|
+
const lines = [`🎯 Best Models for ${task === 'all' ? 'Each Task' : task.toUpperCase()}\n`];
|
|
709
|
+
if (task === 'all') {
|
|
710
|
+
// Show best for each task type
|
|
711
|
+
const tasks = ['code', 'chat', 'vision', 'reasoning'];
|
|
712
|
+
for (const t of tasks) {
|
|
713
|
+
const models = Array.from(merged.values())
|
|
714
|
+
.filter((m) => m.taskStats.some(ts => ts.taskType === t && ts.usageCount >= 2))
|
|
715
|
+
.sort((a, b) => {
|
|
716
|
+
const aTask = a.taskStats.find(ts => ts.taskType === t);
|
|
717
|
+
const bTask = b.taskStats.find(ts => ts.taskType === t);
|
|
718
|
+
if (!aTask)
|
|
719
|
+
return 1;
|
|
720
|
+
if (!bTask)
|
|
721
|
+
return -1;
|
|
722
|
+
return (bTask.avgRating * 0.5 + bTask.successRate * 0.5) -
|
|
723
|
+
(aTask.avgRating * 0.5 + aTask.successRate * 0.5);
|
|
724
|
+
})
|
|
725
|
+
.slice(0, 5);
|
|
726
|
+
lines.push(`\n**${t.toUpperCase()}**:`);
|
|
727
|
+
if (models.length === 0) {
|
|
728
|
+
lines.push(` No data yet. Use models and rate them with /rate.`);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
models.forEach((m, i) => {
|
|
732
|
+
const ts = m.taskStats.find(ts => ts.taskType === t);
|
|
733
|
+
if (ts) {
|
|
734
|
+
lines.push(` ${i + 1}. ${m.modelName} - ${ts.avgRating.toFixed(1)}★ (${ts.usageCount} uses, ${(ts.successRate * 100).toFixed(0)}% success)`);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
// Show best for specific task
|
|
741
|
+
const models = Array.from(merged.values())
|
|
742
|
+
.filter((m) => m.taskStats.some(ts => ts.taskType === task && ts.usageCount >= 1))
|
|
743
|
+
.sort((a, b) => {
|
|
744
|
+
const aTask = a.taskStats.find(ts => ts.taskType === task);
|
|
745
|
+
const bTask = b.taskStats.find(ts => ts.taskType === task);
|
|
746
|
+
if (!aTask)
|
|
747
|
+
return 1;
|
|
748
|
+
if (!bTask)
|
|
749
|
+
return -1;
|
|
750
|
+
return (bTask.avgRating * 0.4 + bTask.successRate * 0.3 + (1 - bTask.avgLatency / 10000) * 0.3) -
|
|
751
|
+
(aTask.avgRating * 0.4 + aTask.successRate * 0.3 + (1 - aTask.avgLatency / 10000) * 0.3);
|
|
752
|
+
});
|
|
753
|
+
if (models.length === 0) {
|
|
754
|
+
return `📊 No models have been used for ${task} yet.\n\nUse models for ${task} tasks and rate them with /rate.`;
|
|
755
|
+
}
|
|
756
|
+
lines.push('Rank | Model | Rating | Success | Latency | Users');
|
|
757
|
+
lines.push('-----|-------------------------|--------|---------|---------|------');
|
|
758
|
+
models.slice(0, 15).forEach((m, i) => {
|
|
759
|
+
const ts = m.taskStats.find(ts => ts.taskType === task);
|
|
760
|
+
if (ts) {
|
|
761
|
+
lines.push(`${(i + 1).toString().padStart(4)} | ${m.modelName.substring(0, 23).padEnd(23)} | ${ts.avgRating.toFixed(1)} | ${(ts.successRate * 100).toFixed(0)}% | ${(ts.avgLatency / 1000).toFixed(1)}s | ${m.contributorCount}`);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
lines.push(`\n_Showing ${Math.min(15, models.length)} models for ${task}_`);
|
|
765
|
+
lines.push('\nCommands: /best-for code | /best-for chat | /best-for vision | /best-for reasoning');
|
|
766
|
+
}
|
|
767
|
+
return lines.join('\n');
|
|
768
|
+
}
|
|
769
|
+
// ============================================================================
|
|
770
|
+
// Re-exports for convenience
|
|
771
|
+
// ============================================================================
|
|
772
|
+
export { AdaptiveModelRouter, createRouter };
|
|
773
|
+
//# sourceMappingURL=router-commands.js.map
|