@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":"
|
|
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"}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -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 {
|
|
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] ??
|
|
68
|
+
const code = context.parsed.tokens[2] ?? stringFlag(context.parsed, "code");
|
|
33
69
|
const token = context.parsed.tokens[3] ?? stringFlag(context.parsed, "token");
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
//
|
|
42
|
-
const
|
|
88
|
+
// ── Auto device-code flow ─────────────────────────────────────────
|
|
89
|
+
const baseConfig = makeBaseConfig(context);
|
|
90
|
+
let deviceCode;
|
|
43
91
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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) {
|
package/dist/lib/cloud-api.d.ts
CHANGED
|
@@ -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"}
|
package/dist/lib/cloud-api.js
CHANGED
|
@@ -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.
|
|
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.
|
|
48
|
+
"@thingd/sdk": "0.40.3"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.0.0"
|