loadout-ai 0.5.9 → 0.6.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.
@@ -0,0 +1,248 @@
1
+ // Sources:
2
+ // Anthropic: https://allaboutclaude.com/models
3
+ // OpenAI: https://benchlm.ai/openai/api-pricing
4
+ // Prices as of August 2026.
5
+ export const MODEL_CATALOG = [
6
+ // --- Anthropic (Claude Code) ---
7
+ { id: "claude-fable-5", provider: "anthropic", name: "Claude Fable 5", tier: "frontier", inputCostPer1M: 10, outputCostPer1M: 50, nativeAgents: ["claude-code"], current: true, generation: "5" },
8
+ { id: "claude-opus-5", provider: "anthropic", name: "Claude Opus 5", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 25, nativeAgents: ["claude-code"], current: true, generation: "5" },
9
+ { id: "claude-sonnet-5", provider: "anthropic", name: "Claude Sonnet 5", tier: "standard", inputCostPer1M: 3, outputCostPer1M: 15, nativeAgents: ["claude-code"], current: true, generation: "5" },
10
+ { id: "claude-opus-4-8", provider: "anthropic", name: "Claude Opus 4.8", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 25, nativeAgents: ["claude-code"], current: false, generation: "4" },
11
+ { id: "claude-opus-4-6", provider: "anthropic", name: "Claude Opus 4.6", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 25, nativeAgents: ["claude-code"], current: false, generation: "4" },
12
+ { id: "claude-sonnet-4-6", provider: "anthropic", name: "Claude Sonnet 4.6", tier: "standard", inputCostPer1M: 3, outputCostPer1M: 15, nativeAgents: ["claude-code"], current: false, generation: "4" },
13
+ { id: "claude-haiku-4-5", provider: "anthropic", name: "Claude Haiku 4.5", tier: "fast", inputCostPer1M: 1, outputCostPer1M: 5, nativeAgents: ["claude-code"], current: true, generation: "4" },
14
+ // --- OpenAI (Codex) — GPT-5.6 family ---
15
+ { id: "gpt-5.6-sol", provider: "openai", name: "GPT-5.6 Sol", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 30, nativeAgents: ["codex"], current: true, generation: "5.6" },
16
+ { id: "gpt-5.6-terra", provider: "openai", name: "GPT-5.6 Terra", tier: "standard", inputCostPer1M: 2, outputCostPer1M: 12, nativeAgents: ["codex"], current: true, generation: "5.6" },
17
+ { id: "gpt-5.6-luna", provider: "openai", name: "GPT-5.6 Luna", tier: "fast", inputCostPer1M: 0.20, outputCostPer1M: 1.20, nativeAgents: ["codex"], current: true, generation: "5.6" },
18
+ // --- OpenAI — GPT-5.5 ---
19
+ { id: "gpt-5.5", provider: "openai", name: "GPT-5.5", tier: "frontier", inputCostPer1M: 5, outputCostPer1M: 30, nativeAgents: ["codex"], current: true, generation: "5.5" },
20
+ // --- OpenAI — GPT-5.4 family ---
21
+ { id: "gpt-5.4", provider: "openai", name: "GPT-5.4", tier: "standard", inputCostPer1M: 2.50, outputCostPer1M: 15, nativeAgents: ["codex"], current: true, generation: "5.4" },
22
+ { id: "gpt-5.4-mini", provider: "openai", name: "GPT-5.4 Mini", tier: "fast", inputCostPer1M: 0.75, outputCostPer1M: 4.50, nativeAgents: ["codex"], current: true, generation: "5.4" },
23
+ // --- OpenAI — Codex-specific ---
24
+ { id: "gpt-5.1-codex", provider: "openai", name: "GPT-5.1 Codex", tier: "standard", inputCostPer1M: 1.25, outputCostPer1M: 10, nativeAgents: ["codex"], current: true, generation: "5.1" },
25
+ // --- OpenAI — o-series reasoning ---
26
+ { id: "o3-pro", provider: "openai", name: "o3-pro", tier: "frontier", inputCostPer1M: 20, outputCostPer1M: 80, nativeAgents: ["codex"], current: true, generation: "o3" },
27
+ { id: "o3", provider: "openai", name: "o3", tier: "frontier", inputCostPer1M: 2, outputCostPer1M: 8, nativeAgents: ["codex"], current: true, generation: "o3" },
28
+ { id: "o4-mini", provider: "openai", name: "o4-mini", tier: "standard", inputCostPer1M: 1.10, outputCostPer1M: 4.40, nativeAgents: ["codex"], current: true, generation: "o4" },
29
+ { id: "o3-mini", provider: "openai", name: "o3-mini", tier: "standard", inputCostPer1M: 1.10, outputCostPer1M: 4.40, nativeAgents: ["codex"], current: true, generation: "o3" },
30
+ ];
31
+ const TIER_LABELS = {
32
+ frontier: "Frontier (deep reasoning)",
33
+ standard: "Standard (balanced)",
34
+ fast: "Fast (high throughput)",
35
+ };
36
+ const PHASE_CONFIG = {
37
+ plan: {
38
+ tier: "frontier",
39
+ reason: "Architecture and decomposition need deep reasoning to avoid costly rework",
40
+ agents: ["claude-code"],
41
+ conserveTier: "standard",
42
+ conserveTradeoff: "Slightly shallower architectural reasoning; review the plan more carefully",
43
+ },
44
+ implement: {
45
+ tier: "standard",
46
+ reason: "Implementation is high-volume; standard models score within 5% of frontier on SWE-bench",
47
+ agents: ["claude-code", "codex"],
48
+ conserveTier: "fast",
49
+ conserveTradeoff: "May need more iterations on complex logic; fine for CRUD and boilerplate",
50
+ },
51
+ review: {
52
+ tier: "frontier",
53
+ reason: "Code review catches subtle bugs that cheaper models miss; worth the cost per-review",
54
+ agents: ["claude-code", "codex"],
55
+ conserveTier: "standard",
56
+ conserveTradeoff: "May miss edge-case bugs; pair with a linter",
57
+ },
58
+ test: {
59
+ tier: "fast",
60
+ reason: "Test generation is pattern-heavy; fast models produce equivalent coverage",
61
+ agents: ["claude-code", "codex"],
62
+ },
63
+ debug: {
64
+ tier: "standard",
65
+ reason: "Targeted debugging needs good reasoning but not frontier-level; standard balances cost and quality",
66
+ agents: ["claude-code", "codex"],
67
+ conserveTier: "fast",
68
+ conserveTradeoff: "Works for simple bugs; complex root-cause analysis may suffer",
69
+ },
70
+ document: {
71
+ tier: "fast",
72
+ reason: "Documentation and comments are well-suited to fast models; any decent model writes docs",
73
+ agents: ["claude-code", "codex"],
74
+ },
75
+ };
76
+ const PHASE_KEYWORDS = {
77
+ plan: ["plan", "design", "architect", "architecture", "rfc", "spec", "decompose", "strategy", "system design"],
78
+ implement: ["implement", "build", "code", "create", "add", "feature", "write", "develop", "make"],
79
+ review: ["review", "audit", "check", "inspect", "critique", "pr", "pull request", "diff"],
80
+ test: ["test", "spec", "coverage", "unit", "integration", "e2e", "assert", "vitest", "jest"],
81
+ debug: ["debug", "fix", "bug", "error", "crash", "broken", "investigate", "troubleshoot", "failing"],
82
+ document: ["document", "docs", "readme", "comment", "jsdoc", "changelog", "explain", "docstring"],
83
+ };
84
+ // ---------------------------------------------------------------------------
85
+ // Classification
86
+ // ---------------------------------------------------------------------------
87
+ export function classifyTask(description) {
88
+ const lower = description.toLowerCase();
89
+ let best = "implement";
90
+ let bestScore = 0;
91
+ for (const [phase, keywords] of Object.entries(PHASE_KEYWORDS)) {
92
+ let score = 0;
93
+ for (const kw of keywords) {
94
+ if (lower.includes(kw))
95
+ score++;
96
+ }
97
+ if (score > bestScore) {
98
+ bestScore = score;
99
+ best = phase;
100
+ }
101
+ }
102
+ return best;
103
+ }
104
+ // ---------------------------------------------------------------------------
105
+ // Model queries
106
+ // ---------------------------------------------------------------------------
107
+ export function modelsForTier(tier, currentOnly = true) {
108
+ return MODEL_CATALOG.filter((m) => m.tier === tier && (!currentOnly || m.current));
109
+ }
110
+ export function modelsByProvider(provider) {
111
+ return MODEL_CATALOG.filter((m) => m.provider === provider && m.current);
112
+ }
113
+ export function cheapestInTier(tier) {
114
+ const models = modelsForTier(tier);
115
+ return models.sort((a, b) => a.inputCostPer1M - b.inputCostPer1M)[0];
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Routing
119
+ // ---------------------------------------------------------------------------
120
+ export function routeTask(description, conserve = false) {
121
+ return routePhase(classifyTask(description), conserve);
122
+ }
123
+ export function routePhase(phase, conserve = false) {
124
+ const config = PHASE_CONFIG[phase];
125
+ if (!config)
126
+ throw new Error(`Unknown phase '${phase}'. Valid: ${Object.keys(PHASE_CONFIG).join(", ")}`);
127
+ const effectiveTier = conserve && config.conserveTier ? config.conserveTier : config.tier;
128
+ const models = modelsForTier(effectiveTier);
129
+ const rec = {
130
+ phase: phase,
131
+ tier: effectiveTier,
132
+ tierLabel: TIER_LABELS[effectiveTier],
133
+ models,
134
+ reason: conserve && config.conserveTier
135
+ ? `Conserve mode: ${config.conserveTradeoff}`
136
+ : config.reason,
137
+ suggestedAgents: config.agents,
138
+ };
139
+ if (!conserve && config.conserveTier) {
140
+ rec.conserveAlternative = {
141
+ tier: config.conserveTier,
142
+ models: modelsForTier(config.conserveTier),
143
+ tradeoff: config.conserveTradeoff,
144
+ };
145
+ }
146
+ return rec;
147
+ }
148
+ export function allPhaseRoutes(conserve = false) {
149
+ return Object.keys(PHASE_CONFIG).map((p) => routePhase(p, conserve));
150
+ }
151
+ export function estimateCostSavings() {
152
+ const normal = allPhaseRoutes(false);
153
+ const conserved = allPhaseRoutes(true);
154
+ const estimates = normal.map((r) => {
155
+ const cheapest = cheapestInTier(r.tier);
156
+ return {
157
+ phase: r.phase,
158
+ tier: r.tier,
159
+ cheapestModel: cheapest?.name ?? "—",
160
+ inputCost: cheapest?.inputCostPer1M ?? 0,
161
+ outputCost: cheapest?.outputCostPer1M ?? 0,
162
+ };
163
+ });
164
+ const normalTotal = estimates.reduce((s, e) => s + e.inputCost, 0);
165
+ const conserveEstimates = conserved.map((r) => cheapestInTier(r.tier)?.inputCostPer1M ?? 0);
166
+ const conserveTotal = conserveEstimates.reduce((s, c) => s + c, 0);
167
+ return { estimates, normalTotal, conserveTotal };
168
+ }
169
+ // ---------------------------------------------------------------------------
170
+ // Formatters
171
+ // ---------------------------------------------------------------------------
172
+ export function formatRouteRecommendation(rec) {
173
+ const modelNames = rec.models.slice(0, 4).map((m) => `${m.name} ($${m.inputCostPer1M}/$${m.outputCostPer1M})`);
174
+ const lines = [
175
+ `Phase: ${rec.phase}`,
176
+ `Tier: ${rec.tierLabel}`,
177
+ `Models: ${modelNames.join("\n ")}`,
178
+ `Agents: ${rec.suggestedAgents.join(", ")}`,
179
+ `Why: ${rec.reason}`,
180
+ ];
181
+ if (rec.conserveAlternative) {
182
+ const alt = rec.conserveAlternative;
183
+ const altCheapest = cheapestInTier(alt.tier);
184
+ lines.push(``, `Conserve: drop to ${alt.tier} tier (${altCheapest?.name ?? "—"} at $${altCheapest?.inputCostPer1M ?? "?"}/$${altCheapest?.outputCostPer1M ?? "?"})`, ` ${alt.tradeoff}`);
185
+ }
186
+ return lines.join("\n");
187
+ }
188
+ export function formatRoutingTable(conserve = false) {
189
+ const routes = allPhaseRoutes(conserve);
190
+ const header = `Phase Tier Cheapest model $/M in $/M out Agents`;
191
+ const divider = "─".repeat(header.length);
192
+ const rows = routes.map((r) => {
193
+ const cheapest = cheapestInTier(r.tier);
194
+ const phase = r.phase.padEnd(12);
195
+ const tier = r.tier.padEnd(11);
196
+ const model = (cheapest?.name ?? "—").padEnd(27);
197
+ const inCost = `$${(cheapest?.inputCostPer1M ?? 0).toFixed(2)}`.padEnd(8);
198
+ const outCost = `$${(cheapest?.outputCostPer1M ?? 0).toFixed(2)}`.padEnd(8);
199
+ const agents = r.suggestedAgents.slice(0, 3).join(", ");
200
+ return `${phase} ${tier} ${model} ${inCost} ${outCost} ${agents}`;
201
+ });
202
+ return [
203
+ conserve ? "ROUTING TABLE (conserve mode — usage-saving tier for each phase)" : "ROUTING TABLE",
204
+ "", header, divider, ...rows,
205
+ ].join("\n");
206
+ }
207
+ export function formatCostTable() {
208
+ const { estimates, normalTotal, conserveTotal } = estimateCostSavings();
209
+ const header = "Phase Tier Cheapest model $/M input";
210
+ const divider = "─".repeat(header.length);
211
+ const rows = estimates.map((e) => {
212
+ const phase = e.phase.padEnd(12);
213
+ const tier = e.tier.padEnd(11);
214
+ const model = e.cheapestModel.padEnd(27);
215
+ return `${phase} ${tier} ${model} $${e.inputCost.toFixed(2)}`;
216
+ });
217
+ const savings = Math.round((1 - conserveTotal / normalTotal) * 100);
218
+ return [
219
+ header, divider, ...rows, divider,
220
+ `Normal total: $${normalTotal.toFixed(2)}/M input across 6 phases`,
221
+ `Conserve total: $${conserveTotal.toFixed(2)}/M input (${savings}% cheaper)`,
222
+ "",
223
+ "Prices are per-million-token list rates. Actual cost depends on prompt length.",
224
+ "Use --conserve when you want to stretch remaining session quota.",
225
+ ].join("\n");
226
+ }
227
+ export function formatModelCatalog(filter) {
228
+ let models = MODEL_CATALOG;
229
+ if (filter?.provider)
230
+ models = models.filter((m) => m.provider === filter.provider);
231
+ if (filter?.tier)
232
+ models = models.filter((m) => m.tier === filter.tier);
233
+ if (filter?.current !== undefined)
234
+ models = models.filter((m) => m.current === filter.current);
235
+ const header = "Model Provider Tier $/M in $/M out Gen Agents";
236
+ const divider = "─".repeat(header.length);
237
+ const rows = models.map((m) => {
238
+ const name = m.name.padEnd(28);
239
+ const provider = m.provider.padEnd(11);
240
+ const tier = m.tier.padEnd(11);
241
+ const inCost = `$${m.inputCostPer1M.toFixed(2)}`.padEnd(8);
242
+ const outCost = `$${m.outputCostPer1M.toFixed(2)}`.padEnd(8);
243
+ const gen = m.generation.padEnd(6);
244
+ const agents = m.nativeAgents.slice(0, 3).join(", ");
245
+ return `${name} ${provider} ${tier} ${inCost} ${outCost} ${gen} ${agents}`;
246
+ });
247
+ return [`${models.length} models${filter?.current === false ? " (including legacy)" : ""}`, "", header, divider, ...rows].join("\n");
248
+ }
package/docs/CATALOG.md CHANGED
@@ -27,10 +27,10 @@ The machine-readable catalog remains the source of truth. Run `loadout catalog -
27
27
  | [Anthropic Skills](https://github.com/anthropics/skills) | `anthropic-skills` | agent-skills | `skill` | **Review required** | [`9d2f1ae18723`](https://github.com/anthropics/skills/tree/9d2f1ae187231d8199c64b5b762e1bdf2244733d) |
28
28
  | [Agent Skills Marketplace](https://github.com/wshobson/agents) | `wshobson-agents` | agent-skills | `skill`, `plugin` | `MIT` | [`b6af37110581`](https://github.com/wshobson/agents/tree/b6af3711058190e4b5c5274b9758498fe626ec5a) |
29
29
  | [Vercel Agent Skills](https://github.com/vercel-labs/agent-skills) | `vercel-agent-skills` | frontend-development | `skill` | **Review required** | [`f8a72b960372`](https://github.com/vercel-labs/agent-skills/tree/f8a72b9603728bb92a217a879b7e62e43ad76c81) |
30
- | [Vercel Skills](https://github.com/vercel-labs/skills) | `vercel-skills` | skill-discovery | `skill` | **Review required** | [`5527c09adc36`](https://github.com/vercel-labs/skills/tree/5527c09adc367612b0bffd9c80e3bc28a6b01b6d) |
30
+ | [Vercel Skills](https://github.com/vercel-labs/skills) | `vercel-skills` | skill-discovery | `skill` | `MIT` | [`5527c09adc36`](https://github.com/vercel-labs/skills/tree/5527c09adc367612b0bffd9c80e3bc28a6b01b6d) |
31
31
  | [Cloudflare MCP Server](https://github.com/cloudflare/mcp-server-cloudflare) | `cloudflare-mcp-server` | cloud-platform | `mcp` | `Apache-2.0` | [`52c633e37684`](https://github.com/cloudflare/mcp-server-cloudflare/tree/52c633e37684fadb94ae236f74909b9bbefc0db8) |
32
32
  | [Supabase MCP](https://github.com/supabase/mcp) | `supabase-mcp` | database | `mcp` | `Apache-2.0` | [`9f0396a1b367`](https://github.com/supabase/mcp/tree/9f0396a1b367dfa6a424f4686a21701c8f05b1c3) |
33
- | [Sentry MCP](https://github.com/getsentry/sentry-mcp) | `sentry-mcp` | observability | `skill`, `mcp` | **Review required** | [`ce099fd25197`](https://github.com/getsentry/sentry-mcp/tree/ce099fd251973774116749016847b78a6049ba0a) |
33
+ | [Sentry MCP](https://github.com/getsentry/sentry-mcp) | `sentry-mcp` | observability | `skill`, `mcp` | `FSL-1.1-ALv2` | [`ce099fd25197`](https://github.com/getsentry/sentry-mcp/tree/ce099fd251973774116749016847b78a6049ba0a) |
34
34
  | [Exa MCP Server](https://github.com/exa-labs/exa-mcp-server) | `exa-mcp-server` | web-research | `skill`, `mcp`, `plugin` | `MIT` | [`8823cbea80b6`](https://github.com/exa-labs/exa-mcp-server/tree/8823cbea80b6ec5999c6217cccd91660fe953b97) |
35
35
  | [Firecrawl MCP Server](https://github.com/firecrawl/firecrawl-mcp-server) | `firecrawl-mcp-server` | web-research | `mcp` | `MIT` | [`3eb1115b1f28`](https://github.com/firecrawl/firecrawl-mcp-server/tree/3eb1115b1f2883ff2fb74e61b5c4acf5a9ac0fb0) |
36
36
  | [Azure DevOps MCP](https://github.com/microsoft/azure-devops-mcp) | `azure-devops-mcp` | devops | `mcp` | `MIT` | [`0a08dc649c95`](https://github.com/microsoft/azure-devops-mcp/tree/0a08dc649c951208eda942154c66187dec9985bd) |