Skip to content

Commit 3db5e3e

Browse files
committed
feat: add dynamic suggestions
1 parent c3dd90f commit 3db5e3e

File tree

1 file changed

+231
-1
lines changed

1 file changed

+231
-1
lines changed

src/turso.ts

Lines changed: 231 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,218 @@
1+
interface Organization {
2+
name: string;
3+
slug: string;
4+
type: string;
5+
overages: boolean;
6+
blocked_reads: boolean;
7+
blocked_writes: boolean;
8+
plan_id: string;
9+
plan_timeline: string;
10+
platform: string;
11+
}
12+
13+
interface Database {
14+
Name: string;
15+
DbId: string;
16+
Hostname: string;
17+
block_reads: boolean;
18+
block_writes: boolean;
19+
regions: string[];
20+
primaryRegion: string;
21+
group: string;
22+
delete_protection: boolean;
23+
parent?: {
24+
id: string;
25+
name: string;
26+
branched_at: string;
27+
};
28+
}
29+
30+
interface DatabasesResponse {
31+
databases: Database[];
32+
}
33+
34+
interface Group {
35+
name: string;
36+
version: string;
37+
uuid: string;
38+
locations: string[];
39+
primary: string;
40+
delete_protection: boolean;
41+
}
42+
43+
interface GroupsResponse {
44+
groups: Group[];
45+
}
46+
47+
async function getAuthToken(
48+
executeCommand: (
49+
args: Fig.ExecuteCommandInput
50+
) => Promise<Fig.ExecuteCommandOutput>
51+
): Promise<string | null> {
52+
try {
53+
const { stdout } = await executeCommand({
54+
command: "turso",
55+
args: ["auth", "token"],
56+
});
57+
58+
// Extract JWT token using regex
59+
const tokenMatch = stdout.match(
60+
/ey[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/
61+
);
62+
return tokenMatch ? tokenMatch[0] : null;
63+
} catch (error) {
64+
return null;
65+
}
66+
}
67+
68+
async function fetchOrganizations(
69+
token: string,
70+
executeCommand: (
71+
args: Fig.ExecuteCommandInput
72+
) => Promise<Fig.ExecuteCommandOutput>
73+
): Promise<Organization[]> {
74+
try {
75+
const { stdout, stderr } = await executeCommand({
76+
command: "curl",
77+
args: [
78+
"-s",
79+
"-H",
80+
`Authorization: Bearer ${token}`,
81+
"https://api.turso.tech/v1/organizations",
82+
],
83+
});
84+
85+
if (stderr) return [];
86+
87+
return JSON.parse(stdout);
88+
} catch (error) {
89+
return [];
90+
}
91+
}
92+
93+
async function fetchDatabases(
94+
token: string,
95+
orgSlug: string,
96+
executeCommand: (
97+
args: Fig.ExecuteCommandInput
98+
) => Promise<Fig.ExecuteCommandOutput>
99+
): Promise<DatabasesResponse> {
100+
try {
101+
const { stdout, stderr } = await executeCommand({
102+
command: "curl",
103+
args: [
104+
"-s",
105+
"-H",
106+
`Authorization: Bearer ${token}`,
107+
`https://api.turso.tech/v1/organizations/${orgSlug}/databases`,
108+
],
109+
});
110+
111+
if (stderr) return { databases: [] };
112+
113+
return JSON.parse(stdout);
114+
} catch (error) {
115+
return { databases: [] };
116+
}
117+
}
118+
119+
async function fetchGroups(
120+
token: string,
121+
orgSlug: string,
122+
executeCommand: (
123+
args: Fig.ExecuteCommandInput
124+
) => Promise<Fig.ExecuteCommandOutput>
125+
): Promise<GroupsResponse> {
126+
try {
127+
const { stdout, stderr } = await executeCommand({
128+
command: "curl",
129+
args: [
130+
"-s",
131+
"-H",
132+
`Authorization: Bearer ${token}`,
133+
`https://api.turso.tech/v1/organizations/${orgSlug}/groups`,
134+
],
135+
});
136+
137+
if (stderr) return { groups: [] };
138+
139+
return JSON.parse(stdout);
140+
} catch (error) {
141+
return { groups: [] };
142+
}
143+
}
144+
145+
// Reusable generators for autocompletion
146+
const databaseGenerator: Fig.Generator = {
147+
custom: async (tokens, executeCommand) => {
148+
try {
149+
const token = await getAuthToken(executeCommand);
150+
if (!token) return [];
151+
152+
const organizations = await fetchOrganizations(token, executeCommand);
153+
if (!organizations || organizations.length === 0) return [];
154+
155+
const orgSlug = organizations[0].slug;
156+
const databases = await fetchDatabases(token, orgSlug, executeCommand);
157+
158+
if (!databases || !databases.databases) return [];
159+
160+
return databases.databases.map((db) => ({
161+
name: db.Name,
162+
description: `Database in ${db.regions.join(", ")} regions`,
163+
icon: "fig://icon?type=box",
164+
}));
165+
} catch (error) {
166+
return [];
167+
}
168+
},
169+
};
170+
171+
const groupGenerator: Fig.Generator = {
172+
custom: async (tokens, executeCommand) => {
173+
try {
174+
const token = await getAuthToken(executeCommand);
175+
if (!token) return [];
176+
177+
const organizations = await fetchOrganizations(token, executeCommand);
178+
if (!organizations || organizations.length === 0) return [];
179+
180+
const orgSlug = organizations[0].slug;
181+
const groups = await fetchGroups(token, orgSlug, executeCommand);
182+
183+
if (!groups || !groups.groups) return [];
184+
185+
return groups.groups.map((group) => ({
186+
name: group.name,
187+
description: `Group in ${group.locations.join(", ")} locations`,
188+
icon: "fig://icon?type=box",
189+
}));
190+
} catch (error) {
191+
return [];
192+
}
193+
},
194+
};
195+
196+
const organizationGenerator: Fig.Generator = {
197+
custom: async (tokens, executeCommand) => {
198+
try {
199+
const token = await getAuthToken(executeCommand);
200+
if (!token) return [];
201+
202+
const organizations = await fetchOrganizations(token, executeCommand);
203+
if (!organizations || organizations.length === 0) return [];
204+
205+
return organizations.map((org) => ({
206+
name: org.slug,
207+
description: `${org.name} (${org.type}, ${org.plan_id})`,
208+
icon: "fig://icon?type=box",
209+
}));
210+
} catch (error) {
211+
return [];
212+
}
213+
},
214+
};
215+
1216
const completionSpec: Fig.Spec = {
2217
name: "turso",
3218
description: "Turso CLI for managing Turso databases",
@@ -38,6 +253,9 @@ const completionSpec: Fig.Spec = {
38253
{
39254
name: "revoke",
40255
description: "Revoke an API token",
256+
args: {
257+
name: "api-token-name",
258+
},
41259
},
42260
],
43261
},
@@ -90,7 +308,7 @@ const completionSpec: Fig.Spec = {
90308
name: "autoupdate",
91309
description: "Configure autoupdate behavior",
92310
args: {
93-
name: "on|off",
311+
suggestions: ["on", "off"],
94312
},
95313
},
96314
{
@@ -134,13 +352,15 @@ const completionSpec: Fig.Spec = {
134352
description: "Destroy a database",
135353
args: {
136354
name: "name",
355+
generators: databaseGenerator,
137356
},
138357
},
139358
{
140359
name: "export",
141360
description: "Export a database snapshot from Turso to SQLite file",
142361
args: {
143362
name: "name",
363+
generators: databaseGenerator,
144364
},
145365
},
146366
{
@@ -157,6 +377,8 @@ const completionSpec: Fig.Spec = {
157377
description: "Inspect database",
158378
args: {
159379
name: "name",
380+
isVariadic: true,
381+
generators: databaseGenerator,
160382
},
161383
},
162384
{
@@ -172,6 +394,7 @@ const completionSpec: Fig.Spec = {
172394
description: "Replicate a database",
173395
args: {
174396
name: "name",
397+
generators: databaseGenerator,
175398
},
176399
},
177400
{
@@ -183,6 +406,7 @@ const completionSpec: Fig.Spec = {
183406
description: "Show information from a database",
184407
args: {
185408
name: "name",
409+
generators: databaseGenerator,
186410
},
187411
},
188412
{
@@ -198,6 +422,7 @@ const completionSpec: Fig.Spec = {
198422
description: "Updates the database to the latest turso version",
199423
args: {
200424
name: "name",
425+
generators: databaseGenerator,
201426
},
202427
},
203428
],
@@ -215,6 +440,7 @@ const completionSpec: Fig.Spec = {
215440
description: "Manage group config",
216441
args: {
217442
name: "name",
443+
generators: groupGenerator,
218444
},
219445
},
220446
{
@@ -229,6 +455,7 @@ const completionSpec: Fig.Spec = {
229455
description: "Destroy a database group",
230456
args: {
231457
name: "name",
458+
generators: groupGenerator,
232459
},
233460
},
234461
{
@@ -259,13 +486,15 @@ const completionSpec: Fig.Spec = {
259486
description: "Unarchive a database group",
260487
args: {
261488
name: "name",
489+
generators: groupGenerator,
262490
},
263491
},
264492
{
265493
name: "update",
266494
description: "Updates the group",
267495
args: {
268496
name: "name",
497+
generators: groupGenerator,
269498
},
270499
},
271500
],
@@ -391,6 +620,7 @@ const completionSpec: Fig.Spec = {
391620
"Switch to an organization as the context for your commands",
392621
args: {
393622
name: "organization",
623+
generators: organizationGenerator,
394624
},
395625
},
396626
],

0 commit comments

Comments
 (0)