@superflag-sh/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.
Files changed (2) hide show
  1. package/dist/index.js +1369 -0
  2. package/package.json +33 -0
package/dist/index.js ADDED
@@ -0,0 +1,1369 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // src/commands/login.ts
5
+ import { randomBytes } from "crypto";
6
+
7
+ // src/lib/server.ts
8
+ function startCallbackServer(expectedState) {
9
+ return new Promise((resolve) => {
10
+ let callbackResolve;
11
+ const callbackPromise = new Promise((res) => {
12
+ callbackResolve = res;
13
+ });
14
+ let server;
15
+ server = Bun.serve({
16
+ port: 0,
17
+ fetch(req) {
18
+ const url = new URL(req.url);
19
+ if (url.pathname === "/callback") {
20
+ const token = url.searchParams.get("token");
21
+ const error = url.searchParams.get("error");
22
+ const state = url.searchParams.get("state");
23
+ if (state !== expectedState) {
24
+ return new Response(html("Error", "Invalid state parameter. Please try again."), { headers: { "Content-Type": "text/html" } });
25
+ }
26
+ if (error) {
27
+ callbackResolve({ error, state: state || "" });
28
+ setTimeout(() => server.stop(), 100);
29
+ return new Response(html("Authorization Cancelled", "You can close this window and return to your terminal."), { headers: { "Content-Type": "text/html" } });
30
+ }
31
+ if (token) {
32
+ callbackResolve({ token, state });
33
+ setTimeout(() => server.stop(), 100);
34
+ return new Response(html("Success!", "You're now logged in. You can close this window and return to your terminal."), { headers: { "Content-Type": "text/html" } });
35
+ }
36
+ return new Response(html("Error", "Missing token. Please try again."), { headers: { "Content-Type": "text/html" } });
37
+ }
38
+ return new Response("Not Found", { status: 404 });
39
+ }
40
+ });
41
+ resolve({
42
+ port: server.port,
43
+ waitForCallback: () => callbackPromise
44
+ });
45
+ });
46
+ }
47
+ function html(title, message) {
48
+ return `<!DOCTYPE html>
49
+ <html>
50
+ <head>
51
+ <title>${title} - Superflag CLI</title>
52
+ <style>
53
+ body {
54
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ min-height: 100vh;
59
+ margin: 0;
60
+ background: #0a0a0a;
61
+ color: #fafafa;
62
+ }
63
+ .container {
64
+ text-align: center;
65
+ padding: 2rem;
66
+ }
67
+ h1 { margin-bottom: 1rem; }
68
+ p { color: #a1a1aa; }
69
+ </style>
70
+ </head>
71
+ <body>
72
+ <div class="container">
73
+ <h1>${title}</h1>
74
+ <p>${message}</p>
75
+ </div>
76
+ </body>
77
+ </html>`;
78
+ }
79
+
80
+ // src/lib/config.ts
81
+ import { homedir } from "os";
82
+ import { join } from "path";
83
+ import { mkdir, readFile, writeFile, unlink } from "fs/promises";
84
+ import { existsSync } from "fs";
85
+ var CONFIG_DIR = join(homedir(), ".superflag");
86
+ var CREDENTIALS_FILE = join(CONFIG_DIR, "credentials.json");
87
+ async function ensureConfigDir() {
88
+ if (!existsSync(CONFIG_DIR)) {
89
+ await mkdir(CONFIG_DIR, { recursive: true });
90
+ }
91
+ }
92
+ async function saveCredentials(credentials) {
93
+ await ensureConfigDir();
94
+ await writeFile(CREDENTIALS_FILE, JSON.stringify(credentials, null, 2));
95
+ }
96
+ async function loadCredentials() {
97
+ try {
98
+ const content = await readFile(CREDENTIALS_FILE, "utf-8");
99
+ return JSON.parse(content);
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+ async function clearCredentials() {
105
+ try {
106
+ await unlink(CREDENTIALS_FILE);
107
+ } catch {}
108
+ }
109
+ function getConfigDir() {
110
+ return CONFIG_DIR;
111
+ }
112
+
113
+ // src/lib/auth.ts
114
+ async function getToken() {
115
+ const credentials = await loadCredentials();
116
+ return credentials?.token ?? null;
117
+ }
118
+ async function setToken(token, email) {
119
+ await saveCredentials({ token, email });
120
+ }
121
+ async function removeToken() {
122
+ await clearCredentials();
123
+ }
124
+ async function isAuthenticated() {
125
+ const token = await getToken();
126
+ return token !== null;
127
+ }
128
+
129
+ // src/lib/api.ts
130
+ var API_BASE = process.env.SUPERFLAG_API_URL || "http://localhost:3000/api/v1";
131
+
132
+ class ApiError extends Error {
133
+ status;
134
+ constructor(message, status) {
135
+ super(message);
136
+ this.status = status;
137
+ this.name = "ApiError";
138
+ }
139
+ }
140
+ async function api(endpoint, options = {}) {
141
+ const token = await getToken();
142
+ if (!token) {
143
+ throw new ApiError("Not authenticated. Run 'superflag login' first.", 401);
144
+ }
145
+ const { method = "GET", body, params } = options;
146
+ let url = `${API_BASE}${endpoint}`;
147
+ if (params) {
148
+ const searchParams = new URLSearchParams(params);
149
+ url += `?${searchParams.toString()}`;
150
+ }
151
+ const response = await fetch(url, {
152
+ method,
153
+ headers: {
154
+ "Content-Type": "application/json",
155
+ Authorization: `Bearer ${token}`
156
+ },
157
+ body: body ? JSON.stringify(body) : undefined
158
+ });
159
+ const data = await response.json();
160
+ if (!response.ok) {
161
+ throw new ApiError(data.error || "API request failed", response.status);
162
+ }
163
+ return data;
164
+ }
165
+ var apiClient = {
166
+ async whoami() {
167
+ return api("/cli-token");
168
+ },
169
+ async listApps() {
170
+ return api("/apps");
171
+ },
172
+ async createApp(name) {
173
+ return api("/apps", { method: "POST", body: { name } });
174
+ },
175
+ async listFlags(appId, env) {
176
+ const result = await api("/flags", { params: { appId, env } });
177
+ const flags = {};
178
+ for (const flag of result.flags) {
179
+ const { key, ...rest } = flag;
180
+ flags[key] = rest;
181
+ }
182
+ return { flags };
183
+ },
184
+ async updateFlag(appId, env, key, updates) {
185
+ return api("/flags", {
186
+ method: "PATCH",
187
+ body: { appId, env, key, ...updates }
188
+ });
189
+ },
190
+ async deleteFlag(appId, env, key) {
191
+ return api("/flags", {
192
+ method: "DELETE",
193
+ params: { appId, env, key }
194
+ });
195
+ },
196
+ async listKeys(appId, envId) {
197
+ return api("/keys", { params: { appId, envId } });
198
+ },
199
+ async createKey(appId, envId, type) {
200
+ return api("/keys", {
201
+ method: "POST",
202
+ body: { appId, envId, type }
203
+ });
204
+ },
205
+ async revokeKey(keyId) {
206
+ return api("/keys", {
207
+ method: "PATCH",
208
+ body: { id: keyId, revoke: true }
209
+ });
210
+ }
211
+ };
212
+
213
+ // src/utils/output.ts
214
+ var colors = {
215
+ reset: "\x1B[0m",
216
+ bold: "\x1B[1m",
217
+ dim: "\x1B[2m",
218
+ red: "\x1B[31m",
219
+ green: "\x1B[32m",
220
+ yellow: "\x1B[33m",
221
+ blue: "\x1B[34m",
222
+ cyan: "\x1B[36m"
223
+ };
224
+ function bold(text) {
225
+ return `${colors.bold}${text}${colors.reset}`;
226
+ }
227
+ function dim(text) {
228
+ return `${colors.dim}${text}${colors.reset}`;
229
+ }
230
+ function red(text) {
231
+ return `${colors.red}${text}${colors.reset}`;
232
+ }
233
+ function green(text) {
234
+ return `${colors.green}${text}${colors.reset}`;
235
+ }
236
+ function yellow(text) {
237
+ return `${colors.yellow}${text}${colors.reset}`;
238
+ }
239
+ function blue(text) {
240
+ return `${colors.blue}${text}${colors.reset}`;
241
+ }
242
+ function error(message) {
243
+ console.error(`${red("error:")} ${message}`);
244
+ }
245
+ function success(message) {
246
+ console.log(`${green("success:")} ${message}`);
247
+ }
248
+ function info(message) {
249
+ console.log(`${blue("info:")} ${message}`);
250
+ }
251
+ function table(headers, rows) {
252
+ const widths = headers.map((h, i) => {
253
+ const maxRow = Math.max(...rows.map((r) => (r[i] || "").length));
254
+ return Math.max(h.length, maxRow);
255
+ });
256
+ const headerLine = headers.map((h, i) => h.padEnd(widths[i])).join(" ");
257
+ console.log(bold(headerLine));
258
+ console.log(dim("-".repeat(headerLine.length)));
259
+ for (const row of rows) {
260
+ console.log(row.map((cell, i) => (cell || "").padEnd(widths[i])).join(" "));
261
+ }
262
+ }
263
+
264
+ // src/commands/login.ts
265
+ var AUTH_URL = process.env.SUPERFLAG_AUTH_URL || "http://localhost:3000/cli-auth";
266
+ async function login() {
267
+ if (await isAuthenticated()) {
268
+ try {
269
+ const { user } = await apiClient.whoami();
270
+ info(`Already logged in as ${user.email}`);
271
+ console.log(`Run ${yellow("superflag logout")} to log out first.`);
272
+ return;
273
+ } catch {}
274
+ }
275
+ const state = randomBytes(16).toString("hex");
276
+ console.log("Starting authentication...");
277
+ const { port, waitForCallback } = await startCallbackServer(state);
278
+ const authUrl = `${AUTH_URL}?port=${port}&state=${state}`;
279
+ console.log(`Opening browser to authenticate...`);
280
+ console.log(`If browser doesn't open, visit: ${authUrl}`);
281
+ const { platform } = process;
282
+ try {
283
+ if (platform === "darwin") {
284
+ Bun.spawn(["open", authUrl]);
285
+ } else if (platform === "win32") {
286
+ Bun.spawn(["cmd", "/c", "start", authUrl]);
287
+ } else {
288
+ Bun.spawn(["xdg-open", authUrl]);
289
+ }
290
+ } catch {}
291
+ console.log(`
292
+ Waiting for authentication...`);
293
+ const result = await waitForCallback();
294
+ if (result.error) {
295
+ error(`Authentication failed: ${result.error}`);
296
+ process.exit(1);
297
+ }
298
+ if (!result.token) {
299
+ error("No token received");
300
+ process.exit(1);
301
+ }
302
+ await setToken(result.token);
303
+ try {
304
+ const { user } = await apiClient.whoami();
305
+ await setToken(result.token, user.email);
306
+ success(`Logged in as ${user.email}`);
307
+ } catch {
308
+ success("Logged in successfully");
309
+ }
310
+ }
311
+
312
+ // src/commands/logout.ts
313
+ async function logout() {
314
+ if (!await isAuthenticated()) {
315
+ info("Not logged in");
316
+ return;
317
+ }
318
+ await removeToken();
319
+ success("Logged out");
320
+ }
321
+
322
+ // src/commands/whoami.ts
323
+ async function whoami() {
324
+ if (!await isAuthenticated()) {
325
+ info("Not logged in");
326
+ return;
327
+ }
328
+ try {
329
+ const { user } = await apiClient.whoami();
330
+ console.log(`Logged in as ${bold(user.email)}`);
331
+ console.log(`User ID: ${user.id}`);
332
+ } catch (e) {
333
+ if (e instanceof ApiError) {
334
+ error(e.message);
335
+ } else {
336
+ error("Failed to get user info");
337
+ }
338
+ process.exit(1);
339
+ }
340
+ }
341
+
342
+ // src/commands/apps.ts
343
+ async function listApps() {
344
+ try {
345
+ const { apps } = await apiClient.listApps();
346
+ if (apps.length === 0) {
347
+ info("No apps found");
348
+ console.log(`Run ${yellow("superflag apps create <name>")} to create one.`);
349
+ return;
350
+ }
351
+ console.log(bold(`
352
+ Apps (${apps.length})
353
+ `));
354
+ table(["NAME", "ID", "ENVIRONMENTS"], apps.map((app) => [
355
+ app.name,
356
+ dim(app.id),
357
+ app.envs?.map((e) => e.slug).join(", ") || ""
358
+ ]));
359
+ console.log("");
360
+ } catch (e) {
361
+ if (e instanceof ApiError) {
362
+ error(e.message);
363
+ } else {
364
+ error("Failed to list apps");
365
+ }
366
+ process.exit(1);
367
+ }
368
+ }
369
+ async function createApp(name) {
370
+ if (!name) {
371
+ error("App name is required");
372
+ console.log(`Usage: ${yellow("superflag apps create <name>")}`);
373
+ process.exit(1);
374
+ }
375
+ try {
376
+ const result = await apiClient.createApp(name);
377
+ success(`Created app "${result.app.name}"`);
378
+ console.log("");
379
+ console.log(bold("Environments:"));
380
+ for (const env of result.envs) {
381
+ console.log(` - ${env.slug}`);
382
+ }
383
+ console.log("");
384
+ console.log(bold("API Keys:"));
385
+ console.log(dim("Save these keys - they won't be shown again!"));
386
+ console.log("");
387
+ for (const key of result.keys) {
388
+ const envLabel = key.env ? ` (${key.env})` : "";
389
+ console.log(` ${key.type.toUpperCase()}${envLabel}:`);
390
+ console.log(` ${yellow(key.token)}`);
391
+ console.log("");
392
+ }
393
+ } catch (e) {
394
+ if (e instanceof ApiError) {
395
+ error(e.message);
396
+ } else {
397
+ error("Failed to create app");
398
+ }
399
+ process.exit(1);
400
+ }
401
+ }
402
+
403
+ // src/commands/envs.ts
404
+ async function findApp(nameOrId) {
405
+ const { apps } = await apiClient.listApps();
406
+ return apps.find((a) => a.name === nameOrId || a.id === nameOrId) || null;
407
+ }
408
+ async function listEnvs(appName) {
409
+ if (!appName) {
410
+ error("App name is required");
411
+ console.log(`Usage: ${yellow("superflag envs list --app <name>")}`);
412
+ process.exit(1);
413
+ }
414
+ try {
415
+ const app = await findApp(appName);
416
+ if (!app) {
417
+ error(`App "${appName}" not found`);
418
+ process.exit(1);
419
+ }
420
+ if (!app.envs || app.envs.length === 0) {
421
+ info(`No environments found for "${app.name}"`);
422
+ return;
423
+ }
424
+ console.log(bold(`
425
+ Environments for "${app.name}" (${app.envs.length})
426
+ `));
427
+ table(["SLUG", "ID", "CREATED"], app.envs.map((env) => [
428
+ env.slug,
429
+ dim(env.id),
430
+ new Date(env.created_at).toLocaleDateString()
431
+ ]));
432
+ console.log("");
433
+ } catch (e) {
434
+ if (e instanceof ApiError) {
435
+ error(e.message);
436
+ } else {
437
+ error("Failed to list environments");
438
+ }
439
+ process.exit(1);
440
+ }
441
+ }
442
+
443
+ // src/lib/context.ts
444
+ import { join as join2 } from "path";
445
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
446
+ var CONTEXT_FILE = join2(getConfigDir(), "context.json");
447
+ var cachedContext = null;
448
+ async function loadContext() {
449
+ if (cachedContext)
450
+ return cachedContext;
451
+ try {
452
+ const content = await readFile2(CONTEXT_FILE, "utf-8");
453
+ cachedContext = JSON.parse(content);
454
+ return cachedContext;
455
+ } catch {
456
+ return {};
457
+ }
458
+ }
459
+ async function saveContext(context) {
460
+ await ensureConfigDir();
461
+ await writeFile2(CONTEXT_FILE, JSON.stringify(context, null, 2));
462
+ cachedContext = context;
463
+ }
464
+ async function getAppAndEnv(flagApp, flagEnv) {
465
+ const context = await loadContext();
466
+ return {
467
+ app: flagApp || context.app,
468
+ env: flagEnv || context.env
469
+ };
470
+ }
471
+
472
+ // src/commands/flags.ts
473
+ async function findApp2(nameOrId) {
474
+ const { apps } = await apiClient.listApps();
475
+ return apps.find((a) => a.name === nameOrId || a.id === nameOrId) || null;
476
+ }
477
+ function formatValue(flag) {
478
+ if (flag.type === "bool") {
479
+ return flag.value ? green("true") : red("false");
480
+ }
481
+ if (flag.type === "json") {
482
+ return dim(JSON.stringify(flag.value));
483
+ }
484
+ return String(flag.value);
485
+ }
486
+ async function listFlags(appName, envSlug) {
487
+ if (!appName || !envSlug) {
488
+ error("App and environment are required");
489
+ console.log(`Usage: ${yellow("superflag flags list --app <name> --env <slug>")}`);
490
+ process.exit(1);
491
+ }
492
+ try {
493
+ const app = await findApp2(appName);
494
+ if (!app) {
495
+ error(`App "${appName}" not found`);
496
+ process.exit(1);
497
+ }
498
+ const { flags } = await apiClient.listFlags(app.id, envSlug);
499
+ const flagEntries = Object.entries(flags);
500
+ if (flagEntries.length === 0) {
501
+ info(`No flags found in "${app.name}" (${envSlug})`);
502
+ console.log(`Run ${yellow("superflag flags create --app <name> --env <slug> --key <key> --type <type> --value <value>")} to create one.`);
503
+ return;
504
+ }
505
+ console.log(bold(`
506
+ Flags in "${app.name}" (${envSlug}) - ${flagEntries.length} total
507
+ `));
508
+ table(["KEY", "TYPE", "VALUE", "CLIENT"], flagEntries.map(([key, flag]) => [
509
+ key,
510
+ flag.type,
511
+ formatValue(flag),
512
+ flag.clientEnabled ? green("yes") : dim("no")
513
+ ]));
514
+ console.log("");
515
+ } catch (e) {
516
+ if (e instanceof ApiError) {
517
+ error(e.message);
518
+ } else {
519
+ error("Failed to list flags");
520
+ }
521
+ process.exit(1);
522
+ }
523
+ }
524
+ async function createFlag(appName, envSlug, key, type, valueStr, clientEnabled) {
525
+ if (!appName || !envSlug || !key || !type || valueStr === undefined) {
526
+ error("Missing required arguments");
527
+ console.log(`Usage: ${yellow("superflag flags create --app <name> --env <slug> --key <key> --type <bool|string|number|json> --value <value> [--client]")}`);
528
+ process.exit(1);
529
+ }
530
+ let value;
531
+ switch (type) {
532
+ case "bool":
533
+ value = valueStr === "true" || valueStr === "1";
534
+ break;
535
+ case "number":
536
+ value = Number(valueStr);
537
+ if (isNaN(value)) {
538
+ error("Invalid number value");
539
+ process.exit(1);
540
+ }
541
+ break;
542
+ case "json":
543
+ try {
544
+ value = JSON.parse(valueStr);
545
+ } catch {
546
+ error("Invalid JSON value");
547
+ process.exit(1);
548
+ }
549
+ break;
550
+ default:
551
+ value = valueStr;
552
+ }
553
+ try {
554
+ const app = await findApp2(appName);
555
+ if (!app) {
556
+ error(`App "${appName}" not found`);
557
+ process.exit(1);
558
+ }
559
+ await apiClient.updateFlag(app.id, envSlug, key, {
560
+ type,
561
+ value,
562
+ clientEnabled
563
+ });
564
+ success(`Created flag "${key}" in "${app.name}" (${envSlug})`);
565
+ } catch (e) {
566
+ if (e instanceof ApiError) {
567
+ error(e.message);
568
+ } else {
569
+ error("Failed to create flag");
570
+ }
571
+ process.exit(1);
572
+ }
573
+ }
574
+ async function setFlag(appName, envSlug, key, valueStr) {
575
+ if (!appName || !envSlug || !key || valueStr === undefined) {
576
+ error("Missing required arguments");
577
+ console.log(`Usage: ${yellow("superflag flags set --app <name> --env <slug> --key <key> --value <value>")}`);
578
+ process.exit(1);
579
+ }
580
+ try {
581
+ const app = await findApp2(appName);
582
+ if (!app) {
583
+ error(`App "${appName}" not found`);
584
+ process.exit(1);
585
+ }
586
+ const { flags } = await apiClient.listFlags(app.id, envSlug);
587
+ const currentFlag = flags[key];
588
+ if (!currentFlag) {
589
+ error(`Flag "${key}" not found`);
590
+ process.exit(1);
591
+ }
592
+ let value;
593
+ switch (currentFlag.type) {
594
+ case "bool":
595
+ value = valueStr === "true" || valueStr === "1";
596
+ break;
597
+ case "number":
598
+ value = Number(valueStr);
599
+ if (isNaN(value)) {
600
+ error("Invalid number value");
601
+ process.exit(1);
602
+ }
603
+ break;
604
+ case "json":
605
+ try {
606
+ value = JSON.parse(valueStr);
607
+ } catch {
608
+ error("Invalid JSON value");
609
+ process.exit(1);
610
+ }
611
+ break;
612
+ default:
613
+ value = valueStr;
614
+ }
615
+ await apiClient.updateFlag(app.id, envSlug, key, { value });
616
+ success(`Updated flag "${key}" to ${formatValue({ ...currentFlag, value })}`);
617
+ } catch (e) {
618
+ if (e instanceof ApiError) {
619
+ error(e.message);
620
+ } else {
621
+ error("Failed to update flag");
622
+ }
623
+ process.exit(1);
624
+ }
625
+ }
626
+ async function toggleFlag(appName, envSlug, key) {
627
+ if (!appName || !envSlug || !key) {
628
+ error("Missing required arguments");
629
+ console.log(`Usage: ${yellow("superflag flags toggle --app <name> --env <slug> --key <key>")}`);
630
+ process.exit(1);
631
+ }
632
+ try {
633
+ const app = await findApp2(appName);
634
+ if (!app) {
635
+ error(`App "${appName}" not found`);
636
+ process.exit(1);
637
+ }
638
+ const { flags } = await apiClient.listFlags(app.id, envSlug);
639
+ const currentFlag = flags[key];
640
+ if (!currentFlag) {
641
+ error(`Flag "${key}" not found`);
642
+ process.exit(1);
643
+ }
644
+ if (currentFlag.type !== "bool") {
645
+ error(`Flag "${key}" is not a boolean flag`);
646
+ process.exit(1);
647
+ }
648
+ const newValue = !currentFlag.value;
649
+ await apiClient.updateFlag(app.id, envSlug, key, { value: newValue });
650
+ success(`Toggled "${key}" to ${newValue ? green("true") : red("false")}`);
651
+ } catch (e) {
652
+ if (e instanceof ApiError) {
653
+ error(e.message);
654
+ } else {
655
+ error("Failed to toggle flag");
656
+ }
657
+ process.exit(1);
658
+ }
659
+ }
660
+ async function deleteFlag(appName, envSlug, key) {
661
+ if (!appName || !envSlug || !key) {
662
+ error("Missing required arguments");
663
+ console.log(`Usage: ${yellow("superflag flags delete --app <name> --env <slug> --key <key>")}`);
664
+ process.exit(1);
665
+ }
666
+ try {
667
+ const app = await findApp2(appName);
668
+ if (!app) {
669
+ error(`App "${appName}" not found`);
670
+ process.exit(1);
671
+ }
672
+ await apiClient.deleteFlag(app.id, envSlug, key);
673
+ success(`Deleted flag "${key}" from "${app.name}" (${envSlug})`);
674
+ } catch (e) {
675
+ if (e instanceof ApiError) {
676
+ error(e.message);
677
+ } else {
678
+ error("Failed to delete flag");
679
+ }
680
+ process.exit(1);
681
+ }
682
+ }
683
+ async function upsertFlag(options) {
684
+ const { app: appName, env: envSlug } = await getAppAndEnv(options.app, options.env);
685
+ if (!appName || !envSlug) {
686
+ error("No context set. Run 'superflag use <app> <env>' or provide --app and --env");
687
+ process.exit(2);
688
+ }
689
+ if (!options.key || !options.type || options.value === undefined) {
690
+ error("Missing required arguments: --key, --type, --value");
691
+ process.exit(1);
692
+ }
693
+ let value;
694
+ switch (options.type) {
695
+ case "bool":
696
+ value = options.value === "true" || options.value === "1";
697
+ break;
698
+ case "number":
699
+ value = Number(options.value);
700
+ if (isNaN(value)) {
701
+ error("Invalid number value");
702
+ process.exit(1);
703
+ }
704
+ break;
705
+ case "json":
706
+ try {
707
+ value = JSON.parse(options.value);
708
+ } catch {
709
+ error("Invalid JSON value");
710
+ process.exit(1);
711
+ }
712
+ break;
713
+ default:
714
+ value = options.value;
715
+ }
716
+ try {
717
+ const app = await findApp2(appName);
718
+ if (!app) {
719
+ error(`App "${appName}" not found`);
720
+ process.exit(3);
721
+ }
722
+ await apiClient.updateFlag(app.id, envSlug, options.key, {
723
+ type: options.type,
724
+ value,
725
+ clientEnabled: options.client || false
726
+ });
727
+ if (!options.quiet) {
728
+ console.log("✓");
729
+ }
730
+ } catch (e) {
731
+ if (e instanceof ApiError) {
732
+ error(e.message);
733
+ process.exit(e.status === 401 ? 2 : 1);
734
+ }
735
+ error("Failed to upsert flag");
736
+ process.exit(1);
737
+ }
738
+ }
739
+ async function bulkSet(options) {
740
+ const { app: appName, env: envSlug } = await getAppAndEnv(options.app, options.env);
741
+ if (!appName || !envSlug) {
742
+ error("No context set. Run 'superflag use <app> <env>' or provide --app and --env");
743
+ process.exit(2);
744
+ }
745
+ try {
746
+ let input;
747
+ if (options.file) {
748
+ input = await Bun.file(options.file).text();
749
+ } else {
750
+ const chunks = [];
751
+ const reader = Bun.stdin.stream().getReader();
752
+ const decoder = new TextDecoder;
753
+ while (true) {
754
+ const { done, value } = await reader.read();
755
+ if (done)
756
+ break;
757
+ chunks.push(decoder.decode(value));
758
+ }
759
+ input = chunks.join("");
760
+ }
761
+ if (!input.trim()) {
762
+ error("No input provided");
763
+ process.exit(1);
764
+ }
765
+ const data = JSON.parse(input);
766
+ const app = await findApp2(appName);
767
+ if (!app) {
768
+ error(`App "${appName}" not found`);
769
+ process.exit(3);
770
+ }
771
+ const { flags: currentFlags } = await apiClient.listFlags(app.id, envSlug);
772
+ let updated = 0;
773
+ for (const [key, value] of Object.entries(data)) {
774
+ const currentFlag = currentFlags[key];
775
+ if (!currentFlag) {
776
+ error(`Flag "${key}" not found. Use 'superflag flags upsert' to create it first.`);
777
+ continue;
778
+ }
779
+ await apiClient.updateFlag(app.id, envSlug, key, { value });
780
+ updated++;
781
+ }
782
+ if (!options.quiet) {
783
+ console.log(`✓ Updated ${updated} flags`);
784
+ }
785
+ } catch (e) {
786
+ if (e instanceof SyntaxError) {
787
+ error("Invalid JSON input");
788
+ process.exit(1);
789
+ }
790
+ if (e instanceof ApiError) {
791
+ error(e.message);
792
+ process.exit(e.status === 401 ? 2 : 1);
793
+ }
794
+ error("Failed to bulk set flags");
795
+ process.exit(1);
796
+ }
797
+ }
798
+
799
+ // src/commands/keys.ts
800
+ async function findApp3(nameOrId) {
801
+ const { apps } = await apiClient.listApps();
802
+ return apps.find((a) => a.name === nameOrId || a.id === nameOrId) || null;
803
+ }
804
+ async function listKeys(appName, envSlug) {
805
+ if (!appName || !envSlug) {
806
+ error("App and environment are required");
807
+ console.log(`Usage: ${yellow("superflag keys list --app <name> --env <slug>")}`);
808
+ process.exit(1);
809
+ }
810
+ try {
811
+ const app = await findApp3(appName);
812
+ if (!app) {
813
+ error(`App "${appName}" not found`);
814
+ process.exit(1);
815
+ }
816
+ const env = app.envs?.find((e) => e.slug === envSlug);
817
+ if (!env) {
818
+ error(`Environment "${envSlug}" not found`);
819
+ process.exit(1);
820
+ }
821
+ const { keys } = await apiClient.listKeys(app.id, env.id);
822
+ if (keys.length === 0) {
823
+ info(`No keys found for "${app.name}" (${envSlug})`);
824
+ return;
825
+ }
826
+ console.log(bold(`
827
+ Keys for "${app.name}" (${envSlug}) - ${keys.length} total
828
+ `));
829
+ table(["TYPE", "NAME", "ID", "STATUS"], keys.map((key) => [
830
+ key.type.toUpperCase(),
831
+ key.name,
832
+ dim(key.id),
833
+ key.revoked_at ? red("revoked") : "active"
834
+ ]));
835
+ console.log("");
836
+ } catch (e) {
837
+ if (e instanceof ApiError) {
838
+ error(e.message);
839
+ } else {
840
+ error("Failed to list keys");
841
+ }
842
+ process.exit(1);
843
+ }
844
+ }
845
+ async function createKey(appName, envSlug, type) {
846
+ if (!appName || !envSlug || !type) {
847
+ error("Missing required arguments");
848
+ console.log(`Usage: ${yellow("superflag keys create --app <name> --env <slug> --type <sdk|pub>")}`);
849
+ process.exit(1);
850
+ }
851
+ if (type !== "sdk" && type !== "pub") {
852
+ error("Type must be 'sdk' or 'pub'");
853
+ process.exit(1);
854
+ }
855
+ try {
856
+ const app = await findApp3(appName);
857
+ if (!app) {
858
+ error(`App "${appName}" not found`);
859
+ process.exit(1);
860
+ }
861
+ const env = app.envs?.find((e) => e.slug === envSlug);
862
+ if (!env) {
863
+ error(`Environment "${envSlug}" not found`);
864
+ process.exit(1);
865
+ }
866
+ const { key, token } = await apiClient.createKey(app.id, env.id, type);
867
+ success(`Created ${type.toUpperCase()} key for "${app.name}" (${envSlug})`);
868
+ console.log("");
869
+ console.log(dim("Save this key - it won't be shown again!"));
870
+ console.log("");
871
+ console.log(` ${yellow(token)}`);
872
+ console.log("");
873
+ } catch (e) {
874
+ if (e instanceof ApiError) {
875
+ error(e.message);
876
+ } else {
877
+ error("Failed to create key");
878
+ }
879
+ process.exit(1);
880
+ }
881
+ }
882
+ async function revokeKey(keyId) {
883
+ if (!keyId) {
884
+ error("Key ID is required");
885
+ console.log(`Usage: ${yellow("superflag keys revoke <key-id>")}`);
886
+ process.exit(1);
887
+ }
888
+ try {
889
+ await apiClient.revokeKey(keyId);
890
+ success(`Revoked key ${keyId}`);
891
+ } catch (e) {
892
+ if (e instanceof ApiError) {
893
+ error(e.message);
894
+ } else {
895
+ error("Failed to revoke key");
896
+ }
897
+ process.exit(1);
898
+ }
899
+ }
900
+
901
+ // src/commands/use.ts
902
+ async function use(appName, envSlug) {
903
+ if (!appName) {
904
+ const context = await loadContext();
905
+ if (context.app && context.env) {
906
+ info(`Current context: ${context.app} (${context.env})`);
907
+ } else {
908
+ info("No context set");
909
+ console.log(dim("Usage: superflag use <app> <env>"));
910
+ }
911
+ return;
912
+ }
913
+ if (!envSlug) {
914
+ error("Environment is required");
915
+ console.log(dim("Usage: superflag use <app> <env>"));
916
+ process.exit(1);
917
+ }
918
+ try {
919
+ const { apps } = await apiClient.listApps();
920
+ const app = apps.find((a) => a.name === appName || a.id === appName);
921
+ if (!app) {
922
+ error(`App "${appName}" not found`);
923
+ process.exit(3);
924
+ }
925
+ const env = app.envs?.find((e) => e.slug === envSlug);
926
+ if (!env) {
927
+ error(`Environment "${envSlug}" not found in "${app.name}"`);
928
+ process.exit(3);
929
+ }
930
+ await saveContext({ app: app.name, env: env.slug });
931
+ success(`Context set to ${app.name} (${env.slug})`);
932
+ } catch (e) {
933
+ error(e instanceof Error ? e.message : "Failed to set context");
934
+ process.exit(1);
935
+ }
936
+ }
937
+
938
+ // src/commands/get.ts
939
+ async function findApp4(nameOrId) {
940
+ const { apps } = await apiClient.listApps();
941
+ return apps.find((a) => a.name === nameOrId || a.id === nameOrId) || null;
942
+ }
943
+ async function get(key, options) {
944
+ const { app: appName, env: envSlug } = await getAppAndEnv(options.app, options.env);
945
+ if (!appName || !envSlug) {
946
+ error("No context set. Run 'superflag use <app> <env>' first.");
947
+ process.exit(2);
948
+ }
949
+ if (!key) {
950
+ error("Flag key is required");
951
+ process.exit(1);
952
+ }
953
+ try {
954
+ const app = await findApp4(appName);
955
+ if (!app) {
956
+ error(`App "${appName}" not found`);
957
+ process.exit(3);
958
+ }
959
+ const { flags } = await apiClient.listFlags(app.id, envSlug);
960
+ const flag = flags[key];
961
+ if (!flag) {
962
+ error(`Flag "${key}" not found`);
963
+ process.exit(3);
964
+ }
965
+ if (options.json) {
966
+ console.log(JSON.stringify({ key, ...flag }));
967
+ } else {
968
+ if (flag.type === "json") {
969
+ console.log(JSON.stringify(flag.value));
970
+ } else {
971
+ console.log(flag.value);
972
+ }
973
+ }
974
+ } catch (e) {
975
+ if (e instanceof ApiError) {
976
+ error(e.message);
977
+ process.exit(e.status === 401 ? 2 : 1);
978
+ }
979
+ error("Failed to get flag");
980
+ process.exit(1);
981
+ }
982
+ }
983
+
984
+ // src/commands/set.ts
985
+ async function findApp5(nameOrId) {
986
+ const { apps } = await apiClient.listApps();
987
+ return apps.find((a) => a.name === nameOrId || a.id === nameOrId) || null;
988
+ }
989
+ async function set(key, valueStr, options) {
990
+ const { app: appName, env: envSlug } = await getAppAndEnv(options.app, options.env);
991
+ if (!appName || !envSlug) {
992
+ error("No context set. Run 'superflag use <app> <env>' first.");
993
+ process.exit(2);
994
+ }
995
+ if (!key || valueStr === undefined) {
996
+ error("Flag key and value are required");
997
+ process.exit(1);
998
+ }
999
+ try {
1000
+ const app = await findApp5(appName);
1001
+ if (!app) {
1002
+ error(`App "${appName}" not found`);
1003
+ process.exit(3);
1004
+ }
1005
+ const { flags } = await apiClient.listFlags(app.id, envSlug);
1006
+ const currentFlag = flags[key];
1007
+ if (!currentFlag) {
1008
+ error(`Flag "${key}" not found. Use 'superflag flags upsert' to create it.`);
1009
+ process.exit(3);
1010
+ }
1011
+ let value;
1012
+ switch (currentFlag.type) {
1013
+ case "bool":
1014
+ value = valueStr === "true" || valueStr === "1";
1015
+ break;
1016
+ case "number":
1017
+ value = Number(valueStr);
1018
+ if (isNaN(value)) {
1019
+ error("Invalid number value");
1020
+ process.exit(1);
1021
+ }
1022
+ break;
1023
+ case "json":
1024
+ try {
1025
+ value = JSON.parse(valueStr);
1026
+ } catch {
1027
+ error("Invalid JSON value");
1028
+ process.exit(1);
1029
+ }
1030
+ break;
1031
+ default:
1032
+ value = valueStr;
1033
+ }
1034
+ await apiClient.updateFlag(app.id, envSlug, key, { value });
1035
+ if (!options.quiet) {
1036
+ console.log("✓");
1037
+ }
1038
+ } catch (e) {
1039
+ if (e instanceof ApiError) {
1040
+ error(e.message);
1041
+ process.exit(e.status === 401 ? 2 : 1);
1042
+ }
1043
+ error("Failed to set flag");
1044
+ process.exit(1);
1045
+ }
1046
+ }
1047
+
1048
+ // src/commands/config.ts
1049
+ async function findApp6(nameOrId) {
1050
+ const { apps } = await apiClient.listApps();
1051
+ return apps.find((a) => a.name === nameOrId || a.id === nameOrId) || null;
1052
+ }
1053
+ async function config(options) {
1054
+ const { app: appName, env: envSlug } = await getAppAndEnv(options.app, options.env);
1055
+ if (!appName || !envSlug) {
1056
+ error("No context set. Run 'superflag use <app> <env>' first.");
1057
+ process.exit(2);
1058
+ }
1059
+ try {
1060
+ const app = await findApp6(appName);
1061
+ if (!app) {
1062
+ error(`App "${appName}" not found`);
1063
+ process.exit(3);
1064
+ }
1065
+ const { flags } = await apiClient.listFlags(app.id, envSlug);
1066
+ console.log(JSON.stringify({ app: app.name, env: envSlug, flags }, null, 2));
1067
+ } catch (e) {
1068
+ if (e instanceof ApiError) {
1069
+ error(e.message);
1070
+ process.exit(e.status === 401 ? 2 : 1);
1071
+ }
1072
+ error("Failed to get config");
1073
+ process.exit(1);
1074
+ }
1075
+ }
1076
+
1077
+ // src/commands/status.ts
1078
+ async function status(options) {
1079
+ const authenticated = await isAuthenticated();
1080
+ const context = await loadContext();
1081
+ let email = null;
1082
+ let apiConnected = false;
1083
+ if (authenticated) {
1084
+ try {
1085
+ const { user } = await apiClient.whoami();
1086
+ email = user.email;
1087
+ apiConnected = true;
1088
+ } catch {}
1089
+ }
1090
+ if (options.json) {
1091
+ console.log(JSON.stringify({
1092
+ authenticated,
1093
+ email,
1094
+ apiConnected,
1095
+ context: context.app && context.env ? { app: context.app, env: context.env } : null
1096
+ }));
1097
+ return;
1098
+ }
1099
+ if (authenticated && email) {
1100
+ console.log(`${green("✓")} Authenticated as ${email}`);
1101
+ } else if (authenticated) {
1102
+ console.log(`${red("✗")} Token invalid or expired`);
1103
+ } else {
1104
+ console.log(`${red("✗")} Not authenticated`);
1105
+ }
1106
+ if (context.app && context.env) {
1107
+ console.log(`${green("✓")} Context: ${context.app} (${context.env})`);
1108
+ } else {
1109
+ console.log(`${dim("○")} No context set`);
1110
+ }
1111
+ if (apiConnected) {
1112
+ console.log(`${green("✓")} API: connected`);
1113
+ } else if (authenticated) {
1114
+ console.log(`${red("✗")} API: connection failed`);
1115
+ }
1116
+ }
1117
+
1118
+ // src/utils/output.ts
1119
+ var colors2 = {
1120
+ reset: "\x1B[0m",
1121
+ bold: "\x1B[1m",
1122
+ dim: "\x1B[2m",
1123
+ red: "\x1B[31m",
1124
+ green: "\x1B[32m",
1125
+ yellow: "\x1B[33m",
1126
+ blue: "\x1B[34m",
1127
+ cyan: "\x1B[36m"
1128
+ };
1129
+ function bold2(text) {
1130
+ return `${colors2.bold}${text}${colors2.reset}`;
1131
+ }
1132
+ function dim2(text) {
1133
+ return `${colors2.dim}${text}${colors2.reset}`;
1134
+ }
1135
+ function red2(text) {
1136
+ return `${colors2.red}${text}${colors2.reset}`;
1137
+ }
1138
+ function yellow2(text) {
1139
+ return `${colors2.yellow}${text}${colors2.reset}`;
1140
+ }
1141
+ function error2(message) {
1142
+ console.error(`${red2("error:")} ${message}`);
1143
+ }
1144
+
1145
+ // src/index.ts
1146
+ var VERSION = "0.1.0";
1147
+ function parseArgs(args) {
1148
+ const command = [];
1149
+ const flags = {};
1150
+ for (let i = 0;i < args.length; i++) {
1151
+ const arg = args[i];
1152
+ if (arg.startsWith("--")) {
1153
+ const key = arg.slice(2);
1154
+ const nextArg = args[i + 1];
1155
+ if (nextArg && !nextArg.startsWith("-")) {
1156
+ flags[key] = nextArg;
1157
+ i++;
1158
+ } else {
1159
+ flags[key] = true;
1160
+ }
1161
+ } else if (arg.startsWith("-")) {
1162
+ const key = arg.slice(1);
1163
+ flags[key] = true;
1164
+ } else {
1165
+ command.push(arg);
1166
+ }
1167
+ }
1168
+ return { command, flags };
1169
+ }
1170
+ function showHelp() {
1171
+ console.log(`
1172
+ ${bold2("Superflag CLI")} ${dim2(`v${VERSION}`)}
1173
+
1174
+ ${bold2("USAGE")}
1175
+ superflag <command> [options]
1176
+
1177
+ ${bold2("QUICK COMMANDS")} ${dim2("(agent-friendly)")}
1178
+ ${yellow2("use")} <app> <env> Set default app/env context
1179
+ ${yellow2("get")} <key> Get a flag value
1180
+ ${yellow2("set")} <key> <value> Set a flag value
1181
+ ${yellow2("config")} Dump full config as JSON
1182
+ ${yellow2("status")} Show auth and context status
1183
+
1184
+ ${bold2("AUTH COMMANDS")}
1185
+ ${yellow2("login")} Authenticate with Superflag
1186
+ ${yellow2("logout")} Log out of Superflag
1187
+ ${yellow2("whoami")} Show current user
1188
+
1189
+ ${bold2("APP COMMANDS")}
1190
+ ${yellow2("apps list")} List all apps
1191
+ ${yellow2("apps create")} <name> Create a new app
1192
+
1193
+ ${bold2("ENV COMMANDS")}
1194
+ ${yellow2("envs list")} --app <name> List environments for an app
1195
+
1196
+ ${bold2("FLAG COMMANDS")}
1197
+ ${yellow2("flags list")} --app <n> --env <e> List flags in an environment
1198
+ ${yellow2("flags create")} --app <n> --env <e> --key <k> --type <t> --value <v> [--client]
1199
+ ${yellow2("flags set")} --app <n> --env <e> --key <k> --value <v>
1200
+ ${yellow2("flags toggle")} --app <n> --env <e> --key <k>
1201
+ ${yellow2("flags delete")} --app <n> --env <e> --key <k>
1202
+ ${yellow2("flags upsert")} --key <k> --type <t> --value <v> [--client]
1203
+ ${dim2("(uses context, idempotent)")}
1204
+ ${yellow2("flags bulk-set")} [--file <path>] Set multiple flags from JSON
1205
+
1206
+ ${bold2("KEY COMMANDS")}
1207
+ ${yellow2("keys list")} --app <n> --env <e> List keys for an environment
1208
+ ${yellow2("keys create")} --app <n> --env <e> --type <sdk|pub>
1209
+ ${yellow2("keys revoke")} <key-id> Revoke a key
1210
+
1211
+ ${bold2("GLOBAL OPTIONS")}
1212
+ --json Output as JSON
1213
+ --quiet, -q Suppress non-essential output
1214
+ --app, --env Override context for single command
1215
+ --help, -h Show this help message
1216
+ --version, -v Show version
1217
+
1218
+ ${bold2("AGENT WORKFLOW")}
1219
+ ${dim2("superflag use myapp prod")}
1220
+ ${dim2("superflag get dark-mode")} ${dim2("\u2192 true")}
1221
+ ${dim2("superflag set dark-mode false")}
1222
+ ${dim2("superflag flags upsert --key new-flag --type bool --value true")}
1223
+ `);
1224
+ }
1225
+ async function main() {
1226
+ const args = process.argv.slice(2);
1227
+ const { command, flags } = parseArgs(args);
1228
+ if (flags.help || flags.h || command.length === 0) {
1229
+ showHelp();
1230
+ return;
1231
+ }
1232
+ if (flags.version || flags.v) {
1233
+ console.log(`superflag v${VERSION}`);
1234
+ return;
1235
+ }
1236
+ const [cmd, subCmd, ...rest] = command;
1237
+ try {
1238
+ switch (cmd) {
1239
+ case "use":
1240
+ await use(subCmd, rest[0]);
1241
+ break;
1242
+ case "get":
1243
+ await get(subCmd, {
1244
+ app: flags.app,
1245
+ env: flags.env,
1246
+ json: Boolean(flags.json)
1247
+ });
1248
+ break;
1249
+ case "set":
1250
+ await set(subCmd, rest[0], {
1251
+ app: flags.app,
1252
+ env: flags.env,
1253
+ quiet: Boolean(flags.quiet || flags.q)
1254
+ });
1255
+ break;
1256
+ case "config":
1257
+ await config({
1258
+ app: flags.app,
1259
+ env: flags.env
1260
+ });
1261
+ break;
1262
+ case "status":
1263
+ await status({ json: Boolean(flags.json) });
1264
+ break;
1265
+ case "login":
1266
+ await login();
1267
+ break;
1268
+ case "logout":
1269
+ await logout();
1270
+ break;
1271
+ case "whoami":
1272
+ await whoami();
1273
+ break;
1274
+ case "apps":
1275
+ switch (subCmd) {
1276
+ case "list":
1277
+ await listApps();
1278
+ break;
1279
+ case "create":
1280
+ await createApp(rest[0] || flags.name);
1281
+ break;
1282
+ default:
1283
+ error2(`Unknown apps command: ${subCmd}`);
1284
+ console.log(`Run ${yellow2("superflag apps --help")} for usage.`);
1285
+ process.exit(1);
1286
+ }
1287
+ break;
1288
+ case "envs":
1289
+ switch (subCmd) {
1290
+ case "list":
1291
+ await listEnvs(flags.app);
1292
+ break;
1293
+ default:
1294
+ error2(`Unknown envs command: ${subCmd}`);
1295
+ process.exit(1);
1296
+ }
1297
+ break;
1298
+ case "flags":
1299
+ switch (subCmd) {
1300
+ case "list":
1301
+ await listFlags(flags.app, flags.env);
1302
+ break;
1303
+ case "create":
1304
+ await createFlag(flags.app, flags.env, flags.key, flags.type, flags.value, Boolean(flags.client));
1305
+ break;
1306
+ case "set":
1307
+ await setFlag(flags.app, flags.env, flags.key, flags.value);
1308
+ break;
1309
+ case "toggle":
1310
+ await toggleFlag(flags.app, flags.env, flags.key);
1311
+ break;
1312
+ case "delete":
1313
+ await deleteFlag(flags.app, flags.env, flags.key);
1314
+ break;
1315
+ case "upsert":
1316
+ await upsertFlag({
1317
+ app: flags.app,
1318
+ env: flags.env,
1319
+ key: flags.key,
1320
+ type: flags.type,
1321
+ value: flags.value,
1322
+ client: Boolean(flags.client),
1323
+ quiet: Boolean(flags.quiet || flags.q)
1324
+ });
1325
+ break;
1326
+ case "bulk-set":
1327
+ await bulkSet({
1328
+ app: flags.app,
1329
+ env: flags.env,
1330
+ file: flags.file,
1331
+ quiet: Boolean(flags.quiet || flags.q)
1332
+ });
1333
+ break;
1334
+ default:
1335
+ error2(`Unknown flags command: ${subCmd}`);
1336
+ process.exit(1);
1337
+ }
1338
+ break;
1339
+ case "keys":
1340
+ switch (subCmd) {
1341
+ case "list":
1342
+ await listKeys(flags.app, flags.env);
1343
+ break;
1344
+ case "create":
1345
+ await createKey(flags.app, flags.env, flags.type);
1346
+ break;
1347
+ case "revoke":
1348
+ await revokeKey(rest[0] || flags.id);
1349
+ break;
1350
+ default:
1351
+ error2(`Unknown keys command: ${subCmd}`);
1352
+ process.exit(1);
1353
+ }
1354
+ break;
1355
+ default:
1356
+ error2(`Unknown command: ${cmd}`);
1357
+ console.log(`Run ${yellow2("superflag --help")} for usage.`);
1358
+ process.exit(1);
1359
+ }
1360
+ } catch (e) {
1361
+ if (e instanceof Error) {
1362
+ error2(e.message);
1363
+ } else {
1364
+ error2("An unexpected error occurred");
1365
+ }
1366
+ process.exit(1);
1367
+ }
1368
+ }
1369
+ main();
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@superflag-sh/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for Superflag feature flags",
5
+ "type": "module",
6
+ "bin": {
7
+ "superflag": "./dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "https://github.com/superflag-sh/cli"
15
+ },
16
+ "keywords": [
17
+ "feature-flags",
18
+ "cli",
19
+ "superflag"
20
+ ],
21
+ "author": "Superflag",
22
+ "license": "MIT",
23
+ "devDependencies": {
24
+ "@types/bun": "^1.3.5",
25
+ "@types/node": "^22.0.0",
26
+ "typescript": "^5.0.0"
27
+ },
28
+ "scripts": {
29
+ "build": "rm -rf dist && bun build ./src/index.ts --outdir dist --target node --format esm && chmod +x dist/index.js",
30
+ "dev": "bun run src/index.ts",
31
+ "typecheck": "tsc --noEmit"
32
+ }
33
+ }