@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Srinivas Pendela
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.svg" alt="Kosha — AI Model Discovery" width="140" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">kosha-discovery — कोश</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><strong>AI Model & Provider Discovery Registry</strong></p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@sriinnu/kosha-discovery"><img src="https://img.shields.io/npm/v/%40sriinnu%2Fkosha-discovery?color=7C3AED&label=npm" alt="npm version" /></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@sriinnu/kosha-discovery"><img src="https://img.shields.io/npm/dm/%40sriinnu%2Fkosha-discovery?color=0EA5E9&label=downloads" alt="npm downloads" /></a>
|
|
12
|
+
<a href="https://github.com/sriinnu/kosha-discovery/blob/main/LICENSE"><img src="https://img.shields.io/github/license/sriinnu/kosha-discovery?color=F59E0B" alt="license" /></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/@sriinnu/kosha-discovery"><img src="https://img.shields.io/node/v/%40sriinnu%2Fkosha-discovery?color=5B21B6" alt="node version" /></a>
|
|
14
|
+
<a href="https://github.com/sriinnu/kosha-discovery/actions/workflows/release-npm.yml"><img src="https://img.shields.io/github/actions/workflow/status/sriinnu/kosha-discovery/release-npm.yml?label=release%20workflow" alt="release workflow status" /></a>
|
|
15
|
+
<a href="https://github.com/sriinnu/kosha-discovery/actions/workflows/update-kosha-snapshot.yml"><img src="https://img.shields.io/github/actions/workflow/status/sriinnu/kosha-discovery/update-kosha-snapshot.yml?label=snapshot%20workflow" alt="snapshot workflow status" /></a>
|
|
16
|
+
<a href="https://github.com/sriinnu/kosha-discovery/actions/workflows/provider-smoke.yml"><img src="https://img.shields.io/github/actions/workflow/status/sriinnu/kosha-discovery/provider-smoke.yml?label=provider%20smoke" alt="provider smoke status" /></a>
|
|
17
|
+
<a href="https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json"><img src="https://img.shields.io/badge/pricing%20source-LiteLLM-2563EB" alt="LiteLLM pricing source" /></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
Kosha (कोश — treasury/repository) automatically discovers AI models across providers, resolves credentials from CLI tools and environment variables, enriches models with pricing data, and exposes the catalog via library, CLI, and HTTP API.
|
|
21
|
+
|
|
22
|
+
## Why
|
|
23
|
+
|
|
24
|
+
AI applications hardcode model IDs, pricing, and provider configs. When providers add models or change pricing, every app breaks. Kosha solves this:
|
|
25
|
+
|
|
26
|
+
- **Dynamic discovery** — fetches real model lists from provider APIs
|
|
27
|
+
- **Smart credentials** — finds API keys from env vars, CLI tools (Claude, Copilot, Gemini CLI), and config files
|
|
28
|
+
- **Pricing enrichment** — fills in costs and context windows from litellm's community-maintained dataset
|
|
29
|
+
- **Model aliases** — `sonnet` → `claude-sonnet-4-20250514`, updated as models evolve
|
|
30
|
+
- **Role matrix** — query provider -> model -> roles (`chat`, `embedding`, `image_generation`, etc.)
|
|
31
|
+
- **Cheapest routing** — rank cheapest eligible models for tasks like embeddings or image generation
|
|
32
|
+
- **Local LLM scanning** — detects Ollama models alongside cloud providers
|
|
33
|
+
- **Three access patterns** — use as a library, CLI tool, or HTTP API
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm add kosha-discovery
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Development (pnpm)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm install
|
|
45
|
+
pnpm run build
|
|
46
|
+
pnpm run test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
### Library
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { createKosha } from "kosha-discovery";
|
|
55
|
+
|
|
56
|
+
const kosha = await createKosha();
|
|
57
|
+
|
|
58
|
+
const models = kosha.models(); // all models
|
|
59
|
+
const embeddings = kosha.models({ mode: "embedding" }); // filter by mode
|
|
60
|
+
const model = kosha.model("sonnet"); // resolve alias
|
|
61
|
+
const cheapest = kosha.cheapestModels({ role: "image", limit: 3 });
|
|
62
|
+
|
|
63
|
+
console.log(model.pricing); // { inputPerMillion: 3, outputPerMillion: 15, ... }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### CLI
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
kosha discover # discover all providers
|
|
70
|
+
kosha list # list models
|
|
71
|
+
kosha list --provider anthropic # filter by provider
|
|
72
|
+
kosha search gemini # fuzzy search
|
|
73
|
+
kosha model sonnet # model details
|
|
74
|
+
kosha cheapest --role embeddings # cheapest for a task
|
|
75
|
+
kosha routes gpt-4o # all provider routes
|
|
76
|
+
kosha providers # provider status
|
|
77
|
+
kosha latest # force-fetch latest provider/model details
|
|
78
|
+
kosha latest --provider openai # latest for one provider
|
|
79
|
+
kosha serve --port 3000 # start HTTP API
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Auto-Fetch JSON Snapshot
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# one-shot latest snapshot
|
|
86
|
+
pnpm run autofetch:once
|
|
87
|
+
|
|
88
|
+
# custom output/provider
|
|
89
|
+
pnpm run autofetch:once -- --provider openai --output ./data/openai-latest.json
|
|
90
|
+
|
|
91
|
+
# continuous loop (default 3600s)
|
|
92
|
+
pnpm run autofetch -- --interval-seconds 900
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
By default this writes JSON to `./data/kosha-latest.json`.
|
|
96
|
+
|
|
97
|
+
### CI / Smoke Checks
|
|
98
|
+
|
|
99
|
+
Workflow files:
|
|
100
|
+
|
|
101
|
+
- `.github/workflows/update-kosha-snapshot.yml`
|
|
102
|
+
- `.github/workflows/provider-smoke.yml`
|
|
103
|
+
|
|
104
|
+
Snapshot workflow:
|
|
105
|
+
|
|
106
|
+
- Scheduled: weekly (Monday 06:00 UTC)
|
|
107
|
+
- Manual: GitHub UI -> Actions -> `Update Kosha Snapshot` -> `Run workflow`
|
|
108
|
+
- Optional manual inputs:
|
|
109
|
+
- `provider` (empty = all providers)
|
|
110
|
+
- `output` (default `data/kosha-latest.json`)
|
|
111
|
+
- Disable scheduled runs without removing the workflow by setting `KOSHA_SNAPSHOT_SCHEDULE_ENABLED=false`
|
|
112
|
+
- Manual `Run workflow` still works even when schedule is disabled
|
|
113
|
+
|
|
114
|
+
Provider smoke workflow:
|
|
115
|
+
|
|
116
|
+
- Scheduled: nightly (03:00 UTC)
|
|
117
|
+
- Manual: GitHub UI -> Actions -> `Provider Smoke Checks` -> `Run workflow`
|
|
118
|
+
- Runs only when repository variable `KOSHA_PROVIDER_SMOKE_ENABLED=true`
|
|
119
|
+
- Manual dispatch can override that gate with `force=true`
|
|
120
|
+
- Installs with pnpm, builds, then runs a node inline smoke script against real provider endpoints
|
|
121
|
+
- Providers without the required secrets are skipped instead of failing the job
|
|
122
|
+
- Always uploads `artifacts/provider-smoke-report.json`
|
|
123
|
+
|
|
124
|
+
Provider smoke secrets:
|
|
125
|
+
|
|
126
|
+
- OpenAI: `OPENAI_API_KEY`
|
|
127
|
+
- Google/Gemini: `GOOGLE_API_KEY` or `GEMINI_API_KEY`
|
|
128
|
+
- Mistral: `MISTRAL_API_KEY`
|
|
129
|
+
- DeepSeek: `DEEPSEEK_API_KEY`
|
|
130
|
+
- Moonshot: `MOONSHOT_API_KEY` or `KIMI_API_KEY`
|
|
131
|
+
- GLM: `GLM_API_KEY` or `ZHIPUAI_API_KEY`
|
|
132
|
+
- Z.AI: `ZAI_API_KEY`
|
|
133
|
+
- MiniMax: `MINIMAX_API_KEY`
|
|
134
|
+
- OpenRouter: optional `OPENROUTER_API_KEY`
|
|
135
|
+
- Bedrock: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_DEFAULT_REGION`
|
|
136
|
+
- Vertex AI: `GOOGLE_APPLICATION_CREDENTIALS_JSON`, `GOOGLE_CLOUD_PROJECT`
|
|
137
|
+
|
|
138
|
+
Security controls in these workflows:
|
|
139
|
+
|
|
140
|
+
- Snapshot workflow commits only the configured snapshot file path plus its checksum file, never broad `git add -A`
|
|
141
|
+
- Snapshot workflow validates the generated snapshot against a local JSON schema before commit
|
|
142
|
+
- Snapshot workflow runs a high-signal secret-pattern scan on snapshot output before commit
|
|
143
|
+
- Snapshot workflow writes `data/kosha-latest.sha256` alongside the snapshot
|
|
144
|
+
- Snapshot workflow uploads an always-on artifact with run metadata, provider summaries, and failure details
|
|
145
|
+
- Provider smoke workflow never echoes secret values and records a machine-readable JSON report
|
|
146
|
+
|
|
147
|
+
### Branch Protection Audit
|
|
148
|
+
|
|
149
|
+
Workflow file: `.github/workflows/branch-protection-check.yml`
|
|
150
|
+
|
|
151
|
+
- Runs on `main` and via `workflow_dispatch`
|
|
152
|
+
- Reads the `main` branch protection rule through the GitHub API
|
|
153
|
+
- Expects the required status checks list to include `Branch Protection Check / audit`
|
|
154
|
+
- Uploads a warning artifact instead of failing when the token cannot read branch protection settings
|
|
155
|
+
|
|
156
|
+
Keep that check name stable when you rename the workflow or job, and update the protection rule whenever you add more required checks.
|
|
157
|
+
|
|
158
|
+
### Where Data Is Stored
|
|
159
|
+
|
|
160
|
+
- Git repo: model/provider discovery data is **not** committed by default.
|
|
161
|
+
- Runtime cache: `~/.kosha/cache/*.json` (machine-local, TTL-based).
|
|
162
|
+
- Exported snapshot: only if you run `autofetch`/`autofetch:once` with an output file and commit it yourself.
|
|
163
|
+
|
|
164
|
+
### HTTP API
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
kosha serve --port 3000
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
GET /api/models — All models (filterable)
|
|
172
|
+
GET /api/models/cheapest — Cheapest ranked models
|
|
173
|
+
GET /api/models/:idOrAlias — Single model
|
|
174
|
+
GET /api/models/:idOrAlias/routes — All provider routes
|
|
175
|
+
GET /api/roles — Provider → model → roles matrix
|
|
176
|
+
GET /api/providers — All providers
|
|
177
|
+
POST /api/refresh — Re-discover
|
|
178
|
+
GET /health — Health check
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Supported Providers
|
|
182
|
+
|
|
183
|
+
| Provider | Discovery | Credential Sources |
|
|
184
|
+
|----------|-----------|-------------------|
|
|
185
|
+
| Anthropic | API (`/v1/models`) | `ANTHROPIC_API_KEY`, Claude CLI, Codex CLI |
|
|
186
|
+
| OpenAI | API (`/v1/models`) | `OPENAI_API_KEY`, GitHub Copilot tokens |
|
|
187
|
+
| Google | API (`/v1beta/models`) | `GOOGLE_API_KEY`, `GEMINI_API_KEY`, Gemini CLI, gcloud |
|
|
188
|
+
| AWS Bedrock | SDK → CLI → static | `AWS_ACCESS_KEY_ID`, `~/.aws/credentials`, SSO, IAM |
|
|
189
|
+
| Vertex AI | API + gcloud | `GOOGLE_APPLICATION_CREDENTIALS`, gcloud ADC |
|
|
190
|
+
| Ollama | Local API | None needed (local) |
|
|
191
|
+
| OpenRouter | API | `OPENROUTER_API_KEY` (optional) |
|
|
192
|
+
| NVIDIA | API | `NVIDIA_API_KEY` |
|
|
193
|
+
| Together AI | API | `TOGETHER_API_KEY` |
|
|
194
|
+
| Fireworks AI | API | `FIREWORKS_API_KEY` |
|
|
195
|
+
| Groq | API | `GROQ_API_KEY` |
|
|
196
|
+
| Mistral AI | API | `MISTRAL_API_KEY` |
|
|
197
|
+
| DeepInfra | API | `DEEPINFRA_API_KEY` |
|
|
198
|
+
| Cohere | API | `CO_API_KEY` |
|
|
199
|
+
| Cerebras | API | `CEREBRAS_API_KEY` |
|
|
200
|
+
| Perplexity | API | `PERPLEXITY_API_KEY` |
|
|
201
|
+
| DeepSeek | API | `DEEPSEEK_API_KEY` |
|
|
202
|
+
| Moonshot (Kimi) | API | `MOONSHOT_API_KEY` / `KIMI_API_KEY` |
|
|
203
|
+
| GLM (Zhipu) | API | `GLM_API_KEY` / `ZHIPUAI_API_KEY` |
|
|
204
|
+
| Z.AI | API | `ZAI_API_KEY` |
|
|
205
|
+
| MiniMax | API | `MINIMAX_API_KEY` |
|
|
206
|
+
|
|
207
|
+
## Security
|
|
208
|
+
|
|
209
|
+
All external data (API responses, CLI output, cache reads) is scanned for 9 threat types before use: credential leaks, base64 payloads, script/shell injection, data URIs, null bytes, prototype pollution, hex blobs, and oversized strings. A pre-commit hook blocks secrets at commit time.
|
|
210
|
+
|
|
211
|
+
See [docs/security.md](docs/security.md) for the full threat catalogue and architecture.
|
|
212
|
+
|
|
213
|
+
## Architecture
|
|
214
|
+
|
|
215
|
+
<p align="center">
|
|
216
|
+
<img src="architecture.svg" alt="Kosha Architecture" width="720" />
|
|
217
|
+
</p>
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
┌─────────────────────────────────────────────────────┐
|
|
221
|
+
│ Your Application │
|
|
222
|
+
│ import { createKosha } from "kosha" │
|
|
223
|
+
└───────────────────────┬─────────────────────────────┘
|
|
224
|
+
│
|
|
225
|
+
┌───────────────────────▼─────────────────────────────┐
|
|
226
|
+
│ ModelRegistry │
|
|
227
|
+
│ models() · providerRoles() · cheapestModels() │
|
|
228
|
+
└──┬──────────┬──────────────┬───────────────┬────────┘
|
|
229
|
+
│ │ │ │
|
|
230
|
+
┌──▼───┐ ┌───▼────────┐ ┌───▼──────────┐ ┌──▼─────────┐
|
|
231
|
+
│Alias │ │ Discovery │ │ Enrichment │ │ Resilience │
|
|
232
|
+
│System│ │ Layer │ │ Layer │ │ Layer │
|
|
233
|
+
└──────┘ └───┬────────┘ └──────┬───────┘ └────────────┘
|
|
234
|
+
│ │ CircuitBreaker
|
|
235
|
+
┌────────┼────────┐ │ HealthTracker
|
|
236
|
+
▼ ▼ ▼ ▼ StaleCachePolicy
|
|
237
|
+
Direct OpenAI- Cloud litellm
|
|
238
|
+
API Compatible Proxies JSON
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Documentation
|
|
242
|
+
|
|
243
|
+
| Doc | What's in it |
|
|
244
|
+
|-----|-------------|
|
|
245
|
+
| [Credentials](docs/credentials.md) | Setup for all 21 providers (env vars, CLI tools, config files) |
|
|
246
|
+
| [CLI Reference](docs/cli.md) | All commands, flags, and example output |
|
|
247
|
+
| [HTTP API](docs/api.md) | All endpoints, parameters, and response schemas |
|
|
248
|
+
| [Configuration](docs/configuration.md) | Aliases, routing, pricing enrichment, programmatic config |
|
|
249
|
+
| [Architecture](docs/architecture.md) | Discovery flow, module map, data pipeline, adding providers |
|
|
250
|
+
| [Resilience](docs/resilience.md) | Circuit breakers, stale cache fallback, health monitoring |
|
|
251
|
+
| [Security](docs/security.md) | Threat catalogue, runtime scanning, pre-commit hook |
|
|
252
|
+
| [Discovery Plane v1](docs/discovery-plane-v1.md) | Stable daemon contract (deltas, SSE watch, binding hints) |
|
|
253
|
+
|
|
254
|
+
## Release & Tagging
|
|
255
|
+
|
|
256
|
+
Package: `@sriinnu/kosha-discovery`
|
|
257
|
+
|
|
258
|
+
This repo uses a human-in-the-loop release flow:
|
|
259
|
+
|
|
260
|
+
1. Update version in `package.json` and lockfiles locally.
|
|
261
|
+
2. Create a signed tag and push it:
|
|
262
|
+
```bash
|
|
263
|
+
git tag -s v0.6.0 -m "v0.6.0"
|
|
264
|
+
git push origin v0.6.0
|
|
265
|
+
```
|
|
266
|
+
3. In GitHub Actions, run `Manual Release (Tag + npm)` and provide `tag=v0.6.0`.
|
|
267
|
+
4. Workflow verifies tag/version match, builds/tests, then publishes to npm (if enabled) and creates a GitHub Release.
|
|
268
|
+
|
|
269
|
+
Required secret for publish:
|
|
270
|
+
- `NPM_TOKEN` (publish rights for `@sriinnu` scope)
|
|
271
|
+
|
|
272
|
+
## Credits
|
|
273
|
+
|
|
274
|
+
- **[litellm](https://github.com/BerriAI/litellm)** -- Community-maintained model pricing database
|
|
275
|
+
- **[openrouter](https://openrouter.ai)** -- Model aggregation API
|
|
276
|
+
- **[ollama](https://ollama.ai)** -- Local LLM runtime
|
|
277
|
+
- **[chitragupta](https://github.com/sriinnu/chitragupta)** -- Autonomous AI Agent Platform whose registry patterns inspired kosha
|
|
278
|
+
- **[takumi](https://github.com/sriinnu/takumi)** -- AI coding agent TUI whose routing needs drove kosha's creation
|
|
279
|
+
|
|
280
|
+
## What "Kosha" Means
|
|
281
|
+
|
|
282
|
+
`Kosha` comes from Sanskrit -- a container, treasury, or layered sheath of knowledge. A standalone model-discovery utility for any AI system.
|
|
283
|
+
|
|
284
|
+
## License
|
|
285
|
+
|
|
286
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kosha-discovery — Model alias resolution system.
|
|
3
|
+
*
|
|
4
|
+
* Provides short, memorable names that resolve to canonical model IDs.
|
|
5
|
+
* Built-in aliases are kept in sync with the latest model releases.
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Curated default aliases mapping short names to canonical model IDs.
|
|
10
|
+
*
|
|
11
|
+
* These are production-ready and cover the most commonly referenced
|
|
12
|
+
* models across major providers. Updated February 2026.
|
|
13
|
+
*/
|
|
14
|
+
export declare const DEFAULT_ALIASES: Readonly<Record<string, string>>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolves short alias names to canonical model IDs.
|
|
17
|
+
*
|
|
18
|
+
* Built-in aliases from {@link DEFAULT_ALIASES} are merged with optional
|
|
19
|
+
* user-provided overrides, where user overrides take precedence.
|
|
20
|
+
*/
|
|
21
|
+
export declare class AliasResolver {
|
|
22
|
+
/** Internal map holding the merged alias -> canonical ID mappings. */
|
|
23
|
+
private aliases;
|
|
24
|
+
/**
|
|
25
|
+
* @param customAliases - Optional user overrides; these are merged on top
|
|
26
|
+
* of the built-in {@link DEFAULT_ALIASES} map.
|
|
27
|
+
*/
|
|
28
|
+
constructor(customAliases?: Record<string, string>);
|
|
29
|
+
/**
|
|
30
|
+
* Resolve an alias to its canonical model ID.
|
|
31
|
+
* Returns the input unchanged if no matching alias is found.
|
|
32
|
+
*/
|
|
33
|
+
resolve(nameOrAlias: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Find all aliases that point to the given canonical model ID.
|
|
36
|
+
*/
|
|
37
|
+
reverseAliases(modelId: string): string[];
|
|
38
|
+
/**
|
|
39
|
+
* Add or overwrite an alias mapping.
|
|
40
|
+
*/
|
|
41
|
+
addAlias(alias: string, modelId: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* Remove an alias mapping.
|
|
44
|
+
*/
|
|
45
|
+
removeAlias(alias: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* Return a snapshot of the full alias map (defaults + custom).
|
|
48
|
+
*/
|
|
49
|
+
all(): Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=aliases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases.d.ts","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAgD5D,CAAC;AAEF;;;;;GAKG;AACH,qBAAa,aAAa;IACzB,sEAAsE;IACtE,OAAO,CAAC,OAAO,CAAsB;IAErC;;;OAGG;gBACS,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAUlD;;;OAGG;IACH,OAAO,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIpC;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAUzC;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9C;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIhC;;OAEG;IACH,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;CAG7B"}
|
package/dist/aliases.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kosha-discovery — Model alias resolution system.
|
|
3
|
+
*
|
|
4
|
+
* Provides short, memorable names that resolve to canonical model IDs.
|
|
5
|
+
* Built-in aliases are kept in sync with the latest model releases.
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Curated default aliases mapping short names to canonical model IDs.
|
|
10
|
+
*
|
|
11
|
+
* These are production-ready and cover the most commonly referenced
|
|
12
|
+
* models across major providers. Updated February 2026.
|
|
13
|
+
*/
|
|
14
|
+
export const DEFAULT_ALIASES = {
|
|
15
|
+
// ── Anthropic — Claude 4.6 family (latest as of Feb 2026) ──
|
|
16
|
+
"opus": "claude-opus-4-6",
|
|
17
|
+
"opus-4": "claude-opus-4-6",
|
|
18
|
+
"sonnet": "claude-sonnet-4-6",
|
|
19
|
+
"sonnet-4": "claude-sonnet-4-6",
|
|
20
|
+
"haiku": "claude-haiku-4-5-20251001",
|
|
21
|
+
"haiku-4.5": "claude-haiku-4-5-20251001",
|
|
22
|
+
// ── OpenAI — latest reasoning and chat models ──
|
|
23
|
+
"gpt4o": "gpt-4o",
|
|
24
|
+
"gpt4o-mini": "gpt-4o-mini",
|
|
25
|
+
"o1": "o1",
|
|
26
|
+
"o3": "o3",
|
|
27
|
+
"o3-mini": "o3-mini",
|
|
28
|
+
"o4-mini": "o4-mini",
|
|
29
|
+
// ── Google — Gemini 2.5 series ──
|
|
30
|
+
"gemini-pro": "gemini-2.5-pro-preview-05-06",
|
|
31
|
+
"gemini-flash": "gemini-2.5-flash-preview-04-17",
|
|
32
|
+
"gemini-flash-lite": "gemini-2.0-flash-lite",
|
|
33
|
+
// ── Local — latest popular open-weight models for Ollama ──
|
|
34
|
+
"qwen": "qwen3:8b",
|
|
35
|
+
"llama": "llama3.3:latest",
|
|
36
|
+
"codestral": "codestral:latest",
|
|
37
|
+
"deepseek": "deepseek-r1:latest",
|
|
38
|
+
// ── NVIDIA — Nemotron family via build.nvidia.com ──
|
|
39
|
+
"nemotron-ultra": "nvidia/llama-3.1-nemotron-ultra-253b-v1",
|
|
40
|
+
"nemotron-super": "nvidia/llama-3.1-nemotron-super-49b-v1",
|
|
41
|
+
"nemotron-nano": "nvidia/llama-3.1-nemotron-nano-8b-v1",
|
|
42
|
+
// ── Mistral AI — latest models ──
|
|
43
|
+
"mistral-large": "mistral-large-latest",
|
|
44
|
+
"mistral-small": "mistral-small-latest",
|
|
45
|
+
"mistral-codestral": "codestral-latest",
|
|
46
|
+
"pixtral": "pixtral-large-latest",
|
|
47
|
+
// ── Groq — fast inference ──
|
|
48
|
+
"groq-llama": "llama-3.3-70b-versatile",
|
|
49
|
+
"groq-mixtral": "mixtral-8x7b-32768",
|
|
50
|
+
// ── Embeddings — OpenAI, Nomic, and Google embedding models ──
|
|
51
|
+
"embed-small": "text-embedding-3-small",
|
|
52
|
+
"embed-large": "text-embedding-3-large",
|
|
53
|
+
"nomic": "nomic-embed-text",
|
|
54
|
+
"gemini-embed": "gemini-embedding-001",
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Resolves short alias names to canonical model IDs.
|
|
58
|
+
*
|
|
59
|
+
* Built-in aliases from {@link DEFAULT_ALIASES} are merged with optional
|
|
60
|
+
* user-provided overrides, where user overrides take precedence.
|
|
61
|
+
*/
|
|
62
|
+
export class AliasResolver {
|
|
63
|
+
/** Internal map holding the merged alias -> canonical ID mappings. */
|
|
64
|
+
aliases;
|
|
65
|
+
/**
|
|
66
|
+
* @param customAliases - Optional user overrides; these are merged on top
|
|
67
|
+
* of the built-in {@link DEFAULT_ALIASES} map.
|
|
68
|
+
*/
|
|
69
|
+
constructor(customAliases) {
|
|
70
|
+
this.aliases = new Map(Object.entries(DEFAULT_ALIASES));
|
|
71
|
+
if (customAliases) {
|
|
72
|
+
for (const [alias, modelId] of Object.entries(customAliases)) {
|
|
73
|
+
this.aliases.set(alias, modelId);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Resolve an alias to its canonical model ID.
|
|
79
|
+
* Returns the input unchanged if no matching alias is found.
|
|
80
|
+
*/
|
|
81
|
+
resolve(nameOrAlias) {
|
|
82
|
+
return this.aliases.get(nameOrAlias) ?? nameOrAlias;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Find all aliases that point to the given canonical model ID.
|
|
86
|
+
*/
|
|
87
|
+
reverseAliases(modelId) {
|
|
88
|
+
const result = [];
|
|
89
|
+
for (const [alias, target] of this.aliases) {
|
|
90
|
+
if (target === modelId) {
|
|
91
|
+
result.push(alias);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Add or overwrite an alias mapping.
|
|
98
|
+
*/
|
|
99
|
+
addAlias(alias, modelId) {
|
|
100
|
+
this.aliases.set(alias, modelId);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Remove an alias mapping.
|
|
104
|
+
*/
|
|
105
|
+
removeAlias(alias) {
|
|
106
|
+
this.aliases.delete(alias);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Return a snapshot of the full alias map (defaults + custom).
|
|
110
|
+
*/
|
|
111
|
+
all() {
|
|
112
|
+
return Object.fromEntries(this.aliases);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=aliases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aliases.js","sourceRoot":"","sources":["../src/aliases.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAqC;IAChE,8DAA8D;IAC9D,MAAM,EAAE,iBAAiB;IACzB,QAAQ,EAAE,iBAAiB;IAC3B,QAAQ,EAAE,mBAAmB;IAC7B,UAAU,EAAE,mBAAmB;IAC/B,OAAO,EAAE,2BAA2B;IACpC,WAAW,EAAE,2BAA2B;IAExC,kDAAkD;IAClD,OAAO,EAAE,QAAQ;IACjB,YAAY,EAAE,aAAa;IAC3B,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,SAAS,EAAE,SAAS;IACpB,SAAS,EAAE,SAAS;IAEpB,mCAAmC;IACnC,YAAY,EAAE,8BAA8B;IAC5C,cAAc,EAAE,gCAAgC;IAChD,mBAAmB,EAAE,uBAAuB;IAE5C,6DAA6D;IAC7D,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,iBAAiB;IAC1B,WAAW,EAAE,kBAAkB;IAC/B,UAAU,EAAE,oBAAoB;IAEhC,sDAAsD;IACtD,gBAAgB,EAAE,yCAAyC;IAC3D,gBAAgB,EAAE,wCAAwC;IAC1D,eAAe,EAAE,sCAAsC;IAEvD,mCAAmC;IACnC,eAAe,EAAE,sBAAsB;IACvC,eAAe,EAAE,sBAAsB;IACvC,mBAAmB,EAAE,kBAAkB;IACvC,SAAS,EAAE,sBAAsB;IAEjC,8BAA8B;IAC9B,YAAY,EAAE,yBAAyB;IACvC,cAAc,EAAE,oBAAoB;IAEpC,gEAAgE;IAChE,aAAa,EAAE,wBAAwB;IACvC,aAAa,EAAE,wBAAwB;IACvC,OAAO,EAAE,kBAAkB;IAC3B,cAAc,EAAE,sBAAsB;CACtC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IACzB,sEAAsE;IAC9D,OAAO,CAAsB;IAErC;;;OAGG;IACH,YAAY,aAAsC;QACjD,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC;QAExD,IAAI,aAAa,EAAE,CAAC;YACnB,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC9D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YAClC,CAAC;QACF,CAAC;IACF,CAAC;IAED;;;OAGG;IACH,OAAO,CAAC,WAAmB;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC;IACrD,CAAC;IAED;;OAEG;IACH,cAAc,CAAC,OAAe;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC5C,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;gBACxB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;QACF,CAAC;QACD,OAAO,MAAM,CAAC;IACf,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,KAAa,EAAE,OAAe;QACtC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAClC,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,KAAa;QACxB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED;;OAEG;IACH,GAAG;QACF,OAAO,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;CACD"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kosha-discovery — File-based JSON cache with TTL.
|
|
3
|
+
*
|
|
4
|
+
* Stores discovered provider data as individual JSON files on disk
|
|
5
|
+
* to avoid re-fetching from provider APIs on every startup.
|
|
6
|
+
* Each cache key maps to a `{key}.json` file inside the cache directory.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
interface CacheEntry<T> {
|
|
10
|
+
data: T;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Simple file-based JSON cache with TTL support.
|
|
15
|
+
* Stores discovered provider data to avoid re-fetching on every startup.
|
|
16
|
+
*
|
|
17
|
+
* Each cache key maps to a `{key}.json` file inside the cache directory.
|
|
18
|
+
*/
|
|
19
|
+
export declare class KoshaCache {
|
|
20
|
+
private readonly cacheDir;
|
|
21
|
+
constructor(cacheDir?: string);
|
|
22
|
+
/**
|
|
23
|
+
* Retrieve cached data by key.
|
|
24
|
+
* Returns null if the entry is missing or the file cannot be read.
|
|
25
|
+
*/
|
|
26
|
+
get<T>(key: string): Promise<CacheEntry<T> | null>;
|
|
27
|
+
/**
|
|
28
|
+
* Write data to the cache under the given key.
|
|
29
|
+
*
|
|
30
|
+
* Uses atomic write (temp file + rename) so a crash mid-write
|
|
31
|
+
* cannot leave a corrupted cache file behind.
|
|
32
|
+
*/
|
|
33
|
+
set<T>(key: string, data: T): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Remove a single cached entry by key.
|
|
36
|
+
*/
|
|
37
|
+
invalidate(key: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Remove all cache files from the cache directory.
|
|
40
|
+
*/
|
|
41
|
+
clear(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Check whether a cached timestamp has exceeded the given TTL.
|
|
44
|
+
*/
|
|
45
|
+
isExpired(timestamp: number, ttlMs: number): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Sanitize a cache key into a safe filename and return the full path.
|
|
48
|
+
* Replaces any character that is not alphanumeric, dash, or underscore with an underscore.
|
|
49
|
+
*/
|
|
50
|
+
private keyToPath;
|
|
51
|
+
/**
|
|
52
|
+
* Ensure the cache directory exists, creating it recursively if needed.
|
|
53
|
+
*/
|
|
54
|
+
private ensureDir;
|
|
55
|
+
}
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,UAAU,UAAU,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC;IACR,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;;GAKG;AACH,qBAAa,UAAU;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,QAAQ,CAAC,EAAE,MAAM;IAI7B;;;OAGG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAmBxD;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBjD;;OAEG;IACG,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS5C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAY5B;;OAEG;IACH,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAQpD;;;OAGG;IACH,OAAO,CAAC,SAAS;IAKjB;;OAEG;YACW,SAAS;CAGvB"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kosha-discovery — File-based JSON cache with TTL.
|
|
3
|
+
*
|
|
4
|
+
* Stores discovered provider data as individual JSON files on disk
|
|
5
|
+
* to avoid re-fetching from provider APIs on every startup.
|
|
6
|
+
* Each cache key maps to a `{key}.json` file inside the cache directory.
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { mkdir, readFile, readdir, rename, unlink, writeFile } from "fs/promises";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { assertCleanPayload } from "./security.js";
|
|
14
|
+
/**
|
|
15
|
+
* Simple file-based JSON cache with TTL support.
|
|
16
|
+
* Stores discovered provider data to avoid re-fetching on every startup.
|
|
17
|
+
*
|
|
18
|
+
* Each cache key maps to a `{key}.json` file inside the cache directory.
|
|
19
|
+
*/
|
|
20
|
+
export class KoshaCache {
|
|
21
|
+
cacheDir;
|
|
22
|
+
constructor(cacheDir) {
|
|
23
|
+
this.cacheDir = cacheDir ?? join(homedir(), ".kosha", "cache");
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Retrieve cached data by key.
|
|
27
|
+
* Returns null if the entry is missing or the file cannot be read.
|
|
28
|
+
*/
|
|
29
|
+
async get(key) {
|
|
30
|
+
try {
|
|
31
|
+
const filePath = this.keyToPath(key);
|
|
32
|
+
const raw = await readFile(filePath, "utf-8");
|
|
33
|
+
const entry = JSON.parse(raw);
|
|
34
|
+
assertCleanPayload(entry, `cache/${key}`);
|
|
35
|
+
return entry;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
if (err instanceof Error && err.message.startsWith("Rejected")) {
|
|
39
|
+
// Security violation — poisoned cache file. Log loudly, invalidate, return miss.
|
|
40
|
+
console.error(`KoshaCache SECURITY: ${err.message}`);
|
|
41
|
+
await this.invalidate(key);
|
|
42
|
+
}
|
|
43
|
+
else if (err instanceof SyntaxError) {
|
|
44
|
+
console.warn(`KoshaCache: corrupted cache file for key "${key}"`);
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Write data to the cache under the given key.
|
|
51
|
+
*
|
|
52
|
+
* Uses atomic write (temp file + rename) so a crash mid-write
|
|
53
|
+
* cannot leave a corrupted cache file behind.
|
|
54
|
+
*/
|
|
55
|
+
async set(key, data) {
|
|
56
|
+
await this.ensureDir();
|
|
57
|
+
const entry = {
|
|
58
|
+
data,
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
};
|
|
61
|
+
const filePath = this.keyToPath(key);
|
|
62
|
+
const tmpPath = `${filePath}.${randomBytes(4).toString("hex")}.tmp`;
|
|
63
|
+
try {
|
|
64
|
+
await writeFile(tmpPath, JSON.stringify(entry, null, "\t"), "utf-8");
|
|
65
|
+
await rename(tmpPath, filePath);
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
await unlink(tmpPath).catch(() => { });
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Remove a single cached entry by key.
|
|
74
|
+
*/
|
|
75
|
+
async invalidate(key) {
|
|
76
|
+
try {
|
|
77
|
+
const filePath = this.keyToPath(key);
|
|
78
|
+
await unlink(filePath);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Ignore if file doesn't exist
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Remove all cache files from the cache directory.
|
|
86
|
+
*/
|
|
87
|
+
async clear() {
|
|
88
|
+
try {
|
|
89
|
+
const files = await readdir(this.cacheDir);
|
|
90
|
+
const removals = files
|
|
91
|
+
.filter((f) => f.endsWith(".json"))
|
|
92
|
+
.map((f) => unlink(join(this.cacheDir, f)).catch(() => { }));
|
|
93
|
+
await Promise.all(removals);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Directory may not exist yet
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Check whether a cached timestamp has exceeded the given TTL.
|
|
101
|
+
*/
|
|
102
|
+
isExpired(timestamp, ttlMs) {
|
|
103
|
+
return Date.now() - timestamp > ttlMs;
|
|
104
|
+
}
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Private helpers
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
/**
|
|
109
|
+
* Sanitize a cache key into a safe filename and return the full path.
|
|
110
|
+
* Replaces any character that is not alphanumeric, dash, or underscore with an underscore.
|
|
111
|
+
*/
|
|
112
|
+
keyToPath(key) {
|
|
113
|
+
const safeKey = key.replace(/[^a-zA-Z0-9\-_]/g, "_");
|
|
114
|
+
return join(this.cacheDir, `${safeKey}.json`);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Ensure the cache directory exists, creating it recursively if needed.
|
|
118
|
+
*/
|
|
119
|
+
async ensureDir() {
|
|
120
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=cache.js.map
|