infernoflow 0.44.20 → 0.45.0
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 +5 -2
- package/dist/bin/infernoflow.mjs +4 -4
- package/dist/lib/amp/io.mjs +20 -20
- package/dist/lib/cleanTree.mjs +7 -7
- package/dist/lib/commands/ai.mjs +6 -2
- package/dist/lib/commands/ask.mjs +2 -2
- package/dist/lib/commands/bookmark.mjs +12 -12
- package/dist/lib/commands/doctor.mjs +3 -2
- package/dist/lib/commands/log.mjs +13 -12
- package/dist/lib/commands/mcp.mjs +1 -0
- package/dist/lib/commands/recap.mjs +4 -4
- package/dist/lib/commands/setup.mjs +8 -10
- package/dist/lib/commands/status.mjs +4 -4
- package/dist/lib/commands/uninstall.mjs +11 -11
- package/dist/lib/cursorHooksInstall.mjs +1 -1
- package/dist/lib/mcpRegistration.mjs +6 -0
- package/dist/lib/security/redact.mjs +1 -0
- package/dist/lib/upgradeCheck.mjs +6 -4
- package/dist/templates/cursor/inferno-mcp-server.mjs +9 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -271,9 +271,12 @@ Local-first by design:
|
|
|
271
271
|
- 🚫 **No `postinstall` script.** `npm install -g infernoflow` runs no code — it only copies files.
|
|
272
272
|
- 🚫 **No network calls in any default command path** (with telemetry off, which is the default).
|
|
273
273
|
- 🚫 **No auto-updates of the package, no background processes, no cloud sync.**
|
|
274
|
-
-
|
|
274
|
+
- ✅ **MCP is registered per project.** Claude Code gets the project's own `.mcp.json` (gitignored — it holds this machine's paths); Cursor and VS Code get `.cursor/mcp.json` / `.vscode/mcp.json`. All of them run the server from the installed package (`infernoflow mcp`), not a copy inside the repo.
|
|
275
|
+
- ⚠️ **Writes outside the project:** Claude Desktop has no per-project config, so `setup` adds one entry per project there (`infernoflow-<repo>`). Pre-0.45 versions left a single `infernoflow` entry in `~/.claude.json` / the Desktop config that pinned every project to one repo; it is removed automatically (backup in `~/.infernoflow/backups/`). Personal settings and API keys live in `~/.infernoflow/`.
|
|
275
276
|
- ✅ **Auto-injected content is wrapped in markers** (`<!-- infernoflow:start -->` / `<!-- infernoflow:end -->`) — your manual edits outside the block are never touched.
|
|
276
|
-
-
|
|
277
|
+
- ✅ **Secrets are redacted before anything is written** — GitHub/npm/OpenAI/Anthropic/AWS/Google/Slack/Stripe tokens, JWTs, private keys, URL credentials and `password=`-style values become `[REDACTED:<kind>]`. Memory files are still plain text committed with your repo, so treat them like code: review `.ai-memory/` diffs.
|
|
278
|
+
- ✅ **Bookmark transcript snapshots stay on your machine** (`.ai-memory/details.local.jsonl`, gitignored). Only context you write explicitly (`--note`, `detail`) is shared with the team.
|
|
279
|
+
- ✅ **API keys never live in the project** — environment variables are used as-is; pasted keys go to `~/.infernoflow/ai-credentials.json` (owner-only).
|
|
277
280
|
- ✅ **No shell is used to run commands** from the MCP server or the prompt hooks (since 0.44.20), and MCP tool arguments are validated against their schema.
|
|
278
281
|
|
|
279
282
|
The optional `infernoflow ai setup` command wires an AI provider (Anthropic / OpenAI / Google / Ollama) for a few enrichment commands — same trust model as using that provider directly. Off by default.
|
package/dist/bin/infernoflow.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
(function(){if(process.platform!=="win32"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const n={"\u2500":"-","\u2501":"-","\u2550":"=","\u2502":"|","\u2503":"|","\u2551":"|","\u250C":"+","\u2510":"+","\u2514":"+","\u2518":"+","\u251C":"+","\u2524":"+","\u252C":"+","\u2534":"+","\u253C":"+","\xB7":"*","\u2192":"->","\u2190":"<-","\u2714":"[OK]","\u2713":"[OK]","\u2718":"[X]","\u2717":"[X]","\u26A0":"[!]",\u2139:"[i]"},r=new RegExp(Object.keys(n).join("|"),"g"),l=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,f=c=>c.replace(r,p=>n[p]).replace(l,"");function u(c){const p=c.write.bind(c);c.write=function(a,...k){return typeof a=="string"?a=f(a):Buffer.isBuffer(a)&&(a=Buffer.from(f(a.toString("utf8")),"utf8")),p(a,...k)}}u(process.stdout),u(process.stderr)})();import{readFileSync as
|
|
3
|
-
`)}const M={Memory:["log","ask","switch","recap","status","refresh","forget","prune","bookmark"],Setup:["init","setup","doctor","context"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function O(){return Object.entries(M).map(([o,n])=>` ${i(o+":")}
|
|
2
|
+
(function(){if(process.platform!=="win32"||process.argv[2]==="mcp"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const n={"\u2500":"-","\u2501":"-","\u2550":"=","\u2502":"|","\u2503":"|","\u2551":"|","\u250C":"+","\u2510":"+","\u2514":"+","\u2518":"+","\u251C":"+","\u2524":"+","\u252C":"+","\u2534":"+","\u253C":"+","\xB7":"*","\u2192":"->","\u2190":"<-","\u2714":"[OK]","\u2713":"[OK]","\u2718":"[X]","\u2717":"[X]","\u26A0":"[!]",\u2139:"[i]"},r=new RegExp(Object.keys(n).join("|"),"g"),l=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,f=c=>c.replace(r,p=>n[p]).replace(l,"");function u(c){const p=c.write.bind(c);c.write=function(a,...k){return typeof a=="string"?a=f(a):Buffer.isBuffer(a)&&(a=Buffer.from(f(a.toString("utf8")),"utf8")),p(a,...k)}}u(process.stdout),u(process.stderr)})();import{readFileSync as C}from"node:fs";import{dirname as $,join as y}from"node:path";import{fileURLToPath as v}from"node:url";import{bold as i,gray as e,cyan as t,red as g}from"../lib/ui/output.mjs";const b=$(v(import.meta.url));function A(o){for(const n of[y(o,"..","..","package.json"),y(o,"..","package.json")])try{return JSON.parse(C(n,"utf8"))}catch{}return{version:"0.0.0-source"}}const I=A(b),m=I.version||"0.0.0",h={log:"Append to session memory (decisions, gotchas, failed attempts)",ask:"Query memory by keyword (gotchas surface first)",switch:"Generate a handoff doc for the next AI agent / session",recap:"End-of-session summary + health score + unlogged-change surfacing",status:"Quick health check \u2014 entries, gotchas, decisions, last activity",refresh:"Rebuild CLAUDE.md / .cursorrules / copilot-instructions.md from memory",forget:"Delete a memory entry by id or unique prefix (--last for the newest)",prune:"Archive stale notes/attempts (gotchas/decisions never touched) \u2014 see --apply",bookmark:"Drop / list / recall named session resume points (bookmarks with context)",init:"Scaffold .ai-memory/ and wire the current IDE in one command",setup:"Re-run wiring (idempotent) \u2014 detects IDE, installs MCP + hooks",doctor:"Diagnose your setup \u2014 Node, git, contract, AI provider, MCP, hooks",context:"Generate AI-ready context for new sessions","install-cursor-hooks":"Install Cursor hooks (afterAgentResponse + stop)","install-vscode-copilot-hooks":"Install VS Code + Copilot agent hooks (Preview)","generate-skills":"Generate Cursor rules + skill files from your developer profile",ai:"Manage AI providers \u2014 setup, status, test, clear",telemetry:"Opt-in anonymous telemetry (on | off | status)",uninstall:"Remove infernoflow from a project (--dry-run to preview)",check:"Validate contract, capabilities, scenarios, changelog",sync:"Cross-machine sync for personal memory \u2014 status/set/clear/migrate",amp:"AI Memory Protocol \u2014 status, migrate, validate (run: infernoflow amp)",mcp:"Run the MCP server (used by AI tool configs \u2014 not run by hand)"},d={log:async o=>(await import("../lib/commands/log.mjs")).logCommand(o),ask:async o=>(await import("../lib/commands/ask.mjs")).askCommand(o),switch:async o=>(await import("../lib/commands/switch.mjs")).switchCommand(o),recap:async o=>(await import("../lib/commands/recap.mjs")).recapCommand(o),status:async o=>(await import("../lib/commands/status.mjs")).statusCommand(o),refresh:async o=>(await import("../lib/commands/refresh.mjs")).refreshCommand(o),forget:async o=>(await import("../lib/commands/forget.mjs")).forgetCommand(o),prune:async o=>(await import("../lib/commands/prune.mjs")).pruneCommand(o),bookmark:async o=>(await import("../lib/commands/bookmark.mjs")).bookmarkCommand(o),init:async o=>(await import("../lib/commands/init.mjs")).initCommand(o),setup:async o=>(await import("../lib/commands/setup.mjs")).setupCommand(o),doctor:async o=>(await import("../lib/commands/doctor.mjs")).doctorCommand(o),context:async o=>(await import("../lib/commands/context.mjs")).contextCommand(o),mcp:async o=>(await import("../lib/commands/mcp.mjs")).mcpCommand(o),"install-cursor-hooks":async o=>(await import("../lib/commands/installCursorHooks.mjs")).installCursorHooksCommand(o),"install-vscode-copilot-hooks":async o=>(await import("../lib/commands/installVsCodeCopilotHooks.mjs")).installVsCodeCopilotHooksCommand(o),"generate-skills":async o=>(await import("../lib/commands/generateSkills.mjs")).generateSkillsCommand(o),ai:async o=>(await import("../lib/commands/ai.mjs")).aiCommand(o),telemetry:async o=>(await import("../lib/telemetry.mjs")).telemetryCommand(o),uninstall:async o=>(await import("../lib/commands/uninstall.mjs")).uninstallCommand(o),check:async o=>(await import("../lib/commands/check.mjs")).checkCommand(o),sync:async o=>(await import("../lib/commands/sync.mjs")).syncCommand(o),amp:async o=>(await import("../lib/commands/amp.mjs")).ampCommand(o)};function G(){const o=Object.keys(h),n=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(h).map(([r,l])=>` ${r.padEnd(n," ")}${l}`).join(`
|
|
3
|
+
`)}const M={Memory:["log","ask","switch","recap","status","refresh","forget","prune","bookmark"],Setup:["init","setup","doctor","context","mcp"],"IDE wiring":["install-cursor-hooks","install-vscode-copilot-hooks","generate-skills"],Configuration:["ai","telemetry","sync","uninstall"],Contract:["check"],"AMP (use: infernoflow amp <verb>)":["status","migrate","validate","version"]};function O(){return Object.entries(M).map(([o,n])=>` ${i(o+":")}
|
|
4
4
|
${n.join(" ")}`).join(`
|
|
5
5
|
|
|
6
|
-
`)}const w=Object.keys(d).length,
|
|
6
|
+
`)}const w=Object.keys(d).length,R=`
|
|
7
7
|
${i("\u{1F525} infernoflow")} ${e("v"+m)}
|
|
8
8
|
${e("Persistent memory for AI coding sessions")}
|
|
9
9
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
${e("Run")} ${t("infernoflow commands")} ${e("to see all "+w+" commands grouped.")}
|
|
32
32
|
${e("Run")} ${t("infernoflow <command> --help")} ${e("for command-specific options.")}
|
|
33
|
-
`;import*as
|
|
33
|
+
`;import*as E from"node:fs";import*as S from"node:path";try{const o=S.join(process.cwd(),"inferno");if(E.existsSync(o)){const{observeCommandStart:n}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&n(o,r)}}catch{}const[,,s,...x]=process.argv;(!s||s==="--help"||s==="-h")&&(console.log(R),process.exit(0)),(s==="--version"||s==="-v")&&(console.log(m),process.exit(0)),s==="commands"&&(console.log(`
|
|
34
34
|
${i("\u{1F525} infernoflow")} ${e("v"+m)} ${e("\u2014 all "+w+" commands")}
|
|
35
35
|
`),console.log(O()),console.log(`
|
|
36
36
|
${e("Run")} ${t("infernoflow <command> --help")} ${e("for options.")}
|
package/dist/lib/amp/io.mjs
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`).filter(Boolean).map(
|
|
3
|
-
`,"utf8")}function
|
|
4
|
-
`))if(
|
|
5
|
-
`))if(
|
|
1
|
+
import*as s from"node:fs";import{redactSecrets as Y}from"../security/redact.mjs";import*as G from"node:os";import*as a from"node:path";import{findProjectRoot as N}from"../projectRoot.mjs";import{getBranchInfo as U}from"../git/branch.mjs";const B="1.0",z=new Set(["gotcha","decision","attempt","note","detection","pattern"]),H=new Set(["copilot","cursor","claude","windsurf","other"]);function V(t){let e=a.basename(a.resolve(t));try{const o=a.join(t,".ai-memory","amp.json");if(s.existsSync(o)){const n=JSON.parse(s.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(e=n.project.trim())}}catch{}return e.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function K(t,e){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const f=JSON.parse(s.readFileSync(a.join(e,"amp.json"),"utf8"));f&&typeof f.globalDir=="string"&&f.globalDir.trim()&&(o=f.globalDir.trim())}catch{}if(!o)return a.join(e,"global.jsonl");let n=o;n.startsWith("~")&&(n=a.join(G.homedir(),n.slice(1).replace(/^[\/\\]/,""))),a.isAbsolute(n)||(n=a.resolve(t,n));const r=V(t);return a.join(n,r,"global.jsonl")}function S(t,e={}){const o=e.literal?a.resolve(t):N(t),n=a.join(o,".ai-memory"),r=a.join(o,"inferno"),f=e.forWrite||s.existsSync(n)||!s.existsSync(r),c=f?n:r,i=f,u=a.join(c,"branches"),l=U(o),d=a.join(u,`${l.currentSlug}.jsonl`),y=l.defaultSlug?a.join(u,`${l.defaultSlug}.jsonl`):null,m=Z(o,c);return{root:c,projectRoot:o,isAmp:i,sessions:a.join(c,"sessions.jsonl"),config:a.join(c,i?"amp.json":"config.json"),handoff:a.join(c,i?"handoff.md":"HANDOFF.md"),globalFile:m,branchesDir:u,currentBranchFile:d,defaultBranchFile:y,branch:l}}function dt(t){try{const e=S(t),o=e.branch&&e.branch.current;return`store: ${e.root}${o&&!e.branch.isSynthetic?` (branch ${o})`:""}`}catch{return"store: (unresolved)"}}function Z(t,e){try{return K(t,e)}catch{return a.join(e,"global.jsonl")}}function A(t){const e=N(t),o=a.join(e,".ai-memory");return s.existsSync(o)||s.mkdirSync(o,{recursive:!0}),o}const E="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function w(){let t=Date.now(),e="";for(let n=0;n<10;n++)e=E[t%32]+e,t=Math.floor(t/32);let o="";for(let n=0;n<16;n++)o+=E[Math.floor(Math.random()*32)];return e+o}function J(t){const e={...t.meta||{}};let o=t.type||"note";z.has(o)||(e.subtype=o,o="note"),t.result&&(e.result=t.result),t.detailRef&&(e.detailRef=t.detailRef);let n;const r=t.agent;r&&H.has(r)?n=r:r&&(e.agent=r);const f=typeof t.ts=="number"?t.ts:t.ts?Date.parse(t.ts):Date.now(),c=t.confidence!=null?t.confidence:t.auto?.7:void 0,i={type:o,msg:t.summary||t.msg||"",ts:f,id:t.id||`amp_${w()}`};return t.file&&(i.file=t.file),t.line&&(i.line=t.line),t.function&&(i.function=t.function),t.tags&&t.tags.length&&(i.tags=t.tags),t.source&&(i.source=t.source),n&&(i.tool=n),t.session&&(i.session=t.session),c!=null&&(i.confidence=c),Object.keys(e).length&&(i.meta=e),i}function q(t){if(t.summary&&!t.msg)return t;const e=t.meta||{},o=e.subtype||t.type||"note",n={ts:t.ts,type:o,summary:t.msg||""};t.id&&(n.id=t.id),t.file&&(n.file=t.file),t.line&&(n.line=t.line),t.function&&(n.function=t.function),t.tags&&(n.tags=t.tags),t.source&&(n.source=t.source),t.tool&&(n.agent=t.tool),e.agent&&(n.agent=e.agent),e.result&&(n.result=e.result),e.detailRef&&(n.detailRef=e.detailRef),t.confidence!=null&&(n.confidence=t.confidence,t.confidence<1&&(n.auto=!0));const{subtype:r,agent:f,result:c,detailRef:i,...u}=e;return Object.keys(u).length&&(n.meta=u),n}function Q(t){if(!t||!s.existsSync(t))return[];try{return s.readFileSync(t,"utf8").split(`
|
|
2
|
+
`).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(Boolean).map(q)}catch{return[]}}function yt(t){const e=S(t),o=new Set,n=[],r=[e.sessions,e.globalFile,e.defaultBranchFile,e.currentBranchFile],f=[...new Set(r.filter(Boolean))];for(const c of f)for(const i of Q(c)){const u=i.id||`${i.ts}|${i.summary}`;o.has(u)||(o.add(u),n.push(i))}return n.sort((c,i)=>{const u=typeof c.ts=="number"?c.ts:Date.parse(c.ts||0),l=typeof i.ts=="number"?i.ts:Date.parse(i.ts||0);return u-l})}function X(t,e){return e.target==="global"?t.globalFile:e.target==="legacy"?t.sessions:e.target==="branch"?t.currentBranchFile:e.type==="preference"?t.globalFile:t.currentBranchFile}const L=new Set;function F(t){return a.join(t,"details.jsonl")}const $="details.local.jsonl";function R(t){return a.join(t,$)}function tt(t,e,o,n=!1){const r=n?R(t):F(t);s.mkdirSync(a.dirname(r),{recursive:!0}),s.appendFileSync(r,JSON.stringify({id:e,body:o})+`
|
|
3
|
+
`,"utf8")}function _(t,e,o=!1){if(!e)return null;let n;try{n=s.readFileSync(o?R(t):F(t),"utf8")}catch{return null}let r=null;for(const f of n.split(`
|
|
4
|
+
`))if(f.trim())try{const c=JSON.parse(f);c.id===e&&typeof c.body=="string"&&(r=c.body)}catch{}return r}function I(t,e,o=!1){const n=o?R(t):F(t);let r;try{r=s.readFileSync(n,"utf8")}catch{return!1}const f=[];let c=!1;for(const i of r.split(`
|
|
5
|
+
`))if(i.trim()){try{if(JSON.parse(i).id===e){c=!0;continue}}catch{}f.push(i)}return c&&s.writeFileSync(n,f.length?f.join(`
|
|
6
6
|
`)+`
|
|
7
|
-
`:"","utf8"),
|
|
8
|
-
`))if(
|
|
9
|
-
`,f++}if(
|
|
10
|
-
`;if(
|
|
11
|
-
`)}catch{continue}const l=[];let
|
|
7
|
+
`:"","utf8"),c}function et(t){const e=a.join(t,"details");if(!s.existsSync(e))return 0;let o;try{o=s.readdirSync(e).filter(c=>c.endsWith(".md"))}catch{return 0}if(!o.length)return 0;const n=new Set;try{const c=s.readFileSync(F(t),"utf8");for(const i of c.split(`
|
|
8
|
+
`))if(i.trim())try{n.add(JSON.parse(i).id)}catch{}}catch{}let r="",f=0;for(const c of o){const i=c.replace(/\.md$/,"");if(n.has(i))continue;let u;try{u=s.readFileSync(a.join(e,c),"utf8")}catch{continue}r+=JSON.stringify({id:i,body:u})+`
|
|
9
|
+
`,f++}if(r)try{s.appendFileSync(F(t),r,"utf8")}catch{return 0}for(const c of o)try{s.unlinkSync(a.join(e,c))}catch{}try{s.rmdirSync(e)}catch{}return f}function ht(t,e){A(t);const o=S(t,{forWrite:!0});if(!L.has(o.root)){L.add(o.root);try{et(o.root)}catch{}}const n=e.id||`amp_${w()}`,r={...e,id:n},f=new Set,c=m=>{if(typeof m!="string")return m;const j=Y(m);return j.kinds.forEach(h=>f.add(h)),j.text};r.msg!==void 0&&(r.msg=c(r.msg)),r.summary!==void 0&&(r.summary=c(r.summary)),Array.isArray(r.tags)&&(r.tags=r.tags.map(c)),typeof r.result=="string"&&(r.result=c(r.result));const i=r.detailLocal===!0;delete r.detailLocal;const u=typeof e.detail=="string"?c(e.detail):e.detail;if(f.size&&(r.meta={...r.meta||{},redacted:[...f]}),typeof u=="string"&&u.trim())try{tt(o.root,n,u,i),r.detailRef=i?$:"details.jsonl"}catch{}delete r.detail;const l=X(o,r),d=J(r),y=JSON.stringify(d)+`
|
|
10
|
+
`;if(s.mkdirSync(a.dirname(l),{recursive:!0}),s.appendFileSync(l,y,"utf8"),l!==o.sessions)try{s.mkdirSync(a.dirname(o.sessions),{recursive:!0}),s.appendFileSync(o.sessions,y,"utf8")}catch{}return d}function pt(t,e){const o=S(t);return a.join(o.root,"details",`${e}.md`)}function mt(t,e){if(!e)return null;const o=e.detailRef||e.meta&&e.meta.detailRef;if(!o)return null;const n=S(t),r=e.id||e.meta&&e.meta.id,f=_(n.root,r,!0);if(f!=null)return f;const c=_(n.root,r);if(c!=null)return c;if(typeof o=="string"&&/^details[\\/][A-Za-z0-9_.-]+\.md$/.test(o)&&!o.includes("..")){const i=a.join(n.root,"details"),u=a.join(n.root,o);try{const l=a.join(s.realpathSync(n.root),"details");if(s.lstatSync(i).isSymbolicLink()||s.realpathSync(i)!==l)return null;const d=s.realpathSync(u);return a.dirname(d)!==l?null:s.readFileSync(d,"utf8")}catch{return null}}return null}function gt(t,e){if(!e)return{removed:0,files:[]};const o=S(t),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(s.existsSync(o.branchesDir))for(const i of s.readdirSync(o.branchesDir))i.endsWith(".jsonl")&&n.push(a.join(o.branchesDir,i))}catch{}const r=[...new Set(n)];let f=0;const c=[];for(const i of r){if(!s.existsSync(i))continue;let u;try{u=s.readFileSync(i,"utf8").split(`
|
|
11
|
+
`)}catch{continue}const l=[];let d=0;for(const y of u){if(!y.trim())continue;let m;try{m=JSON.parse(y).id}catch{l.push(y);continue}if(m===e){d++,f++;continue}l.push(y)}d>0&&(s.writeFileSync(i,l.length?l.join(`
|
|
12
12
|
`)+`
|
|
13
|
-
`:"","utf8"),
|
|
14
|
-
`)}catch{continue}const
|
|
13
|
+
`:"","utf8"),c.push(i))}try{I(o.root,e)}catch{}try{I(o.root,e,!0)}catch{}try{const i=a.join(o.root,"details",`${e}.md`);s.existsSync(i)&&(s.unlinkSync(i),c.push(i))}catch{}return{removed:f,files:c}}const nt=30,ot=["note","attempt","detection"],C=new Set(["gotcha","decision","pattern"]);function rt(t){const e=t&&t.config&&t.config.rotation&&typeof t.config.rotation=="object"?t.config.rotation:{};return{archiveAfterDays:Number.isInteger(e.archiveAfterDays)&&e.archiveAfterDays>0?e.archiveAfterDays:nt,archivableTypes:Array.isArray(e.archivableTypes)&&e.archivableTypes.length?e.archivableTypes:ot,auto:e.auto===!0}}function it(t){const e=[t.sessions,t.currentBranchFile,t.defaultBranchFile].filter(Boolean);try{if(s.existsSync(t.branchesDir))for(const o of s.readdirSync(t.branchesDir))o.endsWith(".jsonl")&&e.push(a.join(t.branchesDir,o))}catch{}return[...new Set(e)]}function st(t,e){const o=new Date(e),n=o.getUTCFullYear(),r=String(o.getUTCMonth()+1).padStart(2,"0");return a.join(t,"archive",`sessions-${n}-${r}.jsonl`)}function St(t,e={}){const o=S(t,{forWrite:!e.dryRun}),n=rt(ct(t)),r=Number.isFinite(e.maxAgeDays)?e.maxAgeDays:n.archiveAfterDays,f=e.archive!==!1,c=new Set((Array.isArray(e.types)&&e.types.length?e.types:n.archivableTypes).filter(h=>!C.has(h))),i=Date.now()-r*24*60*60*1e3,u=it(o),l={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!e.dryRun},d=new Map,y=new Set,m=new Set,j=new Set;for(const h of u){if(!s.existsSync(h))continue;let D;try{D=s.readFileSync(h,"utf8").split(`
|
|
14
|
+
`)}catch{continue}const x=[];let O=0;for(const b of D){if(!b.trim())continue;let g;try{g=JSON.parse(b)}catch{x.push(b);continue}const p=g&&g.id,v=g&&typeof g.type=="string"?g.type:"note",k=typeof g.ts=="number"?g.ts:Date.parse(g.ts||0),P=Number.isFinite(k)&&k<i,M=Array.isArray(g.tags)&&g.tags.includes("bookmark"),W=c.has(v)&&!C.has(v)&&!M;if(p&&!m.has(p)&&(m.add(p),l.scanned++),p||l.scanned++,P&&W){if(O++,(!p||!y.has(p))&&(p&&y.add(p),l.archived++,l.byType[v]=(l.byType[v]||0)+1,f)){const T=st(o.root,k);d.has(T)||d.set(T,[]),d.get(T).push(b)}}else p&&!j.has(p)&&(j.add(p),l.kept++),p||l.kept++,x.push(b)}O>0&&!e.dryRun?(s.writeFileSync(h,x.length?x.join(`
|
|
15
15
|
`)+`
|
|
16
|
-
`:"","utf8"),l.files.push(
|
|
16
|
+
`:"","utf8"),l.files.push(h)):O>0&&l.files.push(h)}if(!e.dryRun&&f&&d.size>0)for(const[h,D]of d)try{s.mkdirSync(a.dirname(h),{recursive:!0}),s.appendFileSync(h,D.join(`
|
|
17
17
|
`)+`
|
|
18
|
-
`,"utf8")}catch{}return l}function
|
|
19
|
-
`,"utf8"),!0}function
|
|
20
|
-
`,"utf8"),n.config.injection}function
|
|
21
|
-
`).filter(Boolean);let
|
|
22
|
-
`,"utf8"),
|
|
18
|
+
`,"utf8")}catch{}return l}function ct(t){const{config:e}=S(t);try{return JSON.parse(s.readFileSync(e,"utf8"))}catch{return null}}function jt(t,e={}){A(t);const{config:o}=S(t,{forWrite:!0});if(s.existsSync(o))return!1;const n={amp:B,project:e.project||a.basename(t),stack:e.stack||{},config:{autoCapture:!0,maxEntries:1e3,rotationStrategy:"archive",inject:["all"],injection:{maxEntries:4,maxCommits:5,maxEntryChars:200,protocolStyle:"compact"},rotation:{archiveAfterDays:30,archivableTypes:["note","attempt","detection"],auto:!1},...e.config||{}}};return s.writeFileSync(o,JSON.stringify(n,null,2)+`
|
|
19
|
+
`,"utf8"),!0}function bt(t,e={}){A(t);const{config:o}=S(t,{forWrite:!0});let n={};try{n=JSON.parse(s.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=B),n.project||(n.project=a.basename(t)),(!n.config||typeof n.config!="object")&&(n.config={});const r=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...r,...e},s.writeFileSync(o,JSON.stringify(n,null,2)+`
|
|
20
|
+
`,"utf8"),n.config.injection}function Ft(t){const e=a.join(t,"inferno"),o=a.join(e,"sessions.jsonl");if(!s.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=a.join(t,".ai-memory"),r=a.join(n,"sessions.jsonl");if(s.existsSync(r))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};A(t);const f=s.readFileSync(o,"utf8").split(`
|
|
21
|
+
`).filter(Boolean);let c=0;for(const i of f)try{const u=JSON.parse(i),l=J(u);s.appendFileSync(r,JSON.stringify(l)+`
|
|
22
|
+
`,"utf8"),c++}catch{}return s.writeFileSync(a.join(n,"MIGRATED.md"),`# Migrated from inferno/
|
|
23
23
|
|
|
24
|
-
Copied ${
|
|
24
|
+
Copied ${c} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
|
|
25
25
|
|
|
26
26
|
The original inferno/sessions.jsonl is untouched. You can delete it once you're confident the new layout works.
|
|
27
|
-
`,"utf8"),{migrated:
|
|
27
|
+
`,"utf8"),{migrated:c,reason:"ok"}}export{B as AMP_VERSION,$ as LOCAL_DETAIL_STORE,S as ampPaths,ht as appendEntry,gt as deleteEntry,dt as describeStore,pt as detailPath,A as ensureAmpDir,q as fromAmp,w as generateULID,Ft as migrateLegacy,V as projectSlug,St as pruneEntries,ct as readConfig,mt as readDetail,yt as readEntries,K as resolveGlobalFile,rt as resolveRotationSettings,J as toAmp,bt as updateInjectionConfig,jt as writeDefaultConfig};
|
package/dist/lib/cleanTree.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import*as m from"node:fs";import*as
|
|
2
|
-
`),
|
|
3
|
-
`),
|
|
4
|
-
`:"")+(e||"")}function h(n,r,i){const o=
|
|
1
|
+
import*as m from"node:fs";import*as u from"node:path";const d="# >>> infernoflow:start",s="# <<< infernoflow:end",g=["",d,"# Personal memory (per-developer, per-machine). Sync via cloud folder","# or `infernoflow sync`, not git.",".ai-memory/global.jsonl",".ai-memory/sessions.jsonl","# Regenerated artifacts \u2014 never commit these.",".ai-memory/handoff.md",".ai-memory/CONTEXT.draft.md",".ai-memory/HANDOFF.md",".ai-memory/.last-cli-version","# Automatic bookmark transcript snapshots \u2014 local only, never committed.",".ai-memory/details.local.jsonl","# AI provider settings \u2014 older versions stored API keys here.","inferno/integrations.json","# MCP runtime stamp (machine-specific).",".ai-memory/.mcp-runtime.json","# Build/publish hygiene \u2014 don't ship memory in published .NET / monorepo bundles.","**/publish/.ai-memory/","**/publish/inferno/","**/dist/.ai-memory/","**/dist/inferno/",s,""].join(`
|
|
2
|
+
`),A=["",d,"# Branch-local memory: append-only JSONL files. Auto-merge concurrent","# additions from different machines/branches as union of lines so","# `home \u2192 work \u2192 home` syncs don't produce conflicts.",".ai-memory/branches/*.jsonl merge=union",s,""].join(`
|
|
3
|
+
`),I="# --- infernoflow (developer-local AI memory; do not commit) ---",y="# --- /infernoflow ---";function T(n){const r=n.indexOf(I),i=n.indexOf(y);if(r===-1||i===-1||i<=r)return n;const o=n.slice(0,r).replace(/\s+$/,""),e=n.slice(i+y.length).replace(/^\s+/,"");return(o?o+`
|
|
4
|
+
`:"")+(e||"")}function h(n,r,i){const o=u.join(n,r);let e="";try{e=m.readFileSync(o,"utf8")}catch{}e=T(e);const c=e.indexOf(d),a=e.indexOf(s),l=i.trim();let t;if(c!==-1&&a!==-1&&a>c){const f=e.slice(0,c).replace(/\s+$/,""),p=e.slice(a+s.length).replace(/^\s+/,"");t=(f?f+`
|
|
5
5
|
|
|
6
6
|
`:"")+l+`
|
|
7
|
-
`+(
|
|
8
|
-
`+
|
|
7
|
+
`+(p?`
|
|
8
|
+
`+p:"")}else e?t=e.replace(/\s+$/,"")+`
|
|
9
9
|
|
|
10
10
|
`+l+`
|
|
11
11
|
`:t=l+`
|
|
12
|
-
`;return t===e?"unchanged":(m.mkdirSync(
|
|
12
|
+
`;return t===e?"unchanged":(m.mkdirSync(u.dirname(o),{recursive:!0}),m.writeFileSync(o,t,"utf8"),e?"updated":"created")}function E(n){return{gitignore:h(n,".gitignore",g),gitattributes:h(n,".gitattributes",A)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
|
package/dist/lib/commands/ai.mjs
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
|
|
1
|
+
import*as u from"node:fs";import*as v from"node:path";import*as x from"node:os";import*as R from"node:https";import*as U from"node:http";import*as G from"node:readline";import{bold as f,cyan as $,gray as c,green as d,yellow as w,red as y}from"../ui/output.mjs";function _(l){return v.join(l,"inferno")}function k(){const l=process.env.INFERNOFLOW_HOME||v.join(x.homedir(),".infernoflow");return v.join(l,"ai-credentials.json")}function K(){try{const l=JSON.parse(u.readFileSync(k(),"utf8"));return l&&typeof l=="object"?l:{}}catch{return{}}}function A(l){const o=k();u.mkdirSync(v.dirname(o),{recursive:!0}),u.writeFileSync(o,JSON.stringify(l,null,2)+`
|
|
2
|
+
`,{encoding:"utf8",mode:384});try{u.chmodSync(o,384)}catch{}}function E(l){const o=v.join(_(l),"integrations.json");let i={};if(u.existsSync(o))try{i=JSON.parse(u.readFileSync(o,"utf8"))||{}}catch{i={}}const n=K(),e=n.providers&&typeof n.providers=="object"?n.providers:{};let t=[];for(const[r,s]of Object.entries(i))s&&typeof s=="object"&&typeof s.apiKey=="string"&&s.apiKey&&(e[r]={...e[r]||{},apiKey:s.apiKey},delete s.apiKey,t.push(r));if(t.length)try{A({...n,providers:e}),u.writeFileSync(o,JSON.stringify(i,null,2)+`
|
|
3
|
+
`),process.stderr.write(` infernoflow: moved API key(s) for ${t.join(", ")} out of inferno/integrations.json into ${k()}.
|
|
4
|
+
infernoflow: if integrations.json was ever committed, revoke those keys with the provider \u2014 they are in your git history.
|
|
5
|
+
`)}catch{}for(const[r,s]of Object.entries(e))s&&typeof s.apiKey=="string"&&s.apiKey&&(i[r]={...i[r]||{},apiKey:s.apiKey});return i}function P(l,o){const i=_(l);u.existsSync(i)||u.mkdirSync(i,{recursive:!0});const n=K(),e=n.providers&&typeof n.providers=="object"?n.providers:{},t={};for(const[r,s]of Object.entries(o)){if(!s||typeof s!="object"){t[r]=s;continue}const{apiKey:a,fromEnv:m,...p}=s;t[r]=p,a&&!m&&(e[r]={...e[r]||{},apiKey:a})}A({...n,providers:e}),u.writeFileSync(v.join(i,"integrations.json"),JSON.stringify(t,null,2)+`
|
|
6
|
+
`)}const g=[{id:"anthropic",name:"Anthropic (Claude)",envKey:"ANTHROPIC_API_KEY",models:["claude-sonnet-4-6","claude-opus-4-6","claude-haiku-4-5-20251001"],default:"claude-sonnet-4-6",keyHint:"sk-ant-api03-\u2026",docsUrl:"https://console.anthropic.com/settings/keys"},{id:"openai",name:"OpenAI (GPT)",envKey:"OPENAI_API_KEY",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo"],default:"gpt-4o",keyHint:"sk-\u2026",docsUrl:"https://platform.openai.com/api-keys"},{id:"gemini",name:"Google Gemini",envKey:"GOOGLE_AI_API_KEY",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],default:"gemini-2.0-flash",keyHint:"AIza\u2026",docsUrl:"https://aistudio.google.com/app/apikey"},{id:"openrouter",name:"OpenRouter",envKey:"OPENROUTER_API_KEY",models:["anthropic/claude-sonnet-4-6","openai/gpt-4o","meta-llama/llama-3.1-8b-instruct:free"],default:"anthropic/claude-sonnet-4-6",keyHint:"sk-or-\u2026",docsUrl:"https://openrouter.ai/keys"},{id:"ollama",name:"Ollama (local)",envKey:null,models:["llama3.2","mistral","codellama","phi3"],default:"llama3.2",keyHint:null,docsUrl:"https://ollama.com"}];function S(l){return new Promise(o=>{const i=new URL(l),e=(i.protocol==="https:"?R:U).request({hostname:i.hostname,port:i.port||(i.protocol==="https:"?443:80),path:i.pathname+(i.search||""),method:"GET",timeout:5e3},t=>{let r="";t.on("data",s=>r+=s),t.on("end",()=>{try{o({status:t.statusCode,body:JSON.parse(r)})}catch{o({status:t.statusCode,body:r})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function j(l,o){const n={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY}[l],e=o[l]?.apiKey,t=n||e,r=n?"env":e?"~/.infernoflow/ai-credentials.json":null,s=o[l]?.model||g.find(a=>a.id===l)?.default;if(l==="ollama"){const a=await S("http://localhost:11434/api/tags").catch(()=>null);if(a?.status===200){const m=a.body?.models?.map(p=>p.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:m}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!t,source:r,model:s,available:null,masked:t?t.slice(0,8)+"\u2026":null}}async function N(l,o,i){try{const n=await import("../ai/providerRouter.mjs");if(typeof n.callAI!="function")return null;const e=`Reply with exactly: "infernoflow AI test OK \u2014 ${l}"`;return await n.callAI(e,i,l)}catch{return null}}function h(l,o){return new Promise(i=>l.question(o,i))}async function Y(l){const o=E(l);console.log(),console.log(` ${f("infernoflow ai")} ${c("\u2014 provider status")}`),console.log();let i=!1;for(const n of g){const e=await j(n.id,o);e.configured&&(i=!0);const t=e.configured?d("\u2713"):c("\u25CB"),r=f(n.name.padEnd(22)),s=e.configured?`${d("configured")} ${c(e.source)} ${c("model: "+e.model)}${e.masked?" "+c(e.masked):""}`:c("not configured");console.log(` ${t} ${r} ${s}`)}console.log(),i?console.log(` ${c("Run")} ${$("infernoflow ai test")} ${c("to verify the active provider.")}`):(console.log(` ${w("No AI providers configured.")} Run: ${$("infernoflow ai setup")}`),console.log(` ${c("Without a provider, explain/why/review use structural fallbacks.")}`)),console.log()}async function T(l){const o=E(l),i={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY};console.log(),console.log(` ${f("\u{1F525} infernoflow ai setup")}`),console.log(` ${c("Connect an AI provider for explain, why, review, and changelog.")}`),console.log(),g.forEach((e,t)=>{const r=i[e.id],s=o[e.id]?.apiKey,a=r?d(" \u2713 key detected in environment"):s?d(" \u2713 key already saved"):"",m=f(String(t+1)),p=e.id==="ollama"?c(" (local, no key needed)"):"";console.log(` ${m}) ${f(e.name.padEnd(22))}${p}${a}`)}),console.log();const n=G.createInterface({input:process.stdin,output:process.stdout});try{const e=await h(n," Select provider [1]: "),t=(parseInt(e.trim())||1)-1;if(t<0||t>=g.length){console.log(y(` Invalid choice. Enter a number 1\u2013${g.length}.`));return}const r=g[t],s=r.id;if(console.log(),console.log(` ${f(r.name)}`),s==="ollama"){const a=await h(n," Ollama host [http://localhost:11434]: "),m=await h(n,` Model [${r.default}]: `);o.ollama={host:a.trim()||"http://localhost:11434",model:m.trim()||r.default},P(l,o),console.log(),process.stdout.write(` ${d("\u2713")} Saved. Testing connection\u2026 `),(await S(`${o.ollama.host}/api/tags`).catch(()=>null))?.status===200?console.log(d("OK")):(console.log(w("not reachable")),console.log(` ${w("\u26A0")} Start Ollama first: ${$("ollama serve")}`))}else{const a=i[s],m=o[s]?.apiKey,p=a||m;if(p){const I=a?"environment variable":"saved config";if(console.log(` ${d("\u2713")} API key detected from ${I}: ${c(p.slice(0,12)+"\u2026")}`),(await h(n," Use this key? [Y/n]: ")).trim().toLowerCase()==="n"){console.log(),r.docsUrl&&console.log(` ${c("Get a key at:")} ${$(r.docsUrl)}`);const b=await h(n," Paste new API key: ");if(!b.trim()){console.log(y(" No key provided. Exiting."));return}o[s]={apiKey:b.trim(),model:o[s]?.model||r.default}}else o[s]={apiKey:p,fromEnv:!!a,model:o[s]?.model||r.default}}else{console.log(` ${c("Get your API key at:")} ${$(r.docsUrl)}`),console.log(` ${c("Tip: paste the key below \u2014 it starts with")} ${c(r.keyHint)}`),console.log();const I=await h(n," Paste API key: ");if(!I.trim()){console.log(y(" No key provided. Exiting."));return}o[s]={apiKey:I.trim(),model:r.default}}const O=o[s].model;console.log(),console.log(` ${c("Available models:")} ${r.models.join(" ")}`);const C=await h(n,` Model [${O}]: `);o[s].model=C.trim()||O,P(l,o),console.log(),process.stdout.write(` ${d("\u2713")} Saved. Testing connection\u2026 `),(await N(s,o,l))?.text?console.log(d("OK")+c(` (${o[s].model})`)):(console.log(w("no response")),console.log(` ${w("\u26A0")} Connection failed \u2014 double-check your API key.`))}console.log(),console.log(` ${d("\u2713")} ${f(r.name)} is ready.`),console.log(` ${c("AI-powered commands:")} explain why review changelog`),console.log(),console.log(` ${c("API key stored outside the project:")} ${$(k())}`),console.log()}finally{n.close()}}async function H(l,o){const i=E(o),n=l.find(t=>!t.startsWith("--"))||null;console.log(),console.log(` ${f("infernoflow ai test")}`),console.log();const e=n?g.filter(t=>t.id===n):g;for(const t of e){if(!(await j(t.id,i)).configured){console.log(` ${c("\u25CB")} ${f(t.name.padEnd(22))} ${c("not configured \u2014 skipping")}`);continue}process.stdout.write(` ${w("\u2026")} ${f(t.name.padEnd(22))} testing\u2026 `);const s=await N(t.id,i,o);s?.text?(console.log(d("OK")+c(` (${s.model||t.id})`)),console.log(` ${c(s.text.trim().slice(0,80))}`)):(console.log(y("FAIL")),console.log(` ${y("No response \u2014 check API key or model name")}`))}console.log()}async function F(l,o){const i=E(o),n=l.find(t=>!t.startsWith("--"));if(n||(console.error(y("\u2717 Usage: infernoflow ai clear <provider>")),console.error(c(" Example: infernoflow ai clear openai")),process.exit(1)),!i[n]){console.log(c(` No config found for "${n}"`));return}delete i[n],P(o,i);const e=K();e.providers&&e.providers[n]&&(delete e.providers[n],A(e)),console.log(d(` \u2713 Cleared config for ${n}`))}async function W(l){const o=(l||[]).slice(1),i=o.find(t=>!t.startsWith("--"))||"status",n=o.filter(t=>t!==i),e=process.cwd();switch(i){case"setup":return T(e);case"status":return Y(e);case"test":return H(n,e);case"clear":return F(n,e);default:console.error(y(`\u2717 Unknown subcommand: "${i}"`)),console.error(c(" Usage: infernoflow ai <setup|status|test|clear>")),process.exit(1)}}export{W as aiCommand};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"node:fs";import"node:path";import{bold as T,cyan as O,gray as r,green as
|
|
1
|
+
import{describeStore as R}from"../amp/io.mjs";import"node:fs";import"node:path";import{bold as T,cyan as O,gray as r,green as I,yellow as L,red as M}from"../ui/output.mjs";import{readEntries as W}from"../amp/io.mjs";const d={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},S={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},N={gotcha:L,decision:I,attempt:O,preference:O,theme:O,note:r,error:M,handoff:r};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(e=>e.length>1)}function j(o,e){const i=[o.summary||"",o.type||""].join(" ").toLowerCase(),n=E(i);let s=0;for(const c of e)(o.summary||"").toLowerCase().includes(c)&&(s+=3),n.includes(c)&&(s+=1),n.some(t=>t.startsWith(c)||c.startsWith(t))&&(s+=.5);return s}function q(o){if(o==null||o==="")return"";const e=new Date(o);if(isNaN(e.getTime()))return"";const i=s=>new Date(s.getFullYear(),s.getMonth(),s.getDate()).getTime(),n=Math.round((i(new Date)-i(e))/864e5);return n<=0?`today ${e.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})}`:n===1?"yesterday":n<7?`${n}d ago`:n<30?`${Math.floor(n/7)}w ago`:e.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function Y(o,e){const i=o.type||"note",n=S[i]||"\xB7",s=N[i]||r,c=o.result?r(` [${o.result}]`):"",t=o.agent?r(` \u2014 ${o.agent}`):"",a=r(` (${q(o.ts)})`);let f=o.summary||"";if(e)for(const u of e){const p=new RegExp(`(${u})`,"gi");f=f.replace(p,$=>T($))}console.log(` ${s(n+" "+i.padEnd(11))}${c}${t}${a}`),console.log(` ${f}`)}function P(o){return W(o)}function v(o,e,i,n){let s=o;i&&(s=s.filter(t=>(t.type||"note")===i));let c;return e.length>0?c=s.map(t=>({entry:t,score:j(t,e)})).filter(({score:t})=>t>0).sort((t,a)=>{if(a.score!==t.score)return a.score-t.score;const f=d[t.entry.type]??9,u=d[a.entry.type]??9;return f!==u?f-u:new Date(a.entry.ts||0).getTime()-new Date(t.entry.ts||0).getTime()}):c=s.map(t=>({entry:t,score:1})).sort((t,a)=>{const f=d[t.entry.type]??9,u=d[a.entry.type]??9;return f!==u?f-u:new Date(a.entry.ts||0).getTime()-new Date(t.entry.ts||0).getTime()}),c.slice(0,n||20)}async function J(o=[]){const e=o,i=e.indexOf("--type"),n=i!==-1?e[i+1]:null,s=e.indexOf("--limit")!==-1?e.indexOf("--limit"):e.indexOf("-n"),c=s!==-1?parseInt(e[s+1]||"20",10):15,t=e.includes("--json"),a=e.includes("--recent")||e.includes("-r"),f=e.slice(1),p=f.filter((l,g)=>!(l.startsWith("--")||g>0&&f[g-1].startsWith("--"))).join(" ").trim(),$=a?[]:E(p),k=process.cwd(),m=P(k);if(m.length===0){if(t){console.log(JSON.stringify({results:[],total:0}));return}console.log(r(`
|
|
2
2
|
No session memory yet.`)),console.log(r(` Run: infernoflow log "<what happened>" --type gotcha
|
|
3
|
-
`));return}const y=a?m.slice(-c).reverse().map(l=>({entry:l,score:1})):
|
|
3
|
+
`));return}const y=a?m.slice(-c).reverse().map(l=>({entry:l,score:1})):v(m,$,n,c);if(t){console.log(JSON.stringify({query:p,type:n,total:m.length,matched:y.length,results:y.map(({entry:l,score:g})=>({...l,relevanceScore:g}))},null,2));return}if(console.log(),console.log(p?` ${T("\u{1F525} infernoflow ask")} ${O(`"${p}"`)}${n?r(` [${n}]`):""}`:a?` ${T("\u{1F525} infernoflow ask")} ${r("\u2014 recent entries")}`:` ${T("\u{1F525} infernoflow ask")} ${r("\u2014 all entries")}${n?r(` [${n}]`):""}`),console.log(r(` ${R(process.cwd())}`)),console.log(r(` ${"\u2500".repeat(52)}`)),y.length===0){console.log(),p?(console.log(r(` No entries found for "${p}"`)),n&&console.log(r(` Try removing --type ${n} to widen the search`))):console.log(r(" No entries found.")),console.log();return}const w=new Map;for(const{entry:l,score:g}of y){const h=l.type||"note";w.has(h)||w.set(h,[]),w.get(h).push({entry:l,score:g})}const x=Object.keys(d).sort((l,g)=>d[l]-d[g]);let D=0;for(const l of x){const g=w.get(l);if(!g?.length)continue;console.log();const h=N[l]||r;console.log(h(` ${S[l]} ${l.toUpperCase()}S (${g.length})`)),console.log(r(" "+"\u2500".repeat(50)));for(const{entry:C}of g)console.log(),Y(C,$),D++}console.log(),console.log(r(` ${D} result${D!==1?"s":""} from ${m.length} total entries`)),y.length===c&&m.length>c&&console.log(r(" Use --limit N to see more")),console.log()}export{J as askCommand};
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import*as k from"node:fs";import*as x from"node:path";import{bold as
|
|
2
|
-
`+
|
|
3
|
-
`)),console.log(t(" Drop: ")+f('infernoflow bookmark "<label>"')+t(" auto-captures the session transcript as context")),console.log(t(" ")+t('add --note "..." / --detail-file <path|-> for explicit context \xB7 --marker for label only')),console.log(t(" List: ")+f("infernoflow bookmark list")),console.log(t(" Recall: ")+f("infernoflow bookmark show <id|label>")),console.log(t(" Remove: ")+f("infernoflow bookmark rm <id|label>")),console.log()}async function M(e=[]){const n=process.cwd(),r=e[0]==="bookmark"?e.slice(1):e;if(r.length===0){O();return}const o=r[0];return o==="list"||o==="ls"?j(n,r.slice(1)):o==="show"||o==="jump"||o==="recall"?U(n,r.slice(1)):o==="rm"||o==="remove"||o==="delete"?T(n,r.slice(1)):_(n,r)}function _(e,n){const r=w(n,"--note"),o=w(n,"--detail-file"),l=w(n,"--tags"),s=new Set([r,o,l].filter(Boolean)),c=n.filter(m=>!m.startsWith("--")&&!s.has(m)).join(" ").trim();c||(O(),process.exit(1));const a=n.includes("--marker");let i=null,
|
|
1
|
+
import*as k from"node:fs";import*as x from"node:path";import{bold as d,cyan as f,gray as t,green as h,yellow as g,red as p}from"../ui/output.mjs";import{readEntries as L,appendEntry as R,deleteEntry as D,readDetail as I}from"../amp/io.mjs";import{harvestSnapshot as B}from"../transcript.mjs";const b="bookmark",F=e=>Array.isArray(e.tags)&&e.tags.includes(b);function y(e){return L(e).filter(F)}function $(e){const n=new Date(e);return isNaN(n.getTime())?"":n.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"})}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"}const w=(e,n)=>{const r=e.indexOf(n);return r!==-1&&e[r+1]?e[r+1]:null};function N(e,n){if(!n)return{error:"no-token"};const r=e.filter(s=>s.id&&(s.id===n||s.id.startsWith(n)));if(r.length===1)return{bookmark:r[0]};if(r.length>1)return{error:"ambiguous",matches:r};const o=n.toLowerCase(),l=e.filter(s=>(s.msg||s.summary||"").toLowerCase().includes(o));return l.length===0?{error:"none"}:l.length===1?{bookmark:l[0]}:{bookmark:l[l.length-1],ambiguousLabel:l}}function O(){console.log(`
|
|
2
|
+
`+d("\u{1F516} infernoflow bookmark")+t(` \u2014 named session resume points
|
|
3
|
+
`)),console.log(t(" Drop: ")+f('infernoflow bookmark "<label>"')+t(" auto-captures the session transcript as context")),console.log(t(" ")+t('add --note "..." / --detail-file <path|-> for explicit context \xB7 --marker for label only')),console.log(t(" List: ")+f("infernoflow bookmark list")),console.log(t(" Recall: ")+f("infernoflow bookmark show <id|label>")),console.log(t(" Remove: ")+f("infernoflow bookmark rm <id|label>")),console.log()}async function M(e=[]){const n=process.cwd(),r=e[0]==="bookmark"?e.slice(1):e;if(r.length===0){O();return}const o=r[0];return o==="list"||o==="ls"?j(n,r.slice(1)):o==="show"||o==="jump"||o==="recall"?U(n,r.slice(1)):o==="rm"||o==="remove"||o==="delete"?T(n,r.slice(1)):_(n,r)}function _(e,n){const r=w(n,"--note"),o=w(n,"--detail-file"),l=w(n,"--tags"),s=new Set([r,o,l].filter(Boolean)),c=n.filter(m=>!m.startsWith("--")&&!s.has(m)).join(" ").trim();c||(O(),process.exit(1));const a=n.includes("--marker");let i=null,u=null;if(o)try{i=o==="-"?k.readFileSync(0,"utf8"):k.readFileSync(o,"utf8"),u="file"}catch(m){console.error(p(`
|
|
4
4
|
\u2718 Could not read --detail-file ${o}: ${m.message}
|
|
5
|
-
`)),process.exit(1)}else if(r)i=r,
|
|
5
|
+
`)),process.exit(1)}else if(r)i=r,u="note";else if(!a)try{i=B(e),i&&(u="transcript")}catch{}const v=x.join(e,".ai-memory"),E=x.join(e,"inferno");!k.existsSync(v)&&!k.existsSync(E)&&(console.error(p(`
|
|
6
6
|
\u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
|
|
7
|
-
`)),process.exit(1));const A=l?l.split(",").map(m=>m.trim()).filter(Boolean):[],C=[b,...A.filter(m=>m!==b)],S=R(e,{ts:new Date().toISOString(),agent:W(),type:"note",summary:c,tags:C,...i&&i.trim()?{detail:i}:{}});console.log(),console.log(" "+h("\u{1F516} Bookmark saved: ")+
|
|
8
|
-
`+
|
|
7
|
+
`)),process.exit(1));const A=l?l.split(",").map(m=>m.trim()).filter(Boolean):[],C=[b,...A.filter(m=>m!==b)],S=R(e,{ts:new Date().toISOString(),agent:W(),type:"note",summary:c,tags:C,...i&&i.trim()?{detail:i}:{},...u==="transcript"?{detailLocal:!0}:{}});console.log(),console.log(" "+h("\u{1F516} Bookmark saved: ")+d(c)+t(` ${S.id}`)),i&&i.trim()?console.log(" "+t(`${u==="transcript"?"auto-captured session transcript":"captured context"} (${i.length} chars) \u2014 recall with `)+f(`infernoflow bookmark show ${S.id.slice(0,12)}`)):console.log(" "+t(`${a?"marker only (--marker)":"marker only \u2014 no session transcript found here"} \u2014 recall by label, or add context with --note / --detail-file`)),console.log()}function j(e,n){const r=y(e);if(n.includes("--json")){console.log(JSON.stringify(r.map(o=>({id:o.id,label:o.msg||o.summary,ts:o.ts,hasContext:!!o.detailRef})),null,2));return}if(console.log(`
|
|
8
|
+
`+d("\u{1F516} infernoflow bookmarks")),console.log(" "+"\u2500".repeat(50)),r.length===0){console.log(t(`
|
|
9
9
|
No bookmarks yet. Drop one: `)+f('infernoflow bookmark "<label>"')+`
|
|
10
10
|
`);return}[...r].reverse().forEach((o,l)=>{const s=o.detailRef?h(" \u25CF"):t(" \u25CB");console.log(` ${t(String(l+1).padStart(3))}${s} ${t($(o.ts))} ${o.msg||o.summary} ${t(o.id.slice(0,12))}`)}),console.log(t(`
|
|
11
11
|
\u25CF has saved context \xB7 \u25CB marker only \u2014 recall with: `)+f("infernoflow bookmark show <id|label>")+`
|
|
12
|
-
`)}function U(e,n){const r=n.includes("--json"),o=n.find(i=>i&&!i.startsWith("-")),l=y(e);l.length===0&&(console.error(
|
|
12
|
+
`)}function U(e,n){const r=n.includes("--json"),o=n.find(i=>i&&!i.startsWith("-")),l=y(e);l.length===0&&(console.error(g(`
|
|
13
13
|
No bookmarks yet.
|
|
14
14
|
`)),process.exit(1));const s=N(l,o);if(s.error==="no-token"&&(console.error(t(`
|
|
15
15
|
Usage: `)+f("infernoflow bookmark show <id|label>")+`
|
|
16
16
|
`),process.exit(1)),s.error==="none"&&(console.error(p(`
|
|
17
17
|
No bookmark matches: ${o}
|
|
18
|
-
`)),process.exit(1)),s.error==="ambiguous"){console.error(
|
|
19
|
-
Ambiguous \u2014 ${s.matches.length} bookmarks match "${o}":`));for(const i of s.matches.slice(0,8))console.error(t(` ${i.id.slice(0,14)} `)+(i.msg||i.summary||""));console.error(""),process.exit(1)}const c=s.bookmark,a=I(e,c);if(r){console.log(JSON.stringify({id:c.id,label:c.msg||c.summary,ts:c.ts,detail:a||null},null,2));return}console.log(),console.log(" "+
|
|
18
|
+
`)),process.exit(1)),s.error==="ambiguous"){console.error(g(`
|
|
19
|
+
Ambiguous \u2014 ${s.matches.length} bookmarks match "${o}":`));for(const i of s.matches.slice(0,8))console.error(t(` ${i.id.slice(0,14)} `)+(i.msg||i.summary||""));console.error(""),process.exit(1)}const c=s.bookmark,a=I(e,c);if(r){console.log(JSON.stringify({id:c.id,label:c.msg||c.summary,ts:c.ts,detail:a||null},null,2));return}console.log(),console.log(" "+d("\u{1F516} "+(c.msg||c.summary))),console.log(" "+t(`${$(c.ts)} \xB7 ${c.id}`)),s.ambiguousLabel&&console.log(" "+t(`(newest of ${s.ambiguousLabel.length} matching "${o}")`)),console.log(" "+"\u2500".repeat(50)),a&&a.trim()?(console.log(),console.log(a.trim()),console.log()):console.log(t(`
|
|
20
20
|
(marker only \u2014 no context was captured for this bookmark)
|
|
21
|
-
`))}function T(e,n){const r=n.find(a=>a&&!a.startsWith("-")),o=y(e);o.length===0&&(console.error(
|
|
21
|
+
`))}function T(e,n){const r=n.find(a=>a&&!a.startsWith("-")),o=y(e);o.length===0&&(console.error(g(`
|
|
22
22
|
No bookmarks to remove.
|
|
23
|
-
`)),process.exit(1));const l=N(o,r);if(l.error){if(l.error==="ambiguous"){console.error(
|
|
23
|
+
`)),process.exit(1));const l=N(o,r);if(l.error){if(l.error==="ambiguous"){console.error(g(`
|
|
24
24
|
Ambiguous \u2014 be specific:`));for(const a of l.matches.slice(0,8))console.error(t(` ${a.id.slice(0,14)} `)+(a.msg||a.summary||""));console.error("")}else l.error==="none"?console.error(p(`
|
|
25
25
|
No bookmark matches: ${r}
|
|
26
26
|
`)):console.error(t(`
|
|
27
27
|
Usage: `)+f("infernoflow bookmark rm <id|label>")+`
|
|
28
|
-
`);process.exit(1)}const s=l.bookmark,{removed:c}=D(e,s.id);console.log(),console.log(c>0?" "+h("\u2714")+" Removed bookmark "+
|
|
28
|
+
`);process.exit(1)}const s=l.bookmark,{removed:c}=D(e,s.id);console.log(),console.log(c>0?" "+h("\u2714")+" Removed bookmark "+d(s.msg||s.summary)+t(` ${s.id}`):" "+g("Nothing removed.")),console.log()}export{b as BOOKMARK_TAG,M as bookmarkCommand};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import*as c from"node:fs";import*as s from"node:path";import*as y from"node:os";import*as
|
|
1
|
+
import{claudeJsonPath as A,claudeDesktopConfigPath as I,isInfernoflowEntry as M}from"../mcpRegistration.mjs";import{HOOK_VERSION_MARKER as _}from"../securityRefresh.mjs";import{redactSecrets as E}from"../security/redact.mjs";import*as c from"node:fs";import*as s from"node:path";import*as y from"node:os";import*as F from"node:http";import{execSync as $,spawnSync as C}from"node:child_process";import{fileURLToPath as b}from"node:url";import{bold as N,cyan as P,gray as S,green as j,yellow as x,red as v}from"../ui/output.mjs";import{detectAvailableProviders as R}from"../ai/providerRouter.mjs";import{readEntries as T,readConfig as D}from"../amp/io.mjs";import{resolveInjectionSettings as G}from"../ruleFiles.mjs";function f(e,o){try{const n=o();return{label:e,...n}}catch(n){return{label:e,status:"error",message:n.message,fix:null}}}function a(e,o){return{status:"pass",message:e,detail:o||null,fix:null}}function l(e,o){return{status:"warn",message:e,detail:null,fix:o||null}}function h(e,o){return{status:"fail",message:e,detail:null,fix:o||null}}function L(){const e=process.version,o=parseInt(e.slice(1).split(".")[0],10);return o>=20?a(`Node.js ${e}`,"Node 20+ recommended"):o>=18?a(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function J(){try{const e=C("infernoflow",["--version"],{encoding:"utf8",timeout:5e3,shell:process.platform==="win32"});return e.status===0?a(`infernoflow v${e.stdout.trim()} installed`):a("infernoflow CLI on PATH (version probe failed but doctor itself ran)")}catch{return a("infernoflow CLI on PATH (version probe threw but doctor itself ran)")}}function H(e){try{return $("git rev-parse --git-dir",{cwd:e,stdio:"ignore"}),a("Git repository detected")}catch{return h("Not a git repository","git init && git add . && git commit -m 'init'")}}function K(e){const o=s.join(e,".ai-memory"),n=s.join(e,"inferno");return c.existsSync(o)&&c.existsSync(n)?a(".ai-memory/ + inferno/ both present"):c.existsSync(o)?a(".ai-memory/ directory exists (memory mode)"):c.existsSync(n)?a("inferno/ directory exists"):h("No memory directory found (.ai-memory/ or inferno/)","infernoflow init")}function U(e){if(w(e)){let n=0;try{n=T(e).length}catch{}return a(n===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${n} session entr${n===1?"y":"ies"}`)}const o=s.join(e,"inferno");for(const n of["contract.json","capabilities.json"]){const t=s.join(o,n);if(c.existsSync(t))try{const i=(JSON.parse(c.readFileSync(t,"utf8")).capabilities||[]).length;return a(`${n} valid \u2014 ${i} capabilities`)}catch{return h(`${n} contains invalid JSON`,`Fix the JSON syntax in inferno/${n}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function w(e){const o=s.join(e,".ai-memory"),n=s.join(e,"inferno","contract.json");if(c.existsSync(o)&&!c.existsSync(n))return!0;try{return JSON.parse(c.readFileSync(s.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function W(e){if(w(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const o=s.join(e,"inferno","scenarios");if(!c.existsSync(o))return l("No scenarios/ directory","infernoflow init");const n=c.readdirSync(o).filter(t=>t.endsWith(".json"));return n.length?a(`${n.length} scenario file${n.length!==1?"s":""} found`):l("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function V(e){if(w(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const o=s.join(e,"inferno","CHANGELOG.md");return c.existsSync(o)?a("inferno/CHANGELOG.md exists"):l("No inferno/CHANGELOG.md","infernoflow init")}function X(e){if(w(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const o=s.join(e,"inferno","CONTEXT.md");if(!c.existsSync(o))return l("No CONTEXT.md generated","infernoflow context");const n=(Date.now()-c.statSync(o).mtimeMs)/(1e3*60*60*24);return n>7?l(`CONTEXT.md is ${Math.round(n)} days old \u2014 may be stale`,"infernoflow context"):a(`CONTEXT.md present (${Math.round(n)}d old)`)}function Y(e){const o=s.join(e,".git","hooks"),n=s.join(o,"post-commit");return c.existsSync(n)&&c.readFileSync(n,"utf8").includes("infernoflow")?a("Git hooks installed (post-commit)"):l("Git hooks not installed","infernoflow setup --yes")}function z(e){const o=[s.join(e,".cursor","mcp.json"),s.join(e,".mcp.json"),s.join(e,".vscode","mcp.json"),s.join(y.homedir(),".claude.json"),s.join(y.homedir(),".cursor","mcp.json"),s.join(y.homedir(),"Library","Application Support","Claude","claude_desktop_config.json"),s.join(y.homedir(),"AppData","Roaming","Claude","claude_desktop_config.json")];for(const n of o)if(c.existsSync(n))try{const t=JSON.parse(c.readFileSync(n,"utf8")),r=t.mcpServers||t.mcp_servers||t.servers||{};if(Object.keys(r).some(i=>i.toLowerCase().includes("inferno")))return a(`MCP server configured in ${s.basename(n)}`)}catch{}return l("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude/Copilot config)")}function q(){const e=[];for(const[o,n]of[["~/.claude.json",A()],["claude_desktop_config.json",I()]])try{const t=JSON.parse(c.readFileSync(n,"utf8"))?.mcpServers?.infernoflow;t&&M(t)&&e.push(`${o} \u2192 ${(t.env||{}).INFERNOFLOW_PROJECT_DIR||"?"}`)}catch{}return e.length?l(`User-level MCP entry pins every project to one repo: ${e.join("; ")}`,"infernoflow setup --yes (registers per project and removes the pinned entry)"):a("No user-level infernoflow MCP entry pinning all projects")}function B(e){const o=[],n=(()=>{try{return c.readFileSync(b(new URL("../../templates/cursor/inferno-mcp-server.mjs",import.meta.url)),"utf8")}catch{return null}})(),t=r=>r.replace(/\r\n/g,`
|
|
2
|
+
`);for(const r of[s.join(".cursor","inferno-mcp-server.mjs"),"inferno-mcp-server.mjs"])try{const i=c.readFileSync(s.join(e,r),"utf8");/serverInfo: \{ name: "infernoflow"|\[infernoflow MCP\]/.test(i)&&n&&t(i)!==t(n)&&o.push(r)}catch{}for(const r of[s.join(".claude","hooks","log-frustration.mjs"),s.join(".cursor","hooks","inferno-session-draft.mjs")])try{c.readFileSync(s.join(e,r),"utf8").includes(_)||o.push(r)}catch{}return o.length?l(`Outdated (vulnerable) copies: ${o.map(r=>r.split(s.sep).join("/")).join(", ")}`,"infernoflow setup --yes"):a("Generated MCP server / hook copies are current")}function Q(e){const o=s.join(e,".ai-memory");if(!c.existsSync(o))return a("No memory store \u2014 nothing to scan");const n=[],t=r=>{for(const i of c.readdirSync(r,{withFileTypes:!0})){const d=s.join(r,i.name);if(i.isDirectory()){t(d);continue}if(/\.(jsonl|json|md)$/.test(i.name)&&!(i.name==="details.local.jsonl"||i.name==="global.jsonl"))try{const u=E(c.readFileSync(d,"utf8")).kinds;u.length&&n.push(`${s.relative(e,d).split(s.sep).join("/")} (${u.join(", ")})`)}catch{}}};try{t(o)}catch{}return n.length?l(`Possible secrets in memory files: ${n.join("; ")}`,"Remove them (infernoflow forget <id>); if already pushed, rotate the secret \u2014 it is in git history"):a("No secrets found in memory files")}function Z(e){const o=s.join(e,"inferno","integrations.json");if(!c.existsSync(o))return a("No API keys stored in the project");let n=!1;try{n=Object.values(JSON.parse(c.readFileSync(o,"utf8"))||{}).some(r=>r&&typeof r=="object"&&r.apiKey)}catch{}let t=!1;try{$("git ls-files --error-unmatch -- inferno/integrations.json",{cwd:e,stdio:"ignore"}),t=!0}catch{}return n&&t?l("inferno/integrations.json holds an API key AND is tracked by git","Rotate the key with the provider, then run: infernoflow ai status (moves keys out of the project)"):n?l("inferno/integrations.json holds an API key","infernoflow ai status (moves it to ~/.infernoflow/ai-credentials.json)"):t?l("inferno/integrations.json is tracked by git","git rm --cached inferno/integrations.json"):a("No API keys stored in the project")}function ee(e,o){const n=s.join(e,".ai-memory",".mcp-runtime.json");if(!c.existsSync(n))return a("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(c.readFileSync(n,"utf8"))}catch{return l(".mcp-runtime.json present but unreadable","Delete .ai-memory/.mcp-runtime.json and restart your AI tool")}return!t||typeof t.version!="string"?l(".mcp-runtime.json malformed","Delete it; the next MCP boot will rewrite it"):t.version===o?a(`MCP runtime v${t.version} matches CLI`):o.startsWith("0.0.0")||t.version.startsWith("0.0.0")?a(`MCP runtime v${t.version} (dev/source) \u2014 skipping version-skew check`):l(`MCP server is running v${t.version} but CLI is v${o} \u2014 restart your AI tool to load the new code.`,"Quit and reopen Cursor / Claude Code / VS Code (the long-running MCP process keeps the old code in memory until restart)")}function ne(e){const o=G(D(e));if(!o.includeProtocol)return a("Memory-protocol injection disabled in config (injection.includeProtocol=false)");const n=o.targets,t=[];for(const r of n)try{const i=c.readFileSync(s.join(e,String(r)),"utf8");i.includes("amp_write")&&/Memory protocol/i.test(i)&&t.push(r)}catch{}return t.length?a(`Memory protocol in ${t.length}/${n.length} configured rule file(s) \u2014 AI knows when to auto-capture`):l("Memory protocol missing from rule files \u2014 the AI won't auto-capture gotchas","infernoflow refresh (writes the amp_write capture protocol into your rule files)")}function oe(e){const o=R(e),n=Object.entries(o).filter(([,t])=>t).map(([t])=>t);return n.length?a(`AI provider${n.length!==1?"s":""}: ${n.join(", ")}`):l("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
|
|
2
3
|
Or install Ollama (ollama.com) for free local AI
|
|
3
|
-
Or use VS Code with GitHub Copilot (zero config)`)}async function
|
|
4
|
+
Or use VS Code with GitHub Copilot (zero config)`)}async function te(){return new Promise(e=>{const o=F.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},n=>{e(a("Ollama running on localhost:11434"))});o.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),o.on("timeout",()=>{o.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function re(){const e=s.join(y.homedir(),".infernoflow","credentials.json");if(!c.existsSync(e))return{status:"info",message:"Not logged in to cloud (optional)",fix:"infernoflow login",detail:null};try{const o=JSON.parse(c.readFileSync(e,"utf8")),n=o.user?.login||o.user?.name||o.user?.email||"unknown";if(o.mode==="supabase"&&o.access_token){if(o.expires_at){const t=new Date(o.expires_at).getTime();if(Date.now()>t)return l(`JWT expired for ${n} \u2014 refresh on next log will retry`,"infernoflow login")}return a(`Authenticated as ${n} (Supabase JWT \u2014 auth.uid() writes)`)}return o.mode==="device-flow"&&o.github_access_token?{status:"info",message:`Identity-only as ${n} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:o.access_token?l(`Legacy login for ${n} \u2014 re-run for authenticated cloud writes`,"infernoflow logout && infernoflow login"):{status:"info",message:"Credentials file present but no recognised token",fix:"infernoflow logout && infernoflow login",detail:null}}catch{return l("Credentials file unreadable","infernoflow logout && infernoflow login")}}function ie(){try{const e=b(import.meta.url),o=s.resolve(s.dirname(e),"..","..","bin","infernoflow.mjs");if(!c.existsSync(o))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...c.readFileSync(o,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=s.resolve(s.dirname(o),"..");for(const d of t){const u=d[1],p=s.join(i,"lib",u);c.existsSync(p)||r.push(u)}return r.length?h(`${r.length} routed command(s) missing module files: ${r.slice(0,3).join(", ")}${r.length>3?"\u2026":""}`,"Restore the missing files or remove their entries from bin/infernoflow.mjs"):a(`All ${t.length} routed commands resolve to real files`)}catch(e){return{status:"info",message:`Router integrity check skipped: ${e.message}`,fix:null,detail:null}}}function se(e){const o=s.join(e,"package.json");if(!c.existsSync(o))return a("No package.json to audit");let n;try{n=JSON.parse(c.readFileSync(o,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=n.scripts||{},r=new Set(["log","ask","switch","recap","status","refresh","forget","prune","bookmark","init","setup","doctor","context","mcp","install-cursor-hooks","install-vscode-copilot-hooks","generate-skills","ai","telemetry","uninstall","check","sync","amp","commands"]),i=[];for(const[u,p]of Object.entries(t)){const k=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!k)continue;const m=k[1];r.has(m)||i.push({scriptName:u,verb:m})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(u=>`${u.scriptName} \u2192 infernoflow ${u.verb}`).join(", ");return l(`package.json references ${i.length} deprecated command(s): ${d}`,"Edit package.json scripts to use the current surface (run `infernoflow --help` to list verbs)")}function ce(e){const o=s.join(e,".gitignore");if(!c.existsSync(o))return{status:"info",message:".gitignore not found",fix:null,detail:null};const n=c.readFileSync(o,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(n)?a(".gitignore excludes node_modules"):l(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function ae(e,o){const n=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of n){const i=r.fix;if(i.startsWith("infernoflow ")){const d=i.slice(12).split(" ");C("infernoflow",d,{cwd:o,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function le(e){return e==="pass"?j("\u2714"):e==="warn"?x("\u26A0"):e==="fail"?v("\u2717"):S("\xB7")}function fe(e,o){const n={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)n[i.status]=(n[i.status]||0)+1;console.log(),console.log(` ${N("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${le(i.status)} ${N(i.label.padEnd(t))} ${i.message}`),i.detail&&console.log(` ${" ".repeat(t)} ${S(i.detail)}`),i.fix&&(i.status==="warn"||i.status==="fail")&&console.log(` ${" ".repeat(t)} ${P("fix:")} ${S(i.fix)}`);console.log();const r=n.fail>0?v("issues found"):n.warn>0?x("warnings"):j("all good");console.log(` ${r} \u2014 ${j(String(n.pass))} pass \xB7 ${x(String(n.warn))} warn \xB7 ${v(String(n.fail))} fail (${o}ms)`),console.log(),(n.warn>0||n.fail>0)&&(console.log(` Run ${P("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function ue(e){const o=e.slice(1),n=o.includes("--json"),t=o.includes("--fix"),r=process.cwd(),i=Date.now();let d="0.0.0-unknown";try{const{fileURLToPath:m}=await import("node:url"),g=s.dirname(m(import.meta.url)),O=s.join(g,"..","..","package.json");d=JSON.parse(c.readFileSync(O,"utf8")).version||d}catch{}const u=[f("Node.js version",()=>L()),f("infernoflow CLI",()=>J()),f("Git repository",()=>H(r)),f("inferno/ directory",()=>K(r)),f("Contract / mode",()=>U(r)),f("Scenarios",()=>W(r)),f("Changelog",()=>V(r)),f("CONTEXT.md",()=>X(r)),f("Git hooks",()=>Y(r)),f("Auto-capture protocol",()=>ne(r)),f("MCP server",()=>z(r)),f("MCP runtime version",()=>ee(r,d)),f("MCP registration",()=>q()),f("Generated copies",()=>B(r)),f("Secrets in memory",()=>Q(r)),f("API keys",()=>Z(r)),f("AI providers",()=>oe(r)),f("Cloud sync",()=>re()),f(".gitignore",()=>ce(r)),f("Router integrity",()=>ie()),f("npm scripts",()=>se(r)),await te().then(m=>({label:"Ollama (local AI)",...m}))],p=Date.now()-i;if(t){const m=ae(u,r);if(m.length)return n||(console.log(),m.forEach(g=>console.log(` ${j("\u2714")} Fixed: ${g}`)),console.log()),ue(n?["doctor","--json"]:["doctor"])}if(n){const m={pass:0,warn:0,fail:0,info:0};u.forEach(g=>m[g.status]=(m[g.status]||0)+1),console.log(JSON.stringify({ok:m.fail===0,counts:m,results:u,elapsed:p}));return}fe(u,p),u.some(m=>m.status==="fail")&&process.exit(1)}export{ue as doctorCommand};
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`)),process.exit(1)),
|
|
3
|
-
`+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!
|
|
1
|
+
import{redactSecrets as I}from"../security/redact.mjs";import{describeStore as V}from"../amp/io.mjs";import*as f from"node:fs";import*as E from"node:path";import"node:os";import{bold as j,cyan as x,gray as o,green as k,yellow as R,red as S,orange as W}from"../ui/output.mjs";import{ampPaths as q,appendEntry as G,readEntries as J,readConfig as Y,resolveRotationSettings as M,pruneEntries as X}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as _}from"../ruleFiles.mjs";function z(){return q(process.cwd())}const T=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],C=["worked","failed","partial","unknown"];function H(){return J(process.cwd())}function K(t,{auto:c=!1,quiet:n=!1}={}){const r=process.cwd(),h=E.join(r,".ai-memory"),g=E.join(r,"inferno");if(c&&!f.existsSync(h)&&!f.existsSync(g))return!1;!f.existsSync(h)&&!f.existsSync(g)&&(n||console.error(S(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
|
|
2
|
+
`)),process.exit(1)),G(r,t);try{M(Y(r)).auto&&X(r)}catch{}try{_(r)}catch{}return!0}function Q(){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 Z(t,c){const n=new Date(t.ts).toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"}),r=t.type||"note",h=r==="gotcha"?R:r==="decision"?x:r==="theme"?W:r==="preference"?x:r==="attempt"?o:r==="error"?S:d=>d,g=t.result?` [${t.result}]`:"",m=t.agent&&t.agent!=="human"?o(` (${t.agent})`):"";return` ${o(String(c+1).padStart(3))} ${o(n)} ${h(r)}${g} ${t.summary}${m}`}async function ce(t){const c=e=>t.includes(e),n=(e,u)=>{const a=t.indexOf(e);return a!==-1&&t[a+1]?t[a+1]:u};c("--no-color")&&(process.env.NO_COLOR="1");const r=c("--show"),h=c("--clear"),g=c("--json"),m=c("--auto"),d=c("--quiet"),w=n("--source",null);if(r||g){const e=H(),u=t[t.indexOf("--show")+1],a=u&&/^\d+$/.test(u)?parseInt(u):20,i=e.slice(-a);if(g){console.log(JSON.stringify(i,null,2));return}if(console.log(`
|
|
3
|
+
`+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+o(V(process.cwd()))),console.log(" "+"\u2500".repeat(50)),!i.length){console.log(o(`
|
|
4
4
|
No entries yet. Start logging with: infernoflow log "<what happened>"
|
|
5
|
-
`));return}console.log(o(` Showing last ${
|
|
6
|
-
`)),
|
|
7
|
-
`));return}console.log(
|
|
8
|
-
`));return}const
|
|
5
|
+
`));return}console.log(o(` Showing last ${i.length} of ${e.length} entries
|
|
6
|
+
`)),i.forEach((l,O)=>console.log(Z(l,e.length-i.length+O))),console.log();return}if(h){const e=z(),u=Date.now(),a=new Set([e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean));try{if(e.branchesDir&&f.existsSync(e.branchesDir))for(const l of f.readdirSync(e.branchesDir))l.endsWith(".jsonl")&&a.add(E.join(e.branchesDir,l))}catch{}const i=[];for(const l of a){if(!f.existsSync(l))continue;const O=l.replace(/\.jsonl$/,`-archive-${u}.jsonl`);try{f.renameSync(l,O),i.push(E.basename(O))}catch{}}if(!i.length){console.log(o(` Nothing to clear.
|
|
7
|
+
`));return}console.log(k(` \u2714 Session log archived (${i.length} file${i.length===1?"":"s"}) \u2014 entries cleared from active memory.
|
|
8
|
+
`));return}const B=new Set([n("--type",""),n("--result",""),n("--agent",""),n("--source",""),n("--file",""),n("--line",""),n("--tags",""),n("--detail",""),n("--detail-file","")].filter(Boolean)),v=t.slice(1).filter(e=>!e.startsWith("--")&&!B.has(e)).join(" ").trim();if(!v){console.log(`
|
|
9
9
|
`+j("\u{1F525} infernoflow log")+` \u2014 append to session memory
|
|
10
10
|
`),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>
|
|
11
|
-
`));return}const y=n("--type","note"),p=n("--result",null),
|
|
12
|
-
`)),process.exit(1)),p&&!
|
|
13
|
-
`)),process.exit(1));const L=F&&/^\d+$/.test(F)?parseInt(F,10):null,N=D?D.split(",").map(e=>e.trim()).filter(Boolean):null,
|
|
14
|
-
`)),process.exit(1)}else
|
|
15
|
-
`)
|
|
11
|
+
`));return}const y=n("--type","note"),p=n("--result",null),P=n("--agent",Q()),A=n("--file",null),F=n("--line",null),D=n("--tags",null);T.includes(y)||(d||console.error(S(` \u2718 Invalid type: ${y}. Valid: ${T.join(", ")}
|
|
12
|
+
`)),process.exit(1)),p&&!C.includes(p)&&(d||console.error(S(` \u2718 Invalid result: ${p}. Valid: ${C.join(", ")}
|
|
13
|
+
`)),process.exit(1));const L=F&&/^\d+$/.test(F)?parseInt(F,10):null,N=D?D.split(",").map(e=>e.trim()).filter(Boolean):null,b=n("--detail",null),$=n("--detail-file",null);let s=null;if($)try{s=$==="-"?f.readFileSync(0,"utf8"):f.readFileSync($,"utf8")}catch(e){d||console.error(S(` \u2718 Could not read --detail-file ${$}: ${e.message}
|
|
14
|
+
`)),process.exit(1)}else b&&(s=b);const U={ts:new Date().toISOString(),agent:P,type:y,summary:v,...p?{result:p}:{},...w?{source:w}:{},...A?{file:A}:{},...L?{line:L}:{},...N&&N.length?{tags:N}:{},...s&&s.trim()?{detail:s}:{},...m?{auto:!0}:{}};if(K(U,{auto:m,quiet:d})){if(t.includes("--refresh-rules"))try{_(process.cwd())}catch{}if(!d){const e=y!=="note"?x(` [${y}]`):"",u=p?o(` \u2192 ${p}`):"",a=w?o(` (via ${w})`):"",i=s&&s.trim()?o(` (+${s.length}-char detail)`):"",l=I(v);console.log(k(` \u2714 Logged${e}${u}${a}: `)+l.text+i+`
|
|
15
|
+
`),(l.kinds.length||s&&I(s).kinds.length)&&console.log(R(` \u26A0 secrets were redacted before saving (${[...new Set([...l.kinds,...s?I(s).kinds:[]])].join(", ")})`)+`
|
|
16
|
+
`)}}}export{ce as logCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as r from"node:path";import{fileURLToPath as o,pathToFileURL as m}from"node:url";const t=r.dirname(o(import.meta.url));async function s(a){const e=r.resolve(t,"../../templates/cursor/inferno-mcp-server.mjs");await import(m(e).href)}export{s as mcpCommand};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import*as C from"node:fs";import*as I from"node:path";import{execSync as
|
|
1
|
+
import{describeStore as M}from"../amp/io.mjs";import*as C from"node:fs";import*as I from"node:path";import{execSync as R}from"node:child_process";import{bold as b,cyan as y,gray as t,green as k,yellow as $,red as D}from"../ui/output.mjs";import{readEntries as v}from"../amp/io.mjs";const N="inferno",L=I.join(N,"contract.json");function A(n){try{return JSON.parse(C.readFileSync(n,"utf8"))}catch{return null}}function P(n,s){if(s){const g=s.match(/^(\d+)h$/i),f=s.match(/^(\d+)d$/i);if(g)return new Date(Date.now()-parseInt(g[1])*36e5);if(f)return new Date(Date.now()-parseInt(f[1])*864e5);const a=new Date(s);if(!isNaN(a.getTime()))return a}const o=new Date(Date.now()-864e5),e=[];for(const g of n)if(g.type==="handoff"){const f=new Date(g.ts||0);isNaN(f.getTime())||e.push(f)}if(e.length===0)return o;const l=300*1e3,u=e[e.length-1];if(Date.now()-u.getTime()<l){if(e.length>=2){const g=e[e.length-2];return g>o?g:o}return o}return u>o?u:o}function _(n){const s=process.cwd(),o=f=>{try{return R(f,{cwd:s,encoding:"utf8",timeout:5e3,stdio:["pipe","pipe","pipe"]}).trim()}catch{return""}},e=n.toISOString().slice(0,19),l=o("git diff --cached --name-only"),u=o("git diff --name-only"),r=o(`git log --since="${e}" --name-only --pretty=format:""`);return[...new Set([...l.split(`
|
|
2
2
|
`),...u.split(`
|
|
3
|
-
`),...
|
|
4
|
-
`)].map(f=>f.trim()).filter(Boolean))]}function
|
|
5
|
-
`)),process.exit(1));const g=
|
|
3
|
+
`),...r.split(`
|
|
4
|
+
`)].map(f=>f.trim()).filter(Boolean))]}function B(n,s){const o=[{keywords:["auth","login","logout","session","jwt","token","password"],topic:"authentication"},{keywords:["stripe","payment","checkout","billing","subscription"],topic:"payments"},{keywords:["upload","file","s3","storage","bucket","cdn"],topic:"file handling"},{keywords:["email","sendgrid","ses","smtp","nodemailer","twilio"],topic:"notifications"},{keywords:["db","database","prisma","mongoose","postgres","mysql","migration"],topic:"database"},{keywords:["deploy","docker","ci","workflow","action","kubernetes"],topic:"deployment"},{keywords:["cache","redis","memcache"],topic:"caching"},{keywords:["test","spec","jest","vitest","cypress","playwright"],topic:"testing"},{keywords:["config","env",".env","environment","secret"],topic:"configuration"},{keywords:["api","route","endpoint","controller","handler"],topic:"API routes"},{keywords:["ui","component","style","css","tailwind","theme"],topic:"UI/styles"}],e=s.map(r=>(r.summary||"").toLowerCase()).join(" "),l=[],u=new Set;for(const r of o){if(u.has(r.topic))continue;const g=n.filter(a=>r.keywords.some(w=>a.toLowerCase().includes(w)));!g.length||r.keywords.some(a=>e.includes(a))||(u.add(r.topic),l.push({topic:r.topic,files:g.slice(0,3),suggestedType:"gotcha"}))}return l}function H(n){const s=new Set(n.map(l=>l.type));let o=0;const e=[];return n.length>0?(o+=20,e.push({ok:!0,label:`${n.length} entr${n.length!==1?"ies":"y"} logged`})):e.push({ok:!1,label:"nothing logged this session"}),s.has("gotcha")?(o+=35,e.push({ok:!0,label:"gotchas captured"})):e.push({ok:!1,label:"no gotchas (most valuable \u2014 log landmines!)"}),s.has("decision")?(o+=25,e.push({ok:!0,label:"decisions recorded"})):e.push({ok:!1,label:"no decisions recorded"}),s.has("attempt")&&(o+=10,e.push({ok:!0,label:"attempts tracked"})),s.has("preference")&&(o+=10,e.push({ok:!0,label:"preferences noted"})),{score:Math.min(o,100),checks:e}}function J(n){if(!n)return"";const s=new Date(n),o=Date.now()-s.getTime(),e=Math.floor(o/6e4);if(e<60)return`${e}m ago`;const l=Math.floor(o/36e5);return l<24?`${l}h ago`:`${Math.floor(o/864e5)}d ago`}const U={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u25C8",note:"\xB7",error:"\u2717",handoff:"\u2192"},G={gotcha:$,decision:k,attempt:y,preference:y,theme:y,note:t,error:D,handoff:t};function Y(n){const s=G[n.type]||t,o=U[n.type]||"\xB7",e=n.result?t(` [${n.result}]`):"",l=t(` (${J(n.ts)})`);console.log(` ${s(o+" "+(n.type||"note").padEnd(11))}${e}${l}`),console.log(` ${n.summary}`)}async function W(n=[]){const s=n,o=s.includes("--json"),e=s.includes("--brief"),l=s.indexOf("--since"),u=l!==-1?s[l+1]:null,r=process.cwd();!C.existsSync(I.join(r,N))&&!C.existsSync(I.join(r,".ai-memory"))&&(o||console.error(D(` \u2718 not initialized \u2014 run: infernoflow init
|
|
5
|
+
`)),process.exit(1));const g=v(r),f=P(g,u),a=g.filter(d=>new Date(d.ts||0)>f),w=_(f),m=B(w,a),{score:i,checks:O}=H(a),F=A(I.join(r,L));if(o){console.log(JSON.stringify({sessionStart:f.toISOString(),entries:a,changedFiles:w,unloggedTopics:m,health:{score:i,checks:O}},null,2));return}if(e){const d=i>=80?"A":i>=60?"B":i>=40?"C":"D",p=i>=60?k:i>=40?$:D;console.log(p(`Session health: ${d} (${i}/100)`)+t(` \u2014 ${a.length} entries logged`)),m.length&&console.log($(` ${m.length} topic${m.length!==1?"s":""} changed but not logged: `)+m.map(c=>c.topic).join(", "));return}const S=t(" "+"\u2500".repeat(52));console.log(),console.log(" "+b("\u{1F525} infernoflow recap")),console.log(" "+t(M(r))),F?.policyId&&console.log(t(` Project: ${F.policyId}`));const j=f.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"});if(console.log(t(` Session since: ${j}`)),console.log(S),console.log(),console.log(" "+b("Captured this session")),console.log(),a.length===0)console.log(t(" Nothing logged yet this session."));else{const d=["gotcha","decision","attempt","preference","theme","note","error"],p=new Map;for(const c of a){const h=c.type||"note";p.has(h)||p.set(h,[]),p.get(h).push(c)}for(const c of d){const h=p.get(c);if(h?.length)for(const x of h)console.log(),Y(x)}}if(m.length>0){console.log(),console.log(S),console.log(),console.log(" "+b("Changed but not logged")+t(" (git diff since session start)")),console.log();for(const{topic:d,files:p}of m){console.log($(` ? ${d}`));for(const c of p)console.log(t(` ${c}`))}console.log(),console.log(t(" Any gotchas or decisions from these areas worth capturing?")),console.log(t(" Run: ")+y('infernoflow log "<what happened>" --type gotcha'))}else w.length>0&&(console.log(),console.log(S),console.log(),console.log(k(" \u2714 ")+t(`${w.length} changed files \u2014 all topics appear to be logged`)));console.log(),console.log(S),console.log(),console.log(" "+b("Session health")),console.log();const E=i>=80?"A":i>=60?"B":i>=40?"C":"D",T=i>=60?k:i>=40?$:D;console.log(` ${T(b(`${E}`))} ${T(`${i}/100`)}`),console.log();for(const{ok:d,label:p}of O){const c=d?k(" \u2714"):$(" \xB7");console.log(`${c} ${d?p:t(p)}`)}{const d=a.filter(h=>h.type==="gotcha").length,p=a.filter(h=>h.type==="decision").length,c=[];if(d===0?c.push(y('infernoflow log "..." --type gotcha')+t(" \u2014 adds 35 pts")):d<3&&i<80&&c.push(t(` ${3-d} more gotcha(s) would push you higher`)),p===0&&c.push(y('infernoflow log "..." --type decision')+t(" \u2014 adds 25 pts")),i>=60&&i<80&&c.push(t(" Almost B! One more entry gets you there.")),i>=80&&c.push(k(" Great session \u2014 your handoff will be excellent.")),c.length){console.log(),console.log(t(" How to improve:"));for(const h of c)console.log(" "+h)}}(a.length>0||m.length>0)&&(console.log(),console.log(S),console.log(),console.log(t(" Before your next session:")),console.log(t(" ")+y("infernoflow switch")+t(" \u2014 generate a handoff summary for the next AI agent")),console.log(t(" ")+y("infernoflow ask --recent")+t(" \u2014 review what's in memory before starting"))),console.log()}export{W as recapCommand};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as l from"node:fs";import*as d from"node:path";import"node:os";import{fileURLToPath as F}from"node:url";import{execSync as J}from"node:child_process";import{detectIdeContext as H}from"../ai/ideDetection.mjs";import{header as O,ok as y,warn as I,info as w,done as L,cyan as i,yellow as j,bold as h,green as f,gray as m}from"../ui/output.mjs";import"../cursorHooksInstall.mjs";import{refreshSecuritySensitiveCopies as N}from"../securityRefresh.mjs";import{updateProjectMcpJson as T,updateIdeMcpJson as U,updateClaudeDesktopPerProject as E,removeLegacyUserLevelEntry as S,claudeJsonPath as V,claudeDesktopConfigPath as C,runningFromNpxCache as W}from"../mcpRegistration.mjs";import"../vsCodeCopilotHooksInstall.mjs";import{updateInjectionConfig as K}from"../amp/io.mjs";import{findProjectRoot as q}from"../projectRoot.mjs";import{injectionPatchFromArgs as B}from"./refresh.mjs";const G=d.dirname(F(import.meta.url));function v(){return d.resolve(G,"../../templates")}function z(n){try{return J(`npx infernoflow ${n}`,{encoding:"utf8",cwd:process.cwd(),timeout:6e4,stdio:["inherit","pipe","pipe"]})}catch(s){return s.stdout||s.stderr||s.message}}function b(n){if(!l.existsSync(n))return{data:{},existed:!1,corrupt:!1,backup:null};let s;try{s=l.readFileSync(n,"utf8")}catch{return{data:{},existed:!0,corrupt:!1,backup:null}}try{const c=JSON.parse(s);return!c||typeof c!="object"||Array.isArray(c)?{data:{},existed:!0,corrupt:!1,backup:null}:{data:c,existed:!0,corrupt:!1,backup:null}}catch{let c=null;try{c=`${n}.corrupt-${Date.now()}.bak`,l.writeFileSync(c,s,"utf8")}catch{c=null}return{data:{},existed:!0,corrupt:!0,backup:c}}}function Q(n){const s=d.join(n,".git");if(!l.existsSync(s))return{installed:!1,skipped:"not-a-git-repo"};const c=d.join(s,"hooks"),o=d.join(c,"post-commit"),r='infernoflow log "commit: $(git log -1 --pretty=%s)" --type note --source git-hook --auto --quiet >/dev/null 2>&1 || true';try{if(l.mkdirSync(c,{recursive:!0}),l.existsSync(o)){const t=l.readFileSync(o,"utf8");if(t.includes("infernoflow"))return{installed:!1,already:!0};const u=t.replace(/\s*$/,"")+`
|
|
2
2
|
|
|
3
3
|
# infernoflow auto-capture
|
|
4
4
|
`+r+`
|
|
5
|
-
`;
|
|
6
|
-
`);
|
|
5
|
+
`;l.writeFileSync(o,u,"utf8")}else{const t=["#!/bin/sh","# infernoflow: auto-capture the commit subject into session memory.","# Best-effort and non-blocking \u2014 never fails a commit.",r,""].join(`
|
|
6
|
+
`);l.writeFileSync(o,t,"utf8")}try{l.chmodSync(o,493)}catch{}return{installed:!0,path:o}}catch(t){return{installed:!1,error:t.message}}}const X=2,_=`#!/usr/bin/env node
|
|
7
7
|
// infernoflow UserPromptSubmit hook (Claude Code / Cursor).
|
|
8
|
-
// infernoflow-hook-version: ${
|
|
8
|
+
// infernoflow-hook-version: ${X}
|
|
9
9
|
// Logs a best-effort 'attempt' entry when the user's prompt shows frustration,
|
|
10
10
|
// so the highest-value capture signal doesn't depend on the model remembering.
|
|
11
11
|
// Never blocks the prompt. Never uses a shell.
|
|
@@ -53,9 +53,7 @@ if (prompt && MARKERS.some((re) => re.test(prompt))) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
process.exit(0);
|
|
56
|
-
`;function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
${
|
|
60
|
-
`),U(n?"init --yes":"init"));const d=H(t);if(Object.keys(d).length)try{T(i,d),k("Injection config updated \u2192 "+JSON.stringify(d))}catch{}console.log(),v("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const e=Z(i,{silent:!1});console.log(),D("infernoflow ready"),console.log(`
|
|
61
|
-
${h("What was set up:")}`),console.log(` ${g("\u2714")} MCP server installed \u2192 ${p(".cursor/inferno-mcp-server.mjs")}`),e.cursorMcp&&console.log(` ${g("\u2714")} Cursor MCP config \u2192 ${p(".cursor/mcp.json")}`),e.vscodeMcp&&console.log(` ${g("\u2714")} VS Code Copilot MCP config \u2192 ${p(".vscode/mcp.json")}`),e.claudeJson&&console.log(` ${g("\u2714")} Claude Code MCP config \u2192 ${p("~/.claude.json")}`),e.claudeSettings&&console.log(` ${g("\u2714")} Auto-approved tools \u2192 ${p(".claude/settings.json")}`),e.claudeDesktop&&console.log(` ${g("\u2714")} Claude Desktop MCP config \u2192 ${p("claude_desktop_config.json")}`),e.gitHooks&&console.log(` ${g("\u2714")} Git post-commit hook \u2192 ${p(".git/hooks/post-commit")}`),e.captureHook&&console.log(` ${g("\u2714")} Capture hook (Claude Code/Cursor) \u2192 ${p(".claude/hooks/log-frustration.mjs")}`);try{const{detectStaleMcpRuntime:f}=await import("../mcpRuntime.mjs"),{readFileSync:m}=await import("node:fs"),{dirname:R,join:x}=await import("node:path"),{fileURLToPath:O}=await import("node:url"),P=R(O(import.meta.url)),F=x(P,"..","..","package.json"),$=JSON.parse(m(F,"utf8")).version,S=f(i,$);S&&(console.log(),console.log(` ${C("\u26A0")} ${h("Restart required:")} ${S.message}`))}catch{}console.log(),console.log(` ${h("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${p('"call the amp_write tool with a test note"')}`),console.log()}export{b as CAPTURE_HOOK_SCRIPT,B as CAPTURE_HOOK_VERSION,z as MCP_TOOLS,Z as autoSetupMcp,V as claudeDesktopConfigPath,G as installClaudeCodeCaptureHook,q as installGitHooks,de as setupCommand,K as updateClaudeDesktopConfig,Q as updateClaudeJson,X as updateVscodeMcpJson,Y as writeClaudeSettings};
|
|
56
|
+
`;function Y(n){const s=d.join(n,".claude","hooks"),c=d.join(s,"log-frustration.mjs");try{l.mkdirSync(s,{recursive:!0}),l.writeFileSync(c,_,"utf8");try{l.chmodSync(c,493)}catch{}}catch(p){return{installed:!1,error:p.message}}const o=d.join(n,".claude","settings.json"),{data:r}=b(o);(!r.hooks||typeof r.hooks!="object")&&(r.hooks={}),Array.isArray(r.hooks.UserPromptSubmit)||(r.hooks.UserPromptSubmit=[]);const t="node .claude/hooks/log-frustration.mjs",u=r.hooks.UserPromptSubmit.some(p=>Array.isArray(p?.hooks)&&p.hooks.some(e=>typeof e?.command=="string"&&e.command.includes("log-frustration.mjs")));u||r.hooks.UserPromptSubmit.push({hooks:[{type:"command",command:t}]});try{l.mkdirSync(d.dirname(o),{recursive:!0}),l.writeFileSync(o,JSON.stringify(r,null,2),"utf8")}catch(p){return{installed:!0,registered:!1,error:p.message}}return{installed:!0,registered:!u}}const Z=["infernoflow_status","infernoflow_run","infernoflow_apply","infernoflow_check","infernoflow_context","infernoflow_implement","infernoflow_git_drift","infernoflow_scan_ui","infernoflow_review","amp_read","amp_write","amp_search","amp_handoff","amp_health"];function ee(n,s){const c=d.join(n,".claude"),o=d.join(c,"settings.json"),{data:r}=b(o),t=new Set(r.allowedTools||[]);for(const p of Z)t.add(`mcp__infernoflow__${p}`);const u={...r,allowedTools:[...t]};return l.mkdirSync(c,{recursive:!0}),l.writeFileSync(o,JSON.stringify(u,null,2),"utf8"),o}function oe(n,{silent:s=!1}={}){const c=v(),o=s?()=>{}:e=>y(e),r=s?()=>{}:e=>I(e),t={mcpServer:!1,projectMcpJson:!1,claudeJson:!1,claudeSettings:!1,claudeDesktop:!1,gitHooks:!1,captureHook:!1,backups:[]},u=(()=>{try{return q(n)}catch{return n}})();try{const e=N(n,{captureHookScript:_});e.length&&(t.securityRefreshed=e,e.some(a=>a.endsWith("inferno-mcp-server.mjs"))&&(t.mcpServer=!0),o("Security update: replaced outdated "+e.map(a=>i(a)).join(", ")))}catch{}W()&&r("infernoflow is running from the npx cache \u2014 MCP configs will point there and break when the cache is cleared. Install it: npm i -g infernoflow, then run: infernoflow setup --yes");let p=!1;try{const e=T(u);e.skipped==="tracked"?r(".mcp.json is tracked by git \u2014 not adding this machine's paths to it. Add the infernoflow server with: claude mcp add infernoflow -s local -- node <infernoflow>/bin/infernoflow.mjs mcp"):e.skipped?r(".mcp.json is unreadable \u2014 left unchanged"):(p=!0,e.updated&&(t.projectMcpJson=!0,o("Registered MCP server in "+i(".mcp.json")+m(" (Claude Code, this project only)"))),e.gitignored&&o("Added "+i(".mcp.json")+" to .gitignore (it holds this machine's paths)"))}catch(e){r(".mcp.json update skipped: "+e.message)}if(p)try{const e=S(V());e.removed&&(t.legacyClaudeJsonRemoved=e,o("Removed the old user-level infernoflow entry from "+i("~/.claude.json")+m(` (was pinned to ${e.pinnedTo||"one project"}; backup: ${e.backup})`)))}catch(e){r("~/.claude.json cleanup skipped: "+e.message)}for(const[e,a]of[["vscode",".vscode/mcp.json"],["cursor",".cursor/mcp.json"]])try{U(u,e).updated&&(t[e==="vscode"?"vscodeMcp":"cursorMcp"]=!0,o("Registered MCP server in "+i(a)))}catch(g){r(a+" update skipped: "+g.message)}try{const e=E(u);if(e.updated&&(t.claudeDesktop=!0,o("Registered MCP server "+i(e.name)+" in "+i("claude_desktop_config.json")+m(" (Claude Desktop app)"))),!e.skipped){const a=S(C());a.removed&&(t.legacyDesktopRemoved=a,o("Removed the old pinned infernoflow entry from "+i("claude_desktop_config.json")+m(` (backup: ${a.backup})`)))}}catch(e){r("Claude Desktop config skipped: "+e.message)}try{ee(n,!1),t.claudeSettings=!0,o("Pre-approved infernoflow tools in "+i(".claude/settings.json"))}catch(e){r(".claude/settings.json skipped: "+e.message)}try{Q(n).installed&&(t.gitHooks=!0,o("Installed git post-commit hook \u2192 "+i(".git/hooks/post-commit")))}catch(e){r("git hook install skipped: "+e.message)}try{Y(n).installed&&(t.captureHook=!0,o("Installed capture hook \u2192 "+i(".claude/hooks/log-frustration.mjs")))}catch(e){r("capture hook install skipped: "+e.message)}return t}async function he(n){const s=process.cwd(),c=n.includes("--force")||n.includes("-f"),o=n.includes("--yes")||n.includes("-y"),r=v();O("infernoflow setup");const{ideDetected:t}=H("auto");w(`IDE detected: ${h(t==="cursor"?"Cursor":t==="vscode"?"VS Code":t==="windsurf"?"Windsurf":"unknown")}`);const p=d.join(s,".ai-memory");l.existsSync(p)?y(".ai-memory/ already exists \u2014 skipping init"):(console.log(`
|
|
57
|
+
${j(".ai-memory/")} not found \u2014 running init ...
|
|
58
|
+
`),z(o?"init --yes":"init"));const e=B(n);if(Object.keys(e).length)try{K(s,e),y("Injection config updated \u2192 "+JSON.stringify(e))}catch{}console.log(),w("Wiring up MCP servers for Cursor / VS Code Copilot / Claude Code ...");const a=oe(s,{silent:!1});console.log(),L("infernoflow ready"),console.log(`
|
|
59
|
+
${h("What was set up:")}`),console.log(` ${f("\u2714")} MCP server \u2192 ${i("infernoflow mcp")} ${m("(runs from the installed package)")}`),a.projectMcpJson&&console.log(` ${f("\u2714")} Claude Code MCP config \u2192 ${i(".mcp.json")} ${m("(this project)")}`),a.cursorMcp&&console.log(` ${f("\u2714")} Cursor MCP config \u2192 ${i(".cursor/mcp.json")}`),a.vscodeMcp&&console.log(` ${f("\u2714")} VS Code Copilot MCP config \u2192 ${i(".vscode/mcp.json")}`),a.legacyClaudeJsonRemoved&&console.log(` ${f("\u2714")} Removed old pinned entry from ${i("~/.claude.json")} ${m("(backup saved)")}`),a.claudeSettings&&console.log(` ${f("\u2714")} Auto-approved tools \u2192 ${i(".claude/settings.json")}`),a.claudeDesktop&&console.log(` ${f("\u2714")} Claude Desktop MCP config \u2192 ${i("claude_desktop_config.json")}`),a.gitHooks&&console.log(` ${f("\u2714")} Git post-commit hook \u2192 ${i(".git/hooks/post-commit")}`),a.captureHook&&console.log(` ${f("\u2714")} Capture hook (Claude Code/Cursor) \u2192 ${i(".claude/hooks/log-frustration.mjs")}`);try{const{detectStaleMcpRuntime:g}=await import("../mcpRuntime.mjs"),{readFileSync:$}=await import("node:fs"),{dirname:x,join:P}=await import("node:path"),{fileURLToPath:M}=await import("node:url"),R=x(M(import.meta.url)),A=P(R,"..","..","package.json"),D=JSON.parse($(A,"utf8")).version,k=g(s,D);k&&(console.log(),console.log(` ${j("\u26A0")} ${h("Restart required:")} ${k.message}`))}catch{}console.log(),console.log(` ${h("Next step:")} Restart your AI tool. Test by asking:`),console.log(` ${i('"call the amp_write tool with a test note"')}`),console.log()}export{_ as CAPTURE_HOOK_SCRIPT,X as CAPTURE_HOOK_VERSION,Z as MCP_TOOLS,oe as autoSetupMcp,C as claudeDesktopConfigPath,Y as installClaudeCodeCaptureHook,Q as installGitHooks,he as setupCommand,ee as writeClaudeSettings};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import*as e from"node:fs";import*as l from"node:path";import{header as
|
|
1
|
+
import*as e from"node:fs";import{describeStore as I}from"../amp/io.mjs";import*as l from"node:path";import{header as L,ok as V,fail as O,warn as A,section as $,bold as r,cyan as v,yellow as u,gray as n,green as h,red as R,white as W}from"../ui/output.mjs";import{readEntries as _}from"../amp/io.mjs";function M(g){const t=Math.floor((Date.now()-g)/1e3);return t<60?"just now":t<3600?`${Math.floor(t/60)}m ago`:t<86400?`${Math.floor(t/3600)}h ago`:`${Math.floor(t/86400)}d ago`}function z(g,t){const a=new Set;if(e.existsSync(g))for(const i of e.readdirSync(g).filter(y=>y.endsWith(".json")))try{(JSON.parse(e.readFileSync(l.join(g,i),"utf8")).capabilitiesCovered||[]).forEach(E=>a.add(E))}catch{}return{covered:t.filter(i=>a.has(i)),uncovered:t.filter(i=>!a.has(i))}}async function X(g=[]){const t=g.includes("--json"),a=process.cwd(),i=l.join(a,"inferno"),y=l.join(a,".ai-memory");t||L("status"),!e.existsSync(i)&&!e.existsSync(y)&&(t&&(console.log(JSON.stringify({ok:!1,error:"not_initialized",hint:"Run: infernoflow init"},null,2)),process.exit(1)),O("not initialized \u2014 neither .ai-memory/ nor inferno/ found","Run: infernoflow init"),console.log(),process.exit(1));const E=l.join(y,"amp.json"),J=l.join(i,"config.json"),m=l.join(i,"contract.json"),U=e.existsSync(E)||e.existsSync(y),D=(()=>{try{return JSON.parse(e.readFileSync(J,"utf8")).mode||null}catch{return null}})();if(U||D==="memory"||!e.existsSync(m)&&e.existsSync(J)){const o=_(a),s=o.filter(d=>d.type==="gotcha").length,c=o.filter(d=>d.type==="decision").length,p=o.filter(d=>d.type==="attempt").length,f=o[o.length-1];if(t){console.log(JSON.stringify({ok:!0,mode:"memory",entries:o.length,gotchas:s,decisions:c,attempts:p,lastEntry:f?f.ts:null},null,2));return}$("Session memory"),console.log(` ${n(I(a))}`),console.log(` ${n("entries")} ${r(String(o.length))}`),console.log(` ${n("gotchas")} ${r(String(s))}`),console.log(` ${n("decisions")} ${r(String(c))}`),console.log(` ${n("attempts")} ${r(String(p))}`),f&&console.log(` ${n("last entry")} ${n(M(new Date(f.ts).getTime()))}`),console.log(),o.length===0?console.log(` ${u("\u25CF")} ${r(u("empty"))} ${n("\u2014 log your first gotcha:")} ${v('infernoflow log "..." --type gotcha')}`):console.log(` ${h("\u25CF")} ${r(h("ready"))} ${n("\u2014 run")} ${v("infernoflow recap")} ${n("for the full session summary")}`),e.existsSync(m)?console.log():console.log(`
|
|
2
2
|
${n("Want capability contracts + CI gates? Run:")} ${v("infernoflow init --mode full")}
|
|
3
|
-
`);return}e.existsSync(m)||(t&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),
|
|
3
|
+
`);return}e.existsSync(m)||(t&&(console.log(JSON.stringify({ok:!1,error:"contract_not_found"},null,2)),process.exit(1)),O("contract.json not found"),console.log(),process.exit(1));const S=JSON.parse(e.readFileSync(m,"utf8")),w=S.capabilities||[],k=e.statSync(m),C=l.join(i,"scenarios"),N=l.join(i,"CHANGELOG.md"),F=l.join(i,"capabilities.json"),{covered:H,uncovered:j}=z(C,w),G=e.existsSync(N)&&/##\s+Unreleased/i.test(e.readFileSync(N,"utf8")),x=[];j.length>0&&x.push(`${j.length} capabilities without scenario coverage`),G||x.push("CHANGELOG missing ## Unreleased section");const b=x.length===0;if(t){const o={ok:b,driftReasons:x,project:{policyId:S.policyId||null,policyVersion:S.policyVersion||null,lastChange:M(k.mtimeMs)},capabilities:{total:w.length,uncovered:j},changelog:{hasUnreleased:G}};console.log(JSON.stringify(o,null,2)),process.exit(b?0:1)}b||($("Drift"),x.forEach(o=>console.log(` ${u("\u26A0")} ${o}`))),$("Project"),console.log(` ${n("policy")} ${r(S.policyId||"\u2014")}`),console.log(` ${n("version")} ${r("v"+(S.policyVersion||"?"))}`),console.log(` ${n("last change")} ${n(M(k.mtimeMs))}`),$(`Capabilities ${n("("+w.length+")")}`);let P={};if(e.existsSync(F))try{(JSON.parse(e.readFileSync(F,"utf8")).capabilities||[]).forEach(s=>{P[s.id]=s})}catch{}if(w.forEach(o=>{const s=P[o],p=H.includes(o)?h("\u2714"):R("\u2718"),f=s?.title?n(` \u2014 ${s.title}`):"",d=s?.since?n(` [${s.since}]`):"";console.log(` ${p} ${W(o)}${f}${d}`)}),j.length>0?console.log(`
|
|
4
4
|
${u("\u26A0")} ${j.length} capability(ies) lack scenario coverage`):console.log(`
|
|
5
|
-
${h("\u2714")} All capabilities have scenario coverage`),$("Scenarios"),e.existsSync(C)){const o=e.readdirSync(C).filter(s=>s.endsWith(".json"));o.length===0?A("No scenario files \u2014 add .json files to inferno/scenarios/"):o.forEach(s=>{try{const c=JSON.parse(e.readFileSync(l.join(C,s),"utf8")),p=c.steps?.length||0,
|
|
6
|
-
`).filter(c=>/^##\s/.test(c)).slice(0,3).forEach(c=>console.log(` ${n(c)}`))}else
|
|
5
|
+
${h("\u2714")} All capabilities have scenario coverage`),$("Scenarios"),e.existsSync(C)){const o=e.readdirSync(C).filter(s=>s.endsWith(".json"));o.length===0?A("No scenario files \u2014 add .json files to inferno/scenarios/"):o.forEach(s=>{try{const c=JSON.parse(e.readFileSync(l.join(C,s),"utf8")),p=c.steps?.length||0,f=(c.capabilitiesCovered||[]).length;console.log(` ${h("\u2714")} ${v(s)} ${n(`\u2014 ${p} steps, ${f} caps covered`)}`)}catch{console.log(` ${R("\u2718")} ${v(s)} ${n("\u2014 invalid JSON")}`)}})}else A("scenarios/ directory not found");if($("Changelog"),e.existsSync(N)){const o=e.readFileSync(N,"utf8");/##\s+Unreleased/i.test(o)?V("Has ## Unreleased section"):O("Missing ## Unreleased section"),o.split(`
|
|
6
|
+
`).filter(c=>/^##\s/.test(c)).slice(0,3).forEach(c=>console.log(` ${n(c)}`))}else O("inferno/CHANGELOG.md not found");console.log(),console.log(b?` ${h("\u25CF")} ${r(h("ready"))} ${n("\u2014 run infernoflow check for full validation")}`:` ${u("\u25CF")} ${r(u("needs attention"))} ${n("\u2014 run infernoflow check for details")}`),console.log()}export{X as statusCommand};
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import*as a from"node:fs";import*as i from"node:path";import*as
|
|
2
|
-
`),l=c.findIndex(f=>f.includes(
|
|
3
|
-
`).trim();!r||r==="#!/bin/sh"||r==="#!/bin/bash"?e.push({type:"rm",path:s,desc:"infernoflow-only hook"}):e.push({type:"edit",path:s,desc:"remove infernoflow section (preserve existing hooks)"})}return e}function
|
|
4
|
-
`,"utf8")}catch{}}}function
|
|
5
|
-
`,"utf8")}catch{}}}function
|
|
6
|
-
`,"utf8")}catch{}}}function
|
|
7
|
-
`,"utf8")}catch{}}}function
|
|
8
|
-
`),r=l.findIndex(g=>g.includes(
|
|
1
|
+
import{claudeDesktopConfigPath as U,isInfernoflowEntry as x}from"../mcpRegistration.mjs";import{findProjectRoot as L}from"../projectRoot.mjs";import*as a from"node:fs";import*as i from"node:path";import*as W from"node:os";import*as $ from"node:readline";import{bold as I,gray as h,green as D,yellow as P,red as J}from"../ui/output.mjs";const y="inferno",_="CLAUDE.md",v=".claude",k=".cursor",E=i.join(k,"inferno-mcp-server.mjs"),N="inferno-mcp-server.mjs",d=i.join(k,"hooks.json"),b=i.join(k,"hooks","inferno-session-draft.mjs"),S=i.join(k,"mcp.json"),w=i.join(W.homedir(),".claude.json"),T=[".git/hooks/post-commit",".git/hooks/pre-push"],O="# infernoflow";function u(t){return a.existsSync(t)}function m(t){try{return JSON.parse(a.readFileSync(t,"utf8"))}catch{return null}}function F(t){try{return a.readFileSync(t,"utf8")}catch{return null}}function G(t){return new Promise(e=>{const s=$.createInterface({input:process.stdin,output:process.stdout});s.question(t,o=>{s.close(),e(o)})})}function V(t,e,s){const o=[],n=i.join(t,y);if(!u(n))return o;if(s)return o.push({type:"skip",path:y,reason:"--keep-inferno"}),o;if(e){const c=a.readdirSync(n);for(const l of c)if(l==="sessions.jsonl")o.push({type:"skip",path:i.join(y,l),reason:"--keep-memory"});else{const r=i.join(n,l);a.statSync(r).isDirectory()?o.push({type:"rmdir",path:i.join(y,l)}):o.push({type:"rm",path:i.join(y,l)})}}else o.push({type:"rmdir",path:y});return o}function q(t){const e=i.join(t,_);return u(e)?[{type:"rm",path:_}]:[]}function B(t){const e=[],s=i.join(t,v,"settings.json");if(!u(s))return e;const o=m(s),n=o?.tools?.some?.(l=>l.startsWith?.("mcp__infernoflow")),c=o&&Object.keys(o).some(l=>l==="tools"?(o.tools||[]).some(r=>!r.startsWith("mcp__infernoflow")):l!=="tools");return n&&!c?e.push({type:"rm",path:i.join(v,"settings.json"),desc:"auto-approved tools"}):n&&e.push({type:"edit",path:i.join(v,"settings.json"),desc:"remove infernoflow tools (preserve other content)"}),e}function Y(t){const e=[];u(i.join(t,E))&&e.push({type:"rm",path:E}),u(i.join(t,N))&&e.push({type:"rm",path:N}),u(i.join(t,b))&&e.push({type:"rm",path:b});const s=i.join(t,d);if(u(s)){const n=m(s)?.hooks,c=Array.isArray(n)?n:n&&typeof n=="object"?Object.values(n).flatMap(r=>Array.isArray(r)?r:[r]).filter(Boolean):[];c.length>0&&c.every(r=>(r?.name||r?.command||"").includes("inferno"))?e.push({type:"rm",path:d,desc:"infernoflow-only hooks config"}):e.push({type:"edit",path:d,desc:"remove infernoflow hook entry (preserve others)"})}return e}function z(t){const e=i.join(t,S);if(!u(e))return[];const s=m(e);return s?.mcpServers?.infernoflow?Object.keys(s.mcpServers||{}).filter(n=>n!=="infernoflow").length===0&&Object.keys(s).length===1?[{type:"rm",path:S,desc:"infernoflow-only file"}]:[{type:"edit",path:S,desc:'remove "infernoflow" key (preserve other servers)'}]:[]}function Q(t){const e=[];u(w)&&x(m(w)?.mcpServers?.infernoflow)&&e.push({type:"edit",path:"~/.claude.json",desc:'remove "infernoflow" MCP entry (preserve other entries)',_realPath:w,_key:"infernoflow"});const s=i.join(t,".mcp.json");u(s)&&m(s)?.mcpServers?.infernoflow&&e.push({type:"edit",path:".mcp.json",desc:'remove "infernoflow" MCP entry (preserve other servers)',_realPath:s,_key:"infernoflow"});const o=i.join(t,".vscode","mcp.json");u(o)&&m(o)?.servers?.infernoflow&&e.push({type:"edit",path:".vscode/mcp.json",desc:'remove "infernoflow" MCP entry (preserve other servers)',_realPath:o,_key:"infernoflow",_serversKey:"servers"});const n=U(),c=u(n)?m(n):null;for(const[l,r]of Object.entries(c?.mcpServers||{})){if(!l.startsWith("infernoflow"))continue;const f=r?.env?.INFERNOFLOW_PROJECT_DIR;f&&i.resolve(f)===i.resolve(t)&&e.push({type:"edit",path:"claude_desktop_config.json",desc:`remove "${l}" MCP entry (preserve other entries)`,_realPath:n,_key:l})}return e}function X(t){const e=[];for(const s of T){const o=i.join(t,s);if(!u(o))continue;const n=F(o);if(!n?.includes(O))continue;const c=n.split(`
|
|
2
|
+
`),l=c.findIndex(f=>f.includes(O)),r=c.slice(0,l).join(`
|
|
3
|
+
`).trim();!r||r==="#!/bin/sh"||r==="#!/bin/bash"?e.push({type:"rm",path:s,desc:"infernoflow-only hook"}):e.push({type:"edit",path:s,desc:"remove infernoflow section (preserve existing hooks)"})}return e}function Z(t,e,s){for(const o of e){if(o.type==="skip")continue;const n=i.join(t,o.path);if(!s)try{o.type==="rmdir"?a.rmSync(n,{recursive:!0,force:!0}):a.unlinkSync(n)}catch{}}}function ee(t,e){if(!e)try{a.unlinkSync(i.join(t,_))}catch{}}function oe(t,e,s){const o=i.join(t,v,"settings.json");for(const n of e)if(!s){if(n.type==="rm")try{a.unlinkSync(i.join(t,n.path))}catch{}else if(n.type==="edit")try{const c=m(o);c?.tools&&(c.tools=c.tools.filter(l=>!l.startsWith("mcp__infernoflow"))),a.writeFileSync(o,JSON.stringify(c,null,2)+`
|
|
4
|
+
`,"utf8")}catch{}}}function ne(t,e,s){if(!s){for(const o of e)if(o.type==="rm")try{a.unlinkSync(i.join(t,o.path))}catch{}else if(o.type==="edit"&&o.path===d)try{const n=m(i.join(t,d));n?.hooks&&(n.hooks=n.hooks.filter(c=>!(c.name||c.command||"").includes("inferno"))),a.writeFileSync(i.join(t,d),JSON.stringify(n,null,2)+`
|
|
5
|
+
`,"utf8")}catch{}}}function te(t,e,s){const o=i.join(t,S);for(const n of e)if(!s){if(n.type==="rm")try{a.unlinkSync(o)}catch{}else if(n.type==="edit")try{const c=m(o);if(!c)continue;c?.mcpServers?.infernoflow&&delete c.mcpServers.infernoflow,a.writeFileSync(o,JSON.stringify(c,null,2)+`
|
|
6
|
+
`,"utf8")}catch{}}}function se(t,e){for(const s of t){if(e)continue;const o=s._realPath||w;if(s.type==="edit")try{const n=m(o);if(!n)continue;const c=s._key||"infernoflow",l=s._serversKey?n?.[s._serversKey]:n?.mcpServers;if(!l?.[c])continue;delete l[c],a.writeFileSync(o,JSON.stringify(n,null,2)+`
|
|
7
|
+
`,"utf8")}catch{}}}function re(t,e,s){for(const o of e){const n=i.join(t,o.path);if(!s){if(o.type==="rm")try{a.unlinkSync(n)}catch{}else if(o.type==="edit")try{const l=F(n).split(`
|
|
8
|
+
`),r=l.findIndex(g=>g.includes(O)),f=l.slice(0,r).join(`
|
|
9
9
|
`).trimEnd();a.writeFileSync(n,f+`
|
|
10
|
-
`,"utf8")}catch{}}}}async function
|
|
10
|
+
`,"utf8")}catch{}}}}async function pe(t=[]){const e=p=>t.includes(p),s=e("--dry-run")||e("--dry"),o=e("--keep-memory"),n=e("--keep-inferno"),c=e("--yes")||e("-y"),l=e("--json"),r=process.cwd(),f={infernoDir:V(r,o,n),claudeMd:q(r),claudeDir:B(r),cursorMcpServer:Y(r),cursorMcpJson:z(r),claudeJson:Q((()=>{try{return L(r)}catch{return r}})()),gitHooks:X(r)},g=Object.values(f).flat(),j=g.filter(p=>p.type!=="skip");if(l){console.log(JSON.stringify({dryRun:s,keepMemory:o,keepInferno:n,plan:f,actionCount:j.length},null,2));return}const C=h(" "+"\u2500".repeat(52));if(console.log(),console.log(" "+I("\u{1F525} infernoflow uninstall")),s&&console.log(P(" DRY RUN \u2014 nothing will be changed")),console.log(C),j.length===0){console.log(),console.log(D(" \u2714 Nothing to remove \u2014 infernoflow is not installed in this project")),console.log();return}console.log(),console.log(" "+I("Will remove:")),console.log();const A={rm:J(" \u2716"),rmdir:J(" \u2716"),edit:P(" ~"),skip:h(" \xB7")};for(const p of g){const H=A[p.type]||" ?",K=p.desc?h(` (${p.desc})`):"";console.log(`${H} ${p.path}${K}`)}if(o&&(console.log(),console.log(h(" \u2139 inferno/sessions.jsonl will be preserved (--keep-memory)"))),console.log(),!s&&!c){if(!(await G(" Continue? "+h("[y/N] "))).trim().toLowerCase().startsWith("y")){console.log(h(`
|
|
11
11
|
Aborted \u2014 nothing changed.
|
|
12
|
-
`));return}console.log()}if(s){console.log(
|
|
13
|
-
`));return}
|
|
12
|
+
`));return}console.log()}if(s){console.log(h(` \u2191 Dry run complete \u2014 run without --dry-run to apply
|
|
13
|
+
`));return}Z(r,f.infernoDir,!1),f.claudeMd.length&&ee(r,!1),f.claudeDir.length&&oe(r,f.claudeDir,!1),f.cursorMcpServer.length&&ne(r,f.cursorMcpServer,!1),f.cursorMcpJson.length&&te(r,f.cursorMcpJson,!1),f.claudeJson.length&&se(f.claudeJson,!1),f.gitHooks.length&&re(r,f.gitHooks,!1),console.log(C),console.log(),console.log(D(" \u2714 infernoflow removed from this project")),console.log();const M=j.filter(p=>p.type==="edit"),R=j.filter(p=>p.type==="rm"||p.type==="rmdir");R.length&&console.log(h(" Deleted: ")+R.map(p=>p.path).join(", ")),M.length&&console.log(h(" Edited: ")+M.map(p=>p.path).join(", ")),o&&(console.log(),console.log(h(" Session memory kept \u2192 inferno/sessions.jsonl")),console.log(h(" Re-run infernoflow init to restore the rest."))),console.log()}export{pe as uninstallCommand};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as f from"node:fs";import*as s from"node:path";import{installInfernoDraftTooling as h}from"./draftToolingInstall.mjs";import{refreshSecuritySensitiveCopies as j}from"./securityRefresh.mjs";import{updateIdeMcpJson as y}from"./mcpRegistration.mjs";function v(c){const{cwd:r,templatesRoot:a,force:p,silent:e}=c,t=c.logOk||(()=>{}),i=c.logWarn||(()=>{});function u(o,n){return f.existsSync(n)&&!p?(e||i("Skipped (exists): "+s.relative(r,n)),!1):(f.mkdirSync(s.dirname(n),{recursive:!0}),f.copyFileSync(o,n),e||t("Created: "+s.relative(r,n)),!0)}h({cwd:r,templatesRoot:a,force:p,silent:e,logOk:t,logWarn:i});const d=s.join(a,"cursor","hooks.json"),l=s.join(r,".cursor","hooks.json"),m=s.join(a,"cursor","hooks","inferno-session-draft.mjs"),k=s.join(r,".cursor","hooks","inferno-session-draft.mjs");u(d,l),u(m,k);try{const o=j(r);o.length&&!e&&t("Security update: replaced "+o.join(", "))}catch{}try{const o=y(r,"cursor");e||(o.updated?t:i)((o.updated?"Updated: ":"Unchanged: ")+".cursor/mcp.json")}catch(o){e||i(".cursor/mcp.json skipped: "+o.message)}}export{v as installCursorHooksArtifacts};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import*as a from"node:fs";import*as l from"node:os";import*as s from"node:path";import*as S from"node:crypto";import{execFileSync as h}from"node:child_process";import{fileURLToPath as g}from"node:url";const k=s.dirname(g(import.meta.url));function v(){return s.resolve(k,"..","bin","infernoflow.mjs")}function E(){return/[\\/]_npx[\\/]/.test(v())}function f(e){return{command:"node",args:[v(),"mcp"],env:{INFERNOFLOW_PROJECT_DIR:e}}}function d(e){if(!a.existsSync(e))return{data:{},existed:!1,ok:!0};try{const t=JSON.parse(a.readFileSync(e,"utf8"));return!t||typeof t!="object"||Array.isArray(t)?{data:{},existed:!0,ok:!1}:{data:t,existed:!0,ok:!0}}catch{return{data:{},existed:!0,ok:!1}}}function m(e,t){a.mkdirSync(s.dirname(e),{recursive:!0});const r=`${e}.infernoflow-${process.pid}-${Date.now()}.tmp`;let o;try{o=a.statSync(e).mode&511}catch{}a.writeFileSync(r,JSON.stringify(t,null,2).replace(/\u0000+/g,"")+`
|
|
2
|
+
`,{encoding:"utf8",...o?{mode:o}:{}});try{a.renameSync(r,e)}catch(n){try{a.unlinkSync(r)}catch{}throw n}}function y(e,t){return!!e&&!!t&&e.command===t.command&&JSON.stringify(e.args)===JSON.stringify(t.args)&&(e.env||{}).INFERNOFLOW_PROJECT_DIR===(t.env||{}).INFERNOFLOW_PROJECT_DIR}function w(e,t){try{return h("git",["ls-files","--error-unmatch","--",t],{cwd:e,stdio:"ignore",windowsHide:!0,timeout:5e3}),!0}catch{return!1}}function x(e,t,r){const o=s.join(e,".gitignore");let n="";try{n=a.readFileSync(o,"utf8")}catch{}if(n.split(/\r?\n/).some(i=>i.trim()===t))return!1;const c=(n&&!n.endsWith(`
|
|
3
|
+
`)?`
|
|
4
|
+
`:"")+(r?`# ${r}
|
|
5
|
+
`:"")+t+`
|
|
6
|
+
`;return a.writeFileSync(o,n+c,"utf8"),!0}function O(e){try{const t=s.join(process.env.INFERNOFLOW_HOME||s.join(l.homedir(),".infernoflow"),"backups");a.mkdirSync(t,{recursive:!0,mode:448});const r=s.join(t,`${s.basename(e)}.${new Date().toISOString().replace(/[:.]/g,"-")}.bak`);a.copyFileSync(e,r);try{a.chmodSync(r,384)}catch{}return r}catch{return null}}function b(e){const t=".mcp.json",r=s.join(e,t),o=a.existsSync(r);if(o&&w(e,t))return{updated:!1,skipped:"tracked",path:r};const{data:n,ok:c}=d(r);if(!c)return{updated:!1,skipped:"unreadable",path:r};(!n.mcpServers||typeof n.mcpServers!="object")&&(n.mcpServers={});const i=f(e);let p=!1;try{p=x(e,".mcp.json","infernoflow: machine-specific MCP config (absolute paths) \u2014 not for git")}catch{}return y(n.mcpServers.infernoflow,i)?{updated:!1,path:r,gitignored:p}:(n.mcpServers.infernoflow=i,m(r,n),{updated:!0,path:r,created:!o,gitignored:p})}function I(e,t){const r=t==="vscode",o=s.join(e,r?".vscode":".cursor","mcp.json"),{data:n,ok:c}=d(o);if(!c)return{updated:!1,skipped:"unreadable",path:o};const i=r?"servers":"mcpServers";(!n[i]||typeof n[i]!="object")&&(n[i]={});const p=r?{type:"stdio",...f(e)}:f(e),u=n[i].infernoflow;return y(u,p)&&(!r||u.type==="stdio")?{updated:!1,path:o}:(p.env={...u&&u.env||{},...p.env},n[i].infernoflow=p,m(o,n),{updated:!0,path:o})}function C(){return s.join(l.homedir(),".claude.json")}function j(){const e=l.homedir();return process.platform==="win32"?s.join(process.env.APPDATA||s.join(e,"AppData","Roaming"),"Claude","claude_desktop_config.json"):process.platform==="darwin"?s.join(e,"Library","Application Support","Claude","claude_desktop_config.json"):s.join(process.env.XDG_CONFIG_HOME||s.join(e,".config"),"Claude","claude_desktop_config.json")}function _(e){if(!e||typeof e!="object")return!1;const t=Array.isArray(e.args)?e.args.join(" "):"";return/inferno-mcp-server\.mjs/.test(t)||/infernoflow(\.mjs)?["']?\s+mcp\b/.test(t)||/bin[\\/]infernoflow\.mjs/.test(t)||!!(e.env&&e.env.INFERNOFLOW_PROJECT_DIR)}function P(e){if(!a.existsSync(e))return{removed:!1};const{data:t,ok:r}=d(e);if(!r||!t.mcpServers||!t.mcpServers.infernoflow)return{removed:!1};const o=t.mcpServers.infernoflow;if(!_(o))return{removed:!1,skipped:"not-ours"};const n=O(e);return n?(delete t.mcpServers.infernoflow,m(e,t),{removed:!0,backup:n,pinnedTo:(o.env||{}).INFERNOFLOW_PROJECT_DIR||null}):{removed:!1,skipped:"backup-failed"}}function F(e,t={}){const r="infernoflow-"+(s.basename(e).toLowerCase().replace(/[^a-z0-9-]+/g,"-").replace(/^-+|-+$/g,"")||"project"),o=t[r];if(!o||(o.env||{}).INFERNOFLOW_PROJECT_DIR===e)return r;const n=S.createHash("sha1").update(e).digest("hex").slice(0,6);return`${r}-${n}`}function J(e){const t=j();if(!a.existsSync(s.dirname(t))&&!a.existsSync(t))return{updated:!1,skipped:"not-installed"};const{data:r,ok:o}=d(t);if(!o)return{updated:!1,skipped:"unreadable",path:t};(!r.mcpServers||typeof r.mcpServers!="object")&&(r.mcpServers={});const n=F(e,r.mcpServers),c=f(e);return y(r.mcpServers[n],c)?{updated:!1,name:n,path:t}:(r.mcpServers[n]=c,m(t,r),{updated:!0,name:n,path:t})}export{j as claudeDesktopConfigPath,C as claudeJsonPath,v as cliEntryPath,F as desktopServerName,x as ensureGitignoreLine,w as isGitTracked,_ as isInfernoflowEntry,f as mcpServerEntry,P as removeLegacyUserLevelEntry,E as runningFromNpxCache,J as updateClaudeDesktopPerProject,I as updateIdeMcpJson,b as updateProjectMcpJson};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const A="(?:api[_-]?key|apikey|secret[_-]?key|client[_-]?secret|secret|access[_-]?token|auth[_-]?token|refresh[_-]?token|bearer[_-]?token|token|passwd|password|pwd|private[_-]?key|aws[_-]?secret[_-]?access[_-]?key|[A-Z][A-Z0-9_]{0,40}_(?:SECRET|TOKEN|PASSWORD|API_KEY|ACCESS_KEY|PRIVATE_KEY))",c=`(?=[^\\s"',;]{0,200}[0-9])(?=[^\\s"',;]{0,200}[A-Za-z])[^\\s"',;]{8,200}|[A-Za-z0-9_\\-+/=]{24,400}`,_=[["private-key",/-----BEGIN [A-Z0-9 ]{0,40}PRIVATE KEY-----[\s\S]{0,20000}?(?:-----END [A-Z0-9 ]{0,40}PRIVATE KEY-----|$)/g],["anthropic-key",/\bsk-ant-[A-Za-z0-9_-]{16,300}/g],["openrouter-key",/\bsk-or-[A-Za-z0-9_-]{16,300}/g],["openai-key",/\bsk-(?:proj-|svcacct-|admin-)?[A-Za-z0-9_-]{20,300}/g],["github-token",/\b(?:gh[pousr]_[A-Za-z0-9]{30,255}|github_pat_[A-Za-z0-9_]{40,255})/g],["npm-token",/\bnpm_[A-Za-z0-9]{30,255}/g],["aws-access-key",/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g],["google-api-key",/\bAIza[0-9A-Za-z_-]{35}\b/g],["slack-token",/\bxox[abposr]-[A-Za-z0-9-]{10,255}/g],["stripe-key",/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,255}/g],["jwt",/\beyJ[A-Za-z0-9_-]{8,4000}\.eyJ[A-Za-z0-9_-]{8,4000}\.[A-Za-z0-9_-]{8,4000}/g],["bearer-token",/\b(Bearer\s{1,5})(?!\[REDACTED)[A-Za-z0-9._~+/=-]{16,4000}/g],["url-credentials",/(?<![a-z0-9+.-])([a-z][a-z0-9+.-]{0,30}:\/\/)[^\s:/@]{1,200}:[^\s@/]{3,200}(?=@)/gi],["connection-string",/\b((?:Password|Pwd|AccountKey|SharedAccessKey)\s{0,5}=\s{0,5})(?!\[REDACTED)[^;\s"']{4,500}/gi],["credential",new RegExp(`(["']?\\b${A}["']?\\s{0,5}[=:]\\s{0,5}["']?)(?!\\[REDACTED)(?:${c})`,"gi")]];function k(e){if(typeof e!="string"||!e)return{text:e,kinds:[]};const a=new Set;let s=e;for(const[n,r]of _)r.lastIndex=0,s=s.replace(r,(o,t)=>(a.add(n),(typeof t=="string"&&o.startsWith(t)?t:"")+`[REDACTED:${n}]`));return{text:s,kinds:[...a]}}function i(e){return k(e).kinds.length>0}export{i as containsSecret,k as redactSecrets};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as s from"node:fs";import*as a from"node:path";const u=new Set(["--help","-h","--version","-v","commands","doctor","setup","init","uninstall","mcp"]);function y(r){const n=a.join(r,".ai-memory"),o=a.join(r,"inferno");return s.existsSync(n)?a.join(n,".last-cli-version"):s.existsSync(o)?a.join(o,".last-cli-version"):null}function m(r){try{return s.readFileSync(r,"utf8").trim()}catch{return""}}async function h(r,n){if(!r||n&&u.has(n))return;const o=process.cwd(),c=y(o);if(!c)return;const i=m(c);if(i===r)return;let f=!1,l=[],p=[],d=i==="";try{const{refreshRuleFilesFromMemory:t}=await import("./ruleFiles.mjs");t(o)}catch{}try{const{applyCleanTreePolicy:t}=await import("./cleanTree.mjs");t(o)}catch{}try{const{autoSetupMcp:t}=await import("./commands/setup.mjs"),e=t(o,{silent:!0});f=!!(e&&(e.mcpServer||e.projectMcpJson||e.cursorMcp||e.vscodeMcp||e.claudeSettings||e.claudeDesktop)),l=e&&e.securityRefreshed||[],p=[e&&e.legacyClaudeJsonRemoved,e&&e.legacyDesktopRemoved].filter(Boolean)}catch{}try{s.writeFileSync(c,r,"utf8")}catch{}l.length&&process.stderr.write(` infernoflow: security update \u2014 replaced outdated ${l.join(", ")}.
|
|
2
2
|
infernoflow: restart your AI tool (Claude Code / Cursor / VS Code) so the new MCP server is loaded.
|
|
3
|
-
`)
|
|
4
|
-
|
|
5
|
-
`)}}
|
|
3
|
+
`);for(const t of p)process.stderr.write(` infernoflow: removed the old user-level MCP entry (it pinned every project to ${t.pinnedTo||"one repo"}). Backup: ${t.backup}
|
|
4
|
+
infernoflow: each project now has its own registration \u2014 run any infernoflow command in your other projects to set theirs up.
|
|
5
|
+
`);if(f||d)try{const{gray:t}=await import("./ui/output.mjs"),e=i?`upgraded ${i} \u2192 ${r}`:`initialized for ${r}`;process.stderr.write(t(` infernoflow: ${e}, wired MCP servers + rule files
|
|
6
|
+
`))}catch{process.stderr.write(` infernoflow: refreshed for v${r}
|
|
7
|
+
`)}}export{h as runUpgradeBackfillIfNeeded};
|
|
@@ -541,7 +541,10 @@ function handleTool(id, name, rawInput) {
|
|
|
541
541
|
// write dirties tracked files and blocks `git checkout`. Within a
|
|
542
542
|
// session, the agent uses amp_read for fresh queries; rule files
|
|
543
543
|
// are for cold-start injection of the *next* session.
|
|
544
|
-
|
|
544
|
+
// D3 (0.45.0): always say which store was written, so a wrong store is visible.
|
|
545
|
+
text = (typeof ampIo.describeStore === "function" ? ampIo.describeStore(PROJECT_DIR) + "\n" : "") +
|
|
546
|
+
`✔ Logged [${written.type}] ${written.id}\n msg: ${written.msg}` +
|
|
547
|
+
(written.meta && written.meta.redacted ? `\n ⚠ secrets redacted: ${written.meta.redacted.join(", ")}` : "") +
|
|
545
548
|
(written.file ? `\n file: ${written.file}${written.line ? ":" + written.line : ""}` : "") +
|
|
546
549
|
(written.tags ? `\n tags: ${written.tags.join(", ")}` : "") +
|
|
547
550
|
(written.meta && written.meta.detailRef ? `\n detail: ${written.meta.detailRef}` : "");
|
|
@@ -577,11 +580,14 @@ function handleTool(id, name, rawInput) {
|
|
|
577
580
|
if (input.note && String(input.note).trim()) {
|
|
578
581
|
entry.detail = String(input.note);
|
|
579
582
|
} else if (harvestSnapshot) {
|
|
580
|
-
|
|
583
|
+
// Automatic transcript snapshots stay on this machine (gitignored store).
|
|
584
|
+
try { const snap = harvestSnapshot(PROJECT_DIR); if (snap) { entry.detail = snap; entry.detailLocal = true; } } catch { /* best-effort */ }
|
|
581
585
|
}
|
|
582
586
|
try {
|
|
583
587
|
const written = ampIo.appendEntry(PROJECT_DIR, entry);
|
|
584
|
-
text =
|
|
588
|
+
text = (typeof ampIo.describeStore === "function" ? ampIo.describeStore(PROJECT_DIR) + "\n" : "") +
|
|
589
|
+
`🔖 Bookmark saved: ${written.msg} (${written.id})` +
|
|
590
|
+
(written.meta && written.meta.redacted ? `\n ⚠ secrets redacted: ${written.meta.redacted.join(", ")}` : "") +
|
|
585
591
|
(written.meta && written.meta.detailRef ? `\n context: ${written.meta.detailRef}` : "");
|
|
586
592
|
} catch (err) {
|
|
587
593
|
return sendError(id, -32000, `amp_bookmark failed (in-process): ${err.message}`);
|
package/package.json
CHANGED