infernoflow 0.44.20 → 0.46.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 +15 -5
- package/dist/bin/infernoflow.mjs +30 -26
- package/dist/lib/amp/io.mjs +22 -19
- package/dist/lib/claudeAssets.mjs +3 -1
- package/dist/lib/cleanTree.mjs +10 -10
- package/dist/lib/commands/ai.mjs +6 -2
- package/dist/lib/commands/ask.mjs +3 -3
- package/dist/lib/commands/bookmark.mjs +12 -12
- package/dist/lib/commands/curate.mjs +10 -0
- package/dist/lib/commands/doctor.mjs +3 -2
- package/dist/lib/commands/hook.mjs +12 -0
- package/dist/lib/commands/log.mjs +15 -14
- package/dist/lib/commands/mcp.mjs +1 -0
- package/dist/lib/commands/move.mjs +14 -0
- package/dist/lib/commands/recap.mjs +5 -5
- package/dist/lib/commands/resolve.mjs +5 -0
- package/dist/lib/commands/resume.mjs +3 -0
- package/dist/lib/commands/setup.mjs +82 -18
- package/dist/lib/commands/status.mjs +4 -4
- package/dist/lib/commands/sync.mjs +27 -27
- package/dist/lib/commands/transcript.mjs +2 -0
- package/dist/lib/commands/uninstall.mjs +11 -11
- package/dist/lib/cursorHooksInstall.mjs +1 -1
- package/dist/lib/mcpRegistration.mjs +6 -0
- package/dist/lib/memoryView.mjs +7 -0
- package/dist/lib/personalConfig.mjs +2 -0
- package/dist/lib/ruleFiles.mjs +11 -11
- package/dist/lib/schema.mjs +1 -0
- package/dist/lib/security/redact.mjs +1 -0
- package/dist/lib/securityRefresh.mjs +1 -1
- package/dist/lib/upgradeCheck.mjs +6 -4
- package/dist/templates/agents/memory-keeper.md +49 -54
- package/dist/templates/cursor/hooks/inferno-session-draft.mjs +8 -3
- package/dist/templates/cursor/inferno-mcp-server.mjs +78 -13
- package/dist/templates/hooks/infernoflow-agent-guard.mjs +32 -0
- package/dist/templates/skills/infernoflow-memory/SKILL.md +68 -86
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -85,7 +85,8 @@ These cover 95% of usage:
|
|
|
85
85
|
| Command | What it does |
|
|
86
86
|
|---|---|
|
|
87
87
|
| `infernoflow log "..."` | Remember a gotcha / decision / attempt / note. `--type gotcha\|decision\|attempt\|preference` |
|
|
88
|
-
| `infernoflow
|
|
88
|
+
| `infernoflow resume` | **🆕 0.46** "Where were we?" in one call — last resume point, open dead ends, recent decisions, uncommitted work. `--file <path>` ranks by file |
|
|
89
|
+
| `infernoflow ask "..."` | Search your memory by keyword — gotchas surface first. `--file <path>` ranks entries about that file first |
|
|
89
90
|
| `infernoflow switch` | Generate a handoff for the next session. `--copy` puts it on your clipboard |
|
|
90
91
|
| `infernoflow recap` | End-of-session summary with health score + unlogged-change detection |
|
|
91
92
|
| `infernoflow status` | Quick health check — entries, gotchas, decisions, last activity |
|
|
@@ -93,6 +94,11 @@ These cover 95% of usage:
|
|
|
93
94
|
| `infernoflow refresh` | Manually rebuild `CLAUDE.md` / `.cursorrules` / `copilot-instructions.md` from memory |
|
|
94
95
|
| `infernoflow forget <id\|prefix>` | Delete a memory entry without hand-editing JSONL. `--last` for the newest |
|
|
95
96
|
| `infernoflow prune` | Archive stale `note` / `attempt` entries older than 30 days. Gotchas/decisions/bookmarks never auto-pruned. Default dry-run; `--apply` to act |
|
|
97
|
+
| `infernoflow resolve <id>` | **🆕 0.46** Mark an entry fixed/outdated — stays searchable, no longer injected. Entries whose file changed since they were written show **"may be stale"** |
|
|
98
|
+
| `infernoflow curate` | **🆕 0.46** Remove noise: old commit notes, duplicates, raw frustration prompts. Dry-run; `--apply` |
|
|
99
|
+
| `infernoflow move <id…> --to <dir>` | **🆕 0.46** Move misfiled entries to another project's memory. Dry-run; `--apply` |
|
|
100
|
+
|
|
101
|
+
Any command takes `--project <dir>` to work on another project's memory (multi-folder workspaces, hooks, agents).
|
|
96
102
|
|
|
97
103
|
In practice you barely run any of these — the MCP-aware AI does it for you. The CLI is for grep-style introspection.
|
|
98
104
|
|
|
@@ -193,8 +199,9 @@ When the MCP server is wired, your AI agent can call these directly in chat:
|
|
|
193
199
|
|
|
194
200
|
| Tool | What it does |
|
|
195
201
|
|---|---|
|
|
196
|
-
| `amp_write` | Log an entry (`type`, `msg`, optional `file` / `line` / `tags` / `detail`) |
|
|
197
|
-
| `
|
|
202
|
+
| `amp_write` | Log an entry (`type`, `msg`, optional `file` / `line` / `tags` / `detail`). In a multi-folder workspace an entry about another open folder's file goes to that folder's memory |
|
|
203
|
+
| `amp_resume` | **🆕 0.46** "Where were we?" — last resume point, open dead ends, recent decisions, uncommitted changes |
|
|
204
|
+
| `amp_read` | Read entries with optional filters (`type`, `query`, `file` — entries about that file rank first) |
|
|
198
205
|
| `amp_search` | Keyword search across entries |
|
|
199
206
|
| `amp_bookmark` | **🆕** Drop a named resume point — auto-captures the current session transcript when no `note` is given |
|
|
200
207
|
| `amp_handoff` | Generate the handoff document for the next AI session |
|
|
@@ -271,9 +278,12 @@ Local-first by design:
|
|
|
271
278
|
- 🚫 **No `postinstall` script.** `npm install -g infernoflow` runs no code — it only copies files.
|
|
272
279
|
- 🚫 **No network calls in any default command path** (with telemetry off, which is the default).
|
|
273
280
|
- 🚫 **No auto-updates of the package, no background processes, no cloud sync.**
|
|
274
|
-
-
|
|
281
|
+
- ✅ **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.
|
|
282
|
+
- ⚠️ **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
283
|
- ✅ **Auto-injected content is wrapped in markers** (`<!-- infernoflow:start -->` / `<!-- infernoflow:end -->`) — your manual edits outside the block are never touched.
|
|
276
|
-
-
|
|
284
|
+
- ✅ **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.
|
|
285
|
+
- ✅ **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.
|
|
286
|
+
- ✅ **API keys never live in the project** — environment variables are used as-is; pasted keys go to `~/.infernoflow/ai-credentials.json` (owner-only).
|
|
277
287
|
- ✅ **No shell is used to run commands** from the MCP server or the prompt hooks (since 0.44.20), and MCP tool arguments are validated against their schema.
|
|
278
288
|
|
|
279
289
|
The optional `infernoflow ai setup` command wires an AI provider (Anthropic / OpenAI / Google / Ollama) for a few enrichment commands — same trust model as using that provider directly. Off by default.
|
package/dist/bin/infernoflow.mjs
CHANGED
|
@@ -1,40 +1,44 @@
|
|
|
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
|
|
3
|
-
`)}const
|
|
4
|
-
${
|
|
2
|
+
(function(){if(process.platform!=="win32"||process.argv[2]==="mcp"||process.env.WT_SESSION||process.env.ConEmuPID||process.env.TERM_PROGRAM==="vscode")return;const t={"\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(t).join("|"),"g"),f=/[\u{1F000}-\u{1FAFF}\u{2600}-\u{27BF}\u{2B00}-\u{2BFF}\u{200D}]\u{FE0F}? ?/gu,w=c=>c.replace(r,y=>t[y]).replace(f,"");function k(c){const y=c.write.bind(c);c.write=function(a,...b){return typeof a=="string"?a=w(a):Buffer.isBuffer(a)&&(a=Buffer.from(w(a.toString("utf8")),"utf8")),y(a,...b)}}k(process.stdout),k(process.stderr)})();import{readFileSync as x}from"node:fs";import{dirname as A,join as C}from"node:path";import{fileURLToPath as I}from"node:url";import{bold as i,gray as e,cyan as s,red as p}from"../lib/ui/output.mjs";const M=A(I(import.meta.url));function S(o){for(const t of[C(o,"..","..","package.json"),C(o,"..","package.json")])try{return JSON.parse(x(t,"utf8"))}catch{}return{version:"0.0.0-source"}}const j=S(M),l=j.version||"0.0.0",v={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)",resume:"Where were we? Latest resume point, open dead ends, recent decisions, uncommitted work",resolve:"Mark an entry resolved (kept searchable, no longer injected) \u2014 resolve <id> [--note]",move:"Move misfiled entries to another project \u2014 move <id\u2026>|--query q --to <dir> [--apply]",curate:"Remove noise: commit notes, duplicates, old raw frustration prompts [--apply]",transcript:"Print this project's current Claude Code session transcript (plain text)",hook:"Entry point for AI-tool hooks (session-start / session-end) \u2014 not run by hand"},h={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),hook:async o=>(await import("../lib/commands/hook.mjs")).hookCommand(o),resume:async o=>(await import("../lib/commands/resume.mjs")).resumeCommand(o),resolve:async o=>(await import("../lib/commands/resolve.mjs")).resolveCommand(o),move:async o=>(await import("../lib/commands/move.mjs")).moveCommand(o),curate:async o=>(await import("../lib/commands/curate.mjs")).curateCommand(o),transcript:async o=>(await import("../lib/commands/transcript.mjs")).transcriptCommand(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 B(){const o=Object.keys(v),t=Math.max(...o.map(r=>r.length),8)+1;return Object.entries(v).map(([r,f])=>` ${r.padEnd(t," ")}${f}`).join(`
|
|
3
|
+
`)}const O={Memory:["log","ask","resume","switch","recap","status","bookmark","resolve","refresh","forget","prune","curate","move","transcript"],Setup:["init","setup","doctor","context","mcp","hook"],"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 R(){return Object.entries(O).map(([o,t])=>` ${i(o+":")}
|
|
4
|
+
${t.join(" ")}`).join(`
|
|
5
5
|
|
|
6
|
-
`)}const
|
|
7
|
-
${i("\u{1F525} infernoflow")} ${e("v"+
|
|
6
|
+
`)}const $=Object.keys(h).length,E=`
|
|
7
|
+
${i("\u{1F525} infernoflow")} ${e("v"+l)}
|
|
8
8
|
${e("Persistent memory for AI coding sessions")}
|
|
9
9
|
|
|
10
10
|
${i("Usage:")}
|
|
11
11
|
infernoflow [command] [options]
|
|
12
12
|
|
|
13
13
|
${i("Memory")} ${e("\u2014 capture, recall & curate")}
|
|
14
|
-
${
|
|
15
|
-
${
|
|
16
|
-
${
|
|
17
|
-
${
|
|
18
|
-
${
|
|
19
|
-
${
|
|
20
|
-
${
|
|
14
|
+
${s("log")} ${e('"..."')} Add to session memory ${e("(--type gotcha|decision|attempt)")}
|
|
15
|
+
${s("ask")} ${e('"..."')} Search your memory by keyword ${e("(gotchas surface first)")}
|
|
16
|
+
${s("switch")} Generate handoff for next AI agent
|
|
17
|
+
${s("recap")} End-of-session health score + unlogged changes
|
|
18
|
+
${s("status")} Quick health check
|
|
19
|
+
${s("forget")} Delete a bad entry ${e("(--last for the newest, or by id/prefix)")}
|
|
20
|
+
${s("prune")} Archive stale notes/attempts ${e("(dry-run; --apply to commit)")}
|
|
21
21
|
|
|
22
22
|
${i("Setup")}
|
|
23
|
-
${
|
|
24
|
-
${
|
|
25
|
-
${
|
|
26
|
-
${
|
|
23
|
+
${s("init")} 60-second setup ${e("(memory mode by default)")}
|
|
24
|
+
${s("setup")} Re-run IDE wiring ${e("(idempotent \u2014 MCP + hooks)")}
|
|
25
|
+
${s("doctor")} Diagnose your setup
|
|
26
|
+
${s("context")} Generate AI-ready context for new sessions
|
|
27
27
|
|
|
28
28
|
${i("Subsystems")} ${e("\u2014 grouped, run for verbs:")}
|
|
29
|
-
${
|
|
29
|
+
${s("amp")} AI Memory Protocol ${e("(status, migrate, validate)")}
|
|
30
30
|
|
|
31
|
-
${e("Run")} ${
|
|
32
|
-
${e("Run")} ${
|
|
33
|
-
`;import*as
|
|
34
|
-
${i("\u{1F525} infernoflow")} ${e("v"+
|
|
35
|
-
`),console.log(
|
|
36
|
-
${e("Run")} ${
|
|
37
|
-
`),process.exit(0));const D=Object.keys(
|
|
38
|
-
Unknown command: ${
|
|
39
|
-
`)),process.exit(1));const
|
|
31
|
+
${e("Run")} ${s("infernoflow commands")} ${e("to see all "+$+" commands grouped.")}
|
|
32
|
+
${e("Run")} ${s("infernoflow <command> --help")} ${e("for command-specific options.")}
|
|
33
|
+
`;import*as m from"node:fs";import*as d from"node:path";try{const o=d.join(process.cwd(),"inferno");if(m.existsSync(o)){const{observeCommandStart:t}=await import("../lib/learning/observe.mjs"),r=process.argv[2];r&&!r.startsWith("-")&&t(o,r)}}catch{}const[,,n,...u]=process.argv;(!n||n==="--help"||n==="-h")&&(console.log(E),process.exit(0)),(n==="--version"||n==="-v")&&(console.log(l),process.exit(0)),n==="commands"&&(console.log(`
|
|
34
|
+
${i("\u{1F525} infernoflow")} ${e("v"+l)} ${e("\u2014 all "+$+" commands")}
|
|
35
|
+
`),console.log(R()),console.log(`
|
|
36
|
+
${e("Run")} ${s("infernoflow <command> --help")} ${e("for options.")}
|
|
37
|
+
`),process.exit(0));const D=Object.keys(h);D.includes(n)||(console.error(p(`
|
|
38
|
+
Unknown command: ${n}`)),console.error(e("Run: infernoflow commands (see all commands)")),console.error(e(`Run: infernoflow --help (quick start)
|
|
39
|
+
`)),process.exit(1));const g=u.indexOf("--project");if(g!==-1){const o=u[g+1],t=o?d.resolve(o):null;(!t||!m.existsSync(t)||!m.statSync(t).isDirectory())&&(console.error(p(`
|
|
40
|
+
\u2718 --project: not a folder: ${o||"(missing)"}
|
|
41
|
+
`)),process.exit(1)),!["init","setup"].includes(n)&&!m.existsSync(d.join(t,".ai-memory"))&&!m.existsSync(d.join(t,"inferno"))&&(console.error(p(`
|
|
42
|
+
\u2718 --project: ${t} has no .ai-memory/ \u2014 run infernoflow init there
|
|
43
|
+
`)),process.exit(1)),u.splice(g,2),process.chdir(t)}const P=[n,...u];try{const{runUpgradeBackfillIfNeeded:o}=await import("../lib/upgradeCheck.mjs");await o(l,n)}catch{}h[n](P).catch(o=>{console.error(p(`
|
|
40
44
|
Error: `)+o.message),process.exit(1)});
|
package/dist/lib/amp/io.mjs
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`).filter(Boolean).map(
|
|
3
|
-
`,"utf8")}function
|
|
4
|
-
`))if(
|
|
5
|
-
`))if(
|
|
1
|
+
import{execFileSync as Y}from"node:child_process";import{readPersonalConfig as H,expandUserPath as z}from"../personalConfig.mjs";import*as c from"node:fs";import{redactSecrets as V}from"../security/redact.mjs";import"node:os";import*as a from"node:path";import{findProjectRoot as B}from"../projectRoot.mjs";import{getBranchInfo as K}from"../git/branch.mjs";const E="1.0",Z=new Set(["gotcha","decision","attempt","note","detection","pattern"]),q=new Set(["copilot","cursor","claude","windsurf","other"]);function Q(t){let e=a.basename(a.resolve(t));try{const r=a.join(t,".ai-memory","amp.json");if(c.existsSync(r)){const n=JSON.parse(c.readFileSync(r,"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 X(t,e){let r=process.env.INFERNOFLOW_GLOBAL_DIR;if(!r){const f=H();typeof f.globalDir=="string"&&f.globalDir.trim()&&(r=f.globalDir.trim())}if(!r)return a.join(e,"global.jsonl");const n=z(r),o=Q(t);return a.join(n,o,"global.jsonl")}function g(t,e={}){const r=e.literal?a.resolve(t):B(t),n=a.join(r,".ai-memory"),o=a.join(r,"inferno"),f=e.forWrite||c.existsSync(n)||!c.existsSync(o),s=f?n:o,i=f,u=a.join(s,"branches"),l=K(r),d=a.join(u,`${l.currentSlug}.jsonl`),y=l.defaultSlug?a.join(u,`${l.defaultSlug}.jsonl`):null,S=tt(r,s);return{root:s,projectRoot:r,isAmp:i,sessions:a.join(s,"sessions.jsonl"),config:a.join(s,i?"amp.json":"config.json"),handoff:a.join(s,i?"handoff.md":"HANDOFF.md"),globalFile:S,branchesDir:u,currentBranchFile:d,defaultBranchFile:y,branch:l}}function Ft(t){try{const e=g(t),r=e.branch&&e.branch.current;return`store: ${e.root}${r&&!e.branch.isSynthetic?` (branch ${r})`:""}`}catch{return"store: (unresolved)"}}function tt(t,e){try{return X(t,e)}catch{return a.join(e,"global.jsonl")}}function A(t){const e=B(t),r=a.join(e,".ai-memory");return c.existsSync(r)||c.mkdirSync(r,{recursive:!0}),r}const _="0123456789ABCDEFGHJKMNPQRSTVWXYZ";function J(){let t=Date.now(),e="";for(let n=0;n<10;n++)e=_[t%32]+e,t=Math.floor(t/32);let r="";for(let n=0;n<16;n++)r+=_[Math.floor(Math.random()*32)];return e+r}function L(t){const e={...t.meta||{}};let r=t.type||"note";Z.has(r)||(e.subtype=r,r="note"),t.result&&(e.result=t.result),t.detailRef&&(e.detailRef=t.detailRef);let n;const o=t.agent;o&&q.has(o)?n=o:o&&(e.agent=o);const f=typeof t.ts=="number"?t.ts:t.ts?Date.parse(t.ts):Date.now(),s=t.confidence!=null?t.confidence:t.auto?.7:void 0,i={type:r,msg:t.summary||t.msg||"",ts:f,id:t.id||`amp_${J()}`};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),s!=null&&(i.confidence=s),Object.keys(e).length&&(i.meta=e),i}function et(t){if(t.summary&&!t.msg)return t;const e=t.meta||{},r=e.subtype||t.type||"note",n={ts:t.ts,type:r,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:o,agent:f,result:s,detailRef:i,...u}=e;return Object.keys(u).length&&(n.meta=u),n}function nt(t){if(!t||!c.existsSync(t))return[];try{return c.readFileSync(t,"utf8").split(`
|
|
2
|
+
`).filter(Boolean).map(e=>{try{return JSON.parse(e)}catch{return null}}).filter(Boolean).map(et)}catch{return[]}}function xt(t){const e=g(t),r=new Set,n=[],o=[e.sessions,e.globalFile,e.defaultBranchFile,e.currentBranchFile],f=[...new Set(o.filter(Boolean))];for(const s of f)for(const i of nt(s)){const u=i.id||`${i.ts}|${i.summary}`;r.has(u)||(r.add(u),n.push(i))}return n.sort((s,i)=>{const u=typeof s.ts=="number"?s.ts:Date.parse(s.ts||0),l=typeof i.ts=="number"?i.ts:Date.parse(i.ts||0);return u-l})}function rt(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 $=new Set;function x(t){return a.join(t,"details.jsonl")}const C="details.local.jsonl";function N(t){return a.join(t,C)}function ot(t,e,r,n=!1){const o=n?N(t):x(t);c.mkdirSync(a.dirname(o),{recursive:!0}),c.appendFileSync(o,JSON.stringify({id:e,body:r})+`
|
|
3
|
+
`,"utf8")}function I(t,e,r=!1){if(!e)return null;let n;try{n=c.readFileSync(r?N(t):x(t),"utf8")}catch{return null}let o=null;for(const f of n.split(`
|
|
4
|
+
`))if(f.trim())try{const s=JSON.parse(f);s.id===e&&typeof s.body=="string"&&(o=s.body)}catch{}return o}function P(t,e,r=!1){const n=r?N(t):x(t);let o;try{o=c.readFileSync(n,"utf8")}catch{return!1}const f=[];let s=!1;for(const i of o.split(`
|
|
5
|
+
`))if(i.trim()){try{if(JSON.parse(i).id===e){s=!0;continue}}catch{}f.push(i)}return s&&c.writeFileSync(n,f.length?f.join(`
|
|
6
6
|
`)+`
|
|
7
|
-
`:"","utf8"),
|
|
8
|
-
`))if(
|
|
9
|
-
`,f++}if(
|
|
10
|
-
`;if(
|
|
11
|
-
`)}catch{continue}
|
|
7
|
+
`:"","utf8"),s}function it(t){const e=a.join(t,"details");if(!c.existsSync(e))return 0;let r;try{r=c.readdirSync(e).filter(s=>s.endsWith(".md"))}catch{return 0}if(!r.length)return 0;const n=new Set;try{const s=c.readFileSync(x(t),"utf8");for(const i of s.split(`
|
|
8
|
+
`))if(i.trim())try{n.add(JSON.parse(i).id)}catch{}}catch{}let o="",f=0;for(const s of r){const i=s.replace(/\.md$/,"");if(n.has(i))continue;let u;try{u=c.readFileSync(a.join(e,s),"utf8")}catch{continue}o+=JSON.stringify({id:i,body:u})+`
|
|
9
|
+
`,f++}if(o)try{c.appendFileSync(x(t),o,"utf8")}catch{return 0}for(const s of r)try{c.unlinkSync(a.join(e,s))}catch{}try{c.rmdirSync(e)}catch{}return f}const O=new Map;function T(t,e){const r=t+"\0"+e.join("\0");if(O.has(r))return O.get(r);let n=null;try{n=Y("git",e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"],windowsHide:!0,timeout:1e4}).trim()||null}catch{n=null}return O.set(r,n),n}function Dt(){O.clear()}function st(t,e,r){const n=t.projectRoot,o={},f=process.env.INFERNOFLOW_AUTHOR||T(n,["config","user.name"])||(T(n,["config","user.email"])||"").split("@")[0]||null;f&&(o.author=f.slice(0,80)),o.repo=a.basename(n),t.branch&&t.branch.current&&!t.branch.isSynthetic&&(o.branch=t.branch.current);const s=a.relative(n,a.resolve(r));if(s&&!s.startsWith("..")&&!a.isAbsolute(s)&&(o.cwd=s.split(a.sep).join("/")),e.file){const i=T(n,["rev-parse","HEAD"]);i&&/^[0-9a-f]{40}$/.test(i)&&(o.commit=i)}return o}function vt(t,e){A(t);const r=g(t,{forWrite:!0});if(!$.has(r.root)){$.add(r.root);try{it(r.root)}catch{}}const n=e.id||`amp_${J()}`,o={...e,id:n},f=new Set,s=j=>{if(typeof j!="string")return j;const h=V(j);return h.kinds.forEach(b=>f.add(b)),h.text};o.msg!==void 0&&(o.msg=s(o.msg)),o.summary!==void 0&&(o.summary=s(o.summary)),Array.isArray(o.tags)&&(o.tags=o.tags.map(s)),typeof o.result=="string"&&(o.result=s(o.result));const i=o.detailLocal===!0;delete o.detailLocal;const u=typeof e.detail=="string"?s(e.detail):e.detail;f.size&&(o.meta={...o.meta||{},redacted:[...f]});try{o.meta={...st(r,o,t),...o.meta||{}}}catch{}const l=o.localOnly===!0;if(delete o.localOnly,typeof u=="string"&&u.trim())try{ot(r.root,n,u,i),o.detailRef=i?C:"details.jsonl"}catch{}delete o.detail;const d=l?r.sessions:rt(r,o),y=L(o),S=JSON.stringify(y)+`
|
|
10
|
+
`;if(c.mkdirSync(a.dirname(d),{recursive:!0}),c.appendFileSync(d,S,"utf8"),d!==r.sessions)try{c.mkdirSync(a.dirname(r.sessions),{recursive:!0}),c.appendFileSync(r.sessions,S,"utf8")}catch{}return y}function At(t,e){const r=g(t);return a.join(r.root,"details",`${e}.md`)}function Ot(t,e){if(!e)return null;const r=e.detailRef||e.meta&&e.meta.detailRef;if(!r)return null;const n=g(t),o=e.id||e.meta&&e.meta.id,f=I(n.root,o,!0);if(f!=null)return f;const s=I(n.root,o);if(s!=null)return s;if(typeof r=="string"&&/^details[\\/][A-Za-z0-9_.-]+\.md$/.test(r)&&!r.includes("..")){const i=a.join(n.root,"details"),u=a.join(n.root,r);try{const l=a.join(c.realpathSync(n.root),"details");if(c.lstatSync(i).isSymbolicLink()||c.realpathSync(i)!==l)return null;const d=c.realpathSync(u);return a.dirname(d)!==l?null:c.readFileSync(d,"utf8")}catch{return null}}return null}function ct(t){const e=g(t),r=[e.sessions,e.globalFile,e.currentBranchFile,e.defaultBranchFile].filter(Boolean);try{if(c.existsSync(e.branchesDir))for(const n of c.readdirSync(e.branchesDir))n.endsWith(".jsonl")&&r.push(a.join(e.branchesDir,n))}catch{}return[...new Set(r)]}function Rt(t,e,r){if(!e||typeof r!="function")return{updated:0,files:[]};let n=0;const o=[];for(const f of ct(t)){if(!c.existsSync(f))continue;let s;try{s=c.readFileSync(f,"utf8").split(`
|
|
11
|
+
`)}catch{continue}let i=!1;const u=s.map(l=>{if(!l.trim())return l;let d;try{d=JSON.parse(l)}catch{return l}if(d.id!==e)return l;const y=r({...d,meta:{...d.meta||{}}})||d;return i=!0,n++,JSON.stringify(y)});i&&(c.writeFileSync(f,u.filter((l,d)=>l.trim()||d<u.length-1).join(`
|
|
12
|
+
`).replace(/\n*$/,`
|
|
13
|
+
`),"utf8"),o.push(f))}return{updated:n,files:o}}function kt(t,e){if(!e)return{removed:0,files:[]};const r=g(t),n=[r.sessions,r.globalFile,r.currentBranchFile,r.defaultBranchFile].filter(Boolean);try{if(c.existsSync(r.branchesDir))for(const i of c.readdirSync(r.branchesDir))i.endsWith(".jsonl")&&n.push(a.join(r.branchesDir,i))}catch{}const o=[...new Set(n)];let f=0;const s=[];for(const i of o){if(!c.existsSync(i))continue;let u;try{u=c.readFileSync(i,"utf8").split(`
|
|
14
|
+
`)}catch{continue}const l=[];let d=0;for(const y of u){if(!y.trim())continue;let S;try{S=JSON.parse(y).id}catch{l.push(y);continue}if(S===e){d++,f++;continue}l.push(y)}d>0&&(c.writeFileSync(i,l.length?l.join(`
|
|
12
15
|
`)+`
|
|
13
|
-
`:"","utf8"),s.push(
|
|
14
|
-
`)}catch{continue}const D=[];let
|
|
16
|
+
`:"","utf8"),s.push(i))}try{P(r.root,e)}catch{}try{P(r.root,e,!0)}catch{}try{const i=a.join(r.root,"details",`${e}.md`);c.existsSync(i)&&(c.unlinkSync(i),s.push(i))}catch{}return{removed:f,files:s}}const at=30,ft=["note","attempt","detection"],M=new Set(["gotcha","decision","pattern"]);function lt(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:at,archivableTypes:Array.isArray(e.archivableTypes)&&e.archivableTypes.length?e.archivableTypes:ft,auto:e.auto===!0}}function ut(t){const e=[t.sessions,t.currentBranchFile,t.defaultBranchFile].filter(Boolean);try{if(c.existsSync(t.branchesDir))for(const r of c.readdirSync(t.branchesDir))r.endsWith(".jsonl")&&e.push(a.join(t.branchesDir,r))}catch{}return[...new Set(e)]}function dt(t,e){const r=new Date(e),n=r.getUTCFullYear(),o=String(r.getUTCMonth()+1).padStart(2,"0");return a.join(t,"archive",`sessions-${n}-${o}.jsonl`)}function wt(t,e={}){const r=g(t,{forWrite:!e.dryRun}),n=lt(yt(t)),o=Number.isFinite(e.maxAgeDays)?e.maxAgeDays:n.archiveAfterDays,f=e.archive!==!1,s=new Set((Array.isArray(e.types)&&e.types.length?e.types:n.archivableTypes).filter(h=>!M.has(h))),i=Date.now()-o*24*60*60*1e3,u=ut(r),l={scanned:0,archived:0,kept:0,files:[],byType:{},dryRun:!!e.dryRun},d=new Map,y=new Set,S=new Set,j=new Set;for(const h of u){if(!c.existsSync(h))continue;let b;try{b=c.readFileSync(h,"utf8").split(`
|
|
17
|
+
`)}catch{continue}const D=[];let R=0;for(const F of b){if(!F.trim())continue;let m;try{m=JSON.parse(F)}catch{D.push(F);continue}const p=m&&m.id,v=m&&typeof m.type=="string"?m.type:"note",k=typeof m.ts=="number"?m.ts:Date.parse(m.ts||0),W=Number.isFinite(k)&&k<i,G=Array.isArray(m.tags)&&m.tags.includes("bookmark"),U=s.has(v)&&!M.has(v)&&!G;if(p&&!S.has(p)&&(S.add(p),l.scanned++),p||l.scanned++,W&&U){if(R++,(!p||!y.has(p))&&(p&&y.add(p),l.archived++,l.byType[v]=(l.byType[v]||0)+1,f)){const w=dt(r.root,k);d.has(w)||d.set(w,[]),d.get(w).push(F)}}else p&&!j.has(p)&&(j.add(p),l.kept++),p||l.kept++,D.push(F)}R>0&&!e.dryRun?(c.writeFileSync(h,D.length?D.join(`
|
|
15
18
|
`)+`
|
|
16
|
-
`:"","utf8"),l.files.push(
|
|
19
|
+
`:"","utf8"),l.files.push(h)):R>0&&l.files.push(h)}if(!e.dryRun&&f&&d.size>0)for(const[h,b]of d)try{c.mkdirSync(a.dirname(h),{recursive:!0}),c.appendFileSync(h,b.join(`
|
|
17
20
|
`)+`
|
|
18
|
-
`,"utf8")}catch{}return l}function
|
|
19
|
-
`,"utf8"),!0}function
|
|
20
|
-
`,"utf8"),n.config.injection}function
|
|
21
|
-
`).filter(Boolean);let s=0;for(const
|
|
22
|
-
`,"utf8"),s++}catch{}return
|
|
21
|
+
`,"utf8")}catch{}return l}function yt(t){const{config:e}=g(t);try{return JSON.parse(c.readFileSync(e,"utf8"))}catch{return null}}function Nt(t,e={}){A(t);const{config:r}=g(t,{forWrite:!0});if(c.existsSync(r))return!1;const n={amp:E,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 c.writeFileSync(r,JSON.stringify(n,null,2)+`
|
|
22
|
+
`,"utf8"),!0}function Tt(t,e={}){A(t);const{config:r}=g(t,{forWrite:!0});let n={};try{n=JSON.parse(c.readFileSync(r,"utf8"))}catch{n={}}(!n||typeof n!="object")&&(n={}),n.amp||(n.amp=E),n.project||(n.project=a.basename(t)),(!n.config||typeof n.config!="object")&&(n.config={});const o=n.config.injection&&typeof n.config.injection=="object"?n.config.injection:{};return n.config.injection={...o,...e},c.writeFileSync(r,JSON.stringify(n,null,2)+`
|
|
23
|
+
`,"utf8"),n.config.injection}function Bt(t){const e=a.join(t,"inferno"),r=a.join(e,"sessions.jsonl");if(!c.existsSync(r))return{migrated:0,reason:"no legacy sessions.jsonl"};const n=a.join(t,".ai-memory"),o=a.join(n,"sessions.jsonl");if(c.existsSync(o))return{migrated:0,reason:".ai-memory/sessions.jsonl already exists"};A(t);const f=c.readFileSync(r,"utf8").split(`
|
|
24
|
+
`).filter(Boolean);let s=0;for(const i of f)try{const u=JSON.parse(i),l=L(u);c.appendFileSync(o,JSON.stringify(l)+`
|
|
25
|
+
`,"utf8"),s++}catch{}return c.writeFileSync(a.join(n,"MIGRATED.md"),`# Migrated from inferno/
|
|
23
26
|
|
|
24
27
|
Copied ${s} entries from inferno/sessions.jsonl on ${new Date().toISOString()}.
|
|
25
28
|
|
|
26
29
|
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{
|
|
30
|
+
`,"utf8"),{migrated:s,reason:"ok"}}export{E as AMP_VERSION,C as LOCAL_DETAIL_STORE,Dt as _resetGitStampCache,g as ampPaths,vt as appendEntry,kt as deleteEntry,Ft as describeStore,At as detailPath,A as ensureAmpDir,et as fromAmp,J as generateULID,ct as memoryFiles,Bt as migrateLegacy,Q as projectSlug,wt as pruneEntries,yt as readConfig,Ot as readDetail,xt as readEntries,X as resolveGlobalFile,lt as resolveRotationSettings,L as toAmp,Rt as updateEntry,Tt as updateInjectionConfig,Nt as writeDefaultConfig};
|
|
@@ -1 +1,3 @@
|
|
|
1
|
-
import*as
|
|
1
|
+
import*as n from"node:fs";import*as r from"node:path";import*as h from"node:crypto";import{fileURLToPath as y}from"node:url";const g=r.dirname(y(import.meta.url));function S(){return r.resolve(g,"../templates")}const o=e=>h.createHash("sha256").update(String(e).replace(/\r\n/g,`
|
|
2
|
+
`)).digest("hex"),b=new Set(["59ca1d6150462664c9677a112bc8cf57412b048788b8af3aef826acfe9a90fde","bb30ab374eace187685923ab2efb476703a9e0f13e0c99d6961104b04baa5953"]),w=[{src:["skills","infernoflow-memory","SKILL.md"],dst:["skills","infernoflow-memory","SKILL.md"],generated:!1},{src:["agents","memory-keeper.md"],dst:["agents","memory-keeper.md"],generated:!1},{src:["hooks","infernoflow-agent-guard.mjs"],dst:["hooks","infernoflow-agent-guard.mjs"],generated:!0}],u=new Set;function j(e){return n.existsSync(r.join(e,".ai-memory"))||n.existsSync(r.join(e,"inferno"))}function v(e){const t={created:[],updated:[],pending:[]};if(!e||!j(e))return t;const m=S();for(const i of w){const p=r.join(m,...i.src),a=r.join(e,".claude",...i.dst),d=[".claude",...i.dst].join("/");let s;try{s=n.readFileSync(p,"utf8")}catch{continue}let c=null;try{c=n.readFileSync(a,"utf8")}catch{}try{if(c==null)n.mkdirSync(r.dirname(a),{recursive:!0}),n.writeFileSync(a,s,"utf8"),t.created.push(d);else if(o(c)!==o(s))if(i.generated||b.has(o(c)))n.writeFileSync(a,s,"utf8"),t.updated.push(d);else{const f=a+".new";let l=null;try{l=n.readFileSync(f,"utf8")}catch{}(l==null||o(l)!==o(s))&&(n.writeFileSync(f,s,"utf8"),t.pending.push(d))}}catch{}}return t}function F(e){try{if(!e||u.has(e))return 0;u.add(e);const t=v(e);return t.pending.length&&process.stderr.write(` infernoflow: a newer version of ${t.pending.join(", ")} is available \u2014 you edited yours, so it was saved as <name>.new; merge when convenient.
|
|
3
|
+
`),t.created.length+t.updated.length}catch{return 0}}export{F as ensureClaudeAssets,v as syncClaudeAssets};
|
package/dist/lib/cleanTree.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
`),
|
|
3
|
-
`),
|
|
4
|
-
`:"")+(e||"")}function h(n,r,i){const o=
|
|
1
|
+
import*as l 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","# Hook state (machine-specific).",".ai-memory/.hook-state.json",".ai-memory/.trigger-state.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=l.readFileSync(o,"utf8")}catch{}e=T(e);const a=e.indexOf(d),c=e.indexOf(s),m=i.trim();let t;if(a!==-1&&c!==-1&&c>a){const f=e.slice(0,a).replace(/\s+$/,""),p=e.slice(c+s.length).replace(/^\s+/,"");t=(f?f+`
|
|
5
5
|
|
|
6
|
-
`:"")+
|
|
7
|
-
`+(
|
|
8
|
-
`+
|
|
6
|
+
`:"")+m+`
|
|
7
|
+
`+(p?`
|
|
8
|
+
`+p:"")}else e?t=e.replace(/\s+$/,"")+`
|
|
9
9
|
|
|
10
|
-
`+
|
|
11
|
-
`:t=
|
|
12
|
-
`;return t===e?"unchanged":(
|
|
10
|
+
`+m+`
|
|
11
|
+
`:t=m+`
|
|
12
|
+
`;return t===e?"unchanged":(l.mkdirSync(u.dirname(o),{recursive:!0}),l.writeFileSync(o,t,"utf8"),e?"updated":"created")}function E(n){return{gitignore:h(n,".gitignore",g),gitattributes:h(n,".gitattributes",A)}}export{s as GITIGNORE_END,d as GITIGNORE_START,E as applyCleanTreePolicy,h as ensureManagedBlock};
|
package/dist/lib/commands/ai.mjs
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import*as
|
|
2
|
-
|
|
1
|
+
import*as u from"node:fs";import*as v from"node:path";import*as x from"node:os";import*as R from"node:https";import*as U from"node:http";import*as G from"node:readline";import{bold as f,cyan as $,gray as c,green as d,yellow as w,red as y}from"../ui/output.mjs";function _(l){return v.join(l,"inferno")}function k(){const l=process.env.INFERNOFLOW_HOME||v.join(x.homedir(),".infernoflow");return v.join(l,"ai-credentials.json")}function K(){try{const l=JSON.parse(u.readFileSync(k(),"utf8"));return l&&typeof l=="object"?l:{}}catch{return{}}}function A(l){const o=k();u.mkdirSync(v.dirname(o),{recursive:!0}),u.writeFileSync(o,JSON.stringify(l,null,2)+`
|
|
2
|
+
`,{encoding:"utf8",mode:384});try{u.chmodSync(o,384)}catch{}}function E(l){const o=v.join(_(l),"integrations.json");let i={};if(u.existsSync(o))try{i=JSON.parse(u.readFileSync(o,"utf8"))||{}}catch{i={}}const n=K(),e=n.providers&&typeof n.providers=="object"?n.providers:{};let t=[];for(const[r,s]of Object.entries(i))s&&typeof s=="object"&&typeof s.apiKey=="string"&&s.apiKey&&(e[r]={...e[r]||{},apiKey:s.apiKey},delete s.apiKey,t.push(r));if(t.length)try{A({...n,providers:e}),u.writeFileSync(o,JSON.stringify(i,null,2)+`
|
|
3
|
+
`),process.stderr.write(` infernoflow: moved API key(s) for ${t.join(", ")} out of inferno/integrations.json into ${k()}.
|
|
4
|
+
infernoflow: if integrations.json was ever committed, revoke those keys with the provider \u2014 they are in your git history.
|
|
5
|
+
`)}catch{}for(const[r,s]of Object.entries(e))s&&typeof s.apiKey=="string"&&s.apiKey&&(i[r]={...i[r]||{},apiKey:s.apiKey});return i}function P(l,o){const i=_(l);u.existsSync(i)||u.mkdirSync(i,{recursive:!0});const n=K(),e=n.providers&&typeof n.providers=="object"?n.providers:{},t={};for(const[r,s]of Object.entries(o)){if(!s||typeof s!="object"){t[r]=s;continue}const{apiKey:a,fromEnv:m,...p}=s;t[r]=p,a&&!m&&(e[r]={...e[r]||{},apiKey:a})}A({...n,providers:e}),u.writeFileSync(v.join(i,"integrations.json"),JSON.stringify(t,null,2)+`
|
|
6
|
+
`)}const g=[{id:"anthropic",name:"Anthropic (Claude)",envKey:"ANTHROPIC_API_KEY",models:["claude-sonnet-4-6","claude-opus-4-6","claude-haiku-4-5-20251001"],default:"claude-sonnet-4-6",keyHint:"sk-ant-api03-\u2026",docsUrl:"https://console.anthropic.com/settings/keys"},{id:"openai",name:"OpenAI (GPT)",envKey:"OPENAI_API_KEY",models:["gpt-4o","gpt-4o-mini","gpt-4-turbo"],default:"gpt-4o",keyHint:"sk-\u2026",docsUrl:"https://platform.openai.com/api-keys"},{id:"gemini",name:"Google Gemini",envKey:"GOOGLE_AI_API_KEY",models:["gemini-2.0-flash","gemini-1.5-pro","gemini-1.5-flash"],default:"gemini-2.0-flash",keyHint:"AIza\u2026",docsUrl:"https://aistudio.google.com/app/apikey"},{id:"openrouter",name:"OpenRouter",envKey:"OPENROUTER_API_KEY",models:["anthropic/claude-sonnet-4-6","openai/gpt-4o","meta-llama/llama-3.1-8b-instruct:free"],default:"anthropic/claude-sonnet-4-6",keyHint:"sk-or-\u2026",docsUrl:"https://openrouter.ai/keys"},{id:"ollama",name:"Ollama (local)",envKey:null,models:["llama3.2","mistral","codellama","phi3"],default:"llama3.2",keyHint:null,docsUrl:"https://ollama.com"}];function S(l){return new Promise(o=>{const i=new URL(l),e=(i.protocol==="https:"?R:U).request({hostname:i.hostname,port:i.port||(i.protocol==="https:"?443:80),path:i.pathname+(i.search||""),method:"GET",timeout:5e3},t=>{let r="";t.on("data",s=>r+=s),t.on("end",()=>{try{o({status:t.statusCode,body:JSON.parse(r)})}catch{o({status:t.statusCode,body:r})}})});e.on("error",()=>o(null)),e.on("timeout",()=>{e.destroy(),o(null)}),e.end()})}async function j(l,o){const n={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY}[l],e=o[l]?.apiKey,t=n||e,r=n?"env":e?"~/.infernoflow/ai-credentials.json":null,s=o[l]?.model||g.find(a=>a.id===l)?.default;if(l==="ollama"){const a=await S("http://localhost:11434/api/tags").catch(()=>null);if(a?.status===200){const m=a.body?.models?.map(p=>p.name)||[];return{configured:!0,source:"local",model:o.ollama?.model||"llama3.2",available:!0,models:m}}return{configured:!1,source:null,model:null,available:!1}}return{configured:!!t,source:r,model:s,available:null,masked:t?t.slice(0,8)+"\u2026":null}}async function N(l,o,i){try{const n=await import("../ai/providerRouter.mjs");if(typeof n.callAI!="function")return null;const e=`Reply with exactly: "infernoflow AI test OK \u2014 ${l}"`;return await n.callAI(e,i,l)}catch{return null}}function h(l,o){return new Promise(i=>l.question(o,i))}async function Y(l){const o=E(l);console.log(),console.log(` ${f("infernoflow ai")} ${c("\u2014 provider status")}`),console.log();let i=!1;for(const n of g){const e=await j(n.id,o);e.configured&&(i=!0);const t=e.configured?d("\u2713"):c("\u25CB"),r=f(n.name.padEnd(22)),s=e.configured?`${d("configured")} ${c(e.source)} ${c("model: "+e.model)}${e.masked?" "+c(e.masked):""}`:c("not configured");console.log(` ${t} ${r} ${s}`)}console.log(),i?console.log(` ${c("Run")} ${$("infernoflow ai test")} ${c("to verify the active provider.")}`):(console.log(` ${w("No AI providers configured.")} Run: ${$("infernoflow ai setup")}`),console.log(` ${c("Without a provider, explain/why/review use structural fallbacks.")}`)),console.log()}async function T(l){const o=E(l),i={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,gemini:process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY,openrouter:process.env.OPENROUTER_API_KEY};console.log(),console.log(` ${f("\u{1F525} infernoflow ai setup")}`),console.log(` ${c("Connect an AI provider for explain, why, review, and changelog.")}`),console.log(),g.forEach((e,t)=>{const r=i[e.id],s=o[e.id]?.apiKey,a=r?d(" \u2713 key detected in environment"):s?d(" \u2713 key already saved"):"",m=f(String(t+1)),p=e.id==="ollama"?c(" (local, no key needed)"):"";console.log(` ${m}) ${f(e.name.padEnd(22))}${p}${a}`)}),console.log();const n=G.createInterface({input:process.stdin,output:process.stdout});try{const e=await h(n," Select provider [1]: "),t=(parseInt(e.trim())||1)-1;if(t<0||t>=g.length){console.log(y(` Invalid choice. Enter a number 1\u2013${g.length}.`));return}const r=g[t],s=r.id;if(console.log(),console.log(` ${f(r.name)}`),s==="ollama"){const a=await h(n," Ollama host [http://localhost:11434]: "),m=await h(n,` Model [${r.default}]: `);o.ollama={host:a.trim()||"http://localhost:11434",model:m.trim()||r.default},P(l,o),console.log(),process.stdout.write(` ${d("\u2713")} Saved. Testing connection\u2026 `),(await S(`${o.ollama.host}/api/tags`).catch(()=>null))?.status===200?console.log(d("OK")):(console.log(w("not reachable")),console.log(` ${w("\u26A0")} Start Ollama first: ${$("ollama serve")}`))}else{const a=i[s],m=o[s]?.apiKey,p=a||m;if(p){const I=a?"environment variable":"saved config";if(console.log(` ${d("\u2713")} API key detected from ${I}: ${c(p.slice(0,12)+"\u2026")}`),(await h(n," Use this key? [Y/n]: ")).trim().toLowerCase()==="n"){console.log(),r.docsUrl&&console.log(` ${c("Get a key at:")} ${$(r.docsUrl)}`);const b=await h(n," Paste new API key: ");if(!b.trim()){console.log(y(" No key provided. Exiting."));return}o[s]={apiKey:b.trim(),model:o[s]?.model||r.default}}else o[s]={apiKey:p,fromEnv:!!a,model:o[s]?.model||r.default}}else{console.log(` ${c("Get your API key at:")} ${$(r.docsUrl)}`),console.log(` ${c("Tip: paste the key below \u2014 it starts with")} ${c(r.keyHint)}`),console.log();const I=await h(n," Paste API key: ");if(!I.trim()){console.log(y(" No key provided. Exiting."));return}o[s]={apiKey:I.trim(),model:r.default}}const O=o[s].model;console.log(),console.log(` ${c("Available models:")} ${r.models.join(" ")}`);const C=await h(n,` Model [${O}]: `);o[s].model=C.trim()||O,P(l,o),console.log(),process.stdout.write(` ${d("\u2713")} Saved. Testing connection\u2026 `),(await N(s,o,l))?.text?console.log(d("OK")+c(` (${o[s].model})`)):(console.log(w("no response")),console.log(` ${w("\u26A0")} Connection failed \u2014 double-check your API key.`))}console.log(),console.log(` ${d("\u2713")} ${f(r.name)} is ready.`),console.log(` ${c("AI-powered commands:")} explain why review changelog`),console.log(),console.log(` ${c("API key stored outside the project:")} ${$(k())}`),console.log()}finally{n.close()}}async function H(l,o){const i=E(o),n=l.find(t=>!t.startsWith("--"))||null;console.log(),console.log(` ${f("infernoflow ai test")}`),console.log();const e=n?g.filter(t=>t.id===n):g;for(const t of e){if(!(await j(t.id,i)).configured){console.log(` ${c("\u25CB")} ${f(t.name.padEnd(22))} ${c("not configured \u2014 skipping")}`);continue}process.stdout.write(` ${w("\u2026")} ${f(t.name.padEnd(22))} testing\u2026 `);const s=await N(t.id,i,o);s?.text?(console.log(d("OK")+c(` (${s.model||t.id})`)),console.log(` ${c(s.text.trim().slice(0,80))}`)):(console.log(y("FAIL")),console.log(` ${y("No response \u2014 check API key or model name")}`))}console.log()}async function F(l,o){const i=E(o),n=l.find(t=>!t.startsWith("--"));if(n||(console.error(y("\u2717 Usage: infernoflow ai clear <provider>")),console.error(c(" Example: infernoflow ai clear openai")),process.exit(1)),!i[n]){console.log(c(` No config found for "${n}"`));return}delete i[n],P(o,i);const e=K();e.providers&&e.providers[n]&&(delete e.providers[n],A(e)),console.log(d(` \u2713 Cleared config for ${n}`))}async function W(l){const o=(l||[]).slice(1),i=o.find(t=>!t.startsWith("--"))||"status",n=o.filter(t=>t!==i),e=process.cwd();switch(i){case"setup":return T(e);case"status":return Y(e);case"test":return H(n,e);case"clear":return F(n,e);default:console.error(y(`\u2717 Unknown subcommand: "${i}"`)),console.error(c(" Usage: infernoflow ai <setup|status|test|clear>")),process.exit(1)}}export{W as aiCommand};
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import"node:fs";import"node:path";import{bold as
|
|
2
|
-
No session memory yet.`)),console.log(
|
|
3
|
-
`));return}const
|
|
1
|
+
import{describeStore as L}from"../amp/io.mjs";import"node:fs";import"node:path";import{bold as O,cyan as T,gray as s,green as M,yellow as j,red as q}from"../ui/output.mjs";import{readEntries as Y}from"../amp/io.mjs";const p={gotcha:0,decision:1,attempt:2,preference:3,theme:4,note:5,error:5,handoff:6},I={gotcha:"\u26A0",decision:"\u2713",attempt:"\u21BA",preference:"\u2666",theme:"\u{1F3A8}",note:"\xB7",error:"\u2717",handoff:"\u2192"},N={gotcha:j,decision:M,attempt:T,preference:T,theme:T,note:s,error:q,handoff:s};function E(n){return n.toLowerCase().replace(/[^a-z0-9\s]/g," ").split(/\s+/).filter(t=>t.length>1)}function P(n,t){const l=[n.summary||"",n.type||""].join(" ").toLowerCase(),o=E(l);let r=0;for(const c of t)(n.summary||"").toLowerCase().includes(c)&&(r+=3),o.includes(c)&&(r+=1),o.some(a=>a.startsWith(c)||c.startsWith(a))&&(r+=.5);return r}function v(n){if(n==null||n==="")return"";const t=new Date(n);if(isNaN(t.getTime()))return"";const l=r=>new Date(r.getFullYear(),r.getMonth(),r.getDate()).getTime(),o=Math.round((l(new Date)-l(t))/864e5);return o<=0?`today ${t.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})}`:o===1?"yesterday":o<7?`${o}d ago`:o<30?`${Math.floor(o/7)}w ago`:t.toLocaleDateString("en-GB",{day:"2-digit",month:"short"})}function F(n,t){const l=n.type||"note",o=I[l]||"\xB7",r=N[l]||s,c=n.result?s(` [${n.result}]`):"",a=n.agent?s(` \u2014 ${n.agent}`):"",e=s(` (${v(n.ts)})`);let i=n.summary||"";if(t)for(const d of t){const u=new RegExp(`(${d})`,"gi");i=i.replace(u,D=>O(D))}console.log(` ${r(o+" "+l.padEnd(11))}${c}${a}${e}`),console.log(` ${i}`)}function _(n){return Y(n)}function W(n,t){if(!t||!n.file)return 0;const l=e=>String(e).replace(/\\/g,"/").replace(/^\.\//,""),o=l(n.file),r=l(t);if(o===r||o.endsWith("/"+r)||r.endsWith("/"+o))return 2;const c=o.includes("/")?o.slice(0,o.lastIndexOf("/")):"",a=r.includes("/")?r.slice(0,r.lastIndexOf("/")):"";return c&&c===a?1:0}function b(n,t,l,o,r=null){let c=n;l&&(c=c.filter(e=>(e.type||"note")===l));let a;return t.length>0?a=c.map(e=>({entry:e,score:P(e,t)})).filter(({score:e})=>e>0).map(e=>({...e,score:e.score+3*W(e.entry,r)})).sort((e,i)=>{if(i.score!==e.score)return i.score-e.score;const d=p[e.entry.type]??9,u=p[i.entry.type]??9;return d!==u?d-u:new Date(i.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}):a=c.map(e=>({entry:e,score:1+W(e,r)})).sort((e,i)=>{if(i.score!==e.score)return i.score-e.score;const d=p[e.entry.type]??9,u=p[i.entry.type]??9;return d!==u?d-u:new Date(i.entry.ts||0).getTime()-new Date(e.entry.ts||0).getTime()}),a.slice(0,o||20)}async function z(n=[]){const t=n,l=t.indexOf("--type"),o=l!==-1?t[l+1]:null,r=t.indexOf("--limit")!==-1?t.indexOf("--limit"):t.indexOf("-n"),c=r!==-1?parseInt(t[r+1]||"20",10):15,a=t.includes("--json"),e=t.includes("--recent")||t.includes("-r"),i=t.indexOf("--file"),d=i!==-1?t[i+1]:null,u=t.slice(1),m=u.filter((f,g)=>!(f.startsWith("--")||g>0&&u[g-1].startsWith("--"))).join(" ").trim(),x=e?[]:E(m),k=process.cwd(),y=_(k);if(y.length===0){if(a){console.log(JSON.stringify({results:[],total:0}));return}console.log(s(`
|
|
2
|
+
No session memory yet.`)),console.log(s(` Run: infernoflow log "<what happened>" --type gotcha
|
|
3
|
+
`));return}const h=e?y.slice(-c).reverse().map(f=>({entry:f,score:1})):b(y,x,o,c,d);if(a){console.log(JSON.stringify({query:m,type:o,total:y.length,matched:h.length,results:h.map(({entry:f,score:g})=>({...f,relevanceScore:g}))},null,2));return}if(console.log(),console.log(m?` ${O("\u{1F525} infernoflow ask")} ${T(`"${m}"`)}${o?s(` [${o}]`):""}`:e?` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 recent entries")}`:` ${O("\u{1F525} infernoflow ask")} ${s("\u2014 all entries")}${o?s(` [${o}]`):""}`),console.log(s(` ${L(process.cwd())}`)),console.log(s(` ${"\u2500".repeat(52)}`)),h.length===0){console.log(),m?(console.log(s(` No entries found for "${m}"`)),o&&console.log(s(` Try removing --type ${o} to widen the search`))):console.log(s(" No entries found.")),console.log();return}const w=new Map;for(const{entry:f,score:g}of h){const $=f.type||"note";w.has($)||w.set($,[]),w.get($).push({entry:f,score:g})}const C=Object.keys(p).sort((f,g)=>p[f]-p[g]);let S=0;for(const f of C){const g=w.get(f);if(!g?.length)continue;console.log();const $=N[f]||s;console.log($(` ${I[f]} ${f.toUpperCase()}S (${g.length})`)),console.log(s(" "+"\u2500".repeat(50)));for(const{entry:R}of g)console.log(),F(R,x),S++}console.log(),console.log(s(` ${S} result${S!==1?"s":""} from ${y.length} total entries`)),h.length===c&&y.length>c&&console.log(s(" Use --limit N to see more")),console.log()}export{z as askCommand};
|
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import*as k from"node:fs";import*as x from"node:path";import{bold as
|
|
2
|
-
`+
|
|
3
|
-
`)),console.log(t(" Drop: ")+f('infernoflow bookmark "<label>"')+t(" auto-captures the session transcript as context")),console.log(t(" ")+t('add --note "..." / --detail-file <path|-> for explicit context \xB7 --marker for label only')),console.log(t(" List: ")+f("infernoflow bookmark list")),console.log(t(" Recall: ")+f("infernoflow bookmark show <id|label>")),console.log(t(" Remove: ")+f("infernoflow bookmark rm <id|label>")),console.log()}async function M(e=[]){const n=process.cwd(),r=e[0]==="bookmark"?e.slice(1):e;if(r.length===0){O();return}const o=r[0];return o==="list"||o==="ls"?j(n,r.slice(1)):o==="show"||o==="jump"||o==="recall"?U(n,r.slice(1)):o==="rm"||o==="remove"||o==="delete"?T(n,r.slice(1)):_(n,r)}function _(e,n){const r=w(n,"--note"),o=w(n,"--detail-file"),l=w(n,"--tags"),s=new Set([r,o,l].filter(Boolean)),c=n.filter(m=>!m.startsWith("--")&&!s.has(m)).join(" ").trim();c||(O(),process.exit(1));const a=n.includes("--marker");let i=null,
|
|
1
|
+
import*as k from"node:fs";import*as x from"node:path";import{bold as d,cyan as f,gray as t,green as h,yellow as g,red as p}from"../ui/output.mjs";import{readEntries as L,appendEntry as R,deleteEntry as D,readDetail as I}from"../amp/io.mjs";import{harvestSnapshot as B}from"../transcript.mjs";const b="bookmark",F=e=>Array.isArray(e.tags)&&e.tags.includes(b);function y(e){return L(e).filter(F)}function $(e){const n=new Date(e);return isNaN(n.getTime())?"":n.toLocaleString("en-GB",{day:"2-digit",month:"short",hour:"2-digit",minute:"2-digit"})}function W(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude":process.env.WINDSURF_SESSION?"windsurf":process.env.INFERNOFLOW_AGENT?process.env.INFERNOFLOW_AGENT:"human"}const w=(e,n)=>{const r=e.indexOf(n);return r!==-1&&e[r+1]?e[r+1]:null};function N(e,n){if(!n)return{error:"no-token"};const r=e.filter(s=>s.id&&(s.id===n||s.id.startsWith(n)));if(r.length===1)return{bookmark:r[0]};if(r.length>1)return{error:"ambiguous",matches:r};const o=n.toLowerCase(),l=e.filter(s=>(s.msg||s.summary||"").toLowerCase().includes(o));return l.length===0?{error:"none"}:l.length===1?{bookmark:l[0]}:{bookmark:l[l.length-1],ambiguousLabel:l}}function O(){console.log(`
|
|
2
|
+
`+d("\u{1F516} infernoflow bookmark")+t(` \u2014 named session resume points
|
|
3
|
+
`)),console.log(t(" Drop: ")+f('infernoflow bookmark "<label>"')+t(" auto-captures the session transcript as context")),console.log(t(" ")+t('add --note "..." / --detail-file <path|-> for explicit context \xB7 --marker for label only')),console.log(t(" List: ")+f("infernoflow bookmark list")),console.log(t(" Recall: ")+f("infernoflow bookmark show <id|label>")),console.log(t(" Remove: ")+f("infernoflow bookmark rm <id|label>")),console.log()}async function M(e=[]){const n=process.cwd(),r=e[0]==="bookmark"?e.slice(1):e;if(r.length===0){O();return}const o=r[0];return o==="list"||o==="ls"?j(n,r.slice(1)):o==="show"||o==="jump"||o==="recall"?U(n,r.slice(1)):o==="rm"||o==="remove"||o==="delete"?T(n,r.slice(1)):_(n,r)}function _(e,n){const r=w(n,"--note"),o=w(n,"--detail-file"),l=w(n,"--tags"),s=new Set([r,o,l].filter(Boolean)),c=n.filter(m=>!m.startsWith("--")&&!s.has(m)).join(" ").trim();c||(O(),process.exit(1));const a=n.includes("--marker");let i=null,u=null;if(o)try{i=o==="-"?k.readFileSync(0,"utf8"):k.readFileSync(o,"utf8"),u="file"}catch(m){console.error(p(`
|
|
4
4
|
\u2718 Could not read --detail-file ${o}: ${m.message}
|
|
5
|
-
`)),process.exit(1)}else if(r)i=r,
|
|
5
|
+
`)),process.exit(1)}else if(r)i=r,u="note";else if(!a)try{i=B(e),i&&(u="transcript")}catch{}const v=x.join(e,".ai-memory"),E=x.join(e,"inferno");!k.existsSync(v)&&!k.existsSync(E)&&(console.error(p(`
|
|
6
6
|
\u2718 no .ai-memory/ or inferno/ \u2014 run: infernoflow init
|
|
7
|
-
`)),process.exit(1));const A=l?l.split(",").map(m=>m.trim()).filter(Boolean):[],C=[b,...A.filter(m=>m!==b)],S=R(e,{ts:new Date().toISOString(),agent:W(),type:"note",summary:c,tags:C,...i&&i.trim()?{detail:i}:{}});console.log(),console.log(" "+h("\u{1F516} Bookmark saved: ")+
|
|
8
|
-
`+
|
|
7
|
+
`)),process.exit(1));const A=l?l.split(",").map(m=>m.trim()).filter(Boolean):[],C=[b,...A.filter(m=>m!==b)],S=R(e,{ts:new Date().toISOString(),agent:W(),type:"note",summary:c,tags:C,...i&&i.trim()?{detail:i}:{},...u==="transcript"?{detailLocal:!0}:{}});console.log(),console.log(" "+h("\u{1F516} Bookmark saved: ")+d(c)+t(` ${S.id}`)),i&&i.trim()?console.log(" "+t(`${u==="transcript"?"auto-captured session transcript":"captured context"} (${i.length} chars) \u2014 recall with `)+f(`infernoflow bookmark show ${S.id.slice(0,12)}`)):console.log(" "+t(`${a?"marker only (--marker)":"marker only \u2014 no session transcript found here"} \u2014 recall by label, or add context with --note / --detail-file`)),console.log()}function j(e,n){const r=y(e);if(n.includes("--json")){console.log(JSON.stringify(r.map(o=>({id:o.id,label:o.msg||o.summary,ts:o.ts,hasContext:!!o.detailRef})),null,2));return}if(console.log(`
|
|
8
|
+
`+d("\u{1F516} infernoflow bookmarks")),console.log(" "+"\u2500".repeat(50)),r.length===0){console.log(t(`
|
|
9
9
|
No bookmarks yet. Drop one: `)+f('infernoflow bookmark "<label>"')+`
|
|
10
10
|
`);return}[...r].reverse().forEach((o,l)=>{const s=o.detailRef?h(" \u25CF"):t(" \u25CB");console.log(` ${t(String(l+1).padStart(3))}${s} ${t($(o.ts))} ${o.msg||o.summary} ${t(o.id.slice(0,12))}`)}),console.log(t(`
|
|
11
11
|
\u25CF has saved context \xB7 \u25CB marker only \u2014 recall with: `)+f("infernoflow bookmark show <id|label>")+`
|
|
12
|
-
`)}function U(e,n){const r=n.includes("--json"),o=n.find(i=>i&&!i.startsWith("-")),l=y(e);l.length===0&&(console.error(
|
|
12
|
+
`)}function U(e,n){const r=n.includes("--json"),o=n.find(i=>i&&!i.startsWith("-")),l=y(e);l.length===0&&(console.error(g(`
|
|
13
13
|
No bookmarks yet.
|
|
14
14
|
`)),process.exit(1));const s=N(l,o);if(s.error==="no-token"&&(console.error(t(`
|
|
15
15
|
Usage: `)+f("infernoflow bookmark show <id|label>")+`
|
|
16
16
|
`),process.exit(1)),s.error==="none"&&(console.error(p(`
|
|
17
17
|
No bookmark matches: ${o}
|
|
18
|
-
`)),process.exit(1)),s.error==="ambiguous"){console.error(
|
|
19
|
-
Ambiguous \u2014 ${s.matches.length} bookmarks match "${o}":`));for(const i of s.matches.slice(0,8))console.error(t(` ${i.id.slice(0,14)} `)+(i.msg||i.summary||""));console.error(""),process.exit(1)}const c=s.bookmark,a=I(e,c);if(r){console.log(JSON.stringify({id:c.id,label:c.msg||c.summary,ts:c.ts,detail:a||null},null,2));return}console.log(),console.log(" "+
|
|
18
|
+
`)),process.exit(1)),s.error==="ambiguous"){console.error(g(`
|
|
19
|
+
Ambiguous \u2014 ${s.matches.length} bookmarks match "${o}":`));for(const i of s.matches.slice(0,8))console.error(t(` ${i.id.slice(0,14)} `)+(i.msg||i.summary||""));console.error(""),process.exit(1)}const c=s.bookmark,a=I(e,c);if(r){console.log(JSON.stringify({id:c.id,label:c.msg||c.summary,ts:c.ts,detail:a||null},null,2));return}console.log(),console.log(" "+d("\u{1F516} "+(c.msg||c.summary))),console.log(" "+t(`${$(c.ts)} \xB7 ${c.id}`)),s.ambiguousLabel&&console.log(" "+t(`(newest of ${s.ambiguousLabel.length} matching "${o}")`)),console.log(" "+"\u2500".repeat(50)),a&&a.trim()?(console.log(),console.log(a.trim()),console.log()):console.log(t(`
|
|
20
20
|
(marker only \u2014 no context was captured for this bookmark)
|
|
21
|
-
`))}function T(e,n){const r=n.find(a=>a&&!a.startsWith("-")),o=y(e);o.length===0&&(console.error(
|
|
21
|
+
`))}function T(e,n){const r=n.find(a=>a&&!a.startsWith("-")),o=y(e);o.length===0&&(console.error(g(`
|
|
22
22
|
No bookmarks to remove.
|
|
23
|
-
`)),process.exit(1));const l=N(o,r);if(l.error){if(l.error==="ambiguous"){console.error(
|
|
23
|
+
`)),process.exit(1));const l=N(o,r);if(l.error){if(l.error==="ambiguous"){console.error(g(`
|
|
24
24
|
Ambiguous \u2014 be specific:`));for(const a of l.matches.slice(0,8))console.error(t(` ${a.id.slice(0,14)} `)+(a.msg||a.summary||""));console.error("")}else l.error==="none"?console.error(p(`
|
|
25
25
|
No bookmark matches: ${r}
|
|
26
26
|
`)):console.error(t(`
|
|
27
27
|
Usage: `)+f("infernoflow bookmark rm <id|label>")+`
|
|
28
|
-
`);process.exit(1)}const s=l.bookmark,{removed:c}=D(e,s.id);console.log(),console.log(c>0?" "+h("\u2714")+" Removed bookmark "+
|
|
28
|
+
`);process.exit(1)}const s=l.bookmark,{removed:c}=D(e,s.id);console.log(),console.log(c>0?" "+h("\u2714")+" Removed bookmark "+d(s.msg||s.summary)+t(` ${s.id}`):" "+g("Nothing removed.")),console.log()}export{b as BOOKMARK_TAG,M as bookmarkCommand};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import*as d from"node:fs";import*as g from"node:path";import{readEntries as h,deleteEntry as w,toAmp as S,projectSlug as $}from"../amp/io.mjs";import{isNoise as j}from"../memoryView.mjs";import{findProjectRoot as k}from"../projectRoot.mjs";import{infernoflowHome as v}from"../personalConfig.mjs";import{bold as N,gray as l,green as b,yellow as A}from"../ui/output.mjs";const m=r=>(typeof r.ts=="number"?r.ts:Date.parse(r.ts||0))||0;function D(r){const i=h(r).sort((o,s)=>m(o)-m(s)),e={commitNotes:[],duplicates:[],oldFrustration:[]},n=new Map,c=Date.now()-14*864e5;for(const o of i){if(Array.isArray(o.tags)&&o.tags.includes("bookmark"))continue;if(j(o)){e.commitNotes.push(o);continue}if(o.source==="hook"&&/^User frustration:/.test(o.summary||"")&&m(o)<c){e.oldFrustration.push(o);continue}const s=`${o.type}\0${o.file||""}\0${String(o.summary||"").trim().toLowerCase()}`;n.has(s)?e.duplicates.push(o):n.set(s,o)}return e}async function M(r=[]){const i=process.cwd(),e=r.includes("--apply"),n=D(i),c=[["commit notes (duplicate git history)",n.commitNotes],["duplicates",n.duplicates],["raw frustration prompts > 14 days",n.oldFrustration]],o=c.reduce((t,[,a])=>t+a.length,0);console.log(`
|
|
2
|
+
`+N("\u{1F525} infernoflow curate"));for(const[t,a]of c){console.log(` ${String(a.length).padStart(4)} ${t}`);for(const y of a.slice(0,3))console.log(l(" "+String(y.summary||"").slice(0,90)))}if(!o){console.log(l(`
|
|
3
|
+
Nothing to curate.
|
|
4
|
+
`));return}if(!e){console.log(`
|
|
5
|
+
`+A("Dry run.")+l(` Add --apply to remove them (archived first).
|
|
6
|
+
`));return}const s=k(i),f=g.join(v(),"backups");d.mkdirSync(f,{recursive:!0});const p=g.join(f,`${$(s)}-curated-${new Date().toISOString().replace(/[:.]/g,"-")}.jsonl`),u=c.flatMap(([,t])=>t);d.writeFileSync(p,u.map(t=>JSON.stringify(S(t))).join(`
|
|
7
|
+
`)+`
|
|
8
|
+
`,"utf8");for(const t of u)w(i,t.id);console.log(`
|
|
9
|
+
`+b(`\u2714 removed ${u.length}`)+l(` \u2014 archived to ${p}
|
|
10
|
+
`))}export{D as curateCandidates,M as curateCommand};
|