mohdel 0.124.0 → 1.0.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 +155 -30
- package/config/curated.schema.json +32 -10
- package/js/client/call.js +76 -19
- package/js/client/gate-binary.js +5 -0
- package/js/client/index.js +1 -0
- package/js/core/envelope.js +5 -1
- package/js/factory/bridge.js +2 -2
- package/js/session/adapters/_cancelled.js +0 -6
- package/js/session/adapters/_chat_completions.js +25 -0
- package/js/session/adapters/_output_cap.js +30 -0
- package/js/session/adapters/_registry.js +44 -0
- package/js/session/adapters/anthropic.js +8 -5
- package/js/session/adapters/gemini.js +4 -0
- package/js/session/adapters/openai.js +2 -1
- package/js/session/run.js +14 -4
- package/js/session/run_image.js +12 -3
- package/package.json +49 -19
- package/src/cli/aliases.js +20 -0
- package/src/cli/ask.js +58 -12
- package/src/cli/backup.js +2 -1
- package/src/cli/check.js +15 -86
- package/src/cli/complete.js +130 -0
- package/src/cli/default.js +34 -13
- package/src/cli/doctor.js +33 -14
- package/src/cli/entry.js +173 -0
- package/src/cli/index.js +77 -66
- package/src/cli/instructions.js +349 -0
- package/src/cli/local.js +14 -0
- package/src/cli/model.js +184 -37
- package/src/cli/onboard.js +186 -121
- package/src/cli/rank.js +2 -1
- package/src/cli/ratelimit.js +3 -3
- package/src/cli/tag.js +2 -0
- package/src/lib/assistants.js +93 -0
- package/src/lib/catalog/openrouter.js +6 -1
- package/src/lib/catalog-review.js +195 -0
- package/src/lib/common.js +14 -1
- package/src/lib/creators.js +35 -0
- package/src/lib/index.js +17 -3
- package/src/lib/local-conventions.js +120 -0
- package/src/lib/provider-info.js +98 -0
- package/src/lib/providers.js +69 -14
- package/src/lib/schema.js +15 -3
- package/src/lib/select.js +125 -67
- package/js/session/adapters/image/index.js +0 -40
package/src/cli/model.js
CHANGED
|
@@ -2,8 +2,14 @@ import mohdel, { silent } from '../lib/index.js'
|
|
|
2
2
|
import providerDefs from '../lib/providers.js'
|
|
3
3
|
import { loadDefaultEnv, getAPIKey, getCuratedModels, saveCuratedModels } from '../lib/common.js'
|
|
4
4
|
import { fieldDefs } from '../lib/schema.js'
|
|
5
|
+
import { providerOf } from '#core/model-id.js'
|
|
5
6
|
import { parseJsonFlag, printAvailableFields, jsonOutput, jsonOutputOne } from './json-output.js'
|
|
6
|
-
import { id, label, tag, price, meta, err, ok } from './colors.js'
|
|
7
|
+
import { id, label, tag, price, meta, err, warn, ok } from './colors.js'
|
|
8
|
+
|
|
9
|
+
// An empty catalog is the fresh-install state, not an error — but printing
|
|
10
|
+
// nothing at all reads as a broken command.
|
|
11
|
+
export const EMPTY_CATALOG = 'Catalog is empty. "mo curate <provider>" adds a provider\'s models, ' +
|
|
12
|
+
'then "mo model instructions <provider>" fills in the prices.'
|
|
7
13
|
|
|
8
14
|
// Fields available for --json on model list/show
|
|
9
15
|
const MODEL_FIELDS = [
|
|
@@ -17,11 +23,13 @@ const MODEL_FIELDS = [
|
|
|
17
23
|
]
|
|
18
24
|
|
|
19
25
|
export async function runModel (args) {
|
|
26
|
+
// parseJsonFlag consumes --json in place; delegated actions parse their own.
|
|
27
|
+
const rawArgs = [...args]
|
|
20
28
|
const jsonFlag = parseJsonFlag(args)
|
|
21
29
|
const [action, arg1] = args
|
|
22
30
|
|
|
23
31
|
if (!action || action === '-h' || action === '--help') {
|
|
24
|
-
console.log(`mohdel model — browse
|
|
32
|
+
console.log(`mohdel model — browse and maintain the catalog
|
|
25
33
|
|
|
26
34
|
Usage:
|
|
27
35
|
model list [--json [fields]] List all curated models
|
|
@@ -33,17 +41,31 @@ Usage:
|
|
|
33
41
|
model set <model> <key> <value> Set a field (custom or reserved)
|
|
34
42
|
model rm <model> <key> Remove a field
|
|
35
43
|
model add <provider>/<model-id> Add a model manually (interactive)
|
|
36
|
-
model
|
|
37
|
-
model check [--
|
|
44
|
+
model instructions [provider] Print a brief for your coding agent
|
|
45
|
+
model check [--entry <file|->] Validate catalog or candidate entries
|
|
46
|
+
model apply <file|-> Write reviewed entries to the catalog
|
|
47
|
+
model backup list|restore|diff Catalog backups: prev, daily, weekly
|
|
38
48
|
model rank [options] Rank models by benchmark performance
|
|
39
49
|
model bench <model> [options] Benchmark a model with live inference
|
|
40
50
|
model bench --tag <tag> [options] Benchmark all models with a tag
|
|
41
|
-
model curate [provider] Add
|
|
51
|
+
model curate [provider] Add a provider's models to the catalog
|
|
42
52
|
|
|
43
53
|
Flags:
|
|
44
54
|
--json List available JSON fields
|
|
45
55
|
--json f1,f2,f3 Output only selected fields as JSON
|
|
46
56
|
|
|
57
|
+
Catalog work with a coding agent:
|
|
58
|
+
mo model instructions openai > mohdel-brief.md
|
|
59
|
+
then start claude / codex / gemini / opencode / cursor-agent on:
|
|
60
|
+
"read mohdel-brief.md, then add gpt-5.6 to my mohdel catalog"
|
|
61
|
+
|
|
62
|
+
The agent drafts mohdel-candidate.json and checks it; you apply it:
|
|
63
|
+
|
|
64
|
+
mo model check --entry mohdel-candidate.json
|
|
65
|
+
mo model apply mohdel-candidate.json ← prints the diff first
|
|
66
|
+
|
|
67
|
+
Launch lines for each agent: mo model instructions --help
|
|
68
|
+
|
|
47
69
|
Aliases:
|
|
48
70
|
mo ls model list
|
|
49
71
|
mo show <model> model show <model>`)
|
|
@@ -84,6 +106,7 @@ Aliases:
|
|
|
84
106
|
jsonOutput(items.map(m => ({ id: m.id, ...m.info })), jsonFlag.fields)
|
|
85
107
|
return
|
|
86
108
|
}
|
|
109
|
+
if (!items.length) { console.log(meta(EMPTY_CATALOG)); return }
|
|
87
110
|
for (const m of items) {
|
|
88
111
|
const tags = (m.info.tags || []).map(t => meta(t)).join(meta(', '))
|
|
89
112
|
const p = formatPrice(m.info)
|
|
@@ -182,7 +205,7 @@ ${meta('tags:')} ${(info.tags || []).map(t => tag(t)).join(', ') || meta
|
|
|
182
205
|
const curated = await getCuratedModels()
|
|
183
206
|
if (!curated[resolvedId]) {
|
|
184
207
|
// Model resolved via fallback but not in curated
|
|
185
|
-
console.error(err(`Model '${modelId}' is not in
|
|
208
|
+
console.error(err(`Model '${modelId}' is not in your catalog. "mo model add ${modelId}" adds it, "mo curate ${providerOf(modelId)}" walks that provider's models.`))
|
|
186
209
|
process.exit(1)
|
|
187
210
|
}
|
|
188
211
|
|
|
@@ -227,7 +250,7 @@ ${meta('tags:')} ${(info.tags || []).map(t => tag(t)).join(', ') || meta
|
|
|
227
250
|
const resolvedId = resolved.id
|
|
228
251
|
const curated = await getCuratedModels()
|
|
229
252
|
if (!curated[resolvedId]) {
|
|
230
|
-
console.error(err(`Model '${modelId}' is not in
|
|
253
|
+
console.error(err(`Model '${modelId}' is not in your catalog.`))
|
|
231
254
|
process.exit(1)
|
|
232
255
|
}
|
|
233
256
|
|
|
@@ -244,7 +267,7 @@ ${meta('tags:')} ${(info.tags || []).map(t => tag(t)).join(', ') || meta
|
|
|
244
267
|
|
|
245
268
|
if (action === 'backup') {
|
|
246
269
|
const { runBackup } = await import('./backup.js')
|
|
247
|
-
await runBackup(
|
|
270
|
+
await runBackup(rawArgs.slice(1))
|
|
248
271
|
return
|
|
249
272
|
}
|
|
250
273
|
|
|
@@ -259,7 +282,8 @@ Usage:
|
|
|
259
282
|
What it does:
|
|
260
283
|
1. Resolves <provider> against the known provider list (anthropic, openai, …)
|
|
261
284
|
2. Pre-fills 'model', 'provider', 'sdk' from that resolution
|
|
262
|
-
3.
|
|
285
|
+
3. Fetches upstream model metadata where the API key is set
|
|
286
|
+
(context, pricing, …)
|
|
263
287
|
4. Prompts for any missing required field
|
|
264
288
|
|
|
265
289
|
Examples:
|
|
@@ -308,7 +332,7 @@ config/curated.example.json for ready-to-copy entries.`)
|
|
|
308
332
|
if (apiKey && providerConfig.catalog !== false) {
|
|
309
333
|
try {
|
|
310
334
|
const sdkConfig = providerConfig.createConfiguration(apiKey)
|
|
311
|
-
const { default: API } = await import(`../lib/
|
|
335
|
+
const { default: API } = await import(`../lib/catalog/${providerConfig.sdk}.js`)
|
|
312
336
|
const noop = () => {}
|
|
313
337
|
const api = API(sdkConfig, {}, { trace: noop, debug: noop, info: noop, warn: noop, error: noop, fatal: noop })
|
|
314
338
|
if (api.getModelInfo) {
|
|
@@ -318,7 +342,19 @@ config/curated.example.json for ready-to-copy entries.`)
|
|
|
318
342
|
console.log(meta('Fetched model info from upstream'))
|
|
319
343
|
}
|
|
320
344
|
}
|
|
321
|
-
} catch {
|
|
345
|
+
} catch (e) {
|
|
346
|
+
console.log(warn(`upstream lookup failed: ${e.message}`))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (providerConfig.pricesFromApi) {
|
|
351
|
+
console.log(meta(`${providerName} publishes prices in its model list, so they are filled in already.`))
|
|
352
|
+
} else {
|
|
353
|
+
const refs = providerConfig.references
|
|
354
|
+
if (refs) {
|
|
355
|
+
console.log(meta(`Prices and limits are in no provider API — read them at ${refs.pricing || refs.models}`))
|
|
356
|
+
}
|
|
357
|
+
console.log(meta(`Or hand the job to your coding agent: mo model instructions ${providerName}`))
|
|
322
358
|
}
|
|
323
359
|
|
|
324
360
|
// Interactive prompts for missing fields
|
|
@@ -333,19 +369,31 @@ config/curated.example.json for ready-to-copy entries.`)
|
|
|
333
369
|
|
|
334
370
|
if (action === 'check') {
|
|
335
371
|
const { runCheck } = await import('./check.js')
|
|
336
|
-
await runCheck(
|
|
372
|
+
await runCheck(rawArgs.slice(1))
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (action === 'apply') {
|
|
377
|
+
const { runApply } = await import('./entry.js')
|
|
378
|
+
await runApply(rawArgs.slice(1))
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (action === 'instructions') {
|
|
383
|
+
const { runInstructions } = await import('./instructions.js')
|
|
384
|
+
await runInstructions(rawArgs.slice(1))
|
|
337
385
|
return
|
|
338
386
|
}
|
|
339
387
|
|
|
340
388
|
if (action === 'rank') {
|
|
341
389
|
const { runRank } = await import('./rank.js')
|
|
342
|
-
await runRank(
|
|
390
|
+
await runRank(rawArgs.slice(1))
|
|
343
391
|
return
|
|
344
392
|
}
|
|
345
393
|
|
|
346
394
|
if (action === 'bench') {
|
|
347
395
|
const { runBench } = await import('./bench.js')
|
|
348
|
-
await runBench(
|
|
396
|
+
await runBench(rawArgs.slice(1))
|
|
349
397
|
return
|
|
350
398
|
}
|
|
351
399
|
|
|
@@ -367,29 +415,31 @@ Examples:
|
|
|
367
415
|
mo curate anthropic
|
|
368
416
|
mo curate openai
|
|
369
417
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
and config/curated.example.json for ready-to-copy entries.
|
|
418
|
+
A provider's model list carries ids, not prices. Fill the rest in with
|
|
419
|
+
'mo model instructions <provider>', which briefs your coding agent, or by hand
|
|
420
|
+
with 'mo model set <id> <key> <value>'. See docs/CATALOG.md for the field
|
|
421
|
+
reference and config/curated.example.json for ready-to-copy entries.
|
|
374
422
|
|
|
375
423
|
Requires an API key for the chosen provider — run 'mo' to configure one.`)
|
|
376
424
|
process.exit(0)
|
|
377
425
|
}
|
|
378
|
-
const {
|
|
379
|
-
const
|
|
426
|
+
const { providerApi, providersWithKeys, processModels } = await import('../lib/select.js')
|
|
427
|
+
const withKeys = providersWithKeys()
|
|
380
428
|
|
|
381
|
-
if (!
|
|
429
|
+
if (!withKeys.length) {
|
|
382
430
|
console.error(err('No providers with API keys configured. Run "mo" to set up.'))
|
|
383
431
|
process.exit(1)
|
|
384
432
|
}
|
|
385
433
|
|
|
386
|
-
// mo model curate <provider> —
|
|
434
|
+
// mo model curate <provider> — only that provider's client is built
|
|
387
435
|
if (arg1) {
|
|
388
|
-
|
|
389
|
-
|
|
436
|
+
const api = await providerApi(arg1)
|
|
437
|
+
if (!api) {
|
|
438
|
+
console.error(err(`Provider "${arg1}" not found or no API key. Available: ${withKeys.join(', ')}`))
|
|
390
439
|
process.exit(1)
|
|
391
440
|
}
|
|
392
|
-
await processModels(arg1, api
|
|
441
|
+
if (!await processModels(arg1, api)) process.exit(1)
|
|
442
|
+
printCurateNext(arg1)
|
|
393
443
|
return
|
|
394
444
|
}
|
|
395
445
|
|
|
@@ -397,10 +447,11 @@ Requires an API key for the chosen provider — run 'mo' to configure one.`)
|
|
|
397
447
|
const { select, isCancel } = await import('@clack/prompts')
|
|
398
448
|
const selected = await select({
|
|
399
449
|
message: 'Select a provider to curate:',
|
|
400
|
-
options:
|
|
450
|
+
options: withKeys.map(name => ({ value: name, label: name }))
|
|
401
451
|
})
|
|
402
452
|
if (isCancel(selected)) return
|
|
403
|
-
await processModels(selected,
|
|
453
|
+
if (!await processModels(selected, await providerApi(selected))) process.exit(1)
|
|
454
|
+
printCurateNext(selected)
|
|
404
455
|
return
|
|
405
456
|
}
|
|
406
457
|
|
|
@@ -412,25 +463,48 @@ export async function runProvider (args) {
|
|
|
412
463
|
const jsonFlag = parseJsonFlag(args)
|
|
413
464
|
const [action, arg1] = args
|
|
414
465
|
|
|
466
|
+
if (action === '-h' || action === '--help') {
|
|
467
|
+
console.log(`mohdel provider — providers and their API keys
|
|
468
|
+
|
|
469
|
+
Usage:
|
|
470
|
+
provider list [--json] List every provider mohdel can route to
|
|
471
|
+
provider list <provider> List your catalog entries for one provider
|
|
472
|
+
provider models <provider> List the models your key reaches upstream
|
|
473
|
+
provider setup <provider> Paste an API key (interactive)
|
|
474
|
+
provider rm <provider> Remove an API key
|
|
475
|
+
|
|
476
|
+
A provider is who serves the call. Who trained the model is its creator —
|
|
477
|
+
see "mo creator --help".`)
|
|
478
|
+
process.exit(0)
|
|
479
|
+
}
|
|
480
|
+
|
|
415
481
|
const mo = await mohdel({ logger: silent })
|
|
416
482
|
const all = mo.list()
|
|
417
483
|
|
|
418
484
|
if ((!action || action === 'list') && !arg1) {
|
|
419
485
|
loadDefaultEnv()
|
|
420
|
-
|
|
486
|
+
// Every provider mohdel can route to, not just the ones the catalog
|
|
487
|
+
// happens to mention — on a fresh install the catalog is empty and this
|
|
488
|
+
// list is how you find out what there is to curate.
|
|
489
|
+
const counts = new Map()
|
|
421
490
|
for (const m of all) {
|
|
422
491
|
const info = mo.use(m.value).info()
|
|
423
492
|
if (!info.provider) continue
|
|
424
|
-
|
|
425
|
-
providerMap.set(info.provider, providerMap.get(info.provider) + 1)
|
|
493
|
+
counts.set(info.provider, (counts.get(info.provider) || 0) + 1)
|
|
426
494
|
}
|
|
427
|
-
const rows =
|
|
428
|
-
.sort((a, b) => a
|
|
429
|
-
.map(
|
|
495
|
+
const rows = Object.keys(providerDefs)
|
|
496
|
+
.sort((a, b) => a.localeCompare(b))
|
|
497
|
+
.map(provider => {
|
|
430
498
|
const def = providerDefs[provider]
|
|
431
|
-
const hasKey = def
|
|
499
|
+
const hasKey = def.apiKeyEnv ? !!getAPIKey(def.apiKeyEnv) : null
|
|
432
500
|
const rl = mo.getProviderRateLimit(provider)
|
|
433
|
-
return {
|
|
501
|
+
return {
|
|
502
|
+
provider,
|
|
503
|
+
count: counts.get(provider) || 0,
|
|
504
|
+
hasKey,
|
|
505
|
+
rpmLimit: rl?.rpmLimit || null,
|
|
506
|
+
tpmLimit: rl?.tpmLimit || null
|
|
507
|
+
}
|
|
434
508
|
})
|
|
435
509
|
|
|
436
510
|
if (jsonFlag.json && !jsonFlag.fields) {
|
|
@@ -441,13 +515,17 @@ export async function runProvider (args) {
|
|
|
441
515
|
jsonOutput(rows, jsonFlag.fields)
|
|
442
516
|
return
|
|
443
517
|
}
|
|
518
|
+
const nameWidth = Math.max(...rows.map(r => r.provider.length))
|
|
519
|
+
const countWidth = Math.max(...rows.map(r => String(r.count).length))
|
|
444
520
|
for (const row of rows) {
|
|
445
521
|
const dot = row.hasKey === null ? ' ' : row.hasKey ? ok('●') : meta('○')
|
|
446
522
|
const rl = []
|
|
447
523
|
if (row.rpmLimit) rl.push(`rpm=${row.rpmLimit}`)
|
|
448
524
|
if (row.tpmLimit) rl.push(`tpm=${row.tpmLimit}`)
|
|
449
|
-
const
|
|
450
|
-
|
|
525
|
+
const models = `${String(row.count).padStart(countWidth)} model${row.count === 1 ? '' : 's'}`
|
|
526
|
+
const cells = [id(row.provider.padEnd(nameWidth)), meta(models)]
|
|
527
|
+
if (rl.length) cells.push(meta(rl.join(' ')))
|
|
528
|
+
console.log(` ${dot} ${cells.join(' ')}`)
|
|
451
529
|
}
|
|
452
530
|
const hasUnconfigured = rows.some(r => r.hasKey === false)
|
|
453
531
|
console.log(`\n${meta('Next:')} mo provider show <name> ${meta('│')} mo curate <name>` +
|
|
@@ -478,6 +556,48 @@ export async function runProvider (args) {
|
|
|
478
556
|
return
|
|
479
557
|
}
|
|
480
558
|
|
|
559
|
+
if (action === 'models') {
|
|
560
|
+
if (!arg1) { console.error('Usage: provider models <provider> [--json]'); process.exit(1) }
|
|
561
|
+
const providerConfig = providerDefs[arg1]
|
|
562
|
+
if (!providerConfig) {
|
|
563
|
+
console.error(err(`Unknown provider: ${arg1}. Known: ${Object.keys(providerDefs).join(', ')}`))
|
|
564
|
+
process.exit(1)
|
|
565
|
+
}
|
|
566
|
+
loadDefaultEnv()
|
|
567
|
+
const { providerApi } = await import('../lib/select.js')
|
|
568
|
+
const api = await providerApi(arg1)
|
|
569
|
+
if (!api?.listModels) {
|
|
570
|
+
const why = providerConfig.catalog === false || !providerConfig.apiKeyEnv
|
|
571
|
+
? `${arg1} publishes no model list`
|
|
572
|
+
: `no API key for ${arg1} — run "mo provider setup ${arg1}"`
|
|
573
|
+
console.error(err(why))
|
|
574
|
+
process.exit(1)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
let upstream
|
|
578
|
+
try {
|
|
579
|
+
upstream = await api.listModels()
|
|
580
|
+
} catch (e) {
|
|
581
|
+
console.error(err(`${arg1}: ${e.message}`))
|
|
582
|
+
process.exit(1)
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const curated = await getCuratedModels()
|
|
586
|
+
const rows = upstream.map(m => ({ id: m.id, label: m.label, curated: !!curated[`${arg1}/${m.id}`] }))
|
|
587
|
+
|
|
588
|
+
if (jsonFlag.json && !jsonFlag.fields) { printAvailableFields(['id', 'label', 'curated']); return }
|
|
589
|
+
if (jsonFlag.json) { jsonOutput(rows, jsonFlag.fields); return }
|
|
590
|
+
|
|
591
|
+
const width = Math.max(...rows.map(r => r.id.length))
|
|
592
|
+
for (const r of rows) {
|
|
593
|
+
console.log(` ${r.curated ? ok('●') : meta('○')} ${id(r.id.padEnd(width))} ${label(r.label)}`)
|
|
594
|
+
}
|
|
595
|
+
const fresh = rows.filter(r => !r.curated).length
|
|
596
|
+
console.log(`\n${meta(`${rows.length} upstream, ${fresh} not in your catalog`)}`)
|
|
597
|
+
console.log(`${meta('Next:')} mo model instructions ${arg1} ${meta('│')} mo curate ${arg1}`)
|
|
598
|
+
return
|
|
599
|
+
}
|
|
600
|
+
|
|
481
601
|
if (action === 'setup') {
|
|
482
602
|
if (!arg1) { console.error('Usage: provider setup <provider>'); process.exit(1) }
|
|
483
603
|
const providerConfig = providerDefs[arg1]
|
|
@@ -535,6 +655,19 @@ export async function runCreator (args) {
|
|
|
535
655
|
const jsonFlag = parseJsonFlag(args)
|
|
536
656
|
const [action, arg1] = args
|
|
537
657
|
|
|
658
|
+
if (action === '-h' || action === '--help') {
|
|
659
|
+
console.log(`mohdel creator — who trained the model
|
|
660
|
+
|
|
661
|
+
Usage:
|
|
662
|
+
creator list [--json] List every creator in your catalog
|
|
663
|
+
creator list <creator> List that creator's models
|
|
664
|
+
creator show <creator> Same, by name
|
|
665
|
+
|
|
666
|
+
One creator is hosted by many providers: Cerebras and Groq both serve
|
|
667
|
+
Alibaba's Qwen. Routing follows the provider — see "mo provider --help".`)
|
|
668
|
+
process.exit(0)
|
|
669
|
+
}
|
|
670
|
+
|
|
538
671
|
const mo = await mohdel({ logger: silent })
|
|
539
672
|
const all = mo.list()
|
|
540
673
|
|
|
@@ -557,6 +690,7 @@ export async function runCreator (args) {
|
|
|
557
690
|
jsonOutput(items, jsonFlag.fields)
|
|
558
691
|
return
|
|
559
692
|
}
|
|
693
|
+
if (!creators.size) { console.log(meta(EMPTY_CATALOG)); return }
|
|
560
694
|
for (const [name, count] of [...creators.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
561
695
|
console.log(`${id(name)} ${meta(`(${count} models)`)}`)
|
|
562
696
|
}
|
|
@@ -590,6 +724,19 @@ export async function runCreator (args) {
|
|
|
590
724
|
process.exit(1)
|
|
591
725
|
}
|
|
592
726
|
|
|
727
|
+
// The provider API returns ids, never prices — curated entries land unpriced.
|
|
728
|
+
function printCurateNext (providerName) {
|
|
729
|
+
const def = providerDefs[providerName]
|
|
730
|
+
if (def?.pricesFromApi) {
|
|
731
|
+
console.log(`\n${meta(`${providerName} publishes prices in its model list — the entries are complete.`)}`)
|
|
732
|
+
console.log(`${meta('Check them:')} mo ls ${meta('│')} mo check`)
|
|
733
|
+
return
|
|
734
|
+
}
|
|
735
|
+
const refs = def?.references
|
|
736
|
+
if (refs) console.log(`\n${meta('Prices and limits are in no provider API — read them at')} ${refs.pricing || refs.models}`)
|
|
737
|
+
console.log(`${meta('Or hand the job to your coding agent:')} mo model instructions ${providerName}`)
|
|
738
|
+
}
|
|
739
|
+
|
|
593
740
|
// Auto-detect value type from string input
|
|
594
741
|
function coerceValue (key, raw) {
|
|
595
742
|
if (raw === 'true') return true
|