@flareflow/cli 0.0.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/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ import { cac } from "cac";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import { build } from "esbuild";
6
+ const cli = cac("flareflow");
7
+ async function loadConfig() {
8
+ const configPath = path.resolve(process.cwd(), "flareflow.config.ts");
9
+ if (!fs.existsSync(configPath))
10
+ return null;
11
+ const outDir = path.join(process.cwd(), ".flareflow");
12
+ if (!fs.existsSync(outDir))
13
+ fs.mkdirSync(outDir);
14
+ const outFile = path.join(outDir, "config.js");
15
+ await build({
16
+ entryPoints: [configPath],
17
+ outfile: outFile,
18
+ bundle: true,
19
+ format: "cjs",
20
+ platform: "node",
21
+ });
22
+ const config = require(outFile);
23
+ return config.default || config;
24
+ }
25
+ function generateWranglerToml(config) {
26
+ let toml = `name = "${config.name}"\n`;
27
+ toml += `main = "src/index.ts"\n`;
28
+ toml += `compatibility_date = "${config.compatibilityDate || "2024-04-05"}"\n\n`;
29
+ if (config.resources?.db) {
30
+ toml += `[[d1_databases]]\nbinding = "DB"\ndatabase_name = "${config.resources.db.name}"\ndatabase_id = "${config.resources.db.database_id}"\n\n`;
31
+ }
32
+ if (config.resources?.cache) {
33
+ toml += `[[kv_namespaces]]\nbinding = "CACHE"\nid = "${config.resources.cache.id}"\n\n`;
34
+ }
35
+ // Durable Objects
36
+ const doClasses = [
37
+ ...(config.actors || []),
38
+ ...(config.rooms || []),
39
+ ...(config.workflows || []),
40
+ ];
41
+ if (doClasses.length > 0) {
42
+ toml += `[durable_objects]\nbindings = [\n`;
43
+ for (const cls of doClasses) {
44
+ toml += ` { name = "${cls}", class_name = "${cls}" },\n`;
45
+ }
46
+ toml += `]\n\n`;
47
+ toml += `[[migrations]]\ntag = "v${Date.now()}"\nnew_classes = [${doClasses.map(c => `"${c}"`).join(", ")}]\n\n`;
48
+ }
49
+ fs.writeFileSync(path.join(process.cwd(), "wrangler.toml"), toml);
50
+ }
51
+ cli
52
+ .command("init <dir>", "Initialize a new flareflow project")
53
+ .action((dir) => {
54
+ const targetDir = path.resolve(process.cwd(), dir);
55
+ if (!fs.existsSync(targetDir))
56
+ fs.mkdirSync(targetDir, { recursive: true });
57
+ const packageJson = {
58
+ name: path.basename(dir),
59
+ version: "0.0.1",
60
+ private: true,
61
+ scripts: {
62
+ dev: "flareflow dev",
63
+ deploy: "flareflow deploy",
64
+ "db:generate": "flareflow db:generate",
65
+ "db:migrate": "flareflow db:migrate"
66
+ },
67
+ dependencies: {
68
+ "@flareflow/core": "0.0.1",
69
+ "@flareflow/db": "0.0.1",
70
+ "@flareflow/actor": "0.0.1",
71
+ "@flareflow/realtime": "0.0.1",
72
+ "@flareflow/workflow": "0.0.1",
73
+ "@flareflow/auth": "0.0.1"
74
+ },
75
+ devDependencies: {
76
+ "typescript": "^5.4.5",
77
+ "wrangler": "^3.50.0",
78
+ "@flareflow/cli": "0.0.1"
79
+ }
80
+ };
81
+ fs.writeFileSync(path.join(targetDir, "package.json"), JSON.stringify(packageJson, null, 2));
82
+ const configTs = `import { defineConfig } from "@flareflow/cli";
83
+
84
+ export default defineConfig({
85
+ name: "${path.basename(dir)}",
86
+ compatibilityDate: "2024-04-05"
87
+ });
88
+ `;
89
+ fs.writeFileSync(path.join(targetDir, "flareflow.config.ts"), configTs);
90
+ const srcDir = path.join(targetDir, "src");
91
+ if (!fs.existsSync(srcDir))
92
+ fs.mkdirSync(srcDir);
93
+ const indexTs = `import { createApp } from "@flareflow/core";
94
+
95
+ const app = createApp({
96
+ name: "${path.basename(dir)}"
97
+ });
98
+
99
+ app.get("/", async (ctx) => {
100
+ return { message: "Hello from flareflow!" };
101
+ });
102
+
103
+ export default app.export();
104
+ `;
105
+ fs.writeFileSync(path.join(srcDir, "index.ts"), indexTs);
106
+ console.log(`flareflow project initialized in ${targetDir}`);
107
+ console.log(`Run: cd ${dir} && npm install`);
108
+ });
109
+ cli
110
+ .command("generate <type> <name>", "Generate boilerplate for entities, actors, etc.")
111
+ .action((type, name) => {
112
+ const srcDir = path.join(process.cwd(), "src");
113
+ if (!fs.existsSync(srcDir))
114
+ fs.mkdirSync(srcDir);
115
+ const plural = type === "entity" ? "entities" : `${type}s`;
116
+ const typeDir = path.join(srcDir, plural);
117
+ if (!fs.existsSync(typeDir))
118
+ fs.mkdirSync(typeDir);
119
+ let template = "";
120
+ const fileName = `${name.toLowerCase()}.${type}.ts`;
121
+ switch (type) {
122
+ case "entity":
123
+ template = `import { entity, field } from "@flareflow/db";
124
+
125
+ export const ${name} = entity("${name.toLowerCase()}s", {
126
+ id: field.id(),
127
+ createdAt: field.timestamp("createdAt"),
128
+ });
129
+ `;
130
+ break;
131
+ case "actor":
132
+ template = `import { actor } from "@flareflow/actor";
133
+
134
+ export const ${name}Actor = actor("${name}Actor", {
135
+ state: {},
136
+ methods: {
137
+ async ping(ctx) {
138
+ return "pong";
139
+ }
140
+ }
141
+ });
142
+ `;
143
+ break;
144
+ case "job":
145
+ template = `import { job } from "@flareflow/jobs";
146
+
147
+ export const ${name}Job = job("${name.toLowerCase()}", {
148
+ handler: async (ctx, payload) => {
149
+ console.log("Running ${name}Job", payload);
150
+ }
151
+ });
152
+ `;
153
+ break;
154
+ }
155
+ if (template) {
156
+ fs.writeFileSync(path.join(typeDir, fileName), template);
157
+ console.log(`Generated ${type} ${name} in src/${plural}/${fileName}`);
158
+ }
159
+ else {
160
+ console.error(`Unknown type: ${type}`);
161
+ }
162
+ });
163
+ cli
164
+ .command("dev", "Start local development server")
165
+ .action(async () => {
166
+ const config = await loadConfig();
167
+ if (config)
168
+ generateWranglerToml(config);
169
+ console.log("Starting wrangler dev...");
170
+ const { spawnSync } = require("child_process");
171
+ spawnSync("npx", ["wrangler", "dev"], { stdio: "inherit" });
172
+ });
173
+ cli
174
+ .command("deploy", "Deploy to Flareflow Workers")
175
+ .action(async () => {
176
+ const config = await loadConfig();
177
+ if (config)
178
+ generateWranglerToml(config);
179
+ console.log("Deploying via wrangler...");
180
+ const { spawnSync } = require("child_process");
181
+ spawnSync("npx", ["wrangler", "deploy"], { stdio: "inherit" });
182
+ });
183
+ cli
184
+ .command("db:generate", "Generate migrations from entities")
185
+ .action(() => {
186
+ console.log("Generating migrations via drizzle-kit...");
187
+ // TODO: Invoke drizzle-kit
188
+ });
189
+ cli
190
+ .command("db:migrate", "Apply migrations to D1")
191
+ .action(() => {
192
+ console.log("Applying migrations via drizzle-kit...");
193
+ // TODO: Invoke drizzle-kit
194
+ });
195
+ cli
196
+ .command("studio", "Start flareflow Studio")
197
+ .action(() => {
198
+ const http = require("http");
199
+ const fs = require("fs");
200
+ const path = require("path");
201
+ const server = http.createServer((req, res) => {
202
+ const studioPath = path.join(__dirname, "studio.html");
203
+ if (fs.existsSync(studioPath)) {
204
+ res.writeHead(200, { "Content-Type": "text/html" });
205
+ res.end(fs.readFileSync(studioPath));
206
+ }
207
+ else {
208
+ // Fallback for development if not in dist
209
+ const devPath = path.join(process.cwd(), "packages/cli/src/studio.html");
210
+ if (fs.existsSync(devPath)) {
211
+ res.writeHead(200, { "Content-Type": "text/html" });
212
+ res.end(fs.readFileSync(devPath));
213
+ }
214
+ else {
215
+ res.writeHead(404);
216
+ res.end("Studio dashboard not found. Please run build.");
217
+ }
218
+ }
219
+ });
220
+ server.listen(4000, () => {
221
+ console.log("🚀 flareflow Studio started at http://localhost:4000");
222
+ console.log("Connects to local app at http://localhost:8787");
223
+ });
224
+ });
225
+ cli.help();
226
+ cli.version("0.0.1");
227
+ cli.parse();
228
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export interface FlareflowConfig {
2
+ name: string;
3
+ compatibilityDate?: string;
4
+ resources?: {
5
+ db?: {
6
+ type: "d1";
7
+ name: string;
8
+ database_id: string;
9
+ };
10
+ cache?: {
11
+ type: "kv";
12
+ name: string;
13
+ id: string;
14
+ };
15
+ storage?: {
16
+ type: "r2";
17
+ name: string;
18
+ };
19
+ queue?: {
20
+ type: "queue";
21
+ name: string;
22
+ };
23
+ };
24
+ actors?: string[];
25
+ rooms?: string[];
26
+ workflows?: string[];
27
+ env?: Record<string, string>;
28
+ }
29
+ export declare function defineConfig(config: FlareflowConfig): FlareflowConfig;
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE;QACV,EAAE,CAAC,EAAE;YAAE,IAAI,EAAE,IAAI,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,WAAW,EAAE,MAAM,CAAA;SAAE,CAAC;QACvD,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,IAAI,CAAC;YAAC,IAAI,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,CAAC;QACjD,OAAO,CAAC,EAAE;YAAE,IAAI,EAAE,IAAI,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QACvC,KAAK,CAAC,EAAE;YAAE,IAAI,EAAE,OAAO,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACzC,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,CAErE"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export function defineConfig(config) {
2
+ return config;
3
+ }
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAeA,MAAM,UAAU,YAAY,CAAC,MAAuB;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,288 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Flareflow Studio</title>
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
10
+ <script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
11
+ <script src="https://unpkg.com/lucide-react"></script>
12
+ <style>
13
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
14
+
15
+ body {
16
+ font-family: 'Inter', sans-serif;
17
+ background-color: #0a0a0a;
18
+ color: #f4f4f4;
19
+ }
20
+
21
+ .glass {
22
+ background: rgba(255, 255, 255, 0.03);
23
+ backdrop-filter: blur(8px);
24
+ border: 1px solid rgba(255, 255, 255, 0.05);
25
+ }
26
+ </style>
27
+ </head>
28
+
29
+ <body>
30
+ <div id="root"></div>
31
+ <script>
32
+ const { useState, useEffect } = React;
33
+ const { Home, Database, Box, Play, MessageCircle, Settings, ChevronRight, Activity, Search } = LucideReact;
34
+
35
+ const API_BASE = 'http://localhost:8787/__Flareflow/studio';
36
+
37
+ function App() {
38
+ const [activeTab, setActiveTab] = useState('dashboard');
39
+ const [meta, setMeta] = useState(null);
40
+ const [loading, setLoading] = useState(true);
41
+
42
+ useEffect(() => {
43
+ fetch(`${API_BASE}/meta`)
44
+ .then(r => r.json())
45
+ .then(data => { setMeta(data); setLoading(false); })
46
+ .catch(e => { console.error(e); setLoading(false); });
47
+ }, []);
48
+
49
+ if (loading) return (
50
+ <div className="flex h-screen items-center justify-center">
51
+ <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-indigo-500"></div>
52
+ </div>
53
+ );
54
+
55
+ return (
56
+ <div className="flex h-screen overflow-hidden">
57
+ {/* Sidebar */}
58
+ <div className="w-64 border-r border-white/5 bg-[#0e0e0e] flex flex-col">
59
+ <div className="p-6 flex items-center gap-2 mb-4">
60
+ <div className="bg-indigo-600 rounded-lg p-1">
61
+ <Box size={20} className="text-white" />
62
+ </div>
63
+ <span className="font-bold text-lg tracking-tight">Flareflow Studio</span>
64
+ </div>
65
+
66
+ <nav className="flex-1 px-4 space-y-1">
67
+ <NavItem icon={<Home size={18} />} label="Dashboard" active={activeTab === 'dashboard'} onClick={() => setActiveTab('dashboard')} />
68
+ <NavItem icon={<Database size={18} />} label="Entities" active={activeTab === 'entities'} onClick={() => setActiveTab('entities')} count={meta?.entities.length} />
69
+ <NavItem icon={<Box size={18} />} label="Actors" active={activeTab === 'actors'} onClick={() => setActiveTab('actors')} count={meta?.actors.length} />
70
+ <NavItem icon={<Play size={18} />} label="Workflows" active={activeTab === 'workflows'} onClick={() => setActiveTab('workflows')} count={meta?.workflows.length} />
71
+ <NavItem icon={<MessageCircle size={18} />} label="Realtime" active={activeTab === 'realtime'} onClick={() => setActiveTab('realtime')} count={meta?.rooms.length} />
72
+ </nav>
73
+
74
+ <div className="p-6 border-t border-white/5 text-xs text-white/40">
75
+ v0.0.1 ALPHA
76
+ </div>
77
+ </div>
78
+
79
+ {/* Content */}
80
+ <div className="flex-1 overflow-auto bg-[#0a0a0a]">
81
+ <header className="h-16 border-b border-white/5 flex items-center justify-between px-8 bg-[#0a0a0a]/80 backdrop-blur-md sticky top-0 z-10">
82
+ <h2 className="font-semibold text-lg">{activeTab.charAt(0).toUpperCase() + activeTab.slice(1)}</h2>
83
+ <div className="flex items-center gap-4">
84
+ <div className="flex items-center gap-2 text-xs text-indigo-400 bg-indigo-500/10 px-3 py-1.5 rounded-full border border-indigo-500/20">
85
+ <Activity size={12} /> Live Sync
86
+ </div>
87
+ <div className="text-xs text-white/40 font-mono">localhost:8787</div>
88
+ </div>
89
+ </header>
90
+
91
+ <main className="p-8">
92
+ {activeTab === 'dashboard' && <DashboardView meta={meta} />}
93
+ {activeTab === 'entities' && <EntitiesView meta={meta} />}
94
+ {activeTab === 'actors' && <ActorsView meta={meta} />}
95
+ </main>
96
+ </div>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ function NavItem({ icon, label, active, onClick, count }) {
102
+ return (
103
+ <button
104
+ onClick={onClick}
105
+ className={`w-full flex items-center justify-between px-4 py-2.5 rounded-lg transition-all ${active ? 'bg-indigo-600/10 text-indigo-400 border border-indigo-500/20' : 'text-white/60 hover:text-white hover:bg-white/5 border border-transparent'}`}
106
+ >
107
+ <div className="flex items-center gap-3">
108
+ {icon}
109
+ <span className="font-medium text-sm">{label}</span>
110
+ </div>
111
+ {count !== undefined && <span className="text-[10px] bg-white/5 px-2 py-0.5 rounded-full border border-white/5">{count}</span>}
112
+ </button>
113
+ );
114
+ }
115
+
116
+ function DashboardView({ meta }) {
117
+ return (
118
+ <div className="space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-500">
119
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
120
+ <StatCard label="D1 Entities" value={meta.entities.length} icon={<Database size={20} />} color="indigo" />
121
+ <StatCard label="Business Actors" value={meta.actors.length} icon={<Box size={20} />} color="emerald" />
122
+ <StatCard label="Active Workflows" value={meta.workflows.length} icon={<Play size={20} />} color="amber" />
123
+ <StatCard label="Realtime Rooms" value={meta.rooms.length} icon={<MessageCircle size={20} />} color="rose" />
124
+ </div>
125
+
126
+ <div className="glass rounded-xl p-8">
127
+ <h3 className="text-xl font-bold mb-4">Welcome to Flareflow Studio</h3>
128
+ <p className="text-white/60 max-w-2xl leading-relaxed">
129
+ Your application <span className="text-indigo-400 font-mono">"{meta.name}"</span> is running locally.
130
+ Use the sidebar to explore your database tables, inspect Durable Object state, and monitor workflow execution history in real-time.
131
+ </p>
132
+ </div>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ function StatCard({ label, value, icon, color }) {
138
+ const colors = {
139
+ indigo: 'text-indigo-400 bg-indigo-500/10 border-indigo-500/20',
140
+ emerald: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/20',
141
+ amber: 'text-amber-400 bg-amber-500/10 border-amber-500/20',
142
+ rose: 'text-rose-400 bg-rose-500/10 border-rose-500/20'
143
+ };
144
+ return (
145
+ <div className="glass rounded-xl p-6 flex items-center gap-6">
146
+ <div className={`p-3 rounded-xl border ${colors[color]}`}>{icon}</div>
147
+ <div>
148
+ <div className="text-white/40 text-sm font-medium">{label}</div>
149
+ <div className="text-2xl font-bold">{value}</div>
150
+ </div>
151
+ </div>
152
+ );
153
+ }
154
+
155
+ function EntitiesView({ meta }) {
156
+ const [selectedTable, setSelectedTable] = useState(meta.entities[0]);
157
+ const [rows, setRows] = useState([]);
158
+ const [loading, setLoading] = useState(false);
159
+
160
+ useEffect(() => {
161
+ if (!selectedTable) return;
162
+ setLoading(true);
163
+ fetch(`${API_BASE}/db/${selectedTable}`)
164
+ .then(r => r.json())
165
+ .then(data => { setRows(data); setLoading(false); })
166
+ .catch(e => setLoading(false));
167
+ }, [selectedTable]);
168
+
169
+ return (
170
+ <div className="space-y-6">
171
+ <div className="flex gap-2">
172
+ {meta.entities.map(t => (
173
+ <button
174
+ key={t}
175
+ onClick={() => setSelectedTable(t)}
176
+ className={`px-4 py-2 rounded-lg text-sm font-medium border transition-all ${selectedTable === t ? 'bg-white/5 border-white/20 text-white' : 'border-transparent text-white/40 hover:text-white/60'}`}
177
+ >
178
+ {t}
179
+ </button>
180
+ ))}
181
+ </div>
182
+
183
+ <div className="glass rounded-xl overflow-hidden">
184
+ {loading ? (
185
+ <div className="p-20 flex justify-center"><div className="animate-spin rounded-full h-8 w-8 border-t-2 border-indigo-500"></div></div>
186
+ ) : (
187
+ <div className="overflow-x-auto">
188
+ <table className="w-full text-left text-sm">
189
+ <thead>
190
+ <tr className="border-b border-white/5 bg-white/[0.02]">
191
+ {rows.length > 0 && Object.keys(rows[0]).map(k => (
192
+ <th key={k} className="px-6 py-4 font-semibold text-white/40">{k}</th>
193
+ ))}
194
+ </tr>
195
+ </thead>
196
+ <tbody className="divide-y divide-white/5">
197
+ {rows.map((r, i) => (
198
+ <tr key={i} className="hover:bg-white/[0.01] transition-colors">
199
+ {Object.values(r).map((v, j) => (
200
+ <td key={j} className="px-6 py-4 text-white/80 font-mono text-xs">
201
+ {typeof v === 'object' ? JSON.stringify(v) : String(v)}
202
+ </td>
203
+ ))}
204
+ </tr>
205
+ ))}
206
+ </tbody>
207
+ </table>
208
+ {rows.length === 0 && <div className="p-20 text-center text-white/20 italic">No rows found</div>}
209
+ </div>
210
+ )}
211
+ </div>
212
+ </div>
213
+ );
214
+ }
215
+
216
+ function ActorsView({ meta }) {
217
+ const [selectedType, setSelectedType] = useState(meta.actors[0]);
218
+ const [actorId, setActorId] = useState('');
219
+ const [state, setState] = useState(null);
220
+ const [loading, setLoading] = useState(false);
221
+
222
+ const fetchState = () => {
223
+ if (!actorId) return;
224
+ setLoading(true);
225
+ fetch(`${API_BASE}/actor/${selectedType}/${actorId}`)
226
+ .then(r => r.json())
227
+ .then(data => { setState(data.state); setLoading(false); })
228
+ .catch(e => setLoading(false));
229
+ };
230
+
231
+ return (
232
+ <div className="space-y-8">
233
+ <div className="max-w-xl space-y-4">
234
+ <div className="text-sm font-medium text-white/40 mb-1">Actor ID (Name)</div>
235
+ <div className="flex gap-3">
236
+ <select
237
+ value={selectedType}
238
+ onChange={e => setSelectedType(e.target.value)}
239
+ className="bg-[#0e0e0e] border border-white/10 rounded-lg px-4 py-2 text-sm focus:outline-none focus:border-indigo-500"
240
+ >
241
+ {meta.actors.map(t => <option key={t} value={t}>{t}</option>)}
242
+ </select>
243
+ <input
244
+ type="text"
245
+ placeholder="Enter instance ID..."
246
+ value={actorId}
247
+ onChange={e => setActorId(e.target.value)}
248
+ className="flex-1 bg-[#0e0e0e] border border-white/10 rounded-lg px-4 py-2 text-sm focus:outline-none focus:border-indigo-500"
249
+ />
250
+ <button
251
+ onClick={fetchState}
252
+ className="bg-indigo-600 hover:bg-indigo-500 text-white px-6 py-2 rounded-lg text-sm font-semibold transition-all shadow-lg shadow-indigo-600/20"
253
+ >
254
+ Inspect
255
+ </button>
256
+ </div>
257
+ </div>
258
+
259
+ <div className="glass rounded-xl p-8 min-h-[400px]">
260
+ {loading ? (
261
+ <div className="flex justify-center py-20"><div className="animate-spin rounded-full h-8 w-8 border-t-2 border-indigo-500"></div></div>
262
+ ) : state ? (
263
+ <div className="space-y-4 animate-in fade-in duration-300">
264
+ <div className="flex items-center gap-2 mb-2">
265
+ <div className="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"></div>
266
+ <span className="text-xs font-bold text-white/40 uppercase tracking-widest">Active State</span>
267
+ </div>
268
+ <pre className="text-indigo-300 font-mono text-xs leading-relaxed bg-black/40 p-6 rounded-lg border border-white/5 overflow-auto max-h-[500px]">
269
+ {JSON.stringify(state, null, 2)}
270
+ </pre>
271
+ </div>
272
+ ) : (
273
+ <div className="flex flex-col items-center justify-center py-20 text-white/20">
274
+ <Search size={48} className="mb-4 opacity-50" />
275
+ <p className="italic">Enter an Actor ID above to inspect its live state</p>
276
+ </div>
277
+ )}
278
+ </div>
279
+ </div>
280
+ );
281
+ }
282
+
283
+ const root = ReactDOM.createRoot(document.getElementById('root'));
284
+ root.render(<App />);
285
+ </script>
286
+ </body>
287
+
288
+ </html>
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@flareflow/cli",
3
+ "version": "0.0.1",
4
+ "description": "flareflow CLI",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "flareflow": "./dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc && cp src/studio.html dist/studio.html",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "dependencies": {
15
+ "cac": "^6.7.14",
16
+ "esbuild": "^0.20.2"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.4.5",
20
+ "@types/node": "^20.0.0"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/flareflow/flareflow"
32
+ }
33
+ }