infernoflow 0.37.1 → 0.37.3
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/CHANGELOG.md +64 -0
- package/dist/bin/infernoflow.mjs +29 -277
- package/dist/lib/adopters/angular.mjs +1 -128
- package/dist/lib/adopters/css.mjs +1 -111
- package/dist/lib/adopters/react.mjs +1 -104
- package/dist/lib/ai/ideDetection.mjs +1 -31
- package/dist/lib/ai/localProvider.mjs +1 -88
- package/dist/lib/ai/providerRouter.mjs +2 -295
- package/dist/lib/commands/adopt.mjs +20 -869
- package/dist/lib/commands/adoptWizard.mjs +9 -320
- package/dist/lib/commands/agent.mjs +5 -191
- package/dist/lib/commands/ai.mjs +2 -407
- package/dist/lib/commands/ask.mjs +4 -299
- package/dist/lib/commands/audit.mjs +13 -300
- package/dist/lib/commands/changelog.mjs +26 -594
- package/dist/lib/commands/check.mjs +3 -184
- package/dist/lib/commands/ci.mjs +3 -208
- package/dist/lib/commands/claudeMd.mjs +30 -135
- package/dist/lib/commands/cloud.mjs +10 -773
- package/dist/lib/commands/context.mjs +34 -346
- package/dist/lib/commands/coverage.mjs +2 -282
- package/dist/lib/commands/dashboard.mjs +123 -635
- package/dist/lib/commands/demo.mjs +8 -465
- package/dist/lib/commands/diff.mjs +5 -274
- package/dist/lib/commands/docGate.mjs +2 -81
- package/dist/lib/commands/doctor.mjs +3 -321
- package/dist/lib/commands/explain.mjs +8 -438
- package/dist/lib/commands/export.mjs +10 -239
- package/dist/lib/commands/feedback.mjs +12 -216
- package/dist/lib/commands/generateSkills.mjs +38 -163
- package/dist/lib/commands/graph.mjs +11 -378
- package/dist/lib/commands/health.mjs +2 -309
- package/dist/lib/commands/impact.mjs +2 -325
- package/dist/lib/commands/implement.mjs +7 -103
- package/dist/lib/commands/init.mjs +45 -631
- package/dist/lib/commands/installCursorHooks.mjs +1 -36
- package/dist/lib/commands/installVsCodeCopilotHooks.mjs +1 -37
- package/dist/lib/commands/link.mjs +2 -342
- package/dist/lib/commands/log.mjs +18 -248
- package/dist/lib/commands/monorepo.mjs +4 -428
- package/dist/lib/commands/notify.mjs +4 -258
- package/dist/lib/commands/onboard.mjs +4 -296
- package/dist/lib/commands/prComment.mjs +2 -361
- package/dist/lib/commands/prImpact.mjs +2 -157
- package/dist/lib/commands/publish.mjs +15 -316
- package/dist/lib/commands/recap.mjs +6 -380
- package/dist/lib/commands/report.mjs +28 -272
- package/dist/lib/commands/review.mjs +9 -223
- package/dist/lib/commands/run.mjs +8 -336
- package/dist/lib/commands/scaffold.mjs +54 -419
- package/dist/lib/commands/scan.mjs +11 -1118
- package/dist/lib/commands/scout.mjs +2 -291
- package/dist/lib/commands/setup.mjs +5 -310
- package/dist/lib/commands/share.mjs +13 -196
- package/dist/lib/commands/snapshot.mjs +3 -383
- package/dist/lib/commands/stability.mjs +2 -293
- package/dist/lib/commands/stats.mjs +5 -402
- package/dist/lib/commands/status.mjs +4 -172
- package/dist/lib/commands/suggest.mjs +21 -563
- package/dist/lib/commands/switch.mjs +13 -520
- package/dist/lib/commands/syncAuto.mjs +1 -96
- package/dist/lib/commands/synthesize.mjs +10 -228
- package/dist/lib/commands/teamSync.mjs +2 -388
- package/dist/lib/commands/test.mjs +6 -363
- package/dist/lib/commands/theme.mjs +18 -195
- package/dist/lib/commands/uninstall.mjs +13 -406
- package/dist/lib/commands/upgrade.mjs +20 -153
- package/dist/lib/commands/version.mjs +2 -282
- package/dist/lib/commands/vibe.mjs +7 -357
- package/dist/lib/commands/watch.mjs +4 -203
- package/dist/lib/commands/why.mjs +4 -358
- package/dist/lib/cursorHooksInstall.mjs +1 -60
- package/dist/lib/draftToolingInstall.mjs +7 -68
- package/dist/lib/git/detect-drift.mjs +4 -208
- package/dist/lib/learning/adapt.mjs +6 -101
- package/dist/lib/learning/observe.mjs +1 -119
- package/dist/lib/learning/patternDetector.mjs +1 -298
- package/dist/lib/learning/profile.mjs +2 -279
- package/dist/lib/learning/skillSynthesizer.mjs +24 -145
- package/dist/lib/telemetry.mjs +19 -269
- package/dist/lib/templates/index.mjs +1 -131
- package/dist/lib/theme/scanner.mjs +4 -343
- package/dist/lib/ui/errors.mjs +1 -142
- package/dist/lib/ui/output.mjs +6 -95
- package/dist/lib/ui/prompts.mjs +6 -147
- package/dist/lib/vsCodeCopilotHooksInstall.mjs +1 -42
- package/package.json +2 -4
- package/scripts/postinstall.js +2 -2
|
@@ -1,104 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
* (runnable via `infernoflow agent run <name>`)
|
|
8
|
-
*
|
|
9
|
-
* SKILLS → inferno/skills/<name>.md
|
|
10
|
-
* + appends to .cursor/rules/infernoflow-auto.mdc
|
|
11
|
-
* + appends to CLAUDE.md (if it exists)
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import * as fs from "node:fs";
|
|
15
|
-
import * as path from "node:path";
|
|
16
|
-
|
|
17
|
-
// ── Agent file writer ─────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Write an agent definition to inferno/agents/<name>.json.
|
|
21
|
-
* Returns the file path written.
|
|
22
|
-
*/
|
|
23
|
-
export function writeAgentFile(infernoDir, candidate) {
|
|
24
|
-
const agentsDir = path.join(infernoDir, "agents");
|
|
25
|
-
fs.mkdirSync(agentsDir, { recursive: true });
|
|
26
|
-
|
|
27
|
-
const agentDef = {
|
|
28
|
-
id: candidate.id,
|
|
29
|
-
name: candidate.name,
|
|
30
|
-
description: candidate.description,
|
|
31
|
-
steps: candidate.steps.map(cmd => ({ command: cmd, args: [] })),
|
|
32
|
-
createdAt: new Date().toISOString(),
|
|
33
|
-
source: "auto-synthesized",
|
|
34
|
-
frequency: candidate.frequency,
|
|
35
|
-
confidence: candidate.confidence,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const filePath = path.join(agentsDir, `${candidate.name}.json`);
|
|
39
|
-
fs.writeFileSync(filePath, JSON.stringify(agentDef, null, 2) + "\n", "utf8");
|
|
40
|
-
return filePath;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// ── Skill file writers ────────────────────────────────────────────────────────
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Write a skill markdown file to inferno/skills/<name>.md.
|
|
47
|
-
*/
|
|
48
|
-
export function writeSkillFile(infernoDir, candidate) {
|
|
49
|
-
const skillsDir = path.join(infernoDir, "skills");
|
|
50
|
-
fs.mkdirSync(skillsDir, { recursive: true });
|
|
51
|
-
|
|
52
|
-
const examples = (candidate.examples || [])
|
|
53
|
-
.map(e => ` - "${e}"`)
|
|
54
|
-
.join("\n");
|
|
55
|
-
|
|
56
|
-
const steps = (candidate.steps || [])
|
|
57
|
-
.map((s, i) => `${i + 1}. Run \`infernoflow ${s}\``)
|
|
58
|
-
.join("\n");
|
|
59
|
-
|
|
60
|
-
const content = `# Skill: ${candidate.name}
|
|
61
|
-
|
|
62
|
-
> Auto-synthesized from ${candidate.frequency} observed sessions (confidence: ${Math.round(candidate.confidence * 100)}%)
|
|
1
|
+
import*as r from"node:fs";import*as u from"node:path";function c(s,e){const n=u.join(s,"agents");r.mkdirSync(n,{recursive:!0});const t={id:e.id,name:e.name,description:e.description,steps:e.steps.map(i=>({command:i,args:[]})),createdAt:new Date().toISOString(),source:"auto-synthesized",frequency:e.frequency,confidence:e.confidence},o=u.join(n,`${e.name}.json`);return r.writeFileSync(o,JSON.stringify(t,null,2)+`
|
|
2
|
+
`,"utf8"),o}function a(s,e){const n=u.join(s,"skills");r.mkdirSync(n,{recursive:!0});const t=(e.examples||[]).map(f=>` - "${f}"`).join(`
|
|
3
|
+
`),o=(e.steps||[]).map((f,p)=>`${p+1}. Run \`infernoflow ${f}\``).join(`
|
|
4
|
+
`),i=`# Skill: ${e.name}
|
|
5
|
+
|
|
6
|
+
> Auto-synthesized from ${e.frequency} observed sessions (confidence: ${Math.round(e.confidence*100)}%)
|
|
63
7
|
|
|
64
8
|
## Trigger pattern
|
|
65
9
|
|
|
66
|
-
\`${
|
|
10
|
+
\`${e.trigger}\`
|
|
67
11
|
|
|
68
12
|
## Example tasks this applies to
|
|
69
13
|
|
|
70
|
-
${
|
|
14
|
+
${t||" (none recorded)"}
|
|
71
15
|
|
|
72
16
|
## Recommended workflow
|
|
73
17
|
|
|
74
18
|
When working on a task matching the pattern above:
|
|
75
19
|
|
|
76
|
-
${
|
|
20
|
+
${o}
|
|
77
21
|
|
|
78
22
|
## Notes
|
|
79
23
|
|
|
80
24
|
- This skill was automatically generated by \`infernoflow synthesize\`
|
|
81
|
-
- Review and edit as needed
|
|
25
|
+
- Review and edit as needed \u2014 it is yours to own
|
|
82
26
|
- Re-run \`infernoflow synthesize\` to update as your patterns evolve
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const filePath = path.join(skillsDir, `${candidate.name}.md`);
|
|
86
|
-
fs.writeFileSync(filePath, content, "utf8");
|
|
87
|
-
return filePath;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Append a skill rule to .cursor/rules/infernoflow-auto.mdc.
|
|
92
|
-
* Creates the file if it doesn't exist.
|
|
93
|
-
*/
|
|
94
|
-
export function appendToCursorRules(cwd, candidate) {
|
|
95
|
-
const rulesDir = path.join(cwd, ".cursor", "rules");
|
|
96
|
-
fs.mkdirSync(rulesDir, { recursive: true });
|
|
97
|
-
|
|
98
|
-
const filePath = path.join(rulesDir, "infernoflow-auto.mdc");
|
|
99
|
-
|
|
100
|
-
const header = fs.existsSync(filePath) ? "" :
|
|
101
|
-
`---
|
|
27
|
+
`,l=u.join(n,`${e.name}.md`);return r.writeFileSync(l,i,"utf8"),l}function m(s,e){const n=u.join(s,".cursor","rules");r.mkdirSync(n,{recursive:!0});const t=u.join(n,"infernoflow-auto.mdc"),o=r.existsSync(t)?"":`---
|
|
102
28
|
description: Auto-generated infernoflow workflow rules
|
|
103
29
|
globs: ["**/*"]
|
|
104
30
|
alwaysApply: true
|
|
@@ -107,67 +33,20 @@ alwaysApply: true
|
|
|
107
33
|
# infernoflow Auto-Generated Workflow Rules
|
|
108
34
|
|
|
109
35
|
These rules are synthesized from your observed development patterns.
|
|
110
|
-
Do not edit manually
|
|
111
|
-
|
|
112
|
-
`;
|
|
36
|
+
Do not edit manually \u2014 re-run \`infernoflow synthesize\` to regenerate.
|
|
113
37
|
|
|
114
|
-
|
|
115
|
-
## ${
|
|
38
|
+
`,i=`
|
|
39
|
+
## ${e.name}
|
|
116
40
|
|
|
117
|
-
Pattern: \`${
|
|
41
|
+
Pattern: \`${e.trigger}\`
|
|
118
42
|
|
|
119
43
|
When working on tasks matching this pattern:
|
|
120
|
-
${(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* Append a skill tip to CLAUDE.md if it exists.
|
|
130
|
-
*/
|
|
131
|
-
export function appendToClaudeMd(cwd, candidate) {
|
|
132
|
-
const claudeMdPath = path.join(cwd, "CLAUDE.md");
|
|
133
|
-
if (!fs.existsSync(claudeMdPath)) return null;
|
|
134
|
-
|
|
135
|
-
const existing = fs.readFileSync(claudeMdPath, "utf8");
|
|
136
|
-
const marker = "## infernoflow Auto-Skills";
|
|
137
|
-
|
|
138
|
-
const tip = `\n- **${candidate.name}**: For tasks like \`${candidate.trigger}\`, run: ${(candidate.steps || []).map(s => `\`infernoflow ${s}\``).join(" → ")}`;
|
|
139
|
-
|
|
140
|
-
if (!existing.includes(marker)) {
|
|
141
|
-
fs.appendFileSync(claudeMdPath, `\n\n${marker}\n${tip}\n`, "utf8");
|
|
142
|
-
} else {
|
|
143
|
-
// Append after the marker section
|
|
144
|
-
const updated = existing.replace(marker, marker + tip);
|
|
145
|
-
fs.writeFileSync(claudeMdPath, updated, "utf8");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return claudeMdPath;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// ── Main synthesize function ──────────────────────────────────────────────────
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Write all files for an approved candidate.
|
|
155
|
-
* Returns { filePaths: string[] } — list of files written.
|
|
156
|
-
*/
|
|
157
|
-
export function synthesizeCandidate(cwd, infernoDir, candidate) {
|
|
158
|
-
const filePaths = [];
|
|
159
|
-
|
|
160
|
-
if (candidate.type === "agent") {
|
|
161
|
-
filePaths.push(writeAgentFile(infernoDir, candidate));
|
|
162
|
-
} else {
|
|
163
|
-
// skill
|
|
164
|
-
filePaths.push(writeSkillFile(infernoDir, candidate));
|
|
165
|
-
try { filePaths.push(appendToCursorRules(cwd, candidate)); } catch {}
|
|
166
|
-
try {
|
|
167
|
-
const p = appendToClaudeMd(cwd, candidate);
|
|
168
|
-
if (p) filePaths.push(p);
|
|
169
|
-
} catch {}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return { filePaths };
|
|
173
|
-
}
|
|
44
|
+
${(e.steps||[]).map((l,f)=>`${f+1}. Run \`infernoflow ${l}\``).join(`
|
|
45
|
+
`)}
|
|
46
|
+
|
|
47
|
+
`;return r.appendFileSync(t,o+i,"utf8"),t}function y(s,e){const n=u.join(s,"CLAUDE.md");if(!r.existsSync(n))return null;const t=r.readFileSync(n,"utf8"),o="## infernoflow Auto-Skills",i=`
|
|
48
|
+
- **${e.name}**: For tasks like \`${e.trigger}\`, run: ${(e.steps||[]).map(l=>`\`infernoflow ${l}\``).join(" \u2192 ")}`;if(!t.includes(o))r.appendFileSync(n,`
|
|
49
|
+
|
|
50
|
+
${o}
|
|
51
|
+
${i}
|
|
52
|
+
`,"utf8");else{const l=t.replace(o,o+i);r.writeFileSync(n,l,"utf8")}return n}function h(s,e,n){const t=[];if(n.type==="agent")t.push(c(e,n));else{t.push(a(e,n));try{t.push(m(s,n))}catch{}try{const o=y(s,n);o&&t.push(o)}catch{}}return{filePaths:t}}export{y as appendToClaudeMd,m as appendToCursorRules,h as synthesizeCandidate,c as writeAgentFile,a as writeSkillFile};
|
package/dist/lib/telemetry.mjs
CHANGED
|
@@ -1,269 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* Consent is requested lazily after 3 interactive runs.
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import * as fs from "node:fs";
|
|
24
|
-
import * as path from "node:path";
|
|
25
|
-
import * as os from "node:os";
|
|
26
|
-
import * as https from "node:https";
|
|
27
|
-
import * as crypto from "node:crypto";
|
|
28
|
-
|
|
29
|
-
const CONFIG_DIR = path.join(os.homedir(), ".infernoflow");
|
|
30
|
-
const TELEMETRY_FILE = path.join(CONFIG_DIR, "telemetry.json");
|
|
31
|
-
const EVENTS_FILE = path.join(CONFIG_DIR, "events.jsonl");
|
|
32
|
-
|
|
33
|
-
const POSTHOG_HOST = "https://eu.i.posthog.com";
|
|
34
|
-
const POSTHOG_KEY = "phc_z6YX7x4zjkuFZigdXTBoFcPTWeGLFAN9NNKVZ5WHQrqk";
|
|
35
|
-
|
|
36
|
-
// ── Config helpers ────────────────────────────────────────────────────────────
|
|
37
|
-
|
|
38
|
-
function readConfig() {
|
|
39
|
-
try { return JSON.parse(fs.readFileSync(TELEMETRY_FILE, "utf8")); }
|
|
40
|
-
catch { return null; }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function writeConfig(data) {
|
|
44
|
-
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
45
|
-
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify(data, null, 2), "utf8");
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Generate a random anonymous install UUID — stored once, never changes */
|
|
49
|
-
function generateInstallId() {
|
|
50
|
-
// Use crypto.randomUUID if available (Node 15.6+), else fallback
|
|
51
|
-
try { return crypto.randomUUID(); }
|
|
52
|
-
catch { return "ifl_" + crypto.randomBytes(16).toString("hex"); }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Get or create the persistent anonymous install ID */
|
|
56
|
-
function getOrCreateInstallId() {
|
|
57
|
-
const cfg = readConfig() || {};
|
|
58
|
-
if (cfg.installId) return cfg.installId;
|
|
59
|
-
const installId = generateInstallId();
|
|
60
|
-
writeConfig({ ...cfg, installId });
|
|
61
|
-
return installId;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function isTelemetryEnabled() {
|
|
65
|
-
return readConfig()?.enabled === true;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function hasConsentDecision() {
|
|
69
|
-
const cfg = readConfig();
|
|
70
|
-
return cfg !== null && typeof cfg.enabled === "boolean";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ── Context detection ─────────────────────────────────────────────────────────
|
|
74
|
-
|
|
75
|
-
function detectIde() {
|
|
76
|
-
if (process.env.CURSOR_SESSION) return "cursor";
|
|
77
|
-
if (process.env.COPILOT_SESSION) return "copilot";
|
|
78
|
-
if (process.env.CLAUDE_CODE_SESSION) return "claude-code";
|
|
79
|
-
if (process.env.WINDSURF_SESSION) return "windsurf";
|
|
80
|
-
if (process.env.TERM_PROGRAM === "vscode") return "vscode";
|
|
81
|
-
return "unknown";
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function detectProjectType() {
|
|
85
|
-
try {
|
|
86
|
-
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"));
|
|
87
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
88
|
-
if (deps["next"] || deps["nuxt"] || deps["remix"]) return "fullstack";
|
|
89
|
-
if (deps["react"] || deps["vue"] || deps["svelte"]) return "frontend";
|
|
90
|
-
if (deps["express"] || deps["fastify"] || deps["koa"]) return "backend";
|
|
91
|
-
if (deps["@angular/core"]) return "frontend";
|
|
92
|
-
return "js";
|
|
93
|
-
} catch {
|
|
94
|
-
return "unknown";
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function getTimezone() {
|
|
99
|
-
try { return Intl.DateTimeFormat().resolvedOptions().timeZone; }
|
|
100
|
-
catch { return "unknown"; }
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function getVersion() {
|
|
104
|
-
try {
|
|
105
|
-
const pkgPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), "../package.json");
|
|
106
|
-
return JSON.parse(fs.readFileSync(pkgPath, "utf8")).version;
|
|
107
|
-
} catch { return "unknown"; }
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// ── Consent prompt ────────────────────────────────────────────────────────────
|
|
111
|
-
|
|
112
|
-
export async function ensureTelemetryConsent() {
|
|
113
|
-
if (hasConsentDecision()) return;
|
|
114
|
-
if (!process.stdin.isTTY) return;
|
|
115
|
-
|
|
116
|
-
// Only ask after 3+ interactive runs — let the user experience it first
|
|
117
|
-
const cfg = readConfig() || {};
|
|
118
|
-
const runs = (cfg.runs || 0) + 1;
|
|
119
|
-
writeConfig({ ...cfg, runs, enabled: false });
|
|
120
|
-
|
|
121
|
-
if (runs < 3) return;
|
|
122
|
-
|
|
123
|
-
const { createInterface } = await import("node:readline");
|
|
124
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
125
|
-
|
|
126
|
-
const answer = await new Promise(resolve => {
|
|
127
|
-
process.stdout.write(
|
|
128
|
-
"\n 📡 Help improve infernoflow?\n" +
|
|
129
|
-
" Share anonymous usage data — command names, OS, timezone. No code. No personal data.\n" +
|
|
130
|
-
" Type 'y' to opt in, anything else to decline. (infernoflow telemetry off to change later)\n" +
|
|
131
|
-
" → "
|
|
132
|
-
);
|
|
133
|
-
rl.question("", resolve);
|
|
134
|
-
});
|
|
135
|
-
rl.close();
|
|
136
|
-
|
|
137
|
-
const enabled = answer.trim().toLowerCase() === "y";
|
|
138
|
-
const installId = enabled ? generateInstallId() : null;
|
|
139
|
-
writeConfig({ enabled, installId, runs, decidedAt: new Date().toISOString() });
|
|
140
|
-
|
|
141
|
-
process.stdout.write(
|
|
142
|
-
enabled
|
|
143
|
-
? " ✔ Telemetry enabled — thank you! (infernoflow telemetry off to disable)\n\n"
|
|
144
|
-
: " ✔ No problem — telemetry off. (infernoflow telemetry on to enable later)\n\n"
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ── Event tracking ────────────────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Track a command invocation. Fire-and-forget — never throws, never blocks.
|
|
152
|
-
* @param {string} command e.g. "log", "switch", "recap"
|
|
153
|
-
*/
|
|
154
|
-
export function trackEvent(command) {
|
|
155
|
-
if (!isTelemetryEnabled()) return;
|
|
156
|
-
|
|
157
|
-
const installId = getOrCreateInstallId();
|
|
158
|
-
|
|
159
|
-
const event = {
|
|
160
|
-
ts: new Date().toISOString(),
|
|
161
|
-
command,
|
|
162
|
-
installId, // anonymous UUID — links events from same install
|
|
163
|
-
version: getVersion(),
|
|
164
|
-
node: process.version,
|
|
165
|
-
os: process.platform,
|
|
166
|
-
timezone: getTimezone(), // geography (continent/country) estimation
|
|
167
|
-
ide: detectIde(),
|
|
168
|
-
projectType: detectProjectType(),
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
// 1. Mirror to local event log
|
|
172
|
-
try {
|
|
173
|
-
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
174
|
-
fs.appendFileSync(EVENTS_FILE, JSON.stringify(event) + "\n", "utf8");
|
|
175
|
-
} catch {}
|
|
176
|
-
|
|
177
|
-
// 2. Fire-and-forget to PostHog
|
|
178
|
-
// PostHog expects: { api_key, event, distinct_id, properties, timestamp }
|
|
179
|
-
_postHog(installId, command, event);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function _postHog(distinctId, eventName, props) {
|
|
183
|
-
try {
|
|
184
|
-
const body = JSON.stringify({
|
|
185
|
-
api_key: POSTHOG_KEY,
|
|
186
|
-
event: eventName,
|
|
187
|
-
distinct_id: distinctId,
|
|
188
|
-
properties: {
|
|
189
|
-
command: props.command,
|
|
190
|
-
version: props.version,
|
|
191
|
-
node: props.node,
|
|
192
|
-
os: props.os,
|
|
193
|
-
timezone: props.timezone,
|
|
194
|
-
ide: props.ide,
|
|
195
|
-
projectType: props.projectType,
|
|
196
|
-
$lib: "infernoflow-cli",
|
|
197
|
-
},
|
|
198
|
-
timestamp: props.ts,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const url = new URL(POSTHOG_HOST + "/capture/");
|
|
202
|
-
const req = https.request({
|
|
203
|
-
hostname: url.hostname,
|
|
204
|
-
path: url.pathname,
|
|
205
|
-
method: "POST",
|
|
206
|
-
headers: {
|
|
207
|
-
"Content-Type": "application/json",
|
|
208
|
-
"Content-Length": Buffer.byteLength(body),
|
|
209
|
-
},
|
|
210
|
-
timeout: 3000,
|
|
211
|
-
});
|
|
212
|
-
req.on("error", () => {}); // never surface telemetry errors
|
|
213
|
-
req.write(body);
|
|
214
|
-
req.end();
|
|
215
|
-
} catch {}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// ── CLI subcommand ────────────────────────────────────────────────────────────
|
|
219
|
-
|
|
220
|
-
export async function telemetryCommand(args) {
|
|
221
|
-
const { bold, cyan, gray, green, yellow, red } = await import("./ui/output.mjs");
|
|
222
|
-
const sub = args[0];
|
|
223
|
-
|
|
224
|
-
if (sub === "on") {
|
|
225
|
-
const cfg = readConfig() || {};
|
|
226
|
-
const installId = cfg.installId || generateInstallId();
|
|
227
|
-
writeConfig({ ...cfg, enabled: true, installId, decidedAt: new Date().toISOString() });
|
|
228
|
-
console.log(green("\n ✔ Telemetry enabled — thank you for helping improve infernoflow!\n"));
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (sub === "off") {
|
|
233
|
-
const cfg = readConfig() || {};
|
|
234
|
-
writeConfig({ ...cfg, enabled: false, decidedAt: new Date().toISOString() });
|
|
235
|
-
console.log(green("\n ✔ Telemetry disabled. No data will be sent.\n"));
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
if (sub === "status" || !sub) {
|
|
240
|
-
const cfg = readConfig();
|
|
241
|
-
const enabled = cfg?.enabled === true;
|
|
242
|
-
const decided = cfg?.decidedAt ? new Date(cfg.decidedAt).toLocaleDateString() : "never";
|
|
243
|
-
const installId = cfg?.installId ? cfg.installId.slice(0, 12) + "…" : "none yet";
|
|
244
|
-
|
|
245
|
-
let eventCount = 0;
|
|
246
|
-
try {
|
|
247
|
-
eventCount = fs.readFileSync(EVENTS_FILE, "utf8").split("\n").filter(Boolean).length;
|
|
248
|
-
} catch {}
|
|
249
|
-
|
|
250
|
-
console.log("\n " + bold("🔥 infernoflow telemetry status") + "\n");
|
|
251
|
-
console.log(" Status " + (enabled ? green("enabled") : yellow("disabled")));
|
|
252
|
-
console.log(" Install ID " + gray(installId + " (anonymous, never linked to identity)"));
|
|
253
|
-
console.log(" Decided " + gray(decided));
|
|
254
|
-
console.log(" Events stored " + gray(eventCount + " locally → " + (enabled ? "also sent to PostHog" : "not sent (disabled)")));
|
|
255
|
-
console.log(" Backend " + gray("PostHog (EU-hosted, no IP stored)"));
|
|
256
|
-
console.log();
|
|
257
|
-
console.log(" " + bold("What we collect:") + " " + gray("command, version, Node, OS, timezone, IDE, project type"));
|
|
258
|
-
console.log(" " + bold("What we never collect:") + " " + gray("code, file names, capability names, email, personal data"));
|
|
259
|
-
console.log();
|
|
260
|
-
console.log(gray(" infernoflow telemetry on — enable"));
|
|
261
|
-
console.log(gray(" infernoflow telemetry off — disable"));
|
|
262
|
-
console.log();
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
console.error(red(`\n ✘ Unknown subcommand: ${sub}`));
|
|
267
|
-
console.log(gray(" Usage: infernoflow telemetry [on | off | status]\n"));
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
1
|
+
import*as s from"node:fs";import*as f from"node:path";import*as b from"node:os";import*as O from"node:https";import*as S from"node:crypto";const d=f.join(b.homedir(),".infernoflow"),w=f.join(d,"telemetry.json"),h=f.join(d,"events.jsonl"),T="https://eu.i.posthog.com",k="phc_z6YX7x4zjkuFZigdXTBoFcPTWeGLFAN9NNKVZ5WHQrqk";function a(){try{return JSON.parse(s.readFileSync(w,"utf8"))}catch{return null}}function u(n){s.existsSync(d)||s.mkdirSync(d,{recursive:!0}),s.writeFileSync(w,JSON.stringify(n,null,2),"utf8")}function y(){try{return S.randomUUID()}catch{return"ifl_"+S.randomBytes(16).toString("hex")}}function N(){const n=a()||{};if(n.installId)return n.installId;const e=y();return u({...n,installId:e}),e}function E(){return a()?.enabled===!0}function D(){const n=a();return n!==null&&typeof n.enabled=="boolean"}function _(){return process.env.CURSOR_SESSION?"cursor":process.env.COPILOT_SESSION?"copilot":process.env.CLAUDE_CODE_SESSION?"claude-code":process.env.WINDSURF_SESSION?"windsurf":process.env.TERM_PROGRAM==="vscode"?"vscode":"unknown"}function j(){try{const n=JSON.parse(s.readFileSync("package.json","utf8")),e={...n.dependencies,...n.devDependencies};return e.next||e.nuxt||e.remix?"fullstack":e.react||e.vue||e.svelte?"frontend":e.express||e.fastify||e.koa?"backend":e["@angular/core"]?"frontend":"js"}catch{return"unknown"}}function C(){try{return Intl.DateTimeFormat().resolvedOptions().timeZone}catch{return"unknown"}}function x(){try{const n=f.resolve(f.dirname(new URL(import.meta.url).pathname),"../package.json");return JSON.parse(s.readFileSync(n,"utf8")).version}catch{return"unknown"}}async function L(){if(D()||!process.stdin.isTTY)return;const n=a()||{},e=(n.runs||0)+1;if(u({...n,runs:e,enabled:!1}),e<3)return;const{createInterface:o}=await import("node:readline"),t=o({input:process.stdin,output:process.stdout}),l=await new Promise(c=>{process.stdout.write(`
|
|
2
|
+
\u{1F4E1} Help improve infernoflow?
|
|
3
|
+
Share anonymous usage data \u2014 command names, OS, timezone. No code. No personal data.
|
|
4
|
+
Type 'y' to opt in, anything else to decline. (infernoflow telemetry off to change later)
|
|
5
|
+
\u2192 `),t.question("",c)});t.close();const i=l.trim().toLowerCase()==="y",p=i?y():null;u({enabled:i,installId:p,runs:e,decidedAt:new Date().toISOString()}),process.stdout.write(i?` \u2714 Telemetry enabled \u2014 thank you! (infernoflow telemetry off to disable)
|
|
6
|
+
|
|
7
|
+
`:` \u2714 No problem \u2014 telemetry off. (infernoflow telemetry on to enable later)
|
|
8
|
+
|
|
9
|
+
`)}function P(n){if(!E())return;const e=N(),o={ts:new Date().toISOString(),command:n,installId:e,version:x(),node:process.version,os:process.platform,timezone:C(),ide:_(),projectType:j()};try{s.existsSync(d)||s.mkdirSync(d,{recursive:!0}),s.appendFileSync(h,JSON.stringify(o)+`
|
|
10
|
+
`,"utf8")}catch{}F(e,n,o)}function F(n,e,o){try{const t=JSON.stringify({api_key:k,event:e,distinct_id:n,properties:{command:o.command,version:o.version,node:o.node,os:o.os,timezone:o.timezone,ide:o.ide,projectType:o.projectType,$lib:"infernoflow-cli"},timestamp:o.ts}),l=new URL(T+"/capture/"),i=O.request({hostname:l.hostname,path:l.pathname,method:"POST",headers:{"Content-Type":"application/json","Content-Length":Buffer.byteLength(t)},timeout:3e3});i.on("error",()=>{}),i.write(t),i.end()}catch{}}async function R(n){const{bold:e,cyan:o,gray:t,green:l,yellow:i,red:p}=await import("./ui/output.mjs"),c=n[0];if(c==="on"){const r=a()||{},m=r.installId||y();u({...r,enabled:!0,installId:m,decidedAt:new Date().toISOString()}),console.log(l(`
|
|
11
|
+
\u2714 Telemetry enabled \u2014 thank you for helping improve infernoflow!
|
|
12
|
+
`));return}if(c==="off"){const r=a()||{};u({...r,enabled:!1,decidedAt:new Date().toISOString()}),console.log(l(`
|
|
13
|
+
\u2714 Telemetry disabled. No data will be sent.
|
|
14
|
+
`));return}if(c==="status"||!c){const r=a(),m=r?.enabled===!0,I=r?.decidedAt?new Date(r.decidedAt).toLocaleDateString():"never",v=r?.installId?r.installId.slice(0,12)+"\u2026":"none yet";let g=0;try{g=s.readFileSync(h,"utf8").split(`
|
|
15
|
+
`).filter(Boolean).length}catch{}console.log(`
|
|
16
|
+
`+e("\u{1F525} infernoflow telemetry status")+`
|
|
17
|
+
`),console.log(" Status "+(m?l("enabled"):i("disabled"))),console.log(" Install ID "+t(v+" (anonymous, never linked to identity)")),console.log(" Decided "+t(I)),console.log(" Events stored "+t(g+" locally \u2192 "+(m?"also sent to PostHog":"not sent (disabled)"))),console.log(" Backend "+t("PostHog (EU-hosted, no IP stored)")),console.log(),console.log(" "+e("What we collect:")+" "+t("command, version, Node, OS, timezone, IDE, project type")),console.log(" "+e("What we never collect:")+" "+t("code, file names, capability names, email, personal data")),console.log(),console.log(t(" infernoflow telemetry on \u2014 enable")),console.log(t(" infernoflow telemetry off \u2014 disable")),console.log();return}console.error(p(`
|
|
18
|
+
\u2718 Unknown subcommand: ${c}`)),console.log(t(` Usage: infernoflow telemetry [on | off | status]
|
|
19
|
+
`)),process.exit(1)}export{L as ensureTelemetryConsent,D as hasConsentDecision,E as isTelemetryEnabled,R as telemetryCommand,P as trackEvent};
|
|
@@ -1,131 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* infernoflow project templates
|
|
3
|
-
*
|
|
4
|
-
* Named starters for `infernoflow init --template <name>`.
|
|
5
|
-
* Each template provides:
|
|
6
|
-
* - capabilities : pre-populated capability list
|
|
7
|
-
* - scenarios : one starter scenario per capability
|
|
8
|
-
* - contextHint : first-session CONTEXT.md guidance
|
|
9
|
-
* - scripts : suggested package.json scripts to add
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const TEMPLATES = {
|
|
13
|
-
|
|
14
|
-
// ── REST API ────────────────────────────────────────────────────────────────
|
|
15
|
-
"rest-api": {
|
|
16
|
-
description: "Express / Fastify / Hono REST API",
|
|
17
|
-
capabilities: [
|
|
18
|
-
{ id: "list-items", description: "GET /items — paginated list with filtering and sorting" },
|
|
19
|
-
{ id: "get-item", description: "GET /items/:id — fetch a single resource by ID" },
|
|
20
|
-
{ id: "create-item", description: "POST /items — create a new resource, validate input" },
|
|
21
|
-
{ id: "update-item", description: "PUT /items/:id — full update of an existing resource" },
|
|
22
|
-
{ id: "delete-item", description: "DELETE /items/:id — soft or hard delete" },
|
|
23
|
-
{ id: "authenticate", description: "POST /auth/login — exchange credentials for a JWT token" },
|
|
24
|
-
{ id: "refresh-token", description: "POST /auth/refresh — extend session with a new token" },
|
|
25
|
-
{ id: "health-check", description: "GET /health — liveness probe for load balancers" },
|
|
26
|
-
{ id: "paginate", description: "Cursor or offset pagination applied across list endpoints" },
|
|
27
|
-
{ id: "validate-input", description: "Schema validation on all incoming request bodies" },
|
|
28
|
-
],
|
|
29
|
-
contextHint: "Building a REST API. Focus on route handlers, middleware, and input validation.",
|
|
30
|
-
scripts: {
|
|
31
|
-
"dev": "node src/index.js",
|
|
32
|
-
"start": "node dist/index.js",
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
|
|
36
|
-
// ── Next.js ─────────────────────────────────────────────────────────────────
|
|
37
|
-
"nextjs": {
|
|
38
|
-
description: "Next.js full-stack app (App Router or Pages Router)",
|
|
39
|
-
capabilities: [
|
|
40
|
-
{ id: "render-home-page", description: "/ — server-rendered landing page with SEO metadata" },
|
|
41
|
-
{ id: "render-dashboard", description: "/dashboard — authenticated user dashboard" },
|
|
42
|
-
{ id: "api-authenticate", description: "POST /api/auth/login — issue session cookie or JWT" },
|
|
43
|
-
{ id: "api-list-resources", description: "GET /api/resources — paginated list, auth-protected" },
|
|
44
|
-
{ id: "api-create-resource", description: "POST /api/resources — create and persist a resource" },
|
|
45
|
-
{ id: "server-side-props", description: "Fetch user-specific data server-side before render" },
|
|
46
|
-
{ id: "static-generation", description: "Pre-render marketing pages at build time" },
|
|
47
|
-
{ id: "image-optimization", description: "next/image usage for responsive, lazy-loaded images" },
|
|
48
|
-
{ id: "middleware-auth", description: "Edge middleware to protect dashboard routes" },
|
|
49
|
-
{ id: "error-boundary", description: "Global error.tsx / _error.tsx user-facing error page" },
|
|
50
|
-
],
|
|
51
|
-
contextHint: "Building a Next.js app. Use Server Components by default; Client Components only when needed.",
|
|
52
|
-
scripts: {
|
|
53
|
-
"dev": "next dev",
|
|
54
|
-
"build": "next build",
|
|
55
|
-
"start": "next start",
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
|
|
59
|
-
// ── CLI tool ────────────────────────────────────────────────────────────────
|
|
60
|
-
"cli": {
|
|
61
|
-
description: "Node.js / Python CLI tool",
|
|
62
|
-
capabilities: [
|
|
63
|
-
{ id: "parse-args", description: "Parse CLI arguments and flags, show help on --help" },
|
|
64
|
-
{ id: "validate-config", description: "Load and validate config file or env vars on startup" },
|
|
65
|
-
{ id: "main-command", description: "Primary command — the core action the CLI performs" },
|
|
66
|
-
{ id: "output-formatter", description: "Format output as text, JSON, or table based on --format flag" },
|
|
67
|
-
{ id: "error-handler", description: "Friendly error messages with exit codes, no raw stack traces" },
|
|
68
|
-
{ id: "progress-display", description: "Spinner or progress bar for long-running operations" },
|
|
69
|
-
{ id: "update-check", description: "Check npm/PyPI for a newer version on startup" },
|
|
70
|
-
{ id: "plugin-loader", description: "Discover and load user-installed plugins at runtime" },
|
|
71
|
-
],
|
|
72
|
-
contextHint: "Building a CLI tool. Prioritise UX: helpful errors, --json flag, zero-config defaults.",
|
|
73
|
-
scripts: {
|
|
74
|
-
"start": "node bin/cli.js",
|
|
75
|
-
"link": "npm link",
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
// ── GraphQL ─────────────────────────────────────────────────────────────────
|
|
80
|
-
"graphql": {
|
|
81
|
-
description: "GraphQL API (Apollo, Pothos, or similar)",
|
|
82
|
-
capabilities: [
|
|
83
|
-
{ id: "query-viewer", description: "query { viewer } — return the authenticated user" },
|
|
84
|
-
{ id: "query-list", description: "query { items(first: N, after: cursor) } — paginated list" },
|
|
85
|
-
{ id: "query-node", description: "query { node(id) } — global object lookup by ID" },
|
|
86
|
-
{ id: "mutation-create", description: "mutation { createItem(input) } — create and return new object" },
|
|
87
|
-
{ id: "mutation-update", description: "mutation { updateItem(id, input) } — update fields" },
|
|
88
|
-
{ id: "mutation-delete", description: "mutation { deleteItem(id) } — remove and return deleted" },
|
|
89
|
-
{ id: "subscription-event", description: "subscription { itemUpdated } — real-time push via WebSocket" },
|
|
90
|
-
{ id: "data-loader", description: "Batch and cache DB calls with DataLoader to avoid N+1" },
|
|
91
|
-
{ id: "auth-directive", description: "@auth directive — enforce authentication on resolvers" },
|
|
92
|
-
{ id: "error-formatting", description: "Consistent GraphQL error shape with extensions and codes" },
|
|
93
|
-
],
|
|
94
|
-
contextHint: "Building a GraphQL API. Use DataLoader for all relations, @auth directive on protected resolvers.",
|
|
95
|
-
scripts: {
|
|
96
|
-
"dev": "ts-node src/server.ts",
|
|
97
|
-
"codegen": "graphql-codegen",
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
|
|
101
|
-
// ── Monorepo ────────────────────────────────────────────────────────────────
|
|
102
|
-
"monorepo": {
|
|
103
|
-
description: "Monorepo workspace (nx / turborepo / pnpm workspaces)",
|
|
104
|
-
capabilities: [
|
|
105
|
-
{ id: "build-all", description: "Build all packages in dependency order" },
|
|
106
|
-
{ id: "test-all", description: "Run tests across all packages in parallel" },
|
|
107
|
-
{ id: "lint-all", description: "Lint all packages with shared ESLint / Prettier config" },
|
|
108
|
-
{ id: "publish-packages", description: "Bump versions and publish changed packages to npm" },
|
|
109
|
-
{ id: "shared-ui-library", description: "packages/ui — shared React component library" },
|
|
110
|
-
{ id: "shared-utils", description: "packages/utils — shared utility functions and types" },
|
|
111
|
-
{ id: "web-app", description: "apps/web — primary Next.js or React web application" },
|
|
112
|
-
{ id: "api-service", description: "apps/api — backend service consumed by apps" },
|
|
113
|
-
{ id: "cache-builds", description: "Remote build cache via Turborepo or Nx Cloud" },
|
|
114
|
-
{ id: "affected-only", description: "Run tasks only for packages affected by a git change" },
|
|
115
|
-
],
|
|
116
|
-
contextHint: "Working in a monorepo. Changes in packages/* may affect apps/*. Check affected packages before building.",
|
|
117
|
-
scripts: {
|
|
118
|
-
"build": "turbo build",
|
|
119
|
-
"dev": "turbo dev",
|
|
120
|
-
"test": "turbo test",
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
export function listTemplates() {
|
|
126
|
-
return Object.entries(TEMPLATES).map(([name, t]) => ({ name, description: t.description }));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function getTemplate(name) {
|
|
130
|
-
return TEMPLATES[name] || null;
|
|
131
|
-
}
|
|
1
|
+
const i={"rest-api":{description:"Express / Fastify / Hono REST API",capabilities:[{id:"list-items",description:"GET /items \u2014 paginated list with filtering and sorting"},{id:"get-item",description:"GET /items/:id \u2014 fetch a single resource by ID"},{id:"create-item",description:"POST /items \u2014 create a new resource, validate input"},{id:"update-item",description:"PUT /items/:id \u2014 full update of an existing resource"},{id:"delete-item",description:"DELETE /items/:id \u2014 soft or hard delete"},{id:"authenticate",description:"POST /auth/login \u2014 exchange credentials for a JWT token"},{id:"refresh-token",description:"POST /auth/refresh \u2014 extend session with a new token"},{id:"health-check",description:"GET /health \u2014 liveness probe for load balancers"},{id:"paginate",description:"Cursor or offset pagination applied across list endpoints"},{id:"validate-input",description:"Schema validation on all incoming request bodies"}],contextHint:"Building a REST API. Focus on route handlers, middleware, and input validation.",scripts:{dev:"node src/index.js",start:"node dist/index.js"}},nextjs:{description:"Next.js full-stack app (App Router or Pages Router)",capabilities:[{id:"render-home-page",description:"/ \u2014 server-rendered landing page with SEO metadata"},{id:"render-dashboard",description:"/dashboard \u2014 authenticated user dashboard"},{id:"api-authenticate",description:"POST /api/auth/login \u2014 issue session cookie or JWT"},{id:"api-list-resources",description:"GET /api/resources \u2014 paginated list, auth-protected"},{id:"api-create-resource",description:"POST /api/resources \u2014 create and persist a resource"},{id:"server-side-props",description:"Fetch user-specific data server-side before render"},{id:"static-generation",description:"Pre-render marketing pages at build time"},{id:"image-optimization",description:"next/image usage for responsive, lazy-loaded images"},{id:"middleware-auth",description:"Edge middleware to protect dashboard routes"},{id:"error-boundary",description:"Global error.tsx / _error.tsx user-facing error page"}],contextHint:"Building a Next.js app. Use Server Components by default; Client Components only when needed.",scripts:{dev:"next dev",build:"next build",start:"next start"}},cli:{description:"Node.js / Python CLI tool",capabilities:[{id:"parse-args",description:"Parse CLI arguments and flags, show help on --help"},{id:"validate-config",description:"Load and validate config file or env vars on startup"},{id:"main-command",description:"Primary command \u2014 the core action the CLI performs"},{id:"output-formatter",description:"Format output as text, JSON, or table based on --format flag"},{id:"error-handler",description:"Friendly error messages with exit codes, no raw stack traces"},{id:"progress-display",description:"Spinner or progress bar for long-running operations"},{id:"update-check",description:"Check npm/PyPI for a newer version on startup"},{id:"plugin-loader",description:"Discover and load user-installed plugins at runtime"}],contextHint:"Building a CLI tool. Prioritise UX: helpful errors, --json flag, zero-config defaults.",scripts:{start:"node bin/cli.js",link:"npm link"}},graphql:{description:"GraphQL API (Apollo, Pothos, or similar)",capabilities:[{id:"query-viewer",description:"query { viewer } \u2014 return the authenticated user"},{id:"query-list",description:"query { items(first: N, after: cursor) } \u2014 paginated list"},{id:"query-node",description:"query { node(id) } \u2014 global object lookup by ID"},{id:"mutation-create",description:"mutation { createItem(input) } \u2014 create and return new object"},{id:"mutation-update",description:"mutation { updateItem(id, input) } \u2014 update fields"},{id:"mutation-delete",description:"mutation { deleteItem(id) } \u2014 remove and return deleted"},{id:"subscription-event",description:"subscription { itemUpdated } \u2014 real-time push via WebSocket"},{id:"data-loader",description:"Batch and cache DB calls with DataLoader to avoid N+1"},{id:"auth-directive",description:"@auth directive \u2014 enforce authentication on resolvers"},{id:"error-formatting",description:"Consistent GraphQL error shape with extensions and codes"}],contextHint:"Building a GraphQL API. Use DataLoader for all relations, @auth directive on protected resolvers.",scripts:{dev:"ts-node src/server.ts",codegen:"graphql-codegen"}},monorepo:{description:"Monorepo workspace (nx / turborepo / pnpm workspaces)",capabilities:[{id:"build-all",description:"Build all packages in dependency order"},{id:"test-all",description:"Run tests across all packages in parallel"},{id:"lint-all",description:"Lint all packages with shared ESLint / Prettier config"},{id:"publish-packages",description:"Bump versions and publish changed packages to npm"},{id:"shared-ui-library",description:"packages/ui \u2014 shared React component library"},{id:"shared-utils",description:"packages/utils \u2014 shared utility functions and types"},{id:"web-app",description:"apps/web \u2014 primary Next.js or React web application"},{id:"api-service",description:"apps/api \u2014 backend service consumed by apps"},{id:"cache-builds",description:"Remote build cache via Turborepo or Nx Cloud"},{id:"affected-only",description:"Run tasks only for packages affected by a git change"}],contextHint:"Working in a monorepo. Changes in packages/* may affect apps/*. Check affected packages before building.",scripts:{build:"turbo build",dev:"turbo dev",test:"turbo test"}}};function r(){return Object.entries(i).map(([e,t])=>({name:e,description:t.description}))}function a(e){return i[e]||null}export{i as TEMPLATES,a as getTemplate,r as listTemplates};
|