@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.
- package/CHANGELOG.md +29 -0
- package/README.md +4 -4
- package/generators.json +0 -26
- package/migrations.json +1 -38
- package/package.json +1 -1
- package/src/executors/e2e-webchat/botonic-package-publish.spec.ts +3 -3
- package/src/executors/serve-bot/executor.js +98 -16
- package/src/executors/serve-bot/schema.json +10 -0
- package/src/generators/action/files/__name__.spec.ts.template +4 -4
- package/src/generators/action/files/__name__.ts.template +5 -5
- package/src/generators/action/generator.js +1 -1
- package/src/generators/bot-app/files/.eslintrc.json.template +30 -1
- package/src/generators/bot-app/files/src/client/webchat/index.tsx.template +1 -6
- package/src/generators/bot-app/files/src/server/bot/actions/not-found.ts.template +7 -6
- package/src/generators/bot-app/files/src/server/bot/actions/welcome.ts.template +7 -6
- package/src/generators/bot-app/files/src/server/bot/index.ts.template +9 -11
- package/src/generators/bot-app/files/src/server/bot/plugins/ai-agents/index.ts.template +4 -4
- package/src/generators/bot-app/files/src/server/bot/plugins/flow-builder/index.ts.template +5 -5
- package/src/generators/bot-app/files/src/server/bot/routes.ts.template +5 -5
- package/src/generators/bot-app/files/src/server/bot/tracking.ts.template +4 -4
- package/src/generators/bot-app/files/src/server/lambda/handler.js.template +1 -6
- package/src/generators/bot-app/files/vite/plugins/dev-log-viewer-html.plugin.ts.template +65 -0
- package/src/generators/bot-app/files/vite/webchat.config.ts.template +14 -1
- package/src/generators/bot-app/generator.js +6 -2
- package/src/generators/bot-app/lilara-version.json +1 -1
- package/src/generators/bot-app/schema.d.ts +1 -0
- package/src/generators/bot-app/schema.json +4 -0
- package/src/generators/custom-message/files/__name__-output.ts.template +12 -10
- package/src/generators/custom-message/generator.js +1 -1
- package/src/{cursor-commands → generators/preset/files/.claude/commands}/update-bot.md +7 -7
- package/src/{cursor-commands → generators/preset/files/.claude/commands}/update-botonic.md +5 -3
- package/src/{migrations/add-botonic-update-bots-skill/files/.cursor → generators/preset/files/.claude}/scripts/update-bot/discover-bots.sh +1 -1
- package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-action/SKILL.md +21 -21
- package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-custom-message/SKILL.md +11 -12
- package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-webview/SKILL.md +8 -8
- package/src/generators/preset/files/.cursor/commands/update-bot.md +1 -3
- package/src/generators/preset/files/.cursor/commands/update-botonic.md +1 -3
- package/src/lib/util/executor-helpers.d.ts +0 -1
- package/src/lib/util/executor-helpers.js +1 -8
- package/src/generators/bot-app-migrations/migrate-fix-css-code-split/generator.d.ts +0 -5
- package/src/generators/bot-app-migrations/migrate-fix-css-code-split/generator.js +0 -92
- package/src/generators/bot-app-migrations/migrate-fix-css-code-split/schema.json +0 -15
- package/src/generators/bot-app-migrations/migrate-pnpm-compat/generator.d.ts +0 -5
- package/src/generators/bot-app-migrations/migrate-pnpm-compat/generator.js +0 -97
- package/src/generators/bot-app-migrations/migrate-pnpm-compat/schema.json +0 -15
- package/src/generators/bot-app-migrations/migrate-webchat-trigger/generator.d.ts +0 -5
- package/src/generators/bot-app-migrations/migrate-webchat-trigger/generator.js +0 -165
- package/src/generators/bot-app-migrations/migrate-webchat-trigger/schema.json +0 -15
- package/src/generators/preset/files/.cursor/scripts/update-bot/discover-bots.sh +0 -67
- package/src/migrations/add-botonic-update-bots-skill/add-botonic-update-bots-skill.migration.d.ts +0 -2
- package/src/migrations/add-botonic-update-bots-skill/add-botonic-update-bots-skill.migration.js +0 -52
- package/src/migrations/add-botonic-update-bots-skill/add-botonic-update-bots-skill.migration.md +0 -23
- package/src/migrations/add-botonic-update-bots-skill/files/.cursor/commands/update-bot.md +0 -5
- package/src/migrations/add-botonic-update-bots-skill/files/.cursor/commands/update-botonic.md +0 -5
- package/src/migrations/add-botonic-update-bots-skill/files/.cursor/scripts/update-bot/find-migration-guides.sh +0 -70
- package/src/migrations/add-botonic-update-bots-skill/schema.json +0 -5
- package/src/migrations/add-lilara-registry/add-lilara-registry.migration.d.ts +0 -2
- package/src/migrations/add-lilara-registry/add-lilara-registry.migration.js +0 -49
- package/src/migrations/add-lilara-registry/schema.json +0 -5
- package/src/migrations/fix-css-code-split/fix-css-code-split.migration.md +0 -45
- package/src/migrations/remove-codeartifact-registry/remove-codeartifact-registry.migration.d.ts +0 -2
- package/src/migrations/remove-codeartifact-registry/remove-codeartifact-registry.migration.js +0 -59
- package/src/migrations/remove-codeartifact-registry/schema.json +0 -5
- package/src/migrations/sync-pending-bot-migrations/schema.json +0 -5
- package/src/migrations/sync-pending-bot-migrations/sync-pending-bot-migrations.migration.d.ts +0 -2
- package/src/migrations/sync-pending-bot-migrations/sync-pending-bot-migrations.migration.js +0 -137
- package/src/migrations/sync-pending-bot-migrations/sync-pending-bot-migrations.migration.md +0 -19
- package/src/migrations/update-cursor-commands-to-stubs/schema.json +0 -5
- package/src/migrations/update-cursor-commands-to-stubs/update-cursor-commands-to-stubs.migration.d.ts +0 -2
- package/src/migrations/update-cursor-commands-to-stubs/update-cursor-commands-to-stubs.migration.js +0 -61
- package/src/migrations/update-pnpm-workspace-scripts/schema.json +0 -4
- package/src/migrations/update-pnpm-workspace-scripts/update-pnpm-workspace-scripts.migration.d.ts +0 -2
- package/src/migrations/update-pnpm-workspace-scripts/update-pnpm-workspace-scripts.migration.js +0 -47
- /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 {
|
|
6
|
+
import { BotonicContext } from '@botonic/shared'
|
|
7
7
|
|
|
8
8
|
import { BotPlugins } from './types'
|
|
9
9
|
|
|
10
10
|
export async function trackEventToHubtypeAnalytics(
|
|
11
|
-
|
|
11
|
+
botonicContext: BotonicContext<BotPlugins>,
|
|
12
12
|
eventName: EventAction,
|
|
13
13
|
args: Omit<HtEventProps, 'action'>
|
|
14
14
|
): Promise<void> {
|
|
15
|
-
const hubtypeAnalyticsPlugin =
|
|
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
|
-
|
|
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
|
-
|
|
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}`);
|
|
@@ -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 {
|
|
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({
|
|
7
|
-
await
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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}(
|
|
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 .
|
|
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.
|
|
81
|
-
2. discover-bots.sh → my-bot on v2.
|
|
82
|
-
3. Ask target: "Available versions: 2.
|
|
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
|
|
85
|
-
6. Run
|
|
86
|
-
7. Report: "Done. 2 generators applied to my-bot up to v2.
|
|
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
|
|
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
|
-
-
|
|
63
|
-
-
|
|
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 .
|
|
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(
|
|
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(
|
|
61
|
+
{ text: /^order.*/i, action: async () => await OrderStatus(botonicContext) },
|
|
62
62
|
|
|
63
63
|
// Payload matcher
|
|
64
|
-
{ payload: 'ORDER_STATUS', action: async () => await OrderStatus(
|
|
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(
|
|
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: `
|
|
76
|
+
#### Building blocks: `sendMessages` and `BotServerMessageFactory`
|
|
77
77
|
|
|
78
|
-
Every action receives `
|
|
78
|
+
Every action receives `BotonicContext` as its argument. The two key pieces for sending responses are:
|
|
79
79
|
|
|
80
|
-
- **`
|
|
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 {
|
|
84
|
+
import { BotonicContext, BotServerMessageFactory } from '@botonic/shared'
|
|
85
85
|
|
|
86
|
-
export async function MyAction({
|
|
87
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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({
|
|
134
|
+
export async function OrderStatus({ sendMessages, request }: BotonicContext) {
|
|
135
135
|
const { payload } = request
|
|
136
136
|
if (!payload?.orderId) {
|
|
137
|
-
await
|
|
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({
|
|
152
|
+
export async function OrderStatus({ sendMessages, request }: BotonicContext) {
|
|
153
153
|
const order = await orderService.getOrder(request.payload?.orderId)
|
|
154
|
-
await
|
|
154
|
+
await sendMessages([BotServerMessageFactory.createText({ text: order.statusMessage })])
|
|
155
155
|
return { status: 200, response: 'OK' }
|
|
156
156
|
}
|
|
157
157
|
```
|
package/src/generators/preset/files/{.cursor → .claude}/skills/botonic-custom-message/SKILL.md
RENAMED
|
@@ -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({
|
|
108
|
+
export async function productCardOutput({ sendMessages, session }: BotonicContext) {
|
|
109
109
|
const button = BotServerMessageFactory.createWebviewButton({ title: 'View details', webview: WEBVIEWS.PRODUCT }, session)
|
|
110
|
-
await
|
|
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),
|
|
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
|
|
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({
|
|
189
|
-
await
|
|
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:
|
|
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 `@
|
|
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 `@
|
|
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
|
|
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 '@
|
|
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 '@
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|