botversion-sdk 1.0.0 → 1.0.2

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/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,293 @@ 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 || 'http://localhost:3000'}/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 || 'http://localhost:3000'}/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 || 'http://localhost:3000'}/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 || 'http://localhost:3000'}/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, nextVersion) {
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
+ // Skip instrumentationHook for Next.js 14.1+ (enabled by default)
537
+ if (nextVersion && nextVersion.major >= 14) {
538
+ // Check minor version too
539
+ const rawVersion = nextVersion.raw || "";
540
+ const match = rawVersion.match(/(\d+)\.(\d+)/);
541
+ const minor = match ? parseInt(match[2], 10) : 0;
542
+ if (nextVersion.major > 14 || (nextVersion.major === 14 && minor >= 1)) {
543
+ return { path: configPath, alreadyPatched: true };
544
+ }
545
+ }
546
+
547
+ if (configContent.includes("instrumentationHook")) {
548
+ return { path: configPath, alreadyPatched: true };
549
+ }
550
+
551
+ let patched = configContent;
552
+
553
+ // Add to existing experimental block
554
+ if (configContent.includes("experimental")) {
555
+ patched = configContent.replace(
556
+ /experimental\s*:\s*\{/,
557
+ "experimental: {\n instrumentationHook: true,",
558
+ );
559
+
560
+ // FIX #7: Handle next.config.mjs style — export default { ... }
561
+ } else if (/export\s+default\s+\{/.test(configContent)) {
562
+ patched = configContent.replace(
563
+ /export\s+default\s+\{/,
564
+ "export default {\n experimental: {\n instrumentationHook: true,\n },",
565
+ );
566
+
567
+ // Handle const nextConfig = { ... } style (next.config.js)
568
+ } else if (/const\s+nextConfig\s*=\s*\{/.test(configContent)) {
569
+ patched = configContent.replace(
570
+ /const\s+nextConfig\s*=\s*\{/,
571
+ "const nextConfig = {\n experimental: {\n instrumentationHook: true,\n },",
572
+ );
573
+
574
+ // Handle module.exports = { ... } style
575
+ } else if (/module\.exports\s*=\s*\{/.test(configContent)) {
576
+ patched = configContent.replace(
577
+ /module\.exports\s*=\s*\{/,
578
+ "module.exports = {\n experimental: {\n instrumentationHook: true,\n },",
579
+ );
580
+ } else {
581
+ // Cannot safely patch — return null so caller prompts manual step
582
+ return null;
583
+ }
584
+
585
+ return { path: configPath, content: patched, alreadyPatched: false };
586
+ }
587
+
588
+ // ─── MANUAL INSTRUCTIONS FOR UNSUPPORTED FRAMEWORKS ──────────────────────────
399
589
 
400
590
  function generateManualInstructions(framework, apiKey) {
401
591
  const instructions = {
@@ -431,47 +621,17 @@ Visit https://docs.botversion.com for manual setup instructions.
431
621
  );
432
622
  }
433
623
 
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 };
624
+ // ─── SCRIPT TAG GENERATION ────────────────────────────────────────────────────
625
+
626
+ function generateScriptTag(projectInfo) {
627
+ return `<script
628
+ id="botversion-loader"
629
+ src="${projectInfo.cdnUrl}"
630
+ data-api-url="${projectInfo.apiUrl}"
631
+ data-project-id="${projectInfo.projectId}"
632
+ data-public-key="${projectInfo.publicKey}"
633
+ data-proxy-url="/api/botversion/chat"
634
+ ></script>`;
475
635
  }
476
636
 
477
637
  module.exports = {
@@ -481,4 +641,5 @@ module.exports = {
481
641
  generateNextAppChatRoute,
482
642
  generateManualInstructions,
483
643
  generateNextConfigPatch,
644
+ generateScriptTag,
484
645
  };