@fluxy-chat/create-fluxy-chat 0.3.0 → 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,16 @@
1
+ # Changelog
2
+
3
+ ## [0.5.0] - 2026-08-19
4
+
5
+ ### Added
6
+
7
+ - `--full` / `--template full` — Vite app with realtime chat, `@assistant` invoke, and tool thread
8
+ - `--mode hosted` — guest JWT via `GET /demo/session` (no local wrangler)
9
+ - `--mode local` — provision against a local worker (`POST /dev/provision`)
10
+ - Template scripts: `pnpm setup`, `pnpm setup:hosted`, `pnpm doctor`, `pnpm dev`
11
+ - CLI next-steps link to `/onboarding?from=cli` (keep / import `.env`)
12
+
13
+ ### Changed
14
+
15
+ - Interactive picker lists Full stack first
16
+ - README hero documents hosted vs local paths
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  // src/utils.ts
16
16
  import fs from "fs";
17
17
  import path from "path";
18
+ import { fileURLToPath } from "url";
18
19
  var PACKAGE_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
19
20
  var PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "yarn", "pnpm"]);
20
21
  function validateProjectName(value) {
@@ -30,7 +31,7 @@ function isPackageManager(value) {
30
31
  return PACKAGE_MANAGERS.has(value);
31
32
  }
32
33
  function isAdapterType(value) {
33
- return ["basic", "slack", "telegram", "discord", "web", "react"].includes(value);
34
+ return ["basic", "slack", "telegram", "discord", "web", "react", "hr-feedback", "full"].includes(value);
34
35
  }
35
36
  function detectPackageManagerFromLockfiles(cwd) {
36
37
  if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) {
@@ -92,20 +93,19 @@ function copyDir(source, destination) {
92
93
  }
93
94
  }
94
95
  function templatesDir() {
95
- return path.resolve(
96
- path.dirname(new URL(import.meta.url).pathname.replace(/^\//, "")),
97
- "..",
98
- "templates"
99
- );
96
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "templates");
100
97
  }
101
98
 
102
99
  // src/prompts.ts
103
100
  var DEFAULT_PROJECT_NAME = "my-fluxy-bot";
101
+ var DEFAULT_FULL_PROJECT_NAME = "my-fluxy-app";
104
102
  async function runPrompts(inputs) {
103
+ const full = inputs.full ?? inputs.adapter === "full";
104
+ const mode = inputs.mode ?? (full ? "local" : void 0);
105
105
  let name = inputs.name;
106
106
  if (!name) {
107
107
  if (inputs.yes) {
108
- name = DEFAULT_PROJECT_NAME;
108
+ name = full ? DEFAULT_FULL_PROJECT_NAME : DEFAULT_PROJECT_NAME;
109
109
  } else {
110
110
  const result = await text({
111
111
  message: "Project name:",
@@ -122,15 +122,19 @@ async function runPrompts(inputs) {
122
122
  }
123
123
  const minimal = inputs.minimal ?? false;
124
124
  let adapter = inputs.adapter;
125
- if (!minimal && !adapter) {
125
+ if (full) {
126
+ adapter = "full";
127
+ } else if (!minimal && !adapter) {
126
128
  if (inputs.yes) {
127
129
  adapter = "react";
128
130
  } else {
129
131
  const result = await select({
130
- message: "Select an adapter:",
132
+ message: "Select a template:",
131
133
  options: [
132
- { label: "Minimal chat widget (ui-kit, recommended)", value: "minimal" },
134
+ { label: "Full stack \u2014 chat + @assistant + setup (recommended)", value: "full" },
135
+ { label: "Minimal chat widget (ui-kit)", value: "minimal" },
133
136
  { label: "React chat app (Vite + useChat)", value: "react" },
137
+ { label: "HR anonymous feedback (compliance starter)", value: "hr-feedback" },
134
138
  { label: "Basic (Cloudflare Workers bot)", value: "basic" },
135
139
  { label: "Slack", value: "slack" },
136
140
  { label: "Telegram", value: "telegram" },
@@ -146,7 +150,8 @@ async function runPrompts(inputs) {
146
150
  }
147
151
  }
148
152
  let language = inputs.language;
149
- if (!language) {
153
+ const isWorkerBot = !minimal && adapter !== "react" && adapter !== "full" && adapter !== "hr-feedback";
154
+ if (isWorkerBot && !language) {
150
155
  if (inputs.yes) {
151
156
  language = "typescript";
152
157
  } else {
@@ -160,6 +165,8 @@ async function runPrompts(inputs) {
160
165
  if (isCancel(result)) return null;
161
166
  language = result;
162
167
  }
168
+ } else if (!language) {
169
+ language = "typescript";
163
170
  }
164
171
  let packageManager = inputs.packageManager;
165
172
  if (!packageManager) {
@@ -194,10 +201,12 @@ async function runPrompts(inputs) {
194
201
  name,
195
202
  adapter: adapter ?? "react",
196
203
  packageManager,
197
- language,
204
+ language: language ?? "typescript",
198
205
  shouldInstall,
199
206
  shouldInitGit,
200
- minimal: minimal || inputs.minimal === true
207
+ minimal: minimal || inputs.minimal === true,
208
+ full: full || adapter === "full",
209
+ mode: mode ?? (adapter === "full" ? "local" : void 0)
201
210
  };
202
211
  }
203
212
 
@@ -798,10 +807,12 @@ import path2 from "path";
798
807
  import { exec } from "child_process";
799
808
  import { promisify } from "util";
800
809
  var execAsync = promisify(exec);
810
+ var TEMPLATE_CHOICES = "react, full, basic, slack, telegram, discord, web, hr-feedback";
801
811
  function parseArgs(argv) {
802
812
  const args = {
803
813
  yes: false,
804
814
  minimal: false,
815
+ full: false,
805
816
  skipInstall: false,
806
817
  noGit: false,
807
818
  help: false
@@ -815,6 +826,21 @@ function parseArgs(argv) {
815
826
  args.yes = true;
816
827
  } else if (arg === "--minimal") {
817
828
  args.minimal = true;
829
+ } else if (arg === "--full") {
830
+ args.full = true;
831
+ args.adapter = "full";
832
+ } else if (arg === "--mode") {
833
+ 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
+ }
840
+ } else {
841
+ console.error(`Invalid mode: ${value}. Choose: local, hosted`);
842
+ process.exit(1);
843
+ }
818
844
  } else if (arg === "--skip-install") {
819
845
  args.skipInstall = true;
820
846
  } else if (arg === "--no-git") {
@@ -825,8 +851,10 @@ function parseArgs(argv) {
825
851
  args.adapter = value;
826
852
  } else if (value === "react") {
827
853
  args.adapter = "react";
854
+ } else if (value === "hr-feedback") {
855
+ args.adapter = "hr-feedback";
828
856
  } else {
829
- console.error(`Invalid template: ${value}. Choose: react, basic, slack, telegram, discord, web`);
857
+ console.error(`Invalid template: ${value}. Choose: ${TEMPLATE_CHOICES}`);
830
858
  process.exit(1);
831
859
  }
832
860
  } else if (arg === "--adapter" || arg === "-a") {
@@ -834,7 +862,7 @@ function parseArgs(argv) {
834
862
  if (value && isAdapterType(value)) {
835
863
  args.adapter = value;
836
864
  } else {
837
- console.error(`Invalid adapter: ${value}. Choose: react, basic, slack, telegram, discord, web`);
865
+ console.error(`Invalid adapter: ${value}. Choose: ${TEMPLATE_CHOICES}`);
838
866
  process.exit(1);
839
867
  }
840
868
  } else if (arg === "--pm" || arg === "--package-manager") {
@@ -872,18 +900,23 @@ ${pc.bold("Usage:")}
872
900
  npx create-fluxy-chat [project-name] [options]
873
901
 
874
902
  ${pc.bold("Options:")}
875
- -a, --adapter <type> Adapter: react, basic, slack, telegram, discord, web
876
- -t, --template <type> Alias for --adapter (e.g. react)
903
+ -a, --adapter <type> Adapter: ${TEMPLATE_CHOICES}
904
+ -t, --template <type> Alias for --adapter (e.g. full, react)
877
905
  --pm <manager> Package manager: npm, pnpm, yarn
878
906
  -l, --language <lang> Language: typescript (default) or javascript
879
907
  -y, --yes Skip prompts and accept defaults
908
+ --full Full stack: chat + @assistant + setup scripts (recommended)
909
+ --mode <local|hosted> hosted = no wrangler (uses fluxychat.com demo session)
880
910
  --minimal Chat-only widget (ui-kit) \u2014 no platform modules
881
911
  --skip-install Skip dependency installation
882
912
  --no-git Skip git repository initialization
883
913
  -h, --help Show this help
884
914
 
885
915
  ${pc.bold("Examples:")}
916
+ ${pc.cyan("npx create-fluxy-chat my-app --mode hosted -y")}
917
+ ${pc.cyan("npx create-fluxy-chat my-app --full -y")}
886
918
  ${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
919
+ ${pc.cyan("npx create-fluxy-chat my-hr-bot --template hr-feedback")}
887
920
  ${pc.cyan("npx create-fluxy-chat my-chat --template react")}
888
921
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter basic")}
889
922
  ${pc.cyan("npx create-fluxy-chat my-bot --adapter slack")}
@@ -904,6 +937,8 @@ async function main() {
904
937
  language: args.language,
905
938
  yes: args.yes,
906
939
  minimal: args.minimal,
940
+ full: args.full,
941
+ mode: args.mode,
907
942
  shouldInstall: args.skipInstall ? false : void 0,
908
943
  shouldInitGit: args.noGit ? false : void 0
909
944
  });
@@ -934,6 +969,26 @@ async function main() {
934
969
  pkg.name = config.name;
935
970
  writeJson(projectDir, "package.json", pkg);
936
971
  s.stop("Minimal chat widget created.");
972
+ } else if (config.full || config.adapter === "full") {
973
+ const templateRoot = path2.join(templatesDir(), "full");
974
+ copyDir(templateRoot, projectDir);
975
+ const pkgPath = path2.join(projectDir, "package.json");
976
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
977
+ pkg.name = config.name;
978
+ writeJson(projectDir, "package.json", pkg);
979
+ if (config.full || config.adapter === "full") {
980
+ fs2.mkdirSync(path2.join(projectDir, ".fluxy"), { recursive: true });
981
+ const setupMode = config.mode === "hosted" ? "hosted" : "local";
982
+ writeFile(
983
+ projectDir,
984
+ ".fluxy/mode",
985
+ `${setupMode}
986
+ `
987
+ );
988
+ }
989
+ s.stop(
990
+ config.mode === "hosted" ? "Full stack app created (hosted mode \u2014 run pnpm setup:hosted)." : "Full stack app created (chat + agent + setup scripts)."
991
+ );
937
992
  } else if (config.adapter === "react") {
938
993
  const templateRoot = path2.join(templatesDir(), "react");
939
994
  copyDir(templateRoot, projectDir);
@@ -942,6 +997,14 @@ async function main() {
942
997
  pkg.name = config.name;
943
998
  writeJson(projectDir, "package.json", pkg);
944
999
  s.stop("React chat app created.");
1000
+ } else if (config.adapter === "hr-feedback") {
1001
+ const templateRoot = path2.join(templatesDir(), "hr-feedback");
1002
+ copyDir(templateRoot, projectDir);
1003
+ const pkgPath = path2.join(projectDir, "package.json");
1004
+ const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
1005
+ pkg.name = config.name;
1006
+ writeJson(projectDir, "package.json", pkg);
1007
+ s.stop("HR feedback starter created.");
945
1008
  } else {
946
1009
  writeJson(projectDir, "package.json", generatePackageJson(config));
947
1010
  if (config.language === "typescript") {
@@ -989,18 +1052,31 @@ async function main() {
989
1052
  );
990
1053
  }
991
1054
  }
992
- note(
993
- config.adapter === "react" ? [
994
- `cd ${config.name}`,
995
- "cp .env.example .env",
996
- `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
997
- ].join("\n") : [
998
- `cd ${config.name}`,
999
- "cp .env.example .dev.vars",
1000
- `${config.packageManager === "npm" ? "npm run" : config.packageManager} dev`
1001
- ].join("\n"),
1002
- "Next steps"
1003
- );
1055
+ const devCmd = config.packageManager === "npm" ? "npm run" : config.packageManager;
1056
+ const nextSteps = config.full || config.adapter === "full" ? config.mode === "hosted" ? [
1057
+ `cd ${config.name}`,
1058
+ `${devCmd} setup:hosted # guest JWT from fluxychat.com`,
1059
+ `${devCmd} dev # http://localhost:5173`,
1060
+ `# Keep this project: https://fluxychat.com/onboarding?from=cli`
1061
+ ].join("\n") : [
1062
+ `# Terminal 1 \u2014 FluxyChat monorepo (if not already running)`,
1063
+ `pnpm --filter @fluxy-chat/worker dev`,
1064
+ ``,
1065
+ `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`
1069
+ ].join("\n") : config.adapter === "react" || config.minimal ? [
1070
+ `cd ${config.name}`,
1071
+ "cp .env.example .env",
1072
+ "# Set VITE_FLUXYCHAT_WORKER_URL + JWT or public room ID",
1073
+ `${devCmd} dev`
1074
+ ].join("\n") : [
1075
+ `cd ${config.name}`,
1076
+ "cp .env.example .dev.vars",
1077
+ `${devCmd} dev`
1078
+ ].join("\n");
1079
+ note(nextSteps, "Next steps");
1004
1080
  outro(
1005
1081
  `${pc.green("Done!")} Visit ${pc.cyan("https://github.com/AlessandroFare/fluxychat")} for the docs.`
1006
1082
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluxy-chat/create-fluxy-chat",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Scaffold a new FluxyChat bot project with a single command",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,7 +13,8 @@
13
13
  "files": [
14
14
  "dist",
15
15
  "templates",
16
- "README.md"
16
+ "README.md",
17
+ "CHANGELOG.md"
17
18
  ],
18
19
  "dependencies": {
19
20
  "@clack/prompts": "^0.7.0",
@@ -38,7 +39,7 @@
38
39
  },
39
40
  "license": "MIT",
40
41
  "scripts": {
41
- "build": "tsup src/index.ts --format esm --dts",
42
+ "build": "tsup src/index.ts --format esm",
42
43
  "dev": "tsup src/index.ts --format esm --watch",
43
44
  "typecheck": "tsc --noEmit"
44
45
  }
package/readme.md CHANGED
@@ -5,19 +5,24 @@ Scaffold a new [FluxyChat](https://github.com/AlessandroFare/fluxychat) bot proj
5
5
  ## Quick start
6
6
 
7
7
  ```bash
8
- # Minimal chat widget (recommended 3 lines in App.tsx)
8
+ # Full stack — chat + @assistant + setup scripts (recommended)
9
+ npx create-fluxy-chat my-app --full -y
10
+ cd my-app && pnpm setup && pnpm dev
11
+
12
+ # Minimal chat widget
9
13
  npx create-fluxy-chat my-chat --minimal
10
14
 
11
- # React + useChat (more control)
15
+ # React + useChat only (bring your own worker URL)
12
16
  npx create-fluxy-chat my-chat --template react
13
17
  ```
14
18
 
15
- Interactive mode defaults to the **React chat app** template.
16
-
17
19
  ## Non-interactive usage
18
20
 
19
21
  ```bash
20
- # React + Vite + useChat (recommended)
22
+ # Full stack (chat + agent + setup)
23
+ npx create-fluxy-chat my-app --full -y
24
+
25
+ # React + Vite + useChat
21
26
  npx create-fluxy-chat my-chat --template react -y
22
27
 
23
28
  # Create a Slack bot with pnpm
@@ -37,12 +42,13 @@ npx create-fluxy-chat my-bot --adapter basic
37
42
 
38
43
  | Flag | Short | Description |
39
44
  | --- | --- | --- |
40
- | `--adapter <type>` | `-a` | Adapter: `react`, `basic`, `slack`, `telegram`, `discord`, `web` |
41
- | `--template <type>` | `-t` | Alias for `--adapter (e.g. react)` |
45
+ | `--adapter <type>` | `-a` | Adapter: `full`, `react`, `basic`, `slack`, `telegram`, `discord`, `web`, `hr-feedback` |
46
+ | `--template <type>` | `-t` | Alias for `--adapter (e.g. full, react)` |
42
47
  | `--pm <manager>` | | Package manager: `npm`, `pnpm`, `yarn` |
43
48
  | `--language <lang>` | `-l` | Language: `typescript` (default) or `javascript` |
44
49
  | `--yes` | `-y` | Skip prompts and accept defaults |
45
- | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`) no platform modules |
50
+ | `--full` | | Full stack template: chat + `@assistant` + `pnpm setup` / `pnpm dev` |
51
+ | `--minimal` | | Chat-only widget (`@fluxy-chat/ui-kit`), no platform modules |
46
52
  | `--skip-install` | | Skip dependency installation |
47
53
  | `--no-git` | | Skip git repository initialization |
48
54
  | `--help` | `-h` | Show help |
@@ -62,14 +68,14 @@ npx create-fluxy-chat my-bot --adapter basic
62
68
 
63
69
  Each generated project includes:
64
70
 
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
71
+ - **`src/index.ts`**: Cloudflare Workers entry point with route handling
72
+ - **`src/bot.ts`**: Bot handler using `@fluxy-chat/sdk`
73
+ - **`fluxy.config.ts`**: Room authz and publish middleware (basic template)
74
+ - **`wrangler.toml`**: Cloudflare Workers deployment config
75
+ - **`.dev.vars`**: Local development environment variables
76
+ - **`.env.example`**: Example environment variables for your adapter
77
+ - **`tsconfig.json`**: TypeScript configuration (for TS projects)
78
+ - **`README.md`**: Project-specific setup instructions
73
79
 
74
80
  ## Package manager detection
75
81
 
@@ -0,0 +1,10 @@
1
+ # Generated by `pnpm setup` — or copy manually after onboarding / first-message.
2
+
3
+ VITE_FLUXYCHAT_WORKER_URL=http://127.0.0.1:8787
4
+ VITE_FLUXYCHAT_MEMBER_JWT=
5
+ VITE_FLUXYCHAT_ROOM_ID=general
6
+ VITE_FLUXYCHAT_AGENT_ID=
7
+ VITE_FLUXYCHAT_AGENT_HANDLE=@assistant
8
+ VITE_FLUXYCHAT_PROJECT_ID=
9
+ VITE_FLUXYCHAT_CONSOLE_URL=http://localhost:3000
10
+ VITE_FLUXYCHAT_USER_ID=demo-user
@@ -0,0 +1,66 @@
1
+ # FluxyChat — full stack starter
2
+
3
+ Chat + AI agent + tool calls in one Vite app. Provisions against a local FluxyChat worker in ~60 seconds.
4
+
5
+ ## Quick start
6
+
7
+ **Hosted (no wrangler):**
8
+
9
+ ```bash
10
+ npx create-fluxy-chat my-app --mode hosted -y
11
+ cd my-app
12
+ pnpm install
13
+ pnpm setup:hosted
14
+ pnpm dev
15
+ ```
16
+
17
+ **Local (full control):**
18
+
19
+ ```bash
20
+ # Terminal 1 — monorepo worker
21
+ pnpm --filter @fluxy-chat/worker dev
22
+
23
+ # Terminal 2 — new project
24
+ npx create-fluxy-chat my-app --full -y
25
+ cd my-app
26
+ pnpm install
27
+ pnpm setup
28
+ pnpm dev
29
+ ```
30
+
31
+ Optional: `pnpm doctor` checks `.env`, worker health, room access.
32
+
33
+ ## What you get
34
+
35
+ - Realtime chat (`useChat` + WebSocket)
36
+ - `@assistant` invoke with tool call thread in the UI
37
+ - Sidebar with project/room/agent metadata
38
+ - Link to operator console
39
+
40
+ ## Scripts
41
+
42
+ | Command | Description |
43
+ |---------|-------------|
44
+ | `pnpm setup:hosted` | Guest JWT via `GET /demo/session` on api.fluxychat.com |
45
+ | `pnpm setup:local` | Local `/dev/provision` (same as `pnpm setup`) |
46
+ | `pnpm doctor` | Health check: .env, worker, room |
47
+ | `pnpm dev` | Start Vite; auto-start worker if FluxyChat monorepo is detected nearby |
48
+ | `pnpm dev:app` | Vite only |
49
+
50
+ ## Environment
51
+
52
+ See `.env.example`. Generated by `pnpm setup`:
53
+
54
+ - `VITE_FLUXYCHAT_WORKER_URL` — default `http://127.0.0.1:8787`
55
+ - `VITE_FLUXYCHAT_MEMBER_JWT` — member token with admin roles
56
+ - `VITE_FLUXYCHAT_ROOM_ID` — `{projectId}-general`
57
+ - `VITE_FLUXYCHAT_AGENT_ID` — built-in `@assistant`
58
+
59
+ ## Hosted credentials
60
+
61
+ Use [fluxychat.com/onboarding](https://fluxychat.com/onboarding) and copy values into `.env` manually.
62
+
63
+ ## Learn more
64
+
65
+ - [One-click product roadmap](https://github.com/AlessandroFare/fluxychat/blob/main/docs/ONE-CLICK-PRODUCT-ROADMAP.md)
66
+ - [Docs](https://docs.fluxychat.com)
@@ -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,29 @@
1
+ {
2
+ "name": "fluxy-chat-full",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "setup": "node scripts/fluxy-setup.mjs",
8
+ "setup:hosted": "node scripts/fluxy-setup.mjs --mode hosted",
9
+ "setup:local": "node scripts/fluxy-setup.mjs --mode local",
10
+ "doctor": "node scripts/fluxy-doctor.mjs",
11
+ "dev": "node scripts/fluxy-dev.mjs",
12
+ "dev:app": "vite",
13
+ "build": "tsc -b && vite build",
14
+ "preview": "vite preview"
15
+ },
16
+ "dependencies": {
17
+ "@fluxy-chat/react": "^0.1.1",
18
+ "@fluxy-chat/sdk": "^0.6.1",
19
+ "react": "^19.0.0",
20
+ "react-dom": "^19.0.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.0.0",
24
+ "@types/react-dom": "^19.0.0",
25
+ "@vitejs/plugin-react": "^4.3.0",
26
+ "typescript": "^5.6.0",
27
+ "vite": "^6.0.0"
28
+ }
29
+ }
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Start Vite app; optionally worker + dashboard from detected monorepo.
4
+ * Auto-runs setup when .env is missing and worker is reachable.
5
+ */
6
+ import { spawn, spawnSync } from "node:child_process";
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { dirname, join, resolve } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
+ const envPath = join(root, ".env");
13
+ const modePath = join(root, ".fluxy", "mode");
14
+ const WORKER_URL = process.env.FLUXY_WORKER_URL || "http://127.0.0.1:8787";
15
+ const POLL_MS = 500;
16
+ const POLL_MAX = 90_000;
17
+ const START_DASHBOARD = process.env.FLUXY_START_DASHBOARD !== "0";
18
+
19
+ function findMonorepo(startDir) {
20
+ const explicit = process.env.FLUXYCHAT_ROOT?.trim();
21
+ if (explicit && existsSync(join(explicit, "apps", "worker", "package.json"))) {
22
+ return explicit;
23
+ }
24
+ let dir = startDir;
25
+ for (let i = 0; i < 10; i++) {
26
+ const workerPkg = join(dir, "apps", "worker", "package.json");
27
+ if (existsSync(workerPkg)) {
28
+ try {
29
+ const pkg = JSON.parse(readFileSync(workerPkg, "utf8"));
30
+ if (pkg.name === "@fluxy-chat/worker") return dir;
31
+ } catch {
32
+ /* ignore */
33
+ }
34
+ }
35
+ const parent = dirname(dir);
36
+ if (parent === dir) break;
37
+ dir = parent;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ function readSetupMode() {
43
+ if (existsSync(modePath)) {
44
+ const m = readFileSync(modePath, "utf8").trim().toLowerCase();
45
+ if (m === "hosted" || m === "local") return m;
46
+ }
47
+ return "local";
48
+ }
49
+
50
+ async function isWorkerUp(url = WORKER_URL) {
51
+ try {
52
+ const r = await fetch(`${url}/health`, { signal: AbortSignal.timeout(1500) });
53
+ return r.ok;
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function sleep(ms) {
60
+ return new Promise((r) => setTimeout(r, ms));
61
+ }
62
+
63
+ function runProc(label, cmd, args, cwd, env = {}) {
64
+ const child = spawn(cmd, args, {
65
+ cwd,
66
+ shell: process.platform === "win32",
67
+ stdio: "inherit",
68
+ env: { ...process.env, ...env },
69
+ });
70
+ child.on("exit", (code) => {
71
+ if (code && code !== 0) {
72
+ console.error(`[${label}] exited with code ${code}`);
73
+ }
74
+ });
75
+ return child;
76
+ }
77
+
78
+ function runSetup(mode) {
79
+ const args = ["scripts/fluxy-setup.mjs"];
80
+ if (mode === "hosted") args.push("--mode", "hosted");
81
+ const result = spawnSync("node", args, {
82
+ cwd: root,
83
+ stdio: "inherit",
84
+ shell: process.platform === "win32",
85
+ });
86
+ return result.status === 0;
87
+ }
88
+
89
+ async function main() {
90
+ const monorepo = findMonorepo(root);
91
+ const children = [];
92
+ const setupMode = readSetupMode();
93
+
94
+ if (!existsSync(envPath)) {
95
+ if (setupMode === "hosted") {
96
+ console.log("No .env — running hosted setup…");
97
+ if (!runSetup("hosted")) {
98
+ console.warn("Hosted setup failed. Fix network or run: pnpm setup -- --mode hosted");
99
+ }
100
+ } else if (await isWorkerUp()) {
101
+ console.log("No .env — running local setup…");
102
+ if (!runSetup("local")) {
103
+ console.warn("Setup failed. Run: pnpm setup");
104
+ }
105
+ } else if (monorepo) {
106
+ console.log("No .env — will start worker then run setup…");
107
+ } else {
108
+ console.warn(
109
+ "No .env found.\n" +
110
+ " Hosted: pnpm setup -- --mode hosted\n" +
111
+ " Local: start worker, then pnpm setup",
112
+ );
113
+ }
114
+ }
115
+
116
+ if (!(await isWorkerUp()) && setupMode === "local" && monorepo) {
117
+ console.log(`Starting worker from monorepo: ${monorepo}`);
118
+ children.push(
119
+ runProc("worker", "pnpm", ["--filter", "@fluxy-chat/worker", "dev"], monorepo, {
120
+ ALLOW_DEV_PROVISION: "true",
121
+ NODE_ENV: "development",
122
+ }),
123
+ );
124
+ const start = Date.now();
125
+ while (Date.now() - start < POLL_MAX) {
126
+ if (await isWorkerUp()) {
127
+ console.log(`Worker ready at ${WORKER_URL}`);
128
+ break;
129
+ }
130
+ await sleep(POLL_MS);
131
+ }
132
+ if (!existsSync(envPath) && (await isWorkerUp())) {
133
+ console.log("Running setup after worker start…");
134
+ runSetup("local");
135
+ }
136
+ } else if (await isWorkerUp()) {
137
+ console.log(`Worker reachable at ${WORKER_URL}`);
138
+ }
139
+
140
+ if (START_DASHBOARD && monorepo && setupMode === "local") {
141
+ console.log("Starting dashboard console on :3000");
142
+ children.push(
143
+ runProc("dashboard", "pnpm", ["--filter", "@fluxy-chat/dashboard", "dev"], monorepo),
144
+ );
145
+ }
146
+
147
+ children.push(runProc("vite", "pnpm", ["run", "dev:app"], root));
148
+
149
+ function shutdown() {
150
+ for (const child of children) {
151
+ if (child && !child.killed) {
152
+ try {
153
+ child.kill("SIGTERM");
154
+ } catch {
155
+ /* ignore */
156
+ }
157
+ }
158
+ }
159
+ process.exit(0);
160
+ }
161
+
162
+ process.on("SIGINT", shutdown);
163
+ process.on("SIGTERM", shutdown);
164
+ }
165
+
166
+ main().catch((err) => {
167
+ console.error(err);
168
+ process.exit(1);
169
+ });