agentflow-core 0.2.0 → 0.2.2

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.cjs CHANGED
@@ -587,10 +587,7 @@ var C = {
587
587
  white: "\x1B[37m"
588
588
  };
589
589
  function parseArgs(argv) {
590
- const config = {
591
- tracesDir: "./traces",
592
- refreshMs: 3e3
593
- };
590
+ const config = { dirs: [], refreshMs: 3e3, recursive: false };
594
591
  const args = argv.slice(0);
595
592
  if (args[0] === "live") args.shift();
596
593
  let i = 0;
@@ -601,138 +598,276 @@ function parseArgs(argv) {
601
598
  process.exit(0);
602
599
  } else if (arg === "--refresh" || arg === "-r") {
603
600
  i++;
604
- const val = parseInt(args[i] ?? "", 10);
605
- if (!isNaN(val) && val > 0) config.refreshMs = val * 1e3;
601
+ const v = parseInt(args[i] ?? "", 10);
602
+ if (!isNaN(v) && v > 0) config.refreshMs = v * 1e3;
606
603
  i++;
607
- } else if (arg === "--traces-dir" || arg === "-t") {
608
- i++;
609
- config.tracesDir = args[i] ?? config.tracesDir;
604
+ } else if (arg === "--recursive" || arg === "-R") {
605
+ config.recursive = true;
610
606
  i++;
611
607
  } else if (!arg.startsWith("-")) {
612
- config.tracesDir = arg;
608
+ config.dirs.push((0, import_node_path2.resolve)(arg));
613
609
  i++;
614
610
  } else {
615
611
  i++;
616
612
  }
617
613
  }
618
- config.tracesDir = (0, import_node_path2.resolve)(config.tracesDir);
614
+ if (config.dirs.length === 0) config.dirs.push((0, import_node_path2.resolve)("."));
619
615
  return config;
620
616
  }
621
617
  function printUsage() {
622
618
  console.log(`
623
619
  AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
624
620
 
621
+ Auto-detects agent traces, state files, job schedulers, and session logs
622
+ from any JSON/JSONL files in the watched directories.
623
+
625
624
  Usage:
626
- agentflow live [traces-dir] [options]
625
+ agentflow live [dir...] [options]
627
626
 
628
627
  Arguments:
629
- traces-dir Path to the traces directory (default: ./traces)
628
+ dir One or more directories to watch (default: .)
630
629
 
631
630
  Options:
632
631
  -r, --refresh <secs> Refresh interval in seconds (default: 3)
633
- -t, --traces-dir <path> Explicit traces directory path
632
+ -R, --recursive Scan subdirectories (1 level deep)
634
633
  -h, --help Show this help message
635
634
 
636
635
  Examples:
637
- agentflow live
638
- agentflow live ./my-traces --refresh 5
639
- agentflow live /var/log/agentflow/traces -r 10
636
+ agentflow live ./data
637
+ agentflow live ./traces ./cron ./workers --refresh 5
638
+ agentflow live /var/lib/myagent -R
640
639
  `.trim());
641
640
  }
642
- function listTraceFiles(tracesDir) {
643
- try {
644
- return (0, import_node_fs2.readdirSync)(tracesDir).filter((f) => f.endsWith(".json")).map((f) => {
645
- const fp = (0, import_node_path2.join)(tracesDir, f);
646
- const stat = (0, import_node_fs2.statSync)(fp);
647
- return { filename: f, path: fp, mtime: stat.mtime.getTime() };
648
- }).sort((a, b) => b.mtime - a.mtime);
649
- } catch {
650
- return [];
641
+ function scanFiles(dirs, recursive) {
642
+ const results = [];
643
+ const seen = /* @__PURE__ */ new Set();
644
+ function scanDir(d, topLevel) {
645
+ try {
646
+ for (const f of (0, import_node_fs2.readdirSync)(d)) {
647
+ if (f.startsWith(".")) continue;
648
+ const fp = (0, import_node_path2.join)(d, f);
649
+ if (seen.has(fp)) continue;
650
+ let stat;
651
+ try {
652
+ stat = (0, import_node_fs2.statSync)(fp);
653
+ } catch {
654
+ continue;
655
+ }
656
+ if (stat.isDirectory() && recursive && topLevel) {
657
+ scanDir(fp, false);
658
+ continue;
659
+ }
660
+ if (!stat.isFile()) continue;
661
+ if (f.endsWith(".json")) {
662
+ seen.add(fp);
663
+ results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".json" });
664
+ } else if (f.endsWith(".jsonl")) {
665
+ seen.add(fp);
666
+ results.push({ filename: f, path: fp, mtime: stat.mtime.getTime(), ext: ".jsonl" });
667
+ }
668
+ }
669
+ } catch {
670
+ }
651
671
  }
672
+ for (const dir of dirs) scanDir(dir, true);
673
+ results.sort((a, b) => b.mtime - a.mtime);
674
+ return results;
652
675
  }
653
- function safeLoadTrace(fp) {
676
+ function safeReadJson(fp) {
654
677
  try {
655
- return loadGraph((0, import_node_fs2.readFileSync)(fp, "utf8"));
678
+ return JSON.parse((0, import_node_fs2.readFileSync)(fp, "utf8"));
656
679
  } catch {
657
680
  return null;
658
681
  }
659
682
  }
660
- function analyze(trace) {
683
+ function nameFromFile(filename) {
684
+ return (0, import_node_path2.basename)(filename).replace(/\.(json|jsonl)$/, "").replace(/-state$/, "");
685
+ }
686
+ function normalizeStatus(val) {
687
+ if (typeof val !== "string") return "unknown";
688
+ const s = val.toLowerCase();
689
+ if (["ok", "success", "completed", "done", "passed", "healthy", "good"].includes(s)) return "ok";
690
+ if (["error", "failed", "failure", "crashed", "unhealthy", "bad", "timeout"].includes(s)) return "error";
691
+ if (["running", "active", "in_progress", "started", "pending", "processing"].includes(s)) return "running";
692
+ return "unknown";
693
+ }
694
+ function findStatus(obj) {
695
+ for (const key of ["status", "state", "lastRunStatus", "lastStatus", "health", "result"]) {
696
+ if (key in obj) {
697
+ const val = obj[key];
698
+ if (typeof val === "string") return normalizeStatus(val);
699
+ if (typeof val === "object" && val !== null && "status" in val) {
700
+ return normalizeStatus(val.status);
701
+ }
702
+ }
703
+ }
704
+ return "unknown";
705
+ }
706
+ function findTimestamp(obj) {
707
+ for (const key of ["ts", "timestamp", "lastRunAtMs", "last_run", "lastExecution", "updated_at", "started_at", "endTime", "startTime"]) {
708
+ const val = obj[key];
709
+ if (typeof val === "number") return val > 1e12 ? val : val * 1e3;
710
+ if (typeof val === "string") {
711
+ const d = Date.parse(val);
712
+ if (!isNaN(d)) return d;
713
+ }
714
+ }
715
+ return 0;
716
+ }
717
+ function extractDetail(obj) {
718
+ const parts = [];
719
+ for (const key of ["summary", "message", "description", "lastError", "error", "name", "jobId", "id"]) {
720
+ const val = obj[key];
721
+ if (typeof val === "string" && val.length > 0 && val.length < 200) {
722
+ parts.push(val.slice(0, 80));
723
+ break;
724
+ }
725
+ }
726
+ for (const key of ["totalExecutions", "runs", "count", "processed", "consecutiveErrors"]) {
727
+ const val = obj[key];
728
+ if (typeof val === "number") {
729
+ parts.push(`${key}: ${val}`);
730
+ break;
731
+ }
732
+ }
733
+ return parts.join(" | ") || "";
734
+ }
735
+ function tryLoadTrace(fp, raw) {
736
+ if (typeof raw !== "object" || raw === null) return null;
737
+ const obj = raw;
738
+ if (!("nodes" in obj)) return null;
739
+ if (!("agentId" in obj) && !("rootNodeId" in obj) && !("rootId" in obj)) return null;
661
740
  try {
662
- const stats = getStats(trace);
663
- const fails = getFailures(trace);
664
- const hung = getHungNodes(trace);
665
- return {
666
- agentId: trace.agentId,
667
- trigger: trace.trigger,
668
- traceId: trace.traceId,
669
- spanId: trace.spanId,
670
- parentSpanId: trace.parentSpanId,
671
- nodes: stats.totalNodes,
672
- success: fails.length === 0 && hung.length === 0,
673
- failures: fails.length,
674
- hung: hung.length
675
- };
741
+ return loadGraph(obj);
676
742
  } catch {
677
743
  return null;
678
744
  }
679
745
  }
680
- function getDistributedDepth(dt, spanId) {
681
- if (!spanId) return 0;
682
- const graph = dt.graphs.get(spanId);
683
- if (!graph || !graph.parentSpanId) return 0;
684
- return 1 + getDistributedDepth(dt, graph.parentSpanId);
746
+ function processJsonFile(file) {
747
+ const raw = safeReadJson(file.path);
748
+ if (raw === null) return [];
749
+ const records = [];
750
+ const trace = tryLoadTrace(file.path, raw);
751
+ if (trace) {
752
+ try {
753
+ const fails = getFailures(trace);
754
+ const hung = getHungNodes(trace);
755
+ const stats = getStats(trace);
756
+ records.push({
757
+ id: trace.agentId,
758
+ source: "trace",
759
+ status: fails.length === 0 && hung.length === 0 ? "ok" : "error",
760
+ lastActive: file.mtime,
761
+ detail: `${stats.totalNodes} nodes [${trace.trigger}]`,
762
+ file: file.filename,
763
+ traceData: trace
764
+ });
765
+ } catch {
766
+ }
767
+ return records;
768
+ }
769
+ if (typeof raw !== "object") return records;
770
+ const arr = Array.isArray(raw) ? raw : Array.isArray(raw.jobs) ? raw.jobs : Array.isArray(raw.tasks) ? raw.tasks : Array.isArray(raw.items) ? raw.items : null;
771
+ if (arr && arr.length > 0 && typeof arr[0] === "object" && arr[0] !== null) {
772
+ for (const item of arr.slice(0, 50)) {
773
+ const name = item.name ?? item.id ?? item.jobId ?? item.agentId;
774
+ if (!name) continue;
775
+ const state = typeof item.state === "object" && item.state !== null ? item.state : item;
776
+ const status2 = findStatus(state);
777
+ const ts2 = findTimestamp(state) || file.mtime;
778
+ const detail2 = extractDetail(state);
779
+ records.push({ id: String(name), source: "jobs", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
780
+ }
781
+ return records;
782
+ }
783
+ const obj = raw;
784
+ for (const containerKey of ["tools", "workers", "services", "agents", "daemons"]) {
785
+ const container = obj[containerKey];
786
+ if (typeof container === "object" && container !== null && !Array.isArray(container)) {
787
+ for (const [name, info] of Object.entries(container)) {
788
+ if (typeof info !== "object" || info === null) continue;
789
+ const w = info;
790
+ const status2 = findStatus(w);
791
+ const ts2 = findTimestamp(w) || findTimestamp(obj) || file.mtime;
792
+ const pid = w.pid;
793
+ const detail2 = pid ? `pid: ${pid}` : extractDetail(w);
794
+ records.push({ id: name, source: "workers", status: status2, lastActive: ts2, detail: detail2, file: file.filename });
795
+ }
796
+ return records;
797
+ }
798
+ }
799
+ const status = findStatus(obj);
800
+ const ts = findTimestamp(obj) || file.mtime;
801
+ const detail = extractDetail(obj);
802
+ records.push({ id: nameFromFile(file.filename), source: "state", status, lastActive: ts, detail, file: file.filename });
803
+ return records;
804
+ }
805
+ function processJsonlFile(file) {
806
+ try {
807
+ const content = (0, import_node_fs2.readFileSync)(file.path, "utf8").trim();
808
+ if (!content) return [];
809
+ const lines = content.split("\n");
810
+ const lastLine = lines[lines.length - 1];
811
+ const obj = JSON.parse(lastLine);
812
+ const name = obj.jobId ?? obj.agentId ?? obj.name ?? obj.id ?? nameFromFile(file.filename);
813
+ const status = findStatus(obj);
814
+ const ts = findTimestamp(obj) || file.mtime;
815
+ const action = obj.action;
816
+ const detail = action ? `${action} (${lines.length} entries)` : `${lines.length} entries`;
817
+ return [{ id: String(name), source: "session", status, lastActive: ts, detail, file: file.filename }];
818
+ } catch {
819
+ return [];
820
+ }
685
821
  }
686
822
  var prevFileCount = 0;
687
823
  var newExecCount = 0;
688
824
  var sessionStart = Date.now();
689
825
  function render(config) {
690
- const files = listTraceFiles(config.tracesDir);
826
+ const files = scanFiles(config.dirs, config.recursive);
691
827
  if (files.length > prevFileCount && prevFileCount > 0) {
692
828
  newExecCount += files.length - prevFileCount;
693
829
  }
694
830
  prevFileCount = files.length;
831
+ const allRecords = [];
695
832
  const allTraces = [];
833
+ for (const f of files.slice(0, 300)) {
834
+ const records = f.ext === ".jsonl" ? processJsonlFile(f) : processJsonFile(f);
835
+ for (const r of records) {
836
+ allRecords.push(r);
837
+ if (r.traceData) allTraces.push(r.traceData);
838
+ }
839
+ }
696
840
  const agents = {};
697
- for (const f of files.slice(0, 200)) {
698
- const trace = safeLoadTrace(f.path);
699
- if (!trace) continue;
700
- allTraces.push(trace);
701
- const a = analyze(trace);
702
- if (!a) continue;
703
- if (!agents[a.agentId]) {
704
- agents[a.agentId] = { name: a.agentId, total: 0, ok: 0, fail: 0, lastTs: 0 };
841
+ for (const r of allRecords) {
842
+ if (!agents[r.id]) {
843
+ agents[r.id] = { name: r.id, total: 0, ok: 0, fail: 0, running: 0, lastTs: 0, source: r.source, detail: "" };
705
844
  }
706
- const ag = agents[a.agentId];
845
+ const ag = agents[r.id];
707
846
  ag.total++;
708
- a.success ? ag.ok++ : ag.fail++;
709
- if (f.mtime > ag.lastTs) ag.lastTs = f.mtime;
847
+ if (r.status === "ok") ag.ok++;
848
+ else if (r.status === "error") ag.fail++;
849
+ else if (r.status === "running") ag.running++;
850
+ if (r.lastActive > ag.lastTs) {
851
+ ag.lastTs = r.lastActive;
852
+ ag.detail = r.detail;
853
+ ag.source = r.source;
854
+ }
710
855
  }
711
- const agentList = Object.values(agents).sort((a, b) => b.total - a.total);
856
+ const agentList = Object.values(agents).sort((a, b) => b.lastTs - a.lastTs);
712
857
  const totExec = agentList.reduce((s, a) => s + a.total, 0);
713
858
  const totFail = agentList.reduce((s, a) => s + a.fail, 0);
859
+ const totRunning = agentList.reduce((s, a) => s + a.running, 0);
714
860
  const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
715
- const recent = [];
716
- for (const f of files.slice(0, 15)) {
717
- const trace = safeLoadTrace(f.path);
718
- if (!trace) continue;
719
- const a = analyze(trace);
720
- if (a) recent.push({ ...a, ts: f.mtime });
721
- }
722
861
  const now = Date.now();
723
862
  const buckets = new Array(12).fill(0);
724
863
  const failBuckets = new Array(12).fill(0);
725
- for (const f of files) {
726
- const age = now - f.mtime;
727
- if (age > 36e5) continue;
864
+ for (const r of allRecords) {
865
+ const age = now - r.lastActive;
866
+ if (age > 36e5 || age < 0) continue;
728
867
  const idx = 11 - Math.floor(age / 3e5);
729
868
  if (idx >= 0 && idx < 12) {
730
- const trace = safeLoadTrace(f.path);
731
- if (!trace) continue;
732
- const a = analyze(trace);
733
- if (!a) continue;
734
869
  buckets[idx]++;
735
- if (!a.success) failBuckets[idx]++;
870
+ if (r.status === "error") failBuckets[idx]++;
736
871
  }
737
872
  }
738
873
  const maxBucket = Math.max(...buckets, 1);
@@ -741,107 +876,153 @@ function render(config) {
741
876
  const level = Math.round(v / maxBucket * 8);
742
877
  return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
743
878
  }).join("");
744
- const traceGroups = groupByTraceId(allTraces);
745
879
  const distributedTraces = [];
746
- for (const [_traceId, graphs] of traceGroups) {
747
- if (graphs.length > 1) {
748
- try {
749
- distributedTraces.push(stitchTrace(graphs));
750
- } catch {
880
+ if (allTraces.length > 1) {
881
+ const traceGroups = groupByTraceId(allTraces);
882
+ for (const [_tid, graphs] of traceGroups) {
883
+ if (graphs.length > 1) {
884
+ try {
885
+ distributedTraces.push(stitchTrace(graphs));
886
+ } catch {
887
+ }
751
888
  }
752
889
  }
890
+ distributedTraces.sort((a, b) => b.startTime - a.startTime);
753
891
  }
754
- distributedTraces.sort((a, b) => b.startTime - a.startTime);
755
892
  const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
756
893
  const upMin = Math.floor(upSec / 60);
757
894
  const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
758
895
  const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
896
+ const sourceTag = (s) => {
897
+ switch (s) {
898
+ case "trace":
899
+ return `${C.cyan}trace${C.reset}`;
900
+ case "jobs":
901
+ return `${C.blue}job${C.reset}`;
902
+ case "workers":
903
+ return `${C.magenta}worker${C.reset}`;
904
+ case "session":
905
+ return `${C.yellow}session${C.reset}`;
906
+ case "state":
907
+ return `${C.dim}state${C.reset}`;
908
+ }
909
+ };
759
910
  process.stdout.write("\x1B[2J\x1B[H");
760
- console.log(`${C.bold}${C.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${C.reset}`);
911
+ console.log(`${C.bold}${C.cyan}\u2554${"\u2550".repeat(70)}\u2557${C.reset}`);
761
912
  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}`);
762
- const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr}`;
913
+ const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr} \xB7 Files: ${files.length}`;
763
914
  const pad1 = Math.max(0, 64 - metaLine.length);
764
915
  console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
765
- console.log(`${C.bold}${C.cyan}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${C.reset}`);
916
+ console.log(`${C.bold}${C.cyan}\u255A${"\u2550".repeat(70)}\u255D${C.reset}`);
766
917
  const sc = totFail === 0 ? C.green : C.yellow;
767
918
  console.log("");
768
- console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}Executions${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Traces${C.reset} ${sc}${files.length}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset} ${C.bold}Distributed${C.reset} ${C.magenta}${distributedTraces.length}${C.reset}`);
919
+ 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}`);
769
920
  console.log("");
770
921
  console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
771
922
  console.log("");
772
- console.log(` ${C.bold}${C.under}Agent Runs OK Fail Rate Last Active${C.reset}`);
773
- for (const ag of agentList) {
774
- const rate = (ag.ok / ag.total * 100).toFixed(0);
775
- const lastTime = new Date(ag.lastTs).toLocaleTimeString();
923
+ console.log(` ${C.bold}${C.under}Agent Type Status Last Active Detail${C.reset}`);
924
+ for (const ag of agentList.slice(0, 30)) {
925
+ const lastTime = ag.lastTs > 0 ? new Date(ag.lastTs).toLocaleTimeString() : "n/a";
776
926
  const isRecent = Date.now() - ag.lastTs < 3e5;
777
- let status;
778
- if (ag.fail > 0) status = `${C.red}\u25CF${C.reset}`;
779
- else if (isRecent) status = `${C.green}\u25CF${C.reset}`;
780
- else status = `${C.dim}\u25CB${C.reset}`;
781
- const name = ag.name.padEnd(28);
782
- const runs = String(ag.total).padStart(5);
783
- const ok = String(ag.ok).padStart(5);
784
- const fail = ag.fail > 0 ? `${C.red}${String(ag.fail).padStart(4)}${C.reset}` : String(ag.fail).padStart(4);
785
- const rateStr = (rate + "%").padStart(5);
786
- const activeStr = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
787
- console.log(` ${status} ${name}${runs}${ok}${fail} ${rateStr} ${activeStr}`);
927
+ let statusIcon;
928
+ let statusText;
929
+ if (ag.fail > 0 && ag.ok === 0 && ag.running === 0) {
930
+ statusIcon = `${C.red}\u25CF${C.reset}`;
931
+ statusText = `${C.red}error${C.reset}`;
932
+ } else if (ag.running > 0) {
933
+ statusIcon = `${C.green}\u25CF${C.reset}`;
934
+ statusText = `${C.green}running${C.reset}`;
935
+ } else if (ag.fail > 0) {
936
+ statusIcon = `${C.yellow}\u25CF${C.reset}`;
937
+ statusText = `${C.yellow}${ag.ok}ok/${ag.fail}err${C.reset}`;
938
+ } else if (ag.ok > 0) {
939
+ statusIcon = isRecent ? `${C.green}\u25CF${C.reset}` : `${C.dim}\u25CB${C.reset}`;
940
+ statusText = ag.total > 1 ? `${C.green}${ag.ok}/${ag.total}${C.reset}` : `${C.green}ok${C.reset}`;
941
+ } else {
942
+ statusIcon = `${C.dim}\u25CB${C.reset}`;
943
+ statusText = `${C.dim}idle${C.reset}`;
944
+ }
945
+ const name = ag.name.length > 23 ? ag.name.slice(0, 22) + "\u2026" : ag.name.padEnd(23);
946
+ const src = sourceTag(ag.source).padEnd(16);
947
+ const active = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
948
+ const detail = ag.detail.length > 30 ? ag.detail.slice(0, 29) + "\u2026" : ag.detail;
949
+ console.log(` ${statusIcon} ${name} ${src} ${statusText.padEnd(18)} ${active.padEnd(20)} ${C.dim}${detail}${C.reset}`);
788
950
  }
789
951
  if (distributedTraces.length > 0) {
790
952
  console.log("");
791
- console.log(` ${C.bold}${C.under}Distributed Traces (multi-agent workflows)${C.reset}`);
792
- for (const dt of distributedTraces.slice(0, 5)) {
953
+ console.log(` ${C.bold}${C.under}Distributed Traces${C.reset}`);
954
+ for (const dt of distributedTraces.slice(0, 3)) {
793
955
  const traceTime = new Date(dt.startTime).toLocaleTimeString();
794
956
  const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
795
957
  const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
796
958
  const tid = dt.traceId.slice(0, 8);
797
- console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime}${C.reset} ${C.dim}${dur}${C.reset} ${C.dim}(${dt.graphs.size} agents)${C.reset}`);
959
+ console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime} ${dur} (${dt.graphs.size} agents)${C.reset}`);
798
960
  const tree = getTraceTree(dt);
799
- for (let i = 0; i < tree.length; i++) {
961
+ for (let i = 0; i < Math.min(tree.length, 6); i++) {
800
962
  const g = tree[i];
801
- const depth = getDistributedDepth(dt, g.spanId);
963
+ const depth = getDistDepth(dt, g.spanId);
802
964
  const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
803
- const isLast = i === tree.length - 1 || getDistributedDepth(dt, tree[i + 1]?.spanId) <= depth;
804
- const connector = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
805
- const gStatus = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
806
- const gDur = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
807
- console.log(`${indent}${connector}${gStatus} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gDur}${C.reset}`);
965
+ const isLast = i === tree.length - 1 || getDistDepth(dt, tree[i + 1]?.spanId) <= depth;
966
+ const conn = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
967
+ const gs = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
968
+ const gd = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
969
+ console.log(`${indent}${conn}${gs} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gd}${C.reset}`);
808
970
  }
809
971
  }
810
972
  }
811
- console.log("");
812
- console.log(` ${C.bold}${C.under}Recent Executions${C.reset}`);
813
- for (const ex of recent.slice(0, 8)) {
814
- const icon = ex.success ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
815
- const t = new Date(ex.ts).toLocaleTimeString();
816
- const agent = ex.agentId.padEnd(28);
817
- const age = Math.floor((Date.now() - ex.ts) / 1e3);
818
- const ageStr = age < 60 ? age + "s ago" : Math.floor(age / 60) + "m ago";
819
- const traceTag = ex.traceId ? ` ${C.magenta}\u29EB${C.reset}` : "";
820
- console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)} ${ex.nodes} nodes${C.reset}${traceTag}`);
973
+ const recentRecords = allRecords.filter((r) => r.lastActive > 0).sort((a, b) => b.lastActive - a.lastActive).slice(0, 8);
974
+ if (recentRecords.length > 0) {
975
+ console.log("");
976
+ console.log(` ${C.bold}${C.under}Recent Activity${C.reset}`);
977
+ for (const r of recentRecords) {
978
+ 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}`;
979
+ const t = new Date(r.lastActive).toLocaleTimeString();
980
+ const agent = r.id.length > 26 ? r.id.slice(0, 25) + "\u2026" : r.id.padEnd(26);
981
+ const age = Math.floor((Date.now() - r.lastActive) / 1e3);
982
+ const ageStr = age < 60 ? age + "s ago" : age < 3600 ? Math.floor(age / 60) + "m ago" : Math.floor(age / 3600) + "h ago";
983
+ const detail = r.detail.length > 25 ? r.detail.slice(0, 24) + "\u2026" : r.detail;
984
+ console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)}${C.reset} ${C.dim}${detail}${C.reset}`);
985
+ }
821
986
  }
822
987
  if (files.length === 0) {
823
- console.log(` ${C.dim}No trace files found. Waiting for traces in:${C.reset}`);
824
- console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
988
+ console.log("");
989
+ console.log(` ${C.dim}No JSON/JSONL files found. Waiting for data in:${C.reset}`);
990
+ for (const d of config.dirs) console.log(` ${C.dim} ${d}${C.reset}`);
825
991
  }
826
992
  console.log("");
827
- console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
993
+ const dirLabel = config.dirs.length === 1 ? config.dirs[0] : `${config.dirs.length} directories`;
994
+ console.log(` ${C.dim}Watching: ${dirLabel}${C.reset}`);
828
995
  console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
829
996
  }
997
+ function getDistDepth(dt, spanId) {
998
+ if (!spanId) return 0;
999
+ const g = dt.graphs.get(spanId);
1000
+ if (!g || !g.parentSpanId) return 0;
1001
+ return 1 + getDistDepth(dt, g.parentSpanId);
1002
+ }
830
1003
  function startLive(argv) {
831
1004
  const config = parseArgs(argv);
832
- if (!(0, import_node_fs2.existsSync)(config.tracesDir)) {
833
- console.error(`Traces directory does not exist: ${config.tracesDir}`);
834
- console.error("Create it or specify a different path: agentflow live <traces-dir>");
1005
+ const valid = config.dirs.filter((d) => (0, import_node_fs2.existsSync)(d));
1006
+ if (valid.length === 0) {
1007
+ console.error(`No valid directories found: ${config.dirs.join(", ")}`);
1008
+ console.error("Specify directories containing JSON/JSONL files: agentflow live <dir> [dir...]");
835
1009
  process.exit(1);
836
1010
  }
1011
+ const invalid = config.dirs.filter((d) => !(0, import_node_fs2.existsSync)(d));
1012
+ if (invalid.length > 0) {
1013
+ console.warn(`Skipping non-existent: ${invalid.join(", ")}`);
1014
+ }
1015
+ config.dirs = valid;
837
1016
  render(config);
838
1017
  let debounce = null;
839
- try {
840
- (0, import_node_fs2.watch)(config.tracesDir, () => {
841
- if (debounce) clearTimeout(debounce);
842
- debounce = setTimeout(() => render(config), 500);
843
- });
844
- } catch {
1018
+ for (const dir of config.dirs) {
1019
+ try {
1020
+ (0, import_node_fs2.watch)(dir, { recursive: config.recursive }, () => {
1021
+ if (debounce) clearTimeout(debounce);
1022
+ debounce = setTimeout(() => render(config), 500);
1023
+ });
1024
+ } catch {
1025
+ }
845
1026
  }
846
1027
  setInterval(() => render(config), config.refreshMs);
847
1028
  process.on("SIGINT", () => {
@@ -859,15 +1040,16 @@ Usage:
859
1040
  agentflow <command> [options]
860
1041
 
861
1042
  Commands:
862
- run [options] -- <cmd> Wrap a command with automatic execution tracing
863
- live [traces-dir] [options] Real-time terminal monitor for trace files
1043
+ run [options] -- <cmd> Wrap a command with automatic execution tracing
1044
+ live [dir...] [options] Real-time terminal monitor (auto-detects any JSON/JSONL)
864
1045
 
865
1046
  Run \`agentflow <command> --help\` for command-specific options.
866
1047
 
867
1048
  Examples:
868
1049
  agentflow run --traces-dir ./traces -- python -m myagent process
869
- agentflow live ./traces
870
- agentflow live ./traces --refresh 5
1050
+ agentflow live ./data
1051
+ agentflow live ./traces ./cron ./workers -R
1052
+ agentflow live ./data --refresh 5
871
1053
  `.trim());
872
1054
  }
873
1055
  function parseRunArgs(argv) {
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  runTraced,
4
4
  startLive
5
- } from "./chunk-TRKBUPIN.js";
5
+ } from "./chunk-WOJEID7V.js";
6
6
 
7
7
  // src/cli.ts
8
8
  import { basename } from "path";
@@ -14,15 +14,16 @@ Usage:
14
14
  agentflow <command> [options]
15
15
 
16
16
  Commands:
17
- run [options] -- <cmd> Wrap a command with automatic execution tracing
18
- live [traces-dir] [options] Real-time terminal monitor for trace files
17
+ run [options] -- <cmd> Wrap a command with automatic execution tracing
18
+ live [dir...] [options] Real-time terminal monitor (auto-detects any JSON/JSONL)
19
19
 
20
20
  Run \`agentflow <command> --help\` for command-specific options.
21
21
 
22
22
  Examples:
23
23
  agentflow run --traces-dir ./traces -- python -m myagent process
24
- agentflow live ./traces
25
- agentflow live ./traces --refresh 5
24
+ agentflow live ./data
25
+ agentflow live ./traces ./cron ./workers -R
26
+ agentflow live ./data --refresh 5
26
27
  `.trim());
27
28
  }
28
29
  function parseRunArgs(argv) {