diff --git a/demo-node/apix-demo.mjs b/demo-node/apix-demo.mjs new file mode 100644 index 0000000..e0373e4 --- /dev/null +++ b/demo-node/apix-demo.mjs @@ -0,0 +1,210 @@ +/** + * APIX Discovery Demo — for OpenClaw + * + * Shows how an agent finds and uses services without hardcoded URLs. + * This is what ClawHub does manually. APIX makes it autonomous. + * + * Requirements: Node 18+ (native fetch, no dependencies) + * Run: node apix-demo.mjs + */ + +const REGISTRY_ROOT = 'https://api-index.org'; +const SANDBOX_REGISTER = `${REGISTRY_ROOT}/sandbox/register`; + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +async function get(url) { + const res = await fetch(url, { headers: { Accept: 'application/json' } }); + if (!res.ok) throw new Error(`GET ${url} → ${res.status}`); + return res.json(); +} + +async function post(url, body, headers = {}) { + const res = await fetch(url, { + method: 'POST', + headers: { 'Content-Type': 'application/json', Accept: 'application/json', ...headers }, + body: JSON.stringify(body), + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`POST ${url} → ${res.status}: ${text}`); + } + return res.json(); +} + +function line(char = '─', n = 60) { return char.repeat(n); } +function hdr(label) { console.log(`\n${line()}\n ${label}\n${line()}`); } + +// ─── Demo ───────────────────────────────────────────────────────────────────── + +async function main() { + + console.log('\n APIX Discovery Demo'); + console.log(' The open agent service registry — api-index.org\n'); + + // ── Step 1: HATEOAS root ─────────────────────────────────────────────────── + hdr('Step 1 Discover the registry root'); + const root = await get(REGISTRY_ROOT); + console.log(` Registry: ${root.name}`); + console.log(` Services registered: ${root.stats.registeredServices}`); + console.log(` Navigation links:`); + for (const [rel, link] of Object.entries(root._links)) { + console.log(` ${rel.padEnd(20)} ${link.href}`); + } + console.log('\n → An agent starts here. No hardcoded service URLs anywhere.'); + + // ── Step 2: Create sandbox ───────────────────────────────────────────────── + hdr('Step 2 Create a sandbox namespace (self-service, instant)'); + const reg = await post(SANDBOX_REGISTER, { + name: 'openclaw-apix-demo', + contactEmail: 'demo@openclaw.dev', + location: 'Vienna, AT', + }); + const { sandboxId, apiKey, tier, ratePerMinute, expiresAt, _links: sLinks } = reg; + console.log(` Sandbox ID : ${sandboxId}`); + console.log(` Tier : ${tier} | Rate limit: ${ratePerMinute} req/min`); + console.log(` Expires : ${expiresAt}`); + console.log(` Dashboard : ${reg.dashboardUrl ?? sLinks?.self?.href ?? '—'}`); + console.log('\n → Each sandbox is isolated. Any agent or developer can create one instantly.'); + + const servicesHref = sLinks.services?.href ?? `${REGISTRY_ROOT}/sandbox/${sandboxId}/services`; + const searchHrefTpl = sLinks.servicesSearch?.href + ?? `${REGISTRY_ROOT}/sandbox/${sandboxId}/services{?capability,stage}`; + + // ── Step 3: Register services (BSM manifests) ────────────────────────────── + hdr('Step 3 Register three carrier services (BSM manifests)'); + + const carriers = [ + { + serviceName: 'NordLogistik Economy EU', + description: 'Economy EU road & rail freight. 5-day delivery. DE/AT/CH specialist.', + endpoint: 'https://demo.api-index.org/carriers/nord/quote', + capabilities: ['shipping-quote', 'logistics', 'eu-delivery'], + registrantEmail: 'api@nordlogistik.example', + registrantName: 'NordLogistik GmbH', + registrantJurisdiction:'DE', + orgType: 'COMMERCIAL', + pricing: { model: 'PER_CALL', amount: 0.012, currency: 'APX', unit: 'per-call' }, + bsmVersion: '0.1', + stage: 'PRODUCTION', + }, + { + serviceName: 'SwiftCargo Express', + description: 'Global express shipping. 2-day delivery to major hubs worldwide.', + endpoint: 'https://demo.api-index.org/carriers/swift/quote', + capabilities: ['shipping-quote', 'express-delivery', 'global-logistics'], + registrantEmail: 'api@swiftcargo.example', + registrantName: 'SwiftCargo Ltd', + registrantJurisdiction:'GB', + orgType: 'COMMERCIAL', + pricing: { model: 'PER_CALL', amount: 0.035, currency: 'APX', unit: 'per-call' }, + bsmVersion: '0.1', + stage: 'PRODUCTION', + }, + { + serviceName: 'PacRim Express', + description: 'Cost-optimised Asia-Pacific shipping. SEA, JP, KR, AU routes.', + endpoint: 'https://demo.api-index.org/carriers/pacrim/quote', + capabilities: ['shipping-quote', 'logistics', 'asia-pacific-delivery'], + registrantEmail: 'api@pacrim.example', + registrantName: 'PacRim Express Pte', + registrantJurisdiction:'SG', + orgType: 'COMMERCIAL', + pricing: { model: 'PER_CALL', amount: 0.018, currency: 'APX', unit: 'per-call' }, + bsmVersion: '0.1', + stage: 'PRODUCTION', + }, + ]; + + for (const manifest of carriers) { + await post(servicesHref, manifest, { 'X-Api-Key': apiKey }); + console.log(` ✓ Registered: ${manifest.serviceName} (${manifest.registrantJurisdiction})`); + } + console.log('\n → Any provider self-registers. The agent discovers them automatically.'); + + // ── Step 4: Agent discovers carriers ────────────────────────────────────── + hdr('Step 4 Agent queries: "who can provide shipping-quote?"'); + const searchUrl = searchHrefTpl.replace('{?capability,stage}', '') + + '?capability=shipping-quote'; + console.log(` Query: GET ${searchUrl}\n`); + + const found = await get(searchUrl); + console.log(` Found ${found.length} carrier service(s):\n`); + for (const svc of found) { + const price = svc.pricing ? `${svc.pricing.amount} ${svc.pricing.currency}/${svc.pricing.unit}` : 'unknown'; + console.log(` ┌─ ${svc.serviceName}`); + console.log(` │ Jurisdiction : ${svc.registrantJurisdiction}`); + console.log(` │ Capabilities : ${svc.capabilities?.join(', ')}`); + console.log(` │ Price : ${price}`); + console.log(` └─ Endpoint : ${svc.endpoint}`); + console.log(); + } + console.log(' → The agent has all three endpoints. It never knew about them before this query.'); + + // ── Step 5: Call discovered services in parallel ─────────────────────────── + hdr('Step 5 Call all carriers in parallel — choose the best quote'); + + const shipment = { from: 'Munich, DE', to: 'London, GB', weightKg: 2.5, dimensions: '30x20x15cm' }; + console.log(` Shipment: ${JSON.stringify(shipment)}\n`); + + // The agent uses endpoints discovered in Step 4 — zero hardcoded carrier URLs + const quoteRequests = found.map(async svc => { + const start = Date.now(); + try { + const quote = await post(svc.endpoint, shipment).catch(() => mockQuote(svc)); + return { name: svc.serviceName, quote, ms: Date.now() - start }; + } catch { + return { name: svc.serviceName, quote: mockQuote(svc), ms: Date.now() - start }; + } + }); + + const results = await Promise.all(quoteRequests); + results.sort((a, b) => a.quote.price.amount - b.quote.price.amount); + + for (const { name, quote, ms } of results) { + const best = results[0].name === name ? ' ← best price' : ''; + console.log(` ${name.padEnd(28)} ${quote.price.amount.toFixed(2)} ${quote.price.currency} · ${quote.estimatedDays}d${best}`); + } + + const winner = results[0]; + console.log(`\n Selected: ${winner.name} (${winner.quote.price.amount.toFixed(2)} ${winner.quote.price.currency})`); + + // ── Summary ──────────────────────────────────────────────────────────────── + hdr('Summary'); + console.log(' What just happened:\n'); + console.log(' 1. Started at a single URL: https://api-index.org'); + console.log(' 2. HATEOAS navigation — followed links, never guessed URLs'); + console.log(' 3. Created an isolated sandbox in one POST'); + console.log(' 4. Three carriers registered their own BSM manifests'); + console.log(' 5. Agent queried capability="shipping-quote" — found all three'); + console.log(' 6. Called them in parallel, picked the best quote'); + console.log(); + console.log(' No hardcoded carrier URLs. No ClawHub lookup. No human in the loop.'); + console.log(); + console.log(' When a new carrier registers in APIX, every OpenClaw agent that'); + console.log(' queries "shipping-quote" discovers it automatically — on the next query.'); + console.log(); + console.log(` Sandbox dashboard: ${reg.dashboardUrl ?? `https://www.api-index.org/sandbox/${sandboxId}`}`); + console.log(); +} + +// Fallback mock quotes (in case demo endpoints are not reachable) +function mockQuote(svc) { + const prices = { + 'NordLogistik Economy EU': { amount: 12.40, currency: 'APX' }, + 'SwiftCargo Express': { amount: 35.00, currency: 'APX' }, + 'PacRim Express': { amount: 18.20, currency: 'APX' }, + }; + const days = { 'NordLogistik Economy EU': 5, 'SwiftCargo Express': 2, 'PacRim Express': 7 }; + return { + carrier: svc.serviceName, + price: prices[svc.serviceName] ?? { amount: 20.00, currency: 'APX' }, + estimatedDays: days[svc.serviceName] ?? 5, + trackingAvailable: true, + }; +} + +main().catch(err => { + console.error('\n Error:', err.message); + process.exit(1); +});