@hyperscale0/cli 1.0.0-alpha.1 → 1.0.0-alpha.2
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/hyperscale.js +4 -3
- package/package.json +1 -1
package/hyperscale.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const DEFAULT_BASE_URL="https://hyperscale0.ai",DEFAULT_ENVIRONMENT="sandbox",USAGE=`hyperscale <command> [options]
|
|
2
|
+
const DEFAULT_BASE_URL="https://hyperscale0.ai",DEFAULT_ENVIRONMENT="sandbox",REQUEST_TIMEOUT_MS=3e4,USAGE=`hyperscale <command> [options]
|
|
3
3
|
|
|
4
4
|
Commands:
|
|
5
5
|
whoami Show the product this API key resolves to
|
|
@@ -24,6 +24,7 @@ Examples:
|
|
|
24
24
|
hyperscale ops list account
|
|
25
25
|
hyperscale call account.list --input '{"limit":5}'
|
|
26
26
|
hyperscale call account.retrieve --input '{"accountId":"acct_sandbox_..."}'
|
|
27
|
-
`;class UserError extends Error{}const FLAGS_WITH_VALUES=new Set(["--api-key","--base-url","--environment","--idempotency-key","--input"]);function parseArgv(
|
|
28
|
-
|
|
27
|
+
`;class UserError extends Error{}const FLAGS_WITH_VALUES=new Set(["--api-key","--base-url","--environment","--idempotency-key","--input"]);function parseArgv(e){const r=[],t={};for(let o=0;o<e.length;o+=1){const n=e[o];if(!n.startsWith("--")){r.push(n);continue}if(FLAGS_WITH_VALUES.has(n)){const s=e[o+1];if(s===void 0)throw new UserError(`${n} needs a value`);t[n]=s,o+=1;continue}if(n==="--help"||n==="--version"||n==="--json"){t[n]=!0;continue}throw new UserError(`Unknown option: ${n}`)}return{flags:t,positional:r}}function resolveConfig(e,r){const t=e["--api-key"]??r.HYPERSCALE_API_KEY;if(!t)throw new UserError("No API key. Set HYPERSCALE_API_KEY, or pass --api-key. Mint a Product API key on the Developers desk.");const o=e["--environment"]??r.HYPERSCALE_ENVIRONMENT??DEFAULT_ENVIRONMENT;if(o!=="sandbox"&&o!=="live")throw new UserError(`Unknown environment: ${o}. Use sandbox or live.`);const n=(e["--base-url"]??r.HYPERSCALE_BASE_URL??DEFAULT_BASE_URL).replace(/\/+$/,"");return{apiKey:t,baseUrl:n,environment:o}}function withoutControlCharacters(e){return e.replace(/\p{Cc}/gu,r=>`
|
|
28
|
+
\r`.includes(r)?r:"")}function originOf(e){try{return new URL(e).origin}catch{return null}}async function request(e,r,t={}){const o=`${e.baseUrl}${r}`,n=originOf(e.baseUrl);if(originOf(o)!==n)throw new UserError(`The product document puts ${r} on another origin than the ${n} you configured; refusing to send the key there.`);try{const s=await fetch(o,{...t,headers:{accept:"application/json",authorization:`Bearer ${e.apiKey}`,"x-hyperscale-environment":e.environment,...t.headers},signal:AbortSignal.timeout(3e4),redirect:"manual"});if(s.status>=300&&s.status<400)throw new UserError(`${o} redirected, so the key was not sent on. Point --base-url at the origin the API answers on; the usual cause is http:// where it serves https://.`);return{response:s,text:withoutControlCharacters(await s.text())}}catch(s){throw s instanceof UserError?s:new UserError(`Could not reach ${o}: ${s.message}`)}}async function readProduct(e){const{response:r,text:t}=await request(e,"/v1/llms.txt");if(!r.ok)throw apiError(r,t);const o=/^# (.+)$/m.exec(t),n=/^> Generated for composition (\S+) /m.exec(t),s=/^## Operations \((\d+)\)$/m.exec(t);if(!o||!s)throw new Error("The product document is not in the expected format; upgrade the CLI.");const i=[],a=/^- ([A-Z]+) (\S+) · (\S+) \((\S+); idempotency (\S+)\)$/gm;for(const c of t.matchAll(a))i.push({idempotency:c[5],method:c[1],name:c[3],operationId:c[4],path:c[2]});const p=Number(s[1]);if(i.length!==p)throw new Error(`The product document declares ${p} operations but ${i.length} parsed; upgrade the CLI.`);const u=/^- Package: (.+)$/m.exec(t);return{compositionId:n?n[1]:null,operations:i,packageName:u?u[1].trim():null,title:o[1].trim()}}function apiError(e,r){let t=r.trim();try{const o=JSON.parse(r);o&&o.error&&typeof o.error.message=="string"&&(t=`${o.error.code}: ${o.error.message}`)}catch{}return new UserError(`HTTP ${e.status}${t?` \xB7 ${t}`:""}`)}async function commandWhoami(e,r){const t=await readProduct(e);if(r["--json"]){print(JSON.stringify({baseUrl:e.baseUrl,compositionId:t.compositionId,environment:e.environment,operationCount:t.operations.length,packageName:t.packageName,product:t.title},null,2));return}const o=[["Product",t.title],["Composition",t.compositionId??"unknown"],["Environment",e.environment],["API",e.baseUrl],["Operations",String(t.operations.length)],["SDK package",t.packageName??"not published"]],n=Math.max(...o.map(([s])=>s.length));for(const[s,i]of o)print(`${s.padEnd(n)} ${i}`)}async function commandOpsList(e,r,t){const o=await readProduct(e),n=t?t.toLowerCase():null,s=n?o.operations.filter(a=>a.name.toLowerCase().includes(n)||a.path.toLowerCase().includes(n)):o.operations;if(r["--json"]){print(JSON.stringify(s,null,2));return}if(s.length===0){print(t?`No operation matches ${t}`:"No operations");return}const i=Math.max(...s.map(a=>a.name.length));for(const a of s)print(`${a.name.padEnd(i)} ${a.method.padEnd(6)}${a.path}`)}function bindPath(e,r){const t={...r},o=[],n=e.path.replace(/\{([^}]+)\}/g,(s,i)=>{const a=t[i];return a==null?(o.push(i),""):(delete t[i],encodeURIComponent(String(a)))});if(o.length>0)throw new UserError(`${e.name} needs ${o.join(", ")} in --input`);return{path:n,remaining:t}}function queryString(e){const r=new URLSearchParams;for(const[o,n]of Object.entries(e))n!=null&&r.set(o,typeof n=="string"?n:JSON.stringify(n));const t=r.toString();return t?`?${t}`:""}async function commandCall(e,r,t){if(!t)throw new UserError("call needs an operation name");const n=(await readProduct(e)).operations.find(d=>d.name===t||d.operationId===t);if(!n)throw new UserError(`${t} is not an operation of this product. Run "hyperscale ops list" to see them.`);const s=parseInput(r["--input"]),{path:i,remaining:a}=bindPath(n,s),p={method:n.method};let u=i;n.method==="GET"?u+=queryString(a):(p.body=JSON.stringify(a),p.headers={"content-type":"application/json",...n.idempotency==="not_required"?{}:{"idempotency-key":r["--idempotency-key"]??globalThis.crypto.randomUUID()}});const{response:c,text:l}=await request(e,u,p);if(!c.ok)throw apiError(c,l);print(formatBody(l))}function parseInput(e){if(e===void 0)return{};let r;try{r=JSON.parse(e)}catch(t){throw new UserError(`--input is not valid JSON: ${t.message}`)}if(r===null||typeof r!="object"||Array.isArray(r))throw new UserError("--input must be a JSON object");return r}function formatBody(e){if(e.trim()==="")return"";try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function print(e){process.stdout.write(`${e}
|
|
29
|
+
`)}async function version(){const{readFileSync:e}=await import("node:fs");return JSON.parse(e(new URL("./package.json",import.meta.url),"utf8")).version}async function main(e,r){const{flags:t,positional:o}=parseArgv(e);if(t["--version"])return print(await version()),0;if(t["--help"]||o.length===0)return process.stdout.write(USAGE),o.length===0&&!t["--help"]?1:0;const[n,...s]=o,i=resolveConfig(t,r);if(n==="whoami")return await commandWhoami(i,t),0;if(n==="ops"){if(s[0]!=="list")throw new UserError('The only ops subcommand is "list"');return await commandOpsList(i,t,s[1]),0}if(n==="call")return await commandCall(i,t,s[0]),0;throw new UserError(`Unknown command: ${n}`)}main(process.argv.slice(2),process.env).then(e=>{process.exitCode=e},e=>{process.stderr.write(`${e instanceof UserError?e.message:e.stack??String(e)}
|
|
29
30
|
`),process.exitCode=1});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperscale0/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "The Hyperscale command line: drive one Product's operation surface with a Product API key.",
|
|
5
5
|
"license": "LicenseRef-Hyperscale-Proprietary",
|
|
6
6
|
"author": "Hyperscale LLC",
|