@opennodes/registry 0.1.15 → 0.1.17

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/web/index.html +33 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennodes/registry",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "OpenNodes (ONP) reference registry: node registration with proof-of-resource qualification (Stage A/B/C), transparent ranking, enforceable estimates, catalog importers (OpenRouter, Hugging Face, models.dev, Ollama), MCP surface, and the web app (catalog, chat, advisor, Node Builder)",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -153,7 +153,7 @@
153
153
  border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer; }
154
154
  .btn.sec { background: transparent; color: var(--accent); }
155
155
  .btn:disabled { opacity: 0.5; cursor: default; }
156
- input.wide { width: 100%; font: inherit; color: var(--ink); background: var(--panel); border: 1px solid var(--line); border-radius: 5px; padding: 0.55rem 0.7rem; }
156
+ input.wide, textarea.wide { width: 100%; box-sizing: border-box; font: inherit; color: var(--ink); background: var(--panel); border: 1px solid var(--line); border-radius: 5px; padding: 0.55rem 0.7rem; }
157
157
  label.f { display: block; font-size: 0.78rem; color: var(--muted); margin: 0.9rem 0 0.25rem; }
158
158
  .ok { color: var(--good); } .fail { color: var(--bad); } .warncol { color: var(--warn); }
159
159
  .err { color: var(--bad); font-size: 0.82rem; margin-top: 0.5rem; }
@@ -232,7 +232,7 @@
232
232
  </div>
233
233
  <details class="panel pad" id="advisor" style="margin:0.2rem 0 0.8rem">
234
234
  <summary style="cursor:pointer; font-weight:600; font-size:0.9rem">Ask the advisor — describe a task, get the best node + model, priced and explained</summary>
235
- <textarea id="adv-task" rows="2" class="wide" style="margin-top:0.6rem; font-size:0.86rem" placeholder="e.g. Translate this 20-page contract into Polish and extract the payment terms as JSON"></textarea>
235
+ <textarea id="adv-task" rows="4" class="wide" style="margin-top:0.6rem; font-size:0.86rem; resize:vertical" placeholder="e.g. Translate this 20-page contract into Polish and extract the payment terms as JSON"></textarea>
236
236
  <div class="toolbar" style="margin-top:0.4rem">
237
237
  <select id="adv-preset"><option value="">balanced</option><option value="cheap">cheapest</option><option value="fast">fastest</option><option value="quality">best quality</option><option value="private">privacy-first</option></select>
238
238
  <span class="switchrow" id="adv-verified"><span class="switch"></span><b style="font-size:0.78rem">Verified only</b></span>
@@ -1743,6 +1743,36 @@ async function loadFeed() {
1743
1743
  }
1744
1744
 
1745
1745
  /* ---------- node detail ---------- */
1746
+ /**
1747
+ * Imported listings invite their operator to claim them (ONP-3 §4: a registered node whose
1748
+ * namespace covers the import's API host supersedes it). Providers reached through the HF router
1749
+ * can't claim via huggingface.co, so they are invited to list their own API instead.
1750
+ */
1751
+ const CLAIM_GUIDE = 'https://github.com/opennodes-io/opennodes/tree/main/node-kit#card-only-listing-you-already-run-an-openai-compatible-api';
1752
+ function claimBanner(n, card) {
1753
+ if (n.source !== 'import' || ['delisted', 'claimed'].includes(n.state)) return '';
1754
+ const name = card.node?.operator?.name || card.node?.name || n.id;
1755
+ let host = null;
1756
+ try { host = new URL(card.endpoints?.openai).hostname; } catch { /* listing without an API */ }
1757
+ const viaHf = host === 'router.huggingface.co';
1758
+ const suggestedId = host && !viaHf ? host.split('.').reverse().join('.') : null;
1759
+ const body = viaHf
1760
+ ? `This listing reaches <b>${esc(name)}</b> through the Hugging Face router, so it is shown as an unprobed catalog import. List your own API directly to have it measured and verified by this registry.`
1761
+ : host
1762
+ ? `Claim it: publish a signed Node Card at <span class="mono">https://${esc(host)}/.well-known/open-node.json</span> with node id <span class="mono">${esc(suggestedId)}</span> (your parent domain works too) and register. Your verified listing replaces this import, the registry measures what you really serve, and nothing in your stack changes.`
1763
+ : 'This listing has no known API endpoint. Publish a Node Card for your OpenAI-compatible API to be listed, measured and verified.';
1764
+ const mail = `mailto:operators@opennodes.io?subject=${encodeURIComponent('Claim ' + n.id)}`;
1765
+ return `<div class="panel pad" style="max-width:56rem; margin:0.4rem 0 0.8rem; border-left:3px solid var(--accent)">
1766
+ <p style="margin:0 0 0.35rem; font-weight:600">Do you run ${esc(name)}?</p>
1767
+ <p style="margin:0 0 0.6rem; font-size:0.86rem; color:var(--muted); overflow-wrap:anywhere">${body} Free, about 15 minutes with the Node Kit.</p>
1768
+ <span style="display:inline-flex; gap:0.6rem; flex-wrap:wrap; font-size:0.84rem">
1769
+ <a href="${CLAIM_GUIDE}" target="_blank" rel="noopener">How to claim →</a>
1770
+ <a href="#/register">Register a node</a>
1771
+ <a href="${mail}">operators@opennodes.io</a>
1772
+ </span>
1773
+ </div>`;
1774
+ }
1775
+
1746
1776
  async function loadNode(id) {
1747
1777
  $('#node-title').textContent = id;
1748
1778
  try {
@@ -1764,6 +1794,7 @@ async function loadNode(id) {
1764
1794
  <td>${(o.serving?.supports || []).filter((s) => s !== 'streaming').map((s) => `<span class="chip">${esc(s)}</span>`).join(' ') || '—'}</td>
1765
1795
  </tr>`).join('');
1766
1796
  $('#node-detail').innerHTML = `
1797
+ ${claimBanner(n, card)}
1767
1798
  ${card.node?.description ? `<p class="desc">${esc(card.node.description)}</p>` : ''}
1768
1799
  <div class="statstrip">
1769
1800
  ${stat(tierChip(tier), 'TRUST TIER')}