@botim/botim-cli 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +287 -63
  2. package/dist/cli.mjs +6 -6
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -13,13 +13,46 @@ import { createReactApp, createVueApp, handleLogin, handleLogout, handleQRCode }
13
13
  import { showMenuOrRequireAuth } from "./commands/auth/index.js";
14
14
  import { displayExamples, displayQuickStart } from "./utils/help.js";
15
15
  import { logger, displayErrorSync } from "./utils/logger.js";
16
- import { isLoggedIn, getCurrentEnvironment, setCurrentEnvironment, getAllEnvironmentStatus } from "./utils/config.js";
16
+ import { isLoggedIn, getCurrentEnvironment, setCurrentEnvironment, getAllEnvironmentStatus, getPartnerId, getPartnerName, getUserToken } from "./utils/config.js";
17
17
  import { setRuntimeEnvironment, parseEnvironment, getEnvironmentDisplayName } from "./utils/environment.js";
18
18
  import { executeWithAuthRetry } from "./utils/auth-handler.js";
19
+ import { getConfigPath } from "./utils/config-sync.js";
20
+ import { switchToPartner } from "./utils/partner-switch.js";
21
+ import { fetchPartners } from "./utils/auth-api.js";
22
+ import { select } from "@inquirer/prompts";
19
23
  import chalk from "chalk";
20
24
  import fs from "fs-extra";
21
- import path from "path";
22
- const version = "0.0.6";
25
+ async function promptSelect(options) {
26
+ const ac = new AbortController();
27
+ const onKeypress = (_ch, key) => {
28
+ if (key?.name === "escape") {
29
+ ac.abort();
30
+ }
31
+ };
32
+ process.stdin.on("keypress", onKeypress);
33
+ try {
34
+ return await select({
35
+ ...options,
36
+ theme: {
37
+ helpMode: "always",
38
+ style: {
39
+ keysHelpTip: (keys) => {
40
+ keys.push(["esc", "back"]);
41
+ return keys.map(([key, desc]) => `\x1B[1m${key}\x1B[22m \x1B[2m${desc}\x1B[22m`).join("\x1B[2m \u2022 \x1B[22m");
42
+ }
43
+ }
44
+ }
45
+ }, { signal: ac.signal });
46
+ } catch (error) {
47
+ if (error.name === "AbortPromptError") {
48
+ return null;
49
+ }
50
+ throw error;
51
+ } finally {
52
+ process.stdin.removeListener("keypress", onKeypress);
53
+ }
54
+ }
55
+ const version = "0.0.8";
23
56
  const program = new Command();
24
57
  program.name("botim-cli").description("CLI tool to generate boilerplate code for React and Vue applications").version(version, "-v, --version", "Output the current version").option("--env <environment>", "Target environment: prod, uat, or beta (overrides default)", (value) => {
25
58
  const env = parseEnvironment(value);
@@ -103,7 +136,7 @@ program.command("status").description("Show login status for all environments").
103
136
  const prodActive = currentEnv === "production" ? chalk.cyan(" (active)") : "";
104
137
  console.log(` ${prodIndicator} Production${prodActive}`);
105
138
  if (prodStatus.loggedIn && prodStatus.partnerName) {
106
- console.log(chalk.gray(` Partner: ${prodStatus.partnerName}`));
139
+ console.log(chalk.gray(` Partner: ${prodStatus.partnerName}${prodStatus.partnerId ? ` (${prodStatus.partnerId})` : ""}`));
107
140
  } else if (!prodStatus.loggedIn) {
108
141
  console.log(chalk.gray(" Not logged in"));
109
142
  }
@@ -113,7 +146,7 @@ program.command("status").description("Show login status for all environments").
113
146
  const uatActive = currentEnv === "uat" ? chalk.cyan(" (active)") : "";
114
147
  console.log(` ${uatIndicator} UAT${uatActive}`);
115
148
  if (uatStatus.loggedIn && uatStatus.partnerName) {
116
- console.log(chalk.gray(` Partner: ${uatStatus.partnerName}`));
149
+ console.log(chalk.gray(` Partner: ${uatStatus.partnerName}${uatStatus.partnerId ? ` (${uatStatus.partnerId})` : ""}`));
117
150
  } else if (!uatStatus.loggedIn) {
118
151
  console.log(chalk.gray(" Not logged in"));
119
152
  }
@@ -123,20 +156,21 @@ program.command("status").description("Show login status for all environments").
123
156
  const betaActive = currentEnv === "beta" ? chalk.cyan(" (active)") : "";
124
157
  console.log(` ${betaIndicator} Beta${betaActive}`);
125
158
  if (betaStatus.loggedIn && betaStatus.partnerName) {
126
- console.log(chalk.gray(` Partner: ${betaStatus.partnerName}`));
159
+ console.log(chalk.gray(` Partner: ${betaStatus.partnerName}${betaStatus.partnerId ? ` (${betaStatus.partnerId})` : ""}`));
127
160
  } else if (!betaStatus.loggedIn) {
128
161
  console.log(chalk.gray(" Not logged in"));
129
162
  }
130
163
  console.log("");
131
164
  console.log(chalk.gray("Tips:"));
132
165
  console.log(chalk.gray(" Switch environment: botim-cli config set env uat"));
166
+ console.log(chalk.gray(" Switch partner: botim-cli switch-partner"));
133
167
  console.log(chalk.gray(" One-time override: botim-cli --env beta <command>"));
134
168
  console.log(chalk.gray(" Login to beta: botim-cli --env beta login\n"));
135
169
  });
136
170
  program.command("auth").description("Access authenticated commands (requires login)").alias("authenticated").action(async () => {
137
171
  await showMenuOrRequireAuth();
138
172
  });
139
- program.command("create-mp-app").description("Create a new Mini-Program app non-interactively (requires authentication)").alias("cmp").requiredOption("--projectName <name>", "Project folder name").option("--framework <framework>", "Template framework: react or vue", "react").option("--title <title>", "App title (default: derived from projectName)").option("--domainPrefix <prefix>", "Domain prefix (default: auto-selected)").option("--reviewNote <note>", "Review note for submission (default: auto-generated)").option("--creatorType <type>", "Creator type: hybrid or h5bridge", "hybrid").option("--website <url>", "Website URL (required if creatorType=h5bridge)").option("--shareText <text>", "Share card description (default: same as title)").option("--screenOrientation <orientation>", "Screen mode: fullscreen, immersive, or standard", "fullscreen").option("--apiLevel <version>", "API level version (default: latest available)").option("--audience <audience>", "Audience: 1=Private or 2=Public", "2").addHelpText("after", `
173
+ program.command("create-mp-app").description("Create a new Mini-Program app non-interactively (requires authentication)").alias("cmp").requiredOption("--projectName <name>", "Project folder name").option("--framework <framework>", "Template framework: react or vue", "react").option("--title <title>", "App title (default: derived from projectName)").option("--domainPrefix <prefix>", "Domain prefix (default: auto-selected)").option("--reviewNote <note>", "Review note for submission (default: auto-generated)").option("--creatorType <type>", "Creator type: hybrid or h5bridge", "hybrid").option("--website <url>", "Website URL (required if creatorType=h5bridge)").option("--shareText <text>", "Share card description (default: same as title)").option("--screenOrientation <orientation>", "Screen mode: fullscreen, immersive, or standard", "fullscreen").option("--apiLevel <version>", "API level version (default: latest available)").option("--audience <audience>", "Audience: 1=Public or 2=Private", "1").addHelpText("after", `
140
174
 
141
175
  Examples:
142
176
  Basic usage with minimal options:
@@ -183,8 +217,8 @@ Screen Orientations:
183
217
  standard - Standard page with title bar
184
218
 
185
219
  Audience Options:
186
- 1 - Private (only accessible to specific users)
187
- 2 - Public (accessible to all users) (default)
220
+ 1 - Public (accessible to all users) (default)
221
+ 2 - Private (only accessible to specific users)
188
222
  `).action(async (options) => {
189
223
  try {
190
224
  const loggedIn = await isLoggedIn();
@@ -214,6 +248,71 @@ Audience Options:
214
248
  process.exit(1);
215
249
  }
216
250
  });
251
+ program.command("register-mp-app").description("Register a new Mini-Program on platform only, no template (requires authentication)").alias("rmp").requiredOption("--title <title>", "App title").option("--domainPrefix <prefix>", "Domain prefix (default: auto-selected)").option("--appIdSuffix <suffix>", "App ID suffix (default: auto-generated)").option("--reviewNote <note>", "Review note for submission (default: auto-generated)").option("--creatorType <type>", "Creator type: hybrid or h5bridge", "hybrid").option("--website <url>", "Website URL (required if creatorType=h5bridge)").option("--shareText <text>", "Share card description (default: same as title)").option("--screenOrientation <orientation>", "Screen mode: fullscreen, immersive, or standard", "fullscreen").option("--apiLevel <version>", "API level version (default: latest available)").option("--audience <audience>", "Audience: 1=Public or 2=Private", "1").addHelpText("after", `
252
+
253
+ Examples:
254
+ Register with minimal options:
255
+ $ botim-cli register-mp-app --title "My App"
256
+
257
+ Register with all options:
258
+ $ botim-cli register-mp-app \\
259
+ --title "My App" \\
260
+ --domainPrefix me.botim.mp.test \\
261
+ --shareText "Explore my amazing app" \\
262
+ --screenOrientation immersive \\
263
+ --audience 2
264
+
265
+ Register an H5BRIDGE app:
266
+ $ botim-cli register-mp-app \\
267
+ --title "My Website" \\
268
+ --creatorType h5bridge \\
269
+ --website https://example.com \\
270
+ --shareText "Visit my website"
271
+
272
+ Use UAT environment:
273
+ $ botim-cli --env uat register-mp-app --title "My UAT App"
274
+
275
+ Creator Types:
276
+ hybrid - Fastest, local resources with full platform capabilities (default)
277
+ h5bridge - Standard, wraps a responsive HTML5 website (requires --website)
278
+
279
+ Screen Orientations:
280
+ fullscreen - Full screen with status bar visible (default)
281
+ immersive - Full screen with status bar hidden
282
+ standard - Standard page with title bar
283
+
284
+ Audience Options:
285
+ 1 - Public (accessible to all users) (default)
286
+ 2 - Private (only accessible to specific users)
287
+ `).action(async (options) => {
288
+ try {
289
+ const loggedIn = await isLoggedIn();
290
+ if (!loggedIn) {
291
+ const currentEnv = await getCurrentEnvironment();
292
+ const envDisplay = getEnvironmentDisplayName(currentEnv);
293
+ console.log(chalk.red(`
294
+ \u274C Authentication required for ${envDisplay}. Please login first using: botim-cli ${currentEnv === "uat" ? "--env uat " : ""}login
295
+ `));
296
+ process.exit(1);
297
+ }
298
+ const { authenticatedCommandRegistry } = await import("./commands/auth/index.js");
299
+ const command = authenticatedCommandRegistry.get("register-mp-app");
300
+ if (command) {
301
+ const processedOptions = {
302
+ ...options,
303
+ audience: parseInt(options.audience, 10)
304
+ };
305
+ await executeWithAuthRetry(
306
+ () => command.execute(processedOptions),
307
+ { commandName: "register-mp-app", retryAfterLogin: true }
308
+ );
309
+ }
310
+ } catch (error) {
311
+ logger.error("register-mp-app command failed", error);
312
+ displayErrorSync(error, "Register MP App failed");
313
+ process.exit(1);
314
+ }
315
+ });
217
316
  program.command("init-mp-app").description("Initialize MP app in current directory (requires authentication)").alias("init-mp").action(async () => {
218
317
  try {
219
318
  const loggedIn = await isLoggedIn();
@@ -264,7 +363,7 @@ program.command("deploy-mp-app").description("Deploy MP app to server (requires
264
363
  process.exit(1);
265
364
  }
266
365
  });
267
- program.command("debug-mp-app").description("Debug MP app (requires authentication and botim_config.json)").alias("debug").action(async () => {
366
+ program.command("debug-mp-app").description("Debug MP app (requires authentication and config file)").alias("debug").action(async () => {
268
367
  try {
269
368
  const loggedIn = await isLoggedIn();
270
369
  if (!loggedIn) {
@@ -289,6 +388,89 @@ program.command("debug-mp-app").description("Debug MP app (requires authenticati
289
388
  process.exit(1);
290
389
  }
291
390
  });
391
+ program.command("switch-partner [partner-id]").description("Switch to a different partner without re-login").alias("sp").action(async (partnerId) => {
392
+ try {
393
+ const currentEnv = await getCurrentEnvironment();
394
+ const envDisplay = getEnvironmentDisplayName(currentEnv);
395
+ const loggedIn = await isLoggedIn();
396
+ if (!loggedIn) {
397
+ console.log(chalk.red(`
398
+ \u274C Authentication required for ${envDisplay}. Please login first using: botim-cli login
399
+ `));
400
+ process.exit(1);
401
+ }
402
+ const userToken = await getUserToken(currentEnv);
403
+ if (!userToken) {
404
+ console.log(chalk.yellow("\n\u26A0\uFE0F No user token available. Please login again to enable partner switching.\n"));
405
+ console.log(chalk.gray("Run: botim-cli login\n"));
406
+ process.exit(1);
407
+ }
408
+ let selectedPartnerId = partnerId;
409
+ if (!selectedPartnerId) {
410
+ const ora = (await import("ora")).default;
411
+ const spinner = ora("Fetching partner list...").start();
412
+ let partnerList;
413
+ try {
414
+ partnerList = await fetchPartners({ userToken, env: currentEnv });
415
+ spinner.succeed("Partner list fetched");
416
+ } catch (error) {
417
+ spinner.fail("Failed to fetch partner list");
418
+ console.log(chalk.yellow("\n\u26A0\uFE0F Could not fetch partner list. Your session may have expired.\n"));
419
+ console.log(chalk.gray("Run: botim-cli login\n"));
420
+ process.exit(1);
421
+ }
422
+ if (!partnerList || partnerList.length === 0) {
423
+ console.log(chalk.yellow("\n\u26A0\uFE0F No partners found for your account.\n"));
424
+ process.exit(1);
425
+ }
426
+ const currentPartnerId2 = await getPartnerId(currentEnv);
427
+ const selected = await promptSelect({
428
+ message: "Select a partner:",
429
+ choices: partnerList.map((p) => ({
430
+ name: `${p.partnerInfo?.company_name || p.partner_id} (${p.partner_id})${p.partner_id === currentPartnerId2 ? chalk.cyan(" \u2190 current") : ""}`,
431
+ value: p.partner_id
432
+ }))
433
+ });
434
+ if (selected === null) {
435
+ process.exit(0);
436
+ }
437
+ selectedPartnerId = selected;
438
+ }
439
+ if (!selectedPartnerId) {
440
+ process.exit(1);
441
+ }
442
+ const currentPartnerId = await getPartnerId(currentEnv);
443
+ if (selectedPartnerId === currentPartnerId) {
444
+ const currentPartnerName = await getPartnerName(currentEnv);
445
+ console.log(chalk.gray(`
446
+ Already using partner: ${currentPartnerName || selectedPartnerId}
447
+ `));
448
+ return;
449
+ }
450
+ const result = await switchToPartner(selectedPartnerId, currentEnv);
451
+ if (result.success) {
452
+ console.log(chalk.green(`
453
+ \u2713 Switched to partner: ${result.partnerName}
454
+ `));
455
+ } else if (result.error === "partner-not-found") {
456
+ console.log(chalk.red(`
457
+ \u274C Partner ${selectedPartnerId} not found in your partner list.
458
+ `));
459
+ process.exit(1);
460
+ } else if (result.error === "no-user-token" || result.error === "user-token-expired") {
461
+ console.log(chalk.yellow("\n\u26A0\uFE0F Your session has expired. Please login again.\n"));
462
+ console.log(chalk.gray("Run: botim-cli login\n"));
463
+ process.exit(1);
464
+ } else {
465
+ console.log(chalk.red("\n\u274C Failed to switch partner. Please try logging in again.\n"));
466
+ process.exit(1);
467
+ }
468
+ } catch (error) {
469
+ logger.error("switch-partner command failed", error);
470
+ displayErrorSync(error, "Switch partner failed");
471
+ process.exit(1);
472
+ }
473
+ });
292
474
  program.command("list-mp-permissions [app-id]").description("List permissions for a mini-program app (requires authentication)").alias("perms").action(async (appId) => {
293
475
  try {
294
476
  const loggedIn = await isLoggedIn();
@@ -530,19 +712,22 @@ async function showMainMenu() {
530
712
  const currentEnv = await getCurrentEnvironment();
531
713
  const envDisplay = getEnvironmentDisplayName(currentEnv);
532
714
  const envColor = currentEnv === "production" ? chalk.green : chalk.yellow;
715
+ const currentPartnerName = await getPartnerName(currentEnv);
533
716
  console.log(chalk.cyan.bold("\n\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
534
717
  console.log(chalk.cyan.bold("\u2551 \u{1F680} Botim CLI Generator \u2551"));
535
718
  console.log(chalk.cyan.bold(`\u2551 v${version.padEnd(32)}\u2551`));
536
- console.log(chalk.cyan.bold(`\u2551 Environment: ${envColor(envDisplay.padEnd(26))}\u2551`));
719
+ console.log(chalk.cyan.bold(`\u2551 Environment: ${envColor(envDisplay.padEnd(27))}\u2551`));
720
+ if (currentPartnerName) {
721
+ console.log(chalk.cyan.bold(`\u2551 Partner: ${chalk.white(currentPartnerName.padEnd(31))}\u2551`));
722
+ }
537
723
  console.log(chalk.cyan.bold("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\n"));
538
- const inquirer = (await import("inquirer")).default;
539
724
  const loggedIn = await isLoggedIn();
540
725
  let authenticatedChoices = [];
541
726
  if (loggedIn) {
542
727
  const { authenticatedCommandRegistry } = await import("./commands/auth/index.js");
543
728
  const commands = authenticatedCommandRegistry.getAll();
544
729
  const currentDir = process.cwd();
545
- const configFilePath = path.join(currentDir, "botim_config.json");
730
+ const configFilePath = getConfigPath(currentDir, currentEnv);
546
731
  const hasConfigFile = await fs.pathExists(configFilePath);
547
732
  const filteredCommands = commands.filter((cmd) => {
548
733
  if (cmd.hideFromMenu) {
@@ -551,7 +736,7 @@ async function showMainMenu() {
551
736
  if (cmd.name === "deploy-mp-app" || cmd.name === "debug-mp-app") {
552
737
  return hasConfigFile;
553
738
  }
554
- if (cmd.name === "create-mp-app" || cmd.name === "init-mp-app") {
739
+ if (cmd.name === "create-mp-app" || cmd.name === "register-mp-app") {
555
740
  return !hasConfigFile;
556
741
  }
557
742
  return true;
@@ -574,20 +759,21 @@ async function showMainMenu() {
574
759
  ...authenticatedChoices,
575
760
  { name: "\u{1F4F1} Create a Mini-Program (Local)", value: "mini-program" },
576
761
  { name: "\u{1F504} Switch Environment", value: "switch-env" },
762
+ { name: "\u{1F500} Switch Partner", value: "switch-partner" },
577
763
  { name: "\u{1F4CA} View Status", value: "status" },
578
764
  { name: "\u{1F6AA} Logout", value: "logout" },
579
765
  { name: "\u{1F4DA} View examples", value: "examples" },
580
766
  { name: "\u2753 Show help", value: "help" },
581
767
  { name: "\u{1F6AA} Exit", value: "exit" }
582
768
  ];
583
- const { action } = await inquirer.prompt([
584
- {
585
- type: "list",
586
- name: "action",
587
- message: loggedIn ? envColor(`\u2713 Authenticated [${envDisplay}]`) + " - What would you like to do?" : `[${envDisplay}] What would you like to do?`,
588
- choices: loggedIn ? authenticatedChoices : unauthenticatedChoices
589
- }
590
- ]);
769
+ const action = await promptSelect({
770
+ message: loggedIn ? envColor(`\u2713 Authenticated [${envDisplay}]`) + " - What would you like to do?" : `[${envDisplay}] What would you like to do?`,
771
+ choices: loggedIn ? authenticatedChoices : unauthenticatedChoices
772
+ });
773
+ if (action === null) {
774
+ console.log(chalk.gray("\nGoodbye! \u{1F44B}\n"));
775
+ process.exit(0);
776
+ }
591
777
  if (action.startsWith("auth:")) {
592
778
  const commandName = action.replace("auth:", "");
593
779
  const { authenticatedCommandRegistry } = await import("./commands/auth/index.js");
@@ -605,20 +791,14 @@ async function showMainMenu() {
605
791
  }
606
792
  }
607
793
  } else if (action === "mini-program") {
608
- const { framework } = await inquirer.prompt([
609
- {
610
- type: "list",
611
- name: "framework",
612
- message: "Select a framework template:",
613
- choices: [
614
- { name: "\u269B\uFE0F React - Modern React 18 with Vite", value: "react" },
615
- { name: "\u{1F596} Vue - Vue 3 with Composition API", value: "vue" },
616
- new inquirer.Separator(),
617
- { name: "\u2190 Back to main menu", value: "back" }
618
- ]
619
- }
620
- ]);
621
- if (framework === "back") {
794
+ const framework = await promptSelect({
795
+ message: "Select a framework template:",
796
+ choices: [
797
+ { name: "\u269B\uFE0F React - Modern React 18 with Vite", value: "react" },
798
+ { name: "\u{1F596} Vue - Vue 3 with Composition API", value: "vue" }
799
+ ]
800
+ });
801
+ if (framework === null) {
622
802
  await showMainMenu();
623
803
  } else if (framework === "react") {
624
804
  console.log(chalk.cyan.bold(`
@@ -633,30 +813,24 @@ async function showMainMenu() {
633
813
  }
634
814
  } else if (action === "switch-env") {
635
815
  const allStatus = await getAllEnvironmentStatus();
636
- const { newEnv } = await inquirer.prompt([
637
- {
638
- type: "list",
639
- name: "newEnv",
640
- message: "Select environment:",
641
- choices: [
642
- {
643
- name: `Production ${allStatus.production.loggedIn ? chalk.green("\u2713 Logged in") : chalk.gray("(not logged in)")}${currentEnv === "production" ? chalk.cyan(" \u2190 current") : ""}`,
644
- value: "production"
645
- },
646
- {
647
- name: `UAT ${allStatus.uat.loggedIn ? chalk.green("\u2713 Logged in") : chalk.gray("(not logged in)")}${currentEnv === "uat" ? chalk.cyan(" \u2190 current") : ""}`,
648
- value: "uat"
649
- },
650
- {
651
- name: `Beta ${allStatus.beta.loggedIn ? chalk.green("\u2713 Logged in") : chalk.gray("(not logged in)")}${currentEnv === "beta" ? chalk.cyan(" \u2190 current") : ""}`,
652
- value: "beta"
653
- },
654
- new inquirer.Separator(),
655
- { name: "\u2190 Back to main menu", value: "back" }
656
- ]
657
- }
658
- ]);
659
- if (newEnv === "back") {
816
+ const newEnv = await promptSelect({
817
+ message: "Select environment:",
818
+ choices: [
819
+ {
820
+ name: `Production ${allStatus.production.loggedIn ? chalk.green("\u2713 Logged in") : chalk.gray("(not logged in)")}${currentEnv === "production" ? chalk.cyan(" \u2190 current") : ""}`,
821
+ value: "production"
822
+ },
823
+ {
824
+ name: `UAT ${allStatus.uat.loggedIn ? chalk.green("\u2713 Logged in") : chalk.gray("(not logged in)")}${currentEnv === "uat" ? chalk.cyan(" \u2190 current") : ""}`,
825
+ value: "uat"
826
+ },
827
+ {
828
+ name: `Beta ${allStatus.beta.loggedIn ? chalk.green("\u2713 Logged in") : chalk.gray("(not logged in)")}${currentEnv === "beta" ? chalk.cyan(" \u2190 current") : ""}`,
829
+ value: "beta"
830
+ }
831
+ ]
832
+ });
833
+ if (newEnv === null) {
660
834
  await showMainMenu();
661
835
  } else {
662
836
  await setCurrentEnvironment(newEnv);
@@ -671,6 +845,56 @@ async function showMainMenu() {
671
845
  }
672
846
  await showMainMenu();
673
847
  }
848
+ } else if (action === "switch-partner") {
849
+ const userToken = await getUserToken(currentEnv);
850
+ if (!userToken) {
851
+ console.log(chalk.yellow("\n\u26A0\uFE0F No user token available. Please login again to enable partner switching.\n"));
852
+ await showMainMenu();
853
+ return;
854
+ }
855
+ const ora = (await import("ora")).default;
856
+ const spinner = ora("Fetching partner list...").start();
857
+ let partnerList;
858
+ try {
859
+ partnerList = await fetchPartners({ userToken, env: currentEnv });
860
+ spinner.succeed("Partner list fetched");
861
+ } catch {
862
+ spinner.fail("Failed to fetch partner list");
863
+ console.log(chalk.yellow("\n\u26A0\uFE0F Could not fetch partner list. Your session may have expired.\n"));
864
+ await showMainMenu();
865
+ return;
866
+ }
867
+ if (!partnerList || partnerList.length === 0) {
868
+ console.log(chalk.yellow("\n\u26A0\uFE0F No partners found for your account.\n"));
869
+ await showMainMenu();
870
+ return;
871
+ }
872
+ const currentPartnerId = await getPartnerId(currentEnv);
873
+ const selectedPartnerId = await promptSelect({
874
+ message: "Select a partner:",
875
+ choices: partnerList.map((p) => ({
876
+ name: `${p.partnerInfo?.company_name || p.partner_id} (${p.partner_id})${p.partner_id === currentPartnerId ? chalk.cyan(" \u2190 current") : ""}`,
877
+ value: p.partner_id
878
+ }))
879
+ });
880
+ if (selectedPartnerId === null) {
881
+ await showMainMenu();
882
+ } else if (selectedPartnerId === currentPartnerId) {
883
+ console.log(chalk.gray("\nAlready using this partner.\n"));
884
+ await showMainMenu();
885
+ } else {
886
+ const result = await switchToPartner(selectedPartnerId, currentEnv);
887
+ if (result.success) {
888
+ console.log(chalk.green(`
889
+ \u2713 Switched to partner: ${result.partnerName}
890
+ `));
891
+ } else if (result.error === "no-user-token" || result.error === "user-token-expired") {
892
+ console.log(chalk.yellow("\n\u26A0\uFE0F Your session has expired. Please login again.\n"));
893
+ } else {
894
+ console.log(chalk.red("\n\u274C Failed to switch partner.\n"));
895
+ }
896
+ await showMainMenu();
897
+ }
674
898
  } else if (action === "status") {
675
899
  const allStatus = await getAllEnvironmentStatus();
676
900
  console.log(chalk.cyan.bold(`
@@ -683,21 +907,21 @@ async function showMainMenu() {
683
907
  const prodActive = currentEnv === "production" ? chalk.cyan(" (active)") : "";
684
908
  console.log(` ${prodIndicator} Production${prodActive}`);
685
909
  if (prodStatus.loggedIn && prodStatus.partnerName) {
686
- console.log(chalk.gray(` Partner: ${prodStatus.partnerName}`));
910
+ console.log(chalk.gray(` Partner: ${prodStatus.partnerName}${prodStatus.partnerId ? ` (${prodStatus.partnerId})` : ""}`));
687
911
  }
688
912
  const uatStatus = allStatus.uat;
689
913
  const uatIndicator = uatStatus.loggedIn ? chalk.green("\u2713") : chalk.red("\u2717");
690
914
  const uatActive = currentEnv === "uat" ? chalk.cyan(" (active)") : "";
691
915
  console.log(` ${uatIndicator} UAT${uatActive}`);
692
916
  if (uatStatus.loggedIn && uatStatus.partnerName) {
693
- console.log(chalk.gray(` Partner: ${uatStatus.partnerName}`));
917
+ console.log(chalk.gray(` Partner: ${uatStatus.partnerName}${uatStatus.partnerId ? ` (${uatStatus.partnerId})` : ""}`));
694
918
  }
695
919
  const betaStatus = allStatus.beta;
696
920
  const betaIndicator = betaStatus.loggedIn ? chalk.green("\u2713") : chalk.red("\u2717");
697
921
  const betaActive = currentEnv === "beta" ? chalk.cyan(" (active)") : "";
698
922
  console.log(` ${betaIndicator} Beta${betaActive}`);
699
923
  if (betaStatus.loggedIn && betaStatus.partnerName) {
700
- console.log(chalk.gray(` Partner: ${betaStatus.partnerName}`));
924
+ console.log(chalk.gray(` Partner: ${betaStatus.partnerName}${betaStatus.partnerId ? ` (${betaStatus.partnerId})` : ""}`));
701
925
  }
702
926
  console.log("");
703
927
  await showMainMenu();