caflip 0.3.0 → 0.3.3

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/README.md +19 -4
  2. package/dist/cli.js +155 -85
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,10 +63,15 @@ bun run dev -- help
63
63
  ## Quick Start
64
64
 
65
65
  ```bash
66
- # Show current account / managed accounts across both providers
66
+ # Show current active account / all managed accounts across both providers
67
67
  caflip status
68
68
  caflip list
69
69
 
70
+ # Pick provider interactively, then add/remove/login
71
+ caflip add
72
+ caflip remove
73
+ caflip login
74
+
70
75
  # Add your first Claude account (must already be logged in)
71
76
  caflip claude add --alias personal
72
77
 
@@ -105,7 +110,10 @@ After switching, restart the target CLI (Claude Code or Codex) to pick up new au
105
110
  |---|---|
106
111
  | `caflip` | Interactive provider picker (Claude/Codex) |
107
112
  | `caflip list` | List managed accounts for Claude and Codex |
108
- | `caflip status` | Show current account for Claude and Codex |
113
+ | `caflip status` | Show current active account for Claude and Codex |
114
+ | `caflip add [--alias name]` | Pick provider, then add current account |
115
+ | `caflip login [-- <args...>]` | Pick provider, then run provider login and register the resulting session |
116
+ | `caflip remove [email]` | Pick provider, then remove an account |
109
117
  | `caflip claude [command]` | Run command for Claude provider |
110
118
  | `caflip codex [command]` | Run command for Codex provider |
111
119
  | `caflip [provider]` | Interactive account picker for that provider |
@@ -115,7 +123,7 @@ After switching, restart the target CLI (Claude Code or Codex) to pick up new au
115
123
  | `caflip [provider] login [-- <args...>]` | Run provider login and register the resulting session |
116
124
  | `caflip [provider] remove [email]` | Remove an account |
117
125
  | `caflip [provider] next` | Rotate to next account |
118
- | `caflip [provider] status` | Show current account |
126
+ | `caflip [provider] status` | Show current active account |
119
127
  | `caflip [provider] alias <name> [email]` | Set alias for current or target account |
120
128
  | `caflip help` | Show help |
121
129
 
@@ -132,16 +140,23 @@ caflip claude alias work hi.lucienlee@gmail.com
132
140
  caflip codex alias work me@company.com
133
141
  ```
134
142
 
135
- `remove` target accepts email only. Omit it to choose from the interactive picker.
143
+ `add`, `remove`, and `login` can be used without a provider prefix. In that case, caflip asks you to choose Claude or Codex first, then continues the normal command flow.
144
+
145
+ `remove` target accepts email only. Omit it to choose from the interactive picker after selecting a provider.
136
146
 
137
147
  `login` can be used without arguments for the default login flow. Pass provider-specific flags after `--`:
138
148
 
139
149
  ```bash
150
+ caflip login
140
151
  caflip claude login
141
152
  caflip claude login -- --email lucien@aibor.io --sso
142
153
  caflip codex login -- --device-auth
143
154
  ```
144
155
 
156
+ `status` shows the currently active account for the selected provider. It does not list every saved account.
157
+
158
+ Use `list` when you want to inspect all managed accounts for a provider.
159
+
145
160
  ## Shell Prompt Integration
146
161
 
147
162
  Show the current account in your prompt:
package/dist/cli.js CHANGED
@@ -617,7 +617,7 @@ function validateAlias(alias) {
617
617
  // package.json
618
618
  var package_default = {
619
619
  name: "caflip",
620
- version: "0.3.0",
620
+ version: "0.3.3",
621
621
  type: "module",
622
622
  bin: {
623
623
  caflip: "bin/caflip"
@@ -3166,6 +3166,7 @@ async function writeLastProvider(provider) {
3166
3166
 
3167
3167
  // src/index.ts
3168
3168
  var ADD_CURRENT_ACCOUNT_CHOICE = "__add_current_account__";
3169
+ var INTERACTIVE_PROVIDER_COMMANDS = ["add", "remove", "login"];
3169
3170
  var activeBackupDir = BACKUP_DIR;
3170
3171
  var activeSequenceFile = SEQUENCE_FILE;
3171
3172
  var activeLockDir = LOCK_DIR;
@@ -3196,6 +3197,76 @@ function showProviderRequiredError(command) {
3196
3197
  console.error(`Try: caflip claude ${command} or caflip codex ${command}`);
3197
3198
  process.exit(2);
3198
3199
  }
3200
+ function supportsInteractiveProviderSelection(command) {
3201
+ return INTERACTIVE_PROVIDER_COMMANDS.includes(command ?? "");
3202
+ }
3203
+ async function resolveProviderForCommand(provider, command, deps = {
3204
+ readCliMeta,
3205
+ pickProvider,
3206
+ writeLastProvider
3207
+ }) {
3208
+ if (provider || !supportsInteractiveProviderSelection(command)) {
3209
+ return provider;
3210
+ }
3211
+ const defaultProvider = deps.readCliMeta().lastProvider;
3212
+ const selectedProvider = await deps.pickProvider(defaultProvider);
3213
+ await deps.writeLastProvider(selectedProvider);
3214
+ return selectedProvider;
3215
+ }
3216
+ async function resolveCliContext(parsed, deps = { resolveProviderForCommand }) {
3217
+ let provider = parsed.provider;
3218
+ const args = parsed.commandArgs;
3219
+ const command = args[0];
3220
+ const isHelpCommand = command === "help" || command === "--help" || command === "-h";
3221
+ const isProviderOptionalReadOnlyCommand = command === "list" || command === "status";
3222
+ const supportsInteractiveProvider = supportsInteractiveProviderSelection(command);
3223
+ if (!parsed.isProviderQualified && command && !isHelpCommand && !isProviderOptionalReadOnlyCommand) {
3224
+ const isReservedCommand = RESERVED_COMMANDS.includes(command);
3225
+ if (!isReservedCommand) {
3226
+ console.error("Error: Alias requires provider prefix.");
3227
+ console.error("Try: caflip claude <alias> or caflip codex <alias>");
3228
+ process.exit(2);
3229
+ }
3230
+ if (!supportsInteractiveProvider) {
3231
+ showProviderRequiredError(command);
3232
+ }
3233
+ }
3234
+ if (!command) {
3235
+ return {
3236
+ mode: "interactive-switch",
3237
+ provider,
3238
+ args,
3239
+ command: undefined
3240
+ };
3241
+ }
3242
+ if (isHelpCommand) {
3243
+ showHelp();
3244
+ process.exit(0);
3245
+ }
3246
+ if (!provider) {
3247
+ if (command === "list" || command === "status") {
3248
+ return {
3249
+ mode: "all-providers",
3250
+ provider: null,
3251
+ args,
3252
+ command
3253
+ };
3254
+ }
3255
+ provider = await deps.resolveProviderForCommand(provider, command);
3256
+ if (!provider) {
3257
+ showProviderRequiredError(command);
3258
+ }
3259
+ }
3260
+ if (!provider) {
3261
+ throw new Error("Provider resolution failed");
3262
+ }
3263
+ return {
3264
+ mode: "provider-command",
3265
+ provider,
3266
+ args,
3267
+ command
3268
+ };
3269
+ }
3199
3270
  async function syncSequenceActiveAccount(seq) {
3200
3271
  const currentEmail = getCurrentAccount();
3201
3272
  const resolvedActive = resolveManagedAccountNumberForEmail(seq, currentEmail);
@@ -3502,20 +3573,21 @@ async function cmdStatus(options) {
3502
3573
  }
3503
3574
  if (summary.email === "none") {
3504
3575
  console.log("none");
3576
+ } else if (summary.alias) {
3577
+ console.log(`${summary.email} [${summary.alias}]`);
3505
3578
  } else {
3506
- if (summary.alias) {
3507
- console.log(`${summary.email} [${summary.alias}]`);
3508
- return;
3509
- }
3510
3579
  console.log(summary.email);
3511
3580
  }
3581
+ console.log(`managed accounts: ${summary.managedCount}`);
3512
3582
  }
3513
3583
  async function getStatusSummaryForActiveProvider() {
3514
3584
  const email = getCurrentAccount();
3515
3585
  let alias = null;
3516
3586
  let managed = false;
3587
+ let managedCount = 0;
3517
3588
  if (email !== "none" && existsSync9(activeSequenceFile)) {
3518
3589
  const seq = await loadSequence(activeSequenceFile);
3590
+ managedCount = Object.keys(seq.accounts).length;
3519
3591
  for (const account of Object.values(seq.accounts)) {
3520
3592
  if (account.email === email) {
3521
3593
  managed = true;
@@ -3523,8 +3595,11 @@ async function getStatusSummaryForActiveProvider() {
3523
3595
  break;
3524
3596
  }
3525
3597
  }
3598
+ } else if (existsSync9(activeSequenceFile)) {
3599
+ const seq = await loadSequence(activeSequenceFile);
3600
+ managedCount = Object.keys(seq.accounts).length;
3526
3601
  }
3527
- return { email, alias, managed };
3602
+ return { email, alias, managed, managedCount };
3528
3603
  }
3529
3604
  async function withActiveProvider(provider, fn) {
3530
3605
  const previousProvider = activeProvider.name;
@@ -3574,13 +3649,12 @@ async function cmdStatusAllProviders() {
3574
3649
  console.log(summary.heading);
3575
3650
  if (summary.email === "none") {
3576
3651
  console.log(" none");
3577
- continue;
3578
- }
3579
- if (summary.alias) {
3652
+ } else if (summary.alias) {
3580
3653
  console.log(` ${summary.email} [${summary.alias}]`);
3581
- continue;
3654
+ } else {
3655
+ console.log(` ${summary.email}`);
3582
3656
  }
3583
- console.log(` ${summary.email}`);
3657
+ console.log(` managed accounts: ${summary.managedCount}`);
3584
3658
  }
3585
3659
  }
3586
3660
  async function cmdAlias(alias, identifier) {
@@ -3652,7 +3726,10 @@ Usage:
3652
3726
  Commands:
3653
3727
  (no args) Interactive provider picker
3654
3728
  list List managed accounts for all providers
3655
- status Show current account for all providers
3729
+ status Show current active account for all providers
3730
+ add [--alias <name>] Pick provider, then add current account
3731
+ login [-- <args...>] Pick provider, then run provider login
3732
+ remove [<email>] Pick provider, then remove an account
3656
3733
  <provider> Interactive account picker for provider
3657
3734
  <provider> <alias> Switch by alias for provider
3658
3735
  <provider> list List all managed accounts
@@ -3660,18 +3737,21 @@ Commands:
3660
3737
  <provider> login [-- <args...>] Run provider login and register session
3661
3738
  <provider> remove [<email>] Remove an account
3662
3739
  <provider> next Rotate to next account
3663
- <provider> status [--json] Show current account
3740
+ <provider> status [--json] Show current active account
3664
3741
  <provider> alias <name> [<email>] Set alias for current or target account
3665
3742
  help Show this help
3666
3743
 
3667
3744
  Examples:
3668
3745
  caflip Pick provider interactively
3669
3746
  caflip list List managed accounts for Claude and Codex
3670
- caflip status Show current account for Claude and Codex
3747
+ caflip status Show current active account for Claude and Codex
3748
+ caflip add Pick provider, then add current account
3749
+ caflip login Pick provider, then run provider login
3750
+ caflip remove Pick provider, then remove an account interactively
3671
3751
  caflip claude Pick Claude account interactively
3672
3752
  caflip claude work Switch Claude account by alias
3673
3753
  caflip claude add --alias personal Add current Claude account with alias
3674
- caflip claude login Run Claude login and register session
3754
+ caflip claude login Run Claude login and register session
3675
3755
  caflip claude login -- --email me@example.com --sso
3676
3756
  Pass provider-specific flags after --
3677
3757
  caflip claude status --json Show Claude status as JSON
@@ -3681,76 +3761,7 @@ Examples:
3681
3761
  caflip codex alias work user@company.com
3682
3762
  Set Codex alias for target email`);
3683
3763
  }
3684
- async function main() {
3685
- const parsed = parseProviderArgs(process.argv.slice(2));
3686
- const provider = parsed.provider;
3687
- const args = parsed.commandArgs;
3688
- const command = args[0];
3689
- let lockHeld = false;
3690
- const runWithLock = async (fn) => {
3691
- setupDirectories();
3692
- acquireLock(activeLockDir);
3693
- lockHeld = true;
3694
- try {
3695
- return await fn();
3696
- } finally {
3697
- if (lockHeld) {
3698
- releaseLock(activeLockDir);
3699
- lockHeld = false;
3700
- }
3701
- }
3702
- };
3703
- const runWithProviderLock = async (targetProvider, fn) => {
3704
- setActiveProvider(targetProvider);
3705
- return await runWithLock(fn);
3706
- };
3707
- const isHelpCommand = command === "help" || command === "--help" || command === "-h";
3708
- const isProviderOptionalReadOnlyCommand = command === "list" || command === "status";
3709
- if (!parsed.isProviderQualified && command && !isHelpCommand && !isProviderOptionalReadOnlyCommand) {
3710
- if (RESERVED_COMMANDS.includes(command)) {
3711
- showProviderRequiredError(command);
3712
- } else {
3713
- console.error("Error: Alias requires provider prefix.");
3714
- console.error("Try: caflip claude <alias> or caflip codex <alias>");
3715
- process.exit(2);
3716
- }
3717
- }
3718
- if (!command) {
3719
- if (!provider) {
3720
- const defaultProvider = readCliMeta().lastProvider;
3721
- const selectedProvider = await pickProvider(defaultProvider);
3722
- await writeLastProvider(selectedProvider);
3723
- await runWithProviderLock(selectedProvider, async () => {
3724
- await cmdInteractiveSwitch();
3725
- });
3726
- return;
3727
- }
3728
- await runWithProviderLock(provider, async () => {
3729
- await cmdInteractiveSwitch();
3730
- });
3731
- return;
3732
- }
3733
- if (isHelpCommand) {
3734
- showHelp();
3735
- return;
3736
- }
3737
- if (!provider) {
3738
- if (command === "list") {
3739
- await cmdListAllProviders();
3740
- return;
3741
- }
3742
- if (command === "status") {
3743
- if (args.includes("--json")) {
3744
- console.error("Error: Provider is required for status --json.");
3745
- console.error("Try: caflip claude status --json");
3746
- process.exit(2);
3747
- }
3748
- await cmdStatusAllProviders();
3749
- return;
3750
- }
3751
- showProviderRequiredError(command);
3752
- }
3753
- setActiveProvider(provider);
3764
+ async function executeProviderCommand(command, args, provider, runWithLock) {
3754
3765
  switch (command) {
3755
3766
  case "list":
3756
3767
  await cmdList();
@@ -3815,6 +3826,62 @@ async function main() {
3815
3826
  }
3816
3827
  }
3817
3828
  }
3829
+ async function main() {
3830
+ const context = await resolveCliContext(parseProviderArgs(process.argv.slice(2)));
3831
+ let lockHeld = false;
3832
+ const runWithLock = async (fn) => {
3833
+ setupDirectories();
3834
+ acquireLock(activeLockDir);
3835
+ lockHeld = true;
3836
+ try {
3837
+ return await fn();
3838
+ } finally {
3839
+ if (lockHeld) {
3840
+ releaseLock(activeLockDir);
3841
+ lockHeld = false;
3842
+ }
3843
+ }
3844
+ };
3845
+ const runWithProviderLock = async (targetProvider, fn) => {
3846
+ setActiveProvider(targetProvider);
3847
+ return await runWithLock(fn);
3848
+ };
3849
+ if (context.mode === "interactive-switch") {
3850
+ if (!context.provider) {
3851
+ const defaultProvider = readCliMeta().lastProvider;
3852
+ const selectedProvider = await pickProvider(defaultProvider);
3853
+ await writeLastProvider(selectedProvider);
3854
+ await runWithProviderLock(selectedProvider, async () => {
3855
+ await cmdInteractiveSwitch();
3856
+ });
3857
+ return;
3858
+ }
3859
+ await runWithProviderLock(context.provider, async () => {
3860
+ await cmdInteractiveSwitch();
3861
+ });
3862
+ return;
3863
+ }
3864
+ if (context.mode === "all-providers") {
3865
+ if (context.command === "list") {
3866
+ await cmdListAllProviders();
3867
+ return;
3868
+ }
3869
+ if (context.command === "status") {
3870
+ if (context.args.includes("--json")) {
3871
+ console.error("Error: Provider is required for status --json.");
3872
+ console.error("Try: caflip claude status --json");
3873
+ process.exit(2);
3874
+ }
3875
+ await cmdStatusAllProviders();
3876
+ return;
3877
+ }
3878
+ }
3879
+ const provider = context.provider;
3880
+ const args = context.args;
3881
+ const command = context.command;
3882
+ setActiveProvider(provider);
3883
+ await executeProviderCommand(command, args, provider, runWithLock);
3884
+ }
3818
3885
  if (__require.main == __require.module) {
3819
3886
  main().catch((err) => {
3820
3887
  if (err instanceof PromptCancelledError) {
@@ -3826,5 +3893,8 @@ if (__require.main == __require.module) {
3826
3893
  });
3827
3894
  }
3828
3895
  export {
3896
+ supportsInteractiveProviderSelection,
3897
+ resolveProviderForCommand,
3898
+ resolveCliContext,
3829
3899
  performSwitch
3830
3900
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caflip",
3
- "version": "0.3.0",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "caflip": "bin/caflip"