@thingd/cli 0.40.2 → 0.40.3

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":"AACA,OAAO,EAAE,KAAK,UAAU,EAA2C,MAAM,aAAa,CAAC;AAiBvF,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BjE"}
1
+ {"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../../src/commands/cloud.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,aAAa,CAAC;AAuDzE,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BjE"}
@@ -1,7 +1,43 @@
1
+ import { execSync } from "node:child_process";
2
+ import { setTimeout as sleep } from "node:timers/promises";
1
3
  import pc from "picocolors";
2
- import { requiredFlag, requiredToken, stringFlag } from "../index.js";
3
- import { CloudApiError, createApiKey, createInstance, createProject, getMe, listInstances, listProjects, } from "../lib/cloud-api.js";
4
+ import { requiredToken, stringFlag } from "../index.js";
5
+ import { CloudApiError, createApiKey, createInstance, createProject, getMe, listInstances, listProjects, pollCliAuth, startCliAuth, } from "../lib/cloud-api.js";
4
6
  import { readCloudConfig, removeCloudConfig, writeCloudConfig, } from "../lib/cloud-config.js";
7
+ const POLL_INTERVAL_MS = 2_000;
8
+ const POLL_TIMEOUT_MS = 5 * 60 * 1_000;
9
+ function cliApiUrl(context) {
10
+ return stringFlag(context.parsed, "url") ?? process.env.THINGD_URL ?? "https://api.thingd.cloud";
11
+ }
12
+ function buildWebUrl(apiUrl) {
13
+ if (apiUrl.includes("//api.") || apiUrl.startsWith("api.")) {
14
+ return apiUrl.replace("api.", "");
15
+ }
16
+ if (apiUrl.includes("localhost:8787") || apiUrl.includes("127.0.0.1:8787")) {
17
+ return "http://localhost:5173";
18
+ }
19
+ return apiUrl;
20
+ }
21
+ function openBrowser(url) {
22
+ try {
23
+ const platform = process.platform;
24
+ if (platform === "darwin") {
25
+ execSync(`open "${url}"`, { timeout: 5_000 });
26
+ }
27
+ else if (platform === "win32") {
28
+ execSync(`start "" "${url}"`, { timeout: 5_000 });
29
+ }
30
+ else {
31
+ execSync(`xdg-open "${url}"`, { timeout: 5_000 });
32
+ }
33
+ }
34
+ catch {
35
+ // browser open is best-effort
36
+ }
37
+ }
38
+ function makeBaseConfig(context) {
39
+ return { token: "", url: cliApiUrl(context) };
40
+ }
5
41
  export async function runCloud(context) {
6
42
  const sub = requiredToken(context.parsed, 1, "subcommand");
7
43
  switch (sub) {
@@ -29,30 +65,75 @@ export async function runCloud(context) {
29
65
  }
30
66
  }
31
67
  async function runLogin(context) {
32
- const code = context.parsed.tokens[2] ?? requiredFlag(context.parsed, "code");
68
+ const code = context.parsed.tokens[2] ?? stringFlag(context.parsed, "code");
33
69
  const token = context.parsed.tokens[3] ?? stringFlag(context.parsed, "token");
34
- if (!token) {
35
- context.stdout.write(`First, open this URL in your browser:\n\n` +
36
- ` ${pc.cyan(`https://thingd.cloud/cli/auth?code=${code}`)}\n\n` +
37
- `Then paste the token shown after logging in:\n\n` +
38
- ` ${pc.dim("$ thingd cloud login --code <code> --token <token>")}\n`);
70
+ // ── Manual --code --token flow (fallback) ─────────────────────────
71
+ if (code && token) {
72
+ const config = { token, url: cliApiUrl(context) };
73
+ try {
74
+ const { user } = await getMe(config);
75
+ writeCloudConfig({ ...config, email: user.email });
76
+ context.stdout.write(pc.green(`✓ Logged in as ${user.email}\n`));
77
+ }
78
+ catch (err) {
79
+ if (err instanceof CloudApiError && err.status === 401) {
80
+ context.stderr.write(pc.red("Invalid token. Please try again.\n"));
81
+ }
82
+ else {
83
+ context.stderr.write(pc.red(`Failed to verify token: ${err}\n`));
84
+ }
85
+ }
39
86
  return;
40
87
  }
41
- // Verify token by calling /api/users/me
42
- const config = { token };
88
+ // ── Auto device-code flow ─────────────────────────────────────────
89
+ const baseConfig = makeBaseConfig(context);
90
+ let deviceCode;
43
91
  try {
44
- const { user } = await getMe(config);
45
- writeCloudConfig({ ...config, email: user.email });
46
- context.stdout.write(pc.green(`✓ Logged in as ${user.email}\n`));
92
+ const result = await startCliAuth(baseConfig);
93
+ deviceCode = result.code;
47
94
  }
48
95
  catch (err) {
49
- if (err instanceof CloudApiError && err.status === 401) {
50
- context.stderr.write(pc.red("Invalid token. Please try again.\n"));
96
+ context.stderr.write(pc.red(`Failed to start CLI auth: ${err}\n`));
97
+ return;
98
+ }
99
+ const webUrl = buildWebUrl(cliApiUrl(context));
100
+ const authUrl = `${webUrl}/cli/auth?code=${deviceCode}`;
101
+ context.stdout.write(`\n ${pc.cyan("Opening browser...")}\n` +
102
+ ` ${pc.dim("If the browser doesn't open, visit:")}\n` +
103
+ ` ${pc.dim(authUrl)}\n\n`);
104
+ openBrowser(authUrl);
105
+ // Poll for token
106
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
107
+ let dots = 0;
108
+ while (Date.now() < deadline) {
109
+ await sleep(POLL_INTERVAL_MS);
110
+ try {
111
+ const result = await pollCliAuth(baseConfig, deviceCode);
112
+ if ("token" in result) {
113
+ const tokenConfig = { token: result.token, url: cliApiUrl(context) };
114
+ try {
115
+ const { user } = await getMe(tokenConfig);
116
+ writeCloudConfig({ ...tokenConfig, email: user.email });
117
+ context.stdout.write(pc.green(`\r✓ Logged in as ${user.email}\n`));
118
+ return;
119
+ }
120
+ catch {
121
+ context.stderr.write(pc.red("\rToken received but verification failed. Try again.\n"));
122
+ return;
123
+ }
124
+ }
125
+ // Show spinner
126
+ dots = (dots + 1) % 4;
127
+ context.stdout.write(`\r ${pc.dim(`Waiting for browser login${".".repeat(dots)}${" ".repeat(3 - dots)}`)}`);
51
128
  }
52
- else {
53
- context.stderr.write(pc.red(`Failed to verify token: ${err}\n`));
129
+ catch (err) {
130
+ if (err instanceof CloudApiError && err.status === 410) {
131
+ context.stderr.write(pc.red("\nCode expired. Run `thingd cloud login` again.\n"));
132
+ return;
133
+ }
54
134
  }
55
135
  }
136
+ context.stderr.write(pc.red("\nTimed out. Run `thingd cloud login` again.\n"));
56
137
  }
57
138
  async function runLogout(context) {
58
139
  removeCloudConfig();
@@ -108,7 +189,6 @@ async function runInstance(context) {
108
189
  const action = requiredToken(context.parsed, 2, "action");
109
190
  if (action === "list") {
110
191
  const projectSlug = requiredToken(context.parsed, 3, "project");
111
- // Resolve project slug to ID by listing all projects
112
192
  const { projects } = await listProjects(config);
113
193
  const project = projects.find((p) => p.slug === projectSlug || p.id === projectSlug);
114
194
  if (!project) {
@@ -46,4 +46,12 @@ export declare function createInstance(config: CloudConfig, projectId: string, n
46
46
  export declare function createApiKey(config: CloudConfig, projectId: string, name?: string): Promise<{
47
47
  key: CloudApiKey;
48
48
  }>;
49
+ export declare function startCliAuth(config: CloudConfig): Promise<{
50
+ code: string;
51
+ }>;
52
+ export declare function pollCliAuth(config: CloudConfig, code: string): Promise<{
53
+ token: string;
54
+ } | {
55
+ status: string;
56
+ }>;
49
57
  //# 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":"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,qBAAa,aAAc,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAC;gBAEH,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;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,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAEpC;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"}
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,qBAAa,aAAc,SAAQ,KAAK;IACtC,MAAM,EAAE,MAAM,CAAC;gBAEH,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;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,GACX,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAEpC;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;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"}
@@ -51,3 +51,23 @@ export async function createApiKey(config, projectId, name) {
51
51
  body: { name: name ?? "CLI key" },
52
52
  });
53
53
  }
54
+ // ── CLI device code auth (unauthenticated) ──────────────────────────
55
+ async function requestUnauthenticated(apiUrl, path, body) {
56
+ const url = `${apiUrl}${path}`;
57
+ const res = await fetch(url, {
58
+ method: "POST",
59
+ headers: { "content-type": "application/json" },
60
+ body: JSON.stringify(body),
61
+ });
62
+ if (!res.ok) {
63
+ const errBody = await res.json().catch(() => ({ message: res.statusText }));
64
+ throw new CloudApiError(res.status, errBody.message ?? res.statusText);
65
+ }
66
+ return res.json();
67
+ }
68
+ export async function startCliAuth(config) {
69
+ return requestUnauthenticated(config.url ?? DEFAULT_API_URL, "/api/auth/cli/start", {});
70
+ }
71
+ export async function pollCliAuth(config, code) {
72
+ return requestUnauthenticated(config.url ?? DEFAULT_API_URL, "/api/auth/cli/poll", { code });
73
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thingd/cli",
3
- "version": "0.40.2",
3
+ "version": "0.40.3",
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.40.2"
48
+ "@thingd/sdk": "0.40.3"
49
49
  },
50
50
  "engines": {
51
51
  "node": ">=24.0.0"