@sriinnu/kosha-discovery 0.5.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/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/aliases.d.ts +51 -0
- package/dist/aliases.d.ts.map +1 -0
- package/dist/aliases.js +115 -0
- package/dist/aliases.js.map +1 -0
- package/dist/cache.d.ts +57 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +123 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli-commands.d.ts +142 -0
- package/dist/cli-commands.d.ts.map +1 -0
- package/dist/cli-commands.js +746 -0
- package/dist/cli-commands.js.map +1 -0
- package/dist/cli-format.d.ts +119 -0
- package/dist/cli-format.d.ts.map +1 -0
- package/dist/cli-format.js +171 -0
- package/dist/cli-format.js.map +1 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +143 -0
- package/dist/cli.js.map +1 -0
- package/dist/credentials/index.d.ts +2 -0
- package/dist/credentials/index.d.ts.map +1 -0
- package/dist/credentials/index.js +2 -0
- package/dist/credentials/index.js.map +1 -0
- package/dist/credentials/resolver.d.ts +170 -0
- package/dist/credentials/resolver.d.ts.map +1 -0
- package/dist/credentials/resolver.js +597 -0
- package/dist/credentials/resolver.js.map +1 -0
- package/dist/discovery/anthropic.d.ts +49 -0
- package/dist/discovery/anthropic.d.ts.map +1 -0
- package/dist/discovery/anthropic.js +101 -0
- package/dist/discovery/anthropic.js.map +1 -0
- package/dist/discovery/base.d.ts +59 -0
- package/dist/discovery/base.d.ts.map +1 -0
- package/dist/discovery/base.js +123 -0
- package/dist/discovery/base.js.map +1 -0
- package/dist/discovery/bedrock.d.ts +122 -0
- package/dist/discovery/bedrock.d.ts.map +1 -0
- package/dist/discovery/bedrock.js +356 -0
- package/dist/discovery/bedrock.js.map +1 -0
- package/dist/discovery/cerebras.d.ts +43 -0
- package/dist/discovery/cerebras.d.ts.map +1 -0
- package/dist/discovery/cerebras.js +65 -0
- package/dist/discovery/cerebras.js.map +1 -0
- package/dist/discovery/cohere.d.ts +57 -0
- package/dist/discovery/cohere.d.ts.map +1 -0
- package/dist/discovery/cohere.js +82 -0
- package/dist/discovery/cohere.js.map +1 -0
- package/dist/discovery/deepinfra.d.ts +41 -0
- package/dist/discovery/deepinfra.d.ts.map +1 -0
- package/dist/discovery/deepinfra.js +86 -0
- package/dist/discovery/deepinfra.js.map +1 -0
- package/dist/discovery/deepseek.d.ts +17 -0
- package/dist/discovery/deepseek.d.ts.map +1 -0
- package/dist/discovery/deepseek.js +45 -0
- package/dist/discovery/deepseek.js.map +1 -0
- package/dist/discovery/fireworks.d.ts +48 -0
- package/dist/discovery/fireworks.d.ts.map +1 -0
- package/dist/discovery/fireworks.js +101 -0
- package/dist/discovery/fireworks.js.map +1 -0
- package/dist/discovery/glm.d.ts +17 -0
- package/dist/discovery/glm.d.ts.map +1 -0
- package/dist/discovery/glm.js +52 -0
- package/dist/discovery/glm.js.map +1 -0
- package/dist/discovery/google.d.ts +53 -0
- package/dist/discovery/google.d.ts.map +1 -0
- package/dist/discovery/google.js +122 -0
- package/dist/discovery/google.js.map +1 -0
- package/dist/discovery/groq.d.ts +44 -0
- package/dist/discovery/groq.d.ts.map +1 -0
- package/dist/discovery/groq.js +89 -0
- package/dist/discovery/groq.js.map +1 -0
- package/dist/discovery/index.d.ts +48 -0
- package/dist/discovery/index.d.ts.map +1 -0
- package/dist/discovery/index.js +116 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/llama-cpp.d.ts +45 -0
- package/dist/discovery/llama-cpp.d.ts.map +1 -0
- package/dist/discovery/llama-cpp.js +137 -0
- package/dist/discovery/llama-cpp.js.map +1 -0
- package/dist/discovery/minimax.d.ts +16 -0
- package/dist/discovery/minimax.d.ts.map +1 -0
- package/dist/discovery/minimax.js +56 -0
- package/dist/discovery/minimax.js.map +1 -0
- package/dist/discovery/mistral.d.ts +45 -0
- package/dist/discovery/mistral.d.ts.map +1 -0
- package/dist/discovery/mistral.js +82 -0
- package/dist/discovery/mistral.js.map +1 -0
- package/dist/discovery/moonshot.d.ts +16 -0
- package/dist/discovery/moonshot.d.ts.map +1 -0
- package/dist/discovery/moonshot.js +46 -0
- package/dist/discovery/moonshot.js.map +1 -0
- package/dist/discovery/nvidia.d.ts +60 -0
- package/dist/discovery/nvidia.d.ts.map +1 -0
- package/dist/discovery/nvidia.js +145 -0
- package/dist/discovery/nvidia.js.map +1 -0
- package/dist/discovery/ollama.d.ts +57 -0
- package/dist/discovery/ollama.d.ts.map +1 -0
- package/dist/discovery/ollama.js +151 -0
- package/dist/discovery/ollama.js.map +1 -0
- package/dist/discovery/openai-compatible.d.ts +126 -0
- package/dist/discovery/openai-compatible.d.ts.map +1 -0
- package/dist/discovery/openai-compatible.js +161 -0
- package/dist/discovery/openai-compatible.js.map +1 -0
- package/dist/discovery/openai.d.ts +62 -0
- package/dist/discovery/openai.d.ts.map +1 -0
- package/dist/discovery/openai.js +157 -0
- package/dist/discovery/openai.js.map +1 -0
- package/dist/discovery/openrouter.d.ts +78 -0
- package/dist/discovery/openrouter.d.ts.map +1 -0
- package/dist/discovery/openrouter.js +184 -0
- package/dist/discovery/openrouter.js.map +1 -0
- package/dist/discovery/perplexity.d.ts +66 -0
- package/dist/discovery/perplexity.d.ts.map +1 -0
- package/dist/discovery/perplexity.js +86 -0
- package/dist/discovery/perplexity.js.map +1 -0
- package/dist/discovery/together.d.ts +41 -0
- package/dist/discovery/together.d.ts.map +1 -0
- package/dist/discovery/together.js +88 -0
- package/dist/discovery/together.js.map +1 -0
- package/dist/discovery/vertex.d.ts +146 -0
- package/dist/discovery/vertex.d.ts.map +1 -0
- package/dist/discovery/vertex.js +371 -0
- package/dist/discovery/vertex.js.map +1 -0
- package/dist/discovery/zai.d.ts +17 -0
- package/dist/discovery/zai.d.ts.map +1 -0
- package/dist/discovery/zai.js +51 -0
- package/dist/discovery/zai.js.map +1 -0
- package/dist/discovery-contract.d.ts +186 -0
- package/dist/discovery-contract.d.ts.map +1 -0
- package/dist/discovery-contract.js +115 -0
- package/dist/discovery-contract.js.map +1 -0
- package/dist/discovery-routes.d.ts +14 -0
- package/dist/discovery-routes.d.ts.map +1 -0
- package/dist/discovery-routes.js +98 -0
- package/dist/discovery-routes.js.map +1 -0
- package/dist/enrichment/index.d.ts +2 -0
- package/dist/enrichment/index.d.ts.map +1 -0
- package/dist/enrichment/index.js +2 -0
- package/dist/enrichment/index.js.map +1 -0
- package/dist/enrichment/litellm.d.ts +52 -0
- package/dist/enrichment/litellm.d.ts.map +1 -0
- package/dist/enrichment/litellm.js +205 -0
- package/dist/enrichment/litellm.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +64 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +166 -0
- package/dist/normalize.js.map +1 -0
- package/dist/provider-catalog.d.ts +62 -0
- package/dist/provider-catalog.d.ts.map +1 -0
- package/dist/provider-catalog.js +331 -0
- package/dist/provider-catalog.js.map +1 -0
- package/dist/registry-delta.d.ts +17 -0
- package/dist/registry-delta.d.ts.map +1 -0
- package/dist/registry-delta.js +74 -0
- package/dist/registry-delta.js.map +1 -0
- package/dist/registry-discovery.d.ts +66 -0
- package/dist/registry-discovery.d.ts.map +1 -0
- package/dist/registry-discovery.js +339 -0
- package/dist/registry-discovery.js.map +1 -0
- package/dist/registry-query.d.ts +74 -0
- package/dist/registry-query.d.ts.map +1 -0
- package/dist/registry-query.js +310 -0
- package/dist/registry-query.js.map +1 -0
- package/dist/registry-runtime.d.ts +48 -0
- package/dist/registry-runtime.d.ts.map +1 -0
- package/dist/registry-runtime.js +255 -0
- package/dist/registry-runtime.js.map +1 -0
- package/dist/registry-selection.d.ts +31 -0
- package/dist/registry-selection.d.ts.map +1 -0
- package/dist/registry-selection.js +167 -0
- package/dist/registry-selection.js.map +1 -0
- package/dist/registry-state.d.ts +92 -0
- package/dist/registry-state.d.ts.map +1 -0
- package/dist/registry-state.js +35 -0
- package/dist/registry-state.js.map +1 -0
- package/dist/registry.d.ts +127 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +248 -0
- package/dist/registry.js.map +1 -0
- package/dist/resilience.d.ts +212 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +292 -0
- package/dist/resilience.js.map +1 -0
- package/dist/security.d.ts +44 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +184 -0
- package/dist/security.js.map +1 -0
- package/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +320 -0
- package/dist/server.js.map +1 -0
- package/dist/shell-safe.d.ts +15 -0
- package/dist/shell-safe.d.ts.map +1 -0
- package/dist/shell-safe.js +20 -0
- package/dist/shell-safe.js.map +1 -0
- package/dist/types.d.ts +365 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/logo.svg +81 -0
- package/package.json +85 -0
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli-commands.ts — Command implementations for the kosha CLI.
|
|
3
|
+
*
|
|
4
|
+
* Each exported `cmd*` function corresponds to a top-level CLI sub-command.
|
|
5
|
+
* Formatting utilities are imported from `./cli-format.js` to keep
|
|
6
|
+
* presentation logic separate from command orchestration.
|
|
7
|
+
*
|
|
8
|
+
* @module cli-commands
|
|
9
|
+
*/
|
|
10
|
+
import { BOLD, CYAN, DIM, GREEN, MAGENTA, RED, YELLOW, c, formatContextWindow, formatNumber, formatPrice, formatTimestamp, line, renderTable, } from "./cli-format.js";
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
const _require = createRequire(import.meta.url);
|
|
13
|
+
const VERSION = _require("../package.json").version;
|
|
14
|
+
/**
|
|
15
|
+
* Standard column layout reused by both `cmdList` and `cmdSearch`.
|
|
16
|
+
* Extracted to avoid duplicating the same six-column definition.
|
|
17
|
+
*/
|
|
18
|
+
const MODEL_TABLE_COLUMNS = [
|
|
19
|
+
{ header: "Provider", width: 12 },
|
|
20
|
+
{ header: "Model", width: 38 },
|
|
21
|
+
{ header: "Mode", width: 10 },
|
|
22
|
+
{ header: "Context", width: 10 },
|
|
23
|
+
{ header: "$/M in", width: 8, align: "right" },
|
|
24
|
+
{ header: "$/M out", width: 8, align: "right" },
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* Return a human-readable label for a provider's credential source.
|
|
28
|
+
* Providers that need no remote auth (e.g. local Ollama) show `"none (local)"`.
|
|
29
|
+
*
|
|
30
|
+
* @param provider The provider info object.
|
|
31
|
+
* @returns A short label suitable for table display.
|
|
32
|
+
*/
|
|
33
|
+
function formatCredentialSource(provider) {
|
|
34
|
+
if (!provider.credentialSource || provider.credentialSource === "none") {
|
|
35
|
+
return c(DIM, "none (local)");
|
|
36
|
+
}
|
|
37
|
+
return `${provider.credentialSource}`;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ensure the registry has at least one provider loaded.
|
|
41
|
+
* If empty (first run or stale cache), triggers a full discovery pass.
|
|
42
|
+
*
|
|
43
|
+
* @param registry The shared model registry instance.
|
|
44
|
+
*/
|
|
45
|
+
async function ensureDiscovered(registry) {
|
|
46
|
+
if (registry.providers_list().length === 0) {
|
|
47
|
+
console.log(c(DIM, "No cached data. Running discovery..."));
|
|
48
|
+
await registry.discover();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build a model-table row from a model card (shared by list/search).
|
|
53
|
+
* @param m A model card object.
|
|
54
|
+
* @returns An array of formatted cell strings.
|
|
55
|
+
*/
|
|
56
|
+
function modelRow(m) {
|
|
57
|
+
return [
|
|
58
|
+
c(CYAN, m.provider), m.id, m.mode,
|
|
59
|
+
formatContextWindow(m.contextWindow),
|
|
60
|
+
formatPrice(m.pricing?.inputPerMillion),
|
|
61
|
+
formatPrice(m.pricing?.outputPerMillion),
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
function parseNumberFlag(value) {
|
|
65
|
+
if (typeof value !== "string")
|
|
66
|
+
return undefined;
|
|
67
|
+
const n = Number(value);
|
|
68
|
+
if (!Number.isFinite(n))
|
|
69
|
+
return undefined;
|
|
70
|
+
return n;
|
|
71
|
+
}
|
|
72
|
+
/** Print a provider summary line (used by discover & refresh). */
|
|
73
|
+
function printProviderSummary(providers) {
|
|
74
|
+
let totalModels = 0;
|
|
75
|
+
for (const p of providers) {
|
|
76
|
+
totalModels += p.models.length;
|
|
77
|
+
// Three-state indicator: green ✓ if models found, yellow ○ if no creds but
|
|
78
|
+
// provider is optional (e.g. Ollama not running), red ✗ if auth required but missing
|
|
79
|
+
const status = p.models.length > 0
|
|
80
|
+
? c(GREEN, "\u2713")
|
|
81
|
+
: p.credentialSource === "none"
|
|
82
|
+
? c(YELLOW, "\u25CB")
|
|
83
|
+
: c(RED, "\u2717");
|
|
84
|
+
const hint = p.models.length === 0 && p.credentialSource !== "none" && !p.authenticated
|
|
85
|
+
? c(DIM, " (no credentials)")
|
|
86
|
+
: p.models.length === 0 && p.credentialSource === "none"
|
|
87
|
+
? c(DIM, " (not running)")
|
|
88
|
+
: "";
|
|
89
|
+
console.log(` ${status} ${c(CYAN, p.name)}: ${p.models.length} models${hint}`);
|
|
90
|
+
}
|
|
91
|
+
console.log(c(DIM, "\n" + line("\u2500", 50)));
|
|
92
|
+
console.log(`${c(BOLD, String(totalModels))} models from ${c(BOLD, String(providers.length))} providers`);
|
|
93
|
+
}
|
|
94
|
+
// ── discover ─────────────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Discover all configured providers and print a summary.
|
|
97
|
+
* With `--json` the entire registry snapshot is emitted as JSON.
|
|
98
|
+
*
|
|
99
|
+
* @param registry The model registry to discover into.
|
|
100
|
+
* @param flags CLI flags (supports `--json`).
|
|
101
|
+
*/
|
|
102
|
+
export async function cmdDiscover(registry, flags) {
|
|
103
|
+
console.log(c(DIM, "Discovering providers and models..."));
|
|
104
|
+
const providers = await registry.discover();
|
|
105
|
+
if (flags.json) {
|
|
106
|
+
console.log(JSON.stringify(registry.toJSON(), null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
printProviderSummary(providers);
|
|
110
|
+
}
|
|
111
|
+
// ── latest ───────────────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Fetch the latest provider/model details by forcing live discovery.
|
|
114
|
+
*
|
|
115
|
+
* Unlike regular list/search commands, this bypasses cache and always
|
|
116
|
+
* performs a fresh discovery pass (plus LiteLLM enrichment).
|
|
117
|
+
*
|
|
118
|
+
* @param registry The model registry to discover into.
|
|
119
|
+
* @param flags CLI flags (supports `--provider`, `--json`).
|
|
120
|
+
*/
|
|
121
|
+
export async function cmdLatest(registry, flags) {
|
|
122
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
123
|
+
console.log(c(DIM, provider
|
|
124
|
+
? `Fetching latest details for provider "${provider}"...`
|
|
125
|
+
: "Fetching latest details for all providers..."));
|
|
126
|
+
const result = await registry.fetchLatestDetails({
|
|
127
|
+
providers: provider ? [provider] : undefined,
|
|
128
|
+
});
|
|
129
|
+
if (flags.json) {
|
|
130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
printProviderSummary(result.providers);
|
|
134
|
+
console.log(c(DIM, `\nFetched at: ${formatTimestamp(result.discoveredAt)}`));
|
|
135
|
+
}
|
|
136
|
+
// ── list ─────────────────────────────────────────────────────────────────
|
|
137
|
+
/**
|
|
138
|
+
* List all known models in a formatted table.
|
|
139
|
+
* Supports filtering by `--provider`, `--origin`, `--mode`, and `--capability`.
|
|
140
|
+
*
|
|
141
|
+
* @param registry The model registry to query.
|
|
142
|
+
* @param flags CLI flags (supports `--provider`, `--origin`, `--mode`, `--capability`, `--json`).
|
|
143
|
+
*/
|
|
144
|
+
export async function cmdList(registry, flags) {
|
|
145
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
146
|
+
const originProvider = typeof flags.origin === "string" ? flags.origin : undefined;
|
|
147
|
+
const mode = typeof flags.mode === "string" ? flags.mode : undefined;
|
|
148
|
+
const capability = typeof flags.capability === "string" ? flags.capability : undefined;
|
|
149
|
+
await ensureDiscovered(registry);
|
|
150
|
+
const models = registry.models({ provider, originProvider, mode, capability });
|
|
151
|
+
if (flags.json) {
|
|
152
|
+
console.log(JSON.stringify(models, null, 2));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (models.length === 0) {
|
|
156
|
+
console.log(c(YELLOW, "No models found matching the given filters."));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
console.log(renderTable(MODEL_TABLE_COLUMNS, models.map(modelRow)));
|
|
160
|
+
const providerCount = new Set(models.map((m) => m.provider)).size;
|
|
161
|
+
console.log(c(DIM, line("\u2500", 90)));
|
|
162
|
+
console.log(`${c(BOLD, String(models.length))} models from ${c(BOLD, String(providerCount))} providers`);
|
|
163
|
+
}
|
|
164
|
+
// ── roles ────────────────────────────────────────────────────────────────
|
|
165
|
+
/**
|
|
166
|
+
* Show a provider -> model -> roles matrix.
|
|
167
|
+
*
|
|
168
|
+
* Useful for assistants that need to answer: "which providers/models can do X?"
|
|
169
|
+
*/
|
|
170
|
+
export async function cmdRoles(registry, flags) {
|
|
171
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
172
|
+
const originProvider = typeof flags.origin === "string" ? flags.origin : undefined;
|
|
173
|
+
const mode = typeof flags.mode === "string" ? flags.mode : undefined;
|
|
174
|
+
const capability = typeof flags.capability === "string" ? flags.capability : undefined;
|
|
175
|
+
const role = typeof flags.role === "string" ? flags.role : undefined;
|
|
176
|
+
await ensureDiscovered(registry);
|
|
177
|
+
const providers = registry.providerRoles({ provider, originProvider, mode, capability, role });
|
|
178
|
+
if (flags.json) {
|
|
179
|
+
console.log(JSON.stringify({
|
|
180
|
+
providers,
|
|
181
|
+
count: providers.length,
|
|
182
|
+
modelCount: providers.reduce((sum, p) => sum + p.models.length, 0),
|
|
183
|
+
missingCredentials: registry.missingCredentialPrompts(providers.map((p) => p.id)),
|
|
184
|
+
}, null, 2));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (providers.length === 0) {
|
|
188
|
+
console.log(c(YELLOW, "No provider/model roles found for the given filters."));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const columns = [
|
|
192
|
+
{ header: "Provider", width: 12 },
|
|
193
|
+
{ header: "Model", width: 40 },
|
|
194
|
+
{ header: "Mode", width: 10 },
|
|
195
|
+
{ header: "Roles", width: 56 },
|
|
196
|
+
];
|
|
197
|
+
const rows = providers.flatMap((providerInfo) => providerInfo.models.map((model) => [
|
|
198
|
+
c(CYAN, providerInfo.id),
|
|
199
|
+
model.id,
|
|
200
|
+
model.mode,
|
|
201
|
+
model.roles.join(", "),
|
|
202
|
+
]));
|
|
203
|
+
console.log(renderTable(columns, rows));
|
|
204
|
+
const missing = registry.missingCredentialPrompts(providers.map((p) => p.id));
|
|
205
|
+
if (missing.length > 0) {
|
|
206
|
+
console.log(c(DIM, "\nMissing provider credentials:"));
|
|
207
|
+
for (const prompt of missing) {
|
|
208
|
+
console.log(` ${c(YELLOW, prompt.providerId)}: ${prompt.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// ── cheapest ─────────────────────────────────────────────────────────────
|
|
213
|
+
/**
|
|
214
|
+
* Return cheapest model candidates for a requested role/capability.
|
|
215
|
+
*/
|
|
216
|
+
export async function cmdCheapest(registry, flags) {
|
|
217
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
218
|
+
const originProvider = typeof flags.origin === "string" ? flags.origin : undefined;
|
|
219
|
+
const mode = typeof flags.mode === "string" ? flags.mode : undefined;
|
|
220
|
+
const capability = typeof flags.capability === "string" ? flags.capability : undefined;
|
|
221
|
+
const role = typeof flags.role === "string" ? flags.role : undefined;
|
|
222
|
+
await ensureDiscovered(registry);
|
|
223
|
+
const result = registry.cheapestModels({
|
|
224
|
+
provider,
|
|
225
|
+
originProvider,
|
|
226
|
+
mode,
|
|
227
|
+
capability,
|
|
228
|
+
role,
|
|
229
|
+
limit: parseNumberFlag(flags.limit),
|
|
230
|
+
priceMetric: typeof flags["price-metric"] === "string"
|
|
231
|
+
? flags["price-metric"]
|
|
232
|
+
: undefined,
|
|
233
|
+
inputWeight: parseNumberFlag(flags["input-weight"]),
|
|
234
|
+
outputWeight: parseNumberFlag(flags["output-weight"]),
|
|
235
|
+
includeUnpriced: flags["include-unpriced"] === true,
|
|
236
|
+
});
|
|
237
|
+
if (flags.json) {
|
|
238
|
+
console.log(JSON.stringify(result, null, 2));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (result.matches.length === 0) {
|
|
242
|
+
console.log(c(YELLOW, "No priced models found for the requested filters."));
|
|
243
|
+
if (result.missingCredentials.length > 0) {
|
|
244
|
+
console.log(c(DIM, "\nMissing provider credentials:"));
|
|
245
|
+
for (const prompt of result.missingCredentials) {
|
|
246
|
+
console.log(` ${c(YELLOW, prompt.providerId)}: ${prompt.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const columns = [
|
|
252
|
+
{ header: "Provider", width: 12 },
|
|
253
|
+
{ header: "Model", width: 38 },
|
|
254
|
+
{ header: "Mode", width: 10 },
|
|
255
|
+
{ header: "Metric", width: 8 },
|
|
256
|
+
{ header: "Score", width: 10, align: "right" },
|
|
257
|
+
{ header: "$/M in", width: 8, align: "right" },
|
|
258
|
+
{ header: "$/M out", width: 8, align: "right" },
|
|
259
|
+
];
|
|
260
|
+
const rows = result.matches.map((match) => [
|
|
261
|
+
c(CYAN, match.model.provider),
|
|
262
|
+
match.model.id,
|
|
263
|
+
match.model.mode,
|
|
264
|
+
match.priceMetric,
|
|
265
|
+
match.score === undefined ? "\u2014" : formatPrice(match.score),
|
|
266
|
+
formatPrice(match.model.pricing?.inputPerMillion),
|
|
267
|
+
formatPrice(match.model.pricing?.outputPerMillion),
|
|
268
|
+
]);
|
|
269
|
+
console.log(renderTable(columns, rows));
|
|
270
|
+
console.log(c(DIM, `\n${result.pricedCandidates}/${result.candidates} candidates had usable pricing.`));
|
|
271
|
+
if (result.missingCredentials.length > 0) {
|
|
272
|
+
console.log(c(DIM, "\nMissing provider credentials:"));
|
|
273
|
+
for (const prompt of result.missingCredentials) {
|
|
274
|
+
console.log(` ${c(YELLOW, prompt.providerId)}: ${prompt.message}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// ── capabilities ─────────────────────────────────────────────────────────
|
|
279
|
+
/**
|
|
280
|
+
* Show an aggregated capability overview across all discovered models.
|
|
281
|
+
* Each row shows a capability, how many models/providers support it,
|
|
282
|
+
* and an example model ID for quick reference.
|
|
283
|
+
*
|
|
284
|
+
* @param registry The model registry to query.
|
|
285
|
+
* @param flags CLI flags (supports `--provider`, `--json`).
|
|
286
|
+
*/
|
|
287
|
+
export async function cmdCapabilities(registry, flags) {
|
|
288
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
289
|
+
await ensureDiscovered(registry);
|
|
290
|
+
const caps = registry.capabilities({ provider });
|
|
291
|
+
if (flags.json) {
|
|
292
|
+
console.log(JSON.stringify(caps, null, 2));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (caps.length === 0) {
|
|
296
|
+
console.log(c(YELLOW, "No capabilities found."));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const columns = [
|
|
300
|
+
{ header: "Capability", width: 20 },
|
|
301
|
+
{ header: "Models", width: 8, align: "right" },
|
|
302
|
+
{ header: "Providers", width: 10, align: "right" },
|
|
303
|
+
{ header: "Example Model", width: 38 },
|
|
304
|
+
];
|
|
305
|
+
const rows = caps.map((cap) => [
|
|
306
|
+
c(CYAN, cap.capability),
|
|
307
|
+
String(cap.modelCount),
|
|
308
|
+
String(cap.providerCount),
|
|
309
|
+
cap.exampleModelId ?? c(DIM, "—"),
|
|
310
|
+
]);
|
|
311
|
+
const totalModels = registry.models({ provider }).length;
|
|
312
|
+
console.log(renderTable(columns, rows));
|
|
313
|
+
console.log(c(DIM, `\n${caps.length} capabilities across ${totalModels} models`));
|
|
314
|
+
}
|
|
315
|
+
// ── capable ──────────────────────────────────────────────────────────────
|
|
316
|
+
/**
|
|
317
|
+
* Show models that support a given capability/role.
|
|
318
|
+
* Normalizes the query so aliases work: "embeddings", "stt", "tools", "vision".
|
|
319
|
+
*
|
|
320
|
+
* @param registry The model registry to query.
|
|
321
|
+
* @param query The capability query (e.g. "vision", "embeddings", "tools").
|
|
322
|
+
* @param flags CLI flags (supports `--provider`, `--origin`, `--json`, `--limit`).
|
|
323
|
+
*/
|
|
324
|
+
export async function cmdCapable(registry, query, flags) {
|
|
325
|
+
if (!query) {
|
|
326
|
+
console.error(c(RED, "Usage: kosha capable <capability>"));
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
330
|
+
const originProvider = typeof flags.origin === "string" ? flags.origin : undefined;
|
|
331
|
+
const mode = typeof flags.mode === "string" ? flags.mode : undefined;
|
|
332
|
+
const limit = parseNumberFlag(flags.limit);
|
|
333
|
+
await ensureDiscovered(registry);
|
|
334
|
+
const normalized = registry.normalizeRoleToken(query);
|
|
335
|
+
const allModels = registry.models({ provider, originProvider, mode });
|
|
336
|
+
let models = allModels.filter((m) => registry.modelSupportsRole(m, normalized));
|
|
337
|
+
if (limit !== undefined && limit > 0) {
|
|
338
|
+
models = models.slice(0, limit);
|
|
339
|
+
}
|
|
340
|
+
if (flags.json) {
|
|
341
|
+
console.log(JSON.stringify(models, null, 2));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (models.length === 0) {
|
|
345
|
+
console.log(c(YELLOW, `No models found with capability "${query}"${normalized !== query ? ` (normalized: "${normalized}")` : ""}`));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const header = normalized !== query
|
|
349
|
+
? `Models with capability ${c(CYAN, normalized)} ${c(DIM, `(from "${query}")`)}`
|
|
350
|
+
: `Models with capability ${c(CYAN, normalized)}`;
|
|
351
|
+
console.log(`\n${c(BOLD, header)}\n`);
|
|
352
|
+
console.log(renderTable(MODEL_TABLE_COLUMNS, models.map(modelRow)));
|
|
353
|
+
const providerCount = new Set(models.map((m) => m.provider)).size;
|
|
354
|
+
console.log(c(DIM, line("\u2500", 90)));
|
|
355
|
+
console.log(`${c(BOLD, String(models.length))} models from ${c(BOLD, String(providerCount))} providers`);
|
|
356
|
+
}
|
|
357
|
+
// ── search ───────────────────────────────────────────────────────────────
|
|
358
|
+
/**
|
|
359
|
+
* Search for models whose id, name, or aliases contain the query string.
|
|
360
|
+
* Case-insensitive substring matching. Optionally pre-filtered by `--origin`.
|
|
361
|
+
*
|
|
362
|
+
* @param registry The model registry to search.
|
|
363
|
+
* @param query The search term (substring).
|
|
364
|
+
* @param flags CLI flags (supports `--origin`, `--json`).
|
|
365
|
+
*/
|
|
366
|
+
export async function cmdSearch(registry, query, flags) {
|
|
367
|
+
if (!query) {
|
|
368
|
+
console.error(c(RED, "Usage: kosha search <query>"));
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
await ensureDiscovered(registry);
|
|
372
|
+
const needle = query.toLowerCase();
|
|
373
|
+
const originProvider = typeof flags.origin === "string" ? flags.origin : undefined;
|
|
374
|
+
// Apply optional origin filter first, then substring-match across id, name, and aliases
|
|
375
|
+
const matches = registry.models({ originProvider }).filter((m) => m.id.toLowerCase().includes(needle) ||
|
|
376
|
+
m.name.toLowerCase().includes(needle) ||
|
|
377
|
+
m.aliases.some((a) => a.toLowerCase().includes(needle)));
|
|
378
|
+
if (flags.json) {
|
|
379
|
+
console.log(JSON.stringify(matches, null, 2));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (matches.length === 0) {
|
|
383
|
+
console.log(c(YELLOW, `No models matching "${query}"`));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
console.log(renderTable(MODEL_TABLE_COLUMNS, matches.map(modelRow)));
|
|
387
|
+
console.log(c(DIM, `\n${matches.length} result${matches.length !== 1 ? "s" : ""} for "${query}"`));
|
|
388
|
+
}
|
|
389
|
+
// ── model (detail view) ─────────────────────────────────────────────────
|
|
390
|
+
/**
|
|
391
|
+
* Show detailed information for a single model identified by id or alias.
|
|
392
|
+
*
|
|
393
|
+
* If an exact match is not found, falls back to fuzzy substring search.
|
|
394
|
+
* Single fuzzy match is shown automatically; multiple matches produce a
|
|
395
|
+
* disambiguation list (capped at 5 suggestions).
|
|
396
|
+
*
|
|
397
|
+
* @param registry The model registry to look up.
|
|
398
|
+
* @param idOrAlias A model id, display name, or alias.
|
|
399
|
+
* @param flags CLI flags (supports `--json`).
|
|
400
|
+
*/
|
|
401
|
+
export async function cmdModel(registry, idOrAlias, flags) {
|
|
402
|
+
if (!idOrAlias) {
|
|
403
|
+
console.error(c(RED, "Usage: kosha model <id|alias>"));
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
await ensureDiscovered(registry);
|
|
407
|
+
const model = registry.model(idOrAlias);
|
|
408
|
+
if (!model) {
|
|
409
|
+
// Fuzzy fallback — substring match across id, name, and aliases so users
|
|
410
|
+
// can type fragments like "sonnet" without knowing the full canonical ID.
|
|
411
|
+
const needle = idOrAlias.toLowerCase();
|
|
412
|
+
const fuzzy = registry.models().filter((m) => m.id.toLowerCase().includes(needle) ||
|
|
413
|
+
m.name.toLowerCase().includes(needle) ||
|
|
414
|
+
m.aliases.some((a) => a.toLowerCase().includes(needle)));
|
|
415
|
+
if (fuzzy.length === 1)
|
|
416
|
+
return cmdModel(registry, fuzzy[0].id, flags);
|
|
417
|
+
if (fuzzy.length > 1) {
|
|
418
|
+
console.log(c(YELLOW, `No exact match for "${idOrAlias}". Did you mean:`));
|
|
419
|
+
for (const m of fuzzy.slice(0, 5))
|
|
420
|
+
console.log(` ${c(CYAN, m.id)} (${m.provider})`);
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
console.error(c(RED, `Model not found: "${idOrAlias}"`));
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
if (flags.json) {
|
|
427
|
+
console.log(JSON.stringify(model, null, 2));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const providerName = registry.provider(model.provider)?.name ?? model.provider;
|
|
431
|
+
const pricingStr = model.pricing
|
|
432
|
+
? `${formatPrice(model.pricing.inputPerMillion)} / ${formatPrice(model.pricing.outputPerMillion)} per million tokens (in/out)`
|
|
433
|
+
: c(DIM, "unknown");
|
|
434
|
+
// Cache pricing is optional — only displayed when the model has it
|
|
435
|
+
const cacheStr = model.pricing?.cacheReadPerMillion !== undefined
|
|
436
|
+
? `\nCache Pricing: ${formatPrice(model.pricing.cacheReadPerMillion)} read / ${formatPrice(model.pricing.cacheWritePerMillion)} write per million tokens`
|
|
437
|
+
: "";
|
|
438
|
+
// Origin provider line: only shown when distinct from the serving-layer provider
|
|
439
|
+
const originLine = model.originProvider && model.originProvider !== model.provider
|
|
440
|
+
? `\n${c(BOLD, "Origin Provider:")} ${c(CYAN, model.originProvider)}`
|
|
441
|
+
: "";
|
|
442
|
+
// Region and project ID are only shown when present (Bedrock / Vertex models)
|
|
443
|
+
const regionLine = model.region ? `\n${c(BOLD, "Region:")} ${model.region}` : "";
|
|
444
|
+
const projectLine = model.projectId ? `\n${c(BOLD, "Project ID:")} ${model.projectId}` : "";
|
|
445
|
+
console.log(`
|
|
446
|
+
${c(BOLD, "Model:")} ${model.id}
|
|
447
|
+
${c(BOLD, "Name:")} ${model.name}
|
|
448
|
+
${c(BOLD, "Provider:")} ${providerName}${originLine}${regionLine}${projectLine}
|
|
449
|
+
${c(BOLD, "Mode:")} ${model.mode}
|
|
450
|
+
${c(BOLD, "Aliases:")} ${model.aliases.length > 0 ? model.aliases.join(", ") : c(DIM, "none")}
|
|
451
|
+
${c(BOLD, "Context Window:")} ${model.contextWindow > 0 ? formatNumber(model.contextWindow) + " tokens" : c(DIM, "unknown")}
|
|
452
|
+
${c(BOLD, "Max Output:")} ${model.maxOutputTokens > 0 ? formatNumber(model.maxOutputTokens) + " tokens" : c(DIM, "unknown")}${model.dimensions ? `\n${c(BOLD, "Dimensions:")} ${formatNumber(model.dimensions)}` : ""}
|
|
453
|
+
${c(BOLD, "Capabilities:")} ${model.capabilities.join(", ")}
|
|
454
|
+
${c(BOLD, "Pricing:")} ${pricingStr}${cacheStr}
|
|
455
|
+
${c(BOLD, "Source:")} ${model.source}
|
|
456
|
+
${c(BOLD, "Discovered:")} ${formatTimestamp(model.discoveredAt)}
|
|
457
|
+
`.trim());
|
|
458
|
+
}
|
|
459
|
+
// ── providers ────────────────────────────────────────────────────────────
|
|
460
|
+
/**
|
|
461
|
+
* List all known providers with their authentication status and model counts.
|
|
462
|
+
*
|
|
463
|
+
* @param registry The model registry to query.
|
|
464
|
+
* @param flags CLI flags (supports `--json`).
|
|
465
|
+
*/
|
|
466
|
+
export async function cmdProviders(registry, flags) {
|
|
467
|
+
await ensureDiscovered(registry);
|
|
468
|
+
const allProviders = registry.providers_list();
|
|
469
|
+
if (flags.json) {
|
|
470
|
+
console.log(JSON.stringify(allProviders, null, 2));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
if (allProviders.length === 0) {
|
|
474
|
+
console.log(c(YELLOW, "No providers found."));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const columns = [
|
|
478
|
+
{ header: "Provider", width: 13 },
|
|
479
|
+
{ header: "Status", width: 16 },
|
|
480
|
+
{ header: "Models", width: 7, align: "right" },
|
|
481
|
+
{ header: "Credential Source", width: 30 },
|
|
482
|
+
];
|
|
483
|
+
const rows = allProviders.map((p) => {
|
|
484
|
+
// Three-way status: authenticated, local (no creds needed), or missing
|
|
485
|
+
const status = p.authenticated
|
|
486
|
+
? c(GREEN, "\u2713 authenticated")
|
|
487
|
+
: p.credentialSource === "none"
|
|
488
|
+
? c(GREEN, "\u2713 local")
|
|
489
|
+
: c(RED, "\u2717 no credentials");
|
|
490
|
+
return [c(CYAN, p.id), status, String(p.models.length), formatCredentialSource(p)];
|
|
491
|
+
});
|
|
492
|
+
console.log(renderTable(columns, rows));
|
|
493
|
+
}
|
|
494
|
+
// ── resolve ──────────────────────────────────────────────────────────────
|
|
495
|
+
/**
|
|
496
|
+
* Resolve a model alias to its canonical provider ID.
|
|
497
|
+
* Unknown aliases are returned as-is with a yellow warning.
|
|
498
|
+
*
|
|
499
|
+
* @param registry The model registry containing alias mappings.
|
|
500
|
+
* @param alias The alias string to resolve.
|
|
501
|
+
* @param flags CLI flags (supports `--json`).
|
|
502
|
+
*/
|
|
503
|
+
export async function cmdResolve(registry, alias, flags) {
|
|
504
|
+
if (!alias) {
|
|
505
|
+
console.error(c(RED, "Usage: kosha resolve <alias>"));
|
|
506
|
+
process.exit(1);
|
|
507
|
+
}
|
|
508
|
+
await ensureDiscovered(registry);
|
|
509
|
+
const resolved = registry.resolve(alias);
|
|
510
|
+
if (flags.json) {
|
|
511
|
+
console.log(JSON.stringify({ alias, resolved }, null, 2));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (resolved === alias) {
|
|
515
|
+
// resolve() returns input unchanged when no alias mapping exists
|
|
516
|
+
console.log(c(YELLOW, `"${alias}" is not a known alias (returned as-is)`));
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
console.log(`${c(DIM, alias)} ${c(DIM, "\u2192")} ${c(CYAN, resolved)}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// ── routes ───────────────────────────────────────────────────────────────
|
|
523
|
+
/**
|
|
524
|
+
* Show every provider route through which a model can be accessed.
|
|
525
|
+
*
|
|
526
|
+
* A "route" is a serving-layer entry whose normalized model ID matches the
|
|
527
|
+
* given identifier. Output includes direct/preferred markers, origin provider,
|
|
528
|
+
* model version hint, and serving base URL.
|
|
529
|
+
*
|
|
530
|
+
* Pricing columns show `—` when data is unavailable.
|
|
531
|
+
*
|
|
532
|
+
* @param registry The model registry to query.
|
|
533
|
+
* @param modelId Canonical model ID or alias to look up.
|
|
534
|
+
* @param flags CLI flags (supports `--json`).
|
|
535
|
+
*/
|
|
536
|
+
export async function cmdRoutes(registry, modelId, flags) {
|
|
537
|
+
if (!modelId) {
|
|
538
|
+
console.error(c(RED, "Usage: kosha routes <model-id|alias>"));
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
await ensureDiscovered(registry);
|
|
542
|
+
const routes = registry.modelRouteInfo(modelId);
|
|
543
|
+
if (flags.json) {
|
|
544
|
+
console.log(JSON.stringify(routes, null, 2));
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (routes.length === 0) {
|
|
548
|
+
console.error(c(RED, `No routes found for model: "${modelId}"`));
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
const columns = [
|
|
552
|
+
{ header: "Pref", width: 4 },
|
|
553
|
+
{ header: "Provider", width: 14 },
|
|
554
|
+
{ header: "Model ID", width: 42 },
|
|
555
|
+
{ header: "Region", width: 14 },
|
|
556
|
+
{ header: "Origin", width: 12 },
|
|
557
|
+
{ header: "Ver", width: 12 },
|
|
558
|
+
{ header: "Base URL", width: 34 },
|
|
559
|
+
{ header: "$/M in", width: 8, align: "right" },
|
|
560
|
+
{ header: "$/M out", width: 8, align: "right" },
|
|
561
|
+
];
|
|
562
|
+
const rows = routes.map((route) => [
|
|
563
|
+
route.isPreferred ? c(GREEN, "*") : route.isDirect ? c(CYAN, "\u00B7") : " ",
|
|
564
|
+
c(CYAN, route.provider),
|
|
565
|
+
route.model.id,
|
|
566
|
+
route.model.region ?? c(DIM, "—"),
|
|
567
|
+
route.originProvider ?? c(DIM, "—"),
|
|
568
|
+
route.version ?? c(DIM, "—"),
|
|
569
|
+
route.baseUrl ?? c(DIM, "—"),
|
|
570
|
+
formatPrice(route.model.pricing?.inputPerMillion),
|
|
571
|
+
formatPrice(route.model.pricing?.outputPerMillion),
|
|
572
|
+
]);
|
|
573
|
+
console.log(`\n${c(BOLD, routes[0].model.name)} ${c(DIM, `(${routes.length} route${routes.length !== 1 ? "s" : ""})`)}\n`);
|
|
574
|
+
console.log(renderTable(columns, rows));
|
|
575
|
+
const preferred = routes.find((route) => route.isPreferred);
|
|
576
|
+
if (preferred) {
|
|
577
|
+
console.log(c(DIM, `\nPreferred route: ${preferred.provider} (${preferred.baseUrl ?? "base URL unknown"})`));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
// ── refresh ──────────────────────────────────────────────────────────────
|
|
581
|
+
/**
|
|
582
|
+
* Force a full re-discovery of all providers, bypassing the cache.
|
|
583
|
+
*
|
|
584
|
+
* @param registry The model registry to refresh.
|
|
585
|
+
* @param flags CLI flags (supports `--json`).
|
|
586
|
+
*/
|
|
587
|
+
export async function cmdRefresh(registry, flags) {
|
|
588
|
+
const provider = typeof flags.provider === "string" ? flags.provider : undefined;
|
|
589
|
+
console.log(c(DIM, provider ? `Refreshing provider "${provider}"...` : "Refreshing all providers..."));
|
|
590
|
+
await registry.refresh(provider);
|
|
591
|
+
if (flags.json) {
|
|
592
|
+
console.log(JSON.stringify({
|
|
593
|
+
...registry.toJSON(),
|
|
594
|
+
modelCount: registry.models().length,
|
|
595
|
+
}, null, 2));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const providers = registry.providers_list();
|
|
599
|
+
printProviderSummary(providers);
|
|
600
|
+
// Overwrite the last summary line to include "Refreshed:" prefix
|
|
601
|
+
// (printProviderSummary already printed the count, so we add context)
|
|
602
|
+
}
|
|
603
|
+
// ── serve ────────────────────────────────────────────────────────────────
|
|
604
|
+
/**
|
|
605
|
+
* Start the HTTP API server on the given port.
|
|
606
|
+
* The server module is loaded via dynamic import to avoid pulling in Hono
|
|
607
|
+
* for purely CLI-based usage.
|
|
608
|
+
*
|
|
609
|
+
* @param flags CLI flags (supports `--port <number>`, default `3000`).
|
|
610
|
+
*/
|
|
611
|
+
export async function cmdServe(flags) {
|
|
612
|
+
const port = typeof flags.port === "string" ? parseInt(flags.port, 10) : 3000;
|
|
613
|
+
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
614
|
+
console.error(c(RED, `Invalid port: ${flags.port}`));
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
// Dynamic import keeps Hono out of the critical path for non-serve commands
|
|
618
|
+
const { startServer } = await import("./server.js");
|
|
619
|
+
await startServer(port);
|
|
620
|
+
}
|
|
621
|
+
// ── help & version ───────────────────────────────────────────────────────
|
|
622
|
+
/** Print the full CLI usage / help text to stdout. */
|
|
623
|
+
export function showHelp() {
|
|
624
|
+
console.log(`
|
|
625
|
+
${c(BOLD, "kosha")} ${c(DIM, "\u2014 AI Model & Provider Discovery Registry")}
|
|
626
|
+
|
|
627
|
+
${c(BOLD, "USAGE")}
|
|
628
|
+
kosha <command> [options]
|
|
629
|
+
|
|
630
|
+
${c(BOLD, "COMMANDS")}
|
|
631
|
+
${c(CYAN, "discover")} Discover all providers and models
|
|
632
|
+
${c(CYAN, "list")} List all known models
|
|
633
|
+
--provider <name> Filter by serving-layer provider
|
|
634
|
+
--origin <name> Filter by origin/creator provider (e.g. anthropic)
|
|
635
|
+
--mode <mode> Filter by mode (chat, embedding, image, audio)
|
|
636
|
+
--capability <cap> Filter by capability (vision, function_calling, etc.)
|
|
637
|
+
${c(CYAN, "search")} <query> Search models by name/ID (fuzzy match)
|
|
638
|
+
--origin <name> Restrict search to a specific origin provider
|
|
639
|
+
${c(CYAN, "model")} <id|alias> Show detailed info for one model
|
|
640
|
+
${c(CYAN, "roles")} Show provider -> model -> roles matrix
|
|
641
|
+
--role <role> Filter by task role (e.g. embeddings, image, tool_use)
|
|
642
|
+
--provider <name> Filter by serving-layer provider
|
|
643
|
+
--origin <name> Filter by model creator provider
|
|
644
|
+
--mode <mode> Filter by mode (chat, embedding, image, audio, moderation)
|
|
645
|
+
--capability <cap> Filter by capability tag
|
|
646
|
+
${c(CYAN, "capabilities")} ${c(DIM, "(caps)")} Show all capabilities across the ecosystem
|
|
647
|
+
--provider <name> Scope to one provider
|
|
648
|
+
${c(CYAN, "capable")} <capability> List models with a given capability
|
|
649
|
+
--provider <name> Filter by serving-layer provider
|
|
650
|
+
--origin <name> Filter by origin/creator provider
|
|
651
|
+
--mode <mode> Filter by mode (chat, embedding, image, audio)
|
|
652
|
+
--limit <n> Maximum models to show
|
|
653
|
+
${c(CYAN, "cheapest")} Find cheapest eligible models
|
|
654
|
+
--role <role> Task role, e.g. embeddings or image
|
|
655
|
+
--capability <cap> Capability filter (vision, embedding, function_calling)
|
|
656
|
+
--mode <mode> Mode filter
|
|
657
|
+
--limit <n> Maximum matches to return (default 5)
|
|
658
|
+
--price-metric <metric> input | output | blended
|
|
659
|
+
--input-weight <n> Weight for blended metric input price
|
|
660
|
+
--output-weight <n> Weight for blended metric output price
|
|
661
|
+
--include-unpriced Include unpriced models after ranked matches
|
|
662
|
+
${c(CYAN, "routes")} <id|alias> Show all provider routes for a model
|
|
663
|
+
${c(CYAN, "providers")} List all providers and their status
|
|
664
|
+
${c(CYAN, "resolve")} <alias> Resolve an alias to canonical model ID
|
|
665
|
+
${c(CYAN, "latest")} Force-fetch latest model/provider details
|
|
666
|
+
--provider <name> Scope latest fetch to one provider
|
|
667
|
+
${c(CYAN, "refresh")} Force re-discover all providers (bypass cache)
|
|
668
|
+
--provider <name> Refresh only one provider
|
|
669
|
+
${c(CYAN, "serve")} [--port 3000] Start HTTP API server
|
|
670
|
+
|
|
671
|
+
${c(BOLD, "OPTIONS")}
|
|
672
|
+
--json Output as JSON (works with any command)
|
|
673
|
+
--help Show this help message
|
|
674
|
+
--version Show version
|
|
675
|
+
|
|
676
|
+
${c(BOLD, "EXAMPLES")}
|
|
677
|
+
${c(DIM, "$")} kosha discover
|
|
678
|
+
${c(DIM, "$")} kosha list --provider anthropic
|
|
679
|
+
${c(DIM, "$")} kosha list --origin anthropic
|
|
680
|
+
${c(DIM, "$")} kosha list --mode embedding --json
|
|
681
|
+
${c(DIM, "$")} kosha search gemini
|
|
682
|
+
${c(DIM, "$")} kosha search claude --origin anthropic
|
|
683
|
+
${c(DIM, "$")} kosha model sonnet
|
|
684
|
+
${c(DIM, "$")} kosha roles --role embeddings
|
|
685
|
+
${c(DIM, "$")} kosha capabilities
|
|
686
|
+
${c(DIM, "$")} kosha capable vision
|
|
687
|
+
${c(DIM, "$")} kosha capable embeddings --limit 5
|
|
688
|
+
${c(DIM, "$")} kosha cheapest --role image --limit 3
|
|
689
|
+
${c(DIM, "$")} kosha routes claude-opus-4-6
|
|
690
|
+
${c(DIM, "$")} kosha routes gpt-4o --json
|
|
691
|
+
${c(DIM, "$")} kosha providers
|
|
692
|
+
${c(DIM, "$")} kosha latest
|
|
693
|
+
${c(DIM, "$")} kosha latest --provider openai --json
|
|
694
|
+
${c(DIM, "$")} kosha resolve haiku
|
|
695
|
+
${c(DIM, "$")} kosha refresh --provider anthropic
|
|
696
|
+
${c(DIM, "$")} kosha serve --port 8080
|
|
697
|
+
`.trim());
|
|
698
|
+
}
|
|
699
|
+
/** Print the CLI version string to stdout. */
|
|
700
|
+
export function showVersion() {
|
|
701
|
+
console.log(`kosha-discovery v${VERSION}`);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Display a branded splash screen when `kosha` is invoked with no arguments.
|
|
705
|
+
*
|
|
706
|
+
* Shows the Kosha logo, tagline, version, and quick-start commands.
|
|
707
|
+
* Uses MAGENTA branding with a clean, minimal layout.
|
|
708
|
+
*/
|
|
709
|
+
export function showSplash() {
|
|
710
|
+
const brandWord = `${c(CYAN, "k")}${c(GREEN, "o")}${c(YELLOW, "s")}${c(MAGENTA, "h")}${c(RED, "a")}`;
|
|
711
|
+
const mascot1 = `${c(CYAN, " /\\_/\\ ")} ${c(DIM, "assistant mascot")}`;
|
|
712
|
+
const mascot2 = `${c(CYAN, "( o.o )")} ${c(DIM, "ready to route")}`;
|
|
713
|
+
const mascot3 = `${c(CYAN, " > ^ < ")} ${c(DIM, "providers + models")}`;
|
|
714
|
+
console.log(`
|
|
715
|
+
${c(MAGENTA, " ╔═══════════════════════════════════════════════════╗")}
|
|
716
|
+
${c(MAGENTA, " ║")} ${c(MAGENTA, "║")}
|
|
717
|
+
${c(MAGENTA, " ║")} ${c(BOLD, " █▄▀ █▀█ █▀ █ █ ▄▀█")} ${c(MAGENTA, "║")}
|
|
718
|
+
${c(MAGENTA, " ║")} ${c(BOLD, " █ █ █▄█ ▄█ █▀█ █▀█")} ${c(DIM, "कोश — treasury")} ${c(MAGENTA, "║")}
|
|
719
|
+
${c(MAGENTA, " ║")} ${c(MAGENTA, "║")}
|
|
720
|
+
${c(MAGENTA, " ║")} ${brandWord} ${c(DIM, "AI Model & Provider Discovery Registry")} ${c(MAGENTA, "║")}
|
|
721
|
+
${c(MAGENTA, " ║")} ${c(DIM, `v${VERSION}`)} ${c(MAGENTA, "║")}
|
|
722
|
+
${c(MAGENTA, " ║")} ${mascot1} ${c(MAGENTA, "║")}
|
|
723
|
+
${c(MAGENTA, " ║")} ${mascot2} ${c(MAGENTA, "║")}
|
|
724
|
+
${c(MAGENTA, " ║")} ${mascot3} ${c(MAGENTA, "║")}
|
|
725
|
+
${c(MAGENTA, " ║")} ${c(MAGENTA, "║")}
|
|
726
|
+
${c(MAGENTA, " ╚═══════════════════════════════════════════════════╝")}
|
|
727
|
+
|
|
728
|
+
${c(BOLD, "Quick start:")}
|
|
729
|
+
|
|
730
|
+
${c(CYAN, "kosha discover")} Scan all providers for models
|
|
731
|
+
${c(CYAN, "kosha list")} List all discovered models
|
|
732
|
+
${c(CYAN, "kosha search")} ${c(DIM, "<q>")} Search by name or ID
|
|
733
|
+
${c(CYAN, "kosha model")} ${c(DIM, "<id>")} Detailed info for one model
|
|
734
|
+
${c(CYAN, "kosha capabilities")} What capabilities exist?
|
|
735
|
+
${c(CYAN, "kosha capable")} ${c(DIM, "<cap>")} Models with a given capability
|
|
736
|
+
${c(CYAN, "kosha roles")} Provider -> model -> roles matrix
|
|
737
|
+
${c(CYAN, "kosha cheapest")} Cheapest models for a role
|
|
738
|
+
${c(CYAN, "kosha routes")} ${c(DIM, "<id>")} All provider routes for a model
|
|
739
|
+
${c(CYAN, "kosha providers")} Show provider status
|
|
740
|
+
${c(CYAN, "kosha latest")} Force-fetch latest provider/model details
|
|
741
|
+
${c(CYAN, "kosha serve")} Start the HTTP API server
|
|
742
|
+
|
|
743
|
+
${c(DIM, "Run")} ${c(CYAN, "kosha --help")} ${c(DIM, "for full usage.")}
|
|
744
|
+
`);
|
|
745
|
+
}
|
|
746
|
+
//# sourceMappingURL=cli-commands.js.map
|