usetraceforge-cli 0.1.4 ā 0.1.6
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/dist/index.js +7 -3
- package/dist/installers/gemini.js +76 -0
- package/dist/installers/nextjs.js +22 -79
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { intro, outro, text, select, spinner } from "@clack/prompts";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import { installNextJs } from "./installers/nextjs.js";
|
|
6
|
+
import { runGeminiAgent } from "./installers/gemini.js";
|
|
6
7
|
async function main() {
|
|
7
8
|
console.log();
|
|
8
9
|
intro(chalk.bgBlue.white.bold(" TraceForge Wizard "));
|
|
@@ -21,8 +22,9 @@ async function main() {
|
|
|
21
22
|
const framework = await select({
|
|
22
23
|
message: "Which framework are you using?",
|
|
23
24
|
options: [
|
|
24
|
-
{ value: "nextjs", label: "Next.js (
|
|
25
|
-
{ value: "express", label: "Express (
|
|
25
|
+
{ value: "nextjs", label: "Next.js (Prebuilt)" },
|
|
26
|
+
{ value: "express", label: "Express.js (Prebuilt)" },
|
|
27
|
+
{ value: "gemini", label: "š¤ Let AI scan my codebase (Gemini Agent)" },
|
|
26
28
|
],
|
|
27
29
|
});
|
|
28
30
|
if (typeof framework !== "string") {
|
|
@@ -47,9 +49,11 @@ async function main() {
|
|
|
47
49
|
await installNextJs(apiKey, endpoint);
|
|
48
50
|
}
|
|
49
51
|
else if (framework === "express") {
|
|
50
|
-
// await installExpress(apiKey);
|
|
51
52
|
console.log(chalk.yellow("Express auto-installation coming soon."));
|
|
52
53
|
}
|
|
54
|
+
else if (framework === "gemini") {
|
|
55
|
+
await runGeminiAgent(apiKey, endpoint);
|
|
56
|
+
}
|
|
53
57
|
outro(chalk.green("⨠You're all set! TraceForge is now protecting your application."));
|
|
54
58
|
}
|
|
55
59
|
main().catch((err) => {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { text, spinner } from "@clack/prompts";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { GoogleGenerativeAI } from "@google/generative-ai";
|
|
6
|
+
function getFiles(dir, fileList = [], depth = 0) {
|
|
7
|
+
if (depth > 3)
|
|
8
|
+
return fileList;
|
|
9
|
+
const files = fs.readdirSync(dir);
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
if (file === "node_modules" || file === ".git" || file === ".next" || file === "dist") {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
const filePath = path.join(dir, file);
|
|
15
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
16
|
+
getFiles(filePath, fileList, depth + 1);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
fileList.push(filePath.replace(process.cwd(), ""));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return fileList;
|
|
23
|
+
}
|
|
24
|
+
export async function runGeminiAgent(apiKey, endpoint) {
|
|
25
|
+
console.log(chalk.blue("\nš¤ Initializing TraceForge AI Agent..."));
|
|
26
|
+
// Try to find Gemini Key in .env
|
|
27
|
+
let geminiKey = process.env.GEMINI_API_KEY;
|
|
28
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
29
|
+
if (!geminiKey && fs.existsSync(envPath)) {
|
|
30
|
+
const envContent = fs.readFileSync(envPath, "utf-8");
|
|
31
|
+
const match = envContent.match(/GEMINI_API_KEY="?([^"\n]+)"?/);
|
|
32
|
+
if (match)
|
|
33
|
+
geminiKey = match[1];
|
|
34
|
+
}
|
|
35
|
+
if (!geminiKey) {
|
|
36
|
+
const keyResponse = await text({
|
|
37
|
+
message: "Please enter your Google Gemini API Key (we won't save this):",
|
|
38
|
+
placeholder: "AIzaSy...",
|
|
39
|
+
});
|
|
40
|
+
if (typeof keyResponse === "symbol" || !keyResponse) {
|
|
41
|
+
console.log(chalk.red("Agent: Cancelled."));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
geminiKey = keyResponse;
|
|
45
|
+
}
|
|
46
|
+
const s = spinner();
|
|
47
|
+
s.start("Agent: Scanning your codebase...");
|
|
48
|
+
const files = getFiles(process.cwd());
|
|
49
|
+
const folderStructure = files.join("\n");
|
|
50
|
+
s.message("Agent: Analyzing architecture with Gemini...");
|
|
51
|
+
try {
|
|
52
|
+
const genAI = new GoogleGenerativeAI(geminiKey);
|
|
53
|
+
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
|
|
54
|
+
const prompt = `
|
|
55
|
+
I am trying to install an SDK called "usetraceforge".
|
|
56
|
+
I have the following files in my project:
|
|
57
|
+
|
|
58
|
+
${folderStructure}
|
|
59
|
+
|
|
60
|
+
Please tell me EXACTLY how to configure TraceForge for my framework.
|
|
61
|
+
If this is Next.js 15, tell me to create 'instrumentation.ts'.
|
|
62
|
+
If this is Express, tell me to add 'TraceForge.expressErrorHandler()'.
|
|
63
|
+
Keep your answer very short, professional, and provide the exact code snippets I need to copy and paste.
|
|
64
|
+
The API Key they should use is: ${apiKey}
|
|
65
|
+
The Endpoint is: ${endpoint}
|
|
66
|
+
`;
|
|
67
|
+
const result = await model.generateContent(prompt);
|
|
68
|
+
const response = result.response;
|
|
69
|
+
s.stop(chalk.green("Agent: Analysis complete!"));
|
|
70
|
+
console.log("\n" + chalk.cyan(response.text()) + "\n");
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
s.stop(chalk.red("Agent: Failed to communicate with Gemini API."));
|
|
74
|
+
console.error(error.message || error);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -4,7 +4,7 @@ import { spinner } from "@clack/prompts";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
export async function installNextJs(apiKey, endpoint) {
|
|
6
6
|
const s = spinner();
|
|
7
|
-
s.start("Configuring Next.js...");
|
|
7
|
+
s.start("Agent: Configuring Next.js Instrumentation...");
|
|
8
8
|
try {
|
|
9
9
|
// 1. Add API key and endpoint to .env.local
|
|
10
10
|
const envPath = path.resolve(process.cwd(), ".env.local");
|
|
@@ -18,92 +18,35 @@ export async function installNextJs(apiKey, endpoint) {
|
|
|
18
18
|
else {
|
|
19
19
|
fs.writeFileSync(envPath, envVar);
|
|
20
20
|
}
|
|
21
|
-
// 2. Generate
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const providerCode = `"use client";
|
|
27
|
-
import { useEffect } from "react";
|
|
28
|
-
import TraceForge from "usetraceforge";
|
|
29
|
-
import { TraceForgeErrorBoundary } from "usetraceforge/react";
|
|
30
|
-
|
|
31
|
-
let initialized = false;
|
|
21
|
+
// 2. Generate instrumentation.ts
|
|
22
|
+
const isSrc = fs.existsSync(path.resolve(process.cwd(), "src"));
|
|
23
|
+
const instrumentationDir = isSrc ? path.resolve(process.cwd(), "src") : process.cwd();
|
|
24
|
+
const instrumentationPath = path.resolve(instrumentationDir, "instrumentation.ts");
|
|
25
|
+
const instrumentationCode = `import TraceForge from "usetraceforge";
|
|
32
26
|
|
|
33
|
-
export function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
});
|
|
41
|
-
initialized = true;
|
|
42
|
-
}, []);
|
|
27
|
+
export function register() {
|
|
28
|
+
TraceForge.init({
|
|
29
|
+
apiKey: process.env.NEXT_PUBLIC_TRACEFORGE_API_KEY!,
|
|
30
|
+
endpoint: process.env.NEXT_PUBLIC_TRACEFORGE_INGEST_URL,
|
|
31
|
+
autoCapture: true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
43
34
|
|
|
44
|
-
|
|
35
|
+
export function onRequestError(err: any, request: any) {
|
|
36
|
+
TraceForge.captureException(err, { tags: { route: request.url } });
|
|
45
37
|
}
|
|
46
38
|
`;
|
|
47
|
-
fs.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
path.resolve(process.cwd(), "app/layout.tsx"),
|
|
51
|
-
path.resolve(process.cwd(), "src/app/layout.tsx"),
|
|
52
|
-
];
|
|
53
|
-
let targetLayout = null;
|
|
54
|
-
for (const p of layoutPaths) {
|
|
55
|
-
if (fs.existsSync(p)) {
|
|
56
|
-
targetLayout = p;
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (!targetLayout) {
|
|
61
|
-
s.stop(chalk.yellow("Could not find app/layout.tsx. Please configure TraceForge manually."));
|
|
62
|
-
return;
|
|
39
|
+
if (!fs.existsSync(instrumentationPath)) {
|
|
40
|
+
fs.writeFileSync(instrumentationPath, instrumentationCode);
|
|
41
|
+
console.log(chalk.green(`\nAgent: Created ${isSrc ? "src/" : ""}instrumentation.ts!`));
|
|
63
42
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (layoutCode.includes("TraceForgeProvider")) {
|
|
67
|
-
s.stop(chalk.green("TraceForge is already configured in layout.tsx!"));
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
const importStatement = `import { TraceForgeProvider } from "@/components/TraceForgeProvider";\n`;
|
|
71
|
-
layoutCode = importStatement + layoutCode;
|
|
72
|
-
layoutCode = layoutCode.replace(/(<body[^>]*>)/g, `$1\n <TraceForgeProvider>`);
|
|
73
|
-
layoutCode = layoutCode.replace(/<\/body>/g, ` </TraceForgeProvider>\n </body>`);
|
|
74
|
-
fs.writeFileSync(targetLayout, layoutCode);
|
|
75
|
-
// 5. Update next.config.js / next.config.mjs
|
|
76
|
-
const configPaths = [
|
|
77
|
-
path.resolve(process.cwd(), "next.config.mjs"),
|
|
78
|
-
path.resolve(process.cwd(), "next.config.js"),
|
|
79
|
-
];
|
|
80
|
-
let targetConfig = null;
|
|
81
|
-
for (const p of configPaths) {
|
|
82
|
-
if (fs.existsSync(p)) {
|
|
83
|
-
targetConfig = p;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (targetConfig) {
|
|
88
|
-
let configCode = fs.readFileSync(targetConfig, "utf-8");
|
|
89
|
-
if (!configCode.includes("withTraceForgeConfig")) {
|
|
90
|
-
const isMjs = targetConfig.endsWith(".mjs") || configCode.includes("export default");
|
|
91
|
-
// Add import
|
|
92
|
-
if (isMjs) {
|
|
93
|
-
configCode = `import { withTraceForgeConfig } from "usetraceforge/next-plugin";\n` + configCode;
|
|
94
|
-
configCode = configCode.replace(/export default (.+);/g, `export default withTraceForgeConfig($1);`);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
configCode = `const { withTraceForgeConfig } = require("usetraceforge/next-plugin");\n` + configCode;
|
|
98
|
-
configCode = configCode.replace(/module\.exports = (.+);/g, `module.exports = withTraceForgeConfig($1);`);
|
|
99
|
-
}
|
|
100
|
-
fs.writeFileSync(targetConfig, configCode);
|
|
101
|
-
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(chalk.yellow(`\nAgent: instrumentation.ts already exists. Please manually add TraceForge.`));
|
|
102
45
|
}
|
|
103
|
-
s.stop(chalk.green("Next.js configuration complete!"));
|
|
46
|
+
s.stop(chalk.green("Agent: Next.js configuration complete!"));
|
|
104
47
|
}
|
|
105
48
|
catch (error) {
|
|
106
|
-
s.stop(chalk.red("
|
|
49
|
+
s.stop(chalk.red("Agent: An error occurred."));
|
|
107
50
|
console.error(error);
|
|
108
51
|
}
|
|
109
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usetraceforge-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "TraceForge CLI Wizard for 2-click installations",
|
|
5
5
|
"bin": {
|
|
6
6
|
"traceforge": "./dist/index.js"
|
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@clack/prompts": "^0.7.0",
|
|
20
|
+
"@google/generative-ai": "^0.24.1",
|
|
20
21
|
"chalk": "^5.3.0",
|
|
21
|
-
"execa": "^9.3.0"
|
|
22
|
+
"execa": "^9.3.0",
|
|
23
|
+
"ts-morph": "^28.0.0"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
26
|
"@types/node": "^20.0.0",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
+
"tsx": "^4.16.2",
|
|
28
|
+
"typescript": "^5.5.4"
|
|
27
29
|
}
|
|
28
30
|
}
|