@botonic/nx-plugin 2.29.0 → 2.31.0

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/executors.json +0 -5
  3. package/package.json +3 -2
  4. package/src/executors/delete-bot/executor.js +0 -2
  5. package/src/executors/deploy-to-hubtype/executor.js +24 -160
  6. package/src/executors/e2e-webchat/botonic-package-publish.spec.ts +7 -11
  7. package/src/executors/integrate-provider/executor.js +0 -2
  8. package/src/executors/login-to-hubtype/executor.js +0 -2
  9. package/src/executors/logout-from-hubtype/executor.js +2 -2
  10. package/src/executors/serve-bot/executor.js +142 -24
  11. package/src/executors/serve-bot/schema.json +13 -5
  12. package/src/generators/bot-app/files/src/client/webchat/index.tsx.template +2 -0
  13. package/src/generators/bot-app/files/src/client/webchat/webchat.tsx.template +111 -0
  14. package/src/generators/bot-app/files/src/server/bot/plugins/flow-builder/index.ts.template +13 -4
  15. package/src/generators/bot-app/files/src/server/bot/plugins/index.ts.template +0 -3
  16. package/src/generators/bot-app/files/src/server/lambda/handler.js.template +1 -1
  17. package/src/generators/bot-app/files/src/server/lambda/package.json +3 -3
  18. package/src/generators/bot-app/files/vite/node.config.ts.template +2 -4
  19. package/src/generators/bot-app/files/vite/webchat.config.ts.template +20 -1
  20. package/src/generators/bot-app/generator.js +6 -5
  21. package/src/generators/bot-app/lilara-version.json +1 -1
  22. package/src/lib/api-service.d.ts +19 -20
  23. package/src/lib/api-service.js +150 -82
  24. package/src/lib/bot-config.d.ts +10 -7
  25. package/src/lib/bot-config.js +5 -1
  26. package/src/lib/constants.d.ts +2 -3
  27. package/src/lib/constants.js +6 -9
  28. package/src/lib/credentials-handler.d.ts +9 -18
  29. package/src/lib/credentials-handler.js +42 -24
  30. package/src/lib/interfaces.d.ts +10 -13
  31. package/src/lib/util/executor-helpers.d.ts +58 -18
  32. package/src/lib/util/executor-helpers.js +501 -102
  33. package/src/plugin.js +6 -15
  34. package/src/executors/deploy-local-runtime/executor.d.ts +0 -5
  35. package/src/executors/deploy-local-runtime/executor.js +0 -144
  36. package/src/executors/deploy-local-runtime/schema.d.js +0 -16
  37. package/src/executors/deploy-local-runtime/schema.json +0 -34
  38. package/src/generators/bot-app/files/src/server/bot/tracking.ts.template +0 -35
  39. package/src/generators/preset/files/package.json +0 -26
@@ -34,15 +34,23 @@
34
34
  "description": "Local port the Lambda listens on (used for cloudflared --url). Default 3001.",
35
35
  "default": 3001
36
36
  },
37
- "logViewer": {
38
- "type": "boolean",
39
- "description": "When true, start a web-based log viewer (frontail) and open webchat with an embedded logs panel.",
40
- "default": false
41
- },
42
37
  "logViewerPort": {
43
38
  "type": "number",
44
39
  "description": "Port for the log viewer web interface",
45
40
  "default": 9001
41
+ },
42
+ "provider": {
43
+ "type": "string",
44
+ "description": "Channel provider to enable local routing for. Currently only 'whatsapp' is supported.",
45
+ "enum": ["whatsapp"]
46
+ },
47
+ "phone": {
48
+ "type": "string",
49
+ "description": "Your phone number in E.164 format (e.g. +34612345678). Required when provider=whatsapp. Messages from this number will route to your local Lambda."
50
+ },
51
+ "botName": {
52
+ "type": "string",
53
+ "description": "Name of the Hubtype bot to use for local development. Skips the interactive bot-selection prompt."
46
54
  }
47
55
  },
48
56
  "required": []
@@ -66,8 +66,10 @@ function App({
66
66
  <WebchatTrigger
67
67
  appId={webchatConfig.appId}
68
68
  position='bottom-right'
69
+ loader={() => import('./webchat')}
69
70
  onReady={handleReady}
70
71
  webchatConfig={{
72
+ ...webchatConfig,
71
73
  avatarConfig,
72
74
  customMessages,
73
75
  title: options.title || '<%= className %>',
@@ -0,0 +1,111 @@
1
+ import type { WebchatController } from '@botonic/webchat-core'
2
+ import type {
3
+ WebchatCompositionExtras,
4
+ WebchatCompositionProps,
5
+ } from '@botonic/webchat-react'
6
+ import {
7
+ useWebchatContext,
8
+ WebchatContainer,
9
+ WebchatHeader,
10
+ WebchatInput,
11
+ WebchatMessages,
12
+ WebchatProvider,
13
+ } from '@botonic/webchat-react'
14
+ import { useEffect, useRef } from 'react'
15
+
16
+ function WebchatContent({
17
+ isOpen,
18
+ onClose,
19
+ onControllerReady,
20
+ title,
21
+ placeholder,
22
+ showBranding,
23
+ }: {
24
+ isOpen: boolean
25
+ onClose: () => void
26
+ onControllerReady: (controller: WebchatController) => void
27
+ title?: string
28
+ placeholder?: string
29
+ showBranding?: boolean
30
+ }) {
31
+ const { controller } = useWebchatContext()
32
+ const hasNotifiedRef = useRef(false)
33
+
34
+ useEffect(() => {
35
+ if (controller && !hasNotifiedRef.current) {
36
+ hasNotifiedRef.current = true
37
+ onControllerReady(controller)
38
+ }
39
+ }, [controller, onControllerReady])
40
+
41
+ useEffect(() => {
42
+ if (!controller) return
43
+ const snapshot = controller.machine.getSnapshot()
44
+ const currentIsOpen = snapshot.context.isOpen ?? false
45
+ if (isOpen && !currentIsOpen) {
46
+ controller.open()
47
+ } else if (!isOpen && currentIsOpen) {
48
+ controller.close()
49
+ }
50
+ }, [isOpen, controller])
51
+
52
+ const handleClose = () => {
53
+ controller?.close()
54
+ onClose()
55
+ }
56
+
57
+ return (
58
+ <WebchatContainer layout='default'>
59
+ <WebchatHeader title={title} onClose={handleClose} />
60
+ <WebchatMessages variant='default' />
61
+ <WebchatInput
62
+ placeholder={placeholder}
63
+ showBranding={showBranding ?? true}
64
+ variant='default'
65
+ />
66
+ </WebchatContainer>
67
+ )
68
+ }
69
+
70
+ export default function Webchat({
71
+ appId,
72
+ isOpen,
73
+ onClose,
74
+ onControllerReady,
75
+ ...extras
76
+ }: WebchatCompositionProps) {
77
+ // `WebchatCompositionProps` exposes extras via a `[key: string]: unknown`
78
+ // index signature, so assert the concrete shape once instead of per field.
79
+ const {
80
+ title,
81
+ placeholder,
82
+ showBranding,
83
+ customMessages = {},
84
+ avatarConfig,
85
+ webchatClientSettings,
86
+ enableResponsiveHeroHeader,
87
+ storage,
88
+ previewUtils,
89
+ } = extras as WebchatCompositionExtras
90
+
91
+ return (
92
+ <WebchatProvider
93
+ appId={appId}
94
+ customMessages={customMessages}
95
+ avatarConfig={avatarConfig}
96
+ webchatClientSettings={webchatClientSettings}
97
+ enableResponsiveHeroHeader={enableResponsiveHeroHeader}
98
+ storage={storage}
99
+ previewUtils={previewUtils}
100
+ >
101
+ <WebchatContent
102
+ isOpen={isOpen}
103
+ onClose={onClose}
104
+ onControllerReady={onControllerReady}
105
+ title={title}
106
+ placeholder={placeholder}
107
+ showBranding={showBranding}
108
+ />
109
+ </WebchatProvider>
110
+ )
111
+ }
@@ -1,12 +1,13 @@
1
+ import { BotonicContext } from '@botonic/core'
1
2
  import { AiAgentArgs } from '@botonic/plugin-ai-agents'
3
+ import { BotonicPluginHubtypeAnalytics } from '@botonic/plugin-hubtype-analytics'
2
4
  import {
3
5
  BotonicPluginFlowBuilder,
4
6
  BotonicPluginFlowBuilderOptions,
5
7
  FlowBuilderJSONVersion,
6
8
  } from '@botonic/plugin-flow-builder'
7
- import { BotonicContext } from '@botonic/core'
9
+ import { EventAction, HtEventProps } from '@botonic/shared'
8
10
 
9
- import { trackEventToHubtypeAnalytics } from '../../tracking'
10
11
  import { isLambdaLocal } from '../../utils'
11
12
  import { getAiAgentResponse } from '../ai-agents'
12
13
 
@@ -14,8 +15,16 @@ export const flowBuilderConfig: BotonicPluginFlowBuilderOptions = {
14
15
  flowVersion: isLambdaLocal()
15
16
  ? FlowBuilderJSONVersion.DRAFT
16
17
  : FlowBuilderJSONVersion.LATEST,
17
- trackEvent: async (botonicContext: BotonicContext, eventAction, args) => {
18
- await trackEventToHubtypeAnalytics(botonicContext, eventAction, args)
18
+ trackEvent: async (
19
+ context: BotonicContext,
20
+ eventAction: EventAction,
21
+ args: Omit<HtEventProps, 'action'>
22
+ ): Promise<void> => {
23
+ const analyticsPlugin = new BotonicPluginHubtypeAnalytics()
24
+ await analyticsPlugin.trackEvent(context, {
25
+ action: eventAction,
26
+ ...args,
27
+ } as HtEventProps)
19
28
  },
20
29
  getAiAgentResponse: async (
21
30
  botonicContext: BotonicContext,
@@ -1,5 +1,3 @@
1
- import { BotonicPluginHubtypeAnalytics } from '@botonic/plugin-hubtype-analytics'
2
-
3
1
  import { aiAgentsPlugin } from './ai-agents'
4
2
  import { flowBuilderPlugin } from './flow-builder'
5
3
 
@@ -7,5 +5,4 @@ export const plugins = {
7
5
  flowBuilder: flowBuilderPlugin,
8
6
  // AI Agents plugin is only loaded if AZURE_OPENAI_API_KEY or OPENAI_API_KEY is set
9
7
  ...(aiAgentsPlugin && { aiAgents: aiAgentsPlugin }),
10
- hubtypeAnalytics: new BotonicPluginHubtypeAnalytics(),
11
8
  }
@@ -3,7 +3,7 @@
3
3
  /*
4
4
  In lambda functions, it only have access to all files the first time it's invoked,
5
5
  and placed into memory, after that it only calls the function that have the information
6
- loadded in memory. For this reason, we initialize the bot the first time, and then we
6
+ loaded in memory. For this reason, we initialize the bot the first time, and then we
7
7
  only execute the input function
8
8
 
9
9
  https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html
@@ -12,9 +12,9 @@
12
12
  "description": "",
13
13
  "dependencies": {
14
14
  "uuid": "^11.1.0",
15
- "@openai/agents": "^0.1.11",
16
- "openai": "^5.23.2",
15
+ "@openai/agents": "^0.10.1",
16
+ "openai": "^6.0.0",
17
17
  "tslib": "^2.3.0",
18
- "zod": "^3.25.76"
18
+ "zod": "^4.4.3"
19
19
  }
20
20
  }
@@ -12,11 +12,9 @@ export function getNodeConfig(): UserConfig {
12
12
  root: projectRoot,
13
13
  cacheDir: resolve(projectRoot, BUILD_CONFIG.CACHE_DIR.node),
14
14
 
15
- // SSR configuration - bundle @botonic/* packages into the lambda
16
- // By default, Vite externalizes node_modules in SSR mode, but we need
17
- // @botonic packages bundled since they won't be available at runtime in Lambda
15
+ // Bundle all dependencies into the Lambda artifact no node_modules at runtime.
18
16
  ssr: {
19
- noExternal: [/@botonic\/.*/],
17
+ noExternal: true,
20
18
  },
21
19
 
22
20
  build: {
@@ -9,6 +9,25 @@ const projectRoot = resolve(__dirname, '..')
9
9
  const output = BUILD_CONFIG.OUTPUT.webchat
10
10
  const server = BUILD_CONFIG.SERVER.webchat
11
11
 
12
+ /**
13
+ * Returns the Vite `base` for the webchat bundle.
14
+ *
15
+ * Why this matters: the dashboard's flow-builder preview panel
16
+ * (`PreviewWebchatAppV2`) loads this bundle cross-origin from the bot's Netlify
17
+ * deploy while running on a different origin (localhost:4200 in dev, the
18
+ * dashboard domain in prod). With an absolute base ('/'), Vite resolves the
19
+ * bundle's dynamically-imported chunks as `/chunk.js` against the *host page's*
20
+ * origin — so the preview requests them from the dashboard origin and they 404.
21
+ *
22
+ * A relative base ('') makes those chunks resolve against the entry module's own
23
+ * origin (import.meta.url = Netlify) instead, so the preview loads the whole
24
+ * bundle from Netlify with no chunk 404s and no service-worker proxy. The dev
25
+ * server keeps '/' so its own HMR/asset paths keep working.
26
+ */
27
+ function getCrossOriginSafeBase(command: 'serve' | 'build'): string {
28
+ return command === 'build' ? '' : '/'
29
+ }
30
+
12
31
  export function getWebchatConfig(command: 'serve' | 'build'): UserConfig {
13
32
  const openPath =
14
33
  command === 'serve' && process.env.LOG_VIEWER_PORT
@@ -26,7 +45,7 @@ export function getWebchatConfig(command: 'serve' | 'build'): UserConfig {
26
45
  cacheDir: resolve(projectRoot, BUILD_CONFIG.CACHE_DIR.webchat),
27
46
  publicDir: resolve(projectRoot, BUILD_CONFIG.PUBLIC_DIR),
28
47
  appType: 'spa',
29
- base: '/',
48
+ base: getCrossOriginSafeBase(command),
30
49
 
31
50
  server: {
32
51
  port: server.port,
@@ -158,10 +158,11 @@ function createPackageJson(tree, options, botonicVersion, lilaraVersion) {
158
158
  "@lilara/ui-web": lilaraVersion,
159
159
  "@lilara/ui-web-react": lilaraVersion,
160
160
  react: "18.3.1",
161
- "react-dom": "18.3.1"
161
+ "react-dom": "18.3.1",
162
+ zod: "^4.4.3"
162
163
  },
163
164
  devDependencies: {
164
- "@nx/vite": "21.6.3",
165
+ "@nx/vite": "21.6.11",
165
166
  "@types/node": "22.13.0",
166
167
  "@types/react": "18.3.1",
167
168
  "@types/react-dom": "18.3.0",
@@ -169,7 +170,7 @@ function createPackageJson(tree, options, botonicVersion, lilaraVersion) {
169
170
  "@vitejs/plugin-react-swc": "^3.5.0",
170
171
  typescript: "5.7.3",
171
172
  vite: "7.1.9",
172
- vitest: "^4.0.15"
173
+ vitest: "4.1.1"
173
174
  }
174
175
  };
175
176
  (0, import_devkit.writeJson)(tree, `${options.projectRoot}/package.json`, packageJson);
@@ -257,7 +258,7 @@ function addBotonicDependencies(tree, botonicVersion) {
257
258
  },
258
259
  // devDependencies
259
260
  {
260
- "@nx/vite": "21.6.3",
261
+ "@nx/vite": "21.6.11",
261
262
  "@types/node": "22.13.0",
262
263
  "@types/react": "18.3.1",
263
264
  "@types/react-dom": "18.3.0",
@@ -265,7 +266,7 @@ function addBotonicDependencies(tree, botonicVersion) {
265
266
  "@vitejs/plugin-react-swc": "3.5.0",
266
267
  typescript: "5.7.3",
267
268
  vite: "7.1.9",
268
- vitest: "^4.0.15"
269
+ vitest: "4.1.1"
269
270
  }
270
271
  );
271
272
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.8.0"
2
+ "version": "0.9.0"
3
3
  }
@@ -1,7 +1,6 @@
1
1
  import { AxiosHeaders, AxiosInstance, AxiosResponse } from 'axios';
2
- import { BotConfigJSON } from './bot-config';
3
- import { BotCredentialsHandler, GlobalCredentialsHandler } from './credentials-handler';
4
- import type { BotDetail, Me, OAuth } from './interfaces';
2
+ import { GlobalCredentialsHandler } from './credentials-handler';
3
+ import type { BotDetail, Me, OAuth, ProviderAccountEntry } from './interfaces';
5
4
  interface IntegrationResponse {
6
5
  detail: string;
7
6
  provider_account: {
@@ -26,9 +25,8 @@ interface IntegrationResponse {
26
25
  netlify_url: string | null;
27
26
  }
28
27
  interface BotonicAPIServiceConfig {
29
- isLocalRuntimeDeployment?: boolean;
30
28
  projectRoot?: string;
31
- /** Monorepo/workspace root. When set, global credentials are stored at workspaceRoot/.botonic/credentials.json (source of truth). */
29
+ /** Monorepo/workspace root. When set, global credentials are stored at workspaceRoot/.botonic/env-credentials.json (source of truth). */
32
30
  workspaceRoot?: string;
33
31
  environmentVariables?: Record<string, string>;
34
32
  targetEnvironment?: string;
@@ -37,18 +35,17 @@ export declare class BotonicAPIService {
37
35
  clientId: string;
38
36
  baseUrl: string;
39
37
  loginUrl: string;
40
- botCredentialsHandler: InstanceType<typeof BotCredentialsHandler>;
38
+ private botsRegistryHandler;
41
39
  globalCredentialsHandler: InstanceType<typeof GlobalCredentialsHandler>;
42
40
  oauth?: OAuth;
41
+ private _tokenExpiresAt?;
43
42
  me?: Me;
44
43
  /** Cached from credentials for display when me is not loaded yet. */
45
44
  loggedUserName?: string;
46
45
  loggedEnvironmentUrl?: string;
47
46
  bot: BotDetail | null;
48
- localRuntimeBot: BotDetail | null;
49
47
  headers: AxiosHeaders;
50
48
  apiClient: AxiosInstance;
51
- isLocalRuntimeDeployment?: boolean;
52
49
  projectRoot?: string;
53
50
  targetEnvironment?: string;
54
51
  constructor(config?: BotonicAPIServiceConfig);
@@ -56,20 +53,21 @@ export declare class BotonicAPIService {
56
53
  botInfo(): BotDetail;
57
54
  getOauth(): OAuth;
58
55
  isAuthenticated(): boolean;
59
- /** True when in local runtime mode and a bot was loaded (e.g. from app .botonic.json). */
60
- hasLocalRuntimeBot(): boolean;
61
56
  ensureAuthenticated(): Promise<void>;
62
- setCurrentBot(bot: any): void;
63
- setLocalRuntimeBot(bot: any): void;
57
+ setCurrentBot(bot: BotDetail | null): void;
58
+ selectBot(bot: BotDetail): Promise<void>;
64
59
  private loadGlobalCredentials;
65
60
  /**
66
61
  * Returns store as env -> credentials. Migrates old single-credential format to { local: creds }.
67
62
  */
68
63
  private normalizeCredentialsStore;
69
- private loadBotCredentials;
64
+ private loadBotFromRegistry;
65
+ private saveToBotsRegistry;
66
+ saveProviders(providers: ProviderAccountEntry[]): void;
67
+ fetchProviders(botId: string): Promise<ProviderAccountEntry[]>;
68
+ saveBotEntry(bot: BotDetail): void;
70
69
  private setHeaders;
71
70
  private saveGlobalCredentials;
72
- private saveBotCredentials;
73
71
  private refreshToken;
74
72
  login(email: string, password: string): Promise<void>;
75
73
  logout(): Promise<void>;
@@ -83,17 +81,12 @@ export declare class BotonicAPIService {
83
81
  getDisplayName(): string;
84
82
  createWebchatIntegration(botId: string, integrationName: string): Promise<AxiosResponse<IntegrationResponse>>;
85
83
  signup(email: string, password: string, orgName: string, campaign: any): Promise<any>;
86
- createBot(botName: string): Promise<AxiosResponse>;
84
+ createBot(botName: string, isTest?: boolean): Promise<AxiosResponse>;
87
85
  getBots(): Promise<AxiosResponse>;
88
86
  deleteBot(botId: string): Promise<AxiosResponse>;
89
87
  getProviders(botId?: string): Promise<AxiosResponse>;
90
88
  deployBot(bundlePath: string, botConfigJson: any): Promise<any>;
91
89
  deployStatus(deployId: string): Promise<AxiosResponse>;
92
- deployLocalRuntime({ botConfigJson, lambdaFunctionName, lambdaEndpoint, }: {
93
- botConfigJson: BotConfigJSON;
94
- lambdaFunctionName: string;
95
- lambdaEndpoint: string;
96
- }): Promise<AxiosResponse<unknown, any, {}>>;
97
90
  build({ projectRoot, projectName, }: {
98
91
  projectRoot: string;
99
92
  projectName: string;
@@ -102,6 +95,12 @@ export declare class BotonicAPIService {
102
95
  private apiPost;
103
96
  private apiGet;
104
97
  private apiDelete;
98
+ private apiPut;
99
+ registerDevSessionWhatsapp(botId: string, chatProviderId: string, devSessionUrl: string, lambdaFunctionName: string, botConfig?: object): Promise<AxiosResponse>;
100
+ registerDevSessionWebchat(botId: string, chatProviderId: string, devSessionUrl: string, lambdaFunctionName: string, botConfig?: object): Promise<AxiosResponse>;
101
+ unregisterDevSessionWhatsapp(botId: string, chatProviderId: string): Promise<AxiosResponse>;
102
+ unregisterDevSessionWebchat(botId: string, chatProviderId: string): Promise<AxiosResponse>;
103
+ refreshTokenIfNeeded(): Promise<void>;
105
104
  private getMe;
106
105
  private validateToken;
107
106
  private isTokenExpired;