@fluxy-chat/create-fluxy-chat 0.5.8 → 0.5.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.11] - 2026-08-25
4
+
5
+ ### Added
6
+
7
+ - React and full templates expose `stopAgentStream` (Stop / Stop generation) while an agent reply is streaming. Comes from `@fluxy-chat/sdk@0.6.3` via `useChat`.
8
+
9
+ ### Changed
10
+
11
+ - Templates pin `@fluxy-chat/sdk@^0.6.3`, `@fluxy-chat/react@^0.1.3`, `@fluxy-chat/ui@^0.1.4`, and `@fluxy-chat/ui-kit@^0.1.1` (minimal). Publish those packages **before** this CLI, or `pnpm create` will 404.
12
+
13
+ ## [0.5.10] - 2026-08-25
14
+
15
+ ### Added
16
+
17
+ - Gold-path outro mentions `@assistant` in the generated React app.
18
+
19
+ ### Changed
20
+
21
+ - Templates still depend on `@fluxy-chat/sdk@^0.6.2` (and `@fluxy-chat/react@^0.1.2` where used) so `pnpm create` works **before and after** sdk `0.6.3` is on npm. `^0.6.2` installs `0.6.3` once published.
22
+
23
+ ## [0.5.9] - 2026-08-21
24
+
25
+ ### Added
26
+
27
+ - `--mode self-host` (alias of `local`). Interactive Worker URL, console URL, and optional Groq key. Writes `.fluxy/answers.json` and `.fluxy/worker.dev.vars` to paste into `apps/worker/.dev.vars`.
28
+ - `pnpm setup:self-host` on the full template. If the Worker is down, setup asks for a URL instead of exiting immediately.
29
+
3
30
  ## [0.5.8] - 2026-08-19
4
31
 
5
32
  ### Fixed
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { intro, log, note, outro, spinner } from "@clack/prompts";
5
5
  import pc from "picocolors";
6
6
 
7
7
  // src/prompts.ts
8
+ import { randomBytes } from "crypto";
8
9
  import {
9
10
  confirm,
10
11
  isCancel,
@@ -97,11 +98,15 @@ function templatesDir() {
97
98
  }
98
99
 
99
100
  // src/prompts.ts
101
+ var DEFAULT_WORKER_URL = "http://127.0.0.1:8787";
102
+ var DEFAULT_CONSOLE_URL = "http://localhost:3000";
103
+ function generateJwtSigningKey() {
104
+ return randomBytes(32).toString("hex");
105
+ }
100
106
  var DEFAULT_PROJECT_NAME = "my-fluxy-bot";
101
107
  var DEFAULT_FULL_PROJECT_NAME = "my-fluxy-app";
102
108
  async function runPrompts(inputs) {
103
109
  const full = inputs.full ?? inputs.adapter === "full";
104
- const mode = inputs.mode ?? (full ? "local" : void 0);
105
110
  let name = inputs.name;
106
111
  if (!name) {
107
112
  if (inputs.yes) {
@@ -197,6 +202,64 @@ async function runPrompts(inputs) {
197
202
  initialValue: true
198
203
  }));
199
204
  if (isCancel(shouldInitGit)) return null;
205
+ const isFull = full || adapter === "full";
206
+ let resolvedMode = inputs.mode;
207
+ let workerUrl;
208
+ let consoleUrl;
209
+ let groqApiKey;
210
+ let jwtSigningKey;
211
+ if (isFull) {
212
+ if (!resolvedMode) {
213
+ if (inputs.yes) {
214
+ resolvedMode = "local";
215
+ } else {
216
+ const picked = await select({
217
+ message: "Where does the Worker run?",
218
+ options: [
219
+ {
220
+ label: "Hosted (fluxychat.com + Clerk \u2014 no wrangler)",
221
+ value: "hosted"
222
+ },
223
+ {
224
+ label: "Self-host (your Worker / wrangler dev)",
225
+ value: "local"
226
+ }
227
+ ]
228
+ });
229
+ if (isCancel(picked)) return null;
230
+ resolvedMode = picked;
231
+ }
232
+ }
233
+ if (resolvedMode === "local") {
234
+ jwtSigningKey = generateJwtSigningKey();
235
+ if (inputs.yes) {
236
+ workerUrl = DEFAULT_WORKER_URL;
237
+ consoleUrl = DEFAULT_CONSOLE_URL;
238
+ } else {
239
+ const workerResult = await text({
240
+ message: "Worker URL:",
241
+ placeholder: DEFAULT_WORKER_URL,
242
+ initialValue: DEFAULT_WORKER_URL
243
+ });
244
+ if (isCancel(workerResult)) return null;
245
+ workerUrl = String(workerResult).trim() || DEFAULT_WORKER_URL;
246
+ const consoleResult = await text({
247
+ message: "Console URL (dashboard):",
248
+ placeholder: DEFAULT_CONSOLE_URL,
249
+ initialValue: DEFAULT_CONSOLE_URL
250
+ });
251
+ if (isCancel(consoleResult)) return null;
252
+ consoleUrl = String(consoleResult).trim() || DEFAULT_CONSOLE_URL;
253
+ const groqResult = await text({
254
+ message: "Groq API key (optional, for @assistant):",
255
+ placeholder: "gsk_\u2026"
256
+ });
257
+ if (isCancel(groqResult)) return null;
258
+ const groq = String(groqResult).trim();
259
+ if (groq) groqApiKey = groq;
260
+ }
261
+ }
262
+ }
200
263
  return {
201
264
  name,
202
265
  adapter: adapter ?? "react",
@@ -205,8 +268,12 @@ async function runPrompts(inputs) {
205
268
  shouldInstall,
206
269
  shouldInitGit,
207
270
  minimal: minimal || inputs.minimal === true,
208
- full: full || adapter === "full",
209
- mode: mode ?? (adapter === "full" ? "local" : void 0)
271
+ full: isFull,
272
+ mode: resolvedMode ?? (isFull ? "local" : void 0),
273
+ workerUrl,
274
+ consoleUrl,
275
+ groqApiKey,
276
+ jwtSigningKey
210
277
  };
211
278
  }
212
279
 
@@ -831,14 +898,12 @@ function parseArgs(argv) {
831
898
  args.adapter = "full";
832
899
  } else if (arg === "--mode") {
833
900
  const value = argv[++i]?.trim().toLowerCase();
834
- if (value === "local" || value === "hosted") {
835
- args.mode = value;
836
- if (value === "hosted") {
837
- args.full = true;
838
- args.adapter = "full";
839
- }
901
+ if (value === "local" || value === "hosted" || value === "self-host") {
902
+ args.mode = value === "self-host" ? "local" : value;
903
+ args.full = true;
904
+ args.adapter = "full";
840
905
  } else {
841
- console.error(`Invalid mode: ${value}. Choose: local, hosted`);
906
+ console.error(`Invalid mode: ${value}. Choose: local, self-host, hosted`);
842
907
  process.exit(1);
843
908
  }
844
909
  } else if (arg === "--skip-install") {
@@ -894,7 +959,7 @@ function parseArgs(argv) {
894
959
  return args;
895
960
  }
896
961
  var HELP_TEXT = `
897
- ${pc.bold("create-fluxy-chat")} \u2014 Scaffold a new FluxyChat bot project
962
+ ${pc.bold("create-fluxy-chat")} \u2014 Scaffold a FluxyChat app or bot worker
898
963
 
899
964
  ${pc.bold("Usage:")}
900
965
  npx @fluxy-chat/create-fluxy-chat [project-name] [options]
@@ -906,22 +971,19 @@ ${pc.bold("Options:")}
906
971
  -l, --language <lang> Language: typescript (default) or javascript
907
972
  -y, --yes Skip prompts and accept defaults
908
973
  --full Full stack: chat + @assistant + setup scripts (recommended)
909
- --mode <local|hosted> hosted = no wrangler (uses fluxychat.com demo session)
910
- --minimal Chat-only widget (ui-kit) \u2014 no platform modules
974
+ --mode <hosted|local|self-host>
975
+ hosted = Clerk on fluxychat.com (no wrangler)
976
+ local / self-host = your Worker (asks for URL + keys)
977
+ --minimal Chat-only widget (ui-kit)
911
978
  --skip-install Skip dependency installation
912
979
  --no-git Skip git repository initialization
913
980
  -h, --help Show this help
914
981
 
915
982
  ${pc.bold("Examples:")}
916
- ${pc.cyan("npx @fluxy-chat/create-fluxy-chat my-app --mode hosted -y")}
917
- ${pc.cyan("npx @fluxy-chat/create-fluxy-chat my-app --full -y")}
918
- ${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
919
- ${pc.cyan("npx create-fluxy-chat my-hr-bot --template hr-feedback")}
920
- ${pc.cyan("npx create-fluxy-chat my-chat --template react")}
921
- ${pc.cyan("npx create-fluxy-chat my-bot --adapter basic")}
922
- ${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
923
- ${pc.cyan("npx create-fluxy-chat my-bot --adapter telegram --pm pnpm")}
924
- ${pc.cyan("npx create-fluxy-chat my-bot -y --adapter discord")}
983
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-app --mode hosted -y")}
984
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-app --mode self-host")}
985
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-chat --minimal")}
986
+ ${pc.cyan("npx @fluxy-chat/create-fluxy-chat@latest my-bot --adapter slack")}
925
987
  `;
926
988
  async function main() {
927
989
  const args = parseArgs(process.argv.slice(2));
@@ -979,12 +1041,39 @@ async function main() {
979
1041
  if (config.full || config.adapter === "full") {
980
1042
  fs2.mkdirSync(path2.join(projectDir, ".fluxy"), { recursive: true });
981
1043
  const setupMode = config.mode === "hosted" ? "hosted" : "local";
1044
+ writeFile(projectDir, ".fluxy/mode", `${setupMode}
1045
+ `);
982
1046
  writeFile(
983
1047
  projectDir,
984
- ".fluxy/mode",
985
- `${setupMode}
1048
+ ".fluxy/answers.json",
1049
+ `${JSON.stringify(
1050
+ {
1051
+ mode: setupMode,
1052
+ workerUrl: config.workerUrl ?? null,
1053
+ consoleUrl: config.consoleUrl ?? null,
1054
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1055
+ },
1056
+ null,
1057
+ 2
1058
+ )}
986
1059
  `
987
1060
  );
1061
+ if (setupMode === "local") {
1062
+ const groqLine = config.groqApiKey ? `GROQ_API_KEY=${config.groqApiKey}` : "# GROQ_API_KEY=";
1063
+ writeFile(
1064
+ projectDir,
1065
+ ".fluxy/worker.dev.vars",
1066
+ [
1067
+ "# Merge into fluxychat/apps/worker/.dev.vars (or paste after clone).",
1068
+ "# Member JWTs are per-project in D1. This signing key is for bootstrap/secrets.",
1069
+ "ALLOW_DEV_PROVISION=true",
1070
+ `JWT_SIGNING_KEY=${config.jwtSigningKey ?? ""}`,
1071
+ groqLine,
1072
+ "AI_MODEL=openai/gpt-oss-20b",
1073
+ ""
1074
+ ].join("\n")
1075
+ );
1076
+ }
988
1077
  }
989
1078
  s.stop(
990
1079
  config.mode === "hosted" ? "Full stack app created (hosted mode \u2014 run pnpm setup:hosted)." : "Full stack app created (chat + agent + setup scripts)."
@@ -1059,18 +1148,18 @@ async function main() {
1059
1148
  `${devCmd} dev # http://localhost:5173`,
1060
1149
  `# Keep this project: https://fluxychat.com/onboarding?from=cli`
1061
1150
  ].join("\n") : [
1062
- `# Terminal 1 \u2014 FluxyChat monorepo (if not already running)`,
1063
- `pnpm --filter @fluxy-chat/worker dev`,
1064
- ``,
1065
1151
  `cd ${config.name}`,
1066
- `${devCmd} setup # provision worker \u2192 writes .env`,
1067
- `${devCmd} dev # http://localhost:5173 (+ dashboard if monorepo nearby)`,
1068
- `# Keep / import .env: http://localhost:3000/onboarding?from=cli`
1152
+ `# 1. Clone FluxyChat and run: pnpm run self-host`,
1153
+ `# Merge .fluxy/worker.dev.vars into apps/worker/.dev.vars`,
1154
+ `# 2. Start Worker: pnpm --filter @fluxy-chat/worker dev`,
1155
+ `${devCmd} setup:local # POST /dev/provision \u2192 writes .env`,
1156
+ `${devCmd} dev # http://localhost:5173`
1069
1157
  ].join("\n") : config.adapter === "react" || config.minimal ? [
1070
1158
  `cd ${config.name}`,
1071
1159
  "cp .env.example .env",
1072
1160
  "# Set VITE_FLUXYCHAT_WORKER_URL + JWT or public room ID",
1073
- `${devCmd} dev`
1161
+ `${devCmd} dev`,
1162
+ "# In the room, send: @assistant hello"
1074
1163
  ].join("\n") : [
1075
1164
  `cd ${config.name}`,
1076
1165
  "cp .env.example .dev.vars",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxy-chat/create-fluxy-chat",
3
- "version": "0.5.8",
3
+ "version": "0.5.11",
4
4
  "description": "Scaffold a new FluxyChat bot project with a single command",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/readme.md CHANGED
@@ -1,41 +1,33 @@
1
1
  # create-fluxy-chat
2
2
 
3
- Scaffold a new [FluxyChat](https://github.com/AlessandroFare/fluxychat) bot project with a single command.
3
+ Scaffold a FluxyChat Vite app or bot worker.
4
4
 
5
5
  ## Quick start
6
6
 
7
7
  ```bash
8
- # Full stack chat + @assistant + setup scripts (recommended)
9
- npx @fluxy-chat/create-fluxy-chat my-app --full -y
10
- cd my-app && pnpm setup && pnpm dev
8
+ # HostedClerk, no wrangler
9
+ npx @fluxy-chat/create-fluxy-chat@latest my-app --mode hosted -y
10
+ cd my-app && pnpm setup:hosted && pnpm dev
11
11
 
12
- # Minimal chat widget
13
- npx create-fluxy-chat my-chat --minimal
12
+ # Your Worker
13
+ npx @fluxy-chat/create-fluxy-chat@latest my-app --mode self-host
14
+ cd my-app && pnpm setup:local && pnpm dev
14
15
 
15
- # React + useChat only (bring your own worker URL)
16
- npx create-fluxy-chat my-chat --template react
16
+ # Minimal widget
17
+ npx @fluxy-chat/create-fluxy-chat@latest my-chat --minimal
17
18
  ```
18
19
 
19
- ## Non-interactive usage
20
-
21
- ```bash
22
- # Full stack (chat + agent + setup)
23
- npx create-fluxy-chat my-app --full -y
24
-
25
- # React + Vite + useChat
26
- npx create-fluxy-chat my-chat --template react -y
20
+ Always use `@fluxy-chat/create-fluxy-chat`. Bare `npx create-fluxy-chat` is not this package.
27
21
 
28
- # Create a Slack bot with pnpm
29
- npx create-fluxy-chat my-bot --adapter slack --pm pnpm
22
+ Self-host writes `.fluxy/worker.dev.vars` (Worker URL, Groq key, signing key). Merge that into `apps/worker/.dev.vars` after `pnpm run self-host` in the FluxyChat repo.
30
23
 
31
- # Create a Telegram bot, skip install
32
- npx create-fluxy-chat my-bot --adapter telegram --skip-install
33
-
34
- # Create a Discord bot with defaults
35
- npx create-fluxy-chat my-bot -y --adapter discord
24
+ ## Non-interactive usage
36
25
 
37
- # Create a basic webhook bot (includes fluxy.config.ts template)
38
- npx create-fluxy-chat my-bot --adapter basic
26
+ ```bash
27
+ npx @fluxy-chat/create-fluxy-chat@latest my-app --mode hosted -y
28
+ npx @fluxy-chat/create-fluxy-chat@latest my-app --full -y
29
+ npx @fluxy-chat/create-fluxy-chat@latest my-chat --template react -y
30
+ npx @fluxy-chat/create-fluxy-chat@latest my-bot --adapter slack --pm pnpm
39
31
  ```
40
32
 
41
33
  ## Options
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "^0.6.0"
12
+ "@fluxy-chat/sdk": "^0.6.3"
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": "^0.6.0",
12
+ "@fluxy-chat/sdk": "^0.6.3",
13
13
  "discord.js": "^14.0.0"
14
14
  },
15
15
  "devDependencies": {
@@ -16,23 +16,29 @@ pnpm dev
16
16
 
17
17
  Localhost opens a 3-step tour. Last step is sign in. After Clerk you come back to a simple chat. Open a second tab to try realtime. Use Open dashboard for rooms and agents.
18
18
 
19
- **Local worker:**
19
+ **Self-host (your Worker):**
20
20
 
21
21
  ```bash
22
+ # In the FluxyChat repo
23
+ pnpm run self-host
22
24
  pnpm --filter @fluxy-chat/worker dev
23
- npx @fluxy-chat/create-fluxy-chat@latest my-app --full -y
25
+
26
+ # In another terminal
27
+ npx @fluxy-chat/create-fluxy-chat@latest my-app --mode self-host
24
28
  cd my-app
25
29
  pnpm install
26
- pnpm setup
30
+ pnpm setup:local
27
31
  pnpm dev
28
32
  ```
29
33
 
34
+ If the Worker is down, `setup:local` asks for the URL. Merge `.fluxy/worker.dev.vars` into `apps/worker/.dev.vars` (Groq key + `ALLOW_DEV_PROVISION=true`).
35
+
30
36
  ## Scripts
31
37
 
32
38
  | Command | Description |
33
39
  |---------|-------------|
34
40
  | `pnpm setup:hosted` | Writes worker + console URLs. Auth happens in the browser via Clerk. |
35
- | `pnpm setup:local` | Local `/dev/provision` |
41
+ | `pnpm setup:local` / `pnpm setup:self-host` | `POST /dev/provision` on your Worker |
36
42
  | `pnpm doctor` | Health check |
37
43
  | `pnpm dev` | Start Vite |
38
44
 
@@ -7,6 +7,7 @@
7
7
  "setup": "node scripts/fluxy-setup.mjs",
8
8
  "setup:hosted": "node scripts/fluxy-setup.mjs --mode hosted",
9
9
  "setup:local": "node scripts/fluxy-setup.mjs --mode local",
10
+ "setup:self-host": "node scripts/fluxy-setup.mjs --mode self-host",
10
11
  "doctor": "node scripts/fluxy-doctor.mjs",
11
12
  "dev": "node scripts/fluxy-dev.mjs",
12
13
  "dev:app": "vite",
@@ -15,9 +16,9 @@
15
16
  "postinstall": "node scripts/hoist-fluxy-sdk.mjs"
16
17
  },
17
18
  "dependencies": {
18
- "@fluxy-chat/react": "^0.1.1",
19
- "@fluxy-chat/sdk": "^0.6.2",
20
- "@fluxy-chat/ui": "^0.1.3",
19
+ "@fluxy-chat/react": "^0.1.3",
20
+ "@fluxy-chat/sdk": "^0.6.3",
21
+ "@fluxy-chat/ui": "^0.1.4",
21
22
  "class-variance-authority": "^0.7.1",
22
23
  "clsx": "^2.1.1",
23
24
  "lucide-react": "^0.511.0",
@@ -39,7 +40,7 @@
39
40
  },
40
41
  "pnpm": {
41
42
  "overrides": {
42
- "@fluxy-chat/sdk": "0.6.2"
43
+ "@fluxy-chat/sdk": "^0.6.3"
43
44
  }
44
45
  }
45
46
  }
@@ -3,22 +3,25 @@
3
3
  * Provision credentials and write .env for the full template.
4
4
  *
5
5
  * Modes:
6
- * local (default) — POST /dev/provision on local worker (ALLOW_DEV_PROVISION=true)
7
- * hosted GET /demo/session on fluxychat.com (no wrangler required)
6
+ * local / self-host — POST /dev/provision on your worker (ALLOW_DEV_PROVISION=true)
7
+ * hosted Clerk on fluxychat.com (no wrangler)
8
8
  *
9
9
  * Usage:
10
10
  * pnpm setup
11
11
  * pnpm setup -- --mode hosted
12
+ * pnpm setup -- --mode self-host
12
13
  * FLUXY_SETUP_MODE=hosted pnpm setup
13
14
  */
14
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
16
  import { dirname, join, resolve } from "node:path";
17
+ import { createInterface } from "node:readline";
16
18
  import { fileURLToPath } from "node:url";
17
19
 
18
20
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
19
21
  const envPath = join(root, ".env");
20
22
  const metaPath = join(root, ".fluxy", "setup.json");
21
23
  const modePath = join(root, ".fluxy", "mode");
24
+ const answersPath = join(root, ".fluxy", "answers.json");
22
25
 
23
26
  const HOSTED_WORKER_DEFAULT = "https://api.fluxychat.com";
24
27
  const HOSTED_CONSOLE_DEFAULT = "https://fluxychat.com";
@@ -41,11 +44,40 @@ function fail(msg) {
41
44
  process.exit(1);
42
45
  }
43
46
 
47
+ function parseSetupMode(raw) {
48
+ const m = String(raw || "").trim().toLowerCase();
49
+ if (m === "hosted") return "hosted";
50
+ if (m === "local" || m === "self-host") return "local";
51
+ return null;
52
+ }
53
+
54
+ function readAnswers() {
55
+ if (!existsSync(answersPath)) return {};
56
+ try {
57
+ return JSON.parse(readFileSync(answersPath, "utf8"));
58
+ } catch {
59
+ return {};
60
+ }
61
+ }
62
+
63
+ function promptLine(question, fallback) {
64
+ if (!process.stdin.isTTY) return Promise.resolve(fallback);
65
+ return new Promise((resolveAnswer) => {
66
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
67
+ rl.question(`${question} [${fallback}]: `, (answer) => {
68
+ rl.close();
69
+ resolveAnswer(String(answer || "").trim() || fallback);
70
+ });
71
+ });
72
+ }
73
+
44
74
  function readDefaultMode() {
45
75
  if (existsSync(modePath)) {
46
- const m = readFileSync(modePath, "utf8").trim().toLowerCase();
47
- if (m === "hosted" || m === "local") return m;
76
+ const parsed = parseSetupMode(readFileSync(modePath, "utf8"));
77
+ if (parsed) return parsed;
48
78
  }
79
+ const fromAnswers = parseSetupMode(readAnswers().mode);
80
+ if (fromAnswers) return fromAnswers;
49
81
  return "local";
50
82
  }
51
83
 
@@ -53,12 +85,12 @@ function resolveMode() {
53
85
  const argv = process.argv.slice(2);
54
86
  const flagIdx = argv.indexOf("--mode");
55
87
  if (flagIdx >= 0 && argv[flagIdx + 1]) {
56
- const m = String(argv[flagIdx + 1]).trim().toLowerCase();
57
- if (m === "hosted" || m === "local") return m;
58
- fail(`Unknown mode "${argv[flagIdx + 1]}". Use: local | hosted`);
88
+ const parsed = parseSetupMode(argv[flagIdx + 1]);
89
+ if (parsed) return parsed;
90
+ fail(`Unknown mode "${argv[flagIdx + 1]}". Use: local | self-host | hosted`);
59
91
  }
60
- const fromEnv = String(process.env.FLUXY_SETUP_MODE || "").trim().toLowerCase();
61
- if (fromEnv === "hosted" || fromEnv === "local") return fromEnv;
92
+ const fromEnv = parseSetupMode(process.env.FLUXY_SETUP_MODE);
93
+ if (fromEnv) return fromEnv;
62
94
  return readDefaultMode();
63
95
  }
64
96
 
@@ -218,20 +250,43 @@ async function setupHosted() {
218
250
  }
219
251
 
220
252
  async function setupLocal() {
221
- const workerUrl =
222
- process.env.FLUXY_WORKER_URL || process.env.FLUXYCHAT_WORKER_URL || LOCAL_WORKER_DEFAULT;
223
- const consoleUrl = process.env.FLUXY_CONSOLE_URL || LOCAL_CONSOLE_DEFAULT;
253
+ const answers = readAnswers();
254
+ let workerUrl =
255
+ process.env.FLUXY_WORKER_URL ||
256
+ process.env.FLUXYCHAT_WORKER_URL ||
257
+ answers.workerUrl ||
258
+ LOCAL_WORKER_DEFAULT;
259
+ const consoleUrl =
260
+ process.env.FLUXY_CONSOLE_URL || answers.consoleUrl || LOCAL_CONSOLE_DEFAULT;
224
261
 
225
262
  console.log(dim(` mode: local · worker: ${workerUrl}`));
226
263
 
227
264
  if (!(await isWorkerUp(workerUrl))) {
228
- fail(
229
- `Worker not reachable at ${workerUrl}\n` +
230
- " Start it from the FluxyChat monorepo:\n" +
231
- " pnpm --filter @fluxy-chat/worker dev\n" +
232
- " Or use hosted mode:\n" +
233
- " pnpm setup -- --mode hosted",
265
+ console.log(
266
+ dim(
267
+ `\n Worker not reachable at ${workerUrl}.\n` +
268
+ " Clone FluxyChat, then:\n" +
269
+ " pnpm install && pnpm run self-host\n" +
270
+ " pnpm --filter @fluxy-chat/worker dev\n" +
271
+ " Merge this project's .fluxy/worker.dev.vars into apps/worker/.dev.vars\n",
272
+ ),
234
273
  );
274
+ if (process.stdin.isTTY) {
275
+ for (let i = 0; i < 3; i += 1) {
276
+ workerUrl = await promptLine("Worker URL", workerUrl);
277
+ if (await isWorkerUp(workerUrl)) break;
278
+ console.log(dim(` Still down at ${workerUrl}`));
279
+ }
280
+ }
281
+ if (!(await isWorkerUp(workerUrl))) {
282
+ fail(
283
+ `Worker not reachable at ${workerUrl}\n` +
284
+ " Start it from the FluxyChat monorepo:\n" +
285
+ " pnpm run self-host && pnpm --filter @fluxy-chat/worker dev\n" +
286
+ " Or use hosted mode:\n" +
287
+ " pnpm setup -- --mode hosted",
288
+ );
289
+ }
235
290
  }
236
291
  ok(`worker healthy at ${workerUrl}`);
237
292
 
@@ -13,5 +13,5 @@ const nestedSdk = join(
13
13
 
14
14
  if (existsSync(nestedSdk)) {
15
15
  rmSync(nestedSdk, { recursive: true, force: true });
16
- console.log("[fluxy] removed nested @fluxy-chat/sdk so Vite uses the complete 0.6.2 package");
16
+ console.log("[fluxy] removed nested @fluxy-chat/sdk so Vite uses the hoisted complete package");
17
17
  }
@@ -116,15 +116,24 @@ function LocalOnboarding() {
116
116
  }
117
117
 
118
118
  function ChatRoom({ session }: { session: CliSession }) {
119
- const { messages, sendMessage, invokeAgent, connectionState, agentTyping, typingUsers, online } =
120
- useChat({
121
- roomId: session.roomId,
122
- agentId: session.agentId || undefined,
123
- markReadLatest: true,
124
- });
119
+ const {
120
+ messages,
121
+ sendMessage,
122
+ invokeAgent,
123
+ connectionState,
124
+ agentTyping,
125
+ typingUsers,
126
+ online,
127
+ stopAgentStream,
128
+ } = useChat({
129
+ roomId: session.roomId,
130
+ agentId: session.agentId || undefined,
131
+ markReadLatest: true,
132
+ });
125
133
 
126
134
  const [error, setError] = useState<string | null>(null);
127
135
  const connected = connectionState.status === "connected";
136
+ const isStreaming = messages.some((m) => m.streaming);
128
137
 
129
138
  async function onSend(content: string) {
130
139
  setError(null);
@@ -152,6 +161,11 @@ function ChatRoom({ session }: { session: CliSession }) {
152
161
  </span>
153
162
  <span className="text-muted-foreground"> · {session.roomId}</span>
154
163
  </span>
164
+ {isStreaming ? (
165
+ <button type="button" className="btn-ghost" onClick={() => stopAgentStream()}>
166
+ Stop generation
167
+ </button>
168
+ ) : null}
155
169
  </div>
156
170
 
157
171
  <div className="chat-frame">
@@ -9,10 +9,10 @@
9
9
  "preview": "vite preview"
10
10
  },
11
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.2",
12
+ "@fluxy-chat/ui-kit": "^0.1.1",
13
+ "@fluxy-chat/ui": "^0.1.4",
14
+ "@fluxy-chat/react": "^0.1.3",
15
+ "@fluxy-chat/sdk": "^0.6.3",
16
16
  "react": "^19.0.0",
17
17
  "react-dom": "^19.0.0"
18
18
  },
@@ -9,8 +9,8 @@
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/react": "^0.1.1",
13
- "@fluxy-chat/sdk": "^0.6.2",
12
+ "@fluxy-chat/react": "^0.1.3",
13
+ "@fluxy-chat/sdk": "^0.6.3",
14
14
  "react": "^19.0.0",
15
15
  "react-dom": "^19.0.0",
16
16
  "zustand": "^5.0.0"
@@ -8,7 +8,9 @@ const publicRoomId = import.meta.env.VITE_FLUXYCHAT_PUBLIC_ROOM_ID?.trim();
8
8
  const configuredRoomId = import.meta.env.VITE_FLUXYCHAT_ROOM_ID?.trim() || "demo";
9
9
 
10
10
  interface FluxySession {
11
- client: FluxyChatClient;
11
+ workerUrl: string;
12
+ token: string;
13
+ userId: string;
12
14
  roomId: string;
13
15
  mode: "member" | "guest";
14
16
  }
@@ -30,11 +32,9 @@ function useFluxySession(): {
30
32
 
31
33
  if (memberJwt) {
32
34
  setSession({
33
- client: new FluxyChatClient({
34
- baseUrl: workerUrl,
35
- userId: "demo-user",
36
- token: memberJwt,
37
- }),
35
+ workerUrl,
36
+ token: memberJwt,
37
+ userId: "demo-user",
38
38
  roomId: configuredRoomId,
39
39
  mode: "member",
40
40
  });
@@ -50,11 +50,9 @@ function useFluxySession(): {
50
50
  .then((guest) => {
51
51
  if (cancelled) return;
52
52
  setSession({
53
- client: new FluxyChatClient({
54
- baseUrl: workerUrl,
55
- userId: guest.userId,
56
- token: guest.token,
57
- }),
53
+ workerUrl,
54
+ token: guest.token,
55
+ userId: guest.userId,
58
56
  roomId: guest.roomId,
59
57
  mode: "guest",
60
58
  });
@@ -79,17 +77,23 @@ function useFluxySession(): {
79
77
  }
80
78
 
81
79
  function ChatPanel({ roomId }: { roomId: string }) {
82
- const { messages, sendMessage, connectionState } = useChat({
80
+ const { messages, sendMessage, connectionState, stopAgentStream } = useChat({
83
81
  roomId,
84
82
  markReadLatest: true,
85
83
  });
86
84
  const [draft, setDraft] = useState("");
85
+ const isStreaming = messages.some((m) => m.streaming);
87
86
 
88
87
  return (
89
88
  <section className="chat-panel">
90
89
  <header className="chat-header">
91
90
  <strong>{roomId}</strong>
92
91
  <span className="status">{connectionState.status}</span>
92
+ {isStreaming ? (
93
+ <button type="button" onClick={() => stopAgentStream()}>
94
+ Stop
95
+ </button>
96
+ ) : null}
93
97
  </header>
94
98
  <ul className="messages">
95
99
  {messages.map((m) => (
@@ -112,7 +116,7 @@ function ChatPanel({ roomId }: { roomId: string }) {
112
116
  <input
113
117
  value={draft}
114
118
  onChange={(e) => setDraft(e.target.value)}
115
- placeholder="Type a message"
119
+ placeholder="Type a message, or @assistant to mention the room agent"
116
120
  aria-label="Message"
117
121
  />
118
122
  <button type="submit">Send</button>
@@ -180,7 +184,11 @@ export function App() {
180
184
  <input value={roomId} onChange={(e) => setRoomId(e.target.value)} />
181
185
  </label>
182
186
  ) : null}
183
- <FluxyRealtimeProvider client={session.client}>
187
+ <FluxyRealtimeProvider
188
+ workerUrl={session.workerUrl}
189
+ authTokenProvider={session.token}
190
+ userId={session.userId}
191
+ >
184
192
  <ChatPanel roomId={activeRoomId} />
185
193
  </FluxyRealtimeProvider>
186
194
  </main>
@@ -9,7 +9,7 @@
9
9
  "type-check": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@fluxy-chat/sdk": "^0.6.0",
12
+ "@fluxy-chat/sdk": "^0.6.3",
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": "^0.6.0",
12
+ "@fluxy-chat/sdk": "^0.6.3",
13
13
  "node-telegram-bot-api": "^0.66.0"
14
14
  },
15
15
  "devDependencies": {