@globio/cli 0.1.3 → 0.1.4

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 CHANGED
@@ -10,97 +10,134 @@ import chalk2 from "chalk";
10
10
 
11
11
  // src/lib/config.ts
12
12
  import chalk from "chalk";
13
- import Conf from "conf";
14
- var store = new Conf({
15
- projectName: "globio",
16
- defaults: {}
17
- });
13
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
14
+ import os from "os";
15
+ import path from "path";
16
+ var baseDir = path.join(os.homedir(), ".globio");
17
+ var profilesDir = path.join(baseDir, "profiles");
18
+ var configPath = path.join(baseDir, "config.json");
19
+ function ensureBaseDir() {
20
+ mkdirSync(baseDir, { recursive: true });
21
+ }
22
+ function ensureProfilesDir() {
23
+ ensureBaseDir();
24
+ mkdirSync(profilesDir, { recursive: true });
25
+ }
26
+ function readGlobalConfig() {
27
+ if (!existsSync(configPath)) {
28
+ return { active_profile: "default" };
29
+ }
30
+ try {
31
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
32
+ return {
33
+ active_profile: raw.active_profile ?? "default"
34
+ };
35
+ } catch {
36
+ return { active_profile: "default" };
37
+ }
38
+ }
39
+ function writeGlobalConfig(data) {
40
+ ensureBaseDir();
41
+ writeFileSync(configPath, JSON.stringify(data, null, 2) + "\n");
42
+ }
43
+ function profilePath(name) {
44
+ return path.join(profilesDir, `${name}.json`);
45
+ }
46
+ function readProfile(name) {
47
+ const file = profilePath(name);
48
+ if (!existsSync(file)) return null;
49
+ try {
50
+ return JSON.parse(readFileSync(file, "utf-8"));
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function writeProfile(name, data) {
56
+ ensureProfilesDir();
57
+ writeFileSync(profilePath(name), JSON.stringify(data, null, 2) + "\n");
58
+ }
18
59
  var config = {
19
- get: () => store.store,
20
- set: (values) => {
21
- Object.entries(values).forEach(([key, value]) => {
22
- if (value !== void 0) {
23
- store.set(key, value);
24
- }
25
- });
60
+ getBaseDir: () => baseDir,
61
+ getProfilesDir: () => profilesDir,
62
+ getActiveProfile: () => readGlobalConfig().active_profile,
63
+ setActiveProfile: (name) => {
64
+ writeGlobalConfig({ active_profile: name });
26
65
  },
27
- clear: () => store.clear(),
28
- getPat: () => store.get("pat"),
29
- requirePat: () => {
30
- const pat = store.get("pat");
31
- if (!pat) {
32
- console.error(chalk.red("Not logged in. Run: npx @globio/cli login"));
33
- process.exit(1);
34
- }
35
- return pat;
66
+ getProfile: (name) => {
67
+ const profileName = name ?? config.getActiveProfile();
68
+ if (!profileName) return null;
69
+ return readProfile(profileName);
36
70
  },
37
- requireProject: () => {
38
- const projectId = store.get("projectId");
39
- if (!projectId) {
40
- console.error(
41
- chalk.red("No active project. Run: npx @globio/cli projects use <projectId>")
42
- );
43
- process.exit(1);
44
- }
45
- return projectId;
71
+ setProfile: (name, data) => {
72
+ const existing = readProfile(name);
73
+ const next = {
74
+ pat: data.pat ?? existing?.pat ?? "",
75
+ account_email: data.account_email ?? existing?.account_email ?? "",
76
+ account_name: data.account_name ?? existing?.account_name ?? "",
77
+ active_project_id: data.active_project_id ?? existing?.active_project_id,
78
+ active_project_name: data.active_project_name ?? existing?.active_project_name,
79
+ project_api_key: data.project_api_key ?? existing?.project_api_key,
80
+ created_at: data.created_at ?? existing?.created_at ?? Date.now()
81
+ };
82
+ writeProfile(name, next);
46
83
  },
47
- setProjectAuth: (projectId, apiKey, projectName) => {
48
- const projectApiKeys = store.get("projectApiKeys") ?? {};
49
- const projectNames = store.get("projectNames") ?? {};
50
- projectApiKeys[projectId] = apiKey;
51
- if (projectName) {
52
- projectNames[projectId] = projectName;
53
- }
54
- store.set("projectApiKeys", projectApiKeys);
55
- store.set("projectNames", projectNames);
56
- store.set("projectId", projectId);
57
- if (projectName) {
58
- store.set("projectName", projectName);
84
+ deleteProfile: (name) => {
85
+ const file = profilePath(name);
86
+ if (existsSync(file)) {
87
+ rmSync(file);
59
88
  }
60
89
  },
61
- getProjectApiKey: (projectId) => {
62
- const projectApiKeys = store.get("projectApiKeys") ?? {};
63
- return projectApiKeys[projectId];
90
+ listProfiles: () => {
91
+ if (!existsSync(profilesDir)) return [];
92
+ return readdirSync(profilesDir).filter((file) => file.endsWith(".json")).map((file) => file.replace(/\.json$/, "")).sort();
64
93
  },
65
- requireProjectApiKey: () => {
66
- const projectId = store.get("projectId");
67
- if (!projectId) {
68
- console.error(
69
- chalk.red("No active project. Run: npx @globio/cli projects use <projectId>")
70
- );
94
+ getActiveProfileData: () => {
95
+ const active = config.getActiveProfile();
96
+ if (!active) return null;
97
+ return config.getProfile(active);
98
+ },
99
+ requireAuth: (profileName) => {
100
+ const resolvedProfile = profileName ?? config.getActiveProfile() ?? "default";
101
+ const profile = config.getProfile(resolvedProfile);
102
+ if (!profile?.pat) {
103
+ console.error(chalk.red("Not logged in. Run: npx @globio/cli login"));
71
104
  process.exit(1);
72
105
  }
73
- const projectApiKeys = store.get("projectApiKeys") ?? {};
74
- const apiKey = projectApiKeys[projectId];
75
- if (!apiKey) {
106
+ return { pat: profile.pat, profileName: resolvedProfile };
107
+ },
108
+ requireProject: (profileName) => {
109
+ const resolvedProfile = profileName ?? config.getActiveProfile() ?? "default";
110
+ const profile = config.getProfile(resolvedProfile);
111
+ if (!profile?.active_project_id) {
76
112
  console.error(
77
- chalk.red(
78
- "No project API key stored for the active project. Run: npx @globio/cli projects use <projectId>"
79
- )
113
+ chalk.red("No active project. Run: npx @globio/cli projects use <projectId>")
80
114
  );
81
115
  process.exit(1);
82
116
  }
83
- return apiKey;
117
+ return {
118
+ projectId: profile.active_project_id,
119
+ projectName: profile.active_project_name ?? "unnamed"
120
+ };
84
121
  }
85
122
  };
86
123
 
87
124
  // src/lib/manage.ts
88
125
  var API_BASE_URL = "https://api.globio.stanlink.online";
89
126
  var CONSOLE_BASE_URL = "https://console.globio.stanlink.online";
90
- function getAuthToken(explicitToken) {
127
+ function getAuthToken(explicitToken, profileName) {
91
128
  if (explicitToken) return explicitToken;
92
- return config.getPat();
129
+ return config.getProfile(profileName)?.pat;
93
130
  }
94
- async function manageRequest(path, options = {}) {
131
+ async function manageRequest(path2, options = {}) {
95
132
  const headers = new Headers();
96
133
  if (!(options.body instanceof FormData)) {
97
134
  headers.set("Content-Type", "application/json");
98
135
  }
99
- const token = getAuthToken(options.token);
136
+ const token = getAuthToken(options.token, options.profileName);
100
137
  if (token) {
101
138
  headers.set("Authorization", `Bearer ${token}`);
102
139
  }
103
- const response = await fetch(`${API_BASE_URL}/manage${path}`, {
140
+ const response = await fetch(`${API_BASE_URL}/manage${path2}`, {
104
141
  method: options.method ?? "GET",
105
142
  headers,
106
143
  body: options.body ? JSON.stringify(options.body) : void 0
@@ -116,7 +153,7 @@ function getConsoleCliAuthUrl(state) {
116
153
  }
117
154
 
118
155
  // src/lib/banner.ts
119
- import { readFileSync } from "fs";
156
+ import { readFileSync as readFileSync2 } from "fs";
120
157
  import figlet from "figlet";
121
158
  import gradientString from "gradient-string";
122
159
  var globioGradient = gradientString(
@@ -144,7 +181,7 @@ var orange = (s) => "\x1B[38;2;244;140;6m" + s + "\x1B[0m";
144
181
  var gold = (s) => "\x1B[38;2;255;208;0m" + s + "\x1B[0m";
145
182
  var muted = (s) => "\x1B[2m" + s + "\x1B[0m";
146
183
  function getCliVersion() {
147
- const file = readFileSync(new URL("../package.json", import.meta.url), "utf8");
184
+ const file = readFileSync2(new URL("../package.json", import.meta.url), "utf8");
148
185
  return JSON.parse(file).version;
149
186
  }
150
187
 
@@ -159,14 +196,10 @@ function sleep(ms) {
159
196
  }
160
197
  async function savePat(token) {
161
198
  const account = await manageRequest("/account", { token });
162
- config.set({
163
- pat: token,
164
- accountEmail: account.email,
165
- accountName: account.display_name ?? account.email
166
- });
167
199
  return account;
168
200
  }
169
- async function runTokenLogin() {
201
+ async function runTokenLogin(profileName) {
202
+ const hadProfiles = config.listProfiles().length > 0;
170
203
  const token = await p.text({
171
204
  message: "Paste your personal access token",
172
205
  placeholder: "glo_pat_...",
@@ -184,17 +217,28 @@ async function runTokenLogin() {
184
217
  spinner2.start("Validating personal access token...");
185
218
  try {
186
219
  const account = await savePat(token);
220
+ config.setProfile(profileName, {
221
+ pat: token,
222
+ account_email: account.email,
223
+ account_name: account.display_name ?? account.email,
224
+ created_at: Date.now()
225
+ });
226
+ if (profileName === "default" || !hadProfiles) {
227
+ config.setActiveProfile(profileName);
228
+ }
187
229
  spinner2.stop("Token validated.");
188
- p.outro(`Logged in as ${account.email}`);
230
+ p.outro(`Logged in as ${account.email}
231
+ Profile: ${profileName}`);
189
232
  } catch (error) {
190
233
  spinner2.stop("Validation failed.");
191
234
  p.outro(chalk2.red(error instanceof Error ? error.message : "Could not validate token"));
192
235
  process.exit(1);
193
236
  }
194
237
  }
195
- async function runBrowserLogin() {
238
+ async function runBrowserLogin(profileName) {
196
239
  const state = crypto.randomUUID();
197
240
  const spinner2 = p.spinner();
241
+ const hadProfiles = config.listProfiles().length > 0;
198
242
  await manageRequest("/cli-auth/request", {
199
243
  method: "POST",
200
244
  body: { state }
@@ -220,13 +264,18 @@ async function runBrowserLogin() {
220
264
  method: "POST",
221
265
  body: { code: status.code }
222
266
  });
223
- config.set({
267
+ config.setProfile(profileName, {
224
268
  pat: exchange.token,
225
- accountEmail: exchange.account.email,
226
- accountName: exchange.account.display_name ?? exchange.account.email
269
+ account_email: exchange.account.email,
270
+ account_name: exchange.account.display_name ?? exchange.account.email,
271
+ created_at: Date.now()
227
272
  });
273
+ if (profileName === "default" || !hadProfiles) {
274
+ config.setActiveProfile(profileName);
275
+ }
228
276
  spinner2.stop("Browser approval received.");
229
- p.outro(`Logged in as ${exchange.account.email}`);
277
+ p.outro(`Logged in as ${exchange.account.email}
278
+ Profile: ${profileName}`);
230
279
  return;
231
280
  }
232
281
  } catch {
@@ -239,8 +288,20 @@ async function runBrowserLogin() {
239
288
  }
240
289
  async function login(options = {}) {
241
290
  printBanner(version);
291
+ const profileName = options.profile ?? "default";
292
+ const existing = config.getProfile(profileName);
293
+ if (existing) {
294
+ const proceed = await p.confirm({
295
+ message: `Already logged in as ${existing.account_email} on profile "${profileName}". Replace?`,
296
+ initialValue: false
297
+ });
298
+ if (p.isCancel(proceed) || !proceed) {
299
+ p.outro("Login cancelled.");
300
+ return;
301
+ }
302
+ }
242
303
  if (options.token) {
243
- await runTokenLogin();
304
+ await runTokenLogin(profileName);
244
305
  return;
245
306
  }
246
307
  const choice = await p.select({
@@ -255,11 +316,11 @@ async function login(options = {}) {
255
316
  process.exit(0);
256
317
  }
257
318
  if (choice === "token") {
258
- await runTokenLogin();
319
+ await runTokenLogin(profileName);
259
320
  return;
260
321
  }
261
322
  try {
262
- await runBrowserLogin();
323
+ await runBrowserLogin(profileName);
263
324
  } catch (error) {
264
325
  p.outro(chalk2.red(error instanceof Error ? error.message : "Could not connect to Globio."));
265
326
  process.exit(1);
@@ -267,51 +328,98 @@ async function login(options = {}) {
267
328
  }
268
329
 
269
330
  // src/auth/logout.ts
270
- import * as p2 from "@clack/prompts";
271
331
  import chalk3 from "chalk";
272
- async function logout() {
273
- config.clear();
274
- p2.outro(chalk3.green("Logged out."));
332
+ async function logout(options = {}) {
333
+ const activeProfile = config.getActiveProfile();
334
+ const profileName = options.profile ?? activeProfile;
335
+ const profile = profileName ? config.getProfile(profileName) : null;
336
+ if (!profileName || !profile) {
337
+ console.log(chalk3.yellow(`No active session on profile "${profileName || "default"}".`));
338
+ return;
339
+ }
340
+ config.deleteProfile(profileName);
341
+ if (profileName === activeProfile) {
342
+ const remaining = config.listProfiles();
343
+ if (remaining.length > 0) {
344
+ config.setActiveProfile(remaining[0]);
345
+ console.log(chalk3.green(`Logged out. Switched to profile: ${remaining[0]}`));
346
+ return;
347
+ }
348
+ config.setActiveProfile("");
349
+ console.log(chalk3.green("Logged out."));
350
+ return;
351
+ }
352
+ console.log(chalk3.green(`Logged out profile: ${profileName}`));
275
353
  }
276
354
 
277
- // src/auth/whoami.ts
355
+ // src/auth/useProfile.ts
278
356
  import chalk4 from "chalk";
279
- async function whoami() {
280
- const cfg = config.get();
281
- if (!cfg.pat) {
282
- console.log(chalk4.red("Not logged in."));
357
+ async function useProfile(profileName) {
358
+ const profile = config.getProfile(profileName);
359
+ if (!profile) {
360
+ console.log(
361
+ chalk4.red(
362
+ `Profile "${profileName}" not found. Run: globio login --profile ${profileName}`
363
+ )
364
+ );
365
+ process.exit(1);
366
+ }
367
+ config.setActiveProfile(profileName);
368
+ console.log(
369
+ chalk4.green("Switched to profile: ") + orange(profileName) + ` (${profile.account_email})`
370
+ );
371
+ }
372
+
373
+ // src/auth/whoami.ts
374
+ import chalk5 from "chalk";
375
+ async function whoami(options = {}) {
376
+ const profileName = options.profile ?? config.getActiveProfile() ?? "default";
377
+ const profile = config.getProfile(profileName);
378
+ if (!profile) {
379
+ console.log(chalk5.red("Not logged in. Run: globio login"));
283
380
  return;
284
381
  }
382
+ const allProfiles = config.listProfiles();
383
+ const activeProfile = config.getActiveProfile();
285
384
  console.log("");
286
- console.log(chalk4.cyan("Account: ") + (cfg.accountEmail ?? "unknown"));
287
- console.log(chalk4.cyan("Name: ") + (cfg.accountName ?? "unknown"));
288
385
  console.log(
289
- chalk4.cyan("Project: ") + (cfg.projectId ? `${cfg.projectName ?? "unnamed"} (${cfg.projectId})` : "none")
386
+ muted("Profile: ") + orange(profileName) + (profileName === activeProfile ? muted(" (active)") : "")
387
+ );
388
+ console.log(muted("Account: ") + profile.account_email);
389
+ console.log(muted("Name: ") + (profile.account_name || "\u2014"));
390
+ console.log(
391
+ muted("Project: ") + (profile.active_project_id ? orange(profile.active_project_name || "unnamed") + muted(` (${profile.active_project_id})`) : chalk5.gray("none \u2014 run: globio projects use <id>"))
290
392
  );
393
+ if (allProfiles.length > 1) {
394
+ console.log("");
395
+ console.log(
396
+ muted("Other profiles: ") + allProfiles.filter((name) => name !== profileName).join(", ")
397
+ );
398
+ }
291
399
  console.log("");
292
400
  }
293
401
 
294
402
  // src/commands/init.ts
295
- import * as p6 from "@clack/prompts";
296
- import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
403
+ import * as p5 from "@clack/prompts";
404
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
297
405
 
298
406
  // src/prompts/init.ts
299
- import * as p3 from "@clack/prompts";
407
+ import * as p2 from "@clack/prompts";
300
408
  async function promptInit() {
301
- return p3.group(
409
+ return p2.group(
302
410
  {
303
- migrateFromFirebase: () => p3.confirm({
411
+ migrateFromFirebase: () => p2.confirm({
304
412
  message: "Migrating from Firebase?",
305
413
  initialValue: false
306
414
  }),
307
- serviceAccountPath: ({ results }) => results.migrateFromFirebase ? p3.text({
415
+ serviceAccountPath: ({ results }) => results.migrateFromFirebase ? p2.text({
308
416
  message: "Path to Firebase service account JSON",
309
417
  placeholder: "./serviceAccountKey.json"
310
418
  }) : Promise.resolve(void 0)
311
419
  },
312
420
  {
313
421
  onCancel: () => {
314
- p3.cancel("Cancelled.");
422
+ p2.cancel("Cancelled.");
315
423
  process.exit(0);
316
424
  }
317
425
  }
@@ -319,15 +427,15 @@ async function promptInit() {
319
427
  }
320
428
 
321
429
  // src/commands/migrate.ts
322
- import * as p4 from "@clack/prompts";
323
- import chalk6 from "chalk";
430
+ import * as p3 from "@clack/prompts";
431
+ import chalk7 from "chalk";
324
432
  import { basename } from "path";
325
433
 
326
434
  // src/lib/firebase.ts
327
435
  async function initFirebase(serviceAccountPath) {
328
436
  const admin = await import("firebase-admin");
329
- const { readFileSync: readFileSync4 } = await import("fs");
330
- const serviceAccount = JSON.parse(readFileSync4(serviceAccountPath, "utf-8"));
437
+ const { readFileSync: readFileSync5 } = await import("fs");
438
+ const serviceAccount = JSON.parse(readFileSync5(serviceAccountPath, "utf-8"));
331
439
  if (!admin.default.apps.length) {
332
440
  admin.default.initializeApp({
333
441
  credential: admin.default.credential.cert(serviceAccount),
@@ -342,12 +450,12 @@ async function initFirebase(serviceAccountPath) {
342
450
  }
343
451
 
344
452
  // src/lib/progress.ts
345
- import chalk5 from "chalk";
453
+ import chalk6 from "chalk";
346
454
  import cliProgress from "cli-progress";
347
455
  function createProgressBar(label) {
348
456
  const bar = new cliProgress.SingleBar(
349
457
  {
350
- format: chalk5.cyan(label) + " [{bar}] {percentage}% | {value}/{total}",
458
+ format: chalk6.cyan(label) + " [{bar}] {percentage}% | {value}/{total}",
351
459
  barCompleteChar: "\u2588",
352
460
  barIncompleteChar: "\u2591",
353
461
  hideCursor: true
@@ -359,31 +467,38 @@ function createProgressBar(label) {
359
467
 
360
468
  // src/lib/sdk.ts
361
469
  import { Globio } from "@globio/sdk";
362
- function getClient() {
363
- const apiKey = config.requireProjectApiKey();
470
+ function getClient(profileName) {
471
+ const { pat } = config.requireAuth(profileName);
472
+ const { projectId } = config.requireProject(profileName);
473
+ const profile = config.getProfile(profileName);
474
+ const apiKey = profile?.project_api_key ?? pat;
475
+ void projectId;
364
476
  return new Globio({ apiKey });
365
477
  }
366
478
 
367
479
  // src/commands/migrate.ts
368
480
  var version2 = getCliVersion();
481
+ function resolveProfileName(profile) {
482
+ return profile ?? config.getActiveProfile() ?? "default";
483
+ }
369
484
  async function migrateFirestore(options) {
370
485
  printBanner(version2);
371
- p4.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
486
+ p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
372
487
  const { firestore } = await initFirebase(options.from);
373
- const client = getClient();
488
+ const client = getClient(resolveProfileName(options.profile));
374
489
  let collections = [];
375
490
  if (options.all) {
376
491
  const snapshot = await firestore.listCollections();
377
492
  collections = snapshot.map((collection) => collection.id);
378
493
  console.log(
379
- chalk6.cyan(
494
+ chalk7.cyan(
380
495
  `Found ${collections.length} collections: ${collections.join(", ")}`
381
496
  )
382
497
  );
383
498
  } else if (options.collection) {
384
499
  collections = [options.collection];
385
500
  } else {
386
- console.log(chalk6.red("Specify --collection <name> or --all"));
501
+ console.log(chalk7.red("Specify --collection <name> or --all"));
387
502
  process.exit(1);
388
503
  }
389
504
  const results = {};
@@ -428,32 +543,32 @@ async function migrateFirestore(options) {
428
543
  }
429
544
  bar.stop();
430
545
  console.log(
431
- chalk6.green(` \u2713 ${results[collectionId].success} documents migrated`)
546
+ chalk7.green(` \u2713 ${results[collectionId].success} documents migrated`)
432
547
  );
433
548
  if (results[collectionId].failed > 0) {
434
- console.log(chalk6.red(` \u2717 ${results[collectionId].failed} failed`));
549
+ console.log(chalk7.red(` \u2717 ${results[collectionId].failed} failed`));
435
550
  console.log(
436
- chalk6.gray(
551
+ chalk7.gray(
437
552
  " Failed IDs: " + results[collectionId].failedIds.slice(0, 10).join(", ") + (results[collectionId].failedIds.length > 10 ? "..." : "")
438
553
  )
439
554
  );
440
555
  }
441
556
  }
442
557
  console.log("");
443
- p4.outro(
558
+ p3.outro(
444
559
  orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
445
560
  );
446
561
  }
447
562
  async function migrateFirebaseStorage(options) {
448
563
  printBanner(version2);
449
- p4.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
564
+ p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
450
565
  const { storage } = await initFirebase(options.from);
451
- const client = getClient();
566
+ const client = getClient(resolveProfileName(options.profile));
452
567
  const bucketName = options.bucket.replace(/^gs:\/\//, "");
453
568
  const bucket = storage.bucket(bucketName);
454
569
  const prefix = options.folder ? options.folder.replace(/^\//, "") : "";
455
570
  const [files] = await bucket.getFiles(prefix ? { prefix } : {});
456
- console.log(chalk6.cyan(`Found ${files.length} files to migrate`));
571
+ console.log(chalk7.cyan(`Found ${files.length} files to migrate`));
457
572
  const bar = createProgressBar("Storage");
458
573
  bar.start(files.length, 0);
459
574
  let success = 0;
@@ -481,39 +596,43 @@ async function migrateFirebaseStorage(options) {
481
596
  }
482
597
  bar.stop();
483
598
  console.log("");
484
- console.log(chalk6.green(` \u2713 ${success} files migrated`));
599
+ console.log(chalk7.green(` \u2713 ${success} files migrated`));
485
600
  if (failed > 0) {
486
- console.log(chalk6.red(` \u2717 ${failed} failed`));
601
+ console.log(chalk7.red(` \u2717 ${failed} failed`));
487
602
  }
488
- p4.outro(
603
+ p3.outro(
489
604
  orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
490
605
  );
491
606
  }
492
607
 
493
608
  // src/commands/projects.ts
494
- import * as p5 from "@clack/prompts";
495
- import chalk7 from "chalk";
609
+ import * as p4 from "@clack/prompts";
610
+ import chalk8 from "chalk";
496
611
  function slugify(value) {
497
612
  return value.toLowerCase().trim().replace(/[^a-z0-9\\s-]/g, "").replace(/\\s+/g, "-").replace(/-+/g, "-");
498
613
  }
499
- async function ensureProjectKey(projectId) {
500
- const existingKey = config.getProjectApiKey(projectId);
501
- if (existingKey) return existingKey;
614
+ function resolveProfileName2(profileName) {
615
+ return profileName ?? config.getActiveProfile() ?? "default";
616
+ }
617
+ async function createServerKey(projectId, profileName) {
502
618
  const created = await manageRequest(`/projects/${projectId}/keys`, {
503
619
  method: "POST",
504
620
  body: {
505
621
  name: "CLI server key",
506
622
  scope: "server"
507
- }
623
+ },
624
+ profileName
508
625
  });
509
626
  if (!created.token) {
510
627
  throw new Error("Management API did not return a project API key");
511
628
  }
512
629
  return created.token;
513
630
  }
514
- async function projectsList() {
515
- const projects2 = await manageRequest("/projects");
516
- const activeProjectId = config.get().projectId;
631
+ async function projectsList(options = {}) {
632
+ const profileName = resolveProfileName2(options.profile);
633
+ config.requireAuth(profileName);
634
+ const projects2 = await manageRequest("/projects", { profileName });
635
+ const activeProjectId = config.getProfile(profileName)?.active_project_id;
517
636
  const grouped = /* @__PURE__ */ new Map();
518
637
  for (const project of projects2) {
519
638
  const list = grouped.get(project.org_name) ?? [];
@@ -522,38 +641,50 @@ async function projectsList() {
522
641
  }
523
642
  console.log("");
524
643
  if (!projects2.length) {
525
- console.log(chalk7.gray("No projects found."));
644
+ console.log(chalk8.gray("No projects found."));
526
645
  console.log("");
527
646
  return;
528
647
  }
529
648
  for (const [orgName, orgProjects] of grouped.entries()) {
530
- console.log(chalk7.cyan(`org: ${orgName}`));
649
+ console.log(chalk8.cyan(`org: ${orgName}`));
531
650
  for (const project of orgProjects) {
532
- const marker = project.id === activeProjectId ? chalk7.green("\u25CF") : chalk7.gray("\u25CB");
533
- const active = project.id === activeProjectId ? chalk7.green(" (active)") : "";
534
- console.log(` ${marker} ${project.slug.padEnd(22)} ${chalk7.gray(project.id)}${active}`);
651
+ const marker = project.id === activeProjectId ? chalk8.green("\u25CF") : chalk8.gray("\u25CB");
652
+ const active = project.id === activeProjectId ? chalk8.green(" (active)") : "";
653
+ console.log(` ${marker} ${project.slug.padEnd(22)} ${chalk8.gray(project.id)}${active}`);
535
654
  }
536
655
  console.log("");
537
656
  }
538
657
  }
539
- async function projectsUse(projectId) {
540
- const projects2 = await manageRequest("/projects");
658
+ async function projectsUse(projectId, options = {}) {
659
+ const profileName = resolveProfileName2(options.profile);
660
+ config.requireAuth(profileName);
661
+ const projects2 = await manageRequest("/projects", { profileName });
541
662
  const project = projects2.find((item) => item.id === projectId);
542
663
  if (!project) {
543
- console.log(chalk7.red(`Project not found: ${projectId}`));
664
+ console.log(chalk8.red(`Project not found: ${projectId}`));
544
665
  process.exit(1);
545
666
  }
546
- const apiKey = await ensureProjectKey(projectId);
547
- config.setProjectAuth(projectId, apiKey, project.name);
548
- console.log(chalk7.green("Active project set to: ") + chalk7.cyan(`${project.name} (${project.id})`));
667
+ await manageRequest(`/projects/${projectId}/keys`, { profileName });
668
+ const apiKey = await createServerKey(projectId, profileName);
669
+ config.setProfile(profileName, {
670
+ active_project_id: project.id,
671
+ active_project_name: project.name,
672
+ project_api_key: apiKey
673
+ });
674
+ config.setActiveProfile(profileName);
675
+ console.log(
676
+ chalk8.green("Active project set to: ") + chalk8.cyan(`${project.name} (${project.id})`)
677
+ );
549
678
  }
550
- async function projectsCreate() {
551
- const orgs = await manageRequest("/orgs");
679
+ async function projectsCreate(options = {}) {
680
+ const profileName = resolveProfileName2(options.profile);
681
+ config.requireAuth(profileName);
682
+ const orgs = await manageRequest("/orgs", { profileName });
552
683
  if (!orgs.length) {
553
- console.log(chalk7.red("No organizations found. Create one in the console first."));
684
+ console.log(chalk8.red("No organizations found. Create one in the console first."));
554
685
  process.exit(1);
555
686
  }
556
- const orgId = await p5.select({
687
+ const orgId = await p4.select({
557
688
  message: "Select an organization",
558
689
  options: orgs.map((org) => ({
559
690
  value: org.id,
@@ -561,22 +692,22 @@ async function projectsCreate() {
561
692
  hint: org.role
562
693
  }))
563
694
  });
564
- if (p5.isCancel(orgId)) {
565
- p5.cancel("Project creation cancelled.");
695
+ if (p4.isCancel(orgId)) {
696
+ p4.cancel("Project creation cancelled.");
566
697
  process.exit(0);
567
698
  }
568
- const values = await p5.group(
699
+ const values = await p4.group(
569
700
  {
570
- name: () => p5.text({
701
+ name: () => p4.text({
571
702
  message: "Project name",
572
703
  validate: (value) => !value ? "Project name is required" : void 0
573
704
  }),
574
- slug: ({ results }) => p5.text({
705
+ slug: ({ results }) => p4.text({
575
706
  message: "Project slug",
576
707
  initialValue: slugify(String(results.name ?? "")),
577
708
  validate: (value) => !value ? "Project slug is required" : void 0
578
709
  }),
579
- environment: () => p5.select({
710
+ environment: () => p4.select({
580
711
  message: "Environment",
581
712
  options: [
582
713
  { value: "development", label: "development" },
@@ -587,7 +718,7 @@ async function projectsCreate() {
587
718
  },
588
719
  {
589
720
  onCancel: () => {
590
- p5.cancel("Project creation cancelled.");
721
+ p4.cancel("Project creation cancelled.");
591
722
  process.exit(0);
592
723
  }
593
724
  }
@@ -599,33 +730,49 @@ async function projectsCreate() {
599
730
  name: values.name,
600
731
  slug: values.slug,
601
732
  environment: values.environment
602
- }
733
+ },
734
+ profileName
735
+ });
736
+ config.setProfile(profileName, {
737
+ active_project_id: result.project.id,
738
+ active_project_name: result.project.name,
739
+ project_api_key: result.keys.server
603
740
  });
604
- config.setProjectAuth(result.project.id, result.keys.server, result.project.name);
741
+ config.setActiveProfile(profileName);
605
742
  console.log("");
606
- console.log(chalk7.green("Project created successfully."));
607
- console.log(chalk7.cyan("Project: ") + `${result.project.name} (${result.project.id})`);
608
- console.log(chalk7.cyan("Client key: ") + result.keys.client);
609
- console.log(chalk7.cyan("Server key: ") + result.keys.server);
743
+ console.log(chalk8.green("Project created successfully."));
744
+ console.log(chalk8.cyan("Project: ") + `${result.project.name} (${result.project.id})`);
745
+ console.log(chalk8.cyan("Client key: ") + result.keys.client);
746
+ console.log(chalk8.cyan("Server key: ") + result.keys.server);
610
747
  console.log("");
611
748
  }
612
749
 
613
750
  // src/commands/init.ts
614
751
  var version3 = getCliVersion();
615
- async function init() {
752
+ async function init(options = {}) {
616
753
  printBanner(version3);
617
- p6.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
618
- const cfg = config.get();
619
- if (!cfg.projectId) {
620
- await projectsCreate();
754
+ p5.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
755
+ const profileName = options.profile ?? config.getActiveProfile() ?? "default";
756
+ const profile = config.getProfile(profileName);
757
+ if (!profile) {
758
+ console.log("Run: npx @globio/cli login --profile " + profileName);
759
+ process.exit(1);
760
+ }
761
+ if (!profile.active_project_id) {
762
+ await projectsCreate({ profile: profileName });
621
763
  } else {
622
- await projectsUse(cfg.projectId);
764
+ await projectsUse(profile.active_project_id, { profile: profileName });
623
765
  }
624
766
  const values = await promptInit();
625
- const activeProjectKey = config.requireProjectApiKey();
626
- const activeProjectId = config.requireProject();
627
- if (!existsSync("globio.config.ts")) {
628
- writeFileSync(
767
+ const activeProfile = config.getProfile(profileName);
768
+ const activeProjectKey = activeProfile?.project_api_key;
769
+ const { projectId: activeProjectId } = config.requireProject(profileName);
770
+ if (!activeProjectKey) {
771
+ console.log("No project API key cached. Run: npx @globio/cli projects use " + activeProjectId);
772
+ process.exit(1);
773
+ }
774
+ if (!existsSync2("globio.config.ts")) {
775
+ writeFileSync2(
629
776
  "globio.config.ts",
630
777
  `import { Globio } from '@globio/sdk';
631
778
 
@@ -636,8 +783,8 @@ export const globio = new Globio({
636
783
  );
637
784
  printSuccess("Created globio.config.ts");
638
785
  }
639
- if (!existsSync(".env")) {
640
- writeFileSync(".env", `GLOBIO_API_KEY=${activeProjectKey}
786
+ if (!existsSync2(".env")) {
787
+ writeFileSync2(".env", `GLOBIO_API_KEY=${activeProjectKey}
641
788
  `);
642
789
  printSuccess("Created .env");
643
790
  }
@@ -646,19 +793,21 @@ export const globio = new Globio({
646
793
  printSuccess("Starting Firebase migration...");
647
794
  await migrateFirestore({
648
795
  from: values.serviceAccountPath,
649
- all: true
796
+ all: true,
797
+ profile: profileName
650
798
  });
651
799
  const serviceAccount = JSON.parse(
652
- readFileSync2(values.serviceAccountPath, "utf-8")
800
+ readFileSync3(values.serviceAccountPath, "utf-8")
653
801
  );
654
802
  await migrateFirebaseStorage({
655
803
  from: values.serviceAccountPath,
656
804
  bucket: `${serviceAccount.project_id}.appspot.com`,
657
- all: true
805
+ all: true,
806
+ profile: profileName
658
807
  });
659
808
  }
660
809
  console.log("");
661
- p6.outro(
810
+ p5.outro(
662
811
  orange("\u21D2\u21D2") + " Your project is ready.\n\n " + muted("Next steps:") + `
663
812
 
664
813
  npm install @globio/sdk
@@ -668,7 +817,7 @@ export const globio = new Globio({
668
817
  }
669
818
 
670
819
  // src/commands/services.ts
671
- import chalk8 from "chalk";
820
+ import chalk9 from "chalk";
672
821
  var ALL_SERVICES = [
673
822
  "id",
674
823
  "doc",
@@ -681,30 +830,36 @@ var ALL_SERVICES = [
681
830
  "brain",
682
831
  "code"
683
832
  ];
684
- async function servicesList() {
833
+ async function servicesList(options = {}) {
834
+ void options.profile;
835
+ void config;
685
836
  console.log("");
686
- console.log(chalk8.cyan("Available Globio services:"));
837
+ console.log(chalk9.cyan("Available Globio services:"));
687
838
  ALL_SERVICES.forEach((service) => {
688
- console.log(" " + chalk8.white(service));
839
+ console.log(" " + chalk9.white(service));
689
840
  });
690
841
  console.log("");
691
842
  console.log(
692
- chalk8.gray("Manage service access via console.globio.stanlink.online")
843
+ chalk9.gray("Manage service access via console.globio.stanlink.online")
693
844
  );
694
845
  console.log("");
695
846
  }
696
847
 
697
848
  // src/commands/functions.ts
698
- import chalk9 from "chalk";
849
+ import chalk10 from "chalk";
699
850
  import ora from "ora";
700
- import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
701
- async function functionsList() {
702
- const client = getClient();
851
+ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
852
+ function resolveProfileName3(profile) {
853
+ return profile ?? config.getActiveProfile() ?? "default";
854
+ }
855
+ async function functionsList(options = {}) {
856
+ const profileName = resolveProfileName3(options.profile);
857
+ const client = getClient(profileName);
703
858
  const spinner2 = ora("Fetching functions...").start();
704
859
  const result = await client.code.listFunctions();
705
860
  spinner2.stop();
706
861
  if (!result.success || !result.data.length) {
707
- console.log(chalk9.gray("No functions found."));
862
+ console.log(chalk10.gray("No functions found."));
708
863
  return;
709
864
  }
710
865
  console.log("");
@@ -718,10 +873,10 @@ async function functionsList() {
718
873
  });
719
874
  console.log("");
720
875
  }
721
- async function functionsCreate(slug) {
876
+ async function functionsCreate(slug, _options = {}) {
722
877
  const filename = `${slug}.js`;
723
- if (existsSync2(filename)) {
724
- console.log(chalk9.yellow(`${filename} already exists.`));
878
+ if (existsSync3(filename)) {
879
+ console.log(chalk10.yellow(`${filename} already exists.`));
725
880
  return;
726
881
  }
727
882
  const template = `/**
@@ -739,24 +894,25 @@ async function handler(input, globio) {
739
894
  };
740
895
  }
741
896
  `;
742
- writeFileSync2(filename, template);
743
- console.log(chalk9.green(`Created ${filename}`));
897
+ writeFileSync3(filename, template);
898
+ console.log(chalk10.green(`Created ${filename}`));
744
899
  console.log(
745
- chalk9.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
900
+ chalk10.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
746
901
  );
747
902
  }
748
903
  async function functionsDeploy(slug, options) {
749
904
  const filename = options.file ?? `${slug}.js`;
750
- if (!existsSync2(filename)) {
905
+ if (!existsSync3(filename)) {
751
906
  console.log(
752
- chalk9.red(
907
+ chalk10.red(
753
908
  `File not found: ${filename}. Create it with: npx @globio/cli functions create ${slug}`
754
909
  )
755
910
  );
756
911
  process.exit(1);
757
912
  }
758
- const code = readFileSync3(filename, "utf-8");
759
- const client = getClient();
913
+ const code = readFileSync4(filename, "utf-8");
914
+ const profileName = resolveProfileName3(options.profile);
915
+ const client = getClient(profileName);
760
916
  const spinner2 = ora(`Deploying ${slug}...`).start();
761
917
  const existing = await client.code.getFunction(slug);
762
918
  let result;
@@ -786,16 +942,17 @@ async function functionsInvoke(slug, options) {
786
942
  try {
787
943
  input = JSON.parse(options.input);
788
944
  } catch {
789
- console.error(chalk9.red("--input must be valid JSON"));
945
+ console.error(chalk10.red("--input must be valid JSON"));
790
946
  process.exit(1);
791
947
  }
792
948
  }
793
- const client = getClient();
949
+ const profileName = resolveProfileName3(options.profile);
950
+ const client = getClient(profileName);
794
951
  const spinner2 = ora(`Invoking ${slug}...`).start();
795
952
  const result = await client.code.invoke(slug, input);
796
953
  spinner2.stop();
797
954
  if (!result.success) {
798
- console.log(chalk9.red("Invocation failed"));
955
+ console.log(chalk10.red("Invocation failed"));
799
956
  console.error(result.error.message);
800
957
  return;
801
958
  }
@@ -807,12 +964,13 @@ Duration: ${result.data.duration_ms}ms`));
807
964
  }
808
965
  async function functionsLogs(slug, options) {
809
966
  const limit = options.limit ? parseInt(options.limit, 10) : 20;
810
- const client = getClient();
967
+ const profileName = resolveProfileName3(options.profile);
968
+ const client = getClient(profileName);
811
969
  const spinner2 = ora("Fetching invocations...").start();
812
970
  const result = await client.code.getInvocations(slug, limit);
813
971
  spinner2.stop();
814
972
  if (!result.success || !result.data.length) {
815
- console.log(chalk9.gray("No invocations yet."));
973
+ console.log(chalk10.gray("No invocations yet."));
816
974
  return;
817
975
  }
818
976
  console.log("");
@@ -820,13 +978,14 @@ async function functionsLogs(slug, options) {
820
978
  const status = inv.success ? "\x1B[38;2;244;140;6m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
821
979
  const date = new Date(inv.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
822
980
  console.log(
823
- ` ${status} ${chalk9.gray(date)} ${inv.duration_ms}ms ${chalk9.gray(`[${inv.trigger_type}]`)}`
981
+ ` ${status} ${chalk10.gray(date)} ${inv.duration_ms}ms ${chalk10.gray(`[${inv.trigger_type}]`)}`
824
982
  );
825
983
  });
826
984
  console.log("");
827
985
  }
828
- async function functionsDelete(slug) {
829
- const client = getClient();
986
+ async function functionsDelete(slug, options = {}) {
987
+ const profileName = resolveProfileName3(options.profile);
988
+ const client = getClient(profileName);
830
989
  const spinner2 = ora(`Deleting ${slug}...`).start();
831
990
  const result = await client.code.deleteFunction(slug);
832
991
  if (!result.success) {
@@ -836,8 +995,9 @@ async function functionsDelete(slug) {
836
995
  }
837
996
  spinner2.succeed(`Deleted ${slug}`);
838
997
  }
839
- async function functionsToggle(slug, active) {
840
- const client = getClient();
998
+ async function functionsToggle(slug, active, options = {}) {
999
+ const profileName = resolveProfileName3(options.profile);
1000
+ const client = getClient(profileName);
841
1001
  const spinner2 = ora(
842
1002
  `${active ? "Enabling" : "Disabling"} ${slug}...`
843
1003
  ).start();
@@ -856,28 +1016,43 @@ var program = new Command();
856
1016
  program.name("globio").description("The official Globio CLI").version(version4).addHelpText("beforeAll", () => {
857
1017
  printBanner(version4);
858
1018
  return "";
859
- });
860
- program.command("login").description("Log in to your Globio account").option("--token", "Use a personal access token").action(login);
861
- program.command("logout").description("Log out").action(logout);
862
- program.command("whoami").description("Show current account and project").action(whoami);
863
- program.command("init").description("Initialize a Globio project").action(init);
1019
+ }).addHelpText(
1020
+ "after",
1021
+ `
1022
+ Examples:
1023
+ $ globio login
1024
+ $ globio login --profile work
1025
+ $ globio use work
1026
+ $ globio projects list
1027
+ $ globio projects use proj_abc123
1028
+ $ globio functions deploy my-function
1029
+ $ globio migrate firestore --from ./key.json --all
1030
+
1031
+ Credentials are stored in ~/.globio/profiles/
1032
+ `
1033
+ );
1034
+ program.command("login").description("Log in to your Globio account").option("-p, --profile <name>", "Profile name", "default").option("--token", "Use a personal access token").action(login);
1035
+ program.command("logout").description("Log out").option("--profile <name>", "Use a specific profile").action(logout);
1036
+ program.command("whoami").description("Show current account and project").option("--profile <name>", "Use a specific profile").action(whoami);
1037
+ program.command("use <profile>").description("Switch active profile").action(useProfile);
1038
+ program.command("init").description("Initialize a Globio project").option("--profile <name>", "Use a specific profile").action(init);
864
1039
  var projects = program.command("projects").description("Manage projects");
865
- projects.command("list").description("List projects").action(projectsList);
866
- projects.command("create").description("Create a project").action(projectsCreate);
867
- projects.command("use <projectId>").description("Set active project").action(projectsUse);
868
- program.command("services").description("List available Globio services").action(servicesList);
1040
+ projects.command("list").description("List projects").option("--profile <name>", "Use a specific profile").action(projectsList);
1041
+ projects.command("create").description("Create a project").option("--profile <name>", "Use a specific profile").action(projectsCreate);
1042
+ projects.command("use <projectId>").description("Set active project").option("--profile <name>", "Use a specific profile").action(projectsUse);
1043
+ program.command("services").description("List available Globio services").option("--profile <name>", "Use a specific profile").action(servicesList);
869
1044
  var functions = program.command("functions").alias("fn").description("Manage GlobalCode edge functions");
870
- functions.command("list").description("List all functions").action(functionsList);
871
- functions.command("create <slug>").description("Scaffold a new function file locally").action(functionsCreate);
872
- functions.command("deploy <slug>").description("Deploy a function to GlobalCode").option("-f, --file <path>", "Path to function file").option("-n, --name <name>", "Display name").action(functionsDeploy);
873
- functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").action(functionsInvoke);
874
- functions.command("logs <slug>").description("Show invocation history").option("-l, --limit <n>", "Number of entries", "20").action(functionsLogs);
875
- functions.command("delete <slug>").description("Delete a function").action(functionsDelete);
876
- functions.command("enable <slug>").description("Enable a function").action((slug) => functionsToggle(slug, true));
877
- functions.command("disable <slug>").description("Disable a function").action((slug) => functionsToggle(slug, false));
1045
+ functions.command("list").description("List all functions").option("--profile <name>", "Use a specific profile").action(functionsList);
1046
+ functions.command("create <slug>").description("Scaffold a new function file locally").option("--profile <name>", "Use a specific profile").action(functionsCreate);
1047
+ functions.command("deploy <slug>").description("Deploy a function to GlobalCode").option("-f, --file <path>", "Path to function file").option("-n, --name <name>", "Display name").option("--profile <name>", "Use a specific profile").action(functionsDeploy);
1048
+ functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").option("--profile <name>", "Use a specific profile").action(functionsInvoke);
1049
+ functions.command("logs <slug>").description("Show invocation history").option("-l, --limit <n>", "Number of entries", "20").option("--profile <name>", "Use a specific profile").action(functionsLogs);
1050
+ functions.command("delete <slug>").description("Delete a function").option("--profile <name>", "Use a specific profile").action(functionsDelete);
1051
+ functions.command("enable <slug>").description("Enable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, true, options));
1052
+ functions.command("disable <slug>").description("Disable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, false, options));
878
1053
  var migrate = program.command("migrate").description("Migrate from Firebase to Globio");
879
- migrate.command("firestore").description("Migrate Firestore collections to GlobalDoc").requiredOption("--from <path>", "Path to Firebase service account JSON").option("--collection <name>", "Migrate a specific collection").option("--all", "Migrate all collections").action(migrateFirestore);
880
- migrate.command("firebase-storage").description("Migrate Firebase Storage to GlobalVault").requiredOption("--from <path>", "Path to Firebase service account JSON").requiredOption("--bucket <name>", "Firebase Storage bucket").option("--folder <path>", "Migrate a specific folder").option("--all", "Migrate all files").action(migrateFirebaseStorage);
1054
+ migrate.command("firestore").description("Migrate Firestore collections to GlobalDoc").requiredOption("--from <path>", "Path to Firebase service account JSON").option("--collection <name>", "Migrate a specific collection").option("--all", "Migrate all collections").option("--profile <name>", "Use a specific profile").action(migrateFirestore);
1055
+ migrate.command("firebase-storage").description("Migrate Firebase Storage to GlobalVault").requiredOption("--from <path>", "Path to Firebase service account JSON").requiredOption("--bucket <name>", "Firebase Storage bucket").option("--folder <path>", "Migrate a specific folder").option("--all", "Migrate all files").option("--profile <name>", "Use a specific profile").action(migrateFirebaseStorage);
881
1056
  async function main() {
882
1057
  if (process.argv.length <= 2) {
883
1058
  program.help();