@thingd/cli 0.58.2 → 0.59.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AAitGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AAoxGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
@@ -69,6 +69,7 @@ let collections = [];
69
69
  let streams = [];
70
70
  let queues = [];
71
71
  let objectsByCollection = new Map();
72
+ const collectionCounts = new Map();
72
73
  const collectionOptions = new Map();
73
74
  const expandedSet = new Set(["cat:collections", "cat:streams", "cat:queues"]);
74
75
  let cursorIndex = 0;
@@ -217,6 +218,35 @@ function formatUptime(ms) {
217
218
  const h = Math.floor(m / 60);
218
219
  return `${h}h ${m % 60}m`;
219
220
  }
221
+ function formatRelativeTime(iso) {
222
+ const ms = Date.now() - new Date(iso).getTime();
223
+ const s = Math.floor(ms / 1000);
224
+ if (s < 0) {
225
+ return "now";
226
+ }
227
+ if (s < 5) {
228
+ return "now";
229
+ }
230
+ if (s < 60) {
231
+ return `${s}s ago`;
232
+ }
233
+ const m = Math.floor(s / 60);
234
+ if (m < 60) {
235
+ return `${m}m ago`;
236
+ }
237
+ const h = Math.floor(m / 60);
238
+ if (h < 24) {
239
+ return `${h}h ago`;
240
+ }
241
+ const d = Math.floor(h / 24);
242
+ return `${d}d ago`;
243
+ }
244
+ function formatCount(n) {
245
+ if (n >= 1000) {
246
+ return `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k`;
247
+ }
248
+ return String(n);
249
+ }
220
250
  async function fetchResourcesFallback() {
221
251
  cloudError = null;
222
252
  // Collections and streams — parallel fetch
@@ -276,7 +306,7 @@ async function fetchResourcesFallback() {
276
306
  listOpts.filter = opts.filter;
277
307
  }
278
308
  const list = await db.listObjects(col, Object.keys(listOpts).length > 0 ? listOpts : undefined);
279
- objectsByCollection.set(col, list.map((o) => o.id));
309
+ objectsByCollection.set(col, list.map((o) => ({ id: o.id, createdAt: o.createdAt })));
280
310
  }
281
311
  catch {
282
312
  objectsByCollection.set(col, []);
@@ -335,7 +365,7 @@ async function fetchResources() {
335
365
  listOpts.filter = opts.filter;
336
366
  }
337
367
  const list = await db.listObjects(col, Object.keys(listOpts).length > 0 ? listOpts : undefined);
338
- objectsByCollection.set(col, list.map((o) => o.id));
368
+ objectsByCollection.set(col, list.map((o) => ({ id: o.id, createdAt: o.createdAt })));
339
369
  }
340
370
  catch {
341
371
  objectsByCollection.set(col, []);
@@ -364,6 +394,20 @@ async function fetchResources() {
364
394
  jobsByQueue.set(q, { active: [], dead: [] });
365
395
  }
366
396
  }));
397
+ // Fetch per-collection counts from schema
398
+ collectionCounts.clear();
399
+ try {
400
+ const schemas = await db.schema();
401
+ for (const s of schemas) {
402
+ collectionCounts.set(s.name, s.objectCount);
403
+ }
404
+ }
405
+ catch {
406
+ // Fallback — count from objectsByCollection
407
+ for (const col of collections) {
408
+ collectionCounts.set(col, objectsByCollection.get(col)?.length ?? 0);
409
+ }
410
+ }
367
411
  // Calculate Deltas for Operations Throughput Rates
368
412
  const prevObjects = objectsHistory.length > 0
369
413
  ? (objectsHistory[objectsHistory.length - 1] ?? totalObjects)
@@ -525,11 +569,13 @@ function buildTree() {
525
569
  for (const col of collections) {
526
570
  const colId = `col:${col}`;
527
571
  const colOpen = expandedSet.has(colId);
572
+ const colCount = collectionCounts.get(col);
573
+ const colSuffix = colCount !== undefined ? pc.dim(` ${formatCount(colCount)}`) : "";
528
574
  nodes.push({
529
575
  id: colId,
530
576
  parentId: "cat:collections",
531
577
  type: "collection",
532
- label: `${colOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.cyan(col)}`,
578
+ label: `${colOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.cyan(col)}${colSuffix}`,
533
579
  depth: 1,
534
580
  expandable: true,
535
581
  ref: { name: col },
@@ -546,15 +592,16 @@ function buildTree() {
546
592
  expandable: false,
547
593
  });
548
594
  }
549
- for (const objId of objs) {
595
+ for (const objData of objs) {
596
+ const objAge = objData.createdAt ? pc.dim(formatRelativeTime(objData.createdAt)) : "";
550
597
  nodes.push({
551
- id: `obj:${col}:${objId}`,
598
+ id: `obj:${col}:${objData.id}`,
552
599
  parentId: colId,
553
600
  type: "object",
554
- label: `${pc.cyan("○")} ${objId}`,
601
+ label: `${pc.cyan("○")} ${objData.id} ${objAge}`,
555
602
  depth: 2,
556
603
  expandable: false,
557
- ref: { collection: col, id: objId },
604
+ ref: { collection: col, id: objData.id },
558
605
  });
559
606
  }
560
607
  }
@@ -581,11 +628,13 @@ function buildTree() {
581
628
  }
582
629
  for (const stream of streams) {
583
630
  const sOpen = expandedSet.has(`stream:${stream}`);
631
+ const evtCount = eventsByStream.get(stream)?.length ?? 0;
632
+ const evtSuffix = evtCount > 0 ? pc.dim(` ${formatCount(evtCount)}`) : "";
584
633
  nodes.push({
585
634
  id: `stream:${stream}`,
586
635
  parentId: "cat:streams",
587
636
  type: "stream",
588
- label: `${sOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.green(stream)}`,
637
+ label: `${sOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.green(stream)}${evtSuffix}`,
589
638
  depth: 1,
590
639
  expandable: true,
591
640
  ref: { name: stream },
@@ -607,7 +656,7 @@ function buildTree() {
607
656
  id: `evt:${stream}:${evt.id}`,
608
657
  parentId: `stream:${stream}`,
609
658
  type: "event",
610
- label: `${pc.dim("·")} ${pc.dim(evt.type || "unknown")}`,
659
+ label: `${pc.dim("·")} ${pc.dim(evt.type || "unknown")} ${pc.dim(formatRelativeTime(evt.createdAt))}`,
611
660
  depth: 2,
612
661
  expandable: false,
613
662
  ref: { stream: stream, eventId: evt.id, eventData: evt },
@@ -637,11 +686,17 @@ function buildTree() {
637
686
  }
638
687
  for (const q of queues) {
639
688
  const qOpen = expandedSet.has(`queue:${q}`);
689
+ const qJobData = jobsByQueue.get(q);
690
+ const qActive = qJobData?.active.length ?? 0;
691
+ const qDead = qJobData?.dead.length ?? 0;
692
+ const qSuffix = qActive > 0 || qDead > 0
693
+ ? ` ${qActive > 0 ? pc.cyan(String(qActive)) : pc.dim("0")}${pc.cyan(" ●")} ${qDead > 0 ? pc.red(String(qDead)) : pc.dim("0")}${pc.red(" ○")}`
694
+ : "";
640
695
  nodes.push({
641
696
  id: `queue:${q}`,
642
697
  parentId: "cat:queues",
643
698
  type: "queue",
644
- label: `${qOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.magenta(q)}`,
699
+ label: `${qOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.magenta(q)}${qSuffix}`,
645
700
  depth: 1,
646
701
  expandable: true,
647
702
  ref: { name: q },
@@ -672,11 +727,13 @@ function buildTree() {
672
727
  });
673
728
  }
674
729
  for (const job of activeJobs) {
730
+ const jobStatus = pc.cyan(job.status ?? "ready");
731
+ const jobAttempts = pc.dim(`${job.attempts}/${job.maxAttempts}`);
675
732
  nodes.push({
676
733
  id: `job:${q}:active:${job.id}`,
677
734
  parentId: `queue:${q}:active`,
678
735
  type: "job",
679
- label: `${pc.cyan("●")} ${pc.dim(job.id.slice(0, 12))}`,
736
+ label: `${pc.cyan("●")} ${pc.dim(job.id.slice(0, 12))} ${jobAttempts} ${jobStatus}`,
680
737
  depth: 3,
681
738
  expandable: false,
682
739
  ref: { queue: q, jobId: job.id, status: "active", jobData: job },
@@ -705,11 +762,13 @@ function buildTree() {
705
762
  });
706
763
  }
707
764
  for (const job of deadJobs) {
765
+ const jobAttempts = pc.dim(`${job.attempts}/${job.maxAttempts}`);
766
+ const lastErr = job.lastError ? pc.dim(job.lastError.slice(0, 20)) : "";
708
767
  nodes.push({
709
768
  id: `job:${q}:dead:${job.id}`,
710
769
  parentId: `queue:${q}:dead`,
711
770
  type: "job",
712
- label: `${pc.red("○")} ${pc.dim(job.id.slice(0, 12))}`,
771
+ label: `${pc.red("○")} ${pc.dim(job.id.slice(0, 12))} ${jobAttempts}${lastErr ? ` ${pc.red("⚠")} ${lastErr}` : ""}`,
713
772
  depth: 3,
714
773
  expandable: false,
715
774
  ref: { queue: q, jobId: job.id, status: "dead", jobData: job },
@@ -828,7 +887,7 @@ async function loadContent(node) {
828
887
  res += pc.dim("No objects in this collection.");
829
888
  }
830
889
  else {
831
- const lines = objs.map((id) => ` ${pc.cyan("○")} ${id}`);
890
+ const lines = objs.map((o) => ` ${pc.cyan("○")} ${o.id}${o.createdAt ? ` ${pc.dim(formatRelativeTime(o.createdAt))}` : ""}`);
832
891
  res += lines.join("\n");
833
892
  }
834
893
  content = res;
@@ -2843,6 +2902,7 @@ async function handleSwitch() {
2843
2902
  streams = [];
2844
2903
  queues = [];
2845
2904
  objectsByCollection = new Map();
2905
+ collectionCounts.clear();
2846
2906
  cursorIndex = 0;
2847
2907
  scrollOffset = 0;
2848
2908
  loadedItemId = "";
@@ -2876,6 +2936,7 @@ async function handleLogout() {
2876
2936
  streams = [];
2877
2937
  queues = [];
2878
2938
  objectsByCollection = new Map();
2939
+ collectionCounts.clear();
2879
2940
  cursorIndex = 0;
2880
2941
  scrollOffset = 0;
2881
2942
  loadedItemId = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thingd/cli",
3
- "version": "0.58.2",
3
+ "version": "0.59.0",
4
4
  "description": "CLI, Interactive TUI Dashboard, and MCP server for thingd — a fast object-first data engine for applications and AI agents.",
5
5
  "type": "module",
6
6
  "homepage": "https://engine.thingd.cloud",
@@ -45,7 +45,7 @@
45
45
  "cli-table3": "^0.6.5",
46
46
  "picocolors": "^1.1.1",
47
47
  "zod": "^4.4.3",
48
- "@thingd/sdk": "0.58.2"
48
+ "@thingd/sdk": "0.59.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24.0.0"