infernoflow 0.44.19 → 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 CHANGED
@@ -267,13 +267,17 @@ That's the whole product. No vendor lock-in (it's JSONL on disk). No SaaS. One C
267
267
 
268
268
  Local-first by design:
269
269
 
270
- - 🚫 **No telemetry.** No analytics, no error reporting, no install pings.
270
+ - ✅ **Telemetry is opt-in and off by default.** After a few runs in an interactive terminal infernoflow asks once; nothing is sent unless you answer yes. It never sends code, file paths or memory content. Check or change it with `infernoflow telemetry status`.
271
271
  - 🚫 **No `postinstall` script.** `npm install -g infernoflow` runs no code — it only copies files.
272
- - 🚫 **No network calls in any default command path.** Everything runs on your machine.
273
- - 🚫 **No auto-updates, no background processes, no cloud sync.**
274
- - ✅ **Reads and writes only inside your project directory** (`.ai-memory/`, plus the three rule files at repo root).
272
+ - 🚫 **No network calls in any default command path** (with telemetry off, which is the default).
273
+ - 🚫 **No auto-updates of the package, no background processes, no cloud sync.**
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
- - ✅ **Secret patterns rejected on capture** — entries matching `sk-`, `ghp_`, `-----BEGIN` are refused at the AMP writer.
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).
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.
277
281
 
278
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.
279
283
 
@@ -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 $}from"node:fs";import{dirname as C,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=C(v(import.meta.url));function A(o){for(const n of[y(o,"..","..","package.json"),y(o,"..","package.json")])try{return JSON.parse($(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)"},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),"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"],"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,E=`
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 R from"node:fs";import*as S from"node:path";try{const o=S.join(process.cwd(),"inferno");if(R.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(E),process.exit(0)),(s==="--version"||s==="-v")&&(console.log(m),process.exit(0)),s==="commands"&&(console.log(`
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.")}
@@ -1,27 +1,27 @@
1
- import*as i from"node:fs";import*as M from"node:os";import*as a from"node:path";import{findProjectRoot as R}from"../projectRoot.mjs";import{getBranchInfo as P}from"../git/branch.mjs";const T="1.0",L=new Set(["gotcha","decision","attempt","note","detection","pattern"]),W=new Set(["copilot","cursor","claude","windsurf","other"]);function Y(e){let t=a.basename(a.resolve(e));try{const o=a.join(e,".ai-memory","amp.json");if(i.existsSync(o)){const n=JSON.parse(i.readFileSync(o,"utf8"));n&&typeof n.project=="string"&&n.project.trim()&&(t=n.project.trim())}}catch{}return t.toLowerCase().replace(/[^a-z0-9_.\-]+/g,"-").replace(/^-+|-+$/g,"").slice(0,64)||"unnamed-project"}function G(e,t){let o=process.env.INFERNOFLOW_GLOBAL_DIR;if(!o)try{const f=JSON.parse(i.readFileSync(a.join(t,"amp.json"),"utf8"));f&&typeof f.globalDir=="string"&&f.globalDir.trim()&&(o=f.globalDir.trim())}catch{}if(!o)return a.join(t,"global.jsonl");let n=o;n.startsWith("~")&&(n=a.join(M.homedir(),n.slice(1).replace(/^[\/\\]/,""))),a.isAbsolute(n)||(n=a.resolve(e,n));const c=Y(e);return a.join(n,c,"global.jsonl")}function m(e,t={}){const o=t.literal?a.resolve(e):R(e),n=a.join(o,".ai-memory"),c=a.join(o,"inferno"),f=t.forWrite||i.existsSync(n)||!i.existsSync(c),s=f?n:c,r=f,u=a.join(s,"branches"),l=P(o),h=a.join(u,`${l.currentSlug}.jsonl`),g=l.defaultSlug?a.join(u,`${l.defaultSlug}.jsonl`):null,S=U(o,s);return{root:s,projectRoot:o,isAmp:r,sessions:a.join(s,"sessions.jsonl"),config:a.join(s,r?"amp.json":"config.json"),handoff:a.join(s,r?"handoff.md":"HANDOFF.md"),globalFile:S,branchesDir:u,currentBranchFile:h,defaultBranchFile:g,branch:l}}function U(e,t){try{return G(e,t)}catch{return a.join(t,"global.jsonl")}}function v(e){const t=R(e),o=a.join(t,".ai-memory");return i.existsSync(o)||i.mkdirSync(o,{recursive:!0}),o}const B="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function w(){let e=Date.now(),t="";for(let n=0;n<10;n++)t=B[e%32]+t,e=Math.floor(e/32);let o="";for(let n=0;n<16;n++)o+=B[Math.floor(Math.random()*32)];return t+o}function E(e){const t={...e.meta||{}};let o=e.type||"note";L.has(o)||(t.subtype=o,o="note"),e.result&&(t.result=e.result),e.detailRef&&(t.detailRef=e.detailRef);let n;const c=e.agent;c&&W.has(c)?n=c:c&&(t.agent=c);const f=typeof e.ts=="number"?e.ts:e.ts?Date.parse(e.ts):Date.now(),s=e.confidence!=null?e.confidence:e.auto?.7:void 0,r={type:o,msg:e.summary||e.msg||"",ts:f,id:e.id||`amp_${w()}`};return e.file&&(r.file=e.file),e.line&&(r.line=e.line),e.function&&(r.function=e.function),e.tags&&e.tags.length&&(r.tags=e.tags),e.source&&(r.source=e.source),n&&(r.tool=n),e.session&&(r.session=e.session),s!=null&&(r.confidence=s),Object.keys(t).length&&(r.meta=t),r}function H(e){if(e.summary&&!e.msg)return e;const t=e.meta||{},o=t.subtype||e.type||"note",n={ts:e.ts,type:o,summary:e.msg||""};e.id&&(n.id=e.id),e.file&&(n.file=e.file),e.line&&(n.line=e.line),e.function&&(n.function=e.function),e.tags&&(n.tags=e.tags),e.source&&(n.source=e.source),e.tool&&(n.agent=e.tool),t.agent&&(n.agent=t.agent),t.result&&(n.result=t.result),t.detailRef&&(n.detailRef=t.detailRef),e.confidence!=null&&(n.confidence=e.confidence,e.confidence<1&&(n.auto=!0));const{subtype:c,agent:f,result:s,detailRef:r,...u}=t;return Object.keys(u).length&&(n.meta=u),n}function V(e){if(!e||!i.existsSync(e))return[];try{return i.readFileSync(e,"utf8").split(`
2
- `).filter(Boolean).map(t=>{try{return JSON.parse(t)}catch{return null}}).filter(Boolean).map(H)}catch{return[]}}function ae(e){const t=m(e),o=new Set,n=[],c=[t.sessions,t.globalFile,t.defaultBranchFile,t.currentBranchFile],f=[...new Set(c.filter(Boolean))];for(const s of f)for(const r of V(s)){const u=r.id||`${r.ts}|${r.summary}`;o.has(u)||(o.add(u),n.push(r))}return n.sort((s,r)=>{const u=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0),l=typeof r.ts=="number"?r.ts:Date.parse(r.ts||0);return u-l})}function z(e,t){return t.target==="global"?e.globalFile:t.target==="legacy"?e.sessions:t.target==="branch"?e.currentBranchFile:t.type==="preference"?e.globalFile:e.currentBranchFile}const J=new Set;function b(e){return a.join(e,"details.jsonl")}function q(e,t,o){const n=b(e);i.mkdirSync(a.dirname(n),{recursive:!0}),i.appendFileSync(n,JSON.stringify({id:t,body:o})+`
3
- `,"utf8")}function K(e,t){if(!t)return null;let o;try{o=i.readFileSync(b(e),"utf8")}catch{return null}let n=null;for(const c of o.split(`
4
- `))if(c.trim())try{const f=JSON.parse(c);f.id===t&&typeof f.body=="string"&&(n=f.body)}catch{}return n}function Q(e,t){const o=b(e);let n;try{n=i.readFileSync(o,"utf8")}catch{return!1}const c=[];let f=!1;for(const s of n.split(`
5
- `))if(s.trim()){try{if(JSON.parse(s).id===t){f=!0;continue}}catch{}c.push(s)}return f&&i.writeFileSync(o,c.length?c.join(`
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"),f}function X(e){const t=a.join(e,"details");if(!i.existsSync(t))return 0;let o;try{o=i.readdirSync(t).filter(s=>s.endsWith(".md"))}catch{return 0}if(!o.length)return 0;const n=new Set;try{const s=i.readFileSync(b(e),"utf8");for(const r of s.split(`
8
- `))if(r.trim())try{n.add(JSON.parse(r).id)}catch{}}catch{}let c="",f=0;for(const s of o){const r=s.replace(/\.md$/,"");if(n.has(r))continue;let u;try{u=i.readFileSync(a.join(t,s),"utf8")}catch{continue}c+=JSON.stringify({id:r,body:u})+`
9
- `,f++}if(c)try{i.appendFileSync(b(e),c,"utf8")}catch{return 0}for(const s of o)try{i.unlinkSync(a.join(t,s))}catch{}try{i.rmdirSync(t)}catch{}return f}function fe(e,t){v(e);const o=m(e,{forWrite:!0});if(!J.has(o.root)){J.add(o.root);try{X(o.root)}catch{}}const n=t.id||`amp_${w()}`,c={...t,id:n};if(typeof t.detail=="string"&&t.detail.trim())try{q(o.root,n,t.detail),c.detailRef="details.jsonl"}catch{}delete c.detail;const f=z(o,c),s=E(c),r=JSON.stringify(s)+`
10
- `;if(i.mkdirSync(a.dirname(f),{recursive:!0}),i.appendFileSync(f,r,"utf8"),f!==o.sessions)try{i.mkdirSync(a.dirname(o.sessions),{recursive:!0}),i.appendFileSync(o.sessions,r,"utf8")}catch{}return s}function le(e,t){const o=m(e);return a.join(o.root,"details",`${t}.md`)}function ue(e,t){if(!t)return null;const o=t.detailRef||t.meta&&t.meta.detailRef;if(!o)return null;const n=m(e),c=t.id||t.meta&&t.meta.id,f=K(n.root,c);if(f!=null)return f;if(typeof o=="string"&&o!=="details.jsonl"){const s=a.isAbsolute(o)?o:a.join(n.root,o);try{return i.readFileSync(s,"utf8")}catch{return null}}return null}function de(e,t){if(!t)return{removed:0,files:[]};const o=m(e),n=[o.sessions,o.globalFile,o.currentBranchFile,o.defaultBranchFile].filter(Boolean);try{if(i.existsSync(o.branchesDir))for(const r of i.readdirSync(o.branchesDir))r.endsWith(".jsonl")&&n.push(a.join(o.branchesDir,r))}catch{}const c=[...new Set(n)];let f=0;const s=[];for(const r of c){if(!i.existsSync(r))continue;let u;try{u=i.readFileSync(r,"utf8").split(`
11
- `)}catch{continue}const l=[];let h=0;for(const g of u){if(!g.trim())continue;let S;try{S=JSON.parse(g).id}catch{l.push(g);continue}if(S===t){h++,f++;continue}l.push(g)}h>0&&(i.writeFileSync(r,l.length?l.join(`
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"),s.push(r))}try{Q(o.root,t)}catch{}try{const r=a.join(o.root,"details",`${t}.md`);i.existsSync(r)&&(i.unlinkSync(r),s.push(r))}catch{}return{removed:f,files:s}}const Z=30,ee=["note","attempt","detection"],I=new Set(["gotcha","decision","pattern"]);function te(e){const t=e&&e.config&&e.config.rotation&&typeof e.config.rotation=="object"?e.config.rotation:{};return{archiveAfterDays:Number.isInteger(t.archiveAfterDays)&&t.archiveAfterDays>0?t.archiveAfterDays:Z,archivableTypes:Array.isArray(t.archivableTypes)&&t.archivableTypes.length?t.archivableTypes:ee,auto:t.auto===!0}}function ne(e){const t=[e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(i.existsSync(e.branchesDir))for(const o of i.readdirSync(e.branchesDir))o.endsWith(".jsonl")&&t.push(a.join(e.branchesDir,o))}catch{}return[...new Set(t)]}function oe(e,t){const o=new Date(t),n=o.getUTCFullYear(),c=String(o.getUTCMonth()+1).padStart(2,"0");return a.join(e,"archive",`sessions-${n}-${c}.jsonl`)}function ye(e,t={}){const o=m(e,{forWrite:!t.dryRun}),n=te(ie(e)),c=Number.isFinite(t.maxAgeDays)?t.maxAgeDays:n.archiveAfterDays,f=t.archive!==!1,s=new Set((Array.isArray(t.types)&&t.types.length?t.types:n.archivableTypes).filter(p=>!I.has(p))),r=Date.now()-c*24*60*60*1e3,u=ne(o),l={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!t.dryRun},h=new Map,g=new Set,S=new Set,N=new Set;for(const p of u){if(!i.existsSync(p))continue;let F;try{F=i.readFileSync(p,"utf8").split(`
14
- `)}catch{continue}const D=[];let A=0;for(const j of F){if(!j.trim())continue;let y;try{y=JSON.parse(j)}catch{D.push(j);continue}const d=y&&y.id,x=y&&typeof y.type=="string"?y.type:"note",O=typeof y.ts=="number"?y.ts:Date.parse(y.ts||0),C=Number.isFinite(O)&&O<r,_=Array.isArray(y.tags)&&y.tags.includes("bookmark"),$=s.has(x)&&!I.has(x)&&!_;if(d&&!S.has(d)&&(S.add(d),l.scanned++),d||l.scanned++,C&&$){if(A++,(!d||!g.has(d))&&(d&&g.add(d),l.archived++,l.byType[x]=(l.byType[x]||0)+1,f)){const k=oe(o.root,O);h.has(k)||h.set(k,[]),h.get(k).push(j)}}else d&&!N.has(d)&&(N.add(d),l.kept++),d||l.kept++,D.push(j)}A>0&&!t.dryRun?(i.writeFileSync(p,D.length?D.join(`
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(p)):A>0&&l.files.push(p)}if(!t.dryRun&&f&&h.size>0)for(const[p,F]of h)try{i.mkdirSync(a.dirname(p),{recursive:!0}),i.appendFileSync(p,F.join(`
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 ie(e){const{config:t}=m(e);try{return JSON.parse(i.readFileSync(t,"utf8"))}catch{return null}}function he(e,t={}){v(e);const{config:o}=m(e,{forWrite:!0});if(i.existsSync(o))return!1;const n={amp:T,project:t.project||a.basename(e),stack:t.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},...t.config||{}}};return i.writeFileSync(o,JSON.stringify(n,null,2)+`
19
- `,"utf8"),!0}function pe(e,t={}){v(e);const{config:o}=m(e,{forWrite:!0});let n={};try{n=JSON.parse(i.readFileSync(o,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=T),n.project||(n.project=a.basename(e)),(!n.config||typeof n.config!="object")&&(n.config={});const c=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...c,...t},i.writeFileSync(o,JSON.stringify(n,null,2)+`
20
- `,"utf8"),n.config.injection}function ge(e){const t=a.join(e,"inferno"),o=a.join(t,"sessions.jsonl");if(!i.existsSync(o))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=a.join(e,".ai-memory"),c=a.join(n,"sessions.jsonl");if(i.existsSync(c))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};v(e);const f=i.readFileSync(o,"utf8").split(`
21
- `).filter(Boolean);let s=0;for(const r of f)try{const u=JSON.parse(r),l=E(u);i.appendFileSync(c,JSON.stringify(l)+`
22
- `,"utf8"),s++}catch{}return i.writeFileSync(a.join(n,"MIGRATED.md"),`# Migrated from inferno/
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 ${s} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
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:s,reason:"ok"}}export{T as AMP_VERSION,m as ampPaths,fe as appendEntry,de as deleteEntry,le as detailPath,v as ensureAmpDir,H as fromAmp,w as generateULID,ge as migrateLegacy,Y as projectSlug,ye as pruneEntries,ie as readConfig,ue as readDetail,ae as readEntries,G as resolveGlobalFile,te as resolveRotationSettings,E as toAmp,pe as updateInjectionConfig,he as writeDefaultConfig};
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};
@@ -1,12 +1,12 @@
1
- import*as m from"node:fs";import*as p 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","# 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
- `),I=["",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
- `),T="# --- infernoflow (developer-local AI memory; do not commit) ---",y="# --- /infernoflow ---";function A(n){const r=n.indexOf(T),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=p.join(n,r);let e="";try{e=m.readFileSync(o,"utf8")}catch{}e=A(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+$/,""),u=e.slice(a+s.length).replace(/^\s+/,"");t=(f?f+`
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
- `+(u?`
8
- `+u:"")}else e?t=e.replace(/\s+$/,"")+`
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(p.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",I)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
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};
@@ -1,2 +1,6 @@
1
- import*as g from"node:fs";import*as A from"node:path";import*as x from"node:https";import*as C from"node:http";import*as R from"node:readline";import{bold as d,cyan as v,gray as i,green as r,yellow as y,red as h}from"../ui/output.mjs";function O(n){return A.join(n,"inferno")}function k(n){const o=A.join(O(n),"integrations.json");if(!g.existsSync(o))return{};try{return JSON.parse(g.readFileSync(o,"utf8"))}catch{return{}}}function E(n,o){const t=O(n);g.existsSync(t)||g.mkdirSync(t,{recursive:!0}),g.writeFileSync(A.join(t,"integrations.json"),JSON.stringify(o,null,2)+`
2
- `)}const m=[{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 _(n){return new Promise(o=>{const t=new URL(n),e=(t.protocol==="https:"?x:C).request({hostname:t.hostname,port:t.port||(t.protocol==="https:"?443:80),path:t.pathname+(t.search||""),method:"GET",timeout:5e3},s=>{let a="";s.on("data",c=>a+=c),s.on("end",()=>{try{o({status:s.statusCode,body:JSON.parse(a)})}catch{o({status:s.statusCode,body:a})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function b(n,o){const l={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}[n],e=o[n]?.apiKey,s=l||e,a=l?"env":e?"integrations.json":null,c=o[n]?.model||m.find(u=>u.id===n)?.default;if(n==="ollama"){const u=await _("http://localhost:11434/api/tags").catch(()=>null);if(u?.status===200){const p=u.body?.models?.map(f=>f.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:p}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!s,source:a,model:c,available:null,masked:s?s.slice(0,8)+"\u2026":null}}async function N(n,o,t){try{const l=await import("../ai/providerRouter.mjs");if(typeof l.callAI!="function")return null;const e=`Reply with exactly: "infernoflow AI test OK \u2014 ${n}"`;return await l.callAI(e,t,n)}catch{return null}}function $(n,o){return new Promise(t=>n.question(o,t))}async function U(n){const o=k(n);console.log(),console.log(` ${d("infernoflow ai")} ${i("\u2014 provider status")}`),console.log();let t=!1;for(const l of m){const e=await b(l.id,o);e.configured&&(t=!0);const s=e.configured?r("\u2713"):i("\u25CB"),a=d(l.name.padEnd(22)),c=e.configured?`${r("configured")} ${i(e.source)} ${i("model: "+e.model)}${e.masked?" "+i(e.masked):""}`:i("not configured");console.log(` ${s} ${a} ${c}`)}console.log(),t?console.log(` ${i("Run")} ${v("infernoflow ai test")} ${i("to verify the active provider.")}`):(console.log(` ${y("No AI providers configured.")} Run: ${v("infernoflow ai setup")}`),console.log(` ${i("Without a provider, explain/why/review use structural fallbacks.")}`)),console.log()}async function G(n){const o=k(n),t={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(` ${d("\u{1F525} infernoflow ai setup")}`),console.log(` ${i("Connect an AI provider for explain, why, review, and changelog.")}`),console.log(),m.forEach((e,s)=>{const a=t[e.id],c=o[e.id]?.apiKey,u=a?r(" \u2713 key detected in environment"):c?r(" \u2713 key already saved"):"",p=d(String(s+1)),f=e.id==="ollama"?i(" (local, no key needed)"):"";console.log(` ${p}) ${d(e.name.padEnd(22))}${f}${u}`)}),console.log();const l=R.createInterface({input:process.stdin,output:process.stdout});try{const e=await $(l," Select provider [1]: "),s=(parseInt(e.trim())||1)-1;if(s<0||s>=m.length){console.log(h(` Invalid choice. Enter a number 1\u2013${m.length}.`));return}const a=m[s],c=a.id;if(console.log(),console.log(` ${d(a.name)}`),c==="ollama"){const p=await $(l," Ollama host [http://localhost:11434]: "),f=await $(l,` Model [${a.default}]: `);o.ollama={host:p.trim()||"http://localhost:11434",model:f.trim()||a.default},E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await _(`${o.ollama.host}/api/tags`).catch(()=>null))?.status===200?console.log(r("OK")):(console.log(y("not reachable")),console.log(` ${y("\u26A0")} Start Ollama first: ${v("ollama serve")}`))}else{const p=t[c],f=o[c]?.apiKey,I=p||f;if(I){const w=p?"environment variable":"saved config";if(console.log(` ${r("\u2713")} API key detected from ${w}: ${i(I.slice(0,12)+"\u2026")}`),(await $(l," Use this key? [Y/n]: ")).trim().toLowerCase()==="n"){console.log(),a.docsUrl&&console.log(` ${i("Get a key at:")} ${v(a.docsUrl)}`);const K=await $(l," Paste new API key: ");if(!K.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:K.trim(),model:o[c]?.model||a.default}}else o[c]={apiKey:I,model:o[c]?.model||a.default}}else{console.log(` ${i("Get your API key at:")} ${v(a.docsUrl)}`),console.log(` ${i("Tip: paste the key below \u2014 it starts with")} ${i(a.keyHint)}`),console.log();const w=await $(l," Paste API key: ");if(!w.trim()){console.log(h(" No key provided. Exiting."));return}o[c]={apiKey:w.trim(),model:a.default}}const P=o[c].model;console.log(),console.log(` ${i("Available models:")} ${a.models.join(" ")}`);const S=await $(l,` Model [${P}]: `);o[c].model=S.trim()||P,E(n,o),console.log(),process.stdout.write(` ${r("\u2713")} Saved. Testing connection\u2026 `),(await N(c,o,n))?.text?console.log(r("OK")+i(` (${o[c].model})`)):(console.log(y("no response")),console.log(` ${y("\u26A0")} Connection failed \u2014 double-check your API key.`))}console.log(),console.log(` ${r("\u2713")} ${d(a.name)} is ready.`),console.log(` ${i("AI-powered commands:")} explain why review changelog`),console.log();const u=A.join(n,".gitignore");g.existsSync(u)&&(g.readFileSync(u,"utf8").includes("integrations.json")||(console.log(` ${y("\u26A0")} Add ${v("inferno/integrations.json")} to your .gitignore to avoid committing your API key.`),console.log()))}finally{l.close()}}async function Y(n,o){const t=k(o),l=n.find(s=>!s.startsWith("--"))||null;console.log(),console.log(` ${d("infernoflow ai test")}`),console.log();const e=l?m.filter(s=>s.id===l):m;for(const s of e){if(!(await b(s.id,t)).configured){console.log(` ${i("\u25CB")} ${d(s.name.padEnd(22))} ${i("not configured \u2014 skipping")}`);continue}process.stdout.write(` ${y("\u2026")} ${d(s.name.padEnd(22))} testing\u2026 `);const c=await N(s.id,t,o);c?.text?(console.log(r("OK")+i(` (${c.model||s.id})`)),console.log(` ${i(c.text.trim().slice(0,80))}`)):(console.log(h("FAIL")),console.log(` ${h("No response \u2014 check API key or model name")}`))}console.log()}async function T(n,o){const t=k(o),l=n.find(e=>!e.startsWith("--"));if(l||(console.error(h("\u2717 Usage: infernoflow ai clear <provider>")),console.error(i(" Example: infernoflow ai clear openai")),process.exit(1)),!t[l]){console.log(i(` No config found for "${l}"`));return}delete t[l],E(o,t),console.log(r(` \u2713 Cleared config for ${l}`))}async function L(n){const o=(n||[]).slice(1),t=o.find(s=>!s.startsWith("--"))||"status",l=o.filter(s=>s!==t),e=process.cwd();switch(t){case"setup":return G(e);case"status":return U(e);case"test":return Y(l,e);case"clear":return T(l,e);default:console.error(h(`\u2717 Unknown subcommand: "${t}"`)),console.error(i(" Usage: infernoflow ai <setup|status|test|clear>")),process.exit(1)}}export{L as aiCommand};
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 R,yellow as I,red as L}from"../ui/output.mjs";import{readEntries as M}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:I,decision:R,attempt:O,preference:O,theme:O,note:r,error:L,handoff:r};function E(o){return o.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(e=>e.length>1)}function W(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 j(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 q(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(` (${j(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 Y(o){return M(o)}function P(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:W(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 G(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=Y(k);if(m.length===0){if(t){console.log(JSON.stringify({results:[],total:0}));return}console.log(r(`
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})):P(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(` ${"\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(),q(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{G as askCommand};
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 u,cyan as f,gray as t,green as h,yellow as d,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
- `+u("\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,g=null;if(o)try{i=o==="-"?k.readFileSync(0,"utf8"):k.readFileSync(o,"utf8"),g="file"}catch(m){console.error(p(`
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,g="note";else if(!a)try{i=B(e),i&&(g="transcript")}catch{}const v=x.join(e,".ai-memory"),E=x.join(e,"inferno");!k.existsSync(v)&&!k.existsSync(E)&&(console.error(p(`
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: ")+u(c)+t(` ${S.id}`)),i&&i.trim()?console.log(" "+t(`${g==="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
- `+u("\u{1F516} infernoflow bookmarks")),console.log(" "+"\u2500".repeat(50)),r.length===0){console.log(t(`
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(d(`
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(d(`
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(" "+u("\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(`
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(d(`
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(d(`
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 "+u(s.msg||s.summary)+t(` ${s.id}`):" "+d("Nothing removed.")),console.log()}export{b as BOOKMARK_TAG,M as bookmarkCommand};
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 A from"node:http";import{execSync as O,spawnSync as v}from"node:child_process";import{fileURLToPath as P}from"node:url";import{bold as C,cyan as b,gray as k,green as w,yellow as S,red as $}from"../ui/output.mjs";import{detectAvailableProviders as I}from"../ai/providerRouter.mjs";import{readEntries as _,readConfig as M}from"../amp/io.mjs";import{resolveInjectionSettings as E}from"../ruleFiles.mjs";function l(e,n){try{const o=n();return{label:e,...o}}catch(o){return{label:e,status:"error",message:o.message,fix:null}}}function a(e,n){return{status:"pass",message:e,detail:n||null,fix:null}}function f(e,n){return{status:"warn",message:e,detail:null,fix:n||null}}function h(e,n){return{status:"fail",message:e,detail:null,fix:n||null}}function T(){const e=process.version,n=parseInt(e.slice(1).split(".")[0],10);return n>=20?a(`Node.js ${e}`,"Node 20+ recommended"):n>=18?a(`Node.js ${e}`):h(`Node.js ${e} \u2014 infernoflow requires Node 18+`,"Install Node 20 from nodejs.org")}function D(){try{const e=v("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 G(e){try{return O("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 F(e){const n=s.join(e,".ai-memory"),o=s.join(e,"inferno");return c.existsSync(n)&&c.existsSync(o)?a(".ai-memory/ + inferno/ both present"):c.existsSync(n)?a(".ai-memory/ directory exists (memory mode)"):c.existsSync(o)?a("inferno/ directory exists"):h("No memory directory found (.ai-memory/ or inferno/)","infernoflow init")}function L(e){if(j(e)){let o=0;try{o=_(e).length}catch{}return a(o===0?"Memory mode \u2014 sessions.jsonl will be created on first log":`Memory mode \u2014 ${o} session entr${o===1?"y":"ies"}`)}const n=s.join(e,"inferno");for(const o of["contract.json","capabilities.json"]){const t=s.join(n,o);if(c.existsSync(t))try{const i=(JSON.parse(c.readFileSync(t,"utf8")).capabilities||[]).length;return a(`${o} valid \u2014 ${i} capabilities`)}catch{return h(`${o} contains invalid JSON`,`Fix the JSON syntax in inferno/${o}`)}}return h("No contract.json/capabilities.json (and not in memory mode)","infernoflow init or infernoflow init --mode full")}function j(e){const n=s.join(e,".ai-memory"),o=s.join(e,"inferno","contract.json");if(c.existsSync(n)&&!c.existsSync(o))return!0;try{return JSON.parse(c.readFileSync(s.join(e,"inferno","config.json"),"utf8")).mode==="memory"}catch{return!1}}function R(e){if(j(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=s.join(e,"inferno","scenarios");if(!c.existsSync(n))return f("No scenarios/ directory","infernoflow init");const o=c.readdirSync(n).filter(t=>t.endsWith(".json"));return o.length?a(`${o.length} scenario file${o.length!==1?"s":""} found`):f("scenarios/ is empty","Add scenario files or run infernoflow suggest")}function J(e){if(j(e))return{status:"info",message:"n/a in memory mode",detail:null,fix:null};const n=s.join(e,"inferno","CHANGELOG.md");return c.existsSync(n)?a("inferno/CHANGELOG.md exists"):f("No inferno/CHANGELOG.md","infernoflow init")}function H(e){if(j(e))return{status:"info",message:"n/a in memory mode (CLAUDE.md is auto-maintained)",detail:null,fix:null};const n=s.join(e,"inferno","CONTEXT.md");if(!c.existsSync(n))return f("No CONTEXT.md generated","infernoflow context");const o=(Date.now()-c.statSync(n).mtimeMs)/(1e3*60*60*24);return o>7?f(`CONTEXT.md is ${Math.round(o)} days old \u2014 may be stale`,"infernoflow context"):a(`CONTEXT.md present (${Math.round(o)}d old)`)}function W(e){const n=s.join(e,".git","hooks"),o=s.join(n,"post-commit");return c.existsSync(o)&&c.readFileSync(o,"utf8").includes("infernoflow")?a("Git hooks installed (post-commit)"):f("Git hooks not installed","infernoflow setup --yes")}function X(e){const n=[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 o of n)if(c.existsSync(o))try{const t=JSON.parse(c.readFileSync(o,"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(o)}`)}catch{}return f("MCP server not configured","infernoflow setup --yes (adds to Cursor/Claude/Copilot config)")}function K(e,n){const o=s.join(e,".ai-memory",".mcp-runtime.json");if(!c.existsSync(o))return a("MCP runtime stamp not present yet \u2014 start your AI tool to write one");let t;try{t=JSON.parse(c.readFileSync(o,"utf8"))}catch{return f(".mcp-runtime.json present but unreadable","Delete .ai-memory/.mcp-runtime.json and restart your AI tool")}return!t||typeof t.version!="string"?f(".mcp-runtime.json malformed","Delete it; the next MCP boot will rewrite it"):t.version===n?a(`MCP runtime v${t.version} matches CLI`):n.startsWith("0.0.0")||t.version.startsWith("0.0.0")?a(`MCP runtime v${t.version} (dev/source) \u2014 skipping version-skew check`):f(`MCP server is running v${t.version} but CLI is v${n} \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 U(e){const n=E(M(e));if(!n.includeProtocol)return a("Memory-protocol injection disabled in config (injection.includeProtocol=false)");const o=n.targets,t=[];for(const r of o)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}/${o.length} configured rule file(s) \u2014 AI knows when to auto-capture`):f("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 V(e){const n=I(e),o=Object.entries(n).filter(([,t])=>t).map(([t])=>t);return o.length?a(`AI provider${o.length!==1?"s":""}: ${o.join(", ")}`):f("No AI provider configured",`Set ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_AI_API_KEY, or OPENROUTER_API_KEY
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 Y(){return new Promise(e=>{const n=A.get({hostname:"localhost",port:11434,path:"/api/tags",timeout:1500},o=>{e(a("Ollama running on localhost:11434"))});n.on("error",()=>e({status:"info",message:"Ollama not running (optional)",fix:"ollama serve",detail:null})),n.on("timeout",()=>{n.destroy(),e({status:"info",message:"Ollama not running (optional)",fix:null,detail:null})})})}function z(){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 n=JSON.parse(c.readFileSync(e,"utf8")),o=n.user?.login||n.user?.name||n.user?.email||"unknown";if(n.mode==="supabase"&&n.access_token){if(n.expires_at){const t=new Date(n.expires_at).getTime();if(Date.now()>t)return f(`JWT expired for ${o} \u2014 refresh on next log will retry`,"infernoflow login")}return a(`Authenticated as ${o} (Supabase JWT \u2014 auth.uid() writes)`)}return n.mode==="device-flow"&&n.github_access_token?{status:"info",message:`Identity-only as ${o} (device flow \u2014 anon-mode writes)`,fix:"infernoflow login (without --device-flow, for full auth)",detail:null}:n.access_token?f(`Legacy login for ${o} \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 f("Credentials file unreadable","infernoflow logout && infernoflow login")}}function q(){try{const e=P(import.meta.url),n=s.resolve(s.dirname(e),"..","..","bin","infernoflow.mjs");if(!c.existsSync(n))return{status:"info",message:"bin/infernoflow.mjs not found from doctor location",fix:null,detail:null};const t=[...c.readFileSync(n,"utf8").matchAll(/import\("\.\.\/lib\/(commands\/[^"]+|telemetry\.mjs)"\)/g)],r=[],i=s.resolve(s.dirname(n),"..");for(const d of t){const m=d[1],p=s.join(i,"lib",m);c.existsSync(p)||r.push(m)}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 B(e){const n=s.join(e,"package.json");if(!c.existsSync(n))return a("No package.json to audit");let o;try{o=JSON.parse(c.readFileSync(n,"utf8"))}catch{return{status:"info",message:"package.json unreadable; skipping audit",detail:null,fix:null}}const t=o.scripts||{},r=new Set(["log","ask","switch","recap","status","refresh","forget","prune","bookmark","init","setup","doctor","context","install-cursor-hooks","install-vscode-copilot-hooks","generate-skills","ai","telemetry","uninstall","check","sync","amp","commands"]),i=[];for(const[m,p]of Object.entries(t)){const x=/\binfernoflow\s+([a-z][a-z0-9-]*)/.exec(String(p));if(!x)continue;const u=x[1];r.has(u)||i.push({scriptName:m,verb:u})}if(i.length===0)return a("npm scripts use current command surface");const d=i.map(m=>`${m.scriptName} \u2192 infernoflow ${m.verb}`).join(", ");return f(`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 Q(e){const n=s.join(e,".gitignore");if(!c.existsSync(n))return{status:"info",message:".gitignore not found",fix:null,detail:null};const o=c.readFileSync(n,"utf8");return/^(?:\*\*\/)?node_modules\/?$/m.test(o)?a(".gitignore excludes node_modules"):f(".gitignore does not exclude node_modules","Add 'node_modules/' (and '**/node_modules/') to .gitignore")}function Z(e,n){const o=e.filter(r=>r.status==="warn"&&r.fix),t=[];for(const r of o){const i=r.fix;if(i.startsWith("infernoflow ")){const d=i.slice(12).split(" ");v("infernoflow",d,{cwd:n,encoding:"utf8",timeout:3e4}).status===0&&t.push(r.label)}}return t}function ee(e){return e==="pass"?w("\u2714"):e==="warn"?S("\u26A0"):e==="fail"?$("\u2717"):k("\xB7")}function oe(e,n){const o={pass:0,warn:0,fail:0,info:0,error:0};for(const i of e)o[i.status]=(o[i.status]||0)+1;console.log(),console.log(` ${C("\u{1F525} infernoflow doctor")}`),console.log();const t=Math.max(...e.map(i=>i.label.length))+2;for(const i of e)console.log(` ${ee(i.status)} ${C(i.label.padEnd(t))} ${i.message}`),i.detail&&console.log(` ${" ".repeat(t)} ${k(i.detail)}`),i.fix&&(i.status==="warn"||i.status==="fail")&&console.log(` ${" ".repeat(t)} ${b("fix:")} ${k(i.fix)}`);console.log();const r=o.fail>0?$("issues found"):o.warn>0?S("warnings"):w("all good");console.log(` ${r} \u2014 ${w(String(o.pass))} pass \xB7 ${S(String(o.warn))} warn \xB7 ${$(String(o.fail))} fail (${n}ms)`),console.log(),(o.warn>0||o.fail>0)&&(console.log(` Run ${b("infernoflow doctor --fix")} to auto-fix warnings`),console.log())}async function ne(e){const n=e.slice(1),o=n.includes("--json"),t=n.includes("--fix"),r=process.cwd(),i=Date.now();let d="0.0.0-unknown";try{const{fileURLToPath:u}=await import("node:url"),g=s.dirname(u(import.meta.url)),N=s.join(g,"..","..","package.json");d=JSON.parse(c.readFileSync(N,"utf8")).version||d}catch{}const m=[l("Node.js version",()=>T()),l("infernoflow CLI",()=>D()),l("Git repository",()=>G(r)),l("inferno/ directory",()=>F(r)),l("Contract / mode",()=>L(r)),l("Scenarios",()=>R(r)),l("Changelog",()=>J(r)),l("CONTEXT.md",()=>H(r)),l("Git hooks",()=>W(r)),l("Auto-capture protocol",()=>U(r)),l("MCP server",()=>X(r)),l("MCP runtime version",()=>K(r,d)),l("AI providers",()=>V(r)),l("Cloud sync",()=>z()),l(".gitignore",()=>Q(r)),l("Router integrity",()=>q()),l("npm scripts",()=>B(r)),await Y().then(u=>({label:"Ollama (local AI)",...u}))],p=Date.now()-i;if(t){const u=Z(m,r);if(u.length)return o||(console.log(),u.forEach(g=>console.log(` ${w("\u2714")} Fixed: ${g}`)),console.log()),ne(o?["doctor","--json"]:["doctor"])}if(o){const u={pass:0,warn:0,fail:0,info:0};m.forEach(g=>u[g.status]=(u[g.status]||0)+1),console.log(JSON.stringify({ok:u.fail===0,counts:u,results:m,elapsed:p}));return}oe(m,p),m.some(u=>u.status==="fail")&&process.exit(1)}export{ne as doctorCommand};
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 c from"node:fs";import*as E from"node:path";import"node:os";import{bold as j,cyan as I,gray as o,green as b,yellow as P,red as S,orange as U}from"../ui/output.mjs";import{ampPaths as V,appendEntry as W,readEntries as q,readConfig as G,resolveRotationSettings as J,pruneEntries as Y}from"../amp/io.mjs";import{refreshRuleFilesFromMemory as R}from"../ruleFiles.mjs";function M(){return V(process.cwd())}const _=["gotcha","decision","attempt","note","detection","pattern","preference","theme","handoff","error"],T=["worked","failed","partial","unknown"];function X(){return q(process.cwd())}function z(t,{auto:l=!1,quiet:n=!1}={}){const r=process.cwd(),h=E.join(r,".ai-memory"),g=E.join(r,"inferno");if(l&&!c.existsSync(h)&&!c.existsSync(g))return!1;!c.existsSync(h)&&!c.existsSync(g)&&(n||console.error(S(` \u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
2
- `)),process.exit(1)),W(r,t);try{J(G(r)).auto&&Y(r)}catch{}try{R(r)}catch{}return!0}function H(){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 K(t,l){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"?P:r==="decision"?I:r==="theme"?U:r==="preference"?I: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(l+1).padStart(3))} ${o(n)} ${h(r)}${g} ${t.summary}${m}`}async function re(t){const l=e=>t.includes(e),n=(e,a)=>{const i=t.indexOf(e);return i!==-1&&t[i+1]?t[i+1]:a};l("--no-color")&&(process.env.NO_COLOR="1");const r=l("--show"),h=l("--clear"),g=l("--json"),m=l("--auto"),d=l("--quiet"),w=n("--source",null);if(r||g){const e=X(),a=t[t.indexOf("--show")+1],i=a&&/^\d+$/.test(a)?parseInt(a):20,s=e.slice(-i);if(g){console.log(JSON.stringify(s,null,2));return}if(console.log(`
3
- `+j("\u{1F525} infernoflow \u2014 session memory")),console.log(" "+"\u2500".repeat(50)),!s.length){console.log(o(`
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 ${s.length} of ${e.length} entries
6
- `)),s.forEach((u,O)=>console.log(K(u,e.length-s.length+O))),console.log();return}if(h){const e=M(),a=Date.now(),i=new Set([e.sessions,e.currentBranchFile,e.defaultBranchFile].filter(Boolean));try{if(e.branchesDir&&c.existsSync(e.branchesDir))for(const u of c.readdirSync(e.branchesDir))u.endsWith(".jsonl")&&i.add(E.join(e.branchesDir,u))}catch{}const s=[];for(const u of i){if(!c.existsSync(u))continue;const O=u.replace(/\.jsonl$/,`-archive-${a}.jsonl`);try{c.renameSync(u,O),s.push(E.basename(O))}catch{}}if(!s.length){console.log(o(` Nothing to clear.
7
- `));return}console.log(b(` \u2714 Session log archived (${s.length} file${s.length===1?"":"s"}) \u2014 entries cleared from active memory.
8
- `));return}const k=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("--")&&!k.has(e)).join(" ").trim();if(!v){console.log(`
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),C=n("--agent",H()),A=n("--file",null),F=n("--line",null),D=n("--tags",null);_.includes(y)||(d||console.error(S(` \u2718 Invalid type: ${y}. Valid: ${_.join(", ")}
12
- `)),process.exit(1)),p&&!T.includes(p)&&(d||console.error(S(` \u2718 Invalid result: ${p}. Valid: ${T.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,x=n("--detail",null),$=n("--detail-file",null);let f=null;if($)try{f=$==="-"?c.readFileSync(0,"utf8"):c.readFileSync($,"utf8")}catch(e){d||console.error(S(` \u2718 Could not read --detail-file ${$}: ${e.message}
14
- `)),process.exit(1)}else x&&(f=x);const B={ts:new Date().toISOString(),agent:C,type:y,summary:v,...p?{result:p}:{},...w?{source:w}:{},...A?{file:A}:{},...L?{line:L}:{},...N&&N.length?{tags:N}:{},...f&&f.trim()?{detail:f}:{},...m?{auto:!0}:{}};if(z(B,{auto:m,quiet:d})){if(t.includes("--refresh-rules"))try{R(process.cwd())}catch{}if(!d){const e=y!=="note"?I(` [${y}]`):"",a=p?o(` \u2192 ${p}`):"",i=w?o(` (via ${w})`):"",s=f&&f.trim()?o(` (+${f.length}-char detail)`):"";console.log(b(` \u2714 Logged${e}${a}${i}: `)+v+s+`
15
- `)}}}export{re as logCommand};
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};