infernoflow 0.32.8 → 0.33.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.
Files changed (81) hide show
  1. package/dist/bin/infernoflow.mjs +84 -255
  2. package/dist/lib/adopters/angular.mjs +1 -128
  3. package/dist/lib/adopters/css.mjs +1 -111
  4. package/dist/lib/adopters/react.mjs +1 -104
  5. package/dist/lib/ai/ideDetection.mjs +1 -31
  6. package/dist/lib/ai/localProvider.mjs +1 -88
  7. package/dist/lib/ai/providerRouter.mjs +2 -295
  8. package/dist/lib/commands/adopt.mjs +20 -869
  9. package/dist/lib/commands/adoptWizard.mjs +9 -320
  10. package/dist/lib/commands/agent.mjs +5 -191
  11. package/dist/lib/commands/ai.mjs +2 -407
  12. package/dist/lib/commands/audit.mjs +13 -300
  13. package/dist/lib/commands/changelog.mjs +26 -594
  14. package/dist/lib/commands/check.mjs +3 -184
  15. package/dist/lib/commands/ci.mjs +3 -208
  16. package/dist/lib/commands/claudeMd.mjs +25 -130
  17. package/dist/lib/commands/cloud.mjs +5 -521
  18. package/dist/lib/commands/context.mjs +34 -287
  19. package/dist/lib/commands/coverage.mjs +2 -282
  20. package/dist/lib/commands/dashboard.mjs +123 -635
  21. package/dist/lib/commands/demo.mjs +8 -465
  22. package/dist/lib/commands/diff.mjs +5 -274
  23. package/dist/lib/commands/docGate.mjs +2 -81
  24. package/dist/lib/commands/doctor.mjs +3 -321
  25. package/dist/lib/commands/explain.mjs +8 -438
  26. package/dist/lib/commands/export.mjs +10 -239
  27. package/dist/lib/commands/generateSkills.mjs +38 -163
  28. package/dist/lib/commands/graph.mjs +203 -321
  29. package/dist/lib/commands/health.mjs +2 -309
  30. package/dist/lib/commands/impact.mjs +2 -325
  31. package/dist/lib/commands/implement.mjs +7 -103
  32. package/dist/lib/commands/init.mjs +23 -475
  33. package/dist/lib/commands/installCursorHooks.mjs +1 -36
  34. package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
  35. package/dist/lib/commands/link.mjs +2 -342
  36. package/dist/lib/commands/log.mjs +16 -0
  37. package/dist/lib/commands/monorepo.mjs +4 -428
  38. package/dist/lib/commands/notify.mjs +4 -258
  39. package/dist/lib/commands/onboard.mjs +4 -296
  40. package/dist/lib/commands/prComment.mjs +2 -361
  41. package/dist/lib/commands/prImpact.mjs +2 -157
  42. package/dist/lib/commands/publish.mjs +15 -316
  43. package/dist/lib/commands/report.mjs +28 -272
  44. package/dist/lib/commands/review.mjs +9 -223
  45. package/dist/lib/commands/run.mjs +8 -336
  46. package/dist/lib/commands/scaffold.mjs +54 -419
  47. package/dist/lib/commands/scan.mjs +5 -558
  48. package/dist/lib/commands/scout.mjs +2 -291
  49. package/dist/lib/commands/setup.mjs +5 -310
  50. package/dist/lib/commands/share.mjs +13 -196
  51. package/dist/lib/commands/snapshot.mjs +3 -383
  52. package/dist/lib/commands/stability.mjs +2 -293
  53. package/dist/lib/commands/status.mjs +4 -172
  54. package/dist/lib/commands/suggest.mjs +21 -563
  55. package/dist/lib/commands/syncAuto.mjs +1 -96
  56. package/dist/lib/commands/synthesize.mjs +10 -228
  57. package/dist/lib/commands/teamSync.mjs +2 -388
  58. package/dist/lib/commands/test.mjs +6 -363
  59. package/dist/lib/commands/theme.mjs +18 -0
  60. package/dist/lib/commands/version.mjs +2 -282
  61. package/dist/lib/commands/vibe.mjs +7 -357
  62. package/dist/lib/commands/watch.mjs +4 -203
  63. package/dist/lib/commands/why.mjs +4 -358
  64. package/dist/lib/cursorHooksInstall.mjs +1 -60
  65. package/dist/lib/draftToolingInstall.mjs +7 -68
  66. package/dist/lib/git/detect-drift.mjs +4 -208
  67. package/dist/lib/learning/adapt.mjs +6 -101
  68. package/dist/lib/learning/observe.mjs +1 -119
  69. package/dist/lib/learning/patternDetector.mjs +1 -298
  70. package/dist/lib/learning/profile.mjs +2 -279
  71. package/dist/lib/learning/skillSynthesizer.mjs +24 -145
  72. package/dist/lib/templates/index.mjs +1 -131
  73. package/dist/lib/theme/scanner.mjs +4 -0
  74. package/dist/lib/ui/errors.mjs +1 -142
  75. package/dist/lib/ui/output.mjs +6 -72
  76. package/dist/lib/ui/prompts.mjs +6 -147
  77. package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
  78. package/dist/templates/cursor/inferno-mcp-server.mjs +29 -0
  79. package/dist/templates/github-app/GITHUB_APP.md +67 -0
  80. package/dist/templates/github-app/app-manifest.json +20 -0
  81. package/package.json +1 -1
@@ -1,295 +1,2 @@
1
- /**
2
- * infernoflow AI provider router
3
- *
4
- * Tries providers in order until one works:
5
- * Tier 1 — VS Code Language Model API (vscode.lm — any Copilot model: Gemini, Claude, GPT)
6
- * Tier 2 — Direct API: Anthropic, OpenAI, Google AI (Gemini), OpenRouter
7
- * Tier 3 — Ollama (local, free, offline)
8
- * Tier 4 — Prompt fallback (print prompt, no AI call)
9
- *
10
- * Config sources (in priority order):
11
- * 1. Environment variables
12
- * 2. inferno/integrations.json
13
- * 3. Auto-detection (Ollama running locally, etc.)
14
- */
15
-
16
- import * as fs from "node:fs";
17
- import * as path from "node:path";
18
- import * as https from "node:https";
19
- import * as http from "node:http";
20
-
21
- // ── Config reader ─────────────────────────────────────────────────────────────
22
-
23
- export function readAiConfig(cwd) {
24
- const p = path.join(cwd, "inferno", "integrations.json");
25
- if (!fs.existsSync(p)) return {};
26
- try { return JSON.parse(fs.readFileSync(p, "utf8")); } catch { return {}; }
27
- }
28
-
29
- // ── HTTP helpers ──────────────────────────────────────────────────────────────
30
-
31
- function post(url, headers, body) {
32
- return new Promise((resolve, reject) => {
33
- const parsed = new URL(url);
34
- const lib = parsed.protocol === "https:" ? https : http;
35
- const data = JSON.stringify(body);
36
-
37
- const req = lib.request({
38
- hostname: parsed.hostname,
39
- port: parsed.port || (parsed.protocol === "https:" ? 443 : 80),
40
- path: parsed.pathname + (parsed.search || ""),
41
- method: "POST",
42
- headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(data), ...headers },
43
- }, (res) => {
44
- let raw = "";
45
- res.on("data", d => (raw += d));
46
- res.on("end", () => {
47
- try { resolve({ status: res.statusCode, body: JSON.parse(raw) }); }
48
- catch { resolve({ status: res.statusCode, body: raw }); }
49
- });
50
- });
51
- req.on("error", reject);
52
- req.write(data);
53
- req.end();
54
- });
55
- }
56
-
57
- // ── Tier 2: Direct API providers ─────────────────────────────────────────────
58
-
59
- async function callAnthropic(prompt, config) {
60
- const apiKey = process.env.ANTHROPIC_API_KEY || config.anthropic?.apiKey;
61
- if (!apiKey) return null;
62
-
63
- const model = config.anthropic?.model || process.env.ANTHROPIC_MODEL || "claude-sonnet-4-6";
64
-
65
- try {
66
- const res = await post(
67
- "https://api.anthropic.com/v1/messages",
68
- { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
69
- {
70
- model,
71
- max_tokens: 1024,
72
- messages: [{ role: "user", content: prompt }],
73
- }
74
- );
75
- if (res.status === 200 && res.body?.content?.[0]?.text) {
76
- return { text: res.body.content[0].text, provider: "anthropic", model };
77
- }
78
- } catch {}
79
- return null;
80
- }
81
-
82
- async function callOpenAI(prompt, config) {
83
- const apiKey = process.env.OPENAI_API_KEY || config.openai?.apiKey;
84
- const endpoint = process.env.OPENAI_ENDPOINT || config.openai?.endpoint || "https://api.openai.com/v1/chat/completions";
85
- if (!apiKey) return null;
86
-
87
- const model = config.openai?.model || process.env.OPENAI_MODEL || "gpt-4o";
88
-
89
- try {
90
- const res = await post(
91
- endpoint,
92
- { "Authorization": `Bearer ${apiKey}` },
93
- {
94
- model,
95
- max_tokens: 1024,
96
- messages: [{ role: "user", content: prompt }],
97
- }
98
- );
99
- if (res.status === 200 && res.body?.choices?.[0]?.message?.content) {
100
- return { text: res.body.choices[0].message.content, provider: "openai", model };
101
- }
102
- } catch {}
103
- return null;
104
- }
105
-
106
- async function callGemini(prompt, config) {
107
- const apiKey = process.env.GOOGLE_AI_API_KEY || process.env.GEMINI_API_KEY || config.gemini?.apiKey;
108
- if (!apiKey) return null;
109
-
110
- const model = config.gemini?.model || process.env.GEMINI_MODEL || "gemini-2.0-flash";
111
-
112
- try {
113
- const res = await post(
114
- `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${apiKey}`,
115
- {},
116
- { contents: [{ parts: [{ text: prompt }] }] }
117
- );
118
- const text = res.body?.candidates?.[0]?.content?.parts?.[0]?.text;
119
- if (res.status === 200 && text) {
120
- return { text, provider: "gemini", model };
121
- }
122
- } catch {}
123
- return null;
124
- }
125
-
126
- async function callOpenRouter(prompt, config) {
127
- const apiKey = process.env.OPENROUTER_API_KEY || config.openrouter?.apiKey;
128
- if (!apiKey) return null;
129
-
130
- const model = config.openrouter?.model || process.env.OPENROUTER_MODEL || "anthropic/claude-sonnet-4-6";
131
-
132
- try {
133
- const res = await post(
134
- "https://openrouter.ai/api/v1/chat/completions",
135
- { "Authorization": `Bearer ${apiKey}`, "HTTP-Referer": "https://infernoflow.dev" },
136
- {
137
- model,
138
- messages: [{ role: "user", content: prompt }],
139
- max_tokens: 1024,
140
- }
141
- );
142
- if (res.status === 200 && res.body?.choices?.[0]?.message?.content) {
143
- return { text: res.body.choices[0].message.content, provider: "openrouter", model };
144
- }
145
- } catch {}
146
- return null;
147
- }
148
-
149
- // ── Tier 3: Ollama (local) ────────────────────────────────────────────────────
150
-
151
- async function callOllama(prompt, config) {
152
- const host = process.env.OLLAMA_HOST || config.ollama?.host || "http://localhost:11434";
153
- const model = process.env.OLLAMA_MODEL || config.ollama?.model || "llama3";
154
-
155
- // Quick liveness check
156
- try {
157
- await new Promise((res, rej) => {
158
- const u = new URL(host);
159
- http.get({ hostname: u.hostname, port: u.port || 11434, path: "/api/tags", timeout: 1500 },
160
- r => res(r)).on("error", rej);
161
- });
162
- } catch { return null; }
163
-
164
- try {
165
- const res = await post(
166
- `${host}/api/generate`,
167
- {},
168
- { model, prompt, stream: false }
169
- );
170
- if (res.status === 200 && res.body?.response) {
171
- return { text: res.body.response, provider: "ollama", model };
172
- }
173
- } catch {}
174
- return null;
175
- }
176
-
177
- // ── Main router ───────────────────────────────────────────────────────────────
178
-
179
- /**
180
- * Call the best available AI provider with a prompt.
181
- *
182
- * @param {string} prompt - The full prompt text
183
- * @param {object} opts
184
- * opts.cwd - Project root (for reading integrations.json)
185
- * opts.provider - Force a specific provider: anthropic|openai|gemini|openrouter|ollama|prompt
186
- * opts.silent - Don't print "using provider X" message
187
- * @returns {{ text: string, provider: string, model: string } | null}
188
- * null means no provider was available → caller should use prompt fallback
189
- */
190
- export async function callAI(prompt, opts = {}) {
191
- const cwd = opts.cwd || process.cwd();
192
- const config = readAiConfig(cwd);
193
- const forced = (opts.provider || "auto").toLowerCase();
194
- const silent = opts.silent ?? true;
195
-
196
- const providers = [
197
- // Tier 2 — direct API (Tier 1 vscode.lm is handled in the VS Code extension)
198
- ["anthropic", () => callAnthropic(prompt, config)],
199
- ["openai", () => callOpenAI(prompt, config)],
200
- ["gemini", () => callGemini(prompt, config)],
201
- ["openrouter", () => callOpenRouter(prompt, config)],
202
- // Tier 3 — local
203
- ["ollama", () => callOllama(prompt, config)],
204
- ];
205
-
206
- // If a specific provider is forced, only try that one
207
- const toTry = forced === "auto" || forced === "prompt"
208
- ? providers
209
- : providers.filter(([name]) => name === forced);
210
-
211
- for (const [name, fn] of toTry) {
212
- try {
213
- const result = await fn();
214
- if (result) {
215
- if (!silent) process.stderr.write(` [infernoflow ai] using ${name}:${result.model}\n`);
216
- return result;
217
- }
218
- } catch {}
219
- }
220
-
221
- return null; // No provider → fallback to prompt output
222
- }
223
-
224
- /**
225
- * Detect which providers are configured (for doctor command).
226
- */
227
- export function detectAvailableProviders(cwd) {
228
- const config = readAiConfig(cwd);
229
- return {
230
- anthropic: !!(process.env.ANTHROPIC_API_KEY || config.anthropic?.apiKey),
231
- openai: !!(process.env.OPENAI_API_KEY || config.openai?.apiKey),
232
- gemini: !!(process.env.GOOGLE_AI_API_KEY || process.env.GEMINI_API_KEY || config.gemini?.apiKey),
233
- openrouter: !!(process.env.OPENROUTER_API_KEY || config.openrouter?.apiKey),
234
- ollama: false, // checked async — doctor runs its own check
235
- };
236
- }
237
-
238
- /**
239
- * Resolve which provider + IDE is available for the `run` command.
240
- * Returns a structured object that run.mjs uses to decide how to proceed.
241
- *
242
- * @param {string} providerRequested - "auto"|"anthropic"|"openai"|etc.
243
- * @param {string} ideRequested - "auto"|"vscode"|"cursor"|etc.
244
- * @returns {{ providerResolved: string, ideDetected: string, agentAvailable: boolean, reasonCodes: string[], error?: string }}
245
- */
246
- export async function resolveProvider(providerRequested = "auto", ideRequested = "auto") {
247
- const cwd = process.cwd();
248
- const config = readAiConfig(cwd);
249
- const reasons = [];
250
-
251
- // Detect IDE
252
- const inVsCode = !!process.env.VSCODE_PID || !!process.env.TERM_PROGRAM?.includes("vscode");
253
- const inCursor = !!process.env.CURSOR_TRACE_ID || !!process.env.CURSOR_CHANNEL;
254
- const ideDetected = inCursor ? "cursor" : inVsCode ? "vscode" : "terminal";
255
-
256
- // Detect available providers
257
- const available = {
258
- anthropic: !!(process.env.ANTHROPIC_API_KEY || config.anthropic?.apiKey),
259
- openai: !!(process.env.OPENAI_API_KEY || config.openai?.apiKey),
260
- gemini: !!(process.env.GOOGLE_AI_API_KEY || process.env.GEMINI_API_KEY || config.gemini?.apiKey),
261
- openrouter: !!(process.env.OPENROUTER_API_KEY || config.openrouter?.apiKey),
262
- ollama: false,
263
- vscode: inVsCode || inCursor,
264
- };
265
-
266
- // Check Ollama quickly (sync port probe via env hint)
267
- if (process.env.OLLAMA_HOST || config.ollama?.host) {
268
- available.ollama = true;
269
- reasons.push("ollama_env");
270
- }
271
-
272
- // Resolve which provider to use
273
- let providerResolved = "none";
274
- const forced = (providerRequested || "auto").toLowerCase();
275
-
276
- if (forced !== "auto" && forced !== "prompt" && available[forced]) {
277
- providerResolved = forced;
278
- reasons.push(`forced_${forced}`);
279
- } else {
280
- // Priority order: vscode/cursor IDE → anthropic → openai → gemini → openrouter → ollama
281
- const priority = ["vscode", "anthropic", "openai", "gemini", "openrouter", "ollama"];
282
- for (const p of priority) {
283
- if (available[p]) { providerResolved = p; reasons.push(`auto_${p}`); break; }
284
- }
285
- }
286
-
287
- const agentAvailable = providerResolved !== "none";
288
-
289
- if (!agentAvailable) {
290
- reasons.push("no_provider");
291
- return { providerResolved: "none", ideDetected, agentAvailable: false, reasonCodes: reasons, error: "agent_unavailable" };
292
- }
293
-
294
- return { providerResolved, ideDetected, agentAvailable: true, reasonCodes: reasons };
295
- }
1
+ import*as v from"node:fs";import*as y from"node:path";import*as A from"node:https";import*as _ from"node:http";function m(n){const t=y.join(n,"inferno","integrations.json");if(!v.existsSync(t))return{};try{return JSON.parse(v.readFileSync(t,"utf8"))}catch{return{}}}function u(n,t,s){return new Promise((o,e)=>{const r=new URL(n),p=r.protocol==="https:"?A:_,l=JSON.stringify(s),c=p.request({hostname:r.hostname,port:r.port||(r.protocol==="https:"?443:80),path:r.pathname+(r.search||""),method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(l),...t}},i=>{let a="";i.on("data",h=>a+=h),i.on("end",()=>{try{o({status:i.statusCode,body:JSON.parse(a)})}catch{o({status:i.statusCode,body:a})}})});c.on("error",e),c.write(l),c.end()})}async function E(n,t){const s=process.env.ANTHROPIC_API_KEY||t.anthropic?.apiKey;if(!s)return null;const o=t.anthropic?.model||process.env.ANTHROPIC_MODEL||"claude-sonnet-4-6";try{const e=await u("https://api.anthropic.com/v1/messages",{"x-api-key":s,"anthropic-version":"2023-06-01"},{model:o,max_tokens:1024,messages:[{role:"user",content:n}]});if(e.status===200&&e.body?.content?.[0]?.text)return{text:e.body.content[0].text,provider:"anthropic",model:o}}catch{}return null}async function O(n,t){const s=process.env.OPENAI_API_KEY||t.openai?.apiKey,o=process.env.OPENAI_ENDPOINT||t.openai?.endpoint||"https://api.openai.com/v1/chat/completions";if(!s)return null;const e=t.openai?.model||process.env.OPENAI_MODEL||"gpt-4o";try{const r=await u(o,{Authorization:`Bearer ${s}`},{model:e,max_tokens:1024,messages:[{role:"user",content:n}]});if(r.status===200&&r.body?.choices?.[0]?.message?.content)return{text:r.body.choices[0].message.content,provider:"openai",model:e}}catch{}return null}async function I(n,t){const s=process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY||t.gemini?.apiKey;if(!s)return null;const o=t.gemini?.model||process.env.GEMINI_MODEL||"gemini-2.0-flash";try{const e=await u(`https://generativelanguage.googleapis.com/v1beta/models/${o}:generateContent?key=${s}`,{},{contents:[{parts:[{text:n}]}]}),r=e.body?.candidates?.[0]?.content?.parts?.[0]?.text;if(e.status===200&&r)return{text:r,provider:"gemini",model:o}}catch{}return null}async function P(n,t){const s=process.env.OPENROUTER_API_KEY||t.openrouter?.apiKey;if(!s)return null;const o=t.openrouter?.model||process.env.OPENROUTER_MODEL||"anthropic/claude-sonnet-4-6";try{const e=await u("https://openrouter.ai/api/v1/chat/completions",{Authorization:`Bearer ${s}`,"HTTP-Referer":"https://infernoflow.dev"},{model:o,messages:[{role:"user",content:n}],max_tokens:1024});if(e.status===200&&e.body?.choices?.[0]?.message?.content)return{text:e.body.choices[0].message.content,provider:"openrouter",model:o}}catch{}return null}async function g(n,t){const s=process.env.OLLAMA_HOST||t.ollama?.host||"http://localhost:11434",o=process.env.OLLAMA_MODEL||t.ollama?.model||"llama3";try{await new Promise((e,r)=>{const p=new URL(s);_.get({hostname:p.hostname,port:p.port||11434,path:"/api/tags",timeout:1500},l=>e(l)).on("error",r)})}catch{return null}try{const e=await u(`${s}/api/generate`,{},{model:o,prompt:n,stream:!1});if(e.status===200&&e.body?.response)return{text:e.body.response,provider:"ollama",model:o}}catch{}return null}async function K(n,t={}){const s=t.cwd||process.cwd(),o=m(s),e=(t.provider||"auto").toLowerCase(),r=t.silent??!0,p=[["anthropic",()=>E(n,o)],["openai",()=>O(n,o)],["gemini",()=>I(n,o)],["openrouter",()=>P(n,o)],["ollama",()=>g(n,o)]],l=e==="auto"||e==="prompt"?p:p.filter(([c])=>c===e);for(const[c,i]of l)try{const a=await i();if(a)return r||process.stderr.write(` [infernoflow ai] using ${c}:${a.model}
2
+ `),a}catch{}return null}function R(n){const t=m(n);return{anthropic:!!(process.env.ANTHROPIC_API_KEY||t.anthropic?.apiKey),openai:!!(process.env.OPENAI_API_KEY||t.openai?.apiKey),gemini:!!(process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY||t.gemini?.apiKey),openrouter:!!(process.env.OPENROUTER_API_KEY||t.openrouter?.apiKey),ollama:!1}}async function N(n="auto",t="auto"){const s=process.cwd(),o=m(s),e=[],r=!!process.env.VSCODE_PID||!!process.env.TERM_PROGRAM?.includes("vscode"),p=!!process.env.CURSOR_TRACE_ID||!!process.env.CURSOR_CHANNEL,l=p?"cursor":r?"vscode":"terminal",c={anthropic:!!(process.env.ANTHROPIC_API_KEY||o.anthropic?.apiKey),openai:!!(process.env.OPENAI_API_KEY||o.openai?.apiKey),gemini:!!(process.env.GOOGLE_AI_API_KEY||process.env.GEMINI_API_KEY||o.gemini?.apiKey),openrouter:!!(process.env.OPENROUTER_API_KEY||o.openrouter?.apiKey),ollama:!1,vscode:r||p};(process.env.OLLAMA_HOST||o.ollama?.host)&&(c.ollama=!0,e.push("ollama_env"));let i="none";const a=(n||"auto").toLowerCase();if(a!=="auto"&&a!=="prompt"&&c[a])i=a,e.push(`forced_${a}`);else{const f=["vscode","anthropic","openai","gemini","openrouter","ollama"];for(const d of f)if(c[d]){i=d,e.push(`auto_${d}`);break}}return i!=="none"?{providerResolved:i,ideDetected:l,agentAvailable:!0,reasonCodes:e}:(e.push("no_provider"),{providerResolved:"none",ideDetected:l,agentAvailable:!1,reasonCodes:e,error:"agent_unavailable"})}export{K as callAI,R as detectAvailableProviders,m as readAiConfig,N as resolveProvider};