@insforge/cli 0.1.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/index.js ADDED
@@ -0,0 +1,2405 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
5
+ import { join as join5 } from "path";
6
+ import { Command } from "commander";
7
+
8
+ // src/commands/login.ts
9
+ import * as clack from "@clack/prompts";
10
+
11
+ // src/lib/config.ts
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
13
+ import { homedir } from "os";
14
+ import { join } from "path";
15
+ var GLOBAL_DIR = join(homedir(), ".insforge");
16
+ var CREDENTIALS_FILE = join(GLOBAL_DIR, "credentials.json");
17
+ var CONFIG_FILE = join(GLOBAL_DIR, "config.json");
18
+ var DEFAULT_PLATFORM_URL = "https://api.insforge.dev";
19
+ function ensureGlobalDir() {
20
+ if (!existsSync(GLOBAL_DIR)) {
21
+ mkdirSync(GLOBAL_DIR, { recursive: true });
22
+ }
23
+ }
24
+ function getGlobalConfig() {
25
+ if (!existsSync(CONFIG_FILE)) {
26
+ return { platform_api_url: DEFAULT_PLATFORM_URL };
27
+ }
28
+ const raw = readFileSync(CONFIG_FILE, "utf-8");
29
+ return JSON.parse(raw);
30
+ }
31
+ function saveGlobalConfig(config) {
32
+ ensureGlobalDir();
33
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
34
+ }
35
+ function getCredentials() {
36
+ if (!existsSync(CREDENTIALS_FILE)) {
37
+ return null;
38
+ }
39
+ const raw = readFileSync(CREDENTIALS_FILE, "utf-8");
40
+ return JSON.parse(raw);
41
+ }
42
+ function saveCredentials(creds) {
43
+ ensureGlobalDir();
44
+ writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { mode: 384 });
45
+ }
46
+ function clearCredentials() {
47
+ if (existsSync(CREDENTIALS_FILE)) {
48
+ unlinkSync(CREDENTIALS_FILE);
49
+ }
50
+ }
51
+ function getLocalConfigDir() {
52
+ return join(process.cwd(), ".insforge");
53
+ }
54
+ function getLocalConfigFile() {
55
+ return join(getLocalConfigDir(), "project.json");
56
+ }
57
+ function getProjectConfig() {
58
+ const file = getLocalConfigFile();
59
+ if (!existsSync(file)) {
60
+ return null;
61
+ }
62
+ const raw = readFileSync(file, "utf-8");
63
+ return JSON.parse(raw);
64
+ }
65
+ function saveProjectConfig(config) {
66
+ const dir = getLocalConfigDir();
67
+ if (!existsSync(dir)) {
68
+ mkdirSync(dir, { recursive: true });
69
+ }
70
+ writeFileSync(getLocalConfigFile(), JSON.stringify(config, null, 2), { mode: 384 });
71
+ }
72
+ function getPlatformApiUrl(override) {
73
+ return process.env.INSFORGE_API_URL ?? override ?? getGlobalConfig().platform_api_url ?? DEFAULT_PLATFORM_URL;
74
+ }
75
+ function getAccessToken() {
76
+ return process.env.INSFORGE_ACCESS_TOKEN ?? getCredentials()?.access_token ?? null;
77
+ }
78
+
79
+ // src/lib/errors.ts
80
+ var CLIError = class extends Error {
81
+ constructor(message, exitCode = 1, code) {
82
+ super(message);
83
+ this.exitCode = exitCode;
84
+ this.code = code;
85
+ this.name = "CLIError";
86
+ }
87
+ };
88
+ var AuthError = class extends CLIError {
89
+ constructor(message = "Not authenticated. Run `insforge login` first.") {
90
+ super(message, 2, "AUTH_ERROR");
91
+ }
92
+ };
93
+ var ProjectNotLinkedError = class extends CLIError {
94
+ constructor() {
95
+ super("No project linked. Run `insforge projects link` first.", 3, "PROJECT_NOT_LINKED");
96
+ }
97
+ };
98
+ function handleError(err, json) {
99
+ if (err instanceof CLIError) {
100
+ if (json) {
101
+ console.error(JSON.stringify({ error: err.message, code: err.code }));
102
+ } else {
103
+ console.error(`Error: ${err.message}`);
104
+ }
105
+ process.exit(err.exitCode);
106
+ }
107
+ const message = err instanceof Error ? err.message : String(err);
108
+ if (json) {
109
+ console.error(JSON.stringify({ error: message, code: "UNKNOWN_ERROR" }));
110
+ } else {
111
+ console.error(`Error: ${message}`);
112
+ }
113
+ process.exit(1);
114
+ }
115
+ function getRootOpts(cmd) {
116
+ let root = cmd;
117
+ while (root.parent) {
118
+ root = root.parent;
119
+ }
120
+ const opts = root.opts();
121
+ return {
122
+ json: opts.json ?? false,
123
+ projectId: opts.projectId,
124
+ apiUrl: opts.apiUrl,
125
+ yes: opts.yes ?? false
126
+ };
127
+ }
128
+
129
+ // src/lib/auth.ts
130
+ import { createServer } from "http";
131
+ import { randomBytes, createHash } from "crypto";
132
+ import { URL } from "url";
133
+ var DEFAULT_CLIENT_ID = "clf_NK8cMUs41gm8ZcfdtSguVw";
134
+ var OAUTH_SCOPES = "user:read organizations:read projects:read projects:write";
135
+ function generatePKCE() {
136
+ const code_verifier = randomBytes(32).toString("base64url");
137
+ const code_challenge = createHash("sha256").update(code_verifier).digest("base64url");
138
+ return { code_verifier, code_challenge };
139
+ }
140
+ function generateState() {
141
+ return randomBytes(16).toString("base64url");
142
+ }
143
+ function buildAuthorizeUrl(params) {
144
+ const url = new URL(`${params.platformUrl}/api/oauth/v1/authorize`);
145
+ url.searchParams.set("client_id", params.clientId);
146
+ url.searchParams.set("redirect_uri", params.redirectUri);
147
+ url.searchParams.set("response_type", "code");
148
+ url.searchParams.set("scope", params.scopes);
149
+ url.searchParams.set("code_challenge", params.codeChallenge);
150
+ url.searchParams.set("code_challenge_method", "S256");
151
+ url.searchParams.set("state", params.state);
152
+ return url.toString();
153
+ }
154
+ async function exchangeCodeForTokens(params) {
155
+ const res = await fetch(`${params.platformUrl}/api/oauth/v1/token`, {
156
+ method: "POST",
157
+ headers: { "Content-Type": "application/json" },
158
+ body: JSON.stringify({
159
+ grant_type: "authorization_code",
160
+ code: params.code,
161
+ redirect_uri: params.redirectUri,
162
+ client_id: params.clientId,
163
+ code_verifier: params.codeVerifier
164
+ })
165
+ });
166
+ if (!res.ok) {
167
+ const err = await res.json().catch(() => ({}));
168
+ throw new Error(err.error_description ?? err.error ?? "Token exchange failed");
169
+ }
170
+ return await res.json();
171
+ }
172
+ async function refreshOAuthToken(params) {
173
+ const res = await fetch(`${params.platformUrl}/api/oauth/v1/token`, {
174
+ method: "POST",
175
+ headers: { "Content-Type": "application/json" },
176
+ body: JSON.stringify({
177
+ grant_type: "refresh_token",
178
+ refresh_token: params.refreshToken,
179
+ client_id: params.clientId
180
+ })
181
+ });
182
+ if (!res.ok) {
183
+ const err = await res.json().catch(() => ({}));
184
+ throw new Error(err.error_description ?? err.error ?? "Token refresh failed");
185
+ }
186
+ return await res.json();
187
+ }
188
+ function startCallbackServer() {
189
+ return new Promise((resolveServer) => {
190
+ let resolveResult;
191
+ let rejectResult;
192
+ const resultPromise = new Promise((resolve2, reject) => {
193
+ resolveResult = resolve2;
194
+ rejectResult = reject;
195
+ });
196
+ const server = createServer((req, res) => {
197
+ const url = new URL(req.url ?? "/", "http://localhost");
198
+ if (url.pathname === "/callback") {
199
+ const code = url.searchParams.get("code");
200
+ const state = url.searchParams.get("state");
201
+ const error = url.searchParams.get("error");
202
+ if (error) {
203
+ const desc = url.searchParams.get("error_description") ?? error;
204
+ res.writeHead(200, { "Content-Type": "text/html" });
205
+ res.end(`<html><body><h2>Authentication failed</h2><p>${desc}</p><p>You can close this window.</p></body></html>`);
206
+ rejectResult(new Error(desc));
207
+ return;
208
+ }
209
+ if (!code || !state) {
210
+ res.writeHead(400, { "Content-Type": "text/html" });
211
+ res.end("<html><body><h2>Invalid callback</h2><p>Missing authorization code.</p></body></html>");
212
+ return;
213
+ }
214
+ res.writeHead(200, { "Content-Type": "text/html" });
215
+ res.end("<html><body><h2>Authentication successful!</h2><p>You can close this window and return to the terminal.</p></body></html>");
216
+ resolveResult({ code, state });
217
+ } else {
218
+ res.writeHead(404);
219
+ res.end("Not found");
220
+ }
221
+ });
222
+ server.listen(0, "127.0.0.1", () => {
223
+ const addr = server.address();
224
+ const port = typeof addr === "object" ? addr.port : 0;
225
+ resolveServer({
226
+ port,
227
+ result: resultPromise,
228
+ close: () => {
229
+ server.close();
230
+ server.closeAllConnections();
231
+ }
232
+ });
233
+ });
234
+ setTimeout(() => {
235
+ rejectResult(new Error("Authentication timed out. Please try again."));
236
+ server.close();
237
+ }, 5 * 60 * 1e3).unref();
238
+ });
239
+ }
240
+
241
+ // src/lib/credentials.ts
242
+ function requireAuth() {
243
+ const creds = getCredentials();
244
+ if (!creds || !creds.access_token) {
245
+ throw new AuthError();
246
+ }
247
+ return creds;
248
+ }
249
+ async function refreshAccessToken(apiUrl) {
250
+ const creds = getCredentials();
251
+ if (!creds?.refresh_token) {
252
+ throw new AuthError("Refresh token not found. Run `insforge login` again.");
253
+ }
254
+ const platformUrl = getPlatformApiUrl(apiUrl);
255
+ const config = getGlobalConfig();
256
+ const clientId = config.oauth_client_id ?? DEFAULT_CLIENT_ID;
257
+ try {
258
+ const data = await refreshOAuthToken({
259
+ platformUrl,
260
+ refreshToken: creds.refresh_token,
261
+ clientId
262
+ });
263
+ const updated = {
264
+ ...creds,
265
+ access_token: data.access_token,
266
+ // Update refresh token if rotated
267
+ refresh_token: data.refresh_token ?? creds.refresh_token
268
+ };
269
+ saveCredentials(updated);
270
+ return data.access_token;
271
+ } catch {
272
+ throw new AuthError("Failed to refresh token. Run `insforge login` again.");
273
+ }
274
+ }
275
+
276
+ // src/lib/api/platform.ts
277
+ async function platformFetch(path3, options = {}, apiUrl) {
278
+ const baseUrl = getPlatformApiUrl(apiUrl);
279
+ const token = getAccessToken();
280
+ if (!token) {
281
+ throw new AuthError();
282
+ }
283
+ const headers = {
284
+ "Content-Type": "application/json",
285
+ Authorization: `Bearer ${token}`,
286
+ ...options.headers ?? {}
287
+ };
288
+ const res = await fetch(`${baseUrl}${path3}`, { ...options, headers });
289
+ if (res.status === 401) {
290
+ const newToken = await refreshAccessToken(apiUrl);
291
+ headers.Authorization = `Bearer ${newToken}`;
292
+ const retryRes = await fetch(`${baseUrl}${path3}`, { ...options, headers });
293
+ if (!retryRes.ok) {
294
+ const err = await retryRes.json().catch(() => ({}));
295
+ throw new CLIError(err.error ?? `Request failed: ${retryRes.status}`, retryRes.status === 403 ? 5 : 1);
296
+ }
297
+ return retryRes;
298
+ }
299
+ if (!res.ok) {
300
+ const err = await res.json().catch(() => ({}));
301
+ throw new CLIError(err.error ?? `Request failed: ${res.status}`, res.status === 403 ? 5 : 1);
302
+ }
303
+ return res;
304
+ }
305
+ async function login(email, password2, apiUrl) {
306
+ const baseUrl = getPlatformApiUrl(apiUrl);
307
+ const res = await fetch(`${baseUrl}/auth/v1/login`, {
308
+ method: "POST",
309
+ headers: { "Content-Type": "application/json" },
310
+ body: JSON.stringify({ email, password: password2 })
311
+ });
312
+ if (!res.ok) {
313
+ const err = await res.json().catch(() => ({}));
314
+ throw new AuthError(err.error ?? "Login failed. Check your email and password.");
315
+ }
316
+ const setCookie = res.headers.get("set-cookie") ?? "";
317
+ const refreshTokenMatch = setCookie.match(/refreshToken=([^;]+)/);
318
+ const data = await res.json();
319
+ return {
320
+ ...data,
321
+ // Attach refresh token to the response for storage
322
+ _refreshToken: refreshTokenMatch?.[1]
323
+ };
324
+ }
325
+ async function getProfile(apiUrl) {
326
+ const res = await platformFetch("/auth/v1/profile", {}, apiUrl);
327
+ const data = await res.json();
328
+ return data.user ?? data;
329
+ }
330
+ async function listOrganizations(apiUrl) {
331
+ const res = await platformFetch("/organizations/v1", {}, apiUrl);
332
+ const data = await res.json();
333
+ return data.organizations ?? data;
334
+ }
335
+ async function listProjects(orgId, apiUrl) {
336
+ const res = await platformFetch(`/organizations/v1/${orgId}/projects`, {}, apiUrl);
337
+ const data = await res.json();
338
+ return data.projects ?? data;
339
+ }
340
+ async function getProject(projectId, apiUrl) {
341
+ const res = await platformFetch(`/projects/v1/${projectId}`, {}, apiUrl);
342
+ const data = await res.json();
343
+ return data.project ?? data;
344
+ }
345
+ async function getProjectApiKey(projectId, apiUrl) {
346
+ const res = await platformFetch(`/projects/v1/${projectId}/access-api-key`, {}, apiUrl);
347
+ const data = await res.json();
348
+ return data.access_api_key;
349
+ }
350
+ async function createProject(orgId, name, region, apiUrl) {
351
+ const body = { name };
352
+ if (region) body.region = region;
353
+ const res = await platformFetch(`/organizations/v1/${orgId}/projects`, {
354
+ method: "POST",
355
+ body: JSON.stringify(body)
356
+ }, apiUrl);
357
+ const data = await res.json();
358
+ return data.project ?? data;
359
+ }
360
+
361
+ // src/commands/login.ts
362
+ function registerLoginCommand(program2) {
363
+ program2.command("login").description("Authenticate with InsForge platform").option("--email", "Login with email and password instead of browser").option("--client-id <id>", "OAuth client ID (defaults to insforge-cli)").action(async (opts, cmd) => {
364
+ const { json, apiUrl } = getRootOpts(cmd);
365
+ try {
366
+ if (opts.email) {
367
+ await loginWithEmail(json, apiUrl);
368
+ } else {
369
+ await loginWithOAuth(json, apiUrl, opts.clientId);
370
+ }
371
+ } catch (err) {
372
+ if (err instanceof Error && err.message.includes("cancelled")) {
373
+ process.exit(0);
374
+ }
375
+ handleError(err, json);
376
+ }
377
+ });
378
+ }
379
+ async function loginWithEmail(json, apiUrl) {
380
+ if (!json) {
381
+ clack.intro("InsForge CLI");
382
+ }
383
+ const email = json ? process.env.INSFORGE_EMAIL : await clack.text({
384
+ message: "Email:",
385
+ validate: (v) => v.includes("@") ? void 0 : "Please enter a valid email"
386
+ });
387
+ if (clack.isCancel(email)) {
388
+ clack.cancel("Login cancelled.");
389
+ throw new Error("cancelled");
390
+ }
391
+ const password2 = json ? process.env.INSFORGE_PASSWORD : await clack.password({
392
+ message: "Password:"
393
+ });
394
+ if (clack.isCancel(password2)) {
395
+ clack.cancel("Login cancelled.");
396
+ throw new Error("cancelled");
397
+ }
398
+ if (!email || !password2) {
399
+ throw new Error("Email and password are required. Set INSFORGE_EMAIL and INSFORGE_PASSWORD environment variables for non-interactive mode.");
400
+ }
401
+ if (!json) {
402
+ const s = clack.spinner();
403
+ s.start("Authenticating...");
404
+ const result = await login(email, password2, apiUrl);
405
+ const creds = {
406
+ access_token: result.token,
407
+ refresh_token: result._refreshToken ?? "",
408
+ user: result.user
409
+ };
410
+ saveCredentials(creds);
411
+ s.stop(`Authenticated as ${result.user.email}`);
412
+ clack.outro("Done");
413
+ } else {
414
+ const result = await login(email, password2, apiUrl);
415
+ const creds = {
416
+ access_token: result.token,
417
+ refresh_token: result._refreshToken ?? "",
418
+ user: result.user
419
+ };
420
+ saveCredentials(creds);
421
+ console.log(JSON.stringify({ success: true, user: result.user }));
422
+ }
423
+ }
424
+ async function loginWithOAuth(json, apiUrl, clientIdOverride) {
425
+ const platformUrl = getPlatformApiUrl(apiUrl);
426
+ const config = getGlobalConfig();
427
+ const clientId = clientIdOverride ?? config.oauth_client_id ?? DEFAULT_CLIENT_ID;
428
+ const pkce = generatePKCE();
429
+ const state = generateState();
430
+ const { port, result, close } = await startCallbackServer();
431
+ const redirectUri = `http://127.0.0.1:${port}/callback`;
432
+ const authUrl = buildAuthorizeUrl({
433
+ platformUrl,
434
+ clientId,
435
+ redirectUri,
436
+ codeChallenge: pkce.code_challenge,
437
+ state,
438
+ scopes: OAUTH_SCOPES
439
+ });
440
+ if (!json) {
441
+ clack.intro("InsForge CLI");
442
+ clack.log.info("Opening browser for authentication...");
443
+ clack.log.info(`If browser doesn't open, visit:
444
+ ${authUrl}`);
445
+ }
446
+ try {
447
+ const open = (await import("open")).default;
448
+ await open(authUrl);
449
+ } catch {
450
+ if (!json) {
451
+ clack.log.warn(`Could not open browser. Please visit the URL above.`);
452
+ }
453
+ }
454
+ if (!json) {
455
+ const s = clack.spinner();
456
+ s.start("Waiting for authentication...");
457
+ try {
458
+ const callbackResult = await result;
459
+ close();
460
+ if (callbackResult.state !== state) {
461
+ s.stop("Authentication failed");
462
+ throw new Error("State mismatch. Possible CSRF attack.");
463
+ }
464
+ s.message("Exchanging authorization code...");
465
+ const tokens = await exchangeCodeForTokens({
466
+ platformUrl,
467
+ code: callbackResult.code,
468
+ redirectUri,
469
+ clientId,
470
+ codeVerifier: pkce.code_verifier
471
+ });
472
+ const creds = {
473
+ access_token: tokens.access_token,
474
+ refresh_token: tokens.refresh_token,
475
+ user: { id: "", name: "", email: "", avatar_url: null, email_verified: true }
476
+ };
477
+ saveCredentials(creds);
478
+ try {
479
+ const profile = await getProfile(apiUrl);
480
+ creds.user = profile;
481
+ saveCredentials(creds);
482
+ s.stop(`Authenticated as ${profile.email}`);
483
+ } catch {
484
+ s.stop("Authenticated successfully");
485
+ }
486
+ clack.outro("Done");
487
+ } catch (err) {
488
+ close();
489
+ s.stop("Authentication failed");
490
+ throw err;
491
+ }
492
+ } else {
493
+ try {
494
+ const callbackResult = await result;
495
+ close();
496
+ if (callbackResult.state !== state) {
497
+ throw new Error("State mismatch.");
498
+ }
499
+ const tokens = await exchangeCodeForTokens({
500
+ platformUrl,
501
+ code: callbackResult.code,
502
+ redirectUri,
503
+ clientId,
504
+ codeVerifier: pkce.code_verifier
505
+ });
506
+ const creds = {
507
+ access_token: tokens.access_token,
508
+ refresh_token: tokens.refresh_token,
509
+ user: { id: "", name: "", email: "", avatar_url: null, email_verified: true }
510
+ };
511
+ saveCredentials(creds);
512
+ try {
513
+ const profile = await getProfile(apiUrl);
514
+ creds.user = profile;
515
+ saveCredentials(creds);
516
+ } catch {
517
+ }
518
+ console.log(JSON.stringify({ success: true, user: creds.user }));
519
+ } catch (err) {
520
+ close();
521
+ throw err;
522
+ }
523
+ }
524
+ }
525
+
526
+ // src/lib/output.ts
527
+ import Table from "cli-table3";
528
+ function outputJson(data) {
529
+ console.log(JSON.stringify(data, null, 2));
530
+ }
531
+ function outputTable(headers, rows) {
532
+ const table = new Table({
533
+ head: headers,
534
+ style: { head: ["cyan"] }
535
+ });
536
+ for (const row of rows) {
537
+ table.push(row);
538
+ }
539
+ console.log(table.toString());
540
+ }
541
+ function outputSuccess(message) {
542
+ console.log(`\u2713 ${message}`);
543
+ }
544
+ function outputInfo(message) {
545
+ console.log(message);
546
+ }
547
+
548
+ // src/commands/logout.ts
549
+ function registerLogoutCommand(program2) {
550
+ program2.command("logout").description("Log out from InsForge platform").action(async (_opts, cmd) => {
551
+ const { json } = getRootOpts(cmd);
552
+ try {
553
+ clearCredentials();
554
+ if (json) {
555
+ outputJson({ success: true, message: "Logged out successfully" });
556
+ } else {
557
+ outputSuccess("Logged out successfully.");
558
+ }
559
+ } catch (err) {
560
+ handleError(err, json);
561
+ }
562
+ });
563
+ }
564
+
565
+ // src/commands/whoami.ts
566
+ function registerWhoamiCommand(program2) {
567
+ program2.command("whoami").description("Show current authenticated user").action(async (_opts, cmd) => {
568
+ const { json, apiUrl } = getRootOpts(cmd);
569
+ try {
570
+ requireAuth();
571
+ const profile = await getProfile(apiUrl);
572
+ if (json) {
573
+ outputJson(profile);
574
+ } else {
575
+ outputInfo(`Logged in as: ${profile.email ?? profile.name}`);
576
+ if (profile.name) outputInfo(`Name: ${profile.name}`);
577
+ if (profile.id) outputInfo(`ID: ${profile.id}`);
578
+ }
579
+ } catch (err) {
580
+ handleError(err, json);
581
+ }
582
+ });
583
+ }
584
+
585
+ // src/commands/orgs/list.ts
586
+ function registerOrgsCommands(orgsCmd2) {
587
+ orgsCmd2.command("list").description("List all organizations").action(async (_opts, cmd) => {
588
+ const { json, apiUrl } = getRootOpts(cmd);
589
+ try {
590
+ requireAuth();
591
+ const orgs = await listOrganizations(apiUrl);
592
+ if (json) {
593
+ outputJson(orgs);
594
+ } else {
595
+ if (!orgs.length) {
596
+ console.log("No organizations found.");
597
+ return;
598
+ }
599
+ outputTable(
600
+ ["ID", "Name", "Type"],
601
+ orgs.map((o) => [
602
+ o.id,
603
+ o.name,
604
+ o.type ?? "-"
605
+ ])
606
+ );
607
+ }
608
+ } catch (err) {
609
+ handleError(err, json);
610
+ }
611
+ });
612
+ }
613
+
614
+ // src/commands/projects/list.ts
615
+ import * as clack2 from "@clack/prompts";
616
+ function registerProjectsCommands(projectsCmd2) {
617
+ projectsCmd2.command("list").description("List all projects in an organization").option("--org-id <id>", "Organization ID (uses default if not specified)").action(async (opts, cmd) => {
618
+ const { json, apiUrl } = getRootOpts(cmd);
619
+ try {
620
+ requireAuth();
621
+ let orgId = opts.orgId ?? getGlobalConfig().default_org_id;
622
+ if (!orgId) {
623
+ const orgs = await listOrganizations(apiUrl);
624
+ if (orgs.length === 0) {
625
+ throw new CLIError("No organizations found. Create one on the InsForge dashboard.");
626
+ }
627
+ if (orgs.length === 1) {
628
+ orgId = orgs[0].id;
629
+ } else if (!json) {
630
+ const selected = await clack2.select({
631
+ message: "Select an organization:",
632
+ options: orgs.map((o) => ({
633
+ value: o.id,
634
+ label: o.name
635
+ }))
636
+ });
637
+ if (clack2.isCancel(selected)) {
638
+ process.exit(0);
639
+ }
640
+ orgId = selected;
641
+ } else {
642
+ throw new CLIError("Multiple organizations found. Specify --org-id.");
643
+ }
644
+ }
645
+ const projects = await listProjects(orgId, apiUrl);
646
+ if (json) {
647
+ outputJson(projects);
648
+ } else {
649
+ if (!projects.length) {
650
+ console.log("No projects found.");
651
+ return;
652
+ }
653
+ outputTable(
654
+ ["ID", "Name", "Region", "Status", "AppKey"],
655
+ projects.map((p) => [p.id, p.name, p.region, p.status, p.appkey])
656
+ );
657
+ }
658
+ } catch (err) {
659
+ handleError(err, json);
660
+ }
661
+ });
662
+ }
663
+
664
+ // src/commands/projects/link.ts
665
+ import * as clack4 from "@clack/prompts";
666
+
667
+ // src/lib/skills.ts
668
+ import { exec } from "child_process";
669
+ import { promisify } from "util";
670
+ import * as clack3 from "@clack/prompts";
671
+ var execAsync = promisify(exec);
672
+ async function installSkills(json) {
673
+ try {
674
+ if (!json) clack3.log.info("Installing InsForge agent skills...");
675
+ await execAsync("npx skills add insforge/agent-skills -y -a antigravity -a augment -a claude-code -a cline -a codex -a cursor -a gemini-cli -a github-copilot -a kilo -a qoder -a qwen-code -a roo -a trae -a windsurf", {
676
+ cwd: process.cwd(),
677
+ timeout: 6e4
678
+ });
679
+ if (!json) clack3.log.success("InsForge agent skills installed.");
680
+ } catch {
681
+ if (!json) clack3.log.warn("Failed to install agent skills. You can run manually: npx skills add insforge/agent-skills");
682
+ }
683
+ }
684
+
685
+ // src/commands/projects/link.ts
686
+ function buildOssHost(appkey, region) {
687
+ return `https://${appkey}.${region}.insforge.app`;
688
+ }
689
+ function registerProjectLinkCommand(program2) {
690
+ program2.command("link").description("Link current directory to an InsForge project").option("--project-id <id>", "Project ID to link").option("--org-id <id>", "Organization ID").action(async (opts, cmd) => {
691
+ const { json, apiUrl } = getRootOpts(cmd);
692
+ try {
693
+ requireAuth();
694
+ let orgId = opts.orgId;
695
+ let projectId = opts.projectId;
696
+ if (!orgId) {
697
+ const orgs = await listOrganizations(apiUrl);
698
+ if (orgs.length === 0) {
699
+ throw new CLIError("No organizations found.");
700
+ }
701
+ if (json) {
702
+ throw new CLIError("Specify --org-id in JSON mode.");
703
+ }
704
+ const selected = await clack4.select({
705
+ message: "Select an organization:",
706
+ options: orgs.map((o) => ({
707
+ value: o.id,
708
+ label: o.name
709
+ }))
710
+ });
711
+ if (clack4.isCancel(selected)) process.exit(0);
712
+ orgId = selected;
713
+ }
714
+ const config = getGlobalConfig();
715
+ config.default_org_id = orgId;
716
+ saveGlobalConfig(config);
717
+ if (!projectId) {
718
+ const projects = await listProjects(orgId, apiUrl);
719
+ if (projects.length === 0) {
720
+ throw new CLIError("No projects found in this organization.");
721
+ }
722
+ if (json) {
723
+ throw new CLIError("Specify --project-id in JSON mode.");
724
+ }
725
+ const selected = await clack4.select({
726
+ message: "Select a project to link:",
727
+ options: projects.map((p) => ({
728
+ value: p.id,
729
+ label: `${p.name} (${p.region}, ${p.status})`
730
+ }))
731
+ });
732
+ if (clack4.isCancel(selected)) process.exit(0);
733
+ projectId = selected;
734
+ }
735
+ const [project, apiKey] = await Promise.all([
736
+ getProject(projectId, apiUrl),
737
+ getProjectApiKey(projectId, apiUrl)
738
+ ]);
739
+ const projectConfig = {
740
+ project_id: project.id,
741
+ project_name: project.name,
742
+ org_id: project.organization_id,
743
+ appkey: project.appkey,
744
+ region: project.region,
745
+ api_key: apiKey,
746
+ oss_host: buildOssHost(project.appkey, project.region)
747
+ };
748
+ saveProjectConfig(projectConfig);
749
+ if (json) {
750
+ outputJson({ success: true, project: { id: project.id, name: project.name, region: project.region } });
751
+ } else {
752
+ outputSuccess(`Linked to project "${project.name}" (${project.appkey}.${project.region})`);
753
+ }
754
+ await installSkills(json);
755
+ } catch (err) {
756
+ handleError(err, json);
757
+ }
758
+ });
759
+ }
760
+
761
+ // src/lib/api/oss.ts
762
+ function requireProjectConfig() {
763
+ const config = getProjectConfig();
764
+ if (!config) {
765
+ throw new ProjectNotLinkedError();
766
+ }
767
+ return config;
768
+ }
769
+ async function getAnonKey() {
770
+ const res = await ossFetch("/api/auth/tokens/anon", { method: "POST" });
771
+ const data = await res.json();
772
+ return data.accessToken;
773
+ }
774
+ async function ossFetch(path3, options = {}) {
775
+ const config = requireProjectConfig();
776
+ const headers = {
777
+ "Content-Type": "application/json",
778
+ Authorization: `Bearer ${config.api_key}`,
779
+ ...options.headers ?? {}
780
+ };
781
+ const res = await fetch(`${config.oss_host}${path3}`, { ...options, headers });
782
+ if (!res.ok) {
783
+ const err = await res.json().catch(() => ({}));
784
+ throw new CLIError(err.error ?? `OSS request failed: ${res.status}`);
785
+ }
786
+ return res;
787
+ }
788
+
789
+ // src/commands/db/query.ts
790
+ function registerDbCommands(dbCmd2) {
791
+ dbCmd2.command("query <sql>").description("Execute a SQL query against the database").option("--unrestricted", "Use unrestricted mode (allows system table access)").action(async (sql, opts, cmd) => {
792
+ const { json } = getRootOpts(cmd);
793
+ try {
794
+ requireAuth();
795
+ const endpoint = opts.unrestricted ? "/api/database/advance/rawsql/unrestricted" : "/api/database/advance/rawsql";
796
+ const res = await ossFetch(endpoint, {
797
+ method: "POST",
798
+ body: JSON.stringify({ query: sql })
799
+ });
800
+ const data = await res.json();
801
+ if (json) {
802
+ outputJson(data);
803
+ } else {
804
+ const rows = data.rows ?? data.data ?? null;
805
+ if (rows && rows.length > 0) {
806
+ const headers = Object.keys(rows[0]);
807
+ outputTable(
808
+ headers,
809
+ rows.map((row) => headers.map((h) => String(row[h] ?? "")))
810
+ );
811
+ console.log(`${rows.length} row(s) returned.`);
812
+ } else {
813
+ console.log("Query executed successfully.");
814
+ if (rows && rows.length === 0) {
815
+ console.log("No rows returned.");
816
+ }
817
+ }
818
+ }
819
+ } catch (err) {
820
+ handleError(err, json);
821
+ }
822
+ });
823
+ }
824
+
825
+ // src/commands/db/tables.ts
826
+ function registerDbTablesCommand(dbCmd2) {
827
+ dbCmd2.command("tables").description("List all database tables").action(async (_opts, cmd) => {
828
+ const { json } = getRootOpts(cmd);
829
+ try {
830
+ requireAuth();
831
+ const res = await ossFetch("/api/database/tables");
832
+ const tables = await res.json();
833
+ if (json) {
834
+ outputJson(tables);
835
+ } else {
836
+ if (tables.length === 0) {
837
+ console.log("No tables found.");
838
+ return;
839
+ }
840
+ outputTable(
841
+ ["Table Name"],
842
+ tables.map((t) => [t])
843
+ );
844
+ }
845
+ } catch (err) {
846
+ handleError(err, json);
847
+ }
848
+ });
849
+ }
850
+
851
+ // src/commands/db/functions.ts
852
+ function str(val) {
853
+ if (val === null || val === void 0) return "-";
854
+ if (Array.isArray(val)) return val.join(", ");
855
+ return String(val);
856
+ }
857
+ function extractArray(raw) {
858
+ if (Array.isArray(raw)) return raw;
859
+ if (raw && typeof raw === "object") {
860
+ const arr = Object.values(raw).find(Array.isArray);
861
+ if (arr) return arr;
862
+ }
863
+ return [];
864
+ }
865
+ function registerDbFunctionsCommand(dbCmd2) {
866
+ dbCmd2.command("functions").description("List all database functions").action(async (_opts, cmd) => {
867
+ const { json } = getRootOpts(cmd);
868
+ try {
869
+ requireAuth();
870
+ const res = await ossFetch("/api/database/functions");
871
+ const raw = await res.json();
872
+ const functions = extractArray(raw);
873
+ if (json) {
874
+ outputJson(raw);
875
+ } else {
876
+ if (functions.length === 0) {
877
+ console.log("No database functions found.");
878
+ return;
879
+ }
880
+ outputTable(
881
+ ["Name", "Schema", "Language", "Return Type", "Arguments"],
882
+ functions.map((f) => [str(f.name), str(f.schema), str(f.language), str(f.returnType), str(f.arguments)])
883
+ );
884
+ }
885
+ } catch (err) {
886
+ handleError(err, json);
887
+ }
888
+ });
889
+ }
890
+
891
+ // src/commands/db/indexes.ts
892
+ function str2(val) {
893
+ if (val === null || val === void 0) return "-";
894
+ if (Array.isArray(val)) return val.join(", ");
895
+ return String(val);
896
+ }
897
+ function extractArray2(raw) {
898
+ if (Array.isArray(raw)) return raw;
899
+ if (raw && typeof raw === "object") {
900
+ const arr = Object.values(raw).find(Array.isArray);
901
+ if (arr) return arr;
902
+ }
903
+ return [];
904
+ }
905
+ function registerDbIndexesCommand(dbCmd2) {
906
+ dbCmd2.command("indexes").description("List all database indexes").action(async (_opts, cmd) => {
907
+ const { json } = getRootOpts(cmd);
908
+ try {
909
+ requireAuth();
910
+ const res = await ossFetch("/api/database/indexes");
911
+ const raw = await res.json();
912
+ const indexes = extractArray2(raw);
913
+ if (json) {
914
+ outputJson(raw);
915
+ } else {
916
+ if (indexes.length === 0) {
917
+ console.log("No database indexes found.");
918
+ return;
919
+ }
920
+ outputTable(
921
+ ["Table", "Index Name", "Definition", "Unique", "Primary"],
922
+ indexes.map((i) => [
923
+ str2(i.tableName),
924
+ str2(i.indexName),
925
+ str2(i.indexDef),
926
+ i.isUnique ? "Yes" : "No",
927
+ i.isPrimary ? "Yes" : "No"
928
+ ])
929
+ );
930
+ }
931
+ } catch (err) {
932
+ handleError(err, json);
933
+ }
934
+ });
935
+ }
936
+
937
+ // src/commands/db/policies.ts
938
+ function str3(val) {
939
+ if (val === null || val === void 0) return "-";
940
+ if (Array.isArray(val)) return val.join(", ");
941
+ return String(val);
942
+ }
943
+ function extractArray3(raw) {
944
+ if (Array.isArray(raw)) return raw;
945
+ if (raw && typeof raw === "object") {
946
+ const arr = Object.values(raw).find(Array.isArray);
947
+ if (arr) return arr;
948
+ }
949
+ return [];
950
+ }
951
+ function registerDbPoliciesCommand(dbCmd2) {
952
+ dbCmd2.command("policies").description("List all RLS policies").action(async (_opts, cmd) => {
953
+ const { json } = getRootOpts(cmd);
954
+ try {
955
+ requireAuth();
956
+ const res = await ossFetch("/api/database/policies");
957
+ const raw = await res.json();
958
+ const policies = extractArray3(raw);
959
+ if (json) {
960
+ outputJson(raw);
961
+ } else {
962
+ if (policies.length === 0) {
963
+ console.log("No RLS policies found.");
964
+ return;
965
+ }
966
+ outputTable(
967
+ ["Table", "Policy Name", "Command", "Roles", "Qual", "With Check"],
968
+ policies.map((p) => [
969
+ str3(p.tableName),
970
+ str3(p.policyName),
971
+ str3(p.cmd),
972
+ str3(p.roles),
973
+ str3(p.qual),
974
+ str3(p.withCheck)
975
+ ])
976
+ );
977
+ }
978
+ } catch (err) {
979
+ handleError(err, json);
980
+ }
981
+ });
982
+ }
983
+
984
+ // src/commands/db/triggers.ts
985
+ function str4(val) {
986
+ if (val === null || val === void 0) return "-";
987
+ if (Array.isArray(val)) return val.join(", ");
988
+ return String(val);
989
+ }
990
+ function extractArray4(raw) {
991
+ if (Array.isArray(raw)) return raw;
992
+ if (raw && typeof raw === "object") {
993
+ const arr = Object.values(raw).find(Array.isArray);
994
+ if (arr) return arr;
995
+ }
996
+ return [];
997
+ }
998
+ function registerDbTriggersCommand(dbCmd2) {
999
+ dbCmd2.command("triggers").description("List all database triggers").action(async (_opts, cmd) => {
1000
+ const { json } = getRootOpts(cmd);
1001
+ try {
1002
+ requireAuth();
1003
+ const res = await ossFetch("/api/database/triggers");
1004
+ const raw = await res.json();
1005
+ const triggers = extractArray4(raw);
1006
+ if (json) {
1007
+ outputJson(raw);
1008
+ } else {
1009
+ if (triggers.length === 0) {
1010
+ console.log("No database triggers found.");
1011
+ return;
1012
+ }
1013
+ outputTable(
1014
+ ["Name", "Table", "Timing", "Events", "Function", "Enabled"],
1015
+ triggers.map((t) => [
1016
+ str4(t.name),
1017
+ str4(t.tableName),
1018
+ str4(t.timing),
1019
+ str4(t.events),
1020
+ str4(t.functionName),
1021
+ t.enabled ? "Yes" : "No"
1022
+ ])
1023
+ );
1024
+ }
1025
+ } catch (err) {
1026
+ handleError(err, json);
1027
+ }
1028
+ });
1029
+ }
1030
+
1031
+ // src/commands/db/rpc.ts
1032
+ function registerDbRpcCommand(dbCmd2) {
1033
+ dbCmd2.command("rpc <functionName>").description("Call a database function via RPC").option("--data <json>", "JSON body to pass as function parameters").action(async (functionName, opts, cmd) => {
1034
+ const { json } = getRootOpts(cmd);
1035
+ try {
1036
+ requireAuth();
1037
+ const body = opts.data ? JSON.stringify(JSON.parse(opts.data)) : void 0;
1038
+ const res = await ossFetch(`/api/database/rpc/${encodeURIComponent(functionName)}`, {
1039
+ method: body ? "POST" : "GET",
1040
+ ...body ? { body } : {}
1041
+ });
1042
+ const result = await res.json();
1043
+ if (json) {
1044
+ outputJson(result);
1045
+ } else {
1046
+ console.log(JSON.stringify(result, null, 2));
1047
+ }
1048
+ } catch (err) {
1049
+ handleError(err, json);
1050
+ }
1051
+ });
1052
+ }
1053
+
1054
+ // src/commands/db/export.ts
1055
+ import { writeFileSync as writeFileSync2 } from "fs";
1056
+ function registerDbExportCommand(dbCmd2) {
1057
+ dbCmd2.command("export").description("Export database schema and/or data").option("--format <format>", "Export format: sql or json", "sql").option("--tables <tables>", "Comma-separated list of tables to export (default: all)").option("--no-data", "Exclude table data (schema only)").option("--include-functions", "Include database functions").option("--include-sequences", "Include sequences").option("--include-views", "Include views").option("--row-limit <n>", "Maximum rows per table").option("-o, --output <file>", "Output file path (default: stdout)").action(async (opts, cmd) => {
1058
+ const { json } = getRootOpts(cmd);
1059
+ try {
1060
+ requireAuth();
1061
+ const body = {
1062
+ format: opts.format,
1063
+ includeData: opts.data !== false
1064
+ };
1065
+ if (opts.tables) {
1066
+ body.tables = opts.tables.split(",").map((t) => t.trim());
1067
+ }
1068
+ if (opts.includeFunctions) body.includeFunctions = true;
1069
+ if (opts.includeSequences) body.includeSequences = true;
1070
+ if (opts.includeViews) body.includeViews = true;
1071
+ if (opts.rowLimit) body.rowLimit = parseInt(opts.rowLimit, 10);
1072
+ const res = await ossFetch("/api/database/advance/export", {
1073
+ method: "POST",
1074
+ body: JSON.stringify(body)
1075
+ });
1076
+ const raw = await res.text();
1077
+ let content;
1078
+ let meta = null;
1079
+ try {
1080
+ const parsed = JSON.parse(raw);
1081
+ if (typeof parsed.content === "string") {
1082
+ content = parsed.content;
1083
+ meta = { format: parsed.format, tables: parsed.tables };
1084
+ } else {
1085
+ content = raw;
1086
+ }
1087
+ } catch {
1088
+ content = raw;
1089
+ }
1090
+ if (json) {
1091
+ outputJson(meta ?? { content });
1092
+ return;
1093
+ }
1094
+ if (opts.output) {
1095
+ writeFileSync2(opts.output, content);
1096
+ const tableCount = meta?.tables?.length;
1097
+ const suffix = tableCount ? ` (${tableCount} tables, format: ${meta?.format ?? opts.format})` : "";
1098
+ outputSuccess(`Exported to ${opts.output}${suffix}`);
1099
+ } else {
1100
+ console.log(content);
1101
+ }
1102
+ } catch (err) {
1103
+ handleError(err, json);
1104
+ }
1105
+ });
1106
+ }
1107
+
1108
+ // src/commands/db/import.ts
1109
+ import { readFileSync as readFileSync2 } from "fs";
1110
+ import { basename } from "path";
1111
+ function registerDbImportCommand(dbCmd2) {
1112
+ dbCmd2.command("import <file>").description("Import database from a local SQL file").option("--truncate", "Truncate existing tables before import").action(async (file, opts, cmd) => {
1113
+ const { json } = getRootOpts(cmd);
1114
+ try {
1115
+ requireAuth();
1116
+ const config = getProjectConfig();
1117
+ if (!config) throw new ProjectNotLinkedError();
1118
+ const fileContent = readFileSync2(file);
1119
+ const fileName = basename(file);
1120
+ const formData = new FormData();
1121
+ formData.append("file", new Blob([fileContent]), fileName);
1122
+ if (opts.truncate) {
1123
+ formData.append("truncate", "true");
1124
+ }
1125
+ const res = await fetch(`${config.oss_host}/api/database/advance/import`, {
1126
+ method: "POST",
1127
+ headers: {
1128
+ Authorization: `Bearer ${config.api_key}`
1129
+ },
1130
+ body: formData
1131
+ });
1132
+ if (!res.ok) {
1133
+ const err = await res.json().catch(() => ({}));
1134
+ throw new CLIError(err.error ?? `Import failed: ${res.status}`);
1135
+ }
1136
+ const data = await res.json();
1137
+ if (json) {
1138
+ outputJson(data);
1139
+ } else {
1140
+ outputSuccess(`Imported ${data.filename} (${data.tables.length} tables, ${data.rowsImported} rows)`);
1141
+ }
1142
+ } catch (err) {
1143
+ handleError(err, json);
1144
+ }
1145
+ });
1146
+ }
1147
+
1148
+ // src/commands/records/list.ts
1149
+ function registerRecordsCommands(recordsCmd2) {
1150
+ recordsCmd2.command("list <table>").description("List records from a table").option("--select <columns>", "Columns to select (comma-separated)").option("--filter <filter>", 'Filter expression (e.g. "name=eq.John")').option("--order <order>", 'Order by (e.g. "created_at.desc")').option("--limit <n>", "Limit number of records", parseInt).option("--offset <n>", "Offset for pagination", parseInt).action(async (table, opts, cmd) => {
1151
+ const { json } = getRootOpts(cmd);
1152
+ try {
1153
+ requireAuth();
1154
+ const params = new URLSearchParams();
1155
+ if (opts.select) params.set("select", opts.select);
1156
+ if (opts.filter) params.set(opts.filter.split("=")[0], opts.filter.split("=").slice(1).join("="));
1157
+ if (opts.order) params.set("order", opts.order);
1158
+ if (opts.limit) params.set("limit", String(opts.limit));
1159
+ if (opts.offset) params.set("offset", String(opts.offset));
1160
+ const query = params.toString();
1161
+ const path3 = `/api/database/records/${encodeURIComponent(table)}${query ? `?${query}` : ""}`;
1162
+ const res = await ossFetch(path3);
1163
+ const data = await res.json();
1164
+ const records = data.data ?? [];
1165
+ if (json) {
1166
+ outputJson(data);
1167
+ } else {
1168
+ if (records.length === 0) {
1169
+ console.log("No records found.");
1170
+ return;
1171
+ }
1172
+ const headers = Object.keys(records[0]);
1173
+ outputTable(
1174
+ headers,
1175
+ records.map((r) => headers.map((h) => {
1176
+ const val = r[h];
1177
+ if (val === null || val === void 0) return "";
1178
+ if (typeof val === "object") return JSON.stringify(val);
1179
+ return String(val);
1180
+ }))
1181
+ );
1182
+ console.log(`${records.length} record(s).`);
1183
+ }
1184
+ } catch (err) {
1185
+ handleError(err, json);
1186
+ }
1187
+ });
1188
+ }
1189
+
1190
+ // src/commands/records/create.ts
1191
+ function registerRecordsCreateCommand(recordsCmd2) {
1192
+ recordsCmd2.command("create <table>").description("Create record(s) in a table").option("--data <json>", "JSON data to insert (object or array of objects)").action(async (table, opts, cmd) => {
1193
+ const { json } = getRootOpts(cmd);
1194
+ try {
1195
+ requireAuth();
1196
+ if (!opts.data) {
1197
+ throw new CLIError(`--data is required. Example: --data '{"name":"John"}'`);
1198
+ }
1199
+ let records;
1200
+ try {
1201
+ const parsed = JSON.parse(opts.data);
1202
+ records = Array.isArray(parsed) ? parsed : [parsed];
1203
+ } catch {
1204
+ throw new CLIError("Invalid JSON in --data. Provide a JSON object or array.");
1205
+ }
1206
+ const res = await ossFetch(
1207
+ `/api/database/records/${encodeURIComponent(table)}?return=representation`,
1208
+ {
1209
+ method: "POST",
1210
+ body: JSON.stringify(records)
1211
+ }
1212
+ );
1213
+ const data = await res.json();
1214
+ if (json) {
1215
+ outputJson(data);
1216
+ } else {
1217
+ const created = data.data ?? [];
1218
+ outputSuccess(`Created ${created.length || records.length} record(s) in "${table}".`);
1219
+ }
1220
+ } catch (err) {
1221
+ handleError(err, json);
1222
+ }
1223
+ });
1224
+ }
1225
+
1226
+ // src/commands/records/update.ts
1227
+ function registerRecordsUpdateCommand(recordsCmd2) {
1228
+ recordsCmd2.command("update <table>").description("Update records in a table matching a filter").option("--filter <filter>", 'Filter expression (e.g. "id=eq.123")').option("--data <json>", "JSON data to update").action(async (table, opts, cmd) => {
1229
+ const { json } = getRootOpts(cmd);
1230
+ try {
1231
+ requireAuth();
1232
+ if (!opts.filter) {
1233
+ throw new CLIError("--filter is required to prevent accidental updates to all rows.");
1234
+ }
1235
+ if (!opts.data) {
1236
+ throw new CLIError(`--data is required. Example: --data '{"name":"Jane"}'`);
1237
+ }
1238
+ let body;
1239
+ try {
1240
+ body = JSON.parse(opts.data);
1241
+ } catch {
1242
+ throw new CLIError("Invalid JSON in --data.");
1243
+ }
1244
+ const params = new URLSearchParams();
1245
+ params.set(opts.filter.split("=")[0], opts.filter.split("=").slice(1).join("="));
1246
+ params.set("return", "representation");
1247
+ const res = await ossFetch(
1248
+ `/api/database/records/${encodeURIComponent(table)}?${params}`,
1249
+ {
1250
+ method: "PATCH",
1251
+ body: JSON.stringify(body)
1252
+ }
1253
+ );
1254
+ const data = await res.json();
1255
+ if (json) {
1256
+ outputJson(data);
1257
+ } else {
1258
+ const updated = data.data ?? [];
1259
+ outputSuccess(`Updated ${updated.length} record(s) in "${table}".`);
1260
+ }
1261
+ } catch (err) {
1262
+ handleError(err, json);
1263
+ }
1264
+ });
1265
+ }
1266
+
1267
+ // src/commands/records/delete.ts
1268
+ function registerRecordsDeleteCommand(recordsCmd2) {
1269
+ recordsCmd2.command("delete <table>").description("Delete records from a table matching a filter").option("--filter <filter>", 'Filter expression (e.g. "id=eq.123")').action(async (table, opts, cmd) => {
1270
+ const { json } = getRootOpts(cmd);
1271
+ try {
1272
+ requireAuth();
1273
+ if (!opts.filter) {
1274
+ throw new CLIError("--filter is required to prevent accidental deletion of all rows.");
1275
+ }
1276
+ const params = new URLSearchParams();
1277
+ params.set(opts.filter.split("=")[0], opts.filter.split("=").slice(1).join("="));
1278
+ params.set("return", "representation");
1279
+ const res = await ossFetch(
1280
+ `/api/database/records/${encodeURIComponent(table)}?${params}`,
1281
+ { method: "DELETE" }
1282
+ );
1283
+ const data = await res.json();
1284
+ if (json) {
1285
+ outputJson(data);
1286
+ } else {
1287
+ const deleted = data.data ?? [];
1288
+ outputSuccess(`Deleted ${deleted.length} record(s) from "${table}".`);
1289
+ }
1290
+ } catch (err) {
1291
+ handleError(err, json);
1292
+ }
1293
+ });
1294
+ }
1295
+
1296
+ // src/commands/functions/list.ts
1297
+ function registerFunctionsCommands(functionsCmd2) {
1298
+ functionsCmd2.command("list").description("List all edge functions").action(async (_opts, cmd) => {
1299
+ const { json } = getRootOpts(cmd);
1300
+ try {
1301
+ requireAuth();
1302
+ const res = await ossFetch("/api/functions");
1303
+ const data = await res.json();
1304
+ const functions = data.functions ?? [];
1305
+ if (json) {
1306
+ outputJson(functions);
1307
+ } else {
1308
+ if (functions.length === 0) {
1309
+ console.log("No functions found.");
1310
+ return;
1311
+ }
1312
+ outputTable(
1313
+ ["Slug", "Name", "Status", "Created At"],
1314
+ functions.map((f) => [
1315
+ f.slug,
1316
+ f.name ?? "-",
1317
+ f.status ?? "-",
1318
+ f.created_at ? new Date(f.created_at).toLocaleString() : "-"
1319
+ ])
1320
+ );
1321
+ }
1322
+ } catch (err) {
1323
+ handleError(err, json);
1324
+ }
1325
+ });
1326
+ }
1327
+
1328
+ // src/commands/functions/deploy.ts
1329
+ import { readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
1330
+ import { join as join2 } from "path";
1331
+ function registerFunctionsDeployCommand(functionsCmd2) {
1332
+ functionsCmd2.command("deploy <slug>").description("Deploy an edge function (create or update)").option("--file <path>", "Path to the function source file").option("--name <name>", "Function display name").option("--description <desc>", "Function description").action(async (slug, opts, cmd) => {
1333
+ const { json } = getRootOpts(cmd);
1334
+ try {
1335
+ requireAuth();
1336
+ const filePath = opts.file ?? join2(process.cwd(), "insforge", "functions", slug, "index.ts");
1337
+ if (!existsSync2(filePath)) {
1338
+ throw new CLIError(
1339
+ `Source file not found: ${filePath}
1340
+ Specify --file <path> or create ${join2("insforge", "functions", slug, "index.ts")}`
1341
+ );
1342
+ }
1343
+ const code = readFileSync3(filePath, "utf-8");
1344
+ const name = opts.name ?? slug;
1345
+ const description = opts.description ?? "";
1346
+ let exists = false;
1347
+ try {
1348
+ await ossFetch(`/api/functions/${encodeURIComponent(slug)}`);
1349
+ exists = true;
1350
+ } catch {
1351
+ exists = false;
1352
+ }
1353
+ if (exists) {
1354
+ await ossFetch(`/api/functions/${encodeURIComponent(slug)}`, {
1355
+ method: "PUT",
1356
+ body: JSON.stringify({ name, description, code })
1357
+ });
1358
+ } else {
1359
+ await ossFetch("/api/functions", {
1360
+ method: "POST",
1361
+ body: JSON.stringify({ slug, name, description, code })
1362
+ });
1363
+ }
1364
+ if (json) {
1365
+ outputJson({ success: true, slug, action: exists ? "updated" : "created" });
1366
+ } else {
1367
+ outputSuccess(`Function "${slug}" ${exists ? "updated" : "created"} successfully.`);
1368
+ }
1369
+ } catch (err) {
1370
+ handleError(err, json);
1371
+ }
1372
+ });
1373
+ }
1374
+
1375
+ // src/commands/functions/invoke.ts
1376
+ function registerFunctionsInvokeCommand(functionsCmd2) {
1377
+ functionsCmd2.command("invoke <slug>").description("Invoke an edge function").option("--data <json>", "JSON body to send to the function").option("--method <method>", "HTTP method (GET, POST, PUT, PATCH, DELETE)", "POST").action(async (slug, opts, cmd) => {
1378
+ const { json } = getRootOpts(cmd);
1379
+ try {
1380
+ requireAuth();
1381
+ const config = getProjectConfig();
1382
+ if (!config) throw new ProjectNotLinkedError();
1383
+ const method = opts.method.toUpperCase();
1384
+ const headers = {
1385
+ "Content-Type": "application/json",
1386
+ Authorization: `Bearer ${config.api_key}`
1387
+ };
1388
+ const fetchOpts = { method, headers };
1389
+ if (opts.data && ["POST", "PUT", "PATCH"].includes(method)) {
1390
+ fetchOpts.body = opts.data;
1391
+ }
1392
+ const res = await fetch(
1393
+ `${config.oss_host}/functions/${encodeURIComponent(slug)}`,
1394
+ fetchOpts
1395
+ );
1396
+ const contentType = res.headers.get("content-type") ?? "";
1397
+ const status = res.status;
1398
+ if (contentType.includes("application/json")) {
1399
+ const data = await res.json();
1400
+ if (json) {
1401
+ outputJson({ status, body: data });
1402
+ } else {
1403
+ if (status >= 400) {
1404
+ console.error(`HTTP ${status}`);
1405
+ }
1406
+ console.log(JSON.stringify(data, null, 2));
1407
+ }
1408
+ } else {
1409
+ const text3 = await res.text();
1410
+ if (!json && status >= 400) {
1411
+ console.error(`HTTP ${status}`);
1412
+ }
1413
+ console.log(text3);
1414
+ }
1415
+ if (status >= 400) {
1416
+ process.exit(1);
1417
+ }
1418
+ } catch (err) {
1419
+ handleError(err, json);
1420
+ }
1421
+ });
1422
+ }
1423
+
1424
+ // src/commands/functions/code.ts
1425
+ function registerFunctionsCodeCommand(functionsCmd2) {
1426
+ functionsCmd2.command("code <slug>").description("Fetch and display the source code of an edge function").action(async (slug, _opts, cmd) => {
1427
+ const { json } = getRootOpts(cmd);
1428
+ try {
1429
+ requireAuth();
1430
+ const res = await ossFetch(`/api/functions/${encodeURIComponent(slug)}`);
1431
+ const fn = await res.json();
1432
+ if (json) {
1433
+ outputJson(fn);
1434
+ } else {
1435
+ console.log(`Function: ${fn.name} (${fn.slug})`);
1436
+ console.log(`Status: ${fn.status}`);
1437
+ if (fn.description) console.log(`Desc: ${fn.description}`);
1438
+ if (fn.deployed_at) console.log(`Deployed: ${fn.deployed_at}`);
1439
+ console.log("---");
1440
+ console.log(fn.code);
1441
+ }
1442
+ } catch (err) {
1443
+ handleError(err, json);
1444
+ }
1445
+ });
1446
+ }
1447
+
1448
+ // src/commands/storage/buckets.ts
1449
+ function registerStorageBucketsCommand(storageCmd2) {
1450
+ storageCmd2.command("buckets").description("List all storage buckets").action(async (_opts, cmd) => {
1451
+ const { json } = getRootOpts(cmd);
1452
+ try {
1453
+ requireAuth();
1454
+ const res = await ossFetch("/api/storage/buckets");
1455
+ const raw = await res.json();
1456
+ let buckets;
1457
+ if (Array.isArray(raw)) {
1458
+ buckets = raw;
1459
+ } else if (raw && typeof raw === "object" && "buckets" in raw && Array.isArray(raw.buckets)) {
1460
+ buckets = raw.buckets;
1461
+ } else {
1462
+ const arr = raw && typeof raw === "object" ? Object.values(raw).find(Array.isArray) : null;
1463
+ buckets = arr ?? [];
1464
+ }
1465
+ if (json) {
1466
+ outputJson(raw);
1467
+ } else {
1468
+ if (buckets.length === 0) {
1469
+ console.log("No buckets found.");
1470
+ return;
1471
+ }
1472
+ outputTable(
1473
+ ["Bucket Name"],
1474
+ buckets.map((b) => [typeof b === "string" ? b : JSON.stringify(b)])
1475
+ );
1476
+ }
1477
+ } catch (err) {
1478
+ handleError(err, json);
1479
+ }
1480
+ });
1481
+ }
1482
+
1483
+ // src/commands/storage/upload.ts
1484
+ import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
1485
+ import { basename as basename2 } from "path";
1486
+ function registerStorageUploadCommand(storageCmd2) {
1487
+ storageCmd2.command("upload <file>").description("Upload a file to a storage bucket").requiredOption("--bucket <name>", "Target bucket name").option("--key <objectKey>", "Object key (defaults to filename)").action(async (file, opts, cmd) => {
1488
+ const { json } = getRootOpts(cmd);
1489
+ try {
1490
+ requireAuth();
1491
+ const config = getProjectConfig();
1492
+ if (!config) throw new ProjectNotLinkedError();
1493
+ if (!existsSync3(file)) {
1494
+ throw new CLIError(`File not found: ${file}`);
1495
+ }
1496
+ const fileContent = readFileSync4(file);
1497
+ const objectKey = opts.key ?? basename2(file);
1498
+ const bucketName = opts.bucket;
1499
+ const formData = new FormData();
1500
+ const blob = new Blob([fileContent]);
1501
+ formData.append("file", blob, objectKey);
1502
+ const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
1503
+ const res = await fetch(url, {
1504
+ method: "PUT",
1505
+ headers: {
1506
+ Authorization: `Bearer ${config.api_key}`
1507
+ },
1508
+ body: formData
1509
+ });
1510
+ if (!res.ok) {
1511
+ const err = await res.json().catch(() => ({}));
1512
+ throw new CLIError(err.error ?? `Upload failed: ${res.status}`);
1513
+ }
1514
+ const data = await res.json();
1515
+ if (json) {
1516
+ outputJson(data);
1517
+ } else {
1518
+ outputSuccess(`Uploaded "${basename2(file)}" to bucket "${bucketName}".`);
1519
+ }
1520
+ } catch (err) {
1521
+ handleError(err, json);
1522
+ }
1523
+ });
1524
+ }
1525
+
1526
+ // src/commands/storage/download.ts
1527
+ import { writeFileSync as writeFileSync3 } from "fs";
1528
+ import { join as join3, basename as basename3 } from "path";
1529
+ function registerStorageDownloadCommand(storageCmd2) {
1530
+ storageCmd2.command("download <objectKey>").description("Download a file from a storage bucket").requiredOption("--bucket <name>", "Source bucket name").option("--output <path>", "Output file path (defaults to current directory)").action(async (objectKey, opts, cmd) => {
1531
+ const { json } = getRootOpts(cmd);
1532
+ try {
1533
+ requireAuth();
1534
+ const config = getProjectConfig();
1535
+ if (!config) throw new ProjectNotLinkedError();
1536
+ const bucketName = opts.bucket;
1537
+ const url = `${config.oss_host}/api/storage/buckets/${encodeURIComponent(bucketName)}/objects/${encodeURIComponent(objectKey)}`;
1538
+ const res = await fetch(url, {
1539
+ headers: {
1540
+ Authorization: `Bearer ${config.api_key}`
1541
+ }
1542
+ });
1543
+ if (!res.ok) {
1544
+ const err = await res.json().catch(() => ({}));
1545
+ throw new CLIError(err.error ?? `Download failed: ${res.status}`);
1546
+ }
1547
+ const buffer = Buffer.from(await res.arrayBuffer());
1548
+ const outputPath = opts.output ?? join3(process.cwd(), basename3(objectKey));
1549
+ writeFileSync3(outputPath, buffer);
1550
+ if (json) {
1551
+ outputJson({ success: true, path: outputPath, size: buffer.length });
1552
+ } else {
1553
+ outputSuccess(`Downloaded "${objectKey}" to ${outputPath} (${buffer.length} bytes).`);
1554
+ }
1555
+ } catch (err) {
1556
+ handleError(err, json);
1557
+ }
1558
+ });
1559
+ }
1560
+
1561
+ // src/commands/storage/create-bucket.ts
1562
+ function registerStorageCreateBucketCommand(storageCmd2) {
1563
+ storageCmd2.command("create-bucket <name>").description("Create a new storage bucket").option("--public", "Make the bucket publicly accessible (default)").option("--private", "Make the bucket private").action(async (name, opts, cmd) => {
1564
+ const { json } = getRootOpts(cmd);
1565
+ try {
1566
+ requireAuth();
1567
+ const isPublic = !opts.private;
1568
+ const res = await ossFetch("/api/storage/buckets", {
1569
+ method: "POST",
1570
+ body: JSON.stringify({ bucketName: name, isPublic })
1571
+ });
1572
+ const data = await res.json();
1573
+ if (json) {
1574
+ outputJson(data);
1575
+ } else {
1576
+ outputSuccess(`Bucket "${name}" created (${isPublic ? "public" : "private"}).`);
1577
+ }
1578
+ } catch (err) {
1579
+ handleError(err, json);
1580
+ }
1581
+ });
1582
+ }
1583
+
1584
+ // src/commands/storage/delete-bucket.ts
1585
+ import * as clack5 from "@clack/prompts";
1586
+ function registerStorageDeleteBucketCommand(storageCmd2) {
1587
+ storageCmd2.command("delete-bucket <name>").description("Delete a storage bucket and all its objects").action(async (name, _opts, cmd) => {
1588
+ const { json, yes } = getRootOpts(cmd);
1589
+ try {
1590
+ requireAuth();
1591
+ if (!yes && !json) {
1592
+ const confirm4 = await clack5.confirm({
1593
+ message: `Delete bucket "${name}" and all its objects? This cannot be undone.`
1594
+ });
1595
+ if (!confirm4 || clack5.isCancel(confirm4)) {
1596
+ process.exit(0);
1597
+ }
1598
+ }
1599
+ const res = await ossFetch(`/api/storage/buckets/${encodeURIComponent(name)}`, {
1600
+ method: "DELETE"
1601
+ });
1602
+ const data = await res.json();
1603
+ if (json) {
1604
+ outputJson(data);
1605
+ } else {
1606
+ outputSuccess(`Bucket "${name}" deleted.`);
1607
+ }
1608
+ } catch (err) {
1609
+ handleError(err, json);
1610
+ }
1611
+ });
1612
+ }
1613
+
1614
+ // src/commands/storage/list-objects.ts
1615
+ function formatSize(bytes) {
1616
+ if (bytes < 1024) return `${bytes} B`;
1617
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1618
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1619
+ }
1620
+ function registerStorageListObjectsCommand(storageCmd2) {
1621
+ storageCmd2.command("list-objects <bucket>").description("List objects in a storage bucket").option("--limit <n>", "Maximum number of objects to return", "100").option("--offset <n>", "Number of objects to skip", "0").option("--prefix <prefix>", "Filter objects by key prefix").option("--search <term>", "Search objects by key (partial match)").option("--sort <field>", "Sort by field: key, size, uploadedAt (default: key)").action(async (bucket, opts, cmd) => {
1622
+ const { json } = getRootOpts(cmd);
1623
+ try {
1624
+ requireAuth();
1625
+ const params = new URLSearchParams();
1626
+ params.set("limit", opts.limit);
1627
+ params.set("offset", opts.offset);
1628
+ if (opts.prefix) params.set("prefix", opts.prefix);
1629
+ if (opts.search) params.set("search", opts.search);
1630
+ const res = await ossFetch(
1631
+ `/api/storage/buckets/${encodeURIComponent(bucket)}/objects?${params.toString()}`
1632
+ );
1633
+ const raw = await res.json();
1634
+ const objects = Array.isArray(raw) ? raw : raw.data ?? [];
1635
+ const sortField = opts.sort ?? "key";
1636
+ objects.sort((a, b) => {
1637
+ if (sortField === "size") return a.size - b.size;
1638
+ if (sortField === "uploadedAt") return a.uploadedAt.localeCompare(b.uploadedAt);
1639
+ return a.key.localeCompare(b.key);
1640
+ });
1641
+ if (json) {
1642
+ outputJson(raw);
1643
+ } else {
1644
+ if (objects.length === 0) {
1645
+ console.log(`No objects found in bucket "${bucket}".`);
1646
+ return;
1647
+ }
1648
+ const total = raw.pagination?.total;
1649
+ if (total !== void 0) {
1650
+ console.log(`Showing ${objects.length} of ${total} objects:
1651
+ `);
1652
+ }
1653
+ outputTable(
1654
+ ["Key", "Size", "Type", "Uploaded At"],
1655
+ objects.map((o) => [
1656
+ o.key,
1657
+ formatSize(o.size),
1658
+ o.mimeType ?? "-",
1659
+ o.uploadedAt
1660
+ ])
1661
+ );
1662
+ }
1663
+ } catch (err) {
1664
+ handleError(err, json);
1665
+ }
1666
+ });
1667
+ }
1668
+
1669
+ // src/commands/create.ts
1670
+ import { exec as exec2 } from "child_process";
1671
+ import { tmpdir } from "os";
1672
+ import { promisify as promisify2 } from "util";
1673
+ import * as fs from "fs/promises";
1674
+ import * as path from "path";
1675
+ import * as clack6 from "@clack/prompts";
1676
+ var execAsync2 = promisify2(exec2);
1677
+ function buildOssHost2(appkey, region) {
1678
+ return `https://${appkey}.${region}.insforge.app`;
1679
+ }
1680
+ async function waitForProjectActive(projectId, apiUrl, timeoutMs = 12e4) {
1681
+ const start = Date.now();
1682
+ while (Date.now() - start < timeoutMs) {
1683
+ const project = await getProject(projectId, apiUrl);
1684
+ if (project.status === "active") return;
1685
+ await new Promise((r) => setTimeout(r, 3e3));
1686
+ }
1687
+ throw new CLIError("Project creation timed out. Check the dashboard for status.");
1688
+ }
1689
+ async function copyDir(src, dest) {
1690
+ const entries = await fs.readdir(src, { withFileTypes: true });
1691
+ for (const entry of entries) {
1692
+ const srcPath = path.join(src, entry.name);
1693
+ const destPath = path.join(dest, entry.name);
1694
+ if (entry.isDirectory()) {
1695
+ await fs.mkdir(destPath, { recursive: true });
1696
+ await copyDir(srcPath, destPath);
1697
+ } else {
1698
+ await fs.copyFile(srcPath, destPath);
1699
+ }
1700
+ }
1701
+ }
1702
+ function registerCreateCommand(program2) {
1703
+ program2.command("create").description("Create a new InsForge project").option("--name <name>", "Project name").option("--org-id <id>", "Organization ID").option("--region <region>", "Deployment region (us-east, us-west, eu-central, ap-southeast)").option("--template <template>", "Template to use: react, nextjs, or empty").action(async (opts, cmd) => {
1704
+ const { json, apiUrl } = getRootOpts(cmd);
1705
+ try {
1706
+ requireAuth();
1707
+ if (!json) {
1708
+ clack6.intro("Create a new InsForge project");
1709
+ }
1710
+ let orgId = opts.orgId;
1711
+ if (!orgId) {
1712
+ const orgs = await listOrganizations(apiUrl);
1713
+ if (orgs.length === 0) {
1714
+ throw new CLIError("No organizations found.");
1715
+ }
1716
+ if (json) {
1717
+ throw new CLIError("Specify --org-id in JSON mode.");
1718
+ }
1719
+ const selected = await clack6.select({
1720
+ message: "Select an organization:",
1721
+ options: orgs.map((o) => ({
1722
+ value: o.id,
1723
+ label: o.name
1724
+ }))
1725
+ });
1726
+ if (clack6.isCancel(selected)) process.exit(0);
1727
+ orgId = selected;
1728
+ }
1729
+ const globalConfig = getGlobalConfig();
1730
+ globalConfig.default_org_id = orgId;
1731
+ saveGlobalConfig(globalConfig);
1732
+ let projectName = opts.name;
1733
+ if (!projectName) {
1734
+ if (json) throw new CLIError("--name is required in JSON mode.");
1735
+ const name = await clack6.text({
1736
+ message: "Project name:",
1737
+ validate: (v) => v.length >= 2 ? void 0 : "Name must be at least 2 characters"
1738
+ });
1739
+ if (clack6.isCancel(name)) process.exit(0);
1740
+ projectName = name;
1741
+ }
1742
+ let template = opts.template;
1743
+ if (!template) {
1744
+ if (json) {
1745
+ template = "empty";
1746
+ } else {
1747
+ const selected = await clack6.select({
1748
+ message: "Choose a starter template:",
1749
+ options: [
1750
+ { value: "react", label: "Web app template with React" },
1751
+ { value: "nextjs", label: "Web app template with Next.js" },
1752
+ { value: "empty", label: "Empty project" }
1753
+ ]
1754
+ });
1755
+ if (clack6.isCancel(selected)) process.exit(0);
1756
+ template = selected;
1757
+ }
1758
+ }
1759
+ const s = !json ? clack6.spinner() : null;
1760
+ s?.start("Creating project...");
1761
+ const project = await createProject(orgId, projectName, opts.region, apiUrl);
1762
+ s?.message("Waiting for project to become active...");
1763
+ await waitForProjectActive(project.id, apiUrl);
1764
+ const apiKey = await getProjectApiKey(project.id, apiUrl);
1765
+ const projectConfig = {
1766
+ project_id: project.id,
1767
+ project_name: project.name,
1768
+ org_id: project.organization_id,
1769
+ appkey: project.appkey,
1770
+ region: project.region,
1771
+ api_key: apiKey,
1772
+ oss_host: buildOssHost2(project.appkey, project.region)
1773
+ };
1774
+ saveProjectConfig(projectConfig);
1775
+ s?.stop(`Project "${project.name}" created and linked`);
1776
+ if (template !== "empty") {
1777
+ await downloadTemplate(template, projectConfig, projectName, json, apiUrl);
1778
+ }
1779
+ await installSkills(json);
1780
+ if (json) {
1781
+ outputJson({
1782
+ success: true,
1783
+ project: { id: project.id, name: project.name, appkey: project.appkey, region: project.region },
1784
+ template
1785
+ });
1786
+ } else {
1787
+ clack6.outro("Done! Run `npm install` to get started.");
1788
+ }
1789
+ } catch (err) {
1790
+ handleError(err, json);
1791
+ }
1792
+ });
1793
+ }
1794
+ async function downloadTemplate(framework, projectConfig, projectName, json, _apiUrl) {
1795
+ const s = !json ? clack6.spinner() : null;
1796
+ s?.start("Downloading template...");
1797
+ try {
1798
+ const anonKey = await getAnonKey();
1799
+ if (!anonKey) {
1800
+ throw new Error("Failed to retrieve anon key from backend");
1801
+ }
1802
+ const tempDir = tmpdir();
1803
+ const targetDir = projectName;
1804
+ const templatePath = path.join(tempDir, targetDir);
1805
+ try {
1806
+ await fs.rm(templatePath, { recursive: true, force: true });
1807
+ } catch {
1808
+ }
1809
+ const frame = framework === "nextjs" ? "nextjs" : "react";
1810
+ const command = `npx create-insforge-app ${targetDir} --frame ${frame} --base-url ${projectConfig.oss_host} --anon-key ${anonKey} --skip-install`;
1811
+ s?.message(`Running create-insforge-app (${frame})...`);
1812
+ await execAsync2(command, {
1813
+ maxBuffer: 10 * 1024 * 1024,
1814
+ cwd: tempDir
1815
+ });
1816
+ s?.message("Copying template files...");
1817
+ const cwd = process.cwd();
1818
+ await copyDir(templatePath, cwd);
1819
+ await fs.rm(templatePath, { recursive: true, force: true }).catch(() => {
1820
+ });
1821
+ s?.stop("Template files downloaded");
1822
+ } catch (err) {
1823
+ s?.stop("Template download failed");
1824
+ if (!json) {
1825
+ clack6.log.warn(`Failed to download template: ${err.message}`);
1826
+ clack6.log.info("You can manually set up the template later.");
1827
+ }
1828
+ }
1829
+ }
1830
+
1831
+ // src/commands/info.ts
1832
+ function registerContextCommand(program2) {
1833
+ program2.command("current").description("Show current CLI context (user, org, project)").action(async (_opts, cmd) => {
1834
+ const { json } = getRootOpts(cmd);
1835
+ try {
1836
+ const creds = getCredentials();
1837
+ const globalConfig = getGlobalConfig();
1838
+ const projectConfig = getProjectConfig();
1839
+ if (json) {
1840
+ outputJson({
1841
+ authenticated: !!creds,
1842
+ user: creds?.user ?? null,
1843
+ default_org_id: globalConfig.default_org_id ?? null,
1844
+ project: projectConfig
1845
+ });
1846
+ return;
1847
+ }
1848
+ console.log("\n InsForge CLI Context\n");
1849
+ if (creds) {
1850
+ console.log(` User: ${creds.user.name} <${creds.user.email}>`);
1851
+ } else {
1852
+ console.log(" User: (not logged in)");
1853
+ }
1854
+ if (globalConfig.default_org_id) {
1855
+ console.log(` Default Org: ${globalConfig.default_org_id}`);
1856
+ } else {
1857
+ console.log(" Default Org: (none)");
1858
+ }
1859
+ if (projectConfig) {
1860
+ console.log("");
1861
+ console.log(` Project: ${projectConfig.project_name} (${projectConfig.project_id})`);
1862
+ console.log(` App Key: ${projectConfig.appkey}`);
1863
+ console.log(` Region: ${projectConfig.region}`);
1864
+ console.log(` OSS Host: ${projectConfig.oss_host}`);
1865
+ } else {
1866
+ console.log("\n Project: (not linked \u2014 run `insforge projects link`)");
1867
+ }
1868
+ console.log("");
1869
+ } catch (err) {
1870
+ handleError(err, json);
1871
+ }
1872
+ });
1873
+ }
1874
+
1875
+ // src/commands/list.ts
1876
+ function registerListCommand(program2) {
1877
+ program2.command("list").description("List all organizations and their projects").action(async (_opts, cmd) => {
1878
+ const { json, apiUrl } = getRootOpts(cmd);
1879
+ try {
1880
+ requireAuth();
1881
+ const orgs = await listOrganizations(apiUrl);
1882
+ if (orgs.length === 0) {
1883
+ if (json) {
1884
+ outputJson([]);
1885
+ } else {
1886
+ console.log("No organizations found.");
1887
+ }
1888
+ return;
1889
+ }
1890
+ const orgProjects = await Promise.all(
1891
+ orgs.map(async (org) => ({
1892
+ org,
1893
+ projects: await listProjects(org.id, apiUrl)
1894
+ }))
1895
+ );
1896
+ if (json) {
1897
+ outputJson(
1898
+ orgProjects.map(({ org, projects }) => ({
1899
+ id: org.id,
1900
+ name: org.name,
1901
+ type: org.type ?? null,
1902
+ projects: projects.map((p) => ({
1903
+ id: p.id,
1904
+ name: p.name,
1905
+ region: p.region,
1906
+ status: p.status,
1907
+ appkey: p.appkey
1908
+ }))
1909
+ }))
1910
+ );
1911
+ return;
1912
+ }
1913
+ const rows = [];
1914
+ for (const { org, projects } of orgProjects) {
1915
+ if (projects.length === 0) {
1916
+ rows.push([org.name, "-", "-", "-", "-"]);
1917
+ } else {
1918
+ for (let i = 0; i < projects.length; i++) {
1919
+ const p = projects[i];
1920
+ rows.push([
1921
+ i === 0 ? org.name : "",
1922
+ p.name,
1923
+ p.region,
1924
+ p.status,
1925
+ p.appkey
1926
+ ]);
1927
+ }
1928
+ }
1929
+ }
1930
+ outputTable(["Organization", "Project", "Region", "Status", "AppKey"], rows);
1931
+ } catch (err) {
1932
+ handleError(err, json);
1933
+ }
1934
+ });
1935
+ }
1936
+
1937
+ // src/commands/deployments/deploy.ts
1938
+ import * as path2 from "path";
1939
+ import * as fs2 from "fs/promises";
1940
+ import * as clack7 from "@clack/prompts";
1941
+ import archiver from "archiver";
1942
+ var POLL_INTERVAL_MS = 5e3;
1943
+ var POLL_TIMEOUT_MS = 12e4;
1944
+ var EXCLUDE_PATTERNS = [
1945
+ "node_modules",
1946
+ ".git",
1947
+ ".next",
1948
+ ".env",
1949
+ ".env.local",
1950
+ "dist",
1951
+ "build",
1952
+ ".DS_Store",
1953
+ ".insforge"
1954
+ ];
1955
+ function shouldExclude(name) {
1956
+ const normalized = name.replace(/\\/g, "/");
1957
+ for (const pattern of EXCLUDE_PATTERNS) {
1958
+ if (normalized === pattern || normalized.startsWith(pattern + "/") || normalized.endsWith("/" + pattern) || normalized.includes("/" + pattern + "/")) {
1959
+ return true;
1960
+ }
1961
+ }
1962
+ if (normalized.endsWith(".log")) return true;
1963
+ return false;
1964
+ }
1965
+ async function createZipBuffer(sourceDir) {
1966
+ return new Promise((resolve2, reject) => {
1967
+ const archive = archiver("zip", { zlib: { level: 9 } });
1968
+ const chunks = [];
1969
+ archive.on("data", (chunk) => chunks.push(chunk));
1970
+ archive.on("end", () => resolve2(Buffer.concat(chunks)));
1971
+ archive.on("error", (err) => reject(err));
1972
+ archive.directory(sourceDir, false, (entry) => {
1973
+ if (shouldExclude(entry.name)) return false;
1974
+ return entry;
1975
+ });
1976
+ void archive.finalize();
1977
+ });
1978
+ }
1979
+ function registerDeploymentsDeployCommand(deploymentsCmd2) {
1980
+ deploymentsCmd2.command("deploy [directory]").description("Deploy a frontend project to Vercel").option("--env <vars>", `Environment variables as JSON (e.g. '{"KEY":"value"}')`).option("--meta <meta>", "Deployment metadata as JSON").action(async (directory, opts, cmd) => {
1981
+ const { json } = getRootOpts(cmd);
1982
+ try {
1983
+ requireAuth();
1984
+ const config = getProjectConfig();
1985
+ if (!config) throw new ProjectNotLinkedError();
1986
+ const sourceDir = path2.resolve(directory ?? ".");
1987
+ const stats = await fs2.stat(sourceDir).catch(() => null);
1988
+ if (!stats?.isDirectory()) {
1989
+ throw new CLIError(`"${sourceDir}" is not a valid directory.`);
1990
+ }
1991
+ const s = !json ? clack7.spinner() : null;
1992
+ s?.start("Creating deployment...");
1993
+ const createRes = await ossFetch("/api/deployments", { method: "POST" });
1994
+ const { id: deploymentId, uploadUrl, uploadFields } = await createRes.json();
1995
+ s?.message("Compressing source files...");
1996
+ const zipBuffer = await createZipBuffer(sourceDir);
1997
+ s?.message("Uploading...");
1998
+ const formData = new FormData();
1999
+ for (const [key, value] of Object.entries(uploadFields)) {
2000
+ formData.append(key, value);
2001
+ }
2002
+ formData.append(
2003
+ "file",
2004
+ new Blob([zipBuffer], { type: "application/zip" }),
2005
+ "deployment.zip"
2006
+ );
2007
+ const uploadRes = await fetch(uploadUrl, { method: "POST", body: formData });
2008
+ if (!uploadRes.ok) {
2009
+ const uploadErr = await uploadRes.text();
2010
+ throw new CLIError(`Failed to upload: ${uploadErr}`);
2011
+ }
2012
+ s?.message("Starting deployment...");
2013
+ const startBody = {};
2014
+ if (opts.env) {
2015
+ try {
2016
+ const parsed = JSON.parse(opts.env);
2017
+ if (Array.isArray(parsed)) {
2018
+ startBody.envVars = parsed;
2019
+ } else {
2020
+ startBody.envVars = Object.entries(parsed).map(([key, value]) => ({ key, value }));
2021
+ }
2022
+ } catch {
2023
+ throw new CLIError("Invalid --env JSON.");
2024
+ }
2025
+ }
2026
+ if (opts.meta) {
2027
+ try {
2028
+ startBody.meta = JSON.parse(opts.meta);
2029
+ } catch {
2030
+ throw new CLIError("Invalid --meta JSON.");
2031
+ }
2032
+ }
2033
+ const startRes = await ossFetch(`/api/deployments/${deploymentId}/start`, {
2034
+ method: "POST",
2035
+ body: JSON.stringify(startBody)
2036
+ });
2037
+ await startRes.json();
2038
+ s?.message("Building and deploying...");
2039
+ const startTime = Date.now();
2040
+ let deployment = null;
2041
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
2042
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
2043
+ try {
2044
+ const statusRes = await ossFetch(`/api/deployments/${deploymentId}`);
2045
+ deployment = await statusRes.json();
2046
+ if (deployment.status === "ready" || deployment.status === "READY") {
2047
+ break;
2048
+ }
2049
+ if (deployment.status === "error" || deployment.status === "ERROR" || deployment.status === "canceled") {
2050
+ s?.stop("Deployment failed");
2051
+ throw new CLIError(deployment.error ?? `Deployment failed with status: ${deployment.status}`);
2052
+ }
2053
+ const elapsed = Math.round((Date.now() - startTime) / 1e3);
2054
+ s?.message(`Building and deploying... (${elapsed}s, status: ${deployment.status})`);
2055
+ } catch (err) {
2056
+ if (err instanceof CLIError) throw err;
2057
+ }
2058
+ }
2059
+ const isReady = deployment?.status === "ready" || deployment?.status === "READY";
2060
+ if (isReady) {
2061
+ s?.stop("Deployment complete");
2062
+ if (json) {
2063
+ outputJson(deployment);
2064
+ } else {
2065
+ const liveUrl = deployment?.deploymentUrl ?? deployment?.url;
2066
+ if (liveUrl) {
2067
+ clack7.log.success(`Live at: ${liveUrl}`);
2068
+ }
2069
+ clack7.log.info(`Deployment ID: ${deploymentId}`);
2070
+ }
2071
+ } else {
2072
+ s?.stop("Deployment is still building");
2073
+ if (json) {
2074
+ outputJson({ id: deploymentId, status: deployment?.status ?? "building", timedOut: true });
2075
+ } else {
2076
+ clack7.log.info(`Deployment ID: ${deploymentId}`);
2077
+ clack7.log.warn("Deployment did not finish within 2 minutes.");
2078
+ clack7.log.info(`Check status with: insforge deployments status ${deploymentId}`);
2079
+ }
2080
+ }
2081
+ } catch (err) {
2082
+ handleError(err, json);
2083
+ }
2084
+ });
2085
+ }
2086
+
2087
+ // src/commands/deployments/list.ts
2088
+ function registerDeploymentsListCommand(deploymentsCmd2) {
2089
+ deploymentsCmd2.command("list").description("List all deployments").option("--limit <n>", "Limit number of results", "20").option("--offset <n>", "Offset for pagination", "0").action(async (opts, cmd) => {
2090
+ const { json } = getRootOpts(cmd);
2091
+ try {
2092
+ requireAuth();
2093
+ if (!getProjectConfig()) throw new ProjectNotLinkedError();
2094
+ const res = await ossFetch(`/api/deployments?limit=${opts.limit}&offset=${opts.offset}`);
2095
+ const raw = await res.json();
2096
+ let deployments;
2097
+ if (Array.isArray(raw)) {
2098
+ deployments = raw;
2099
+ } else if (raw && typeof raw === "object" && "data" in raw && Array.isArray(raw.data)) {
2100
+ deployments = raw.data;
2101
+ } else {
2102
+ deployments = [];
2103
+ }
2104
+ if (json) {
2105
+ outputJson(raw);
2106
+ } else {
2107
+ if (!deployments.length) {
2108
+ console.log("No deployments found.");
2109
+ return;
2110
+ }
2111
+ outputTable(
2112
+ ["ID", "Status", "Provider", "URL", "Created"],
2113
+ deployments.map((d) => [
2114
+ String(d.id ?? "-"),
2115
+ String(d.status ?? "-"),
2116
+ String(d.provider ?? "-"),
2117
+ String(d.deploymentUrl ?? d.url ?? "-"),
2118
+ d.createdAt ?? d.created_at ? new Date(String(d.createdAt ?? d.created_at)).toLocaleString() : "-"
2119
+ ])
2120
+ );
2121
+ }
2122
+ } catch (err) {
2123
+ handleError(err, json);
2124
+ }
2125
+ });
2126
+ }
2127
+
2128
+ // src/commands/deployments/status.ts
2129
+ function registerDeploymentsStatusCommand(deploymentsCmd2) {
2130
+ deploymentsCmd2.command("status <id>").description("Get deployment details and sync status from Vercel").option("--sync", "Sync status from Vercel before showing").action(async (id, opts, cmd) => {
2131
+ const { json } = getRootOpts(cmd);
2132
+ try {
2133
+ requireAuth();
2134
+ if (!getProjectConfig()) throw new ProjectNotLinkedError();
2135
+ if (opts.sync) {
2136
+ await ossFetch(`/api/deployments/${id}/sync`, { method: "POST" });
2137
+ }
2138
+ const res = await ossFetch(`/api/deployments/${id}`);
2139
+ const d = await res.json();
2140
+ if (json) {
2141
+ outputJson(d);
2142
+ } else {
2143
+ outputTable(
2144
+ ["Field", "Value"],
2145
+ [
2146
+ ["ID", d.id],
2147
+ ["Status", d.status],
2148
+ ["Provider", d.provider ?? "-"],
2149
+ ["Provider ID", d.providerDeploymentId ?? "-"],
2150
+ ["URL", d.deploymentUrl ?? d.url ?? "-"],
2151
+ ["Created", new Date(d.createdAt).toLocaleString()],
2152
+ ["Updated", new Date(d.updatedAt).toLocaleString()],
2153
+ ...d.error ? [["Error", d.error]] : []
2154
+ ]
2155
+ );
2156
+ }
2157
+ } catch (err) {
2158
+ handleError(err, json);
2159
+ }
2160
+ });
2161
+ }
2162
+
2163
+ // src/commands/deployments/cancel.ts
2164
+ import * as clack8 from "@clack/prompts";
2165
+ function registerDeploymentsCancelCommand(deploymentsCmd2) {
2166
+ deploymentsCmd2.command("cancel <id>").description("Cancel a deployment").action(async (id, _opts, cmd) => {
2167
+ const { json, yes } = getRootOpts(cmd);
2168
+ try {
2169
+ requireAuth();
2170
+ if (!getProjectConfig()) throw new ProjectNotLinkedError();
2171
+ if (!yes && !json) {
2172
+ const confirmed = await clack8.confirm({
2173
+ message: `Cancel deployment ${id}?`
2174
+ });
2175
+ if (clack8.isCancel(confirmed) || !confirmed) process.exit(0);
2176
+ }
2177
+ const res = await ossFetch(`/api/deployments/${id}/cancel`, { method: "POST" });
2178
+ const result = await res.json();
2179
+ if (json) {
2180
+ outputJson(result);
2181
+ } else {
2182
+ outputSuccess(`Deployment ${id} cancelled.`);
2183
+ }
2184
+ } catch (err) {
2185
+ handleError(err, json);
2186
+ }
2187
+ });
2188
+ }
2189
+
2190
+ // src/commands/secrets/list.ts
2191
+ function registerSecretsListCommand(secretsCmd2) {
2192
+ secretsCmd2.command("list").description("List secrets (metadata only, values are hidden)").option("--all", "Include inactive (deleted) secrets").action(async (opts, cmd) => {
2193
+ const { json } = getRootOpts(cmd);
2194
+ try {
2195
+ requireAuth();
2196
+ const res = await ossFetch("/api/secrets");
2197
+ const data = await res.json();
2198
+ let secrets = data.secrets ?? [];
2199
+ if (!opts.all) {
2200
+ secrets = secrets.filter((s) => s.isActive !== false);
2201
+ }
2202
+ if (json) {
2203
+ outputJson(opts.all ? data : { secrets });
2204
+ } else {
2205
+ if (!secrets.length) {
2206
+ console.log("No secrets found.");
2207
+ return;
2208
+ }
2209
+ const headers = opts.all ? ["Key", "Active", "Reserved", "Expires", "Updated"] : ["Key", "Reserved", "Expires", "Updated"];
2210
+ outputTable(
2211
+ headers,
2212
+ secrets.map((s) => {
2213
+ const row = [
2214
+ String(s.key ?? "-"),
2215
+ ...opts.all ? [s.isActive ? "Yes" : "No"] : [],
2216
+ s.isReserved ? "Yes" : "No",
2217
+ s.expiresAt ? new Date(String(s.expiresAt)).toLocaleString() : "-",
2218
+ s.updatedAt ? new Date(String(s.updatedAt)).toLocaleString() : "-"
2219
+ ];
2220
+ return row;
2221
+ })
2222
+ );
2223
+ }
2224
+ } catch (err) {
2225
+ handleError(err, json);
2226
+ }
2227
+ });
2228
+ }
2229
+
2230
+ // src/commands/secrets/get.ts
2231
+ function registerSecretsGetCommand(secretsCmd2) {
2232
+ secretsCmd2.command("get <key>").description("Get the decrypted value of a secret").action(async (key, _opts, cmd) => {
2233
+ const { json } = getRootOpts(cmd);
2234
+ try {
2235
+ requireAuth();
2236
+ const res = await ossFetch(`/api/secrets/${encodeURIComponent(key)}`);
2237
+ const data = await res.json();
2238
+ if (json) {
2239
+ outputJson(data);
2240
+ } else {
2241
+ console.log(`${data.key} = ${data.value}`);
2242
+ }
2243
+ } catch (err) {
2244
+ handleError(err, json);
2245
+ }
2246
+ });
2247
+ }
2248
+
2249
+ // src/commands/secrets/add.ts
2250
+ function registerSecretsAddCommand(secretsCmd2) {
2251
+ secretsCmd2.command("add <key> <value>").description("Create a new secret").option("--reserved", "Mark secret as protected from deletion").option("--expires <date>", "Expiration date (ISO 8601 format)").action(async (key, value, opts, cmd) => {
2252
+ const { json } = getRootOpts(cmd);
2253
+ try {
2254
+ requireAuth();
2255
+ const body = { key, value };
2256
+ if (opts.reserved) body.isReserved = true;
2257
+ if (opts.expires) body.expiresAt = opts.expires;
2258
+ const res = await ossFetch("/api/secrets", {
2259
+ method: "POST",
2260
+ body: JSON.stringify(body)
2261
+ });
2262
+ const data = await res.json();
2263
+ if (json) {
2264
+ outputJson(data);
2265
+ } else {
2266
+ outputSuccess(data.message ?? `Secret ${key} created.`);
2267
+ }
2268
+ } catch (err) {
2269
+ handleError(err, json);
2270
+ }
2271
+ });
2272
+ }
2273
+
2274
+ // src/commands/secrets/update.ts
2275
+ function registerSecretsUpdateCommand(secretsCmd2) {
2276
+ secretsCmd2.command("update <key>").description("Update an existing secret").option("--value <value>", "New secret value").option("--active <bool>", "Set active status (true/false)").option("--reserved <bool>", "Set reserved status (true/false)").option("--expires <date>", 'Expiration date (ISO 8601, or "null" to remove)').action(async (key, opts, cmd) => {
2277
+ const { json } = getRootOpts(cmd);
2278
+ try {
2279
+ requireAuth();
2280
+ const body = {};
2281
+ if (opts.value !== void 0) body.value = opts.value;
2282
+ if (opts.active !== void 0) body.isActive = opts.active === "true";
2283
+ if (opts.reserved !== void 0) body.isReserved = opts.reserved === "true";
2284
+ if (opts.expires !== void 0) body.expiresAt = opts.expires === "null" ? null : opts.expires;
2285
+ if (Object.keys(body).length === 0) {
2286
+ throw new CLIError("Provide at least one option to update (--value, --active, --reserved, --expires).");
2287
+ }
2288
+ const res = await ossFetch(`/api/secrets/${encodeURIComponent(key)}`, {
2289
+ method: "PUT",
2290
+ body: JSON.stringify(body)
2291
+ });
2292
+ const data = await res.json();
2293
+ if (json) {
2294
+ outputJson(data);
2295
+ } else {
2296
+ outputSuccess(data.message ?? `Secret ${key} updated.`);
2297
+ }
2298
+ } catch (err) {
2299
+ handleError(err, json);
2300
+ }
2301
+ });
2302
+ }
2303
+
2304
+ // src/commands/secrets/delete.ts
2305
+ import * as clack9 from "@clack/prompts";
2306
+ function registerSecretsDeleteCommand(secretsCmd2) {
2307
+ secretsCmd2.command("delete <key>").description("Delete a secret").action(async (key, _opts, cmd) => {
2308
+ const { json, yes } = getRootOpts(cmd);
2309
+ try {
2310
+ requireAuth();
2311
+ if (!yes && !json) {
2312
+ const confirm4 = await clack9.confirm({
2313
+ message: `Delete secret "${key}"? This cannot be undone.`
2314
+ });
2315
+ if (!confirm4 || clack9.isCancel(confirm4)) {
2316
+ process.exit(0);
2317
+ }
2318
+ }
2319
+ const res = await ossFetch(`/api/secrets/${encodeURIComponent(key)}`, {
2320
+ method: "DELETE"
2321
+ });
2322
+ const data = await res.json();
2323
+ if (json) {
2324
+ outputJson(data);
2325
+ } else {
2326
+ outputSuccess(data.message ?? `Secret ${key} deleted.`);
2327
+ }
2328
+ } catch (err) {
2329
+ handleError(err, json);
2330
+ }
2331
+ });
2332
+ }
2333
+
2334
+ // src/index.ts
2335
+ var INSFORGE_LOGO = `
2336
+ \u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
2337
+ \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
2338
+ \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2557
2339
+ \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D
2340
+ \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557
2341
+ \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D
2342
+ `;
2343
+ function showLogoOnFirstRun() {
2344
+ if (process.argv.includes("--json")) return;
2345
+ const localDir = join5(process.cwd(), ".insforge");
2346
+ if (existsSync4(localDir)) return;
2347
+ console.log(INSFORGE_LOGO);
2348
+ console.log(" Welcome to InsForge CLI! Run `insforge login` to get started.\n");
2349
+ mkdirSync2(localDir, { recursive: true });
2350
+ }
2351
+ showLogoOnFirstRun();
2352
+ var program = new Command();
2353
+ program.name("insforge").description("InsForge CLI - Command line tool for InsForge platform").version("0.1.0");
2354
+ program.option("--json", "Output in JSON format").option("--project-id <id>", "Override linked project ID").option("--api-url <url>", "Override Platform API URL").option("-y, --yes", "Skip confirmation prompts");
2355
+ registerLoginCommand(program);
2356
+ registerLogoutCommand(program);
2357
+ registerWhoamiCommand(program);
2358
+ registerCreateCommand(program);
2359
+ registerContextCommand(program);
2360
+ registerListCommand(program);
2361
+ registerProjectLinkCommand(program);
2362
+ var orgsCmd = program.command("orgs", { hidden: true }).description("Manage organizations");
2363
+ registerOrgsCommands(orgsCmd);
2364
+ var projectsCmd = program.command("projects", { hidden: true }).description("Manage projects");
2365
+ registerProjectsCommands(projectsCmd);
2366
+ var dbCmd = program.command("db").description("Database operations");
2367
+ registerDbCommands(dbCmd);
2368
+ registerDbTablesCommand(dbCmd);
2369
+ registerDbFunctionsCommand(dbCmd);
2370
+ registerDbIndexesCommand(dbCmd);
2371
+ registerDbPoliciesCommand(dbCmd);
2372
+ registerDbTriggersCommand(dbCmd);
2373
+ registerDbRpcCommand(dbCmd);
2374
+ registerDbExportCommand(dbCmd);
2375
+ registerDbImportCommand(dbCmd);
2376
+ var recordsCmd = program.command("records", { hidden: true }).description("CRUD operations on table records");
2377
+ registerRecordsCommands(recordsCmd);
2378
+ registerRecordsCreateCommand(recordsCmd);
2379
+ registerRecordsUpdateCommand(recordsCmd);
2380
+ registerRecordsDeleteCommand(recordsCmd);
2381
+ var functionsCmd = program.command("functions").description("Manage edge functions");
2382
+ registerFunctionsCommands(functionsCmd);
2383
+ registerFunctionsCodeCommand(functionsCmd);
2384
+ registerFunctionsDeployCommand(functionsCmd);
2385
+ registerFunctionsInvokeCommand(functionsCmd);
2386
+ var storageCmd = program.command("storage").description("Manage storage");
2387
+ registerStorageBucketsCommand(storageCmd);
2388
+ registerStorageCreateBucketCommand(storageCmd);
2389
+ registerStorageDeleteBucketCommand(storageCmd);
2390
+ registerStorageListObjectsCommand(storageCmd);
2391
+ registerStorageUploadCommand(storageCmd);
2392
+ registerStorageDownloadCommand(storageCmd);
2393
+ var deploymentsCmd = program.command("deployments").description("Deploy and manage frontend sites");
2394
+ registerDeploymentsDeployCommand(deploymentsCmd);
2395
+ registerDeploymentsListCommand(deploymentsCmd);
2396
+ registerDeploymentsStatusCommand(deploymentsCmd);
2397
+ registerDeploymentsCancelCommand(deploymentsCmd);
2398
+ var secretsCmd = program.command("secrets").description("Manage secrets");
2399
+ registerSecretsListCommand(secretsCmd);
2400
+ registerSecretsGetCommand(secretsCmd);
2401
+ registerSecretsAddCommand(secretsCmd);
2402
+ registerSecretsUpdateCommand(secretsCmd);
2403
+ registerSecretsDeleteCommand(secretsCmd);
2404
+ program.parse();
2405
+ //# sourceMappingURL=index.js.map