create-claude-flow-codex-ui 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +82 -0
- package/bin/claude-flow-codex-ui.mjs +3 -0
- package/bin/create-claude-flow-codex-ui.mjs +8 -0
- package/package.json +39 -0
- package/src/cli/init.mjs +246 -0
- package/templates/admin/README.md +49 -0
- package/templates/admin/dashboard.css +919 -0
- package/templates/admin/dashboard.html +120 -0
- package/templates/admin/dashboard.js +1004 -0
- package/templates/admin/dev-server.mjs +314 -0
- package/templates/admin/state/swarm-state.json +14 -0
- package/templates/admin/sync-telemetry.mjs +324 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# create-claude-flow-codex-ui
|
|
2
|
+
|
|
3
|
+
`npm create` initializer that sets up Claude Flow Codex and adds a live agent-progress admin UI.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
When you run the initializer, it:
|
|
8
|
+
|
|
9
|
+
1. Runs:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx claude-flow@alpha init --codex --full
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
2. Adds admin dashboard files to:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
src/admin/
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
3. Adds npm scripts in the target project:
|
|
22
|
+
|
|
23
|
+
- `claudeflow:admin:sync`
|
|
24
|
+
- `claudeflow:admin:sync:once`
|
|
25
|
+
- `claudeflow:admin:ui`
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Recommended (`npm create`):
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm create claude-flow-codex-ui@alpha my-project
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Initialize in current directory:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm create claude-flow-codex-ui@alpha .
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Equivalent direct invocation:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx create-claude-flow-codex-ui@alpha my-project
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Legacy explicit command still supported:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
npx create-claude-flow-codex-ui@alpha init my-project
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Options:
|
|
54
|
+
|
|
55
|
+
- `--force` overwrite existing `src/admin/*` files and script keys
|
|
56
|
+
- `--skip-claude-flow-init` skip running `npx claude-flow@alpha init --codex --full`
|
|
57
|
+
|
|
58
|
+
For `npm create`, pass initializer flags after `--`:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm create claude-flow-codex-ui@alpha my-project -- --force
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Run the admin UI in scaffolded project
|
|
65
|
+
|
|
66
|
+
Terminal 1:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run claudeflow:admin:sync
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Terminal 2:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm run claudeflow:admin:ui
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then open:
|
|
79
|
+
|
|
80
|
+
```text
|
|
81
|
+
http://127.0.0.1:4173
|
|
82
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-claude-flow-codex-ui",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"description": "npm create initializer for Claude Flow Codex full setup plus live swarm admin dashboard.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-claude-flow-codex-ui": "bin/create-claude-flow-codex-ui.mjs",
|
|
8
|
+
"claude-flow-codex-ui": "bin/create-claude-flow-codex-ui.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src/cli",
|
|
13
|
+
"templates",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"check": "node --check bin/create-claude-flow-codex-ui.mjs && node --check src/cli/init.mjs",
|
|
19
|
+
"package:dry-run": "npm pack --dry-run"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"create",
|
|
23
|
+
"initializer",
|
|
24
|
+
"boilerplate",
|
|
25
|
+
"claude-flow",
|
|
26
|
+
"codex",
|
|
27
|
+
"swarm",
|
|
28
|
+
"dashboard",
|
|
29
|
+
"telemetry"
|
|
30
|
+
],
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"tag": "alpha"
|
|
37
|
+
},
|
|
38
|
+
"license": "MIT"
|
|
39
|
+
}
|
package/src/cli/init.mjs
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { access, copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const packageRoot = path.resolve(__dirname, "..", "..");
|
|
8
|
+
const templateRoot = path.join(packageRoot, "templates", "admin");
|
|
9
|
+
|
|
10
|
+
const RUNTIME_GITIGNORE_ENTRIES = [
|
|
11
|
+
".codex/",
|
|
12
|
+
".agents/",
|
|
13
|
+
".claude-flow/",
|
|
14
|
+
".swarm/",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const ADMIN_SCRIPTS = {
|
|
18
|
+
"claudeflow:admin:sync": "node src/admin/sync-telemetry.mjs --interval 2000",
|
|
19
|
+
"claudeflow:admin:sync:once": "node src/admin/sync-telemetry.mjs --once",
|
|
20
|
+
"claudeflow:admin:ui": "node src/admin/dev-server.mjs",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function run(argv) {
|
|
24
|
+
const args = normalizeArgs(argv);
|
|
25
|
+
|
|
26
|
+
if (args.includes("-h") || args.includes("--help") || args[0] === "help") {
|
|
27
|
+
printHelp();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const options = parseOptions(args);
|
|
32
|
+
const targetDir = path.resolve(process.cwd(), options.targetDir || ".");
|
|
33
|
+
|
|
34
|
+
await mkdir(targetDir, { recursive: true });
|
|
35
|
+
|
|
36
|
+
log(`Target directory: ${targetDir}`);
|
|
37
|
+
|
|
38
|
+
if (!options.skipClaudeFlowInit) {
|
|
39
|
+
await runClaudeFlowInit(targetDir);
|
|
40
|
+
} else {
|
|
41
|
+
log("Skipping claude-flow init (--skip-claude-flow-init).", "warn");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const copied = await copyTemplateTree(templateRoot, path.join(targetDir, "src", "admin"), {
|
|
45
|
+
force: options.force,
|
|
46
|
+
});
|
|
47
|
+
log(`Admin UI files copied: ${copied.copied} (skipped: ${copied.skipped})`);
|
|
48
|
+
|
|
49
|
+
const pkgResult = await patchTargetPackageJson(targetDir, { force: options.force });
|
|
50
|
+
log(`package.json ${pkgResult.created ? "created" : "updated"}; scripts added: ${pkgResult.addedScripts}`);
|
|
51
|
+
|
|
52
|
+
await ensureGitignoreEntries(targetDir, RUNTIME_GITIGNORE_ENTRIES);
|
|
53
|
+
log(".gitignore updated with Claude Flow runtime entries.");
|
|
54
|
+
|
|
55
|
+
log("Scaffold complete.", "success");
|
|
56
|
+
log("Run 'npm run claudeflow:admin:sync' and 'npm run claudeflow:admin:ui' in the target project.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function normalizeArgs(argv) {
|
|
60
|
+
const args = [...argv];
|
|
61
|
+
if (!args.length) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
if (args[0] === "init") {
|
|
65
|
+
return args.slice(1);
|
|
66
|
+
}
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseOptions(tokens) {
|
|
71
|
+
const options = {
|
|
72
|
+
targetDir: ".",
|
|
73
|
+
skipClaudeFlowInit: false,
|
|
74
|
+
force: false,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
for (let i = 0; i < tokens.length; i += 1) {
|
|
78
|
+
const token = tokens[i];
|
|
79
|
+
if (token === "--skip-claude-flow-init") {
|
|
80
|
+
options.skipClaudeFlowInit = true;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (token === "--force") {
|
|
84
|
+
options.force = true;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (token.startsWith("--")) {
|
|
88
|
+
throw new Error(`Unknown option: ${token}`);
|
|
89
|
+
}
|
|
90
|
+
if (options.targetDir !== ".") {
|
|
91
|
+
throw new Error(`Unexpected argument: ${token}`);
|
|
92
|
+
}
|
|
93
|
+
options.targetDir = token;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return options;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function runClaudeFlowInit(targetDir) {
|
|
100
|
+
log("Running: npx claude-flow@alpha init --codex --full");
|
|
101
|
+
const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
102
|
+
|
|
103
|
+
await runCommand(npxCommand, ["claude-flow@alpha", "init", "--codex", "--full"], {
|
|
104
|
+
cwd: targetDir,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function patchTargetPackageJson(targetDir, { force }) {
|
|
109
|
+
const packageJsonPath = path.join(targetDir, "package.json");
|
|
110
|
+
let pkg;
|
|
111
|
+
let created = false;
|
|
112
|
+
|
|
113
|
+
if (await fileExists(packageJsonPath)) {
|
|
114
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
115
|
+
pkg = JSON.parse(raw);
|
|
116
|
+
} else {
|
|
117
|
+
created = true;
|
|
118
|
+
pkg = {
|
|
119
|
+
name: inferPackageName(targetDir),
|
|
120
|
+
version: "0.1.0",
|
|
121
|
+
private: true,
|
|
122
|
+
scripts: {},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!pkg.scripts || typeof pkg.scripts !== "object") {
|
|
127
|
+
pkg.scripts = {};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let addedScripts = 0;
|
|
131
|
+
for (const [name, command] of Object.entries(ADMIN_SCRIPTS)) {
|
|
132
|
+
if (!(name in pkg.scripts) || force) {
|
|
133
|
+
pkg.scripts[name] = command;
|
|
134
|
+
addedScripts += 1;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const serialized = `${JSON.stringify(pkg, null, 2)}\n`;
|
|
139
|
+
await writeFile(packageJsonPath, serialized, "utf8");
|
|
140
|
+
|
|
141
|
+
return { created, addedScripts };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function ensureGitignoreEntries(targetDir, entries) {
|
|
145
|
+
const gitignorePath = path.join(targetDir, ".gitignore");
|
|
146
|
+
const current = (await fileExists(gitignorePath))
|
|
147
|
+
? await readFile(gitignorePath, "utf8")
|
|
148
|
+
: "";
|
|
149
|
+
|
|
150
|
+
const lines = current.split(/\r?\n/);
|
|
151
|
+
const toAdd = entries.filter((entry) => !lines.includes(entry));
|
|
152
|
+
|
|
153
|
+
if (!toAdd.length) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const prefix = current && !current.endsWith("\n") ? "\n" : "";
|
|
158
|
+
const content = `${current}${prefix}${toAdd.join("\n")}\n`;
|
|
159
|
+
await writeFile(gitignorePath, content, "utf8");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function copyTemplateTree(fromDir, toDir, { force }) {
|
|
163
|
+
const counters = { copied: 0, skipped: 0 };
|
|
164
|
+
await mkdir(toDir, { recursive: true });
|
|
165
|
+
|
|
166
|
+
const entries = await readdir(fromDir, { withFileTypes: true });
|
|
167
|
+
for (const entry of entries) {
|
|
168
|
+
const sourcePath = path.join(fromDir, entry.name);
|
|
169
|
+
const targetPath = path.join(toDir, entry.name);
|
|
170
|
+
|
|
171
|
+
if (entry.isDirectory()) {
|
|
172
|
+
const nested = await copyTemplateTree(sourcePath, targetPath, { force });
|
|
173
|
+
counters.copied += nested.copied;
|
|
174
|
+
counters.skipped += nested.skipped;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const exists = await fileExists(targetPath);
|
|
179
|
+
if (exists && !force) {
|
|
180
|
+
counters.skipped += 1;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
185
|
+
await copyFile(sourcePath, targetPath);
|
|
186
|
+
counters.copied += 1;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return counters;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function runCommand(command, args, options = {}) {
|
|
193
|
+
await new Promise((resolve, reject) => {
|
|
194
|
+
const child = spawn(command, args, {
|
|
195
|
+
cwd: options.cwd || process.cwd(),
|
|
196
|
+
stdio: "inherit",
|
|
197
|
+
shell: false,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
child.on("error", reject);
|
|
201
|
+
child.on("exit", (code) => {
|
|
202
|
+
if (code === 0) {
|
|
203
|
+
resolve();
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
reject(new Error(`Command failed (${code}): ${command} ${args.join(" ")}`));
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function inferPackageName(targetDir) {
|
|
212
|
+
const raw = path.basename(targetDir).toLowerCase();
|
|
213
|
+
const safe = raw
|
|
214
|
+
.replace(/[^a-z0-9._-]/g, "-")
|
|
215
|
+
.replace(/--+/g, "-")
|
|
216
|
+
.replace(/^-+|-+$/g, "");
|
|
217
|
+
return safe || "claude-flow-project";
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function fileExists(filePath) {
|
|
221
|
+
try {
|
|
222
|
+
await access(filePath);
|
|
223
|
+
return true;
|
|
224
|
+
} catch {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function log(message, level = "info") {
|
|
230
|
+
const tag = level.toUpperCase().padEnd(7, " ");
|
|
231
|
+
process.stdout.write(`[${tag}] ${message}\n`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function printHelp() {
|
|
235
|
+
process.stdout.write(
|
|
236
|
+
`create-claude-flow-codex-ui\n\n` +
|
|
237
|
+
`Usage:\n` +
|
|
238
|
+
` npm create claude-flow-codex-ui@alpha [target-dir] [-- --force] [-- --skip-claude-flow-init]\n` +
|
|
239
|
+
` npx create-claude-flow-codex-ui@alpha [target-dir] [--force] [--skip-claude-flow-init]\n` +
|
|
240
|
+
` npx create-claude-flow-codex-ui@alpha init [target-dir] [--force] [--skip-claude-flow-init]\n\n` +
|
|
241
|
+
`What it does:\n` +
|
|
242
|
+
` 1) Runs: npx claude-flow@alpha init --codex --full\n` +
|
|
243
|
+
` 2) Adds a live admin UI at src/admin for agent/task progress\n` +
|
|
244
|
+
` 3) Adds npm scripts for telemetry sync and dashboard server\n`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Swarm Admin Visualization
|
|
2
|
+
|
|
3
|
+
This dashboard visualizes live agent activity from `src/admin/state/swarm-state.json`.
|
|
4
|
+
|
|
5
|
+
## 1) Start Telemetry Sync (real data)
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
node src/admin/sync-telemetry.mjs --interval 2000
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
By default it reads:
|
|
12
|
+
- `.claude-flow/agents/store.json`
|
|
13
|
+
- `.claude-flow/tasks/store.json`
|
|
14
|
+
|
|
15
|
+
And writes:
|
|
16
|
+
- `src/admin/state/swarm-state.json`
|
|
17
|
+
|
|
18
|
+
Optional flags:
|
|
19
|
+
- `--once` (single sync tick and exit)
|
|
20
|
+
- `--swarm-id <id>`
|
|
21
|
+
- `--orch-id <id>`
|
|
22
|
+
- `--agents-path <path>`
|
|
23
|
+
- `--tasks-path <path>`
|
|
24
|
+
- `--output <path>`
|
|
25
|
+
- `--max-events <n>`
|
|
26
|
+
|
|
27
|
+
## 2) Start Dashboard Server
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node src/admin/dev-server.mjs
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Open:
|
|
34
|
+
- `http://127.0.0.1:4173`
|
|
35
|
+
|
|
36
|
+
The dashboard now uses a WebSocket stream (`/__swarm_ws`) for state updates and falls back to HTTP polling if the socket drops.
|
|
37
|
+
|
|
38
|
+
## Validation (one-shot)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
node src/admin/sync-telemetry.mjs --once
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This confirms state JSON can be generated from live Claude Flow stores.
|
|
45
|
+
|
|
46
|
+
## Simulation Mode
|
|
47
|
+
|
|
48
|
+
Add `?simulate=1` if you want visual playback in addition to real telemetry:
|
|
49
|
+
- `http://127.0.0.1:4173/?simulate=1`
|