@thingd/cli 0.53.2 → 0.54.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/interactive.d.ts.map +1 -1
- package/dist/interactive.js +155 -9
- package/package.json +2 -2
|
@@ -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":"AAyvEA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
|
package/dist/interactive.js
CHANGED
|
@@ -89,6 +89,7 @@ let objectWriteRateHistory = [];
|
|
|
89
89
|
let eventAppendRateHistory = [];
|
|
90
90
|
let viewerLines = ["Select an item to view details."];
|
|
91
91
|
let viewerScroll = 0;
|
|
92
|
+
let lastNeighborsRef = "";
|
|
92
93
|
let loadedItemId = "";
|
|
93
94
|
let loadTimer = null;
|
|
94
95
|
let pollTimer = null;
|
|
@@ -551,6 +552,14 @@ function buildTree() {
|
|
|
551
552
|
});
|
|
552
553
|
}
|
|
553
554
|
}
|
|
555
|
+
// Links
|
|
556
|
+
nodes.push({
|
|
557
|
+
id: "node:links",
|
|
558
|
+
type: "link",
|
|
559
|
+
label: `${pc.blue("◈")} ${pc.dim("Links")} ${pc.dim(`(${totalLinksCount})`)}`,
|
|
560
|
+
depth: 0,
|
|
561
|
+
expandable: false,
|
|
562
|
+
});
|
|
554
563
|
// Metrics
|
|
555
564
|
nodes.push({
|
|
556
565
|
id: "node:status",
|
|
@@ -742,6 +751,19 @@ async function loadContent(node) {
|
|
|
742
751
|
content += `\n ${pc.yellow("⚠")} ${pc.dim(cloudError)}\n`;
|
|
743
752
|
}
|
|
744
753
|
}
|
|
754
|
+
else if (node.type === "link") {
|
|
755
|
+
content = [
|
|
756
|
+
` ${pc.bold("Links")} ${pc.dim(`(${totalLinksCount} total`)}${lastNeighborsRef ? pc.dim(`, last browsed: ${lastNeighborsRef}`) : ""})`,
|
|
757
|
+
"",
|
|
758
|
+
totalLinksCount === 0
|
|
759
|
+
? ` ${pc.dim("No links yet.")}`
|
|
760
|
+
: ` ${pc.dim("Select an object and press")} ${pc.bold("n")} ${pc.dim("to browse its neighbors.")}`,
|
|
761
|
+
"",
|
|
762
|
+
` ${pc.bold("Operations")}`,
|
|
763
|
+
` ${pc.bold("[c]")} Create a new link`,
|
|
764
|
+
` ${pc.bold("[d]")} Delete a link by ID`,
|
|
765
|
+
].join("\n");
|
|
766
|
+
}
|
|
745
767
|
else if (node.type === "category") {
|
|
746
768
|
content = pc.dim("Expand to browse items.");
|
|
747
769
|
}
|
|
@@ -881,7 +903,7 @@ function draw() {
|
|
|
881
903
|
help = ` ${pc.dim("↑↓")} nav ${pc.dim("enter")} connect ${pc.dim("q")} quit `;
|
|
882
904
|
}
|
|
883
905
|
else {
|
|
884
|
-
help = ` ${pc.dim("↑↓")} nav ${pc.dim("←→")} toggle ${pc.dim("c")} create ${pc.dim("e")} edit ${pc.dim("d")} delete ${pc.dim("/")} search ${pc.dim("i")} info ${pc.dim("r")} refresh ${pc.dim("s")} switch ${pc.dim("l")} logout ${pc.dim("q")} quit `;
|
|
906
|
+
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("i")} info ${pc.dim("r")} refresh ${pc.dim("s")} switch ${pc.dim("l")} logout ${pc.dim("q")} quit `;
|
|
885
907
|
}
|
|
886
908
|
buf += `${pc.dim("─".repeat(W))}\n`;
|
|
887
909
|
buf += padToWidth(help, W);
|
|
@@ -1058,23 +1080,33 @@ async function handleCreate(selected) {
|
|
|
1058
1080
|
openForm("Create Resource", [
|
|
1059
1081
|
{
|
|
1060
1082
|
id: "kind",
|
|
1061
|
-
label: "Kind (object, event, queue)",
|
|
1062
|
-
value: defaultStream
|
|
1063
|
-
|
|
1083
|
+
label: "Kind (object, event, queue, link)",
|
|
1084
|
+
value: defaultStream
|
|
1085
|
+
? "event"
|
|
1086
|
+
: defaultQueue
|
|
1087
|
+
? "queue"
|
|
1088
|
+
: selected?.type === "link"
|
|
1089
|
+
? "link"
|
|
1090
|
+
: "object",
|
|
1091
|
+
options: ["object", "event", "queue", "link"],
|
|
1064
1092
|
},
|
|
1065
1093
|
{
|
|
1066
1094
|
id: "target",
|
|
1067
|
-
label: "Target (Collection, Stream, or
|
|
1095
|
+
label: "Target (Collection, Stream, Queue, or From Reference)",
|
|
1068
1096
|
value: defaultCol || defaultStream || defaultQueue,
|
|
1069
1097
|
options: Array.from(new Set([...collections, ...streams, ...queues])).sort(),
|
|
1070
1098
|
allowCustom: true,
|
|
1071
1099
|
},
|
|
1072
1100
|
{
|
|
1073
1101
|
id: "objId",
|
|
1074
|
-
label: "Object
|
|
1102
|
+
label: "Object / To Reference ID (auto if blank for objects)",
|
|
1075
1103
|
placeholder: "Leave blank to auto-generate",
|
|
1076
1104
|
},
|
|
1077
|
-
{
|
|
1105
|
+
{
|
|
1106
|
+
id: "payload",
|
|
1107
|
+
label: "Data, Link Type, or JSON Fields",
|
|
1108
|
+
placeholder: 'e.g. name="John" age=30 or {"linkType":"follows","weight":1}',
|
|
1109
|
+
},
|
|
1078
1110
|
], async (vals) => {
|
|
1079
1111
|
const kind = (vals.kind || "").toLowerCase();
|
|
1080
1112
|
const target = (vals.target || "").trim();
|
|
@@ -1111,8 +1143,34 @@ async function handleCreate(selected) {
|
|
|
1111
1143
|
await db.queue(target).push(data);
|
|
1112
1144
|
expandedSet.add("cat:queues");
|
|
1113
1145
|
}
|
|
1146
|
+
else if (kind === "link") {
|
|
1147
|
+
const toRef = (vals.objId || "").trim();
|
|
1148
|
+
if (!toRef) {
|
|
1149
|
+
throw new Error("To Reference is required (use Object ID field).");
|
|
1150
|
+
}
|
|
1151
|
+
let linkType = "related";
|
|
1152
|
+
let weight;
|
|
1153
|
+
let metadataJson;
|
|
1154
|
+
try {
|
|
1155
|
+
const parsed = JSON.parse(vals.payload || "{}");
|
|
1156
|
+
linkType = parsed.linkType || parsed.link_type || "related";
|
|
1157
|
+
if (parsed.weight !== undefined) {
|
|
1158
|
+
weight = Number(parsed.weight);
|
|
1159
|
+
}
|
|
1160
|
+
if (parsed.metadata || parsed.metadataJson) {
|
|
1161
|
+
metadataJson =
|
|
1162
|
+
typeof parsed.metadata === "string"
|
|
1163
|
+
? parsed.metadata
|
|
1164
|
+
: JSON.stringify(parsed.metadata);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
catch {
|
|
1168
|
+
linkType = (vals.payload || "").trim() || "related";
|
|
1169
|
+
}
|
|
1170
|
+
await db.links.create(target, linkType, toRef, weight, metadataJson);
|
|
1171
|
+
}
|
|
1114
1172
|
else {
|
|
1115
|
-
throw new Error("Kind must be 'object', 'event', or '
|
|
1173
|
+
throw new Error("Kind must be 'object', 'event', 'queue', or 'link'.");
|
|
1116
1174
|
}
|
|
1117
1175
|
});
|
|
1118
1176
|
}
|
|
@@ -1204,8 +1262,36 @@ async function handleDelete(selected) {
|
|
|
1204
1262
|
}
|
|
1205
1263
|
});
|
|
1206
1264
|
}
|
|
1265
|
+
else if (selected.type === "link" || loadedItemId === "neighbors_result") {
|
|
1266
|
+
openForm("Delete Link", [
|
|
1267
|
+
{
|
|
1268
|
+
id: "linkId",
|
|
1269
|
+
label: "Link ID",
|
|
1270
|
+
placeholder: "Paste the link ID from the neighbors view",
|
|
1271
|
+
},
|
|
1272
|
+
{ id: "confirm", label: 'Type "yes" to confirm deletion', placeholder: "yes" },
|
|
1273
|
+
], async (vals) => {
|
|
1274
|
+
const linkId = (vals.linkId || "").trim();
|
|
1275
|
+
if (!linkId) {
|
|
1276
|
+
throw new Error("Link ID is required.");
|
|
1277
|
+
}
|
|
1278
|
+
if ((vals.confirm || "").toLowerCase() !== "yes") {
|
|
1279
|
+
throw new Error("Canceled");
|
|
1280
|
+
}
|
|
1281
|
+
const ok = await db.links.delete(linkId);
|
|
1282
|
+
if (!ok) {
|
|
1283
|
+
throw new Error(`Link '${linkId}' not found.`);
|
|
1284
|
+
}
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1207
1287
|
else {
|
|
1208
|
-
openForm("Delete Not Supported", [
|
|
1288
|
+
openForm("Delete Not Supported", [
|
|
1289
|
+
{
|
|
1290
|
+
id: "msg",
|
|
1291
|
+
label: "Error",
|
|
1292
|
+
value: "Deletion is only available for Objects, Links, and Queues.",
|
|
1293
|
+
},
|
|
1294
|
+
], async () => { });
|
|
1209
1295
|
}
|
|
1210
1296
|
}
|
|
1211
1297
|
async function handleSearch() {
|
|
@@ -1318,6 +1404,63 @@ async function handleInfo() {
|
|
|
1318
1404
|
viewerLines = lines;
|
|
1319
1405
|
loadedItemId = "info_status";
|
|
1320
1406
|
}
|
|
1407
|
+
async function handleNeighbors(selected) {
|
|
1408
|
+
if (selected?.type !== "object" || !selected.ref) {
|
|
1409
|
+
viewerLines = [
|
|
1410
|
+
` ${pc.yellow("Neighbors")}`,
|
|
1411
|
+
"",
|
|
1412
|
+
` ${pc.dim("Select an object first, then press")} ${pc.bold("n")} ${pc.dim("to browse its links.")}`,
|
|
1413
|
+
];
|
|
1414
|
+
loadedItemId = "neighbors_info";
|
|
1415
|
+
draw();
|
|
1416
|
+
return;
|
|
1417
|
+
}
|
|
1418
|
+
const ref = selected.ref;
|
|
1419
|
+
const fromRef = `${ref.collection}/${ref.id}`;
|
|
1420
|
+
openForm(`Neighbors of ${fromRef}`, [
|
|
1421
|
+
{
|
|
1422
|
+
id: "direction",
|
|
1423
|
+
label: "Direction",
|
|
1424
|
+
value: "Both",
|
|
1425
|
+
options: ["Both", "Outgoing", "Incoming"],
|
|
1426
|
+
},
|
|
1427
|
+
{
|
|
1428
|
+
id: "linkType",
|
|
1429
|
+
label: "Link Type (optional, leave blank for all)",
|
|
1430
|
+
placeholder: "e.g. follows, owns",
|
|
1431
|
+
},
|
|
1432
|
+
{
|
|
1433
|
+
id: "limit",
|
|
1434
|
+
label: "Max Results (optional)",
|
|
1435
|
+
placeholder: "50",
|
|
1436
|
+
},
|
|
1437
|
+
], async (vals) => {
|
|
1438
|
+
const direction = (vals.direction || "Both");
|
|
1439
|
+
const linkType = (vals.linkType || "").trim() || undefined;
|
|
1440
|
+
const limitStr = vals.limit || "";
|
|
1441
|
+
const limit = limitStr ? parseInt(limitStr, 10) || undefined : undefined;
|
|
1442
|
+
const links = await db.links.neighbors(fromRef, direction, { linkType, limit });
|
|
1443
|
+
lastNeighborsRef = fromRef;
|
|
1444
|
+
viewerLines = [
|
|
1445
|
+
` ${pc.bold("Neighbors of")} ${pc.cyan(fromRef)}`,
|
|
1446
|
+
` ${pc.dim(`(${links.length} link${links.length !== 1 ? "s" : ""}${direction !== "Both" ? `, ${direction.toLowerCase()}` : ""}${linkType ? `, type: ${linkType}` : ""})`)}`,
|
|
1447
|
+
"",
|
|
1448
|
+
...(links.length === 0 ? [` ${pc.dim("No links found.")}`] : []),
|
|
1449
|
+
...links.flatMap((link) => [
|
|
1450
|
+
` ${pc.blue("◈")} ${pc.dim(link.id)}`,
|
|
1451
|
+
` ${link.fromRef} ${pc.cyan(link.linkType)} ${link.toRef}`,
|
|
1452
|
+
link.weight !== undefined ? ` ${pc.dim(`weight: ${link.weight}`)}` : "",
|
|
1453
|
+
link.metadataJson && link.metadataJson !== "{}"
|
|
1454
|
+
? ` ${pc.dim(`metadata: ${link.metadataJson}`)}`
|
|
1455
|
+
: "",
|
|
1456
|
+
"",
|
|
1457
|
+
]),
|
|
1458
|
+
pc.dim("[d] Delete a link by ID [c] Create a new link"),
|
|
1459
|
+
];
|
|
1460
|
+
loadedItemId = "neighbors_result";
|
|
1461
|
+
draw();
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1321
1464
|
async function handleMaintenance() {
|
|
1322
1465
|
// Cycle through maintenance operations with each press of 'm'
|
|
1323
1466
|
const operations = ["health", "checkpoint", "backup"];
|
|
@@ -1613,6 +1756,9 @@ function setupKeypress() {
|
|
|
1613
1756
|
else if (str === "i" || str === "I") {
|
|
1614
1757
|
await handleInfo();
|
|
1615
1758
|
}
|
|
1759
|
+
else if (str === "n" || str === "N") {
|
|
1760
|
+
await handleNeighbors(tree[cursorIndex]);
|
|
1761
|
+
}
|
|
1616
1762
|
else if (str === "m" || str === "M") {
|
|
1617
1763
|
await handleMaintenance();
|
|
1618
1764
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thingd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.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.54.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.0.0"
|