@globio/cli 0.1.3 → 0.1.5

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,23 @@ 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
+ function warnOnDuplicateAccount(accountEmail, targetProfileName) {
202
+ const allProfiles = config.listProfiles();
203
+ const duplicate = allProfiles.find((name) => {
204
+ const profile = config.getProfile(name);
205
+ return profile?.account_email === accountEmail && name !== targetProfileName;
206
+ });
207
+ if (!duplicate) return;
208
+ console.log("");
209
+ console.log(
210
+ chalk2.yellow(" \u26A0 ") + chalk2.white(accountEmail) + chalk2.gray(" is already logged in under profile ") + orange(`"${duplicate}"`) + chalk2.gray(".")
211
+ );
212
+ console.log("");
213
+ }
214
+ async function runTokenLogin(profileName) {
215
+ const hadProfiles = config.listProfiles().length > 0;
170
216
  const token = await p.text({
171
217
  message: "Paste your personal access token",
172
218
  placeholder: "glo_pat_...",
@@ -184,17 +230,29 @@ async function runTokenLogin() {
184
230
  spinner2.start("Validating personal access token...");
185
231
  try {
186
232
  const account = await savePat(token);
233
+ warnOnDuplicateAccount(account.email, profileName);
234
+ config.setProfile(profileName, {
235
+ pat: token,
236
+ account_email: account.email,
237
+ account_name: account.display_name ?? account.email,
238
+ created_at: Date.now()
239
+ });
240
+ if (profileName === "default" || !hadProfiles) {
241
+ config.setActiveProfile(profileName);
242
+ }
187
243
  spinner2.stop("Token validated.");
188
- p.outro(`Logged in as ${account.email}`);
244
+ p.outro(`Logged in as ${account.email}
245
+ Profile: ${profileName}`);
189
246
  } catch (error) {
190
247
  spinner2.stop("Validation failed.");
191
248
  p.outro(chalk2.red(error instanceof Error ? error.message : "Could not validate token"));
192
249
  process.exit(1);
193
250
  }
194
251
  }
195
- async function runBrowserLogin() {
252
+ async function runBrowserLogin(profileName) {
196
253
  const state = crypto.randomUUID();
197
254
  const spinner2 = p.spinner();
255
+ const hadProfiles = config.listProfiles().length > 0;
198
256
  await manageRequest("/cli-auth/request", {
199
257
  method: "POST",
200
258
  body: { state }
@@ -220,13 +278,19 @@ async function runBrowserLogin() {
220
278
  method: "POST",
221
279
  body: { code: status.code }
222
280
  });
223
- config.set({
281
+ warnOnDuplicateAccount(exchange.account.email, profileName);
282
+ config.setProfile(profileName, {
224
283
  pat: exchange.token,
225
- accountEmail: exchange.account.email,
226
- accountName: exchange.account.display_name ?? exchange.account.email
284
+ account_email: exchange.account.email,
285
+ account_name: exchange.account.display_name ?? exchange.account.email,
286
+ created_at: Date.now()
227
287
  });
288
+ if (profileName === "default" || !hadProfiles) {
289
+ config.setActiveProfile(profileName);
290
+ }
228
291
  spinner2.stop("Browser approval received.");
229
- p.outro(`Logged in as ${exchange.account.email}`);
292
+ p.outro(`Logged in as ${exchange.account.email}
293
+ Profile: ${profileName}`);
230
294
  return;
231
295
  }
232
296
  } catch {
@@ -239,8 +303,20 @@ async function runBrowserLogin() {
239
303
  }
240
304
  async function login(options = {}) {
241
305
  printBanner(version);
306
+ const profileName = options.profile ?? "default";
307
+ const existing = config.getProfile(profileName);
308
+ if (existing) {
309
+ const proceed = await p.confirm({
310
+ message: `Already logged in as ${existing.account_email} on profile "${profileName}". Replace?`,
311
+ initialValue: false
312
+ });
313
+ if (p.isCancel(proceed) || !proceed) {
314
+ p.outro("Login cancelled.");
315
+ return;
316
+ }
317
+ }
242
318
  if (options.token) {
243
- await runTokenLogin();
319
+ await runTokenLogin(profileName);
244
320
  return;
245
321
  }
246
322
  const choice = await p.select({
@@ -255,11 +331,11 @@ async function login(options = {}) {
255
331
  process.exit(0);
256
332
  }
257
333
  if (choice === "token") {
258
- await runTokenLogin();
334
+ await runTokenLogin(profileName);
259
335
  return;
260
336
  }
261
337
  try {
262
- await runBrowserLogin();
338
+ await runBrowserLogin(profileName);
263
339
  } catch (error) {
264
340
  p.outro(chalk2.red(error instanceof Error ? error.message : "Could not connect to Globio."));
265
341
  process.exit(1);
@@ -267,51 +343,98 @@ async function login(options = {}) {
267
343
  }
268
344
 
269
345
  // src/auth/logout.ts
270
- import * as p2 from "@clack/prompts";
271
346
  import chalk3 from "chalk";
272
- async function logout() {
273
- config.clear();
274
- p2.outro(chalk3.green("Logged out."));
347
+ async function logout(options = {}) {
348
+ const activeProfile = config.getActiveProfile();
349
+ const profileName = options.profile ?? activeProfile;
350
+ const profile = profileName ? config.getProfile(profileName) : null;
351
+ if (!profileName || !profile) {
352
+ console.log(chalk3.yellow(`No active session on profile "${profileName || "default"}".`));
353
+ return;
354
+ }
355
+ config.deleteProfile(profileName);
356
+ if (profileName === activeProfile) {
357
+ const remaining = config.listProfiles();
358
+ if (remaining.length > 0) {
359
+ config.setActiveProfile(remaining[0]);
360
+ console.log(chalk3.green(`Logged out. Switched to profile: ${remaining[0]}`));
361
+ return;
362
+ }
363
+ config.setActiveProfile("");
364
+ console.log(chalk3.green("Logged out."));
365
+ return;
366
+ }
367
+ console.log(chalk3.green(`Logged out profile: ${profileName}`));
275
368
  }
276
369
 
277
- // src/auth/whoami.ts
370
+ // src/auth/useProfile.ts
278
371
  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."));
372
+ async function useProfile(profileName) {
373
+ const profile = config.getProfile(profileName);
374
+ if (!profile) {
375
+ console.log(
376
+ chalk4.red(
377
+ `Profile "${profileName}" not found. Run: globio login --profile ${profileName}`
378
+ )
379
+ );
380
+ process.exit(1);
381
+ }
382
+ config.setActiveProfile(profileName);
383
+ console.log(
384
+ chalk4.green("Switched to profile: ") + orange(profileName) + ` (${profile.account_email})`
385
+ );
386
+ }
387
+
388
+ // src/auth/whoami.ts
389
+ import chalk5 from "chalk";
390
+ async function whoami(options = {}) {
391
+ const profileName = options.profile ?? config.getActiveProfile() ?? "default";
392
+ const profile = config.getProfile(profileName);
393
+ if (!profile) {
394
+ console.log(chalk5.red("Not logged in. Run: globio login"));
283
395
  return;
284
396
  }
397
+ const allProfiles = config.listProfiles();
398
+ const activeProfile = config.getActiveProfile();
285
399
  console.log("");
286
- console.log(chalk4.cyan("Account: ") + (cfg.accountEmail ?? "unknown"));
287
- console.log(chalk4.cyan("Name: ") + (cfg.accountName ?? "unknown"));
288
400
  console.log(
289
- chalk4.cyan("Project: ") + (cfg.projectId ? `${cfg.projectName ?? "unnamed"} (${cfg.projectId})` : "none")
401
+ muted("Profile: ") + orange(profileName) + (profileName === activeProfile ? muted(" (active)") : "")
290
402
  );
403
+ console.log(muted("Account: ") + profile.account_email);
404
+ console.log(muted("Name: ") + (profile.account_name || "\u2014"));
405
+ console.log(
406
+ 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>"))
407
+ );
408
+ if (allProfiles.length > 1) {
409
+ console.log("");
410
+ console.log(
411
+ muted("Other profiles: ") + allProfiles.filter((name) => name !== profileName).join(", ")
412
+ );
413
+ }
291
414
  console.log("");
292
415
  }
293
416
 
294
417
  // src/commands/init.ts
295
- import * as p6 from "@clack/prompts";
296
- import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
418
+ import * as p5 from "@clack/prompts";
419
+ import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
297
420
 
298
421
  // src/prompts/init.ts
299
- import * as p3 from "@clack/prompts";
422
+ import * as p2 from "@clack/prompts";
300
423
  async function promptInit() {
301
- return p3.group(
424
+ return p2.group(
302
425
  {
303
- migrateFromFirebase: () => p3.confirm({
426
+ migrateFromFirebase: () => p2.confirm({
304
427
  message: "Migrating from Firebase?",
305
428
  initialValue: false
306
429
  }),
307
- serviceAccountPath: ({ results }) => results.migrateFromFirebase ? p3.text({
430
+ serviceAccountPath: ({ results }) => results.migrateFromFirebase ? p2.text({
308
431
  message: "Path to Firebase service account JSON",
309
432
  placeholder: "./serviceAccountKey.json"
310
433
  }) : Promise.resolve(void 0)
311
434
  },
312
435
  {
313
436
  onCancel: () => {
314
- p3.cancel("Cancelled.");
437
+ p2.cancel("Cancelled.");
315
438
  process.exit(0);
316
439
  }
317
440
  }
@@ -319,15 +442,15 @@ async function promptInit() {
319
442
  }
320
443
 
321
444
  // src/commands/migrate.ts
322
- import * as p4 from "@clack/prompts";
323
- import chalk6 from "chalk";
445
+ import * as p3 from "@clack/prompts";
446
+ import chalk7 from "chalk";
324
447
  import { basename } from "path";
325
448
 
326
449
  // src/lib/firebase.ts
327
450
  async function initFirebase(serviceAccountPath) {
328
451
  const admin = await import("firebase-admin");
329
- const { readFileSync: readFileSync4 } = await import("fs");
330
- const serviceAccount = JSON.parse(readFileSync4(serviceAccountPath, "utf-8"));
452
+ const { readFileSync: readFileSync5 } = await import("fs");
453
+ const serviceAccount = JSON.parse(readFileSync5(serviceAccountPath, "utf-8"));
331
454
  if (!admin.default.apps.length) {
332
455
  admin.default.initializeApp({
333
456
  credential: admin.default.credential.cert(serviceAccount),
@@ -342,12 +465,12 @@ async function initFirebase(serviceAccountPath) {
342
465
  }
343
466
 
344
467
  // src/lib/progress.ts
345
- import chalk5 from "chalk";
468
+ import chalk6 from "chalk";
346
469
  import cliProgress from "cli-progress";
347
470
  function createProgressBar(label) {
348
471
  const bar = new cliProgress.SingleBar(
349
472
  {
350
- format: chalk5.cyan(label) + " [{bar}] {percentage}% | {value}/{total}",
473
+ format: chalk6.cyan(label) + " [{bar}] {percentage}% | {value}/{total}",
351
474
  barCompleteChar: "\u2588",
352
475
  barIncompleteChar: "\u2591",
353
476
  hideCursor: true
@@ -359,31 +482,38 @@ function createProgressBar(label) {
359
482
 
360
483
  // src/lib/sdk.ts
361
484
  import { Globio } from "@globio/sdk";
362
- function getClient() {
363
- const apiKey = config.requireProjectApiKey();
485
+ function getClient(profileName) {
486
+ const { pat } = config.requireAuth(profileName);
487
+ const { projectId } = config.requireProject(profileName);
488
+ const profile = config.getProfile(profileName);
489
+ const apiKey = profile?.project_api_key ?? pat;
490
+ void projectId;
364
491
  return new Globio({ apiKey });
365
492
  }
366
493
 
367
494
  // src/commands/migrate.ts
368
495
  var version2 = getCliVersion();
496
+ function resolveProfileName(profile) {
497
+ return profile ?? config.getActiveProfile() ?? "default";
498
+ }
369
499
  async function migrateFirestore(options) {
370
500
  printBanner(version2);
371
- p4.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
501
+ p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
372
502
  const { firestore } = await initFirebase(options.from);
373
- const client = getClient();
503
+ const client = getClient(resolveProfileName(options.profile));
374
504
  let collections = [];
375
505
  if (options.all) {
376
506
  const snapshot = await firestore.listCollections();
377
507
  collections = snapshot.map((collection) => collection.id);
378
508
  console.log(
379
- chalk6.cyan(
509
+ chalk7.cyan(
380
510
  `Found ${collections.length} collections: ${collections.join(", ")}`
381
511
  )
382
512
  );
383
513
  } else if (options.collection) {
384
514
  collections = [options.collection];
385
515
  } else {
386
- console.log(chalk6.red("Specify --collection <name> or --all"));
516
+ console.log(chalk7.red("Specify --collection <name> or --all"));
387
517
  process.exit(1);
388
518
  }
389
519
  const results = {};
@@ -428,32 +558,32 @@ async function migrateFirestore(options) {
428
558
  }
429
559
  bar.stop();
430
560
  console.log(
431
- chalk6.green(` \u2713 ${results[collectionId].success} documents migrated`)
561
+ chalk7.green(` \u2713 ${results[collectionId].success} documents migrated`)
432
562
  );
433
563
  if (results[collectionId].failed > 0) {
434
- console.log(chalk6.red(` \u2717 ${results[collectionId].failed} failed`));
564
+ console.log(chalk7.red(` \u2717 ${results[collectionId].failed} failed`));
435
565
  console.log(
436
- chalk6.gray(
566
+ chalk7.gray(
437
567
  " Failed IDs: " + results[collectionId].failedIds.slice(0, 10).join(", ") + (results[collectionId].failedIds.length > 10 ? "..." : "")
438
568
  )
439
569
  );
440
570
  }
441
571
  }
442
572
  console.log("");
443
- p4.outro(
573
+ p3.outro(
444
574
  orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
445
575
  );
446
576
  }
447
577
  async function migrateFirebaseStorage(options) {
448
578
  printBanner(version2);
449
- p4.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
579
+ p3.intro(gold("\u21D2\u21D2") + " Firebase \u2192 Globio Migration");
450
580
  const { storage } = await initFirebase(options.from);
451
- const client = getClient();
581
+ const client = getClient(resolveProfileName(options.profile));
452
582
  const bucketName = options.bucket.replace(/^gs:\/\//, "");
453
583
  const bucket = storage.bucket(bucketName);
454
584
  const prefix = options.folder ? options.folder.replace(/^\//, "") : "";
455
585
  const [files] = await bucket.getFiles(prefix ? { prefix } : {});
456
- console.log(chalk6.cyan(`Found ${files.length} files to migrate`));
586
+ console.log(chalk7.cyan(`Found ${files.length} files to migrate`));
457
587
  const bar = createProgressBar("Storage");
458
588
  bar.start(files.length, 0);
459
589
  let success = 0;
@@ -481,39 +611,43 @@ async function migrateFirebaseStorage(options) {
481
611
  }
482
612
  bar.stop();
483
613
  console.log("");
484
- console.log(chalk6.green(` \u2713 ${success} files migrated`));
614
+ console.log(chalk7.green(` \u2713 ${success} files migrated`));
485
615
  if (failed > 0) {
486
- console.log(chalk6.red(` \u2717 ${failed} failed`));
616
+ console.log(chalk7.red(` \u2717 ${failed} failed`));
487
617
  }
488
- p4.outro(
618
+ p3.outro(
489
619
  orange("\u2713") + " Migration complete.\n\n " + muted("Your Firebase data is intact.") + "\n " + muted("Delete it manually when ready.")
490
620
  );
491
621
  }
492
622
 
493
623
  // src/commands/projects.ts
494
- import * as p5 from "@clack/prompts";
495
- import chalk7 from "chalk";
624
+ import * as p4 from "@clack/prompts";
625
+ import chalk8 from "chalk";
496
626
  function slugify(value) {
497
627
  return value.toLowerCase().trim().replace(/[^a-z0-9\\s-]/g, "").replace(/\\s+/g, "-").replace(/-+/g, "-");
498
628
  }
499
- async function ensureProjectKey(projectId) {
500
- const existingKey = config.getProjectApiKey(projectId);
501
- if (existingKey) return existingKey;
629
+ function resolveProfileName2(profileName) {
630
+ return profileName ?? config.getActiveProfile() ?? "default";
631
+ }
632
+ async function createServerKey(projectId, profileName) {
502
633
  const created = await manageRequest(`/projects/${projectId}/keys`, {
503
634
  method: "POST",
504
635
  body: {
505
636
  name: "CLI server key",
506
637
  scope: "server"
507
- }
638
+ },
639
+ profileName
508
640
  });
509
641
  if (!created.token) {
510
642
  throw new Error("Management API did not return a project API key");
511
643
  }
512
644
  return created.token;
513
645
  }
514
- async function projectsList() {
515
- const projects2 = await manageRequest("/projects");
516
- const activeProjectId = config.get().projectId;
646
+ async function projectsList(options = {}) {
647
+ const profileName = resolveProfileName2(options.profile);
648
+ config.requireAuth(profileName);
649
+ const projects2 = await manageRequest("/projects", { profileName });
650
+ const activeProjectId = config.getProfile(profileName)?.active_project_id;
517
651
  const grouped = /* @__PURE__ */ new Map();
518
652
  for (const project of projects2) {
519
653
  const list = grouped.get(project.org_name) ?? [];
@@ -522,38 +656,50 @@ async function projectsList() {
522
656
  }
523
657
  console.log("");
524
658
  if (!projects2.length) {
525
- console.log(chalk7.gray("No projects found."));
659
+ console.log(chalk8.gray("No projects found."));
526
660
  console.log("");
527
661
  return;
528
662
  }
529
663
  for (const [orgName, orgProjects] of grouped.entries()) {
530
- console.log(chalk7.cyan(`org: ${orgName}`));
664
+ console.log(chalk8.cyan(`org: ${orgName}`));
531
665
  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}`);
666
+ const marker = project.id === activeProjectId ? chalk8.green("\u25CF") : chalk8.gray("\u25CB");
667
+ const active = project.id === activeProjectId ? chalk8.green(" (active)") : "";
668
+ console.log(` ${marker} ${project.slug.padEnd(22)} ${chalk8.gray(project.id)}${active}`);
535
669
  }
536
670
  console.log("");
537
671
  }
538
672
  }
539
- async function projectsUse(projectId) {
540
- const projects2 = await manageRequest("/projects");
673
+ async function projectsUse(projectId, options = {}) {
674
+ const profileName = resolveProfileName2(options.profile);
675
+ config.requireAuth(profileName);
676
+ const projects2 = await manageRequest("/projects", { profileName });
541
677
  const project = projects2.find((item) => item.id === projectId);
542
678
  if (!project) {
543
- console.log(chalk7.red(`Project not found: ${projectId}`));
679
+ console.log(chalk8.red(`Project not found: ${projectId}`));
544
680
  process.exit(1);
545
681
  }
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})`));
682
+ await manageRequest(`/projects/${projectId}/keys`, { profileName });
683
+ const apiKey = await createServerKey(projectId, profileName);
684
+ config.setProfile(profileName, {
685
+ active_project_id: project.id,
686
+ active_project_name: project.name,
687
+ project_api_key: apiKey
688
+ });
689
+ config.setActiveProfile(profileName);
690
+ console.log(
691
+ chalk8.green("Active project set to: ") + chalk8.cyan(`${project.name} (${project.id})`)
692
+ );
549
693
  }
550
- async function projectsCreate() {
551
- const orgs = await manageRequest("/orgs");
694
+ async function projectsCreate(options = {}) {
695
+ const profileName = resolveProfileName2(options.profile);
696
+ config.requireAuth(profileName);
697
+ const orgs = await manageRequest("/orgs", { profileName });
552
698
  if (!orgs.length) {
553
- console.log(chalk7.red("No organizations found. Create one in the console first."));
699
+ console.log(chalk8.red("No organizations found. Create one in the console first."));
554
700
  process.exit(1);
555
701
  }
556
- const orgId = await p5.select({
702
+ const orgId = await p4.select({
557
703
  message: "Select an organization",
558
704
  options: orgs.map((org) => ({
559
705
  value: org.id,
@@ -561,22 +707,22 @@ async function projectsCreate() {
561
707
  hint: org.role
562
708
  }))
563
709
  });
564
- if (p5.isCancel(orgId)) {
565
- p5.cancel("Project creation cancelled.");
710
+ if (p4.isCancel(orgId)) {
711
+ p4.cancel("Project creation cancelled.");
566
712
  process.exit(0);
567
713
  }
568
- const values = await p5.group(
714
+ const values = await p4.group(
569
715
  {
570
- name: () => p5.text({
716
+ name: () => p4.text({
571
717
  message: "Project name",
572
718
  validate: (value) => !value ? "Project name is required" : void 0
573
719
  }),
574
- slug: ({ results }) => p5.text({
720
+ slug: ({ results }) => p4.text({
575
721
  message: "Project slug",
576
722
  initialValue: slugify(String(results.name ?? "")),
577
723
  validate: (value) => !value ? "Project slug is required" : void 0
578
724
  }),
579
- environment: () => p5.select({
725
+ environment: () => p4.select({
580
726
  message: "Environment",
581
727
  options: [
582
728
  { value: "development", label: "development" },
@@ -587,7 +733,7 @@ async function projectsCreate() {
587
733
  },
588
734
  {
589
735
  onCancel: () => {
590
- p5.cancel("Project creation cancelled.");
736
+ p4.cancel("Project creation cancelled.");
591
737
  process.exit(0);
592
738
  }
593
739
  }
@@ -599,33 +745,49 @@ async function projectsCreate() {
599
745
  name: values.name,
600
746
  slug: values.slug,
601
747
  environment: values.environment
602
- }
748
+ },
749
+ profileName
603
750
  });
604
- config.setProjectAuth(result.project.id, result.keys.server, result.project.name);
751
+ config.setProfile(profileName, {
752
+ active_project_id: result.project.id,
753
+ active_project_name: result.project.name,
754
+ project_api_key: result.keys.server
755
+ });
756
+ config.setActiveProfile(profileName);
605
757
  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);
758
+ console.log(chalk8.green("Project created successfully."));
759
+ console.log(chalk8.cyan("Project: ") + `${result.project.name} (${result.project.id})`);
760
+ console.log(chalk8.cyan("Client key: ") + result.keys.client);
761
+ console.log(chalk8.cyan("Server key: ") + result.keys.server);
610
762
  console.log("");
611
763
  }
612
764
 
613
765
  // src/commands/init.ts
614
766
  var version3 = getCliVersion();
615
- async function init() {
767
+ async function init(options = {}) {
616
768
  printBanner(version3);
617
- p6.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
618
- const cfg = config.get();
619
- if (!cfg.projectId) {
620
- await projectsCreate();
769
+ p5.intro(orange("\u21D2\u21D2") + " Initialize your Globio project");
770
+ const profileName = options.profile ?? config.getActiveProfile() ?? "default";
771
+ const profile = config.getProfile(profileName);
772
+ if (!profile) {
773
+ console.log("Run: npx @globio/cli login --profile " + profileName);
774
+ process.exit(1);
775
+ }
776
+ if (!profile.active_project_id) {
777
+ await projectsCreate({ profile: profileName });
621
778
  } else {
622
- await projectsUse(cfg.projectId);
779
+ await projectsUse(profile.active_project_id, { profile: profileName });
623
780
  }
624
781
  const values = await promptInit();
625
- const activeProjectKey = config.requireProjectApiKey();
626
- const activeProjectId = config.requireProject();
627
- if (!existsSync("globio.config.ts")) {
628
- writeFileSync(
782
+ const activeProfile = config.getProfile(profileName);
783
+ const activeProjectKey = activeProfile?.project_api_key;
784
+ const { projectId: activeProjectId } = config.requireProject(profileName);
785
+ if (!activeProjectKey) {
786
+ console.log("No project API key cached. Run: npx @globio/cli projects use " + activeProjectId);
787
+ process.exit(1);
788
+ }
789
+ if (!existsSync2("globio.config.ts")) {
790
+ writeFileSync2(
629
791
  "globio.config.ts",
630
792
  `import { Globio } from '@globio/sdk';
631
793
 
@@ -636,8 +798,8 @@ export const globio = new Globio({
636
798
  );
637
799
  printSuccess("Created globio.config.ts");
638
800
  }
639
- if (!existsSync(".env")) {
640
- writeFileSync(".env", `GLOBIO_API_KEY=${activeProjectKey}
801
+ if (!existsSync2(".env")) {
802
+ writeFileSync2(".env", `GLOBIO_API_KEY=${activeProjectKey}
641
803
  `);
642
804
  printSuccess("Created .env");
643
805
  }
@@ -646,19 +808,21 @@ export const globio = new Globio({
646
808
  printSuccess("Starting Firebase migration...");
647
809
  await migrateFirestore({
648
810
  from: values.serviceAccountPath,
649
- all: true
811
+ all: true,
812
+ profile: profileName
650
813
  });
651
814
  const serviceAccount = JSON.parse(
652
- readFileSync2(values.serviceAccountPath, "utf-8")
815
+ readFileSync3(values.serviceAccountPath, "utf-8")
653
816
  );
654
817
  await migrateFirebaseStorage({
655
818
  from: values.serviceAccountPath,
656
819
  bucket: `${serviceAccount.project_id}.appspot.com`,
657
- all: true
820
+ all: true,
821
+ profile: profileName
658
822
  });
659
823
  }
660
824
  console.log("");
661
- p6.outro(
825
+ p5.outro(
662
826
  orange("\u21D2\u21D2") + " Your project is ready.\n\n " + muted("Next steps:") + `
663
827
 
664
828
  npm install @globio/sdk
@@ -667,8 +831,30 @@ export const globio = new Globio({
667
831
  );
668
832
  }
669
833
 
834
+ // src/commands/profiles.ts
835
+ import chalk9 from "chalk";
836
+ async function profilesList() {
837
+ const profiles2 = config.listProfiles();
838
+ const active = config.getActiveProfile();
839
+ if (!profiles2.length) {
840
+ console.log(chalk9.gray("No profiles found. Run: globio login"));
841
+ return;
842
+ }
843
+ console.log("");
844
+ for (const name of profiles2) {
845
+ const data = config.getProfile(name);
846
+ const isActive = name === active;
847
+ const bullet = isActive ? orange("\u25CF") : chalk9.gray("\u25CB");
848
+ const label = isActive ? orange(name) : chalk9.white(name);
849
+ const email = data?.account_email ? muted(data.account_email) : chalk9.gray("unknown");
850
+ const tag = isActive ? muted(" (active)") : "";
851
+ console.log(` ${bullet} ${label} ${email}${tag}`);
852
+ }
853
+ console.log("");
854
+ }
855
+
670
856
  // src/commands/services.ts
671
- import chalk8 from "chalk";
857
+ import chalk10 from "chalk";
672
858
  var ALL_SERVICES = [
673
859
  "id",
674
860
  "doc",
@@ -681,30 +867,36 @@ var ALL_SERVICES = [
681
867
  "brain",
682
868
  "code"
683
869
  ];
684
- async function servicesList() {
870
+ async function servicesList(options = {}) {
871
+ void options.profile;
872
+ void config;
685
873
  console.log("");
686
- console.log(chalk8.cyan("Available Globio services:"));
874
+ console.log(chalk10.cyan("Available Globio services:"));
687
875
  ALL_SERVICES.forEach((service) => {
688
- console.log(" " + chalk8.white(service));
876
+ console.log(" " + chalk10.white(service));
689
877
  });
690
878
  console.log("");
691
879
  console.log(
692
- chalk8.gray("Manage service access via console.globio.stanlink.online")
880
+ chalk10.gray("Manage service access via console.globio.stanlink.online")
693
881
  );
694
882
  console.log("");
695
883
  }
696
884
 
697
885
  // src/commands/functions.ts
698
- import chalk9 from "chalk";
886
+ import chalk11 from "chalk";
699
887
  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();
888
+ import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
889
+ function resolveProfileName3(profile) {
890
+ return profile ?? config.getActiveProfile() ?? "default";
891
+ }
892
+ async function functionsList(options = {}) {
893
+ const profileName = resolveProfileName3(options.profile);
894
+ const client = getClient(profileName);
703
895
  const spinner2 = ora("Fetching functions...").start();
704
896
  const result = await client.code.listFunctions();
705
897
  spinner2.stop();
706
898
  if (!result.success || !result.data.length) {
707
- console.log(chalk9.gray("No functions found."));
899
+ console.log(chalk11.gray("No functions found."));
708
900
  return;
709
901
  }
710
902
  console.log("");
@@ -718,10 +910,10 @@ async function functionsList() {
718
910
  });
719
911
  console.log("");
720
912
  }
721
- async function functionsCreate(slug) {
913
+ async function functionsCreate(slug, _options = {}) {
722
914
  const filename = `${slug}.js`;
723
- if (existsSync2(filename)) {
724
- console.log(chalk9.yellow(`${filename} already exists.`));
915
+ if (existsSync3(filename)) {
916
+ console.log(chalk11.yellow(`${filename} already exists.`));
725
917
  return;
726
918
  }
727
919
  const template = `/**
@@ -739,24 +931,25 @@ async function handler(input, globio) {
739
931
  };
740
932
  }
741
933
  `;
742
- writeFileSync2(filename, template);
743
- console.log(chalk9.green(`Created ${filename}`));
934
+ writeFileSync3(filename, template);
935
+ console.log(chalk11.green(`Created ${filename}`));
744
936
  console.log(
745
- chalk9.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
937
+ chalk11.gray(`Deploy with: npx @globio/cli functions deploy ${slug}`)
746
938
  );
747
939
  }
748
940
  async function functionsDeploy(slug, options) {
749
941
  const filename = options.file ?? `${slug}.js`;
750
- if (!existsSync2(filename)) {
942
+ if (!existsSync3(filename)) {
751
943
  console.log(
752
- chalk9.red(
944
+ chalk11.red(
753
945
  `File not found: ${filename}. Create it with: npx @globio/cli functions create ${slug}`
754
946
  )
755
947
  );
756
948
  process.exit(1);
757
949
  }
758
- const code = readFileSync3(filename, "utf-8");
759
- const client = getClient();
950
+ const code = readFileSync4(filename, "utf-8");
951
+ const profileName = resolveProfileName3(options.profile);
952
+ const client = getClient(profileName);
760
953
  const spinner2 = ora(`Deploying ${slug}...`).start();
761
954
  const existing = await client.code.getFunction(slug);
762
955
  let result;
@@ -786,16 +979,17 @@ async function functionsInvoke(slug, options) {
786
979
  try {
787
980
  input = JSON.parse(options.input);
788
981
  } catch {
789
- console.error(chalk9.red("--input must be valid JSON"));
982
+ console.error(chalk11.red("--input must be valid JSON"));
790
983
  process.exit(1);
791
984
  }
792
985
  }
793
- const client = getClient();
986
+ const profileName = resolveProfileName3(options.profile);
987
+ const client = getClient(profileName);
794
988
  const spinner2 = ora(`Invoking ${slug}...`).start();
795
989
  const result = await client.code.invoke(slug, input);
796
990
  spinner2.stop();
797
991
  if (!result.success) {
798
- console.log(chalk9.red("Invocation failed"));
992
+ console.log(chalk11.red("Invocation failed"));
799
993
  console.error(result.error.message);
800
994
  return;
801
995
  }
@@ -807,12 +1001,13 @@ Duration: ${result.data.duration_ms}ms`));
807
1001
  }
808
1002
  async function functionsLogs(slug, options) {
809
1003
  const limit = options.limit ? parseInt(options.limit, 10) : 20;
810
- const client = getClient();
1004
+ const profileName = resolveProfileName3(options.profile);
1005
+ const client = getClient(profileName);
811
1006
  const spinner2 = ora("Fetching invocations...").start();
812
1007
  const result = await client.code.getInvocations(slug, limit);
813
1008
  spinner2.stop();
814
1009
  if (!result.success || !result.data.length) {
815
- console.log(chalk9.gray("No invocations yet."));
1010
+ console.log(chalk11.gray("No invocations yet."));
816
1011
  return;
817
1012
  }
818
1013
  console.log("");
@@ -820,13 +1015,14 @@ async function functionsLogs(slug, options) {
820
1015
  const status = inv.success ? "\x1B[38;2;244;140;6m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
821
1016
  const date = new Date(inv.invoked_at * 1e3).toISOString().replace("T", " ").slice(0, 19);
822
1017
  console.log(
823
- ` ${status} ${chalk9.gray(date)} ${inv.duration_ms}ms ${chalk9.gray(`[${inv.trigger_type}]`)}`
1018
+ ` ${status} ${chalk11.gray(date)} ${inv.duration_ms}ms ${chalk11.gray(`[${inv.trigger_type}]`)}`
824
1019
  );
825
1020
  });
826
1021
  console.log("");
827
1022
  }
828
- async function functionsDelete(slug) {
829
- const client = getClient();
1023
+ async function functionsDelete(slug, options = {}) {
1024
+ const profileName = resolveProfileName3(options.profile);
1025
+ const client = getClient(profileName);
830
1026
  const spinner2 = ora(`Deleting ${slug}...`).start();
831
1027
  const result = await client.code.deleteFunction(slug);
832
1028
  if (!result.success) {
@@ -836,8 +1032,9 @@ async function functionsDelete(slug) {
836
1032
  }
837
1033
  spinner2.succeed(`Deleted ${slug}`);
838
1034
  }
839
- async function functionsToggle(slug, active) {
840
- const client = getClient();
1035
+ async function functionsToggle(slug, active, options = {}) {
1036
+ const profileName = resolveProfileName3(options.profile);
1037
+ const client = getClient(profileName);
841
1038
  const spinner2 = ora(
842
1039
  `${active ? "Enabling" : "Disabling"} ${slug}...`
843
1040
  ).start();
@@ -856,28 +1053,45 @@ var program = new Command();
856
1053
  program.name("globio").description("The official Globio CLI").version(version4).addHelpText("beforeAll", () => {
857
1054
  printBanner(version4);
858
1055
  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);
1056
+ }).addHelpText(
1057
+ "after",
1058
+ `
1059
+ Examples:
1060
+ $ globio login
1061
+ $ globio login --profile work
1062
+ $ globio use work
1063
+ $ globio projects list
1064
+ $ globio projects use proj_abc123
1065
+ $ globio functions deploy my-function
1066
+ $ globio migrate firestore --from ./key.json --all
1067
+
1068
+ Credentials are stored in ~/.globio/profiles/
1069
+ `
1070
+ );
1071
+ 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);
1072
+ program.command("logout").description("Log out").option("--profile <name>", "Use a specific profile").action(logout);
1073
+ program.command("whoami").description("Show current account and project").option("--profile <name>", "Use a specific profile").action(whoami);
1074
+ program.command("use <profile>").description("Switch active profile").action(useProfile);
1075
+ program.command("init").description("Initialize a Globio project").option("--profile <name>", "Use a specific profile").action(init);
1076
+ var profiles = program.command("profiles").description("Manage login profiles").action(profilesList);
1077
+ profiles.command("list").description("List all profiles").action(profilesList);
864
1078
  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);
1079
+ projects.command("list").description("List projects").option("--profile <name>", "Use a specific profile").action(projectsList);
1080
+ projects.command("create").description("Create a project").option("--profile <name>", "Use a specific profile").action(projectsCreate);
1081
+ projects.command("use <projectId>").description("Set active project").option("--profile <name>", "Use a specific profile").action(projectsUse);
1082
+ program.command("services").description("List available Globio services").option("--profile <name>", "Use a specific profile").action(servicesList);
869
1083
  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));
1084
+ functions.command("list").description("List all functions").option("--profile <name>", "Use a specific profile").action(functionsList);
1085
+ functions.command("create <slug>").description("Scaffold a new function file locally").option("--profile <name>", "Use a specific profile").action(functionsCreate);
1086
+ 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);
1087
+ functions.command("invoke <slug>").description("Invoke a function").option("-i, --input <json>", "JSON input payload").option("--profile <name>", "Use a specific profile").action(functionsInvoke);
1088
+ 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);
1089
+ functions.command("delete <slug>").description("Delete a function").option("--profile <name>", "Use a specific profile").action(functionsDelete);
1090
+ functions.command("enable <slug>").description("Enable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, true, options));
1091
+ functions.command("disable <slug>").description("Disable a function").option("--profile <name>", "Use a specific profile").action((slug, options) => functionsToggle(slug, false, options));
878
1092
  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);
1093
+ 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);
1094
+ 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
1095
  async function main() {
882
1096
  if (process.argv.length <= 2) {
883
1097
  program.help();