telemeister 0.1.21 → 0.2.8

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 (130) hide show
  1. package/README.md +55 -13
  2. package/bin/telemeister-cli.js +1220 -0
  3. package/bin/telemeister.js +2 -1
  4. package/dist/bot/index.d.ts +10 -0
  5. package/dist/bot/index.d.ts.map +1 -0
  6. package/dist/bot/index.js +9 -0
  7. package/dist/bot/index.js.map +1 -0
  8. package/dist/bot/polling.d.ts +20 -0
  9. package/dist/bot/polling.d.ts.map +1 -0
  10. package/dist/bot/polling.js +129 -0
  11. package/dist/bot/polling.js.map +1 -0
  12. package/dist/bot/session.d.ts +54 -0
  13. package/dist/bot/session.d.ts.map +1 -0
  14. package/dist/bot/session.js +89 -0
  15. package/dist/bot/session.js.map +1 -0
  16. package/dist/bot/webhook.d.ts +36 -0
  17. package/dist/bot/webhook.d.ts.map +1 -0
  18. package/dist/bot/webhook.js +213 -0
  19. package/dist/bot/webhook.js.map +1 -0
  20. package/dist/bot-state-types.d.ts.map +1 -0
  21. package/dist/bot-state-types.js +3 -0
  22. package/dist/bot-state-types.js.map +1 -0
  23. package/dist/cli/create-bot.d.ts.map +1 -1
  24. package/dist/cli/create-bot.js +32 -10
  25. package/dist/cli/create-bot.js.map +1 -1
  26. package/dist/cli/state-manager.d.ts.map +1 -1
  27. package/dist/cli/state-manager.js +14 -1
  28. package/dist/cli/state-manager.js.map +1 -1
  29. package/dist/core/app-states.d.ts +8 -0
  30. package/dist/core/app-states.d.ts.map +1 -0
  31. package/dist/core/app-states.js +8 -0
  32. package/dist/core/app-states.js.map +1 -0
  33. package/dist/core/bot/polling.d.ts.map +1 -1
  34. package/dist/core/bot/polling.js +4 -7
  35. package/dist/core/bot/polling.js.map +1 -1
  36. package/dist/core/bot/webhook.d.ts.map +1 -1
  37. package/dist/core/bot/webhook.js +28 -9
  38. package/dist/core/bot/webhook.js.map +1 -1
  39. package/dist/core/builder.d.ts +1 -1
  40. package/dist/core/builder.d.ts.map +1 -1
  41. package/dist/core/builder.js +2 -2
  42. package/dist/core/builder.js.map +1 -1
  43. package/dist/core/index.d.ts +1 -0
  44. package/dist/core/index.d.ts.map +1 -1
  45. package/dist/core/index.js.map +1 -1
  46. package/dist/core/types.d.ts +3 -2
  47. package/dist/core/types.d.ts.map +1 -1
  48. package/dist/core/types.js.map +1 -1
  49. package/dist/database.d.ts +43 -0
  50. package/dist/database.d.ts.map +1 -0
  51. package/dist/database.js +127 -0
  52. package/dist/database.js.map +1 -0
  53. package/dist/generated/prisma/browser.d.ts +15 -0
  54. package/dist/generated/prisma/browser.d.ts.map +1 -0
  55. package/dist/generated/prisma/browser.js +18 -0
  56. package/dist/generated/prisma/browser.js.map +1 -0
  57. package/dist/generated/prisma/client.d.ts +32 -0
  58. package/dist/generated/prisma/client.d.ts.map +1 -0
  59. package/dist/generated/prisma/client.js +33 -0
  60. package/dist/generated/prisma/client.js.map +1 -0
  61. package/dist/generated/prisma/commonInputTypes.d.ts +166 -0
  62. package/dist/generated/prisma/commonInputTypes.d.ts.map +1 -0
  63. package/dist/generated/prisma/commonInputTypes.js +11 -0
  64. package/dist/generated/prisma/commonInputTypes.js.map +1 -0
  65. package/dist/generated/prisma/enums.d.ts +2 -0
  66. package/dist/generated/prisma/enums.d.ts.map +1 -0
  67. package/dist/generated/prisma/enums.js +11 -0
  68. package/dist/generated/prisma/enums.js.map +1 -0
  69. package/dist/generated/prisma/internal/class.d.ts +138 -0
  70. package/dist/generated/prisma/internal/class.d.ts.map +1 -0
  71. package/dist/generated/prisma/internal/class.js +50 -0
  72. package/dist/generated/prisma/internal/class.js.map +1 -0
  73. package/dist/generated/prisma/internal/prismaNamespace.d.ts +591 -0
  74. package/dist/generated/prisma/internal/prismaNamespace.d.ts.map +1 -0
  75. package/dist/generated/prisma/internal/prismaNamespace.js +96 -0
  76. package/dist/generated/prisma/internal/prismaNamespace.js.map +1 -0
  77. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +56 -0
  78. package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts.map +1 -0
  79. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +67 -0
  80. package/dist/generated/prisma/internal/prismaNamespaceBrowser.js.map +1 -0
  81. package/dist/generated/prisma/models/User.d.ts +1169 -0
  82. package/dist/generated/prisma/models/User.d.ts.map +1 -0
  83. package/dist/generated/prisma/models/User.js +2 -0
  84. package/dist/generated/prisma/models/User.js.map +1 -0
  85. package/dist/generated/prisma/models/UserInfo.d.ts +1101 -0
  86. package/dist/generated/prisma/models/UserInfo.d.ts.map +1 -0
  87. package/dist/generated/prisma/models/UserInfo.js +2 -0
  88. package/dist/generated/prisma/models/UserInfo.js.map +1 -0
  89. package/dist/generated/prisma/models.d.ts +4 -0
  90. package/dist/generated/prisma/models.d.ts.map +1 -0
  91. package/dist/generated/prisma/models.js +2 -0
  92. package/dist/generated/prisma/models.js.map +1 -0
  93. package/dist/handlers/idle/index.d.ts +2 -0
  94. package/dist/handlers/idle/index.d.ts.map +1 -0
  95. package/dist/handlers/idle/index.js +22 -0
  96. package/dist/handlers/idle/index.js.map +1 -0
  97. package/dist/handlers/index.d.ts +12 -0
  98. package/dist/handlers/index.d.ts.map +1 -0
  99. package/dist/handlers/index.js +14 -0
  100. package/dist/handlers/index.js.map +1 -0
  101. package/dist/handlers/menu/index.d.ts +2 -0
  102. package/dist/handlers/menu/index.d.ts.map +1 -0
  103. package/dist/handlers/menu/index.js +35 -0
  104. package/dist/handlers/menu/index.js.map +1 -0
  105. package/dist/handlers/menu.d.ts +2 -0
  106. package/dist/handlers/menu.d.ts.map +1 -0
  107. package/dist/handlers/menu.js +37 -0
  108. package/dist/handlers/menu.js.map +1 -0
  109. package/dist/handlers/welcome/index.d.ts +2 -0
  110. package/dist/handlers/welcome/index.d.ts.map +1 -0
  111. package/dist/handlers/welcome/index.js +22 -0
  112. package/dist/handlers/welcome/index.js.map +1 -0
  113. package/dist/handlers/welcome.d.ts +2 -0
  114. package/dist/handlers/welcome.d.ts.map +1 -0
  115. package/dist/handlers/welcome.js +30 -0
  116. package/dist/handlers/welcome.js.map +1 -0
  117. package/dist/index.d.ts +3 -0
  118. package/dist/index.d.ts.map +1 -0
  119. package/dist/index.js +39 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/templates/README.md.ejs +6 -5
  122. package/dist/templates/handler.ts.ejs +71 -8
  123. package/dist/templates/package.json.ejs +7 -0
  124. package/dist/templates/templates/README.md.ejs +12 -11
  125. package/dist/templates/templates/handler.ts.ejs +70 -8
  126. package/dist/templates/templates/package.json.ejs +7 -0
  127. package/package.json +17 -38
  128. package/src/templates/README.md.ejs +12 -11
  129. package/src/templates/handler.ts.ejs +70 -8
  130. package/src/templates/package.json.ejs +7 -0
@@ -17,7 +17,23 @@ appBuilder
17
17
  // Called when user enters this state
18
18
  // Can optionally return a state name to immediately transition
19
19
 
20
- await context.send("Hello from <%= stateName %> state!");
20
+ // Access Grammy context via context.ctx
21
+ // See Grammy docs: https://grammy.dev/guide/context
22
+ await context.ctx.reply("Hello from <%= stateName %> state!");
23
+
24
+ // === INLINE KEYBOARD EXAMPLE ===
25
+ // import { InlineKeyboard } from 'grammy';
26
+ // const keyboard = new InlineKeyboard()
27
+ // .text('Button 1', 'btn1')
28
+ // .text('Button 2', 'btn2');
29
+ // await context.ctx.reply('Choose:', { reply_markup: keyboard });
30
+
31
+ // === POLL EXAMPLE ===
32
+ // await context.ctx.replyWithPoll(
33
+ // 'Question?',
34
+ // ['Option A', 'Option B', 'Option C'],
35
+ // { is_anonymous: false }
36
+ // );
21
37
 
22
38
  // Database helpers (see src/lib/database.ts):
23
39
  // import { getUserByTelegramId, createOrUpdateUser } from '../../lib/database.js';
@@ -30,20 +46,66 @@ appBuilder
30
46
  <% } %>
31
47
  })
32
48
  <% if (transitionStates.length > 0) { %>
33
- .onResponse(async (context: AppContext, response): <%= pascalCase(stateName) %>Transitions => {
49
+ .onResponse(async (context: AppContext): <%= pascalCase(stateName) %>Transitions => {
34
50
  <% } else { %>
35
- .onResponse(async (context: AppContext, response) => {
51
+ .onResponse(async (context: AppContext) => {
36
52
  <% } %>
37
- // Called when user sends a message in this state
53
+ // Called when user sends any update in this state (message, callback, poll, etc.)
38
54
  // Return a state name to transition, or nothing to stay
39
55
 
40
- const text = response.trim();
56
+ // Handle different update types via Grammy context (context.ctx):
57
+ // - context.ctx.message?.text - text messages
58
+ // - context.ctx.callbackQuery?.data - inline button callbacks
59
+ // - context.ctx.message?.photo - photo messages
60
+ // - context.ctx.pollAnswer - poll responses
61
+ // See Grammy docs: https://grammy.dev/guide/context
62
+
63
+ // === INLINE KEYBOARD CALLBACK EXAMPLE ===
64
+ // if (context.ctx.callbackQuery?.data) {
65
+ // await context.ctx.answerCallbackQuery();
66
+ // const data = context.ctx.callbackQuery.data;
67
+ // switch (data) {
68
+ // case 'btn1':
69
+ // await context.ctx.reply('You clicked Button 1!');
70
+ // break;
71
+ // case 'btn2':
72
+ // return 'otherState'; // Transition to another state
73
+ // }
74
+ // return;
75
+ // }
76
+
77
+ // === POLL ANSWER EXAMPLE ===
78
+ // if (context.ctx.pollAnswer) {
79
+ // const optionIds = context.ctx.pollAnswer.option_ids;
80
+ // const options = ['Option A', 'Option B', 'Option C'];
81
+ // const selected = optionIds.map(id => options[id]).join(', ');
82
+ // await context.ctx.reply(`You voted for: ${selected}`);
83
+ // return;
84
+ // }
85
+
86
+ // === COMMAND HANDLING EXAMPLE ===
87
+ // const text = context.ctx.message?.text?.trim();
88
+ // if (text?.startsWith('/')) {
89
+ // const command = text.split(' ')[0].toLowerCase();
90
+ // switch (command) {
91
+ // case '/start':
92
+ // await context.ctx.reply('Welcome!');
93
+ // return 'welcome';
94
+ // case '/menu':
95
+ // return 'menu';
96
+ // default:
97
+ // await context.ctx.reply(`Unknown command: ${command}`);
98
+ // }
99
+ // return;
100
+ // }
101
+
102
+ const text = context.ctx.message?.text?.trim();
103
+ if (text) {
104
+ await context.ctx.reply(`You said: ${text}`);
105
+ }
41
106
  <% if (transitionStates.length > 0) { %>
42
107
  // Available transitions: <%= transitionStates.join(', ') %>
43
108
  <% } %>
44
- // Handle user response
45
- await context.send(`You said: ${text}`);
46
-
47
109
  // Database helpers (see src/lib/database.ts):
48
110
  // import { updateUserState } from '../../lib/database.js';
49
111
  // await updateUserState(String(context.telegramId), 'nextState', { lastMessage: text });
@@ -4,11 +4,18 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "tsx watch src/index.ts",
7
+ "dev:webhook": "BOT_MODE=webhook tsx watch src/index.ts",
7
8
  "build": "tsc",
8
9
  "start": "node dist/index.js",
10
+ "start:webhook": "BOT_MODE=webhook node dist/index.js",
9
11
  "db:generate": "prisma generate",
10
12
  "db:migrate": "prisma migrate dev",
13
+ "db:deploy": "prisma migrate deploy",
14
+ "db:push": "prisma db push",
11
15
  "db:studio": "prisma studio",
16
+ "webhook:set": "tsx scripts/set-webhook.ts",
17
+ "webhook:delete": "tsx scripts/delete-webhook.ts",
18
+ "webhook:info": "tsx scripts/webhook-info.ts",
12
19
  "state:add": "telemeister state:add",
13
20
  "state:delete": "telemeister state:delete",
14
21
  "state:sync": "telemeister state:sync",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "telemeister",
3
- "version": "0.1.21",
3
+ "version": "0.2.8",
4
4
  "description": "TypeScript Telegram Bot Boilerplate with XState",
5
5
  "type": "module",
6
6
  "main": "./dist/core/index.js",
@@ -33,40 +33,9 @@
33
33
  "README.md",
34
34
  "LICENSE"
35
35
  ],
36
- "scripts": {
37
- "build": "tsc && cp -r src/templates dist/templates",
38
- "dev": "tsx src/index.ts",
39
- "dev:watch": "tsx watch src/index.ts",
40
- "dev:webhook": "BOT_MODE=webhook tsx src/index.ts",
41
- "dev:webhook:watch": "BOT_MODE=webhook tsx watch src/index.ts",
42
- "start": "node dist/index.js",
43
- "start:webhook": "BOT_MODE=webhook node dist/index.js",
44
- "db:generate": "prisma generate",
45
- "db:migrate": "prisma migrate dev",
46
- "db:deploy": "prisma migrate deploy",
47
- "db:push": "prisma db push",
48
- "db:studio": "prisma studio",
49
- "webhook:set": "tsx scripts/set-webhook.ts",
50
- "webhook:delete": "tsx scripts/delete-webhook.ts",
51
- "webhook:info": "tsx scripts/webhook-info.ts",
52
- "telemeister:state:add": "tsx src/cli/cli.ts state:add",
53
- "telemeister:state:delete": "tsx src/cli/cli.ts state:delete",
54
- "telemeister:state:sync": "tsx src/cli/cli.ts state:sync",
55
- "telemeister:state:transition:add": "tsx src/cli/cli.ts state:transition:add",
56
- "telemeister:state:transition:delete": "tsx src/cli/cli.ts state:transition:delete",
57
- "telemeister:create-bot": "tsx src/cli/cli.ts create-bot",
58
- "state:add": "telemeister state:add",
59
- "state:delete": "telemeister state:delete",
60
- "state:sync": "telemeister state:sync",
61
- "state:transition:add": "telemeister state:transition:add",
62
- "state:transition:delete": "telemeister state:transition:delete",
63
- "prepublishOnly": "npm run build",
64
- "prepare": "husky",
65
- "lint": "eslint src/ scripts/",
66
- "lint:fix": "eslint src/ scripts/ --fix",
67
- "format": "prettier --write .",
68
- "format:check": "prettier --check ."
69
- },
36
+ "workspaces": [
37
+ "examples/*"
38
+ ],
70
39
  "lint-staged": {
71
40
  "*.{ts,tsx}": [
72
41
  "eslint --fix",
@@ -87,8 +56,7 @@
87
56
  ],
88
57
  "author": "George Khromchenko",
89
58
  "publishConfig": {
90
- "access": "public",
91
- "provenance": true
59
+ "access": "public"
92
60
  },
93
61
  "license": "GPL-3.0-only",
94
62
  "dependencies": {
@@ -110,6 +78,7 @@
110
78
  "@types/ejs": "^3.1.5",
111
79
  "@types/express": "^5.0.0",
112
80
  "@types/node": "^25.0.6",
81
+ "esbuild": "^0.27.3",
113
82
  "eslint": "^9.39.2",
114
83
  "eslint-config-prettier": "^10.1.8",
115
84
  "husky": "^9.1.7",
@@ -119,5 +88,15 @@
119
88
  "tsx": "^4.21.0",
120
89
  "typescript": "^5.9.3",
121
90
  "typescript-eslint": "^8.39.0"
91
+ },
92
+ "scripts": {
93
+ "build": "tsc && cp -r src/templates dist/templates && pnpm run build:cli",
94
+ "build:cli": "esbuild dist/cli/cli.js --bundle --platform=node --format=esm --outfile=bin/telemeister-cli.js --external:typescript --external:tsx --external:prisma",
95
+ "example:recreate": "rm -rf examples/demo-bot && tsx src/cli/cli.ts create-bot demo-bot && mv demo-bot examples/demo-bot && npm pkg set --prefix examples/demo-bot dependencies.telemeister='workspace:*' && rm -rf examples/demo-bot/node_modules examples/demo-bot/pnpm-lock.yaml examples/demo-bot/dev.db examples/demo-bot/.env",
96
+ "lint": "eslint src/ scripts/",
97
+ "lint:fix": "eslint src/ scripts/ --fix",
98
+ "format": "prettier --write .",
99
+ "format:check": "prettier --check .",
100
+ "release": "./scripts/release.sh"
122
101
  }
123
- }
102
+ }
@@ -143,9 +143,9 @@ export async function createOrder(
143
143
  const user = await prisma.user.findUnique({
144
144
  where: { telegramId },
145
145
  });
146
-
146
+
147
147
  if (!user) throw new Error('User not found');
148
-
148
+
149
149
  return await prisma.order.create({
150
150
  data: {
151
151
  userId: user.id,
@@ -163,11 +163,11 @@ export async function getUserOrderStats(telegramId: string) {
163
163
  orders: true,
164
164
  },
165
165
  });
166
-
166
+
167
167
  if (!user) return { count: 0, total: 0 };
168
-
168
+
169
169
  const total = user.orders.reduce((sum, order) => sum + order.amount, 0);
170
-
170
+
171
171
  return {
172
172
  count: user.orders.length,
173
173
  total,
@@ -181,6 +181,7 @@ Use in handlers:
181
181
 
182
182
  ```typescript
183
183
  import { appBuilder, type AppContext } from 'telemeister/core';
184
+ import type { Context } from 'grammy';
184
185
  import type { MenuTransitions } from '../../bot-state-types.js';
185
186
  import { getUserWithOrders, createOrder } from '../../lib/database.js';
186
187
 
@@ -188,17 +189,17 @@ appBuilder
188
189
  .forState('menu')
189
190
  .onEnter(async (context: AppContext): MenuTransitions => {
190
191
  const user = await getUserWithOrders(String(context.telegramId));
191
-
192
+
192
193
  if (user?.orders.length) {
193
- await context.send(`You have ${user.orders.length} orders!`);
194
+ await context.ctx.reply(`You have ${user.orders.length} orders!`);
194
195
  } else {
195
- await context.send('Welcome! You have no orders yet.');
196
+ await context.ctx.reply('Welcome! You have no orders yet.');
196
197
  }
197
198
  })
198
- .onResponse(async (context: AppContext, response): MenuTransitions => {
199
- if (response === 'order') {
199
+ .onResponse(async (context: AppContext, ctx: Context): MenuTransitions => {
200
+ if (ctx.message?.text === 'order') {
200
201
  await createOrder(String(context.telegramId), 'Product A', 99.99);
201
- await context.send('Order created!');
202
+ await ctx.reply('Order created!');
202
203
  }
203
204
  });
204
205
 
@@ -17,7 +17,23 @@ appBuilder
17
17
  // Called when user enters this state
18
18
  // Can optionally return a state name to immediately transition
19
19
 
20
- await context.send("Hello from <%= stateName %> state!");
20
+ // Access Grammy context via context.ctx
21
+ // See Grammy docs: https://grammy.dev/guide/context
22
+ await context.ctx.reply("Hello from <%= stateName %> state!");
23
+
24
+ // === INLINE KEYBOARD EXAMPLE ===
25
+ // import { InlineKeyboard } from 'grammy';
26
+ // const keyboard = new InlineKeyboard()
27
+ // .text('Button 1', 'btn1')
28
+ // .text('Button 2', 'btn2');
29
+ // await context.ctx.reply('Choose:', { reply_markup: keyboard });
30
+
31
+ // === POLL EXAMPLE ===
32
+ // await context.ctx.replyWithPoll(
33
+ // 'Question?',
34
+ // ['Option A', 'Option B', 'Option C'],
35
+ // { is_anonymous: false }
36
+ // );
21
37
 
22
38
  // Database helpers (see src/lib/database.ts):
23
39
  // import { getUserByTelegramId, createOrUpdateUser } from '../../lib/database.js';
@@ -30,20 +46,66 @@ appBuilder
30
46
  <% } %>
31
47
  })
32
48
  <% if (transitionStates.length > 0) { %>
33
- .onResponse(async (context: AppContext, response): <%= pascalCase(stateName) %>Transitions => {
49
+ .onResponse(async (context: AppContext): <%= pascalCase(stateName) %>Transitions => {
34
50
  <% } else { %>
35
- .onResponse(async (context: AppContext, response) => {
51
+ .onResponse(async (context: AppContext) => {
36
52
  <% } %>
37
- // Called when user sends a message in this state
53
+ // Called when user sends any update in this state (message, callback, poll, etc.)
38
54
  // Return a state name to transition, or nothing to stay
39
55
 
40
- const text = response.trim();
56
+ // Handle different update types via Grammy context (context.ctx):
57
+ // - context.ctx.message?.text - text messages
58
+ // - context.ctx.callbackQuery?.data - inline button callbacks
59
+ // - context.ctx.message?.photo - photo messages
60
+ // - context.ctx.pollAnswer - poll responses
61
+ // See Grammy docs: https://grammy.dev/guide/context
62
+
63
+ // === INLINE KEYBOARD CALLBACK EXAMPLE ===
64
+ // if (context.ctx.callbackQuery?.data) {
65
+ // await context.ctx.answerCallbackQuery();
66
+ // const data = context.ctx.callbackQuery.data;
67
+ // switch (data) {
68
+ // case 'btn1':
69
+ // await context.ctx.reply('You clicked Button 1!');
70
+ // break;
71
+ // case 'btn2':
72
+ // return 'otherState'; // Transition to another state
73
+ // }
74
+ // return;
75
+ // }
76
+
77
+ // === POLL ANSWER EXAMPLE ===
78
+ // if (context.ctx.pollAnswer) {
79
+ // const optionIds = context.ctx.pollAnswer.option_ids;
80
+ // const options = ['Option A', 'Option B', 'Option C'];
81
+ // const selected = optionIds.map(id => options[id]).join(', ');
82
+ // await context.ctx.reply(`You voted for: ${selected}`);
83
+ // return;
84
+ // }
85
+
86
+ // === COMMAND HANDLING EXAMPLE ===
87
+ // const text = context.ctx.message?.text?.trim();
88
+ // if (text?.startsWith('/')) {
89
+ // const command = text.split(' ')[0].toLowerCase();
90
+ // switch (command) {
91
+ // case '/start':
92
+ // await context.ctx.reply('Welcome!');
93
+ // return 'welcome';
94
+ // case '/menu':
95
+ // return 'menu';
96
+ // default:
97
+ // await context.ctx.reply(`Unknown command: ${command}`);
98
+ // }
99
+ // return;
100
+ // }
101
+
102
+ const text = context.ctx.message?.text?.trim();
103
+ if (text) {
104
+ await context.ctx.reply(`You said: ${text}`);
105
+ }
41
106
  <% if (transitionStates.length > 0) { %>
42
107
  // Available transitions: <%= transitionStates.join(', ') %>
43
108
  <% } %>
44
- // Handle user response
45
- await context.send(`You said: ${text}`);
46
-
47
109
  // Database helpers (see src/lib/database.ts):
48
110
  // import { updateUserState } from '../../lib/database.js';
49
111
  // await updateUserState(String(context.telegramId), 'nextState', { lastMessage: text });
@@ -4,11 +4,18 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "tsx watch src/index.ts",
7
+ "dev:webhook": "BOT_MODE=webhook tsx watch src/index.ts",
7
8
  "build": "tsc",
8
9
  "start": "node dist/index.js",
10
+ "start:webhook": "BOT_MODE=webhook node dist/index.js",
9
11
  "db:generate": "prisma generate",
10
12
  "db:migrate": "prisma migrate dev",
13
+ "db:deploy": "prisma migrate deploy",
14
+ "db:push": "prisma db push",
11
15
  "db:studio": "prisma studio",
16
+ "webhook:set": "tsx scripts/set-webhook.ts",
17
+ "webhook:delete": "tsx scripts/delete-webhook.ts",
18
+ "webhook:info": "tsx scripts/webhook-info.ts",
12
19
  "state:add": "telemeister state:add",
13
20
  "state:delete": "telemeister state:delete",
14
21
  "state:sync": "telemeister state:sync",