@botonic/nx-plugin 2.25.0 → 2.27.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 (74) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +4 -4
  3. package/generators.json +0 -26
  4. package/migrations.json +1 -38
  5. package/package.json +1 -1
  6. package/src/executors/e2e-webchat/botonic-package-publish.spec.ts +3 -3
  7. package/src/executors/serve-bot/executor.js +98 -16
  8. package/src/executors/serve-bot/schema.json +10 -0
  9. package/src/generators/action/files/__name__.spec.ts.template +4 -4
  10. package/src/generators/action/files/__name__.ts.template +5 -5
  11. package/src/generators/action/generator.js +1 -1
  12. package/src/generators/bot-app/files/.eslintrc.json.template +30 -1
  13. package/src/generators/bot-app/files/src/client/webchat/index.tsx.template +1 -6
  14. package/src/generators/bot-app/files/src/server/bot/actions/not-found.ts.template +7 -6
  15. package/src/generators/bot-app/files/src/server/bot/actions/welcome.ts.template +7 -6
  16. package/src/generators/bot-app/files/src/server/bot/index.ts.template +9 -11
  17. package/src/generators/bot-app/files/src/server/bot/plugins/ai-agents/index.ts.template +4 -4
  18. package/src/generators/bot-app/files/src/server/bot/plugins/flow-builder/index.ts.template +5 -5
  19. package/src/generators/bot-app/files/src/server/bot/routes.ts.template +5 -5
  20. package/src/generators/bot-app/files/src/server/bot/tracking.ts.template +4 -4
  21. package/src/generators/bot-app/files/src/server/lambda/handler.js.template +1 -6
  22. package/src/generators/bot-app/files/vite/plugins/dev-log-viewer-html.plugin.ts.template +65 -0
  23. package/src/generators/bot-app/files/vite/webchat.config.ts.template +14 -1
  24. package/src/generators/bot-app/generator.js +6 -2
  25. package/src/generators/bot-app/lilara-version.json +1 -1
  26. package/src/generators/bot-app/schema.d.ts +1 -0
  27. package/src/generators/bot-app/schema.json +4 -0
  28. package/src/generators/custom-message/files/__name__-output.ts.template +12 -10
  29. package/src/generators/custom-message/generator.js +1 -1
  30. package/src/{cursor-commands → generators/preset/files/.claude/commands}/update-bot.md +7 -7
  31. package/src/{cursor-commands → generators/preset/files/.claude/commands}/update-botonic.md +5 -3
  32. package/src/{migrations/add-botonic-update-bots-skill/files/.cursor → generators/preset/files/.claude}/scripts/update-bot/discover-bots.sh +1 -1
  33. package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-action/SKILL.md +21 -21
  34. package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-custom-message/SKILL.md +11 -12
  35. package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-webview/SKILL.md +8 -8
  36. package/src/generators/preset/files/.cursor/commands/update-bot.md +1 -3
  37. package/src/generators/preset/files/.cursor/commands/update-botonic.md +1 -3
  38. package/src/lib/util/executor-helpers.d.ts +0 -1
  39. package/src/lib/util/executor-helpers.js +1 -8
  40. package/src/generators/bot-app-migrations/migrate-fix-css-code-split/generator.d.ts +0 -5
  41. package/src/generators/bot-app-migrations/migrate-fix-css-code-split/generator.js +0 -92
  42. package/src/generators/bot-app-migrations/migrate-fix-css-code-split/schema.json +0 -15
  43. package/src/generators/bot-app-migrations/migrate-pnpm-compat/generator.d.ts +0 -5
  44. package/src/generators/bot-app-migrations/migrate-pnpm-compat/generator.js +0 -97
  45. package/src/generators/bot-app-migrations/migrate-pnpm-compat/schema.json +0 -15
  46. package/src/generators/bot-app-migrations/migrate-webchat-trigger/generator.d.ts +0 -5
  47. package/src/generators/bot-app-migrations/migrate-webchat-trigger/generator.js +0 -165
  48. package/src/generators/bot-app-migrations/migrate-webchat-trigger/schema.json +0 -15
  49. package/src/generators/preset/files/.cursor/scripts/update-bot/discover-bots.sh +0 -67
  50. package/src/migrations/add-botonic-update-bots-skill/add-botonic-update-bots-skill.migration.d.ts +0 -2
  51. package/src/migrations/add-botonic-update-bots-skill/add-botonic-update-bots-skill.migration.js +0 -52
  52. package/src/migrations/add-botonic-update-bots-skill/add-botonic-update-bots-skill.migration.md +0 -23
  53. package/src/migrations/add-botonic-update-bots-skill/files/.cursor/commands/update-bot.md +0 -5
  54. package/src/migrations/add-botonic-update-bots-skill/files/.cursor/commands/update-botonic.md +0 -5
  55. package/src/migrations/add-botonic-update-bots-skill/files/.cursor/scripts/update-bot/find-migration-guides.sh +0 -70
  56. package/src/migrations/add-botonic-update-bots-skill/schema.json +0 -5
  57. package/src/migrations/add-lilara-registry/add-lilara-registry.migration.d.ts +0 -2
  58. package/src/migrations/add-lilara-registry/add-lilara-registry.migration.js +0 -49
  59. package/src/migrations/add-lilara-registry/schema.json +0 -5
  60. package/src/migrations/fix-css-code-split/fix-css-code-split.migration.md +0 -45
  61. package/src/migrations/remove-codeartifact-registry/remove-codeartifact-registry.migration.d.ts +0 -2
  62. package/src/migrations/remove-codeartifact-registry/remove-codeartifact-registry.migration.js +0 -59
  63. package/src/migrations/remove-codeartifact-registry/schema.json +0 -5
  64. package/src/migrations/sync-pending-bot-migrations/schema.json +0 -5
  65. package/src/migrations/sync-pending-bot-migrations/sync-pending-bot-migrations.migration.d.ts +0 -2
  66. package/src/migrations/sync-pending-bot-migrations/sync-pending-bot-migrations.migration.js +0 -137
  67. package/src/migrations/sync-pending-bot-migrations/sync-pending-bot-migrations.migration.md +0 -19
  68. package/src/migrations/update-cursor-commands-to-stubs/schema.json +0 -5
  69. package/src/migrations/update-cursor-commands-to-stubs/update-cursor-commands-to-stubs.migration.d.ts +0 -2
  70. package/src/migrations/update-cursor-commands-to-stubs/update-cursor-commands-to-stubs.migration.js +0 -61
  71. package/src/migrations/update-pnpm-workspace-scripts/schema.json +0 -4
  72. package/src/migrations/update-pnpm-workspace-scripts/update-pnpm-workspace-scripts.migration.d.ts +0 -2
  73. package/src/migrations/update-pnpm-workspace-scripts/update-pnpm-workspace-scripts.migration.js +0 -47
  74. /package/src/generators/preset/files/{.cursor → .claude}/scripts/update-bot/find-migration-guides.sh +0 -0
@@ -3,16 +3,16 @@ import {
3
3
  EventAction,
4
4
  HtEventProps,
5
5
  } from '@botonic/plugin-hubtype-analytics'
6
- import { BotContext } from '@botonic/shared'
6
+ import { BotonicContext } from '@botonic/shared'
7
7
 
8
8
  import { BotPlugins } from './types'
9
9
 
10
10
  export async function trackEventToHubtypeAnalytics(
11
- botContext: BotContext<BotPlugins>,
11
+ botonicContext: BotonicContext<BotPlugins>,
12
12
  eventName: EventAction,
13
13
  args: Omit<HtEventProps, 'action'>
14
14
  ): Promise<void> {
15
- const hubtypeAnalyticsPlugin = botContext.plugins.hubtypeAnalytics
15
+ const hubtypeAnalyticsPlugin = botonicContext.plugins.hubtypeAnalytics
16
16
  const htEventProps = {
17
17
  action: eventName,
18
18
  ...args,
@@ -27,7 +27,7 @@ export async function trackEventToHubtypeAnalytics(
27
27
  }
28
28
 
29
29
  const response = await hubtypeAnalyticsPlugin.trackEvent(
30
- botContext,
30
+ botonicContext,
31
31
  htEventProps
32
32
  )
33
33
  console.log('TrackEvent Response', response, args)
@@ -11,12 +11,7 @@
11
11
  import { app } from './dist/index.js'
12
12
 
13
13
  export const botonic = async (event, aws_context) => {
14
- const { user_input, context } = event
15
-
16
- await app.bot.input({
17
- input: user_input || {},
18
- session: context || {},
19
- })
14
+ await app.bot.run(event)
20
15
 
21
16
  return {
22
17
  statusCode: 200,
@@ -0,0 +1,65 @@
1
+ import type { Plugin } from 'vite'
2
+
3
+ /** Split layout + iframe for serve-bot log viewer (?logs= port). Serve-only — never applied on build. */
4
+ const LAYOUT_STYLE = `
5
+ .dev-layout {
6
+ display: flex;
7
+ height: 100vh;
8
+ margin: 0;
9
+ padding: 0;
10
+ }
11
+ .dev-layout .logs-panel {
12
+ flex: 1;
13
+ border: none;
14
+ border-right: 1px solid #ddd;
15
+ }
16
+ .dev-layout .webchat-panel {
17
+ flex: 1;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ background: #f5f5f5;
22
+ }
23
+ `.trim()
24
+
25
+ /** Runs before the inline boot script so #webchat-root exists when render runs. */
26
+ const LAYOUT_SCRIPT = `
27
+ document.addEventListener('DOMContentLoaded', function botonicDevLogViewerLayout() {
28
+ var urlParams = new URLSearchParams(window.location.search)
29
+ var logsPort = urlParams.get('logs')
30
+ if (!logsPort) return
31
+ document.body.innerHTML = ''
32
+ document.body.className = 'dev-layout'
33
+ var logsIframe = document.createElement('iframe')
34
+ logsIframe.className = 'logs-panel'
35
+ logsIframe.src = 'http://localhost:' + logsPort
36
+ var webchatPanel = document.createElement('div')
37
+ webchatPanel.className = 'webchat-panel'
38
+ webchatPanel.id = 'webchat-root'
39
+ document.body.appendChild(logsIframe)
40
+ document.body.appendChild(webchatPanel)
41
+ })
42
+ `.trim()
43
+
44
+ export function devLogViewerIndexHtmlPlugin(): Plugin {
45
+ return {
46
+ name: 'botonic-dev-log-viewer-index-html',
47
+ apply: 'serve',
48
+ transformIndexHtml(html) {
49
+ const marker = '<script type="text/javascript">'
50
+ const pos = html.indexOf(marker)
51
+ if (pos === -1) {
52
+ return html
53
+ }
54
+
55
+ const styleTag = `<style data-botonic-dev-log-viewer>${LAYOUT_STYLE}</style>`
56
+ const scriptTag = `<script>${LAYOUT_SCRIPT}</script>`
57
+ const withStyle = html.replace('</head>', `${styleTag}</head>`)
58
+ const pos2 = withStyle.indexOf(marker)
59
+ if (pos2 === -1) {
60
+ return html
61
+ }
62
+ return `${withStyle.slice(0, pos2)}${scriptTag}${withStyle.slice(pos2)}`
63
+ },
64
+ }
65
+ }
@@ -2,6 +2,7 @@ import { resolve } from 'path'
2
2
  import type { UserConfig } from 'vite'
3
3
 
4
4
  import { BUILD_CONFIG } from './build.config'
5
+ import { devLogViewerIndexHtmlPlugin } from './plugins/dev-log-viewer-html.plugin'
5
6
  import { moveHtmlToRootPlugin } from './plugins/move-html.plugin'
6
7
 
7
8
  const projectRoot = resolve(__dirname, '..')
@@ -9,6 +10,11 @@ const output = BUILD_CONFIG.OUTPUT.webchat
9
10
  const server = BUILD_CONFIG.SERVER.webchat
10
11
 
11
12
  export function getWebchatConfig(command: 'serve' | 'build'): UserConfig {
13
+ const openPath =
14
+ command === 'serve' && process.env.LOG_VIEWER_PORT
15
+ ? `/?logs=${process.env.LOG_VIEWER_PORT}`
16
+ : '/'
17
+
12
18
  return {
13
19
  // For serve mode, use the webchat directory as root so dev server finds index.html
14
20
  // For build mode, use project root to avoid relative path issues
@@ -25,12 +31,18 @@ export function getWebchatConfig(command: 'serve' | 'build'): UserConfig {
25
31
  server: {
26
32
  port: server.port,
27
33
  host: 'localhost',
28
- open: '/',
34
+ open: process.env.VITE_SERVE_OPEN === 'false' ? false : openPath,
35
+ allowedHosts: process.env.VITE_ALLOWED_HOSTS
36
+ ? process.env.VITE_ALLOWED_HOSTS.split(',')
37
+ : [],
29
38
  },
30
39
 
31
40
  preview: {
32
41
  port: server.preview,
33
42
  host: 'localhost',
43
+ allowedHosts: process.env.VITE_ALLOWED_HOSTS
44
+ ? process.env.VITE_ALLOWED_HOSTS.split(',')
45
+ : [],
34
46
  },
35
47
 
36
48
  build: {
@@ -52,6 +64,7 @@ export function getWebchatConfig(command: 'serve' | 'build'): UserConfig {
52
64
  },
53
65
 
54
66
  plugins: [
67
+ devLogViewerIndexHtmlPlugin(),
55
68
  moveHtmlToRootPlugin(projectRoot, BUILD_CONFIG.TARGET_APPS.WEBCHAT),
56
69
  ],
57
70
  }
@@ -36,7 +36,11 @@ var import_child_process = require("child_process");
36
36
  var fs = __toESM(require("fs"));
37
37
  var path = __toESM(require("path"));
38
38
  const MODULE_DIR = __dirname;
39
- function getLilaraVersion() {
39
+ function getLilaraVersion(options) {
40
+ const version = options.lilaraVersion;
41
+ if (version && version !== "latest") {
42
+ return version.replace(/^[\^~]/, "");
43
+ }
40
44
  const jsonPath = path.join(MODULE_DIR, "lilara-version.json");
41
45
  try {
42
46
  if (fs.existsSync(jsonPath)) {
@@ -280,7 +284,7 @@ function addBotonicFiles(tree, options, botonicVersion) {
280
284
  }
281
285
  async function generator_default(tree, options) {
282
286
  const botonicVersion = getBotonicVersion(options);
283
- const lilaraVersion = getLilaraVersion();
287
+ const lilaraVersion = getLilaraVersion(options);
284
288
  const tasks = [];
285
289
  console.log(`\u{1F916} Using Botonic version: ${botonicVersion}`);
286
290
  console.log(`\u{1F3A8} Using Lilara version: ${lilaraVersion}`);
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.4.0"
2
+ "version": "0.6.0"
3
3
  }
@@ -3,4 +3,5 @@ export interface BotAppGeneratorSchema {
3
3
  directory?: string;
4
4
  packageManager?: 'pnpm' | 'npm';
5
5
  botonicVersion?: string;
6
+ lilaraVersion?: string;
6
7
  }
@@ -30,6 +30,10 @@
30
30
  "botonicVersion": {
31
31
  "type": "string",
32
32
  "description": "Botonic packages version. Leave empty to use the same version as the installed @botonic/nx-plugin."
33
+ },
34
+ "lilaraVersion": {
35
+ "type": "string",
36
+ "description": "Lilara packages version. Leave empty to use the version pinned by the installed generator."
33
37
  }
34
38
  },
35
39
  "required": ["name"]
@@ -1,19 +1,21 @@
1
- import { BotContext } from '@botonic/core'
1
+ import { BotonicContext } from '@botonic/core'
2
2
  import { MessageType } from '@botonic/shared'
3
3
 
4
4
  import { CUSTOM_MESSAGE_TYPES } from '../../../shared/constants'
5
5
 
6
- export async function <%= propertyName %>Output({ sendMessage }: BotContext) {
7
- await sendMessage({
8
- type: MessageType.Custom,
9
- data: {
10
- name: CUSTOM_MESSAGE_TYPES.<%= constantName %>,
11
- props: {
12
- // Add your custom props here
13
- // Example: title: 'Hello from <%= className %>!'
6
+ export async function <%= propertyName %>Output({ sendMessages }: BotonicContext) {
7
+ await sendMessages([
8
+ {
9
+ type: MessageType.Custom,
10
+ data: {
11
+ name: CUSTOM_MESSAGE_TYPES.<%= constantName %>,
12
+ props: {
13
+ // Add your custom props here
14
+ // Example: title: 'Hello from <%= className %>!'
15
+ },
14
16
  },
15
17
  },
16
- })
18
+ ])
17
19
  return {
18
20
  status: 200,
19
21
  response: 'OK',
@@ -196,7 +196,7 @@ function updateRoutes(tree, options) {
196
196
  (0, import_bot_app_utils.insertRouteBeforeFlowBuilder)(tree, options.routesPath, [
197
197
  ` {`,
198
198
  ` text: '${options.fileName}',`,
199
- ` action: async () => await ${actionImport}(botContext),`,
199
+ ` action: async () => await ${actionImport}(botonicContext),`,
200
200
  ` },`
201
201
  ]);
202
202
  }
@@ -21,7 +21,7 @@ If the `pending` array is empty, tell user: "All bot migrations are already appl
21
21
 
22
22
  ### Step 2: Discover Bots
23
23
 
24
- Run `bash .cursor/scripts/update-bot/discover-bots.sh` from the workspace root.
24
+ Run `bash .claude/scripts/update-bot/discover-bots.sh` from the workspace root.
25
25
  Expected output: JSON list of bots with name, path, current version.
26
26
 
27
27
  ### Step 3: Ask Which Bot
@@ -77,13 +77,13 @@ For each pending migration (in version order, filtered to `version` ≤ target):
77
77
 
78
78
  User says: "Update my-bot"
79
79
 
80
- 1. Read pending-bot-migrations.json → 2 entries (v2.16.0, v2.17.0), my-bot not in appliedTo for either
81
- 2. discover-bots.sh → my-bot on v2.15.0
82
- 3. Ask target: "Available versions: 2.16.0, 2.17.0. Update to which? [default: 2.17.0]" → user picks 2.17.0
80
+ 1. Read pending-bot-migrations.json → 2 entries (v2.0.1, v2.1.0), my-bot not in appliedTo for either
81
+ 2. discover-bots.sh → my-bot on v2.0.0
82
+ 3. Ask target: "Available versions: 2.0.1, 2.1.0. Update to which? [default: 2.1.0]" → user picks 2.1.0
83
83
  4. Branch: update/my-bot-pending-migrations
84
- 5. Run migrate-webchat-trigger (v2.16.0) → read guide → commit → mark applied
85
- 6. Run migrate-fix-css-code-split (v2.17.0) → no guide → commit → mark applied
86
- 7. Report: "Done. 2 generators applied to my-bot up to v2.17.0."
84
+ 5. Run first pending generator (v2.0.1) → read guide if present → commit → mark applied
85
+ 6. Run second pending generator (v2.1.0) → commit → mark applied
86
+ 7. Report: "Done. 2 generators applied to my-bot up to v2.1.0."
87
87
 
88
88
  ### Example 2: Bot already up to date
89
89
 
@@ -45,7 +45,9 @@ If output says "no migrations to run" and `package.json` was already at the late
45
45
  <package-manager> nx migrate --run-migrations
46
46
  ```
47
47
 
48
- This runs workspace-level migrations and writes `.botonic/pending-bot-migrations.json` with any bot-app generators that need to be applied per bot.
48
+ This runs any workspace-level migrations shipped by the target `@botonic/nx-plugin` version.
49
+
50
+ If that version also queues bot-app generators, it may write `.botonic/pending-bot-migrations.json` for `/update-bot`.
49
51
 
50
52
  ### Step 6: Commit
51
53
 
@@ -59,5 +61,5 @@ Report:
59
61
 
60
62
  - New version installed
61
63
  - Any workspace changes applied
62
- - How many bot-app generators are pending in `.botonic/pending-bot-migrations.json`
63
- - Instruct user to run `/update-bot` to apply pending generators to each bot
64
+ - Whether `.botonic/pending-bot-migrations.json` was created and how many generators are pending
65
+ - If generators are pending, instruct user to run `/update-bot` to apply them per bot
@@ -2,7 +2,7 @@
2
2
  # Discovers botonic bot apps and their current versions.
3
3
  # Uses nx show projects with tag:botonic:bot-app filter.
4
4
  # Run from workspace root. Output: JSON array of { name, path, version }.
5
- # Usage: bash .cursor/scripts/update-bot/discover-bots.sh
5
+ # Usage: bash .claude/scripts/update-bot/discover-bots.sh
6
6
 
7
7
  set -euo pipefail
8
8
 
@@ -44,7 +44,7 @@ Open `src/server/bot/routes.ts` and show the user the entry that was inserted:
44
44
  ```typescript
45
45
  {
46
46
  text: 'order-status',
47
- action: async () => await OrderStatus(botContext),
47
+ action: async () => await OrderStatus(botonicContext),
48
48
  },
49
49
  ```
50
50
 
@@ -58,13 +58,13 @@ If the matcher needs changing, edit `routes.ts` directly:
58
58
 
59
59
  ```typescript
60
60
  // Regex matcher
61
- { text: /^order.*/i, action: async () => await OrderStatus(botContext) },
61
+ { text: /^order.*/i, action: async () => await OrderStatus(botonicContext) },
62
62
 
63
63
  // Payload matcher
64
- { payload: 'ORDER_STATUS', action: async () => await OrderStatus(botContext) },
64
+ { payload: 'ORDER_STATUS', action: async () => await OrderStatus(botonicContext) },
65
65
 
66
66
  // Message type matcher
67
- { type: 'postback', payload: 'ORDER_STATUS', action: async () => await OrderStatus(botContext) },
67
+ { type: 'postback', payload: 'ORDER_STATUS', action: async () => await OrderStatus(botonicContext) },
68
68
  ```
69
69
 
70
70
  ### Step 4 — Ask what to implement
@@ -73,18 +73,18 @@ If not already clear from context, ask: "What should this action do?"
73
73
 
74
74
  ### Step 5 — Implement the action
75
75
 
76
- #### Building blocks: `sendMessage` and `BotServerMessageFactory`
76
+ #### Building blocks: `sendMessages` and `BotServerMessageFactory`
77
77
 
78
- Every action receives `BotContext` as its argument. The two key pieces for sending responses are:
78
+ Every action receives `BotonicContext` as its argument. The two key pieces for sending responses are:
79
79
 
80
- - **`sendMessage`** — async function from `BotContext`. Call it once per message you want to send. You can call it multiple times to send a sequence of messages.
80
+ - **`sendMessages`** — async function from `BotonicContext`. Pass an array of messages to send them in a single call.
81
81
  - **`BotServerMessageFactory`** — imported from `@botonic/shared`. Use its factory methods to build every message; never construct raw message objects by hand.
82
82
 
83
83
  ```typescript
84
- import { BotContext, BotServerMessageFactory } from '@botonic/shared'
84
+ import { BotonicContext, BotServerMessageFactory } from '@botonic/shared'
85
85
 
86
- export async function MyAction({ sendMessage }: BotContext) {
87
- await sendMessage(BotServerMessageFactory.createText({ text: 'Hello!' }))
86
+ export async function MyAction({ sendMessages }: BotonicContext) {
87
+ await sendMessages([BotServerMessageFactory.createText({ text: 'Hello!' })])
88
88
  return { status: 200, response: 'OK' }
89
89
  }
90
90
  ```
@@ -103,38 +103,38 @@ return { status: 400, response: 'Bad Request' } // validation error
103
103
  **Text message**
104
104
 
105
105
  ```typescript
106
- await sendMessage(BotServerMessageFactory.createText({ text: 'Your order is on the way!' }))
106
+ await sendMessages([BotServerMessageFactory.createText({ text: 'Your order is on the way!' })])
107
107
  ```
108
108
 
109
109
  **Button message** — build each button with the appropriate factory method; never pass raw button objects
110
110
 
111
111
  ```typescript
112
- await sendMessage(
112
+ await sendMessages([
113
113
  BotServerMessageFactory.createButtonMessage({
114
114
  text: 'Need help with your order?',
115
115
  buttons: [BotServerMessageFactory.createPostbackButton({ title: 'Order status', payload: 'ORDER_STATUS' }), BotServerMessageFactory.createUrlButton({ title: 'Visit website', url: 'https://example.com' }), BotServerMessageFactory.createWebviewButton({ title: 'Open form', webview: WEBVIEWS.ORDER_FORM }, session)],
116
- })
117
- )
116
+ }),
117
+ ])
118
118
  ```
119
119
 
120
120
  **Custom message**
121
121
 
122
122
  ```typescript
123
- await sendMessage(
123
+ await sendMessages([
124
124
  BotServerMessageFactory.createCustom({
125
125
  name: CUSTOM_MESSAGE_TYPES.ORDER_CARD,
126
126
  props: { orderId, status },
127
- })
128
- )
127
+ }),
128
+ ])
129
129
  ```
130
130
 
131
131
  **Conditional logic with early return**
132
132
 
133
133
  ```typescript
134
- export async function OrderStatus({ sendMessage, request }: BotContext) {
134
+ export async function OrderStatus({ sendMessages, request }: BotonicContext) {
135
135
  const { payload } = request
136
136
  if (!payload?.orderId) {
137
- await sendMessage(BotServerMessageFactory.createText({ text: 'Please provide an order ID.' }))
137
+ await sendMessages([BotServerMessageFactory.createText({ text: 'Please provide an order ID.' })])
138
138
  return { status: 400, response: 'Bad Request' }
139
139
  }
140
140
  // fetch order and respond...
@@ -149,9 +149,9 @@ Keep HTTP calls in a dedicated service file; import and call it from the action:
149
149
  ```typescript
150
150
  import { orderService } from '../../services/order-service'
151
151
 
152
- export async function OrderStatus({ sendMessage, request }: BotContext) {
152
+ export async function OrderStatus({ sendMessages, request }: BotonicContext) {
153
153
  const order = await orderService.getOrder(request.payload?.orderId)
154
- await sendMessage(BotServerMessageFactory.createText({ text: order.statusMessage }))
154
+ await sendMessages([BotServerMessageFactory.createText({ text: order.statusMessage })])
155
155
  return { status: 200, response: 'OK' }
156
156
  }
157
157
  ```
@@ -105,26 +105,25 @@ export const ProductCard: React.FC<ProductCardProps> = (props: any) => {
105
105
  In the output action, build the button server-side and pass it as a prop:
106
106
 
107
107
  ```typescript
108
- export async function productCardOutput({ sendMessage, session }: BotContext) {
108
+ export async function productCardOutput({ sendMessages, session }: BotonicContext) {
109
109
  const button = BotServerMessageFactory.createWebviewButton({ title: 'View details', webview: WEBVIEWS.PRODUCT }, session)
110
- await sendMessage(
110
+ await sendMessages([
111
111
  BotServerMessageFactory.createCustom({
112
112
  name: CUSTOM_MESSAGE_TYPES.PRODUCT_CARD,
113
113
  props: { title: 'Widget Pro', price: '€9.99', button },
114
- })
115
- )
114
+ }),
115
+ ])
116
116
  return { status: 200, response: 'OK' }
117
117
  }
118
118
  ```
119
119
 
120
120
  #### Multiple bubbles: send multiple messages from the action
121
121
 
122
- `Message` always renders exactly one bubble. If the design requires multiple separate bubbles (e.g. an image bubble followed by a text bubble), send them as separate messages from the output action — do not try to nest multiple `MessageBubble` components inside one `Message`.
122
+ `Message` always renders exactly one bubble. If the design requires multiple separate bubbles (e.g. an image bubble followed by a text bubble), pass them all in a single `sendMessages` call — do not try to nest multiple `MessageBubble` components inside one `Message`.
123
123
 
124
124
  ```typescript
125
125
  // In the output action
126
- await sendMessage(BotServerMessageFactory.createCustom({ name: CUSTOM_MESSAGE_TYPES.PRODUCT_IMAGE, props: { url } }))
127
- await sendMessage(BotServerMessageFactory.createCustom({ name: CUSTOM_MESSAGE_TYPES.PRODUCT_DETAIL, props: { title, price } }))
126
+ await sendMessages([BotServerMessageFactory.createCustom({ name: CUSTOM_MESSAGE_TYPES.PRODUCT_IMAGE, props: { url } }), BotServerMessageFactory.createCustom({ name: CUSTOM_MESSAGE_TYPES.PRODUCT_DETAIL, props: { title, price } })])
128
127
  ```
129
128
 
130
129
  #### Styling: CSS modules only, no inline styles
@@ -185,16 +184,16 @@ Use these same tokens inside the component CSS so colours stay in sync:
185
184
  Update `<name>-output.ts` to pass the required props in `data`:
186
185
 
187
186
  ```typescript
188
- export async function ratingCardOutput({ sendMessage }: BotContext) {
189
- await sendMessage(
187
+ export async function ratingCardOutput({ sendMessages }: BotonicContext) {
188
+ await sendMessages([
190
189
  BotServerMessageFactory.createCustom({
191
190
  name: CUSTOM_MESSAGE_TYPES.RATING_CARD,
192
191
  props: {
193
192
  rating: 0,
194
193
  label: 'How was your experience?',
195
194
  },
196
- })
197
- )
195
+ }),
196
+ ])
198
197
  return { status: 200, response: 'OK' }
199
198
  }
200
199
  ```
@@ -208,7 +207,7 @@ The output action is already wired to a route in `routes.ts`. Call it from anoth
208
207
  ```typescript
209
208
  import { ratingCardOutput } from './rating-card-output'
210
209
 
211
- export async function EndConversation(ctx: BotContext) {
210
+ export async function EndConversation(ctx: BotonicContext) {
212
211
  await ratingCardOutput(ctx)
213
212
  return { status: 200, response: 'OK' }
214
213
  }
@@ -45,11 +45,11 @@ Access request data and the close handler via `useWebviewRequest`.
45
45
 
46
46
  > **CSS loading.** `WebviewApp` (from `@botonic/webviews`) loads `@lilara/foundations` reset + tokens internally. The webview entry (`src/client/webviews/index.tsx`) only imports `@lilara/ui-web-react/styles.css` for Lilara component styles — this is generated for new bots.
47
47
 
48
- #### Always use UI components from `@lilara/ui-web-react`
48
+ #### Always use UI components from `@botonic/webchat-react`
49
49
 
50
- Never use raw HTML elements (`<input>`, `<button>`, `<select>`, etc.) in webviews. Interactive and display primitives come from `@lilara/ui-web-react` (design system). Use `@botonic/webchat-react` only for webchat-specific pieces (e.g. `ButtonComponent` for message-style buttons).
50
+ Never use raw HTML elements (`<input>`, `<button>`, `<select>`, etc.) in webviews. Interactive and display primitives come from `@botonic/webchat-react` (design system gateway). Never import UI components directly from `@lilara/ui-web-react` or `@lilara/ui-web` the ESLint warn rule will flag it.
51
51
 
52
- The table below lists **@lilara/ui-web-react** exports (import from that package).
52
+ The table below lists available components (import from `@botonic/webchat-react`).
53
53
 
54
54
  > **react-aria conventions.** These components are built on react-aria — prop names differ from standard HTML. Check the `.d.ts` types before using any component. Key differences: `Button` uses `onPress` not `onClick`; `Checkbox` and `Switch` use `isSelected` not `checked`, and `onChange` receives a `boolean` not an event.
55
55
 
@@ -90,7 +90,7 @@ export const OrderDetail = () => {
90
90
  **Form with submission**
91
91
 
92
92
  ```tsx
93
- import { Button, TextInput } from '@lilara/ui-web-react'
93
+ import { Button, TextInput } from '@botonic/webchat-react'
94
94
  import { useWebviewRequest, WebviewBody, WebviewFrame, WebviewHeader } from '@botonic/webviews'
95
95
 
96
96
  export const BookingForm = () => {
@@ -119,7 +119,7 @@ export const BookingForm = () => {
119
119
  **Fetching data on load**
120
120
 
121
121
  ```tsx
122
- import { Banner, Spinner } from '@lilara/ui-web-react'
122
+ import { Banner, Spinner } from '@botonic/webchat-react'
123
123
  import { useWebviewRequest, WebviewBody, WebviewFrame, WebviewHeader } from '@botonic/webviews'
124
124
 
125
125
  export const OrderDetail = () => {
@@ -162,12 +162,12 @@ After the webview is ready, remind the user to open it from an action using `Bot
162
162
  ```typescript
163
163
  import { WEBVIEWS } from '../../shared/constants'
164
164
 
165
- await sendMessage(
165
+ await sendMessages([
166
166
  BotServerMessageFactory.createButtonMessage({
167
167
  text: 'Need to book a slot?',
168
168
  buttons: [BotServerMessageFactory.createWebviewButton({ title: 'Open form', webview: WEBVIEWS.BOOKING_FORM }, session)],
169
- })
170
- )
169
+ }),
170
+ ])
171
171
  ```
172
172
 
173
173
  Use the `botonic-action` skill if the user also needs to create the triggering action.
@@ -1,5 +1,3 @@
1
1
  # Update Botonic Bot
2
2
 
3
- Applies pending bot-app migrations from `.botonic/pending-bot-migrations.json` to a single bot.
4
-
5
- Follow the full workflow in: `node_modules/@botonic/nx-plugin/src/cursor-commands/update-bot.md`
3
+ Follow the workflow in `.claude/commands/update-bot.md`
@@ -1,5 +1,3 @@
1
1
  # Update Botonic Workspace
2
2
 
3
- Updates the Botonic plugin to the latest version and queues pending bot-app migrations.
4
-
5
- Follow the full workflow in: `node_modules/@botonic/nx-plugin/src/cursor-commands/update-botonic.md`
3
+ Follow the workflow in `.claude/commands/update-botonic.md`
@@ -32,7 +32,6 @@ declare const TARGET_ENV_NAMES: readonly ["local", "dev", "dev2", "qa", "prod"];
32
32
  export declare function writeAppIdToEnvFile(projectRoot: string, appId: string, targetEnv: (typeof TARGET_ENV_NAMES)[number], appIdKey?: string): Promise<void>;
33
33
  /**
34
34
  * Writes or updates a single env var (key=value) in .env.[targetEnv].
35
- * Used e.g. for VITE_HUBTYPE_BOT_ID so the webchat auth URL uses the correct bot.
36
35
  */
37
36
  export declare function writeEnvVarToEnvFile(projectRoot: string, key: string, value: string, targetEnv: (typeof TARGET_ENV_NAMES)[number]): void;
38
37
  export declare function handleExecutorError(error: any, operation: string, showHelp?: boolean): {
@@ -522,15 +522,8 @@ async function performDeployLocalRuntimeWithEndpoint(context, projectRoot, endpo
522
522
  await writeAppIdToEnvFile(projectRoot, appId, resolvedTargetEnv);
523
523
  }
524
524
  botonicApiService.saveAllCredentials();
525
- const botId = botonicApiService.botInfo().id;
526
- writeEnvVarToEnvFile(
527
- projectRoot,
528
- "VITE_HUBTYPE_BOT_ID",
529
- botId,
530
- resolvedTargetEnv
531
- );
532
525
  return {
533
- botId,
526
+ botId: botonicApiService.botInfo().id,
534
527
  targetEnvironment,
535
528
  environmentVariables
536
529
  };
@@ -1,5 +0,0 @@
1
- import type { Tree } from '@nx/devkit';
2
- export interface MigrateFixCssCodeSplitSchema {
3
- project: string;
4
- }
5
- export default function migrateFixCssCodeSplitGenerator(tree: Tree, options: MigrateFixCssCodeSplitSchema): Promise<void>;