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.
@@ -647,7 +647,7 @@ var C = {
647
647
  white: "\x1B[37m"
648
648
  };
649
649
  function parseArgs(argv) {
650
- const config = { tracesDir: ".", refreshMs: 3e3, recursive: false };
650
+ const config = { dirs: [], refreshMs: 3e3, recursive: false };
651
651
  const args = argv.slice(0);
652
652
  if (args[0] === "live") args.shift();
653
653
  let i = 0;
@@ -664,18 +664,14 @@ function parseArgs(argv) {
664
664
  } else if (arg === "--recursive" || arg === "-R") {
665
665
  config.recursive = true;
666
666
  i++;
667
- } else if (arg === "--traces-dir" || arg === "-t") {
668
- i++;
669
- config.tracesDir = args[i] ?? config.tracesDir;
670
- i++;
671
667
  } else if (!arg.startsWith("-")) {
672
- config.tracesDir = arg;
668
+ config.dirs.push(resolve2(arg));
673
669
  i++;
674
670
  } else {
675
671
  i++;
676
672
  }
677
673
  }
678
- config.tracesDir = resolve2(config.tracesDir);
674
+ if (config.dirs.length === 0) config.dirs.push(resolve2("."));
679
675
  return config;
680
676
  }
681
677
  function printUsage() {
@@ -683,13 +679,13 @@ function printUsage() {
683
679
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
684
680
 
685
681
  Auto-detects agent traces, state files, job schedulers, and session logs
686
- from any JSON/JSONL files in the watched directory.
682
+ from any JSON/JSONL files in the watched directories.
687
683
 
688
684
  Usage:
689
- agentflow live [directory] [options]
685
+ agentflow live [dir...] [options]
690
686
 
691
687
  Arguments:
692
- directory Directory to watch (default: current directory)
688
+ dir One or more directories to watch (default: .)
693
689
 
694
690
  Options:
695
691
  -r, --refresh <secs> Refresh interval in seconds (default: 3)
@@ -698,38 +694,42 @@ Options:
698
694
 
699
695
  Examples:
700
696
  agentflow live ./data
701
- agentflow live ./traces --refresh 5
697
+ agentflow live ./traces ./cron ./workers --refresh 5
702
698
  agentflow live /var/lib/myagent -R
703
699
  `.trim());
704
700
  }
705
- function scanFiles(dir, recursive) {
701
+ function scanFiles(dirs, recursive) {
706
702
  const results = [];
707
- function scanDir(d) {
703
+ const seen = /* @__PURE__ */ new Set();
704
+ function scanDir(d, topLevel) {
708
705
  try {
709
706
  for (const f of readdirSync2(d)) {
710
707
  if (f.startsWith(".")) continue;
711
708
  const fp = join2(d, f);
709
+ if (seen.has(fp)) continue;
712
710
  let stat;
713
711
  try {
714
712
  stat = statSync2(fp);
715
713
  } catch {
716
714
  continue;
717
715
  }
718
- if (stat.isDirectory() && recursive && d === dir) {
719
- scanDir(fp);
716
+ if (stat.isDirectory() && recursive && topLevel) {
717
+ scanDir(fp, false);
720
718
  continue;
721
719
  }
722
720
  if (!stat.isFile()) continue;
723
721
  if (f.endsWith(".json")) {
722
+ seen.add(fp);
724
723
  results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
725
724
  } else if (f.endsWith(".jsonl")) {
725
+ seen.add(fp);
726
726
  results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
727
727
  }
728
728
  }
729
729
  } catch {
730
730
  }
731
731
  }
732
- scanDir(dir);
732
+ for (const dir of dirs) scanDir(dir, true);
733
733
  results.sort((a, b) => b.mtime - a.mtime);
734
734
  return results;
735
735
  }
@@ -879,11 +879,21 @@ function processJsonlFile(file) {
879
879
  return [];
880
880
  }
881
881
  }
882
+ var K = "\x1B[K";
883
+ function writeLine(lines, text) {
884
+ lines.push(text + K);
885
+ }
886
+ function flushLines(lines) {
887
+ process.stdout.write("\x1B[H");
888
+ process.stdout.write(lines.join("\n") + "\n");
889
+ process.stdout.write("\x1B[J");
890
+ }
882
891
  var prevFileCount = 0;
883
892
  var newExecCount = 0;
884
893
  var sessionStart = Date.now();
894
+ var firstRender = true;
885
895
  function render(config) {
886
- const files = scanFiles(config.tracesDir, config.recursive);
896
+ const files = scanFiles(config.dirs, config.recursive);
887
897
  if (files.length > prevFileCount && prevFileCount > 0) {
888
898
  newExecCount += files.length - prevFileCount;
889
899
  }
@@ -897,26 +907,58 @@ function render(config) {
897
907
  if (r.traceData) allTraces.push(r.traceData);
898
908
  }
899
909
  }
900
- const agents = {};
910
+ const byFile = /* @__PURE__ */ new Map();
901
911
  for (const r of allRecords) {
902
- if (!agents[r.id]) {
903
- agents[r.id] = { name: r.id, total: 0, ok: 0, fail: 0, running: 0, lastTs: 0, source: r.source, detail: "" };
904
- }
905
- const ag = agents[r.id];
906
- ag.total++;
907
- if (r.status === "ok") ag.ok++;
908
- else if (r.status === "error") ag.fail++;
909
- else if (r.status === "running") ag.running++;
910
- if (r.lastActive > ag.lastTs) {
911
- ag.lastTs = r.lastActive;
912
- ag.detail = r.detail;
913
- ag.source = r.source;
912
+ const arr = byFile.get(r.file) ?? [];
913
+ arr.push(r);
914
+ byFile.set(r.file, arr);
915
+ }
916
+ const groups = [];
917
+ for (const [file, records] of byFile) {
918
+ if (records.length === 1) {
919
+ const r = records[0];
920
+ groups.push({
921
+ name: r.id,
922
+ source: r.source,
923
+ status: r.status,
924
+ lastTs: r.lastActive,
925
+ detail: r.detail,
926
+ children: [],
927
+ ok: r.status === "ok" ? 1 : 0,
928
+ fail: r.status === "error" ? 1 : 0,
929
+ running: r.status === "running" ? 1 : 0,
930
+ total: 1
931
+ });
932
+ } else {
933
+ const groupName = nameFromFile(file);
934
+ let lastTs = 0;
935
+ let ok = 0, fail = 0, running = 0;
936
+ for (const r of records) {
937
+ if (r.lastActive > lastTs) lastTs = r.lastActive;
938
+ if (r.status === "ok") ok++;
939
+ else if (r.status === "error") fail++;
940
+ else if (r.status === "running") running++;
941
+ }
942
+ const status = fail > 0 ? "error" : running > 0 ? "running" : ok > 0 ? "ok" : "unknown";
943
+ groups.push({
944
+ name: groupName,
945
+ source: records[0].source,
946
+ status,
947
+ lastTs,
948
+ detail: `${records.length} agents`,
949
+ children: records.sort((a, b) => b.lastActive - a.lastActive),
950
+ ok,
951
+ fail,
952
+ running,
953
+ total: records.length
954
+ });
914
955
  }
915
956
  }
916
- const agentList = Object.values(agents).sort((a, b) => b.lastTs - a.lastTs);
917
- const totExec = agentList.reduce((s, a) => s + a.total, 0);
918
- const totFail = agentList.reduce((s, a) => s + a.fail, 0);
919
- const totRunning = agentList.reduce((s, a) => s + a.running, 0);
957
+ groups.sort((a, b) => b.lastTs - a.lastTs);
958
+ const totExec = allRecords.length;
959
+ const totFail = allRecords.filter((r) => r.status === "error").length;
960
+ const totRunning = allRecords.filter((r) => r.status === "running").length;
961
+ const uniqueAgents = new Set(allRecords.map((r) => r.id)).size;
920
962
  const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
921
963
  const now = Date.now();
922
964
  const buckets = new Array(12).fill(0);
@@ -953,7 +995,21 @@ function render(config) {
953
995
  const upMin = Math.floor(upSec / 60);
954
996
  const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
955
997
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
956
- const sourceTag = (s) => {
998
+ function statusIcon(s, recent) {
999
+ if (s === "error") return `${C.red}\u25CF${C.reset}`;
1000
+ if (s === "running") return `${C.green}\u25CF${C.reset}`;
1001
+ if (s === "ok" && recent) return `${C.green}\u25CF${C.reset}`;
1002
+ if (s === "ok") return `${C.dim}\u25CB${C.reset}`;
1003
+ return `${C.dim}\u25CB${C.reset}`;
1004
+ }
1005
+ function statusText(g) {
1006
+ if (g.fail > 0 && g.ok === 0 && g.running === 0) return `${C.red}error${C.reset}`;
1007
+ if (g.running > 0) return `${C.green}running${C.reset}`;
1008
+ if (g.fail > 0) return `${C.yellow}${g.ok}ok/${g.fail}err${C.reset}`;
1009
+ if (g.ok > 0) return g.total > 1 ? `${C.green}${g.ok}/${g.total} ok${C.reset}` : `${C.green}ok${C.reset}`;
1010
+ return `${C.dim}idle${C.reset}`;
1011
+ }
1012
+ function sourceTag(s) {
957
1013
  switch (s) {
958
1014
  case "trace":
959
1015
  return `${C.cyan}trace${C.reset}`;
@@ -966,92 +1022,115 @@ function render(config) {
966
1022
  case "state":
967
1023
  return `${C.dim}state${C.reset}`;
968
1024
  }
969
- };
970
- process.stdout.write("\x1B[2J\x1B[H");
971
- console.log(`${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
972
- 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}`);
1025
+ }
1026
+ function timeStr(ts) {
1027
+ if (ts <= 0) return "n/a";
1028
+ return new Date(ts).toLocaleTimeString();
1029
+ }
1030
+ function truncate(s, max) {
1031
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1032
+ }
1033
+ if (firstRender) {
1034
+ process.stdout.write("\x1B[2J");
1035
+ firstRender = false;
1036
+ }
1037
+ const L = [];
1038
+ writeLine(L, `${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
1039
+ 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}`);
973
1040
  const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
974
1041
  const pad1 = Math.max(0, 64 - metaLine.length);
975
- console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
976
- console.log(`${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
1042
+ writeLine(L, `${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
1043
+ writeLine(L, `${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
977
1044
  const sc = totFail === 0 ? C.green : C.yellow;
978
- console.log("");
979
- 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}`);
980
- console.log("");
981
- console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
982
- console.log("");
983
- console.log(` ${C.bold}${C.under}Agent Type Status Last Active Detail${C.reset}`);
984
- for (const ag of agentList.slice(0, 30)) {
985
- const lastTime = ag.lastTs > 0 ? new Date(ag.lastTs).toLocaleTimeString() : "n/a";
986
- const isRecent = Date.now() - ag.lastTs < 3e5;
987
- let statusIcon;
988
- let statusText;
989
- if (ag.fail > 0 && ag.ok === 0 && ag.running === 0) {
990
- statusIcon = `${C.red}\u25CF${C.reset}`;
991
- statusText = `${C.red}error${C.reset}`;
992
- } else if (ag.running > 0) {
993
- statusIcon = `${C.green}\u25CF${C.reset}`;
994
- statusText = `${C.green}running${C.reset}`;
995
- } else if (ag.fail > 0) {
996
- statusIcon = `${C.yellow}\u25CF${C.reset}`;
997
- statusText = `${C.yellow}${ag.ok}ok/${ag.fail}err${C.reset}`;
998
- } else if (ag.ok > 0) {
999
- statusIcon = isRecent ? `${C.green}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
1000
- statusText = ag.total > 1 ? `${C.green}${ag.ok}/${ag.total}${C.reset}` : `${C.green}ok${C.reset}`;
1045
+ writeLine(L, "");
1046
+ 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}`);
1047
+ writeLine(L, "");
1048
+ writeLine(L, ` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
1049
+ writeLine(L, "");
1050
+ writeLine(L, ` ${C.bold}${C.under}Agent Status Last Active Detail${C.reset}`);
1051
+ let lineCount = 0;
1052
+ for (const g of groups) {
1053
+ if (lineCount > 35) break;
1054
+ const isRecent = Date.now() - g.lastTs < 3e5;
1055
+ const icon = statusIcon(g.status, isRecent);
1056
+ const active = isRecent ? `${C.green}${timeStr(g.lastTs)}${C.reset}` : `${C.dim}${timeStr(g.lastTs)}${C.reset}`;
1057
+ if (g.children.length === 0) {
1058
+ const name = truncate(g.name, 26).padEnd(26);
1059
+ const st = statusText(g);
1060
+ const det = truncate(g.detail, 30);
1061
+ writeLine(L, ` ${icon} ${name} ${st.padEnd(20)} ${active.padEnd(20)} ${C.dim}${det}${C.reset}`);
1062
+ lineCount++;
1001
1063
  } else {
1002
- statusIcon = `${C.dim}\u25CB${C.reset}`;
1003
- statusText = `${C.dim}idle${C.reset}`;
1064
+ const name = truncate(g.name, 24).padEnd(24);
1065
+ const st = statusText(g);
1066
+ const tag = sourceTag(g.source);
1067
+ writeLine(L, ` ${icon} ${C.bold}${name}${C.reset} ${st.padEnd(20)} ${active.padEnd(20)} ${tag} ${C.dim}(${g.children.length} agents)${C.reset}`);
1068
+ lineCount++;
1069
+ const kids = g.children.slice(0, 12);
1070
+ for (let i = 0; i < kids.length; i++) {
1071
+ if (lineCount > 35) break;
1072
+ const child = kids[i];
1073
+ const isLast = i === kids.length - 1;
1074
+ const connector = isLast ? "\u2514\u2500" : "\u251C\u2500";
1075
+ const cIcon = statusIcon(child.status, Date.now() - child.lastActive < 3e5);
1076
+ const cName = truncate(child.id, 22).padEnd(22);
1077
+ const cActive = `${C.dim}${timeStr(child.lastActive)}${C.reset}`;
1078
+ const cDet = truncate(child.detail, 25);
1079
+ writeLine(L, ` ${C.dim}${connector}${C.reset} ${cIcon} ${cName} ${cActive.padEnd(20)} ${C.dim}${cDet}${C.reset}`);
1080
+ lineCount++;
1081
+ }
1082
+ if (g.children.length > 12) {
1083
+ writeLine(L, ` ${C.dim} ... +${g.children.length - 12} more${C.reset}`);
1084
+ lineCount++;
1085
+ }
1004
1086
  }
1005
- const name = ag.name.length > 23 ? ag.name.slice(0, 22) + "\u2026" : ag.name.padEnd(23);
1006
- const src = sourceTag(ag.source).padEnd(16);
1007
- const active = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
1008
- const detail = ag.detail.length > 30 ? ag.detail.slice(0, 29) + "\u2026" : ag.detail;
1009
- console.log(` ${statusIcon} ${name} ${src} ${statusText.padEnd(18)} ${active.padEnd(20)} ${C.dim}${detail}${C.reset}`);
1010
1087
  }
1011
1088
  if (distributedTraces.length > 0) {
1012
- console.log("");
1013
- console.log(` ${C.bold}${C.under}Distributed Traces${C.reset}`);
1089
+ writeLine(L, "");
1090
+ writeLine(L, ` ${C.bold}${C.under}Distributed Traces${C.reset}`);
1014
1091
  for (const dt of distributedTraces.slice(0, 3)) {
1015
1092
  const traceTime = new Date(dt.startTime).toLocaleTimeString();
1016
- const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1093
+ const si = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1017
1094
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
1018
1095
  const tid = dt.traceId.slice(0, 8);
1019
- console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1096
+ writeLine(L, ` ${si} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
1020
1097
  const tree = getTraceTree(dt);
1021
1098
  for (let i = 0; i < Math.min(tree.length, 6); i++) {
1022
- const g = tree[i];
1023
- const depth = getDistDepth(dt, g.spanId);
1099
+ const tg = tree[i];
1100
+ const depth = getDistDepth(dt, tg.spanId);
1024
1101
  const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
1025
1102
  const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
1026
1103
  const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
1027
- const gs = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1028
- const gd = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
1029
- console.log(`${indent}${conn}${gs} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gd}${C.reset}`);
1104
+ const gs = tg.status === "completed" ? `${C.green}\u2713${C.reset}` : tg.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
1105
+ const gd = tg.endTime ? `${tg.endTime - tg.startTime}ms` : "running";
1106
+ writeLine(L, `${indent}${conn}${gs} ${C.bold}${tg.agentId}${C.reset} ${C.dim}[${tg.trigger}] ${gd}${C.reset}`);
1030
1107
  }
1031
1108
  }
1032
1109
  }
1033
- const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 8);
1110
+ const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 6);
1034
1111
  if (recentRecords.length > 0) {
1035
- console.log("");
1036
- console.log(` ${C.bold}${C.under}Recent Activity${C.reset}`);
1112
+ writeLine(L, "");
1113
+ writeLine(L, ` ${C.bold}${C.under}Recent Activity${C.reset}`);
1037
1114
  for (const r of recentRecords) {
1038
1115
  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}`;
1039
1116
  const t = new Date(r.lastActive).toLocaleTimeString();
1040
- const agent = r.id.length > 26 ? r.id.slice(0, 25) + "\u2026" : r.id.padEnd(26);
1117
+ const agent = truncate(r.id, 26).padEnd(26);
1041
1118
  const age = Math.floor((Date.now() - r.lastActive) / 1e3);
1042
1119
  const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
1043
- const detail = r.detail.length > 25 ? r.detail.slice(0, 24) + "\u2026" : r.detail;
1044
- console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${detail}${C.reset}`);
1120
+ const det = truncate(r.detail, 25);
1121
+ writeLine(L, ` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${det}${C.reset}`);
1045
1122
  }
1046
1123
  }
1047
1124
  if (files.length === 0) {
1048
- console.log("");
1049
- console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
1050
- console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
1125
+ writeLine(L, "");
1126
+ writeLine(L, ` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
1127
+ for (const d of config.dirs) writeLine(L, ` ${C.dim} ${d}${C.reset}`);
1051
1128
  }
1052
- console.log("");
1053
- console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
1054
- console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1129
+ writeLine(L, "");
1130
+ const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
1131
+ writeLine(L, ` ${C.dim}Watching: ${dirLabel}${C.reset}`);
1132
+ writeLine(L, ` ${C.dim}Press Ctrl+C to exit${C.reset}`);
1133
+ flushLines(L);
1055
1134
  }
1056
1135
  function getDistDepth(dt, spanId) {
1057
1136
  if (!spanId) return 0;
@@ -1061,19 +1140,27 @@ function getDistDepth(dt, spanId) {
1061
1140
  }
1062
1141
  function startLive(argv) {
1063
1142
  const config = parseArgs(argv);
1064
- if (!existsSync2(config.tracesDir)) {
1065
- console.error(`Directory does not exist: ${config.tracesDir}`);
1066
- console.error("Specify a directory containing JSON/JSONL files: agentflow live <dir>");
1143
+ const valid = config.dirs.filter((d) => existsSync2(d));
1144
+ if (valid.length === 0) {
1145
+ console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1146
+ console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
1067
1147
  process.exit(1);
1068
1148
  }
1149
+ const invalid = config.dirs.filter((d) => !existsSync2(d));
1150
+ if (invalid.length > 0) {
1151
+ console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1152
+ }
1153
+ config.dirs = valid;
1069
1154
  render(config);
1070
1155
  let debounce = null;
1071
- try {
1072
- watch(config.tracesDir, { recursive: config.recursive }, () => {
1073
- if (debounce) clearTimeout(debounce);
1074
- debounce = setTimeout(() => render(config), 500);
1075
- });
1076
- } catch {
1156
+ for (const dir of config.dirs) {
1157
+ try {
1158
+ watch(dir, { recursive: config.recursive }, () => {
1159
+ if (debounce) clearTimeout(debounce);
1160
+ debounce = setTimeout(() => render(config), 500);
1161
+ });
1162
+ } catch {
1163
+ }
1077
1164
  }
1078
1165
  setInterval(() => render(config), config.refreshMs);
1079
1166
  process.on("SIGINT", () => {