@thingd/cli 0.53.1 → 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 +161 -10
- 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",
|
|
@@ -588,7 +597,12 @@ function scheduleLoad(node) {
|
|
|
588
597
|
? ` Ephemeral in-memory database.\n All data is destroyed on exit.\n\n ${pc.dim("Best for: testing, prototyping")}\n\n ${pc.dim("Press")} ${pc.bold("Enter")} ${pc.dim("to connect.")}`
|
|
589
598
|
: d === "native"
|
|
590
599
|
? ` Persistent SQLite database.\n Data is stored on disk.\n\n ${pc.dim("Best for: local development, single-node")}\n\n ${pc.dim("Press")} ${pc.bold("Enter")} ${pc.dim("to connect.")}`
|
|
591
|
-
:
|
|
600
|
+
: (() => {
|
|
601
|
+
const hasCfg = !!readCloudConfig()?.token;
|
|
602
|
+
return hasCfg
|
|
603
|
+
? ` Connect to a remote thingd instance.\n Requires a URL and optional auth token.\n\n ${pc.dim("Best for: production, multi-node")}\n\n ${pc.dim("Press")} ${pc.bold("Enter")} ${pc.dim("to connect.")}`
|
|
604
|
+
: ` ${pc.yellow("Not logged in to thingd Cloud.")}\n\n Run ${pc.cyan("thingd cloud login")} to authenticate, or\n press ${pc.bold("Enter")} to connect with a URL and token manually.`;
|
|
605
|
+
})(),
|
|
592
606
|
].join("\n");
|
|
593
607
|
viewerLines = info.split("\n");
|
|
594
608
|
loadedItemId = node.id;
|
|
@@ -737,6 +751,19 @@ async function loadContent(node) {
|
|
|
737
751
|
content += `\n ${pc.yellow("⚠")} ${pc.dim(cloudError)}\n`;
|
|
738
752
|
}
|
|
739
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
|
+
}
|
|
740
767
|
else if (node.type === "category") {
|
|
741
768
|
content = pc.dim("Expand to browse items.");
|
|
742
769
|
}
|
|
@@ -876,7 +903,7 @@ function draw() {
|
|
|
876
903
|
help = ` ${pc.dim("↑↓")} nav ${pc.dim("enter")} connect ${pc.dim("q")} quit `;
|
|
877
904
|
}
|
|
878
905
|
else {
|
|
879
|
-
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 `;
|
|
880
907
|
}
|
|
881
908
|
buf += `${pc.dim("─".repeat(W))}\n`;
|
|
882
909
|
buf += padToWidth(help, W);
|
|
@@ -1053,23 +1080,33 @@ async function handleCreate(selected) {
|
|
|
1053
1080
|
openForm("Create Resource", [
|
|
1054
1081
|
{
|
|
1055
1082
|
id: "kind",
|
|
1056
|
-
label: "Kind (object, event, queue)",
|
|
1057
|
-
value: defaultStream
|
|
1058
|
-
|
|
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"],
|
|
1059
1092
|
},
|
|
1060
1093
|
{
|
|
1061
1094
|
id: "target",
|
|
1062
|
-
label: "Target (Collection, Stream, or
|
|
1095
|
+
label: "Target (Collection, Stream, Queue, or From Reference)",
|
|
1063
1096
|
value: defaultCol || defaultStream || defaultQueue,
|
|
1064
1097
|
options: Array.from(new Set([...collections, ...streams, ...queues])).sort(),
|
|
1065
1098
|
allowCustom: true,
|
|
1066
1099
|
},
|
|
1067
1100
|
{
|
|
1068
1101
|
id: "objId",
|
|
1069
|
-
label: "Object
|
|
1102
|
+
label: "Object / To Reference ID (auto if blank for objects)",
|
|
1070
1103
|
placeholder: "Leave blank to auto-generate",
|
|
1071
1104
|
},
|
|
1072
|
-
{
|
|
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
|
+
},
|
|
1073
1110
|
], async (vals) => {
|
|
1074
1111
|
const kind = (vals.kind || "").toLowerCase();
|
|
1075
1112
|
const target = (vals.target || "").trim();
|
|
@@ -1106,8 +1143,34 @@ async function handleCreate(selected) {
|
|
|
1106
1143
|
await db.queue(target).push(data);
|
|
1107
1144
|
expandedSet.add("cat:queues");
|
|
1108
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
|
+
}
|
|
1109
1172
|
else {
|
|
1110
|
-
throw new Error("Kind must be 'object', 'event', or '
|
|
1173
|
+
throw new Error("Kind must be 'object', 'event', 'queue', or 'link'.");
|
|
1111
1174
|
}
|
|
1112
1175
|
});
|
|
1113
1176
|
}
|
|
@@ -1199,8 +1262,36 @@ async function handleDelete(selected) {
|
|
|
1199
1262
|
}
|
|
1200
1263
|
});
|
|
1201
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
|
+
}
|
|
1202
1287
|
else {
|
|
1203
|
-
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 () => { });
|
|
1204
1295
|
}
|
|
1205
1296
|
}
|
|
1206
1297
|
async function handleSearch() {
|
|
@@ -1313,6 +1404,63 @@ async function handleInfo() {
|
|
|
1313
1404
|
viewerLines = lines;
|
|
1314
1405
|
loadedItemId = "info_status";
|
|
1315
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
|
+
}
|
|
1316
1464
|
async function handleMaintenance() {
|
|
1317
1465
|
// Cycle through maintenance operations with each press of 'm'
|
|
1318
1466
|
const operations = ["health", "checkpoint", "backup"];
|
|
@@ -1608,6 +1756,9 @@ function setupKeypress() {
|
|
|
1608
1756
|
else if (str === "i" || str === "I") {
|
|
1609
1757
|
await handleInfo();
|
|
1610
1758
|
}
|
|
1759
|
+
else if (str === "n" || str === "N") {
|
|
1760
|
+
await handleNeighbors(tree[cursorIndex]);
|
|
1761
|
+
}
|
|
1611
1762
|
else if (str === "m" || str === "M") {
|
|
1612
1763
|
await handleMaintenance();
|
|
1613
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"
|