cornilius 1.0.3 → 1.0.4

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.
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import J from"node:readline";import w from"node:fs";import Y from"node:os";import F from"node:path";import R from"node:crypto";import G from"node:http";import{spawn as Q}from"node:child_process";var H=process.argv.slice(2),I=H.includes("--demo")||process.env.CORNILIUS_DEMO==="1",m=(process.env.CORNILIUS_API||"https://api.cornilius.ai").replace(/\/+$/,""),z=F.join(Y.homedir(),".cornilius"),_=F.join(z,"credentials.json"),T=null,V=process.stdout.isTTY&&!process.env.NO_COLOR,x=t=>e=>V?`\x1B[${t}m${e}\x1B[0m`:e,b=x("38;5;208"),a=t=>t,r=x("38;5;245"),D=x("1"),p=x("38;5;203"),X={C:[" \u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2551 ","\u2588\u2588\u2551 ","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D"],O:[" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ","\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D "],R:["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2551 \u2588\u2588\u2551","\u255A\u2550\u255D \u255A\u2550\u255D"],N:["\u2588\u2588\u2588\u2557 \u2588\u2588\u2557","\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551","\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551","\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"],I:["\u2588\u2588\u2557","\u2588\u2588\u2551","\u2588\u2588\u2551","\u2588\u2588\u2551","\u2588\u2588\u2551","\u255A\u2550\u255D"],L:["\u2588\u2588\u2557 ","\u2588\u2588\u2551 ","\u2588\u2588\u2551 ","\u2588\u2588\u2551 ","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"],U:["\u2588\u2588\u2557 \u2588\u2588\u2557","\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D "],S:["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551","\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"]};function Z(t){let e=["","","","","",""];for(let o of t){let n=X[o],i=Math.max(...n.map(s=>[...s].length));for(let s=0;s<6;s++)e[s]+=n[s].padEnd(i," ")+" "}return e.join(`
3
- `)}function M(){let t=I?"demo mode (offline, sample data)":new URL(m).host;console.log(),console.log(Z("CORNILIUS")),console.log(),console.log(" "+D(a("Head of Analytics"))+r(" \xB7 ")+a(t));let e=I?r("(demo)"):W()?b("\u25CF token loaded"):r("\u25CB not signed in \u2014 /login");console.log(" "+e),console.log(" "+r("Type your analytics question, or ")+a("/help")+r(" for commands. ")+a("/exit")+r(" to quit.")),console.log()}function W(){return process.env.CORNILIUS_TOKEN?process.env.CORNILIUS_TOKEN.trim():T}function q(){try{return JSON.parse(w.readFileSync(_,"utf8"))}catch{return{}}}function L(t){let{token:e,...o}=t;w.mkdirSync(z,{recursive:!0,mode:448});let n=`${_}.tmp`;w.writeFileSync(n,JSON.stringify(o),{mode:384}),w.renameSync(n,_);try{w.chmodSync(_,384)}catch{}}function ee(){try{let t=JSON.parse(w.readFileSync(_,"utf8"));t&&t.token&&L(t)}catch{}}var U="api.cornilius.ai",P=!1,te=3e4;async function A(t){if(I)return de(t);let e=W();if(!e)return{status:401,body:{error:"no token"}};let o;try{let d=new URL(m);if(o=d.host,d.protocol!=="https:"&&process.env.CORNILIUS_INSECURE!=="1")return{status:0,networkError:`refusing to send your token over ${d.protocol}// \u2014 set CORNILIUS_INSECURE=1 to override`}}catch{return{status:0,networkError:"invalid CORNILIUS_API"}}o!==U&&!P&&(P=!0,console.log(" "+p(`sending your token to ${o}`)+r(` (not the default ${U})`)));let n=new AbortController,i=setTimeout(()=>n.abort(),te),s;try{s=await fetch(`${m}/api/command`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({command:t}),signal:n.signal})}catch(d){return{status:0,networkError:d.name==="AbortError"?"request timed out":String(d.cause?.code||d.message||d)}}finally{clearTimeout(i)}let c=null;try{c=await s.json()}catch{c=null}return{status:s.status,body:c}}var B=[],k=null;function E(t,e=a){let o=t&&t.content||[];for(let n of o)n&&typeof n.text=="string"&&console.log(" "+e(n.text))}function j(t,{status:e,body:o,networkError:n}){if(n!==void 0){console.log(" "+p(`Couldn't reach Cornilius (${n}).`)+r(` [${m}]`));return}if(e===0)return;if(e===401){console.log(" "+p("Not signed in.")+r(" Run ")+a("/login")+r(" (or set CORNILIUS_TOKEN)."));return}if(e===429){let c=o&&(o.message||o.content&&o.content[0]&&o.content[0].text)||"Quota exceeded.";console.log(" "+p("\u26A0 "+c)),o&&o.upgrade_url&&console.log(" "+r(o.upgrade_url));return}if(!o){console.log(" "+p(`Unexpected response (HTTP ${e}).`));return}let i=o.kind;o.structuredContent&&Array.isArray(o.structuredContent.playbooks)&&(B=o.structuredContent.playbooks);let s=o.structuredContent&&o.structuredContent.confirmation_token;if(s&&/\/delete\b/.test(t)&&!/--confirm\b/.test(t)){let c=t.match(/\/delete\s+(\S+)\s+(\S+)/);if(c){k={type:c[1],id:c[2],token:s},E(o,p),console.log(" "+a("Type the word ")+D(a("delete"))+a(" to confirm, or anything else to cancel."));return}}if(i==="error"||o.isError)return E(o,p);i!=="noop"&&E(o,a)}function oe(){let t=[["just type\u2026","ask anything \u2014 Cornilius runs the analysis"],["/playbooks","list saved playbooks (numbered)"],["/run <#|id>","run an exact playbook \u2014 no guessing"],["/recall [topic]","what Cornilius remembers (issues, insights, history)"],["/delete <type> <id>","delete something \u2014 two-step, asks you to confirm"],["/status","tenant, session, usage"],["/whoami","who am I \u2014 your tenant id"],["/login /logout","sign in / sign out"],["/clear","clear the screen"],["/help","this help"],["/exit","quit (or Ctrl-D)"]];console.log();for(let[e,o]of t)console.log(" "+a(e.padEnd(22))+r(o));console.log()}function N(t){return t.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function ne(){let t=N(R.randomBytes(32)),e=N(R.createHash("sha256").update(t,"ascii").digest());return{verifier:t,challenge:e}}function re(){return N(R.randomBytes(24))}async function se(t){let e=q();if(e.client_id&&e.api===m)return e.client_id;let o=await fetch(`${m}/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:"Cornilius CLI",redirect_uris:[t],grant_types:["authorization_code"],token_endpoint_auth_method:"none"})});if(!o.ok)throw new Error(`registration failed (${o.status}): ${(await o.text()).slice(0,200)}`);let n=await o.json();if(!n.client_id)throw new Error("registration returned no client_id");return L({...e,client_id:n.client_id,api:m}),n.client_id}function ie(t){let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",o=process.platform==="win32"?["/c","start","",t]:[t];try{return Q(e,o,{stdio:"ignore",detached:!0}).unref(),!0}catch{return!1}}function ce(t){return`<!doctype html><meta charset="utf-8"><title>Cornilius</title><body style="font:16px system-ui;background:#0A0A0A;color:#FFF7ED;display:grid;place-items:center;height:100vh;margin:0"><div style="max-width:32rem;padding:2rem;text-align:center"><div style="color:#F59E0B;font-size:1.4rem;font-weight:700;margin-bottom:.75rem">Cornilius</div><p>${t==="success"?"Signed in to Cornilius. You can close this tab and return to your terminal.":t==="denied"?"Sign-in was cancelled. You can close this tab.":"Something went wrong. Return to your terminal and try again."}</p></div></body>`}async function ae(){if(I){console.log(" "+r("(demo) sign-in is simulated."));return}if(process.env.CORNILIUS_TOKEN){console.log(" "+r("CORNILIUS_TOKEN is set \u2014 that token is already used; /login not needed."));return}let t="/callback",e=18e4,{verifier:o,challenge:n}=ne(),i=re(),s,c,d=new Promise((l,f)=>{s=l,c=f}),S=G.createServer((l,f)=>{let g=new URL(l.url,"http://127.0.0.1");if(g.pathname!==t){f.writeHead(404).end();return}let u=g.searchParams.get("error"),y=g.searchParams.get("code"),v=g.searchParams.get("state"),C=!u&&y&&v===i;f.writeHead(C?200:400,{"Content-Type":"text/html; charset=utf-8"}),f.end(ce(C?"success":u==="access_denied"?"denied":"error")),u?c(new Error(u==="access_denied"?"you cancelled sign-in.":`authorization error: ${u}`)):v!==i?c(new Error("state mismatch \u2014 possible CSRF; aborting.")):y?s(y):c(new Error("no authorization code in callback."))});await new Promise(l=>S.listen(0,"127.0.0.1",l));let O=`http://127.0.0.1:${S.address().port}${t}`;try{let l=await se(O),f=`${m}/authorize?`+new URLSearchParams({response_type:"code",client_id:l,redirect_uri:O,state:i,code_challenge:n,code_challenge_method:"S256",scope:"mcp"}).toString();console.log(" "+b("Opening your browser to sign in\u2026")),console.log(" "+r(`If it doesn't open, paste this URL:
4
- `)+a(f)),ie(f);let g=await Promise.race([d,new Promise((v,C)=>setTimeout(()=>C(new Error("timed out after 3 minutes waiting for sign-in.")),e))]),u=await fetch(`${m}/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:g,redirect_uri:O,client_id:l,code_verifier:o}).toString()});if(!u.ok)throw new Error(`token exchange failed (${u.status}): ${(await u.text()).slice(0,200)}`);let y=await u.json();if(!y.access_token)throw new Error("token endpoint returned no access_token");T=y.access_token,L({...q(),client_id:l,api:m}),console.log(" "+b("\u2713 Signed in for this session.")+r(" Run ")+a("/status")+r(" to verify. You'll sign in again next time you open the CLI."))}catch(l){console.log(" "+p(`Sign-in failed: ${l.message}`)),process.exitCode=1}finally{S.close()}}function le(){T=null,console.log(" "+r("Signed out \u2014 session token cleared."))}async function K(t){let e=t.trim();if(!e)return;if(k){let{type:i,id:s,token:c}=k;k=null,e==="delete"?j(`/delete ${i} ${s}`,await A(`/delete ${i} ${s} --token ${c} --confirm delete`)):console.log(" "+r("cancelled \u2014 nothing was deleted."));return}if(e==="/help"||e==="/?"||e==="/")return oe();if(e==="/clear"){console.clear(),M();return}if((e==="/exit"||e==="/quit")&&(console.log(r(" bye.")),process.exit(0)),e==="/logout")return le();if(e==="/login")return ae();let o=e,n=e.match(/^\/run\s+(\d+)\s*$/);if(n){let i=B.find(s=>s.number===Number(n[1]));if(i&&i.id)o=`/run ${i.id}`;else{console.log(" "+p("Run /playbooks first, then /run <#>")+r(" (or /run <playbook-id> directly)."));return}}j(o,await A(o))}function de(t){let e=t.trim(),o=[{number:1,id:"pb-builtin-funnel-pa-step-dropoff",name:"Funnel step drop-off scan"},{number:2,id:"pb-builtin-retention-pa-cohort-curve",name:"Cohort retention curve diagnosis"},{number:3,id:"pb-builtin-activation-aha-finder",name:"Activation aha-moment finder"}];if(e==="/playbooks")return{status:200,body:{kind:"dispatch",structuredContent:{playbooks:o},content:[{type:"text",text:`\u{1F4CB} Saved playbooks (demo)
5
- `+o.map(n=>` ${n.number}. ${n.name}`).join(`
6
- `)}]}};if(e.startsWith("/run "))return{status:200,body:{kind:"dispatch",content:[{type:"text",text:`\u25B8 running ${e.slice(5)} (demo) \u2192 analyze(playbook_id=\u2026)`}]}};if(e.startsWith("/recall"))return{status:200,body:{kind:"dispatch",content:[{type:"text",text:"\u{1F9E0} Remembered (demo): 2 open issues \xB7 5 insights"}]}};if(e.startsWith("/status")||e.startsWith("/whoami"))return{status:200,body:{kind:"local",content:[{type:"text",text:"tenant acme-corp \xB7 ready (demo)"}]}};if(e.startsWith("/delete ")){let n=e.match(/\/delete\s+(\S+)\s+(\S+)/);return/--confirm\b/.test(e)?{status:200,body:{kind:"dispatch",content:[{type:"text",text:`\u2713 deleted ${n?n[1]+" '"+n[2]+"'":"item"} (demo) \u2014 recoverable for 30 days`}]}}:{status:200,body:{kind:"dispatch",isError:!0,structuredContent:{confirmation_token:"demo-token"},content:[{type:"text",text:`\u26A0 This will delete ${n?n[1]+" '"+n[2]+"'":"an item"} (demo).`}]}}}return e.startsWith("/")?{status:400,body:{kind:"error",content:[{type:"text",text:"unknown command (demo)"}]}}:{status:200,body:{kind:"dispatch",content:[{type:"text",text:`\u25B8 analyzing (demo) \u2192 analyze(type='custom', prompt='${e}')`}]}}}function ue(){console.log(`
2
+ import K from"node:readline";import w from"node:fs";import J from"node:os";import F from"node:path";import R from"node:crypto";import Y from"node:http";import{spawn as G}from"node:child_process";var z=process.argv.slice(2),I=z.includes("--demo")||process.env.CORNILIUS_DEMO==="1",m=(process.env.CORNILIUS_API||"https://api.cornilius.ai").replace(/\/+$/,""),D=F.join(J.homedir(),".cornilius"),_=F.join(D,"credentials.json"),T=null,Q=process.stdout.isTTY&&!process.env.NO_COLOR,x=t=>e=>Q?`\x1B[${t}m${e}\x1B[0m`:e,b=x("38;5;208"),a=t=>t,r=x("38;5;245"),V=x("1"),p=x("38;5;203"),X={C:[" \u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2551 ","\u2588\u2588\u2551 ","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D"],O:[" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ","\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D "],R:["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D","\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557","\u2588\u2588\u2551 \u2588\u2588\u2551","\u255A\u2550\u255D \u255A\u2550\u255D"],N:["\u2588\u2588\u2588\u2557 \u2588\u2588\u2557","\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551","\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551","\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551","\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"],I:["\u2588\u2588\u2557","\u2588\u2588\u2551","\u2588\u2588\u2551","\u2588\u2588\u2551","\u2588\u2588\u2551","\u255A\u2550\u255D"],L:["\u2588\u2588\u2557 ","\u2588\u2588\u2551 ","\u2588\u2588\u2551 ","\u2588\u2588\u2551 ","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"],U:["\u2588\u2588\u2557 \u2588\u2588\u2557","\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551","\u2588\u2588\u2551 \u2588\u2588\u2551","\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D"," \u255A\u2550\u2550\u2550\u2550\u2550\u255D "],S:["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557","\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551","\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551","\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D"]};function Z(t){let e=["","","","","",""];for(let n of t){let o=X[n],i=Math.max(...o.map(s=>[...s].length));for(let s=0;s<6;s++)e[s]+=o[s].padEnd(i," ")+" "}return e.join(`
3
+ `)}function H(){console.log(),console.log(Z("CORNILIUS")),console.log();let t=I?r("(demo) \u2014 offline, sample data"):M()?b("\u25CF token loaded"):r("\u25CB not signed in \u2014 /login"),e=I?"":r(" \xB7 ")+a(new URL(m).host);console.log(" "+t+e),console.log(" "+r("Type your analytics question, or ")+a("/help")+r(" for commands. ")+a("/exit")+r(" to quit.")),console.log()}function M(){return process.env.CORNILIUS_TOKEN?process.env.CORNILIUS_TOKEN.trim():T}function W(){try{return JSON.parse(w.readFileSync(_,"utf8"))}catch{return{}}}function L(t){let{token:e,...n}=t;w.mkdirSync(D,{recursive:!0,mode:448});let o=`${_}.tmp`;w.writeFileSync(o,JSON.stringify(n),{mode:384}),w.renameSync(o,_);try{w.chmodSync(_,384)}catch{}}function ee(){try{let t=JSON.parse(w.readFileSync(_,"utf8"));t&&t.token&&L(t)}catch{}}var U="api.cornilius.ai",P=!1,te=3e4;async function A(t){if(I)return ue(t);let e=M();if(!e)return{status:401,body:{error:"no token"}};let n;try{let u=new URL(m);if(n=u.host,u.protocol!=="https:"&&process.env.CORNILIUS_INSECURE!=="1")return{status:0,networkError:`refusing to send your token over ${u.protocol}// \u2014 set CORNILIUS_INSECURE=1 to override`}}catch{return{status:0,networkError:"invalid CORNILIUS_API"}}n!==U&&!P&&(P=!0,console.log(" "+p(`sending your token to ${n}`)+r(` (not the default ${U})`)));let o=new AbortController,i=setTimeout(()=>o.abort(),te),s;try{s=await fetch(`${m}/api/command`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${e}`},body:JSON.stringify({command:t}),signal:o.signal})}catch(u){return{status:0,networkError:u.name==="AbortError"?"request timed out":String(u.cause?.code||u.message||u)}}finally{clearTimeout(i)}let c=null;try{c=await s.json()}catch{c=null}return{status:s.status,body:c}}var q=[],k=null;function E(t,e=a){let n=t&&t.content||[];for(let o of n)o&&typeof o.text=="string"&&console.log(" "+e(o.text))}function j(t,{status:e,body:n,networkError:o}){if(o!==void 0){console.log(" "+p(`Couldn't reach Cornilius (${o}).`)+r(` [${m}]`));return}if(e===0)return;if(e===401){console.log(" "+p("Not signed in.")+r(" Run ")+a("/login")+r(" (or set CORNILIUS_TOKEN)."));return}if(e===429){let c=n&&(n.message||n.content&&n.content[0]&&n.content[0].text)||"Quota exceeded.";console.log(" "+p("\u26A0 "+c)),n&&n.upgrade_url&&console.log(" "+r(n.upgrade_url));return}if(!n){console.log(" "+p(`Unexpected response (HTTP ${e}).`));return}let i=n.kind;n.structuredContent&&Array.isArray(n.structuredContent.playbooks)&&(q=n.structuredContent.playbooks);let s=n.structuredContent&&n.structuredContent.confirmation_token;if(s&&/\/delete\b/.test(t)&&!/--confirm\b/.test(t)){let c=t.match(/\/delete\s+(\S+)\s+(\S+)/);if(c){k={type:c[1],id:c[2],token:s},E(n,p),console.log(" "+a("Type the word ")+V(a("delete"))+a(" to confirm, or anything else to cancel."));return}}if(i==="error"||n.isError)return E(n,p);i!=="noop"&&E(n,a)}function ne(){let t=[["just type\u2026","ask anything \u2014 Cornilius runs the analysis"],["/playbooks","list saved playbooks (numbered)"],["/run <#|id>","run an exact playbook \u2014 no guessing"],["/recall [topic]","what Cornilius remembers (issues, insights, history)"],["/delete <type> <id>","delete something \u2014 two-step, asks you to confirm"],["/status","tenant, session, usage"],["/whoami","who am I \u2014 your tenant id"],["/login /logout","sign in / sign out"],["/clear","clear the screen"],["/help","this help"],["/exit","quit (or Ctrl-D)"]];console.log();for(let[e,n]of t)console.log(" "+a(e.padEnd(22))+r(n));console.log()}function N(t){return t.toString("base64").replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function oe(){let t=N(R.randomBytes(32)),e=N(R.createHash("sha256").update(t,"ascii").digest());return{verifier:t,challenge:e}}function re(){return N(R.randomBytes(24))}async function se(t){let e=W();if(e.client_id&&e.api===m)return e.client_id;let n=await fetch(`${m}/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({client_name:"Cornilius CLI",redirect_uris:[t],grant_types:["authorization_code"],token_endpoint_auth_method:"none"})});if(!n.ok)throw new Error(`registration failed (${n.status}): ${(await n.text()).slice(0,200)}`);let o=await n.json();if(!o.client_id)throw new Error("registration returned no client_id");return L({...e,client_id:o.client_id,api:m}),o.client_id}function ie(t){let e=process.platform==="darwin"?"open":process.platform==="win32"?"cmd":"xdg-open",n=process.platform==="win32"?["/c","start","",t]:[t];try{return G(e,n,{stdio:"ignore",detached:!0}).unref(),!0}catch{return!1}}function ce(t){return`<!doctype html><meta charset="utf-8"><title>Cornilius</title><body style="font:16px system-ui;background:#0A0A0A;color:#FFF7ED;display:grid;place-items:center;height:100vh;margin:0"><div style="max-width:32rem;padding:2rem;text-align:center"><div style="color:#F59E0B;font-size:1.4rem;font-weight:700;margin-bottom:.75rem">Cornilius</div><p>${t==="success"?"Signed in to Cornilius. You can close this tab and return to your terminal.":t==="denied"?"Sign-in was cancelled. You can close this tab.":"Something went wrong. Return to your terminal and try again."}</p></div></body>`}async function ae(){if(I){console.log(" "+r("(demo) sign-in is simulated."));return}if(process.env.CORNILIUS_TOKEN){console.log(" "+r("CORNILIUS_TOKEN is set \u2014 that token is already used; /login not needed."));return}let t="/callback",e=18e4,{verifier:n,challenge:o}=oe(),i=re(),s,c,u=new Promise((l,f)=>{s=l,c=f}),S=Y.createServer((l,f)=>{let g=new URL(l.url,"http://127.0.0.1");if(g.pathname!==t){f.writeHead(404).end();return}let d=g.searchParams.get("error"),y=g.searchParams.get("code"),v=g.searchParams.get("state"),C=!d&&y&&v===i;f.writeHead(C?200:400,{"Content-Type":"text/html; charset=utf-8"}),f.end(ce(C?"success":d==="access_denied"?"denied":"error")),d?c(new Error(d==="access_denied"?"you cancelled sign-in.":`authorization error: ${d}`)):v!==i?c(new Error("state mismatch \u2014 possible CSRF; aborting.")):y?s(y):c(new Error("no authorization code in callback."))});await new Promise(l=>S.listen(0,"127.0.0.1",l));let O=`http://127.0.0.1:${S.address().port}${t}`;try{let l=await se(O),f=`${m}/authorize?`+new URLSearchParams({response_type:"code",client_id:l,redirect_uri:O,state:i,code_challenge:o,code_challenge_method:"S256",scope:"mcp"}).toString();console.log(" "+b("Opening your browser to sign in\u2026")),console.log(" "+r(`If it doesn't open, paste this URL:
4
+ `)+a(f)),ie(f);let g=await Promise.race([u,new Promise((v,C)=>setTimeout(()=>C(new Error("timed out after 3 minutes waiting for sign-in.")),e))]),d=await fetch(`${m}/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({grant_type:"authorization_code",code:g,redirect_uri:O,client_id:l,code_verifier:n}).toString()});if(!d.ok)throw new Error(`token exchange failed (${d.status}): ${(await d.text()).slice(0,200)}`);let y=await d.json();if(!y.access_token)throw new Error("token endpoint returned no access_token");T=y.access_token,L({...W(),client_id:l,api:m}),console.log(" "+b("\u2713 Signed in for this session.")+r(" Run ")+a("/status")+r(" to verify. You'll sign in again next time you open the CLI."))}catch(l){console.log(" "+p(`Sign-in failed: ${l.message}`)),process.exitCode=1}finally{S.close()}}function le(){T=null,console.log(" "+r("Signed out \u2014 session token cleared."))}async function B(t){let e=t.trim();if(!e)return;if(k){let{type:i,id:s,token:c}=k;k=null,e==="delete"?j(`/delete ${i} ${s}`,await A(`/delete ${i} ${s} --token ${c} --confirm delete`)):console.log(" "+r("cancelled \u2014 nothing was deleted."));return}if(e==="/help"||e==="/?"||e==="/")return ne();if(e==="/clear"){console.clear(),H();return}if((e==="/exit"||e==="/quit")&&(console.log(r(" bye.")),process.exit(0)),e==="/logout")return le();if(e==="/login")return ae();let n=e,o=e.match(/^\/run\s+(\d+)\s*$/);if(o){let i=q.find(s=>s.number===Number(o[1]));if(i&&i.id)n=`/run ${i.id}`;else{console.log(" "+p("Run /playbooks first, then /run <#>")+r(" (or /run <playbook-id> directly)."));return}}j(n,await A(n))}function ue(t){let e=t.trim(),n=[{number:1,id:"pb-builtin-funnel-pa-step-dropoff",name:"Funnel step drop-off scan"},{number:2,id:"pb-builtin-retention-pa-cohort-curve",name:"Cohort retention curve diagnosis"},{number:3,id:"pb-builtin-activation-aha-finder",name:"Activation aha-moment finder"}];if(e==="/playbooks")return{status:200,body:{kind:"dispatch",structuredContent:{playbooks:n},content:[{type:"text",text:`\u{1F4CB} Saved playbooks (demo)
5
+ `+n.map(o=>` ${o.number}. ${o.name}`).join(`
6
+ `)}]}};if(e.startsWith("/run "))return{status:200,body:{kind:"dispatch",content:[{type:"text",text:`\u25B8 running ${e.slice(5)} (demo) \u2192 analyze(playbook_id=\u2026)`}]}};if(e.startsWith("/recall"))return{status:200,body:{kind:"dispatch",content:[{type:"text",text:"\u{1F9E0} Remembered (demo): 2 open issues \xB7 5 insights"}]}};if(e.startsWith("/status")||e.startsWith("/whoami"))return{status:200,body:{kind:"local",content:[{type:"text",text:"tenant acme-corp \xB7 ready (demo)"}]}};if(e.startsWith("/delete ")){let o=e.match(/\/delete\s+(\S+)\s+(\S+)/);return/--confirm\b/.test(e)?{status:200,body:{kind:"dispatch",content:[{type:"text",text:`\u2713 deleted ${o?o[1]+" '"+o[2]+"'":"item"} (demo) \u2014 recoverable for 30 days`}]}}:{status:200,body:{kind:"dispatch",isError:!0,structuredContent:{confirmation_token:"demo-token"},content:[{type:"text",text:`\u26A0 This will delete ${o?o[1]+" '"+o[2]+"'":"an item"} (demo).`}]}}}return e.startsWith("/")?{status:400,body:{kind:"error",content:[{type:"text",text:"unknown command (demo)"}]}}:{status:200,body:{kind:"dispatch",content:[{type:"text",text:`\u25B8 analyzing (demo) \u2192 analyze(type='custom', prompt='${e}')`}]}}}function de(){console.log(`
7
7
  Cornilius \u2014 operate your Head of Analytics from the terminal.
8
8
 
9
9
  Usage:
@@ -18,5 +18,5 @@ import J from"node:readline";import w from"node:fs";import Y from"node:os";impor
18
18
 
19
19
  Env: CORNILIUS_API (default https://api.cornilius.ai), CORNILIUS_TOKEN (override),
20
20
  CORNILIUS_DEMO=1, NO_COLOR=1.
21
- `)}var h=H.filter(t=>t!=="--demo");(h[0]==="--version"||h[0]==="-v")&&(console.log("1.0.3"),process.exit(0));(h[0]==="--help"||h[0]==="-h")&&(ue(),process.exit(0));ee();h.length&&h[0]!=="--splash"&&(await K(h.join(" ")),k&&console.log(" "+r("Two-step delete needs interactive mode \u2014 run ")+a("cornilius")+r(" with no arguments.")),process.exit(0));M();h[0]==="--splash"&&process.exit(0);var $=J.createInterface({input:process.stdin,output:process.stdout,prompt:b("cornilius> ")});$.prompt();for await(let t of $){try{await K(t)}catch(e){console.log(" "+p("error: "+(e.message||e)))}$.prompt()}console.log(r(`
21
+ `)}var h=z.filter(t=>t!=="--demo");(h[0]==="--version"||h[0]==="-v")&&(console.log("1.0.4"),process.exit(0));(h[0]==="--help"||h[0]==="-h")&&(de(),process.exit(0));ee();h.length&&h[0]!=="--splash"&&(await B(h.join(" ")),k&&console.log(" "+r("Two-step delete needs interactive mode \u2014 run ")+a("cornilius")+r(" with no arguments.")),process.exit(0));H();h[0]==="--splash"&&process.exit(0);var $=K.createInterface({input:process.stdin,output:process.stdout,prompt:b("cornilius> ")});$.prompt();for await(let t of $){try{await B(t)}catch(e){console.log(" "+p("error: "+(e.message||e)))}$.prompt()}console.log(r(`
22
22
  bye.`));process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cornilius",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Cornilius CLI — operate your Head of Analytics from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {