@aggc/or-info 0.2.16 → 0.2.18

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 CHANGED
@@ -352,33 +352,7 @@ Additional entry points:
352
352
 
353
353
  ## Release
354
354
 
355
- Releases are published automatically to npm from GitHub Actions when a version tag is pushed.
356
- The package is public under the `@aggc` npm scope.
357
-
358
- Release checklist:
359
-
360
- 1. Update `CHANGELOG.md`.
361
- 2. Bump `version` in `package.json` **and** `manifest.json` (must match).
362
- 3. Run `npm test`.
363
- 4. Commit and push to `main`.
364
- 5. Create and push a matching tag:
365
-
366
- ```bash
367
- git tag v0.x.0
368
- git push origin v0.x.0
369
- ```
370
-
371
- The publish workflow runs `npm ci`, `npm run test:local`, then
372
- `npm publish --provenance --access public`.
373
-
374
- Repository release requirements:
375
-
376
- - GitHub secret `NPM_TOKEN` must exist for `jmtrs/or-info`.
377
- - The token must have npm publish permission for `@aggc/or-info`.
378
- - `id-token: write` is enabled so npm provenance is attached to published versions.
379
-
380
- After the initial package bootstrap, prefer migrating to npm Trusted Publishing and then
381
- remove `NPM_TOKEN` from the repository secrets.
355
+ See [CHANGELOG.md](CHANGELOG.md) for release history and details.
382
356
 
383
357
  ## Contributing
384
358
 
package/bin/or-info.mjs CHANGED
@@ -4,7 +4,7 @@ import { InvalidArgumentError, program } from 'commander';
4
4
  import chalk from 'chalk';
5
5
 
6
6
  const { version } = createRequire(import.meta.url)('../package.json');
7
- import { fetchModels, findModel, pricePerMillion, contextLength } from '../lib/openrouter.mjs';
7
+ import { fetchModels, findModel, pricePerMillion, contextLength, isFree } from '../lib/openrouter.mjs';
8
8
  import { getElo, getAllElo, loadLeaderboard } from '../lib/lmarena.mjs';
9
9
  import { rankModels } from '../lib/scorer.mjs';
10
10
  import { clearAll, status } from '../lib/cache.mjs';
package/lib/formatter.mjs CHANGED
@@ -18,6 +18,15 @@ function fmtPrice(n) {
18
18
  return RED(`$${n.toFixed(4)}`);
19
19
  }
20
20
 
21
+ function maybeFreeWarning(model, price) {
22
+ // OpenRouter sometimes reports pricing="0" for non-free models (e.g. z-ai/glm-5.1).
23
+ // Show a warning so users don't get surprised by charges.
24
+ if (price.input === 0 && price.output === 0 && !model?.id?.endsWith(':free') && model?.id !== 'openrouter/free') {
25
+ return YELLOW(' ⚠ reported free — may incur charges on OpenRouter');
26
+ }
27
+ return null;
28
+ }
29
+
21
30
  function fmtCtx(n) {
22
31
  if (!n) return DIM('n/a');
23
32
  if (n >= 1_000_000) return CYAN(`${(n / 1_000_000).toFixed(1)}M`);
@@ -46,8 +55,9 @@ export function modelTable(models, { showTags = false } = {}) {
46
55
  const price = pricePerMillion(m);
47
56
  const ctx = contextLength(m);
48
57
  const tags = showTags ? modelTags(m) : [];
58
+ const idLabel = maybeFreeWarning(m, price) ? m.id + YELLOW(' ⚠') : m.id;
49
59
  return {
50
- id: m.id,
60
+ id: idLabel,
51
61
  name: m.name ?? m.id,
52
62
  input: fmtPrice(price.input),
53
63
  output: fmtPrice(price.output),
@@ -95,6 +105,9 @@ export function modelDetail(model, bench = null) {
95
105
  ` Output ${fmtPrice(price.output)} per 1M tokens`,
96
106
  ];
97
107
 
108
+ const freeWarn = maybeFreeWarning(model, price);
109
+ if (freeWarn) lines.push(freeWarn);
110
+
98
111
  if (price.image !== null) lines.push(` Image ${fmtPrice(price.image)} per 1M tokens`);
99
112
  if (price.cacheRead !== null) lines.push(` Cache↓ ${fmtPrice(price.cacheRead)} per 1M tokens`);
100
113
  if (price.cacheWrite !== null) lines.push(` Cache↑ ${fmtPrice(price.cacheWrite)} per 1M tokens`);
@@ -135,6 +135,11 @@ export function modelTags(model) {
135
135
  }
136
136
 
137
137
  export function isFree(model) {
138
- const p = pricePerMillion(model);
139
- return p.input === 0 && p.output === 0;
138
+ // A model is genuinely free only if it carries the :free suffix
139
+ // or is a known free-tier model. OpenRouter sometimes reports
140
+ // pricing.prompt="0" & pricing.completion="0" for models that are NOT free
141
+ // (e.g. z-ai/glm-5.1) — a known API inconsistency.
142
+ if (model?.id?.endsWith(':free')) return true;
143
+ if (model?.id === 'openrouter/free') return true;
144
+ return false;
140
145
  }
package/mcp/server.mjs CHANGED
@@ -3,7 +3,7 @@ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
3
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
4
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
5
5
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
- import { fetchModels, findModel, pricePerMillion, contextLength, modelTags } from '../lib/openrouter.mjs';
6
+ import { fetchModels, findModel, pricePerMillion, contextLength, modelTags, isFree } from '../lib/openrouter.mjs';
7
7
  import { getElo, getAllElo, loadLeaderboard } from '../lib/lmarena.mjs';
8
8
  import { rankModels } from '../lib/scorer.mjs';
9
9
  import { getApiKey } from '../lib/secrets.mjs';
@@ -294,7 +294,7 @@ async function handleTool(name, args) {
294
294
 
295
295
  let models = await fetchModels({ apiKey: key });
296
296
  if (filter) models = models.filter((m) => m.id.toLowerCase().includes(filter) || (m.name ?? '').toLowerCase().includes(filter));
297
- if (freeOnly) models = models.filter((m) => { const p = pricePerMillion(m); return p.input === 0 && p.output === 0; });
297
+ if (freeOnly) models = models.filter(isFree);
298
298
 
299
299
  if (sortBy === 'price') models.sort((a, b) => (pricePerMillion(a).output ?? Infinity) - (pricePerMillion(b).output ?? Infinity));
300
300
  else if (sortBy === 'context') models.sort((a, b) => (contextLength(b) ?? 0) - (contextLength(a) ?? 0));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aggc/or-info",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "CLI + MCP server for OpenRouter models: prices, benchmarks, context and comparisons",
5
5
  "type": "module",
6
6
  "engines": {