@thingd/cli 0.56.0 → 0.58.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":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAUL,MAAM,EACN,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AASrB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,KAAK,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAsCF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAwFF,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA8EjB;AAk6BD,wBAAsB,MAAM,CAC1B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAef;AA4BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,iBAAiB,CAqDxE;AAiDD,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAGlF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrF;AAyFD,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAQA,OAAO,EAUL,MAAM,EACN,KAAK,YAAY,EAClB,MAAM,aAAa,CAAC;AASrB,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAEjD,KAAK,YAAY,GAAG;IAClB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,KAAK,YAAY,GAAG,MAAM,CAAC,cAAc,CAAC;AAE1C,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAsCF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAwFF,wBAAsB,MAAM,CAC1B,IAAI,WAAwB,EAC5B,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA8EjB;AA08BD,wBAAsB,MAAM,CAC1B,OAAO,EAAE,UAAU,EACnB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CAef;AA4BD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,iBAAiB,CAqDxE;AAiDD,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAGlF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrF;AAyFD,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE"}
package/dist/index.js CHANGED
@@ -290,6 +290,10 @@ async function runCommand(context) {
290
290
  await runBackup(context);
291
291
  return;
292
292
  }
293
+ if (command === "completions") {
294
+ await runCompletions(context);
295
+ return;
296
+ }
293
297
  if (command === "db") {
294
298
  const sub = context.parsed.tokens[1];
295
299
  if (!sub) {
@@ -381,6 +385,39 @@ async function runDbIntegrity(context) {
381
385
  }
382
386
  });
383
387
  }
388
+ async function runCompletions(context) {
389
+ const shell = optionalToken(context.parsed, 1) ?? "bash";
390
+ const cmds = [
391
+ "status",
392
+ "search",
393
+ "mcp",
394
+ "cloud",
395
+ "objects",
396
+ "events",
397
+ "collections",
398
+ "streams",
399
+ "queues",
400
+ "links",
401
+ "schema",
402
+ "nlq",
403
+ "aggregate",
404
+ "timeseries",
405
+ "metrics",
406
+ "dashboard",
407
+ "export",
408
+ "import",
409
+ "snapshot",
410
+ "backup",
411
+ "db",
412
+ "completions",
413
+ ];
414
+ const script = shell === "zsh"
415
+ ? `#compdef thingd\ncompdef _thingd thingd\n_thingd() {\n _describe 'thingd commands' ${JSON.stringify(cmds.map((c) => `${c}:thingd ${c}`))}\n}\n`
416
+ : shell === "fish"
417
+ ? `complete -c thingd -f\n${cmds.map((c) => `complete -c thingd -n '__fish_use_subcommand' -a ${c}`).join("\n")}\n`
418
+ : `_thingd() {\n local cur=${"${COMP_WORDS[COMP_CWORD]}"}\n if [ $COMP_CWORD -eq 1 ]; then\n COMPREPLY=($(compgen -W "${cmds.join(" ")}" -- "$cur"))\n fi\n}\ncomplete -F _thingd thingd\n`;
419
+ writeText(context.stdout, script);
420
+ }
384
421
  async function runStatus(context) {
385
422
  const connection = resolveConnection(context);
386
423
  if (!connection.cloud) {
@@ -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":"AAitGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
@@ -92,6 +92,22 @@ 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;
96
+ const loading = false;
97
+ const toasts = [];
98
+ function addToast(msg) {
99
+ toasts.push(msg);
100
+ if (toasts.length > 3) {
101
+ toasts.shift();
102
+ }
103
+ setTimeout(() => {
104
+ const idx = toasts.indexOf(msg);
105
+ if (idx !== -1) {
106
+ toasts.splice(idx, 1);
107
+ draw();
108
+ }
109
+ }, 3000);
110
+ }
95
111
  let lastNeighborsRef = "";
96
112
  let loadedItemId = "";
97
113
  let loadTimer = null;
@@ -915,6 +931,38 @@ async function loadContent(node) {
915
931
  content += ` ${pc.dim("Links".padEnd(14))} ${pc.blue(String(totalLinksCount).padEnd(6))} ${pc.dim("total")}\n`;
916
932
  content += ` ${pc.dim("Active Jobs".padEnd(14))} ${pc.yellow(String(totalActiveJobsCount).padEnd(6))} ${pc.dim("in flight")}\n`;
917
933
  content += ` ${pc.dim("Dead Jobs".padEnd(14))} ${pc.red(String(totalDeadJobsCount).padEnd(6))} ${pc.dim("failed")}\n\n`;
934
+ // Collection breakdown bar chart
935
+ if (collections.length > 0) {
936
+ content += ` ${pc.bold("Collections")}\n`;
937
+ const maxCount = Math.max(1, ...collections.map((c) => (objectsByCollection.get(c) ?? []).length));
938
+ const barMax = Math.max(5, viewW - 30);
939
+ for (const col of collections) {
940
+ const count = (objectsByCollection.get(col) ?? []).length;
941
+ const barLen = Math.max(1, Math.round((count / maxCount) * barMax));
942
+ const bar = barLen > 0 ? pc.cyan("█".repeat(barLen)) : "";
943
+ content += ` ${pc.dim(col.slice(0, 12).padEnd(12))} ${bar} ${pc.dim(String(count))}\n`;
944
+ }
945
+ content += "\n";
946
+ }
947
+ // Capacity gauge bar
948
+ const totalHistorical = totalObjects +
949
+ totalEventsCount +
950
+ totalLinksCount +
951
+ totalActiveJobsCount +
952
+ totalDeadJobsCount;
953
+ if (totalHistorical > 0) {
954
+ const capBarW = Math.max(5, viewW - 20);
955
+ const objFrac = totalObjects / totalHistorical;
956
+ const evtFrac = totalEventsCount / totalHistorical;
957
+ const linkFrac = totalLinksCount / totalHistorical;
958
+ const objChars = Math.round(objFrac * capBarW);
959
+ const evtChars = Math.round(evtFrac * capBarW);
960
+ const linkChars = Math.round(linkFrac * capBarW);
961
+ const jobChars = capBarW - objChars - evtChars - linkChars;
962
+ content += ` ${pc.bold("Distribution")}\n`;
963
+ 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`;
964
+ content += ` ${pc.cyan("■")} objs ${pc.green("■")} evts ${pc.blue("■")} links ${pc.yellow("■")} jobs\n\n`;
965
+ }
918
966
  content += ` ${pc.bold("Connection")}\n`;
919
967
  content += ` ${pc.dim("Driver".padEnd(14))} ${driverName}\n`;
920
968
  content += ` ${pc.dim("Path".padEnd(14))} ${dbPath || ":memory:"}\n`;
@@ -998,14 +1046,20 @@ function draw() {
998
1046
  scrollOffset = Math.max(0, Math.min(scrollOffset, Math.max(0, tree.length - bodyH)));
999
1047
  let buf = "\u001B[H"; // Move to top-left
1000
1048
  // Header — opencode style: clean, no inverse bar
1001
- if (!connected) {
1002
- buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("Select Environment")}\n`;
1049
+ if (showHelp) {
1050
+ buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("Help (press any key)")}\n`;
1051
+ }
1052
+ else if (!connected) {
1053
+ buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("Select Environment")} ${pc.dim("[?] help")}\n`;
1003
1054
  }
1004
1055
  else if (formState?.active) {
1005
- buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())} ${pc.dim("Input Mode")}\n`;
1056
+ buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())} ${pc.dim("Input Mode")} ${pc.dim("[?] help")}\n`;
1006
1057
  }
1007
1058
  else {
1008
- const label = ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())} ${pc.dim(dbPath)}`;
1059
+ const breadcrumb = computeBreadcrumbs();
1060
+ const base = ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.cyan(driver.toUpperCase())}`;
1061
+ const dash = breadcrumb ? ` ${pc.dim("▸")} ` : "";
1062
+ const label = `${base}${dash}${breadcrumb} ${pc.dim("[?] help")}`;
1009
1063
  buf += `${padToWidth(label, W)}\n`;
1010
1064
  }
1011
1065
  buf += `${pc.dim("─".repeat(W))}\n`;
@@ -1091,14 +1145,115 @@ function draw() {
1091
1145
  help = ` ${pc.dim("↑↓")} nav ${pc.dim("enter")} connect ${pc.dim("q")} quit `;
1092
1146
  }
1093
1147
  else {
1094
- help = ` ${pc.dim("↑↓")} nav ${pc.dim("←→")} toggle ${pc.dim("c")} create ${pc.dim("e")} edit ${pc.dim("d")} delete ${pc.dim("/")} search ${pc.dim("n")} neighbors ${pc.dim("N")} nlq ${pc.dim("a")} agg ${pc.dim("t")} ts ${pc.dim("o")} options ${pc.dim("b")} batch ${pc.dim("i")} info ${pc.dim("r")} refresh ${pc.dim("s")} switch ${pc.dim("l")} logout ${pc.dim("q")} quit `;
1148
+ help = ` ${pc.dim("↑↓")} nav ${pc.dim("←→")} toggle ${pc.dim("c")} create ${pc.dim("e")} edit ${pc.dim("d")} delete ${pc.dim("/")} search ${pc.dim("n")} neighbors ${pc.dim("N")} nlq ${pc.dim("a")} agg ${pc.dim("t")} ts ${pc.dim("o")} opts ${pc.dim("b")} batch ${pc.dim("x")} export ${pc.dim("p")} import ${pc.dim("w")} web ${pc.dim("i")} info ${pc.dim("r")} refresh ${pc.dim("s")} switch ${pc.dim("l")} logout ${pc.dim("q")} quit `;
1095
1149
  }
1096
1150
  buf += `${pc.dim("─".repeat(W))}\n`;
1097
1151
  buf += padToWidth(help, W);
1152
+ if (loading) {
1153
+ buf += `\n${pc.cyan("◇")} ${pc.dim("Working...")}`;
1154
+ }
1155
+ if (toasts.length > 0) {
1156
+ for (const t of toasts) {
1157
+ buf += `\n ${pc.green("●")} ${pc.dim(t)}`;
1158
+ }
1159
+ }
1098
1160
  // Clear to end
1099
1161
  buf += "\u001B[J";
1162
+ if (showHelp) {
1163
+ buf += computeHelpOverlay(W, H);
1164
+ }
1100
1165
  process.stdout.write(buf);
1101
1166
  }
1167
+ function computeBreadcrumbs() {
1168
+ if (!connected) {
1169
+ return "";
1170
+ }
1171
+ const tree = buildTree();
1172
+ const node = tree[cursorIndex];
1173
+ if (!node) {
1174
+ return "";
1175
+ }
1176
+ const labels = [];
1177
+ const climb = (id) => {
1178
+ if (!id) {
1179
+ return;
1180
+ }
1181
+ const n = tree.find((t) => t.id === id);
1182
+ if (!n) {
1183
+ return;
1184
+ }
1185
+ if (n.parentId) {
1186
+ climb(n.parentId);
1187
+ }
1188
+ const clean = n.label
1189
+ .replace(/[▾▸●○·◇◈◉⬡]/g, "")
1190
+ .trim()
1191
+ .replace(/^\S+\s+/, "")
1192
+ .trim();
1193
+ if (clean && !clean.startsWith("(")) {
1194
+ labels.push(clean);
1195
+ }
1196
+ };
1197
+ climb(node.id);
1198
+ return labels.length > 0 ? pc.dim(labels.join(" > ")) : "";
1199
+ }
1200
+ function computeHelpOverlay(W, H) {
1201
+ const lines = [
1202
+ `${pc.bold(" thingd TUI Help")}`,
1203
+ "",
1204
+ ` ${pc.bold("Navigation")}`,
1205
+ ` ${pc.dim("↑↓/j/k")} Move cursor`,
1206
+ ` ${pc.dim("←→/h/l")} Expand/collapse tree`,
1207
+ ` ${pc.dim("Enter")} Toggle expand`,
1208
+ "",
1209
+ ` ${pc.bold("Operations")}`,
1210
+ ` ${pc.dim("c")} Create resource`,
1211
+ ` ${pc.dim("e")} Edit object/job`,
1212
+ ` ${pc.dim("d")} Delete object/link/job`,
1213
+ ` ${pc.dim("r")} Refresh data`,
1214
+ ` ${pc.dim("i")} Connection info`,
1215
+ "",
1216
+ ` ${pc.bold("Data Views")}`,
1217
+ ` ${pc.dim("n")} Object neighbors (links)`,
1218
+ ` ${pc.dim("a")} Aggregate (count/sum/avg/min/max)`,
1219
+ ` ${pc.dim("t")} Time-series query`,
1220
+ ` ${pc.dim("N")} Natural language query`,
1221
+ ` ${pc.dim("/f")} Search objects`,
1222
+ ` ${pc.dim("o")} Object listing options`,
1223
+ ` ${pc.dim("b")} Batch put/delete`,
1224
+ "",
1225
+ ` ${pc.bold("System")}`,
1226
+ ` ${pc.dim("s")} Switch driver`,
1227
+ ` ${pc.dim("m")} Maintenance`,
1228
+ ` ${pc.dim("l")} Logout`,
1229
+ ` ${pc.dim("q")} Quit`,
1230
+ ` ${pc.dim("?")} Toggle this help`,
1231
+ "",
1232
+ ` ${pc.dim("Press any key to close help.")}`,
1233
+ ];
1234
+ const helpW = 44;
1235
+ const helpH = lines.length + 2;
1236
+ const col = Math.max(0, Math.floor((W - helpW) / 2));
1237
+ const row = Math.max(0, Math.floor((H - helpH) / 2));
1238
+ let out = "";
1239
+ for (let r = 0; r < H; r++) {
1240
+ if (r >= row && r < row + helpH) {
1241
+ const lineIdx = r - row;
1242
+ if (lineIdx === 0 || lineIdx === helpH - 1) {
1243
+ out += `${" ".repeat(col) + pc.dim(`┌${"─".repeat(helpW - 2)}┐`)}\n`;
1244
+ }
1245
+ else {
1246
+ const text = lines[lineIdx - 1] ?? "";
1247
+ const padded = text + " ".repeat(Math.max(0, helpW - visibleWidth(text) - 2));
1248
+ out += `${" ".repeat(col) + pc.dim("│")} ${padded} ${pc.dim("│")}\n`;
1249
+ }
1250
+ }
1251
+ else {
1252
+ out += "\n";
1253
+ }
1254
+ }
1255
+ return out;
1256
+ }
1102
1257
  /** Pad/truncate `text` to exactly `width` visible characters. */
1103
1258
  function fitToWidth(text, width, highlight) {
1104
1259
  const vw = visibleWidth(text);
@@ -1315,6 +1470,7 @@ async function handleCreate(selected) {
1315
1470
  await db.put(target, { id, ...data });
1316
1471
  expandedSet.add("cat:collections");
1317
1472
  expandedSet.add(`col:${target}`);
1473
+ addToast(`Created object ${id} in ${target}`);
1318
1474
  }
1319
1475
  else if (kind === "event") {
1320
1476
  if (!vals.payload?.trim()) {
@@ -1322,6 +1478,7 @@ async function handleCreate(selected) {
1322
1478
  }
1323
1479
  await db.events.append(target, { type: vals.payload.trim() });
1324
1480
  expandedSet.add("cat:streams");
1481
+ addToast(`Appended event to ${target}`);
1325
1482
  }
1326
1483
  else if (kind === "queue") {
1327
1484
  if (!vals.payload?.trim()) {
@@ -1330,6 +1487,7 @@ async function handleCreate(selected) {
1330
1487
  const data = parsePayload(vals.payload);
1331
1488
  await db.queue(target).push(data);
1332
1489
  expandedSet.add("cat:queues");
1490
+ addToast(`Pushed job to queue ${target}`);
1333
1491
  }
1334
1492
  else if (kind === "link") {
1335
1493
  const toRef = (vals.objId || "").trim();
@@ -1356,6 +1514,7 @@ async function handleCreate(selected) {
1356
1514
  linkType = (vals.payload || "").trim() || "related";
1357
1515
  }
1358
1516
  await db.links.create(target, linkType, toRef, weight, metadataJson);
1517
+ addToast(`Created link: ${target} ${linkType} ${toRef}`);
1359
1518
  }
1360
1519
  else {
1361
1520
  throw new Error("Kind must be 'object', 'event', 'queue', or 'link'.");
@@ -1461,6 +1620,7 @@ async function handleDelete(selected) {
1461
1620
  if (result && !result.deleted) {
1462
1621
  throw new Error(`Object '${ref.id}' not found in collection '${ref.collection}'`);
1463
1622
  }
1623
+ addToast(`Deleted object ${ref.id}`);
1464
1624
  });
1465
1625
  }
1466
1626
  else if (selected.type === "queue" && selected.ref) {
@@ -1505,6 +1665,7 @@ async function handleDelete(selected) {
1505
1665
  if (!ok) {
1506
1666
  throw new Error(`Link '${linkId}' not found.`);
1507
1667
  }
1668
+ addToast(`Deleted link ${linkId}`);
1508
1669
  });
1509
1670
  }
1510
1671
  else if (selected.type === "job" && selected.ref) {
@@ -2021,6 +2182,71 @@ async function handleBatchOps(selected) {
2021
2182
  }
2022
2183
  });
2023
2184
  }
2185
+ async function handleExport(selected) {
2186
+ const colName = selected?.type === "collection"
2187
+ ? (selected.ref?.name ?? "")
2188
+ : selected?.type === "object"
2189
+ ? (selected.ref?.collection ?? "")
2190
+ : "";
2191
+ if (!colName) {
2192
+ viewerLines = [pc.yellow("Select a collection first, then press [x] to export.")];
2193
+ loadedItemId = "export_info";
2194
+ draw();
2195
+ return;
2196
+ }
2197
+ openForm(`Export: ${colName}`, [{ id: "path", label: "Output File Path", placeholder: "/tmp/export.jsonl" }], async (vals) => {
2198
+ const filePath = (vals.path || "").trim();
2199
+ if (!filePath) {
2200
+ throw new Error("File path is required.");
2201
+ }
2202
+ const objs = await db.listObjects(colName);
2203
+ const lines = objs.map((o) => JSON.stringify(o)).join("\n");
2204
+ await fs.promises.writeFile(filePath, lines, "utf-8");
2205
+ addToast(`Exported ${objs.length} objects to ${filePath}`);
2206
+ });
2207
+ }
2208
+ async function handleImport(selected) {
2209
+ const colName = selected?.type === "collection"
2210
+ ? (selected.ref?.name ?? "")
2211
+ : selected?.type === "object"
2212
+ ? (selected.ref?.collection ?? "")
2213
+ : "";
2214
+ if (!colName) {
2215
+ viewerLines = [pc.yellow("Select a collection first, then press [p] to import.")];
2216
+ loadedItemId = "import_info";
2217
+ draw();
2218
+ return;
2219
+ }
2220
+ openForm(`Import: ${colName}`, [{ id: "path", label: "Input File Path (JSONL)", placeholder: "/tmp/export.jsonl" }], async (vals) => {
2221
+ const filePath = (vals.path || "").trim();
2222
+ if (!filePath) {
2223
+ throw new Error("File path is required.");
2224
+ }
2225
+ const text = await fs.promises.readFile(filePath, "utf-8");
2226
+ const objects = text
2227
+ .split("\n")
2228
+ .filter(Boolean)
2229
+ .map((line) => JSON.parse(line));
2230
+ const result = await db.putBatch(colName, objects);
2231
+ addToast(`Imported ${result.length} objects into ${colName}`);
2232
+ });
2233
+ }
2234
+ async function handleDashboard() {
2235
+ const bin = process.argv[1] || "thingd";
2236
+ const args = ["dashboard", "--driver", driver];
2237
+ if (dbPath) {
2238
+ args.push("--path", dbPath);
2239
+ }
2240
+ if (authToken) {
2241
+ args.push("--auth-token", authToken);
2242
+ }
2243
+ const child = spawn(bin, args, {
2244
+ stdio: "inherit",
2245
+ detached: true,
2246
+ });
2247
+ child.unref();
2248
+ addToast(`Launching dashboard: ${bin} dashboard`);
2249
+ }
2024
2250
  async function handleMaintenance() {
2025
2251
  // Cycle through maintenance operations with each press of 'm'
2026
2252
  const operations = ["health", "checkpoint", "backup"];
@@ -2105,6 +2331,12 @@ function setupKeypress() {
2105
2331
  if (!key) {
2106
2332
  return;
2107
2333
  }
2334
+ // Help overlay dismisses on any keypress
2335
+ if (showHelp) {
2336
+ showHelp = false;
2337
+ draw();
2338
+ return;
2339
+ }
2108
2340
  // Quit
2109
2341
  if ((key.ctrl && key.name === "c") || key.name === "q") {
2110
2342
  if (formState?.active && key.name !== "q") {
@@ -2221,13 +2453,17 @@ function setupKeypress() {
2221
2453
  }
2222
2454
  }
2223
2455
  else if (!connected) {
2224
- // Driver selection mode — only Enter works
2456
+ // Driver selection mode — only Enter and ? work
2225
2457
  if (key.name === "return") {
2226
2458
  const node = tree[cursorIndex];
2227
2459
  if (node) {
2228
2460
  await handleConnect(node);
2229
2461
  }
2230
2462
  }
2463
+ else if (str === "?") {
2464
+ showHelp = !showHelp;
2465
+ draw();
2466
+ }
2231
2467
  }
2232
2468
  else {
2233
2469
  // Connected mode — full set of shortcuts
@@ -2313,6 +2549,10 @@ function setupKeypress() {
2313
2549
  else if (str === "/" || str === "f" || str === "F") {
2314
2550
  await handleSearch();
2315
2551
  }
2552
+ else if (str === "?") {
2553
+ showHelp = !showHelp;
2554
+ draw();
2555
+ }
2316
2556
  else if (str === "i" || str === "I") {
2317
2557
  await handleInfo();
2318
2558
  }
@@ -2334,6 +2574,15 @@ function setupKeypress() {
2334
2574
  else if (str === "b" || str === "B") {
2335
2575
  await handleBatchOps(tree[cursorIndex]);
2336
2576
  }
2577
+ else if (str === "x" || str === "X") {
2578
+ await handleExport(tree[cursorIndex]);
2579
+ }
2580
+ else if (str === "p" || str === "P") {
2581
+ await handleImport(tree[cursorIndex]);
2582
+ }
2583
+ else if (str === "w" || str === "W") {
2584
+ await handleDashboard();
2585
+ }
2337
2586
  else if (str === "m" || str === "M") {
2338
2587
  await handleMaintenance();
2339
2588
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thingd/cli",
3
- "version": "0.56.0",
3
+ "version": "0.58.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.58.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24.0.0"