usetraceforge-cli 0.1.5 ā 0.1.7
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 +81 -0
- package/dist/installers/nextjs.js +24 -83
- package/package.json +2 -1
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,81 @@
|
|
|
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
|
+
// Use gemini-pro as a safe fallback since 1.5-flash threw a 404 on your key
|
|
54
|
+
const model = genAI.getGenerativeModel({ model: "gemini/gemini-3.1-flash-lite" });
|
|
55
|
+
const prompt = `
|
|
56
|
+
I am trying to install an SDK called "usetraceforge".
|
|
57
|
+
I have the following files in my project:
|
|
58
|
+
|
|
59
|
+
${folderStructure}
|
|
60
|
+
|
|
61
|
+
Here is the documentation for usetraceforge:
|
|
62
|
+
- Next.js 15+ Server: Create 'instrumentation.ts' (or 'src/instrumentation.ts') exporting 'register' and 'onRequestError(err, request)' which calls 'TraceForge.captureException(err, { tags: { route: request.url } })'.
|
|
63
|
+
- Next.js Client: Wrap your app with '<TraceForgeProvider>' from 'usetraceforge/react'.
|
|
64
|
+
- Express: Call 'app.use(TraceForge.expressErrorHandler())' AFTER all routes but BEFORE custom error handlers.
|
|
65
|
+
- Initialization: You must always call 'TraceForge.init({ apiKey, endpoint })' as early as possible.
|
|
66
|
+
|
|
67
|
+
The user's API Key is: ${apiKey}
|
|
68
|
+
The user's Endpoint is: ${endpoint}
|
|
69
|
+
|
|
70
|
+
Based on the folder structure above, tell the developer exactly which files they need to edit and provide the exact code snippets they need to copy and paste to configure TraceForge. Be concise, accurate, and professional.
|
|
71
|
+
`;
|
|
72
|
+
const result = await model.generateContent(prompt);
|
|
73
|
+
const response = result.response;
|
|
74
|
+
s.stop(chalk.green("Agent: Analysis complete!"));
|
|
75
|
+
console.log("\n" + chalk.cyan(response.text()) + "\n");
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
s.stop(chalk.red("Agent: Failed to communicate with Gemini API."));
|
|
79
|
+
console.error(error.message || error);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -2,10 +2,9 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { spinner } from "@clack/prompts";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
-
import { Project, SyntaxKind } from "ts-morph";
|
|
6
5
|
export async function installNextJs(apiKey, endpoint) {
|
|
7
6
|
const s = spinner();
|
|
8
|
-
s.start("Agent:
|
|
7
|
+
s.start("Agent: Configuring Next.js Instrumentation...");
|
|
9
8
|
try {
|
|
10
9
|
// 1. Add API key and endpoint to .env.local
|
|
11
10
|
const envPath = path.resolve(process.cwd(), ".env.local");
|
|
@@ -19,93 +18,35 @@ export async function installNextJs(apiKey, endpoint) {
|
|
|
19
18
|
else {
|
|
20
19
|
fs.writeFileSync(envPath, envVar);
|
|
21
20
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
namedImports: ["withTraceForgeConfig"],
|
|
44
|
-
moduleSpecifier: "usetraceforge/next-plugin"
|
|
45
|
-
});
|
|
46
|
-
const defaultExport = sourceFile.getExportAssignment(d => !d.isExportEquals());
|
|
47
|
-
if (defaultExport) {
|
|
48
|
-
const expression = defaultExport.getExpression();
|
|
49
|
-
defaultExport.setExpression(`withTraceForgeConfig(${expression.getText()})`);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
console.log(chalk.yellow("\nAgent: Could not parse export default in next.config. Please wrap your config manually."));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// Fallback to simpler regex for CJS files if AST binary expression is too complex
|
|
57
|
-
let configCode = fs.readFileSync(targetConfig, "utf-8");
|
|
58
|
-
configCode = `const { withTraceForgeConfig } = require("usetraceforge/next-plugin");\n` + configCode;
|
|
59
|
-
configCode = configCode.replace(/module\.exports = (.+);/g, `module.exports = withTraceForgeConfig($1);`);
|
|
60
|
-
fs.writeFileSync(targetConfig, configCode);
|
|
61
|
-
}
|
|
62
|
-
sourceFile.saveSync();
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
console.log(chalk.yellow("\nAgent: Could not locate next.config.ts/mjs. Please configure Webpack loader manually."));
|
|
67
|
-
}
|
|
68
|
-
// 3. Layout AST Injection
|
|
69
|
-
const layoutPaths = [
|
|
70
|
-
path.resolve(process.cwd(), "app/layout.tsx"),
|
|
71
|
-
path.resolve(process.cwd(), "src/app/layout.tsx"),
|
|
72
|
-
];
|
|
73
|
-
let targetLayout = null;
|
|
74
|
-
for (const p of layoutPaths) {
|
|
75
|
-
if (fs.existsSync(p)) {
|
|
76
|
-
targetLayout = p;
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
if (targetLayout) {
|
|
81
|
-
const sourceFile = project.addSourceFileAtPath(targetLayout);
|
|
82
|
-
if (!sourceFile.getFullText().includes("TraceForgeProvider")) {
|
|
83
|
-
sourceFile.addImportDeclaration({
|
|
84
|
-
namedImports: ["TraceForgeProvider"],
|
|
85
|
-
moduleSpecifier: "usetraceforge/react"
|
|
86
|
-
});
|
|
87
|
-
// Find body tag
|
|
88
|
-
const jsxElements = sourceFile.getDescendantsOfKind(SyntaxKind.JsxElement);
|
|
89
|
-
let bodyTag = jsxElements.find(el => el.getOpeningElement().getTagNameNode().getText() === "body");
|
|
90
|
-
if (bodyTag) {
|
|
91
|
-
const childrenText = bodyTag.getJsxChildren().map(c => c.getText()).join("");
|
|
92
|
-
const opening = bodyTag.getOpeningElement().getText();
|
|
93
|
-
const closing = bodyTag.getClosingElement().getText();
|
|
94
|
-
bodyTag.replaceWithText(`${opening}\n <TraceForgeProvider>\n ${childrenText}\n </TraceForgeProvider>\n ${closing}`);
|
|
95
|
-
sourceFile.saveSync();
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
console.log(chalk.yellow("\nAgent: Could not find <body> tag in layout.tsx. Please add <TraceForgeProvider> manually."));
|
|
99
|
-
}
|
|
100
|
-
}
|
|
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";
|
|
26
|
+
|
|
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
|
+
}
|
|
34
|
+
|
|
35
|
+
export function onRequestError(err: any, request: any) {
|
|
36
|
+
TraceForge.captureException(err, { tags: { route: request.url } });
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
if (!fs.existsSync(instrumentationPath)) {
|
|
40
|
+
fs.writeFileSync(instrumentationPath, instrumentationCode);
|
|
41
|
+
console.log(chalk.green(`\nAgent: Created ${isSrc ? "src/" : ""}instrumentation.ts!`));
|
|
101
42
|
}
|
|
102
43
|
else {
|
|
103
|
-
console.log(chalk.yellow(
|
|
44
|
+
console.log(chalk.yellow(`\nAgent: instrumentation.ts already exists. Please manually add TraceForge.`));
|
|
104
45
|
}
|
|
105
46
|
s.stop(chalk.green("Agent: Next.js configuration complete!"));
|
|
106
47
|
}
|
|
107
48
|
catch (error) {
|
|
108
|
-
s.stop(chalk.red("Agent: An error occurred
|
|
49
|
+
s.stop(chalk.red("Agent: An error occurred."));
|
|
109
50
|
console.error(error);
|
|
110
51
|
}
|
|
111
52
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "usetraceforge-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "TraceForge CLI Wizard for 2-click installations",
|
|
5
5
|
"bin": {
|
|
6
6
|
"traceforge": "./dist/index.js"
|
|
@@ -17,6 +17,7 @@
|
|
|
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
22
|
"execa": "^9.3.0",
|
|
22
23
|
"ts-morph": "^28.0.0"
|