@fluxy-chat/create-fluxy-chat 0.4.0 → 0.5.1
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 +22 -0
- package/dist/index.js +94 -31
- package/package.json +3 -2
- package/readme.md +13 -7
- package/templates/full/.env.example +10 -0
- package/templates/full/README.md +66 -0
- package/templates/full/index.html +12 -0
- package/templates/full/package.json +29 -0
- package/templates/full/scripts/fluxy-dev.mjs +169 -0
- package/templates/full/scripts/fluxy-doctor.mjs +122 -0
- package/templates/full/scripts/fluxy-setup.mjs +323 -0
- package/templates/full/src/App.tsx +223 -0
- package/templates/full/src/index.css +242 -0
- package/templates/full/src/main.tsx +10 -0
- package/templates/full/src/vite-env.d.ts +16 -0
- package/templates/full/tsconfig.json +21 -0
- package/templates/full/vite.config.ts +7 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.5.1] - 2026-08-19
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Docs and CLI help use the scoped npm name: `npx @fluxy-chat/create-fluxy-chat@latest` (unscoped `create-fluxy-chat` 404s on the registry)
|
|
8
|
+
|
|
9
|
+
## [0.5.0] - 2026-08-19
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `--full` / `--template full` — Vite app with realtime chat, `@assistant` invoke, and tool thread
|
|
14
|
+
- `--mode hosted` — guest JWT via `GET /demo/session` (no local wrangler)
|
|
15
|
+
- `--mode local` — provision against a local worker (`POST /dev/provision`)
|
|
16
|
+
- Template scripts: `pnpm setup`, `pnpm setup:hosted`, `pnpm doctor`, `pnpm dev`
|
|
17
|
+
- CLI next-steps link to `/onboarding?from=cli` (keep / import `.env`)
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- Interactive picker lists Full stack first
|
|
22
|
+
- 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", "hr-feedback"].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,14 +122,17 @@ async function runPrompts(inputs) {
|
|
|
122
122
|
}
|
|
123
123
|
const minimal = inputs.minimal ?? false;
|
|
124
124
|
let adapter = inputs.adapter;
|
|
125
|
-
if (
|
|
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
|
|
132
|
+
message: "Select a template:",
|
|
131
133
|
options: [
|
|
132
|
-
{ label: "
|
|
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" },
|
|
134
137
|
{ label: "HR anonymous feedback (compliance starter)", value: "hr-feedback" },
|
|
135
138
|
{ label: "Basic (Cloudflare Workers bot)", value: "basic" },
|
|
@@ -147,7 +150,8 @@ async function runPrompts(inputs) {
|
|
|
147
150
|
}
|
|
148
151
|
}
|
|
149
152
|
let language = inputs.language;
|
|
150
|
-
|
|
153
|
+
const isWorkerBot = !minimal && adapter !== "react" && adapter !== "full" && adapter !== "hr-feedback";
|
|
154
|
+
if (isWorkerBot && !language) {
|
|
151
155
|
if (inputs.yes) {
|
|
152
156
|
language = "typescript";
|
|
153
157
|
} else {
|
|
@@ -161,6 +165,8 @@ async function runPrompts(inputs) {
|
|
|
161
165
|
if (isCancel(result)) return null;
|
|
162
166
|
language = result;
|
|
163
167
|
}
|
|
168
|
+
} else if (!language) {
|
|
169
|
+
language = "typescript";
|
|
164
170
|
}
|
|
165
171
|
let packageManager = inputs.packageManager;
|
|
166
172
|
if (!packageManager) {
|
|
@@ -195,10 +201,12 @@ async function runPrompts(inputs) {
|
|
|
195
201
|
name,
|
|
196
202
|
adapter: adapter ?? "react",
|
|
197
203
|
packageManager,
|
|
198
|
-
language,
|
|
204
|
+
language: language ?? "typescript",
|
|
199
205
|
shouldInstall,
|
|
200
206
|
shouldInitGit,
|
|
201
|
-
minimal: minimal || inputs.minimal === true
|
|
207
|
+
minimal: minimal || inputs.minimal === true,
|
|
208
|
+
full: full || adapter === "full",
|
|
209
|
+
mode: mode ?? (adapter === "full" ? "local" : void 0)
|
|
202
210
|
};
|
|
203
211
|
}
|
|
204
212
|
|
|
@@ -799,10 +807,12 @@ import path2 from "path";
|
|
|
799
807
|
import { exec } from "child_process";
|
|
800
808
|
import { promisify } from "util";
|
|
801
809
|
var execAsync = promisify(exec);
|
|
810
|
+
var TEMPLATE_CHOICES = "react, full, basic, slack, telegram, discord, web, hr-feedback";
|
|
802
811
|
function parseArgs(argv) {
|
|
803
812
|
const args = {
|
|
804
813
|
yes: false,
|
|
805
814
|
minimal: false,
|
|
815
|
+
full: false,
|
|
806
816
|
skipInstall: false,
|
|
807
817
|
noGit: false,
|
|
808
818
|
help: false
|
|
@@ -816,6 +826,21 @@ function parseArgs(argv) {
|
|
|
816
826
|
args.yes = true;
|
|
817
827
|
} else if (arg === "--minimal") {
|
|
818
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
|
+
}
|
|
819
844
|
} else if (arg === "--skip-install") {
|
|
820
845
|
args.skipInstall = true;
|
|
821
846
|
} else if (arg === "--no-git") {
|
|
@@ -829,7 +854,7 @@ function parseArgs(argv) {
|
|
|
829
854
|
} else if (value === "hr-feedback") {
|
|
830
855
|
args.adapter = "hr-feedback";
|
|
831
856
|
} else {
|
|
832
|
-
console.error(`Invalid template: ${value}. Choose:
|
|
857
|
+
console.error(`Invalid template: ${value}. Choose: ${TEMPLATE_CHOICES}`);
|
|
833
858
|
process.exit(1);
|
|
834
859
|
}
|
|
835
860
|
} else if (arg === "--adapter" || arg === "-a") {
|
|
@@ -837,7 +862,7 @@ function parseArgs(argv) {
|
|
|
837
862
|
if (value && isAdapterType(value)) {
|
|
838
863
|
args.adapter = value;
|
|
839
864
|
} else {
|
|
840
|
-
console.error(`Invalid adapter: ${value}. Choose:
|
|
865
|
+
console.error(`Invalid adapter: ${value}. Choose: ${TEMPLATE_CHOICES}`);
|
|
841
866
|
process.exit(1);
|
|
842
867
|
}
|
|
843
868
|
} else if (arg === "--pm" || arg === "--package-manager") {
|
|
@@ -872,20 +897,24 @@ var HELP_TEXT = `
|
|
|
872
897
|
${pc.bold("create-fluxy-chat")} \u2014 Scaffold a new FluxyChat bot project
|
|
873
898
|
|
|
874
899
|
${pc.bold("Usage:")}
|
|
875
|
-
npx create-fluxy-chat [project-name] [options]
|
|
900
|
+
npx @fluxy-chat/create-fluxy-chat [project-name] [options]
|
|
876
901
|
|
|
877
902
|
${pc.bold("Options:")}
|
|
878
|
-
-a, --adapter <type> Adapter:
|
|
879
|
-
-t, --template <type> Alias for --adapter (e.g.
|
|
903
|
+
-a, --adapter <type> Adapter: ${TEMPLATE_CHOICES}
|
|
904
|
+
-t, --template <type> Alias for --adapter (e.g. full, react)
|
|
880
905
|
--pm <manager> Package manager: npm, pnpm, yarn
|
|
881
906
|
-l, --language <lang> Language: typescript (default) or javascript
|
|
882
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)
|
|
883
910
|
--minimal Chat-only widget (ui-kit) \u2014 no platform modules
|
|
884
911
|
--skip-install Skip dependency installation
|
|
885
912
|
--no-git Skip git repository initialization
|
|
886
913
|
-h, --help Show this help
|
|
887
914
|
|
|
888
915
|
${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")}
|
|
889
918
|
${pc.cyan("npx create-fluxy-chat my-chat --minimal")}
|
|
890
919
|
${pc.cyan("npx create-fluxy-chat my-hr-bot --template hr-feedback")}
|
|
891
920
|
${pc.cyan("npx create-fluxy-chat my-chat --template react")}
|
|
@@ -908,6 +937,8 @@ async function main() {
|
|
|
908
937
|
language: args.language,
|
|
909
938
|
yes: args.yes,
|
|
910
939
|
minimal: args.minimal,
|
|
940
|
+
full: args.full,
|
|
941
|
+
mode: args.mode,
|
|
911
942
|
shouldInstall: args.skipInstall ? false : void 0,
|
|
912
943
|
shouldInitGit: args.noGit ? false : void 0
|
|
913
944
|
});
|
|
@@ -938,6 +969,26 @@ async function main() {
|
|
|
938
969
|
pkg.name = config.name;
|
|
939
970
|
writeJson(projectDir, "package.json", pkg);
|
|
940
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
|
+
);
|
|
941
992
|
} else if (config.adapter === "react") {
|
|
942
993
|
const templateRoot = path2.join(templatesDir(), "react");
|
|
943
994
|
copyDir(templateRoot, projectDir);
|
|
@@ -1001,19 +1052,31 @@ async function main() {
|
|
|
1001
1052
|
);
|
|
1002
1053
|
}
|
|
1003
1054
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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");
|
|
1017
1080
|
outro(
|
|
1018
1081
|
`${pc.green("Done!")} Visit ${pc.cyan("https://github.com/AlessandroFare/fluxychat")} for the docs.`
|
|
1019
1082
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluxy-chat/create-fluxy-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
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",
|
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
|
-
#
|
|
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
|
|
11
|
+
|
|
12
|
+
# Minimal chat widget
|
|
9
13
|
npx create-fluxy-chat my-chat --minimal
|
|
10
14
|
|
|
11
|
-
# React + useChat (
|
|
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
|
-
#
|
|
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,11 +42,12 @@ 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 |
|
|
50
|
+
| `--full` | | Full stack template: chat + `@assistant` + `pnpm setup` / `pnpm dev` |
|
|
45
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 |
|
|
@@ -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 @fluxy-chat/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
|
+
});
|