infernoflow 0.43.5 → 0.43.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -13
- package/dist/bin/infernoflow.mjs +24 -25
- package/dist/lib/commands/adopt.mjs +10 -10
- package/dist/lib/commands/graph.mjs +55 -94
- package/dist/lib/commands/log.mjs +17 -17
- package/package.json +61 -63
- package/dist/lib/cloud/credentials.mjs +0 -2
- package/dist/lib/cloud/supabase.mjs +0 -1
- package/dist/lib/commands/cloud.mjs +0 -10
- package/dist/lib/commands/dashboard.mjs +0 -442
- package/dist/lib/commands/login.mjs +0 -35
- package/scripts/postinstall.js +0 -2
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
import*as m from"node:fs";import*as g from"node:path";import*as V from"node:http";import*as K from"node:os";import{execSync as S,spawn as z}from"node:child_process";import{fileURLToPath as Q}from"node:url";import{header as X,ok as Z,info as P,warn as q,cyan as tt}from"../ui/output.mjs";const T=g.dirname(Q(import.meta.url));function et(t){const n=g.join(t,"contract.json");if(!m.existsSync(n))return null;try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return null}}function nt(t){for(const n of["capabilities.json","contract.json"]){const l=g.join(t,n);if(m.existsSync(l))try{return(JSON.parse(m.readFileSync(l,"utf8")).capabilities||[]).map(d=>typeof d=="string"?{id:d,title:d}:d)}catch{}}return[]}function at(t){const n=g.join(t,"developer-profile.json");if(!m.existsSync(n))return null;try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return null}}function it(t){const n=g.join(t,"agents");return m.existsSync(n)?m.readdirSync(n).filter(l=>l.endsWith(".json")).map(l=>{try{return JSON.parse(m.readFileSync(g.join(n,l),"utf8"))}catch{return null}}).filter(Boolean):[]}function ot(t){const n=g.join(t,"HOOK.log");if(!m.existsSync(n))return null;try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return null}}function st(t){try{const n=S("npx infernoflow check --json",{cwd:g.dirname(t),encoding:"utf8",timeout:15e3,stdio:["ignore","pipe","pipe"]});return JSON.parse(n)}catch(n){try{return JSON.parse(n.stdout||"{}")}catch{return{status:"error",error:"check failed"}}}}function ct(t){const n=g.join(t,"audit.json");if(!m.existsSync(n))return null;try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return null}}function rt(t){const n=g.join(t,"links.json");if(!m.existsSync(n))return[];try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return[]}}function dt(t,n){try{let d=function(e){const i=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate())),p=i.getUTCDay()||7;i.setUTCDate(i.getUTCDate()+4-p);const v=new Date(Date.UTC(i.getUTCFullYear(),0,1)),x=Math.ceil(((i-v)/864e5+1)/7);return`${i.getUTCFullYear()}-W${String(x).padStart(2,"0")}`};var l=d;const u=S('git log --since="90 days ago" --format="%aI|%ae|%s" -- inferno/',{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"],timeout:8e3}).trim();if(!u)return{velocity:[],contributors:[],healthTrend:[]};const r=u.split(`
|
|
2
|
-
`).filter(Boolean).map(e=>{const[i,p,...v]=e.split("|");return{date:new Date(i),email:p||"unknown",subject:v.join("|")}}),h=new Map;for(const e of r){const i=d(e.date);h.set(i,(h.get(i)||0)+1)}const s=[],o=new Date;for(let e=12;e>=0;e--){const i=new Date(o);i.setDate(i.getDate()-e*7);const p=d(i);s.push({week:p,commits:h.get(p)||0})}const b=new Map;for(const e of r){const i=e.email.split("@")[0];b.set(i,(b.get(i)||0)+1)}const y=[...b.entries()].map(([e,i])=>({name:e,count:i})).sort((e,i)=>i.count-e.count).slice(0,8),c=s.map(e=>({week:e.week,score:e.commits===0?40:e.commits<=2?75:e.commits<=5?90:85,label:e.commits===0?"stale":e.commits<=2?"ok":e.commits<=5?"healthy":"busy"}));return{velocity:s,contributors:y,healthTrend:c}}catch{return{velocity:[],contributors:[],healthTrend:[]}}}function lt(t){const n=g.join(t,"scan.json");if(!m.existsSync(n))return null;try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return null}}function pt(t){const n=g.join(t,"graph.json");if(!m.existsSync(n))return null;try{return JSON.parse(m.readFileSync(n,"utf8"))}catch{return null}}function E(t){const n=nt(t),l=et(t),u=at(t),r=it(t),d=ot(t),h=st(t),s=ct(t),o=rt(t),b=u?.recentSessions?.slice(-10)||[],y=[...u?.agentCandidates||[],...u?.skillCandidates||[]],c=g.dirname(t),e=dt(c,t),i=lt(t),p=pt(t);return{caps:n,contract:l,agents:r,hookLog:d,check:h,sessions:b,candidates:y,audit:s,links:o,analytics:e,scan:i,graph:p,infernoDir:t}}function ut(t,n,l="#f97316",u=80){const d=u,h=t.length;if(!h)return`<svg width="600" height="${d}"></svg>`;const s=Math.max(...t,1),o=Math.floor(600/h)-4,b=t.map((y,c)=>{const e=Math.max(2,Math.round(y/s*(d-20))),i=c*(600/h)+2,p=d-e-10;return`<rect x="${i}" y="${p}" width="${o}" height="${e}" fill="${l}" rx="2" opacity="0.85"/>
|
|
3
|
-
<title>${n[c]}: ${y}</title>`}).join(`
|
|
4
|
-
`);return`<svg viewBox="0 0 600 ${d}" width="100%" height="${d}" xmlns="http://www.w3.org/2000/svg">${b}</svg>`}function ht(t,n="#3b82f6",l=80){const r=l,d=t.length;if(d<2)return`<svg width="600" height="${r}"></svg>`;const h=Math.max(...t,1),s=Math.min(...t,0),o=h-s||1,b=t.map((y,c)=>{const e=Math.round(c/(d-1)*580)+10,i=Math.round(r-10-(y-s)/o*(r-20));return`${e},${i}`}).join(" ");return`<svg viewBox="0 0 600 ${r}" width="100%" height="${r}" xmlns="http://www.w3.org/2000/svg">
|
|
5
|
-
<polyline points="${b}" fill="none" stroke="${n}" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
6
|
-
${t.map((y,c)=>{const[e,i]=b.split(" ")[c].split(",");return`<circle cx="${e}" cy="${i}" r="4" fill="${n}"><title>${y}</title></circle>`}).join("")}
|
|
7
|
-
</svg>`}function mt(t,n,l){const u=l>0?Math.round(n/l*100):0,r=u>70?"#f97316":u>40?"#f59e0b":u>10?"#3b82f6":"#2d3148";return`<div class="heat-row">
|
|
8
|
-
<span class="heat-name">${f(t)}</span>
|
|
9
|
-
<div class="heat-bar-wrap"><div class="heat-bar" style="width:${u}%;background:${r}"></div></div>
|
|
10
|
-
<span class="heat-count">${n}</span>
|
|
11
|
-
</div>`}function ft(t,n){const{caps:l,agents:u,check:r,sessions:d,candidates:h,audit:s,links:o,analytics:b}=t,y=r?.status==="ok"?"#22c55e":r?.status==="warning"?"#f59e0b":r?.status==="error"?"#ef4444":"#6b7280",c=r?.status||"unknown",e=l.length,i=u.length,p=(r?.issues||[]).length,v=l.map(a=>{const w=a.status?`<span class="badge">${a.status}</span>`:"";return`<tr>
|
|
12
|
-
<td><code>${f(a.id)}</code></td>
|
|
13
|
-
<td>${f(a.title||"")}${w}</td>
|
|
14
|
-
<td>${f(a.since||"")}</td>
|
|
15
|
-
</tr>`}).join(`
|
|
16
|
-
`),x=u.map(a=>{const w=(a.steps||[]).map(j=>typeof j=="string"?j:j.command).join(" \u2192 "),C=a.confidence?`${Math.round(a.confidence*100)}%`:"\u2014";return`<tr>
|
|
17
|
-
<td><strong>${f(a.name)}</strong></td>
|
|
18
|
-
<td>${f(a.description||w)}</td>
|
|
19
|
-
<td><code>${f(w)}</code></td>
|
|
20
|
-
<td>${C}</td>
|
|
21
|
-
</tr>`}).join(`
|
|
22
|
-
`),H=(r?.issues||[]).map(a=>`<li class="issue">${f(typeof a=="string"?a:a.message||JSON.stringify(a))}</li>`).join(`
|
|
23
|
-
`),L=d.slice().reverse().map(a=>{const w=(a.commands||[]).join(", "),C=a.startedAt?new Date(a.startedAt).toLocaleString():"unknown";return`<div class="session-item">
|
|
24
|
-
<span class="session-date">${f(C)}</span>
|
|
25
|
-
<span class="session-cmds">${f(w||"no commands recorded")}</span>
|
|
26
|
-
</div>`}).join(`
|
|
27
|
-
`),R=h.map(a=>`<li class="candidate">${f(a.name||a.id||"unnamed")}: ${f(a.description||"")}</li>`).join(`
|
|
28
|
-
`),O=b?.velocity||[],$=b?.contributors||[],F=b?.healthTrend||[],J=O.map(a=>a.commits),A=O.map(a=>a.week),U=ut(J,A,"#f97316",90),D=F.map(a=>a.score),W=ht(D,"#3b82f6",80),B=$.length?Math.max(...$.map(a=>a.count)):1,_=$.length?$.map(a=>mt(a.name,a.count,B)).join(`
|
|
29
|
-
`):'<div class="empty">No git history in inferno/ yet</div>',k=s?.stats||null,I=k?.high??"\u2014",G=k?.medium??"\u2014",N=o.length;return`<!DOCTYPE html>
|
|
30
|
-
<html lang="en">
|
|
31
|
-
<head>
|
|
32
|
-
<meta charset="UTF-8">
|
|
33
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
34
|
-
<title>infernoflow \u2014 ${f(n)}</title>
|
|
35
|
-
<style>
|
|
36
|
-
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
37
|
-
:root {
|
|
38
|
-
--bg: #0f1117; --surface: #1a1d27; --border: #2d3148;
|
|
39
|
-
--text: #e2e8f0; --muted: #64748b; --accent: #f97316;
|
|
40
|
-
--green: #22c55e; --yellow: #f59e0b; --red: #ef4444; --blue: #3b82f6;
|
|
41
|
-
}
|
|
42
|
-
body { background: var(--bg); color: var(--text); font-family: system-ui, sans-serif; font-size: 14px; line-height: 1.5; }
|
|
43
|
-
header { background: var(--surface); border-bottom: 1px solid var(--border); padding: 16px 24px; display: flex; align-items: center; gap: 12px; }
|
|
44
|
-
header h1 { font-size: 18px; font-weight: 700; }
|
|
45
|
-
header .flame { font-size: 22px; }
|
|
46
|
-
header .project { color: var(--muted); font-size: 13px; }
|
|
47
|
-
header .live { margin-left: auto; font-size: 11px; color: var(--green); display: flex; align-items: center; gap: 4px; }
|
|
48
|
-
header .live::before { content: ""; display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; }
|
|
49
|
-
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
50
|
-
main { max-width: 1100px; margin: 0 auto; padding: 24px; display: grid; gap: 20px; }
|
|
51
|
-
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; }
|
|
52
|
-
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 18px; }
|
|
53
|
-
.card .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted); margin-bottom: 8px; }
|
|
54
|
-
.card .value { font-size: 28px; font-weight: 700; }
|
|
55
|
-
.card .sub { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
|
56
|
-
.status-ok { color: var(--green); }
|
|
57
|
-
.status-warning { color: var(--yellow); }
|
|
58
|
-
.status-error { color: var(--red); }
|
|
59
|
-
section { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
|
|
60
|
-
section h2 { font-size: 13px; font-weight: 600; padding: 14px 18px; border-bottom: 1px solid var(--border); color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
|
|
61
|
-
table { width: 100%; border-collapse: collapse; }
|
|
62
|
-
th, td { padding: 10px 18px; text-align: left; border-bottom: 1px solid var(--border); }
|
|
63
|
-
th { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); background: rgba(255,255,255,0.02); }
|
|
64
|
-
tr:last-child td { border-bottom: none; }
|
|
65
|
-
tr:hover td { background: rgba(255,255,255,0.03); }
|
|
66
|
-
code { font-family: monospace; font-size: 12px; background: rgba(255,255,255,0.07); padding: 1px 5px; border-radius: 4px; }
|
|
67
|
-
.badge { font-size: 10px; background: rgba(249,115,22,0.15); color: var(--accent); padding: 1px 6px; border-radius: 9px; margin-left: 6px; }
|
|
68
|
-
.issues-list, .candidates-list { list-style: none; padding: 14px 18px; display: flex; flex-direction: column; gap: 8px; }
|
|
69
|
-
.issue { color: var(--red); font-size: 13px; }
|
|
70
|
-
.candidate { color: var(--yellow); font-size: 13px; }
|
|
71
|
-
.empty { padding: 24px 18px; color: var(--muted); text-align: center; font-size: 13px; }
|
|
72
|
-
.session-item { display: flex; gap: 16px; align-items: baseline; padding: 9px 18px; border-bottom: 1px solid var(--border); }
|
|
73
|
-
.session-item:last-child { border-bottom: none; }
|
|
74
|
-
.session-date { font-size: 11px; color: var(--muted); white-space: nowrap; min-width: 140px; }
|
|
75
|
-
.session-cmds { font-size: 12px; color: var(--text); }
|
|
76
|
-
/* Analytics */
|
|
77
|
-
.chart-wrap { padding: 16px 18px; }
|
|
78
|
-
.chart-label { font-size: 11px; color: var(--muted); margin-top: 6px; text-align: center; }
|
|
79
|
-
.analytics-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
80
|
-
.heat-row { display: flex; align-items: center; gap: 10px; padding: 6px 18px; border-bottom: 1px solid var(--border); }
|
|
81
|
-
.heat-row:last-child { border-bottom: none; }
|
|
82
|
-
.heat-name { min-width: 110px; font-size: 12px; color: var(--text); font-family: monospace; }
|
|
83
|
-
.heat-bar-wrap { flex: 1; height: 10px; background: var(--border); border-radius: 5px; overflow: hidden; }
|
|
84
|
-
.heat-bar { height: 100%; border-radius: 5px; transition: width 0.3s; }
|
|
85
|
-
.heat-count { font-size: 12px; color: var(--muted); min-width: 30px; text-align: right; }
|
|
86
|
-
.audit-tags { display: flex; gap: 8px; padding: 14px 18px; flex-wrap: wrap; }
|
|
87
|
-
.tag { font-size: 12px; padding: 4px 10px; border-radius: 9px; font-weight: 600; }
|
|
88
|
-
.tag-high { background: rgba(239,68,68,0.15); color: #ef4444; }
|
|
89
|
-
.tag-medium { background: rgba(245,158,11,0.15); color: #f59e0b; }
|
|
90
|
-
.tag-low { background: rgba(34,197,94,0.15); color: #22c55e; }
|
|
91
|
-
.tag-link { background: rgba(59,130,246,0.15); color: #3b82f6; }
|
|
92
|
-
footer { text-align: center; color: var(--muted); font-size: 11px; padding: 24px; }
|
|
93
|
-
</style>
|
|
94
|
-
</head>
|
|
95
|
-
<body>
|
|
96
|
-
<header>
|
|
97
|
-
<span class="flame">\u{1F525}</span>
|
|
98
|
-
<div>
|
|
99
|
-
<h1>infernoflow</h1>
|
|
100
|
-
<div class="project">${f(n)}</div>
|
|
101
|
-
</div>
|
|
102
|
-
<div class="live">Live</div>
|
|
103
|
-
</header>
|
|
104
|
-
<main>
|
|
105
|
-
|
|
106
|
-
<!-- Stat cards -->
|
|
107
|
-
<div class="cards">
|
|
108
|
-
<div class="card">
|
|
109
|
-
<div class="label">Contract status</div>
|
|
110
|
-
<div class="value status-${c}" style="color:${y}">${c.toUpperCase()}</div>
|
|
111
|
-
<div class="sub">${p>0?p+" issue"+(p!==1?"s":""):"All checks passed"}</div>
|
|
112
|
-
</div>
|
|
113
|
-
<div class="card">
|
|
114
|
-
<div class="label">Capabilities</div>
|
|
115
|
-
<div class="value">${e}</div>
|
|
116
|
-
<div class="sub">tracked in contract</div>
|
|
117
|
-
</div>
|
|
118
|
-
<div class="card">
|
|
119
|
-
<div class="label">Agents</div>
|
|
120
|
-
<div class="value">${i}</div>
|
|
121
|
-
<div class="sub">synthesized workflows</div>
|
|
122
|
-
</div>
|
|
123
|
-
<div class="card">
|
|
124
|
-
<div class="label">Sessions</div>
|
|
125
|
-
<div class="value">${d.length}</div>
|
|
126
|
-
<div class="sub">recent sessions logged</div>
|
|
127
|
-
</div>
|
|
128
|
-
${k?`
|
|
129
|
-
<div class="card">
|
|
130
|
-
<div class="label">Security surface</div>
|
|
131
|
-
<div class="value" style="color:${I>0?"var(--red)":"var(--green)"}">${I}</div>
|
|
132
|
-
<div class="sub">${I} high \xB7 ${G} medium risk caps</div>
|
|
133
|
-
</div>`:""}
|
|
134
|
-
<div class="card">
|
|
135
|
-
<div class="label">Linked tickets</div>
|
|
136
|
-
<div class="value" style="color:var(--blue)">${N}</div>
|
|
137
|
-
<div class="sub">caps linked to Jira/Linear/GitHub</div>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
${p>0?`
|
|
142
|
-
<!-- Issues -->
|
|
143
|
-
<section>
|
|
144
|
-
<h2>\u26A0 Issues</h2>
|
|
145
|
-
<ul class="issues-list">${H}</ul>
|
|
146
|
-
</section>`:""}
|
|
147
|
-
|
|
148
|
-
<!-- Capabilities -->
|
|
149
|
-
<section>
|
|
150
|
-
<h2>Capabilities (${e})</h2>
|
|
151
|
-
${e>0?`
|
|
152
|
-
<table>
|
|
153
|
-
<thead><tr><th>ID</th><th>Title</th><th>Since</th></tr></thead>
|
|
154
|
-
<tbody>${v}</tbody>
|
|
155
|
-
</table>`:'<div class="empty">No capabilities found in inferno/capabilities.json</div>'}
|
|
156
|
-
</section>
|
|
157
|
-
|
|
158
|
-
<!-- Agents -->
|
|
159
|
-
<section>
|
|
160
|
-
<h2>Synthesized Agents (${i})</h2>
|
|
161
|
-
${i>0?`
|
|
162
|
-
<table>
|
|
163
|
-
<thead><tr><th>Name</th><th>Description</th><th>Steps</th><th>Confidence</th></tr></thead>
|
|
164
|
-
<tbody>${x}</tbody>
|
|
165
|
-
</table>`:'<div class="empty">No agents yet \u2014 run <code>infernoflow synthesize</code> to generate them</div>'}
|
|
166
|
-
</section>
|
|
167
|
-
|
|
168
|
-
${h.length>0?`
|
|
169
|
-
<!-- Candidates -->
|
|
170
|
-
<section>
|
|
171
|
-
<h2>Workflow Candidates (${h.length})</h2>
|
|
172
|
-
<ul class="candidates-list">${R}</ul>
|
|
173
|
-
</section>`:""}
|
|
174
|
-
|
|
175
|
-
<!-- Session timeline -->
|
|
176
|
-
<section>
|
|
177
|
-
<h2>Recent Sessions</h2>
|
|
178
|
-
${d.length>0?`<div>${L}</div>`:'<div class="empty">No session data yet \u2014 sessions are logged automatically as you use infernoflow</div>'}
|
|
179
|
-
</section>
|
|
180
|
-
|
|
181
|
-
<!-- Analytics: velocity + health trend -->
|
|
182
|
-
${O.length>0?`
|
|
183
|
-
<div class="analytics-grid">
|
|
184
|
-
<section>
|
|
185
|
-
<h2>\u{1F4C8} Capability Velocity (13 weeks)</h2>
|
|
186
|
-
<div class="chart-wrap">
|
|
187
|
-
${U}
|
|
188
|
-
<div class="chart-label">Commits touching inferno/ per week</div>
|
|
189
|
-
</div>
|
|
190
|
-
</section>
|
|
191
|
-
<section>
|
|
192
|
-
<h2>\u{1F49A} Health Score Trend</h2>
|
|
193
|
-
<div class="chart-wrap">
|
|
194
|
-
${W}
|
|
195
|
-
<div class="chart-label">Heuristic health score over last 13 weeks</div>
|
|
196
|
-
</div>
|
|
197
|
-
</section>
|
|
198
|
-
</div>`:""}
|
|
199
|
-
|
|
200
|
-
<!-- Contributor heatmap -->
|
|
201
|
-
${$.length>0?`
|
|
202
|
-
<section>
|
|
203
|
-
<h2>\u{1F465} Contributor Heatmap (90 days)</h2>
|
|
204
|
-
${_}
|
|
205
|
-
</section>`:""}
|
|
206
|
-
|
|
207
|
-
<!-- Audit surface map (if audit.json exists) -->
|
|
208
|
-
${k?`
|
|
209
|
-
<section>
|
|
210
|
-
<h2>\u{1F510} Security Surface (last audit)</h2>
|
|
211
|
-
<div class="audit-tags">
|
|
212
|
-
<span class="tag tag-high">\u{1F534} ${k.high} HIGH</span>
|
|
213
|
-
<span class="tag tag-medium">\u{1F7E1} ${k.medium} MEDIUM</span>
|
|
214
|
-
<span class="tag tag-low">\u{1F7E2} ${k.low} LOW</span>
|
|
215
|
-
${N>0?`<span class="tag tag-link">\u{1F517} ${N} linked to tickets</span>`:""}
|
|
216
|
-
</div>
|
|
217
|
-
${s.capabilities?`
|
|
218
|
-
<table>
|
|
219
|
-
<thead><tr><th>Severity</th><th>Capability</th><th>Tags</th></tr></thead>
|
|
220
|
-
<tbody>
|
|
221
|
-
${s.capabilities.filter(a=>a.severity==="high"||a.severity==="medium").slice(0,10).map(a=>`
|
|
222
|
-
<tr>
|
|
223
|
-
<td style="color:${a.severity==="high"?"var(--red)":"var(--yellow)"}">${a.severity}</td>
|
|
224
|
-
<td><code>${f(a.id)}</code></td>
|
|
225
|
-
<td>${f((a.tags||[]).join(", "))}</td>
|
|
226
|
-
</tr>`).join("")}
|
|
227
|
-
</tbody>
|
|
228
|
-
</table>`:""}
|
|
229
|
-
<div style="padding:8px 18px;font-size:11px;color:var(--muted)">Run <code>infernoflow audit</code> to refresh \xB7 Last run: ${f(s.runAt?new Date(s.runAt).toLocaleString():"unknown")}</div>
|
|
230
|
-
</section>`:`
|
|
231
|
-
<section>
|
|
232
|
-
<h2>\u{1F510} Security Surface</h2>
|
|
233
|
-
<div class="empty">No audit data yet \u2014 run <code>infernoflow audit</code> to classify capabilities by security sensitivity</div>
|
|
234
|
-
</section>`}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
<!-- \u2500\u2500 Command Center \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 -->
|
|
238
|
-
<section id="command-center">
|
|
239
|
-
<h2>\u{1F39B}\uFE0F Command Center</h2>
|
|
240
|
-
<div class="cc-layout">
|
|
241
|
-
<!-- Left: capability list -->
|
|
242
|
-
<div class="cc-caps">
|
|
243
|
-
<h3>Capabilities</h3>
|
|
244
|
-
<div class="cc-cap-list" id="cc-cap-list">
|
|
245
|
-
${t.caps.map(a=>{const w=a.stability||"experimental",C=w==="frozen"?"\u{1F9CA}":w==="stable"?"\u3030\uFE0F":"\u{1F30A}",M=t.scan?.capabilities?.find(Y=>Y.id===a.id)?.codeAnalysis?.sourceFiles||[];return`<div class="cc-cap-row" onclick="capDetail('${f(a.id)}')">
|
|
246
|
-
<span class="cc-icon">${C}</span>
|
|
247
|
-
<div class="cc-cap-info">
|
|
248
|
-
<span class="cc-cap-id">${f(a.id)}</span>
|
|
249
|
-
${M.length?`<span class="cc-cap-file">${f(M[0])}</span>`:""}
|
|
250
|
-
</div>
|
|
251
|
-
<span class="cc-stab cc-stab-${w}" onclick="event.stopPropagation();cycleStability('${f(a.id)}','${w}')" title="Click to change stability">${w}</span>
|
|
252
|
-
</div>`}).join("")}
|
|
253
|
-
${t.caps.length===0?'<div class="empty">No capabilities \u2014 run <code>infernoflow init</code></div>':""}
|
|
254
|
-
</div>
|
|
255
|
-
</div>
|
|
256
|
-
|
|
257
|
-
<!-- Middle: quick command buttons -->
|
|
258
|
-
<div class="cc-commands">
|
|
259
|
-
<h3>Quick Commands</h3>
|
|
260
|
-
<div class="cc-btn-grid">
|
|
261
|
-
<button class="cc-btn cc-btn-blue" onclick="runCmd('scan')">\u{1F52C} scan</button>
|
|
262
|
-
<button class="cc-btn cc-btn-blue" onclick="runCmd('graph')">\u{1F578}\uFE0F graph</button>
|
|
263
|
-
<button class="cc-btn cc-btn-blue" onclick="runCmd('stability')">\u{1F4A7} stability</button>
|
|
264
|
-
<button class="cc-btn cc-btn-blue" onclick="runCmd('check')">\u2705 check</button>
|
|
265
|
-
<button class="cc-btn cc-btn-orange" onclick="runCmd('doctor')">\u{1FA7A} doctor</button>
|
|
266
|
-
<button class="cc-btn cc-btn-orange" onclick="runCmd('coverage')">\u{1F4CA} coverage</button>
|
|
267
|
-
<button class="cc-btn cc-btn-green" onclick="runCmd('status')">\u{1F4E1} status</button>
|
|
268
|
-
<button class="cc-btn cc-btn-green" onclick="runCmd('health')">\u2764\uFE0F health</button>
|
|
269
|
-
</div>
|
|
270
|
-
|
|
271
|
-
<h3 style="margin-top:18px">Capability Actions</h3>
|
|
272
|
-
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:10px">
|
|
273
|
-
<input id="cc-capinput" class="cc-input" placeholder="capability-id" style="flex:1;min-width:120px"/>
|
|
274
|
-
<button class="cc-btn cc-btn-blue" onclick="runCapCmd('why')">\u{1F50D} why</button>
|
|
275
|
-
<button class="cc-btn cc-btn-blue" onclick="runCapCmd('impact')">\u{1F4A5} impact</button>
|
|
276
|
-
<button class="cc-btn cc-btn-red" onclick="runCapCmd('freeze')">\u{1F9CA} freeze</button>
|
|
277
|
-
<button class="cc-btn cc-btn-green" onclick="runCapCmd('thaw')">\u{1F30A} thaw</button>
|
|
278
|
-
</div>
|
|
279
|
-
|
|
280
|
-
<!-- Terminal output -->
|
|
281
|
-
<h3>Terminal Output</h3>
|
|
282
|
-
<div class="cc-terminal" id="cc-terminal">
|
|
283
|
-
<span class="cc-prompt">Ready. Click a command or capability to begin.</span>
|
|
284
|
-
</div>
|
|
285
|
-
</div>
|
|
286
|
-
|
|
287
|
-
<!-- Right: cap detail -->
|
|
288
|
-
<div class="cc-detail" id="cc-detail">
|
|
289
|
-
<h3>Capability Detail</h3>
|
|
290
|
-
<div class="empty" id="cc-detail-inner">Click a capability to see its impact analysis.</div>
|
|
291
|
-
</div>
|
|
292
|
-
</div>
|
|
293
|
-
</section>
|
|
294
|
-
|
|
295
|
-
</main>
|
|
296
|
-
<footer>infernoflow dashboard \xB7 auto-refreshes when inferno/ changes \xB7 <a href="/" style="color:var(--muted)">refresh now</a></footer>
|
|
297
|
-
<style>
|
|
298
|
-
/* Command Center styles */
|
|
299
|
-
.cc-layout { display:grid; grid-template-columns:220px 1fr 280px; gap:16px; margin-top:12px; min-height:420px; }
|
|
300
|
-
.cc-caps { background:var(--card); border-radius:8px; padding:12px; overflow:hidden; }
|
|
301
|
-
.cc-caps h3, .cc-commands h3, .cc-detail h3 { font-size:12px; text-transform:uppercase; letter-spacing:.06em; color:var(--muted); margin:0 0 10px 0; }
|
|
302
|
-
.cc-cap-list { display:flex; flex-direction:column; gap:4px; max-height:360px; overflow-y:auto; }
|
|
303
|
-
.cc-cap-row { display:flex; align-items:center; gap:8px; padding:7px 8px; border-radius:6px; cursor:pointer; transition:background .15s; }
|
|
304
|
-
.cc-cap-row:hover { background:rgba(255,255,255,.06); }
|
|
305
|
-
.cc-icon { font-size:14px; flex-shrink:0; }
|
|
306
|
-
.cc-cap-info { flex:1; min-width:0; }
|
|
307
|
-
.cc-cap-id { display:block; font-size:12px; font-weight:600; color:var(--fg); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
308
|
-
.cc-cap-file { display:block; font-size:10px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
309
|
-
.cc-stab { font-size:10px; padding:2px 6px; border-radius:10px; cursor:pointer; white-space:nowrap; flex-shrink:0; }
|
|
310
|
-
.cc-stab-frozen { background:#7f1d1d; color:#fca5a5; }
|
|
311
|
-
.cc-stab-stable { background:#78350f; color:#fcd34d; }
|
|
312
|
-
.cc-stab-experimental { background:#14532d; color:#86efac; }
|
|
313
|
-
.cc-commands { background:var(--card); border-radius:8px; padding:12px; display:flex; flex-direction:column; }
|
|
314
|
-
.cc-btn-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
|
|
315
|
-
.cc-btn { border:none; border-radius:6px; padding:8px 12px; font-size:12px; font-weight:600; cursor:pointer; transition:opacity .15s; }
|
|
316
|
-
.cc-btn:hover { opacity:.85; }
|
|
317
|
-
.cc-btn-blue { background:#1d4ed8; color:#fff; }
|
|
318
|
-
.cc-btn-orange { background:#c2410c; color:#fff; }
|
|
319
|
-
.cc-btn-green { background:#15803d; color:#fff; }
|
|
320
|
-
.cc-btn-red { background:#b91c1c; color:#fff; }
|
|
321
|
-
.cc-input { background:#1e2030; border:1px solid #374151; border-radius:6px; color:var(--fg); padding:7px 10px; font-size:12px; outline:none; }
|
|
322
|
-
.cc-input:focus { border-color:#3b82f6; }
|
|
323
|
-
.cc-terminal { background:#0d0f1a; border:1px solid #1e2030; border-radius:6px; padding:12px; font-family:monospace; font-size:11px; line-height:1.6; color:#a3e635; flex:1; min-height:180px; max-height:240px; overflow-y:auto; white-space:pre-wrap; word-break:break-all; margin-top:4px; }
|
|
324
|
-
.cc-prompt { color:var(--muted); }
|
|
325
|
-
.cc-detail { background:var(--card); border-radius:8px; padding:12px; overflow-y:auto; }
|
|
326
|
-
.cc-detail-section { margin-bottom:14px; }
|
|
327
|
-
.cc-detail-section h4 { font-size:11px; color:var(--muted); margin:0 0 6px 0; text-transform:uppercase; letter-spacing:.05em; }
|
|
328
|
-
.cc-detail-row { display:flex; justify-content:space-between; font-size:12px; padding:3px 0; border-bottom:1px solid #1e2030; }
|
|
329
|
-
.cc-detail-dep { font-size:12px; padding:3px 0; }
|
|
330
|
-
.cc-risk-low { color:#22c55e; font-weight:700; }
|
|
331
|
-
.cc-risk-medium { color:#f59e0b; font-weight:700; }
|
|
332
|
-
.cc-risk-high { color:#ef4444; font-weight:700; }
|
|
333
|
-
.cc-risk-critical { color:#ef4444; font-weight:700; text-transform:uppercase; }
|
|
334
|
-
</style>
|
|
335
|
-
<script>
|
|
336
|
-
// SSE live reload
|
|
337
|
-
const es = new EventSource('/events');
|
|
338
|
-
es.onmessage = () => window.location.reload();
|
|
339
|
-
es.onerror = () => {};
|
|
340
|
-
|
|
341
|
-
// \u2500\u2500 Command runner \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
|
|
342
|
-
const terminal = document.getElementById('cc-terminal');
|
|
343
|
-
|
|
344
|
-
async function runCmd(command, args = []) {
|
|
345
|
-
terminal.textContent = '$ infernoflow ' + command + (args.length ? ' ' + args.join(' ') : '') + '\\n';
|
|
346
|
-
try {
|
|
347
|
-
const res = await fetch('/api/run', {
|
|
348
|
-
method: 'POST',
|
|
349
|
-
headers: { 'Content-Type': 'application/json' },
|
|
350
|
-
body: JSON.stringify({ command, args }),
|
|
351
|
-
});
|
|
352
|
-
const reader = res.body.getReader();
|
|
353
|
-
const dec = new TextDecoder();
|
|
354
|
-
while (true) {
|
|
355
|
-
const { done, value } = await reader.read();
|
|
356
|
-
if (done) break;
|
|
357
|
-
terminal.textContent += dec.decode(value);
|
|
358
|
-
terminal.scrollTop = terminal.scrollHeight;
|
|
359
|
-
}
|
|
360
|
-
} catch (e) {
|
|
361
|
-
terminal.textContent += '\\nError: ' + e.message;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function runCapCmd(command) {
|
|
366
|
-
const capId = document.getElementById('cc-capinput').value.trim();
|
|
367
|
-
if (!capId) { terminal.textContent = 'Enter a capability ID first.'; return; }
|
|
368
|
-
runCmd(command, [capId]);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// \u2500\u2500 Capability detail panel \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
|
|
372
|
-
async function capDetail(capId) {
|
|
373
|
-
document.getElementById('cc-capinput').value = capId;
|
|
374
|
-
const detail = document.getElementById('cc-detail-inner');
|
|
375
|
-
detail.innerHTML = '<div class="empty">Loading\u2026</div>';
|
|
376
|
-
|
|
377
|
-
try {
|
|
378
|
-
const [why, impact] = await Promise.all([
|
|
379
|
-
fetch('/api/cap/' + encodeURIComponent(capId) + '/why').then(r => r.json()),
|
|
380
|
-
fetch('/api/cap/' + encodeURIComponent(capId) + '/impact').then(r => r.json()),
|
|
381
|
-
]);
|
|
382
|
-
|
|
383
|
-
const w = Array.isArray(why) ? why[0] : why;
|
|
384
|
-
const im = impact?.capId ? impact : null;
|
|
385
|
-
|
|
386
|
-
let html = '';
|
|
387
|
-
|
|
388
|
-
if (w) {
|
|
389
|
-
html += '<div class="cc-detail-section">';
|
|
390
|
-
html += '<h4>\u{1F4CD} ' + (w.name || w.capId) + '</h4>';
|
|
391
|
-
html += '<div class="cc-detail-row"><span>Stability</span><span class="cc-stab cc-stab-' + w.stability + '">' + w.stability + '</span></div>';
|
|
392
|
-
if (w.sourceFiles?.length) html += '<div class="cc-detail-row"><span>Files</span><span style="color:#7dd3fc">' + w.sourceFiles.join(', ') + '</span></div>';
|
|
393
|
-
if (w.services?.length) html += '<div class="cc-detail-row"><span>Uses</span><span style="color:#a78bfa">' + w.services.join(', ') + '</span></div>';
|
|
394
|
-
if (w.throws?.length) html += '<div class="cc-detail-row"><span>Throws</span><span style="color:#f97316">' + w.throws.join(', ') + '</span></div>';
|
|
395
|
-
html += '</div>';
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (im) {
|
|
399
|
-
const riskCls = 'cc-risk-' + im.risk;
|
|
400
|
-
html += '<div class="cc-detail-section">';
|
|
401
|
-
html += '<h4>\u{1F4A5} Impact</h4>';
|
|
402
|
-
html += '<div class="cc-detail-row"><span>Risk</span><span class="' + riskCls + '">' + im.risk.toUpperCase() + '</span></div>';
|
|
403
|
-
html += '<div class="cc-detail-row"><span>Direct deps</span><span>' + im.summary.directCount + '</span></div>';
|
|
404
|
-
html += '<div class="cc-detail-row"><span>Transitive</span><span>' + im.summary.transitiveCount + '</span></div>';
|
|
405
|
-
if (im.direct?.length) {
|
|
406
|
-
html += '<h4 style="margin-top:10px">Direct dependents</h4>';
|
|
407
|
-
im.direct.forEach(d => { html += '<div class="cc-detail-dep">\u2192 <code>' + d + '</code></div>'; });
|
|
408
|
-
}
|
|
409
|
-
if (im.affectedScenarios?.length) {
|
|
410
|
-
html += '<h4 style="margin-top:10px">Scenarios at risk</h4>';
|
|
411
|
-
im.affectedScenarios.forEach(s => { html += '<div class="cc-detail-dep">\u26A0\uFE0F ' + s + '</div>'; });
|
|
412
|
-
}
|
|
413
|
-
html += '</div>';
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (!html) html = '<div class="empty">No data found for ' + capId + ' \u2014 run infernoflow scan first.</div>';
|
|
417
|
-
detail.innerHTML = html;
|
|
418
|
-
} catch (e) {
|
|
419
|
-
detail.innerHTML = '<div class="empty">Error: ' + e.message + '</div>';
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// \u2500\u2500 Stability cycle \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
|
|
424
|
-
async function cycleStability(capId, current) {
|
|
425
|
-
const next = current === 'experimental' ? 'stable' : current === 'stable' ? 'frozen' : 'experimental';
|
|
426
|
-
if (!confirm('Change ' + capId + ' from ' + current + ' \u2192 ' + next + '?')) return;
|
|
427
|
-
await fetch('/api/freeze', {
|
|
428
|
-
method: 'POST',
|
|
429
|
-
headers: { 'Content-Type': 'application/json' },
|
|
430
|
-
body: JSON.stringify({ capId, level: next }),
|
|
431
|
-
});
|
|
432
|
-
window.location.reload();
|
|
433
|
-
}
|
|
434
|
-
</script>
|
|
435
|
-
</body>
|
|
436
|
-
</html>`}function f(t){return String(t||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function gt(t,n){const l=g.dirname(t),u=g.basename(l),r=new Set;let d=null;try{m.watch(t,{recursive:!0},()=>{clearTimeout(d),d=setTimeout(()=>{for(const s of r)try{s.write(`data: reload
|
|
437
|
-
|
|
438
|
-
`)}catch{}},500)})}catch{}const h=V.createServer((s,o)=>{if(s.url==="/events"){o.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"}),r.add(o),s.on("close",()=>r.delete(o));return}if(s.url==="/api/data"){const c=E(t);o.writeHead(200,{"Content-Type":"application/json"}),o.end(JSON.stringify(c,null,2));return}if(s.url==="/api/run"&&s.method==="POST"){let c="";s.on("data",e=>{c+=e}),s.on("end",()=>{try{const{command:e="",args:i=[]}=JSON.parse(c),p=g.join(T,"../../bin/infernoflow.mjs");o.writeHead(200,{"Content-Type":"text/plain; charset=utf-8","Transfer-Encoding":"chunked","Cache-Control":"no-cache"});const v=z(process.execPath,[p,e,...i],{cwd:l,env:{...process.env,FORCE_COLOR:"0"}});v.stdout.on("data",x=>o.write(x)),v.stderr.on("data",x=>o.write(x)),v.on("close",x=>{o.write(`
|
|
439
|
-
[exit ${x}]
|
|
440
|
-
`),o.end()}),v.on("error",x=>{o.write(`
|
|
441
|
-
Error spawning command: ${x.message}
|
|
442
|
-
`),o.end()})}catch(e){o.writeHead(400,{"Content-Type":"text/plain"}),o.end("Bad request: "+e.message)}});return}const b=s.url?.match(/^\/api\/cap\/([^/]+)\/why$/);if(b){const c=decodeURIComponent(b[1]),e=g.join(T,"../../bin/infernoflow.mjs");let i="";const p=z(process.execPath,[e,"why",c,"--json"],{cwd:l,env:{...process.env,FORCE_COLOR:"0"}});p.stdout.on("data",v=>{i+=v}),p.stderr.on("data",()=>{}),p.on("close",()=>{try{o.writeHead(200,{"Content-Type":"application/json"}),o.end(i.trim()||"[]")}catch{}});return}const y=s.url?.match(/^\/api\/cap\/([^/]+)\/impact$/);if(y){const c=decodeURIComponent(y[1]),e=g.join(T,"../../bin/infernoflow.mjs");let i="";const p=z(process.execPath,[e,"impact",c,"--json"],{cwd:l,env:{...process.env,FORCE_COLOR:"0"}});p.stdout.on("data",v=>{i+=v}),p.stderr.on("data",()=>{}),p.on("close",()=>{try{o.writeHead(200,{"Content-Type":"application/json"}),o.end(i.trim()||"{}")}catch{}});return}if(s.url==="/api/freeze"&&s.method==="POST"){let c="";s.on("data",e=>{c+=e}),s.on("end",()=>{try{const{capId:e,level:i}=JSON.parse(c),p=g.join(T,"../../bin/infernoflow.mjs"),v=i==="experimental"?"thaw":"freeze",x=i==="stable"?[e,"--stable"]:[e];z(process.execPath,[p,v,...x],{cwd:l}).on("close",()=>{o.writeHead(200,{"Content-Type":"application/json"}),o.end(JSON.stringify({ok:!0}))})}catch(e){o.writeHead(400,{"Content-Type":"application/json"}),o.end(JSON.stringify({ok:!1,error:e.message}))}});return}try{const c=E(t),e=ft(c,u);o.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),o.end(e)}catch(c){o.writeHead(500,{"Content-Type":"text/plain"}),o.end(`Error: ${c.message}`)}});return h.listen(n,"127.0.0.1",()=>{}),h}function bt(t){const n=K.platform();try{n==="darwin"?S(`open "${t}"`,{stdio:"ignore"}):n==="win32"?S(`start "" "${t}"`,{stdio:"ignore",shell:!0}):S(`xdg-open "${t}"`,{stdio:"ignore"})}catch{}}async function $t(t){const n=t.slice(1),l=n.includes("--no-open"),u=n.indexOf("--port"),r=u!==-1?parseInt(n[u+1],10):7337,d=process.cwd(),h=g.join(d,"inferno");X("infernoflow dashboard"),m.existsSync(h)||(q("inferno/ not found \u2014 run: infernoflow init"),process.exit(1));const s=`http://localhost:${r}`;gt(h,r),Z(`Dashboard running \u2192 ${tt(s)}`),P("Auto-refreshes when inferno/ files change"),P("Press Ctrl+C to stop"),console.log(),l||bt(s),await new Promise(()=>{})}export{$t as dashboardCommand};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import*as $ from"node:https";import*as k from"node:http";import*as O from"node:crypto";import{execSync as R}from"node:child_process";import{bold as p,cyan as m,gray as a,green as _,red as v,yellow as x}from"../ui/output.mjs";import{readCredentials as L,writeCredentials as A,deleteCredentials as M,isLoggedIn as G}from"../cloud/credentials.mjs";import{SUPABASE_URL as N,getUser as B}from"../cloud/supabase.mjs";const T="Ov23liYuUKwDRTzrywsa",J=[47655,47656,47657,47658,47659],z=300*1e3;function S(e,s,d){return new Promise((t,l)=>{const o=new URLSearchParams(d).toString(),n={hostname:e,port:443,path:s,method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",Accept:"application/json","User-Agent":"infernoflow-cli","Content-Length":Buffer.byteLength(o)}},i=$.request(n,r=>{let c="";r.on("data",u=>c+=u),r.on("end",()=>{try{t(JSON.parse(c))}catch{t(c)}})});i.on("error",l),i.setTimeout(15e3,()=>i.destroy(new Error("timeout"))),i.write(o),i.end()})}function W(e,s,d){return new Promise((t,l)=>{const o={hostname:e,port:443,path:s,method:"GET",headers:{Accept:"application/json","User-Agent":"infernoflow-cli",Authorization:`Bearer ${d}`}},n=$.request(o,i=>{let r="";i.on("data",c=>r+=c),i.on("end",()=>{try{t(JSON.parse(r))}catch{t(r)}})});n.on("error",l),n.setTimeout(1e4,()=>n.destroy(new Error("timeout"))),n.end()})}function C(e){return new Promise(s=>setTimeout(s,e))}function E(e){try{const s=process.platform==="win32"?`start "" "${e}"`:process.platform==="darwin"?`open "${e}"`:`xdg-open "${e}"`;R(s,{stdio:"ignore"})}catch{}}function F(e){return new Promise((s,d)=>{let t=0;const l=()=>{if(t>=e.length)return d(new Error("no available local port for callback"));const o=e[t++],n=k.createServer();n.on("error",()=>l()),n.listen(o,"127.0.0.1",()=>{n.close(()=>s(o))})};l()})}const Y=`<!DOCTYPE html>
|
|
2
|
-
<html><head><meta charset="utf-8"><title>infernoflow login</title>
|
|
3
|
-
<style>
|
|
4
|
-
body { font-family: ui-sans-serif, system-ui, -apple-system, sans-serif; max-width: 480px; margin: 60px auto; padding: 0 24px; color: #0f1117; }
|
|
5
|
-
.ok { color: #16a34a; }
|
|
6
|
-
.err { color: #dc2626; }
|
|
7
|
-
code { background: #f4f4f5; padding: 2px 6px; border-radius: 4px; font-size: 13px; }
|
|
8
|
-
</style></head>
|
|
9
|
-
<body>
|
|
10
|
-
<h2>\u{1F525} infernoflow</h2>
|
|
11
|
-
<p id="status">Completing login\u2026</p>
|
|
12
|
-
<script>
|
|
13
|
-
(async () => {
|
|
14
|
-
const status = document.getElementById('status');
|
|
15
|
-
const hash = window.location.hash.substring(1);
|
|
16
|
-
if (!hash) {
|
|
17
|
-
const params = new URLSearchParams(window.location.search);
|
|
18
|
-
const errMsg = params.get('error_description') || params.get('error') || 'No tokens received.';
|
|
19
|
-
status.innerHTML = '<span class="err">\u2718 Login failed: ' + errMsg + '</span>';
|
|
20
|
-
try { await fetch('/error?msg=' + encodeURIComponent(errMsg)); } catch (_) {}
|
|
21
|
-
return;
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
const r = await fetch('/token', { method: 'POST', body: hash, headers: { 'Content-Type': 'application/x-www-form-urlencoded' } });
|
|
25
|
-
if (r.ok) {
|
|
26
|
-
status.innerHTML = '<span class="ok">\u2714 Logged in.</span> You can close this tab and return to your terminal.';
|
|
27
|
-
} else {
|
|
28
|
-
status.innerHTML = '<span class="err">\u2718 Forwarding failed (HTTP ' + r.status + ').</span>';
|
|
29
|
-
}
|
|
30
|
-
} catch (e) {
|
|
31
|
-
status.innerHTML = '<span class="err">\u2718 Could not reach the local CLI: ' + e.message + '</span>';
|
|
32
|
-
}
|
|
33
|
-
})();
|
|
34
|
-
</script>
|
|
35
|
-
</body></html>`;async function K(){const e=await F(J),s=`http://localhost:${e}/callback`,d=O.randomBytes(16).toString("hex"),t=new URL(`${N}/auth/v1/authorize`);return t.searchParams.set("provider","github"),t.searchParams.set("redirect_to",s),t.searchParams.set("scopes","read:user user:email"),new Promise((l,o)=>{let n=!1,i;const r=u=>{if(!n){n=!0,clearTimeout(c);try{i?.close()}catch{}u()}},c=setTimeout(()=>{r(()=>o(new Error("login timed out \u2014 close the browser tab and try again")))},z);i=k.createServer((u,g)=>{const w=new URL(u.url,`http://localhost:${e}`);if(u.method==="GET"&&w.pathname==="/callback"){g.writeHead(200,{"Content-Type":"text/html; charset=utf-8"}),g.end(Y);return}if(u.method==="POST"&&w.pathname==="/token"){let h="";u.on("data",f=>h+=f),u.on("end",()=>{const f=new URLSearchParams(h),y=f.get("access_token"),I=f.get("refresh_token"),b=parseInt(f.get("expires_in")||"0",10),D=f.get("provider_token"),H=f.get("provider_refresh_token");if(!y){g.writeHead(400,{"Content-Type":"application/json"}),g.end(JSON.stringify({error:"no access_token in fragment"}));return}g.writeHead(204),g.end(),r(()=>l({access_token:y,refresh_token:I,expires_at:b?new Date(Date.now()+b*1e3).toISOString():null,provider_token:D,provider_refresh_token:H}))});return}if(u.method==="GET"&&w.pathname==="/error"){const h=w.searchParams.get("msg")||"unknown error";g.writeHead(204),g.end(),r(()=>o(new Error(h)));return}g.writeHead(404),g.end()}),i.on("error",u=>r(()=>o(u))),i.listen(e,"127.0.0.1",()=>{console.log(),console.log(` ${p("\u{1F525} infernoflow login")}`),console.log(),console.log(` ${a("Opening your browser to sign in with GitHub via Supabase\u2026")}`),console.log(),console.log(` ${p("If the browser doesn't open, paste this URL:")}`),console.log(` ${m(t.toString())}`),console.log(),console.log(` ${a(`Listening for the callback on http://localhost:${e}/callback`)}`),console.log(` ${a("(this prompt will close automatically when you finish)")}`),console.log(),E(t.toString())})})}async function j(){console.log(),console.log(` ${p("\u{1F525} infernoflow login")} ${a("(device-flow / identity-only)")}`),console.log(),console.log(` ${x("\u26A0")} ${a("Device flow gives us your GitHub identity but no Supabase JWT.")}`),console.log(` ${a("Cloud writes will fall back to anon-key dev mode. Run without --device-flow")}`),console.log(` ${a("for the proper authenticated flow.")}`),console.log();let e;try{e=await S("github.com","/login/device/code",{client_id:T,scope:"read:user user:email"})}catch(r){throw new Error(`could not reach GitHub: ${r.message}`)}if(!e.device_code)throw new Error(`GitHub error: ${JSON.stringify(e)}`);const{device_code:s,user_code:d,verification_uri:t,expires_in:l,interval:o}=e,n=(o||5)*1e3;console.log(` ${p("Open:")} ${m(t)}`),console.log(` ${p("Code:")} ${p(m(d))}`),console.log(),E(t),console.log(` ${a("Waiting for you to authorize\u2026")} ${a("(Ctrl+C to cancel)")}`),console.log();const i=Date.now()+(l||900)*1e3;for(;Date.now()<i;){await C(n);let r;try{r=await S("github.com","/login/oauth/access_token",{client_id:T,device_code:s,grant_type:"urn:ietf:params:oauth:grant-type:device_code"})}catch{continue}if(r.error!=="authorization_pending"){if(r.error==="slow_down"){await C(5e3);continue}if(r.error==="expired_token")throw new Error("code expired \u2014 run infernoflow login again");if(r.error==="access_denied")throw new Error("access denied");if(r.access_token){const c=await W("api.github.com","/user",r.access_token);return{mode:"device-flow",github_access_token:r.access_token,user:{provider:"github",login:c?.login||null,name:c?.name||null,email:c?.email||null,id:c?.id||null,avatar_url:c?.avatar_url||null}}}}}throw new Error("login timed out")}async function Q(e){if(G()){const o=L(),n=o?.user?.login||o?.user?.name||o?.user?.email||"unknown";console.log(),console.log(` ${_("\u2714")} Already logged in as ${p(n)}`),console.log(` Run ${m("infernoflow logout")} to sign out.`),console.log();return}const s=e.includes("--browser"),d=!s;let t;try{if(d){const o=await j();t={mode:"device-flow",github_access_token:o.github_access_token,user:o.user,logged_in_at:new Date().toISOString()}}else{const o=await K(),n=await B(o.access_token).catch(()=>null);t={mode:"supabase",access_token:o.access_token,refresh_token:o.refresh_token,expires_at:o.expires_at,provider_token:o.provider_token,provider_refresh_token:o.provider_refresh_token,user:n?{provider:"github",id:n.id||null,email:n.email||null,login:n.user_metadata?.user_name||n.user_metadata?.preferred_username||n.identities?.[0]?.identity_data?.user_name||null,name:n.user_metadata?.full_name||n.user_metadata?.name||null,avatar_url:n.user_metadata?.avatar_url||null}:{provider:"github"},logged_in_at:new Date().toISOString()}}}catch(o){console.log(),console.log(` ${v("\u2718")} Login failed: ${o.message}`),console.log(s?` ${a("If --browser fails, fall back to the default flow:")} ${m("infernoflow login")}`:` ${a("To try the experimental authenticated browser flow:")} ${m("infernoflow login --browser")}`),console.log(),process.exit(1)}A(t);const l=t.user?.login||t.user?.name||t.user?.email||"unknown";console.log(),console.log(` ${_("\u2714")} Logged in as ${p(l)}`),console.log(),t.mode==="supabase"?console.log(` ${a("Cloud sync is now authenticated. Every")} ${m("infernoflow log")} ${a("writes under your auth.uid().")}`):console.log(` ${a("Identity-only login (device flow). Cloud writes still use the anon-key dev mode.")}`),console.log()}function P(){const e=M();console.log(),console.log(e?` ${_("\u2714")} Logged out. Local credentials removed.`:` ${a("Already logged out.")}`),console.log()}function U(){const e=L();if(console.log(),!e?.access_token&&!e?.github_access_token){console.log(` ${a("Not logged in.")} Run ${m("infernoflow login")}`),console.log();return}const s=e.user?.login||e.user?.name||e.user?.email||"unknown",d=e.user?.email||a("(no email)"),t=e.logged_in_at?new Date(e.logged_in_at).toLocaleDateString():"unknown",l=e.mode==="supabase"?_("\u2714 authenticated (Supabase JWT)"):e.mode==="device-flow"?x("\u26A0 identity-only (device flow)"):a("legacy");if(console.log(` ${p("\u{1F525} infernoflow")} \u2014 logged in as:`),console.log(),console.log(` User: ${p(s)}`),console.log(` Email: ${d}`),console.log(` Since: ${a(t)}`),console.log(` Mode: ${l}`),e.expires_at){const o=new Date(e.expires_at),n=Date.now()>o.getTime();console.log(` Expires: ${a(o.toLocaleString())}${n?" "+v("(expired \u2014 run login again)"):""}`)}console.log()}async function ee(e){const s=e[1];return s==="logout"?P():s==="whoami"?U():Q(e)}async function oe(){return P()}async function ne(){return U()}export{ee as loginCommand,oe as logoutCommand,ne as whoamiCommand};
|
package/scripts/postinstall.js
DELETED