@thingd/cli 0.58.2 → 0.60.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/commands/cloud.d.ts.map +1 -1
- package/dist/commands/cloud.js +208 -46
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/interactive.d.ts.map +1 -1
- package/dist/interactive.js +81 -19
- package/dist/lib/cloud-api.d.ts +27 -0
- package/dist/lib/cloud-api.d.ts.map +1 -1
- package/dist/lib/cloud-api.js +34 -1
- package/dist/lib/cloud-config.d.ts +8 -3
- package/dist/lib/cloud-config.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/commands/cloud.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/commands/cloud.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,aAAa,CAAC;AAiHzE,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAmCjE"}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
+
import * as os from "node:os";
|
|
2
3
|
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
4
5
|
import pc from "picocolors";
|
|
5
6
|
import { requiredToken, stringFlag } from "../index.js";
|
|
6
|
-
import { addOrganizationMember, CloudApiError, createApiKey, createInstance, createOrganization, createProject, getMe, getOrganization, listInstances, listOrganizationMembers, listOrganizations, listProjects, pollCliAuth, removeOrganizationMember, resolveAllInstances, startCliAuth, } from "../lib/cloud-api.js";
|
|
7
|
+
import { addOrganizationMember, CloudApiError, createApiKey, createInstance, createOrganization, createProject, createUserToken, getMe, getOrganization, listInstances, listOrganizationMembers, listOrganizations, listProjects, listUserTokens, parseUserTokenId, pollCliAuth, removeOrganizationMember, resolveAllInstances, revokeUserToken, startCliAuth, updateUserToken, } from "../lib/cloud-api.js";
|
|
7
8
|
import { readCloudConfig, removeCloudConfig, writeCloudConfig, } from "../lib/cloud-config.js";
|
|
8
9
|
const POLL_INTERVAL_MS = 2_000;
|
|
9
10
|
const POLL_TIMEOUT_MS = 5 * 60 * 1_000;
|
|
@@ -37,7 +38,7 @@ function openBrowser(url) {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
function makeBaseConfig(context) {
|
|
40
|
-
return {
|
|
41
|
+
return { url: cliApiUrl(context) };
|
|
41
42
|
}
|
|
42
43
|
async function askQuestion(rl, query) {
|
|
43
44
|
return rl.question(query);
|
|
@@ -77,35 +78,6 @@ async function pickAndSaveInstance(context, cloudConfig) {
|
|
|
77
78
|
rl.close();
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
|
-
/**
|
|
81
|
-
* Ensure a data-plane API key exists in the cloud config.
|
|
82
|
-
* Best-effort — if creation fails, the JWT is still usable for management API.
|
|
83
|
-
*/
|
|
84
|
-
async function ensureApiKey(context, cloudConfig) {
|
|
85
|
-
if (cloudConfig.apiKey) {
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
let projectId = cloudConfig.projectId;
|
|
90
|
-
if (!projectId) {
|
|
91
|
-
const { projects } = await listProjects(cloudConfig);
|
|
92
|
-
if (projects.length === 0) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
projectId = projects[0]?.id;
|
|
96
|
-
}
|
|
97
|
-
if (!projectId) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const result = await createApiKey(cloudConfig, projectId);
|
|
101
|
-
cloudConfig.apiKey = result.token;
|
|
102
|
-
writeCloudConfig(cloudConfig);
|
|
103
|
-
context.stderr.write(` ${pc.dim("API key created for data access\n")}`);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// API key creation is best-effort
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
81
|
export async function runCloud(context) {
|
|
110
82
|
const sub = requiredToken(context.parsed, 1, "subcommand");
|
|
111
83
|
switch (sub) {
|
|
@@ -118,6 +90,9 @@ export async function runCloud(context) {
|
|
|
118
90
|
case "status":
|
|
119
91
|
await runCloudStatus(context);
|
|
120
92
|
return;
|
|
93
|
+
case "token":
|
|
94
|
+
await runToken(context);
|
|
95
|
+
return;
|
|
121
96
|
case "project":
|
|
122
97
|
await runProject(context);
|
|
123
98
|
return;
|
|
@@ -125,6 +100,7 @@ export async function runCloud(context) {
|
|
|
125
100
|
await runInstance(context);
|
|
126
101
|
return;
|
|
127
102
|
case "api-key":
|
|
103
|
+
context.stderr.write(pc.yellow("Deprecated. Use `thingd cloud token create` for CLI tokens. Project API keys are managed in the dashboard.\n"));
|
|
128
104
|
await runApiKey(context);
|
|
129
105
|
return;
|
|
130
106
|
case "org":
|
|
@@ -132,7 +108,7 @@ export async function runCloud(context) {
|
|
|
132
108
|
return;
|
|
133
109
|
default:
|
|
134
110
|
context.stderr.write(`Unknown cloud subcommand: ${sub}\n` +
|
|
135
|
-
"Available: login, logout, status, org, project, instance, api-key\n");
|
|
111
|
+
"Available: login, logout, status, token, org, project, instance, api-key\n");
|
|
136
112
|
}
|
|
137
113
|
}
|
|
138
114
|
async function runLogin(context) {
|
|
@@ -140,17 +116,19 @@ async function runLogin(context) {
|
|
|
140
116
|
const token = context.parsed.tokens[3] ?? stringFlag(context.parsed, "token");
|
|
141
117
|
// ── Manual --code --token flow (fallback) ─────────────────────────
|
|
142
118
|
if (code && token) {
|
|
143
|
-
const config = {
|
|
119
|
+
const config = { url: cliApiUrl(context) };
|
|
144
120
|
try {
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
//
|
|
121
|
+
// Verify JWT and get user info
|
|
122
|
+
const { user } = await getMe({ ...config, token });
|
|
123
|
+
// Create a permanent user token
|
|
124
|
+
const hostname = os.hostname();
|
|
125
|
+
const { token: userToken } = await createUserToken({ ...config, token }, `cli-${hostname}`);
|
|
126
|
+
// Save config with user token (not JWT)
|
|
127
|
+
const cloudConfig = { userToken, email: user.email, ...config };
|
|
148
128
|
writeCloudConfig(cloudConfig);
|
|
149
129
|
context.stdout.write(pc.green(`✓ Logged in as ${user.email}\n`));
|
|
150
130
|
// Discover and select an instance (interactive if multiple)
|
|
151
131
|
await pickAndSaveInstance(context, cloudConfig);
|
|
152
|
-
// Create a data-plane API key for REST/MCP operations
|
|
153
|
-
await ensureApiKey(context, cloudConfig);
|
|
154
132
|
}
|
|
155
133
|
catch (err) {
|
|
156
134
|
if (err instanceof CloudApiError && err.status === 401) {
|
|
@@ -187,17 +165,20 @@ async function runLogin(context) {
|
|
|
187
165
|
try {
|
|
188
166
|
const result = await pollCliAuth(baseConfig, deviceCode);
|
|
189
167
|
if ("token" in result) {
|
|
190
|
-
const
|
|
168
|
+
const jwt = result.token;
|
|
169
|
+
const tokenConfig = { url: cliApiUrl(context) };
|
|
191
170
|
try {
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
//
|
|
171
|
+
// Verify temporary JWT
|
|
172
|
+
const { user } = await getMe({ ...tokenConfig, token: jwt });
|
|
173
|
+
// Create permanent user token
|
|
174
|
+
const hostname = os.hostname();
|
|
175
|
+
const { token: userToken } = await createUserToken({ ...tokenConfig, token: jwt }, `cli-${hostname}`);
|
|
176
|
+
// Save config with user token (not JWT)
|
|
177
|
+
const cloudConfig = { userToken, email: user.email, ...tokenConfig };
|
|
195
178
|
writeCloudConfig(cloudConfig);
|
|
196
179
|
context.stdout.write(pc.green(`\r✓ Logged in as ${user.email}\n`));
|
|
197
180
|
// Discover and select an instance (interactive if multiple)
|
|
198
181
|
await pickAndSaveInstance(context, cloudConfig);
|
|
199
|
-
// Create a data-plane API key for REST/MCP operations
|
|
200
|
-
await ensureApiKey(context, cloudConfig);
|
|
201
182
|
return;
|
|
202
183
|
}
|
|
203
184
|
catch {
|
|
@@ -219,6 +200,18 @@ async function runLogin(context) {
|
|
|
219
200
|
context.stderr.write(pc.red("\nTimed out. Run `thingd cloud login` again.\n"));
|
|
220
201
|
}
|
|
221
202
|
async function runLogout(context) {
|
|
203
|
+
const config = readCloudConfig();
|
|
204
|
+
if (config?.userToken) {
|
|
205
|
+
try {
|
|
206
|
+
const tokenId = parseUserTokenId(config.userToken);
|
|
207
|
+
if (tokenId) {
|
|
208
|
+
await revokeUserToken(config, tokenId);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// Best-effort — token may already be revoked or API unreachable
|
|
213
|
+
}
|
|
214
|
+
}
|
|
222
215
|
removeCloudConfig();
|
|
223
216
|
context.stdout.write(pc.green("✓ Logged out\n"));
|
|
224
217
|
}
|
|
@@ -228,10 +221,31 @@ async function runCloudStatus(context) {
|
|
|
228
221
|
context.stdout.write(`Not logged in. Run ${pc.cyan("thingd cloud login")}\n`);
|
|
229
222
|
return;
|
|
230
223
|
}
|
|
224
|
+
// Warn about old config format
|
|
225
|
+
if (config.token && !config.userToken) {
|
|
226
|
+
context.stdout.write(`${pc.yellow("Your credentials use an older format.")} Run ${pc.cyan("thingd cloud login")} to upgrade to a persistent CLI token.\n\n`);
|
|
227
|
+
}
|
|
231
228
|
try {
|
|
232
229
|
const { user } = await getMe(config);
|
|
233
|
-
context.stdout.write(`Logged in as ${pc.green(user.email)} (${user.role})\n`
|
|
234
|
-
|
|
230
|
+
context.stdout.write(`Logged in as ${pc.green(user.email)} (${user.role})\n`);
|
|
231
|
+
// Show token info
|
|
232
|
+
if (config.userToken) {
|
|
233
|
+
try {
|
|
234
|
+
const { userTokens } = await listUserTokens(config);
|
|
235
|
+
const active = userTokens.find((t) => !t.revokedAt);
|
|
236
|
+
if (active) {
|
|
237
|
+
context.stdout.write(`CLI Token: ${pc.cyan(active.name)}\n` +
|
|
238
|
+
` Prefix: ${pc.dim(active.prefix)}\n` +
|
|
239
|
+
` Created: ${pc.dim(formatTimeAgo(active.createdAt))}\n` +
|
|
240
|
+
` Last used:${pc.dim(active.lastUsedAt ? formatTimeAgo(active.lastUsedAt) : "never")}\n` +
|
|
241
|
+
` Access: ${pc.dim(active.projectAccess === "all" ? "All projects" : active.projectAccess)}\n`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
context.stdout.write(` ${pc.dim("(token info unavailable)")}\n`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
context.stdout.write(`API: ${config.url ?? "https://api.thingd.cloud"}\n`);
|
|
235
249
|
if (config.projectSlug && config.instanceSlug && config.instanceUrl) {
|
|
236
250
|
context.stdout.write(`Instance: ${pc.cyan(config.projectSlug)}/${pc.cyan(config.instanceSlug)}\n` +
|
|
237
251
|
` ${pc.dim(config.instanceUrl)}\n`);
|
|
@@ -256,6 +270,154 @@ async function runCloudStatus(context) {
|
|
|
256
270
|
context.stdout.write(`Token expired. Run ${pc.cyan("thingd cloud login")}\n`);
|
|
257
271
|
}
|
|
258
272
|
}
|
|
273
|
+
function formatTimeAgo(iso) {
|
|
274
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
275
|
+
const s = Math.floor(ms / 1000);
|
|
276
|
+
if (s < 5)
|
|
277
|
+
return "just now";
|
|
278
|
+
if (s < 60)
|
|
279
|
+
return `${s}s ago`;
|
|
280
|
+
const m = Math.floor(s / 60);
|
|
281
|
+
if (m < 60)
|
|
282
|
+
return `${m}m ago`;
|
|
283
|
+
const h = Math.floor(m / 60);
|
|
284
|
+
if (h < 24)
|
|
285
|
+
return `${h}h ago`;
|
|
286
|
+
const d = Math.floor(h / 24);
|
|
287
|
+
return `${d}d ago`;
|
|
288
|
+
}
|
|
289
|
+
// ── Token subcommands ────────────────────────────────────────────────
|
|
290
|
+
function requireLoggedInConfig(context) {
|
|
291
|
+
const config = readCloudConfig();
|
|
292
|
+
if (!config?.userToken && !config?.token) {
|
|
293
|
+
context.stderr.write(pc.yellow("Not logged in. Run thingd cloud login first.\n"));
|
|
294
|
+
throw new Error("not_logged_in");
|
|
295
|
+
}
|
|
296
|
+
return config;
|
|
297
|
+
}
|
|
298
|
+
async function runToken(context) {
|
|
299
|
+
const sub = context.parsed.tokens[2];
|
|
300
|
+
if (!sub) {
|
|
301
|
+
context.stderr.write("Usage: thingd cloud token <list|create|revoke|restrict|unrestricted>\n");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
switch (sub) {
|
|
305
|
+
case "list":
|
|
306
|
+
await runTokenList(context);
|
|
307
|
+
return;
|
|
308
|
+
case "create":
|
|
309
|
+
await runTokenCreate(context);
|
|
310
|
+
return;
|
|
311
|
+
case "revoke":
|
|
312
|
+
await runTokenRevoke(context);
|
|
313
|
+
return;
|
|
314
|
+
case "restrict":
|
|
315
|
+
await runTokenRestrict(context);
|
|
316
|
+
return;
|
|
317
|
+
case "unrestricted":
|
|
318
|
+
await runTokenUnrestricted(context);
|
|
319
|
+
return;
|
|
320
|
+
default:
|
|
321
|
+
context.stderr.write(`Unknown token action: ${sub}. Available: list, create, revoke, restrict, unrestricted\n`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
async function runTokenList(context) {
|
|
325
|
+
const config = requireLoggedInConfig(context);
|
|
326
|
+
try {
|
|
327
|
+
const { userTokens } = await listUserTokens(config);
|
|
328
|
+
if (userTokens.length === 0) {
|
|
329
|
+
context.stdout.write("No CLI tokens found. Create one with `thingd cloud token create <name>`\n");
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
// Header
|
|
333
|
+
const header = `${pc.bold("Name".padEnd(18))} ${pc.bold("Prefix".padEnd(22))} ${pc.bold("Created".padEnd(14))} ${pc.bold("Last Used".padEnd(14))} ${pc.bold("Access")}`;
|
|
334
|
+
context.stdout.write(`${header}\n`);
|
|
335
|
+
context.stdout.write(`${pc.dim("─".repeat(header.length))}\n`);
|
|
336
|
+
for (const t of userTokens) {
|
|
337
|
+
if (t.revokedAt)
|
|
338
|
+
continue; // Skip revoked tokens
|
|
339
|
+
const access = t.projectAccess === "all" ? "All" : t.projectAccess;
|
|
340
|
+
context.stdout.write(`${t.name.padEnd(18)} ${pc.dim(t.prefix.padEnd(22))} ${formatTimeAgo(t.createdAt).padEnd(14)} ${(t.lastUsedAt ? formatTimeAgo(t.lastUsedAt) : "never").padEnd(14)} ${pc.cyan(access)}\n`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (err) {
|
|
344
|
+
context.stderr.write(pc.red(`Failed to list tokens: ${err}\n`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async function runTokenCreate(context) {
|
|
348
|
+
const config = requireLoggedInConfig(context);
|
|
349
|
+
const name = context.parsed.tokens[3] ?? "cli-token";
|
|
350
|
+
try {
|
|
351
|
+
const { token: userToken } = await createUserToken(config, name);
|
|
352
|
+
context.stdout.write(`\n${pc.green("✓ Token created")}\n\n` +
|
|
353
|
+
`${pc.bold(userToken)}\n\n` +
|
|
354
|
+
`${pc.yellow("⚠ This token will only be shown once. Copy it now.\n")}` +
|
|
355
|
+
`${pc.dim("Press Enter to continue...")}\n`);
|
|
356
|
+
// Wait for user to acknowledge
|
|
357
|
+
const rl = createInterface({
|
|
358
|
+
input: context.stdin,
|
|
359
|
+
output: context.stderr,
|
|
360
|
+
});
|
|
361
|
+
await rl.question("");
|
|
362
|
+
rl.close();
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
context.stderr.write(pc.red(`Failed to create token: ${err}\n`));
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function runTokenRevoke(context) {
|
|
369
|
+
const config = requireLoggedInConfig(context);
|
|
370
|
+
const tokenId = requiredToken(context.parsed, 3, "token-id");
|
|
371
|
+
try {
|
|
372
|
+
await revokeUserToken(config, tokenId);
|
|
373
|
+
context.stdout.write(pc.green(`✓ Token ${tokenId} revoked\n`));
|
|
374
|
+
}
|
|
375
|
+
catch (err) {
|
|
376
|
+
if (err instanceof CloudApiError && err.status === 404) {
|
|
377
|
+
context.stderr.write(pc.red("Token not found.\n"));
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
context.stderr.write(pc.red(`Failed to revoke token: ${err}\n`));
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function runTokenRestrict(context) {
|
|
385
|
+
const config = requireLoggedInConfig(context);
|
|
386
|
+
const tokenId = requiredToken(context.parsed, 3, "token-id");
|
|
387
|
+
const projectSlug = requiredToken(context.parsed, 4, "project");
|
|
388
|
+
try {
|
|
389
|
+
const { userToken } = await updateUserToken(config, tokenId, {
|
|
390
|
+
projectAccess: projectSlug,
|
|
391
|
+
});
|
|
392
|
+
context.stdout.write(pc.green(`✓ Token "${userToken.name}" restricted to: ${projectSlug}\n`));
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
if (err instanceof CloudApiError && err.status === 404) {
|
|
396
|
+
context.stderr.write(pc.red("Token not found.\n"));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
context.stderr.write(pc.red(`Failed to update token: ${err}\n`));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function runTokenUnrestricted(context) {
|
|
404
|
+
const config = requireLoggedInConfig(context);
|
|
405
|
+
const tokenId = requiredToken(context.parsed, 3, "token-id");
|
|
406
|
+
try {
|
|
407
|
+
const { userToken } = await updateUserToken(config, tokenId, {
|
|
408
|
+
projectAccess: "all",
|
|
409
|
+
});
|
|
410
|
+
context.stdout.write(pc.green(`✓ Token "${userToken.name}" now has access to all projects\n`));
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
if (err instanceof CloudApiError && err.status === 404) {
|
|
414
|
+
context.stderr.write(pc.red("Token not found.\n"));
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
context.stderr.write(pc.red(`Failed to update token: ${err}\n`));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
259
421
|
// ── Org helpers ──────────────────────────────────────────────────────
|
|
260
422
|
async function resolveOrg(config, slugOrId) {
|
|
261
423
|
// Try direct ID lookup first, then assume it's a slug and list
|
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;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,
|
|
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,CAsDxE;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
|
@@ -1002,6 +1002,7 @@ export function resolveConnection(context) {
|
|
|
1002
1002
|
}
|
|
1003
1003
|
const resolvedAuthToken = stringFlag(context.parsed, "auth-token") ??
|
|
1004
1004
|
context.env.THINGD_AUTH_TOKEN ??
|
|
1005
|
+
cloudCfg?.userToken ??
|
|
1005
1006
|
cloudCfg?.apiKey ??
|
|
1006
1007
|
cloudCfg?.token;
|
|
1007
1008
|
// Prefer instanceUrl (resolved MCP endpoint) over raw cloudCfg.url (API base)
|
|
@@ -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":"AAqxGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
|
package/dist/interactive.js
CHANGED
|
@@ -69,6 +69,7 @@ let collections = [];
|
|
|
69
69
|
let streams = [];
|
|
70
70
|
let queues = [];
|
|
71
71
|
let objectsByCollection = new Map();
|
|
72
|
+
const collectionCounts = new Map();
|
|
72
73
|
const collectionOptions = new Map();
|
|
73
74
|
const expandedSet = new Set(["cat:collections", "cat:streams", "cat:queues"]);
|
|
74
75
|
let cursorIndex = 0;
|
|
@@ -217,6 +218,35 @@ function formatUptime(ms) {
|
|
|
217
218
|
const h = Math.floor(m / 60);
|
|
218
219
|
return `${h}h ${m % 60}m`;
|
|
219
220
|
}
|
|
221
|
+
function formatRelativeTime(iso) {
|
|
222
|
+
const ms = Date.now() - new Date(iso).getTime();
|
|
223
|
+
const s = Math.floor(ms / 1000);
|
|
224
|
+
if (s < 0) {
|
|
225
|
+
return "now";
|
|
226
|
+
}
|
|
227
|
+
if (s < 5) {
|
|
228
|
+
return "now";
|
|
229
|
+
}
|
|
230
|
+
if (s < 60) {
|
|
231
|
+
return `${s}s ago`;
|
|
232
|
+
}
|
|
233
|
+
const m = Math.floor(s / 60);
|
|
234
|
+
if (m < 60) {
|
|
235
|
+
return `${m}m ago`;
|
|
236
|
+
}
|
|
237
|
+
const h = Math.floor(m / 60);
|
|
238
|
+
if (h < 24) {
|
|
239
|
+
return `${h}h ago`;
|
|
240
|
+
}
|
|
241
|
+
const d = Math.floor(h / 24);
|
|
242
|
+
return `${d}d ago`;
|
|
243
|
+
}
|
|
244
|
+
function formatCount(n) {
|
|
245
|
+
if (n >= 1000) {
|
|
246
|
+
return `${(n / 1000).toFixed(1).replace(/\.0$/, "")}k`;
|
|
247
|
+
}
|
|
248
|
+
return String(n);
|
|
249
|
+
}
|
|
220
250
|
async function fetchResourcesFallback() {
|
|
221
251
|
cloudError = null;
|
|
222
252
|
// Collections and streams — parallel fetch
|
|
@@ -276,7 +306,7 @@ async function fetchResourcesFallback() {
|
|
|
276
306
|
listOpts.filter = opts.filter;
|
|
277
307
|
}
|
|
278
308
|
const list = await db.listObjects(col, Object.keys(listOpts).length > 0 ? listOpts : undefined);
|
|
279
|
-
objectsByCollection.set(col, list.map((o) => o.id));
|
|
309
|
+
objectsByCollection.set(col, list.map((o) => ({ id: o.id, createdAt: o.createdAt })));
|
|
280
310
|
}
|
|
281
311
|
catch {
|
|
282
312
|
objectsByCollection.set(col, []);
|
|
@@ -335,7 +365,7 @@ async function fetchResources() {
|
|
|
335
365
|
listOpts.filter = opts.filter;
|
|
336
366
|
}
|
|
337
367
|
const list = await db.listObjects(col, Object.keys(listOpts).length > 0 ? listOpts : undefined);
|
|
338
|
-
objectsByCollection.set(col, list.map((o) => o.id));
|
|
368
|
+
objectsByCollection.set(col, list.map((o) => ({ id: o.id, createdAt: o.createdAt })));
|
|
339
369
|
}
|
|
340
370
|
catch {
|
|
341
371
|
objectsByCollection.set(col, []);
|
|
@@ -364,6 +394,20 @@ async function fetchResources() {
|
|
|
364
394
|
jobsByQueue.set(q, { active: [], dead: [] });
|
|
365
395
|
}
|
|
366
396
|
}));
|
|
397
|
+
// Fetch per-collection counts from schema
|
|
398
|
+
collectionCounts.clear();
|
|
399
|
+
try {
|
|
400
|
+
const schemas = await db.schema();
|
|
401
|
+
for (const s of schemas) {
|
|
402
|
+
collectionCounts.set(s.name, s.objectCount);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// Fallback — count from objectsByCollection
|
|
407
|
+
for (const col of collections) {
|
|
408
|
+
collectionCounts.set(col, objectsByCollection.get(col)?.length ?? 0);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
367
411
|
// Calculate Deltas for Operations Throughput Rates
|
|
368
412
|
const prevObjects = objectsHistory.length > 0
|
|
369
413
|
? (objectsHistory[objectsHistory.length - 1] ?? totalObjects)
|
|
@@ -525,11 +569,13 @@ function buildTree() {
|
|
|
525
569
|
for (const col of collections) {
|
|
526
570
|
const colId = `col:${col}`;
|
|
527
571
|
const colOpen = expandedSet.has(colId);
|
|
572
|
+
const colCount = collectionCounts.get(col);
|
|
573
|
+
const colSuffix = colCount !== undefined ? pc.dim(` ${formatCount(colCount)}`) : "";
|
|
528
574
|
nodes.push({
|
|
529
575
|
id: colId,
|
|
530
576
|
parentId: "cat:collections",
|
|
531
577
|
type: "collection",
|
|
532
|
-
label: `${colOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.cyan(col)}`,
|
|
578
|
+
label: `${colOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.cyan(col)}${colSuffix}`,
|
|
533
579
|
depth: 1,
|
|
534
580
|
expandable: true,
|
|
535
581
|
ref: { name: col },
|
|
@@ -546,15 +592,16 @@ function buildTree() {
|
|
|
546
592
|
expandable: false,
|
|
547
593
|
});
|
|
548
594
|
}
|
|
549
|
-
for (const
|
|
595
|
+
for (const objData of objs) {
|
|
596
|
+
const objAge = objData.createdAt ? pc.dim(formatRelativeTime(objData.createdAt)) : "";
|
|
550
597
|
nodes.push({
|
|
551
|
-
id: `obj:${col}:${
|
|
598
|
+
id: `obj:${col}:${objData.id}`,
|
|
552
599
|
parentId: colId,
|
|
553
600
|
type: "object",
|
|
554
|
-
label: `${pc.cyan("○")} ${
|
|
601
|
+
label: `${pc.cyan("○")} ${objData.id} ${objAge}`,
|
|
555
602
|
depth: 2,
|
|
556
603
|
expandable: false,
|
|
557
|
-
ref: { collection: col, id:
|
|
604
|
+
ref: { collection: col, id: objData.id },
|
|
558
605
|
});
|
|
559
606
|
}
|
|
560
607
|
}
|
|
@@ -581,11 +628,13 @@ function buildTree() {
|
|
|
581
628
|
}
|
|
582
629
|
for (const stream of streams) {
|
|
583
630
|
const sOpen = expandedSet.has(`stream:${stream}`);
|
|
631
|
+
const evtCount = eventsByStream.get(stream)?.length ?? 0;
|
|
632
|
+
const evtSuffix = evtCount > 0 ? pc.dim(` ${formatCount(evtCount)}`) : "";
|
|
584
633
|
nodes.push({
|
|
585
634
|
id: `stream:${stream}`,
|
|
586
635
|
parentId: "cat:streams",
|
|
587
636
|
type: "stream",
|
|
588
|
-
label: `${sOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.green(stream)}`,
|
|
637
|
+
label: `${sOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.green(stream)}${evtSuffix}`,
|
|
589
638
|
depth: 1,
|
|
590
639
|
expandable: true,
|
|
591
640
|
ref: { name: stream },
|
|
@@ -607,7 +656,7 @@ function buildTree() {
|
|
|
607
656
|
id: `evt:${stream}:${evt.id}`,
|
|
608
657
|
parentId: `stream:${stream}`,
|
|
609
658
|
type: "event",
|
|
610
|
-
label: `${pc.dim("·")} ${pc.dim(evt.type || "unknown")}`,
|
|
659
|
+
label: `${pc.dim("·")} ${pc.dim(evt.type || "unknown")} ${pc.dim(formatRelativeTime(evt.createdAt))}`,
|
|
611
660
|
depth: 2,
|
|
612
661
|
expandable: false,
|
|
613
662
|
ref: { stream: stream, eventId: evt.id, eventData: evt },
|
|
@@ -637,11 +686,17 @@ function buildTree() {
|
|
|
637
686
|
}
|
|
638
687
|
for (const q of queues) {
|
|
639
688
|
const qOpen = expandedSet.has(`queue:${q}`);
|
|
689
|
+
const qJobData = jobsByQueue.get(q);
|
|
690
|
+
const qActive = qJobData?.active.length ?? 0;
|
|
691
|
+
const qDead = qJobData?.dead.length ?? 0;
|
|
692
|
+
const qSuffix = qActive > 0 || qDead > 0
|
|
693
|
+
? ` ${qActive > 0 ? pc.cyan(String(qActive)) : pc.dim("0")}${pc.cyan(" ●")} ${qDead > 0 ? pc.red(String(qDead)) : pc.dim("0")}${pc.red(" ○")}`
|
|
694
|
+
: "";
|
|
640
695
|
nodes.push({
|
|
641
696
|
id: `queue:${q}`,
|
|
642
697
|
parentId: "cat:queues",
|
|
643
698
|
type: "queue",
|
|
644
|
-
label: `${qOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.magenta(q)}`,
|
|
699
|
+
label: `${qOpen ? pc.cyan("▾") : pc.dim("▸")} ${pc.magenta(q)}${qSuffix}`,
|
|
645
700
|
depth: 1,
|
|
646
701
|
expandable: true,
|
|
647
702
|
ref: { name: q },
|
|
@@ -672,11 +727,13 @@ function buildTree() {
|
|
|
672
727
|
});
|
|
673
728
|
}
|
|
674
729
|
for (const job of activeJobs) {
|
|
730
|
+
const jobStatus = pc.cyan(job.status ?? "ready");
|
|
731
|
+
const jobAttempts = pc.dim(`${job.attempts}/${job.maxAttempts}`);
|
|
675
732
|
nodes.push({
|
|
676
733
|
id: `job:${q}:active:${job.id}`,
|
|
677
734
|
parentId: `queue:${q}:active`,
|
|
678
735
|
type: "job",
|
|
679
|
-
label: `${pc.cyan("●")} ${pc.dim(job.id.slice(0, 12))}`,
|
|
736
|
+
label: `${pc.cyan("●")} ${pc.dim(job.id.slice(0, 12))} ${jobAttempts} ${jobStatus}`,
|
|
680
737
|
depth: 3,
|
|
681
738
|
expandable: false,
|
|
682
739
|
ref: { queue: q, jobId: job.id, status: "active", jobData: job },
|
|
@@ -705,11 +762,13 @@ function buildTree() {
|
|
|
705
762
|
});
|
|
706
763
|
}
|
|
707
764
|
for (const job of deadJobs) {
|
|
765
|
+
const jobAttempts = pc.dim(`${job.attempts}/${job.maxAttempts}`);
|
|
766
|
+
const lastErr = job.lastError ? pc.dim(job.lastError.slice(0, 20)) : "";
|
|
708
767
|
nodes.push({
|
|
709
768
|
id: `job:${q}:dead:${job.id}`,
|
|
710
769
|
parentId: `queue:${q}:dead`,
|
|
711
770
|
type: "job",
|
|
712
|
-
label: `${pc.red("○")} ${pc.dim(job.id.slice(0, 12))}`,
|
|
771
|
+
label: `${pc.red("○")} ${pc.dim(job.id.slice(0, 12))} ${jobAttempts}${lastErr ? ` ${pc.red("⚠")} ${lastErr}` : ""}`,
|
|
713
772
|
depth: 3,
|
|
714
773
|
expandable: false,
|
|
715
774
|
ref: { queue: q, jobId: job.id, status: "dead", jobData: job },
|
|
@@ -765,7 +824,8 @@ function scheduleLoad(node) {
|
|
|
765
824
|
: d === "native"
|
|
766
825
|
? ` 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.")}`
|
|
767
826
|
: (() => {
|
|
768
|
-
const
|
|
827
|
+
const cfg = readCloudConfig();
|
|
828
|
+
const hasCfg = !!(cfg?.userToken ?? cfg?.token);
|
|
769
829
|
return hasCfg
|
|
770
830
|
? ` 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.")}`
|
|
771
831
|
: ` ${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.`;
|
|
@@ -828,7 +888,7 @@ async function loadContent(node) {
|
|
|
828
888
|
res += pc.dim("No objects in this collection.");
|
|
829
889
|
}
|
|
830
890
|
else {
|
|
831
|
-
const lines = objs.map((
|
|
891
|
+
const lines = objs.map((o) => ` ${pc.cyan("○")} ${o.id}${o.createdAt ? ` ${pc.dim(formatRelativeTime(o.createdAt))}` : ""}`);
|
|
832
892
|
res += lines.join("\n");
|
|
833
893
|
}
|
|
834
894
|
content = res;
|
|
@@ -2672,14 +2732,14 @@ async function handleConnect(node) {
|
|
|
2672
2732
|
cloudCfg.projectSlug = selected.projectSlug;
|
|
2673
2733
|
cloudCfg.instanceSlug = selected.instanceSlug;
|
|
2674
2734
|
writeCloudConfig(cloudCfg);
|
|
2675
|
-
await connectToDriver("cloud", deriveRestUrl(selected.mcpUrl), deriveRestUrl(selected.mcpUrl), cloudCfg.apiKey ?? cloudCfg.token, selected.instanceSlug);
|
|
2735
|
+
await connectToDriver("cloud", deriveRestUrl(selected.mcpUrl), deriveRestUrl(selected.mcpUrl), cloudCfg.userToken ?? cloudCfg.apiKey ?? cloudCfg.token, selected.instanceSlug);
|
|
2676
2736
|
});
|
|
2677
2737
|
return;
|
|
2678
2738
|
}
|
|
2679
2739
|
// No instances found — try saved URL or show error
|
|
2680
2740
|
if (cloudCfg.instanceUrl) {
|
|
2681
2741
|
const restUrl = deriveRestUrl(cloudCfg.instanceUrl);
|
|
2682
|
-
await connectToDriver("cloud", restUrl, restUrl, cloudCfg.apiKey ?? cloudCfg.token, cloudCfg.instanceSlug);
|
|
2742
|
+
await connectToDriver("cloud", restUrl, restUrl, cloudCfg.userToken ?? cloudCfg.apiKey ?? cloudCfg.token, cloudCfg.instanceSlug);
|
|
2683
2743
|
return;
|
|
2684
2744
|
}
|
|
2685
2745
|
viewerLines = [
|
|
@@ -2692,7 +2752,7 @@ async function handleConnect(node) {
|
|
|
2692
2752
|
catch {
|
|
2693
2753
|
if (cloudCfg.instanceUrl) {
|
|
2694
2754
|
const restUrl = deriveRestUrl(cloudCfg.instanceUrl);
|
|
2695
|
-
await connectToDriver("cloud", restUrl, restUrl, cloudCfg.apiKey ?? cloudCfg.token, cloudCfg.instanceSlug);
|
|
2755
|
+
await connectToDriver("cloud", restUrl, restUrl, cloudCfg.userToken ?? cloudCfg.apiKey ?? cloudCfg.token, cloudCfg.instanceSlug);
|
|
2696
2756
|
return;
|
|
2697
2757
|
}
|
|
2698
2758
|
viewerLines = [
|
|
@@ -2843,6 +2903,7 @@ async function handleSwitch() {
|
|
|
2843
2903
|
streams = [];
|
|
2844
2904
|
queues = [];
|
|
2845
2905
|
objectsByCollection = new Map();
|
|
2906
|
+
collectionCounts.clear();
|
|
2846
2907
|
cursorIndex = 0;
|
|
2847
2908
|
scrollOffset = 0;
|
|
2848
2909
|
loadedItemId = "";
|
|
@@ -2876,6 +2937,7 @@ async function handleLogout() {
|
|
|
2876
2937
|
streams = [];
|
|
2877
2938
|
queues = [];
|
|
2878
2939
|
objectsByCollection = new Map();
|
|
2940
|
+
collectionCounts.clear();
|
|
2879
2941
|
cursorIndex = 0;
|
|
2880
2942
|
scrollOffset = 0;
|
|
2881
2943
|
loadedItemId = "";
|
|
@@ -2906,12 +2968,12 @@ export async function runInteractiveCli() {
|
|
|
2906
2968
|
}
|
|
2907
2969
|
// Auto-connect to cloud if credentials exist
|
|
2908
2970
|
const cloudCfg = readCloudConfig();
|
|
2909
|
-
if (cloudCfg?.token) {
|
|
2971
|
+
if (cloudCfg?.userToken ?? cloudCfg?.token) {
|
|
2910
2972
|
const cloudUrl = resolveCloudUrl(cloudCfg);
|
|
2911
2973
|
if (cloudUrl) {
|
|
2912
2974
|
try {
|
|
2913
2975
|
const restUrl = deriveRestUrl(cloudUrl);
|
|
2914
|
-
await connectToDriver("cloud", restUrl, restUrl, cloudCfg.apiKey ?? cloudCfg.token, cloudCfg.instanceSlug);
|
|
2976
|
+
await connectToDriver("cloud", restUrl, restUrl, cloudCfg.userToken ?? cloudCfg.apiKey ?? cloudCfg.token, cloudCfg.instanceSlug);
|
|
2915
2977
|
}
|
|
2916
2978
|
catch (err) {
|
|
2917
2979
|
const msg = err instanceof Error ? err.message : String(err);
|
package/dist/lib/cloud-api.d.ts
CHANGED
|
@@ -19,6 +19,15 @@ export type CloudApiKey = {
|
|
|
19
19
|
token?: string;
|
|
20
20
|
createdAt: string;
|
|
21
21
|
};
|
|
22
|
+
export type UserTokenDto = {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
prefix: string;
|
|
26
|
+
projectAccess: string;
|
|
27
|
+
createdAt: string;
|
|
28
|
+
lastUsedAt: string | null;
|
|
29
|
+
revokedAt: string | null;
|
|
30
|
+
};
|
|
22
31
|
export type CloudOrganization = {
|
|
23
32
|
id: string;
|
|
24
33
|
name: string;
|
|
@@ -105,6 +114,24 @@ export declare function deriveRestUrl(mcpUrl: string): string;
|
|
|
105
114
|
* Returns null if no projects or instances exist.
|
|
106
115
|
*/
|
|
107
116
|
export declare function resolveFirstInstance(config: CloudConfig): Promise<ResolvedInstance | null>;
|
|
117
|
+
export declare function createUserToken(config: CloudConfig, name: string, projectAccess?: string): Promise<{
|
|
118
|
+
token: string;
|
|
119
|
+
userToken: UserTokenDto;
|
|
120
|
+
}>;
|
|
121
|
+
export declare function listUserTokens(config: CloudConfig): Promise<{
|
|
122
|
+
userTokens: UserTokenDto[];
|
|
123
|
+
}>;
|
|
124
|
+
export declare function revokeUserToken(config: CloudConfig, tokenId: string): Promise<void>;
|
|
125
|
+
export declare function updateUserToken(config: CloudConfig, tokenId: string, updates: {
|
|
126
|
+
name?: string;
|
|
127
|
+
projectAccess?: string;
|
|
128
|
+
}): Promise<{
|
|
129
|
+
userToken: UserTokenDto;
|
|
130
|
+
}>;
|
|
131
|
+
/**
|
|
132
|
+
* Extract the token ID from a full user token string (md_user_<hexId>_<secret> → utk_<hexId>).
|
|
133
|
+
*/
|
|
134
|
+
export declare function parseUserTokenId(userToken: string): string | null;
|
|
108
135
|
/**
|
|
109
136
|
* Fetch all cloud instances across all projects for the logged-in user.
|
|
110
137
|
* Returns an empty array if no projects or instances exist.
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
1
|
+
{"version":3,"file":"cloud-api.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-api.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/cloud-api.js
CHANGED
|
@@ -7,10 +7,14 @@ export class CloudApiError extends Error {
|
|
|
7
7
|
this.name = "CloudApiError";
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
|
+
function resolveAuthToken(config) {
|
|
11
|
+
return config.userToken ?? config.token ?? config.apiKey ?? "";
|
|
12
|
+
}
|
|
10
13
|
async function request(config, path, opts = {}) {
|
|
11
14
|
const url = `${config.url ?? DEFAULT_API_URL}${path}`;
|
|
15
|
+
const authToken = resolveAuthToken(config);
|
|
12
16
|
const headers = {
|
|
13
|
-
authorization: `Bearer ${
|
|
17
|
+
authorization: `Bearer ${authToken}`,
|
|
14
18
|
"content-type": "application/json",
|
|
15
19
|
};
|
|
16
20
|
const res = await fetch(url, {
|
|
@@ -137,6 +141,35 @@ export async function resolveFirstInstance(config) {
|
|
|
137
141
|
}
|
|
138
142
|
return null;
|
|
139
143
|
}
|
|
144
|
+
// ── User Token API ──────────────────────────────────────────────────
|
|
145
|
+
export async function createUserToken(config, name, projectAccess) {
|
|
146
|
+
return request(config, "/auth/user-tokens", {
|
|
147
|
+
method: "POST",
|
|
148
|
+
body: { name, projectAccess },
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export async function listUserTokens(config) {
|
|
152
|
+
return request(config, "/auth/user-tokens");
|
|
153
|
+
}
|
|
154
|
+
export async function revokeUserToken(config, tokenId) {
|
|
155
|
+
await request(config, `/auth/user-tokens/${tokenId}`, { method: "DELETE" });
|
|
156
|
+
}
|
|
157
|
+
export async function updateUserToken(config, tokenId, updates) {
|
|
158
|
+
return request(config, `/auth/user-tokens/${tokenId}`, {
|
|
159
|
+
method: "PATCH",
|
|
160
|
+
body: updates,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Extract the token ID from a full user token string (md_user_<hexId>_<secret> → utk_<hexId>).
|
|
165
|
+
*/
|
|
166
|
+
export function parseUserTokenId(userToken) {
|
|
167
|
+
const match = /^md_user_([a-f0-9]{20})_/.exec(userToken);
|
|
168
|
+
if (!match?.[1]) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return `utk_${match[1]}`;
|
|
172
|
+
}
|
|
140
173
|
/**
|
|
141
174
|
* Fetch all cloud instances across all projects for the logged-in user.
|
|
142
175
|
* Returns an empty array if no projects or instances exist.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type CloudConfig = {
|
|
2
|
-
token
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/** Primary credential — user token (md_user_*) created on login. */
|
|
3
|
+
userToken?: string;
|
|
4
|
+
/** Email of the logged-in user. */
|
|
5
5
|
email?: string;
|
|
6
|
+
/** API base URL (defaults to https://api.thingd.cloud). */
|
|
6
7
|
url?: string;
|
|
7
8
|
/** Currently active organization context (set by `thingd cloud org use`). */
|
|
8
9
|
organizationId?: string;
|
|
@@ -14,6 +15,10 @@ export type CloudConfig = {
|
|
|
14
15
|
projectSlug?: string;
|
|
15
16
|
/** Active instance slug (set when instanceUrl is resolved). */
|
|
16
17
|
instanceSlug?: string;
|
|
18
|
+
/** @deprecated Old JWT — still read as fallback if no userToken. */
|
|
19
|
+
token?: string;
|
|
20
|
+
/** @deprecated Old project API key — still read as fallback if no userToken or token. */
|
|
21
|
+
apiKey?: string;
|
|
17
22
|
};
|
|
18
23
|
export declare function cloudConfigPath(): string;
|
|
19
24
|
export declare function readCloudConfig(): CloudConfig | null;
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-config.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2DAA2D;IAC3D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8GAA8G;IAC9G,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yFAAyF;IACzF,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thingd/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.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.60.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.0.0"
|