@thingd/cli 0.59.0 → 0.61.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.
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/commands/cloud.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,aAAa,CAAC;AAyIzE,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BjE"}
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/commands/cloud.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,aAAa,CAAC;AAkHzE,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCjE"}
@@ -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, cleanupSessions, 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 { token: "", url: cliApiUrl(context) };
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 = { token, url: cliApiUrl(context) };
119
+ const config = { url: cliApiUrl(context) };
144
120
  try {
145
- const { user } = await getMe(config);
146
- const cloudConfig = { ...config, email: user.email };
147
- // Save config immediately so login always persists
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 tokenConfig = { token: result.token, url: cliApiUrl(context) };
168
+ const jwt = result.token;
169
+ const tokenConfig = { url: cliApiUrl(context) };
191
170
  try {
192
- const { user } = await getMe(tokenConfig);
193
- const cloudConfig = { ...tokenConfig, email: user.email };
194
- // Save config immediately so login always persists
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
- `API: ${config.url ?? "https://api.thingd.cloud"}\n`);
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,173 @@ 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
+ }
279
+ if (s < 60) {
280
+ return `${s}s ago`;
281
+ }
282
+ const m = Math.floor(s / 60);
283
+ if (m < 60) {
284
+ return `${m}m ago`;
285
+ }
286
+ const h = Math.floor(m / 60);
287
+ if (h < 24) {
288
+ return `${h}h ago`;
289
+ }
290
+ const d = Math.floor(h / 24);
291
+ return `${d}d ago`;
292
+ }
293
+ // ── Token subcommands ────────────────────────────────────────────────
294
+ function requireLoggedInConfig(context) {
295
+ const config = readCloudConfig();
296
+ if (!config?.userToken && !config?.token) {
297
+ context.stderr.write(pc.yellow("Not logged in. Run thingd cloud login first.\n"));
298
+ throw new Error("not_logged_in");
299
+ }
300
+ return config;
301
+ }
302
+ async function runToken(context) {
303
+ const sub = context.parsed.tokens[2];
304
+ if (!sub) {
305
+ context.stderr.write("Usage: thingd cloud token <list|create|revoke|restrict|unrestricted>\n");
306
+ return;
307
+ }
308
+ switch (sub) {
309
+ case "list":
310
+ await runTokenList(context);
311
+ return;
312
+ case "create":
313
+ await runTokenCreate(context);
314
+ return;
315
+ case "revoke":
316
+ await runTokenRevoke(context);
317
+ return;
318
+ case "restrict":
319
+ await runTokenRestrict(context);
320
+ return;
321
+ case "unrestricted":
322
+ await runTokenUnrestricted(context);
323
+ return;
324
+ case "cleanup":
325
+ await runTokenCleanup(context);
326
+ return;
327
+ default:
328
+ context.stderr.write(`Unknown token action: ${sub}. Available: list, create, revoke, restrict, unrestricted, cleanup\n`);
329
+ }
330
+ }
331
+ async function runTokenCleanup(context) {
332
+ const config = requireLoggedInConfig(context);
333
+ try {
334
+ // Clean up expired sessions on the user's account
335
+ const { removed } = await cleanupSessions(config);
336
+ context.stdout.write(pc.green(`✓ Cleaned up ${removed} expired sessions\n`));
337
+ }
338
+ catch (err) {
339
+ context.stderr.write(pc.red(`Failed to clean up sessions: ${err}\n`));
340
+ }
341
+ }
342
+ async function runTokenList(context) {
343
+ const config = requireLoggedInConfig(context);
344
+ try {
345
+ const { userTokens } = await listUserTokens(config);
346
+ if (userTokens.length === 0) {
347
+ context.stdout.write("No CLI tokens found. Create one with `thingd cloud token create <name>`\n");
348
+ return;
349
+ }
350
+ // Header
351
+ 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")}`;
352
+ context.stdout.write(`${header}\n`);
353
+ context.stdout.write(`${pc.dim("─".repeat(header.length))}\n`);
354
+ for (const t of userTokens) {
355
+ if (t.revokedAt) {
356
+ continue; // Skip revoked tokens
357
+ }
358
+ const access = t.projectAccess === "all" ? "All" : t.projectAccess;
359
+ 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`);
360
+ }
361
+ }
362
+ catch (err) {
363
+ context.stderr.write(pc.red(`Failed to list tokens: ${err}\n`));
364
+ }
365
+ }
366
+ async function runTokenCreate(context) {
367
+ const config = requireLoggedInConfig(context);
368
+ const name = context.parsed.tokens[3] ?? "cli-token";
369
+ try {
370
+ const { token: userToken } = await createUserToken(config, name);
371
+ context.stdout.write(`\n${pc.green("✓ Token created")}\n\n` +
372
+ `${pc.bold(userToken)}\n\n` +
373
+ `${pc.yellow("⚠ This token will only be shown once. Copy it now.\n")}` +
374
+ `${pc.dim("Press Enter to continue...")}\n`);
375
+ // Wait for user to acknowledge
376
+ const rl = createInterface({
377
+ input: context.stdin,
378
+ output: context.stderr,
379
+ });
380
+ await rl.question("");
381
+ rl.close();
382
+ }
383
+ catch (err) {
384
+ context.stderr.write(pc.red(`Failed to create token: ${err}\n`));
385
+ }
386
+ }
387
+ async function runTokenRevoke(context) {
388
+ const config = requireLoggedInConfig(context);
389
+ const tokenId = requiredToken(context.parsed, 3, "token-id");
390
+ try {
391
+ await revokeUserToken(config, tokenId);
392
+ context.stdout.write(pc.green(`✓ Token ${tokenId} revoked\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 revoke token: ${err}\n`));
400
+ }
401
+ }
402
+ }
403
+ async function runTokenRestrict(context) {
404
+ const config = requireLoggedInConfig(context);
405
+ const tokenId = requiredToken(context.parsed, 3, "token-id");
406
+ const projectSlug = requiredToken(context.parsed, 4, "project");
407
+ try {
408
+ const { userToken } = await updateUserToken(config, tokenId, {
409
+ projectAccess: projectSlug,
410
+ });
411
+ context.stdout.write(pc.green(`✓ Token "${userToken.name}" restricted to: ${projectSlug}\n`));
412
+ }
413
+ catch (err) {
414
+ if (err instanceof CloudApiError && err.status === 404) {
415
+ context.stderr.write(pc.red("Token not found.\n"));
416
+ }
417
+ else {
418
+ context.stderr.write(pc.red(`Failed to update token: ${err}\n`));
419
+ }
420
+ }
421
+ }
422
+ async function runTokenUnrestricted(context) {
423
+ const config = requireLoggedInConfig(context);
424
+ const tokenId = requiredToken(context.parsed, 3, "token-id");
425
+ try {
426
+ const { userToken } = await updateUserToken(config, tokenId, {
427
+ projectAccess: "all",
428
+ });
429
+ context.stdout.write(pc.green(`✓ Token "${userToken.name}" now has access to all projects\n`));
430
+ }
431
+ catch (err) {
432
+ if (err instanceof CloudApiError && err.status === 404) {
433
+ context.stderr.write(pc.red("Token not found.\n"));
434
+ }
435
+ else {
436
+ context.stderr.write(pc.red(`Failed to update token: ${err}\n`));
437
+ }
438
+ }
439
+ }
259
440
  // ── Org helpers ──────────────────────────────────────────────────────
260
441
  async function resolveOrg(config, slugOrId) {
261
442
  // Try direct ID lookup first, then assume it's a slug and list
@@ -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,CAqDxE;AAiDD,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjE;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/E;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAGlF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrE;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMrF;AAyFD,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAElE"}
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":"AAoxGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
1
+ {"version":3,"file":"interactive.d.ts","sourceRoot":"","sources":["../src/interactive.ts"],"names":[],"mappings":"AAqxGA,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA0FvD"}
@@ -824,7 +824,8 @@ function scheduleLoad(node) {
824
824
  : d === "native"
825
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.")}`
826
826
  : (() => {
827
- const hasCfg = !!readCloudConfig()?.token;
827
+ const cfg = readCloudConfig();
828
+ const hasCfg = !!(cfg?.userToken ?? cfg?.token);
828
829
  return hasCfg
829
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.")}`
830
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.`;
@@ -2731,14 +2732,14 @@ async function handleConnect(node) {
2731
2732
  cloudCfg.projectSlug = selected.projectSlug;
2732
2733
  cloudCfg.instanceSlug = selected.instanceSlug;
2733
2734
  writeCloudConfig(cloudCfg);
2734
- 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);
2735
2736
  });
2736
2737
  return;
2737
2738
  }
2738
2739
  // No instances found — try saved URL or show error
2739
2740
  if (cloudCfg.instanceUrl) {
2740
2741
  const restUrl = deriveRestUrl(cloudCfg.instanceUrl);
2741
- 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);
2742
2743
  return;
2743
2744
  }
2744
2745
  viewerLines = [
@@ -2751,7 +2752,7 @@ async function handleConnect(node) {
2751
2752
  catch {
2752
2753
  if (cloudCfg.instanceUrl) {
2753
2754
  const restUrl = deriveRestUrl(cloudCfg.instanceUrl);
2754
- 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);
2755
2756
  return;
2756
2757
  }
2757
2758
  viewerLines = [
@@ -2967,12 +2968,12 @@ export async function runInteractiveCli() {
2967
2968
  }
2968
2969
  // Auto-connect to cloud if credentials exist
2969
2970
  const cloudCfg = readCloudConfig();
2970
- if (cloudCfg?.token) {
2971
+ if (cloudCfg?.userToken ?? cloudCfg?.token) {
2971
2972
  const cloudUrl = resolveCloudUrl(cloudCfg);
2972
2973
  if (cloudUrl) {
2973
2974
  try {
2974
2975
  const restUrl = deriveRestUrl(cloudUrl);
2975
- 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);
2976
2977
  }
2977
2978
  catch (err) {
2978
2979
  const msg = err instanceof Error ? err.message : String(err);
@@ -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,27 @@ 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 cleanupSessions(config: CloudConfig): Promise<{
135
+ removed: number;
136
+ }>;
137
+ export declare function parseUserTokenId(userToken: string): string | null;
108
138
  /**
109
139
  * Fetch all cloud instances across all projects for the logged-in user.
110
140
  * 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;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,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAK9C;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,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAuBhG;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAyB1F"}
1
+ {"version":3,"file":"cloud-api.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-api.ts"],"names":[],"mappings":"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"}
@@ -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 ${config.token}`,
17
+ authorization: `Bearer ${authToken}`,
14
18
  "content-type": "application/json",
15
19
  };
16
20
  const res = await fetch(url, {
@@ -137,6 +141,38 @@ 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 async function cleanupSessions(config) {
167
+ return request(config, "/auth/sessions/cleanup", { method: "DELETE" });
168
+ }
169
+ export function parseUserTokenId(userToken) {
170
+ const match = /^md_user_([a-f0-9]{20})_/.exec(userToken);
171
+ if (!match?.[1]) {
172
+ return null;
173
+ }
174
+ return `utk_${match[1]}`;
175
+ }
140
176
  /**
141
177
  * Fetch all cloud instances across all projects for the logged-in user.
142
178
  * Returns an empty array if no projects or instances exist.
@@ -1,8 +1,9 @@
1
1
  export type CloudConfig = {
2
- token: string;
3
- /** API key for data plane operations (REST/MCP) — created after login. */
4
- apiKey?: string;
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,KAAK,EAAE,MAAM,CAAC;IACd,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,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,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;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"}
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.59.0",
3
+ "version": "0.61.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.59.0"
48
+ "@thingd/sdk": "0.61.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24.0.0"