infernoflow 0.43.4 → 0.43.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.
@@ -1,115 +1,76 @@
1
- import*as I from"node:fs";import*as v from"node:path";import{bold as w,cyan as X,gray as p,green as j,yellow as T,red as S}from"../ui/output.mjs";function D(i){try{return JSON.parse(I.readFileSync(i,"utf8"))}catch{return null}}function se(i){return i?.stability||"experimental"}const C={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},F={frozen:S,stable:T,experimental:j};function Z(i,o){const l={},c={},s={},n={};for(const t of i){const g=o.find(d=>d.id===t.id)||{};l[t.id]={id:t.id,name:t.name||g.name||g.title||t.id,stability:g.stability||"experimental",functions:t.codeAnalysis?.functions||[],calls:t.codeAnalysis?.calls||[],services:t.codeAnalysis?.services||[],dbCalls:t.codeAnalysis?.dbCalls||[],httpCalls:t.codeAnalysis?.httpCalls||[]},c[t.id]=new Set,s[t.id]=new Set;for(const d of t.codeAnalysis?.functions||[]){const f=d.replace(/\(\)$/,"");n[f]=t.id,n[f.toLowerCase()]=t.id}}for(const[t,g]of Object.entries(l))for(const d of g.calls){const f=d.replace(/\(\)$/,""),u=n[f]||n[f.toLowerCase()];u&&u!==t&&c[t]&&s[u]&&(c[t].add(u),s[u].add(t))}const y={},h={};for(const t of Object.keys(l))y[t]=[...c[t]],h[t]=[...s[t]];return{nodes:l,edges:y,reverse:h}}function q(i){const{nodes:o,edges:l,reverse:c}=i,s=Object.keys(o).sort();console.log(),console.log(w(" Capability Dependency Graph")),console.log(p(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();let n=!1;for(const h of s){const t=o[h],g=l[h]||[],d=c[h]||[],f=C[t.stability]||"\u{1F30A}",u=F[t.stability]||j;if(!(g.length===0&&d.length===0)){if(n=!0,console.log(` ${f} ${w(u(h))}`),g.length>0){console.log(p(" calls \u2192"));for(const $ of g){const b=o[$],H=C[b?.stability]||"\u{1F30A}";console.log(p(` ${H} ${$}`))}}if(d.length>0){console.log(p(" called by \u2190"));for(const $ of d){const b=C[o[$]?.stability]||"\u{1F30A}";console.log(p(` ${b} ${$}`))}}console.log()}}n||(console.log(p(" No inter-capability dependencies detected.")),console.log(p(" Run `infernoflow scan` first to populate call data.")),console.log());const y=Object.values(i.edges).reduce((h,t)=>h+t.length,0);console.log(p(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(p(` ${s.length} capabilities \xB7 ${y} dependency edge(s)`)),console.log()}function K(i,o){const{nodes:l,edges:c,reverse:s}=o,n=l[i];n||(console.error(S(`\u2717 Capability "${i}" not found in graph.`)),process.exit(1));const y=C[n.stability]||"\u{1F30A}",h=F[n.stability]||j;console.log(),console.log(w(` ${y} ${h(i)}`)+p(` (${n.stability})`)),n.services?.length&&console.log(p(" external: ")+X(n.services.join(", "))),console.log();const t=c[i]||[],g=s[i]||[];if(t.length>0){console.log(w(" Calls (downstream dependencies):"));for(const d of t){const f=l[d],u=F[f?.stability]||j,$=C[f?.stability]||"\u{1F30A}";console.log(` ${$} ${u(d)}`+p(f?.services?.length?` [${f.services.join(", ")}]`:""))}console.log()}else console.log(p(" No downstream dependencies.")),console.log();if(g.length>0){console.log(w(" Called by (upstream dependents):"));for(const d of g){const f=l[d],u=F[f?.stability]||j,$=C[f?.stability]||"\u{1F30A}";console.log(` ${$} ${u(d)}`)}console.log()}else console.log(p(" No capabilities call this one.")),console.log();if((n.stability==="frozen"||n.stability==="stable")&&g.length>0){const d=n.stability==="frozen"?S:T;console.log(d(` \u26A0 This capability is ${n.stability}. Changing it may break:`));for(const f of g)console.log(d(` \u2022 ${f}`));console.log()}}function Q(i,o){const l=[];if(!i||!o)return l;for(const[c,s]of Object.entries(o.nodes)){if(s.stability==="experimental")continue;const n=new Set(i.reverse?.[c]||[]),h=[...new Set(o.reverse[c]||[])].filter(t=>!n.has(t));if(h.length>0&&l.push({type:"new-dependents",capId:c,stability:s.stability,detail:`${h.join(", ")} now depend on this`}),s.stability==="frozen"){const t=new Set(i.edges?.[c]||[]),g=new Set(o.edges[c]||[]),d=[...g].filter(u=>!t.has(u)),f=[...t].filter(u=>!g.has(u));(d.length>0||f.length>0)&&l.push({type:"frozen-internals-changed",capId:c,stability:s.stability,detail:[d.length?`added calls: ${d.join(", ")}`:"",f.length?`removed calls: ${f.join(", ")}`:""].filter(Boolean).join("; ")})}}return l}async function ae(i){const o=(i||[]).slice(1),l=o.includes("--json"),c=o.includes("--check"),s=o.includes("--mermaid"),n=o.includes("--html"),y=o.indexOf("--cap"),h=y!==-1?o[y+1]:null,t=process.cwd(),g=v.join(t,"inferno"),d=v.join(g,"scan.json"),f=v.join(g,"graph.json"),u=v.join(g,"capabilities.json"),$=300*1e3;let b=D(d);if(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0||I.existsSync(d)&&Date.now()-I.statSync(d).mtimeMs>$){console.log(p(" \u27F3 Running infernoflow scan first (scan.json missing or stale)\u2026"));try{const{scanCommand:e}=await import("./scan.mjs");await e(["scan"]),b=D(d)}catch(e){console.error(S(`\u2717 Could not run scan automatically: ${e.message}`)),console.error(p(" Run `infernoflow scan` manually and try again.")),process.exit(1)}}(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0)&&(console.error(S("\u2717 inferno/scan.json still empty after scan.")),console.error(p(" Make sure your contract has at least one capability and your code matches.")),process.exit(1));let P=[];const L=D(u);L&&(P=Array.isArray(L)?L:L.capabilities||[]);const W=b.capabilities||[],a=Z(W,P),k=Array.isArray(b.components)?b.components:[],A={};for(const e of W){const r=(e.codeAnalysis?.files||[]).map(x=>x.replace(/\\/g,"/"));for(const x of r)A[x]||(A[x]=new Set),A[x].add(e.id)}const Y=[/(?:^|\/)src\/App\.(jsx|tsx|js|ts|vue|svelte)$/i,/(?:^|\/)src\/main\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/index\.(jsx|tsx|js|ts)$/i,/(?:^|\/)pages\/_app\.(jsx|tsx|js|ts)$/i,/(?:^|\/)app\/layout\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/App\.(jsx|tsx)$/i],E=new Set;for(const e of k)Y.some(r=>r.test(e.file))&&E.add(e.name);let B=0;for(const e of k){const r=`comp:${e.name}`;a.nodes[r]={id:r,name:e.name,stability:E.has(e.name)?"entry":"component",kind:"component",isEntry:E.has(e.name),file:e.file,functions:[],calls:[]},a.edges[r]=a.edges[r]||new Set,a.reverse[r]=a.reverse[r]||new Set;const x=A[e.file]?[...A[e.file]]:[];for(const m of x)a.edges[r].add(m),a.reverse[m]||(a.reverse[m]=new Set),a.reverse[m].add(r),B++}let N=0;for(const e of k){const r=`comp:${e.name}`;for(const x of e.renders||[]){const m=`comp:${x}`;a.nodes[m]&&r!==m&&(a.edges[r].add(m),a.reverse[m]||(a.reverse[m]=new Set),a.reverse[m].add(r),N++)}}const G=Array.isArray(b.uiElements)?b.uiElements:[],_={};for(const e of k)_[e.file]||(_[e.file]=e.name);const M={};for(const e of W){const r=e.codeAnalysis?.functions||[];for(const x of r){const m=x.replace(/\(\)$/,"");M[m]=e.id,M[m.toLowerCase()]=e.id}}let z=0;for(const e of G){const r=`ui:${e.tag}:${e.handler}:${e.file.replace(/[^a-z0-9]/gi,"_")}`;a.nodes[r]={id:r,name:e.label||e.handler,stability:"ui",kind:"ui",tag:e.tag,handler:e.handler,file:e.file,functions:[],calls:[]},a.edges[r]=a.edges[r]||new Set,a.reverse[r]=a.reverse[r]||new Set;const x=_[e.file];if(x){const O=`comp:${x}`;if(a.nodes[O]){a.edges[r].add(O),a.reverse[O]||(a.reverse[O]=new Set),a.reverse[O].add(r),z++;continue}}const m=M[e.handler]||M[e.handler?.toLowerCase()];m&&(a.edges[r].add(m),a.reverse[m]||(a.reverse[m]=new Set),a.reverse[m].add(r),z++)}!l&&!s&&!n&&(B>0&&console.log(p(` \u{1F9E9} Wired ${k.length} component${k.length===1?"":"s"} to capabilities.`)),N>0&&console.log(p(` \u{1F333} Found ${N} component render relationship${N===1?"":"s"} (parent \u2192 child).`)),z>0&&console.log(p(` \u26A1 Wired ${z} UI element${z===1?"":"s"}.`)),E.size>0&&console.log(p(` \u{1F6AA} Entry: ${[...E].join(", ")}`)));const V=D(f),J=Q(V,a),U={builtAt:new Date().toISOString(),capabilities:Object.keys(a.nodes).length,edges:Object.values(a.edges).reduce((e,r)=>e+r.length,0),nodes:a.nodes,deps:a.edges,dependents:a.reverse};if(l||I.writeFileSync(f,JSON.stringify(U,null,2)),l){console.log(JSON.stringify(U,null,2));return}if(s){console.log(ee(a));return}if(n){const e=v.join(g,"graph.html");I.writeFileSync(e,te(a)),console.log(j("\u2714 Interactive graph saved \u2192 inferno/graph.html")),console.log(p(` Open it: file://${e.replace(/\\/g,"/")}`));return}if(h?K(h,a):q(a),J.length>0){console.log(T(" \u26A0 Dependency changes detected:"));for(const e of J){const r=e.stability==="frozen"?S("\u{1F9CA}"):T("\u3030\uFE0F ");console.log(` ${r} ${w(e.capId)} \u2014 ${e.detail}`)}console.log(),c&&process.exit(1)}l||console.log(p(" Graph saved \u2192 inferno/graph.json"))}function ee(i){const o=[];o.push("```mermaid"),o.push("graph LR"),o.push(" classDef frozen fill:#fee,stroke:#c44,color:#900;"),o.push(" classDef stable fill:#fffbe6,stroke:#cc9,color:#840;"),o.push(" classDef experimental fill:#eef,stroke:#88c,color:#226;"),o.push(" classDef component fill:#fff3e0,stroke:#ff9800,color:#bf6d00;"),o.push(" classDef entry fill:#fce4ec,stroke:#e91e63,color:#880e4f,stroke-width:3px;"),o.push(" classDef ui fill:#e8f5e9,stroke:#4caf50,color:#2e7d32,stroke-dasharray:4 2;");for(const l of Object.keys(i.nodes)){const c=R(l),s=i.nodes[l];if(s.kind==="ui"){const y=`${ne(s.tag)} ${s.name||s.handler}<br/><small>&lt;${s.tag}&gt;</small>`;o.push(` ${c}(["${y}"]):::ui`)}else if(s.kind==="component")s.isEntry?o.push(` ${c}{{"\u{1F6AA} ${s.name} (entry)"}}:::entry`):o.push(` ${c}{{"\u{1F9E9} ${s.name}"}}:::component`);else{const n=s.functions?.length||0,y=`${s.name||l}<br/><small>${n} fn${n===1?"":"s"}</small>`;o.push(` ${c}["${y}"]:::${s.stability||"experimental"}`)}}for(const[l,c]of Object.entries(i.edges)){const s=c instanceof Set?[...c]:Array.isArray(c)?c:[];for(const n of s)o.push(` ${R(l)} --> ${R(n)}`)}return o.push("```"),o.join(`
2
- `)}function R(i){return String(i).replace(/[^a-zA-Z0-9_]/g,"_")}function ne(i){switch(i){case"button":return"\u{1F518}";case"input":return"\u2328\uFE0F ";case"form":return"\u{1F4DD}";case"link":return"\u{1F517}";case"select":return"\u25BE";default:return"\u{1F9E9}"}}function te(i){const o=Object.keys(i.nodes).map(s=>{const n=i.nodes[s];return{id:s,name:n.name||s,stability:n.stability||"experimental",kind:n.kind||"capability",isEntry:!!n.isEntry,tag:n.tag||null,handler:n.handler||null,file:n.file||null,functions:n.functions?.length||0}}),l=[];for(const[s,n]of Object.entries(i.edges)){const y=n instanceof Set?[...n]:Array.isArray(n)?n:[];for(const h of y)l.push({source:s,target:h})}const c=JSON.stringify({nodes:o,links:l});return`<!DOCTYPE html>
1
+ import*as z from"node:fs";import*as x from"node:path";import{bold as j,cyan as Z,gray as h,green as k,yellow as M,red as C}from"../ui/output.mjs";function I(a){try{return JSON.parse(z.readFileSync(a,"utf8"))}catch{return null}}function te(a){return a?.stability||"experimental"}const S={frozen:"\u{1F9CA}",stable:"\u3030\uFE0F ",experimental:"\u{1F30A}"},Y={frozen:C,stable:M,experimental:k};function q(a,e){const f={},c={},r={},t={};for(const n of a){const g=e.find(p=>p.id===n.id)||{};f[n.id]={id:n.id,name:n.name||g.name||g.title||n.id,stability:g.stability||"experimental",functions:n.codeAnalysis?.functions||[],calls:n.codeAnalysis?.calls||[],services:n.codeAnalysis?.services||[],dbCalls:n.codeAnalysis?.dbCalls||[],httpCalls:n.codeAnalysis?.httpCalls||[]},c[n.id]=new Set,r[n.id]=new Set;for(const p of n.codeAnalysis?.functions||[]){const m=p.replace(/\(\)$/,"");t[m]=n.id,t[m.toLowerCase()]=n.id}}for(const[n,g]of Object.entries(f))for(const p of g.calls){const m=p.replace(/\(\)$/,""),y=t[m]||t[m.toLowerCase()];y&&y!==n&&c[n]&&r[y]&&(c[n].add(y),r[y].add(n))}const d={},i={};for(const n of Object.keys(f))d[n]=[...c[n]],i[n]=[...r[n]];return{nodes:f,edges:d,reverse:i}}function K(a){const{nodes:e,edges:f,reverse:c}=a,r=Object.keys(e).sort();console.log(),console.log(j(" Capability Dependency Graph")),console.log(h(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log();let t=!1;for(const i of r){const n=e[i],g=f[i]||[],p=c[i]||[],m=S[n.stability]||"\u{1F30A}",y=Y[n.stability]||k;if(!(g.length===0&&p.length===0)){if(t=!0,console.log(` ${m} ${j(y(i))}`),g.length>0){console.log(h(" calls \u2192"));for(const w of g){const b=e[w],X=S[b?.stability]||"\u{1F30A}";console.log(h(` ${X} ${w}`))}}if(p.length>0){console.log(h(" called by \u2190"));for(const w of p){const b=S[e[w]?.stability]||"\u{1F30A}";console.log(h(` ${b} ${w}`))}}console.log()}}t||(console.log(h(" No inter-capability dependencies detected.")),console.log(h(" Run `infernoflow scan` first to populate call data.")),console.log());const d=Object.values(a.edges).reduce((i,n)=>i+n.length,0);console.log(h(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")),console.log(h(` ${r.length} capabilities \xB7 ${d} dependency edge(s)`)),console.log()}function Q(a,e){const{nodes:f,edges:c,reverse:r}=e,t=f[a];t||(console.error(C(`\u2717 Capability "${a}" not found in graph.`)),process.exit(1));const d=S[t.stability]||"\u{1F30A}",i=Y[t.stability]||k;console.log(),console.log(j(` ${d} ${i(a)}`)+h(` (${t.stability})`)),t.services?.length&&console.log(h(" external: ")+Z(t.services.join(", "))),console.log();const n=c[a]||[],g=r[a]||[];if(n.length>0){console.log(j(" Calls (downstream dependencies):"));for(const p of n){const m=f[p],y=Y[m?.stability]||k,w=S[m?.stability]||"\u{1F30A}";console.log(` ${w} ${y(p)}`+h(m?.services?.length?` [${m.services.join(", ")}]`:""))}console.log()}else console.log(h(" No downstream dependencies.")),console.log();if(g.length>0){console.log(j(" Called by (upstream dependents):"));for(const p of g){const m=f[p],y=Y[m?.stability]||k,w=S[m?.stability]||"\u{1F30A}";console.log(` ${w} ${y(p)}`)}console.log()}else console.log(h(" No capabilities call this one.")),console.log();if((t.stability==="frozen"||t.stability==="stable")&&g.length>0){const p=t.stability==="frozen"?C:M;console.log(p(` \u26A0 This capability is ${t.stability}. Changing it may break:`));for(const m of g)console.log(p(` \u2022 ${m}`));console.log()}}function ee(a,e){const f=[];if(!a||!e)return f;for(const[c,r]of Object.entries(e.nodes)){if(r.stability==="experimental")continue;const t=new Set(a.reverse?.[c]||[]),i=[...new Set(e.reverse[c]||[])].filter(n=>!t.has(n));if(i.length>0&&f.push({type:"new-dependents",capId:c,stability:r.stability,detail:`${i.join(", ")} now depend on this`}),r.stability==="frozen"){const n=new Set(a.edges?.[c]||[]),g=new Set(e.edges[c]||[]),p=[...g].filter(y=>!n.has(y)),m=[...n].filter(y=>!g.has(y));(p.length>0||m.length>0)&&f.push({type:"frozen-internals-changed",capId:c,stability:r.stability,detail:[p.length?`added calls: ${p.join(", ")}`:"",m.length?`removed calls: ${m.join(", ")}`:""].filter(Boolean).join("; ")})}}return f}async function ae(a){const e=(a||[]).slice(1),f=e.includes("--json"),c=e.includes("--check"),r=e.includes("--mermaid"),t=e.includes("--html"),d=e.indexOf("--cap"),i=d!==-1?e[d+1]:null,n=process.cwd(),g=x.join(n,"inferno"),p=x.join(g,"scan.json"),m=x.join(g,"graph.json"),y=x.join(g,"capabilities.json"),w=300*1e3;let b=I(p);if(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0||z.existsSync(p)&&Date.now()-z.statSync(p).mtimeMs>w){console.log(h(" \u27F3 Running infernoflow scan first (scan.json missing or stale)\u2026"));try{const{scanCommand:o}=await import("./scan.mjs");await o(["scan"]),b=I(p)}catch(o){console.error(C(`\u2717 Could not run scan automatically: ${o.message}`)),console.error(h(" Run `infernoflow scan` manually and try again.")),process.exit(1)}}(!b||!Array.isArray(b.capabilities)||b.capabilities.length===0)&&(console.error(C("\u2717 inferno/scan.json still empty after scan.")),console.error(h(" Make sure your contract has at least one capability and your code matches.")),process.exit(1));let P=[];const N=I(y);N&&(P=Array.isArray(N)?N:N.capabilities||[]);const R=b.capabilities||[],s=q(R,P),v=Array.isArray(b.components)?b.components:[],D={};for(const o of R){const l=(o.codeAnalysis?.files||[]).map($=>$.replace(/\\/g,"/"));for(const $ of l)D[$]||(D[$]=new Set),D[$].add(o.id)}const G=[/(?:^|\/)src\/App\.(jsx|tsx|js|ts|vue|svelte)$/i,/(?:^|\/)src\/main\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/index\.(jsx|tsx|js|ts)$/i,/(?:^|\/)pages\/_app\.(jsx|tsx|js|ts)$/i,/(?:^|\/)app\/layout\.(jsx|tsx|js|ts)$/i,/(?:^|\/)src\/App\.(jsx|tsx)$/i],E=new Set;for(const o of v)G.some(l=>l.test(o.file))&&E.add(o.name);let W=0;for(const o of v){const l=`comp:${o.name}`;s.nodes[l]={id:l,name:o.name,stability:E.has(o.name)?"entry":"component",kind:"component",isEntry:E.has(o.name),file:o.file,functions:[],calls:[]},s.edges[l]=s.edges[l]||new Set,s.reverse[l]=s.reverse[l]||new Set;const $=D[o.file]?[...D[o.file]]:[];for(const u of $)s.edges[l].add(u),s.reverse[u]||(s.reverse[u]=new Set),s.reverse[u].add(l),W++}let T=0;for(const o of v){const l=`comp:${o.name}`;for(const $ of o.renders||[]){const u=`comp:${$}`;s.nodes[u]&&l!==u&&(s.edges[l].add(u),s.reverse[u]||(s.reverse[u]=new Set),s.reverse[u].add(l),T++)}}const V=Array.isArray(b.uiElements)?b.uiElements:[],_={};for(const o of v)_[o.file]||(_[o.file]=o.name);const F={};for(const o of R){const l=o.codeAnalysis?.functions||[];for(const $ of l){const u=$.replace(/\(\)$/,"");F[u]=o.id,F[u.toLowerCase()]=o.id}}let O=0;for(const o of V){const l=`ui:${o.tag}:${o.handler}:${o.file.replace(/[^a-z0-9]/gi,"_")}`;s.nodes[l]={id:l,name:o.label||o.handler,stability:"ui",kind:"ui",tag:o.tag,handler:o.handler,file:o.file,functions:[],calls:[]},s.edges[l]=s.edges[l]||new Set,s.reverse[l]=s.reverse[l]||new Set;const $=_[o.file];if($){const L=`comp:${$}`;if(s.nodes[L]){s.edges[l].add(L),s.reverse[L]||(s.reverse[L]=new Set),s.reverse[L].add(l),O++;continue}}const u=F[o.handler]||F[o.handler?.toLowerCase()];u&&(s.edges[l].add(u),s.reverse[u]||(s.reverse[u]=new Set),s.reverse[u].add(l),O++)}!f&&!r&&!t&&(W>0&&console.log(h(` \u{1F9E9} Wired ${v.length} component${v.length===1?"":"s"} to capabilities.`)),T>0&&console.log(h(` \u{1F333} Found ${T} component render relationship${T===1?"":"s"} (parent \u2192 child).`)),O>0&&console.log(h(` \u26A1 Wired ${O} UI element${O===1?"":"s"}.`)),E.size>0&&console.log(h(` \u{1F6AA} Entry: ${[...E].join(", ")}`)));const H=I(m),B=ee(H,s),J={builtAt:new Date().toISOString(),capabilities:Object.keys(s.nodes).length,edges:Object.values(s.edges).reduce((o,l)=>o+l.length,0),nodes:s.nodes,deps:s.edges,dependents:s.reverse};if(f||z.writeFileSync(m,JSON.stringify(J,null,2)),f){console.log(JSON.stringify(J,null,2));return}if(r){console.log(oe(s));return}if(t){const o=x.join(g,"graph.html");z.writeFileSync(o,ne(s)),console.log(k("\u2714 Interactive graph saved \u2192 inferno/graph.html")),console.log(h(` Open it: file://${o.replace(/\\/g,"/")}`));return}if(i?Q(i,s):K(s),B.length>0){console.log(M(" \u26A0 Dependency changes detected:"));for(const o of B){const l=o.stability==="frozen"?C("\u{1F9CA}"):M("\u3030\uFE0F ");console.log(` ${l} ${j(o.capId)} \u2014 ${o.detail}`)}console.log(),c&&process.exit(1)}f||console.log(h(" Graph saved \u2192 inferno/graph.json"))}function oe(a){const e=[];e.push("```mermaid"),e.push("graph LR"),e.push(" classDef frozen fill:#fee,stroke:#c44,color:#900;"),e.push(" classDef stable fill:#fffbe6,stroke:#cc9,color:#840;"),e.push(" classDef experimental fill:#eef,stroke:#88c,color:#226;"),e.push(" classDef component fill:#fff3e0,stroke:#ff9800,color:#bf6d00;"),e.push(" classDef entry fill:#fce4ec,stroke:#e91e63,color:#880e4f,stroke-width:3px;"),e.push(" classDef ui fill:#e8f5e9,stroke:#4caf50,color:#2e7d32,stroke-dasharray:4 2;");for(const f of Object.keys(a.nodes)){const c=A(f),r=a.nodes[f];if(r.kind==="ui"){const d=`${U(r.tag)} ${r.name||r.handler}<br/><small>&lt;${r.tag}&gt;</small>`;e.push(` ${c}(["${d}"]):::ui`)}else if(r.kind==="component")r.isEntry?e.push(` ${c}{{"\u{1F6AA} ${r.name} (entry)"}}:::entry`):e.push(` ${c}{{"\u{1F9E9} ${r.name}"}}:::component`);else{const t=r.functions?.length||0,d=`${r.name||f}<br/><small>${t} fn${t===1?"":"s"}</small>`;e.push(` ${c}["${d}"]:::${r.stability||"experimental"}`)}}for(const[f,c]of Object.entries(a.edges)){const r=c instanceof Set?[...c]:Array.isArray(c)?c:[];for(const t of r)e.push(` ${A(f)} --> ${A(t)}`)}return e.push("```"),e.join(`
2
+ `)}function A(a){return String(a).replace(/[^a-zA-Z0-9_]/g,"_")}function U(a){switch(a){case"button":return"\u{1F518}";case"input":return"\u2328\uFE0F ";case"form":return"\u{1F4DD}";case"link":return"\u{1F517}";case"select":return"\u25BE";default:return"\u{1F9E9}"}}function ne(a){const e=[];e.push("graph LR"),e.push(" classDef frozen fill:#3a1a1a,stroke:#d43f3a,color:#ff8a80,stroke-width:2px;"),e.push(" classDef stable fill:#3a2a1a,stroke:#f0ad4e,color:#ffd180,stroke-width:2px;"),e.push(" classDef experimental fill:#1a2a3a,stroke:#5bc0de,color:#9fd6ed,stroke-width:2px;"),e.push(" classDef component fill:#2a1f0a,stroke:#ff9800,color:#ffcc80,stroke-width:2px;"),e.push(" classDef entry fill:#3a0a1f,stroke:#e91e63,color:#ff80ab,stroke-width:3px;"),e.push(" classDef ui fill:#1a2e1a,stroke:#4caf50,color:#a5d6a7,stroke-width:2px,stroke-dasharray: 4 2;");for(const t of Object.keys(a.nodes)){const d=A(t),i=a.nodes[t];if(i.kind==="ui"){const g=`${U(i.tag)} ${i.name||i.handler}`;e.push(` ${d}(["${g}"]):::ui`)}else if(i.kind==="component")i.isEntry?e.push(` ${d}{{"\u{1F6AA} ${i.name}"}}:::entry`):e.push(` ${d}{{"\u{1F9E9} ${i.name}"}}:::component`);else{const n=i.functions?.length||0,g=`${i.name||t}<br/><small>${n} fn${n===1?"":"s"}</small>`;e.push(` ${d}["${g}"]:::${i.stability||"experimental"}`)}}for(const[t,d]of Object.entries(a.edges)){const i=d instanceof Set?[...d]:Array.isArray(d)?d:[];for(const n of i)e.push(` ${A(t)} --> ${A(n)}`)}const f=e.join(`
3
+ `),c=Object.keys(a.nodes).length,r=Object.values(a.edges).reduce((t,d)=>t+(d instanceof Set?d.size:Array.isArray(d)?d.length:0),0);return`<!DOCTYPE html>
3
4
  <html lang="en">
4
5
  <head>
5
6
  <meta charset="UTF-8" />
6
- <title>infernoflow \u2014 capability graph</title>
7
+ <title>infernoflow \u2014 code map</title>
7
8
  <style>
8
- body { margin: 0; padding: 0; background: #1e1e1e; color: #ccc; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; overflow: hidden; }
9
+ html, body { margin: 0; padding: 0; background: #1e1e1e; color: #ccc; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; height: 100%; overflow: hidden; }
9
10
  header { padding: 12px 24px; background: #2a2a2a; border-bottom: 1px solid #3a3a3a; }
10
11
  header h1 { margin: 0; font-size: 16px; font-weight: 600; }
11
12
  header .meta { font-size: 12px; color: #999; margin-top: 4px; }
12
13
  header .meta span { margin-right: 16px; }
13
14
  header .legend { margin-top: 8px; font-size: 11px; }
14
- header .legend .swatch { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 4px; vertical-align: middle; }
15
- svg { width: 100vw; height: calc(100vh - 88px); cursor: grab; }
16
- svg:active { cursor: grabbing; }
17
- .link { stroke: #555; stroke-opacity: 0.7; }
18
- .node circle { stroke: #1e1e1e; stroke-width: 2px; cursor: pointer; }
19
- .node text { fill: #ddd; font-size: 11px; font-weight: 500; pointer-events: none; }
20
- .node.frozen circle { fill: #d43f3a; }
21
- .node.stable circle { fill: #f0ad4e; }
22
- .node.experimental circle { fill: #5bc0de; }
23
- .node.component circle { fill: #ff9800; }
24
- .node.component text { fill: #ffd180; }
25
- .node.entry circle { fill: #e91e63; stroke: #ff80ab; stroke-width: 4px; }
26
- .node.entry text { fill: #ff80ab; font-weight: 700; }
27
- .node.ui circle { fill: #4caf50; stroke-dasharray: 3 2; }
28
- .node.ui text { fill: #aef; font-weight: 400; font-style: italic; }
29
- .node:hover circle { stroke: #fff; stroke-width: 3px; }
30
- .tooltip { position: fixed; background: #2a2a2a; border: 1px solid #555; padding: 8px 12px; border-radius: 4px; font-size: 12px; pointer-events: none; opacity: 0; transition: opacity 0.15s; max-width: 300px; }
15
+ header .legend .chip { display: inline-block; padding: 2px 8px; border-radius: 4px; margin-right: 8px; font-size: 11px; border: 1px solid; }
16
+ header .legend .entry { color: #ff80ab; border-color: #e91e63; background: #3a0a1f; }
17
+ header .legend .component { color: #ffcc80; border-color: #ff9800; background: #2a1f0a; }
18
+ header .legend .capability { color: #9fd6ed; border-color: #5bc0de; background: #1a2a3a; }
19
+ header .legend .ui { color: #a5d6a7; border-color: #4caf50; background: #1a2e1a; border-style: dashed; }
20
+ #map-wrap { width: 100vw; height: calc(100vh - 88px); overflow: auto; cursor: grab; }
21
+ #map-wrap:active { cursor: grabbing; }
22
+ #map { padding: 24px; display: inline-block; min-width: 100%; }
23
+ .mermaid { background: transparent; }
24
+ .mermaid svg { max-width: none !important; height: auto !important; }
25
+ .empty { padding: 40px 24px; color: #888; font-size: 14px; }
31
26
  </style>
32
27
  </head>
33
28
  <body>
34
29
  <header>
35
- <h1>\u{1F525} infernoflow \u2014 capability graph</h1>
30
+ <h1>\u{1F525} infernoflow \u2014 code map</h1>
36
31
  <div class="meta">
37
- <span>Generated: ${new Date().toLocaleString()}</span>
38
- <span>${o.length} capabilities \xB7 ${l.length} edges</span>
32
+ <span>Generated: \${new Date().toLocaleString()}</span>
33
+ <span>\${nodeCount} nodes \xB7 \${edgeCount} edges</span>
39
34
  </div>
40
35
  <div class="legend">
41
- <span><span class="swatch" style="background:#e91e63"></span>entry (App.jsx / main / index)</span>
42
- <span><span class="swatch" style="background:#ff9800"></span>component</span>
43
- <span><span class="swatch" style="background:#5bc0de"></span>capability</span>
44
- <span><span class="swatch" style="background:#4caf50"></span>UI element</span>
45
- <span><span class="swatch" style="background:#d43f3a"></span>frozen</span>
46
- <span style="color:#666; margin-left:16px;">drag \xB7 scroll to zoom \xB7 hover for details</span>
36
+ <span class="chip entry">\u{1F6AA} entry</span>
37
+ <span class="chip component">\u{1F9E9} component</span>
38
+ <span class="chip capability">capability</span>
39
+ <span class="chip ui">UI element</span>
40
+ <span style="color:#666;">scroll to pan \xB7 pinch / Ctrl-scroll to zoom \xB7 click-drag empty area</span>
47
41
  </div>
48
42
  </header>
49
- <svg></svg>
50
- <div class="tooltip" id="tt"></div>
51
- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script>
52
- <script>
53
- const data = ${c};
54
- const svg = d3.select("svg");
55
- const W = window.innerWidth, H = window.innerHeight - 88;
56
- const g = svg.append("g");
57
-
58
- const zoom = d3.zoom().scaleExtent([0.3, 4]).on("zoom", e => g.attr("transform", e.transform));
59
- svg.call(zoom);
60
-
61
- const sim = d3.forceSimulation(data.nodes)
62
- .force("link", d3.forceLink(data.links).id(d => d.id).distance(120))
63
- .force("charge", d3.forceManyBody().strength(-400))
64
- .force("center", d3.forceCenter(W/2, H/2))
65
- .force("collide", d3.forceCollide(40));
66
-
67
- const link = g.append("g").selectAll("line")
68
- .data(data.links).enter().append("line")
69
- .attr("class", "link").attr("marker-end", "url(#arrow)");
70
-
71
- svg.append("defs").append("marker").attr("id","arrow").attr("viewBox","0 -5 10 10").attr("refX",18).attr("refY",0).attr("markerWidth",6).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M0,-5L10,0L0,5").attr("fill","#888");
72
-
73
- const node = g.append("g").selectAll(".node")
74
- .data(data.nodes).enter().append("g")
75
- .attr("class", d => "node " + (d.isEntry ? "entry" : (d.kind === "ui" ? "ui" : (d.kind === "component" ? "component" : d.stability))))
76
- .call(d3.drag()
77
- .on("start", (e,d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx=d.x; d.fy=d.y; })
78
- .on("drag", (e,d) => { d.fx=e.x; d.fy=e.y; })
79
- .on("end", (e,d) => { if (!e.active) sim.alphaTarget(0); d.fx=null; d.fy=null; }));
80
-
81
- node.append("circle").attr("r", d => {
82
- if (d.isEntry) return 18; // entry component is biggest \u2014 root of the tree
83
- if (d.kind === "ui") return 7;
84
- if (d.kind === "component") return 11;
85
- return 12 + Math.min(d.functions, 8);
86
- });
87
- node.append("text").attr("dx", 18).attr("dy", 4).text(d => {
88
- if (d.kind === "ui") {
89
- const emoji = { button: "\u{1F518}", input: "\u2328\uFE0F", form: "\u{1F4DD}", link: "\u{1F517}", select: "\u25BE" }[d.tag] || "\u{1F9E9}";
90
- return emoji + " " + d.name;
91
- }
92
- if (d.kind === "component") return "\u{1F9E9} " + d.name;
93
- return d.name;
94
- });
95
-
96
- const tt = d3.select("#tt");
97
- node.on("mouseover", (e,d) => {
98
- let html;
99
- if (d.kind === "ui") {
100
- html = \`<strong>\${d.name}</strong><br/>UI element: &lt;\${d.tag}&gt;<br/>Handler: \${d.handler || "\u2014"}\`;
101
- } else if (d.kind === "component") {
102
- html = \`<strong>\u{1F9E9} \${d.name}</strong><br/>Component<br/>\${d.file || ""}\`;
103
- } else {
104
- html = \`<strong>\${d.name}</strong><br/>Capability \xB7 \${d.stability}<br/>Functions: \${d.functions}\`;
105
- }
106
- tt.html(html).style("left", (e.pageX+12)+"px").style("top", (e.pageY+12)+"px").style("opacity", 1);
107
- }).on("mouseout", () => tt.style("opacity", 0));
43
+ <div id="map-wrap">
44
+ <div id="map">
45
+ \${nodeCount === 0
46
+ ? '<div class="empty">No nodes to render \u2014 run <code>infernoflow scan</code> first.</div>'
47
+ : '<pre class="mermaid">' + \`${f.replace(/`/g,"`")}\` + '</pre>'
48
+ }
49
+ </div>
50
+ </div>
51
+ <script type="module">
52
+ import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
53
+ mermaid.initialize({
54
+ startOnLoad: true,
55
+ theme: "dark",
56
+ securityLevel: "loose",
57
+ flowchart: { useMaxWidth: false, htmlLabels: true, curve: "linear", padding: 20, nodeSpacing: 60, rankSpacing: 80 },
58
+ });
108
59
 
109
- sim.on("tick", () => {
110
- link.attr("x1", d=>d.source.x).attr("y1", d=>d.source.y).attr("x2", d=>d.target.x).attr("y2", d=>d.target.y);
111
- node.attr("transform", d => \`translate(\${d.x},\${d.y})\`);
112
- });
60
+ // Drag-to-pan on the wrapper (zoom is native via wheel/pinch + browser zoom)
61
+ const wrap = document.getElementById("map-wrap");
62
+ let dragging = false, startX = 0, startY = 0, scrollX = 0, scrollY = 0;
63
+ wrap.addEventListener("mousedown", e => {
64
+ if (e.target.closest("a, button")) return;
65
+ dragging = true; startX = e.pageX; startY = e.pageY; scrollX = wrap.scrollLeft; scrollY = wrap.scrollTop;
66
+ e.preventDefault();
67
+ });
68
+ window.addEventListener("mousemove", e => {
69
+ if (!dragging) return;
70
+ wrap.scrollLeft = scrollX - (e.pageX - startX);
71
+ wrap.scrollTop = scrollY - (e.pageY - startY);
72
+ });
73
+ window.addEventListener("mouseup", () => { dragging = false; });
113
74
  </script>
114
75
  </body>
115
- </html>`}function ie(i){return D(v.join(i,"graph.json"))}export{ae as graphCommand,ie as loadGraph};
76
+ </html>`}function re(a){return I(x.join(a,"graph.json"))}export{ae as graphCommand,re as loadGraph};
@@ -1,20 +1,20 @@
1
- import*as l from"node:fs";import*as p from"node:path";import"node:os";import{bold as N,cyan as A,gray as t,green as D,red as b}from"../ui/output.mjs";import{readCredentials as R,isLoggedIn as _}from"../cloud/credentials.mjs";import{pushEntry as C}from"../cloud/supabase.mjs";import{ampPaths as P,appendEntry as U,readEntries as F,AMP_MARKERS as E}from"../amp/io.mjs";function L(){return P(process.cwd())}function W(){try{const e=F(process.cwd()),a=e.filter(n=>n.type==="gotcha"),c=e.filter(n=>n.type==="decision"),r=e.filter(n=>n.type==="attempt"&&(n.result==="failed"||n.result==="partial")),s=["# Project Context (auto-generated by infernoflow)","",`> Last updated: ${new Date().toISOString()}`,""];if(a.length){s.push("## \u26A0\uFE0F Known Gotchas (Read These First)","");for(const n of a)s.push(`- ${n.summary}`);s.push("")}if(c.length){s.push("## \u2713 Decisions In Effect","");for(const n of c)s.push(`- ${n.summary}`);s.push("")}if(r.length){s.push("## \u274C Things That Don't Work (Don't Try These)","");for(const n of r)s.push(`- ${n.summary}`);s.push("")}const d=s.join(`
2
- `),m=`${E.start}
3
- ${d}
1
+ import*as f from"node:fs";import*as m from"node:path";import"node:os";import{bold as j,cyan as A,gray as o,green as D,red as b}from"../ui/output.mjs";import{ampPaths as R,appendEntry as k,readEntries as N,AMP_MARKERS as E}from"../amp/io.mjs";function _(){return R(process.cwd())}function C(){try{const e=N(process.cwd()),l=e.filter(n=>n.type==="gotcha"),i=e.filter(n=>n.type==="decision"),s=e.filter(n=>n.type==="attempt"&&(n.result==="failed"||n.result==="partial")),t=["# Project Context (auto-generated by infernoflow)","",`> Last updated: ${new Date().toISOString()}`,""];if(l.length){t.push("## \u26A0\uFE0F Known Gotchas (Read These First)","");for(const n of l)t.push(`- ${n.summary}`);t.push("")}if(i.length){t.push("## \u2713 Decisions In Effect","");for(const n of i)t.push(`- ${n.summary}`);t.push("")}if(s.length){t.push("## \u274C Things That Don't Work (Don't Try These)","");for(const n of s)t.push(`- ${n.summary}`);t.push("")}const p=t.join(`
2
+ `),g=`${E.start}
3
+ ${p}
4
4
  ${E.end}
5
- `,u=process.cwd(),h=n=>{let i="";try{i=l.readFileSync(n,"utf8")}catch{}const $=i.indexOf(E.start),w=i.indexOf(E.end);let x;$!==-1&&w!==-1&&w>$?x=i.slice(0,$)+m+i.slice(w+E.end.length).replace(/^\n+/,""):i?x=i.replace(/\s+$/,"")+`
5
+ `,a=process.cwd(),d=n=>{let c="";try{c=f.readFileSync(n,"utf8")}catch{}const w=c.indexOf(E.start),S=c.indexOf(E.end);let $;w!==-1&&S!==-1&&S>w?$=c.slice(0,w)+g+c.slice(S+E.end.length).replace(/^\n+/,""):c?$=c.replace(/\s+$/,"")+`
6
6
 
7
- `+m:x=m,l.writeFileSync(n,x,"utf8")},I=p.join(u,"CLAUDE.md");l.existsSync(I)&&h(I);const j=p.join(u,".cursorrules");(l.existsSync(j)||l.existsSync(p.join(u,".cursor")))&&h(j);const S=p.join(u,".github","copilot-instructions.md");l.existsSync(p.join(u,".github"))&&h(S)}catch{}}const T=["note","attempt","decision","gotcha","preference","theme","handoff","error"],v=["worked","failed","partial","unknown"];function V(){return F(process.cwd())}function q(e,{auto:a=!1,quiet:c=!1}={}){const r=process.cwd(),s=p.join(r,".ai-memory"),d=p.join(r,"inferno");return a&&!l.existsSync(s)&&!l.existsSync(d)?!1:(!l.existsSync(s)&&!l.existsSync(d)&&(c||console.error(b(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
8
- `)),process.exit(1)),U(r,e),!0)}function G(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function M(e,a){const c=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),r=e.type||"note",s=r==="gotcha"?"\x1B[33m":r==="decision"?"\x1B[36m":r==="theme"?"\x1B[35m":r==="preference"?"\x1B[34m":r==="attempt"?"\x1B[90m":r==="error"?"\x1B[31m":"\x1B[0m",d="\x1B[0m",m=e.result?` [${e.result}]`:"",u=e.agent&&e.agent!=="human"?t(` (${e.agent})`):"";return` ${t(String(a+1).padStart(3))} ${t(c)} ${s}${r}${d}${m} ${e.summary}${u}`}async function H(e){const a=o=>e.includes(o),c=(o,f)=>{const g=e.indexOf(o);return g!==-1&&e[g+1]?e[g+1]:f},r=a("--show"),s=a("--clear"),d=a("--json"),m=a("--auto"),u=a("--quiet"),h=c("--source",null);if(r||d){const o=V(),f=e[e.indexOf("--show")+1],g=f&&/^\d+$/.test(f)?parseInt(f):20,y=o.slice(-g);if(d){console.log(JSON.stringify(y,null,2));return}if(console.log(`
9
- `+N("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!y.length){console.log(t(`
7
+ `+g:$=g,f.writeFileSync(n,$,"utf8")},O=m.join(a,"CLAUDE.md");f.existsSync(O)&&d(O);const I=m.join(a,".cursorrules");(f.existsSync(I)||f.existsSync(m.join(a,".cursor")))&&d(I);const y=m.join(a,".github","copilot-instructions.md");f.existsSync(m.join(a,".github"))&&d(y)}catch{}}const F=["note","attempt","decision","gotcha","preference","theme","handoff","error"],L=["worked","failed","partial","unknown"];function P(){return N(process.cwd())}function U(e,{auto:l=!1,quiet:i=!1}={}){const s=process.cwd(),t=m.join(s,".ai-memory"),p=m.join(s,"inferno");return l&&!f.existsSync(t)&&!f.existsSync(p)?!1:(!f.existsSync(t)&&!f.existsSync(p)&&(i||console.error(b(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
8
+ `)),process.exit(1)),k(s,e),!0)}function W(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}function V(e,l){const i=new Date(e.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),s=e.type||"note",t=s==="gotcha"?"\x1B[33m":s==="decision"?"\x1B[36m":s==="theme"?"\x1B[35m":s==="preference"?"\x1B[34m":s==="attempt"?"\x1B[90m":s==="error"?"\x1B[31m":"\x1B[0m",p="\x1B[0m",g=e.result?` [${e.result}]`:"",a=e.agent&&e.agent!=="human"?o(` (${e.agent})`):"";return` ${o(String(l+1).padStart(3))} ${o(i)} ${t}${s}${p}${g} ${e.summary}${a}`}async function J(e){const l=r=>e.includes(r),i=(r,u)=>{const h=e.indexOf(r);return h!==-1&&e[h+1]?e[h+1]:u},s=l("--show"),t=l("--clear"),p=l("--json"),g=l("--auto"),a=l("--quiet"),d=i("--source",null);if(s||p){const r=P(),u=e[e.indexOf("--show")+1],h=u&&/^\d+$/.test(u)?parseInt(u):20,x=r.slice(-h);if(p){console.log(JSON.stringify(x,null,2));return}if(console.log(`
9
+ `+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!x.length){console.log(o(`
10
10
  No entries yet. Start logging with: infernoflow log "<what happened>"
11
- `));return}console.log(t(` Showing last ${y.length} of ${o.length} entries
12
- `)),y.forEach((O,k)=>console.log(M(O,o.length-y.length+k))),console.log();return}if(s){const{sessions:o}=L();if(!l.existsSync(o)){console.log(t(` Nothing to clear.
13
- `));return}const f=o.replace(".jsonl",`-archive-${Date.now()}.jsonl`);l.renameSync(o,f),console.log(D(` \u2714 Session log archived \u2192 ${p.basename(f)}
14
- `));return}const I=new Set([c("--type",""),c("--result",""),c("--agent",""),c("--source","")].filter(Boolean)),S=e.slice(1).filter(o=>!o.startsWith("--")&&!I.has(o)).join(" ").trim();if(!S){console.log(`
15
- `+N("\u{1F525} infernoflow log")+` \u2014 append to session memory
16
- `),console.log(t(" Usage:")),console.log(t(' infernoflow log "what happened"')),console.log(t(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(t(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(t(' infernoflow log "switched to dark mode" --type theme')),console.log(t(" infernoflow log --show Print last 20 entries")),console.log(t(" infernoflow log --json Print as JSON")),console.log(),console.log(t(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(t(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(t(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
17
- `));return}const n=c("--type","note"),i=c("--result",null),$=c("--agent",G());T.includes(n)||(u||console.error(b(` \u2718 Invalid type: ${n}. Valid: ${T.join(", ")}
18
- `)),process.exit(1)),i&&!v.includes(i)&&(u||console.error(b(` \u2718 Invalid result: ${i}. Valid: ${v.join(", ")}
19
- `)),process.exit(1));const w={ts:new Date().toISOString(),agent:$,type:n,summary:S,...i?{result:i}:{},...h?{source:h}:{},...m?{auto:!0}:{}};if(q(w,{auto:m,quiet:u})){W();try{if(_()){const o=R(),f=o.user_token||o.user?.id||o.user?.login||"anonymous",g=(()=>{try{const{config:y}=L(),O=JSON.parse(l.readFileSync(y,"utf8"));return O.project||O.projectId||p.basename(process.cwd())}catch{return p.basename(process.cwd())}})();await C(w,f,g)}}catch{}if(!u){const o=n!=="note"?A(` [${n}]`):"",f=i?t(` \u2192 ${i}`):"",g=h?t(` (via ${h})`):"";console.log(D(` \u2714 Logged${o}${f}${g}: `)+S+`
20
- `)}}}export{H as logCommand};
11
+ `));return}console.log(o(` Showing last ${x.length} of ${r.length} entries
12
+ `)),x.forEach((v,T)=>console.log(V(v,r.length-x.length+T))),console.log();return}if(t){const{sessions:r}=_();if(!f.existsSync(r)){console.log(o(` Nothing to clear.
13
+ `));return}const u=r.replace(".jsonl",`-archive-${Date.now()}.jsonl`);f.renameSync(r,u),console.log(D(` \u2714 Session log archived \u2192 ${m.basename(u)}
14
+ `));return}const O=new Set([i("--type",""),i("--result",""),i("--agent",""),i("--source","")].filter(Boolean)),y=e.slice(1).filter(r=>!r.startsWith("--")&&!O.has(r)).join(" ").trim();if(!y){console.log(`
15
+ `+j("\u{1F525} infernoflow log")+` \u2014 append to session memory
16
+ `),console.log(o(" Usage:")),console.log(o(' infernoflow log "what happened"')),console.log(o(' infernoflow log "tried X, failed because Y" --type attempt --result failed')),console.log(o(' infernoflow log "always use multipart/form-data" --type gotcha')),console.log(o(' infernoflow log "switched to dark mode" --type theme')),console.log(o(" infernoflow log --show Print last 20 entries")),console.log(o(" infernoflow log --json Print as JSON")),console.log(),console.log(o(" Types: note \xB7 attempt \xB7 decision \xB7 gotcha \xB7 preference \xB7 theme \xB7 handoff \xB7 error")),console.log(o(" Results: worked \xB7 failed \xB7 partial \xB7 unknown")),console.log(o(` Auto-capture: --auto (silent skip if no inferno/) \xB7 --quiet \xB7 --source <name>
17
+ `));return}const n=i("--type","note"),c=i("--result",null),w=i("--agent",W());F.includes(n)||(a||console.error(b(` \u2718 Invalid type: ${n}. Valid: ${F.join(", ")}
18
+ `)),process.exit(1)),c&&!L.includes(c)&&(a||console.error(b(` \u2718 Invalid result: ${c}. Valid: ${L.join(", ")}
19
+ `)),process.exit(1));const S={ts:new Date().toISOString(),agent:w,type:n,summary:y,...c?{result:c}:{},...d?{source:d}:{},...g?{auto:!0}:{}};if(U(S,{auto:g,quiet:a})&&(C(),!a)){const r=n!=="note"?A(` [${n}]`):"",u=c?o(` \u2192 ${c}`):"",h=d?o(` (via ${d})`):"";console.log(D(` \u2714 Logged${r}${u}${h}: `)+y+`
20
+ `)}}export{J as logCommand};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infernoflow",
3
- "version": "0.43.4",
3
+ "version": "0.43.6",
4
4
  "description": "Persistent memory for AI coding sessions \u2014 captures what agents can't infer from code alone. Works with Copilot, Cursor, Claude, and Windsurf.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,16 +13,14 @@
13
13
  "dist/bin",
14
14
  "dist/lib",
15
15
  "dist/templates",
16
- "README.md",
17
- "scripts/postinstall.js"
16
+ "README.md"
18
17
  ],
19
18
  "scripts": {
20
19
  "test": "node scripts/smoke.mjs && node scripts/json-smoke.mjs && node scripts/json-negative-smoke.mjs && node scripts/implement-smoke.mjs && node scripts/adopt-smoke.mjs && node scripts/pr-impact-smoke.mjs && node scripts/sync-smoke.mjs && node scripts/run-smoke.mjs",
21
20
  "test:help": "node bin/infernoflow.mjs --help",
22
21
  "build": "node build.mjs",
23
22
  "prepublishOnly": "echo skipping build",
24
- "inferno:promote-draft": "node scripts/inferno-promote-draft.mjs",
25
- "postinstall": "node scripts/postinstall.js"
23
+ "inferno:promote-draft": "node scripts/inferno-promote-draft.mjs"
26
24
  },
27
25
  "dependencies": {},
28
26
  "keywords": [
@@ -1,2 +0,0 @@
1
- import*as r from"node:fs";import*as i from"node:path";import*as c from"node:os";const n=i.join(c.homedir(),".infernoflow"),t=i.join(n,"credentials.json");function o(){try{return r.existsSync(t)?JSON.parse(r.readFileSync(t,"utf8")):null}catch{return null}}function u(e){r.existsSync(n)||r.mkdirSync(n,{recursive:!0}),r.writeFileSync(t,JSON.stringify(e,null,2)+`
2
- `,{mode:384})}function a(){try{return r.existsSync(t)&&r.unlinkSync(t),!0}catch{return!1}}function f(){const e=o();return e?!!(e.mode==="supabase"&&e.access_token||e.mode==="device-flow"&&e.github_access_token||!e.mode&&e.access_token):!1}function l(){const e=o();if(!e||e.mode!=="supabase"||!e.access_token)return null;if(e.expires_at){const s=Date.parse(e.expires_at);if(!Number.isNaN(s)&&Date.now()>s-6e4)return null}return e.access_token}export{a as deleteCredentials,l as getSupabaseAccessToken,f as isLoggedIn,o as readCredentials,u as writeCredentials};
@@ -1 +0,0 @@
1
- import*as A from"node:https";import{readCredentials as S,writeCredentials as w,getSupabaseAccessToken as d}from"./credentials.mjs";const o=process.env.INFERNOFLOW_SUPABASE_URL||"https://vscesbbtmrsctfroigyx.supabase.co",i=process.env.INFERNOFLOW_SUPABASE_ANON_KEY||"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZzY2VzYmJ0bXJzY3Rmcm9pZ3l4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Nzc0ODAxMjcsImV4cCI6MjA5MzA1NjEyN30.4WCXr0aGBlqC2m29DnlCSu5qKl0L-fDQoaV9AGu8-68";function c(e,r,s,t){return new Promise((n,h)=>{const f=new URL(r),u=s?JSON.stringify(s):null,_={hostname:f.hostname,port:443,path:f.pathname+f.search,method:e,headers:{"Content-Type":"application/json",Accept:"application/json","User-Agent":"infernoflow-cli",apikey:i,...t,...u?{"Content-Length":Buffer.byteLength(u)}:{}}},a=A.request(_,p=>{let l="";p.on("data",y=>l+=y),p.on("end",()=>{try{n({status:p.statusCode,body:JSON.parse(l)})}catch{n({status:p.statusCode,body:l})}})});a.on("error",h),a.setTimeout(8e3,()=>{a.destroy(new Error("timeout"))}),u&&a.write(u),a.end()})}async function m(){const e=S();if(!e||e.mode!=="supabase"||!e.refresh_token)return;const r=e.expires_at?Date.parse(e.expires_at):0,s=300*1e3;if(!(Number.isFinite(r)&&Date.now()<r-s))try{const t=await c("POST",`${o}/auth/v1/token?grant_type=refresh_token`,{refresh_token:e.refresh_token},{Authorization:`Bearer ${i}`});if(t.status!==200||!t.body?.access_token)return;const n={...e,access_token:t.body.access_token,refresh_token:t.body.refresh_token||e.refresh_token,expires_at:t.body.expires_in?new Date(Date.now()+t.body.expires_in*1e3).toISOString():e.expires_at};w(n)}catch{}}async function x(e,r,s){try{await m();const t=d(),n=!!t,h={project_id:s,ts:e.ts,type:e.type||"note",summary:e.summary,result:e.result||null,source:e.source||null,auto:e.auto||!1,agent:e.agent||null,...n?{}:{user_token:r}};await c("POST",`${o}/rest/v1/entries`,h,{Authorization:`Bearer ${n?t:i}`,apikey:i,Prefer:"return=minimal"})}catch{}}async function I(e){const r=await c("GET",`${o}/auth/v1/user`,null,{Authorization:`Bearer ${e}`});return r.status===200?r.body:null}async function k(e){await m();const r=d(),s=new URLSearchParams({project_id:`eq.${e}`,order:"ts.asc",limit:"10000"}),t=await c("GET",`${o}/rest/v1/entries?${s.toString()}`,null,{Authorization:`Bearer ${r||i}`});return t.status===200&&Array.isArray(t.body)?t.body:[]}async function g(e){return await c("POST",`${o}/auth/v1/token?grant_type=pkce`,{auth_code:e},{})}function O(e,r){const s=new URLSearchParams({provider:"github",redirect_to:r,state:e});return`${o}/auth/v1/authorize?${s.toString()}`}export{i as SUPABASE_ANON_KEY,o as SUPABASE_URL,g as exchangeCodeForSession,O as getOAuthUrl,I as getUser,k as pullEntries,x as pushEntry,m as refreshSessionIfNeeded};
@@ -1,10 +0,0 @@
1
- import*as j from"node:fs";import*as O from"node:path";import*as K from"node:https";import*as W from"node:http";import*as M from"node:crypto";import{header as C,ok as E,warn as p,info as k,done as T,bold as m,cyan as h,gray as w,green as A,red as z,yellow as I}from"../ui/output.mjs";const Q="https://cloud.infernoflow.dev",H=".cloud.json";function R(e){const r=O.join(e,H);if(!j.existsSync(r))return null;try{return JSON.parse(j.readFileSync(r,"utf8"))}catch{return null}}function V(e,r){const t=O.join(e,H);j.writeFileSync(t,JSON.stringify(r,null,2)+`
2
- `)}function P(e,r){const t=r.indexOf("--token");return t!==-1?r[t+1]:process.env.INFERNOFLOW_TOKEN||e?.token||null}function b(e,r){const t=r.indexOf("--endpoint");return t!==-1?r[t+1]:process.env.INFERNOFLOW_ENDPOINT||e?.endpoint||Q}function J(e,r,t,o){return new Promise((n,f)=>{const i=new URL(r),u=i.protocol==="https:",g=u?K:W,s=t?JSON.stringify(t):null,a={hostname:i.hostname,port:i.port||(u?443:80),path:i.pathname+(i.search||""),method:e,headers:{"Content-Type":"application/json",Accept:"application/json","User-Agent":"infernoflow-cli",...o?{Authorization:`Bearer ${o}`}:{},...s?{"Content-Length":Buffer.byteLength(s)}:{}}},c=g.request(a,l=>{let d="";l.on("data",$=>d+=$),l.on("end",()=>{try{n({status:l.statusCode,body:JSON.parse(d)})}catch{n({status:l.statusCode,body:d})}})});c.on("error",f),s&&c.write(s),c.end()})}function L(e){const r=["contract.json","capabilities.json"];for(const t of r){const o=O.join(e,t);if(j.existsSync(o))try{return JSON.parse(j.readFileSync(o,"utf8"))}catch{}}return null}function x(e){return M.createHash("sha256").update(JSON.stringify(e)).digest("hex").slice(0,12)}const D="sessions.jsonl";function U(e){const r=O.join(e,D);return j.existsSync(r)?j.readFileSync(r,"utf8").split(`
3
- `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean):[]}function X(e,r){const t=O.join(e,D);j.writeFileSync(t,r.map(o=>JSON.stringify(o)).join(`
4
- `)+`
5
- `,"utf8")}function v(e){return M.createHash("sha256").update(JSON.stringify(e.map(r=>r.ts+r.summary))).digest("hex").slice(0,12)}function Y(e,r){const t=new Set(e.map(n=>`${n.ts}|${n.summary}`)),o=[...e];for(const n of r)t.has(`${n.ts}|${n.summary}`)||o.push(n);return o.sort((n,f)=>n.ts.localeCompare(f.ts))}async function _(e,r,t,o=!1){const n=e.includes("--json"),f=e.includes("--dry-run"),i=P(t,e),u=b(t,e),g=t?.projectId;if(!i||!g){const c="No token/project found. Run: infernoflow cloud init";return n?console.log(JSON.stringify({ok:!1,error:c})):o||p(c),{ok:!1}}const s=U(r);if(!s.length)return!o&&!n&&k("No session memory to push (inferno/sessions.jsonl is empty)."),{ok:!0,entries:0};const a=v(s);if(f)return n?console.log(JSON.stringify({ok:!0,dryRun:!0,entries:s.length,hash:a})):o||k(`Dry run \u2014 would push ${m(String(s.length))} memory entries (hash: ${a})`),{ok:!0,dryRun:!0};try{const c=await J("PUT",`${u}/api/projects/${g}/memory`,{entries:s,hash:a,pushedAt:new Date().toISOString()},i),l=c.status===200||c.status===201||c.status===204;return n?console.log(JSON.stringify({ok:l,entries:s.length,hash:a})):o||(l?E(`Pushed ${m(String(s.length))} memory entries`):p(`Cloud returned ${c.status}`)),{ok:l,entries:s.length}}catch(c){return n?console.log(JSON.stringify({ok:!1,error:c.message})):o||p(`Memory push failed: ${c.message}`),{ok:!1}}}async function B(e,r,t,o=!1){const n=e.includes("--json"),f=e.includes("--dry-run"),i=P(t,e),u=b(t,e),g=t?.projectId,s=e.includes("--force")||e.includes("-f");if(!i||!g){const a="No token/project found. Run: infernoflow cloud init";return n?console.log(JSON.stringify({ok:!1,error:a})):o||p(a),{ok:!1}}try{const a=await J("GET",`${u}/api/projects/${g}/memory`,null,i);if(a.status!==200){const S=`Cloud returned ${a.status}`;return n?console.log(JSON.stringify({ok:!1,error:S})):o||p(S),{ok:!1}}const c=a.body?.entries;if(!c||!c.length)return!o&&!n&&k("No session memory in cloud yet. Push first."),{ok:!0,entries:0};const l=U(r),d=s?c:Y(l,c),$=d.length-l.length;return f?(n?console.log(JSON.stringify({ok:!0,dryRun:!0,remote:c.length,local:l.length,merged:d.length})):o||k(`Dry run \u2014 would merge ${m(String(c.length))} remote + ${m(String(l.length))} local = ${m(String(d.length))} entries`),{ok:!0,dryRun:!0}):(X(r,d),n?console.log(JSON.stringify({ok:!0,remote:c.length,local:l.length,merged:d.length,newEntries:$})):o||E(`Merged ${m(String(c.length))} remote entries \u2192 ${m(String(d.length))} total (${$} new)`),{ok:!0,entries:d.length})}catch(a){return n?console.log(JSON.stringify({ok:!1,error:a.message})):o||p(`Memory pull failed: ${a.message}`),{ok:!1}}}async function Z(e,r,t){const o=e.includes("--json"),n=R(t),f=P(n,e),i=b(n,e),u=e[0],g=e.slice(1);if(u==="push")return o||C("Pushing session memory to cloud"),_(g,t,n);if(u==="pull")return o||C("Pulling session memory from cloud"),B(g,t,n);if(u==="status"||!u){const s=U(t),a=n?.projectId;if(!n||!f){o?console.log(JSON.stringify({ok:!1,error:"Not initialised. Run: infernoflow cloud init"})):p("Cloud not configured. Run: infernoflow cloud init");return}let c=null,l=null,d=!1;try{const S=await J("GET",`${i}/api/projects/${a}/memory`,null,f);S.status===200&&S.body?.entries&&(d=!0,c=S.body.entries.length,l=v(S.body.entries))}catch{}const $=s.length?v(s):null;if(o){console.log(JSON.stringify({ok:!0,local:{entries:s.length,hash:$},remote:d?{entries:c,hash:l}:null,reachable:d,inSync:$===l}));return}console.log(),console.log(` ${m("infernoflow cloud memory status")}`),console.log(),console.log(` Local: ${m(String(s.length))} entries ${w("(hash: "+($||"none")+")")}`),d?(console.log(` Cloud: ${m(String(c))} entries ${w("(hash: "+(l||"none")+")")}`),console.log($===l?`
6
- ${A("\u2714")} Memory in sync`:`
7
- ${I("\u26A0")} Out of sync \u2014 run ${h("infernoflow cloud memory push")} or ${h("infernoflow cloud memory pull")}`)):console.log(` Cloud: ${I("unreachable")}`),console.log();return}console.log(),console.log(` ${m("infernoflow cloud memory")} \u2014 session memory sync`),console.log(),console.log(` ${h("infernoflow cloud memory push")} Upload sessions.jsonl to cloud`),console.log(` ${h("infernoflow cloud memory pull")} Download + merge remote memory`),console.log(` ${h("infernoflow cloud memory status")} Compare local vs remote`),console.log()}async function q(e,r,t){const o=e.includes("--json"),n=b(null,e),f=e.includes("--dry-run"),i=R(t);if(i&&!e.includes("--force")&&!e.includes("-f")){o?console.log(JSON.stringify({ok:!1,error:"Already initialised. Use --force to overwrite.",config:i})):(p("Cloud already configured for this project."),console.log(` Token: ${w(i.token)}`),console.log(` Endpoint: ${w(i.endpoint)}`),console.log(` Project: ${w(i.projectId)}`),console.log(),k("Use --force to generate a new token."));return}const u=M.randomBytes(8).toString("hex"),g=M.randomBytes(24).toString("base64url"),s={projectId:u,token:g,endpoint:n,createdAt:new Date().toISOString()};if(f){o?console.log(JSON.stringify({ok:!0,dryRun:!0,config:s})):(k("Dry run \u2014 would write inferno/.cloud.json:"),console.log(" "+JSON.stringify(s,null,2).split(`
8
- `).join(`
9
- `)));return}o||C("Initialising infernoflow cloud");try{const a=await J("POST",`${n}/api/projects`,{projectId:u},null);(a.status===200||a.status===201)&&(o||E("Project registered on cloud"))}catch{o||k("Cloud endpoint unreachable \u2014 saved config locally (will connect on first push)")}V(t,s),o?console.log(JSON.stringify({ok:!0,projectId:u,endpoint:n})):(T("Cloud configured!"),console.log(),console.log(` Project ID: ${h(u)}`),console.log(` Endpoint: ${w(n)}`),console.log(` Token: ${w(g.slice(0,8)+"\u2026")} (stored in inferno/.cloud.json)`),console.log(),console.log(` ${w("Share the dashboard:")} ${h(`${n}/p/${u}`)}`),console.log(),console.log(` ${I("\u26A0")} Add inferno/.cloud.json to .gitignore to protect your token!`),console.log(` ${w("echo 'inferno/.cloud.json' >> .gitignore")}`),console.log())}async function oo(e,r,t){const o=e.includes("--json"),n=e.includes("--dry-run"),f=R(t),i=P(f,e),u=b(f,e);if(!i){const l="No token found. Run: infernoflow cloud init";o?console.log(JSON.stringify({ok:!1,error:l})):p(l),process.exit(1)}const g=L(t);if(!g){const l="No contract.json found. Run: infernoflow init";o?console.log(JSON.stringify({ok:!1,error:l})):p(l),process.exit(1)}const s=f?.projectId||"unknown",a=x(g),c=(g.capabilities||[]).length;if(n){o?console.log(JSON.stringify({ok:!0,dryRun:!0,projectId:s,hash:a,capabilities:c})):k(`Dry run \u2014 would push ${m(String(c))} capabilities (hash: ${a}) to ${u}`);return}o||C("Pushing contract to cloud");try{const l=await J("PUT",`${u}/api/projects/${s}/contract`,{contract:g,hash:a,pushedAt:new Date().toISOString()},i);if(l.status===200||l.status===201||l.status===204)o?console.log(JSON.stringify({ok:!0,projectId:s,hash:a,capabilities:c})):(T(`Pushed ${m(String(c))} capabilities`),console.log(` ${w("Dashboard:")} ${h(`${u}/p/${s}`)}`),console.log()),e.includes("--memory")&&(o||k("Pushing session memory..."),await _(e,t,f,o),o||E("Session memory pushed"));else{const d=`Cloud returned ${l.status}`;o?console.log(JSON.stringify({ok:!1,error:d,status:l.status})):p(d),process.exit(1)}}catch(l){const d=O.join(t,".cloud-pending.json");j.writeFileSync(d,JSON.stringify({hash:a,pendingAt:new Date().toISOString()})),o?console.log(JSON.stringify({ok:!1,error:l.message,pending:!0})):(p("Cloud unreachable \u2014 push queued locally."),k("Changes will sync automatically on next successful connection."))}}async function eo(e,r,t){const o=e.includes("--json"),n=e.includes("--dry-run"),f=R(t),i=P(f,e),u=b(f,e);if(!i){const s="No token found. Run: infernoflow cloud init";o?console.log(JSON.stringify({ok:!1,error:s})):p(s),process.exit(1)}const g=f?.projectId||"unknown";o||C("Pulling contract from cloud");try{const s=await J("GET",`${u}/api/projects/${g}/contract`,null,i);if(s.status!==200){const y=`Cloud returned ${s.status}`;o?console.log(JSON.stringify({ok:!1,error:y})):p(y),process.exit(1)}const a=s.body?.contract,c=L(t);if(!a){const y="No contract found on cloud. Push first.";o?console.log(JSON.stringify({ok:!1,error:y})):p(y);return}const l=(c?.capabilities||[]).map(y=>typeof y=="string"?y:y.id),d=(a.capabilities||[]).map(y=>typeof y=="string"?y:y.id),$=new Set(l),S=new Set(d),N=l.filter(y=>!S.has(y)),F=d.filter(y=>!$.has(y));if(N.length>0&&F.length>0&&(o?console.log(JSON.stringify({ok:!1,conflict:!0,onlyLocal:N,onlyRemote:F})):(p("Diverged contracts detected:"),N.forEach(y=>console.log(` ${z("-")} local-only: ${y}`)),F.forEach(y=>console.log(` ${A("+")} remote-only: ${y}`)),console.log(),p("Merge manually or use --force to overwrite local with remote.")),!e.includes("--force")&&!e.includes("-f")))return;if(n){o?console.log(JSON.stringify({ok:!0,dryRun:!0,capabilities:d.length,hash:x(a)})):k(`Dry run \u2014 would write ${m(String(d.length))} capabilities from cloud`);return}const G=O.join(t,"contract.json");j.writeFileSync(G,JSON.stringify(a,null,2)+`
10
- `),o?console.log(JSON.stringify({ok:!0,capabilities:d.length,hash:x(a)})):(T(`Pulled ${m(String(d.length))} capabilities from cloud`),N.length&&p(`${N.length} local-only capabilities were overwritten.`),console.log()),e.includes("--memory")&&(o||k("Pulling session memory..."),await B(e,t,f,o))}catch(s){o?console.log(JSON.stringify({ok:!1,error:s.message})):p(`Cloud unreachable: ${s.message}`),process.exit(1)}}async function no(e,r,t){const o=e.includes("--json"),n=R(t),f=P(n,e),i=b(n,e);if(!n){o?console.log(JSON.stringify({ok:!1,error:"Not initialised. Run: infernoflow cloud init"})):p("Cloud not configured. Run: infernoflow cloud init");return}const u=n.projectId,g=L(t),s=g?x(g):null,a=(g?.capabilities||[]).length;o||C("Cloud status");let c=null,l=0,d=!1;try{const N=await J("GET",`${i}/api/projects/${u}/contract`,null,f);N.status===200&&N.body?.contract&&(d=!0,c=x(N.body.contract),l=(N.body.contract?.capabilities||[]).length)}catch{}const $=s===c,S=j.existsSync(O.join(t,".cloud-pending.json"));if(o){console.log(JSON.stringify({ok:!0,projectId:u,endpoint:i,reachable:d,inSync:$,pending:S,local:{hash:s,capabilities:a},remote:d?{hash:c,capabilities:l}:null}));return}console.log(` Project: ${h(u)}`),console.log(` Endpoint: ${w(i)}`),console.log(` Dashboard: ${h(`${i}/p/${u}`)}`),console.log(),console.log(` Local: ${m(String(a))} capabilities ${w("(hash: "+(s||"none")+")")}`),d?(console.log(` Cloud: ${m(String(l))} capabilities ${w("(hash: "+(c||"none")+")")}`),console.log(),console.log($?` ${A("\u2714")} In sync with cloud`:` ${I("\u26A0")} Out of sync \u2014 run ${h("infernoflow cloud push")} or ${h("infernoflow cloud pull")}`)):console.log(` Cloud: ${I("unreachable")}`),S&&console.log(` ${I("\u26A0")} Pending push queued (cloud was unreachable last time)`),console.log()}async function to(e,r,t){const o=R(t),n=b(o,e),f=o?.projectId,i=e.includes("--json");if(!f){i?console.log(JSON.stringify({ok:!1,error:"Run: infernoflow cloud init first"})):p("Not configured. Run: infernoflow cloud init first.");return}const u=`${n}/p/${f}`;if(i){console.log(JSON.stringify({ok:!0,url:u}));return}console.log(),console.log(` ${m("\u{1F525} infernoflow cloud dashboard")}`),console.log(),console.log(` ${h(u)}`),console.log(),console.log(` ${w("Share this URL with your whole team.")}`),console.log();try{const{execSync:g}=await import("node:child_process"),s=process.platform==="win32"?`start "" "${u}"`:process.platform==="darwin"?`open "${u}"`:`xdg-open "${u}"`;g(s,{stdio:"ignore"})}catch{}}async function lo(e){const r=e.slice(1),t=r[0],o=process.cwd(),n=O.join(o,"inferno");if(!j.existsSync(n)){const i="inferno/ directory not found. Run: infernoflow init";r.includes("--json")?console.log(JSON.stringify({ok:!1,error:i})):p(i),process.exit(1)}const f=r.slice(1);switch(t){case"init":return q(f,o,n);case"push":return oo(f,o,n);case"pull":return eo(f,o,n);case"status":return no(f,o,n);case"dashboard":return to(f,o,n);case"memory":return Z(f,o,n);default:{const i=r.includes("--json"),u=`Unknown cloud sub-command: ${t||"(none)"}. Use: init | push | pull | memory | status | dashboard`;i?console.log(JSON.stringify({ok:!1,error:u})):(console.log(),console.log(` ${m("infernoflow cloud")} \u2014 hosted contract + memory sync`),console.log(),console.log(` ${h("infernoflow cloud init")} Set up cloud sync for this project`),console.log(` ${h("infernoflow cloud push")} Upload local contract to cloud`),console.log(` ${h("infernoflow cloud push --memory")} Also push sessions.jsonl`),console.log(` ${h("infernoflow cloud pull")} Download latest contract from cloud`),console.log(` ${h("infernoflow cloud pull --memory")} Also pull + merge session memory`),console.log(` ${h("infernoflow cloud memory push/pull")} Session memory only`),console.log(` ${h("infernoflow cloud status")} Compare local vs cloud`),console.log(` ${h("infernoflow cloud dashboard")} Open hosted dashboard in browser`),console.log())}}}export{lo as cloudCommand};