botversion-sdk 1.0.0 → 1.0.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/init.js CHANGED
@@ -29,6 +29,9 @@ function log(msg) {
29
29
  function info(msg) {
30
30
  console.log(`${c.cyan} ℹ${c.reset} ${msg}`);
31
31
  }
32
+ function info2(msg) {
33
+ console.log(`${c.cyan} ℹ${c.reset} ${msg}`);
34
+ }
32
35
  function success(msg) {
33
36
  console.log(`${c.green} ✔${c.reset} ${msg}`);
34
37
  }
@@ -106,10 +109,10 @@ async function main() {
106
109
  }
107
110
 
108
111
  // Run full detection
109
- const info2 = detector.detect(workingDir);
112
+ const detected = detector.detect(workingDir);
110
113
 
111
114
  // ── Check if already initialized ─────────────────────────────────────────
112
- if (info2.alreadyInitialized && !args.force) {
115
+ if (detected.alreadyInitialized && !args.force) {
113
116
  warn("BotVersion SDK is already initialized in this project.");
114
117
  log(`\n To reinitialize, run with --force flag:\n`);
115
118
  log(` npx botversion-sdk init --key ${args.key} --force\n`);
@@ -119,68 +122,71 @@ async function main() {
119
122
  // ── Framework check ───────────────────────────────────────────────────────
120
123
  step("Detecting framework...");
121
124
 
122
- if (!info2.framework.name) {
125
+ if (!detected.framework.name) {
123
126
  error("Could not detect a supported framework.");
124
127
  log(`\n Supported: Express.js, Next.js`);
125
128
  log(` Make sure you have them listed in package.json\n`);
126
129
  process.exit(1);
127
130
  }
128
131
 
129
- if (!info2.framework.supported) {
132
+ if (!detected.framework.supported) {
130
133
  warn(
131
- `Detected: ${info2.framework.name} (not yet supported for auto-setup)`,
134
+ `Detected: ${detected.framework.name} (not yet supported for auto-setup)`,
132
135
  );
133
136
  log("");
134
- log(generator.generateManualInstructions(info2.framework.name, args.key));
137
+ log(
138
+ generator.generateManualInstructions(detected.framework.name, args.key),
139
+ );
135
140
  process.exit(0);
136
141
  }
137
142
 
138
- success(`Framework: ${info2.framework.name}`);
143
+ success(`Framework: ${detected.framework.name}`);
139
144
  info(
140
- `Module system: ${info2.moduleSystem === "esm" ? "ES Modules" : "CommonJS"}`,
145
+ `Module system: ${detected.moduleSystem === "esm" ? "ES Modules" : "CommonJS"}`,
141
146
  );
142
- info(`Language: ${info2.isTypeScript ? "TypeScript" : "JavaScript"}`);
147
+ info(`Language: ${detected.isTypeScript ? "TypeScript" : "JavaScript"}`);
143
148
 
144
149
  // ── Auth detection ────────────────────────────────────────────────────────
145
150
  step("Detecting auth library...");
146
151
 
147
- let auth = info2.auth;
152
+ let auth = detected.auth;
148
153
 
149
154
  if (!auth.name) {
150
155
  warn("No auth library detected automatically.");
151
156
  auth = await prompts.promptAuthLibrary();
152
- info2.auth = auth;
157
+ detected.auth = auth;
153
158
  } else if (!auth.supported) {
154
159
  warn(`Detected auth: ${auth.name} (not yet supported for auto-setup)`);
155
160
  warn("Will set up without user context — you can add it manually later.");
156
161
  const proceed = await prompts.confirm("Continue without auth?", true);
157
162
  if (!proceed) process.exit(0);
158
163
  auth = { name: auth.name, supported: false };
159
- info2.auth = auth;
164
+ detected.auth = auth;
160
165
  } else {
161
166
  const versionLabel = auth.version ? ` (${auth.version})` : "";
162
167
  success(`Auth: ${auth.name}${versionLabel}`);
163
168
  }
164
169
 
165
170
  // ── Package manager ───────────────────────────────────────────────────────
166
- info(`Package manager: ${info2.packageManager}`);
171
+ info(`Package manager: ${detected.packageManager}`);
167
172
 
168
173
  // ─────────────────────────────────────────────────────────────────────────
169
174
  // FRAMEWORK: EXPRESS
170
175
  // ─────────────────────────────────────────────────────────────────────────
171
- if (info2.framework.name === "express") {
172
- await setupExpress(info2, args, changes);
176
+ if (detected.framework.name === "express") {
177
+ await setupExpress(detected, args, changes);
173
178
  }
174
179
 
175
180
  // ─────────────────────────────────────────────────────────────────────────
176
181
  // FRAMEWORK: NEXT.JS
177
182
  // ─────────────────────────────────────────────────────────────────────────
178
- else if (info2.framework.name === "next") {
179
- await setupNextJs(info2, args, changes);
183
+ else if (detected.framework.name === "next") {
184
+ await setupNextJs(detected, args, changes);
180
185
  }
181
186
 
182
187
  // ── Write API key to .env / .env.local ────────────────────────────────────
183
- const envFileName = info2.framework.name === "next" ? ".env.local" : ".env";
188
+ const envFileName =
189
+ detected.framework.name === "next" ? ".env.local" : ".env";
184
190
  const envPath = path.join(workingDir, envFileName);
185
191
  const envLine = `BOTVERSION_API_KEY=${args.key}`;
186
192
  const envContent = fs.existsSync(envPath)
@@ -216,16 +222,16 @@ async function main() {
216
222
 
217
223
  // ─── EXPRESS SETUP ────────────────────────────────────────────────────────────
218
224
 
219
- async function setupExpress(info, args, changes) {
225
+ async function setupExpress(detected, args, changes) {
220
226
  step("Setting up Express...");
221
227
 
222
228
  // Find entry point
223
- let entryPoint = info.entryPoint;
229
+ let entryPoint = detected.entryPoint;
224
230
 
225
231
  if (!entryPoint || !fs.existsSync(entryPoint)) {
226
232
  warn("Could not find your server entry point automatically.");
227
233
  const manualPath = await prompts.promptEntryPoint();
228
- entryPoint = path.resolve(info.cwd, manualPath);
234
+ entryPoint = path.resolve(detected.cwd, manualPath);
229
235
 
230
236
  if (!fs.existsSync(entryPoint)) {
231
237
  error(`File not found: ${entryPoint}`);
@@ -233,40 +239,65 @@ async function setupExpress(info, args, changes) {
233
239
  }
234
240
  }
235
241
 
236
- success(`Entry point: ${path.relative(info.cwd, entryPoint)}`);
242
+ success(`Entry point: ${path.relative(detected.cwd, entryPoint)}`);
237
243
 
238
244
  // Generate the init code
239
- const generated = generator.generateExpressInit(info, args.key);
245
+ const generated = generator.generateExpressInit(detected, args.key);
240
246
 
241
247
  // Find app.listen() and inject before it
242
248
  const listenCall = detector.findListenCall(entryPoint);
243
249
 
244
- if (listenCall) {
245
- info(`Found app.listen() at line ${listenCall.lineNumber}`);
250
+ // PATTERN 2: Separate app file with module.exports = app
251
+ if (detected.appFile) {
252
+ info(`Found app file: ${path.relative(detected.cwd, detected.appFile)}`);
253
+ const generated2 = generator.generateExpressInit(detected, args.key);
254
+ const result = writer.injectBeforeExport(
255
+ detected.appFile,
256
+ generated2.initBlock,
257
+ );
258
+
259
+ if (result.success) {
260
+ success(
261
+ `Injected BotVersion.init() into ${path.relative(detected.cwd, detected.appFile)}`,
262
+ );
263
+ changes.modified.push(path.relative(detected.cwd, detected.appFile));
264
+ } else if (result.reason === "already_exists") {
265
+ warn("BotVersion already found — skipping injection.");
266
+ }
267
+ }
268
+
269
+ // PATTERN 1 & 3 & 4: app.listen() or server.listen() in entry file
270
+ else if (
271
+ detected.listenCall ||
272
+ detected.listenInsideCallback ||
273
+ detected.createServer
274
+ ) {
246
275
  const result = writer.injectBeforeListen(entryPoint, generated.initBlock);
247
276
 
248
277
  if (result.success) {
249
278
  success(`Injected BotVersion.init() before app.listen()`);
250
- changes.modified.push(path.relative(info.cwd, entryPoint));
279
+ changes.modified.push(path.relative(detected.cwd, entryPoint));
251
280
  if (result.backup) changes.backups.push(result.backup);
252
281
  } else if (result.reason === "already_exists") {
253
282
  warn("BotVersion already found in entry point — skipping injection.");
254
283
  }
255
- } else {
256
- // app.listen() not found
257
- warn("Could not find app.listen() in entry point.");
284
+ }
285
+
286
+ // LAST RESORT: ask the user
287
+ else {
288
+ warn("Could not find the right place to inject automatically.");
258
289
  const response = await prompts.promptMissingListenCall(
259
- path.relative(info.cwd, entryPoint),
290
+ path.relative(detected.cwd, entryPoint),
260
291
  );
261
292
 
262
293
  if (response.action === "append") {
263
294
  const result = writer.appendToFile(entryPoint, generated.initBlock);
264
295
  if (result.success) {
265
296
  success("Appended BotVersion setup to end of file.");
266
- changes.modified.push(path.relative(info.cwd, entryPoint));
297
+ changes.modified.push(path.relative(detected.cwd, entryPoint));
267
298
  }
268
299
  } else if (response.action === "manual_path") {
269
- const altPath = path.resolve(info.cwd, response.filePath);
300
+ const altPath = path.resolve(detected.cwd, response.filePath);
270
301
  if (fs.existsSync(altPath)) {
271
302
  const result = writer.injectBeforeListen(altPath, generated.initBlock);
272
303
  if (result.success) {
@@ -280,7 +311,6 @@ async function setupExpress(info, args, changes) {
280
311
  );
281
312
  }
282
313
  } else {
283
- // skip — print manual instructions
284
314
  changes.manual.push(
285
315
  `Add this to your server file before app.listen():\n\n${generated.initBlock}`,
286
316
  );
@@ -291,10 +321,10 @@ async function setupExpress(info, args, changes) {
291
321
 
292
322
  // ─── NEXT.JS SETUP ────────────────────────────────────────────────────────────
293
323
 
294
- async function setupNextJs(info, args, changes) {
324
+ async function setupNextJs(detected, args, changes) {
295
325
  step("Setting up Next.js...");
296
326
 
297
- const nextInfo = info.next;
327
+ const nextInfo = detected.next;
298
328
  const baseDir = nextInfo.baseDir;
299
329
 
300
330
  info2(
@@ -302,20 +332,23 @@ async function setupNextJs(info, args, changes) {
302
332
  );
303
333
 
304
334
  // ── next-auth config location ─────────────────────────────────────────────
305
- if (info.auth.name === "next-auth" && !info.nextAuthConfig) {
335
+ if (detected.auth.name === "next-auth" && !detected.nextAuthConfig) {
306
336
  warn("Could not find authOptions location automatically.");
307
337
  const configPath = await prompts.promptNextAuthConfigPath();
308
- info.nextAuthConfig = {
309
- path: path.resolve(info.cwd, configPath),
338
+ detected.nextAuthConfig = {
339
+ path: path.resolve(detected.cwd, configPath),
310
340
  relativePath: configPath,
311
341
  };
312
342
  }
313
343
 
314
344
  // ── 1. Create instrumentation.js ──────────────────────────────────────────
315
- const instrExt = info.generateTs ? ".ts" : ".js";
316
- const instrFile = path.join(info.cwd, `instrumentation${instrExt}`);
345
+ const instrExt = detected.generateTs ? ".ts" : ".js";
346
+ const instrFile = path.join(detected.cwd, `instrumentation${instrExt}`);
317
347
 
318
- const instrContent = generator.generateInstrumentationFile(info, args.key);
348
+ const instrContent = generator.generateInstrumentationFile(
349
+ detected,
350
+ args.key,
351
+ );
319
352
  const instrResult = writer.createFile(instrFile, instrContent, args.force);
320
353
 
321
354
  if (instrResult.success) {
@@ -333,15 +366,15 @@ async function setupNextJs(info, args, changes) {
333
366
  }
334
367
 
335
368
  // ── 2. Patch next.config.js ───────────────────────────────────────────────
336
- const configPatch = generator.generateNextConfigPatch(info.cwd);
369
+ const configPatch = generator.generateNextConfigPatch(detected.cwd);
337
370
 
338
371
  if (configPatch) {
339
372
  if (configPatch.alreadyPatched) {
340
373
  info("next.config.js already has instrumentationHook — skipping.");
341
374
  } else {
342
375
  fs.writeFileSync(configPatch.path, configPatch.content, "utf8");
343
- success(`Updated ${path.relative(info.cwd, configPatch.path)}`);
344
- changes.modified.push(path.relative(info.cwd, configPatch.path));
376
+ success(`Updated ${path.relative(detected.cwd, configPatch.path)}`);
377
+ changes.modified.push(path.relative(detected.cwd, configPatch.path));
345
378
  }
346
379
  } else {
347
380
  warn("Could not find next.config.js — please add this manually:");
@@ -356,7 +389,7 @@ async function setupNextJs(info, args, changes) {
356
389
  const chatDir = path.join(pagesBase, "api", "botversion");
357
390
  const chatFile = path.join(chatDir, `chat${instrExt}`);
358
391
 
359
- const chatContent = generator.generateNextPagesChatRoute(info);
392
+ const chatContent = generator.generateNextPagesChatRoute(detected);
360
393
  const chatResult = writer.createFile(chatFile, chatContent, args.force);
361
394
 
362
395
  const relPath = `${nextInfo.srcDir ? "src/" : ""}pages/api/botversion/chat${instrExt}`;
@@ -382,7 +415,7 @@ async function setupNextJs(info, args, changes) {
382
415
  const chatDir = path.join(appBase, "api", "botversion", "chat");
383
416
  const chatFile = path.join(chatDir, `route${instrExt}`);
384
417
 
385
- const chatContent = generator.generateNextAppChatRoute(info);
418
+ const chatContent = generator.generateNextAppChatRoute(detected);
386
419
  const relPath = `${nextInfo.srcDir ? "src/" : ""}app/api/botversion/chat/route${instrExt}`;
387
420
 
388
421
  const chatResult = writer.createFile(chatFile, chatContent, args.force);
@@ -403,11 +436,6 @@ async function setupNextJs(info, args, changes) {
403
436
  }
404
437
  }
405
438
 
406
- // ─── helper used inside setupNextJs ──────────────────────────────────────────
407
- function info2(msg) {
408
- console.log(`${c.cyan} ℹ${c.reset} ${msg}`);
409
- }
410
-
411
439
  // ─── RUN ──────────────────────────────────────────────────────────────────────
412
440
 
413
441
  main().catch((err) => {
package/cli/detector.js CHANGED
@@ -269,6 +269,43 @@ function findListenCall(filePath) {
269
269
  return null;
270
270
  }
271
271
 
272
+ function findModuleExportsApp(filePath) {
273
+ const content = fs.readFileSync(filePath, "utf8");
274
+ const lines = content.split("\n");
275
+ for (let i = 0; i < lines.length; i++) {
276
+ if (/module\.exports\s*=\s*app/.test(lines[i])) {
277
+ return { lineIndex: i, lineNumber: i + 1, content: lines[i] };
278
+ }
279
+ }
280
+ return null;
281
+ }
282
+
283
+ function findListenInsideCallback(filePath) {
284
+ const content = fs.readFileSync(filePath, "utf8");
285
+ const lines = content.split("\n");
286
+ for (let i = 0; i < lines.length; i++) {
287
+ if (/app\.listen\s*\(/.test(lines[i])) {
288
+ // Check if it's inside a callback (indented or preceded by .then)
289
+ const indentation = lines[i].match(/^(\s*)/)[1].length;
290
+ if (indentation > 0) {
291
+ return { lineIndex: i, lineNumber: i + 1, insideCallback: true };
292
+ }
293
+ }
294
+ }
295
+ return null;
296
+ }
297
+
298
+ function findCreateServer(filePath) {
299
+ const content = fs.readFileSync(filePath, "utf8");
300
+ const lines = content.split("\n");
301
+ for (let i = 0; i < lines.length; i++) {
302
+ if (/createServer\s*\(\s*app\s*\)/.test(lines[i])) {
303
+ return { lineIndex: i, lineNumber: i + 1, content: lines[i] };
304
+ }
305
+ }
306
+ return null;
307
+ }
308
+
272
309
  // ─── AUTH DETECTION ──────────────────────────────────────────────────────────
273
310
 
274
311
  const AUTH_LIBS = [
@@ -481,6 +518,16 @@ function findFileWithContent(dir, searchString, extensions, maxDepth) {
481
518
  return walk(dir, 0);
482
519
  }
483
520
 
521
+ function detectAppVarName(filePath) {
522
+ try {
523
+ const content = fs.readFileSync(filePath, "utf8");
524
+ const match = content.match(/(?:const|let|var)\s+(\w+)\s*=\s*express\s*\(/);
525
+ return match ? match[1] : "app";
526
+ } catch (e) {
527
+ return "app";
528
+ }
529
+ }
530
+
484
531
  // ─── MAIN DETECT FUNCTION ────────────────────────────────────────────────────
485
532
 
486
533
  function detect(cwd) {
@@ -524,6 +571,29 @@ function detect(cwd) {
524
571
  result.entryPoint = detectExpressEntry(cwd, pkg);
525
572
  if (result.entryPoint) {
526
573
  result.listenCall = findListenCall(result.entryPoint);
574
+ result.moduleExportsApp = findModuleExportsApp(result.entryPoint);
575
+ result.listenInsideCallback = findListenInsideCallback(result.entryPoint);
576
+ result.createServer = findCreateServer(result.entryPoint);
577
+ result.appVarName = detectAppVarName(result.entryPoint);
578
+
579
+ // Also check for app file separately (pattern 2)
580
+ const appFileCandidates = [
581
+ "src/app.js",
582
+ "src/app.ts",
583
+ "app.js",
584
+ "app.ts",
585
+ ];
586
+ for (const candidate of appFileCandidates) {
587
+ const fullPath = path.join(cwd, candidate);
588
+ if (fs.existsSync(fullPath) && fullPath !== result.entryPoint) {
589
+ const exportCall = findModuleExportsApp(fullPath);
590
+ if (exportCall) {
591
+ result.appFile = fullPath;
592
+ result.appFileExport = exportCall;
593
+ break;
594
+ }
595
+ }
596
+ }
527
597
  }
528
598
  }
529
599
 
@@ -555,4 +625,8 @@ module.exports = {
555
625
  detectExistingBotVersion,
556
626
  findFileWithContent,
557
627
  findListenCall,
628
+ findModuleExportsApp,
629
+ findListenInsideCallback,
630
+ findCreateServer,
631
+ detectAppVarName,
558
632
  };
package/cli/generator.js CHANGED
@@ -1,15 +1,18 @@
1
1
  // botversion-sdk/cli/generator.js
2
-
3
2
  "use strict";
4
3
 
5
4
  const path = require("path");
5
+ const fs = require("fs");
6
6
 
7
- // ─── EXPRESS CODE GENERATION ─────────────────────────────────────────────────
7
+ // ─── EXPRESS CODE GENERATION ──────────────────────────────────────────────────
8
8
 
9
9
  function generateExpressInit(info, apiKey) {
10
10
  const { moduleSystem, isTypeScript, auth } = info;
11
11
  const isESM = moduleSystem === "esm";
12
12
 
13
+ // FIX #2: Detect the actual app variable name used in the entry file
14
+ const appVarName = info.appVarName || "app";
15
+
13
16
  const importLine = isESM
14
17
  ? `import BotVersion from 'botversion-sdk';`
15
18
  : `const BotVersion = require('botversion-sdk');`;
@@ -20,11 +23,11 @@ function generateExpressInit(info, apiKey) {
20
23
  // BotVersion AI Agent — auto-added by botversion-sdk init
21
24
  ${importLine}
22
25
 
23
- BotVersion.init(app, {
24
- apiKey: '${apiKey}',${getUserContext.option}
26
+ BotVersion.init(${appVarName}, {
27
+ apiKey: process.env.BOTVERSION_API_KEY,
25
28
  });
26
29
 
27
- app.post('/api/botversion/chat', (req, res) => {
30
+ ${appVarName}.post('/api/botversion/chat', (req, res) => {
28
31
  BotVersion.chat(req, res);
29
32
  });
30
33
  `;
@@ -96,30 +99,54 @@ function generateExpressUserContext(auth) {
96
99
  }
97
100
  }
98
101
 
99
- // ─── NEXT.JS LIB FILE GENERATION ─────────────────────────────────────────────
102
+ // ─── NEXT.JS INSTRUMENTATION FILE ────────────────────────────────────────────
103
+
100
104
  function generateInstrumentationFile(info, apiKey) {
101
- const pagesDir = info.next?.srcDir
102
- ? `path.join(process.cwd(), 'src', 'pages')`
103
- : `path.join(process.cwd(), 'pages')`;
105
+ const { next, moduleSystem } = info;
106
+
107
+ // FIX #3: Only include pagesDir if Pages Router actually exists
108
+ // FIX #8: Use dynamic import instead of require() to support ESM projects
109
+ const hasPagesRouter = next?.pagesRouter;
110
+ const hasAppRouter = next?.appRouter;
111
+
112
+ const pagesDirLine = hasPagesRouter
113
+ ? next?.srcDir
114
+ ? `path.join(process.cwd(), 'src', 'pages')`
115
+ : `path.join(process.cwd(), 'pages')`
116
+ : null;
117
+
118
+ const appDirLine = hasAppRouter
119
+ ? next?.srcDir
120
+ ? `path.join(process.cwd(), 'src', 'app')`
121
+ : `path.join(process.cwd(), 'app')`
122
+ : null;
123
+
124
+ // Build pagesDir option only if Pages Router exists
125
+ const pagesDirOption = pagesDirLine
126
+ ? `\n pagesDir: ${pagesDirLine},`
127
+ : "";
128
+
129
+ // Build appDir option only if App Router exists (for future scanner support)
130
+ const appDirOption = appDirLine ? `\n appDir: ${appDirLine},` : "";
104
131
 
105
132
  return `export async function register() {
106
133
  if (process.env.NEXT_RUNTIME === 'nodejs') {
107
- const BotVersion = require('botversion-sdk');
108
- const path = require('path');
134
+ // FIX: Use dynamic import to support both CJS and ESM projects
135
+ const { default: BotVersion } = await import('botversion-sdk');
136
+ const { default: path } = await import('path');
137
+
109
138
  BotVersion.init({
110
- apiKey: process.env.BOTVERSION_API_KEY,
111
- pagesDir: ${pagesDir},
139
+ apiKey: process.env.BOTVERSION_API_KEY,${pagesDirOption}${appDirOption}
112
140
  });
113
141
  }
114
142
  }
115
143
  `;
116
144
  }
117
145
 
118
- // ─── NEXT.JS CHAT ROUTE — PAGES ROUTER ──────────────────────────────────────
146
+ // ─── NEXT.JS CHAT ROUTE — PAGES ROUTER ───────────────────────────────────────
119
147
 
120
148
  function generateNextPagesChatRoute(info) {
121
- const { auth, isTypeScript, nextAuthConfig, moduleSystem } = info;
122
- const isESM = moduleSystem === "esm";
149
+ const { auth } = info;
123
150
 
124
151
  switch (auth.name) {
125
152
  case "next-auth":
@@ -137,18 +164,10 @@ function generateNextAuthPagesRoute(info) {
137
164
  const { nextAuthConfig, auth, next, generateTs } = info;
138
165
  const isV5 = auth.version === "v5";
139
166
 
140
- // The chat file lives at: {base}/pages/api/botversion/chat.js
141
- // We need the import path relative to THAT file
142
- const chatFileDir = path.join(
143
- next.baseDir, // handles src/ automatically
144
- "pages",
145
- "api",
146
- "botversion",
147
- );
167
+ // Compute correct relative import path for authOptions
168
+ const chatFileDir = path.join(next.baseDir, "pages", "api", "botversion");
148
169
 
149
- // Determine the import path for authOptions
150
170
  let authImportPath = "../auth/[...nextauth]";
151
-
152
171
  if (nextAuthConfig) {
153
172
  const rel = path
154
173
  .relative(chatFileDir, nextAuthConfig.path)
@@ -168,8 +187,7 @@ export default BotVersion.nextHandler({
168
187
  `;
169
188
  }
170
189
 
171
- // generateTs: user has allowJs:false — generate proper TypeScript
172
- if (info.generateTs) {
190
+ if (generateTs) {
173
191
  return `import BotVersion from 'botversion-sdk';
174
192
  import { getServerSession } from 'next-auth';
175
193
  import { authOptions } from '${authImportPath}';
@@ -184,7 +202,6 @@ export default BotVersion.nextHandler({
184
202
  `;
185
203
  }
186
204
 
187
- // Plain JS — works for all standard Next.js projects
188
205
  return `import BotVersion from 'botversion-sdk';
189
206
  import { getServerSession } from 'next-auth';
190
207
  import { authOptions } from '${authImportPath}';
@@ -233,9 +250,7 @@ function generateAuthlessPagesRoute(info) {
233
250
 
234
251
  const comment =
235
252
  auth.name && !auth.supported
236
- ? `// TODO: We detected ${auth.name} but don't have automatic support yet.
237
- // Add your own getSession below to pass user context to the agent.
238
- // See: https://docs.botversion.com/auth\n`
253
+ ? `// TODO: We detected ${auth.name} but don't have automatic support yet.\n// Add your own getSession below to pass user context to the agent.\n// See: https://docs.botversion.com/auth\n`
239
254
  : "";
240
255
 
241
256
  if (isTypeScript) {
@@ -269,7 +284,7 @@ export default BotVersion.nextHandler({
269
284
  // ─── NEXT.JS CHAT ROUTE — APP ROUTER ─────────────────────────────────────────
270
285
 
271
286
  function generateNextAppChatRoute(info) {
272
- const { auth, isTypeScript } = info;
287
+ const { auth } = info;
273
288
 
274
289
  switch (auth.name) {
275
290
  case "next-auth":
@@ -284,118 +299,282 @@ function generateNextAppChatRoute(info) {
284
299
  }
285
300
 
286
301
  function generateNextAuthAppRoute(info) {
287
- const { auth, isTypeScript } = info;
302
+ const { auth, isTypeScript, next, nextAuthConfig } = info;
288
303
  const isV5 = auth.version === "v5";
289
304
 
305
+ // FIX #5: Compute the correct relative import path instead of hardcoding @/lib/auth
306
+ const chatFileDir = path.join(
307
+ next.baseDir,
308
+ "app",
309
+ "api",
310
+ "botversion",
311
+ "chat",
312
+ );
313
+
314
+ let authImportPath = "@/lib/auth"; // fallback alias
315
+ if (nextAuthConfig) {
316
+ const rel = path
317
+ .relative(chatFileDir, nextAuthConfig.path)
318
+ .replace(/\\/g, "/")
319
+ .replace(/\.(js|ts)$/, "");
320
+ authImportPath = rel.startsWith(".") ? rel : "./" + rel;
321
+ }
322
+
323
+ const typeAnnotation = isTypeScript ? ": NextRequest" : "";
324
+ const nextRequestImport = isTypeScript
325
+ ? `import { NextRequest, NextResponse } from 'next/server';\n`
326
+ : `import { NextResponse } from 'next/server';\n`;
327
+
290
328
  if (isV5) {
291
329
  return `import BotVersion from 'botversion-sdk';
292
- import { auth } from '@/auth';
293
- import { NextRequest } from 'next/server';
294
-
295
- export async function POST(req${isTypeScript ? ": NextRequest" : ""}) {
296
- const session = await auth();
297
- const body = await req.json();
330
+ import { auth } from '${authImportPath}';
331
+ ${nextRequestImport}
332
+ // FIX #1: appRouterHandler is implemented here directly since App Router
333
+ // does not support the nextHandler() Pages pattern
334
+ export async function POST(req${typeAnnotation}) {
335
+ try {
336
+ const session = await auth();
337
+ const body = await req.json();
338
+
339
+ const result = await BotVersion.nextHandler({
340
+ apiKey: process.env.BOTVERSION_API_KEY,
341
+ getSession: async () => session,
342
+ })({ ...req, body }, { json: (d) => d, status: () => ({ json: (d) => d }) });
298
343
 
299
- return BotVersion.appRouterHandler({
300
- body,
301
- userContext: {
302
- userId: session?.user?.id,
303
- email: session?.user?.email,
304
- name: session?.user?.name,
305
- },
306
- });
344
+ return NextResponse.json(result);
345
+ } catch (err) {
346
+ console.error('[BotVersion] App Router handler error:', err);
347
+ return NextResponse.json({ error: 'Agent error' }, { status: 500 });
348
+ }
307
349
  }
308
350
  `;
309
351
  }
310
352
 
311
353
  return `import BotVersion from 'botversion-sdk';
312
354
  import { getServerSession } from 'next-auth';
313
- import { authOptions } from '@/lib/auth';
314
- import { NextRequest } from 'next/server';
315
-
316
- export async function POST(req${isTypeScript ? ": NextRequest" : ""}) {
317
- const session = await getServerSession(authOptions);
318
- const body = await req.json();
355
+ import { authOptions } from '${authImportPath}';
356
+ ${nextRequestImport}
357
+ export async function POST(req${typeAnnotation}) {
358
+ try {
359
+ const session = await getServerSession(authOptions);
360
+ const body = await req.json();
319
361
 
320
- return BotVersion.appRouterHandler({
321
- body,
322
- userContext: {
362
+ const userContext = {
323
363
  userId: session?.user?.id,
324
364
  email: session?.user?.email,
325
365
  name: session?.user?.name,
326
- },
327
- });
366
+ };
367
+
368
+ // Forward to BotVersion platform directly
369
+ const response = await fetch(\`\${process.env.BOTVERSION_PLATFORM_URL || 'https://chatbusiness-two.vercel.app'}/api/chatbot/widget-chat\`, {
370
+ method: 'POST',
371
+ headers: { 'Content-Type': 'application/json' },
372
+ body: JSON.stringify({
373
+ chatbotId: body.chatbotId,
374
+ publicKey: body.publicKey,
375
+ query: body.message,
376
+ previousChats: body.conversationHistory || [],
377
+ pageContext: body.pageContext || {},
378
+ userContext,
379
+ }),
380
+ });
381
+
382
+ const data = await response.json();
383
+ return NextResponse.json(data);
384
+ } catch (err) {
385
+ console.error('[BotVersion] App Router handler error:', err);
386
+ return NextResponse.json({ error: 'Agent error' }, { status: 500 });
387
+ }
328
388
  }
329
389
  `;
330
390
  }
331
391
 
332
392
  function generateClerkAppRoute(info) {
333
- const { auth, isTypeScript } = info;
393
+ const { isTypeScript } = info;
394
+ const typeAnnotation = isTypeScript ? ": NextRequest" : "";
395
+ const nextRequestImport = isTypeScript
396
+ ? `import { NextRequest, NextResponse } from 'next/server';\n`
397
+ : `import { NextResponse } from 'next/server';\n`;
334
398
 
335
399
  return `import BotVersion from 'botversion-sdk';
336
400
  import { auth } from '@clerk/nextjs/server';
337
- import { NextRequest } from 'next/server';
338
-
339
- export async function POST(req${isTypeScript ? ": NextRequest" : ""}) {
340
- const { userId } = auth();
341
- const body = await req.json();
401
+ ${nextRequestImport}
402
+ export async function POST(req${typeAnnotation}) {
403
+ try {
404
+ // FIX #6: auth() is async in Clerk v5+ — must be awaited
405
+ const { userId } = await auth();
406
+ const body = await req.json();
407
+
408
+ const response = await fetch(\`\${process.env.BOTVERSION_PLATFORM_URL || 'https://chatbusiness-two.vercel.app'}/api/chatbot/widget-chat\`, {
409
+ method: 'POST',
410
+ headers: { 'Content-Type': 'application/json' },
411
+ body: JSON.stringify({
412
+ chatbotId: body.chatbotId,
413
+ publicKey: body.publicKey,
414
+ query: body.message,
415
+ previousChats: body.conversationHistory || [],
416
+ pageContext: body.pageContext || {},
417
+ userContext: { userId },
418
+ }),
419
+ });
342
420
 
343
- return BotVersion.appRouterHandler({
344
- body,
345
- userContext: { userId },
346
- });
421
+ const data = await response.json();
422
+ return NextResponse.json(data);
423
+ } catch (err) {
424
+ console.error('[BotVersion] App Router handler error:', err);
425
+ return NextResponse.json({ error: 'Agent error' }, { status: 500 });
426
+ }
347
427
  }
348
428
  `;
349
429
  }
350
430
 
351
431
  function generateSupabaseAppRoute(info) {
352
432
  const { isTypeScript } = info;
433
+ const typeAnnotation = isTypeScript ? ": NextRequest" : "";
434
+ const nextRequestImport = isTypeScript
435
+ ? `import { NextRequest, NextResponse } from 'next/server';\n`
436
+ : `import { NextResponse } from 'next/server';\n`;
353
437
 
354
- return `import BotVersion from 'botversion-sdk';
355
- import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
438
+ return `import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
356
439
  import { cookies } from 'next/headers';
357
- import { NextRequest } from 'next/server';
358
-
359
- export async function POST(req${isTypeScript ? ": NextRequest" : ""}) {
360
- const supabase = createRouteHandlerClient({ cookies });
361
- const { data: { session } } = await supabase.auth.getSession();
362
- const body = await req.json();
440
+ ${nextRequestImport}
441
+ export async function POST(req${typeAnnotation}) {
442
+ try {
443
+ const supabase = createRouteHandlerClient({ cookies });
444
+ const { data: { session } } = await supabase.auth.getSession();
445
+ const body = await req.json();
446
+
447
+ const response = await fetch(\`\${process.env.BOTVERSION_PLATFORM_URL || 'https://chatbusiness-two.vercel.app'}/api/chatbot/widget-chat\`, {
448
+ method: 'POST',
449
+ headers: { 'Content-Type': 'application/json' },
450
+ body: JSON.stringify({
451
+ chatbotId: body.chatbotId,
452
+ publicKey: body.publicKey,
453
+ query: body.message,
454
+ previousChats: body.conversationHistory || [],
455
+ pageContext: body.pageContext || {},
456
+ userContext: {
457
+ userId: session?.user?.id,
458
+ email: session?.user?.email,
459
+ },
460
+ }),
461
+ });
363
462
 
364
- return BotVersion.appRouterHandler({
365
- body,
366
- userContext: {
367
- userId: session?.user?.id,
368
- email: session?.user?.email,
369
- },
370
- });
463
+ const data = await response.json();
464
+ return NextResponse.json(data);
465
+ } catch (err) {
466
+ console.error('[BotVersion] App Router handler error:', err);
467
+ return NextResponse.json({ error: 'Agent error' }, { status: 500 });
468
+ }
371
469
  }
372
470
  `;
373
471
  }
374
472
 
375
473
  function generateAuthlessAppRoute(info) {
376
474
  const { auth, isTypeScript } = info;
475
+ const typeAnnotation = isTypeScript ? ": NextRequest" : "";
476
+ const nextRequestImport = isTypeScript
477
+ ? `import { NextRequest, NextResponse } from 'next/server';\n`
478
+ : `import { NextResponse } from 'next/server';\n`;
377
479
 
378
480
  const comment =
379
481
  auth.name && !auth.supported
380
- ? `// TODO: We detected ${auth.name} but don't have automatic support yet.
381
- // Add your own user context below.
382
- // See: https://docs.botversion.com/auth\n\n`
482
+ ? `// TODO: We detected ${auth.name} but don't have automatic support yet.\n// Add your own user context below.\n// See: https://docs.botversion.com/auth\n\n`
383
483
  : "";
384
484
 
385
- return `${comment}import BotVersion from 'botversion-sdk';
386
- import { NextRequest } from 'next/server';
387
-
388
- export async function POST(req${isTypeScript ? ": NextRequest" : ""}) {
389
- const body = await req.json();
485
+ return `${comment}${nextRequestImport}
486
+ export async function POST(req${typeAnnotation}) {
487
+ try {
488
+ const body = await req.json();
489
+
490
+ // No auth detected — agent works without user context
491
+ // Add userContext here if needed:
492
+ // const userContext = { userId: '...', email: '...' };
493
+
494
+ const response = await fetch(\`\${process.env.BOTVERSION_PLATFORM_URL || 'https://chatbusiness-two.vercel.app'}/api/chatbot/widget-chat\`, {
495
+ method: 'POST',
496
+ headers: { 'Content-Type': 'application/json' },
497
+ body: JSON.stringify({
498
+ chatbotId: body.chatbotId,
499
+ publicKey: body.publicKey,
500
+ query: body.message,
501
+ previousChats: body.conversationHistory || [],
502
+ pageContext: body.pageContext || {},
503
+ userContext: {},
504
+ }),
505
+ });
390
506
 
391
- // No auth detected — agent works without user context
392
- // Add userContext here if needed
393
- return BotVersion.appRouterHandler({ body });
507
+ const data = await response.json();
508
+ return NextResponse.json(data);
509
+ } catch (err) {
510
+ console.error('[BotVersion] App Router handler error:', err);
511
+ return NextResponse.json({ error: 'Agent error' }, { status: 500 });
512
+ }
394
513
  }
395
514
  `;
396
515
  }
397
516
 
398
- // ─── MANUAL INSTRUCTIONS FOR UNSUPPORTED CASES ───────────────────────────────
517
+ // ─── NEXT.JS CONFIG PATCH ─────────────────────────────────────────────────────
518
+
519
+ function generateNextConfigPatch(cwd) {
520
+ const candidates = ["next.config.js", "next.config.mjs", "next.config.ts"];
521
+
522
+ let configPath = null;
523
+ let configContent = null;
524
+
525
+ for (const candidate of candidates) {
526
+ const fullPath = path.join(cwd, candidate);
527
+ if (fs.existsSync(fullPath)) {
528
+ configPath = fullPath;
529
+ configContent = fs.readFileSync(fullPath, "utf8");
530
+ break;
531
+ }
532
+ }
533
+
534
+ if (!configPath) return null;
535
+
536
+ if (configContent.includes("instrumentationHook")) {
537
+ return { path: configPath, alreadyPatched: true };
538
+ }
539
+
540
+ let patched = configContent;
541
+
542
+ // Add to existing experimental block
543
+ if (configContent.includes("experimental")) {
544
+ patched = configContent.replace(
545
+ /experimental\s*:\s*\{/,
546
+ "experimental: {\n instrumentationHook: true,",
547
+ );
548
+
549
+ // FIX #7: Handle next.config.mjs style — export default { ... }
550
+ } else if (/export\s+default\s+\{/.test(configContent)) {
551
+ patched = configContent.replace(
552
+ /export\s+default\s+\{/,
553
+ "export default {\n experimental: {\n instrumentationHook: true,\n },",
554
+ );
555
+
556
+ // Handle const nextConfig = { ... } style (next.config.js)
557
+ } else if (/const\s+nextConfig\s*=\s*\{/.test(configContent)) {
558
+ patched = configContent.replace(
559
+ /const\s+nextConfig\s*=\s*\{/,
560
+ "const nextConfig = {\n experimental: {\n instrumentationHook: true,\n },",
561
+ );
562
+
563
+ // Handle module.exports = { ... } style
564
+ } else if (/module\.exports\s*=\s*\{/.test(configContent)) {
565
+ patched = configContent.replace(
566
+ /module\.exports\s*=\s*\{/,
567
+ "module.exports = {\n experimental: {\n instrumentationHook: true,\n },",
568
+ );
569
+ } else {
570
+ // Cannot safely patch — return null so caller prompts manual step
571
+ return null;
572
+ }
573
+
574
+ return { path: configPath, content: patched, alreadyPatched: false };
575
+ }
576
+
577
+ // ─── MANUAL INSTRUCTIONS FOR UNSUPPORTED FRAMEWORKS ──────────────────────────
399
578
 
400
579
  function generateManualInstructions(framework, apiKey) {
401
580
  const instructions = {
@@ -431,49 +610,6 @@ Visit https://docs.botversion.com for manual setup instructions.
431
610
  );
432
611
  }
433
612
 
434
- function generateNextConfigPatch(cwd) {
435
- const fs = require("fs");
436
- const path = require("path");
437
-
438
- const candidates = ["next.config.js", "next.config.mjs", "next.config.ts"];
439
-
440
- let configPath = null;
441
- let configContent = null;
442
-
443
- for (const candidate of candidates) {
444
- const fullPath = path.join(cwd, candidate);
445
- if (fs.existsSync(fullPath)) {
446
- configPath = fullPath;
447
- configContent = fs.readFileSync(fullPath, "utf8");
448
- break;
449
- }
450
- }
451
-
452
- if (!configPath) return null;
453
-
454
- // Already has instrumentationHook
455
- if (configContent.includes("instrumentationHook")) {
456
- return { path: configPath, alreadyPatched: true };
457
- }
458
-
459
- // Add instrumentationHook: true to experimental block if exists
460
- if (configContent.includes("experimental")) {
461
- const patched = configContent.replace(
462
- /experimental\s*:\s*\{/,
463
- "experimental: {\n instrumentationHook: true,",
464
- );
465
- return { path: configPath, content: patched, alreadyPatched: false };
466
- }
467
-
468
- // Add experimental block before the closing of config object
469
- const patched = configContent.replace(
470
- /const nextConfig\s*=\s*\{/,
471
- "const nextConfig = {\n experimental: {\n instrumentationHook: true,\n },",
472
- );
473
-
474
- return { path: configPath, content: patched, alreadyPatched: false };
475
- }
476
-
477
613
  module.exports = {
478
614
  generateExpressInit,
479
615
  generateInstrumentationFile,
package/cli/writer.js CHANGED
@@ -17,7 +17,7 @@ function writeFile(filePath, content) {
17
17
 
18
18
  function backupFile(filePath) {
19
19
  if (!fs.existsSync(filePath)) return null;
20
- const backupPath = filePath + ".botversion.bak";
20
+ const backupPath = filePath + ".backup-before-botversion";
21
21
  fs.copyFileSync(filePath, backupPath);
22
22
  return backupPath;
23
23
  }
@@ -58,10 +58,10 @@ function injectBeforeListen(filePath, codeToInject) {
58
58
  const injectedLines = ["", ...codeToInject.split("\n"), ""];
59
59
 
60
60
  const newContent = [...before, ...injectedLines, ...after].join("\n");
61
- backupFile(filePath);
61
+ const backup = backupFile(filePath);
62
62
  fs.writeFileSync(filePath, newContent, "utf8");
63
63
 
64
- return { success: true, lineNumber: listenLineIndex + 1 };
64
+ return { success: true, lineNumber: listenLineIndex + 1, backup };
65
65
  }
66
66
 
67
67
  // ─── APPEND CODE TO END OF FILE ──────────────────────────────────────────────
@@ -94,6 +94,84 @@ function createFile(filePath, content, force) {
94
94
  return { success: true, path: filePath };
95
95
  }
96
96
 
97
+ function injectBeforeExport(filePath, codeToInject) {
98
+ const content = fs.readFileSync(filePath, "utf8");
99
+ const lines = content.split("\n");
100
+
101
+ if (content.includes("botversion-sdk") || content.includes("BotVersion")) {
102
+ return { success: false, reason: "already_exists" };
103
+ }
104
+
105
+ let insertIndex = -1;
106
+
107
+ // Find module.exports = app
108
+ let exportsLine = -1;
109
+ for (let i = 0; i < lines.length; i++) {
110
+ if (/module\.exports\s*=\s*app/.test(lines[i])) {
111
+ exportsLine = i;
112
+ break;
113
+ }
114
+ }
115
+
116
+ if (exportsLine !== -1) {
117
+ // Walk backwards from module.exports to skip error handler lines
118
+ // Skip lines that are: blank, closing braces, or known error middleware
119
+ let i = exportsLine - 1;
120
+ while (i >= 0) {
121
+ const line = lines[i].trim();
122
+ if (
123
+ line === "" ||
124
+ line === "})" ||
125
+ line === "});" ||
126
+ line === "}," ||
127
+ line === "}" ||
128
+ line === ");" ||
129
+ line === "next();" ||
130
+ /app\.use\s*\(\s*errorHandler/.test(line) ||
131
+ /app\.use\s*\(\s*errorConverter/.test(line) ||
132
+ /app\.use\s*\(\s*\(req,\s*res,\s*next\)/.test(line) ||
133
+ /next\(new/.test(line) ||
134
+ /NOT_FOUND/.test(line) ||
135
+ /Not found/i.test(line) ||
136
+ /\/\/ (handle|convert|send back) error/.test(lines[i]) ||
137
+ /\/\/ send back a 404/.test(lines[i])
138
+ ) {
139
+ i--;
140
+ } else {
141
+ break;
142
+ }
143
+ }
144
+ insertIndex = i + 1;
145
+ }
146
+
147
+ // Fallback: before app.listen()
148
+ if (insertIndex === -1) {
149
+ for (let i = 0; i < lines.length; i++) {
150
+ if (/app\.listen\s*\(/.test(lines[i])) {
151
+ insertIndex = i;
152
+ break;
153
+ }
154
+ }
155
+ }
156
+
157
+ // Final fallback: append to end of file
158
+ if (insertIndex === -1) {
159
+ const backup = backupFile(filePath);
160
+ const newContent = content.trimEnd() + "\n\n" + codeToInject + "\n";
161
+ fs.writeFileSync(filePath, newContent, "utf8");
162
+ return { success: true, backup };
163
+ }
164
+
165
+ const before = lines.slice(0, insertIndex);
166
+ const after = lines.slice(insertIndex);
167
+ const injectedLines = ["", ...codeToInject.split("\n"), ""];
168
+ const newContent = [...before, ...injectedLines, ...after].join("\n");
169
+
170
+ const backup = backupFile(filePath);
171
+ fs.writeFileSync(filePath, newContent, "utf8");
172
+ return { success: true, backup };
173
+ }
174
+
97
175
  // ─── MERGE INTO EXISTING MIDDLEWARE (Next.js) ────────────────────────────────
98
176
 
99
177
  function mergeIntoMiddleware(middlewarePath, authName) {
@@ -168,4 +246,5 @@ module.exports = {
168
246
  createFile,
169
247
  mergeIntoMiddleware,
170
248
  writeSummary,
249
+ injectBeforeExport,
171
250
  };
package/client.js CHANGED
@@ -20,6 +20,10 @@ function BotVersionClient(options) {
20
20
  this._queue = [];
21
21
  this._flushTimer = null;
22
22
  this._flushDelay = options.flushDelay || 3000; // batch every 3 seconds
23
+ var self = this;
24
+ process.on("beforeExit", function () {
25
+ if (self._queue.length > 0) self._flush();
26
+ });
23
27
  }
24
28
 
25
29
  /**
package/index.js CHANGED
@@ -61,7 +61,8 @@ var BotVersion = {
61
61
 
62
62
  this._client = new BotVersionClient({
63
63
  apiKey: options.apiKey,
64
- platformUrl: options.platformUrl || "http://localhost:3000",
64
+ platformUrl:
65
+ options.platformUrl || "https://chatbusiness-two.vercel.app/",
65
66
  debug: options.debug || false,
66
67
  timeout: options.timeout || 30000,
67
68
  });
@@ -205,9 +206,9 @@ var BotVersion = {
205
206
  .registerEndpoints(endpoints)
206
207
  .then(function () {
207
208
  console.log(
208
- "[BotVersion SDK] ✅ Static scan complete —",
209
+ "[BotVersion SDK] ✅ Endpoints queued —",
209
210
  endpoints.length,
210
- "endpoints registered",
211
+ "endpoints will be sent shortly",
211
212
  );
212
213
  })
213
214
  .catch(function (err) {
@@ -571,6 +572,14 @@ BotVersion.nextHandler = function (options) {
571
572
 
572
573
  BotVersion.nextHandler = BotVersion.nextHandler.bind(BotVersion);
573
574
 
575
+ BotVersion.appRouterHandler = function () {
576
+ throw new Error(
577
+ "[BotVersion SDK] appRouterHandler is not supported. " +
578
+ "Please run: npx botversion-sdk init --key YOUR_KEY --force " +
579
+ "to regenerate the correct route file.",
580
+ );
581
+ };
582
+
574
583
  module.exports = BotVersion;
575
584
  module.exports.default = BotVersion;
576
585
  module.exports.init = BotVersion.init;
@@ -578,6 +587,7 @@ module.exports.getEndpoints = BotVersion.getEndpoints;
578
587
  module.exports.registerEndpoint = BotVersion.registerEndpoint;
579
588
  module.exports.chat = BotVersion.chat;
580
589
  module.exports.nextHandler = BotVersion.nextHandler;
590
+ module.exports.appRouterHandler = BotVersion.appRouterHandler;
581
591
 
582
592
  // ── Framework detection ──────────────────────────────────────────────────────
583
593
  function detectFramework(app) {
package/interceptor.js CHANGED
@@ -267,8 +267,8 @@ function makeLocalCall(req, call) {
267
267
  var lib = isHttps ? https : http;
268
268
 
269
269
  var options = {
270
- hostname: req.hostname,
271
- port: (req.socket && req.socket.localPort) || (isHttps ? 443 : 80),
270
+ hostname: "127.0.0.1",
271
+ port: process.env.PORT || 3000,
272
272
  path: call.path,
273
273
  method: call.method,
274
274
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botversion-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "BotVersion SDK — auto-detect and register your API endpoints",
5
5
  "main": "index.js",
6
6
  "bin": {
package/scanner.js CHANGED
@@ -8,6 +8,8 @@ function scanExpressRoutes(app) {
8
8
  const endpoints = [];
9
9
  const seen = new Set();
10
10
 
11
+ // Force Express to initialize its router if it hasn't yet
12
+ if (app.lazyrouter) app.lazyrouter();
11
13
  const router = app._router || app.router || (app.stack ? app : null);
12
14
 
13
15
  if (!router) {
@@ -325,35 +327,32 @@ function extractQueryFieldsFromFile(content) {
325
327
  function regexpToPath(regexp, keys) {
326
328
  if (!regexp) return "";
327
329
 
328
- // Express stores the fast path string on the regexp object directly
329
- if (regexp.source === "^\\/?(?=\\/|$)") return ""; // root mount, no prefix
330
+ // Express 4.x stores the original path string directly
331
+ if (regexp.source === "^\\/?(?=\\/|$)") return "";
330
332
 
331
- // Try to extract a clean path from the regexp source
332
333
  try {
333
334
  var src = regexp.source;
334
335
 
335
- // A plain string mount like app.use('/api/v1', ...) produces
336
- // source: "^\\/api\\/v1\\/?(?=\\/|$)" — extract the prefix part
337
- var match = src.match(/^\^\\\/(.+?)\\\/\?\(\?=\\\/\|\$\)$/);
338
- if (match) {
339
- return "/" + match[1].replace(/\\\//g, "/");
340
- }
336
+ // Remove anchors and cleanup
337
+ src = src
338
+ .replace(/^\^/, "")
339
+ .replace(/\\\//g, "/")
340
+ .replace(/\/\?\(\?=\/\|\$\)$/, "")
341
+ .replace(/\/\?\$?$/, "")
342
+ .replace(/\(\?:\(\[\^\/\]\+\?\)\)/g, function (_, i) {
343
+ return keys && keys[i] ? ":" + keys[i].name : ":param";
344
+ });
341
345
 
342
- // Simpler pattern: "^\\/api\\/?(?=\\/|$)"
343
- var match2 = src.match(/^\^(\\.+?)\\\/\?\(\?=\\\/\|\$\)$/);
344
- if (match2) {
345
- return match2[1].replace(/\\\//g, "/");
346
- }
347
- } catch (e) {
348
- // ignore
349
- }
346
+ // Clean up any remaining regex artifacts
347
+ src = src.replace(/\(\?:/g, "").replace(/\)/g, "");
350
348
 
351
- // If keys exist, fall back to building from keys (parameterised mount — rare)
352
- if (keys && keys.length > 0) {
353
- return "/:param";
354
- }
349
+ if (!src || src === "/") return "";
350
+ if (!src.startsWith("/")) src = "/" + src;
355
351
 
356
- return "";
352
+ return src;
353
+ } catch (e) {
354
+ return "";
355
+ }
357
356
  }
358
357
 
359
358
  module.exports = {