@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.d.ts +1 -0
- package/dist/index.js +946 -0
- package/package.json +45 -0
- package/readme.md +89 -0
- package/templates/basic/package.json +19 -0
- package/templates/basic/readme.md +36 -0
- package/templates/basic/src/bot.ts +40 -0
- package/templates/basic/src/index.ts +17 -0
- package/templates/basic/tsconfig.json +18 -0
- package/templates/basic/wrangler.toml +9 -0
- package/templates/discord/package.json +20 -0
- package/templates/discord/readme.md +37 -0
- package/templates/discord/src/bot.ts +59 -0
- package/templates/discord/src/index.ts +17 -0
- package/templates/slack/package.json +21 -0
- package/templates/slack/readme.md +37 -0
- package/templates/slack/src/bot.ts +58 -0
- package/templates/slack/src/index.ts +19 -0
- package/templates/telegram/package.json +20 -0
- package/templates/telegram/readme.md +39 -0
- package/templates/telegram/src/bot.ts +47 -0
- package/templates/telegram/src/index.ts +18 -0
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
|
+
});
|