create-teact 0.1.0-alpha.1

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 (3) hide show
  1. package/README.md +84 -0
  2. package/index.ts +559 -0
  3. package/package.json +14 -0
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # create-teact
2
+
3
+ Interactive project scaffolder for Teact. The fastest way to start a new Telegram bot.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ bun create teact my-bot
9
+ ```
10
+
11
+ Or with other package managers:
12
+
13
+ ```bash
14
+ npx create-teact my-bot
15
+ pnpm create teact my-bot
16
+ ```
17
+
18
+ ## What It Does
19
+
20
+ 1. Prompts for a project name (if not provided as an argument)
21
+ 2. Lets you pick a template: `router`, `counter`, `full`, or `empty`
22
+ 3. Lets you select features: storage, conversations, streaming, auth, i18n, payments
23
+ 4. Asks which package manager to use (`bun`, `npm`, or `pnpm`)
24
+ 5. Asks whether to install dependencies
25
+ 6. Generates the project with `package.json`, `tsconfig.json`, `teact.config.ts`, `.env`, `.gitignore`, and `src/` files
26
+ 7. Feature pages are wired into the router and main menu automatically
27
+
28
+ ## Templates
29
+
30
+ | Template | Description |
31
+ |----------|-------------|
32
+ | `router` | Multi-page bot with routing and pages directory (recommended) |
33
+ | `counter` | Minimal counter bot with inline keyboard |
34
+ | `full` | Router + all features pre-wired (storage, conversations, streaming, auth, i18n, payments) |
35
+ | `empty` | Bare project with no pages |
36
+
37
+ ## Features
38
+
39
+ When you pick the `router` template, you can opt into features:
40
+
41
+ | Feature | What it adds |
42
+ |---------|-------------|
43
+ | Storage | `teact.config.ts` with `storagePlugin`, Settings page with `useStorage` |
44
+ | Conversations | `conversationsPlugin` in config |
45
+ | Streaming | `streamPlugin` in config, StreamDemo page with `useStream` |
46
+ | Auth | `authPlugin` in config |
47
+ | i18n | Locale JSON files, LanguagePage with `useLocale`, `createI18n` in index |
48
+ | Payments | StorePage with `useInvoice`, `PAYMENT_PROVIDER_TOKEN` in `.env` |
49
+
50
+ ## Generated Structure
51
+
52
+ ```
53
+ my-bot/
54
+ ├── teact.config.ts # Plugin configuration
55
+ ├── package.json
56
+ ├── tsconfig.json
57
+ ├── .env # Bot token + provider token
58
+ ├── .gitignore
59
+ └── src/
60
+ ├── index.tsx # Bot entry — router, commands, providers
61
+ ├── pages/
62
+ │ ├── MainMenu.tsx # Home page with feature buttons
63
+ │ ├── About.tsx
64
+ │ ├── Settings.tsx # (storage)
65
+ │ ├── StreamDemo.tsx # (streaming)
66
+ │ ├── LanguagePage.tsx # (i18n)
67
+ │ └── StorePage.tsx # (payments)
68
+ └── locales/ # (i18n)
69
+ ├── en.json
70
+ └── am.json
71
+ ```
72
+
73
+ ## After Scaffolding
74
+
75
+ ```bash
76
+ cd my-bot
77
+ # Add your bot token to .env
78
+ bun dev
79
+ ```
80
+
81
+ ## See Also
82
+
83
+ - [`@teactjs/cli`](../cli) for the full CLI tool
84
+ - [Root README](../../README.md) for getting started
package/index.ts ADDED
@@ -0,0 +1,559 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { mkdirSync, writeFileSync, existsSync } from 'fs';
4
+ import { resolve, join } from 'path';
5
+ import * as p from '@clack/prompts';
6
+
7
+ const args = process.argv.slice(2);
8
+
9
+ async function main() {
10
+ p.intro('create-teact');
11
+
12
+ const nameArg = args[0];
13
+ const name = nameArg ?? (await p.text({
14
+ message: 'Project name',
15
+ placeholder: 'my-bot',
16
+ validate(v) {
17
+ if (!v) return 'Name is required';
18
+ if (/[^a-z0-9-_]/.test(v)) return 'Use lowercase letters, numbers, hyphens, or underscores';
19
+ },
20
+ }) as string);
21
+
22
+ if (p.isCancel(name)) { p.cancel('Cancelled'); process.exit(0); }
23
+
24
+ const dir = resolve(process.cwd(), name);
25
+ if (existsSync(dir)) {
26
+ p.log.error(`Directory "${name}" already exists`);
27
+ process.exit(1);
28
+ }
29
+
30
+ const template = await p.select({
31
+ message: 'Pick a template',
32
+ options: [
33
+ { value: 'router', label: 'Router', hint: 'multi-page bot with navigation (recommended)' },
34
+ { value: 'counter', label: 'Counter', hint: 'simple stateful counter' },
35
+ { value: 'full', label: 'Full-Stack', hint: 'router + all features pre-wired' },
36
+ { value: 'empty', label: 'Empty', hint: 'bare minimum' },
37
+ ],
38
+ }) as string;
39
+
40
+ if (p.isCancel(template)) { p.cancel('Cancelled'); process.exit(0); }
41
+
42
+ const features = await p.multiselect({
43
+ message: 'Select features',
44
+ options: [
45
+ { value: 'storage', label: 'Storage', hint: 'persistent state with useStorage' },
46
+ { value: 'conversations', label: 'Conversations', hint: 'multi-step flows' },
47
+ { value: 'streaming', label: 'Streaming', hint: 'live text updates with useStream' },
48
+ { value: 'auth', label: 'Auth', hint: 'role-based access + useAuthSession' },
49
+ { value: 'i18n', label: 'Internationalization', hint: 'multi-language with i18next' },
50
+ { value: 'payments', label: 'Payments', hint: 'Telegram payments with useInvoice' },
51
+ ],
52
+ initialValues: template === 'full' ? ['storage', 'conversations', 'streaming', 'auth', 'i18n', 'payments'] : [],
53
+ required: false,
54
+ }) as string[];
55
+
56
+ if (p.isCancel(features)) { p.cancel('Cancelled'); process.exit(0); }
57
+
58
+ const pm = await p.select({
59
+ message: 'Package manager',
60
+ options: [
61
+ { value: 'bun', label: 'bun', hint: 'recommended' },
62
+ { value: 'npm', label: 'npm' },
63
+ { value: 'pnpm', label: 'pnpm' },
64
+ ],
65
+ }) as string;
66
+
67
+ if (p.isCancel(pm)) { p.cancel('Cancelled'); process.exit(0); }
68
+
69
+ const shouldInstall = await p.confirm({ message: 'Install dependencies?' });
70
+ if (p.isCancel(shouldInstall)) { p.cancel('Cancelled'); process.exit(0); }
71
+
72
+ const spin = p.spinner();
73
+ spin.start('Generating project');
74
+
75
+ mkdirSync(join(dir, 'src', 'pages'), { recursive: true });
76
+
77
+ const has = (f: string) => template === 'full' || features.includes(f);
78
+
79
+ const deps: Record<string, string> = {
80
+ '@teactjs/core': '^0.1.0-alpha.1',
81
+ '@teactjs/ui': '^0.1.0-alpha.1',
82
+ '@teactjs/telegram': '^0.1.0-alpha.1',
83
+ '@teactjs/cli': '^0.1.0-alpha.1',
84
+ react: '^19.0.0',
85
+ };
86
+ if (has('storage')) deps['@teactjs/storage'] = '^0.1.0-alpha.1';
87
+ if (has('i18n')) {
88
+ deps['i18next'] = '^23.0.0';
89
+ deps['react-i18next'] = '^15.0.0';
90
+ }
91
+
92
+ writeFileSync(join(dir, 'package.json'), JSON.stringify({
93
+ name,
94
+ version: '0.1.0',
95
+ private: true,
96
+ type: 'module',
97
+ scripts: {
98
+ dev: 'teact dev',
99
+ build: 'teact build',
100
+ start: 'bun run dist/index.js',
101
+ },
102
+ dependencies: deps,
103
+ devDependencies: {
104
+ typescript: '^5.7.0',
105
+ '@types/bun': 'latest',
106
+ '@types/react': '^19.0.0',
107
+ },
108
+ }, null, 2));
109
+
110
+ writeFileSync(join(dir, 'tsconfig.json'), JSON.stringify({
111
+ compilerOptions: {
112
+ target: 'ES2022',
113
+ module: 'ESNext',
114
+ moduleResolution: 'bundler',
115
+ lib: ['ES2022'],
116
+ jsx: 'react-jsx',
117
+ jsxImportSource: 'react',
118
+ strict: true,
119
+ esModuleInterop: true,
120
+ skipLibCheck: true,
121
+ resolveJsonModule: true,
122
+ },
123
+ include: ['src'],
124
+ }, null, 2));
125
+
126
+ let envContent = 'TELEGRAM_BOT_TOKEN=\n';
127
+ if (has('payments')) envContent += 'PAYMENT_PROVIDER_TOKEN=\n';
128
+
129
+ writeFileSync(join(dir, '.env'), envContent);
130
+ writeFileSync(join(dir, '.gitignore'), 'node_modules/\ndist/\n.env\n.teact/\n*.log\n.DS_Store\n');
131
+
132
+ const files = buildFiles(template, features);
133
+ for (const [filePath, content] of Object.entries(files)) {
134
+ const fullPath = join(dir, filePath);
135
+ mkdirSync(resolve(fullPath, '..'), { recursive: true });
136
+ writeFileSync(fullPath, content);
137
+ }
138
+
139
+ spin.stop('Project generated');
140
+
141
+ if (shouldInstall) {
142
+ const installSpin = p.spinner();
143
+ installSpin.start(`Installing with ${pm}`);
144
+ try {
145
+ const proc = Bun.spawnSync([pm, 'install'], { cwd: dir, stderr: 'pipe', stdout: 'pipe' });
146
+ installSpin.stop(proc.exitCode === 0 ? 'Dependencies installed' : 'Install failed — run manually');
147
+ } catch {
148
+ installSpin.stop('Install failed — run manually');
149
+ }
150
+ }
151
+
152
+ const runCmd = pm === 'bun' ? 'bun dev' : `${pm} run dev`;
153
+ p.note(
154
+ [
155
+ `cd ${name}`,
156
+ '# Add TELEGRAM_BOT_TOKEN to .env',
157
+ runCmd,
158
+ ].join('\n'),
159
+ 'Next steps',
160
+ );
161
+
162
+ p.outro('Happy building!');
163
+ }
164
+
165
+ // ---- File generators ----
166
+
167
+ function buildFiles(template: string, features: string[]): Record<string, string> {
168
+ const has = (f: string) => template === 'full' || features.includes(f);
169
+
170
+ if (template === 'counter') return counterFiles();
171
+ if (template === 'empty') return emptyFiles();
172
+
173
+ return routerFiles(has);
174
+ }
175
+
176
+ function emptyFiles(): Record<string, string> {
177
+ return {
178
+ 'src/index.tsx': `import { createBot } from '@teactjs/core';
179
+ import { Message } from '@teactjs/ui';
180
+ import { TelegramAdapter } from '@teactjs/telegram';
181
+
182
+ function App() {
183
+ return <Message text="Hello from Teact!" />;
184
+ }
185
+
186
+ const bot = createBot({
187
+ component: App,
188
+ adapter: new TelegramAdapter(),
189
+ commands: { start: { description: 'Start the bot' } },
190
+ });
191
+
192
+ bot.start();
193
+ `,
194
+ };
195
+ }
196
+
197
+ function counterFiles(): Record<string, string> {
198
+ return {
199
+ 'src/index.tsx': `import { useState } from 'react';
200
+ import { createBot } from '@teactjs/core';
201
+ import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
202
+ import { TelegramAdapter } from '@teactjs/telegram';
203
+
204
+ function App() {
205
+ const [count, setCount] = useState(0);
206
+
207
+ return (
208
+ <Message text={\`Count: \${count}\`}>
209
+ <InlineKeyboard>
210
+ <ButtonRow>
211
+ <Button text="-1" onClick={() => setCount(c => c - 1)} />
212
+ <Button text={\`[ \${count} ]\`} onClick={() => {}} />
213
+ <Button text="+1" onClick={() => setCount(c => c + 1)} />
214
+ </ButtonRow>
215
+ <ButtonRow>
216
+ <Button text="Reset" onClick={() => setCount(0)} />
217
+ </ButtonRow>
218
+ </InlineKeyboard>
219
+ </Message>
220
+ );
221
+ }
222
+
223
+ const bot = createBot({
224
+ component: App,
225
+ adapter: new TelegramAdapter(),
226
+ commands: { start: { description: 'Start the counter' } },
227
+ });
228
+
229
+ bot.start();
230
+ `,
231
+ };
232
+ }
233
+
234
+ function routerFiles(has: (f: string) => boolean): Record<string, string> {
235
+ const files: Record<string, string> = {};
236
+
237
+ // ---- teact.config.ts ----
238
+ const configPlugins: string[] = [];
239
+ const configImportsCore: string[] = ['defineConfig'];
240
+ const configImportsTelegram: string[] = [];
241
+ const configImportsStorage: string[] = [];
242
+
243
+ if (has('storage')) {
244
+ configImportsStorage.push('storagePlugin');
245
+ configPlugins.push("storagePlugin({ driver: 'file', path: '.teact/storage.json' })");
246
+ }
247
+ if (has('conversations')) {
248
+ configImportsTelegram.push('conversationsPlugin');
249
+ configPlugins.push('conversationsPlugin()');
250
+ }
251
+ if (has('streaming')) {
252
+ configImportsTelegram.push('streamPlugin');
253
+ configPlugins.push('streamPlugin()');
254
+ }
255
+ if (has('auth')) {
256
+ configImportsCore.push('authPlugin');
257
+ configPlugins.push('authPlugin({ admins: [] })');
258
+ }
259
+
260
+ let configSrc = `import { ${configImportsCore.join(', ')} } from '@teactjs/core';\n`;
261
+ if (configImportsTelegram.length > 0) {
262
+ configSrc += `import { ${configImportsTelegram.join(', ')} } from '@teactjs/telegram';\n`;
263
+ }
264
+ if (configImportsStorage.length > 0) {
265
+ configSrc += `import { ${configImportsStorage.join(', ')} } from '@teactjs/storage';\n`;
266
+ }
267
+ configSrc += `\nexport default defineConfig({\n mode: 'polling',\n`;
268
+ if (configPlugins.length > 0) {
269
+ configSrc += `\n plugins: [\n ${configPlugins.join(',\n ')},\n ],\n`;
270
+ }
271
+ configSrc += `});\n`;
272
+ files['teact.config.ts'] = configSrc;
273
+
274
+ // ---- Routes and pages ----
275
+ const routes: Array<{ path: string; component: string; file: string }> = [
276
+ { path: '/', component: 'MainMenu', file: './pages/MainMenu' },
277
+ { path: '/about', component: 'About', file: './pages/About' },
278
+ ];
279
+ const commands: Record<string, string> = {
280
+ start: "'/'",
281
+ about: "'/about'",
282
+ };
283
+
284
+ if (has('storage')) {
285
+ routes.push({ path: '/settings', component: 'Settings', file: './pages/Settings' });
286
+ commands['settings'] = "'/settings'";
287
+ }
288
+ if (has('streaming')) {
289
+ routes.push({ path: '/stream', component: 'StreamDemo', file: './pages/StreamDemo' });
290
+ commands['stream'] = "'/stream'";
291
+ }
292
+ if (has('i18n')) {
293
+ routes.push({ path: '/language', component: 'LanguagePage', file: './pages/LanguagePage' });
294
+ commands['language'] = "'/language'";
295
+ }
296
+ if (has('payments')) {
297
+ routes.push({ path: '/store', component: 'StorePage', file: './pages/StorePage' });
298
+ commands['store'] = "'/store'";
299
+ }
300
+
301
+ // ---- src/index.tsx ----
302
+ let indexSrc = `import React from 'react';\n`;
303
+ indexSrc += `import { createBot, createRouter${has('i18n') ? ', createI18n' : ''} } from '@teactjs/core';\n`;
304
+ indexSrc += `import { TelegramAdapter } from '@teactjs/telegram';\n\n`;
305
+
306
+ for (const r of routes) {
307
+ indexSrc += `import { ${r.component} } from '${r.file}';\n`;
308
+ }
309
+
310
+ if (has('i18n')) {
311
+ indexSrc += `\nimport en from './locales/en.json';\nimport am from './locales/am.json';\n`;
312
+ indexSrc += `\nconst i18n = createI18n({\n defaultLocale: 'en',\n resources: {\n en: { translation: en },\n am: { translation: am },\n },\n});\n`;
313
+ }
314
+
315
+ indexSrc += `\nconst router = createRouter({\n`;
316
+ for (const r of routes) {
317
+ indexSrc += ` '${r.path}': ${r.component},\n`;
318
+ }
319
+ indexSrc += `});\n`;
320
+
321
+ indexSrc += `\nconst bot = createBot({\n adapter: new TelegramAdapter(),\n router,\n`;
322
+ if (has('i18n')) {
323
+ indexSrc += ` providers: ({ children }) => (\n <i18n.Provider>{children}</i18n.Provider>\n ),\n`;
324
+ }
325
+ indexSrc += ` commands: {\n`;
326
+ for (const [cmd, route] of Object.entries(commands)) {
327
+ indexSrc += ` ${cmd}: { description: '${cmd.charAt(0).toUpperCase() + cmd.slice(1)}', route: ${route} },\n`;
328
+ }
329
+ indexSrc += ` },\n});\n\nbot.start();\n`;
330
+ files['src/index.tsx'] = indexSrc;
331
+
332
+ // ---- MainMenu ----
333
+ const menuButtons: string[] = [];
334
+ menuButtons.push(` <Button text="ℹ️ About" onClick={() => navigate('/about')} />`);
335
+ if (has('storage')) menuButtons.push(` <Button text="⚙️ Settings" onClick={() => navigate('/settings')} />`);
336
+ if (has('streaming')) menuButtons.push(` <Button text="⚡ Stream Demo" onClick={() => navigate('/stream')} />`);
337
+ if (has('i18n')) menuButtons.push(` <Button text="🌐 Language" onClick={() => navigate('/language')} />`);
338
+ if (has('payments')) menuButtons.push(` <Button text="💎 Store" onClick={() => navigate('/store')} />`);
339
+
340
+ const menuRows: string[] = [];
341
+ for (let i = 0; i < menuButtons.length; i += 2) {
342
+ const pair = menuButtons.slice(i, i + 2);
343
+ menuRows.push(` <ButtonRow>\n${pair.join('\n')}\n </ButtonRow>`);
344
+ }
345
+
346
+ files['src/pages/MainMenu.tsx'] = `import { useNavigate } from '@teactjs/core';
347
+ import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
348
+
349
+ export function MainMenu() {
350
+ const navigate = useNavigate();
351
+
352
+ return (
353
+ <Message text="🤖 Welcome!\\n\\nPick an option:">
354
+ <InlineKeyboard>
355
+ ${menuRows.join('\n')}
356
+ </InlineKeyboard>
357
+ </Message>
358
+ );
359
+ }
360
+ `;
361
+
362
+ // ---- About ----
363
+ files['src/pages/About.tsx'] = `import { useNavigate } from '@teactjs/core';
364
+ import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
365
+
366
+ export function About() {
367
+ const navigate = useNavigate();
368
+
369
+ return (
370
+ <Message text="ℹ️ About\\n\\nBuilt with Teact — React for Telegram bots.\\nhttps://github.com/leuliance/teact">
371
+ <InlineKeyboard>
372
+ <ButtonRow>
373
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
374
+ </ButtonRow>
375
+ </InlineKeyboard>
376
+ </Message>
377
+ );
378
+ }
379
+ `;
380
+
381
+ // ---- Settings (storage) ----
382
+ if (has('storage')) {
383
+ files['src/pages/Settings.tsx'] = `import { useNavigate } from '@teactjs/core';
384
+ import { useStorage } from '@teactjs/storage';
385
+ import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
386
+
387
+ export function Settings() {
388
+ const navigate = useNavigate();
389
+ const [theme, setTheme] = useStorage('theme', 'light');
390
+ const [notify, setNotify] = useStorage('notifications', true);
391
+
392
+ return (
393
+ <Message text={\`⚙️ Settings\\n\\n🎨 Theme: \${theme}\\n🔔 Notifications: \${notify ? 'On' : 'Off'}\`}>
394
+ <InlineKeyboard>
395
+ <ButtonRow>
396
+ <Button text={\`🎨 \${theme}\`} onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')} />
397
+ <Button text={\`🔔 \${notify ? 'On' : 'Off'}\`} onClick={() => setNotify(n => !n)} />
398
+ </ButtonRow>
399
+ <ButtonRow>
400
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
401
+ </ButtonRow>
402
+ </InlineKeyboard>
403
+ </Message>
404
+ );
405
+ }
406
+ `;
407
+ }
408
+
409
+ // ---- StreamDemo ----
410
+ if (has('streaming')) {
411
+ files['src/pages/StreamDemo.tsx'] = `import { useNavigate, useStream } from '@teactjs/core';
412
+ import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
413
+
414
+ const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
415
+
416
+ async function* generateText() {
417
+ yield 'Generating';
418
+ await delay(500);
419
+ yield ' your ';
420
+ await delay(500);
421
+ yield 'response...\\n\\n';
422
+ await delay(600);
423
+ yield 'Hello from the stream!';
424
+ }
425
+
426
+ export function StreamDemo() {
427
+ const navigate = useNavigate();
428
+ const { text, isStreaming, stream } = useStream({ throttleMs: 400 });
429
+
430
+ return (
431
+ <Message text={text || '⚡ Stream Demo\\n\\nWatch text generate live!'}>
432
+ <InlineKeyboard>
433
+ <ButtonRow>
434
+ <Button
435
+ text={isStreaming ? '⏳ Streaming...' : '▶️ Start Stream'}
436
+ onClick={() => { if (!isStreaming) stream(generateText()); }}
437
+ />
438
+ </ButtonRow>
439
+ <ButtonRow>
440
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
441
+ </ButtonRow>
442
+ </InlineKeyboard>
443
+ </Message>
444
+ );
445
+ }
446
+ `;
447
+ }
448
+
449
+ // ---- LanguagePage (i18n) ----
450
+ if (has('i18n')) {
451
+ files['src/locales/en.json'] = `{
452
+ "welcome": "Welcome!",
453
+ "about": "About this bot",
454
+ "settings": "Settings",
455
+ "language": "Language",
456
+ "back": "Back"
457
+ }
458
+ `;
459
+ files['src/locales/am.json'] = `{
460
+ "welcome": "እንኳን ደህና መጡ!",
461
+ "about": "ስለ ቦት",
462
+ "settings": "ቅንብሮች",
463
+ "language": "ቋንቋ",
464
+ "back": "ተመለስ"
465
+ }
466
+ `;
467
+ files['src/pages/LanguagePage.tsx'] = `import { useNavigate, useLocale } from '@teactjs/core';
468
+ import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
469
+
470
+ export function LanguagePage() {
471
+ const navigate = useNavigate();
472
+ const { t, locale, setLocale } = useLocale();
473
+
474
+ return (
475
+ <Message text={\`🌐 \${t('language')}\\n\\nCurrent: \${locale}\`}>
476
+ <InlineKeyboard>
477
+ <ButtonRow>
478
+ <Button text="🇬🇧 English" onClick={() => setLocale('en')} />
479
+ <Button text="🇪🇹 አማርኛ" onClick={() => setLocale('am')} />
480
+ </ButtonRow>
481
+ <ButtonRow>
482
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
483
+ </ButtonRow>
484
+ </InlineKeyboard>
485
+ </Message>
486
+ );
487
+ }
488
+ `;
489
+ }
490
+
491
+ // ---- StorePage (payments) ----
492
+ if (has('payments')) {
493
+ files['src/pages/StorePage.tsx'] = `import { useNavigate, useInvoice } from '@teactjs/core';
494
+ import { Message, Button, InlineKeyboard, ButtonRow, Alert } from '@teactjs/ui';
495
+
496
+ export function StorePage() {
497
+ const navigate = useNavigate();
498
+ const invoice = useInvoice({
499
+ title: 'Premium Access',
500
+ description: 'Unlock all features for 30 days',
501
+ payload: 'premium-30d',
502
+ providerToken: process.env.PAYMENT_PROVIDER_TOKEN ?? '',
503
+ currency: 'USD',
504
+ prices: [{ label: 'Premium (30 days)', amount: 499 }],
505
+ });
506
+
507
+ if (invoice.status === 'success') {
508
+ return (
509
+ <Message text={\`✅ Payment successful!\\n\\nCharge ID: \${invoice.receipt?.telegramPaymentChargeId}\`}>
510
+ <InlineKeyboard>
511
+ <ButtonRow>
512
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
513
+ </ButtonRow>
514
+ </InlineKeyboard>
515
+ </Message>
516
+ );
517
+ }
518
+
519
+ if (invoice.status === 'failed') {
520
+ return (
521
+ <>
522
+ <Alert variant="error" title="Payment Failed">
523
+ {invoice.error ?? 'Unknown error'}
524
+ </Alert>
525
+ <Message text="Please check your payment configuration.">
526
+ <InlineKeyboard>
527
+ <ButtonRow>
528
+ <Button text="🔄 Retry" onClick={invoice.send} />
529
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
530
+ </ButtonRow>
531
+ </InlineKeyboard>
532
+ </Message>
533
+ </>
534
+ );
535
+ }
536
+
537
+ return (
538
+ <Message text={\`💎 Premium Access\\n\\nUnlock all features.\\n💰 Price: $4.99\${invoice.status === 'pending' ? '\\n\\n⏳ Processing...' : ''}\`}>
539
+ <InlineKeyboard>
540
+ <ButtonRow>
541
+ <Button text="💳 Buy — $4.99" onClick={invoice.send} />
542
+ </ButtonRow>
543
+ <ButtonRow>
544
+ <Button text="🏠 Menu" onClick={() => navigate('/')} />
545
+ </ButtonRow>
546
+ </InlineKeyboard>
547
+ </Message>
548
+ );
549
+ }
550
+ `;
551
+ }
552
+
553
+ return files;
554
+ }
555
+
556
+ main().catch((err) => {
557
+ console.error(err);
558
+ process.exit(1);
559
+ });
package/package.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "create-teact",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Create a new Teact bot project — scaffolds a ready-to-run Telegram bot",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-teact": "index.ts"
8
+ },
9
+ "files": ["index.ts", "README.md"],
10
+ "dependencies": {
11
+ "@clack/prompts": "^1.1.0"
12
+ },
13
+ "license": "MIT"
14
+ }