@fluxy-chat/create-fluxy-chat 0.1.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/dist/index.js ADDED
@@ -0,0 +1,946 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { intro, log, note, outro, spinner } from "@clack/prompts";
5
+ import pc from "picocolors";
6
+
7
+ // src/prompts.ts
8
+ import {
9
+ confirm,
10
+ isCancel,
11
+ select,
12
+ text
13
+ } from "@clack/prompts";
14
+
15
+ // src/utils.ts
16
+ import fs from "fs";
17
+ import path from "path";
18
+ var PACKAGE_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
19
+ var PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "yarn", "pnpm"]);
20
+ function validateProjectName(value) {
21
+ const name = value?.trim() ?? "";
22
+ if (!name) {
23
+ return "Project name is required";
24
+ }
25
+ if (name.startsWith(".") || name.startsWith("_") || name.includes("..") || !PACKAGE_NAME_PATTERN.test(name)) {
26
+ return "Use a valid npm package name (unscoped), like my-bot";
27
+ }
28
+ }
29
+ function isPackageManager(value) {
30
+ return PACKAGE_MANAGERS.has(value);
31
+ }
32
+ function isAdapterType(value) {
33
+ return ["basic", "slack", "telegram", "discord", "web"].includes(value);
34
+ }
35
+ function detectPackageManagerFromLockfiles(cwd) {
36
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
37
+ return "pnpm";
38
+ }
39
+ if (fs.existsSync(path.join(cwd, "yarn.lock"))) {
40
+ return "yarn";
41
+ }
42
+ return "npm";
43
+ }
44
+ function detectPackageManager(userAgent = "") {
45
+ if (userAgent.startsWith("pnpm")) {
46
+ return "pnpm";
47
+ }
48
+ if (userAgent.startsWith("yarn")) {
49
+ return "yarn";
50
+ }
51
+ return "npm";
52
+ }
53
+ function installCommand(pm) {
54
+ switch (pm) {
55
+ case "pnpm":
56
+ return "pnpm install";
57
+ case "yarn":
58
+ return "yarn install";
59
+ default:
60
+ return "npm install";
61
+ }
62
+ }
63
+ function devCommand(pm) {
64
+ switch (pm) {
65
+ case "pnpm":
66
+ return "pnpm dev";
67
+ case "yarn":
68
+ return "yarn dev";
69
+ default:
70
+ return "npm run dev";
71
+ }
72
+ }
73
+ function writeFile(projectDir, filePath, content) {
74
+ const fullPath = path.join(projectDir, filePath);
75
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
76
+ fs.writeFileSync(fullPath, content);
77
+ }
78
+ function writeJson(projectDir, filePath, value) {
79
+ writeFile(projectDir, filePath, `${JSON.stringify(value, null, 2)}
80
+ `);
81
+ }
82
+
83
+ // src/prompts.ts
84
+ var DEFAULT_PROJECT_NAME = "my-fluxy-bot";
85
+ async function runPrompts(inputs) {
86
+ let name = inputs.name;
87
+ if (!name) {
88
+ if (inputs.yes) {
89
+ name = DEFAULT_PROJECT_NAME;
90
+ } else {
91
+ const result = await text({
92
+ message: "Project name:",
93
+ placeholder: DEFAULT_PROJECT_NAME,
94
+ validate: validateProjectName
95
+ });
96
+ if (isCancel(result)) return null;
97
+ name = String(result).trim();
98
+ }
99
+ }
100
+ const nameError = validateProjectName(name);
101
+ if (nameError) {
102
+ throw new Error(nameError);
103
+ }
104
+ let adapter = inputs.adapter;
105
+ if (!adapter) {
106
+ if (inputs.yes) {
107
+ adapter = "basic";
108
+ } else {
109
+ const result = await select({
110
+ message: "Select an adapter:",
111
+ options: [
112
+ { label: "Basic (Cloudflare Workers)", value: "basic" },
113
+ { label: "Slack", value: "slack" },
114
+ { label: "Telegram", value: "telegram" },
115
+ { label: "Discord", value: "discord" },
116
+ { label: "Web Chat", value: "web" }
117
+ ]
118
+ });
119
+ if (isCancel(result)) return null;
120
+ adapter = result;
121
+ }
122
+ }
123
+ let language = inputs.language;
124
+ if (!language) {
125
+ if (inputs.yes) {
126
+ language = "typescript";
127
+ } else {
128
+ const result = await select({
129
+ message: "Language:",
130
+ options: [
131
+ { label: "TypeScript", value: "typescript" },
132
+ { label: "JavaScript", value: "javascript" }
133
+ ]
134
+ });
135
+ if (isCancel(result)) return null;
136
+ language = result;
137
+ }
138
+ }
139
+ let packageManager = inputs.packageManager;
140
+ if (!packageManager) {
141
+ const detected = detectPackageManagerFromLockfiles(process.cwd()) || detectPackageManager(process.env.npm_config_user_agent);
142
+ if (inputs.yes) {
143
+ packageManager = detected;
144
+ } else {
145
+ const result = await select({
146
+ message: "Package manager:",
147
+ initialValue: detected,
148
+ options: [
149
+ { label: "npm", value: "npm" },
150
+ { label: "pnpm", value: "pnpm" },
151
+ { label: "yarn", value: "yarn" }
152
+ ]
153
+ });
154
+ if (isCancel(result)) return null;
155
+ packageManager = result;
156
+ }
157
+ }
158
+ const shouldInstall = inputs.shouldInstall ?? (inputs.yes ? true : await confirm({
159
+ message: "Install dependencies?",
160
+ initialValue: true
161
+ }));
162
+ if (isCancel(shouldInstall)) return null;
163
+ const shouldInitGit = inputs.shouldInitGit ?? (inputs.yes ? true : await confirm({
164
+ message: "Initialize git repository?",
165
+ initialValue: true
166
+ }));
167
+ if (isCancel(shouldInitGit)) return null;
168
+ return {
169
+ name,
170
+ adapter,
171
+ packageManager,
172
+ language,
173
+ shouldInstall,
174
+ shouldInitGit
175
+ };
176
+ }
177
+
178
+ // src/templates.ts
179
+ function generatePackageJson(config) {
180
+ const deps = {
181
+ "@fluxy-chat/sdk": "latest"
182
+ };
183
+ const devDeps = {
184
+ typescript: "^5.6.0",
185
+ "@cloudflare/workers-types": "^4.0.0",
186
+ wrangler: "^3.0.0"
187
+ };
188
+ switch (config.adapter) {
189
+ case "slack":
190
+ deps["@slack/web-api"] = "^7.0.0";
191
+ deps["@slack/bolt"] = "^4.0.0";
192
+ break;
193
+ case "telegram":
194
+ deps["node-telegram-bot-api"] = "^0.66.0";
195
+ break;
196
+ case "discord":
197
+ deps["discord.js"] = "^14.0.0";
198
+ break;
199
+ case "web":
200
+ break;
201
+ }
202
+ const scripts = {
203
+ dev: "wrangler dev",
204
+ deploy: "wrangler deploy",
205
+ "type-check": "tsc --noEmit"
206
+ };
207
+ return {
208
+ name: config.name,
209
+ version: "0.1.0",
210
+ type: "module",
211
+ private: true,
212
+ scripts,
213
+ dependencies: sortRecord(deps),
214
+ devDependencies: sortRecord(devDeps)
215
+ };
216
+ }
217
+ function sortRecord(record) {
218
+ return Object.fromEntries(
219
+ Object.entries(record).sort(([a], [b]) => a.localeCompare(b))
220
+ );
221
+ }
222
+ function generateTsConfig() {
223
+ return {
224
+ compilerOptions: {
225
+ target: "ES2022",
226
+ module: "ESNext",
227
+ moduleResolution: "bundler",
228
+ lib: ["ES2022"],
229
+ types: ["@cloudflare/workers-types"],
230
+ strict: true,
231
+ esModuleInterop: true,
232
+ skipLibCheck: true,
233
+ forceConsistentCasingInFileNames: true,
234
+ resolveJsonModule: true,
235
+ allowSyntheticDefaultImports: true,
236
+ noEmit: true
237
+ },
238
+ include: ["src/**/*"],
239
+ exclude: ["node_modules", "dist"]
240
+ };
241
+ }
242
+ function generateWranglerToml(config) {
243
+ return `name = "${config.name}"
244
+ main = "src/index.ts"
245
+ compatibility_date = "2024-12-01"
246
+
247
+ [vars]
248
+ FLUXY_BASE_URL = "https://your-fluxychat-worker.example.com"
249
+
250
+ # Set these via \`wrangler secret put\` for production
251
+ # wrangler secret put FLUXY_API_KEY
252
+ `;
253
+ }
254
+ function generateDevVars() {
255
+ return `# Local development environment variables
256
+ # Copy this file to .dev.vars and fill in your values
257
+
258
+ FLUXY_API_KEY=your-api-key-here
259
+ FLUXY_BASE_URL=http://localhost:8787
260
+ `;
261
+ }
262
+ function generateGitignore() {
263
+ return `node_modules/
264
+ dist/
265
+ .dev.vars
266
+ .wrangler/
267
+ *.log
268
+ .DS_Store
269
+ .env
270
+ .env.local
271
+ `;
272
+ }
273
+ function generateEnvExample(config) {
274
+ const lines = [
275
+ "# Bot Configuration",
276
+ `FLUXY_BASE_URL=https://your-fluxychat-worker.example.com`,
277
+ "FLUXY_API_KEY=your-api-key-here",
278
+ ""
279
+ ];
280
+ switch (config.adapter) {
281
+ case "slack":
282
+ lines.push(
283
+ "# Slack Configuration",
284
+ "SLACK_BOT_TOKEN=xoxb-your-bot-token",
285
+ "SLACK_SIGNING_SECRET=your-signing-secret",
286
+ "SLACK_PORT=3000",
287
+ ""
288
+ );
289
+ break;
290
+ case "telegram":
291
+ lines.push(
292
+ "# Telegram Configuration",
293
+ "TELEGRAM_BOT_TOKEN=your-telegram-bot-token",
294
+ "TELEGRAM_WEBHOOK_URL=https://your-worker.example.com/telegram/webhook",
295
+ ""
296
+ );
297
+ break;
298
+ case "discord":
299
+ lines.push(
300
+ "# Discord Configuration",
301
+ "DISCORD_BOT_TOKEN=your-discord-bot-token",
302
+ "DISCORD_APPLICATION_ID=your-application-id",
303
+ "DISCORD_PUBLIC_KEY=your-public-key",
304
+ ""
305
+ );
306
+ break;
307
+ case "web":
308
+ lines.push(
309
+ "# Web Chat Configuration",
310
+ "# The web adapter uses FluxyChat SDK directly, no extra secrets needed",
311
+ ""
312
+ );
313
+ break;
314
+ }
315
+ return `${lines.join("\n").trimEnd()}
316
+ `;
317
+ }
318
+ function generateReadme(config) {
319
+ const adapterTitle = config.adapter.charAt(0).toUpperCase() + config.adapter.slice(1);
320
+ const pm = config.packageManager;
321
+ const endpoints = [];
322
+ if (config.adapter === "basic") {
323
+ endpoints.push("- Health check: `/`");
324
+ endpoints.push("- Webhook: `/webhook`");
325
+ } else if (config.adapter === "slack") {
326
+ endpoints.push("- Slack Events API: `/slack/events`");
327
+ endpoints.push("- Slack Interactive: `/slack/interactive`");
328
+ } else if (config.adapter === "telegram") {
329
+ endpoints.push("- Telegram Webhook: `/telegram/webhook`");
330
+ } else if (config.adapter === "discord") {
331
+ endpoints.push("- Discord Interactions: `/discord/interactions`");
332
+ } else if (config.adapter === "web") {
333
+ endpoints.push("- Chat API: `/api/chat`");
334
+ endpoints.push("- Health: `/`");
335
+ }
336
+ return `# ${config.name}
337
+
338
+ A ${adapterTitle} bot built with [FluxyChat](https://github.com/AlessandroFare/fluxychat) and deployed on Cloudflare Workers.
339
+
340
+ ## Getting Started
341
+
342
+ 1. Copy the example environment file and fill in your credentials:
343
+
344
+ \`\`\`bash
345
+ cp .env.example .dev.vars
346
+ \`\`\`
347
+
348
+ 2. Start the dev server:
349
+
350
+ \`\`\`bash
351
+ ${devCommand(pm)}
352
+ \`\`\`
353
+
354
+ 3. Deploy to Cloudflare Workers:
355
+
356
+ \`\`\`bash
357
+ ${pm} run deploy
358
+ \`\`\`
359
+
360
+ ## Endpoints
361
+
362
+ ${endpoints.join("\n")}
363
+
364
+ ## Project Structure
365
+
366
+ \`\`\`
367
+ src/
368
+ index.ts Worker entry point
369
+ bot.ts Bot handler with ${adapterTitle} adapter
370
+ .dev.vars Local development environment variables
371
+ wrangler.toml Cloudflare Workers configuration
372
+ \`\`\`
373
+
374
+ ## Scripts
375
+
376
+ | Command | Description |
377
+ | --- | --- |
378
+ | \`${devCommand(pm)}\` | Start the development server |
379
+ | \`${pm} run deploy\` | Deploy to Cloudflare Workers |
380
+ | \`${pm} run type-check\` | Type-check the project |
381
+
382
+ ## Environment Variables
383
+
384
+ See \`.env.example\` for all required environment variables.
385
+
386
+ ## Learn More
387
+
388
+ - [FluxyChat SDK Documentation](https://github.com/AlessandroFare/fluxychat/tree/main/packages/sdk)
389
+ - [Cloudflare Workers Docs](https://developers.cloudflare.com/workers/)
390
+
391
+ ## License
392
+
393
+ MIT
394
+ `;
395
+ }
396
+ function generateWorkerIndex(config) {
397
+ switch (config.adapter) {
398
+ case "slack":
399
+ return `import { handleSlackRequest, createSlackBot } from "./bot.js";
400
+
401
+ const bot = createSlackBot();
402
+
403
+ export default {
404
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
405
+ const url = new URL(request.url);
406
+
407
+ if (url.pathname === "/slack/events" || url.pathname === "/slack/interactive") {
408
+ return handleSlackRequest(request, env, bot);
409
+ }
410
+
411
+ if (url.pathname === "/") {
412
+ return new Response("FluxyChat Slack bot is running!", { status: 200 });
413
+ }
414
+
415
+ return new Response("Not Found", { status: 404 });
416
+ },
417
+ };
418
+ `;
419
+ case "telegram":
420
+ return `import { handleTelegramUpdate } from "./bot.js";
421
+
422
+ export default {
423
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
424
+ const url = new URL(request.url);
425
+
426
+ if (url.pathname === "/telegram/webhook" && request.method === "POST") {
427
+ const update = await request.json();
428
+ return handleTelegramUpdate(update, env);
429
+ }
430
+
431
+ if (url.pathname === "/") {
432
+ return new Response("FluxyChat Telegram bot is running!", { status: 200 });
433
+ }
434
+
435
+ return new Response("Not Found", { status: 404 });
436
+ },
437
+ };
438
+ `;
439
+ case "discord":
440
+ return `import { handleDiscordInteraction } from "./bot.js";
441
+
442
+ export default {
443
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
444
+ const url = new URL(request.url);
445
+
446
+ if (url.pathname === "/discord/interactions" && request.method === "POST") {
447
+ return handleDiscordInteraction(request, env);
448
+ }
449
+
450
+ if (url.pathname === "/") {
451
+ return new Response("FluxyChat Discord bot is running!", { status: 200 });
452
+ }
453
+
454
+ return new Response("Not Found", { status: 404 });
455
+ },
456
+ };
457
+ `;
458
+ case "web":
459
+ return `import { handleWebChat } from "./bot.js";
460
+
461
+ export default {
462
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
463
+ const url = new URL(request.url);
464
+
465
+ if (url.pathname === "/api/chat" && request.method === "POST") {
466
+ return handleWebChat(request, env);
467
+ }
468
+
469
+ if (url.pathname === "/") {
470
+ return new Response("FluxyChat Web bot is running!", { status: 200 });
471
+ }
472
+
473
+ return new Response("Not Found", { status: 404 });
474
+ },
475
+ };
476
+ `;
477
+ default:
478
+ return `import { handleWebhook } from "./bot.js";
479
+
480
+ export default {
481
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
482
+ const url = new URL(request.url);
483
+
484
+ if (url.pathname === "/webhook" && request.method === "POST") {
485
+ return handleWebhook(request, env);
486
+ }
487
+
488
+ if (url.pathname === "/") {
489
+ return new Response("FluxyChat bot is running!", { status: 200 });
490
+ }
491
+
492
+ return new Response("Not Found", { status: 404 });
493
+ },
494
+ };
495
+ `;
496
+ }
497
+ }
498
+ function generateBotHandler(config) {
499
+ switch (config.adapter) {
500
+ case "slack":
501
+ return `import { FluxyChatClient } from "@fluxy-chat/sdk";
502
+
503
+ interface BotEnv {
504
+ FLUXY_BASE_URL: string;
505
+ FLUXY_API_KEY: string;
506
+ SLACK_BOT_TOKEN: string;
507
+ SLACK_SIGNING_SECRET: string;
508
+ }
509
+
510
+ export interface SlackBot {
511
+ fluxyClient: FluxyChatClient;
512
+ }
513
+
514
+ export function createSlackBot(): SlackBot {
515
+ return {
516
+ fluxyClient: null as unknown as FluxyChatClient,
517
+ };
518
+ }
519
+
520
+ export async function handleSlackRequest(
521
+ request: Request,
522
+ env: BotEnv,
523
+ bot: SlackBot,
524
+ ): Promise<Response> {
525
+ const body = await request.text();
526
+
527
+ // Initialize FluxyChat client
528
+ const client = new FluxyChatClient({
529
+ baseUrl: env.FLUXY_BASE_URL,
530
+ userId: "slack-bot",
531
+ apiKey: env.FLUXY_API_KEY,
532
+ });
533
+
534
+ try {
535
+ const payload = JSON.parse(body);
536
+
537
+ // Handle Slack URL verification challenge
538
+ if (payload.type === "url_verification") {
539
+ return new Response(JSON.stringify({ challenge: payload.challenge }), {
540
+ headers: { "Content-Type": "application/json" },
541
+ });
542
+ }
543
+
544
+ // Handle events
545
+ if (payload.event) {
546
+ const event = payload.event;
547
+
548
+ if (event.type === "message" && !event.bot_id) {
549
+ // Forward message to FluxyChat
550
+ const roomId = \`slack-\${event.channel}\`;
551
+ await client.createMessage(roomId, event.text || "");
552
+ }
553
+ }
554
+
555
+ return new Response("OK", { status: 200 });
556
+ } catch (error) {
557
+ console.error("Slack event error:", error);
558
+ return new Response("Internal Server Error", { status: 500 });
559
+ }
560
+ }
561
+ `;
562
+ case "telegram":
563
+ return `import { FluxyChatClient } from "@fluxy-chat/sdk";
564
+
565
+ interface BotEnv {
566
+ FLUXY_BASE_URL: string;
567
+ FLUXY_API_KEY: string;
568
+ TELEGRAM_BOT_TOKEN: string;
569
+ }
570
+
571
+ export async function handleTelegramUpdate(
572
+ update: Record<string, unknown>,
573
+ env: BotEnv,
574
+ ): Promise<Response> {
575
+ const client = new FluxyChatClient({
576
+ baseUrl: env.FLUXY_BASE_URL,
577
+ userId: "telegram-bot",
578
+ apiKey: env.FLUXY_API_KEY,
579
+ });
580
+
581
+ try {
582
+ const message = update.message as
583
+ | { chat?: { id?: number }; text?: string; from?: { first_name?: string } }
584
+ | undefined;
585
+
586
+ if (message?.chat?.id && message.text) {
587
+ const roomId = \`telegram-\${message.chat.id}\`;
588
+
589
+ // Forward to FluxyChat
590
+ await client.createMessage(roomId, message.text);
591
+
592
+ // Send reply back to Telegram
593
+ const telegramApiUrl = \`https://api.telegram.org/bot\${env.TELEGRAM_BOT_TOKEN}/sendMessage\`;
594
+ await fetch(telegramApiUrl, {
595
+ method: "POST",
596
+ headers: { "Content-Type": "application/json" },
597
+ body: JSON.stringify({
598
+ chat_id: message.chat.id,
599
+ text: \`Hi \${message.from?.first_name ?? "there"}! Message received.\`,
600
+ }),
601
+ });
602
+ }
603
+
604
+ return new Response("OK", { status: 200 });
605
+ } catch (error) {
606
+ console.error("Telegram update error:", error);
607
+ return new Response("Internal Server Error", { status: 500 });
608
+ }
609
+ }
610
+ `;
611
+ case "discord":
612
+ return `import { FluxyChatClient } from "@fluxy-chat/sdk";
613
+
614
+ interface BotEnv {
615
+ FLUXY_BASE_URL: string;
616
+ FLUXY_API_KEY: string;
617
+ DISCORD_BOT_TOKEN: string;
618
+ DISCORD_PUBLIC_KEY: string;
619
+ }
620
+
621
+ export async function handleDiscordInteraction(
622
+ request: Request,
623
+ env: BotEnv,
624
+ ): Promise<Response> {
625
+ const body = await request.text();
626
+
627
+ const client = new FluxyChatClient({
628
+ baseUrl: env.FLUXY_BASE_URL,
629
+ userId: "discord-bot",
630
+ apiKey: env.FLUXY_API_KEY,
631
+ });
632
+
633
+ try {
634
+ const interaction = JSON.parse(body);
635
+
636
+ // Handle Discord ping (verification)
637
+ if (interaction.type === 1) {
638
+ return new Response(JSON.stringify({ type: 1 }), {
639
+ headers: { "Content-Type": "application/json" },
640
+ });
641
+ }
642
+
643
+ // Handle application command
644
+ if (interaction.type === 2) {
645
+ const commandName = interaction.data?.name;
646
+
647
+ if (commandName === "chat") {
648
+ const userId = interaction.member?.user?.id ?? interaction.user?.id ?? "unknown";
649
+ const text = interaction.data?.options?.[0]?.value ?? "";
650
+
651
+ // Forward to FluxyChat
652
+ const roomId = \`discord-\${interaction.channel_id}\`;
653
+ await client.createMessage(roomId, String(text));
654
+
655
+ return new Response(
656
+ JSON.stringify({
657
+ type: 4,
658
+ data: { content: "Message forwarded to FluxyChat!" },
659
+ }),
660
+ { headers: { "Content-Type": "application/json" } },
661
+ );
662
+ }
663
+ }
664
+
665
+ return new Response("Unknown interaction type", { status: 400 });
666
+ } catch (error) {
667
+ console.error("Discord interaction error:", error);
668
+ return new Response("Internal Server Error", { status: 500 });
669
+ }
670
+ }
671
+ `;
672
+ case "web":
673
+ return `import { FluxyChatClient } from "@fluxy-chat/sdk";
674
+
675
+ interface BotEnv {
676
+ FLUXY_BASE_URL: string;
677
+ FLUXY_API_KEY: string;
678
+ }
679
+
680
+ export async function handleWebChat(
681
+ request: Request,
682
+ env: BotEnv,
683
+ ): Promise<Response> {
684
+ const body = await request.json() as {
685
+ roomId?: string;
686
+ message?: string;
687
+ userId?: string;
688
+ };
689
+
690
+ if (!body.roomId || !body.message) {
691
+ return new Response(
692
+ JSON.stringify({ error: "Missing roomId or message" }),
693
+ { status: 400, headers: { "Content-Type": "application/json" } },
694
+ );
695
+ }
696
+
697
+ const client = new FluxyChatClient({
698
+ baseUrl: env.FLUXY_BASE_URL,
699
+ userId: body.userId ?? "web-user",
700
+ apiKey: env.FLUXY_API_KEY,
701
+ });
702
+
703
+ try {
704
+ // Send user message to FluxyChat
705
+ await client.createMessage(body.roomId, body.message);
706
+
707
+ // Return a simple echo response
708
+ return new Response(
709
+ JSON.stringify({
710
+ ok: true,
711
+ reply: \`You said: \${body.message}\`,
712
+ }),
713
+ { status: 200, headers: { "Content-Type": "application/json" } },
714
+ );
715
+ } catch (error) {
716
+ console.error("Web chat error:", error);
717
+ return new Response(
718
+ JSON.stringify({ error: "Failed to process message" }),
719
+ { status: 500, headers: { "Content-Type": "application/json" } },
720
+ );
721
+ }
722
+ }
723
+ `;
724
+ default:
725
+ return `import { FluxyChatClient } from "@fluxy-chat/sdk";
726
+
727
+ interface BotEnv {
728
+ FLUXY_BASE_URL: string;
729
+ FLUXY_API_KEY: string;
730
+ }
731
+
732
+ export async function handleWebhook(
733
+ request: Request,
734
+ env: BotEnv,
735
+ ): Promise<Response> {
736
+ const body = await request.json() as {
737
+ roomId?: string;
738
+ message?: string;
739
+ userId?: string;
740
+ };
741
+
742
+ const client = new FluxyChatClient({
743
+ baseUrl: env.FLUXY_BASE_URL,
744
+ userId: body.userId ?? "bot",
745
+ apiKey: env.FLUXY_API_KEY,
746
+ });
747
+
748
+ try {
749
+ if (body.roomId && body.message) {
750
+ await client.createMessage(body.roomId, body.message);
751
+ }
752
+
753
+ return new Response(
754
+ JSON.stringify({ ok: true }),
755
+ { status: 200, headers: { "Content-Type": "application/json" } },
756
+ );
757
+ } catch (error) {
758
+ console.error("Webhook error:", error);
759
+ return new Response(
760
+ JSON.stringify({ error: "Internal server error" }),
761
+ { status: 500, headers: { "Content-Type": "application/json" } },
762
+ );
763
+ }
764
+ }
765
+ `;
766
+ }
767
+ }
768
+
769
+ // src/index.ts
770
+ import fs2 from "fs";
771
+ import path2 from "path";
772
+ import { exec } from "child_process";
773
+ import { promisify } from "util";
774
+ var execAsync = promisify(exec);
775
+ function parseArgs(argv) {
776
+ const args = {
777
+ yes: false,
778
+ skipInstall: false,
779
+ noGit: false,
780
+ help: false
781
+ };
782
+ const positional = [];
783
+ for (let i = 0; i < argv.length; i++) {
784
+ const arg = argv[i];
785
+ if (arg === "-h" || arg === "--help") {
786
+ args.help = true;
787
+ } else if (arg === "-y" || arg === "--yes") {
788
+ args.yes = true;
789
+ } else if (arg === "--skip-install") {
790
+ args.skipInstall = true;
791
+ } else if (arg === "--no-git") {
792
+ args.noGit = true;
793
+ } else if (arg === "--adapter" || arg === "-a") {
794
+ const value = argv[++i];
795
+ if (value && isAdapterType(value)) {
796
+ args.adapter = value;
797
+ } else {
798
+ console.error(`Invalid adapter: ${value}. Choose: basic, slack, telegram, discord, web`);
799
+ process.exit(1);
800
+ }
801
+ } else if (arg === "--pm" || arg === "--package-manager") {
802
+ const value = argv[++i];
803
+ if (value && isPackageManager(value)) {
804
+ args.pm = value;
805
+ } else {
806
+ console.error(`Invalid package manager: ${value}. Choose: npm, pnpm, yarn`);
807
+ process.exit(1);
808
+ }
809
+ } else if (arg === "--language" || arg === "-l") {
810
+ const value = argv[++i];
811
+ if (value === "typescript" || value === "javascript") {
812
+ args.language = value;
813
+ } else {
814
+ console.error(`Invalid language: ${value}. Choose: typescript or javascript`);
815
+ process.exit(1);
816
+ }
817
+ } else if (arg?.startsWith("--")) {
818
+ console.error(`Unknown option: ${arg}`);
819
+ process.exit(1);
820
+ } else if (arg) {
821
+ positional.push(arg);
822
+ }
823
+ }
824
+ if (positional.length > 0) {
825
+ args.name = positional[0];
826
+ }
827
+ return args;
828
+ }
829
+ var HELP_TEXT = `
830
+ ${pc.bold("create-fluxy-chat")} \u2014 Scaffold a new FluxyChat bot project
831
+
832
+ ${pc.bold("Usage:")}
833
+ npx create-fluxy-chat [project-name] [options]
834
+
835
+ ${pc.bold("Options:")}
836
+ -a, --adapter <type> Adapter: basic, slack, telegram, discord, web
837
+ --pm <manager> Package manager: npm, pnpm, yarn
838
+ -l, --language <lang> Language: typescript (default) or javascript
839
+ -y, --yes Skip prompts and accept defaults
840
+ --skip-install Skip dependency installation
841
+ --no-git Skip git repository initialization
842
+ -h, --help Show this help
843
+
844
+ ${pc.bold("Examples:")}
845
+ ${pc.cyan("npx create-fluxy-chat my-bot")}
846
+ ${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
847
+ ${pc.cyan("npx create-fluxy-chat my-bot --adapter telegram --pm pnpm")}
848
+ ${pc.cyan("npx create-fluxy-chat my-bot -y --adapter discord")}
849
+ `;
850
+ async function main() {
851
+ const args = parseArgs(process.argv.slice(2));
852
+ if (args.help) {
853
+ console.log(HELP_TEXT);
854
+ process.exit(0);
855
+ }
856
+ intro(pc.bgCyan(pc.black(" create-fluxy-chat ")));
857
+ const config = await runPrompts({
858
+ name: args.name,
859
+ adapter: args.adapter,
860
+ packageManager: args.pm,
861
+ language: args.language,
862
+ yes: args.yes,
863
+ shouldInstall: args.skipInstall ? false : void 0,
864
+ shouldInitGit: args.noGit ? false : void 0
865
+ });
866
+ if (!config) {
867
+ outro(pc.gray("Cancelled."));
868
+ process.exitCode = 0;
869
+ return;
870
+ }
871
+ const projectDir = path2.resolve(process.cwd(), config.name);
872
+ if (fs2.existsSync(projectDir) && fs2.readdirSync(projectDir).length > 0) {
873
+ outro(
874
+ pc.red(
875
+ `Directory "${config.name}" already exists and is not empty.`
876
+ )
877
+ );
878
+ process.exitCode = 1;
879
+ return;
880
+ }
881
+ const s = spinner();
882
+ s.start("Creating project files");
883
+ try {
884
+ fs2.mkdirSync(projectDir, { recursive: true });
885
+ writeJson(projectDir, "package.json", generatePackageJson(config));
886
+ if (config.language === "typescript") {
887
+ writeJson(projectDir, "tsconfig.json", generateTsConfig());
888
+ }
889
+ writeFile(projectDir, "wrangler.toml", generateWranglerToml(config));
890
+ writeFile(projectDir, ".dev.vars", generateDevVars());
891
+ writeFile(projectDir, ".env.example", generateEnvExample(config));
892
+ writeFile(projectDir, ".gitignore", generateGitignore());
893
+ const ext = config.language === "typescript" ? "ts" : "js";
894
+ writeFile(projectDir, `src/index.${ext}`, generateWorkerIndex(config));
895
+ writeFile(projectDir, `src/bot.${ext}`, generateBotHandler(config));
896
+ writeFile(projectDir, "README.md", generateReadme(config));
897
+ s.stop("Project files created.");
898
+ } catch (error) {
899
+ s.stop("Failed to create project files.");
900
+ throw error;
901
+ }
902
+ if (config.shouldInitGit) {
903
+ const gitSpinner = spinner();
904
+ gitSpinner.start("Initializing git repository");
905
+ try {
906
+ await execAsync("git init", { cwd: projectDir });
907
+ gitSpinner.stop("Git repository initialized.");
908
+ } catch {
909
+ gitSpinner.stop("Failed to initialize git repository.");
910
+ log.warning('Run "git init" manually in the project directory.');
911
+ }
912
+ }
913
+ if (config.shouldInstall) {
914
+ const installSpinner = spinner();
915
+ installSpinner.start(
916
+ `Installing dependencies with ${config.packageManager}`
917
+ );
918
+ try {
919
+ await execAsync(installCommand(config.packageManager), {
920
+ cwd: projectDir
921
+ });
922
+ installSpinner.stop("Dependencies installed.");
923
+ } catch {
924
+ installSpinner.stop("Failed to install dependencies.");
925
+ log.warning(
926
+ `Run "${installCommand(config.packageManager)}" manually in the project directory.`
927
+ );
928
+ }
929
+ }
930
+ note(
931
+ [
932
+ `cd ${config.name}`,
933
+ "cp .env.example .dev.vars",
934
+ `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
935
+ ].join("\n"),
936
+ "Next steps"
937
+ );
938
+ outro(
939
+ `${pc.green("Done!")} Visit ${pc.cyan("https://github.com/AlessandroFare/fluxychat")} for the docs.`
940
+ );
941
+ }
942
+ main().catch((error) => {
943
+ const message = error instanceof Error ? error.message : String(error);
944
+ outro(pc.red(message));
945
+ process.exitCode = 1;
946
+ });