@thingd/cli 0.40.2 → 0.41.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 +210 -21
- package/dist/lib/cloud-api.d.ts +42 -1
- package/dist/lib/cloud-api.d.ts.map +1 -1
- package/dist/lib/cloud-api.js +50 -2
- package/dist/lib/cloud-config.d.ts +2 -0
- 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":"AAGA,OAAO,EAAE,KAAK,UAAU,EAA6B,MAAM,aAAa,CAAC;AA6DzE,wBAAsB,QAAQ,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA+BjE"}
|
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, addOrganizationMember, createApiKey, createInstance, createOrganization, createProject, getMe, getOrganization, listInstances, listOrganizationMembers, listOrganizations, listProjects, pollCliAuth, removeOrganizationMember, 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) {
|
|
@@ -23,36 +59,84 @@ export async function runCloud(context) {
|
|
|
23
59
|
case "api-key":
|
|
24
60
|
await runApiKey(context);
|
|
25
61
|
return;
|
|
62
|
+
case "org":
|
|
63
|
+
await runOrg(context);
|
|
64
|
+
return;
|
|
26
65
|
default:
|
|
27
66
|
context.stderr.write(`Unknown cloud subcommand: ${sub}\n` +
|
|
28
|
-
"Available: login, logout, status, project, instance, api-key\n");
|
|
67
|
+
"Available: login, logout, status, org, project, instance, api-key\n");
|
|
29
68
|
}
|
|
30
69
|
}
|
|
31
70
|
async function runLogin(context) {
|
|
32
|
-
const code = context.parsed.tokens[2] ??
|
|
71
|
+
const code = context.parsed.tokens[2] ?? stringFlag(context.parsed, "code");
|
|
33
72
|
const token = context.parsed.tokens[3] ?? stringFlag(context.parsed, "token");
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
73
|
+
// ── Manual --code --token flow (fallback) ─────────────────────────
|
|
74
|
+
if (code && token) {
|
|
75
|
+
const config = { token, url: cliApiUrl(context) };
|
|
76
|
+
try {
|
|
77
|
+
const { user } = await getMe(config);
|
|
78
|
+
writeCloudConfig({ ...config, email: user.email });
|
|
79
|
+
context.stdout.write(pc.green(`✓ Logged in as ${user.email}\n`));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
if (err instanceof CloudApiError && err.status === 401) {
|
|
83
|
+
context.stderr.write(pc.red("Invalid token. Please try again.\n"));
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
context.stderr.write(pc.red(`Failed to verify token: ${err}\n`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
39
89
|
return;
|
|
40
90
|
}
|
|
41
|
-
//
|
|
42
|
-
const
|
|
91
|
+
// ── Auto device-code flow ─────────────────────────────────────────
|
|
92
|
+
const baseConfig = makeBaseConfig(context);
|
|
93
|
+
let deviceCode;
|
|
43
94
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
context.stdout.write(pc.green(`✓ Logged in as ${user.email}\n`));
|
|
95
|
+
const result = await startCliAuth(baseConfig);
|
|
96
|
+
deviceCode = result.code;
|
|
47
97
|
}
|
|
48
98
|
catch (err) {
|
|
49
|
-
|
|
50
|
-
|
|
99
|
+
context.stderr.write(pc.red(`Failed to start CLI auth: ${err}\n`));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const webUrl = buildWebUrl(cliApiUrl(context));
|
|
103
|
+
const authUrl = `${webUrl}/cli/auth?code=${deviceCode}`;
|
|
104
|
+
context.stdout.write(`\n ${pc.cyan("Opening browser...")}\n` +
|
|
105
|
+
` ${pc.dim("If the browser doesn't open, visit:")}\n` +
|
|
106
|
+
` ${pc.dim(authUrl)}\n\n`);
|
|
107
|
+
openBrowser(authUrl);
|
|
108
|
+
// Poll for token
|
|
109
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
110
|
+
let dots = 0;
|
|
111
|
+
while (Date.now() < deadline) {
|
|
112
|
+
await sleep(POLL_INTERVAL_MS);
|
|
113
|
+
try {
|
|
114
|
+
const result = await pollCliAuth(baseConfig, deviceCode);
|
|
115
|
+
if ("token" in result) {
|
|
116
|
+
const tokenConfig = { token: result.token, url: cliApiUrl(context) };
|
|
117
|
+
try {
|
|
118
|
+
const { user } = await getMe(tokenConfig);
|
|
119
|
+
writeCloudConfig({ ...tokenConfig, email: user.email });
|
|
120
|
+
context.stdout.write(pc.green(`\r✓ Logged in as ${user.email}\n`));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
context.stderr.write(pc.red("\rToken received but verification failed. Try again.\n"));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Show spinner
|
|
129
|
+
dots = (dots + 1) % 4;
|
|
130
|
+
context.stdout.write(`\r ${pc.dim(`Waiting for browser login${".".repeat(dots)}${" ".repeat(3 - dots)}`)}`);
|
|
51
131
|
}
|
|
52
|
-
|
|
53
|
-
|
|
132
|
+
catch (err) {
|
|
133
|
+
if (err instanceof CloudApiError && err.status === 410) {
|
|
134
|
+
context.stderr.write(pc.red("\nCode expired. Run `thingd cloud login` again.\n"));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
54
137
|
}
|
|
55
138
|
}
|
|
139
|
+
context.stderr.write(pc.red("\nTimed out. Run `thingd cloud login` again.\n"));
|
|
56
140
|
}
|
|
57
141
|
async function runLogout(context) {
|
|
58
142
|
removeCloudConfig();
|
|
@@ -68,11 +152,109 @@ async function runCloudStatus(context) {
|
|
|
68
152
|
const { user } = await getMe(config);
|
|
69
153
|
context.stdout.write(`Logged in as ${pc.green(user.email)} (${user.role})\n` +
|
|
70
154
|
`API: ${config.url ?? "https://api.thingd.cloud"}\n`);
|
|
155
|
+
if (config.organizationId) {
|
|
156
|
+
try {
|
|
157
|
+
const { organization, role } = await getOrganization(config, config.organizationId);
|
|
158
|
+
context.stdout.write(`Org: ${pc.cyan(organization.name)} (${pc.dim(organization.slug)}) — ${role}\n`);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
context.stdout.write(`Org: ${pc.dim("unreachable")}\n`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
context.stdout.write(`Org: ${pc.dim("none — set with thingd cloud org use <slug>")}\n`);
|
|
166
|
+
}
|
|
71
167
|
}
|
|
72
168
|
catch {
|
|
73
169
|
context.stdout.write(`Token expired. Run ${pc.cyan("thingd cloud login")}\n`);
|
|
74
170
|
}
|
|
75
171
|
}
|
|
172
|
+
// ── Org helpers ──────────────────────────────────────────────────────
|
|
173
|
+
async function resolveOrg(config, slugOrId) {
|
|
174
|
+
// Try direct ID lookup first, then assume it's a slug and list
|
|
175
|
+
try {
|
|
176
|
+
const { organization } = await getOrganization(config, slugOrId);
|
|
177
|
+
return organization;
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Fall through to slug resolution
|
|
181
|
+
}
|
|
182
|
+
const { organizations } = await listOrganizations(config);
|
|
183
|
+
const found = organizations.find((o) => o.slug === slugOrId);
|
|
184
|
+
if (!found) {
|
|
185
|
+
throw new Error(`Organization not found: ${slugOrId}`);
|
|
186
|
+
}
|
|
187
|
+
return found;
|
|
188
|
+
}
|
|
189
|
+
async function runOrg(context) {
|
|
190
|
+
const config = await requireConfig(context);
|
|
191
|
+
const action = context.parsed.tokens[2];
|
|
192
|
+
if (!action || action === "list") {
|
|
193
|
+
const { organizations } = await listOrganizations(config);
|
|
194
|
+
if (organizations.length === 0) {
|
|
195
|
+
context.stdout.write("No organizations found.\n");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (const org of organizations) {
|
|
199
|
+
const active = org.id === config.organizationId ? pc.green(" ●") : "";
|
|
200
|
+
context.stdout.write(`${pc.cyan(org.slug)} ${org.name}${active}\n`);
|
|
201
|
+
}
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (action === "create") {
|
|
205
|
+
const name = requiredToken(context.parsed, 3, "name");
|
|
206
|
+
const { organization } = await createOrganization(config, name);
|
|
207
|
+
context.stdout.write(pc.green(`✓ Created organization: ${organization.slug}\n`));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (action === "use") {
|
|
211
|
+
const slugOrId = requiredToken(context.parsed, 3, "organization");
|
|
212
|
+
const org = await resolveOrg(config, slugOrId);
|
|
213
|
+
config.organizationId = org.id;
|
|
214
|
+
writeCloudConfig(config);
|
|
215
|
+
context.stdout.write(pc.green(`✓ Active organization: ${org.slug}\n`));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (action === "members") {
|
|
219
|
+
await runOrgMembers(context, config);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
context.stderr.write(`Unknown org action: ${action}. Available: list, create, use, members\n`);
|
|
223
|
+
}
|
|
224
|
+
async function runOrgMembers(context, config) {
|
|
225
|
+
const sub = context.parsed.tokens[3];
|
|
226
|
+
if (!sub || sub === "list") {
|
|
227
|
+
const orgSlug = requiredToken(context.parsed, 4, "organization");
|
|
228
|
+
const org = await resolveOrg(config, orgSlug);
|
|
229
|
+
const { members } = await listOrganizationMembers(config, org.id);
|
|
230
|
+
if (members.length === 0) {
|
|
231
|
+
context.stdout.write("No members found.\n");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
for (const m of members) {
|
|
235
|
+
context.stdout.write(`${pc.cyan(m.userId)} ${m.role}\n`);
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (sub === "add") {
|
|
240
|
+
const orgSlug = requiredToken(context.parsed, 4, "organization");
|
|
241
|
+
const userId = requiredToken(context.parsed, 5, "user-id");
|
|
242
|
+
const role = stringFlag(context.parsed, "role") ?? "member";
|
|
243
|
+
const org = await resolveOrg(config, orgSlug);
|
|
244
|
+
const { member } = await addOrganizationMember(config, org.id, userId, role);
|
|
245
|
+
context.stdout.write(pc.green(`✓ Added ${member.userId} as ${member.role}\n`));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (sub === "remove") {
|
|
249
|
+
const orgSlug = requiredToken(context.parsed, 4, "organization");
|
|
250
|
+
const userId = requiredToken(context.parsed, 5, "user-id");
|
|
251
|
+
const org = await resolveOrg(config, orgSlug);
|
|
252
|
+
await removeOrganizationMember(config, org.id, userId);
|
|
253
|
+
context.stdout.write(pc.green(`✓ Removed ${userId}\n`));
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
context.stderr.write(`Unknown members action: ${sub}. Available: list, add, remove\n`);
|
|
257
|
+
}
|
|
76
258
|
async function requireConfig(context) {
|
|
77
259
|
const config = readCloudConfig();
|
|
78
260
|
if (!config) {
|
|
@@ -97,8 +279,16 @@ async function runProject(context) {
|
|
|
97
279
|
}
|
|
98
280
|
if (action === "create") {
|
|
99
281
|
const name = requiredToken(context.parsed, 3, "name");
|
|
100
|
-
|
|
101
|
-
|
|
282
|
+
// Optional --org flag for team projects
|
|
283
|
+
let organizationId;
|
|
284
|
+
const orgSlug = stringFlag(context.parsed, "org");
|
|
285
|
+
if (orgSlug) {
|
|
286
|
+
const org = await resolveOrg(config, orgSlug);
|
|
287
|
+
organizationId = org.id;
|
|
288
|
+
}
|
|
289
|
+
const { project } = await createProject(config, name, organizationId);
|
|
290
|
+
const ctxNote = organizationId ? ` (under org ${orgSlug})` : "";
|
|
291
|
+
context.stdout.write(pc.green(`✓ Created project: ${project.slug}${ctxNote}\n`));
|
|
102
292
|
return;
|
|
103
293
|
}
|
|
104
294
|
context.stderr.write(`Unknown project action: ${action}. Available: list, create\n`);
|
|
@@ -108,7 +298,6 @@ async function runInstance(context) {
|
|
|
108
298
|
const action = requiredToken(context.parsed, 2, "action");
|
|
109
299
|
if (action === "list") {
|
|
110
300
|
const projectSlug = requiredToken(context.parsed, 3, "project");
|
|
111
|
-
// Resolve project slug to ID by listing all projects
|
|
112
301
|
const { projects } = await listProjects(config);
|
|
113
302
|
const project = projects.find((p) => p.slug === projectSlug || p.id === projectSlug);
|
|
114
303
|
if (!project) {
|
package/dist/lib/cloud-api.d.ts
CHANGED
|
@@ -19,6 +19,20 @@ export type CloudApiKey = {
|
|
|
19
19
|
token?: string;
|
|
20
20
|
createdAt: string;
|
|
21
21
|
};
|
|
22
|
+
export type CloudOrganization = {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
slug: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
};
|
|
28
|
+
export type CloudOrganizationMember = {
|
|
29
|
+
id: string;
|
|
30
|
+
organizationId: string;
|
|
31
|
+
userId: string;
|
|
32
|
+
role: string;
|
|
33
|
+
invitedBy: string;
|
|
34
|
+
joinedAt: string;
|
|
35
|
+
};
|
|
22
36
|
export declare class CloudApiError extends Error {
|
|
23
37
|
status: number;
|
|
24
38
|
constructor(status: number, message: string);
|
|
@@ -34,7 +48,7 @@ export declare function getMe(config: CloudConfig): Promise<{
|
|
|
34
48
|
export declare function listProjects(config: CloudConfig): Promise<{
|
|
35
49
|
projects: CloudProject[];
|
|
36
50
|
}>;
|
|
37
|
-
export declare function createProject(config: CloudConfig, name: string): Promise<{
|
|
51
|
+
export declare function createProject(config: CloudConfig, name: string, organizationId?: string): Promise<{
|
|
38
52
|
project: CloudProject;
|
|
39
53
|
}>;
|
|
40
54
|
export declare function listInstances(config: CloudConfig, projectId: string): Promise<{
|
|
@@ -46,4 +60,31 @@ export declare function createInstance(config: CloudConfig, projectId: string, n
|
|
|
46
60
|
export declare function createApiKey(config: CloudConfig, projectId: string, name?: string): Promise<{
|
|
47
61
|
key: CloudApiKey;
|
|
48
62
|
}>;
|
|
63
|
+
export declare function createOrganization(config: CloudConfig, name: string): Promise<{
|
|
64
|
+
organization: CloudOrganization;
|
|
65
|
+
}>;
|
|
66
|
+
export declare function listOrganizations(config: CloudConfig): Promise<{
|
|
67
|
+
organizations: CloudOrganization[];
|
|
68
|
+
}>;
|
|
69
|
+
export declare function getOrganization(config: CloudConfig, orgId: string): Promise<{
|
|
70
|
+
organization: CloudOrganization;
|
|
71
|
+
role: string;
|
|
72
|
+
}>;
|
|
73
|
+
export declare function listOrganizationMembers(config: CloudConfig, orgId: string): Promise<{
|
|
74
|
+
members: CloudOrganizationMember[];
|
|
75
|
+
}>;
|
|
76
|
+
export declare function addOrganizationMember(config: CloudConfig, orgId: string, userId: string, role?: string): Promise<{
|
|
77
|
+
member: CloudOrganizationMember;
|
|
78
|
+
}>;
|
|
79
|
+
export declare function removeOrganizationMember(config: CloudConfig, orgId: string, userId: string): Promise<{
|
|
80
|
+
ok: boolean;
|
|
81
|
+
}>;
|
|
82
|
+
export declare function startCliAuth(config: CloudConfig): Promise<{
|
|
83
|
+
code: string;
|
|
84
|
+
}>;
|
|
85
|
+
export declare function pollCliAuth(config: CloudConfig, code: string): Promise<{
|
|
86
|
+
token: string;
|
|
87
|
+
} | {
|
|
88
|
+
status: string;
|
|
89
|
+
}>;
|
|
49
90
|
//# 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,
|
|
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;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,EACZ,cAAc,CAAC,EAAE,MAAM,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,YAAY,CAAA;CAAE,CAAC,CAMpC;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,SAAS,EAAE,aAAa,EAAE,CAAA;CAAE,CAAC,CAEzC;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,QAAQ,EAAE,aAAa,CAAA;CAAE,CAAC,CAKtC;AAED,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,GAAG,EAAE,WAAW,CAAA;CAAE,CAAC,CAK/B;AAID,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,YAAY,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAE9C;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC;IAAE,aAAa,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC,CAEjD;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,YAAY,EAAE,iBAAiB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAE5D;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC;IAAE,OAAO,EAAE,uBAAuB,EAAE,CAAA;CAAE,CAAC,CAEjD;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAAiB,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,uBAAuB,CAAA;CAAE,CAAC,CAK9C;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,CAAC,CAI1B;AAkBD,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAEjF;AAED,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAEjD"}
|
package/dist/lib/cloud-api.js
CHANGED
|
@@ -33,8 +33,12 @@ export async function getMe(config) {
|
|
|
33
33
|
export async function listProjects(config) {
|
|
34
34
|
return request(config, "/api/projects");
|
|
35
35
|
}
|
|
36
|
-
export async function createProject(config, name) {
|
|
37
|
-
|
|
36
|
+
export async function createProject(config, name, organizationId) {
|
|
37
|
+
const body = { name };
|
|
38
|
+
if (organizationId) {
|
|
39
|
+
body.organizationId = organizationId;
|
|
40
|
+
}
|
|
41
|
+
return request(config, "/api/projects", { method: "POST", body });
|
|
38
42
|
}
|
|
39
43
|
export async function listInstances(config, projectId) {
|
|
40
44
|
return request(config, `/api/projects/${projectId}/instances`);
|
|
@@ -51,3 +55,47 @@ export async function createApiKey(config, projectId, name) {
|
|
|
51
55
|
body: { name: name ?? "CLI key" },
|
|
52
56
|
});
|
|
53
57
|
}
|
|
58
|
+
// ── Organization API ─────────────────────────────────────────────────
|
|
59
|
+
export async function createOrganization(config, name) {
|
|
60
|
+
return request(config, "/api/organizations", { method: "POST", body: { name } });
|
|
61
|
+
}
|
|
62
|
+
export async function listOrganizations(config) {
|
|
63
|
+
return request(config, "/api/organizations");
|
|
64
|
+
}
|
|
65
|
+
export async function getOrganization(config, orgId) {
|
|
66
|
+
return request(config, `/api/organizations/${orgId}`);
|
|
67
|
+
}
|
|
68
|
+
export async function listOrganizationMembers(config, orgId) {
|
|
69
|
+
return request(config, `/api/organizations/${orgId}/members`);
|
|
70
|
+
}
|
|
71
|
+
export async function addOrganizationMember(config, orgId, userId, role = "member") {
|
|
72
|
+
return request(config, `/api/organizations/${orgId}/members`, {
|
|
73
|
+
method: "POST",
|
|
74
|
+
body: { userId, role },
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function removeOrganizationMember(config, orgId, userId) {
|
|
78
|
+
return request(config, `/api/organizations/${orgId}/members/${userId}`, {
|
|
79
|
+
method: "DELETE",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// ── CLI device code auth (unauthenticated) ──────────────────────────
|
|
83
|
+
async function requestUnauthenticated(apiUrl, path, body) {
|
|
84
|
+
const url = `${apiUrl}${path}`;
|
|
85
|
+
const res = await fetch(url, {
|
|
86
|
+
method: "POST",
|
|
87
|
+
headers: { "content-type": "application/json" },
|
|
88
|
+
body: JSON.stringify(body),
|
|
89
|
+
});
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
const errBody = await res.json().catch(() => ({ message: res.statusText }));
|
|
92
|
+
throw new CloudApiError(res.status, errBody.message ?? res.statusText);
|
|
93
|
+
}
|
|
94
|
+
return res.json();
|
|
95
|
+
}
|
|
96
|
+
export async function startCliAuth(config) {
|
|
97
|
+
return requestUnauthenticated(config.url ?? DEFAULT_API_URL, "/api/auth/cli/start", {});
|
|
98
|
+
}
|
|
99
|
+
export async function pollCliAuth(config, code) {
|
|
100
|
+
return requestUnauthenticated(config.url ?? DEFAULT_API_URL, "/api/auth/cli/poll", { code });
|
|
101
|
+
}
|
|
@@ -2,6 +2,8 @@ export type CloudConfig = {
|
|
|
2
2
|
token: string;
|
|
3
3
|
email?: string;
|
|
4
4
|
url?: string;
|
|
5
|
+
/** Currently active organization context (set by `thingd cloud org use`). */
|
|
6
|
+
organizationId?: string;
|
|
5
7
|
};
|
|
6
8
|
export declare function cloudConfigPath(): string;
|
|
7
9
|
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,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"cloud-config.d.ts","sourceRoot":"","sources":["../../src/lib/cloud-config.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6EAA6E;IAC7E,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,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,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.41.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.41.0"
|
|
49
49
|
},
|
|
50
50
|
"engines": {
|
|
51
51
|
"node": ">=24.0.0"
|