@tyvm/knowhow 0.0.104 → 0.0.106

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 (233) hide show
  1. package/CONFIG.md +8 -5
  2. package/package.json +3 -2
  3. package/scripts/check-model-pricing.ts +509 -0
  4. package/scripts/compare-openrouter-coverage.ts +576 -0
  5. package/src/agents/base/base.ts +127 -2
  6. package/src/agents/tools/execCommand.ts +4 -0
  7. package/src/agents/tools/executeScript/definition.ts +1 -1
  8. package/src/agents/tools/index.ts +0 -1
  9. package/src/agents/tools/list.ts +3 -43
  10. package/src/agents/tools/writeFile.ts +1 -1
  11. package/src/auth/browserLogin.ts +9 -4
  12. package/src/chat/modules/RemoteSyncModule.ts +3 -0
  13. package/src/cli.ts +31 -1
  14. package/src/clients/cerebras.ts +10 -0
  15. package/src/clients/contextLimits.ts +7 -2
  16. package/src/clients/copilot.ts +23 -0
  17. package/src/clients/deepseek.ts +16 -0
  18. package/src/clients/fireworks.ts +15 -0
  19. package/src/clients/gemini.ts +45 -2
  20. package/src/clients/github.ts +16 -0
  21. package/src/clients/groq.ts +15 -0
  22. package/src/clients/http.ts +190 -6
  23. package/src/clients/index.ts +215 -9
  24. package/src/clients/llama.ts +16 -0
  25. package/src/clients/mistral.ts +16 -0
  26. package/src/clients/nvidia.ts +16 -0
  27. package/src/clients/openai.ts +41 -11
  28. package/src/clients/openrouter.ts +17 -0
  29. package/src/clients/pricing/anthropic.ts +105 -78
  30. package/src/clients/pricing/cerebras.ts +11 -0
  31. package/src/clients/pricing/copilot.ts +60 -0
  32. package/src/clients/pricing/deepseek.ts +15 -0
  33. package/src/clients/pricing/fireworks.ts +32 -0
  34. package/src/clients/pricing/github.ts +69 -0
  35. package/src/clients/pricing/google.ts +245 -206
  36. package/src/clients/pricing/groq.ts +56 -0
  37. package/src/clients/pricing/index.ts +43 -6
  38. package/src/clients/pricing/llama.ts +18 -0
  39. package/src/clients/pricing/mistral.ts +34 -0
  40. package/src/clients/pricing/models.ts +23 -0
  41. package/src/clients/pricing/nvidia.ts +102 -0
  42. package/src/clients/pricing/openai.ts +347 -171
  43. package/src/clients/pricing/openrouter.ts +36 -0
  44. package/src/clients/pricing/types.ts +110 -0
  45. package/src/clients/pricing/xai.ts +123 -66
  46. package/src/clients/types.ts +4 -0
  47. package/src/clients/xai.ts +152 -2
  48. package/src/fileSync.ts +8 -2
  49. package/src/login.ts +11 -3
  50. package/src/services/AgentSyncFs.ts +36 -12
  51. package/src/services/KnowhowClient.ts +11 -0
  52. package/src/services/LazyToolsService.ts +6 -0
  53. package/src/services/S3.ts +0 -7
  54. package/src/services/SyncedAgentWatcher.ts +13 -298
  55. package/src/services/index.ts +1 -0
  56. package/src/services/modules/index.ts +11 -2
  57. package/src/services/watchers/FsSyncer.ts +155 -0
  58. package/src/services/watchers/RemoteSyncer.ts +153 -0
  59. package/src/services/watchers/index.ts +2 -0
  60. package/src/types.ts +56 -279
  61. package/src/worker.ts +174 -0
  62. package/tests/clients/pricing.test.ts +37 -0
  63. package/tests/manual/clients/completions.json +838 -226
  64. package/tests/manual/clients/completions.test.ts +46 -31
  65. package/ts_build/package.json +3 -2
  66. package/ts_build/src/agents/base/base.d.ts +17 -1
  67. package/ts_build/src/agents/base/base.js +82 -1
  68. package/ts_build/src/agents/base/base.js.map +1 -1
  69. package/ts_build/src/agents/tools/execCommand.js +3 -0
  70. package/ts_build/src/agents/tools/execCommand.js.map +1 -1
  71. package/ts_build/src/agents/tools/executeScript/definition.js +1 -1
  72. package/ts_build/src/agents/tools/executeScript/definition.js.map +1 -1
  73. package/ts_build/src/agents/tools/index.d.ts +0 -1
  74. package/ts_build/src/agents/tools/index.js +0 -1
  75. package/ts_build/src/agents/tools/index.js.map +1 -1
  76. package/ts_build/src/agents/tools/list.js +3 -38
  77. package/ts_build/src/agents/tools/list.js.map +1 -1
  78. package/ts_build/src/agents/tools/visionTool.d.ts +1 -1
  79. package/ts_build/src/agents/tools/writeFile.js +1 -1
  80. package/ts_build/src/agents/tools/writeFile.js.map +1 -1
  81. package/ts_build/src/ai.d.ts +1 -1
  82. package/ts_build/src/auth/browserLogin.d.ts +2 -1
  83. package/ts_build/src/auth/browserLogin.js +10 -3
  84. package/ts_build/src/auth/browserLogin.js.map +1 -1
  85. package/ts_build/src/chat/modules/RemoteSyncModule.js +1 -0
  86. package/ts_build/src/chat/modules/RemoteSyncModule.js.map +1 -1
  87. package/ts_build/src/cli.js +19 -0
  88. package/ts_build/src/cli.js.map +1 -1
  89. package/ts_build/src/clients/anthropic.d.ts +1 -82
  90. package/ts_build/src/clients/cerebras.d.ts +4 -0
  91. package/ts_build/src/clients/cerebras.js +14 -0
  92. package/ts_build/src/clients/cerebras.js.map +1 -0
  93. package/ts_build/src/clients/contextLimits.js +7 -2
  94. package/ts_build/src/clients/contextLimits.js.map +1 -1
  95. package/ts_build/src/clients/copilot.d.ts +4 -0
  96. package/ts_build/src/clients/copilot.js +15 -0
  97. package/ts_build/src/clients/copilot.js.map +1 -0
  98. package/ts_build/src/clients/deepseek.d.ts +4 -0
  99. package/ts_build/src/clients/deepseek.js +15 -0
  100. package/ts_build/src/clients/deepseek.js.map +1 -0
  101. package/ts_build/src/clients/fireworks.d.ts +4 -0
  102. package/ts_build/src/clients/fireworks.js +15 -0
  103. package/ts_build/src/clients/fireworks.js.map +1 -0
  104. package/ts_build/src/clients/gemini.d.ts +1 -0
  105. package/ts_build/src/clients/gemini.js +28 -1
  106. package/ts_build/src/clients/gemini.js.map +1 -1
  107. package/ts_build/src/clients/github.d.ts +4 -0
  108. package/ts_build/src/clients/github.js +15 -0
  109. package/ts_build/src/clients/github.js.map +1 -0
  110. package/ts_build/src/clients/groq.d.ts +4 -0
  111. package/ts_build/src/clients/groq.js +15 -0
  112. package/ts_build/src/clients/groq.js.map +1 -0
  113. package/ts_build/src/clients/http.d.ts +22 -1
  114. package/ts_build/src/clients/http.js +132 -7
  115. package/ts_build/src/clients/http.js.map +1 -1
  116. package/ts_build/src/clients/index.d.ts +22 -0
  117. package/ts_build/src/clients/index.js +150 -5
  118. package/ts_build/src/clients/index.js.map +1 -1
  119. package/ts_build/src/clients/llama.d.ts +4 -0
  120. package/ts_build/src/clients/llama.js +15 -0
  121. package/ts_build/src/clients/llama.js.map +1 -0
  122. package/ts_build/src/clients/mistral.d.ts +4 -0
  123. package/ts_build/src/clients/mistral.js +15 -0
  124. package/ts_build/src/clients/mistral.js.map +1 -0
  125. package/ts_build/src/clients/nvidia.d.ts +4 -0
  126. package/ts_build/src/clients/nvidia.js +15 -0
  127. package/ts_build/src/clients/nvidia.js.map +1 -0
  128. package/ts_build/src/clients/openai.d.ts +4 -206
  129. package/ts_build/src/clients/openai.js +27 -9
  130. package/ts_build/src/clients/openai.js.map +1 -1
  131. package/ts_build/src/clients/openrouter.d.ts +4 -0
  132. package/ts_build/src/clients/openrouter.js +15 -0
  133. package/ts_build/src/clients/openrouter.js.map +1 -0
  134. package/ts_build/src/clients/pricing/anthropic.d.ts +26 -78
  135. package/ts_build/src/clients/pricing/anthropic.js +75 -78
  136. package/ts_build/src/clients/pricing/anthropic.js.map +1 -1
  137. package/ts_build/src/clients/pricing/cerebras.d.ts +4 -0
  138. package/ts_build/src/clients/pricing/cerebras.js +11 -0
  139. package/ts_build/src/clients/pricing/cerebras.js.map +1 -0
  140. package/ts_build/src/clients/pricing/copilot.d.ts +5 -0
  141. package/ts_build/src/clients/pricing/copilot.js +35 -0
  142. package/ts_build/src/clients/pricing/copilot.js.map +1 -0
  143. package/ts_build/src/clients/pricing/deepseek.d.ts +5 -0
  144. package/ts_build/src/clients/pricing/deepseek.js +10 -0
  145. package/ts_build/src/clients/pricing/deepseek.js.map +1 -0
  146. package/ts_build/src/clients/pricing/fireworks.d.ts +5 -0
  147. package/ts_build/src/clients/pricing/fireworks.js +21 -0
  148. package/ts_build/src/clients/pricing/fireworks.js.map +1 -0
  149. package/ts_build/src/clients/pricing/github.d.ts +4 -0
  150. package/ts_build/src/clients/pricing/github.js +58 -0
  151. package/ts_build/src/clients/pricing/github.js.map +1 -0
  152. package/ts_build/src/clients/pricing/google.d.ts +59 -6
  153. package/ts_build/src/clients/pricing/google.js +214 -167
  154. package/ts_build/src/clients/pricing/google.js.map +1 -1
  155. package/ts_build/src/clients/pricing/groq.d.ts +5 -0
  156. package/ts_build/src/clients/pricing/groq.js +41 -0
  157. package/ts_build/src/clients/pricing/groq.js.map +1 -0
  158. package/ts_build/src/clients/pricing/index.d.ts +17 -6
  159. package/ts_build/src/clients/pricing/index.js +65 -10
  160. package/ts_build/src/clients/pricing/index.js.map +1 -1
  161. package/ts_build/src/clients/pricing/llama.d.ts +4 -0
  162. package/ts_build/src/clients/pricing/llama.js +14 -0
  163. package/ts_build/src/clients/pricing/llama.js.map +1 -0
  164. package/ts_build/src/clients/pricing/mistral.d.ts +5 -0
  165. package/ts_build/src/clients/pricing/mistral.js +23 -0
  166. package/ts_build/src/clients/pricing/mistral.js.map +1 -0
  167. package/ts_build/src/clients/pricing/models.d.ts +9 -0
  168. package/ts_build/src/clients/pricing/models.js +19 -0
  169. package/ts_build/src/clients/pricing/models.js.map +1 -0
  170. package/ts_build/src/clients/pricing/nvidia.d.ts +8 -0
  171. package/ts_build/src/clients/pricing/nvidia.js +96 -0
  172. package/ts_build/src/clients/pricing/nvidia.js.map +1 -0
  173. package/ts_build/src/clients/pricing/openai.d.ts +86 -197
  174. package/ts_build/src/clients/pricing/openai.js +294 -168
  175. package/ts_build/src/clients/pricing/openai.js.map +1 -1
  176. package/ts_build/src/clients/pricing/openrouter.d.ts +4 -0
  177. package/ts_build/src/clients/pricing/openrouter.js +29 -0
  178. package/ts_build/src/clients/pricing/openrouter.js.map +1 -0
  179. package/ts_build/src/clients/pricing/types.d.ts +46 -0
  180. package/ts_build/src/clients/pricing/types.js +49 -0
  181. package/ts_build/src/clients/pricing/types.js.map +1 -0
  182. package/ts_build/src/clients/pricing/xai.d.ts +39 -64
  183. package/ts_build/src/clients/pricing/xai.js +93 -60
  184. package/ts_build/src/clients/pricing/xai.js.map +1 -1
  185. package/ts_build/src/clients/types.d.ts +1 -0
  186. package/ts_build/src/clients/xai.d.ts +2 -58
  187. package/ts_build/src/clients/xai.js +123 -2
  188. package/ts_build/src/clients/xai.js.map +1 -1
  189. package/ts_build/src/fileSync.js +7 -2
  190. package/ts_build/src/fileSync.js.map +1 -1
  191. package/ts_build/src/login.js +8 -2
  192. package/ts_build/src/login.js.map +1 -1
  193. package/ts_build/src/services/AgentSyncFs.js +1 -0
  194. package/ts_build/src/services/AgentSyncFs.js.map +1 -1
  195. package/ts_build/src/services/KnowhowClient.d.ts +1 -0
  196. package/ts_build/src/services/KnowhowClient.js +7 -0
  197. package/ts_build/src/services/KnowhowClient.js.map +1 -1
  198. package/ts_build/src/services/LazyToolsService.d.ts +1 -0
  199. package/ts_build/src/services/LazyToolsService.js +3 -0
  200. package/ts_build/src/services/LazyToolsService.js.map +1 -1
  201. package/ts_build/src/services/S3.js +0 -7
  202. package/ts_build/src/services/S3.js.map +1 -1
  203. package/ts_build/src/services/SyncedAgentWatcher.d.ts +0 -51
  204. package/ts_build/src/services/SyncedAgentWatcher.js +1 -282
  205. package/ts_build/src/services/SyncedAgentWatcher.js.map +1 -1
  206. package/ts_build/src/services/index.d.ts +1 -0
  207. package/ts_build/src/services/index.js +1 -0
  208. package/ts_build/src/services/index.js.map +1 -1
  209. package/ts_build/src/services/modules/index.js +41 -1
  210. package/ts_build/src/services/modules/index.js.map +1 -1
  211. package/ts_build/src/services/watchers/FsSyncer.d.ts +27 -0
  212. package/ts_build/src/services/watchers/FsSyncer.js +135 -0
  213. package/ts_build/src/services/watchers/FsSyncer.js.map +1 -0
  214. package/ts_build/src/services/watchers/RemoteSyncer.d.ts +28 -0
  215. package/ts_build/src/services/watchers/RemoteSyncer.js +126 -0
  216. package/ts_build/src/services/watchers/RemoteSyncer.js.map +1 -0
  217. package/ts_build/src/services/watchers/index.d.ts +2 -0
  218. package/ts_build/src/services/watchers/index.js +19 -0
  219. package/ts_build/src/services/watchers/index.js.map +1 -0
  220. package/ts_build/src/types.d.ts +163 -124
  221. package/ts_build/src/types.js +33 -213
  222. package/ts_build/src/types.js.map +1 -1
  223. package/ts_build/src/worker.d.ts +4 -0
  224. package/ts_build/src/worker.js +140 -0
  225. package/ts_build/src/worker.js.map +1 -1
  226. package/ts_build/tests/clients/pricing.test.js +21 -0
  227. package/ts_build/tests/clients/pricing.test.js.map +1 -1
  228. package/ts_build/tests/manual/clients/completions.test.js +27 -24
  229. package/ts_build/tests/manual/clients/completions.test.js.map +1 -1
  230. package/src/clients/pricing/catalog.ts +0 -287
  231. package/ts_build/src/clients/pricing/catalog.d.ts +0 -28
  232. package/ts_build/src/clients/pricing/catalog.js +0 -179
  233. package/ts_build/src/clients/pricing/catalog.js.map +0 -1
package/CONFIG.md CHANGED
@@ -296,13 +296,16 @@ There are many commands you can call from a chat session, try TAB to see a list
296
296
 
297
297
  ### knowhow chat: custom agents
298
298
  ```json
299
- "assistants": [
299
+ "agents": [
300
300
  {
301
- "name": "linter",
302
- "description": "Clean up your code",
303
- "instructions": "Read the files that are loaded via vim plugin and provide debugging and linter suggestions"
301
+ "name": "Example agent",
302
+ "description": "You can define agents in the config. They will have access to all tools.",
303
+ "instructions": "Reply to the user saying 'Hello, world!'",
304
+ "model": "gpt-4o-2024-08-06",
305
+ "provider": "openai"
304
306
  }
305
- ]
307
+ ],
308
+
306
309
  ```
307
310
  You can configure new agents via the config above. This would create a new option when you use `agents` in a chat session.
308
311
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tyvm/knowhow",
3
- "version": "0.0.104",
3
+ "version": "0.0.106",
4
4
  "description": "ai cli with plugins and agents",
5
5
  "main": "ts_build/src/index.js",
6
6
  "bin": {
@@ -20,7 +20,8 @@
20
20
  "check:isolated": "bash scripts/check-isolated-deps.sh",
21
21
  "lint": "tslint ./src/**/*.ts",
22
22
  "lint:deps": "depcheck --config=.depcheckrc",
23
- "lint:all": "npm run lint && npm run lint:deps"
23
+ "lint:all": "npm run lint && npm run lint:deps",
24
+ "check:model-pricing": "ts-node scripts/check-model-pricing.ts"
24
25
  },
25
26
  "keywords": [],
26
27
  "author": "Micah Riggan",
@@ -0,0 +1,509 @@
1
+ #!/usr/bin/env npx ts-node
2
+ /**
3
+ * check-model-pricing.ts
4
+ *
5
+ * Compares models.dev live data against knowhow's registered providers & pricing.
6
+ *
7
+ * Usage:
8
+ * npx ts-node scripts/check-model-pricing.ts
9
+ * npx ts-node scripts/check-model-pricing.ts --provider groq
10
+ * npx ts-node scripts/check-model-pricing.ts --provider openai --show-all
11
+ *
12
+ * Options:
13
+ * --provider <name> Only check a specific provider
14
+ * --show-all Show all models, not just mismatches
15
+ * --free-only Only show models with $0 cost on models.dev
16
+ * --output <file> Write results to a markdown file (e.g. analysis.md)
17
+ * --include-deprecated Include deprecated/retired models in gap analysis (default: excluded)
18
+ */
19
+
20
+ import https from "https";
21
+ import { ALL_MODEL_CATALOG } from "../src/clients/pricing/models";
22
+ import { ModelCatalogEntry } from "../src/clients/pricing/types";
23
+ import { GroqTextPricing } from "../src/clients/pricing/groq";
24
+ import { DeepSeekTextPricing } from "../src/clients/pricing/deepseek";
25
+ import { MistralTextPricing } from "../src/clients/pricing/mistral";
26
+ import { NvidiaTextPricing, NvidiaImagePricing } from "../src/clients/pricing/nvidia";
27
+ import { GitHubModelsTextPricing } from "../src/clients/pricing/github";
28
+ import { OpenRouterTextPricing } from "../src/clients/pricing/openrouter";
29
+ import { LlamaTextPricing } from "../src/clients/pricing/llama";
30
+ import { CopilotTextPricing } from "../src/clients/pricing/copilot";
31
+ import { CerebrasTextPricing } from "../src/clients/pricing/cerebras";
32
+
33
+ // Build per-provider pricing maps for providers not yet in ALL_MODEL_CATALOG
34
+ const EXTRA_PROVIDER_PRICING: Record<string, Record<string, { input: number; output: number }>> = {
35
+ groq: GroqTextPricing,
36
+ deepseek: DeepSeekTextPricing,
37
+ mistral: MistralTextPricing,
38
+ nvidia: { ...NvidiaTextPricing, ...NvidiaImagePricing },
39
+ github: GitHubModelsTextPricing,
40
+ openrouter: OpenRouterTextPricing,
41
+ llama: LlamaTextPricing,
42
+ "github-copilot": CopilotTextPricing,
43
+ cerebras: CerebrasTextPricing,
44
+ };
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // CLI args
48
+ // ---------------------------------------------------------------------------
49
+ const args = process.argv.slice(2);
50
+ const getArg = (flag: string) => {
51
+ const idx = args.indexOf(flag);
52
+ return idx !== -1 ? args[idx + 1] : null;
53
+ };
54
+ const hasFlag = (flag: string) => args.includes(flag);
55
+
56
+ const filterProvider = getArg("--provider");
57
+ const showAll = hasFlag("--show-all");
58
+ const freeOnly = hasFlag("--free-only");
59
+ const outputFile = getArg("--output");
60
+ const includeDeprecated = hasFlag("--include-deprecated");
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // models.dev API types
64
+ // The API endpoint is https://models.dev/api.json
65
+ // It returns an object keyed by provider ID, each value has a `models` sub-object
66
+ // ---------------------------------------------------------------------------
67
+ interface ModelDevModel {
68
+ id: string;
69
+ name?: string;
70
+ cost?: {
71
+ input?: number | null;
72
+ output?: number | null;
73
+ input_cached?: number | null;
74
+ };
75
+ limit?: {
76
+ context?: number;
77
+ output?: number;
78
+ };
79
+ }
80
+
81
+ interface ModelDevProvider {
82
+ id: string;
83
+ name?: string;
84
+ models: Record<string, ModelDevModel>;
85
+ }
86
+
87
+ interface ModelDevApiResponse {
88
+ [providerId: string]: ModelDevProvider;
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Fetch helpers
93
+ // ---------------------------------------------------------------------------
94
+ function fetchJson<T>(url: string): Promise<T> {
95
+ return new Promise((resolve, reject) => {
96
+ https
97
+ .get(url, { headers: { "User-Agent": "knowhow-pricing-check/1.0" } }, (res) => {
98
+ let data = "";
99
+ res.on("data", (chunk) => (data += chunk));
100
+ res.on("end", () => {
101
+ try {
102
+ resolve(JSON.parse(data) as T);
103
+ } catch (e) {
104
+ reject(new Error(`Failed to parse response from ${url}: ${e}`));
105
+ }
106
+ });
107
+ })
108
+ .on("error", reject);
109
+ });
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // models.dev provider ID → knowhow provider name mapping
114
+ // ---------------------------------------------------------------------------
115
+ const PROVIDER_MAP: Record<string, string> = {
116
+ openai: "openai",
117
+ anthropic: "anthropic",
118
+ google: "google",
119
+ "x-ai": "xai",
120
+ xai: "xai",
121
+ groq: "groq",
122
+ mistral: "mistral",
123
+ deepseek: "deepseek",
124
+ nvidia: "nvidia",
125
+ "github-models": "github",
126
+ github: "github",
127
+ openrouter: "openrouter",
128
+ "github-copilot": "github-copilot",
129
+ "meta-llama": "llama",
130
+ llama: "llama",
131
+ cerebras: "cerebras",
132
+ };
133
+
134
+ // Providers we have knowhow clients for
135
+ const SUPPORTED_PROVIDERS = new Set(Object.values(PROVIDER_MAP));
136
+
137
+ // ---------------------------------------------------------------------------
138
+ // Result types
139
+ // ---------------------------------------------------------------------------
140
+ interface ModelComparison {
141
+ modelId: string;
142
+ provider: string; // knowhow provider name
143
+ devProvider: string; // models.dev provider id
144
+ devInputPrice: number | null;
145
+ devOutputPrice: number | null;
146
+ khInputPrice: number | null;
147
+ khOutputPrice: number | null;
148
+ status:
149
+ | "ok"
150
+ | "price-mismatch"
151
+ | "missing-in-knowhow"
152
+ | "missing-pricing"
153
+ | "extra-in-knowhow";
154
+ details?: string;
155
+ }
156
+
157
+ // ---------------------------------------------------------------------------
158
+ // Main
159
+ // ---------------------------------------------------------------------------
160
+ async function main() {
161
+ console.log("šŸ” Fetching model data from models.dev...\n");
162
+
163
+ // Flat list of all dev models with provider attached
164
+ interface FlatDevModel extends ModelDevModel {
165
+ provider: string;
166
+ }
167
+
168
+ let allDevModels: FlatDevModel[] = [];
169
+
170
+ try {
171
+ const response = await fetchJson<ModelDevApiResponse>("https://models.dev/api.json");
172
+ for (const [providerId, providerData] of Object.entries(response)) {
173
+ if (!providerData || typeof providerData.models !== "object") continue;
174
+ for (const [modelId, model] of Object.entries(providerData.models)) {
175
+ allDevModels.push({
176
+ ...model,
177
+ id: model.id || modelId,
178
+ provider: providerId,
179
+ });
180
+ }
181
+ }
182
+ } catch (e: any) {
183
+ console.error(`āŒ Failed to fetch from models.dev: ${e.message}`);
184
+ process.exit(1);
185
+ }
186
+
187
+ console.log(`āœ… Fetched ${allDevModels.length} models from models.dev\n`);
188
+
189
+ // Build static knowhow model catalog from ALL_MODEL_CATALOG + extra provider pricing maps
190
+ // This does NOT require API keys — it uses the static pricing files directly.
191
+ const khCatalogEntries: ModelCatalogEntry[] = [...ALL_MODEL_CATALOG];
192
+
193
+ // Add entries for providers that have pricing maps but aren't yet in ALL_MODEL_CATALOG
194
+ for (const [provider, pricingMap] of Object.entries(EXTRA_PROVIDER_PRICING)) {
195
+ for (const [modelId, pricing] of Object.entries(pricingMap)) {
196
+ khCatalogEntries.push({
197
+ id: modelId,
198
+ provider,
199
+ type: "completion",
200
+ pricing: { input: pricing.input, output: pricing.output },
201
+ });
202
+ }
203
+ }
204
+
205
+ // Build lookup maps
206
+ const khPriceMap = new Map<string, ModelCatalogEntry>();
207
+ const khModelsByProvider = new Map<string, Set<string>>();
208
+ for (const entry of khCatalogEntries) {
209
+ khPriceMap.set(`${entry.provider}:${entry.id}`, entry);
210
+ if (!khModelsByProvider.has(entry.provider)) {
211
+ khModelsByProvider.set(entry.provider, new Set());
212
+ }
213
+ khModelsByProvider.get(entry.provider)!.add(entry.id);
214
+ }
215
+
216
+ // ---------------------------------------------------------------------------
217
+ // Fuzzy model matching helpers
218
+ // models.dev often uses generic aliases (e.g. "grok-4", "grok-3")
219
+ // while knowhow uses versioned IDs (e.g. "grok-4-0709", "grok-3-beta").
220
+ // Also, anthropic uses base IDs in knowhow (e.g. "claude-opus-4") while
221
+ // models.dev uses dated versions (e.g. "claude-opus-4-20250514").
222
+ //
223
+ // To avoid false matches like kh "gpt-5" matching dev "gpt-5-pro",
224
+ // we only consider a suffix to be a "version suffix" if it looks like:
225
+ // - A date: all digits, e.g. "20250514", "1212", "0709"
226
+ // - A known version word: "beta", "latest", "preview"
227
+ // - A short alphanumeric version tag that starts with a digit, e.g. "001"
228
+ // NOT accepted as version suffix: "pro", "mini", "nano", "fast", "turbo", etc.
229
+ // ---------------------------------------------------------------------------
230
+ const VERSION_SUFFIX_RE = /^(\d+|beta|latest|preview|exp|rc\d*|v\d+)$/i;
231
+
232
+ function isVersionSuffix(part: string): boolean {
233
+ return VERSION_SUFFIX_RE.test(part);
234
+ }
235
+
236
+ // We match if:
237
+ // 1. Exact match, OR
238
+ // 2. Any knowhow model ID starts with the dev model ID + "-" or "/"
239
+ // (e.g. dev "grok-4" matches kh "grok-4-0709")
240
+ // ---------------------------------------------------------------------------
241
+ function findKhEntry(khProvider: string, devModelId: string): ModelCatalogEntry | undefined {
242
+ // 1. Exact match
243
+ const exact = khPriceMap.get(`${khProvider}:${devModelId}`);
244
+ if (exact) return exact;
245
+
246
+ const khModels = khModelsByProvider.get(khProvider);
247
+ if (!khModels) return undefined;
248
+
249
+ // Collect all candidates and pick the longest match (most specific)
250
+ let bestMatch: ModelCatalogEntry | undefined;
251
+ let bestMatchLen = 0;
252
+
253
+ for (const khId of khModels) {
254
+ // 2. kh is more specific: kh ID starts with dev ID + "-" or "/"
255
+ // e.g. dev "grok-4" matches kh "grok-4-0709"
256
+ if (khId.startsWith(devModelId + "-") || khId.startsWith(devModelId + "/")) {
257
+ const suffix = khId.slice(devModelId.length + 1);
258
+ // Only match if the extra suffix looks like a version (not a different model variant)
259
+ if (suffix.split("-").every(isVersionSuffix)) {
260
+ if (khId.length > bestMatchLen) {
261
+ bestMatch = khPriceMap.get(`${khProvider}:${khId}`);
262
+ bestMatchLen = khId.length;
263
+ }
264
+ }
265
+ }
266
+ // 3. dev is more specific: dev ID starts with kh ID + "-" or "/"
267
+ // e.g. dev "claude-opus-4-20250514" matches kh "claude-opus-4"
268
+ // e.g. dev "claude-3-7-sonnet-20250219" matches kh "claude-3-7-sonnet"
269
+ if (devModelId.startsWith(khId + "-") || devModelId.startsWith(khId + "/")) {
270
+ const suffix = devModelId.slice(khId.length + 1);
271
+ // Only match if the extra suffix looks like a version (not a different model variant)
272
+ if (suffix.split("-").every(isVersionSuffix)) {
273
+ if (khId.length > bestMatchLen) {
274
+ bestMatch = khPriceMap.get(`${khProvider}:${khId}`);
275
+ bestMatchLen = khId.length;
276
+ }
277
+ }
278
+ }
279
+ }
280
+
281
+ return bestMatch;
282
+ }
283
+
284
+ // ---------------------------------------------------------------------------
285
+ // Compare
286
+ // ---------------------------------------------------------------------------
287
+ const results: ModelComparison[] = [];
288
+ const checkedProviders = new Set<string>();
289
+
290
+ for (const devModel of allDevModels) {
291
+ const rawProvider = devModel.provider?.toLowerCase() || "";
292
+ const khProvider = PROVIDER_MAP[rawProvider] || rawProvider;
293
+
294
+ if (!SUPPORTED_PROVIDERS.has(khProvider)) continue;
295
+ if (filterProvider && khProvider !== filterProvider && rawProvider !== filterProvider) continue;
296
+ if (freeOnly && devModel.cost?.input !== 0) continue;
297
+
298
+ checkedProviders.add(khProvider);
299
+
300
+ const modelId = devModel.id;
301
+ const devInputPrice = devModel.cost?.input ?? null;
302
+ const devOutputPrice = devModel.cost?.output ?? null;
303
+
304
+ // Check if model is registered in knowhow (exact or prefix match)
305
+ const khEntry = findKhEntry(khProvider, modelId);
306
+ const isRegistered = !!khEntry;
307
+
308
+ // Skip deprecated/retired models — they shouldn't appear in coverage gaps
309
+ if (!includeDeprecated && (khEntry?.deprecated || khEntry?.limitedAvailability || khEntry?.type === "live")) {
310
+ continue;
311
+ }
312
+
313
+ const khInputPrice = khEntry?.pricing?.input ?? null;
314
+ // For image models, prefer image_generation_per_1m_tokens for output comparison
315
+ // since models.dev reports image output as per-1M-tokens rate
316
+ const khOutputPrice =
317
+ (khEntry?.pricing as any)?.image_generation_per_1m_tokens ??
318
+ khEntry?.pricing?.output ??
319
+ null;
320
+
321
+ let status: ModelComparison["status"] = "ok";
322
+ let details: string | undefined;
323
+
324
+ if (!isRegistered) {
325
+ status = "missing-in-knowhow";
326
+ details = `Model not registered for provider "${khProvider}" in knowhow`;
327
+ } else if (!khEntry) {
328
+ status = "missing-pricing";
329
+ details = `Model registered but no pricing data in knowhow`;
330
+ } else {
331
+ // If models.dev says FREE but we have a :free variant registered, it's not a mismatch —
332
+ // models.dev is tracking the free tier while we also track the paid tier separately.
333
+ const devIsFree = devInputPrice === 0 && devOutputPrice === 0;
334
+ const hasFreeVariant = khModelsByProvider.get(khProvider)?.has(`${modelId}:free`);
335
+ if (devIsFree && hasFreeVariant) {
336
+ // models.dev tracks the free tier; we have both free and paid — skip comparison
337
+ }
338
+
339
+ // Check for price mismatches (allow 5% floating point tolerance)
340
+ const inputMismatch =
341
+ !(devIsFree && hasFreeVariant) &&
342
+ devInputPrice !== null &&
343
+ khInputPrice !== null &&
344
+ Math.abs((devInputPrice - khInputPrice) / Math.max(devInputPrice, 0.0001)) > 0.05;
345
+ const outputMismatch =
346
+ !(devIsFree && hasFreeVariant) &&
347
+ devOutputPrice !== null &&
348
+ khOutputPrice !== null &&
349
+ Math.abs((devOutputPrice - khOutputPrice) / Math.max(devOutputPrice, 0.0001)) > 0.05;
350
+
351
+ if (inputMismatch || outputMismatch) {
352
+ status = "price-mismatch";
353
+ const parts: string[] = [];
354
+ if (inputMismatch) {
355
+ parts.push(
356
+ `input: models.dev=$${devInputPrice}/1M, knowhow=$${khInputPrice}/1M`
357
+ );
358
+ }
359
+ if (outputMismatch) {
360
+ parts.push(
361
+ `output: models.dev=$${devOutputPrice}/1M, knowhow=$${khOutputPrice}/1M`
362
+ );
363
+ }
364
+ details = parts.join("; ");
365
+ }
366
+ }
367
+
368
+ if (showAll || status !== "ok") {
369
+ results.push({
370
+ modelId,
371
+ provider: khProvider,
372
+ devProvider: rawProvider,
373
+ devInputPrice,
374
+ devOutputPrice,
375
+ khInputPrice,
376
+ khOutputPrice,
377
+ status,
378
+ details,
379
+ });
380
+ }
381
+ }
382
+
383
+ // Also check for models in knowhow that aren't on models.dev
384
+ const devModelIds = new Set(allDevModels.map((m) => m.id));
385
+ for (const [khProvider, models] of khModelsByProvider.entries()) {
386
+ if (filterProvider && khProvider !== filterProvider) continue;
387
+ if (!SUPPORTED_PROVIDERS.has(khProvider)) continue;
388
+ for (const modelId of models) {
389
+ // A kh model is "extra" only if no dev model is a prefix of it
390
+ // (i.e. no dev model "grok-4" that would prefix-match kh "grok-4-0709")
391
+ const hasDevAlias = [...devModelIds].some(
392
+ (devId) =>
393
+ modelId === devId ||
394
+ modelId.startsWith(devId + "-") ||
395
+ modelId.startsWith(devId + "/")
396
+ );
397
+ if (!hasDevAlias) {
398
+ if (showAll) {
399
+ results.push({
400
+ modelId,
401
+ provider: khProvider,
402
+ devProvider: "",
403
+ devInputPrice: null,
404
+ devOutputPrice: null,
405
+ khInputPrice: null,
406
+ khOutputPrice: null,
407
+ status: "extra-in-knowhow",
408
+ details: "Model in knowhow but not found on models.dev",
409
+ });
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ // ---------------------------------------------------------------------------
416
+ // Output
417
+ // ---------------------------------------------------------------------------
418
+ const summary = generateReport(results, checkedProviders, allDevModels.length);
419
+ console.log(summary);
420
+
421
+ if (outputFile) {
422
+ const fs = await import("fs");
423
+ fs.writeFileSync(outputFile, summary, "utf-8");
424
+ console.log(`\nšŸ“„ Report written to ${outputFile}`);
425
+ }
426
+ }
427
+
428
+
429
+ function generateReport(
430
+ results: ModelComparison[],
431
+ checkedProviders: Set<string>,
432
+ totalDevModels: number
433
+ ): string {
434
+ const lines: string[] = [];
435
+ lines.push("# models.dev Pricing Comparison Report");
436
+ lines.push(`\n> Generated: ${new Date().toISOString()}`);
437
+ lines.push(`> models.dev total models fetched: ${totalDevModels}`);
438
+ lines.push(`> Checked providers: ${[...checkedProviders].sort().join(", ")}`);
439
+ lines.push("");
440
+
441
+ // Summary counts
442
+ const byStatus = new Map<string, ModelComparison[]>();
443
+ for (const r of results) {
444
+ const list = byStatus.get(r.status) || [];
445
+ list.push(r);
446
+ byStatus.set(r.status, list);
447
+ }
448
+
449
+ lines.push("## Summary");
450
+ lines.push("");
451
+ lines.push("| Status | Count |");
452
+ lines.push("|--------|-------|");
453
+ const statusLabels: Record<string, string> = {
454
+ ok: "āœ… OK (registered & priced correctly)",
455
+ "missing-in-knowhow": "āŒ Missing in knowhow (not registered)",
456
+ "missing-pricing": "āš ļø Missing pricing data",
457
+ "price-mismatch": "šŸ”“ Price mismatch",
458
+ "extra-in-knowhow": "ā„¹ļø In knowhow but not on models.dev",
459
+ };
460
+ for (const [status, label] of Object.entries(statusLabels)) {
461
+ const count = byStatus.get(status)?.length || 0;
462
+ if (count > 0) {
463
+ lines.push(`| ${label} | ${count} |`);
464
+ }
465
+ }
466
+ lines.push("");
467
+
468
+ // Detailed sections
469
+ const orderedStatuses = [
470
+ "price-mismatch",
471
+ "missing-pricing",
472
+ "missing-in-knowhow",
473
+ "extra-in-knowhow",
474
+ "ok",
475
+ ];
476
+
477
+ for (const status of orderedStatuses) {
478
+ const items = byStatus.get(status);
479
+ if (!items || items.length === 0) continue;
480
+
481
+ const label = statusLabels[status] || status;
482
+ lines.push(`## ${label}`);
483
+ lines.push("");
484
+ lines.push(
485
+ "| Provider | Model ID | models.dev Input | models.dev Output | knowhow Input | knowhow Output | Details |"
486
+ );
487
+ lines.push(
488
+ "|----------|----------|-----------------|------------------|--------------|---------------|---------|"
489
+ );
490
+
491
+ for (const r of items.sort((a, b) =>
492
+ `${a.provider}/${a.modelId}`.localeCompare(`${b.provider}/${b.modelId}`)
493
+ )) {
494
+ const fmt = (v: number | null) =>
495
+ v === null ? "—" : v === 0 ? "**FREE**" : `$${v.toFixed(3)}`;
496
+ lines.push(
497
+ `| ${r.provider} | \`${r.modelId}\` | ${fmt(r.devInputPrice)} | ${fmt(r.devOutputPrice)} | ${fmt(r.khInputPrice)} | ${fmt(r.khOutputPrice)} | ${r.details || ""} |`
498
+ );
499
+ }
500
+ lines.push("");
501
+ }
502
+
503
+ return lines.join("\n");
504
+ }
505
+
506
+ main().catch((e) => {
507
+ console.error("Fatal error:", e);
508
+ process.exit(1);
509
+ });