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.
Files changed (252) hide show
  1. package/HEARTBEAT.md +239 -0
  2. package/IDENTITY.md +12 -0
  3. package/README.md +138 -4
  4. package/SOUL.md +21 -0
  5. package/dist/package.json +10 -5
  6. package/dist/src/agent/bootstrap-loader.js +295 -66
  7. package/dist/src/agent/bootstrap-loader.js.map +1 -1
  8. package/dist/src/agent/context-pruning.js +10 -5
  9. package/dist/src/agent/context-pruning.js.map +1 -1
  10. package/dist/src/agent/embedded-runner.js +29 -15
  11. package/dist/src/agent/embedded-runner.js.map +1 -1
  12. package/dist/src/agent/index.js +5 -2
  13. package/dist/src/agent/index.js.map +1 -1
  14. package/dist/src/agent/system-prompt.js +167 -20
  15. package/dist/src/agent/system-prompt.js.map +1 -1
  16. package/dist/src/agent/tools/spawn-agent.js +72 -5
  17. package/dist/src/agent/tools/spawn-agent.js.map +1 -1
  18. package/dist/src/channels/slack/webhook.js +2 -2
  19. package/dist/src/channels/slack/webhook.js.map +1 -1
  20. package/dist/src/channels/telegram/bot.js +4 -4
  21. package/dist/src/channels/telegram/bot.js.map +1 -1
  22. package/dist/src/channels/whatsapp/integration.js +4 -4
  23. package/dist/src/channels/whatsapp/integration.js.map +1 -1
  24. package/dist/src/cli/commands/gateway.js +9 -10
  25. package/dist/src/cli/commands/gateway.js.map +1 -1
  26. package/dist/src/cli/commands/init.js +90 -0
  27. package/dist/src/cli/commands/init.js.map +1 -1
  28. package/dist/src/cli/commands/setup.js +53 -0
  29. package/dist/src/cli/commands/setup.js.map +1 -1
  30. package/dist/src/cli/index.js +0 -0
  31. package/dist/src/config/unified-config.js +5 -0
  32. package/dist/src/config/unified-config.js.map +1 -1
  33. package/dist/src/cron/index.js +2 -0
  34. package/dist/src/cron/index.js.map +1 -1
  35. package/dist/src/cron/nl-parser.js +522 -0
  36. package/dist/src/cron/nl-parser.js.map +1 -0
  37. package/dist/src/cron/runner.js +6 -31
  38. package/dist/src/cron/runner.js.map +1 -1
  39. package/dist/src/event-bus/index.js.map +1 -1
  40. package/dist/src/extensions/core/agent-orchestrator-extension.js +200 -148
  41. package/dist/src/extensions/core/agent-orchestrator-extension.js.map +1 -1
  42. package/dist/src/extensions/core/diagnostics-extension.js +93 -56
  43. package/dist/src/extensions/core/diagnostics-extension.js.map +1 -1
  44. package/dist/src/extensions/core/draconic-safety-extension.js +256 -3
  45. package/dist/src/extensions/core/draconic-safety-extension.js.map +1 -1
  46. package/dist/src/extensions/core/heartbeat-extension.js +416 -150
  47. package/dist/src/extensions/core/heartbeat-extension.js.map +1 -1
  48. package/dist/src/extensions/core/{generative-agents-extension.js → learning-extension.js} +90 -128
  49. package/dist/src/extensions/core/learning-extension.js.map +1 -0
  50. package/dist/src/extensions/core/perennial-memory-extension.js +884 -87
  51. package/dist/src/extensions/core/perennial-memory-extension.js.map +1 -1
  52. package/dist/src/extensions/core/routed-ollama-extension.js +345 -0
  53. package/dist/src/extensions/core/routed-ollama-extension.js.map +1 -0
  54. package/dist/src/extensions/core/task-manager-extension.js +25 -2
  55. package/dist/src/extensions/core/task-manager-extension.js.map +1 -1
  56. package/dist/src/extensions/core/websearch-enhanced-extension.js +81 -23
  57. package/dist/src/extensions/core/websearch-enhanced-extension.js.map +1 -1
  58. package/dist/src/extensions/core/workspace-footer-extension.js +40 -63
  59. package/dist/src/extensions/core/workspace-footer-extension.js.map +1 -1
  60. package/dist/src/extensions/loader.js +5 -1
  61. package/dist/src/extensions/loader.js.map +1 -1
  62. package/dist/src/gateway/gateway-server.js +74 -3
  63. package/dist/src/gateway/gateway-server.js.map +1 -1
  64. package/dist/src/gateway/index.js +2 -2
  65. package/dist/src/gateway/index.js.map +1 -1
  66. package/dist/src/gateway/methods/agent.js +1 -1
  67. package/dist/src/gateway/methods/agent.js.map +1 -1
  68. package/dist/src/gateway/methods/index.js +4 -0
  69. package/dist/src/gateway/methods/index.js.map +1 -1
  70. package/dist/src/gateway/methods/node.js +261 -0
  71. package/dist/src/gateway/methods/node.js.map +1 -0
  72. package/dist/src/gateway/queue-modes.js +356 -0
  73. package/dist/src/gateway/queue-modes.js.map +1 -0
  74. package/dist/src/index.js +47 -6
  75. package/dist/src/index.js.map +1 -1
  76. package/dist/src/llm/community-analytics.js +569 -0
  77. package/dist/src/llm/community-analytics.js.map +1 -0
  78. package/dist/src/llm/index.js +28 -4
  79. package/dist/src/llm/index.js.map +1 -1
  80. package/dist/src/llm/model-discovery.js +335 -0
  81. package/dist/src/llm/model-discovery.js.map +1 -0
  82. package/dist/src/llm/model-popularity.js +566 -0
  83. package/dist/src/llm/model-popularity.js.map +1 -0
  84. package/dist/src/llm/model-scoring-db.js +553 -0
  85. package/dist/src/llm/model-scoring-db.js.map +1 -0
  86. package/dist/src/llm/multi-provider.js +258 -0
  87. package/dist/src/llm/multi-provider.js.map +1 -0
  88. package/dist/src/llm/ollama.js +133 -187
  89. package/dist/src/llm/ollama.js.map +1 -1
  90. package/dist/src/llm/router-commands.js +773 -0
  91. package/dist/src/llm/router-commands.js.map +1 -0
  92. package/dist/src/llm/router-core.js +600 -0
  93. package/dist/src/llm/router-core.js.map +1 -0
  94. package/dist/src/memory/checkpoint-manager.js +278 -0
  95. package/dist/src/memory/checkpoint-manager.js.map +1 -0
  96. package/dist/src/memory/conflict-detector.js +279 -0
  97. package/dist/src/memory/conflict-detector.js.map +1 -0
  98. package/dist/src/memory/context-graph.js +360 -0
  99. package/dist/src/memory/context-graph.js.map +1 -0
  100. package/dist/src/memory/dialectic/benchmark-cli.js +200 -0
  101. package/dist/src/memory/dialectic/benchmark-cli.js.map +1 -0
  102. package/dist/src/memory/dialectic/debug-reasoning.js +37 -0
  103. package/dist/src/memory/dialectic/debug-reasoning.js.map +1 -0
  104. package/dist/src/memory/dialectic/index.js +135 -0
  105. package/dist/src/memory/dialectic/index.js.map +1 -0
  106. package/dist/src/memory/dialectic/nudges.js +380 -0
  107. package/dist/src/memory/dialectic/nudges.js.map +1 -0
  108. package/dist/src/memory/dialectic/reasoning-engine.js +607 -0
  109. package/dist/src/memory/dialectic/reasoning-engine.js.map +1 -0
  110. package/dist/src/memory/dialectic/reasoning.js +390 -0
  111. package/dist/src/memory/dialectic/reasoning.js.map +1 -0
  112. package/dist/src/memory/dialectic/skill-creation.js +481 -0
  113. package/dist/src/memory/dialectic/skill-creation.js.map +1 -0
  114. package/dist/src/memory/dialectic/store.js +663 -0
  115. package/dist/src/memory/dialectic/store.js.map +1 -0
  116. package/dist/src/memory/dialectic/types.js +11 -0
  117. package/dist/src/memory/dialectic/types.js.map +1 -0
  118. package/dist/src/memory/index.js +24 -2
  119. package/dist/src/memory/index.js.map +1 -1
  120. package/dist/src/memory/memory-decay.js +350 -0
  121. package/dist/src/memory/memory-decay.js.map +1 -0
  122. package/dist/src/memory/memory-integration.js +5 -5
  123. package/dist/src/memory/memory-integration.js.map +1 -1
  124. package/dist/src/memory/migrate-memory-stream.js +97 -0
  125. package/dist/src/memory/migrate-memory-stream.js.map +1 -0
  126. package/dist/src/memory/session-memory-bridge.js +49 -5
  127. package/dist/src/memory/session-memory-bridge.js.map +1 -1
  128. package/dist/src/memory/session-store.js +123 -0
  129. package/dist/src/memory/session-store.js.map +1 -1
  130. package/dist/src/memory/smart-write-rules.js +164 -0
  131. package/dist/src/memory/smart-write-rules.js.map +1 -0
  132. package/dist/src/memory/tiered-memory.js +436 -0
  133. package/dist/src/memory/tiered-memory.js.map +1 -0
  134. package/dist/src/memory/types.js +6 -0
  135. package/dist/src/memory/types.js.map +1 -0
  136. package/dist/src/memory/verify-migration.js +99 -0
  137. package/dist/src/memory/verify-migration.js.map +1 -0
  138. package/dist/src/pi-config.js +11 -9
  139. package/dist/src/pi-config.js.map +1 -1
  140. package/dist/src/skills/conditional-skills.js +464 -0
  141. package/dist/src/skills/conditional-skills.js.map +1 -0
  142. package/dist/src/skills/index.js +5 -0
  143. package/dist/src/skills/index.js.map +1 -1
  144. package/dist/src/skills/loader.js +56 -0
  145. package/dist/src/skills/loader.js.map +1 -1
  146. package/dist/src/skills/skill-manage.js +417 -0
  147. package/dist/src/skills/skill-manage.js.map +1 -0
  148. package/dist/src/tui/commands/orchestration-commands.js +62 -0
  149. package/dist/src/tui/commands/orchestration-commands.js.map +1 -1
  150. package/package.json +10 -5
  151. package/skills/model-router-test.ts +65 -0
  152. package/dist/src/extensions/core/auto-security-scan-extension.js +0 -41
  153. package/dist/src/extensions/core/auto-security-scan-extension.js.map +0 -1
  154. package/dist/src/extensions/core/cloudflare-browser-extension.js +0 -389
  155. package/dist/src/extensions/core/cloudflare-browser-extension.js.map +0 -1
  156. package/dist/src/extensions/core/compaction-safeguard.js +0 -223
  157. package/dist/src/extensions/core/compaction-safeguard.js.map +0 -1
  158. package/dist/src/extensions/core/generative-agents-extension.js.map +0 -1
  159. package/dist/src/extensions/core/obsidian-bridge-extension.js +0 -488
  160. package/dist/src/extensions/core/obsidian-bridge-extension.js.map +0 -1
  161. package/dist/src/llm/router.js +0 -145
  162. package/dist/src/llm/router.js.map +0 -1
  163. package/skills/kobold-scan-skill/node_modules/.package-lock.json +0 -172
  164. package/skills/kobold-scan-skill/node_modules/ansi-styles/index.d.ts +0 -345
  165. package/skills/kobold-scan-skill/node_modules/ansi-styles/index.js +0 -163
  166. package/skills/kobold-scan-skill/node_modules/ansi-styles/license +0 -9
  167. package/skills/kobold-scan-skill/node_modules/ansi-styles/package.json +0 -56
  168. package/skills/kobold-scan-skill/node_modules/ansi-styles/readme.md +0 -152
  169. package/skills/kobold-scan-skill/node_modules/balanced-match/.github/FUNDING.yml +0 -2
  170. package/skills/kobold-scan-skill/node_modules/balanced-match/LICENSE.md +0 -21
  171. package/skills/kobold-scan-skill/node_modules/balanced-match/README.md +0 -97
  172. package/skills/kobold-scan-skill/node_modules/balanced-match/index.js +0 -62
  173. package/skills/kobold-scan-skill/node_modules/balanced-match/package.json +0 -48
  174. package/skills/kobold-scan-skill/node_modules/brace-expansion/.github/FUNDING.yml +0 -2
  175. package/skills/kobold-scan-skill/node_modules/brace-expansion/LICENSE +0 -21
  176. package/skills/kobold-scan-skill/node_modules/brace-expansion/README.md +0 -135
  177. package/skills/kobold-scan-skill/node_modules/brace-expansion/index.js +0 -203
  178. package/skills/kobold-scan-skill/node_modules/brace-expansion/package.json +0 -49
  179. package/skills/kobold-scan-skill/node_modules/chalk/index.d.ts +0 -415
  180. package/skills/kobold-scan-skill/node_modules/chalk/license +0 -9
  181. package/skills/kobold-scan-skill/node_modules/chalk/package.json +0 -68
  182. package/skills/kobold-scan-skill/node_modules/chalk/readme.md +0 -341
  183. package/skills/kobold-scan-skill/node_modules/chalk/source/index.js +0 -229
  184. package/skills/kobold-scan-skill/node_modules/chalk/source/templates.js +0 -134
  185. package/skills/kobold-scan-skill/node_modules/chalk/source/util.js +0 -39
  186. package/skills/kobold-scan-skill/node_modules/color-convert/CHANGELOG.md +0 -54
  187. package/skills/kobold-scan-skill/node_modules/color-convert/LICENSE +0 -21
  188. package/skills/kobold-scan-skill/node_modules/color-convert/README.md +0 -68
  189. package/skills/kobold-scan-skill/node_modules/color-convert/conversions.js +0 -839
  190. package/skills/kobold-scan-skill/node_modules/color-convert/index.js +0 -81
  191. package/skills/kobold-scan-skill/node_modules/color-convert/package.json +0 -48
  192. package/skills/kobold-scan-skill/node_modules/color-convert/route.js +0 -97
  193. package/skills/kobold-scan-skill/node_modules/color-name/LICENSE +0 -8
  194. package/skills/kobold-scan-skill/node_modules/color-name/README.md +0 -11
  195. package/skills/kobold-scan-skill/node_modules/color-name/index.js +0 -152
  196. package/skills/kobold-scan-skill/node_modules/color-name/package.json +0 -28
  197. package/skills/kobold-scan-skill/node_modules/commander/LICENSE +0 -22
  198. package/skills/kobold-scan-skill/node_modules/commander/Readme.md +0 -1129
  199. package/skills/kobold-scan-skill/node_modules/commander/esm.mjs +0 -16
  200. package/skills/kobold-scan-skill/node_modules/commander/index.js +0 -27
  201. package/skills/kobold-scan-skill/node_modules/commander/lib/argument.js +0 -147
  202. package/skills/kobold-scan-skill/node_modules/commander/lib/command.js +0 -2179
  203. package/skills/kobold-scan-skill/node_modules/commander/lib/error.js +0 -45
  204. package/skills/kobold-scan-skill/node_modules/commander/lib/help.js +0 -461
  205. package/skills/kobold-scan-skill/node_modules/commander/lib/option.js +0 -326
  206. package/skills/kobold-scan-skill/node_modules/commander/lib/suggestSimilar.js +0 -100
  207. package/skills/kobold-scan-skill/node_modules/commander/package-support.json +0 -16
  208. package/skills/kobold-scan-skill/node_modules/commander/package.json +0 -80
  209. package/skills/kobold-scan-skill/node_modules/commander/typings/index.d.ts +0 -891
  210. package/skills/kobold-scan-skill/node_modules/fs.realpath/LICENSE +0 -43
  211. package/skills/kobold-scan-skill/node_modules/fs.realpath/README.md +0 -33
  212. package/skills/kobold-scan-skill/node_modules/fs.realpath/index.js +0 -66
  213. package/skills/kobold-scan-skill/node_modules/fs.realpath/old.js +0 -303
  214. package/skills/kobold-scan-skill/node_modules/fs.realpath/package.json +0 -26
  215. package/skills/kobold-scan-skill/node_modules/glob/LICENSE +0 -15
  216. package/skills/kobold-scan-skill/node_modules/glob/README.md +0 -399
  217. package/skills/kobold-scan-skill/node_modules/glob/common.js +0 -244
  218. package/skills/kobold-scan-skill/node_modules/glob/glob.js +0 -790
  219. package/skills/kobold-scan-skill/node_modules/glob/package.json +0 -55
  220. package/skills/kobold-scan-skill/node_modules/glob/sync.js +0 -486
  221. package/skills/kobold-scan-skill/node_modules/has-flag/index.d.ts +0 -39
  222. package/skills/kobold-scan-skill/node_modules/has-flag/index.js +0 -8
  223. package/skills/kobold-scan-skill/node_modules/has-flag/license +0 -9
  224. package/skills/kobold-scan-skill/node_modules/has-flag/package.json +0 -46
  225. package/skills/kobold-scan-skill/node_modules/has-flag/readme.md +0 -89
  226. package/skills/kobold-scan-skill/node_modules/inflight/LICENSE +0 -15
  227. package/skills/kobold-scan-skill/node_modules/inflight/README.md +0 -37
  228. package/skills/kobold-scan-skill/node_modules/inflight/inflight.js +0 -54
  229. package/skills/kobold-scan-skill/node_modules/inflight/package.json +0 -29
  230. package/skills/kobold-scan-skill/node_modules/inherits/LICENSE +0 -16
  231. package/skills/kobold-scan-skill/node_modules/inherits/README.md +0 -42
  232. package/skills/kobold-scan-skill/node_modules/inherits/inherits.js +0 -9
  233. package/skills/kobold-scan-skill/node_modules/inherits/inherits_browser.js +0 -27
  234. package/skills/kobold-scan-skill/node_modules/inherits/package.json +0 -29
  235. package/skills/kobold-scan-skill/node_modules/minimatch/LICENSE +0 -15
  236. package/skills/kobold-scan-skill/node_modules/minimatch/README.md +0 -259
  237. package/skills/kobold-scan-skill/node_modules/minimatch/lib/path.js +0 -4
  238. package/skills/kobold-scan-skill/node_modules/minimatch/minimatch.js +0 -944
  239. package/skills/kobold-scan-skill/node_modules/minimatch/package.json +0 -35
  240. package/skills/kobold-scan-skill/node_modules/once/LICENSE +0 -15
  241. package/skills/kobold-scan-skill/node_modules/once/README.md +0 -79
  242. package/skills/kobold-scan-skill/node_modules/once/once.js +0 -42
  243. package/skills/kobold-scan-skill/node_modules/once/package.json +0 -33
  244. package/skills/kobold-scan-skill/node_modules/supports-color/browser.js +0 -5
  245. package/skills/kobold-scan-skill/node_modules/supports-color/index.js +0 -135
  246. package/skills/kobold-scan-skill/node_modules/supports-color/license +0 -9
  247. package/skills/kobold-scan-skill/node_modules/supports-color/package.json +0 -53
  248. package/skills/kobold-scan-skill/node_modules/supports-color/readme.md +0 -76
  249. package/skills/kobold-scan-skill/node_modules/wrappy/LICENSE +0 -15
  250. package/skills/kobold-scan-skill/node_modules/wrappy/README.md +0 -36
  251. package/skills/kobold-scan-skill/node_modules/wrappy/package.json +0 -29
  252. 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