agentflow-core 0.2.1 → 0.2.3

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/index.cjs CHANGED
@@ -693,7 +693,7 @@ var C = {
693
693
  white: "\x1B[37m"
694
694
  };
695
695
  function parseArgs(argv) {
696
- const config = { tracesDir: ".", refreshMs: 3e3, recursive: false };
696
+ const config = { dirs: [], refreshMs: 3e3, recursive: false };
697
697
  const args = argv.slice(0);
698
698
  if (args[0] === "live") args.shift();
699
699
  let i = 0;
@@ -710,18 +710,14 @@ function parseArgs(argv) {
710
710
  } else if (arg === "--recursive" || arg === "-R") {
711
711
  config.recursive = true;
712
712
  i++;
713
- } else if (arg === "--traces-dir" || arg === "-t") {
714
- i++;
715
- config.tracesDir = args[i] ?? config.tracesDir;
716
- i++;
717
713
  } else if (!arg.startsWith("-")) {
718
- config.tracesDir = arg;
714
+ config.dirs.push((0, import_node_path2.resolve)(arg));
719
715
  i++;
720
716
  } else {
721
717
  i++;
722
718
  }
723
719
  }
724
- config.tracesDir = (0, import_node_path2.resolve)(config.tracesDir);
720
+ if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
725
721
  return config;
726
722
  }
727
723
  function printUsage() {
@@ -729,13 +725,13 @@ function printUsage() {
729
725
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
730
726
 
731
727
  Auto-detects agent traces, state files, job schedulers, and session logs
732
- from any JSON/JSONL files in the watched directory.
728
+ from any JSON/JSONL files in the watched directories.
733
729
 
734
730
  Usage:
735
- agentflow live [directory] [options]
731
+ agentflow live [dir...] [options]
736
732
 
737
733
  Arguments:
738
- directory Directory to watch (default: current directory)
734
+ dir One or more directories to watch (default: .)
739
735
 
740
736
  Options:
741
737
  -r, --refresh <secs> Refresh interval in seconds (default: 3)
@@ -744,38 +740,42 @@ Options:
744
740
 
745
741
  Examples:
746
742
  agentflow live ./data
747
- agentflow live ./traces --refresh 5
743
+ agentflow live ./traces ./cron ./workers --refresh 5
748
744
  agentflow live /var/lib/myagent -R
749
745
  `.trim());
750
746
  }
751
- function scanFiles(dir, recursive) {
747
+ function scanFiles(dirs, recursive) {
752
748
  const results = [];
753
- function scanDir(d) {
749
+ const seen = /* @__PURE__ */ new Set();
750
+ function scanDir(d, topLevel) {
754
751
  try {
755
752
  for (const f of (0, import_node_fs2.readdirSync)(d)) {
756
753
  if (f.startsWith(".")) continue;
757
754
  const fp = (0, import_node_path2.join)(d, f);
755
+ if (seen.has(fp)) continue;
758
756
  let stat;
759
757
  try {
760
758
  stat = (0, import_node_fs2.statSync)(fp);
761
759
  } catch {
762
760
  continue;
763
761
  }
764
- if (stat.isDirectory() && recursive && d === dir) {
765
- scanDir(fp);
762
+ if (stat.isDirectory() && recursive && topLevel) {
763
+ scanDir(fp, false);
766
764
  continue;
767
765
  }
768
766
  if (!stat.isFile()) continue;
769
767
  if (f.endsWith(".json")) {
768
+ seen.add(fp);
770
769
  results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
771
770
  } else if (f.endsWith(".jsonl")) {
771
+ seen.add(fp);
772
772
  results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
773
773
  }
774
774
  }
775
775
  } catch {
776
776
  }
777
777
  }
778
- scanDir(dir);
778
+ for (const dir of dirs) scanDir(dir, true);
779
779
  results.sort((a, b) => b.mtime - a.mtime);
780
780
  return results;
781
781
  }
@@ -925,11 +925,21 @@ function processJsonlFile(file) {
925
925
  return [];
926
926
  }
927
927
  }
928
+ var K = "\x1B[K";
929
+ function writeLine(lines, text) {
930
+ lines.push(text + K);
931
+ }
932
+ function flushLines(lines) {
933
+ process.stdout.write("\x1B[H");
934
+ process.stdout.write(lines.join("\n") + "\n");
935
+ process.stdout.write("\x1B[J");
936
+ }
928
937
  var prevFileCount = 0;
929
938
  var newExecCount = 0;
930
939
  var sessionStart = Date.now();
940
+ var firstRender = true;
931
941
  function render(config) {
932
- const files = scanFiles(config.tracesDir, config.recursive);
942
+ const files = scanFiles(config.dirs, config.recursive);
933
943
  if (files.length > prevFileCount && prevFileCount > 0) {
934
944
  newExecCount += files.length - prevFileCount;
935
945
  }
@@ -943,26 +953,58 @@ function render(config) {
943
953
  if (r.traceData) allTraces.push(r.traceData);
944
954
  }
945
955
  }
946
- const agents = {};
956
+ const byFile = /* @__PURE__ */ new Map();
947
957
  for (const r of allRecords) {
948
- if (!agents[r.id]) {
949
- agents[r.id] = { name: r.id, total: 0, ok: 0, fail: 0, running: 0, lastTs: 0, source: r.source, detail: "" };
950
- }
951
- const ag = agents[r.id];
952
- ag.total++;
953
- if (r.status === "ok") ag.ok++;
954
- else if (r.status === "error") ag.fail++;
955
- else if (r.status === "running") ag.running++;
956
- if (r.lastActive > ag.lastTs) {
957
- ag.lastTs = r.lastActive;
958
- ag.detail = r.detail;
959
- ag.source = r.source;
958
+ const arr = byFile.get(r.file) ?? [];
959
+ arr.push(r);
960
+ byFile.set(r.file, arr);
961
+ }
962
+ const groups = [];
963
+ for (const [file, records] of byFile) {
964
+ if (records.length === 1) {
965
+ const r = records[0];
966
+ groups.push({
967
+ name: r.id,
968
+ source: r.source,
969
+ status: r.status,
970
+ lastTs: r.lastActive,
971
+ detail: r.detail,
972
+ children: [],
973
+ ok: r.status === "ok" ? 1 : 0,
974
+ fail: r.status === "error" ? 1 : 0,
975
+ running: r.status === "running" ? 1 : 0,
976
+ total: 1
977
+ });
978
+ } else {
979
+ const groupName = nameFromFile(file);
980
+ let lastTs = 0;
981
+ let ok = 0, fail = 0, running = 0;
982
+ for (const r of records) {
983
+ if (r.lastActive > lastTs) lastTs = r.lastActive;
984
+ if (r.status === "ok") ok++;
985
+ else if (r.status === "error") fail++;
986
+ else if (r.status === "running") running++;
987
+ }
988
+ const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
989
+ groups.push({
990
+ name: groupName,
991
+ source: records[0].source,
992
+ status,
993
+ lastTs,
994
+ detail: `${records.length} agents`,
995
+ children: records.sort((a, b) => b.lastActive - a.lastActive),
996
+ ok,
997
+ fail,
998
+ running,
999
+ total: records.length
1000
+ });
960
1001
  }
961
1002
  }
962
- const agentList = Object.values(agents).sort((a, b) => b.lastTs - a.lastTs);
963
- const totExec = agentList.reduce((s, a) => s + a.total, 0);
964
- const totFail = agentList.reduce((s, a) => s + a.fail, 0);
965
- const totRunning = agentList.reduce((s, a) => s + a.running, 0);
1003
+ groups.sort((a, b) => b.lastTs - a.lastTs);
1004
+ const totExec = allRecords.length;
1005
+ const totFail = allRecords.filter((r) => r.status === "error").length;
1006
+ const totRunning = allRecords.filter((r) => r.status === "running").length;
1007
+ const uniqueAgents = new Set(allRecords.map((r) => r.id)).size;
966
1008
  const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
967
1009
  const now = Date.now();
968
1010
  const buckets = new Array(12).fill(0);
@@ -999,7 +1041,21 @@ function render(config) {
999
1041
  const upMin = Math.floor(upSec / 60);
1000
1042
  const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
1001
1043
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
1002
- const sourceTag = (s) => {
1044
+ function statusIcon(s, recent) {
1045
+ if (s === "error") return `${C.red}\u25CF${C.reset}`;
1046
+ if (s === "running") return `${C.green}\u25CF${C.reset}`;
1047
+ if (s === "ok" && recent) return `${C.green}\u25CF${C.reset}`;
1048
+ if (s === "ok") return `${C.dim}\u25CB${C.reset}`;
1049
+ return `${C.dim}\u25CB${C.reset}`;
1050
+ }
1051
+ function statusText(g) {
1052
+ if (g.fail > 0 && g.ok === 0 && g.running === 0) return `${C.red}error${C.reset}`;
1053
+ if (g.running > 0) return `${C.green}running${C.reset}`;
1054
+ if (g.fail > 0) return `${C.yellow}${g.ok}ok/${g.fail}err${C.reset}`;
1055
+ if (g.ok > 0) return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1056
+ return `${C.dim}idle${C.reset}`;
1057
+ }
1058
+ function sourceTag(s) {
1003
1059
  switch (s) {
1004
1060
  case "trace":
1005
1061
  return `${C.cyan}trace${C.reset}`;
@@ -1012,92 +1068,115 @@ function render(config) {
1012
1068
  case "state":
1013
1069
  return `${C.dim}state${C.reset}`;
1014
1070
  }
1015
- };
1016
- process.stdout.write("\x1B[2J\x1B[H");
1017
- console.log(`${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1018
- console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`);
1071
+ }
1072
+ function timeStr(ts) {
1073
+ if (ts <= 0) return "n/a";
1074
+ return new Date(ts).toLocaleTimeString();
1075
+ }
1076
+ function truncate(s, max) {
1077
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1078
+ }
1079
+ if (firstRender) {
1080
+ process.stdout.write("\x1B[2J");
1081
+ firstRender = false;
1082
+ }
1083
+ const L = [];
1084
+ writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1085
+ writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`);
1019
1086
  const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
1020
1087
  const pad1 = Math.max(0, 64 - metaLine.length);
1021
- console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
1022
- console.log(`${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1088
+ writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
1089
+ writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1023
1090
  const sc = totFail === 0 ? C.green : C.yellow;
1024
- console.log("");
1025
- console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`);
1026
- console.log("");
1027
- console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1028
- console.log("");
1029
- console.log(` ${C.bold}${C.under}Agent Type Status Last Active Detail${C.reset}`);
1030
- for (const ag of agentList.slice(0, 30)) {
1031
- const lastTime = ag.lastTs > 0 ? new Date(ag.lastTs).toLocaleTimeString() : "n/a";
1032
- const isRecent = Date.now() - ag.lastTs < 3e5;
1033
- let statusIcon;
1034
- let statusText;
1035
- if (ag.fail > 0 && ag.ok === 0 && ag.running === 0) {
1036
- statusIcon = `${C.red}\u25CF${C.reset}`;
1037
- statusText = `${C.red}error${C.reset}`;
1038
- } else if (ag.running > 0) {
1039
- statusIcon = `${C.green}\u25CF${C.reset}`;
1040
- statusText = `${C.green}running${C.reset}`;
1041
- } else if (ag.fail > 0) {
1042
- statusIcon = `${C.yellow}\u25CF${C.reset}`;
1043
- statusText = `${C.yellow}${ag.ok}ok/${ag.fail}err${C.reset}`;
1044
- } else if (ag.ok > 0) {
1045
- statusIcon = isRecent ? `${C.green}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
1046
- statusText = ag.total > 1 ? `${C.green}${ag.ok}/${ag.total}${C.reset}` : `${C.green}ok${C.reset}`;
1091
+ writeLine(L, "");
1092
+ writeLine(L, ` ${C.bold}Agents${C.reset} ${sc}${uniqueAgents}${C.reset} ${C.bold}Records${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Running${C.reset} ${C.green}${totRunning}${C.reset} ${C.bold}Errors${C.reset} ${totFail > 0 ? C.red : C.dim}${totFail}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset}`);
1093
+ writeLine(L, "");
1094
+ writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1095
+ writeLine(L, "");
1096
+ writeLine(L, ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`);
1097
+ let lineCount = 0;
1098
+ for (const g of groups) {
1099
+ if (lineCount > 35) break;
1100
+ const isRecent = Date.now() - g.lastTs < 3e5;
1101
+ const icon = statusIcon(g.status, isRecent);
1102
+ const active = isRecent ? `${C.green}${timeStr(g.lastTs)}${C.reset}` : `${C.dim}${timeStr(g.lastTs)}${C.reset}`;
1103
+ if (g.children.length === 0) {
1104
+ const name = truncate(g.name, 26).padEnd(26);
1105
+ const st = statusText(g);
1106
+ const det = truncate(g.detail, 30);
1107
+ writeLine(L, ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`);
1108
+ lineCount++;
1047
1109
  } else {
1048
- statusIcon = `${C.dim}\u25CB${C.reset}`;
1049
- statusText = `${C.dim}idle${C.reset}`;
1110
+ const name = truncate(g.name, 24).padEnd(24);
1111
+ const st = statusText(g);
1112
+ const tag = sourceTag(g.source);
1113
+ writeLine(L, ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`);
1114
+ lineCount++;
1115
+ const kids = g.children.slice(0, 12);
1116
+ for (let i = 0; i < kids.length; i++) {
1117
+ if (lineCount > 35) break;
1118
+ const child = kids[i];
1119
+ const isLast = i === kids.length - 1;
1120
+ const connector = isLast ? "\u2514\u2500" : "\u251C\u2500";
1121
+ const cIcon = statusIcon(child.status, Date.now() - child.lastActive < 3e5);
1122
+ const cName = truncate(child.id, 22).padEnd(22);
1123
+ const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
1124
+ const cDet = truncate(child.detail, 25);
1125
+ writeLine(L, ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`);
1126
+ lineCount++;
1127
+ }
1128
+ if (g.children.length > 12) {
1129
+ writeLine(L, ` ${C.dim} ... +${g.children.length - 12} more${C.reset}`);
1130
+ lineCount++;
1131
+ }
1050
1132
  }
1051
- const name = ag.name.length > 23 ? ag.name.slice(0, 22) + "\u2026" : ag.name.padEnd(23);
1052
- const src = sourceTag(ag.source).padEnd(16);
1053
- const active = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
1054
- const detail = ag.detail.length > 30 ? ag.detail.slice(0, 29) + "\u2026" : ag.detail;
1055
- console.log(` ${statusIcon} ${name} ${src} ${statusText.padEnd(18)} ${active.padEnd(20)} ${C.dim}${detail}${C.reset}`);
1056
1133
  }
1057
1134
  if (distributedTraces.length > 0) {
1058
- console.log("");
1059
- console.log(` ${C.bold}${C.under}Distributed Traces${C.reset}`);
1135
+ writeLine(L, "");
1136
+ writeLine(L, ` ${C.bold}${C.under}Distributed Traces${C.reset}`);
1060
1137
  for (const dt of distributedTraces.slice(0, 3)) {
1061
1138
  const traceTime = new Date(dt.startTime).toLocaleTimeString();
1062
- const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1139
+ const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1063
1140
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1064
1141
  const tid = dt.traceId.slice(0, 8);
1065
- console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1142
+ writeLine(L, ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1066
1143
  const tree = getTraceTree(dt);
1067
1144
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1068
- const g = tree[i];
1069
- const depth = getDistDepth(dt, g.spanId);
1145
+ const tg = tree[i];
1146
+ const depth = getDistDepth(dt, tg.spanId);
1070
1147
  const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
1071
1148
  const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
1072
1149
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1073
- const gs = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1074
- const gd = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
1075
- console.log(`${indent}${conn}${gs} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gd}${C.reset}`);
1150
+ const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1151
+ const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
1152
+ writeLine(L, `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`);
1076
1153
  }
1077
1154
  }
1078
1155
  }
1079
- const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 8);
1156
+ const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 6);
1080
1157
  if (recentRecords.length > 0) {
1081
- console.log("");
1082
- console.log(` ${C.bold}${C.under}Recent Activity${C.reset}`);
1158
+ writeLine(L, "");
1159
+ writeLine(L, ` ${C.bold}${C.under}Recent Activity${C.reset}`);
1083
1160
  for (const r of recentRecords) {
1084
1161
  const icon = r.status === "ok" ? `${C.green}\u2713${C.reset}` : r.status === "error" ? `${C.red}\u2717${C.reset}` : r.status === "running" ? `${C.green}\u25B6${C.reset}` : `${C.dim}\u25CB${C.reset}`;
1085
1162
  const t = new Date(r.lastActive).toLocaleTimeString();
1086
- const agent = r.id.length > 26 ? r.id.slice(0, 25) + "\u2026" : r.id.padEnd(26);
1163
+ const agent = truncate(r.id, 26).padEnd(26);
1087
1164
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1088
1165
  const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1089
- const detail = r.detail.length > 25 ? r.detail.slice(0, 24) + "\u2026" : r.detail;
1090
- console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${detail}${C.reset}`);
1166
+ const det = truncate(r.detail, 25);
1167
+ writeLine(L, ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`);
1091
1168
  }
1092
1169
  }
1093
1170
  if (files.length === 0) {
1094
- console.log("");
1095
- console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
1096
- console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
1171
+ writeLine(L, "");
1172
+ writeLine(L, ` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
1173
+ for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
1097
1174
  }
1098
- console.log("");
1099
- console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
1100
- console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1175
+ writeLine(L, "");
1176
+ const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
1177
+ writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
1178
+ writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1179
+ flushLines(L);
1101
1180
  }
1102
1181
  function getDistDepth(dt, spanId) {
1103
1182
  if (!spanId) return 0;
@@ -1107,19 +1186,27 @@ function getDistDepth(dt, spanId) {
1107
1186
  }
1108
1187
  function startLive(argv) {
1109
1188
  const config = parseArgs(argv);
1110
- if (!(0, import_node_fs2.existsSync)(config.tracesDir)) {
1111
- console.error(`Directory does not exist: ${config.tracesDir}`);
1112
- console.error("Specify a directory containing JSON/JSONL files: agentflow live <dir>");
1189
+ const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
1190
+ if (valid.length === 0) {
1191
+ console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1192
+ console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
1113
1193
  process.exit(1);
1114
1194
  }
1195
+ const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
1196
+ if (invalid.length > 0) {
1197
+ console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1198
+ }
1199
+ config.dirs = valid;
1115
1200
  render(config);
1116
1201
  let debounce = null;
1117
- try {
1118
- (0, import_node_fs2.watch)(config.tracesDir, { recursive: config.recursive }, () => {
1119
- if (debounce) clearTimeout(debounce);
1120
- debounce = setTimeout(() => render(config), 500);
1121
- });
1122
- } catch {
1202
+ for (const dir of config.dirs) {
1203
+ try {
1204
+ (0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
1205
+ if (debounce) clearTimeout(debounce);
1206
+ debounce = setTimeout(() => render(config), 500);
1207
+ });
1208
+ } catch {
1209
+ }
1123
1210
  }
1124
1211
  setInterval(() => render(config), config.refreshMs);
1125
1212
  process.on("SIGINT", () => {
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  runTraced,
19
19
  startLive,
20
20
  stitchTrace
21
- } from "./chunk-FJVQYJFB.js";
21
+ } from "./chunk-5SPZPOFN.js";
22
22
  export {
23
23
  createGraphBuilder,
24
24
  findWaitingOn,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentflow-core",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Universal execution tracing for AI agent systems",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",