@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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +255 -6
- package/package.json +2 -2
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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":"
|
|
1
|
+
{"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AAitGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
|
package/dist/interactive.js
CHANGED
|
@@ -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 (
|
|
1002
|
-
buf += ` ${pc.cyan("◈")} ${pc.bold("thingd")} ${pc.dim("
|
|
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
|
|
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")}
|
|
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
|
|
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.
|
|
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.
|
|
48
|
+
"@thingd/sdk": "0.58.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.0.0"
|