infernoflow 0.43.5 → 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.
- 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 +3 -5
- 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,20 +1,20 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`),
|
|
3
|
-
${
|
|
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
|
-
`,
|
|
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
|
-
`+
|
|
8
|
-
`)),process.exit(1)),
|
|
9
|
-
`+
|
|
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(
|
|
12
|
-
`)),
|
|
13
|
-
`));return}const
|
|
14
|
-
`));return}const
|
|
15
|
-
`+
|
|
16
|
-
`),console.log(
|
|
17
|
-
`));return}const n=
|
|
18
|
-
`)),process.exit(1)),
|
|
19
|
-
`)),process.exit(1));const
|
|
20
|
-
`)}}
|
|
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.
|
|
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};
|
|
@@ -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};
|