loadout-ai 0.5.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +181 -121
- package/catalog/discovered.json +28066 -23196
- package/catalog/packages.json +2 -2
- package/dist/src/cli.js +2 -2
- package/dist/src/commands/agents.js +6 -23
- package/dist/src/commands/catalog.js +254 -27
- package/dist/src/commands/health.js +6 -6
- package/dist/src/commands/inventory.js +21 -121
- package/dist/src/commands/lifecycle.js +13 -51
- package/dist/src/commands/mcp.js +10 -73
- package/dist/src/commands/setup.js +5 -5
- package/dist/src/commands/sharing.js +11 -108
- package/dist/src/commands/support.js +42 -11
- package/dist/src/core/{adapters.js → agents/adapters.js} +2 -2
- package/dist/src/core/{agent-inspection.js → agents/agent-inspection.js} +26 -6
- package/dist/src/core/{codex-mcp.js → agents/codex-mcp.js} +1 -1
- package/dist/src/core/{health-score-evidence.js → agents/health-score-evidence.js} +8 -8
- package/dist/src/core/{runtime-tools.js → agents/runtime-tools.js} +2 -2
- package/dist/src/core/{audit.js → catalog/audit.js} +4 -4
- package/dist/src/core/{catalog.js → catalog/catalog.js} +18 -5
- package/dist/src/core/{components.js → catalog/components.js} +2 -2
- package/dist/src/core/{conformance.js → catalog/conformance.js} +2 -2
- package/dist/src/core/{profiles.js → catalog/profiles.js} +2 -2
- package/dist/src/core/{provenance.js → catalog/provenance.js} +3 -3
- package/dist/src/core/{registry.js → catalog/registry.js} +1 -1
- package/dist/src/core/{skill-compare.js → catalog/skill-compare.js} +1 -1
- package/dist/src/core/{skill-inventory.js → catalog/skill-inventory.js} +3 -3
- package/dist/src/core/{skills.js → catalog/skills.js} +1 -1
- package/dist/src/core/{candidate-intelligence.js → discovery/candidate-intelligence.js} +6 -6
- package/dist/src/core/{discovery-connector.js → discovery/discovery-connector.js} +2 -2
- package/dist/src/core/{evaluate.js → discovery/evaluate.js} +2 -2
- package/dist/src/core/{mcp-registry-discovery.js → discovery/mcp-registry-discovery.js} +1 -1
- package/dist/src/core/{observations.js → discovery/observations.js} +3 -3
- package/dist/src/core/{review-queue.js → discovery/review-queue.js} +9 -4
- package/dist/src/core/{skills-sh-discovery.js → discovery/skills-sh-discovery.js} +1 -1
- package/dist/src/core/{adopt.js → install/adopt.js} +3 -3
- package/dist/src/core/{catalog-install.js → install/catalog-install.js} +11 -11
- package/dist/src/core/{file-lock.js → install/file-lock.js} +1 -1
- package/dist/src/core/{install.js → install/install.js} +5 -5
- package/dist/src/core/{package.js → install/package.js} +3 -3
- package/dist/src/core/{reconcile.js → install/reconcile.js} +5 -5
- package/dist/src/core/{remove.js → install/remove.js} +4 -4
- package/dist/src/core/{snapshot.js → install/snapshot.js} +1 -1
- package/dist/src/core/{source.js → install/source.js} +1 -1
- package/dist/src/core/{sync.js → install/sync.js} +9 -9
- package/dist/src/core/{transaction.js → install/transaction.js} +1 -1
- package/dist/src/core/{uninstall.js → install/uninstall.js} +4 -4
- package/dist/src/core/{update.js → install/update.js} +4 -4
- package/dist/src/core/reporting/cli-guide.js +103 -0
- package/dist/src/core/reporting/doctor.js +141 -0
- package/dist/src/core/{freshness-alerts.js → reporting/freshness-alerts.js} +5 -5
- package/dist/src/core/{health.js → reporting/health.js} +6 -6
- package/dist/src/core/{loadout-card.js → reporting/loadout-card.js} +1 -1
- package/dist/src/core/{readme-claims.js → reporting/readme-claims.js} +1 -1
- package/dist/src/core/{readme-facts.js → reporting/readme-facts.js} +2 -2
- package/dist/src/core/{share-report.js → reporting/share-report.js} +3 -3
- package/dist/src/core/{upgrade.js → reporting/upgrade.js} +7 -7
- package/dist/src/core/routing/first-party-skills.js +118 -0
- package/dist/src/core/routing/handoff.js +278 -0
- package/dist/src/core/{model-config.js → routing/model-config.js} +4 -4
- package/dist/src/core/routing/route.js +539 -0
- package/dist/src/core/{api.js → runtime/api.js} +4 -4
- package/dist/src/core/{canary.js → runtime/canary.js} +1 -1
- package/dist/src/core/{github.js → runtime/github.js} +1 -1
- package/dist/src/core/{mcp-recipes.js → runtime/mcp-recipes.js} +1 -1
- package/dist/src/core/{mcp.js → runtime/mcp.js} +2 -2
- package/dist/src/core/{scheduler.js → runtime/scheduler.js} +4 -4
- package/dist/src/core/{active-policy.js → workspace/active-policy.js} +2 -2
- package/dist/src/core/{active-set.js → workspace/active-set.js} +2 -2
- package/dist/src/core/{improve.js → workspace/improve.js} +3 -3
- package/dist/src/core/{manifest.js → workspace/manifest.js} +2 -2
- package/dist/src/core/{outcomes.js → workspace/outcomes.js} +3 -3
- package/dist/src/core/{portable.js → workspace/portable.js} +3 -3
- package/dist/src/core/{profile-state.js → workspace/profile-state.js} +1 -1
- package/dist/src/core/{recommend.js → workspace/recommend.js} +1 -1
- package/dist/src/core/{state.js → workspace/state.js} +3 -3
- package/docs/CATALOG.md +2 -2
- package/docs/DISCOVERED.md +249 -247
- package/docs/UPSTREAM_LICENSE_DECISIONS.md +19 -12
- package/docs/evidence/readme-claims.json +22 -19
- package/package.json +2 -1
- package/skills/loadout-curator/SKILL.md +105 -0
- package/skills/loadout-router/SKILL.md +120 -0
- package/dist/src/core/cli-guide.js +0 -118
- package/dist/src/core/doctor.js +0 -95
- /package/dist/src/core/{agent-health-score.js → agents/agent-health-score.js} +0 -0
- /package/dist/src/core/{agent-versions.js → agents/agent-versions.js} +0 -0
- /package/dist/src/core/{conversion.js → agents/conversion.js} +0 -0
- /package/dist/src/core/{paths.js → agents/paths.js} +0 -0
- /package/dist/src/core/{runtime-tool-recipe.js → agents/runtime-tool-recipe.js} +0 -0
- /package/dist/src/core/{catalog-coverage.js → catalog/catalog-coverage.js} +0 -0
- /package/dist/src/core/{safety.js → catalog/safety.js} +0 -0
- /package/dist/src/core/{skill-security.js → catalog/skill-security.js} +0 -0
- /package/dist/src/core/{community.js → discovery/community.js} +0 -0
- /package/dist/src/core/{github-discovery.js → discovery/github-discovery.js} +0 -0
- /package/dist/src/core/{private-discovery.js → discovery/private-discovery.js} +0 -0
- /package/dist/src/core/{ranking.js → discovery/ranking.js} +0 -0
- /package/dist/src/core/{atomic-file.js → install/atomic-file.js} +0 -0
- /package/dist/src/core/{diff.js → install/diff.js} +0 -0
- /package/dist/src/core/{update-watch.js → install/update-watch.js} +0 -0
- /package/dist/src/core/{completion.js → reporting/completion.js} +0 -0
- /package/dist/src/core/{loadout-badge.js → reporting/loadout-badge.js} +0 -0
- /package/dist/src/core/{terminal.js → reporting/terminal.js} +0 -0
- /package/dist/src/core/{access.js → routing/access.js} +0 -0
- /package/dist/src/core/{credentials.js → routing/credentials.js} +0 -0
- /package/dist/src/core/{sandbox.js → runtime/sandbox.js} +0 -0
- /package/dist/src/core/{active-limit.js → workspace/active-limit.js} +0 -0
- /package/dist/src/core/{target-occupancy.js → workspace/target-occupancy.js} +0 -0
|
@@ -0,0 +1,539 @@
|
|
|
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
|
+
{
|
|
8
|
+
id: "claude-fable-5",
|
|
9
|
+
provider: "anthropic",
|
|
10
|
+
name: "Claude Fable 5",
|
|
11
|
+
tier: "frontier",
|
|
12
|
+
inputCostPer1M: 10,
|
|
13
|
+
outputCostPer1M: 50,
|
|
14
|
+
nativeAgents: ["claude-code"],
|
|
15
|
+
current: true,
|
|
16
|
+
generation: "5",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "claude-opus-5",
|
|
20
|
+
provider: "anthropic",
|
|
21
|
+
name: "Claude Opus 5",
|
|
22
|
+
tier: "frontier",
|
|
23
|
+
inputCostPer1M: 5,
|
|
24
|
+
outputCostPer1M: 25,
|
|
25
|
+
nativeAgents: ["claude-code"],
|
|
26
|
+
current: true,
|
|
27
|
+
generation: "5",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "claude-sonnet-5",
|
|
31
|
+
provider: "anthropic",
|
|
32
|
+
name: "Claude Sonnet 5",
|
|
33
|
+
tier: "standard",
|
|
34
|
+
inputCostPer1M: 3,
|
|
35
|
+
outputCostPer1M: 15,
|
|
36
|
+
nativeAgents: ["claude-code"],
|
|
37
|
+
current: true,
|
|
38
|
+
generation: "5",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "claude-opus-4-8",
|
|
42
|
+
provider: "anthropic",
|
|
43
|
+
name: "Claude Opus 4.8",
|
|
44
|
+
tier: "frontier",
|
|
45
|
+
inputCostPer1M: 5,
|
|
46
|
+
outputCostPer1M: 25,
|
|
47
|
+
nativeAgents: ["claude-code"],
|
|
48
|
+
current: false,
|
|
49
|
+
generation: "4",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "claude-opus-4-6",
|
|
53
|
+
provider: "anthropic",
|
|
54
|
+
name: "Claude Opus 4.6",
|
|
55
|
+
tier: "frontier",
|
|
56
|
+
inputCostPer1M: 5,
|
|
57
|
+
outputCostPer1M: 25,
|
|
58
|
+
nativeAgents: ["claude-code"],
|
|
59
|
+
current: false,
|
|
60
|
+
generation: "4",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "claude-sonnet-4-6",
|
|
64
|
+
provider: "anthropic",
|
|
65
|
+
name: "Claude Sonnet 4.6",
|
|
66
|
+
tier: "standard",
|
|
67
|
+
inputCostPer1M: 3,
|
|
68
|
+
outputCostPer1M: 15,
|
|
69
|
+
nativeAgents: ["claude-code"],
|
|
70
|
+
current: false,
|
|
71
|
+
generation: "4",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "claude-haiku-4-5",
|
|
75
|
+
provider: "anthropic",
|
|
76
|
+
name: "Claude Haiku 4.5",
|
|
77
|
+
tier: "fast",
|
|
78
|
+
inputCostPer1M: 1,
|
|
79
|
+
outputCostPer1M: 5,
|
|
80
|
+
nativeAgents: ["claude-code"],
|
|
81
|
+
current: true,
|
|
82
|
+
generation: "4",
|
|
83
|
+
},
|
|
84
|
+
// --- OpenAI (Codex) — GPT-5.6 family ---
|
|
85
|
+
{
|
|
86
|
+
id: "gpt-5.6-sol",
|
|
87
|
+
provider: "openai",
|
|
88
|
+
name: "GPT-5.6 Sol",
|
|
89
|
+
tier: "frontier",
|
|
90
|
+
inputCostPer1M: 5,
|
|
91
|
+
outputCostPer1M: 30,
|
|
92
|
+
nativeAgents: ["codex"],
|
|
93
|
+
current: true,
|
|
94
|
+
generation: "5.6",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "gpt-5.6-terra",
|
|
98
|
+
provider: "openai",
|
|
99
|
+
name: "GPT-5.6 Terra",
|
|
100
|
+
tier: "standard",
|
|
101
|
+
inputCostPer1M: 2,
|
|
102
|
+
outputCostPer1M: 12,
|
|
103
|
+
nativeAgents: ["codex"],
|
|
104
|
+
current: true,
|
|
105
|
+
generation: "5.6",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "gpt-5.6-luna",
|
|
109
|
+
provider: "openai",
|
|
110
|
+
name: "GPT-5.6 Luna",
|
|
111
|
+
tier: "fast",
|
|
112
|
+
inputCostPer1M: 0.2,
|
|
113
|
+
outputCostPer1M: 1.2,
|
|
114
|
+
nativeAgents: ["codex"],
|
|
115
|
+
current: true,
|
|
116
|
+
generation: "5.6",
|
|
117
|
+
},
|
|
118
|
+
// --- OpenAI — GPT-5.5 ---
|
|
119
|
+
{
|
|
120
|
+
id: "gpt-5.5",
|
|
121
|
+
provider: "openai",
|
|
122
|
+
name: "GPT-5.5",
|
|
123
|
+
tier: "frontier",
|
|
124
|
+
inputCostPer1M: 5,
|
|
125
|
+
outputCostPer1M: 30,
|
|
126
|
+
nativeAgents: ["codex"],
|
|
127
|
+
current: true,
|
|
128
|
+
generation: "5.5",
|
|
129
|
+
},
|
|
130
|
+
// --- OpenAI — GPT-5.4 family ---
|
|
131
|
+
{
|
|
132
|
+
id: "gpt-5.4",
|
|
133
|
+
provider: "openai",
|
|
134
|
+
name: "GPT-5.4",
|
|
135
|
+
tier: "standard",
|
|
136
|
+
inputCostPer1M: 2.5,
|
|
137
|
+
outputCostPer1M: 15,
|
|
138
|
+
nativeAgents: ["codex"],
|
|
139
|
+
current: true,
|
|
140
|
+
generation: "5.4",
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "gpt-5.4-mini",
|
|
144
|
+
provider: "openai",
|
|
145
|
+
name: "GPT-5.4 Mini",
|
|
146
|
+
tier: "fast",
|
|
147
|
+
inputCostPer1M: 0.75,
|
|
148
|
+
outputCostPer1M: 4.5,
|
|
149
|
+
nativeAgents: ["codex"],
|
|
150
|
+
current: true,
|
|
151
|
+
generation: "5.4",
|
|
152
|
+
},
|
|
153
|
+
// --- OpenAI — Codex-specific ---
|
|
154
|
+
{
|
|
155
|
+
id: "gpt-5.1-codex",
|
|
156
|
+
provider: "openai",
|
|
157
|
+
name: "GPT-5.1 Codex",
|
|
158
|
+
tier: "standard",
|
|
159
|
+
inputCostPer1M: 1.25,
|
|
160
|
+
outputCostPer1M: 10,
|
|
161
|
+
nativeAgents: ["codex"],
|
|
162
|
+
current: true,
|
|
163
|
+
generation: "5.1",
|
|
164
|
+
},
|
|
165
|
+
// --- OpenAI — o-series reasoning ---
|
|
166
|
+
{
|
|
167
|
+
id: "o3-pro",
|
|
168
|
+
provider: "openai",
|
|
169
|
+
name: "o3-pro",
|
|
170
|
+
tier: "frontier",
|
|
171
|
+
inputCostPer1M: 20,
|
|
172
|
+
outputCostPer1M: 80,
|
|
173
|
+
nativeAgents: ["codex"],
|
|
174
|
+
current: true,
|
|
175
|
+
generation: "o3",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "o3",
|
|
179
|
+
provider: "openai",
|
|
180
|
+
name: "o3",
|
|
181
|
+
tier: "frontier",
|
|
182
|
+
inputCostPer1M: 2,
|
|
183
|
+
outputCostPer1M: 8,
|
|
184
|
+
nativeAgents: ["codex"],
|
|
185
|
+
current: true,
|
|
186
|
+
generation: "o3",
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: "o4-mini",
|
|
190
|
+
provider: "openai",
|
|
191
|
+
name: "o4-mini",
|
|
192
|
+
tier: "standard",
|
|
193
|
+
inputCostPer1M: 1.1,
|
|
194
|
+
outputCostPer1M: 4.4,
|
|
195
|
+
nativeAgents: ["codex"],
|
|
196
|
+
current: true,
|
|
197
|
+
generation: "o4",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
id: "o3-mini",
|
|
201
|
+
provider: "openai",
|
|
202
|
+
name: "o3-mini",
|
|
203
|
+
tier: "standard",
|
|
204
|
+
inputCostPer1M: 1.1,
|
|
205
|
+
outputCostPer1M: 4.4,
|
|
206
|
+
nativeAgents: ["codex"],
|
|
207
|
+
current: true,
|
|
208
|
+
generation: "o3",
|
|
209
|
+
},
|
|
210
|
+
];
|
|
211
|
+
const TIER_LABELS = {
|
|
212
|
+
frontier: "Frontier (deep reasoning)",
|
|
213
|
+
standard: "Standard (balanced)",
|
|
214
|
+
fast: "Fast (high throughput)",
|
|
215
|
+
};
|
|
216
|
+
const PHASE_CONFIG = {
|
|
217
|
+
plan: {
|
|
218
|
+
tier: "frontier",
|
|
219
|
+
reason: "Architecture and decomposition need deep reasoning to avoid costly rework",
|
|
220
|
+
agents: ["claude-code"],
|
|
221
|
+
conserveTier: "standard",
|
|
222
|
+
conserveTradeoff: "Slightly shallower architectural reasoning; review the plan more carefully",
|
|
223
|
+
},
|
|
224
|
+
implement: {
|
|
225
|
+
tier: "standard",
|
|
226
|
+
reason: "Implementation is high-volume; standard models score within 5% of frontier on SWE-bench",
|
|
227
|
+
agents: ["claude-code", "codex"],
|
|
228
|
+
conserveTier: "fast",
|
|
229
|
+
conserveTradeoff: "May need more iterations on complex logic; fine for CRUD and boilerplate",
|
|
230
|
+
},
|
|
231
|
+
review: {
|
|
232
|
+
tier: "frontier",
|
|
233
|
+
reason: "Code review catches subtle bugs that cheaper models miss; worth the cost per-review",
|
|
234
|
+
agents: ["claude-code", "codex"],
|
|
235
|
+
conserveTier: "standard",
|
|
236
|
+
conserveTradeoff: "May miss edge-case bugs; pair with a linter",
|
|
237
|
+
},
|
|
238
|
+
test: {
|
|
239
|
+
tier: "fast",
|
|
240
|
+
reason: "Test generation is pattern-heavy; fast models produce equivalent coverage",
|
|
241
|
+
agents: ["claude-code", "codex"],
|
|
242
|
+
},
|
|
243
|
+
debug: {
|
|
244
|
+
tier: "standard",
|
|
245
|
+
reason: "Targeted debugging needs good reasoning but not frontier-level; standard balances cost and quality",
|
|
246
|
+
agents: ["claude-code", "codex"],
|
|
247
|
+
conserveTier: "fast",
|
|
248
|
+
conserveTradeoff: "Works for simple bugs; complex root-cause analysis may suffer",
|
|
249
|
+
},
|
|
250
|
+
document: {
|
|
251
|
+
tier: "fast",
|
|
252
|
+
reason: "Documentation and comments are well-suited to fast models; any decent model writes docs",
|
|
253
|
+
agents: ["claude-code", "codex"],
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
const PHASE_KEYWORDS = {
|
|
257
|
+
plan: [
|
|
258
|
+
"plan",
|
|
259
|
+
"design",
|
|
260
|
+
"architect",
|
|
261
|
+
"architecture",
|
|
262
|
+
"rfc",
|
|
263
|
+
"spec",
|
|
264
|
+
"decompose",
|
|
265
|
+
"strategy",
|
|
266
|
+
"system design",
|
|
267
|
+
],
|
|
268
|
+
implement: [
|
|
269
|
+
"implement",
|
|
270
|
+
"build",
|
|
271
|
+
"code",
|
|
272
|
+
"create",
|
|
273
|
+
"add",
|
|
274
|
+
"feature",
|
|
275
|
+
"write",
|
|
276
|
+
"develop",
|
|
277
|
+
"make",
|
|
278
|
+
],
|
|
279
|
+
review: [
|
|
280
|
+
"review",
|
|
281
|
+
"audit",
|
|
282
|
+
"check",
|
|
283
|
+
"inspect",
|
|
284
|
+
"critique",
|
|
285
|
+
"pr",
|
|
286
|
+
"pull request",
|
|
287
|
+
"diff",
|
|
288
|
+
],
|
|
289
|
+
test: [
|
|
290
|
+
"test",
|
|
291
|
+
"spec",
|
|
292
|
+
"coverage",
|
|
293
|
+
"unit",
|
|
294
|
+
"integration",
|
|
295
|
+
"e2e",
|
|
296
|
+
"assert",
|
|
297
|
+
"vitest",
|
|
298
|
+
"jest",
|
|
299
|
+
],
|
|
300
|
+
debug: [
|
|
301
|
+
"debug",
|
|
302
|
+
"fix",
|
|
303
|
+
"bug",
|
|
304
|
+
"error",
|
|
305
|
+
"crash",
|
|
306
|
+
"broken",
|
|
307
|
+
"investigate",
|
|
308
|
+
"troubleshoot",
|
|
309
|
+
"failing",
|
|
310
|
+
],
|
|
311
|
+
document: [
|
|
312
|
+
"document",
|
|
313
|
+
"docs",
|
|
314
|
+
"readme",
|
|
315
|
+
"comment",
|
|
316
|
+
"jsdoc",
|
|
317
|
+
"changelog",
|
|
318
|
+
"explain",
|
|
319
|
+
"docstring",
|
|
320
|
+
],
|
|
321
|
+
};
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Classification
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
export function classifyTask(description) {
|
|
326
|
+
const lower = description.toLowerCase();
|
|
327
|
+
let best = "implement";
|
|
328
|
+
let bestScore = 0;
|
|
329
|
+
for (const [phase, keywords] of Object.entries(PHASE_KEYWORDS)) {
|
|
330
|
+
let score = 0;
|
|
331
|
+
for (const kw of keywords) {
|
|
332
|
+
if (lower.includes(kw))
|
|
333
|
+
score++;
|
|
334
|
+
}
|
|
335
|
+
if (score > bestScore) {
|
|
336
|
+
bestScore = score;
|
|
337
|
+
best = phase;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return best;
|
|
341
|
+
}
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Model queries
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
export function modelsForTier(tier, currentOnly = true) {
|
|
346
|
+
return MODEL_CATALOG.filter((m) => m.tier === tier && (!currentOnly || m.current));
|
|
347
|
+
}
|
|
348
|
+
export function modelsByProvider(provider) {
|
|
349
|
+
return MODEL_CATALOG.filter((m) => m.provider === provider && m.current);
|
|
350
|
+
}
|
|
351
|
+
export function cheapestInTier(tier) {
|
|
352
|
+
const models = modelsForTier(tier);
|
|
353
|
+
return models.sort((a, b) => a.inputCostPer1M - b.inputCostPer1M)[0];
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Narrow a recommendation to the agents actually present on this machine. A
|
|
357
|
+
* recommendation that names an agent the user has not installed is noise, so
|
|
358
|
+
* the resolved list drives both the printed advice and the handoff command.
|
|
359
|
+
*/
|
|
360
|
+
export function resolveAvailableAgents(suggested, installed) {
|
|
361
|
+
const present = new Set(installed);
|
|
362
|
+
return {
|
|
363
|
+
available: suggested.filter((id) => present.has(id)),
|
|
364
|
+
missing: suggested.filter((id) => !present.has(id)),
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/** Models in the recommended tier that the given agents can actually run. */
|
|
368
|
+
export function modelsRunnableBy(models, agents) {
|
|
369
|
+
if (!agents.length)
|
|
370
|
+
return models;
|
|
371
|
+
const wanted = new Set(agents);
|
|
372
|
+
return models.filter((m) => m.nativeAgents.some((id) => wanted.has(id)));
|
|
373
|
+
}
|
|
374
|
+
// ---------------------------------------------------------------------------
|
|
375
|
+
// Routing
|
|
376
|
+
// ---------------------------------------------------------------------------
|
|
377
|
+
export function routeTask(description, conserve = false) {
|
|
378
|
+
return routePhase(classifyTask(description), conserve);
|
|
379
|
+
}
|
|
380
|
+
export function routePhase(phase, conserve = false) {
|
|
381
|
+
const config = PHASE_CONFIG[phase];
|
|
382
|
+
if (!config)
|
|
383
|
+
throw new Error(`Unknown phase '${phase}'. Valid: ${Object.keys(PHASE_CONFIG).join(", ")}`);
|
|
384
|
+
const effectiveTier = conserve && config.conserveTier ? config.conserveTier : config.tier;
|
|
385
|
+
const models = modelsForTier(effectiveTier);
|
|
386
|
+
const rec = {
|
|
387
|
+
phase: phase,
|
|
388
|
+
tier: effectiveTier,
|
|
389
|
+
tierLabel: TIER_LABELS[effectiveTier],
|
|
390
|
+
models,
|
|
391
|
+
reason: conserve && config.conserveTier
|
|
392
|
+
? `Conserve mode: ${config.conserveTradeoff}`
|
|
393
|
+
: config.reason,
|
|
394
|
+
suggestedAgents: config.agents,
|
|
395
|
+
};
|
|
396
|
+
if (!conserve && config.conserveTier) {
|
|
397
|
+
rec.conserveAlternative = {
|
|
398
|
+
tier: config.conserveTier,
|
|
399
|
+
models: modelsForTier(config.conserveTier),
|
|
400
|
+
tradeoff: config.conserveTradeoff,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
return rec;
|
|
404
|
+
}
|
|
405
|
+
export function allPhaseRoutes(conserve = false) {
|
|
406
|
+
return Object.keys(PHASE_CONFIG).map((p) => routePhase(p, conserve));
|
|
407
|
+
}
|
|
408
|
+
export function estimateCostSavings() {
|
|
409
|
+
const normal = allPhaseRoutes(false);
|
|
410
|
+
const conserved = allPhaseRoutes(true);
|
|
411
|
+
const estimates = normal.map((r) => {
|
|
412
|
+
const cheapest = cheapestInTier(r.tier);
|
|
413
|
+
return {
|
|
414
|
+
phase: r.phase,
|
|
415
|
+
tier: r.tier,
|
|
416
|
+
cheapestModel: cheapest?.name ?? "—",
|
|
417
|
+
inputCost: cheapest?.inputCostPer1M ?? 0,
|
|
418
|
+
outputCost: cheapest?.outputCostPer1M ?? 0,
|
|
419
|
+
};
|
|
420
|
+
});
|
|
421
|
+
const normalTotal = estimates.reduce((s, e) => s + e.inputCost, 0);
|
|
422
|
+
const conserveEstimates = conserved.map((r) => cheapestInTier(r.tier)?.inputCostPer1M ?? 0);
|
|
423
|
+
const conserveTotal = conserveEstimates.reduce((s, c) => s + c, 0);
|
|
424
|
+
return { estimates, normalTotal, conserveTotal };
|
|
425
|
+
}
|
|
426
|
+
/** Shell-quote a task description for the suggested handoff command. */
|
|
427
|
+
function shellQuote(value) {
|
|
428
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
429
|
+
}
|
|
430
|
+
export function formatRouteRecommendation(rec, context = {}) {
|
|
431
|
+
// `undefined` means detection never ran, so every suggested agent stands. An
|
|
432
|
+
// empty array means detection ran and found nothing, which must not be
|
|
433
|
+
// silently upgraded back to the full suggestion list.
|
|
434
|
+
const checked = context.installedAgents !== undefined;
|
|
435
|
+
const { available, missing } = checked
|
|
436
|
+
? resolveAvailableAgents(rec.suggestedAgents, context.installedAgents)
|
|
437
|
+
: { available: rec.suggestedAgents, missing: [] };
|
|
438
|
+
// Only recommend models an available agent can actually run.
|
|
439
|
+
const runnable = checked
|
|
440
|
+
? modelsRunnableBy(rec.models, available)
|
|
441
|
+
: rec.models;
|
|
442
|
+
const shown = (runnable.length ? runnable : rec.models).slice(0, 4);
|
|
443
|
+
const modelNames = shown.map((m) => `${m.name} ($${m.inputCostPer1M}/$${m.outputCostPer1M})`);
|
|
444
|
+
const lines = [
|
|
445
|
+
`Phase: ${rec.phase}`,
|
|
446
|
+
`Tier: ${rec.tierLabel}`,
|
|
447
|
+
`Models: ${modelNames.join("\n ")}`,
|
|
448
|
+
`Agents: ${available.length ? available.join(", ") : "none detected"}${missing.length ? ` (not installed: ${missing.join(", ")})` : ""}`,
|
|
449
|
+
`Why: ${rec.reason}`,
|
|
450
|
+
];
|
|
451
|
+
if (rec.conserveAlternative) {
|
|
452
|
+
const alt = rec.conserveAlternative;
|
|
453
|
+
const altCheapest = cheapestInTier(alt.tier);
|
|
454
|
+
lines.push(``, `Conserve: drop to ${alt.tier} tier (${altCheapest?.name ?? "—"} at $${altCheapest?.inputCostPer1M ?? "?"}/$${altCheapest?.outputCostPer1M ?? "?"})`, ` ${alt.tradeoff}`);
|
|
455
|
+
}
|
|
456
|
+
// Actionable next step: hand this task to a detected agent.
|
|
457
|
+
if (context.description && available.length) {
|
|
458
|
+
const target = available[0];
|
|
459
|
+
lines.push(``, `Hand off:`, ` loadout handoff send ${target} ${shellQuote(context.description)}`);
|
|
460
|
+
if (context.handoffReady === false)
|
|
461
|
+
lines.push(` (run \`loadout handoff init\` once to enable)`);
|
|
462
|
+
}
|
|
463
|
+
return lines.join("\n");
|
|
464
|
+
}
|
|
465
|
+
export function formatRoutingTable(conserve = false) {
|
|
466
|
+
const routes = allPhaseRoutes(conserve);
|
|
467
|
+
const header = `Phase Tier Cheapest model $/M in $/M out Agents`;
|
|
468
|
+
const divider = "─".repeat(header.length);
|
|
469
|
+
const rows = routes.map((r) => {
|
|
470
|
+
const cheapest = cheapestInTier(r.tier);
|
|
471
|
+
const phase = r.phase.padEnd(12);
|
|
472
|
+
const tier = r.tier.padEnd(11);
|
|
473
|
+
const model = (cheapest?.name ?? "—").padEnd(27);
|
|
474
|
+
const inCost = `$${(cheapest?.inputCostPer1M ?? 0).toFixed(2)}`.padEnd(8);
|
|
475
|
+
const outCost = `$${(cheapest?.outputCostPer1M ?? 0).toFixed(2)}`.padEnd(8);
|
|
476
|
+
const agents = r.suggestedAgents.slice(0, 3).join(", ");
|
|
477
|
+
return `${phase} ${tier} ${model} ${inCost} ${outCost} ${agents}`;
|
|
478
|
+
});
|
|
479
|
+
return [
|
|
480
|
+
conserve
|
|
481
|
+
? "ROUTING TABLE (conserve mode — usage-saving tier for each phase)"
|
|
482
|
+
: "ROUTING TABLE",
|
|
483
|
+
"",
|
|
484
|
+
header,
|
|
485
|
+
divider,
|
|
486
|
+
...rows,
|
|
487
|
+
].join("\n");
|
|
488
|
+
}
|
|
489
|
+
export function formatCostTable() {
|
|
490
|
+
const { estimates, normalTotal, conserveTotal } = estimateCostSavings();
|
|
491
|
+
const header = "Phase Tier Cheapest model $/M input";
|
|
492
|
+
const divider = "─".repeat(header.length);
|
|
493
|
+
const rows = estimates.map((e) => {
|
|
494
|
+
const phase = e.phase.padEnd(12);
|
|
495
|
+
const tier = e.tier.padEnd(11);
|
|
496
|
+
const model = e.cheapestModel.padEnd(27);
|
|
497
|
+
return `${phase} ${tier} ${model} $${e.inputCost.toFixed(2)}`;
|
|
498
|
+
});
|
|
499
|
+
const savings = Math.round((1 - conserveTotal / normalTotal) * 100);
|
|
500
|
+
return [
|
|
501
|
+
header,
|
|
502
|
+
divider,
|
|
503
|
+
...rows,
|
|
504
|
+
divider,
|
|
505
|
+
`Normal total: $${normalTotal.toFixed(2)}/M input across 6 phases`,
|
|
506
|
+
`Conserve total: $${conserveTotal.toFixed(2)}/M input (${savings}% cheaper)`,
|
|
507
|
+
"",
|
|
508
|
+
"Prices are per-million-token list rates. Actual cost depends on prompt length.",
|
|
509
|
+
"Use --conserve when you want to stretch remaining session quota.",
|
|
510
|
+
].join("\n");
|
|
511
|
+
}
|
|
512
|
+
export function formatModelCatalog(filter) {
|
|
513
|
+
let models = MODEL_CATALOG;
|
|
514
|
+
if (filter?.provider)
|
|
515
|
+
models = models.filter((m) => m.provider === filter.provider);
|
|
516
|
+
if (filter?.tier)
|
|
517
|
+
models = models.filter((m) => m.tier === filter.tier);
|
|
518
|
+
if (filter?.current !== undefined)
|
|
519
|
+
models = models.filter((m) => m.current === filter.current);
|
|
520
|
+
const header = "Model Provider Tier $/M in $/M out Gen Agents";
|
|
521
|
+
const divider = "─".repeat(header.length);
|
|
522
|
+
const rows = models.map((m) => {
|
|
523
|
+
const name = m.name.padEnd(28);
|
|
524
|
+
const provider = m.provider.padEnd(11);
|
|
525
|
+
const tier = m.tier.padEnd(11);
|
|
526
|
+
const inCost = `$${m.inputCostPer1M.toFixed(2)}`.padEnd(8);
|
|
527
|
+
const outCost = `$${m.outputCostPer1M.toFixed(2)}`.padEnd(8);
|
|
528
|
+
const gen = m.generation.padEnd(6);
|
|
529
|
+
const agents = m.nativeAgents.slice(0, 3).join(", ");
|
|
530
|
+
return `${name} ${provider} ${tier} ${inCost} ${outCost} ${gen} ${agents}`;
|
|
531
|
+
});
|
|
532
|
+
return [
|
|
533
|
+
`${models.length} models${filter?.current === false ? " (including legacy)" : ""}`,
|
|
534
|
+
"",
|
|
535
|
+
header,
|
|
536
|
+
divider,
|
|
537
|
+
...rows,
|
|
538
|
+
].join("\n");
|
|
539
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createServer, } from "node:http";
|
|
2
|
-
import { loadEffectiveCatalog, rankCatalog } from "
|
|
3
|
-
import { detectAgents } from "
|
|
4
|
-
import { buildUpdatePlan } from "
|
|
5
|
-
import { buildHealthReport } from "
|
|
2
|
+
import { loadEffectiveCatalog, rankCatalog } from "../catalog/catalog.js";
|
|
3
|
+
import { detectAgents } from "../agents/paths.js";
|
|
4
|
+
import { buildUpdatePlan } from "../install/update.js";
|
|
5
|
+
import { buildHealthReport } from "../reporting/health.js";
|
|
6
6
|
function json(response, statusCode, body) {
|
|
7
7
|
const payload = JSON.stringify(body);
|
|
8
8
|
response.writeHead(statusCode, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { loadoutHome } from "
|
|
4
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
5
5
|
function normalizeRepository(input) {
|
|
6
6
|
const value = input
|
|
7
7
|
.trim()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
|
-
import { createCredentialResolver } from "
|
|
4
|
+
import { createCredentialResolver } from "../routing/credentials.js";
|
|
5
5
|
import { planMcpConfig } from "./mcp.js";
|
|
6
6
|
/**
|
|
7
7
|
* Small, source-linked recipes. These configure a connection only; authorizing
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile, readdir, mkdir, rename, writeFile, rm, } from "node:fs/promises";
|
|
2
2
|
import { join, resolve, dirname, basename } from "node:path";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
-
import { loadoutHome } from "
|
|
5
|
-
import { runMutationTransaction, withMutationLock } from "
|
|
4
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
5
|
+
import { runMutationTransaction, withMutationLock, } from "../install/transaction.js";
|
|
6
6
|
const missingConfigMarker = "loadout:mcp-config:missing";
|
|
7
7
|
async function configFingerprint(path) {
|
|
8
8
|
let content;
|
|
@@ -2,10 +2,10 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { rm } from "node:fs/promises";
|
|
3
3
|
import { dirname, win32, join } from "node:path";
|
|
4
4
|
import { promisify } from "node:util";
|
|
5
|
-
import { writeFileAtomically } from "
|
|
6
|
-
import { ensureDirectory, loadoutHome, userHome } from "
|
|
7
|
-
import { createSnapshot, recordSnapshotPostMutationState } from "
|
|
8
|
-
import { beginTransaction, completeTransaction, markTransactionCommitting, recoverPendingTransactions, rollbackTransaction, } from "
|
|
5
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
6
|
+
import { ensureDirectory, loadoutHome, userHome } from "../agents/paths.js";
|
|
7
|
+
import { createSnapshot, recordSnapshotPostMutationState, } from "../install/snapshot.js";
|
|
8
|
+
import { beginTransaction, completeTransaction, markTransactionCommitting, recoverPendingTransactions, rollbackTransaction, } from "../install/transaction.js";
|
|
9
9
|
const execFileAsync = promisify(execFile);
|
|
10
10
|
function parseTime(value) {
|
|
11
11
|
const match = /^(\d{2}):(\d{2})$/.exec(value);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { applyActivationChange, formatActivationPlan, planActivationChange, } from "./active-set.js";
|
|
2
2
|
import { DEFAULT_ACTIVE_SKILL_LIMIT } from "./active-limit.js";
|
|
3
|
-
import { detectAgents } from "
|
|
3
|
+
import { detectAgents } from "../agents/paths.js";
|
|
4
4
|
import { formatDetectedSignals, scanProject } from "./recommend.js";
|
|
5
|
-
import { scanInstalledSkills } from "
|
|
5
|
+
import { scanInstalledSkills } from "../catalog/skill-inventory.js";
|
|
6
6
|
import { readInstallState } from "./state.js";
|
|
7
7
|
import { outcomeAdjustment, projectTaskFamilies, readLocalOutcomes, } from "./outcomes.js";
|
|
8
8
|
const FOUNDATION = new Set([
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { cp, lstat, mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
-
import { agentSkillsDirectory, ensureDirectory } from "
|
|
4
|
+
import { agentSkillsDirectory, ensureDirectory } from "../agents/paths.js";
|
|
5
5
|
import { activationLibraryPath, installStatePath, readInstallState, writeInstallState, } from "./state.js";
|
|
6
6
|
import { inspectTargetOccupancy } from "./target-occupancy.js";
|
|
7
|
-
import { runMutationTransaction } from "
|
|
7
|
+
import { runMutationTransaction } from "../install/transaction.js";
|
|
8
8
|
/** Resolve a managed file to its active location or disabled library copy. */
|
|
9
9
|
export function managedFileReadPath(packageId, filePath, activations) {
|
|
10
10
|
const disabled = activations.find((activation) => activation.packageId === packageId &&
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { buildHealthReport } from "
|
|
1
|
+
import { buildHealthReport } from "../reporting/health.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
|
-
import { loadoutHome } from "
|
|
6
|
-
import { detectSecretKinds } from "
|
|
5
|
+
import { loadoutHome } from "../agents/paths.js";
|
|
6
|
+
import { detectSecretKinds } from "../catalog/safety.js";
|
|
7
7
|
export function proposeImprovements(report) {
|
|
8
8
|
const proposals = [];
|
|
9
9
|
if (report.driftedFiles)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { formatSchemaError, loadoutManifestSchema } from "
|
|
3
|
+
import { formatSchemaError, loadoutManifestSchema, } from "../../shared/schemas.js";
|
|
4
4
|
import { readInstallState } from "./state.js";
|
|
5
|
-
import { writeFileAtomically } from "
|
|
5
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
6
6
|
const AGENTS = new Set([
|
|
7
7
|
"claude-code",
|
|
8
8
|
"codex",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { writeFileAtomically } from "
|
|
5
|
-
import { ensureDirectory, loadoutHome } from "
|
|
6
|
-
import { withFileLock } from "
|
|
4
|
+
import { writeFileAtomically } from "../install/atomic-file.js";
|
|
5
|
+
import { ensureDirectory, loadoutHome } from "../agents/paths.js";
|
|
6
|
+
import { withFileLock } from "../install/file-lock.js";
|
|
7
7
|
const outcomePath = () => join(loadoutHome(), "outcomes.json");
|
|
8
8
|
const outcomeLockPath = () => join(loadoutHome(), "outcomes.lock");
|
|
9
9
|
const RESULTS = new Set([
|