@aiready/cli 0.9.28 → 0.9.29

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.js CHANGED
@@ -1011,7 +1011,9 @@ Or specify a custom report:
1011
1011
  console.log("Building graph from report...");
1012
1012
  const { GraphBuilder } = await import("@aiready/visualizer/graph");
1013
1013
  const graph = GraphBuilder.buildFromReport(report, dirPath);
1014
- if (options.dev) {
1014
+ let useDevMode = options.dev || false;
1015
+ let devServerStarted = false;
1016
+ if (useDevMode) {
1015
1017
  try {
1016
1018
  const monorepoWebDir = (0, import_path6.resolve)(dirPath, "packages/visualizer");
1017
1019
  let webDir = "";
@@ -1047,96 +1049,100 @@ Or specify a custom report:
1047
1049
  }
1048
1050
  }
1049
1051
  }
1050
- const spawnCwd = webDir || process.cwd();
1051
- const nodeBinCandidate = process.execPath;
1052
- const nodeBin = (0, import_fs3.existsSync)(nodeBinCandidate) ? nodeBinCandidate : "node";
1053
- if (!visualizerAvailable) {
1054
- console.error(import_chalk6.default.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
1055
- console.log(import_chalk6.default.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
1052
+ const webViteConfigExists = webDir && (0, import_fs3.existsSync)((0, import_path6.resolve)(webDir, "web", "vite.config.ts"));
1053
+ if (visualizerAvailable && webViteConfigExists) {
1054
+ const spawnCwd = webDir;
1055
+ const { watch } = await import("fs");
1056
+ const copyReportToViz = () => {
1057
+ try {
1058
+ const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
1059
+ (0, import_fs3.copyFileSync)(reportPath, destPath);
1060
+ console.log(`\u{1F4CB} Report synced to ${destPath}`);
1061
+ } catch (e) {
1062
+ console.error("Failed to sync report:", e);
1063
+ }
1064
+ };
1065
+ copyReportToViz();
1066
+ let watchTimeout = null;
1067
+ const reportWatcher = watch(reportPath, () => {
1068
+ if (watchTimeout) clearTimeout(watchTimeout);
1069
+ watchTimeout = setTimeout(copyReportToViz, 100);
1070
+ });
1071
+ const envForSpawn = {
1072
+ ...process.env,
1073
+ AIREADY_REPORT_PATH: reportPath,
1074
+ AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
1075
+ };
1076
+ const vite = (0, import_child_process.spawn)("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
1077
+ const onExit = () => {
1078
+ try {
1079
+ reportWatcher.close();
1080
+ } catch (e) {
1081
+ }
1082
+ try {
1083
+ vite.kill();
1084
+ } catch (e) {
1085
+ }
1086
+ process.exit(0);
1087
+ };
1088
+ process.on("SIGINT", onExit);
1089
+ process.on("SIGTERM", onExit);
1090
+ devServerStarted = true;
1056
1091
  return;
1092
+ } else {
1093
+ console.log(import_chalk6.default.yellow("\u26A0\uFE0F Dev server not available (requires local @aiready/visualizer with web assets)."));
1094
+ console.log(import_chalk6.default.cyan(" Falling back to static HTML generation...\n"));
1095
+ useDevMode = false;
1057
1096
  }
1058
- const { watch } = await import("fs");
1059
- const copyReportToViz = () => {
1060
- try {
1061
- const destPath = (0, import_path6.resolve)(spawnCwd, "web", "report-data.json");
1062
- (0, import_fs3.copyFileSync)(reportPath, destPath);
1063
- console.log(`\u{1F4CB} Report synced to ${destPath}`);
1064
- } catch (e) {
1065
- console.error("Failed to sync report:", e);
1066
- }
1067
- };
1068
- copyReportToViz();
1069
- let watchTimeout = null;
1070
- const reportWatcher = watch(reportPath, () => {
1071
- if (watchTimeout) clearTimeout(watchTimeout);
1072
- watchTimeout = setTimeout(copyReportToViz, 100);
1073
- });
1074
- const envForSpawn = {
1075
- ...process.env,
1076
- AIREADY_REPORT_PATH: reportPath,
1077
- AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
1078
- };
1079
- const vite = (0, import_child_process.spawn)("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
1080
- const onExit = () => {
1081
- try {
1082
- reportWatcher.close();
1083
- } catch (e) {
1084
- }
1085
- try {
1086
- vite.kill();
1087
- } catch (e) {
1088
- }
1089
- process.exit(0);
1090
- };
1091
- process.on("SIGINT", onExit);
1092
- process.on("SIGTERM", onExit);
1093
- return;
1094
1097
  } catch (err) {
1095
1098
  console.error("Failed to start dev server:", err);
1099
+ console.log(import_chalk6.default.cyan(" Falling back to static HTML generation...\n"));
1100
+ useDevMode = false;
1096
1101
  }
1097
1102
  }
1098
1103
  console.log("Generating HTML...");
1099
1104
  const html = (0, import_core6.generateHTML)(graph);
1100
- const outPath = (0, import_path6.resolve)(dirPath, options.output || "packages/visualizer/visualization.html");
1105
+ const defaultOutput = "visualization.html";
1106
+ const outPath = (0, import_path6.resolve)(dirPath, options.output || defaultOutput);
1101
1107
  (0, import_fs3.writeFileSync)(outPath, html, "utf8");
1102
- console.log("Visualization written to:", outPath);
1103
- if (options.open) {
1108
+ console.log(import_chalk6.default.green(`\u2705 Visualization written to: ${outPath}`));
1109
+ if (options.open || options.serve) {
1104
1110
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1105
- (0, import_child_process.spawn)(opener, [`"${outPath}"`], { shell: true });
1106
- }
1107
- if (options.serve) {
1108
- try {
1109
- const port = typeof options.serve === "number" ? options.serve : 5173;
1110
- const http = await import("http");
1111
- const fsp = await import("fs/promises");
1112
- const server = http.createServer(async (req, res) => {
1113
- try {
1114
- const urlPath = req.url || "/";
1115
- if (urlPath === "/" || urlPath === "/index.html") {
1116
- const content = await fsp.readFile(outPath, "utf8");
1117
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1118
- res.end(content);
1119
- return;
1111
+ if (options.serve) {
1112
+ try {
1113
+ const port = typeof options.serve === "number" ? options.serve : 5173;
1114
+ const http = await import("http");
1115
+ const fsp = await import("fs/promises");
1116
+ const server = http.createServer(async (req, res) => {
1117
+ try {
1118
+ const urlPath = req.url || "/";
1119
+ if (urlPath === "/" || urlPath === "/index.html") {
1120
+ const content = await fsp.readFile(outPath, "utf8");
1121
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1122
+ res.end(content);
1123
+ return;
1124
+ }
1125
+ res.writeHead(404, { "Content-Type": "text/plain" });
1126
+ res.end("Not found");
1127
+ } catch (e) {
1128
+ res.writeHead(500, { "Content-Type": "text/plain" });
1129
+ res.end("Server error");
1120
1130
  }
1121
- res.writeHead(404, { "Content-Type": "text/plain" });
1122
- res.end("Not found");
1123
- } catch (e) {
1124
- res.writeHead(500, { "Content-Type": "text/plain" });
1125
- res.end("Server error");
1126
- }
1127
- });
1128
- server.listen(port, () => {
1129
- const addr = `http://localhost:${port}/`;
1130
- console.log(`Local visualization server running at ${addr}`);
1131
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1132
- (0, import_child_process.spawn)(opener, [`"${addr}"`], { shell: true });
1133
- });
1134
- process.on("SIGINT", () => {
1135
- server.close();
1136
- process.exit(0);
1137
- });
1138
- } catch (err) {
1139
- console.error("Failed to start local server:", err);
1131
+ });
1132
+ server.listen(port, () => {
1133
+ const addr = `http://localhost:${port}/`;
1134
+ console.log(import_chalk6.default.cyan(`\u{1F310} Local visualization server running at ${addr}`));
1135
+ (0, import_child_process.spawn)(opener, [`"${addr}"`], { shell: true });
1136
+ });
1137
+ process.on("SIGINT", () => {
1138
+ server.close();
1139
+ process.exit(0);
1140
+ });
1141
+ } catch (err) {
1142
+ console.error("Failed to start local server:", err);
1143
+ }
1144
+ } else if (options.open) {
1145
+ (0, import_child_process.spawn)(opener, [`"${outPath}"`], { shell: true });
1140
1146
  }
1141
1147
  }
1142
1148
  } catch (err) {
@@ -1145,7 +1151,7 @@ Or specify a custom report:
1145
1151
  }
1146
1152
  var visualizeHelpText = `
1147
1153
  EXAMPLES:
1148
- $ aiready visualize . # Auto-detects latest report
1154
+ $ aiready visualize . # Auto-detects latest report, generates HTML
1149
1155
  $ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
1150
1156
  $ aiready visualize . --report report.json -o out/visualization.html --open
1151
1157
  $ aiready visualize . --report report.json --serve
@@ -1155,23 +1161,19 @@ EXAMPLES:
1155
1161
  NOTES:
1156
1162
  - The value passed to --report is interpreted relative to the directory argument (first positional).
1157
1163
  If the report is not found, the CLI will suggest running 'aiready scan' to generate it.
1158
- - Default output path: packages/visualizer/visualization.html (relative to the directory argument).
1164
+ - Default output path: visualization.html (in the current directory).
1159
1165
  - --serve starts a tiny single-file HTTP server (default port: 5173) and opens your browser.
1160
- It serves only the generated HTML (no additional asset folders).
1161
- - Relatedness is represented by node proximity and size; explicit 'related' edges are not drawn to
1162
- reduce clutter and improve interactivity on large graphs.
1163
- - For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
1164
- allow the browser a moment to stabilize after load.
1166
+ - --dev starts a Vite dev server with live reload (requires local @aiready/visualizer installation).
1167
+ When --dev is not available, it falls back to static HTML generation.
1165
1168
  `;
1166
1169
  var visualiseHelpText = `
1167
1170
  EXAMPLES:
1168
1171
  $ aiready visualise . # Auto-detects latest report
1169
1172
  $ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
1170
- $ aiready visualise . --report report.json --dev
1171
1173
  $ aiready visualise . --report report.json --serve 8080
1172
1174
 
1173
1175
  NOTES:
1174
- - Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
1176
+ - Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
1175
1177
  `;
1176
1178
 
1177
1179
  // src/cli.ts
package/dist/cli.mjs CHANGED
@@ -939,7 +939,9 @@ Or specify a custom report:
939
939
  console.log("Building graph from report...");
940
940
  const { GraphBuilder } = await import("@aiready/visualizer/graph");
941
941
  const graph = GraphBuilder.buildFromReport(report, dirPath);
942
- if (options.dev) {
942
+ let useDevMode = options.dev || false;
943
+ let devServerStarted = false;
944
+ if (useDevMode) {
943
945
  try {
944
946
  const monorepoWebDir = resolvePath6(dirPath, "packages/visualizer");
945
947
  let webDir = "";
@@ -975,96 +977,100 @@ Or specify a custom report:
975
977
  }
976
978
  }
977
979
  }
978
- const spawnCwd = webDir || process.cwd();
979
- const nodeBinCandidate = process.execPath;
980
- const nodeBin = existsSync2(nodeBinCandidate) ? nodeBinCandidate : "node";
981
- if (!visualizerAvailable) {
982
- console.error(chalk6.red("\u274C Cannot start dev server: @aiready/visualizer not available."));
983
- console.log(chalk6.dim("Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer"));
980
+ const webViteConfigExists = webDir && existsSync2(resolvePath6(webDir, "web", "vite.config.ts"));
981
+ if (visualizerAvailable && webViteConfigExists) {
982
+ const spawnCwd = webDir;
983
+ const { watch } = await import("fs");
984
+ const copyReportToViz = () => {
985
+ try {
986
+ const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
987
+ copyFileSync2(reportPath, destPath);
988
+ console.log(`\u{1F4CB} Report synced to ${destPath}`);
989
+ } catch (e) {
990
+ console.error("Failed to sync report:", e);
991
+ }
992
+ };
993
+ copyReportToViz();
994
+ let watchTimeout = null;
995
+ const reportWatcher = watch(reportPath, () => {
996
+ if (watchTimeout) clearTimeout(watchTimeout);
997
+ watchTimeout = setTimeout(copyReportToViz, 100);
998
+ });
999
+ const envForSpawn = {
1000
+ ...process.env,
1001
+ AIREADY_REPORT_PATH: reportPath,
1002
+ AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
1003
+ };
1004
+ const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
1005
+ const onExit = () => {
1006
+ try {
1007
+ reportWatcher.close();
1008
+ } catch (e) {
1009
+ }
1010
+ try {
1011
+ vite.kill();
1012
+ } catch (e) {
1013
+ }
1014
+ process.exit(0);
1015
+ };
1016
+ process.on("SIGINT", onExit);
1017
+ process.on("SIGTERM", onExit);
1018
+ devServerStarted = true;
984
1019
  return;
1020
+ } else {
1021
+ console.log(chalk6.yellow("\u26A0\uFE0F Dev server not available (requires local @aiready/visualizer with web assets)."));
1022
+ console.log(chalk6.cyan(" Falling back to static HTML generation...\n"));
1023
+ useDevMode = false;
985
1024
  }
986
- const { watch } = await import("fs");
987
- const copyReportToViz = () => {
988
- try {
989
- const destPath = resolvePath6(spawnCwd, "web", "report-data.json");
990
- copyFileSync2(reportPath, destPath);
991
- console.log(`\u{1F4CB} Report synced to ${destPath}`);
992
- } catch (e) {
993
- console.error("Failed to sync report:", e);
994
- }
995
- };
996
- copyReportToViz();
997
- let watchTimeout = null;
998
- const reportWatcher = watch(reportPath, () => {
999
- if (watchTimeout) clearTimeout(watchTimeout);
1000
- watchTimeout = setTimeout(copyReportToViz, 100);
1001
- });
1002
- const envForSpawn = {
1003
- ...process.env,
1004
- AIREADY_REPORT_PATH: reportPath,
1005
- AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
1006
- };
1007
- const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
1008
- const onExit = () => {
1009
- try {
1010
- reportWatcher.close();
1011
- } catch (e) {
1012
- }
1013
- try {
1014
- vite.kill();
1015
- } catch (e) {
1016
- }
1017
- process.exit(0);
1018
- };
1019
- process.on("SIGINT", onExit);
1020
- process.on("SIGTERM", onExit);
1021
- return;
1022
1025
  } catch (err) {
1023
1026
  console.error("Failed to start dev server:", err);
1027
+ console.log(chalk6.cyan(" Falling back to static HTML generation...\n"));
1028
+ useDevMode = false;
1024
1029
  }
1025
1030
  }
1026
1031
  console.log("Generating HTML...");
1027
1032
  const html = generateHTML(graph);
1028
- const outPath = resolvePath6(dirPath, options.output || "packages/visualizer/visualization.html");
1033
+ const defaultOutput = "visualization.html";
1034
+ const outPath = resolvePath6(dirPath, options.output || defaultOutput);
1029
1035
  writeFileSync2(outPath, html, "utf8");
1030
- console.log("Visualization written to:", outPath);
1031
- if (options.open) {
1036
+ console.log(chalk6.green(`\u2705 Visualization written to: ${outPath}`));
1037
+ if (options.open || options.serve) {
1032
1038
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1033
- spawn(opener, [`"${outPath}"`], { shell: true });
1034
- }
1035
- if (options.serve) {
1036
- try {
1037
- const port = typeof options.serve === "number" ? options.serve : 5173;
1038
- const http = await import("http");
1039
- const fsp = await import("fs/promises");
1040
- const server = http.createServer(async (req, res) => {
1041
- try {
1042
- const urlPath = req.url || "/";
1043
- if (urlPath === "/" || urlPath === "/index.html") {
1044
- const content = await fsp.readFile(outPath, "utf8");
1045
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1046
- res.end(content);
1047
- return;
1039
+ if (options.serve) {
1040
+ try {
1041
+ const port = typeof options.serve === "number" ? options.serve : 5173;
1042
+ const http = await import("http");
1043
+ const fsp = await import("fs/promises");
1044
+ const server = http.createServer(async (req, res) => {
1045
+ try {
1046
+ const urlPath = req.url || "/";
1047
+ if (urlPath === "/" || urlPath === "/index.html") {
1048
+ const content = await fsp.readFile(outPath, "utf8");
1049
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1050
+ res.end(content);
1051
+ return;
1052
+ }
1053
+ res.writeHead(404, { "Content-Type": "text/plain" });
1054
+ res.end("Not found");
1055
+ } catch (e) {
1056
+ res.writeHead(500, { "Content-Type": "text/plain" });
1057
+ res.end("Server error");
1048
1058
  }
1049
- res.writeHead(404, { "Content-Type": "text/plain" });
1050
- res.end("Not found");
1051
- } catch (e) {
1052
- res.writeHead(500, { "Content-Type": "text/plain" });
1053
- res.end("Server error");
1054
- }
1055
- });
1056
- server.listen(port, () => {
1057
- const addr = `http://localhost:${port}/`;
1058
- console.log(`Local visualization server running at ${addr}`);
1059
- const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
1060
- spawn(opener, [`"${addr}"`], { shell: true });
1061
- });
1062
- process.on("SIGINT", () => {
1063
- server.close();
1064
- process.exit(0);
1065
- });
1066
- } catch (err) {
1067
- console.error("Failed to start local server:", err);
1059
+ });
1060
+ server.listen(port, () => {
1061
+ const addr = `http://localhost:${port}/`;
1062
+ console.log(chalk6.cyan(`\u{1F310} Local visualization server running at ${addr}`));
1063
+ spawn(opener, [`"${addr}"`], { shell: true });
1064
+ });
1065
+ process.on("SIGINT", () => {
1066
+ server.close();
1067
+ process.exit(0);
1068
+ });
1069
+ } catch (err) {
1070
+ console.error("Failed to start local server:", err);
1071
+ }
1072
+ } else if (options.open) {
1073
+ spawn(opener, [`"${outPath}"`], { shell: true });
1068
1074
  }
1069
1075
  }
1070
1076
  } catch (err) {
@@ -1073,7 +1079,7 @@ Or specify a custom report:
1073
1079
  }
1074
1080
  var visualizeHelpText = `
1075
1081
  EXAMPLES:
1076
- $ aiready visualize . # Auto-detects latest report
1082
+ $ aiready visualize . # Auto-detects latest report, generates HTML
1077
1083
  $ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
1078
1084
  $ aiready visualize . --report report.json -o out/visualization.html --open
1079
1085
  $ aiready visualize . --report report.json --serve
@@ -1083,23 +1089,19 @@ EXAMPLES:
1083
1089
  NOTES:
1084
1090
  - The value passed to --report is interpreted relative to the directory argument (first positional).
1085
1091
  If the report is not found, the CLI will suggest running 'aiready scan' to generate it.
1086
- - Default output path: packages/visualizer/visualization.html (relative to the directory argument).
1092
+ - Default output path: visualization.html (in the current directory).
1087
1093
  - --serve starts a tiny single-file HTTP server (default port: 5173) and opens your browser.
1088
- It serves only the generated HTML (no additional asset folders).
1089
- - Relatedness is represented by node proximity and size; explicit 'related' edges are not drawn to
1090
- reduce clutter and improve interactivity on large graphs.
1091
- - For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
1092
- allow the browser a moment to stabilize after load.
1094
+ - --dev starts a Vite dev server with live reload (requires local @aiready/visualizer installation).
1095
+ When --dev is not available, it falls back to static HTML generation.
1093
1096
  `;
1094
1097
  var visualiseHelpText = `
1095
1098
  EXAMPLES:
1096
1099
  $ aiready visualise . # Auto-detects latest report
1097
1100
  $ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
1098
- $ aiready visualise . --report report.json --dev
1099
1101
  $ aiready visualise . --report report.json --serve 8080
1100
1102
 
1101
1103
  NOTES:
1102
- - Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
1104
+ - Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
1103
1105
  `;
1104
1106
 
1105
1107
  // src/cli.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiready/cli",
3
- "version": "0.9.28",
3
+ "version": "0.9.29",
4
4
  "description": "Unified CLI for AIReady analysis tools",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -11,11 +11,11 @@
11
11
  "dependencies": {
12
12
  "commander": "^14.0.0",
13
13
  "chalk": "^5.3.0",
14
- "@aiready/pattern-detect": "0.11.22",
15
14
  "@aiready/core": "0.9.22",
16
- "@aiready/context-analyzer": "0.9.26",
15
+ "@aiready/pattern-detect": "0.11.22",
17
16
  "@aiready/visualizer": "0.1.28",
18
- "@aiready/consistency": "0.8.22"
17
+ "@aiready/consistency": "0.8.22",
18
+ "@aiready/context-analyzer": "0.9.26"
19
19
  },
20
20
  "devDependencies": {
21
21
  "tsup": "^8.3.5",
@@ -65,17 +65,21 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
65
65
  const { GraphBuilder } = await import('@aiready/visualizer/graph');
66
66
  const graph = GraphBuilder.buildFromReport(report, dirPath);
67
67
 
68
- if (options.dev) {
68
+ // Check if --dev mode is requested and available
69
+ let useDevMode = options.dev || false;
70
+ let devServerStarted = false;
71
+
72
+ if (useDevMode) {
69
73
  try {
70
74
  const monorepoWebDir = resolvePath(dirPath, 'packages/visualizer');
71
75
  let webDir = '';
72
76
  let visualizerAvailable = false;
77
+
73
78
  if (existsSync(monorepoWebDir)) {
74
79
  webDir = monorepoWebDir;
75
80
  visualizerAvailable = true;
76
81
  } else {
77
82
  // Try to resolve installed @aiready/visualizer package from node_modules
78
- // Check multiple locations to support pnpm, npm, yarn, etc.
79
83
  const nodemodulesLocations: string[] = [
80
84
  resolvePath(dirPath, 'node_modules', '@aiready', 'visualizer'),
81
85
  resolvePath(process.cwd(), 'node_modules', '@aiready', 'visualizer'),
@@ -86,7 +90,7 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
86
90
  while (currentDir !== '/' && currentDir !== '.') {
87
91
  nodemodulesLocations.push(resolvePath(currentDir, 'node_modules', '@aiready', 'visualizer'));
88
92
  const parent = resolvePath(currentDir, '..');
89
- if (parent === currentDir) break; // Reached filesystem root
93
+ if (parent === currentDir) break;
90
94
  currentDir = parent;
91
95
  }
92
96
 
@@ -109,109 +113,113 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
109
113
  }
110
114
  }
111
115
  }
112
- const spawnCwd = webDir || process.cwd();
113
- const nodeBinCandidate = process.execPath;
114
- const nodeBin = existsSync(nodeBinCandidate) ? nodeBinCandidate : 'node';
115
- if (!visualizerAvailable) {
116
- console.error(chalk.red('❌ Cannot start dev server: @aiready/visualizer not available.'));
117
- console.log(chalk.dim('Install @aiready/visualizer in your project with:\n npm install @aiready/visualizer'));
118
- return;
119
- }
120
-
121
- // Inline report watcher: copy report to web/report-data.json and watch for changes
122
- const { watch } = await import('fs');
123
- const copyReportToViz = () => {
124
- try {
125
- const destPath = resolvePath(spawnCwd, 'web', 'report-data.json');
126
- copyFileSync(reportPath!, destPath);
127
- console.log(`📋 Report synced to ${destPath}`);
128
- } catch (e) {
129
- console.error('Failed to sync report:', e);
130
- }
131
- };
132
116
 
133
- // Initial copy
134
- copyReportToViz();
117
+ // Check if web directory with vite config exists (required for dev mode)
118
+ const webViteConfigExists = webDir && existsSync(resolvePath(webDir, 'web', 'vite.config.ts'));
135
119
 
136
- // Watch source report for changes
137
- let watchTimeout: NodeJS.Timeout | null = null;
138
- const reportWatcher = watch(reportPath, () => {
139
- // Debounce to avoid multiple copies during file write
140
- if (watchTimeout) clearTimeout(watchTimeout);
141
- watchTimeout = setTimeout(copyReportToViz, 100);
142
- });
143
-
144
- const envForSpawn = {
145
- ...process.env,
146
- AIREADY_REPORT_PATH: reportPath,
147
- AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
148
- };
149
- const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
150
- const onExit = () => {
151
- try {
152
- reportWatcher.close();
153
- } catch (e) {}
154
- try {
155
- vite.kill();
156
- } catch (e) {}
157
- process.exit(0);
158
- };
159
- process.on("SIGINT", onExit);
160
- process.on("SIGTERM", onExit);
161
- return;
120
+ if (visualizerAvailable && webViteConfigExists) {
121
+ // Dev mode is available - start Vite dev server
122
+ const spawnCwd = webDir!;
123
+
124
+ // Inline report watcher: copy report to web/report-data.json and watch for changes
125
+ const { watch } = await import('fs');
126
+ const copyReportToViz = () => {
127
+ try {
128
+ const destPath = resolvePath(spawnCwd, 'web', 'report-data.json');
129
+ copyFileSync(reportPath!, destPath);
130
+ console.log(`📋 Report synced to ${destPath}`);
131
+ } catch (e) {
132
+ console.error('Failed to sync report:', e);
133
+ }
134
+ };
135
+
136
+ // Initial copy
137
+ copyReportToViz();
138
+
139
+ // Watch source report for changes
140
+ let watchTimeout: NodeJS.Timeout | null = null;
141
+ const reportWatcher = watch(reportPath, () => {
142
+ if (watchTimeout) clearTimeout(watchTimeout);
143
+ watchTimeout = setTimeout(copyReportToViz, 100);
144
+ });
145
+
146
+ const envForSpawn = {
147
+ ...process.env,
148
+ AIREADY_REPORT_PATH: reportPath,
149
+ AIREADY_VISUALIZER_CONFIG: envVisualizerConfig
150
+ };
151
+ const vite = spawn("pnpm", ["run", "dev:web"], { cwd: spawnCwd, stdio: "inherit", shell: true, env: envForSpawn });
152
+ const onExit = () => {
153
+ try { reportWatcher.close(); } catch (e) {}
154
+ try { vite.kill(); } catch (e) {}
155
+ process.exit(0);
156
+ };
157
+ process.on("SIGINT", onExit);
158
+ process.on("SIGTERM", onExit);
159
+ devServerStarted = true;
160
+ return;
161
+ } else {
162
+ console.log(chalk.yellow('⚠️ Dev server not available (requires local @aiready/visualizer with web assets).'));
163
+ console.log(chalk.cyan(' Falling back to static HTML generation...\n'));
164
+ useDevMode = false;
165
+ }
162
166
  } catch (err) {
163
167
  console.error("Failed to start dev server:", err);
168
+ console.log(chalk.cyan(' Falling back to static HTML generation...\n'));
169
+ useDevMode = false;
164
170
  }
165
171
  }
166
172
 
173
+ // Generate static HTML (default behavior or fallback from failed --dev)
167
174
  console.log("Generating HTML...");
168
175
  const html = generateHTML(graph);
169
- const outPath = resolvePath(dirPath, options.output || 'packages/visualizer/visualization.html');
176
+ const defaultOutput = 'visualization.html';
177
+ const outPath = resolvePath(dirPath, options.output || defaultOutput);
170
178
  writeFileSync(outPath, html, 'utf8');
171
- console.log("Visualization written to:", outPath);
179
+ console.log(chalk.green(`✅ Visualization written to: ${outPath}`));
172
180
 
173
181
 
174
- if (options.open) {
182
+ if (options.open || options.serve) {
175
183
  const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
176
- spawn(opener, [`"${outPath}"`], { shell: true });
177
- }
184
+
185
+ if (options.serve) {
186
+ try {
187
+ const port = typeof options.serve === 'number' ? options.serve : 5173;
188
+ const http = await import('http');
189
+ const fsp = await import('fs/promises');
178
190
 
179
- if (options.serve) {
180
- try {
181
- const port = typeof options.serve === 'number' ? options.serve : 5173;
182
- const http = await import('http');
183
- const fsp = await import('fs/promises');
184
-
185
- const server = http.createServer(async (req, res) => {
186
- try {
187
- const urlPath = req.url || '/';
188
- if (urlPath === '/' || urlPath === '/index.html') {
189
- const content = await fsp.readFile(outPath, 'utf8');
190
- res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
191
- res.end(content);
192
- return;
191
+ const server = http.createServer(async (req, res) => {
192
+ try {
193
+ const urlPath = req.url || '/';
194
+ if (urlPath === '/' || urlPath === '/index.html') {
195
+ const content = await fsp.readFile(outPath, 'utf8');
196
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
197
+ res.end(content);
198
+ return;
199
+ }
200
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
201
+ res.end('Not found');
202
+ } catch (e: any) {
203
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
204
+ res.end('Server error');
193
205
  }
194
- res.writeHead(404, { 'Content-Type': 'text/plain' });
195
- res.end('Not found');
196
- } catch (e: any) {
197
- res.writeHead(500, { 'Content-Type': 'text/plain' });
198
- res.end('Server error');
199
- }
200
- });
201
-
202
- server.listen(port, () => {
203
- const addr = `http://localhost:${port}/`;
204
- console.log(`Local visualization server running at ${addr}`);
205
- const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
206
- spawn(opener, [`"${addr}"`], { shell: true });
207
- });
208
-
209
- process.on('SIGINT', () => {
210
- server.close();
211
- process.exit(0);
212
- });
213
- } catch (err) {
214
- console.error('Failed to start local server:', err);
206
+ });
207
+
208
+ server.listen(port, () => {
209
+ const addr = `http://localhost:${port}/`;
210
+ console.log(chalk.cyan(`🌐 Local visualization server running at ${addr}`));
211
+ spawn(opener, [`"${addr}"`], { shell: true });
212
+ });
213
+
214
+ process.on('SIGINT', () => {
215
+ server.close();
216
+ process.exit(0);
217
+ });
218
+ } catch (err) {
219
+ console.error('Failed to start local server:', err);
220
+ }
221
+ } else if (options.open) {
222
+ spawn(opener, [`"${outPath}"`], { shell: true });
215
223
  }
216
224
  }
217
225
 
@@ -222,7 +230,7 @@ export async function visualizeAction(directory: string, options: VisualizeOptio
222
230
 
223
231
  export const visualizeHelpText = `
224
232
  EXAMPLES:
225
- $ aiready visualize . # Auto-detects latest report
233
+ $ aiready visualize . # Auto-detects latest report, generates HTML
226
234
  $ aiready visualize . --report .aiready/aiready-report-20260217-143022.json
227
235
  $ aiready visualize . --report report.json -o out/visualization.html --open
228
236
  $ aiready visualize . --report report.json --serve
@@ -232,22 +240,18 @@ EXAMPLES:
232
240
  NOTES:
233
241
  - The value passed to --report is interpreted relative to the directory argument (first positional).
234
242
  If the report is not found, the CLI will suggest running 'aiready scan' to generate it.
235
- - Default output path: packages/visualizer/visualization.html (relative to the directory argument).
243
+ - Default output path: visualization.html (in the current directory).
236
244
  - --serve starts a tiny single-file HTTP server (default port: 5173) and opens your browser.
237
- It serves only the generated HTML (no additional asset folders).
238
- - Relatedness is represented by node proximity and size; explicit 'related' edges are not drawn to
239
- reduce clutter and improve interactivity on large graphs.
240
- - For very large graphs, consider narrowing the input with --include/--exclude or use --serve and
241
- allow the browser a moment to stabilize after load.
245
+ - --dev starts a Vite dev server with live reload (requires local @aiready/visualizer installation).
246
+ When --dev is not available, it falls back to static HTML generation.
242
247
  `;
243
248
 
244
249
  export const visualiseHelpText = `
245
250
  EXAMPLES:
246
251
  $ aiready visualise . # Auto-detects latest report
247
252
  $ aiready visualise . --report .aiready/aiready-report-20260217-143022.json
248
- $ aiready visualise . --report report.json --dev
249
253
  $ aiready visualise . --report report.json --serve 8080
250
254
 
251
255
  NOTES:
252
- - Same options as 'visualize'. Use --dev for live reload and --serve to host a static HTML.
256
+ - Same options as 'visualize'. Use --serve to host the static HTML, or --dev for live reload.
253
257
  `;