@hyperscale0/cli 1.0.0-alpha.4 → 1.0.0-alpha.6

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 (3) hide show
  1. package/README.md +14 -4
  2. package/hyperscale.js +29 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @hyperscale0/cli
2
2
 
3
3
  The Hyperscale founder command line. It logs a founder in, discovers public
4
- Blueprints, composes Products, manages sandbox API keys, and calls one
5
- Product's current operation catalog.
4
+ Blueprints, composes Products, manages sandbox API keys, and runs one Product's
5
+ frozen public verbs.
6
6
 
7
7
  ## Use
8
8
 
@@ -15,13 +15,23 @@ bunx @hyperscale0/cli product create --blueprint vehicle-escrow-marketplace --na
15
15
  bunx @hyperscale0/cli key create --name "Founder laptop"
16
16
 
17
17
  export HYPERSCALE_API_KEY=<your Product API key>
18
+ bunx @hyperscale0/cli product verbs
19
+ bunx @hyperscale0/cli product verbs escrow.release --json
20
+ bunx @hyperscale0/cli product run escrow.release --input '{"saleId":"sale_sandbox_..."}'
18
21
  bunx @hyperscale0/cli whoami
19
22
  bunx @hyperscale0/cli ops list account
20
23
  bunx @hyperscale0/cli call account.list --input '{"limit":5}'
21
24
  ```
22
25
 
23
- `npx @hyperscale0/cli` runs the same command. Add `--json` to any of them for
24
- machine-readable output.
26
+ `product verbs` lists the frozen Product surface. Pass one exact public
27
+ operation id such as `escrow.release` to return its full input schema, output
28
+ schema, policies, build id, surface version, and operation-set digest.
29
+ `npx @hyperscale0/cli` runs the same commands. Add
30
+ `--json` for machine-readable success and failure output.
31
+
32
+ Product-key commands need no founder login profile. On a clean machine, set
33
+ `HYPERSCALE_API_KEY` and pass `--product <id>`. Use `--base-url` and
34
+ `--environment` when their defaults do not name the Product's plane.
25
35
 
26
36
  Point it at another host with `--base-url`, and at the live plane with
27
37
  `--environment live`. Live keys and sandbox keys are never interchangeable.
package/hyperscale.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import{spawn as ce}from"node:child_process";import{createHash as ue}from"node:crypto";import{chmod as x,mkdir as de,readdir as le,readFile as L,rename as pe,unlink as fe,writeFile as we}from"node:fs/promises";import{homedir as me}from"node:os";import{basename as he,dirname as R,join as P}from"node:path";const v="https://hyperscale0.ai",O="sandbox",_="hyperscale-cli-session",W=process.platform==="darwin"?"/usr/bin/security":null,ye="config.json",z=3e4,ke=`hyperscale <command> [options]
2
+ import{spawn as he}from"node:child_process";import{createHash as me}from"node:crypto";import{chmod as j,mkdir as ye,readdir as ge,readFile as _,rename as $e,unlink as be,writeFile as ke}from"node:fs/promises";import{homedir as Se}from"node:os";import{basename as Pe,dirname as J,join as A}from"node:path";const E="https://hyperscale0.ai",B="sandbox",F="hyperscale-cli-session",Z=process.platform==="darwin"?"/usr/bin/security":null,Ie="config.json",X=3e4,ve=`hyperscale <command> [options]
3
3
 
4
4
  Commands:
5
5
  auth login Log in with email, password, and MFA when required
@@ -10,6 +10,9 @@ Commands:
10
10
  product create Create a Product from a Blueprint
11
11
  product list List Products in the signed-in company
12
12
  product show [id] Show a Product (default: selected Product)
13
+ product verbs [operation-id]
14
+ List frozen public verbs or describe one exactly
15
+ product run <operation-id> Run one frozen public verb
13
16
  key create Create a sandbox Product API key
14
17
  key list List keys for the selected Product
15
18
  key revoke <id> Revoke a Product API key
@@ -19,22 +22,22 @@ Commands:
19
22
 
20
23
  Options:
21
24
  --api-key <key> Product API key (default: $HYPERSCALE_API_KEY)
22
- --base-url <url> API origin (default: $HYPERSCALE_BASE_URL, else ${v})
23
- --environment <name> sandbox or live (default: $HYPERSCALE_ENVIRONMENT, else ${O})
25
+ --base-url <url> API origin (default: $HYPERSCALE_BASE_URL, else ${E})
26
+ --environment <name> sandbox or live (default: $HYPERSCALE_ENVIRONMENT, else ${B})
24
27
  --email <email> Login email (password and MFA stay on hidden stdin)
25
28
  --blueprint <id> Blueprint for product create
26
29
  --name <name> Product or key name
27
- --product <id> Product override for key commands
28
- --input <json> JSON input object for call
29
- --input-file <path> Read call input from a JSON file
30
- --idempotency-key <key> Idempotency key for a mutation (default: generated)
30
+ --product <id> Product id for key commands and Product-key verbs
31
+ --input <json> JSON input object for call or product run
32
+ --input-file <path> Read call or product run input from a JSON file
33
+ --idempotency-key <key> Idempotency key for a mutation or public verb (default: generated)
31
34
  --passkey Print the portal pointer for passkey login
32
35
  --json Print machine-readable JSON
33
36
  --version Print the CLI version
34
37
  --help Print this help
35
38
 
36
39
  Password, MFA code, and user-session bearer never ride in arguments or
37
- environment variables. Passkey login runs in the portal at ${v}/signin.
40
+ environment variables. Passkey login runs in the portal at ${E}/signin.
38
41
 
39
42
  The Product key rides in the environment, never on the command line, unless
40
43
  you are pasting a one-off into a throwaway shell.
@@ -47,21 +50,26 @@ Examples:
47
50
  hyperscale blueprint list
48
51
  hyperscale product create --blueprint vehicle-escrow-marketplace --name "Vehicle Escrow"
49
52
  hyperscale key create --name "Founder laptop"
53
+ hyperscale product verbs
54
+ hyperscale product verbs escrow.release --json
55
+ hyperscale product run escrow.release --input '{"saleId":"sale_sandbox_..."}'
50
56
  hyperscale ops list account
51
57
  hyperscale call account.list --input '{"limit":5}'
52
58
  hyperscale call account.list --input-file input.json
53
59
  printf '{"limit":5}' | hyperscale call account.list
54
60
  hyperscale call account.retrieve --input '{"accountId":"acct_sandbox_..."}'
55
- `;class c extends Error{}const ge=new Set(["--api-key","--base-url","--blueprint","--email","--environment","--idempotency-key","--input","--input-file","--name","--product"]);function $e(e){const n=[],t={};for(let s=0;s<e.length;s+=1){const o=e[s];if(!o.startsWith("--")){n.push(o);continue}if(ge.has(o)){const r=e[s+1];if(r===void 0)throw new c(`${o} needs a value`);t[o]=r,s+=1;continue}if(o==="--help"||o==="--version"||o==="--json"||o==="--passkey"){t[o]=!0;continue}throw new c(`Unknown option: ${o}`)}return{flags:t,positional:n}}function be(e,n){const t=e["--api-key"]??n.HYPERSCALE_API_KEY;if(!t)throw new c("No API key. Set HYPERSCALE_API_KEY, or pass --api-key. Request access at https://hyperscale0.ai/request-access.");const s=e["--environment"]??n.HYPERSCALE_ENVIRONMENT??O;if(s!=="sandbox"&&s!=="live")throw new c(`Unknown environment: ${s}. Use sandbox or live.`);const o=(e["--base-url"]??n.HYPERSCALE_BASE_URL??v).replace(/\/+$/,"");return{apiKey:t,baseUrl:o,environment:s}}function j(e,n,t={}){const s=e["--environment"]??n.HYPERSCALE_ENVIRONMENT??t.environment??O;if(s!=="sandbox"&&s!=="live")throw new c(`Unknown environment: ${s}. Use sandbox or live.`);const o=(e["--base-url"]??n.HYPERSCALE_BASE_URL??t.baseUrl??v).replace(/\/+$/,"");let r;try{r=new URL(o)}catch{r=null}const i=r?.origin??null;if(!i||i!==o)throw new c(`Invalid API origin: ${o}. Use an origin such as https://hyperscale0.ai.`);const a=["localhost","127.0.0.1","[::1]","::1"].includes(r.hostname);if(r.protocol!=="https:"&&!(r.protocol==="http:"&&a))throw new c(`Refusing cleartext API origin ${o}. Use HTTPS unless the API runs on loopback.`);return{baseUrl:o,environment:s}}function Q(e){return e.HYPERSCALE_CONFIG_DIR??P(me(),".config","hyperscale")}function X(e){return P(Q(e),ye)}function g(e,n){return P(Q(n),`session-${b(e)}.json`)}async function J(e,n){const t=g(e,n),s=`${he(t)}.`;let o;try{o=await le(R(t),{withFileTypes:!0})}catch(r){if(r&&r.code==="ENOENT")return;throw r}for(const r of o){if(!r.isFile()||!r.name.startsWith(s))continue;const i=r.name.slice(s.length);/^\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/.test(i)&&await A(P(R(t),r.name))}}async function Se(e){try{return JSON.parse(await L(e,"utf8"))}catch(n){if(n&&n.code==="ENOENT")return null;throw n instanceof SyntaxError?new c(`${e} is not valid JSON`):n}}async function B(e,n){const t=Ie(e);await de(t,{mode:448,recursive:!0}),await x(t,448);const s=`${e}.${process.pid}.${globalThis.crypto.randomUUID()}.tmp`;await we(s,n,{flag:"wx",mode:384}),await x(s,384);try{await pe(s,e),await x(e,384)}catch(o){throw await A(s),o}}function Ie(e){return R(e)}async function A(e){try{await fe(e)}catch(n){if(!n||n.code!=="ENOENT")throw n}}function Pe(e,n){if(!e||typeof e!="object"||Array.isArray(e))throw new c(`${n} does not contain a CLI profile`);const{baseUrl:t,email:s,environment:o,selectedProduct:r}=e;if(typeof t!="string"||typeof s!="string"||o!=="sandbox"&&o!=="live"||!(r===null||typeof r=="string"))throw new c(`${n} does not contain a valid CLI profile`);return{baseUrl:t,email:s,environment:o,selectedProduct:r}}async function E(e){const n=X(e),t=await Se(n);return t===null?null:Pe(t,n)}async function Z(e,n){const t={email:n.email,baseUrl:n.baseUrl,environment:n.environment,selectedProduct:n.selectedProduct??null};await B(X(e),`${JSON.stringify(t,null,2)}
56
- `)}function b(e){return ue("sha256").update(`${e.baseUrl}\0${e.environment}\0${e.email}`).digest("hex")}async function F(e,n){return W===null?{error:null,status:null,stdout:"",unavailable:!0}:await new Promise(t=>{const s=ce(W,e,{stdio:["pipe","pipe","pipe"]}),o=[];s.stdout.on("data",r=>o.push(r)),s.stderr.on("data",()=>{}),s.once("error",r=>t({error:r,status:null,stdout:"",unavailable:r?.code==="ENOENT"})),s.once("close",r=>t({error:null,status:r,stdout:Buffer.concat(o).toString("utf8"),unavailable:!1})),n===void 0?s.stdin.end():s.stdin.end(`${n}
57
- `)})}function ee(e){try{const n=typeof e=="string"?JSON.parse(e):e;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.sessionToken=="string"&&typeof n.tenantId=="string")return{sessionToken:n.sessionToken,tenantId:n.tenantId}}catch{}return null}async function ve(e,n,t){await J(e,t);const s=b(e),o=JSON.stringify(n),r=["add-generic-password","-a",s,"-s",_,"-U","-X",Buffer.from(o,"utf8").toString("hex")].join(" ");return(await F(["-i"],r)).status===0?(await A(g(e,t)),"keychain"):(await B(g(e,t),`${JSON.stringify({account:s,credential:n})}
58
- `),"file")}async function Ae(e,n){const t=b(e);try{const s=JSON.parse(await L(g(e,n),"utf8"));if(!s||typeof s!="object"||Array.isArray(s)||s.account!==t)return{state:"invalid"};const o=ee(s.credential);return o?{credential:o,state:"valid"}:{state:"invalid"}}catch(s){return s&&s.code==="ENOENT"?{state:"missing"}:{state:"invalid"}}}async function M(e,n){await J(e,n);const t=await Ae(e,n);if(t.state==="valid")return t.credential;if(t.state==="invalid")return null;const s=await F(["find-generic-password","-a",b(e),"-s",_,"-w"]);if(s.status===0){const o=ee(s.stdout.trim());if(o)return o}return null}async function H(e,n){await J(e,n);const t=b(e),s=await F(["delete-generic-password","-a",b(e),"-s",_]);return!s.unavailable&&s.status!==0&&s.status!==44?(await B(g(e,n),`${JSON.stringify({account:t,revoked:!0})}
59
- `),{keychainDeleted:!1}):(await A(g(e,n)),{keychainDeleted:!0})}async function S(e,n){const t=await E(n);if(!t)throw new c('Not logged in. Run "hyperscale auth login".');const s=j(e,n,t);if(s.baseUrl!==t.baseUrl||s.environment!==t.environment)throw new c(`The active session belongs to ${t.baseUrl} (${t.environment}). Log in again to change it.`);const o=await M(t,n);if(!o)throw new c('Session credential is missing. Run "hyperscale auth login".');return{...t,...o}}function te(e){return e.replace(/\p{Cc}/gu,n=>`
60
- \r`.includes(n)?n:"")}function T(e){try{return new URL(e).origin}catch{return null}}function ne(e,n){let t=e;for(const s of n)typeof s=="string"&&s.length>0&&(t=t.replaceAll(s,"[redacted]"));return t}async function oe(e,n,t={},s){const o=`${e.baseUrl}${n}`,r=T(e.baseUrl);if(T(o)!==r)throw new c(`The requested path ${n} leaves the ${r} origin; refusing the request.`);try{const i=await fetch(o,{...t,headers:{accept:"application/json",...s?{authorization:`Bearer ${s}`}:{},"x-hyperscale-environment":e.environment,...t.headers},signal:AbortSignal.timeout(z),redirect:"manual"});if(i.status>=300&&i.status<400)throw new c(`${o} redirected, so no credential was sent on. Point --base-url at the origin the API answers on.`);return{response:i,text:te(await i.text())}}catch(i){throw i instanceof c?i:new c(`Could not reach ${o}: ${i.message}`)}}async function y(e,n,t={},s,o=[]){const{response:r,text:i}=await oe(e,n,t,s);if(!r.ok)throw C(r,i,o);try{return JSON.parse(i)}catch{throw new c(`HTTP ${r.status} returned invalid JSON`)}}function I(e){return{body:JSON.stringify(e),headers:{"content-type":"application/json","idempotency-key":globalThis.crypto.randomUUID()},method:"POST"}}let m="";async function K(e,n=!1){return process.stderr.write(e),process.stdin.isTTY&&typeof process.stdin.setRawMode=="function"?await Te(n):await Ee()}async function Ee(){const e=se();if(e!==null)return e;if(process.stdin.readableEnded){const n=m.replace(/\r$/,"");return m="",n}return await new Promise((n,t)=>{const s=a=>{m+=a.toString("utf8");const d=se();d!==null&&(i(),n(d))},o=()=>{const a=m.replace(/\r$/,"");m="",i(),n(a)},r=a=>{i(),t(a)},i=()=>{process.stdin.off("data",s),process.stdin.off("end",o),process.stdin.off("error",r),process.stdin.pause()};process.stdin.on("data",s),process.stdin.once("end",o),process.stdin.once("error",r),process.stdin.resume()})}function se(){const e=/\r?\n/.exec(m);if(!e||e.index===void 0)return null;const n=m.slice(0,e.index);return m=m.slice(e.index+e[0].length),n}async function Te(e){return await new Promise((n,t)=>{const s=process.stdin.isRaw;let o="";const r=(d,l)=>{process.stdin.off("data",a),process.stdin.off("error",i),process.stdin.setRawMode(!!s),process.stdin.pause(),process.stderr.write(`
61
- `),l?t(l):n(d)},i=d=>r("",d),a=d=>{for(const l of d.toString("utf8")){if(l===""){r("",new c("Login cancelled"));return}if(l==="\r"||l===`
62
- `){r(o);return}if(l==="\x7F"||l==="\b"){if(o.length===0)continue;o=o.slice(0,-1),e||process.stderr.write("\b \b");continue}o+=l,e||process.stderr.write(l)}};process.stdin.setRawMode(!0),process.stdin.on("data",a),process.stdin.once("error",i),process.stdin.resume()})}async function Ce(){const e=[];for await(const n of process.stdin)e.push(Buffer.from(n));return Buffer.concat(e).toString("utf8")}async function re(e,n,t={}){const s=`${e.baseUrl}${n}`,o=T(e.baseUrl);if(T(s)!==o)throw new c(`The product document puts ${n} on another origin than the ${o} you configured; refusing to send the key there.`);try{const r=await fetch(s,{...t,headers:{accept:"application/json",authorization:`Bearer ${e.apiKey}`,"x-hyperscale-environment":e.environment,...t.headers},signal:AbortSignal.timeout(z),redirect:"manual"});if(r.status>=300&&r.status<400)throw new c(`${s} 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:r,text:te(await r.text())}}catch(r){throw r instanceof c?r:new c(`Could not reach ${s}: ${r.message}`)}}async function Y(e){const{response:n,text:t}=await re(e,"/v1/llms.txt");if(!n.ok)throw C(n,t,[e.apiKey]);const s=/^# (.+)$/m.exec(t),o=/^> Generated for composition (\S+) /m.exec(t),r=/^## Operations \((\d+)\)$/m.exec(t);if(!s||!r)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 f of t.matchAll(a))i.push({idempotency:f[5],method:f[1],name:f[3],operationId:f[4],path:f[2]});const d=Number(r[1]);if(i.length!==d)throw new Error(`The product document declares ${d} operations but ${i.length} parsed; upgrade the CLI.`);const l=/^- Package: (.+)$/m.exec(t);return{compositionId:o?o[1]:null,operations:i,packageName:l?l[1].trim():null,title:s[1].trim()}}function C(e,n,t=[]){let s=ne(n.trim(),t);try{const o=JSON.parse(n);o&&o.error&&typeof o.error.message=="string"&&(s=ne(`${o.error.code}: ${o.error.message}`,t))}catch{}return new c(`HTTP ${e.status}${s?` \xB7 ${s}`:""}`)}function ie(e,n){return{authenticated:n,baseUrl:e.baseUrl,email:e.email,environment:e.environment,selectedProduct:e.selectedProduct}}function Ne(e,n){if(!e||typeof e!="object"||e.status!=="authenticated"||typeof e.sessionToken!="string"||typeof e.tenantId!="string")throw new c(`Login did not return a new session. Use the portal at ${n}/signin.`);return e}async function Ue(e,n){const t=j(e,n);if(e["--passkey"]){u(`Passkey login runs in the portal: ${t.baseUrl}/signin`);return}const s=e["--email"]??(await K("Email: ",!1)).trim();if(!s)throw new c("Login email is required");const o=await K("Password: ",!0);if(!o)throw new c("Password is required");let r=await y(t,"/v1/identity/login",I({email:s,password:o}),void 0,[o]);if(r?.status==="mfa_required"){const w=r.mfaChallengeToken;if(typeof w!="string"||w.length===0)throw new c(`MFA must continue in the portal: ${t.baseUrl}/signin`);for(;r?.status!=="authenticated";){const h=await K("MFA code: ",!0);if(!h)throw new c("MFA code is required");if(r=await y(t,"/v1/identity/login/mfa/verify",I({code:h,mfaChallengeToken:w}),void 0,[o,w,h]),r?.status==="denied"){const k=r.attemptsRemaining;if(typeof k!="number"||k<=0)throw new c(`MFA was denied. Continue in the portal: ${t.baseUrl}/signin`);process.stderr.write(`MFA denied. ${k} attempts remain.
63
- `)}}}const i=Ne(r,t.baseUrl),a=await E(n),d={email:s,...t,selectedProduct:a&&a.email===s&&a.baseUrl===t.baseUrl&&a.environment===t.environment?a.selectedProduct:null},l=await ve(d,{sessionToken:i.sessionToken,tenantId:i.tenantId},n);try{await Z(n,d)}catch(w){throw await H(d,n),w}l==="file"&&process.stderr.write(`macOS Keychain is unavailable. The session is in ${g(d,n)} with mode 0600.
64
- `);const f=ie(d,!0);e["--json"]?p(f):u(`Logged in as ${s} to ${t.baseUrl} (${t.environment}).`)}async function xe(e,n){const t=await E(n);if(!t){const r={authenticated:!1};e["--json"]?p(r):u("Not logged in.");return}const s=await M(t,n),o=ie(t,s!==null);if(e["--json"]){p(o);return}u(s?`Logged in as ${t.email}.`:"Session credential missing."),u(`API: ${t.baseUrl}`),u(`Environment: ${t.environment}`),u(`Product: ${t.selectedProduct??"not selected"}`)}async function Le(e,n){const t=await E(n);if(!t){e["--json"]?p({status:"signed_out"}):u("Already logged out.");return}const s=await M(t,n);if(!s){const d=await H(t,n);q(e,{cleanup:d,message:"Already logged out.",status:"signed_out"});return}const{response:o,text:r}=await oe(t,"/v1/identity/logout",I({}),s.sessionToken);if(!o.ok&&o.status!==401&&o.status!==403)throw C(o,r,[s.sessionToken]);const i=await H(t,n);if(o.status===401||o.status===403){q(e,{cleanup:i,message:"The server had already rejected this session.",serverStatus:o.status,status:"signed_out"});return}let a={};try{a=JSON.parse(r)}catch{}q(e,{cleanup:i,message:"The server revoked the session.",revokedAt:a.revokedAt??null,status:a.status??"revoked"})}function q(e,n){const t=n.cleanup.keychainDeleted?"complete":"tombstoned";e["--json"]?p({credentialCleanup:t,...n.revokedAt!==void 0?{revokedAt:n.revokedAt}:{},...n.serverStatus!==void 0?{serverStatus:n.serverStatus}:{},status:n.status}):u(n.message),n.cleanup.keychainDeleted||process.stderr.write(`Keychain cleanup failed. A 0600 profile tombstone blocks the stale session.
65
- `)}function N(e,n){if(!e||typeof e!="object"||!Array.isArray(e.items))throw new c(`${n} returned an invalid collection`);return e.items}async function D(e,n,t,s,o,r=[]){const i=[],a=new Set;let d,l;for(;;){const k=new URLSearchParams(t);d&&k.set("cursor",d);const ae=k.size>0?`?${k}`:"",U=await y(e,`${n}${ae}`,{},o,r);l===void 0&&(l=U),i.push(...N(U,s));const $=U.nextCursor;if(typeof $!="string"||$.length===0)break;if(a.has($))throw new c(`${s} repeated cursor ${$}`);a.add($),d=$}const{items:f,nextCursor:w,...h}=l;return{...h,items:i}}async function Re(e,n){const t=await D(e,"/v1/blueprints",{},"blueprint.list"),s=N(t,"blueprint.list");if(n["--json"]){p(t);return}for(const o of s)u(`${o.id} ${o.name}${o.summary?` ${o.summary}`:""}`)}async function Oe(e,n,t){if(!t)throw new c("blueprint show needs a Blueprint id");const s=await y(e,`/v1/blueprints/${encodeURIComponent(t)}`);p(s)}async function _e(e,n){const t=e["--blueprint"],s=e["--name"];if(!t)throw new c("product create needs --blueprint <id>");if(!s)throw new c("product create needs --name <name>");const o=await S(e,n),r=await y(o,"/v1/workspaces/blueprint-compose",I({blueprintId:t,name:s,tenantId:o.tenantId}),o.sessionToken,[o.sessionToken]);if(typeof r?.workspaceId!="string"||typeof r?.blueprintId!="string"||typeof r?.workspaceBuildId!="string")throw new c("workspace.blueprint.compose returned an invalid Product");await Z(n,{...o,selectedProduct:r.workspaceId});const i={blueprintId:r.blueprintId,nextSteps:[`hyperscale product show ${r.workspaceId}`,'hyperscale key create --name "Founder CLI"'],workspaceBuildId:r.workspaceBuildId,workspaceId:r.workspaceId};if(e["--json"]){p(i);return}u(`Product: ${i.workspaceId}`),u(`Blueprint: ${i.blueprintId}`),u(`Build: ${i.workspaceBuildId}`),u("Next:");for(const a of i.nextSteps)u(` ${a}`)}function je(e){return new URLSearchParams({tenantId:e}).toString()}async function Je(e,n){const t=await S(e,n),s=await D(t,"/v1/products",{tenantId:t.tenantId},"product.list",t.sessionToken,[t.sessionToken]),o=N(s,"product.list");if(e["--json"]){p(s);return}for(const r of o){const i=r.workspaceId??r.id??"unknown",a=r.displayName??r.name??"unnamed";u(`${i} ${a}${r.status?` ${r.status}`:""}`)}}async function Be(e,n,t){const s=await S(e,n),o=t??s.selectedProduct;if(!o)throw new c("product show needs an id because no Product is selected. Create a Product first.");const r=await y(s,`/v1/products/${encodeURIComponent(o)}?${je(s.tenantId)}`,{},s.sessionToken,[s.sessionToken]);p(r)}function V(e,n){const t=n["--product"]??e.selectedProduct;if(!t)throw new c("No Product selected. Pass --product <id> or create a Product first.");return t}async function Fe(e,n){const t=await S(e,n),s=V(t,e),o=await y(t,"/v1/sandbox-api-keys",I({name:e["--name"]??"Founder CLI",tenantId:t.tenantId,workspaceId:s}),t.sessionToken,[t.sessionToken]);if(e["--json"]){p(o);return}u(`Key: ${o.apiKeyId??"unknown"}`),u(`Product: ${o.workspaceId??s}`),u(`Status: ${o.status??"unknown"}`),o.secret?(u(`Secret: ${o.secret}`),u("Store it now. Hyperscale will not return it again.")):u("The key already existed, so the one-time secret is unavailable.")}async function Me(e,n){const t=await S(e,n),s=V(t,e),o=await D(t,"/v1/api-keys",{tenantId:t.tenantId},"api_key.list",t.sessionToken,[t.sessionToken]),r=N(o,"api_key.list").filter(a=>a.workspaceId===s),i={...o,items:r};if(e["--json"]){p(i);return}for(const a of r)u(`${a.apiKeyId} ${a.name??"unnamed"} ${a.status??"unknown"}`)}async function He(e,n,t){if(!t)throw new c("key revoke needs an API key id");const s=await S(e,n),o=V(s,e),r=await y(s,`/v1/api-keys/${encodeURIComponent(t)}/revoke`,I({tenantId:s.tenantId,workspaceId:o}),s.sessionToken,[s.sessionToken]);e["--json"]?p(r):u(`Key ${r.apiKeyId??t}: ${r.status??"revoked"}`)}async function Ke(e,n){const t=await Y(e);if(n["--json"]){u(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 s=[["Product",t.title],["Composition",t.compositionId??"unknown"],["Environment",e.environment],["API",e.baseUrl],["Operations",String(t.operations.length)],["SDK package",t.packageName??"not published"]],o=Math.max(...s.map(([r])=>r.length));for(const[r,i]of s)u(`${r.padEnd(o)} ${i}`)}async function Ye(e,n,t){const s=await Y(e),o=t?t.toLowerCase():null,r=o?s.operations.filter(a=>a.name.toLowerCase().includes(o)||a.path.toLowerCase().includes(o)):s.operations;if(n["--json"]){u(JSON.stringify(r,null,2));return}if(r.length===0){u(t?`No operation matches ${t}`:"No operations");return}const i=Math.max(...r.map(a=>a.name.length));for(const a of r)u(`${a.name.padEnd(i)} ${a.method.padEnd(6)}${a.path}`)}function qe(e,n){const t={...n},s=[],o=e.path.replace(/\{([^}]+)\}/g,(r,i)=>{const a=t[i];return a==null?(s.push(i),""):(delete t[i],encodeURIComponent(String(a)))});if(s.length>0)throw new c(`${e.name} needs ${s.join(", ")} in --input`);return{path:o,remaining:t}}function De(e){const n=new URLSearchParams;for(const[s,o]of Object.entries(e))o!=null&&n.set(s,typeof o=="string"?o:JSON.stringify(o));const t=n.toString();return t?`?${t}`:""}async function Ve(e,n,t){if(!t)throw new c("call needs an operation name");const o=(await Y(e)).operations.find(h=>h.name===t||h.operationId===t);if(!o)throw new c(`${t} is not an operation of this product. Run "hyperscale ops list" to see them.`);const r=await Ge(n),{path:i,remaining:a}=qe(o,r),d={method:o.method};let l=i;o.method==="GET"?l+=De(a):(d.body=JSON.stringify(a),d.headers={"content-type":"application/json",...o.idempotency==="not_required"?{}:{"idempotency-key":n["--idempotency-key"]??globalThis.crypto.randomUUID()}});const{response:f,text:w}=await re(e,l,d);if(!f.ok)throw C(f,w,[e.apiKey]);u(We(w))}async function Ge(e){if(e["--input"]!==void 0&&e["--input-file"]!==void 0)throw new c("Use only one of --input and --input-file");if(e["--input"]!==void 0)return G(e["--input"],"--input");if(e["--input-file"]!==void 0){let n;try{n=await L(e["--input-file"],"utf8")}catch(t){throw new c(`Could not read ${e["--input-file"]}: ${t.message}`)}return G(n,"--input-file")}if(!process.stdin.isTTY){const n=await Ce();if(n.trim()!=="")return G(n,"stdin")}return{}}function G(e,n="--input"){if(e===void 0)return{};let t;try{t=JSON.parse(e)}catch(s){throw new c(`${n} is not valid JSON: ${s.message}`)}if(t===null||typeof t!="object"||Array.isArray(t))throw new c(`${n} must be a JSON object`);return t}function We(e){if(e.trim()==="")return"";try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function u(e){process.stdout.write(`${e}
66
- `)}function p(e){u(JSON.stringify(e,null,2))}async function ze(){const{readFileSync:e}=await import("node:fs");return JSON.parse(e(new URL("./package.json",import.meta.url),"utf8")).version}async function Qe(e,n){const{flags:t,positional:s}=$e(e);if(t["--version"])return u(await ze()),0;if(t["--help"]||s.length===0)return process.stdout.write(ke),s.length===0&&!t["--help"]?1:0;const[o,...r]=s;if(o==="auth"){if(r[0]==="login")await Ue(t,n);else if(r[0]==="status")await xe(t,n);else if(r[0]==="logout")await Le(t,n);else throw new c('Auth subcommands are "login", "status", and "logout"');return 0}if(o==="blueprint"){const a=j(t,n);if(r[0]==="list")await Re(a,t);else if(r[0]==="show")await Oe(a,t,r[1]);else throw new c('Blueprint subcommands are "list" and "show"');return 0}if(o==="product"){if(r[0]==="create")await _e(t,n);else if(r[0]==="list")await Je(t,n);else if(r[0]==="show")await Be(t,n,r[1]);else throw new c('Product subcommands are "create", "list", and "show"');return 0}if(o==="key"){if(r[0]==="create")await Fe(t,n);else if(r[0]==="list")await Me(t,n);else if(r[0]==="revoke")await He(t,n,r[1]);else throw new c('Key subcommands are "create", "list", and "revoke"');return 0}const i=be(t,n);if(o==="whoami")return await Ke(i,t),0;if(o==="ops"){if(r[0]!=="list")throw new c('The only ops subcommand is "list"');return await Ye(i,t,r[1]),0}if(o==="call")return await Ve(i,t,r[0]),0;throw new c(`Unknown command: ${o}`)}Qe(process.argv.slice(2),process.env).then(e=>{process.exitCode=e},e=>{process.stderr.write(`${e instanceof c?e.message:e.stack??String(e)}
67
- `),process.exitCode=1});
61
+ `;class c extends Error{}const Ae=new Set(["--api-key","--base-url","--blueprint","--email","--environment","--idempotency-key","--input","--input-file","--name","--product"]);function Ee(e){const n=[],t={};for(let r=0;r<e.length;r+=1){const o=e[r];if(!o.startsWith("--")){n.push(o);continue}if(Ae.has(o)){const s=e[r+1];if(s===void 0)throw new c(`${o} needs a value`);t[o]=s,r+=1;continue}if(o==="--help"||o==="--version"||o==="--json"||o==="--passkey"){t[o]=!0;continue}throw new c(`Unknown option: ${o}`)}return{flags:t,positional:n}}function Te(e,n){const t=e["--api-key"]??n.HYPERSCALE_API_KEY;if(!t)throw new c("No API key. Set HYPERSCALE_API_KEY, or pass --api-key. Request access at https://hyperscale0.ai/request-access.");const r=e["--environment"]??n.HYPERSCALE_ENVIRONMENT??B;if(r!=="sandbox"&&r!=="live")throw new c(`Unknown environment: ${r}. Use sandbox or live.`);const o=(e["--base-url"]??n.HYPERSCALE_BASE_URL??E).replace(/\/+$/,"");return{apiKey:t,baseUrl:o,environment:r}}function T(e,n,t={}){const r=e["--environment"]??n.HYPERSCALE_ENVIRONMENT??t.environment??B;if(r!=="sandbox"&&r!=="live")throw new c(`Unknown environment: ${r}. Use sandbox or live.`);const o=(e["--base-url"]??n.HYPERSCALE_BASE_URL??t.baseUrl??E).replace(/\/+$/,"");let s;try{s=new URL(o)}catch{s=null}const i=s?.origin??null;if(!i||i!==o)throw new c(`Invalid API origin: ${o}. Use an origin such as https://hyperscale0.ai.`);const a=["localhost","127.0.0.1","[::1]","::1"].includes(s.hostname);if(s.protocol!=="https:"&&!(s.protocol==="http:"&&a))throw new c(`Refusing cleartext API origin ${o}. Use HTTPS unless the API runs on loopback.`);return{baseUrl:o,environment:r}}function ee(e){return e.HYPERSCALE_CONFIG_DIR??A(Se(),".config","hyperscale")}function te(e){return A(ee(e),Ie)}function $(e,n){return A(ee(n),`session-${S(e)}.json`)}async function D(e,n){const t=$(e,n),r=`${Pe(t)}.`;let o;try{o=await ge(J(t),{withFileTypes:!0})}catch(s){if(s&&s.code==="ENOENT")return;throw s}for(const s of o){if(!s.isFile()||!s.name.startsWith(r))continue;const i=s.name.slice(r.length);/^\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}\.tmp$/.test(i)&&await N(A(J(t),s.name))}}async function Ne(e){try{return JSON.parse(await _(e,"utf8"))}catch(n){if(n&&n.code==="ENOENT")return null;throw n instanceof SyntaxError?new c(`${e} is not valid JSON`):n}}async function K(e,n){const t=Ue(e);await ye(t,{mode:448,recursive:!0}),await j(t,448);const r=`${e}.${process.pid}.${globalThis.crypto.randomUUID()}.tmp`;await ke(r,n,{flag:"wx",mode:384}),await j(r,384);try{await $e(r,e),await j(e,384)}catch(o){throw await N(r),o}}function Ue(e){return J(e)}async function N(e){try{await be(e)}catch(n){if(!n||n.code!=="ENOENT")throw n}}function Ce(e,n){if(!e||typeof e!="object"||Array.isArray(e))throw new c(`${n} does not contain a CLI profile`);const{baseUrl:t,email:r,environment:o,selectedProduct:s}=e;if(typeof t!="string"||typeof r!="string"||o!=="sandbox"&&o!=="live"||!(s===null||typeof s=="string"))throw new c(`${n} does not contain a valid CLI profile`);return{baseUrl:t,email:r,environment:o,selectedProduct:s}}async function v(e){const n=te(e),t=await Ne(n);return t===null?null:Ce(t,n)}async function ne(e,n){const t={email:n.email,baseUrl:n.baseUrl,environment:n.environment,selectedProduct:n.selectedProduct??null};await K(te(e),`${JSON.stringify(t,null,2)}
62
+ `)}function S(e){return me("sha256").update(`${e.baseUrl}\0${e.environment}\0${e.email}`).digest("hex")}async function H(e,n){return Z===null?{error:null,status:null,stdout:"",unavailable:!0}:await new Promise(t=>{const r=he(Z,e,{stdio:["pipe","pipe","pipe"]}),o=[];r.stdout.on("data",s=>o.push(s)),r.stderr.on("data",()=>{}),r.once("error",s=>t({error:s,status:null,stdout:"",unavailable:s?.code==="ENOENT"})),r.once("close",s=>t({error:null,status:s,stdout:Buffer.concat(o).toString("utf8"),unavailable:!1})),n===void 0?r.stdin.end():r.stdin.end(`${n}
63
+ `)})}function re(e){try{const n=typeof e=="string"?JSON.parse(e):e;if(n&&typeof n=="object"&&!Array.isArray(n)&&typeof n.sessionToken=="string"&&typeof n.tenantId=="string")return{sessionToken:n.sessionToken,tenantId:n.tenantId}}catch{}return null}async function Oe(e,n,t){await D(e,t);const r=S(e),o=JSON.stringify(n),s=["add-generic-password","-a",r,"-s",F,"-U","-X",Buffer.from(o,"utf8").toString("hex")].join(" ");return(await H(["-i"],s)).status===0?(await N($(e,t)),"keychain"):(await K($(e,t),`${JSON.stringify({account:r,credential:n})}
64
+ `),"file")}async function xe(e,n){const t=S(e);try{const r=JSON.parse(await _($(e,n),"utf8"));if(!r||typeof r!="object"||Array.isArray(r)||r.account!==t)return{state:"invalid"};const o=re(r.credential);return o?{credential:o,state:"valid"}:{state:"invalid"}}catch(r){return r&&r.code==="ENOENT"?{state:"missing"}:{state:"invalid"}}}async function M(e,n){await D(e,n);const t=await xe(e,n);if(t.state==="valid")return t.credential;if(t.state==="invalid")return null;const r=await H(["find-generic-password","-a",S(e),"-s",F,"-w"]);if(r.status===0){const o=re(r.stdout.trim());if(o)return o}return null}async function Y(e,n){await D(e,n);const t=S(e),r=await H(["delete-generic-password","-a",S(e),"-s",F]);return!r.unavailable&&r.status!==0&&r.status!==44?(await K($(e,n),`${JSON.stringify({account:t,revoked:!0})}
65
+ `),{keychainDeleted:!1}):(await N($(e,n)),{keychainDeleted:!0})}async function P(e,n){const t=await v(n);if(!t)throw new c('Not logged in. Run "hyperscale auth login".');const r=T(e,n,t);if(r.baseUrl!==t.baseUrl||r.environment!==t.environment)throw new c(`The active session belongs to ${t.baseUrl} (${t.environment}). Log in again to change it.`);const o=await M(t,n);if(!o)throw new c('Session credential is missing. Run "hyperscale auth login".');return{...t,...o}}function oe(e){return e.replace(/\p{Cc}/gu,n=>`
66
+ \r`.includes(n)?n:"")}function U(e){try{return new URL(e).origin}catch{return null}}function b(e,n){let t=e;for(const r of n)typeof r=="string"&&r.length>0&&(t=t.replaceAll(r,"[redacted]"));return t}async function se(e,n,t={},r){const o=`${e.baseUrl}${n}`,s=U(e.baseUrl);if(U(o)!==s)throw new c(`The requested path ${n} leaves the ${s} origin; refusing the request.`);try{const i=await fetch(o,{...t,headers:{accept:"application/json",...r?{authorization:`Bearer ${r}`}:{},"x-hyperscale-environment":e.environment,...t.headers},signal:AbortSignal.timeout(X),redirect:"manual"});if(i.status>=300&&i.status<400)throw new c(`${o} redirected, so no credential was sent on. Point --base-url at the origin the API answers on.`);return{response:i,text:oe(await i.text())}}catch(i){throw i instanceof c?i:new c(`Could not reach ${o}: ${i.message}`)}}async function y(e,n,t={},r,o=[]){const{response:s,text:i}=await se(e,n,t,r);if(!s.ok)throw O(s,i,o);try{return JSON.parse(i)}catch{throw new c(`HTTP ${s.status} returned invalid JSON`)}}function I(e){return{body:JSON.stringify(e),headers:{"content-type":"application/json","idempotency-key":globalThis.crypto.randomUUID()},method:"POST"}}let h="";async function q(e,n=!1){return process.stderr.write(e),process.stdin.isTTY&&typeof process.stdin.setRawMode=="function"?await Le(n):await Re()}async function Re(){const e=ie();if(e!==null)return e;if(process.stdin.readableEnded){const n=h.replace(/\r$/,"");return h="",n}return await new Promise((n,t)=>{const r=a=>{h+=a.toString("utf8");const d=ie();d!==null&&(i(),n(d))},o=()=>{const a=h.replace(/\r$/,"");h="",i(),n(a)},s=a=>{i(),t(a)},i=()=>{process.stdin.off("data",r),process.stdin.off("end",o),process.stdin.off("error",s),process.stdin.pause()};process.stdin.on("data",r),process.stdin.once("end",o),process.stdin.once("error",s),process.stdin.resume()})}function ie(){const e=/\r?\n/.exec(h);if(!e||e.index===void 0)return null;const n=h.slice(0,e.index);return h=h.slice(e.index+e[0].length),n}async function Le(e){return await new Promise((n,t)=>{const r=process.stdin.isRaw;let o="";const s=(d,l)=>{process.stdin.off("data",a),process.stdin.off("error",i),process.stdin.setRawMode(!!r),process.stdin.pause(),process.stderr.write(`
67
+ `),l?t(l):n(d)},i=d=>s("",d),a=d=>{for(const l of d.toString("utf8")){if(l===""){s("",new c("Login cancelled"));return}if(l==="\r"||l===`
68
+ `){s(o);return}if(l==="\x7F"||l==="\b"){if(o.length===0)continue;o=o.slice(0,-1),e||process.stderr.write("\b \b");continue}o+=l,e||process.stderr.write(l)}};process.stdin.setRawMode(!0),process.stdin.on("data",a),process.stdin.once("error",i),process.stdin.resume()})}async function je(){const e=[];for await(const n of process.stdin)e.push(Buffer.from(n));return Buffer.concat(e).toString("utf8")}async function C(e,n,t={}){const r=`${e.baseUrl}${n}`,o=U(e.baseUrl);if(U(r)!==o)throw new c(`The product document puts ${n} on another origin than the ${o} you configured; refusing to send the key there.`);try{const s=await fetch(r,{...t,headers:{accept:"application/json",authorization:`Bearer ${e.apiKey}`,"x-hyperscale-environment":e.environment,...t.headers},signal:AbortSignal.timeout(X),redirect:"manual"});if(s.status>=300&&s.status<400)throw new c(`${r} 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:oe(await s.text())}}catch(s){throw s instanceof c?s:new c(`Could not reach ${r}: ${s.message}`)}}async function V(e){const{response:n,text:t}=await C(e,"/v1/llms.txt");if(!n.ok)throw O(n,t,[e.apiKey]);const r=/^# (.+)$/m.exec(t),o=/^> Generated for composition (\S+) /m.exec(t),s=/^## Operations \((\d+)\)$/m.exec(t);if(!r||!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 f of t.matchAll(a))i.push({idempotency:f[5],method:f[1],name:f[3],operationId:f[4],path:f[2]});const d=Number(s[1]);if(i.length!==d)throw new Error(`The product document declares ${d} operations but ${i.length} parsed; upgrade the CLI.`);const l=/^- Package: (.+)$/m.exec(t);return{compositionId:o?o[1]:null,operations:i,packageName:l?l[1].trim():null,title:r[1].trim()}}function O(e,n,t=[]){let r=b(n.trim(),t);try{const o=JSON.parse(n);o&&o.error&&typeof o.error.message=="string"&&(r=b(`${o.error.code}: ${o.error.message}`,t))}catch{}return new c(`HTTP ${e.status}${r?` \xB7 ${r}`:""}`)}function ae(e,n){return{authenticated:n,baseUrl:e.baseUrl,email:e.email,environment:e.environment,selectedProduct:e.selectedProduct}}function _e(e,n){if(!e||typeof e!="object"||e.status!=="authenticated"||typeof e.sessionToken!="string"||typeof e.tenantId!="string")throw new c(`Login did not return a new session. Use the portal at ${n}/signin.`);return e}async function Je(e,n){const t=T(e,n);if(e["--passkey"]){u(`Passkey login runs in the portal: ${t.baseUrl}/signin`);return}const r=e["--email"]??(await q("Email: ",!1)).trim();if(!r)throw new c("Login email is required");const o=await q("Password: ",!0);if(!o)throw new c("Password is required");let s=await y(t,"/v1/identity/login",I({email:r,password:o}),void 0,[o]);if(s?.status==="mfa_required"){const w=s.mfaChallengeToken;if(typeof w!="string"||w.length===0)throw new c(`MFA must continue in the portal: ${t.baseUrl}/signin`);for(;s?.status!=="authenticated";){const m=await q("MFA code: ",!0);if(!m)throw new c("MFA code is required");if(s=await y(t,"/v1/identity/login/mfa/verify",I({code:m,mfaChallengeToken:w}),void 0,[o,w,m]),s?.status==="denied"){const g=s.attemptsRemaining;if(typeof g!="number"||g<=0)throw new c(`MFA was denied. Continue in the portal: ${t.baseUrl}/signin`);process.stderr.write(`MFA denied. ${g} attempts remain.
69
+ `)}}}const i=_e(s,t.baseUrl),a=await v(n),d={email:r,...t,selectedProduct:a&&a.email===r&&a.baseUrl===t.baseUrl&&a.environment===t.environment?a.selectedProduct:null},l=await Oe(d,{sessionToken:i.sessionToken,tenantId:i.tenantId},n);try{await ne(n,d)}catch(w){throw await Y(d,n),w}l==="file"&&process.stderr.write(`macOS Keychain is unavailable. The session is in ${$(d,n)} with mode 0600.
70
+ `);const f=ae(d,!0);e["--json"]?p(f):u(`Logged in as ${r} to ${t.baseUrl} (${t.environment}).`)}async function Be(e,n){const t=await v(n);if(!t){const s={authenticated:!1};e["--json"]?p(s):u("Not logged in.");return}const r=await M(t,n),o=ae(t,r!==null);if(e["--json"]){p(o);return}u(r?`Logged in as ${t.email}.`:"Session credential missing."),u(`API: ${t.baseUrl}`),u(`Environment: ${t.environment}`),u(`Product: ${t.selectedProduct??"not selected"}`)}async function Fe(e,n){const t=await v(n);if(!t){e["--json"]?p({status:"signed_out"}):u("Already logged out.");return}const r=await M(t,n);if(!r){const d=await Y(t,n);z(e,{cleanup:d,message:"Already logged out.",status:"signed_out"});return}const{response:o,text:s}=await se(t,"/v1/identity/logout",I({}),r.sessionToken);if(!o.ok&&o.status!==401&&o.status!==403)throw O(o,s,[r.sessionToken]);const i=await Y(t,n);if(o.status===401||o.status===403){z(e,{cleanup:i,message:"The server had already rejected this session.",serverStatus:o.status,status:"signed_out"});return}let a={};try{a=JSON.parse(s)}catch{}z(e,{cleanup:i,message:"The server revoked the session.",revokedAt:a.revokedAt??null,status:a.status??"revoked"})}function z(e,n){const t=n.cleanup.keychainDeleted?"complete":"tombstoned";e["--json"]?p({credentialCleanup:t,...n.revokedAt!==void 0?{revokedAt:n.revokedAt}:{},...n.serverStatus!==void 0?{serverStatus:n.serverStatus}:{},status:n.status}):u(n.message),n.cleanup.keychainDeleted||process.stderr.write(`Keychain cleanup failed. A 0600 profile tombstone blocks the stale session.
71
+ `)}function x(e,n){if(!e||typeof e!="object"||!Array.isArray(e.items))throw new c(`${n} returned an invalid collection`);return e.items}async function G(e,n,t,r,o,s=[]){const i=[],a=new Set;let d,l;for(;;){const g=new URLSearchParams(t);d&&g.set("cursor",d);const we=g.size>0?`?${g}`:"",L=await y(e,`${n}${we}`,{},o,s);l===void 0&&(l=L),i.push(...x(L,r));const k=L.nextCursor;if(typeof k!="string"||k.length===0)break;if(a.has(k))throw new c(`${r} repeated cursor ${k}`);a.add(k),d=k}const{items:f,nextCursor:w,...m}=l;return{...m,items:i}}async function De(e,n){const t=await G(e,"/v1/blueprints",{},"blueprint.list"),r=x(t,"blueprint.list");if(n["--json"]){p(t);return}for(const o of r)u(`${o.id} ${o.name}${o.summary?` ${o.summary}`:""}`)}async function Ke(e,n,t){if(!t)throw new c("blueprint show needs a Blueprint id");const r=await y(e,`/v1/blueprints/${encodeURIComponent(t)}`);p(r)}async function He(e,n){const t=e["--blueprint"],r=e["--name"];if(!t)throw new c("product create needs --blueprint <id>");if(!r)throw new c("product create needs --name <name>");const o=await P(e,n),s=await y(o,"/v1/workspaces/blueprint-compose",I({blueprintId:t,name:r,tenantId:o.tenantId}),o.sessionToken,[o.sessionToken]);if(typeof s?.workspaceId!="string"||typeof s?.blueprintId!="string"||typeof s?.workspaceBuildId!="string")throw new c("workspace.blueprint.compose returned an invalid Product");await ne(n,{...o,selectedProduct:s.workspaceId});const i={blueprintId:s.blueprintId,nextSteps:[`hyperscale product show ${s.workspaceId}`,'hyperscale key create --name "Founder CLI"'],workspaceBuildId:s.workspaceBuildId,workspaceId:s.workspaceId};if(e["--json"]){p(i);return}u(`Product: ${i.workspaceId}`),u(`Blueprint: ${i.blueprintId}`),u(`Build: ${i.workspaceBuildId}`),u("Next:");for(const a of i.nextSteps)u(` ${a}`)}function Me(e){return new URLSearchParams({tenantId:e}).toString()}async function Ye(e,n){const t=await P(e,n),r=await G(t,"/v1/products",{tenantId:t.tenantId},"product.list",t.sessionToken,[t.sessionToken]),o=x(r,"product.list");if(e["--json"]){p(r);return}for(const s of o){const i=s.workspaceId??s.id??"unknown",a=s.displayName??s.name??"unnamed";u(`${i} ${a}${s.status?` ${s.status}`:""}`)}}async function qe(e,n,t){const r=await P(e,n),o=t??r.selectedProduct;if(!o)throw new c("product show needs an id because no Product is selected. Create a Product first.");const s=await y(r,`/v1/products/${encodeURIComponent(o)}?${Me(r.tenantId)}`,{},r.sessionToken,[r.sessionToken]);p(s)}function R(e,n){const t=n["--product"]??e.selectedProduct;if(!t)throw new c("No Product selected. Pass --product <id> or create a Product first.");return t}async function ce(e,n){const t=await v(n),r=R(t??{},e),o=T(e,n,t??{});if(t&&(o.baseUrl!==t.baseUrl||o.environment!==t.environment))throw new c(`The selected Product belongs to ${t.baseUrl} (${t.environment}). Select a Product at the configured API origin first.`);const s=e["--api-key"]??n.HYPERSCALE_API_KEY;if(!s)throw new c("No API key. Set HYPERSCALE_API_KEY, or pass --api-key. Request access at https://hyperscale0.ai/request-access.");return{...o,apiKey:s,workspaceId:r}}async function Ve(e,n,t){const r=await ce(e,n),o=`/v1/workspaces/${encodeURIComponent(r.workspaceId)}/operation-set`,{response:s,text:i}=await C(r,o);if(!s.ok)throw de(s,i,[r.apiKey]);const a=Ge(i,s.status);if(t){ze(a,t,e);return}if(e["--json"]){p(a);return}if(a.status==="legacy"){u("This Product uses a legacy surface. It has no frozen public verbs yet."),u(`Build: ${a.workspaceBuildId}`);return}if(a.publicVerbs.length===0)u("No public verbs");else{u("Operation ID Input Kind");for(const d of a.publicVerbs){const l=d.idempotencyPolicy?.mode==="not_required"?"read-only":"mutation";u(`${d.noun}.${d.name} ${We(d.inputSchema)} ${l}`)}}u(`Digest: ${a.tenantOperationSetDigest}`),u(`Build: ${a.workspaceBuildId}`),u(`Surface version: ${a.tenantSurfaceVersion}`)}function ze(e,n,t){if(e.status==="legacy")throw new c(`This Product uses a legacy surface and has no frozen public verb named ${n}. Run "hyperscale product verbs" after rebuilding the Product.`);const r=e.publicVerbs.find(s=>s&&`${s.noun}.${s.name}`===n);if(!r)throw new c(`Unknown frozen public verb: ${n}. Run "hyperscale product verbs" to list exact names.`);const o={status:e.status,workspaceBuildId:e.workspaceBuildId,tenantSurfaceVersion:e.tenantSurfaceVersion,tenantOperationSetDigest:e.tenantOperationSetDigest,publicVerb:r};if(t["--json"]){p(o);return}u(`Operation ID: ${r.noun}.${r.name}`),u(`Noun: ${r.noun}`),u(`Input schema: ${JSON.stringify(r.inputSchema,null,2)}`),u(`Output schema: ${JSON.stringify(r.outputSchema,null,2)}`),u(`Actor policy: ${JSON.stringify(r.actorPolicy,null,2)}`),u(`Port policy: ${JSON.stringify(r.portPolicy,null,2)}`),u(`Idempotency policy: ${JSON.stringify(r.idempotencyPolicy,null,2)}`),u(`Receipt policy: ${JSON.stringify(r.receiptPolicy,null,2)}`),u(`Receipt evidence: ${JSON.stringify(r.receiptEvidence,null,2)}`),u(`Digest: ${e.tenantOperationSetDigest}`),u(`Build: ${e.workspaceBuildId}`),u(`Surface version: ${e.tenantSurfaceVersion}`)}function Ge(e,n){let t;try{t=JSON.parse(e)}catch{throw new c(`HTTP ${n} returned invalid JSON`)}if(!t||typeof t!="object"||t.status!=="legacy"&&t.status!=="frozen"||typeof t.workspaceBuildId!="string")throw new c("The Product operation set is not in the expected format. Upgrade the CLI.");if(t.status==="legacy")return t;if(typeof t.tenantSurfaceVersion!="number"||typeof t.tenantOperationSetDigest!="string"||!Array.isArray(t.publicVerbs))throw new c("The Product operation set is not in the expected format. Upgrade the CLI.");return t}function We(e){if(!e||typeof e!="object")return"input schema unavailable";const n=e.properties&&typeof e.properties=="object"&&!Array.isArray(e.properties)?e.properties:{},t=new Set(Array.isArray(e.required)?e.required.filter(o=>typeof o=="string"):[]),r=Object.entries(n).map(([o,s])=>`${o}${t.has(o)?"":"?"}: ${ue(s)}`);return r.length===0?"no input":r.join(", ")}function ue(e){return!e||typeof e!="object"?"value":Array.isArray(e.enum)&&e.enum.length>0?e.enum.map(n=>String(n)).join(" | "):e.type==="array"?`${ue(e.items)}[]`:typeof e.type=="string"?typeof e.format=="string"?`${e.type} (${e.format})`:e.type:"value"}async function Qe(e,n,t){if(!t)throw new c("product run needs a public verb");const r=await ce(e,n),o=await pe(e),{response:s,text:i}=await C(r,`/v1/operations/${encodeURIComponent(t)}`,{body:JSON.stringify(o),headers:{"content-type":"application/json","idempotency-key":e["--idempotency-key"]??globalThis.crypto.randomUUID()},method:"POST"});if(!s.ok)throw de(s,i,[r.apiKey]);const a=s.headers.get("hyperscale-operation-id"),d=s.headers.get("hyperscale-receipt-id");if(e["--json"]){p({operationId:a,receiptId:d,output:Ze(i)});return}u(`Operation: ${a??"not returned"}`),u(`Receipt: ${d??"not returned"}`);const l=fe(i);l&&u(l)}function Ze(e){if(e.trim()==="")return null;try{return JSON.parse(e)}catch{return e}}function de(e,n,t=[]){const r=b(n.trim(),t);let o;try{o=JSON.parse(n)}catch{return new c(`HTTP ${e.status}${r?` \xB7 ${r}`:""}`)}if(!o?.error||typeof o.error.message!="string")return new c(`HTTP ${e.status}`);const s=typeof o.error.code=="string"?`${o.error.code}: `:"",i=b(`HTTP ${e.status} \xB7 ${s}${o.error.message}`,t),a=Xe(o.error.details,t);return new c([i,...a].join(`
72
+ `))}function Xe(e,n){if(!e||typeof e!="object")return[];if(Array.isArray(e.issues))return e.issues.flatMap(r=>{if(!r||typeof r!="object")return[];const s=(Array.isArray(r.path)?r.path.join("."):"input")||"input";return typeof r.message=="string"?[`${s}: ${b(r.message,n)}`]:[]});const t=[];return W(e,"",t,n),t}function W(e,n,t,r){if(Array.isArray(e)){if(e.every(o=>o===null||typeof o!="object")){t.push(`${le(n)}: ${b(e.join(", "),r)}`);return}e.forEach((o,s)=>W(o,`${n}[${s}]`,t,r));return}if(e&&typeof e=="object"){for(const[o,s]of Object.entries(e))W(s,n?`${n}.${o}`:o,t,r);return}n&&t.push(`${le(n)}: ${b(String(e),r)}`)}function le(e){const n=e.replace(/([a-z0-9])([A-Z])/g,"$1 $2").replaceAll("."," ");return n.charAt(0).toUpperCase()+n.slice(1)}async function et(e,n){const t=await P(e,n),r=R(t,e),o=await y(t,"/v1/sandbox-api-keys",I({name:e["--name"]??"Founder CLI",tenantId:t.tenantId,workspaceId:r}),t.sessionToken,[t.sessionToken]);if(e["--json"]){p(o);return}u(`Key: ${o.apiKeyId??"unknown"}`),u(`Product: ${o.workspaceId??r}`),u(`Status: ${o.status??"unknown"}`),o.secret?(u(`Secret: ${o.secret}`),u("Store it now. Hyperscale will not return it again.")):u("The key already existed, so the one-time secret is unavailable.")}async function tt(e,n){const t=await P(e,n),r=R(t,e),o=await G(t,"/v1/api-keys",{tenantId:t.tenantId},"api_key.list",t.sessionToken,[t.sessionToken]),s=x(o,"api_key.list").filter(a=>a.workspaceId===r),i={...o,items:s};if(e["--json"]){p(i);return}for(const a of s)u(`${a.apiKeyId} ${a.name??"unnamed"} ${a.status??"unknown"}`)}async function nt(e,n,t){if(!t)throw new c("key revoke needs an API key id");const r=await P(e,n),o=R(r,e),s=await y(r,`/v1/api-keys/${encodeURIComponent(t)}/revoke`,I({tenantId:r.tenantId,workspaceId:o}),r.sessionToken,[r.sessionToken]);e["--json"]?p(s):u(`Key ${s.apiKeyId??t}: ${s.status??"revoked"}`)}async function rt(e,n){const t=await V(e);if(n["--json"]){u(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 r=[["Product",t.title],["Composition",t.compositionId??"unknown"],["Environment",e.environment],["API",e.baseUrl],["Operations",String(t.operations.length)],["SDK package",t.packageName??"not published"]],o=Math.max(...r.map(([s])=>s.length));for(const[s,i]of r)u(`${s.padEnd(o)} ${i}`)}async function ot(e,n,t){const r=await V(e),o=t?t.toLowerCase():null,s=o?r.operations.filter(a=>a.name.toLowerCase().includes(o)||a.path.toLowerCase().includes(o)):r.operations;if(n["--json"]){u(JSON.stringify(s,null,2));return}if(s.length===0){u(t?`No operation matches ${t}`:"No operations");return}const i=Math.max(...s.map(a=>a.name.length));for(const a of s)u(`${a.name.padEnd(i)} ${a.method.padEnd(6)}${a.path}`)}function st(e,n){const t={...n},r=[],o=e.path.replace(/\{([^}]+)\}/g,(s,i)=>{const a=t[i];return a==null?(r.push(i),""):(delete t[i],encodeURIComponent(String(a)))});if(r.length>0)throw new c(`${e.name} needs ${r.join(", ")} in --input`);return{path:o,remaining:t}}function it(e){const n=new URLSearchParams;for(const[r,o]of Object.entries(e))o!=null&&n.set(r,typeof o=="string"?o:JSON.stringify(o));const t=n.toString();return t?`?${t}`:""}async function at(e,n,t){if(!t)throw new c("call needs an operation name");const o=(await V(e)).operations.find(m=>m.name===t||m.operationId===t);if(!o)throw new c(`${t} is not an operation of this product. Run "hyperscale ops list" to see them.`);const s=await pe(n),{path:i,remaining:a}=st(o,s),d={method:o.method};let l=i;o.method==="GET"?l+=it(a):(d.body=JSON.stringify(a),d.headers={"content-type":"application/json",...o.idempotency==="not_required"?{}:{"idempotency-key":n["--idempotency-key"]??globalThis.crypto.randomUUID()}});const{response:f,text:w}=await C(e,l,d);if(!f.ok)throw O(f,w,[e.apiKey]);u(fe(w))}async function pe(e){if(e["--input"]!==void 0&&e["--input-file"]!==void 0)throw new c("Use only one of --input and --input-file");if(e["--input"]!==void 0)return Q(e["--input"],"--input");if(e["--input-file"]!==void 0){let n;try{n=await _(e["--input-file"],"utf8")}catch(t){throw new c(`Could not read ${e["--input-file"]}: ${t.message}`)}return Q(n,"--input-file")}if(!process.stdin.isTTY){const n=await je();if(n.trim()!=="")return Q(n,"stdin")}return{}}function Q(e,n="--input"){if(e===void 0)return{};let t;try{t=JSON.parse(e)}catch(r){throw new c(`${n} is not valid JSON: ${r.message}`)}if(t===null||typeof t!="object"||Array.isArray(t))throw new c(`${n} must be a JSON object`);return t}function fe(e){if(e.trim()==="")return"";try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}function u(e){process.stdout.write(`${e}
73
+ `)}function p(e){u(JSON.stringify(e,null,2))}function ct(e,n){if(!n.includes("--json")){process.stderr.write(`${e instanceof c?e.message:e.stack??String(e)}
74
+ `);return}const t=e instanceof c;process.stderr.write(`${JSON.stringify({error:{code:t?"user_error":"internal_error",message:t?e.message:"The command failed unexpectedly.",fix:t?'Follow the message, or run "hyperscale --help" for command usage.':"Report the failure without including credentials."}})}
75
+ `)}async function ut(){const{readFileSync:e}=await import("node:fs");return JSON.parse(e(new URL("./package.json",import.meta.url),"utf8")).version}async function dt(e,n){const{flags:t,positional:r}=Ee(e);if(t["--version"])return u(await ut()),0;if(t["--help"]||r.length===0){if(r.length===0&&!t["--help"]&&t["--json"])throw new c('No command. Run "hyperscale --help" to list commands.');return process.stdout.write(ve),r.length===0&&!t["--help"]?1:0}const[o,...s]=r;if(o==="auth"){if(s[0]==="login")await Je(t,n);else if(s[0]==="status")await Be(t,n);else if(s[0]==="logout")await Fe(t,n);else throw new c('Auth subcommands are "login", "status", and "logout"');return 0}if(o==="blueprint"){const a=T(t,n);if(s[0]==="list")await De(a,t);else if(s[0]==="show")await Ke(a,t,s[1]);else throw new c('Blueprint subcommands are "list" and "show"');return 0}if(o==="product"){if(s[0]==="create")await He(t,n);else if(s[0]==="list")await Ye(t,n);else if(s[0]==="show")await qe(t,n,s[1]);else if(s[0]==="verbs")await Ve(t,n,s[1]);else if(s[0]==="run")await Qe(t,n,s[1]);else throw new c('Product subcommands are "create", "list", "show", "verbs", and "run"');return 0}if(o==="key"){if(s[0]==="create")await et(t,n);else if(s[0]==="list")await tt(t,n);else if(s[0]==="revoke")await nt(t,n,s[1]);else throw new c('Key subcommands are "create", "list", and "revoke"');return 0}const i=Te(t,n);if(o==="whoami")return await rt(i,t),0;if(o==="ops"){if(s[0]!=="list")throw new c('The only ops subcommand is "list"');return await ot(i,t,s[1]),0}if(o==="call")return await at(i,t,s[0]),0;throw new c(`Unknown command: ${o}`)}dt(process.argv.slice(2),process.env).then(e=>{process.exitCode=e},e=>{ct(e,process.argv.slice(2)),process.exitCode=1});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperscale0/cli",
3
- "version": "1.0.0-alpha.4",
3
+ "version": "1.0.0-alpha.6",
4
4
  "description": "The Hyperscale founder command line: compose Products, manage keys, and call Product operations.",
5
5
  "license": "LicenseRef-Hyperscale-Proprietary",
6
6
  "author": "Hyperscale LLC",