@agentlink.sh/cli 0.10.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/README.md +345 -0
- package/dist/chunk-DOWH47I5.js +592 -0
- package/dist/chunk-G3HVUCWY.js +62 -0
- package/dist/cloud-N7CILHLD.js +46 -0
- package/dist/index.js +4989 -0
- package/dist/oauth-J4X62CY4.js +199 -0
- package/package.json +32 -0
|
@@ -0,0 +1,592 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
amber,
|
|
4
|
+
blue,
|
|
5
|
+
bold,
|
|
6
|
+
dim,
|
|
7
|
+
link,
|
|
8
|
+
red,
|
|
9
|
+
sb
|
|
10
|
+
} from "./chunk-G3HVUCWY.js";
|
|
11
|
+
|
|
12
|
+
// src/cloud.ts
|
|
13
|
+
import { execSync as execSync2 } from "child_process";
|
|
14
|
+
import fs2 from "fs";
|
|
15
|
+
import os from "os";
|
|
16
|
+
import path from "path";
|
|
17
|
+
import { confirm, input, select } from "@inquirer/prompts";
|
|
18
|
+
|
|
19
|
+
// src/utils.ts
|
|
20
|
+
import { execSync, spawn } from "child_process";
|
|
21
|
+
import crypto from "crypto";
|
|
22
|
+
import fs from "fs";
|
|
23
|
+
var LOG_FILE = "agentlink-debug.log";
|
|
24
|
+
var debugEnabled = false;
|
|
25
|
+
var logInitialized = false;
|
|
26
|
+
function ensureLogFile() {
|
|
27
|
+
if (!logInitialized) {
|
|
28
|
+
fs.writeFileSync(LOG_FILE, "");
|
|
29
|
+
logInitialized = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function appendLog(msg, force = false) {
|
|
33
|
+
if (!debugEnabled && !force) return;
|
|
34
|
+
ensureLogFile();
|
|
35
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
36
|
+
fs.appendFileSync(LOG_FILE, `[${timestamp}] ${msg}
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
function initLog(debug) {
|
|
40
|
+
debugEnabled = debug;
|
|
41
|
+
if (debug) {
|
|
42
|
+
ensureLogFile();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function runCommand(cmd, cwd, onData, env) {
|
|
46
|
+
appendLog(`$ ${cmd}${cwd ? ` (in ${cwd})` : ""}`);
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const child = spawn("sh", ["-c", cmd], {
|
|
49
|
+
cwd,
|
|
50
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51
|
+
env: env ? { ...process.env, ...env } : void 0
|
|
52
|
+
});
|
|
53
|
+
const stdout = [];
|
|
54
|
+
const stderr = [];
|
|
55
|
+
child.stdout.on("data", (data) => {
|
|
56
|
+
const text = data.toString();
|
|
57
|
+
stdout.push(text);
|
|
58
|
+
if (onData) {
|
|
59
|
+
for (const line of text.split("\n").filter(Boolean)) {
|
|
60
|
+
onData(line.trim());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
child.stderr.on("data", (data) => {
|
|
65
|
+
const text = data.toString();
|
|
66
|
+
stderr.push(text);
|
|
67
|
+
if (onData) {
|
|
68
|
+
for (const line of text.split("\n").filter(Boolean)) {
|
|
69
|
+
onData(line.trim());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
child.on("close", (code) => {
|
|
74
|
+
const out = stdout.join("").trim();
|
|
75
|
+
const errOut = stderr.join("").trim();
|
|
76
|
+
if (out) appendLog(out);
|
|
77
|
+
if (errOut) appendLog(`STDERR: ${errOut}`);
|
|
78
|
+
if (code !== 0) {
|
|
79
|
+
appendLog(`$ ${cmd}${cwd ? ` (in ${cwd})` : ""}`, true);
|
|
80
|
+
appendLog(`EXIT CODE ${code}`, true);
|
|
81
|
+
if (errOut) appendLog(`STDERR: ${errOut}`, true);
|
|
82
|
+
const detail = errOut;
|
|
83
|
+
const msg = detail ? `Command failed: ${cmd}
|
|
84
|
+
${detail}` : `Command failed: ${cmd}`;
|
|
85
|
+
reject(new Error(msg));
|
|
86
|
+
} else {
|
|
87
|
+
resolve(out);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
child.on("error", (err) => {
|
|
91
|
+
appendLog(`$ ${cmd}${cwd ? ` (in ${cwd})` : ""}`, true);
|
|
92
|
+
appendLog(`SPAWN ERROR: ${err.message}`, true);
|
|
93
|
+
reject(
|
|
94
|
+
new Error(`Command failed: ${cmd}
|
|
95
|
+
${err.message}`)
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
function delay(ms) {
|
|
101
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
102
|
+
}
|
|
103
|
+
function checkCommand(cmd) {
|
|
104
|
+
try {
|
|
105
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
106
|
+
return true;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function skillDisplayName(skill) {
|
|
112
|
+
if (skill.includes("@")) return skill.split("@").pop();
|
|
113
|
+
const flag = skill.match(/--skill\s+(\S+)/);
|
|
114
|
+
if (flag) return flag[1];
|
|
115
|
+
return skill.split("/").pop();
|
|
116
|
+
}
|
|
117
|
+
function generateDbPassword(length = 32) {
|
|
118
|
+
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
|
119
|
+
const bytes = crypto.randomBytes(length);
|
|
120
|
+
return Array.from(bytes, (b) => chars[b % chars.length]).join("");
|
|
121
|
+
}
|
|
122
|
+
function validateProjectName(name, mode = "new") {
|
|
123
|
+
if (!name) return "Project name is required.";
|
|
124
|
+
if (/\s/.test(name)) return "Project name cannot contain spaces.";
|
|
125
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(name))
|
|
126
|
+
return "Project name can only contain letters, numbers, hyphens, dots, and underscores.";
|
|
127
|
+
if (mode === "new" && fs.existsSync(name))
|
|
128
|
+
return `Directory "${name}" already exists.`;
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// src/cloud.ts
|
|
133
|
+
var theme = {
|
|
134
|
+
prefix: { idle: blue("?"), done: blue("\u2714") },
|
|
135
|
+
style: {
|
|
136
|
+
answer: (text) => amber(text),
|
|
137
|
+
message: (text) => bold(text),
|
|
138
|
+
error: (text) => red(`> ${text}`),
|
|
139
|
+
help: (text) => dim(text),
|
|
140
|
+
highlight: (text) => blue(text),
|
|
141
|
+
key: (text) => blue(bold(`<${text}>`))
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
function credentialsPath() {
|
|
145
|
+
return path.join(os.homedir(), ".config", "agentlink", "credentials.json");
|
|
146
|
+
}
|
|
147
|
+
function loadCredentials() {
|
|
148
|
+
try {
|
|
149
|
+
return JSON.parse(fs2.readFileSync(credentialsPath(), "utf-8"));
|
|
150
|
+
} catch {
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
function saveCredentials(creds) {
|
|
155
|
+
const filePath = credentialsPath();
|
|
156
|
+
fs2.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
157
|
+
fs2.writeFileSync(filePath, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
|
|
158
|
+
}
|
|
159
|
+
async function ensureAccessToken(nonInteractive, projectDir) {
|
|
160
|
+
if (process.env.SUPABASE_ACCESS_TOKEN) return;
|
|
161
|
+
if (projectDir) {
|
|
162
|
+
const envPath = path.join(projectDir, ".env.local");
|
|
163
|
+
if (fs2.existsSync(envPath)) {
|
|
164
|
+
const content = fs2.readFileSync(envPath, "utf-8");
|
|
165
|
+
const match = content.match(/^SUPABASE_ACCESS_TOKEN=(.+)$/m);
|
|
166
|
+
if (match?.[1]) {
|
|
167
|
+
let val = match[1].trim();
|
|
168
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
169
|
+
val = val.slice(1, -1);
|
|
170
|
+
}
|
|
171
|
+
process.env.SUPABASE_ACCESS_TOKEN = val;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const creds = loadCredentials();
|
|
177
|
+
if (creds.oauth) {
|
|
178
|
+
if (creds.oauth.expires_at > Date.now() / 1e3 + 60) {
|
|
179
|
+
process.env.SUPABASE_ACCESS_TOKEN = creds.oauth.access_token;
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (creds.oauth.refresh_token) {
|
|
183
|
+
try {
|
|
184
|
+
const { refreshOAuthToken } = await import("./oauth-J4X62CY4.js");
|
|
185
|
+
const tokens = await refreshOAuthToken(creds.oauth.refresh_token);
|
|
186
|
+
const updated = {
|
|
187
|
+
...creds,
|
|
188
|
+
oauth: {
|
|
189
|
+
access_token: tokens.access_token,
|
|
190
|
+
refresh_token: tokens.refresh_token,
|
|
191
|
+
expires_at: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
saveCredentials(updated);
|
|
195
|
+
process.env.SUPABASE_ACCESS_TOKEN = tokens.access_token;
|
|
196
|
+
return;
|
|
197
|
+
} catch {
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (creds.supabase_access_token) {
|
|
202
|
+
process.env.SUPABASE_ACCESS_TOKEN = creds.supabase_access_token;
|
|
203
|
+
const masked = creds.supabase_access_token.slice(0, 8) + "..." + creds.supabase_access_token.slice(-4);
|
|
204
|
+
console.log(` ${blue("\u25CF")} Existing access token found ${dim(`(source: ${credentialsPath()})`)}`);
|
|
205
|
+
console.log(` ${dim(masked)}`);
|
|
206
|
+
console.log();
|
|
207
|
+
if (!nonInteractive) {
|
|
208
|
+
const replace = await confirm({
|
|
209
|
+
message: "Use stored token?",
|
|
210
|
+
default: true,
|
|
211
|
+
theme
|
|
212
|
+
});
|
|
213
|
+
if (replace) return;
|
|
214
|
+
delete process.env.SUPABASE_ACCESS_TOKEN;
|
|
215
|
+
} else {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (nonInteractive) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
"SUPABASE_ACCESS_TOKEN is required for cloud mode.\n Set it via --token, SUPABASE_ACCESS_TOKEN env var, or run `agentlink login`."
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
const method = await select({
|
|
225
|
+
message: "How would you like to authenticate?",
|
|
226
|
+
theme,
|
|
227
|
+
choices: [
|
|
228
|
+
{ name: "Login with Supabase (opens browser)", value: "oauth" },
|
|
229
|
+
{ name: "Enter access token manually", value: "manual" }
|
|
230
|
+
]
|
|
231
|
+
});
|
|
232
|
+
if (method === "oauth") {
|
|
233
|
+
const { oauthLogin } = await import("./oauth-J4X62CY4.js");
|
|
234
|
+
const tokens = await oauthLogin();
|
|
235
|
+
process.env.SUPABASE_ACCESS_TOKEN = tokens.access_token;
|
|
236
|
+
saveCredentials({
|
|
237
|
+
...loadCredentials(),
|
|
238
|
+
oauth: {
|
|
239
|
+
access_token: tokens.access_token,
|
|
240
|
+
refresh_token: tokens.refresh_token,
|
|
241
|
+
expires_at: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
console.log(` ${blue("\u2714")} Authenticated via Supabase OAuth`);
|
|
245
|
+
console.log(` ${dim("Token stored at " + credentialsPath())}`);
|
|
246
|
+
console.log();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const tokenUrl = "https://supabase.com/dashboard/account/tokens";
|
|
250
|
+
try {
|
|
251
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
252
|
+
execSync2(`${cmd} ${JSON.stringify(tokenUrl)}`, { stdio: "ignore" });
|
|
253
|
+
console.log(` ${blue("\u25CF")} Opened ${link(tokenUrl)}`);
|
|
254
|
+
} catch {
|
|
255
|
+
console.log(` ${amber("Could not open browser.")} Visit: ${link(tokenUrl)}`);
|
|
256
|
+
}
|
|
257
|
+
console.log();
|
|
258
|
+
const token = await input({
|
|
259
|
+
message: "Enter your Supabase access token:",
|
|
260
|
+
theme,
|
|
261
|
+
transformer: (value, { isFinal }) => {
|
|
262
|
+
if (isFinal) {
|
|
263
|
+
const t = value.trim();
|
|
264
|
+
return t.length > 12 ? t.slice(0, 8) + "\u25CF".repeat(t.length - 12) + t.slice(-4) : "\u25CF".repeat(t.length);
|
|
265
|
+
}
|
|
266
|
+
return "\u25CF".repeat(value.length);
|
|
267
|
+
},
|
|
268
|
+
validate: (val) => val.trim().length > 0 || "Token is required"
|
|
269
|
+
});
|
|
270
|
+
const trimmed = token.trim();
|
|
271
|
+
process.env.SUPABASE_ACCESS_TOKEN = trimmed;
|
|
272
|
+
saveCredentials({ ...loadCredentials(), supabase_access_token: trimmed });
|
|
273
|
+
console.log(` ${blue("\u25CF")} Token stored at ${dim(credentialsPath())}`);
|
|
274
|
+
console.log();
|
|
275
|
+
saveTokenToEnvLocal(trimmed, projectDir);
|
|
276
|
+
}
|
|
277
|
+
function saveTokenToEnvLocal(token, projectDir) {
|
|
278
|
+
if (!projectDir) return;
|
|
279
|
+
const envPath = path.join(projectDir, ".env.local");
|
|
280
|
+
if (fs2.existsSync(envPath)) {
|
|
281
|
+
let content = fs2.readFileSync(envPath, "utf-8");
|
|
282
|
+
if (/^SUPABASE_ACCESS_TOKEN=/m.test(content)) {
|
|
283
|
+
content = content.replace(/^SUPABASE_ACCESS_TOKEN=.*$/m, `SUPABASE_ACCESS_TOKEN=${token}`);
|
|
284
|
+
} else {
|
|
285
|
+
content = content.trimEnd() + `
|
|
286
|
+
SUPABASE_ACCESS_TOKEN=${token}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
fs2.writeFileSync(envPath, content);
|
|
290
|
+
} else {
|
|
291
|
+
fs2.writeFileSync(envPath, `SUPABASE_ACCESS_TOKEN=${token}
|
|
292
|
+
`);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
var REGIONS = [
|
|
296
|
+
{ id: "us-east-1", name: "US East (N. Virginia)" },
|
|
297
|
+
{ id: "us-east-2", name: "US East (Ohio)" },
|
|
298
|
+
{ id: "us-west-1", name: "US West (N. California)" },
|
|
299
|
+
{ id: "us-west-2", name: "US West (Oregon)" },
|
|
300
|
+
{ id: "ca-central-1", name: "Canada (Central)" },
|
|
301
|
+
{ id: "sa-east-1", name: "South America (S\xE3o Paulo)" },
|
|
302
|
+
{ id: "eu-west-1", name: "EU West (Ireland)" },
|
|
303
|
+
{ id: "eu-west-2", name: "EU West (London)" },
|
|
304
|
+
{ id: "eu-west-3", name: "EU West (Paris)" },
|
|
305
|
+
{ id: "eu-central-1", name: "EU Central (Frankfurt)" },
|
|
306
|
+
{ id: "eu-central-2", name: "EU Central (Zurich)" },
|
|
307
|
+
{ id: "eu-north-1", name: "EU North (Stockholm)" },
|
|
308
|
+
{ id: "ap-southeast-1", name: "Asia Pacific (Singapore)" },
|
|
309
|
+
{ id: "ap-southeast-2", name: "Asia Pacific (Sydney)" },
|
|
310
|
+
{ id: "ap-northeast-1", name: "Asia Pacific (Tokyo)" },
|
|
311
|
+
{ id: "ap-south-1", name: "Asia Pacific (Mumbai)" }
|
|
312
|
+
];
|
|
313
|
+
async function listOrganizations() {
|
|
314
|
+
const out = await runCommand(`${sb()} orgs list -o json`);
|
|
315
|
+
return JSON.parse(out);
|
|
316
|
+
}
|
|
317
|
+
function listRegions() {
|
|
318
|
+
return REGIONS;
|
|
319
|
+
}
|
|
320
|
+
function detectClosestRegion() {
|
|
321
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone ?? "";
|
|
322
|
+
const cityMap = {
|
|
323
|
+
"America/New_York": "us-east-1",
|
|
324
|
+
"America/Toronto": "us-east-1",
|
|
325
|
+
"America/Detroit": "us-east-1",
|
|
326
|
+
"America/Chicago": "us-east-2",
|
|
327
|
+
"America/Denver": "us-west-2",
|
|
328
|
+
"America/Phoenix": "us-west-2",
|
|
329
|
+
"America/Los_Angeles": "us-west-1",
|
|
330
|
+
"America/Vancouver": "us-west-1",
|
|
331
|
+
"America/Edmonton": "ca-central-1",
|
|
332
|
+
"America/Winnipeg": "ca-central-1",
|
|
333
|
+
"America/Sao_Paulo": "sa-east-1",
|
|
334
|
+
"America/Argentina/Buenos_Aires": "sa-east-1",
|
|
335
|
+
"America/Bogota": "sa-east-1",
|
|
336
|
+
"America/Lima": "sa-east-1",
|
|
337
|
+
"America/Santiago": "sa-east-1",
|
|
338
|
+
"America/Mexico_City": "us-east-1",
|
|
339
|
+
"Europe/London": "eu-west-2",
|
|
340
|
+
"Europe/Dublin": "eu-west-1",
|
|
341
|
+
"Europe/Paris": "eu-west-3",
|
|
342
|
+
"Europe/Madrid": "eu-west-3",
|
|
343
|
+
"Europe/Rome": "eu-west-3",
|
|
344
|
+
"Europe/Brussels": "eu-west-3",
|
|
345
|
+
"Europe/Berlin": "eu-central-1",
|
|
346
|
+
"Europe/Amsterdam": "eu-central-1",
|
|
347
|
+
"Europe/Vienna": "eu-central-1",
|
|
348
|
+
"Europe/Zurich": "eu-central-2",
|
|
349
|
+
"Europe/Stockholm": "eu-north-1",
|
|
350
|
+
"Europe/Helsinki": "eu-north-1",
|
|
351
|
+
"Europe/Oslo": "eu-north-1",
|
|
352
|
+
"Europe/Warsaw": "eu-central-1",
|
|
353
|
+
"Asia/Tokyo": "ap-northeast-1",
|
|
354
|
+
"Asia/Seoul": "ap-northeast-1",
|
|
355
|
+
"Asia/Singapore": "ap-southeast-1",
|
|
356
|
+
"Asia/Kuala_Lumpur": "ap-southeast-1",
|
|
357
|
+
"Asia/Jakarta": "ap-southeast-1",
|
|
358
|
+
"Asia/Bangkok": "ap-southeast-1",
|
|
359
|
+
"Asia/Kolkata": "ap-south-1",
|
|
360
|
+
"Asia/Mumbai": "ap-south-1",
|
|
361
|
+
"Asia/Dubai": "ap-south-1",
|
|
362
|
+
"Australia/Sydney": "ap-southeast-2",
|
|
363
|
+
"Australia/Melbourne": "ap-southeast-2",
|
|
364
|
+
"Pacific/Auckland": "ap-southeast-2"
|
|
365
|
+
};
|
|
366
|
+
if (cityMap[tz]) return cityMap[tz];
|
|
367
|
+
if (tz.startsWith("America/")) return "us-east-1";
|
|
368
|
+
if (tz.startsWith("US/")) return "us-east-1";
|
|
369
|
+
if (tz.startsWith("Canada/")) return "ca-central-1";
|
|
370
|
+
if (tz.startsWith("Europe/")) return "eu-central-1";
|
|
371
|
+
if (tz.startsWith("Asia/")) return "ap-southeast-1";
|
|
372
|
+
if (tz.startsWith("Australia/") || tz.startsWith("Pacific/")) return "ap-southeast-2";
|
|
373
|
+
if (tz.startsWith("Africa/")) return "eu-west-3";
|
|
374
|
+
return "us-east-1";
|
|
375
|
+
}
|
|
376
|
+
async function listProjects() {
|
|
377
|
+
const out = await runCommand(`${sb()} projects list -o json`);
|
|
378
|
+
return JSON.parse(out) ?? [];
|
|
379
|
+
}
|
|
380
|
+
async function createProject(opts) {
|
|
381
|
+
const out = await runCommand(
|
|
382
|
+
`${sb()} projects create ${JSON.stringify(opts.name)} --org-id ${opts.orgId} --region ${opts.region} --db-password ${opts.dbPass} -o json`
|
|
383
|
+
);
|
|
384
|
+
return JSON.parse(out);
|
|
385
|
+
}
|
|
386
|
+
async function getProject(ref) {
|
|
387
|
+
const out = await runCommand(`${sb()} projects list -o json`);
|
|
388
|
+
const projects = JSON.parse(out);
|
|
389
|
+
const project = projects.find((p) => p.id === ref);
|
|
390
|
+
if (!project) {
|
|
391
|
+
throw new Error(`Project ${ref} not found`);
|
|
392
|
+
}
|
|
393
|
+
return project;
|
|
394
|
+
}
|
|
395
|
+
async function getApiKeys(ref) {
|
|
396
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
397
|
+
if (!token) {
|
|
398
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required to fetch API keys");
|
|
399
|
+
}
|
|
400
|
+
const res = await fetch(
|
|
401
|
+
`https://api.supabase.com/v1/projects/${ref}/api-keys?reveal=true`,
|
|
402
|
+
{
|
|
403
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
404
|
+
}
|
|
405
|
+
);
|
|
406
|
+
if (!res.ok) {
|
|
407
|
+
const body = await res.text();
|
|
408
|
+
throw new Error(`Failed to fetch API keys (${res.status}): ${body}`);
|
|
409
|
+
}
|
|
410
|
+
const keys = await res.json();
|
|
411
|
+
const publishable = keys.find((k) => k.type === "publishable");
|
|
412
|
+
const secret = keys.find((k) => k.type === "secret");
|
|
413
|
+
if (!publishable?.api_key || !secret?.api_key) {
|
|
414
|
+
throw new Error("Could not find publishable/secret API keys for project");
|
|
415
|
+
}
|
|
416
|
+
return { publishableKey: publishable.api_key, secretKey: secret.api_key };
|
|
417
|
+
}
|
|
418
|
+
async function runCloudSQL(sql, projectRef) {
|
|
419
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
420
|
+
if (!token) {
|
|
421
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required for cloud SQL execution");
|
|
422
|
+
}
|
|
423
|
+
const res = await fetch(
|
|
424
|
+
`https://api.supabase.com/v1/projects/${projectRef}/database/query`,
|
|
425
|
+
{
|
|
426
|
+
method: "POST",
|
|
427
|
+
headers: {
|
|
428
|
+
Authorization: `Bearer ${token}`,
|
|
429
|
+
"Content-Type": "application/json"
|
|
430
|
+
},
|
|
431
|
+
body: JSON.stringify({ query: sql })
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
if (!res.ok) {
|
|
435
|
+
const body = await res.text();
|
|
436
|
+
throw new Error(`Cloud SQL query failed (${res.status}): ${body}`);
|
|
437
|
+
}
|
|
438
|
+
const rows = await res.json();
|
|
439
|
+
if (!Array.isArray(rows) || rows.length === 0) return "";
|
|
440
|
+
const firstValue = Object.values(rows[0])[0];
|
|
441
|
+
if (typeof firstValue === "object" && firstValue !== null) return JSON.stringify(firstValue);
|
|
442
|
+
return String(firstValue ?? "");
|
|
443
|
+
}
|
|
444
|
+
async function updatePostgrestConfig(projectRef, config) {
|
|
445
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
446
|
+
if (!token) {
|
|
447
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required to update PostgREST config");
|
|
448
|
+
}
|
|
449
|
+
const res = await fetch(
|
|
450
|
+
`https://api.supabase.com/v1/projects/${projectRef}/postgrest`,
|
|
451
|
+
{
|
|
452
|
+
method: "PATCH",
|
|
453
|
+
headers: {
|
|
454
|
+
Authorization: `Bearer ${token}`,
|
|
455
|
+
"Content-Type": "application/json"
|
|
456
|
+
},
|
|
457
|
+
body: JSON.stringify(config)
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
if (!res.ok) {
|
|
461
|
+
const body = await res.text();
|
|
462
|
+
throw new Error(`Failed to update PostgREST config (${res.status}): ${body}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function updateAuthConfig(projectRef, config) {
|
|
466
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
467
|
+
if (!token) {
|
|
468
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required to update auth config");
|
|
469
|
+
}
|
|
470
|
+
const res = await fetch(
|
|
471
|
+
`https://api.supabase.com/v1/projects/${projectRef}/config/auth`,
|
|
472
|
+
{
|
|
473
|
+
method: "PATCH",
|
|
474
|
+
headers: {
|
|
475
|
+
Authorization: `Bearer ${token}`,
|
|
476
|
+
"Content-Type": "application/json"
|
|
477
|
+
},
|
|
478
|
+
body: JSON.stringify(config)
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
if (!res.ok) {
|
|
482
|
+
const body = await res.text();
|
|
483
|
+
throw new Error(`Failed to update auth config (${res.status}): ${body}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
async function waitForProjectReady(ref, spinner, timeoutMs = 3e5) {
|
|
487
|
+
const start = Date.now();
|
|
488
|
+
while (Date.now() - start < timeoutMs) {
|
|
489
|
+
try {
|
|
490
|
+
const out = await runCommand(`${sb()} projects list -o json`);
|
|
491
|
+
const projects = JSON.parse(out);
|
|
492
|
+
const project = projects.find((p) => p.id === ref);
|
|
493
|
+
if (project?.status === "ACTIVE_HEALTHY") {
|
|
494
|
+
return project;
|
|
495
|
+
}
|
|
496
|
+
if (spinner && project) {
|
|
497
|
+
spinner.text = `Creating Supabase cloud project \u2014 status: ${project.status}`;
|
|
498
|
+
}
|
|
499
|
+
} catch {
|
|
500
|
+
}
|
|
501
|
+
await delay(5e3);
|
|
502
|
+
}
|
|
503
|
+
throw new Error(
|
|
504
|
+
`Project ${ref} did not become ready within ${timeoutMs / 1e3}s.
|
|
505
|
+
Check status at: https://supabase.com/dashboard/project/${ref}`
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
function saveProjectCredential(projectRef, dbPassword) {
|
|
509
|
+
const creds = loadCredentials();
|
|
510
|
+
if (!creds.project_credentials) {
|
|
511
|
+
creds.project_credentials = {};
|
|
512
|
+
}
|
|
513
|
+
creds.project_credentials[projectRef] = { db_password: dbPassword };
|
|
514
|
+
saveCredentials(creds);
|
|
515
|
+
}
|
|
516
|
+
function loadProjectCredential(projectRef) {
|
|
517
|
+
const creds = loadCredentials();
|
|
518
|
+
return creds.project_credentials?.[projectRef]?.db_password;
|
|
519
|
+
}
|
|
520
|
+
function resolveDbUrlForEnv(env, password) {
|
|
521
|
+
return `postgresql://postgres.${env.projectRef}:${encodeURIComponent(password)}@aws-0-${env.region}.pooler.supabase.com:5432/postgres`;
|
|
522
|
+
}
|
|
523
|
+
async function listSecrets(projectRef) {
|
|
524
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
525
|
+
if (!token) {
|
|
526
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required to list secrets");
|
|
527
|
+
}
|
|
528
|
+
const res = await fetch(
|
|
529
|
+
`https://api.supabase.com/v1/projects/${projectRef}/secrets`,
|
|
530
|
+
{
|
|
531
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
532
|
+
}
|
|
533
|
+
);
|
|
534
|
+
if (!res.ok) {
|
|
535
|
+
const body = await res.text();
|
|
536
|
+
throw new Error(`Failed to list secrets (${res.status}): ${body}`);
|
|
537
|
+
}
|
|
538
|
+
const secrets = await res.json();
|
|
539
|
+
return secrets.map((s) => s.name);
|
|
540
|
+
}
|
|
541
|
+
async function setSecrets(projectRef, secrets) {
|
|
542
|
+
const token = process.env.SUPABASE_ACCESS_TOKEN;
|
|
543
|
+
if (!token) {
|
|
544
|
+
throw new Error("SUPABASE_ACCESS_TOKEN is required to set secrets");
|
|
545
|
+
}
|
|
546
|
+
const body = Object.entries(secrets).map(([name, value]) => ({ name, value }));
|
|
547
|
+
const res = await fetch(
|
|
548
|
+
`https://api.supabase.com/v1/projects/${projectRef}/secrets`,
|
|
549
|
+
{
|
|
550
|
+
method: "POST",
|
|
551
|
+
headers: {
|
|
552
|
+
Authorization: `Bearer ${token}`,
|
|
553
|
+
"Content-Type": "application/json"
|
|
554
|
+
},
|
|
555
|
+
body: JSON.stringify(body)
|
|
556
|
+
}
|
|
557
|
+
);
|
|
558
|
+
if (!res.ok) {
|
|
559
|
+
const respBody = await res.text();
|
|
560
|
+
throw new Error(`Failed to set secrets (${res.status}): ${respBody}`);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export {
|
|
565
|
+
initLog,
|
|
566
|
+
runCommand,
|
|
567
|
+
delay,
|
|
568
|
+
checkCommand,
|
|
569
|
+
skillDisplayName,
|
|
570
|
+
generateDbPassword,
|
|
571
|
+
validateProjectName,
|
|
572
|
+
credentialsPath,
|
|
573
|
+
loadCredentials,
|
|
574
|
+
saveCredentials,
|
|
575
|
+
ensureAccessToken,
|
|
576
|
+
listOrganizations,
|
|
577
|
+
listRegions,
|
|
578
|
+
detectClosestRegion,
|
|
579
|
+
listProjects,
|
|
580
|
+
createProject,
|
|
581
|
+
getProject,
|
|
582
|
+
getApiKeys,
|
|
583
|
+
runCloudSQL,
|
|
584
|
+
updatePostgrestConfig,
|
|
585
|
+
updateAuthConfig,
|
|
586
|
+
waitForProjectReady,
|
|
587
|
+
saveProjectCredential,
|
|
588
|
+
loadProjectCredential,
|
|
589
|
+
resolveDbUrlForEnv,
|
|
590
|
+
listSecrets,
|
|
591
|
+
setSecrets
|
|
592
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/constants.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var require2 = createRequire(import.meta.url);
|
|
8
|
+
var supabasePkg = require2("supabase/package.json");
|
|
9
|
+
var SUPPORTED_SUPABASE_CLI = supabasePkg.version;
|
|
10
|
+
var SKILLS_VERSION = "1.4.3";
|
|
11
|
+
var OAUTH_CLIENT_ID = "d8996260-5912-4f7e-b114-3b542f20ae8b";
|
|
12
|
+
var OAUTH_CLIENT_SECRET = "sba_3424fb2fb9766e08cbc85aa094843ff07f8328da";
|
|
13
|
+
function supabaseBin() {
|
|
14
|
+
return path.join(
|
|
15
|
+
path.dirname(require2.resolve("supabase/package.json")),
|
|
16
|
+
"bin",
|
|
17
|
+
"supabase"
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
function sb() {
|
|
21
|
+
return JSON.stringify(supabaseBin());
|
|
22
|
+
}
|
|
23
|
+
function pgdeltaDir() {
|
|
24
|
+
const main = require2.resolve("@supabase/pg-delta");
|
|
25
|
+
return path.resolve(path.dirname(main), "..");
|
|
26
|
+
}
|
|
27
|
+
var pgdeltaPkg = JSON.parse(
|
|
28
|
+
fs.readFileSync(path.join(pgdeltaDir(), "package.json"), "utf-8")
|
|
29
|
+
);
|
|
30
|
+
var SUPPORTED_PGDELTA = pgdeltaPkg.version;
|
|
31
|
+
function pgdeltaBin() {
|
|
32
|
+
return path.join(pgdeltaDir(), "dist", "cli", "bin", "cli.js");
|
|
33
|
+
}
|
|
34
|
+
function pgd() {
|
|
35
|
+
return JSON.stringify(pgdeltaBin());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/theme.ts
|
|
39
|
+
var rgb = (r, g, b) => (s) => `\x1B[38;2;${r};${g};${b}m${s}\x1B[0m`;
|
|
40
|
+
var blue = rgb(92, 184, 228);
|
|
41
|
+
var amber = rgb(232, 168, 56);
|
|
42
|
+
var red = rgb(239, 68, 68);
|
|
43
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
44
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
45
|
+
var link = (url, label) => `\x1B]8;;${url}\x07${label ?? url}\x1B]8;;\x07`;
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
SUPPORTED_SUPABASE_CLI,
|
|
49
|
+
SKILLS_VERSION,
|
|
50
|
+
OAUTH_CLIENT_ID,
|
|
51
|
+
OAUTH_CLIENT_SECRET,
|
|
52
|
+
supabaseBin,
|
|
53
|
+
sb,
|
|
54
|
+
pgdeltaBin,
|
|
55
|
+
pgd,
|
|
56
|
+
blue,
|
|
57
|
+
amber,
|
|
58
|
+
red,
|
|
59
|
+
dim,
|
|
60
|
+
bold,
|
|
61
|
+
link
|
|
62
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createProject,
|
|
4
|
+
credentialsPath,
|
|
5
|
+
detectClosestRegion,
|
|
6
|
+
ensureAccessToken,
|
|
7
|
+
getApiKeys,
|
|
8
|
+
getProject,
|
|
9
|
+
listOrganizations,
|
|
10
|
+
listProjects,
|
|
11
|
+
listRegions,
|
|
12
|
+
listSecrets,
|
|
13
|
+
loadCredentials,
|
|
14
|
+
loadProjectCredential,
|
|
15
|
+
resolveDbUrlForEnv,
|
|
16
|
+
runCloudSQL,
|
|
17
|
+
saveCredentials,
|
|
18
|
+
saveProjectCredential,
|
|
19
|
+
setSecrets,
|
|
20
|
+
updateAuthConfig,
|
|
21
|
+
updatePostgrestConfig,
|
|
22
|
+
waitForProjectReady
|
|
23
|
+
} from "./chunk-DOWH47I5.js";
|
|
24
|
+
import "./chunk-G3HVUCWY.js";
|
|
25
|
+
export {
|
|
26
|
+
createProject,
|
|
27
|
+
credentialsPath,
|
|
28
|
+
detectClosestRegion,
|
|
29
|
+
ensureAccessToken,
|
|
30
|
+
getApiKeys,
|
|
31
|
+
getProject,
|
|
32
|
+
listOrganizations,
|
|
33
|
+
listProjects,
|
|
34
|
+
listRegions,
|
|
35
|
+
listSecrets,
|
|
36
|
+
loadCredentials,
|
|
37
|
+
loadProjectCredential,
|
|
38
|
+
resolveDbUrlForEnv,
|
|
39
|
+
runCloudSQL,
|
|
40
|
+
saveCredentials,
|
|
41
|
+
saveProjectCredential,
|
|
42
|
+
setSecrets,
|
|
43
|
+
updateAuthConfig,
|
|
44
|
+
updatePostgrestConfig,
|
|
45
|
+
waitForProjectReady
|
|
46
|
+
};
|