@thingd/cli 0.49.9 → 0.50.1
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/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +62 -5
- package/dist/dashboard/public/assets/index-ChtmW1gL.js +4 -0
- package/dist/dashboard/public/index.html +1 -1
- package/dist/data-movement.d.ts.map +1 -1
- package/dist/data-movement.js +5 -5
- package/dist/doctor.js +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +197 -40
- package/dist/lib/cloud-api.d.ts +10 -0
- package/dist/lib/cloud-api.d.ts.map +1 -1
- package/dist/lib/cloud-api.js +29 -0
- package/dist/lib/cloud-config.d.ts +12 -0
- package/dist/lib/cloud-config.d.ts.map +1 -1
- package/dist/lib/cloud-config.js +11 -0
- package/package.json +2 -2
- package/dist/dashboard/public/assets/index-CtIMsyl9.js +0 -4
package/dist/interactive.js
CHANGED
|
@@ -7,8 +7,9 @@ import readline from "node:readline";
|
|
|
7
7
|
import { ThingD } from "@thingd/sdk";
|
|
8
8
|
import pc from "picocolors";
|
|
9
9
|
import { listInstances, listProjects } from "./lib/cloud-api.js";
|
|
10
|
-
import { readCloudConfig } from "./lib/cloud-config.js";
|
|
10
|
+
import { readCloudConfig, removeCloudConfig, resolveCloudUrl, writeCloudConfig, } from "./lib/cloud-config.js";
|
|
11
11
|
import { logoText } from "./logo.js";
|
|
12
|
+
import { defaultThingdDbPath } from "./paths.js";
|
|
12
13
|
// ── Helpers ──────────────────────────────────────────────────────────
|
|
13
14
|
function highlightJson(val) {
|
|
14
15
|
const str = JSON.stringify(val, null, 2);
|
|
@@ -77,6 +78,8 @@ let totalObjects = 0;
|
|
|
77
78
|
let totalEventsCount = 0;
|
|
78
79
|
let totalActiveJobsCount = 0;
|
|
79
80
|
let totalDeadJobsCount = 0;
|
|
81
|
+
let totalLinksCount = 0;
|
|
82
|
+
let cloudError = null;
|
|
80
83
|
let objectsHistory = [];
|
|
81
84
|
let eventsHistory = [];
|
|
82
85
|
let activeJobsHistory = [];
|
|
@@ -91,7 +94,7 @@ let loadTimer = null;
|
|
|
91
94
|
let pollTimer = null;
|
|
92
95
|
let keypressHandler = null;
|
|
93
96
|
let formState = null;
|
|
94
|
-
function openForm(title, fields, onSubmit) {
|
|
97
|
+
function openForm(title, fields, onSubmit, keepViewer) {
|
|
95
98
|
formState = {
|
|
96
99
|
active: true,
|
|
97
100
|
title,
|
|
@@ -126,6 +129,10 @@ function openForm(title, fields, onSubmit) {
|
|
|
126
129
|
try {
|
|
127
130
|
await onSubmit(vals);
|
|
128
131
|
formState = null;
|
|
132
|
+
if (keepViewer) {
|
|
133
|
+
draw();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
129
136
|
viewerLines = [];
|
|
130
137
|
loadedItemId = ""; // Force reload
|
|
131
138
|
await fetchResources();
|
|
@@ -190,12 +197,13 @@ function formatUptime(ms) {
|
|
|
190
197
|
return `${h}h ${m % 60}m`;
|
|
191
198
|
}
|
|
192
199
|
async function fetchResourcesFallback() {
|
|
200
|
+
cloudError = null;
|
|
193
201
|
try {
|
|
194
202
|
const nativeCollections = await db.listCollections();
|
|
195
203
|
const nativeStreams = await db.listStreams();
|
|
196
204
|
// Maintain default collections/streams for UI if none exist
|
|
197
|
-
const defaultCols = new Set(
|
|
198
|
-
const defaultStrs = new Set(
|
|
205
|
+
const defaultCols = new Set();
|
|
206
|
+
const defaultStrs = new Set();
|
|
199
207
|
for (const c of nativeCollections) {
|
|
200
208
|
defaultCols.add(c);
|
|
201
209
|
}
|
|
@@ -210,10 +218,16 @@ async function fetchResourcesFallback() {
|
|
|
210
218
|
totalActiveJobsCount = await db.countActiveJobs();
|
|
211
219
|
totalDeadJobsCount = await db.countDeadJobs();
|
|
212
220
|
}
|
|
213
|
-
catch {
|
|
221
|
+
catch (err) {
|
|
222
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
223
|
+
cloudError = `Failed to load resources: ${msg}`;
|
|
214
224
|
collections = [];
|
|
215
225
|
streams = [];
|
|
216
226
|
totalObjects = 0;
|
|
227
|
+
// Show error in viewer if it's currently showing the default message
|
|
228
|
+
if (viewerLines.length === 1 && viewerLines[0] === "Select an item to view details.") {
|
|
229
|
+
viewerLines = [pc.yellow(cloudError), pc.dim("Press 'r' to retry or 's' to switch driver.")];
|
|
230
|
+
}
|
|
217
231
|
}
|
|
218
232
|
// Queues
|
|
219
233
|
try {
|
|
@@ -221,18 +235,37 @@ async function fetchResourcesFallback() {
|
|
|
221
235
|
queues = (listed ?? []).sort();
|
|
222
236
|
}
|
|
223
237
|
catch {
|
|
224
|
-
queues = [
|
|
238
|
+
queues = [];
|
|
239
|
+
}
|
|
240
|
+
// Link count
|
|
241
|
+
try {
|
|
242
|
+
totalLinksCount = await db.countLinks();
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
totalLinksCount = 0;
|
|
246
|
+
}
|
|
247
|
+
// Populate objectsByCollection from live data
|
|
248
|
+
objectsByCollection.clear();
|
|
249
|
+
for (const col of collections) {
|
|
250
|
+
try {
|
|
251
|
+
const list = await db.listObjects(col);
|
|
252
|
+
objectsByCollection.set(col, list.map((o) => o.id));
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
objectsByCollection.set(col, []);
|
|
256
|
+
}
|
|
225
257
|
}
|
|
226
258
|
}
|
|
227
259
|
async function fetchResources() {
|
|
228
260
|
if (driver === "native" && dbPath) {
|
|
229
261
|
try {
|
|
230
262
|
// Override the tracked totals with the actual exact DB count!
|
|
231
|
-
const [objCount, evtCount, activeCount, deadCount, nativeCollections, nativeStreams, nativeQueues,] = await Promise.all([
|
|
263
|
+
const [objCount, evtCount, activeCount, deadCount, linkCount, nativeCollections, nativeStreams, nativeQueues,] = await Promise.all([
|
|
232
264
|
db.countObjects(),
|
|
233
265
|
db.countEvents(),
|
|
234
266
|
db.countActiveJobs(),
|
|
235
267
|
db.countDeadJobs(),
|
|
268
|
+
db.countLinks(),
|
|
236
269
|
db.listCollections(),
|
|
237
270
|
db.listStreams(),
|
|
238
271
|
db.listQueues?.() ?? Promise.resolve([]),
|
|
@@ -243,12 +276,10 @@ async function fetchResources() {
|
|
|
243
276
|
Number.isNaN(activeCount) || activeCount === 0 ? totalActiveJobsCount : activeCount;
|
|
244
277
|
totalDeadJobsCount =
|
|
245
278
|
Number.isNaN(deadCount) || deadCount === 0 ? totalDeadJobsCount : deadCount;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
: ["project:thingd", "load-events", "activity-log"];
|
|
251
|
-
queues = nativeQueues.length > 0 ? nativeQueues : ["embed", "load-queue", "worker-queue"];
|
|
279
|
+
totalLinksCount = Number.isNaN(linkCount) ? totalLinksCount : linkCount;
|
|
280
|
+
collections = nativeCollections.length > 0 ? nativeCollections : [];
|
|
281
|
+
streams = nativeStreams.length > 0 ? nativeStreams : [];
|
|
282
|
+
queues = nativeQueues.length > 0 ? nativeQueues : [];
|
|
252
283
|
}
|
|
253
284
|
catch {
|
|
254
285
|
// Fallback if sqlite3 fails
|
|
@@ -258,6 +289,17 @@ async function fetchResources() {
|
|
|
258
289
|
else {
|
|
259
290
|
await fetchResourcesFallback();
|
|
260
291
|
}
|
|
292
|
+
// Populate objectsByCollection from live data
|
|
293
|
+
objectsByCollection.clear();
|
|
294
|
+
for (const col of collections) {
|
|
295
|
+
try {
|
|
296
|
+
const list = await db.listObjects(col);
|
|
297
|
+
objectsByCollection.set(col, list.map((o) => o.id));
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
objectsByCollection.set(col, []);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
261
303
|
// Calculate Deltas for Operations Throughput Rates
|
|
262
304
|
const prevObjects = objectsHistory.length > 0
|
|
263
305
|
? (objectsHistory[objectsHistory.length - 1] ?? totalObjects)
|
|
@@ -532,7 +574,7 @@ function buildTree() {
|
|
|
532
574
|
depth: 0,
|
|
533
575
|
expandable: true,
|
|
534
576
|
children: [
|
|
535
|
-
{ id: "maintenance:integrity", label: "Run
|
|
577
|
+
{ id: "maintenance:integrity", label: "Run Health Check" },
|
|
536
578
|
{ id: "maintenance:checkpoint", label: "WAL Checkpoint" },
|
|
537
579
|
{ id: "maintenance:backup", label: "Create Backup" },
|
|
538
580
|
],
|
|
@@ -675,12 +717,18 @@ async function loadContent(node) {
|
|
|
675
717
|
content += ` ${pc.bold("Capacity & Storage")}\n`;
|
|
676
718
|
content += ` ${pc.dim("Objects".padEnd(14))} ${pc.cyan(String(totalObjects).padEnd(6))} ${pc.dim("total")}\n`;
|
|
677
719
|
content += ` ${pc.dim("Events".padEnd(14))} ${pc.green(String(totalEventsCount).padEnd(6))} ${pc.dim("total")}\n`;
|
|
720
|
+
content += ` ${pc.dim("Links".padEnd(14))} ${pc.blue(String(totalLinksCount).padEnd(6))} ${pc.dim("total")}\n`;
|
|
678
721
|
content += ` ${pc.dim("Active Jobs".padEnd(14))} ${pc.yellow(String(totalActiveJobsCount).padEnd(6))} ${pc.dim("in flight")}\n`;
|
|
679
722
|
content += ` ${pc.dim("Dead Jobs".padEnd(14))} ${pc.red(String(totalDeadJobsCount).padEnd(6))} ${pc.dim("failed")}\n\n`;
|
|
680
723
|
content += ` ${pc.bold("Connection")}\n`;
|
|
681
724
|
content += ` ${pc.dim("Driver".padEnd(14))} ${driverName}\n`;
|
|
682
725
|
content += ` ${pc.dim("Path".padEnd(14))} ${dbPath || ":memory:"}\n`;
|
|
683
|
-
content += ` ${pc.dim("Size".padEnd(14))} ${dbSizeStr}\n
|
|
726
|
+
content += ` ${pc.dim("Size".padEnd(14))} ${dbSizeStr}\n`;
|
|
727
|
+
if (driver === "native") {
|
|
728
|
+
const sizeSpark = drawSparkline(dbSizeHistory, 5, Math.max(10, viewW - 55));
|
|
729
|
+
content += ` ${pc.dim("Size History".padEnd(14))} ${pc.cyan(sizeSpark)}\n`;
|
|
730
|
+
}
|
|
731
|
+
content += `\n`;
|
|
684
732
|
// ── Throughput & Activity Metrics
|
|
685
733
|
const currentWrite = objectWriteRateHistory[objectWriteRateHistory.length - 1] ?? 0;
|
|
686
734
|
const currentAppend = eventAppendRateHistory[eventAppendRateHistory.length - 1] ?? 0;
|
|
@@ -832,7 +880,7 @@ function draw() {
|
|
|
832
880
|
help = ` ${pc.dim("↑↓")} nav ${pc.dim("enter")} connect ${pc.dim("q")} quit `;
|
|
833
881
|
}
|
|
834
882
|
else {
|
|
835
|
-
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("q")} quit `;
|
|
883
|
+
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 `;
|
|
836
884
|
}
|
|
837
885
|
buf += `${pc.dim("─".repeat(W))}\n`;
|
|
838
886
|
buf += padToWidth(help, W);
|
|
@@ -1127,7 +1175,10 @@ async function handleDelete(selected) {
|
|
|
1127
1175
|
if ((vals.confirm || "").toLowerCase() !== "yes") {
|
|
1128
1176
|
throw new Error("Canceled");
|
|
1129
1177
|
}
|
|
1130
|
-
await db.delete(ref.collection, ref.id);
|
|
1178
|
+
const result = await db.delete(ref.collection, ref.id);
|
|
1179
|
+
if (result && !result.deleted) {
|
|
1180
|
+
throw new Error(`Object '${ref.id}' not found in collection '${ref.collection}'`);
|
|
1181
|
+
}
|
|
1131
1182
|
});
|
|
1132
1183
|
}
|
|
1133
1184
|
else if (selected.type === "queue" && selected.ref) {
|
|
@@ -1188,7 +1239,7 @@ async function handleSearch() {
|
|
|
1188
1239
|
}),
|
|
1189
1240
|
];
|
|
1190
1241
|
loadedItemId = "search_results";
|
|
1191
|
-
});
|
|
1242
|
+
}, true);
|
|
1192
1243
|
}
|
|
1193
1244
|
async function handleInfo() {
|
|
1194
1245
|
const lines = [
|
|
@@ -1241,7 +1292,7 @@ async function handleInfo() {
|
|
|
1241
1292
|
}
|
|
1242
1293
|
async function handleMaintenance() {
|
|
1243
1294
|
// Cycle through maintenance operations with each press of 'm'
|
|
1244
|
-
const operations = ["
|
|
1295
|
+
const operations = ["health", "checkpoint", "backup"];
|
|
1245
1296
|
const idx = maintenanceCursor % operations.length;
|
|
1246
1297
|
maintenanceCursor = (maintenanceCursor + 1) % operations.length;
|
|
1247
1298
|
const op = operations[idx];
|
|
@@ -1285,18 +1336,25 @@ async function handleMaintenance() {
|
|
|
1285
1336
|
}
|
|
1286
1337
|
}
|
|
1287
1338
|
else {
|
|
1339
|
+
// Health check — verify read path
|
|
1288
1340
|
try {
|
|
1289
1341
|
if (typeof db !== "undefined" && typeof db.countObjects === "function") {
|
|
1290
|
-
await db.countObjects();
|
|
1291
|
-
|
|
1342
|
+
const objCount = await db.countObjects();
|
|
1343
|
+
const evtCount = await db.countEvents();
|
|
1344
|
+
const jobCount = typeof db.countActiveJobs === "function" ? await db.countActiveJobs() : 0;
|
|
1345
|
+
viewerLines = [
|
|
1346
|
+
` Health check passed`,
|
|
1347
|
+
` Objects: ${objCount}, Events: ${evtCount}, Active jobs: ${jobCount}`,
|
|
1348
|
+
``,
|
|
1349
|
+
];
|
|
1292
1350
|
}
|
|
1293
1351
|
else {
|
|
1294
|
-
viewerLines = [`
|
|
1352
|
+
viewerLines = [` Health check not available`, ``];
|
|
1295
1353
|
}
|
|
1296
1354
|
}
|
|
1297
1355
|
catch (err) {
|
|
1298
1356
|
viewerLines = [
|
|
1299
|
-
`
|
|
1357
|
+
` Health check failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1300
1358
|
``,
|
|
1301
1359
|
];
|
|
1302
1360
|
}
|
|
@@ -1530,6 +1588,9 @@ function setupKeypress() {
|
|
|
1530
1588
|
else if (str === "m" || str === "M") {
|
|
1531
1589
|
await handleMaintenance();
|
|
1532
1590
|
}
|
|
1591
|
+
else if (str === "l" || str === "L") {
|
|
1592
|
+
await handleLogout();
|
|
1593
|
+
}
|
|
1533
1594
|
}
|
|
1534
1595
|
};
|
|
1535
1596
|
process.stdin.on("keypress", keypressHandler);
|
|
@@ -1563,27 +1624,61 @@ async function handleConnect(node) {
|
|
|
1563
1624
|
return;
|
|
1564
1625
|
}
|
|
1565
1626
|
const selectedDriver = node.ref.driver;
|
|
1566
|
-
// Cloud with saved credentials — fetch projects/instances and
|
|
1627
|
+
// Cloud with saved credentials — fetch projects/instances and let user pick
|
|
1567
1628
|
if (selectedDriver === "cloud") {
|
|
1568
1629
|
const cloudCfg = readCloudConfig();
|
|
1569
|
-
if (cloudCfg?.token
|
|
1630
|
+
if (cloudCfg?.token) {
|
|
1570
1631
|
try {
|
|
1571
1632
|
const { projects } = await listProjects(cloudCfg);
|
|
1633
|
+
const instanceOptions = [];
|
|
1572
1634
|
for (const project of projects) {
|
|
1573
1635
|
try {
|
|
1574
1636
|
const { instances } = await listInstances(cloudCfg, project.id);
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1637
|
+
for (const inst of instances) {
|
|
1638
|
+
if (inst.mcpUrl) {
|
|
1639
|
+
instanceOptions.push({
|
|
1640
|
+
label: `${project.slug}/${inst.slug}`,
|
|
1641
|
+
mcpUrl: inst.mcpUrl,
|
|
1642
|
+
projectSlug: project.slug,
|
|
1643
|
+
instanceSlug: inst.slug,
|
|
1644
|
+
});
|
|
1580
1645
|
}
|
|
1581
1646
|
}
|
|
1582
1647
|
}
|
|
1583
1648
|
catch {
|
|
1584
|
-
//
|
|
1649
|
+
// Skip projects that fail to list instances
|
|
1585
1650
|
}
|
|
1586
1651
|
}
|
|
1652
|
+
if (instanceOptions.length > 0) {
|
|
1653
|
+
// Pre-select the saved instance if it exists
|
|
1654
|
+
const savedIdx = cloudCfg.instanceUrl
|
|
1655
|
+
? instanceOptions.findIndex((o) => o.mcpUrl === cloudCfg.instanceUrl)
|
|
1656
|
+
: -1;
|
|
1657
|
+
const defaultVal = savedIdx >= 0 ? instanceOptions[savedIdx]?.label : instanceOptions[0]?.label;
|
|
1658
|
+
openForm("Connect to Cloud", [
|
|
1659
|
+
{
|
|
1660
|
+
id: "instance",
|
|
1661
|
+
label: "Instance",
|
|
1662
|
+
value: defaultVal ?? "",
|
|
1663
|
+
options: instanceOptions.map((o) => o.label),
|
|
1664
|
+
},
|
|
1665
|
+
], async (vals) => {
|
|
1666
|
+
const selected = instanceOptions.find((o) => o.label === vals.instance);
|
|
1667
|
+
if (!selected) {
|
|
1668
|
+
viewerLines = [pc.red("No instance selected.")];
|
|
1669
|
+
draw();
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
// Save selection to cloud config
|
|
1673
|
+
cloudCfg.instanceUrl = selected.mcpUrl;
|
|
1674
|
+
cloudCfg.projectSlug = selected.projectSlug;
|
|
1675
|
+
cloudCfg.instanceSlug = selected.instanceSlug;
|
|
1676
|
+
writeCloudConfig(cloudCfg);
|
|
1677
|
+
await connectToDriver("cloud", selected.mcpUrl, selected.mcpUrl, cloudCfg.token);
|
|
1678
|
+
});
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
// No instances found — fall through to manual form
|
|
1587
1682
|
viewerLines = [
|
|
1588
1683
|
pc.yellow("No cloud instances found."),
|
|
1589
1684
|
pc.dim("Create one at https://thingd.cloud, or enter the Cloud URL manually."),
|
|
@@ -1641,18 +1736,39 @@ async function handleConnect(node) {
|
|
|
1641
1736
|
{
|
|
1642
1737
|
id: "path",
|
|
1643
1738
|
label: "Database Path",
|
|
1644
|
-
value:
|
|
1739
|
+
value: defaultThingdDbPath(),
|
|
1645
1740
|
},
|
|
1646
1741
|
]),
|
|
1647
1742
|
], async (vals) => {
|
|
1648
1743
|
let cloudUrl;
|
|
1649
1744
|
if (selectedDriver === "cloud") {
|
|
1650
1745
|
if (isCloudWithConfig) {
|
|
1746
|
+
if (!vals.project || !vals.instance) {
|
|
1747
|
+
viewerLines = [pc.red("Project and instance slugs are required.")];
|
|
1748
|
+
draw();
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1651
1751
|
// Construct URL from project + instance slugs
|
|
1652
|
-
cloudUrl = `${baseUrl}/mcp/${encodeURIComponent(vals.project
|
|
1752
|
+
cloudUrl = `${baseUrl}/mcp/${encodeURIComponent(vals.project)}/${encodeURIComponent(vals.instance)}`;
|
|
1753
|
+
// Save selection to cloud config
|
|
1754
|
+
const cfg = cloudCfg ?? { token: vals.token, url: baseUrl };
|
|
1755
|
+
cfg.instanceUrl = cloudUrl;
|
|
1756
|
+
cfg.projectSlug = vals.project;
|
|
1757
|
+
cfg.instanceSlug = vals.instance;
|
|
1758
|
+
if (vals.token) {
|
|
1759
|
+
cfg.token = vals.token;
|
|
1760
|
+
}
|
|
1761
|
+
writeCloudConfig(cfg);
|
|
1653
1762
|
}
|
|
1654
1763
|
else {
|
|
1655
|
-
|
|
1764
|
+
if (!vals.url) {
|
|
1765
|
+
viewerLines = [pc.red("Cloud URL is required.")];
|
|
1766
|
+
draw();
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
cloudUrl = vals.url;
|
|
1770
|
+
// Save manual credentials to cloud config
|
|
1771
|
+
writeCloudConfig({ token: vals.token || "", url: cloudUrl });
|
|
1656
1772
|
}
|
|
1657
1773
|
await connectToDriver(selectedDriver, cloudUrl, cloudUrl, vals.token);
|
|
1658
1774
|
}
|
|
@@ -1722,6 +1838,39 @@ async function handleSwitch() {
|
|
|
1722
1838
|
scheduleLoad(first);
|
|
1723
1839
|
}
|
|
1724
1840
|
}
|
|
1841
|
+
async function handleLogout() {
|
|
1842
|
+
// Close current connection if any
|
|
1843
|
+
if (connected && db) {
|
|
1844
|
+
try {
|
|
1845
|
+
await db.close();
|
|
1846
|
+
}
|
|
1847
|
+
catch {
|
|
1848
|
+
// ignore close errors
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
// Remove cloud credentials from disk
|
|
1852
|
+
removeCloudConfig();
|
|
1853
|
+
// Reset state
|
|
1854
|
+
connected = false;
|
|
1855
|
+
driver = "";
|
|
1856
|
+
dbPath = "";
|
|
1857
|
+
authToken = "";
|
|
1858
|
+
cloudError = null;
|
|
1859
|
+
collections = [];
|
|
1860
|
+
streams = [];
|
|
1861
|
+
queues = [];
|
|
1862
|
+
objectsByCollection = new Map();
|
|
1863
|
+
cursorIndex = 0;
|
|
1864
|
+
scrollOffset = 0;
|
|
1865
|
+
loadedItemId = "";
|
|
1866
|
+
viewerLines = [pc.green("Logged out."), pc.dim("Select an environment to connect.")];
|
|
1867
|
+
draw();
|
|
1868
|
+
const tree = buildTree();
|
|
1869
|
+
const first = tree[cursorIndex];
|
|
1870
|
+
if (first) {
|
|
1871
|
+
scheduleLoad(first);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1725
1874
|
// ── Entry Point ──────────────────────────────────────────────────────
|
|
1726
1875
|
export async function runInteractiveCli() {
|
|
1727
1876
|
// Go straight into the TUI — no pre-prompts
|
|
@@ -1741,12 +1890,20 @@ export async function runInteractiveCli() {
|
|
|
1741
1890
|
}
|
|
1742
1891
|
// Auto-connect to cloud if credentials exist
|
|
1743
1892
|
const cloudCfg = readCloudConfig();
|
|
1744
|
-
if (cloudCfg?.token
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1893
|
+
if (cloudCfg?.token) {
|
|
1894
|
+
const cloudUrl = resolveCloudUrl(cloudCfg);
|
|
1895
|
+
if (cloudUrl) {
|
|
1896
|
+
try {
|
|
1897
|
+
await connectToDriver("cloud", cloudUrl, cloudUrl, cloudCfg.token);
|
|
1898
|
+
}
|
|
1899
|
+
catch (err) {
|
|
1900
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1901
|
+
viewerLines = [
|
|
1902
|
+
pc.yellow(`Auto-connect failed: ${msg}`),
|
|
1903
|
+
pc.dim("Select an environment to connect, or press 'r' to retry."),
|
|
1904
|
+
];
|
|
1905
|
+
draw();
|
|
1906
|
+
}
|
|
1750
1907
|
}
|
|
1751
1908
|
}
|
|
1752
1909
|
setupKeypress();
|
package/dist/lib/cloud-api.d.ts
CHANGED
|
@@ -87,4 +87,14 @@ export declare function pollCliAuth(config: CloudConfig, code: string): Promise<
|
|
|
87
87
|
} | {
|
|
88
88
|
status: string;
|
|
89
89
|
}>;
|
|
90
|
+
export type ResolvedInstance = {
|
|
91
|
+
mcpUrl: string;
|
|
92
|
+
projectSlug: string;
|
|
93
|
+
instanceSlug: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Fetch the first available cloud instance for the logged-in user.
|
|
97
|
+
* Returns null if no projects or instances exist.
|
|
98
|
+
*/
|
|
99
|
+
export declare function resolveFirstInstance(config: CloudConfig): Promise<ResolvedInstance | null>;
|
|
90
100
|
//# sourceMappingURL=cloud-api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-api.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-api.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cloud-api.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AASrD,MAAM,MAAM,YAAY,GAAG;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,qBAAa,aAAc,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAC;IAEf,YAAY,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAI1C;CACF;AA0BD,wBAAsB,KAAK,CACzB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC;IAAE,IAAI,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAE9E;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC,CAE7F;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAMpC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,SAAS,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAEzC;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,CAAC,CAKtC;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,GAAG,EAAE,WAAW,CAAA;CAAE,CAAC,CAK/B;AAID,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,YAAY,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAE9C;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC;IAAE,aAAa,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC,CAEjD;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,YAAY,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAE5D;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,uBAAuB,EAAE,CAAA;CAAE,CAAC,CAEjD;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAAiB,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAK9C;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,CAAC,CAI1B;AAkBD,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAEjF;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAEjD;AAID,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAsBhG"}
|
package/dist/lib/cloud-api.js
CHANGED
|
@@ -99,3 +99,32 @@ export async function startCliAuth(config) {
|
|
|
99
99
|
export async function pollCliAuth(config, code) {
|
|
100
100
|
return requestUnauthenticated(config.url ?? DEFAULT_API_URL, "/auth/cli/poll", { code });
|
|
101
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Fetch the first available cloud instance for the logged-in user.
|
|
104
|
+
* Returns null if no projects or instances exist.
|
|
105
|
+
*/
|
|
106
|
+
export async function resolveFirstInstance(config) {
|
|
107
|
+
try {
|
|
108
|
+
const { projects } = await listProjects(config);
|
|
109
|
+
for (const project of projects) {
|
|
110
|
+
try {
|
|
111
|
+
const { instances } = await listInstances(config, project.id);
|
|
112
|
+
const instance = instances[0];
|
|
113
|
+
if (instance?.mcpUrl) {
|
|
114
|
+
return {
|
|
115
|
+
mcpUrl: instance.mcpUrl,
|
|
116
|
+
projectSlug: project.slug,
|
|
117
|
+
instanceSlug: instance.slug,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
// Skip projects that fail to list instances
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// API unreachable or token invalid
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
@@ -4,9 +4,21 @@ export type CloudConfig = {
|
|
|
4
4
|
url?: string;
|
|
5
5
|
/** Currently active organization context (set by `thingd cloud org use`). */
|
|
6
6
|
organizationId?: string;
|
|
7
|
+
/** Resolved MCP URL for the active cloud instance (auto-discovered or set by `thingd cloud instance use`). */
|
|
8
|
+
instanceUrl?: string;
|
|
9
|
+
/** Active project slug (set when instanceUrl is resolved). */
|
|
10
|
+
projectSlug?: string;
|
|
11
|
+
/** Active instance slug (set when instanceUrl is resolved). */
|
|
12
|
+
instanceSlug?: string;
|
|
7
13
|
};
|
|
8
14
|
export declare function cloudConfigPath(): string;
|
|
9
15
|
export declare function readCloudConfig(): CloudConfig | null;
|
|
10
16
|
export declare function writeCloudConfig(config: CloudConfig): void;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the best available cloud MCP URL from saved config.
|
|
19
|
+
* Only returns URL when a concrete instance endpoint is available
|
|
20
|
+
* (instanceUrl) — the bare API url is not enough for MCP connections.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveCloudUrl(config: CloudConfig): string | undefined;
|
|
11
23
|
export declare function removeCloudConfig(): void;
|
|
12
24
|
//# sourceMappingURL=cloud-config.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-config.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-config.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,eAAe,IAAI,WAAW,GAAG,IAAI,CAUpD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAG1D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAKvE;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAMxC"}
|
package/dist/lib/cloud-config.js
CHANGED
|
@@ -21,6 +21,17 @@ export function writeCloudConfig(config) {
|
|
|
21
21
|
ensureThingdDir();
|
|
22
22
|
writeFileSync(cloudConfigPath(), JSON.stringify(config, null, 2), "utf-8");
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Returns the best available cloud MCP URL from saved config.
|
|
26
|
+
* Only returns URL when a concrete instance endpoint is available
|
|
27
|
+
* (instanceUrl) — the bare API url is not enough for MCP connections.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveCloudUrl(config) {
|
|
30
|
+
if (config.instanceUrl) {
|
|
31
|
+
return config.instanceUrl;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
24
35
|
export function removeCloudConfig() {
|
|
25
36
|
try {
|
|
26
37
|
unlinkSync(cloudConfigPath());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thingd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.1",
|
|
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.50.1"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.0.0"
|