infernoflow 0.7.0 → 0.7.1
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/bin/infernoflow.mjs +1 -0
- package/lib/commands/context.mjs +97 -199
- package/package.json +1 -1
package/bin/infernoflow.mjs
CHANGED
|
@@ -23,6 +23,7 @@ const HELP = `
|
|
|
23
23
|
--working "..." What you are building right now
|
|
24
24
|
--decision "..." Record a decision or note
|
|
25
25
|
--show Print context without writing file
|
|
26
|
+
--copy, -c Copy context to clipboard instantly
|
|
26
27
|
--reset Clear all stored state
|
|
27
28
|
|
|
28
29
|
${bold("Typical workflow:")}
|
package/lib/commands/context.mjs
CHANGED
|
@@ -1,230 +1,128 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
3
4
|
import { bold, gray, cyan, red, green, yellow } from "../ui/output.mjs";
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
const CONTEXT_FILE = path.join(INFERNO_DIR, "CONTEXT.md");
|
|
7
|
-
const STATE_FILE = path.join(INFERNO_DIR, "context-state.json");
|
|
8
|
-
|
|
9
|
-
function readJSON(f) {
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(fs.readFileSync(f, "utf8"));
|
|
12
|
-
} catch {
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
function readFile(f) {
|
|
6
|
+
function copyToClipboard(text) {
|
|
17
7
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const r = readFile(STATE_FILE);
|
|
25
|
-
if (!r) return {};
|
|
26
|
-
try {
|
|
27
|
-
return JSON.parse(r);
|
|
28
|
-
} catch {
|
|
29
|
-
return {};
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function saveState(s) {
|
|
33
|
-
fs.writeFileSync(STATE_FILE, JSON.stringify(s, null, 2), "utf8");
|
|
8
|
+
const p = process.platform;
|
|
9
|
+
if (p === "win32") execSync("clip", { input: text });
|
|
10
|
+
else if (p === "darwin") execSync("pbcopy", { input: text });
|
|
11
|
+
else { try { execSync("xclip -selection clipboard", { input: text }); } catch { execSync("xsel --clipboard --input", { input: text }); } }
|
|
12
|
+
return true;
|
|
13
|
+
} catch { return false; }
|
|
34
14
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
cur = { title: line.replace("## ", "").trim(), items: [] };
|
|
52
|
-
} else if (cur && line.startsWith("- ")) {
|
|
53
|
-
cur.items.push(line.replace("- ", "").trim());
|
|
54
|
-
}
|
|
15
|
+
|
|
16
|
+
const INFERNO_DIR = "inferno";
|
|
17
|
+
const CONTEXT_FILE = path.join(INFERNO_DIR, "CONTEXT.md");
|
|
18
|
+
const STATE_FILE = path.join(INFERNO_DIR, "context-state.json");
|
|
19
|
+
|
|
20
|
+
function readJSON(f) { try { return JSON.parse(fs.readFileSync(f,"utf8")); } catch { return null; } }
|
|
21
|
+
function readFile(f) { try { return fs.readFileSync(f,"utf8"); } catch { return null; } }
|
|
22
|
+
function loadState() { const r=readFile(STATE_FILE); if(!r) return {}; try { return JSON.parse(r); } catch { return {}; } }
|
|
23
|
+
function saveState(s) { fs.writeFileSync(STATE_FILE,JSON.stringify(s,null,2),"utf8"); }
|
|
24
|
+
function fmtDate(iso) { if(!iso) return "unknown"; return new Date(iso).toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"}); }
|
|
25
|
+
function parseChangelog(txt,max) {
|
|
26
|
+
if(!txt) return [];
|
|
27
|
+
const entries=[]; let cur=null;
|
|
28
|
+
for(const line of txt.split("\n")) {
|
|
29
|
+
if(line.startsWith("## ")) { if(cur&&entries.length<max) entries.push(cur); if(entries.length>=max) break; cur={title:line.replace("## ","").trim(),items:[]}; }
|
|
30
|
+
else if(cur&&line.startsWith("- ")) cur.items.push(line.replace("- ","").trim());
|
|
55
31
|
}
|
|
56
|
-
if
|
|
57
|
-
return entries.filter(
|
|
32
|
+
if(cur&&entries.length<max) entries.push(cur);
|
|
33
|
+
return entries.filter(e=>e.items.length>0);
|
|
58
34
|
}
|
|
59
35
|
|
|
60
36
|
export async function contextCommand(args) {
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
const intent = flag("--intent") || flag("-i");
|
|
68
|
-
const working = flag("--working") || flag("-w");
|
|
37
|
+
const has = (f) => args.includes(f);
|
|
38
|
+
const flag = (f) => { const i=args.indexOf(f); return i!==-1&&args[i+1]?args[i+1]:null; };
|
|
39
|
+
|
|
40
|
+
const intent = flag("--intent") || flag("-i");
|
|
41
|
+
const working = flag("--working") || flag("-w");
|
|
69
42
|
const decision = flag("--decision") || flag("-d");
|
|
70
|
-
const showOnly = has("--show")
|
|
43
|
+
const showOnly = has("--show") || has("-s");
|
|
44
|
+
const copyFlag = has("--copy") || has("-c");
|
|
45
|
+
const resetFlag= has("--reset");
|
|
71
46
|
|
|
72
|
-
console.log("\n "
|
|
73
|
-
console.log(" "
|
|
47
|
+
console.log("\n "+bold("��� infernoflow — context"));
|
|
48
|
+
console.log(" "+"─".repeat(50)+"\n");
|
|
74
49
|
|
|
75
|
-
if
|
|
50
|
+
if(!fs.existsSync(INFERNO_DIR)){
|
|
76
51
|
console.error(red(" ✘ inferno/ not found"));
|
|
77
52
|
console.error(gray(" → Run: infernoflow init\n"));
|
|
78
53
|
process.exit(1);
|
|
79
54
|
}
|
|
80
55
|
|
|
81
|
-
const contract
|
|
82
|
-
const capabilities = readJSON(path.join(INFERNO_DIR,
|
|
83
|
-
const changelog
|
|
56
|
+
const contract = readJSON(path.join(INFERNO_DIR,"contract.json"));
|
|
57
|
+
const capabilities = readJSON(path.join(INFERNO_DIR,"capabilities.json"));
|
|
58
|
+
const changelog = readFile(path.join(INFERNO_DIR,"CHANGELOG.md"));
|
|
84
59
|
|
|
85
|
-
if
|
|
60
|
+
if(!contract||!capabilities){
|
|
86
61
|
console.error(red(" ✘ Missing contract.json or capabilities.json\n"));
|
|
87
62
|
process.exit(1);
|
|
88
63
|
}
|
|
89
64
|
|
|
90
65
|
let state = loadState();
|
|
91
|
-
|
|
92
|
-
if (intent)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const capList = capabilities.capabilities || [];
|
|
110
|
-
const allInSync = capList.length === (contract.capabilities || []).length;
|
|
111
|
-
const recent = parseChangelog(changelog, 3);
|
|
112
|
-
const version = String(contract.policyVersion).replace(/^v/i, "");
|
|
113
|
-
const now = new Date().toLocaleDateString("en-GB", {
|
|
114
|
-
day: "2-digit",
|
|
115
|
-
month: "short",
|
|
116
|
-
year: "numeric",
|
|
117
|
-
});
|
|
118
|
-
const syncBadge = allInSync ? "✓ validated" : "⚠ out of sync";
|
|
119
|
-
|
|
120
|
-
const capLines = capList
|
|
121
|
-
.map((c) => "- **" + c.id + "** — " + c.title)
|
|
122
|
-
.join("\n");
|
|
123
|
-
|
|
124
|
-
const changelogLines =
|
|
125
|
-
recent.length > 0
|
|
126
|
-
? recent
|
|
127
|
-
.map(
|
|
128
|
-
(e) =>
|
|
129
|
-
"### " +
|
|
130
|
-
e.title +
|
|
131
|
-
"\n" +
|
|
132
|
-
e.items.map((i) => " - " + i).join("\n"),
|
|
133
|
-
)
|
|
134
|
-
.join("\n\n")
|
|
135
|
-
: "_No recent changes_";
|
|
136
|
-
|
|
137
|
-
const intentLine = state.intent
|
|
138
|
-
? state.intent + " _(" + formatDate(state.intentUpdated) + ")_"
|
|
139
|
-
: '_Not set — run: infernoflow context --intent "..._"';
|
|
140
|
-
|
|
141
|
-
const workingLine = state.working
|
|
142
|
-
? state.working + " _(" + formatDate(state.workingUpdated) + ")_"
|
|
143
|
-
: '_Not set — run: infernoflow context --working "..._"';
|
|
144
|
-
|
|
145
|
-
const decLines =
|
|
146
|
-
state.decisions && state.decisions.length > 0
|
|
147
|
-
? state.decisions
|
|
148
|
-
.slice(-5)
|
|
149
|
-
.map((d) => "- " + d.text + " _(" + formatDate(d.date) + ")_")
|
|
150
|
-
.join("\n")
|
|
151
|
-
: "_No decisions recorded_";
|
|
66
|
+
if(resetFlag){ state={}; console.log(yellow(" ⚠ State reset\n")); }
|
|
67
|
+
if(intent) { state.intent=intent; state.intentUpdated=new Date().toISOString(); console.log(green(' ✔ Intent saved: "'+intent+'"')); }
|
|
68
|
+
if(working) { state.working=working; state.workingUpdated=new Date().toISOString(); console.log(green(' ✔ Working on: "'+working+'"')); }
|
|
69
|
+
if(decision) { if(!state.decisions) state.decisions=[]; state.decisions.push({text:decision,date:new Date().toISOString()}); console.log(green(' ✔ Decision recorded: "'+decision+'"')); }
|
|
70
|
+
if(intent||working||decision) saveState(state);
|
|
71
|
+
|
|
72
|
+
const capList = capabilities.capabilities||[];
|
|
73
|
+
const allInSync = capList.length===(contract.capabilities||[]).length;
|
|
74
|
+
const recent = parseChangelog(changelog,3);
|
|
75
|
+
const version = String(contract.policyVersion).replace(/^v/i,"");
|
|
76
|
+
const now = new Date().toLocaleDateString("en-GB",{day:"2-digit",month:"short",year:"numeric"});
|
|
77
|
+
const syncBadge = allInSync?"✓ validated":"⚠ out of sync";
|
|
78
|
+
|
|
79
|
+
const capLines = capList.map(c=>"- **"+c.id+"** — "+c.title).join("\n");
|
|
80
|
+
const chgLines = recent.length>0 ? recent.map(e=>"### "+e.title+"\n"+e.items.map(i=>" - "+i).join("\n")).join("\n\n") : "_No recent changes_";
|
|
81
|
+
const intentLine = state.intent ? state.intent+" _("+fmtDate(state.intentUpdated)+")_" : "_Not set — run: infernoflow context --intent \"...\"_";
|
|
82
|
+
const workingLine = state.working ? state.working+" _("+fmtDate(state.workingUpdated)+")_" : "_Not set — run: infernoflow context --working \"...\"_";
|
|
83
|
+
const decLines = state.decisions&&state.decisions.length>0 ? state.decisions.slice(-5).map(d=>"- "+d.text+" _("+fmtDate(d.date)+")_").join("\n") : "_No decisions recorded_";
|
|
152
84
|
|
|
153
85
|
const md = [
|
|
154
|
-
"# Project Context — "
|
|
155
|
-
"> Generated by infernoflow | "
|
|
156
|
-
"",
|
|
157
|
-
"---",
|
|
158
|
-
"",
|
|
159
|
-
"##
|
|
160
|
-
"",
|
|
161
|
-
|
|
162
|
-
"",
|
|
163
|
-
"---",
|
|
164
|
-
"",
|
|
165
|
-
"##
|
|
166
|
-
"",
|
|
167
|
-
|
|
168
|
-
"",
|
|
169
|
-
"---",
|
|
170
|
-
"",
|
|
171
|
-
"## Current state",
|
|
172
|
-
"",
|
|
173
|
-
"- **Capabilities:** " + capList.length,
|
|
174
|
-
"- **Version:** v" + version,
|
|
175
|
-
"- **Sync:** " + syncBadge,
|
|
176
|
-
"",
|
|
177
|
-
"---",
|
|
178
|
-
"",
|
|
179
|
-
"## What I am working on right now",
|
|
180
|
-
"",
|
|
181
|
-
workingLine,
|
|
182
|
-
"",
|
|
183
|
-
"---",
|
|
184
|
-
"",
|
|
185
|
-
"## Intent — what I want to build next",
|
|
186
|
-
"",
|
|
187
|
-
intentLine,
|
|
188
|
-
"",
|
|
189
|
-
"---",
|
|
190
|
-
"",
|
|
191
|
-
"## Decisions & notes",
|
|
192
|
-
"",
|
|
193
|
-
decLines,
|
|
194
|
-
"",
|
|
195
|
-
"---",
|
|
196
|
-
"_Paste this block at the start of any new AI session._",
|
|
86
|
+
"# Project Context — "+contract.policyId+" v"+version,
|
|
87
|
+
"> Generated by infernoflow | "+now+" | "+syncBadge,
|
|
88
|
+
"","---","",
|
|
89
|
+
"## What this system does","",capLines,"","---","",
|
|
90
|
+
"## Recent changes","",chgLines,"","---","",
|
|
91
|
+
"## Current state","",
|
|
92
|
+
"- **Capabilities:** "+capList.length,
|
|
93
|
+
"- **Version:** v"+version,
|
|
94
|
+
"- **Sync:** "+syncBadge,
|
|
95
|
+
"","---","",
|
|
96
|
+
"## What I am working on right now","",workingLine,"","---","",
|
|
97
|
+
"## Intent — what I want to build next","",intentLine,"","---","",
|
|
98
|
+
"## Decisions & notes","",decLines,"","---",
|
|
99
|
+
"_Paste this block at the start of any new AI session._"
|
|
197
100
|
].join("\n");
|
|
198
101
|
|
|
199
|
-
if
|
|
200
|
-
|
|
201
|
-
|
|
102
|
+
if(!showOnly){ fs.writeFileSync(CONTEXT_FILE,md,"utf8"); console.log(green("\n ✔ Context written → "+CONTEXT_FILE)); }
|
|
103
|
+
|
|
104
|
+
if(copyFlag){
|
|
105
|
+
const ok=copyToClipboard(md);
|
|
106
|
+
console.log(ok ? green(" ✔ Copied to clipboard — paste with Ctrl+V") : yellow(" ⚠ Clipboard copy failed — open inferno/CONTEXT.md manually"));
|
|
202
107
|
}
|
|
203
108
|
|
|
204
|
-
console.log("\n "
|
|
205
|
-
console.log(" "
|
|
206
|
-
console.log(" Project "
|
|
207
|
-
console.log(" Capabilities "
|
|
208
|
-
console.log(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
);
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
"
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
console.log(" " + bold("Ready to use:"));
|
|
225
|
-
console.log(" " + cyan("1.") + " Open " + cyan("inferno/CONTEXT.md"));
|
|
226
|
-
console.log(" " + cyan("2.") + " Copy everything");
|
|
227
|
-
console.log(
|
|
228
|
-
" " + cyan("3.") + " Paste at the start of your next AI session\n",
|
|
229
|
-
);
|
|
109
|
+
console.log("\n "+bold("Context Summary"));
|
|
110
|
+
console.log(" "+"─".repeat(50));
|
|
111
|
+
console.log(" Project "+contract.policyId+" — v"+version);
|
|
112
|
+
console.log(" Capabilities "+capList.length+" registered");
|
|
113
|
+
console.log(" Sync "+(allInSync?green("✓ in sync"):yellow("⚠ check needed")));
|
|
114
|
+
console.log(" Working on "+(state.working?cyan(state.working):gray("not set")));
|
|
115
|
+
console.log(" Intent "+(state.intent ?cyan(state.intent) :gray("not set")));
|
|
116
|
+
console.log(" Decisions "+(state.decisions?state.decisions.length:0)+" recorded\n");
|
|
117
|
+
|
|
118
|
+
if(copyFlag){
|
|
119
|
+
console.log(" "+bold("Ready to use:"));
|
|
120
|
+
console.log(" "+cyan("→")+" Paste into Claude / Cursor / Copilot with "+cyan("Ctrl+V")+"\n");
|
|
121
|
+
} else {
|
|
122
|
+
console.log(" "+bold("Ready to use:"));
|
|
123
|
+
console.log(" "+cyan("1.")+" Open "+cyan("inferno/CONTEXT.md"));
|
|
124
|
+
console.log(" "+cyan("2.")+" Copy everything");
|
|
125
|
+
console.log(" "+cyan("3.")+" Paste at the start of your next AI session");
|
|
126
|
+
console.log(" "+gray(" tip: use --copy to skip steps 1-2 automatically")+"\n");
|
|
127
|
+
}
|
|
230
128
|
}
|