@fluxy-chat/create-fluxy-chat 0.2.0 → 0.4.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.
Files changed (34) hide show
  1. package/dist/index.js +101 -22
  2. package/package.json +2 -2
  3. package/readme.md +21 -11
  4. package/templates/basic/package.json +1 -1
  5. package/templates/discord/package.json +1 -1
  6. package/templates/hr-feedback/.env.example +9 -0
  7. package/templates/hr-feedback/README.md +45 -0
  8. package/templates/hr-feedback/package.json +19 -0
  9. package/templates/hr-feedback/src/feedback.ts +71 -0
  10. package/templates/hr-feedback/src/index.ts +22 -0
  11. package/templates/hr-feedback/tsconfig.json +18 -0
  12. package/templates/hr-feedback/wrangler.toml +7 -0
  13. package/templates/minimal/.env.example +3 -0
  14. package/templates/minimal/README.md +14 -0
  15. package/templates/minimal/index.html +12 -0
  16. package/templates/minimal/package.json +26 -0
  17. package/templates/minimal/src/App.tsx +24 -0
  18. package/templates/minimal/src/index.css +13 -0
  19. package/templates/minimal/src/main.tsx +10 -0
  20. package/templates/minimal/src/vite-env.d.ts +7 -0
  21. package/templates/minimal/tsconfig.json +17 -0
  22. package/templates/minimal/vite.config.ts +7 -0
  23. package/templates/react/.env.example +9 -0
  24. package/templates/react/README.md +43 -0
  25. package/templates/react/index.html +12 -0
  26. package/templates/react/package.json +25 -0
  27. package/templates/react/src/App.tsx +188 -0
  28. package/templates/react/src/index.css +116 -0
  29. package/templates/react/src/main.tsx +10 -0
  30. package/templates/react/src/vite-env.d.ts +12 -0
  31. package/templates/react/tsconfig.json +21 -0
  32. package/templates/react/vite.config.ts +7 -0
  33. package/templates/slack/package.json +1 -1
  34. package/templates/telegram/package.json +1 -1
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ function isPackageManager(value) {
30
30
  return PACKAGE_MANAGERS.has(value);
31
31
  }
32
32
  function isAdapterType(value) {
33
- return ["basic", "slack", "telegram", "discord", "web"].includes(value);
33
+ return ["basic", "slack", "telegram", "discord", "web", "react", "hr-feedback"].includes(value);
34
34
  }
35
35
  function detectPackageManagerFromLockfiles(cwd) {
36
36
  if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
@@ -79,6 +79,25 @@ function writeJson(projectDir, filePath, value) {
79
79
  writeFile(projectDir, filePath, `${JSON.stringify(value, null, 2)}
80
80
  `);
81
81
  }
82
+ function copyDir(source, destination) {
83
+ fs.mkdirSync(destination, { recursive: true });
84
+ for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
85
+ const srcPath = path.join(source, entry.name);
86
+ const destPath = path.join(destination, entry.name);
87
+ if (entry.isDirectory()) {
88
+ copyDir(srcPath, destPath);
89
+ } else {
90
+ fs.copyFileSync(srcPath, destPath);
91
+ }
92
+ }
93
+ }
94
+ function templatesDir() {
95
+ return path.resolve(
96
+ path.dirname(new URL(import.meta.url).pathname.replace(/^\//, "")),
97
+ "..",
98
+ "templates"
99
+ );
100
+ }
82
101
 
83
102
  // src/prompts.ts
84
103
  var DEFAULT_PROJECT_NAME = "my-fluxy-bot";
@@ -101,15 +120,19 @@ async function runPrompts(inputs) {
101
120
  if (nameError) {
102
121
  throw new Error(nameError);
103
122
  }
123
+ const minimal = inputs.minimal ?? false;
104
124
  let adapter = inputs.adapter;
105
- if (!adapter) {
125
+ if (!minimal && !adapter) {
106
126
  if (inputs.yes) {
107
- adapter = "basic";
127
+ adapter = "react";
108
128
  } else {
109
129
  const result = await select({
110
130
  message: "Select an adapter:",
111
131
  options: [
112
- { label: "Basic (Cloudflare Workers)", value: "basic" },
132
+ { label: "Minimal chat widget (ui-kit, recommended)", value: "minimal" },
133
+ { label: "React chat app (Vite + useChat)", value: "react" },
134
+ { label: "HR anonymous feedback (compliance starter)", value: "hr-feedback" },
135
+ { label: "Basic (Cloudflare Workers bot)", value: "basic" },
113
136
  { label: "Slack", value: "slack" },
114
137
  { label: "Telegram", value: "telegram" },
115
138
  { label: "Discord", value: "discord" },
@@ -117,6 +140,9 @@ async function runPrompts(inputs) {
117
140
  ]
118
141
  });
119
142
  if (isCancel(result)) return null;
143
+ if (result === "minimal") {
144
+ return runPrompts({ ...inputs, minimal: true, adapter: "react" });
145
+ }
120
146
  adapter = result;
121
147
  }
122
148
  }
@@ -167,11 +193,12 @@ async function runPrompts(inputs) {
167
193
  if (isCancel(shouldInitGit)) return null;
168
194
  return {
169
195
  name,
170
- adapter,
196
+ adapter: adapter ?? "react",
171
197
  packageManager,
172
198
  language,
173
199
  shouldInstall,
174
- shouldInitGit
200
+ shouldInitGit,
201
+ minimal: minimal || inputs.minimal === true
175
202
  };
176
203
  }
177
204
 
@@ -775,6 +802,7 @@ var execAsync = promisify(exec);
775
802
  function parseArgs(argv) {
776
803
  const args = {
777
804
  yes: false,
805
+ minimal: false,
778
806
  skipInstall: false,
779
807
  noGit: false,
780
808
  help: false
@@ -786,16 +814,30 @@ function parseArgs(argv) {
786
814
  args.help = true;
787
815
  } else if (arg === "-y" || arg === "--yes") {
788
816
  args.yes = true;
817
+ } else if (arg === "--minimal") {
818
+ args.minimal = true;
789
819
  } else if (arg === "--skip-install") {
790
820
  args.skipInstall = true;
791
821
  } else if (arg === "--no-git") {
792
822
  args.noGit = true;
823
+ } else if (arg === "--template" || arg === "-t") {
824
+ const value = argv[++i];
825
+ if (value && isAdapterType(value)) {
826
+ args.adapter = value;
827
+ } else if (value === "react") {
828
+ args.adapter = "react";
829
+ } else if (value === "hr-feedback") {
830
+ args.adapter = "hr-feedback";
831
+ } else {
832
+ console.error(`Invalid template: ${value}. Choose: react, basic, slack, telegram, discord, web, hr-feedback`);
833
+ process.exit(1);
834
+ }
793
835
  } else if (arg === "--adapter" || arg === "-a") {
794
836
  const value = argv[++i];
795
837
  if (value && isAdapterType(value)) {
796
838
  args.adapter = value;
797
839
  } else {
798
- console.error(`Invalid adapter: ${value}. Choose: basic, slack, telegram, discord, web`);
840
+ console.error(`Invalid adapter: ${value}. Choose: react, basic, slack, telegram, discord, web, hr-feedback`);
799
841
  process.exit(1);
800
842
  }
801
843
  } else if (arg === "--pm" || arg === "--package-manager") {
@@ -833,16 +875,21 @@ ${pc.bold("Usage:")}
833
875
  npx create-fluxy-chat [project-name] [options]
834
876
 
835
877
  ${pc.bold("Options:")}
836
- -a, --adapter <type> Adapter: basic, slack, telegram, discord, web
878
+ -a, --adapter <type> Adapter: react, basic, slack, telegram, discord, web, hr-feedback
879
+ -t, --template <type> Alias for --adapter (e.g. react, hr-feedback)
837
880
  --pm <manager> Package manager: npm, pnpm, yarn
838
881
  -l, --language <lang> Language: typescript (default) or javascript
839
882
  -y, --yes Skip prompts and accept defaults
883
+ --minimal Chat-only widget (ui-kit) \u2014 no platform modules
840
884
  --skip-install Skip dependency installation
841
885
  --no-git Skip git repository initialization
842
886
  -h, --help Show this help
843
887
 
844
888
  ${pc.bold("Examples:")}
845
- ${pc.cyan("npx create-fluxy-chat my-bot")}
889
+ ${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
890
+ ${pc.cyan("npx create-fluxy-chat my-hr-bot --template hr-feedback")}
891
+ ${pc.cyan("npx create-fluxy-chat my-chat --template react")}
892
+ ${pc.cyan("npx create-fluxy-chat my-bot --adapter basic")}
846
893
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
847
894
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter telegram --pm pnpm")}
848
895
  ${pc.cyan("npx create-fluxy-chat my-bot -y --adapter discord")}
@@ -860,6 +907,7 @@ async function main() {
860
907
  packageManager: args.pm,
861
908
  language: args.language,
862
909
  yes: args.yes,
910
+ minimal: args.minimal,
863
911
  shouldInstall: args.skipInstall ? false : void 0,
864
912
  shouldInitGit: args.noGit ? false : void 0
865
913
  });
@@ -882,19 +930,45 @@ async function main() {
882
930
  s.start("Creating project files");
883
931
  try {
884
932
  fs2.mkdirSync(projectDir, { recursive: true });
885
- writeJson(projectDir, "package.json", generatePackageJson(config));
886
- if (config.language === "typescript") {
887
- writeJson(projectDir, "tsconfig.json", generateTsConfig());
933
+ if (config.minimal) {
934
+ const templateRoot = path2.join(templatesDir(), "minimal");
935
+ copyDir(templateRoot, projectDir);
936
+ const pkgPath = path2.join(projectDir, "package.json");
937
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
938
+ pkg.name = config.name;
939
+ writeJson(projectDir, "package.json", pkg);
940
+ s.stop("Minimal chat widget created.");
941
+ } else if (config.adapter === "react") {
942
+ const templateRoot = path2.join(templatesDir(), "react");
943
+ copyDir(templateRoot, projectDir);
944
+ const pkgPath = path2.join(projectDir, "package.json");
945
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
946
+ pkg.name = config.name;
947
+ writeJson(projectDir, "package.json", pkg);
948
+ s.stop("React chat app created.");
949
+ } else if (config.adapter === "hr-feedback") {
950
+ const templateRoot = path2.join(templatesDir(), "hr-feedback");
951
+ copyDir(templateRoot, projectDir);
952
+ const pkgPath = path2.join(projectDir, "package.json");
953
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
954
+ pkg.name = config.name;
955
+ writeJson(projectDir, "package.json", pkg);
956
+ s.stop("HR feedback starter created.");
957
+ } else {
958
+ writeJson(projectDir, "package.json", generatePackageJson(config));
959
+ if (config.language === "typescript") {
960
+ writeJson(projectDir, "tsconfig.json", generateTsConfig());
961
+ }
962
+ writeFile(projectDir, "wrangler.toml", generateWranglerToml(config));
963
+ writeFile(projectDir, ".dev.vars", generateDevVars());
964
+ writeFile(projectDir, ".env.example", generateEnvExample(config));
965
+ writeFile(projectDir, ".gitignore", generateGitignore());
966
+ const ext = config.language === "typescript" ? "ts" : "js";
967
+ writeFile(projectDir, `src/index.${ext}`, generateWorkerIndex(config));
968
+ writeFile(projectDir, `src/bot.${ext}`, generateBotHandler(config));
969
+ writeFile(projectDir, "README.md", generateReadme(config));
970
+ s.stop("Project files created.");
888
971
  }
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
972
  } catch (error) {
899
973
  s.stop("Failed to create project files.");
900
974
  throw error;
@@ -928,7 +1002,12 @@ async function main() {
928
1002
  }
929
1003
  }
930
1004
  note(
931
- [
1005
+ config.adapter === "react" ? [
1006
+ `cd ${config.name}`,
1007
+ "cp .env.example .env",
1008
+ "# Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_PUBLIC_ROOM_ID (guest) or MEMBER_JWT",
1009
+ `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
1010
+ ].join("\n") : [
932
1011
  `cd ${config.name}`,
933
1012
  "cp .env.example .dev.vars",
934
1013
  `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxy-chat/create-fluxy-chat",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Scaffold a new FluxyChat bot project with a single command",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "license": "MIT",
40
40
  "scripts": {
41
- "build": "tsup src/index.ts --format esm --dts",
41
+ "build": "tsup src/index.ts --format esm",
42
42
  "dev": "tsup src/index.ts --format esm --watch",
43
43
  "typecheck": "tsc --noEmit"
44
44
  }
package/readme.md CHANGED
@@ -5,14 +5,21 @@ Scaffold a new [FluxyChat](https://github.com/AlessandroFare/fluxychat) bot proj
5
5
  ## Quick start
6
6
 
7
7
  ```bash
8
- npx create-fluxy-chat my-bot
8
+ # Minimal chat widget (recommended: 3 lines in App.tsx)
9
+ npx create-fluxy-chat my-chat --minimal
10
+
11
+ # React + useChat (guest room ~60s or member JWT)
12
+ npx create-fluxy-chat my-chat --template react
9
13
  ```
10
14
 
11
- This launches an interactive prompt to configure your bot project.
15
+ Interactive mode defaults to the **React chat app** template.
12
16
 
13
17
  ## Non-interactive usage
14
18
 
15
19
  ```bash
20
+ # React + Vite + useChat (recommended)
21
+ npx create-fluxy-chat my-chat --template react -y
22
+
16
23
  # Create a Slack bot with pnpm
17
24
  npx create-fluxy-chat my-bot --adapter slack --pm pnpm
18
25
 
@@ -30,10 +37,12 @@ npx create-fluxy-chat my-bot --adapter basic
30
37
 
31
38
  | Flag | Short | Description |
32
39
  | --- | --- | --- |
33
- | `--adapter <type>` | `-a` | Adapter: `basic`, `slack`, `telegram`, `discord`, `web` |
40
+ | `--adapter <type>` | `-a` | Adapter: `react`, `basic`, `slack`, `telegram`, `discord`, `web` |
41
+ | `--template <type>` | `-t` | Alias for `--adapter (e.g. react)` |
34
42
  | `--pm <manager>` | | Package manager: `npm`, `pnpm`, `yarn` |
35
43
  | `--language <lang>` | `-l` | Language: `typescript` (default) or `javascript` |
36
44
  | `--yes` | `-y` | Skip prompts and accept defaults |
45
+ | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`), no platform modules |
37
46
  | `--skip-install` | | Skip dependency installation |
38
47
  | `--no-git` | | Skip git repository initialization |
39
48
  | `--help` | `-h` | Show help |
@@ -42,6 +51,7 @@ npx create-fluxy-chat my-bot --adapter basic
42
51
 
43
52
  | Adapter | Description | Platform |
44
53
  | --- | --- | --- |
54
+ | `react` | Vite + React chat UI with `useChat` | Browser / SPA |
45
55
  | `basic` | Generic webhook bot | Cloudflare Workers |
46
56
  | `slack` | Slack Events API bot | Slack |
47
57
  | `telegram` | Telegram webhook bot | Telegram |
@@ -52,14 +62,14 @@ npx create-fluxy-chat my-bot --adapter basic
52
62
 
53
63
  Each generated project includes:
54
64
 
55
- - **`src/index.ts`** Cloudflare Workers entry point with route handling
56
- - **`src/bot.ts`** Bot handler using `@fluxy-chat/sdk`
57
- - **`fluxy.config.ts`** Room authz and publish middleware (basic template)
58
- - **`wrangler.toml`** Cloudflare Workers deployment config
59
- - **`.dev.vars`** Local development environment variables
60
- - **`.env.example`** Example environment variables for your adapter
61
- - **`tsconfig.json`** TypeScript configuration (for TS projects)
62
- - **`README.md`** Project-specific setup instructions
65
+ - **`src/index.ts`**: Cloudflare Workers entry point with route handling
66
+ - **`src/bot.ts`**: Bot handler using `@fluxy-chat/sdk`
67
+ - **`fluxy.config.ts`**: Room authz and publish middleware (basic template)
68
+ - **`wrangler.toml`**: Cloudflare Workers deployment config
69
+ - **`.dev.vars`**: Local development environment variables
70
+ - **`.env.example`**: Example environment variables for your adapter
71
+ - **`tsconfig.json`**: TypeScript configuration (for TS projects)
72
+ - **`README.md`**: Project-specific setup instructions
63
73
 
64
74
  ## Package manager detection
65
75
 
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "latest"
12
+ "@fluxy-chat/sdk": "^0.6.0"
13
13
  },
14
14
  "devDependencies": {
15
15
  "@cloudflare/workers-types": "^4.0.0",
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "latest",
12
+ "@fluxy-chat/sdk": "^0.6.0",
13
13
  "discord.js": "^14.0.0"
14
14
  },
15
15
  "devDependencies": {
@@ -0,0 +1,9 @@
1
+ # FluxyChat worker URL + service API key (server-side only)
2
+ FLUXY_BASE_URL=https://your-fluxychat-worker.example.com
3
+ FLUXY_API_KEY=your-api-key-here
4
+
5
+ # Optional dedicated room for aggregated summaries
6
+ HR_FEEDBACK_ROOM_ID=hr-anonymous-feedback
7
+
8
+ # Optional webhook for Path B HR escalation (de-identified payload only)
9
+ HR_ESCALATION_WEBHOOK_URL=
@@ -0,0 +1,45 @@
1
+ # HR Anonymous Feedback Starter
2
+
3
+ Production starter for anonymous employee feedback with sensitive classification and privacy-safe audit.
4
+
5
+ ## Features
6
+
7
+ - **Path A** — routine feedback → aggregated anonymous summary room
8
+ - **Path B** — sensitive categories → HR escalation hook (category + confidence only, no identity)
9
+ - Reuses FluxyChat `approvalChain` + room timeline audit when wired to agent tools
10
+ - Classification runs on the worker via `POST /anonymous-feedback`
11
+
12
+ ## Quick start
13
+
14
+ ```bash
15
+ cp .env.example .dev.vars
16
+ # fill FLUXY_BASE_URL + FLUXY_API_KEY
17
+ npm install
18
+ npm run dev
19
+ ```
20
+
21
+ Submit feedback:
22
+
23
+ ```bash
24
+ curl -X POST http://localhost:8787/feedback \
25
+ -H "Content-Type: application/json" \
26
+ -d '{"content":"My manager makes hostile comments in meetings"}'
27
+ ```
28
+
29
+ ## Endpoints
30
+
31
+ | Route | Description |
32
+ | --- | --- |
33
+ | `GET /` | Health check |
34
+ | `POST /feedback` | Anonymous submission (no user id stored) |
35
+
36
+ ## Privacy
37
+
38
+ - Raw message content is **not** persisted by this template
39
+ - Worker audit stores **category + timestamp + path** only
40
+ - Configure `HR_ESCALATION_WEBHOOK_URL` for your HRIS / ticketing integration
41
+
42
+ ## Learn more
43
+
44
+ - [FluxyChat docs](https://github.com/AlessandroFare/fluxychat)
45
+ - Worker route: `POST /anonymous-feedback` (JWT) for in-app widgets
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "hr-feedback-bot",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler deploy",
9
+ "type-check": "tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/sdk": "latest"
13
+ },
14
+ "devDependencies": {
15
+ "@cloudflare/workers-types": "^4.0.0",
16
+ "typescript": "^5.6.0",
17
+ "wrangler": "^3.0.0"
18
+ }
19
+ }
@@ -0,0 +1,71 @@
1
+ interface FeedbackEnv {
2
+ FLUXY_BASE_URL: string;
3
+ FLUXY_API_KEY: string;
4
+ HR_FEEDBACK_ROOM_ID?: string;
5
+ HR_ESCALATION_WEBHOOK_URL?: string;
6
+ }
7
+
8
+ interface FeedbackBody {
9
+ content?: string;
10
+ roomId?: string;
11
+ }
12
+
13
+ export interface FeedbackResult {
14
+ ok: boolean;
15
+ path?: string;
16
+ category?: string;
17
+ confidence?: number;
18
+ message?: string;
19
+ error?: string;
20
+ }
21
+
22
+ /**
23
+ * Submit anonymous feedback through the FluxyChat worker classifier.
24
+ * Content is classified server-side; only metadata is returned.
25
+ */
26
+ export async function submitHrFeedback(
27
+ env: FeedbackEnv,
28
+ body: FeedbackBody,
29
+ ): Promise<FeedbackResult> {
30
+ const content = String(body.content ?? "").trim();
31
+ if (!content) return { ok: false, error: "content required" };
32
+ if (content.length > 8000) return { ok: false, error: "content_too_long" };
33
+
34
+ const baseUrl = env.FLUXY_BASE_URL.replace(/\/$/, "");
35
+ const res = await fetch(`${baseUrl}/anonymous-feedback`, {
36
+ method: "POST",
37
+ headers: {
38
+ "Content-Type": "application/json",
39
+ Authorization: `Bearer ${env.FLUXY_API_KEY}`,
40
+ },
41
+ body: JSON.stringify({
42
+ content,
43
+ roomId: body.roomId ?? env.HR_FEEDBACK_ROOM_ID ?? null,
44
+ }),
45
+ });
46
+
47
+ const data = (await res.json().catch(() => ({}))) as FeedbackResult;
48
+ if (!res.ok) {
49
+ return { ok: false, error: data.error ?? `worker_${res.status}` };
50
+ }
51
+
52
+ if (data.path === "hr_escalation" && env.HR_ESCALATION_WEBHOOK_URL) {
53
+ await fetch(env.HR_ESCALATION_WEBHOOK_URL, {
54
+ method: "POST",
55
+ headers: { "Content-Type": "application/json" },
56
+ body: JSON.stringify({
57
+ category: data.category,
58
+ confidence: data.confidence,
59
+ at: new Date().toISOString(),
60
+ }),
61
+ }).catch(() => undefined);
62
+ }
63
+
64
+ return {
65
+ ok: true,
66
+ path: data.path,
67
+ category: data.category,
68
+ confidence: data.confidence,
69
+ message: data.message,
70
+ };
71
+ }
@@ -0,0 +1,22 @@
1
+ import { submitHrFeedback } from "./feedback.js";
2
+
3
+ export default {
4
+ async fetch(request: Request, env: Record<string, string>): Promise<Response> {
5
+ const url = new URL(request.url);
6
+
7
+ if (url.pathname === "/" && request.method === "GET") {
8
+ return new Response("HR anonymous feedback bot is running.", { status: 200 });
9
+ }
10
+
11
+ if (url.pathname === "/feedback" && request.method === "POST") {
12
+ const body = (await request.json().catch(() => ({}))) as {
13
+ content?: string;
14
+ roomId?: string;
15
+ };
16
+ const result = await submitHrFeedback(env, body);
17
+ return Response.json(result, { status: result.ok ? 200 : 400 });
18
+ }
19
+
20
+ return new Response("Not Found", { status: 404 });
21
+ },
22
+ };
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "types": ["@cloudflare/workers-types"],
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "noEmit": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": ["node_modules", "dist"]
18
+ }
@@ -0,0 +1,7 @@
1
+ name = "hr-feedback-bot"
2
+ main = "src/index.ts"
3
+ compatibility_date = "2024-12-01"
4
+
5
+ [vars]
6
+ FLUXY_BASE_URL = "https://your-fluxychat-worker.example.com"
7
+ HR_FEEDBACK_ROOM_ID = "hr-anonymous-feedback"
@@ -0,0 +1,3 @@
1
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.com
2
+ VITE_FLUXYCHAT_MEMBER_JWT=your-member-jwt
3
+ VITE_FLUXYCHAT_ROOM_ID=general
@@ -0,0 +1,14 @@
1
+ # FluxyChat minimal starter
2
+
3
+ Three-line chat widget with no platform modules in the generated project.
4
+
5
+ ```bash
6
+ cp .env.example .env
7
+ pnpm dev
8
+ ```
9
+
10
+ Uses `@fluxy-chat/ui-kit` (`FluxyChatWidget`).
11
+
12
+ ## Full platform
13
+
14
+ See [docs.fluxychat.com](https://docs.fluxychat.com/docs) for agents, stream, IoT, and self-host.
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FluxyChat</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "fluxy-chat-minimal",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/ui-kit": "^0.1.0",
13
+ "@fluxy-chat/ui": "^0.1.2",
14
+ "@fluxy-chat/react": "^0.1.1",
15
+ "@fluxy-chat/sdk": "^0.6.0",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/react": "^19.0.0",
21
+ "@types/react-dom": "^19.0.0",
22
+ "@vitejs/plugin-react": "^4.3.0",
23
+ "typescript": "^5.6.0",
24
+ "vite": "^6.0.0"
25
+ }
26
+ }
@@ -0,0 +1,24 @@
1
+ import { FluxyChatWidget } from "@fluxy-chat/ui-kit";
2
+
3
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL;
4
+ const token = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT;
5
+ const roomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID || "general";
6
+
7
+ export function App() {
8
+ return (
9
+ <main style={{ maxWidth: 720, margin: "2rem auto", padding: "0 1rem" }}>
10
+ <h1 style={{ fontSize: "1.25rem", marginBottom: "1rem" }}>FluxyChat</h1>
11
+ <FluxyChatWidget
12
+ roomId={roomId}
13
+ workerUrl={workerUrl}
14
+ token={token}
15
+ theme="default"
16
+ height={520}
17
+ />
18
+ <p style={{ marginTop: "1rem", fontSize: 13, color: "#71717a" }}>
19
+ Need stream, IoT, or agents? See{" "}
20
+ <a href="https://docs.fluxychat.com/docs">full platform docs</a>.
21
+ </p>
22
+ </main>
23
+ );
24
+ }
@@ -0,0 +1,13 @@
1
+ :root {
2
+ font-family: system-ui, sans-serif;
3
+ color: #18181b;
4
+ background: #fafafa;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ }
10
+
11
+ a {
12
+ color: #c2410c;
13
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true
15
+ },
16
+ "include": ["src"]
17
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173 },
7
+ });
@@ -0,0 +1,9 @@
1
+ # FluxyChat worker URL (hosted or self-hosted)
2
+ VITE_FLUXYCHAT_WORKER_URL=https://your-worker.example.workers.dev
3
+
4
+ # Option A — member JWT (~2 min via onboarding)
5
+ VITE_FLUXYCHAT_MEMBER_JWT=eyJ...
6
+ VITE_FLUXYCHAT_ROOM_ID=demo
7
+
8
+ # Option B — public guest room (~60s, no signup)
9
+ # VITE_FLUXYCHAT_PUBLIC_ROOM_ID=your-public-room-id
@@ -0,0 +1,43 @@
1
+ # FluxyChat React starter
2
+
3
+ Minimal Vite + React app with `useChat`. First message in about 60 seconds via the public guest room.
4
+
5
+ ## Quick start (guest, fastest)
6
+
7
+ ```bash
8
+ cp .env.example .env
9
+ # Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_PUBLIC_ROOM_ID (public room from console)
10
+ npm install
11
+ npm run dev
12
+ ```
13
+
14
+ Open http://localhost:5173. A guest JWT is minted automatically via `joinPublicRoomAsGuest`.
15
+
16
+ ## Quick start (member JWT)
17
+
18
+ ```bash
19
+ cp .env.example .env
20
+ # Set VITE_FLUXYCHAT_WORKER_URL + VITE_FLUXYCHAT_MEMBER_JWT + VITE_FLUXYCHAT_ROOM_ID
21
+ npm install
22
+ npm run dev
23
+ ```
24
+
25
+ ## Get credentials
26
+
27
+ 1. **Guest:** create a **public** room in console, copy room ID, set `VITE_FLUXYCHAT_PUBLIC_ROOM_ID`
28
+ 2. **Member:** [fluxychat.com/onboarding](https://fluxychat.com/onboarding) for Worker URL and JWT
29
+ 3. **Local monorepo:** `pnpm run first-message` from the FluxyChat repo
30
+
31
+ ## Scripts
32
+
33
+ | Command | Description |
34
+ |---------|-------------|
35
+ | `npm run dev` | Vite dev server |
36
+ | `npm run build` | Production build |
37
+ | `npm run preview` | Preview production build |
38
+
39
+ ## Next steps
40
+
41
+ - Add `@fluxy-chat/ui` themes (`default`, `dark`, `minimal`, `brand`)
42
+ - Use `useInbox` for unified feed
43
+ - See [chat-only quickstart](https://docs.fluxychat.com/docs/getting-started/chat-only-quickstart)
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>FluxyChat</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "fluxy-chat-app",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "@fluxy-chat/react": "^0.1.1",
13
+ "@fluxy-chat/sdk": "^0.6.0",
14
+ "react": "^19.0.0",
15
+ "react-dom": "^19.0.0",
16
+ "zustand": "^5.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/react": "^19.0.0",
20
+ "@types/react-dom": "^19.0.0",
21
+ "@vitejs/plugin-react": "^4.3.0",
22
+ "typescript": "^5.6.0",
23
+ "vite": "^6.0.0"
24
+ }
25
+ }
@@ -0,0 +1,188 @@
1
+ import { useEffect, useMemo, useState } from "react";
2
+ import { FluxyChatClient } from "@fluxy-chat/sdk";
3
+ import { FluxyRealtimeProvider, useChat } from "@fluxy-chat/react";
4
+
5
+ const workerUrl = import.meta.env.VITE_FLUXYCHAT_WORKER_URL?.trim();
6
+ const memberJwt = import.meta.env.VITE_FLUXYCHAT_MEMBER_JWT?.trim();
7
+ const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
8
+ const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
9
+
10
+ interface FluxySession {
11
+ client: FluxyChatClient;
12
+ roomId: string;
13
+ mode: "member" | "guest";
14
+ }
15
+
16
+ function useFluxySession(): {
17
+ session: FluxySession | null;
18
+ loading: boolean;
19
+ error: string | null;
20
+ } {
21
+ const [session, setSession] = useState<FluxySession | null>(null);
22
+ const [loading, setLoading] = useState(Boolean(workerUrl));
23
+ const [error, setError] = useState<string | null>(null);
24
+
25
+ useEffect(() => {
26
+ if (!workerUrl) {
27
+ setLoading(false);
28
+ return;
29
+ }
30
+
31
+ if (memberJwt) {
32
+ setSession({
33
+ client: new FluxyChatClient({
34
+ baseUrl: workerUrl,
35
+ userId: "demo-user",
36
+ token: memberJwt,
37
+ }),
38
+ roomId: configuredRoomId,
39
+ mode: "member",
40
+ });
41
+ setLoading(false);
42
+ return;
43
+ }
44
+
45
+ if (publicRoomId) {
46
+ let cancelled = false;
47
+ void FluxyChatClient.joinPublicRoomAsGuest(workerUrl, publicRoomId, {
48
+ displayName: "Guest",
49
+ })
50
+ .then((guest) => {
51
+ if (cancelled) return;
52
+ setSession({
53
+ client: new FluxyChatClient({
54
+ baseUrl: workerUrl,
55
+ userId: guest.userId,
56
+ token: guest.token,
57
+ }),
58
+ roomId: guest.roomId,
59
+ mode: "guest",
60
+ });
61
+ })
62
+ .catch((err) => {
63
+ if (!cancelled) {
64
+ setError(err instanceof Error ? err.message : "Guest session failed");
65
+ }
66
+ })
67
+ .finally(() => {
68
+ if (!cancelled) setLoading(false);
69
+ });
70
+ return () => {
71
+ cancelled = true;
72
+ };
73
+ }
74
+
75
+ setLoading(false);
76
+ }, []);
77
+
78
+ return { session, loading, error };
79
+ }
80
+
81
+ function ChatPanel({ roomId }: { roomId: string }) {
82
+ const { messages, sendMessage, connectionState } = useChat({
83
+ roomId,
84
+ markReadLatest: true,
85
+ });
86
+ const [draft, setDraft] = useState("");
87
+
88
+ return (
89
+ <section className="chat-panel">
90
+ <header className="chat-header">
91
+ <strong>{roomId}</strong>
92
+ <span className="status">{connectionState.status}</span>
93
+ </header>
94
+ <ul className="messages">
95
+ {messages.map((m) => (
96
+ <li key={m.id} className="message">
97
+ <span className="author">{m.userId}</span>
98
+ <span>{m.content}</span>
99
+ </li>
100
+ ))}
101
+ </ul>
102
+ <form
103
+ className="composer"
104
+ onSubmit={(e) => {
105
+ e.preventDefault();
106
+ const text = draft.trim();
107
+ if (!text) return;
108
+ void sendMessage(text);
109
+ setDraft("");
110
+ }}
111
+ >
112
+ <input
113
+ value={draft}
114
+ onChange={(e) => setDraft(e.target.value)}
115
+ placeholder="Type a message…"
116
+ aria-label="Message"
117
+ />
118
+ <button type="submit">Send</button>
119
+ </form>
120
+ </section>
121
+ );
122
+ }
123
+
124
+ export function App() {
125
+ const { session, loading, error } = useFluxySession();
126
+ const [roomId, setRoomId] = useState(configuredRoomId);
127
+
128
+ const activeRoomId = useMemo(() => {
129
+ if (session?.mode === "guest") return session.roomId;
130
+ return roomId.trim() || configuredRoomId;
131
+ }, [session, roomId]);
132
+
133
+ if (!workerUrl) {
134
+ return (
135
+ <main className="shell">
136
+ <h1>FluxyChat — chat-only starter</h1>
137
+ <p>
138
+ Copy <code>.env.example</code> to <code>.env</code> and set{" "}
139
+ <code>VITE_FLUXYCHAT_WORKER_URL</code>.
140
+ </p>
141
+ <p>
142
+ Then either <code>VITE_FLUXYCHAT_MEMBER_JWT</code> (member) or{" "}
143
+ <code>VITE_FLUXYCHAT_PUBLIC_ROOM_ID</code> (guest, ~60s to first message).
144
+ </p>
145
+ </main>
146
+ );
147
+ }
148
+
149
+ if (loading) {
150
+ return (
151
+ <main className="shell">
152
+ <p>Connecting…</p>
153
+ </main>
154
+ );
155
+ }
156
+
157
+ if (error || !session) {
158
+ return (
159
+ <main className="shell">
160
+ <h1>FluxyChat</h1>
161
+ <p className="error">{error ?? "Set JWT or public room ID in .env"}</p>
162
+ <p>
163
+ Get credentials from{" "}
164
+ <a href="https://fluxychat.com/onboarding" target="_blank" rel="noreferrer">
165
+ onboarding
166
+ </a>{" "}
167
+ or use a public room ID for guest mode.
168
+ </p>
169
+ </main>
170
+ );
171
+ }
172
+
173
+ return (
174
+ <main className="shell">
175
+ <h1>FluxyChat</h1>
176
+ <p className="mode-badge">{session.mode === "guest" ? "Guest session" : "Member JWT"}</p>
177
+ {session.mode === "member" ? (
178
+ <label className="room-picker">
179
+ Room
180
+ <input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
181
+ </label>
182
+ ) : null}
183
+ <FluxyRealtimeProvider client={session.client}>
184
+ <ChatPanel roomId={activeRoomId} />
185
+ </FluxyRealtimeProvider>
186
+ </main>
187
+ );
188
+ }
@@ -0,0 +1,116 @@
1
+ :root {
2
+ font-family: system-ui, -apple-system, sans-serif;
3
+ line-height: 1.5;
4
+ color: #0f172a;
5
+ background: #f8fafc;
6
+ }
7
+
8
+ * {
9
+ box-sizing: border-box;
10
+ }
11
+
12
+ body {
13
+ margin: 0;
14
+ }
15
+
16
+ .shell {
17
+ max-width: 720px;
18
+ margin: 0 auto;
19
+ padding: 1.5rem;
20
+ }
21
+
22
+ .chat-panel {
23
+ display: flex;
24
+ flex-direction: column;
25
+ height: min(70vh, 560px);
26
+ border: 1px solid #e2e8f0;
27
+ border-radius: 12px;
28
+ background: #fff;
29
+ overflow: hidden;
30
+ }
31
+
32
+ .chat-header {
33
+ display: flex;
34
+ justify-content: space-between;
35
+ padding: 0.75rem 1rem;
36
+ border-bottom: 1px solid #e2e8f0;
37
+ background: #2563eb;
38
+ color: #fff;
39
+ }
40
+
41
+ .status {
42
+ font-size: 0.75rem;
43
+ opacity: 0.9;
44
+ }
45
+
46
+ .messages {
47
+ flex: 1;
48
+ overflow-y: auto;
49
+ list-style: none;
50
+ margin: 0;
51
+ padding: 1rem;
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 0.5rem;
55
+ }
56
+
57
+ .message {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 0.15rem;
61
+ padding: 0.5rem 0.75rem;
62
+ border-radius: 8px;
63
+ background: #f1f5f9;
64
+ }
65
+
66
+ .author {
67
+ font-size: 0.7rem;
68
+ color: #64748b;
69
+ }
70
+
71
+ .composer {
72
+ display: flex;
73
+ gap: 0.5rem;
74
+ padding: 0.75rem;
75
+ border-top: 1px solid #e2e8f0;
76
+ }
77
+
78
+ .composer input {
79
+ flex: 1;
80
+ border: 1px solid #cbd5e1;
81
+ border-radius: 8px;
82
+ padding: 0.5rem 0.75rem;
83
+ }
84
+
85
+ .composer button {
86
+ border: none;
87
+ border-radius: 8px;
88
+ background: #2563eb;
89
+ color: #fff;
90
+ padding: 0.5rem 1rem;
91
+ cursor: pointer;
92
+ }
93
+
94
+ .mode-badge {
95
+ font-size: 0.75rem;
96
+ color: #64748b;
97
+ margin: 0 0 0.75rem;
98
+ }
99
+
100
+ .error {
101
+ color: #b91c1c;
102
+ }
103
+
104
+ .room-picker {
105
+ display: flex;
106
+ flex-direction: column;
107
+ gap: 0.25rem;
108
+ margin-bottom: 1rem;
109
+ font-size: 0.875rem;
110
+ }
111
+
112
+ .room-picker input {
113
+ padding: 0.5rem 0.75rem;
114
+ border: 1px solid #cbd5e1;
115
+ border-radius: 8px;
116
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { App } from "./App";
4
+ import "./index.css";
5
+
6
+ createRoot(document.getElementById("root")!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ );
@@ -0,0 +1,12 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ interface ImportMetaEnv {
4
+ readonly VITE_FLUXYCHAT_WORKER_URL: string;
5
+ readonly VITE_FLUXYCHAT_MEMBER_JWT?: string;
6
+ readonly VITE_FLUXYCHAT_ROOM_ID?: string;
7
+ readonly VITE_FLUXYCHAT_PUBLIC_ROOM_ID?: string;
8
+ }
9
+
10
+ interface ImportMeta {
11
+ readonly env: ImportMetaEnv;
12
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedSideEffectImports": true
19
+ },
20
+ "include": ["src"]
21
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: { port: 5173 },
7
+ });
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "latest",
12
+ "@fluxy-chat/sdk": "^0.6.0",
13
13
  "@slack/bolt": "^4.0.0",
14
14
  "@slack/web-api": "^7.0.0"
15
15
  },
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "latest",
12
+ "@fluxy-chat/sdk": "^0.6.0",
13
13
  "node-telegram-bot-api": "^0.66.0"
14
14
  },
15
15
  "devDependencies": {