@axiomatic-labs/claudeflow 2.32.2 → 2.32.4

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.
Files changed (3) hide show
  1. package/bin/cli.js +71 -44
  2. package/lib/preview.js +91 -73
  3. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -16,16 +16,27 @@
16
16
  // (has `.claudeflow/`), passthrough to claude.
17
17
  // * Otherwise, run `install` to match the npx-first-time path.
18
18
 
19
- const fs = require('fs');
20
- const path = require('path');
19
+ const fs = require("fs");
20
+ const path = require("path");
21
21
 
22
- const SUBCOMMANDS = new Set(['install', 'version', '--version', '-v', 'help', '--help', '-h', 'doctor', 'panel', 'preview']);
22
+ const SUBCOMMANDS = new Set([
23
+ "install",
24
+ "version",
25
+ "--version",
26
+ "-v",
27
+ "help",
28
+ "--help",
29
+ "-h",
30
+ "doctor",
31
+ "panel",
32
+ "preview",
33
+ ]);
23
34
 
24
35
  function inClaudeflowProject(startDir) {
25
36
  let dir = path.resolve(startDir);
26
37
  const root = path.parse(dir).root;
27
38
  while (true) {
28
- if (fs.existsSync(path.join(dir, '.claudeflow'))) return true;
39
+ if (fs.existsSync(path.join(dir, ".claudeflow"))) return true;
29
40
  if (dir === root) return false;
30
41
  const parent = path.dirname(dir);
31
42
  if (parent === dir) return false;
@@ -35,61 +46,77 @@ function inClaudeflowProject(startDir) {
35
46
 
36
47
  async function runSubcommand(command) {
37
48
  switch (command) {
38
- case 'install': {
39
- const install = require('../lib/install.js');
49
+ case "install": {
50
+ const install = require("../lib/install.js");
40
51
  await install();
41
52
  return;
42
53
  }
43
- case 'version':
44
- case '--version':
45
- case '-v': {
46
- const version = require('../lib/version.js');
54
+ case "version":
55
+ case "--version":
56
+ case "-v": {
57
+ const version = require("../lib/version.js");
47
58
  await version();
48
59
  return;
49
60
  }
50
- case 'doctor': {
51
- const doctor = require('../lib/doctor.js');
61
+ case "doctor": {
62
+ const doctor = require("../lib/doctor.js");
52
63
  const code = await doctor(process.argv.slice(3));
53
64
  process.exit(code || 0);
54
65
  }
55
- case 'panel': {
56
- const panel = require('../lib/panel.js');
66
+ case "panel": {
67
+ const panel = require("../lib/panel.js");
57
68
  await panel(process.argv.slice(3));
58
69
  return;
59
70
  }
60
- case 'preview': {
61
- const preview = require('../lib/preview.js');
71
+ case "preview": {
72
+ const preview = require("../lib/preview.js");
62
73
  await preview(process.argv.slice(3));
63
74
  return;
64
75
  }
65
- case 'help':
66
- case '--help':
67
- case '-h':
76
+ case "help":
77
+ case "--help":
78
+ case "-h":
68
79
  default: {
69
- const ui = require('../lib/ui.js');
80
+ const ui = require("../lib/ui.js");
70
81
  ui.banner();
71
- console.log(' Usage: claudeflow [command | claude-args...]');
72
- console.log('');
73
- console.log(' Claudeflow commands:');
74
- console.log(` ${ui.CYAN}install${ui.RESET} Install or update Claudeflow in the current project`);
75
- console.log(` ${ui.CYAN}doctor${ui.RESET} Diagnose local issues (CDP port, stale lockfiles); add --fix to repair`);
76
- console.log(` ${ui.CYAN}panel${ui.RESET} Open the local web dashboard for hooks, CLAUDE.md, and run state`);
77
- console.log(` ${ui.CYAN}preview${ui.RESET} Live-reload server for prototypes (.claudeflow/tmp/prototypes/) — run once, leave it`);
82
+ console.log(" Usage: claudeflow [command | claude-args...]");
83
+ console.log("");
84
+ console.log(" Claudeflow commands:");
85
+ console.log(
86
+ ` ${ui.CYAN}install${ui.RESET} Install or update Claudeflow in the current project`,
87
+ );
88
+ console.log(
89
+ ` ${ui.CYAN}doctor${ui.RESET} Diagnose local issues (CDP port, stale lockfiles); add --fix to repair`,
90
+ );
91
+ console.log(
92
+ ` ${ui.CYAN}panel${ui.RESET} Open the local web dashboard for hooks, CLAUDE.md, and run state`,
93
+ );
94
+ console.log(
95
+ ` ${ui.CYAN}preview${ui.RESET} Live-reload server for prototypes (.claudeflow/prototypes/) — run once, leave it`,
96
+ );
78
97
  console.log(` ${ui.CYAN}version${ui.RESET} Show version info`);
79
98
  console.log(` ${ui.CYAN}help${ui.RESET} Show this message`);
80
- console.log('');
81
- console.log(' Any other arguments are passed through to `claude`, with');
82
- console.log(' `--append-system-prompt-file ./append-system-prompt.md`');
83
- console.log(' auto-injected when the project ships that file.');
84
- console.log('');
85
- console.log(' Examples:');
86
- console.log(' claudeflow # open claude in current project');
87
- console.log(' claudeflow --continue # continue last claude session');
99
+ console.log("");
100
+ console.log(" Any other arguments are passed through to `claude`, with");
101
+ console.log(" `--append-system-prompt-file ./append-system-prompt.md`");
102
+ console.log(" auto-injected when the project ships that file.");
103
+ console.log("");
104
+ console.log(" Examples:");
105
+ console.log(
106
+ " claudeflow # open claude in current project",
107
+ );
108
+ console.log(
109
+ " claudeflow --continue # continue last claude session",
110
+ );
88
111
  console.log(' claudeflow -p "fix bug" # one-shot prompt');
89
- console.log(' claudeflow install # install or update claudeflow');
90
- console.log('');
91
- console.log(' Env:');
92
- console.log(' CLAUDEFLOW_NO_SYSTEM_PROMPT=1 skip the append-system-prompt flag');
112
+ console.log(
113
+ " claudeflow install # install or update claudeflow",
114
+ );
115
+ console.log("");
116
+ console.log(" Env:");
117
+ console.log(
118
+ " CLAUDEFLOW_NO_SYSTEM_PROMPT=1 skip the append-system-prompt flag",
119
+ );
93
120
  return;
94
121
  }
95
122
  }
@@ -104,25 +131,25 @@ async function main() {
104
131
  return;
105
132
  }
106
133
 
107
- if (first && first.startsWith('-')) {
134
+ if (first && first.startsWith("-")) {
108
135
  // Flags → passthrough to claude
109
- require('../lib/claude-passthrough.js').run(args);
136
+ require("../lib/claude-passthrough.js").run(args);
110
137
  return;
111
138
  }
112
139
 
113
140
  if (!first) {
114
141
  // No args: passthrough inside a claudeflow project, else install
115
142
  if (inClaudeflowProject(process.cwd())) {
116
- require('../lib/claude-passthrough.js').run([]);
143
+ require("../lib/claude-passthrough.js").run([]);
117
144
  } else {
118
- await runSubcommand('install');
145
+ await runSubcommand("install");
119
146
  }
120
147
  return;
121
148
  }
122
149
 
123
150
  // Unknown subcommand-looking arg → passthrough (claude owns its own
124
151
  // subcommands like `claude mcp`, `claude config`, etc.)
125
- require('../lib/claude-passthrough.js').run(args);
152
+ require("../lib/claude-passthrough.js").run(args);
126
153
  }
127
154
 
128
155
  main().catch((err) => {
package/lib/preview.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // `claudeflow preview` — standing, decoupled live-preview server for prototypes.
2
2
  //
3
- // Serves .claudeflow/tmp/prototypes/ over http://127.0.0.1:<port>, watches the
3
+ // Serves .claudeflow/prototypes/ over http://127.0.0.1:<port>, watches the
4
4
  // files, and pushes live updates over SSE — CSS edits hot-swap (no flicker),
5
5
  // HTML edits reload only the changed document / the content iframe (the shell
6
6
  // stays). The user runs this ONCE and leaves it; the /claudeflow-prototype flow
@@ -13,32 +13,32 @@
13
13
  // a prototype is still a static snapshot; live-reload is purely this server's job
14
14
  // (the live client is INJECTED into served HTML, so prototype files stay pure).
15
15
 
16
- const http = require('http');
17
- const fs = require('fs');
18
- const path = require('path');
19
- const { spawn } = require('child_process');
16
+ const http = require("http");
17
+ const fs = require("fs");
18
+ const path = require("path");
19
+ const { spawn } = require("child_process");
20
20
 
21
- const ui = require('./ui.js');
21
+ const ui = require("./ui.js");
22
22
 
23
23
  const MIME = {
24
- '.html': 'text/html; charset=utf-8',
25
- '.css': 'text/css; charset=utf-8',
26
- '.js': 'text/javascript; charset=utf-8',
27
- '.json': 'application/json; charset=utf-8',
28
- '.svg': 'image/svg+xml',
29
- '.png': 'image/png',
30
- '.jpg': 'image/jpeg',
31
- '.jpeg': 'image/jpeg',
32
- '.gif': 'image/gif',
33
- '.webp': 'image/webp',
34
- '.ico': 'image/x-icon',
35
- '.woff': 'font/woff',
36
- '.woff2': 'font/woff2',
37
- '.ttf': 'font/ttf',
38
- '.map': 'application/json; charset=utf-8',
24
+ ".html": "text/html; charset=utf-8",
25
+ ".css": "text/css; charset=utf-8",
26
+ ".js": "text/javascript; charset=utf-8",
27
+ ".json": "application/json; charset=utf-8",
28
+ ".svg": "image/svg+xml",
29
+ ".png": "image/png",
30
+ ".jpg": "image/jpeg",
31
+ ".jpeg": "image/jpeg",
32
+ ".gif": "image/gif",
33
+ ".webp": "image/webp",
34
+ ".ico": "image/x-icon",
35
+ ".woff": "font/woff",
36
+ ".woff2": "font/woff2",
37
+ ".ttf": "font/ttf",
38
+ ".map": "application/json; charset=utf-8",
39
39
  };
40
40
 
41
- const SSE_PATH = '/__livereload';
41
+ const SSE_PATH = "/__livereload";
42
42
 
43
43
  // Injected into every served .html. Connects to the SSE stream; CSS changes
44
44
  // hot-swap the <link>s (flicker-free), other changes reload the changed
@@ -82,7 +82,7 @@ function listPrototypes(root) {
82
82
  return entries
83
83
  .filter((e) => e.isDirectory())
84
84
  .map((e) => e.name)
85
- .filter((name) => fs.existsSync(path.join(root, name, 'index.html')))
85
+ .filter((name) => fs.existsSync(path.join(root, name, "index.html")))
86
86
  .sort();
87
87
  }
88
88
 
@@ -91,18 +91,18 @@ function listingPage(root) {
91
91
  const items = protos.length
92
92
  ? protos
93
93
  .map((p) => `<li><a href="/${p}/index.html">${p}</a></li>`)
94
- .join('\n')
95
- : '<li class="muted">No prototypes yet — assemble one with /claudeflow-prototype (writes into .claudeflow/tmp/prototypes/).</li>';
94
+ .join("\n")
95
+ : '<li class="muted">No prototypes yet — assemble one with /claudeflow-prototype (writes into .claudeflow/prototypes/).</li>';
96
96
  return `<!doctype html><html><head><meta charset="utf-8"><title>claudeflow preview</title>
97
97
  <style>body{font:15px/1.5 -apple-system,system-ui,sans-serif;max-width:640px;margin:48px auto;padding:0 20px;color:#1c1c22}
98
98
  h1{font-size:18px}a{color:#5050d4}.muted{color:#717180}li{margin:6px 0}</style></head>
99
- <body><h1>claudeflow preview</h1><p class="muted">Serving <code>.claudeflow/tmp/prototypes/</code> with live reload.</p>
99
+ <body><h1>claudeflow preview</h1><p class="muted">Serving <code>.claudeflow/prototypes/</code> with live reload.</p>
100
100
  <ul>${items}</ul></body></html>`;
101
101
  }
102
102
 
103
103
  // Resolve the requested URL to a file under root, blocking path traversal.
104
104
  function resolveFile(root, urlPath) {
105
- const clean = decodeURIComponent(urlPath.split('?')[0]).replace(/\/+$/, '');
105
+ const clean = decodeURIComponent(urlPath.split("?")[0]).replace(/\/+$/, "");
106
106
  const abs = path.normalize(path.join(root, clean));
107
107
  if (abs !== root && !abs.startsWith(root + path.sep)) return null; // traversal guard
108
108
  return abs;
@@ -111,7 +111,7 @@ function resolveFile(root, urlPath) {
111
111
  function injectLiveClient(html) {
112
112
  const marker = /<\/body>/i;
113
113
  return marker.test(html)
114
- ? html.replace(marker, LIVE_CLIENT + '\n</body>')
114
+ ? html.replace(marker, LIVE_CLIENT + "\n</body>")
115
115
  : html + LIVE_CLIENT;
116
116
  }
117
117
 
@@ -119,7 +119,7 @@ function injectLiveClient(html) {
119
119
  // supported on every platform/Node). Calls onChange(relPath) on edits.
120
120
  function watch(root, onChange) {
121
121
  let timer = null;
122
- let lastPath = '';
122
+ let lastPath = "";
123
123
  const fire = (rel) => {
124
124
  lastPath = rel || lastPath;
125
125
  clearTimeout(timer);
@@ -127,9 +127,9 @@ function watch(root, onChange) {
127
127
  };
128
128
  try {
129
129
  const w = fs.watch(root, { recursive: true }, (_evt, filename) => {
130
- if (filename) fire(String(filename).split(path.sep).join('/'));
130
+ if (filename) fire(String(filename).split(path.sep).join("/"));
131
131
  });
132
- w.on('error', () => {});
132
+ w.on("error", () => {});
133
133
  return () => w.close();
134
134
  } catch {
135
135
  // Polling fallback: scan mtimes every second.
@@ -143,7 +143,7 @@ function watch(root, onChange) {
143
143
  }
144
144
  for (const e of ents) {
145
145
  const abs = path.join(dir, e.name);
146
- const rel = base ? base + '/' + e.name : e.name;
146
+ const rel = base ? base + "/" + e.name : e.name;
147
147
  if (e.isDirectory()) {
148
148
  scan(abs, rel);
149
149
  } else {
@@ -158,22 +158,22 @@ function watch(root, onChange) {
158
158
  }
159
159
  }
160
160
  };
161
- scan(root, '');
162
- const iv = setInterval(() => scan(root, ''), 1000);
161
+ scan(root, "");
162
+ const iv = setInterval(() => scan(root, ""), 1000);
163
163
  return () => clearInterval(iv);
164
164
  }
165
165
  }
166
166
 
167
167
  function openBrowser(url) {
168
168
  const cmd =
169
- process.platform === 'darwin'
170
- ? { c: 'open', a: [url] }
171
- : process.platform === 'win32'
172
- ? { c: 'cmd', a: ['/c', 'start', '', url] }
173
- : { c: 'xdg-open', a: [url] };
169
+ process.platform === "darwin"
170
+ ? { c: "open", a: [url] }
171
+ : process.platform === "win32"
172
+ ? { c: "cmd", a: ["/c", "start", "", url] }
173
+ : { c: "xdg-open", a: [url] };
174
174
  try {
175
- const child = spawn(cmd.c, cmd.a, { stdio: 'ignore', detached: true });
176
- child.on('error', () => {});
175
+ const child = spawn(cmd.c, cmd.a, { stdio: "ignore", detached: true });
176
+ child.on("error", () => {});
177
177
  child.unref();
178
178
  } catch {
179
179
  /* best-effort */
@@ -184,9 +184,9 @@ function parseArgs(argv) {
184
184
  const out = { proto: null, port: null, open: true };
185
185
  for (let i = 0; i < argv.length; i++) {
186
186
  const a = argv[i];
187
- if (a === '--port' || a === '-p') out.port = Number(argv[++i]);
188
- else if (a === '--no-open') out.open = false;
189
- else if (!a.startsWith('-')) out.proto = a;
187
+ if (a === "--port" || a === "-p") out.port = Number(argv[++i]);
188
+ else if (a === "--no-open") out.open = false;
189
+ else if (!a.startsWith("-")) out.proto = a;
190
190
  }
191
191
  return out;
192
192
  }
@@ -194,7 +194,7 @@ function parseArgs(argv) {
194
194
  async function preview(argv = []) {
195
195
  const { proto, port: wantPort, open } = parseArgs(argv);
196
196
  const cwd = process.cwd();
197
- const protosRoot = path.join(cwd, '.claudeflow', 'tmp', 'prototypes');
197
+ const protosRoot = path.join(cwd, ".claudeflow", "prototypes");
198
198
 
199
199
  // Root to serve: a specific prototype subdir, or the prototypes parent (listing).
200
200
  let root = protosRoot;
@@ -216,24 +216,30 @@ async function preview(argv = []) {
216
216
 
217
217
  const clients = new Set();
218
218
  const server = http.createServer((req, res) => {
219
- const urlPath = (req.url || '/').split('?')[0];
219
+ const urlPath = (req.url || "/").split("?")[0];
220
220
 
221
221
  if (urlPath === SSE_PATH) {
222
222
  res.writeHead(200, {
223
- 'Content-Type': 'text/event-stream',
224
- 'Cache-Control': 'no-cache',
225
- Connection: 'keep-alive',
223
+ "Content-Type": "text/event-stream",
224
+ "Cache-Control": "no-cache",
225
+ Connection: "keep-alive",
226
226
  });
227
- res.write(': connected\n\n');
227
+ res.write(": connected\n\n");
228
228
  clients.add(res);
229
- req.on('close', () => clients.delete(res));
229
+ req.on("close", () => clients.delete(res));
230
230
  return;
231
231
  }
232
232
 
233
233
  // Listing at root when serving the prototypes parent and no index there.
234
- if ((urlPath === '/' || urlPath === '') && !fs.existsSync(path.join(root, 'index.html'))) {
234
+ if (
235
+ (urlPath === "/" || urlPath === "") &&
236
+ !fs.existsSync(path.join(root, "index.html"))
237
+ ) {
235
238
  const body = listingPage(root);
236
- res.writeHead(200, { 'Content-Type': MIME['.html'], 'Cache-Control': 'no-store' });
239
+ res.writeHead(200, {
240
+ "Content-Type": MIME[".html"],
241
+ "Cache-Control": "no-store",
242
+ });
237
243
  res.end(injectLiveClient(body));
238
244
  return;
239
245
  }
@@ -241,11 +247,11 @@ async function preview(argv = []) {
241
247
  let file = resolveFile(root, urlPath);
242
248
  if (!file) {
243
249
  res.writeHead(403);
244
- res.end('Forbidden');
250
+ res.end("Forbidden");
245
251
  return;
246
252
  }
247
253
  try {
248
- if (fs.statSync(file).isDirectory()) file = path.join(file, 'index.html');
254
+ if (fs.statSync(file).isDirectory()) file = path.join(file, "index.html");
249
255
  } catch {
250
256
  /* fall through to 404 below */
251
257
  }
@@ -253,17 +259,21 @@ async function preview(argv = []) {
253
259
  try {
254
260
  buf = fs.readFileSync(file);
255
261
  } catch {
256
- res.writeHead(404, { 'Content-Type': MIME['.html'] });
257
- res.end(injectLiveClient('<!doctype html><body style="font-family:system-ui;padding:40px">404 — not found</body>'));
262
+ res.writeHead(404, { "Content-Type": MIME[".html"] });
263
+ res.end(
264
+ injectLiveClient(
265
+ '<!doctype html><body style="font-family:system-ui;padding:40px">404 — not found</body>',
266
+ ),
267
+ );
258
268
  return;
259
269
  }
260
270
  const ext = path.extname(file).toLowerCase();
261
- const type = MIME[ext] || 'application/octet-stream';
262
- if (ext === '.html') {
263
- res.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'no-store' });
264
- res.end(injectLiveClient(buf.toString('utf8')));
271
+ const type = MIME[ext] || "application/octet-stream";
272
+ if (ext === ".html") {
273
+ res.writeHead(200, { "Content-Type": type, "Cache-Control": "no-store" });
274
+ res.end(injectLiveClient(buf.toString("utf8")));
265
275
  } else {
266
- res.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'no-store' });
276
+ res.writeHead(200, { "Content-Type": type, "Cache-Control": "no-store" });
267
277
  res.end(buf);
268
278
  }
269
279
  });
@@ -280,25 +290,33 @@ async function preview(argv = []) {
280
290
 
281
291
  const onPort = (actualPort) => {
282
292
  const url = `http://127.0.0.1:${actualPort}/`;
283
- console.log('');
284
- console.log(` ${ui.GREEN}claudeflow preview${ui.RESET} — live reload for prototypes`);
285
- console.log(` ${ui.CYAN}${url}${ui.RESET} ${ui.DIM}(serving .claudeflow/tmp/prototypes/)${ui.RESET}`);
286
- console.log(` ${ui.DIM}CSS edits hot-swap (no flicker); HTML edits reload the changed view. Ctrl-C to stop.${ui.RESET}`);
287
- console.log('');
293
+ console.log("");
294
+ console.log(
295
+ ` ${ui.GREEN}claudeflow preview${ui.RESET} — live reload for prototypes`,
296
+ );
297
+ console.log(
298
+ ` ${ui.CYAN}${url}${ui.RESET} ${ui.DIM}(serving .claudeflow/prototypes/)${ui.RESET}`,
299
+ );
300
+ console.log(
301
+ ` ${ui.DIM}CSS edits hot-swap (no flicker); HTML edits reload the changed view. Ctrl-C to stop.${ui.RESET}`,
302
+ );
303
+ console.log("");
288
304
  if (open) openBrowser(proto ? url : url);
289
305
  };
290
306
 
291
307
  await new Promise((resolve) => {
292
308
  const tryListen = (p, allowFallback) => {
293
- server.once('error', (err) => {
294
- if (err && err.code === 'EADDRINUSE' && allowFallback) {
309
+ server.once("error", (err) => {
310
+ if (err && err.code === "EADDRINUSE" && allowFallback) {
295
311
  tryListen(0, false); // OS-assigned free port
296
312
  } else {
297
- console.error(`${ui.RED}preview server error: ${err.message}${ui.RESET}`);
313
+ console.error(
314
+ `${ui.RED}preview server error: ${err.message}${ui.RESET}`,
315
+ );
298
316
  resolve();
299
317
  }
300
318
  });
301
- server.listen(p, '127.0.0.1', () => {
319
+ server.listen(p, "127.0.0.1", () => {
302
320
  onPort(server.address().port);
303
321
  resolve();
304
322
  });
@@ -329,8 +347,8 @@ async function preview(argv = []) {
329
347
  server.close(() => process.exit(0));
330
348
  setTimeout(() => process.exit(0), 300).unref();
331
349
  };
332
- process.on('SIGINT', shutdown);
333
- process.on('SIGTERM', shutdown);
350
+ process.on("SIGINT", shutdown);
351
+ process.on("SIGTERM", shutdown);
334
352
 
335
353
  // Keep the process alive (standing server).
336
354
  return new Promise(() => {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiomatic-labs/claudeflow",
3
- "version": "2.32.2",
3
+ "version": "2.32.4",
4
4
  "description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
5
5
  "bin": {
6
6
  "claudeflow": "./bin/cli.js"