@thingd/cli 0.56.0 → 0.57.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":"AAu8FA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AA8lGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
@@ -92,6 +92,7 @@ let objectWriteRateHistory = [];
92
92
  let eventAppendRateHistory = [];
93
93
  let viewerLines = ["Select an item to view details."];
94
94
  let viewerScroll = 0;
95
+ let showHelp = false;
95
96
  let lastNeighborsRef = "";
96
97
  let loadedItemId = "";
97
98
  let loadTimer = null;
@@ -915,6 +916,38 @@ async function loadContent(node) {
915
916
  content += ` ${pc.dim("Links".padEnd(14))} ${pc.blue(String(totalLinksCount).padEnd(6))} ${pc.dim("total")}\n`;
916
917
  content += ` ${pc.dim("Active Jobs".padEnd(14))} ${pc.yellow(String(totalActiveJobsCount).padEnd(6))} ${pc.dim("in flight")}\n`;
917
918
  content += ` ${pc.dim("Dead Jobs".padEnd(14))} ${pc.red(String(totalDeadJobsCount).padEnd(6))} ${pc.dim("failed")}\n\n`;
919
+ // Collection breakdown bar chart
920
+ if (collections.length > 0) {
921
+ content += ` ${pc.bold("Collections")}\n`;
922
+ const maxCount = Math.max(1, ...collections.map((c) => (objectsByCollection.get(c) ?? []).length));
923
+ const barMax = Math.max(5, viewW - 30);
924
+ for (const col of collections) {
925
+ const count = (objectsByCollection.get(col) ?? []).length;
926
+ const barLen = Math.max(1, Math.round((count / maxCount) * barMax));
927
+ const bar = barLen > 0 ? pc.cyan("█".repeat(barLen)) : "";
928
+ content += ` ${pc.dim(col.slice(0, 12).padEnd(12))} ${bar} ${pc.dim(String(count))}\n`;
929
+ }
930
+ content += "\n";
931
+ }
932
+ // Capacity gauge bar
933
+ const totalHistorical = totalObjects +
934
+ totalEventsCount +
935
+ totalLinksCount +
936
+ totalActiveJobsCount +
937
+ totalDeadJobsCount;
938
+ if (totalHistorical > 0) {
939
+ const capBarW = Math.max(5, viewW - 20);
940
+ const objFrac = totalObjects / totalHistorical;
941
+ const evtFrac = totalEventsCount / totalHistorical;
942
+ const linkFrac = totalLinksCount / totalHistorical;
943
+ const objChars = Math.round(objFrac * capBarW);
944
+ const evtChars = Math.round(evtFrac * capBarW);
945
+ const linkChars = Math.round(linkFrac * capBarW);
946
+ const jobChars = capBarW - objChars - evtChars - linkChars;
947
+ content += ` ${pc.bold("Distribution")}\n`;
948
+ content += ` ${pc.dim(" ")}${pc.cyan("█".repeat(Math.max(0, objChars)))}${pc.green("█".repeat(Math.max(0, evtChars)))}${pc.blue("█".repeat(Math.max(0, linkChars)))}${pc.yellow("█".repeat(Math.max(0, jobChars)))}\n`;
949
+ content += ` ${pc.cyan("■")} objs ${pc.green("■")} evts ${pc.blue("■")} links ${pc.yellow("■")} jobs\n\n`;
950
+ }
918
951
  content += ` ${pc.bold("Connection")}\n`;
919
952
  content += ` ${pc.dim("Driver".padEnd(14))} ${driverName}\n`;
920
953
  content += ` ${pc.dim("Path".padEnd(14))} ${dbPath || ":memory:"}\n`;
@@ -998,14 +1031,20 @@ function draw() {
998
1031
  scrollOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, tree.length - bodyH)));
999
1032
  let buf = "\u001B[H"; // Move to top-left
1000
1033
  // Header — opencode style: clean, no inverse bar
1001
- if (!connected) {
1002
- buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("Select Environment")}\n`;
1034
+ if (showHelp) {
1035
+ buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("Help (press any key)")}\n`;
1036
+ }
1037
+ else if (!connected) {
1038
+ buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("Select Environment")} ${pc.dim("[?] help")}\n`;
1003
1039
  }
1004
1040
  else if (formState?.active) {
1005
- buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())} ${pc.dim("Input Mode")}\n`;
1041
+ buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())} ${pc.dim("Input Mode")} ${pc.dim("[?] help")}\n`;
1006
1042
  }
1007
1043
  else {
1008
- const label = ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())} ${pc.dim(dbPath)}`;
1044
+ const breadcrumb = computeBreadcrumbs();
1045
+ const base = ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())}`;
1046
+ const dash = breadcrumb ? ` ${pc.dim("▸")} ` : "";
1047
+ const label = `${base}${dash}${breadcrumb} ${pc.dim("[?] help")}`;
1009
1048
  buf += `${padToWidth(label, W)}\n`;
1010
1049
  }
1011
1050
  buf += `${pc.dim("─".repeat(W))}\n`;
@@ -1097,8 +1136,101 @@ function draw() {
1097
1136
  buf += padToWidth(help, W);
1098
1137
  // Clear to end
1099
1138
  buf += "\u001B[J";
1139
+ if (showHelp) {
1140
+ buf += computeHelpOverlay(W, H);
1141
+ }
1100
1142
  process.stdout.write(buf);
1101
1143
  }
1144
+ function computeBreadcrumbs() {
1145
+ if (!connected) {
1146
+ return "";
1147
+ }
1148
+ const tree = buildTree();
1149
+ const node = tree[cursorIndex];
1150
+ if (!node) {
1151
+ return "";
1152
+ }
1153
+ const labels = [];
1154
+ const climb = (id) => {
1155
+ if (!id) {
1156
+ return;
1157
+ }
1158
+ const n = tree.find((t) => t.id === id);
1159
+ if (!n) {
1160
+ return;
1161
+ }
1162
+ if (n.parentId) {
1163
+ climb(n.parentId);
1164
+ }
1165
+ const clean = n.label
1166
+ .replace(/[▾▸●○·◇◈◉⬡]/g, "")
1167
+ .trim()
1168
+ .replace(/^\S+\s+/, "")
1169
+ .trim();
1170
+ if (clean && !clean.startsWith("(")) {
1171
+ labels.push(clean);
1172
+ }
1173
+ };
1174
+ climb(node.id);
1175
+ return labels.length > 0 ? pc.dim(labels.join(" > ")) : "";
1176
+ }
1177
+ function computeHelpOverlay(W, H) {
1178
+ const lines = [
1179
+ `${pc.bold(" thingd TUI Help")}`,
1180
+ "",
1181
+ ` ${pc.bold("Navigation")}`,
1182
+ ` ${pc.dim("↑↓/j/k")} Move cursor`,
1183
+ ` ${pc.dim("←→/h/l")} Expand/collapse tree`,
1184
+ ` ${pc.dim("Enter")} Toggle expand`,
1185
+ "",
1186
+ ` ${pc.bold("Operations")}`,
1187
+ ` ${pc.dim("c")} Create resource`,
1188
+ ` ${pc.dim("e")} Edit object/job`,
1189
+ ` ${pc.dim("d")} Delete object/link/job`,
1190
+ ` ${pc.dim("r")} Refresh data`,
1191
+ ` ${pc.dim("i")} Connection info`,
1192
+ "",
1193
+ ` ${pc.bold("Data Views")}`,
1194
+ ` ${pc.dim("n")} Object neighbors (links)`,
1195
+ ` ${pc.dim("a")} Aggregate (count/sum/avg/min/max)`,
1196
+ ` ${pc.dim("t")} Time-series query`,
1197
+ ` ${pc.dim("N")} Natural language query`,
1198
+ ` ${pc.dim("/f")} Search objects`,
1199
+ ` ${pc.dim("o")} Object listing options`,
1200
+ ` ${pc.dim("b")} Batch put/delete`,
1201
+ "",
1202
+ ` ${pc.bold("System")}`,
1203
+ ` ${pc.dim("s")} Switch driver`,
1204
+ ` ${pc.dim("m")} Maintenance`,
1205
+ ` ${pc.dim("l")} Logout`,
1206
+ ` ${pc.dim("q")} Quit`,
1207
+ ` ${pc.dim("?")} Toggle this help`,
1208
+ "",
1209
+ ` ${pc.dim("Press any key to close help.")}`,
1210
+ ];
1211
+ const helpW = 44;
1212
+ const helpH = lines.length + 2;
1213
+ const col = Math.max(0, Math.floor((W - helpW) / 2));
1214
+ const row = Math.max(0, Math.floor((H - helpH) / 2));
1215
+ let out = "";
1216
+ for (let r = 0; r < H; r++) {
1217
+ if (r >= row && r < row + helpH) {
1218
+ const lineIdx = r - row;
1219
+ if (lineIdx === 0 || lineIdx === helpH - 1) {
1220
+ out += `${" ".repeat(col) + pc.dim(`┌${"─".repeat(helpW - 2)}┐`)}\n`;
1221
+ }
1222
+ else {
1223
+ const text = lines[lineIdx - 1] ?? "";
1224
+ const padded = text + " ".repeat(Math.max(0, helpW - visibleWidth(text) - 2));
1225
+ out += `${" ".repeat(col) + pc.dim("│")} ${padded} ${pc.dim("│")}\n`;
1226
+ }
1227
+ }
1228
+ else {
1229
+ out += "\n";
1230
+ }
1231
+ }
1232
+ return out;
1233
+ }
1102
1234
  /** Pad/truncate `text` to exactly `width` visible characters. */
1103
1235
  function fitToWidth(text, width, highlight) {
1104
1236
  const vw = visibleWidth(text);
@@ -2105,6 +2237,12 @@ function setupKeypress() {
2105
2237
  if (!key) {
2106
2238
  return;
2107
2239
  }
2240
+ // Help overlay dismisses on any keypress
2241
+ if (showHelp) {
2242
+ showHelp = false;
2243
+ draw();
2244
+ return;
2245
+ }
2108
2246
  // Quit
2109
2247
  if ((key.ctrl && key.name === "c") || key.name === "q") {
2110
2248
  if (formState?.active && key.name !== "q") {
@@ -2221,13 +2359,17 @@ function setupKeypress() {
2221
2359
  }
2222
2360
  }
2223
2361
  else if (!connected) {
2224
- // Driver selection mode — only Enter works
2362
+ // Driver selection mode — only Enter and ? work
2225
2363
  if (key.name === "return") {
2226
2364
  const node = tree[cursorIndex];
2227
2365
  if (node) {
2228
2366
  await handleConnect(node);
2229
2367
  }
2230
2368
  }
2369
+ else if (str === "?") {
2370
+ showHelp = !showHelp;
2371
+ draw();
2372
+ }
2231
2373
  }
2232
2374
  else {
2233
2375
  // Connected mode — full set of shortcuts
@@ -2313,6 +2455,10 @@ function setupKeypress() {
2313
2455
  else if (str === "/" || str === "f" || str === "F") {
2314
2456
  await handleSearch();
2315
2457
  }
2458
+ else if (str === "?") {
2459
+ showHelp = !showHelp;
2460
+ draw();
2461
+ }
2316
2462
  else if (str === "i" || str === "I") {
2317
2463
  await handleInfo();
2318
2464
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thingd/cli",
3
- "version": "0.56.0",
3
+ "version": "0.57.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.56.0"
48
+ "@thingd/sdk": "0.57.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24.0.0"