caflip 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +29 -7
  2. package/dist/cli.js +455 -106
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -63,7 +63,7 @@ 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
 
@@ -110,7 +110,7 @@ After switching, restart the target CLI (Claude Code or Codex) to pick up new au
110
110
  |---|---|
111
111
  | `caflip` | Interactive provider picker (Claude/Codex) |
112
112
  | `caflip list` | List managed accounts for Claude and Codex |
113
- | `caflip status` | Show current account for Claude and Codex |
113
+ | `caflip status` | Show current active account for Claude and Codex |
114
114
  | `caflip add [--alias name]` | Pick provider, then add current account |
115
115
  | `caflip login [-- <args...>]` | Pick provider, then run provider login and register the resulting session |
116
116
  | `caflip remove [email]` | Pick provider, then remove an account |
@@ -123,8 +123,8 @@ After switching, restart the target CLI (Claude Code or Codex) to pick up new au
123
123
  | `caflip [provider] login [-- <args...>]` | Run provider login and register the resulting session |
124
124
  | `caflip [provider] remove [email]` | Remove an account |
125
125
  | `caflip [provider] next` | Rotate to next account |
126
- | `caflip [provider] status` | Show current account |
127
- | `caflip [provider] alias <name> [email]` | Set alias for current or target account |
126
+ | `caflip [provider] status` | Show current active account |
127
+ | `caflip [provider] alias <name> [account]` | Set alias for current or target account |
128
128
  | `caflip help` | Show help |
129
129
 
130
130
  ### Alias Usage
@@ -133,13 +133,31 @@ After switching, restart the target CLI (Claude Code or Codex) to pick up new au
133
133
  # Set alias for current active account
134
134
  caflip claude alias work
135
135
 
136
- # Set alias for a specific managed account
137
- caflip claude alias work hi.lucienlee@gmail.com
136
+ # Set alias by list number
137
+ caflip codex list
138
+ # 1: me@example.com · team(org-ab12Cd)
139
+ # 2: me@example.com · team(org-xy98Qw)
140
+ caflip codex alias aibor 2
141
+
142
+ # Reuse an existing alias as the target
143
+ caflip claude alias primary work
138
144
 
139
- # Codex alias
145
+ # Email works only when it matches exactly one managed account
140
146
  caflip codex alias work me@company.com
141
147
  ```
142
148
 
149
+ `<account>` accepts:
150
+ - the account number shown in `caflip [provider] list`
151
+ - an existing alias
152
+ - an email, only when that email matches exactly one managed account
153
+
154
+ If the same email exists in multiple workspaces or organizations, use the list number or an existing alias instead.
155
+
156
+ Codex display labels use provider metadata conservatively:
157
+ - workspace plans such as `team` or `business` show `email · plan(orgShortId)`
158
+ - `free` shows `email · free`
159
+ - alias is the primary human-readable name when you need your own team label
160
+
143
161
  `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
162
 
145
163
  `remove` target accepts email only. Omit it to choose from the interactive picker after selecting a provider.
@@ -153,6 +171,10 @@ caflip claude login -- --email lucien@aibor.io --sso
153
171
  caflip codex login -- --device-auth
154
172
  ```
155
173
 
174
+ `status` shows the currently active account for the selected provider. It does not list every saved account.
175
+
176
+ Use `list` when you want to inspect all managed accounts for a provider.
177
+
156
178
  ## Shell Prompt Integration
157
179
 
158
180
  Show the current account in your prompt:
package/dist/cli.js CHANGED
@@ -186,8 +186,11 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
186
186
  // src/config.ts
187
187
  import { homedir } from "os";
188
188
  import { join } from "path";
189
+ function getHomeDir() {
190
+ return process.env.HOME ?? homedir();
191
+ }
189
192
  function getBackupDir(provider) {
190
- return join(homedir(), ".caflip-backup", provider);
193
+ return join(getHomeDir(), ".caflip-backup", provider);
191
194
  }
192
195
  function getSequenceFile(provider) {
193
196
  return join(getBackupDir(provider), "sequence.json");
@@ -248,6 +251,43 @@ async function writeJsonAtomic(filePath, data) {
248
251
  }
249
252
 
250
253
  // src/accounts.ts
254
+ function getShortOrganizationId(organizationId) {
255
+ return organizationId.slice(0, 10);
256
+ }
257
+ function normalizeClaudeOrganizationName(email, organizationName) {
258
+ if (!organizationName) {
259
+ return null;
260
+ }
261
+ if (organizationName === `${email}'s Organization`) {
262
+ return "Personal";
263
+ }
264
+ return organizationName;
265
+ }
266
+ function getManagedAccountLabel(account) {
267
+ const provider = account.identity?.provider;
268
+ const organizationId = account.identity?.organizationId;
269
+ const organizationName = provider === "claude" ? normalizeClaudeOrganizationName(account.email, account.display?.organizationName) : account.display?.organizationName;
270
+ const planType = account.display?.planType;
271
+ if (provider === "codex") {
272
+ if (planType === "free") {
273
+ return `${account.email} · free`;
274
+ }
275
+ const orgShortId = organizationId ? getShortOrganizationId(organizationId) : null;
276
+ if (planType && orgShortId) {
277
+ return `${account.email} · ${planType}(${orgShortId})`;
278
+ }
279
+ if (orgShortId) {
280
+ return `${account.email} · ${orgShortId}`;
281
+ }
282
+ if (planType) {
283
+ return `${account.email} · ${planType}`;
284
+ }
285
+ }
286
+ if (organizationName) {
287
+ return `${account.email} · ${organizationName}`;
288
+ }
289
+ return account.email;
290
+ }
251
291
  async function initSequenceFile(path) {
252
292
  if (existsSync2(path))
253
293
  return;
@@ -260,7 +300,34 @@ async function initSequenceFile(path) {
260
300
  await writeJsonAtomic(path, data);
261
301
  }
262
302
  async function loadSequence(path) {
263
- return JSON.parse(readFileSync2(path, "utf-8"));
303
+ const raw = JSON.parse(readFileSync2(path, "utf-8"));
304
+ const accounts = Object.fromEntries(Object.entries(raw.accounts).map(([num, account]) => {
305
+ const display = account.display ?? {
306
+ email: account.email,
307
+ accountName: null,
308
+ organizationName: null,
309
+ planType: null,
310
+ role: null,
311
+ label: account.email
312
+ };
313
+ display.label = getManagedAccountLabel({
314
+ email: account.email,
315
+ display,
316
+ identity: account.identity
317
+ });
318
+ return [
319
+ num,
320
+ {
321
+ ...account,
322
+ display,
323
+ legacyUuid: account.legacyUuid ?? (account.identity ? undefined : account.uuid)
324
+ }
325
+ ];
326
+ }));
327
+ return {
328
+ ...raw,
329
+ accounts
330
+ };
264
331
  }
265
332
  function getNextAccountNumber(seq) {
266
333
  const keys = Object.keys(seq.accounts).map(Number);
@@ -268,8 +335,11 @@ function getNextAccountNumber(seq) {
268
335
  return 1;
269
336
  return Math.max(...keys) + 1;
270
337
  }
271
- function accountExists(seq, email) {
272
- return Object.values(seq.accounts).some((a) => a.email === email);
338
+ function accountExists(seq, identifier) {
339
+ if (typeof identifier === "string") {
340
+ return Object.values(seq.accounts).some((a) => a.email === identifier);
341
+ }
342
+ return resolveManagedAccount(seq, identifier) !== null;
273
343
  }
274
344
  function addAccountToSequence(seq, info) {
275
345
  const num = getNextAccountNumber(seq);
@@ -282,6 +352,15 @@ function addAccountToSequence(seq, info) {
282
352
  if (info.alias) {
283
353
  account.alias = info.alias;
284
354
  }
355
+ if (info.display) {
356
+ account.display = info.display;
357
+ }
358
+ if (info.identity) {
359
+ account.identity = info.identity;
360
+ }
361
+ if (info.providerMetadata) {
362
+ account.providerMetadata = info.providerMetadata;
363
+ }
285
364
  return {
286
365
  ...seq,
287
366
  accounts: { ...seq.accounts, [numStr]: account },
@@ -321,15 +400,35 @@ function getPostRemovalAction(original, updated, removedAccountNum) {
321
400
  targetAccountNumber: String(updated.activeAccountNumber)
322
401
  };
323
402
  }
324
- function resolveManagedAccountNumberForEmail(seq, currentEmail) {
325
- if (!currentEmail || currentEmail === "none") {
403
+ function resolveManagedAccountNumber(seq, currentAccount) {
404
+ const resolved = resolveManagedAccount(seq, currentAccount);
405
+ return resolved === null ? null : Number(resolved);
406
+ }
407
+ function resolveManagedAccount(seq, currentAccount) {
408
+ if (!currentAccount?.email || currentAccount.email === "none") {
326
409
  return null;
327
410
  }
328
- const accountNum = resolveAccountIdentifier(seq, currentEmail);
329
- if (!accountNum) {
411
+ if (currentAccount.uniqueKey) {
412
+ for (const [accountNum2, account2] of Object.entries(seq.accounts)) {
413
+ if (account2.identity?.uniqueKey === currentAccount.uniqueKey) {
414
+ return accountNum2;
415
+ }
416
+ }
417
+ }
418
+ const emailMatches = Object.entries(seq.accounts).filter(([, account2]) => {
419
+ if (account2.email !== currentAccount.email) {
420
+ return false;
421
+ }
422
+ if (!currentAccount.provider) {
423
+ return true;
424
+ }
425
+ return !account2.identity || account2.identity.provider === currentAccount.provider;
426
+ });
427
+ if (emailMatches.length !== 1) {
330
428
  return null;
331
429
  }
332
- return Number(accountNum);
430
+ const [accountNum, account] = emailMatches[0];
431
+ return account.identity ? null : accountNum;
333
432
  }
334
433
  function getNextInSequence(seq) {
335
434
  const currentIndex = seq.sequence.indexOf(seq.activeAccountNumber);
@@ -347,27 +446,29 @@ function resolveAccountIdentifier(seq, identifier) {
347
446
  }
348
447
  return null;
349
448
  }
350
- for (const [num, account] of Object.entries(seq.accounts)) {
351
- if (account.email === identifier)
352
- return num;
449
+ const emailMatches = Object.entries(seq.accounts).filter(([, account]) => account.email === identifier);
450
+ if (emailMatches.length === 1) {
451
+ return emailMatches[0][0];
353
452
  }
354
453
  return null;
355
454
  }
356
- function resolveAliasTargetAccount(seq, options) {
357
- if (options.identifier) {
358
- if (/^\d+$/.test(options.identifier)) {
359
- return null;
360
- }
361
- for (const [num, account] of Object.entries(seq.accounts)) {
362
- if (account.email === options.identifier)
363
- return num;
364
- }
365
- return null;
455
+ function resolveAccountTarget(seq, identifier) {
456
+ if (/^\d+$/.test(identifier)) {
457
+ const resolved = resolveAccountIdentifier(seq, identifier);
458
+ return resolved ? { status: "resolved", accountNum: resolved } : { status: "missing" };
366
459
  }
367
- if (!options.currentEmail || options.currentEmail === "none") {
368
- return null;
460
+ const aliasMatch = findAccountByAlias(seq, identifier);
461
+ if (aliasMatch) {
462
+ return { status: "resolved", accountNum: aliasMatch };
463
+ }
464
+ const emailMatches = Object.entries(seq.accounts).filter(([, account]) => account.email === identifier).map(([accountNum]) => accountNum);
465
+ if (emailMatches.length === 1) {
466
+ return { status: "resolved", accountNum: emailMatches[0] };
369
467
  }
370
- return resolveAccountIdentifier(seq, options.currentEmail);
468
+ if (emailMatches.length > 1) {
469
+ return { status: "ambiguous", matches: emailMatches };
470
+ }
471
+ return { status: "missing" };
371
472
  }
372
473
  function getDisplayAccountNumber(seq, accountNum) {
373
474
  const idx = seq.sequence.indexOf(Number(accountNum));
@@ -510,8 +611,11 @@ function isProcessAlive(pid) {
510
611
  import { homedir as homedir2 } from "os";
511
612
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
512
613
  import { join as join4 } from "path";
614
+ function getHomeDir2() {
615
+ return process.env.HOME ?? homedir2();
616
+ }
513
617
  function getBackupDir2(provider) {
514
- return join4(homedir2(), ".caflip-backup", provider);
618
+ return join4(getHomeDir2(), ".caflip-backup", provider);
515
619
  }
516
620
  function getSequenceFile2(provider) {
517
621
  return join4(getBackupDir2(provider), "sequence.json");
@@ -554,14 +658,14 @@ function detectPlatform() {
554
658
  return "unknown";
555
659
  }
556
660
  }
557
- function getClaudeConfigDir(env = process.env, home = homedir2()) {
661
+ function getClaudeConfigDir(env = process.env, home = env.HOME ?? homedir2()) {
558
662
  const customDir = env.CLAUDE_CONFIG_DIR?.trim();
559
663
  if (customDir) {
560
664
  return customDir;
561
665
  }
562
666
  return join4(home, ".claude");
563
667
  }
564
- function getClaudeConfigPath(env = process.env, home = homedir2()) {
668
+ function getClaudeConfigPath(env = process.env, home = env.HOME ?? homedir2()) {
565
669
  const customDir = env.CLAUDE_CONFIG_DIR?.trim();
566
670
  if (customDir) {
567
671
  return join4(customDir, ".claude.json");
@@ -617,7 +721,7 @@ function validateAlias(alias) {
617
721
  // package.json
618
722
  var package_default = {
619
723
  name: "caflip",
620
- version: "0.3.1",
724
+ version: "0.4.0",
621
725
  type: "module",
622
726
  bin: {
623
727
  caflip: "bin/caflip"
@@ -2258,6 +2362,45 @@ var dist_default5 = createPrompt((config, done) => {
2258
2362
  `).trimEnd();
2259
2363
  return `${lines}${cursorHide}`;
2260
2364
  });
2365
+ // src/accounts.ts
2366
+ function getShortOrganizationId2(organizationId) {
2367
+ return organizationId.slice(0, 10);
2368
+ }
2369
+ function normalizeClaudeOrganizationName2(email, organizationName) {
2370
+ if (!organizationName) {
2371
+ return null;
2372
+ }
2373
+ if (organizationName === `${email}'s Organization`) {
2374
+ return "Personal";
2375
+ }
2376
+ return organizationName;
2377
+ }
2378
+ function getManagedAccountLabel2(account) {
2379
+ const provider = account.identity?.provider;
2380
+ const organizationId = account.identity?.organizationId;
2381
+ const organizationName = provider === "claude" ? normalizeClaudeOrganizationName2(account.email, account.display?.organizationName) : account.display?.organizationName;
2382
+ const planType = account.display?.planType;
2383
+ if (provider === "codex") {
2384
+ if (planType === "free") {
2385
+ return `${account.email} · free`;
2386
+ }
2387
+ const orgShortId = organizationId ? getShortOrganizationId2(organizationId) : null;
2388
+ if (planType && orgShortId) {
2389
+ return `${account.email} · ${planType}(${orgShortId})`;
2390
+ }
2391
+ if (orgShortId) {
2392
+ return `${account.email} · ${orgShortId}`;
2393
+ }
2394
+ if (planType) {
2395
+ return `${account.email} · ${planType}`;
2396
+ }
2397
+ }
2398
+ if (organizationName) {
2399
+ return `${account.email} · ${organizationName}`;
2400
+ }
2401
+ return account.email;
2402
+ }
2403
+
2261
2404
  // src/interactive.ts
2262
2405
  class PromptCancelledError extends Error {
2263
2406
  constructor() {
@@ -2303,13 +2446,13 @@ async function wrapPromptCancellation(fn) {
2303
2446
  cleanup();
2304
2447
  }
2305
2448
  }
2306
- function formatAccount(num, email, alias, isActive) {
2307
- let label = `${num}: ${email}`;
2449
+ function formatAccount(num, label, alias, isActive) {
2450
+ let formatted = `${num}: ${label}`;
2308
2451
  if (alias)
2309
- label += ` [${alias}]`;
2452
+ formatted += ` [${alias}]`;
2310
2453
  if (isActive)
2311
- label += " (active)";
2312
- return label;
2454
+ formatted += " (active)";
2455
+ return formatted;
2313
2456
  }
2314
2457
  async function pickAccount(seq, message = "Switch to account:", promptSelect = dist_default5, extraChoices = []) {
2315
2458
  const choices = [
@@ -2321,7 +2464,7 @@ async function pickAccount(seq, message = "Switch to account:", promptSelect = d
2321
2464
  }
2322
2465
  const isActive = num === seq.activeAccountNumber;
2323
2466
  return {
2324
- name: formatAccount(String(index + 1), account.email, account.alias, isActive),
2467
+ name: formatAccount(String(index + 1), getManagedAccountLabel2(account), account.alias, isActive),
2325
2468
  value: numStr
2326
2469
  };
2327
2470
  }),
@@ -2482,14 +2625,14 @@ function activeSecretToolAttrs() {
2482
2625
  function backupSecretToolAttrs(accountNum, email) {
2483
2626
  return ["service", "ccflip", "account", accountNum, "email", email];
2484
2627
  }
2485
- function getClaudeCredentialsDir(env = process.env, home = homedir3()) {
2628
+ function getClaudeCredentialsDir(env = process.env, home = env.HOME ?? homedir3()) {
2486
2629
  const customDir = env.CLAUDE_CONFIG_DIR?.trim();
2487
2630
  if (customDir) {
2488
2631
  return customDir;
2489
2632
  }
2490
2633
  return join5(home, ".claude");
2491
2634
  }
2492
- function getClaudeCredentialsPath(env = process.env, home = homedir3()) {
2635
+ function getClaudeCredentialsPath(env = process.env, home = env.HOME ?? homedir3()) {
2493
2636
  return join5(getClaudeCredentialsDir(env, home), ".credentials.json");
2494
2637
  }
2495
2638
  async function secretToolLookup(attrs) {
@@ -2818,7 +2961,21 @@ function getClaudeCurrentAccount() {
2818
2961
  return null;
2819
2962
  }
2820
2963
  const accountId = typeof content?.oauthAccount?.accountUuid === "string" ? content.oauthAccount.accountUuid : undefined;
2821
- return { email, accountId };
2964
+ const organizationId = typeof content?.oauthAccount?.organizationUuid === "string" ? content.oauthAccount.organizationUuid : undefined;
2965
+ const organizationName = typeof content?.oauthAccount?.organizationName === "string" ? content.oauthAccount.organizationName : undefined;
2966
+ const workspaceRole = typeof content?.oauthAccount?.workspaceRole === "string" ? content.oauthAccount.workspaceRole : undefined;
2967
+ const organizationRole = typeof content?.oauthAccount?.organizationRole === "string" ? content.oauthAccount.organizationRole : undefined;
2968
+ const accountName = typeof content?.oauthAccount?.displayName === "string" ? content.oauthAccount.displayName : undefined;
2969
+ return {
2970
+ email,
2971
+ accountId,
2972
+ organizationId,
2973
+ organizationName,
2974
+ role: workspaceRole ?? organizationRole,
2975
+ accountName,
2976
+ uniqueKey: accountId && organizationId ? `claude:${accountId}:${organizationId}` : undefined,
2977
+ identityStatus: accountId && organizationId ? "resolved" : "partial"
2978
+ };
2822
2979
  }
2823
2980
  function getClaudeCurrentAccountEmail() {
2824
2981
  return getClaudeCurrentAccount()?.email ?? "none";
@@ -3012,35 +3169,50 @@ async function deleteCodexAccountAuthBackup(accountNum, email, credentialsDir) {
3012
3169
  const backupPath = join6(credentialsDir, `.codex-auth-${accountNum}-${email}.json`);
3013
3170
  rmSync4(backupPath, { force: true });
3014
3171
  }
3015
- function getCodexCurrentAccount() {
3172
+ function resolveCodexCurrentAccount() {
3016
3173
  const authPath = getCodexAuthPath();
3017
3174
  if (!existsSync7(authPath)) {
3018
- return null;
3175
+ return { account: null, ambiguousOrganization: false };
3019
3176
  }
3020
3177
  try {
3021
3178
  const authObj = JSON.parse(readFileSync7(authPath, "utf-8"));
3022
3179
  const idToken = authObj.tokens?.id_token;
3023
3180
  if (!idToken) {
3024
- return null;
3181
+ return { account: null, ambiguousOrganization: false };
3025
3182
  }
3026
3183
  const payload = decodeJwtPayload(idToken);
3027
3184
  if (!payload) {
3028
- return null;
3185
+ return { account: null, ambiguousOrganization: false };
3029
3186
  }
3030
3187
  const email = typeof payload.email === "string" ? payload.email : null;
3031
3188
  if (!email) {
3032
- return null;
3189
+ return { account: null, ambiguousOrganization: false };
3033
3190
  }
3034
3191
  const authPayload = payload["https://api.openai.com/auth"];
3035
3192
  const accountId = authPayload?.chatgpt_account_id ?? authObj.tokens?.account_id;
3193
+ const organizations = Array.isArray(authPayload?.organizations) ? authPayload.organizations : [];
3194
+ const organization = organizations.find((candidate) => candidate.is_default === true) ?? (organizations.length === 1 ? organizations[0] : undefined);
3195
+ const ambiguousOrganization = organizations.length > 1 && !organization;
3036
3196
  return {
3037
- email,
3038
- accountId
3197
+ ambiguousOrganization,
3198
+ account: {
3199
+ email,
3200
+ accountId,
3201
+ organizationId: organization?.id,
3202
+ organizationName: organization?.title,
3203
+ planType: authPayload?.chatgpt_plan_type,
3204
+ role: organization?.role,
3205
+ uniqueKey: accountId && organization?.id ? `codex:${accountId}:${organization.id}` : undefined,
3206
+ identityStatus: ambiguousOrganization ? "ambiguous" : accountId && organization?.id ? "resolved" : "partial"
3207
+ }
3039
3208
  };
3040
3209
  } catch {
3041
- return null;
3210
+ return { account: null, ambiguousOrganization: false };
3042
3211
  }
3043
3212
  }
3213
+ function getCodexCurrentAccount() {
3214
+ return resolveCodexCurrentAccount().account;
3215
+ }
3044
3216
  function readCodexAuthFile() {
3045
3217
  const authPath = getCodexAuthPath();
3046
3218
  if (!existsSync7(authPath)) {
@@ -3085,17 +3257,28 @@ async function verifyCodexLogin(commandRunner = runCapturedCommand) {
3085
3257
  }
3086
3258
  };
3087
3259
  }
3088
- const currentAccount = getCodexCurrentAccount();
3260
+ const { account: currentAccount, ambiguousOrganization } = resolveCodexCurrentAccount();
3089
3261
  if (!currentAccount?.email) {
3090
3262
  return {
3091
3263
  ok: false,
3092
3264
  reason: "codex auth file did not resolve a current account email"
3093
3265
  };
3094
3266
  }
3267
+ if (ambiguousOrganization) {
3268
+ return {
3269
+ ok: false,
3270
+ reason: "codex login resolved an ambiguous workspace context: multiple organizations without a default workspace"
3271
+ };
3272
+ }
3095
3273
  return {
3096
3274
  ok: true,
3097
3275
  email: currentAccount.email,
3098
- details: currentAccount.accountId ? { accountId: currentAccount.accountId } : undefined
3276
+ details: {
3277
+ accountId: currentAccount.accountId,
3278
+ organizationId: currentAccount.organizationId,
3279
+ organizationName: currentAccount.organizationName,
3280
+ planType: currentAccount.planType
3281
+ }
3099
3282
  };
3100
3283
  }
3101
3284
  var codexLoginAdapter = {
@@ -3186,12 +3369,115 @@ function setupDirectories() {
3186
3369
  mkdirSync6(dir, { recursive: true, mode: 448 });
3187
3370
  }
3188
3371
  }
3372
+ function getCurrentAccountIdentity() {
3373
+ return activeProvider.getCurrentAccount();
3374
+ }
3189
3375
  function getCurrentAccount() {
3190
- return activeProvider.getCurrentAccountEmail();
3376
+ return getCurrentAccountIdentity()?.email ?? "none";
3191
3377
  }
3192
3378
  function getProviderLabel() {
3193
3379
  return activeProvider.name === "codex" ? "Codex" : "Claude Code";
3194
3380
  }
3381
+ function hasMultipleProviderEmailMatches(seq, email, provider) {
3382
+ return Object.values(seq.accounts).filter((account) => {
3383
+ if (account.email !== email) {
3384
+ return false;
3385
+ }
3386
+ return !account.identity || account.identity.provider === provider;
3387
+ }).length > 1;
3388
+ }
3389
+ function resolveCurrentManagedAccountForSwitch(seq, currentIdentity, currentEmail) {
3390
+ if (currentIdentity) {
3391
+ const resolved = resolveManagedAccount(seq, currentIdentity);
3392
+ if (resolved) {
3393
+ return resolved;
3394
+ }
3395
+ const providerEmailMatchCount = Object.values(seq.accounts).filter((account) => {
3396
+ if (account.email !== currentIdentity.email) {
3397
+ return false;
3398
+ }
3399
+ return !account.identity || account.identity.provider === activeProvider.name;
3400
+ }).length;
3401
+ if (currentIdentity.identityStatus === "ambiguous") {
3402
+ throw new Error(`Cannot determine which managed account is currently active for ${currentIdentity.email}.`);
3403
+ }
3404
+ if (currentIdentity.email !== "none" && providerEmailMatchCount > 0) {
3405
+ throw new Error(`Cannot determine which managed account is currently active for ${currentIdentity.email}.`);
3406
+ }
3407
+ return null;
3408
+ }
3409
+ if (currentEmail === "none") {
3410
+ return null;
3411
+ }
3412
+ return resolveAccountIdentifier(seq, currentEmail);
3413
+ }
3414
+ function getAccountDisplayLabel(account) {
3415
+ return getManagedAccountLabel(account);
3416
+ }
3417
+ function getCurrentAccountDisplayLabel(currentAccount) {
3418
+ if (!currentAccount) {
3419
+ return "none";
3420
+ }
3421
+ return getManagedAccountLabel({
3422
+ email: currentAccount.email,
3423
+ display: {
3424
+ email: currentAccount.email,
3425
+ accountName: currentAccount.accountName ?? null,
3426
+ organizationName: currentAccount.organizationName ?? null,
3427
+ planType: currentAccount.planType ?? null,
3428
+ role: currentAccount.role ?? null,
3429
+ label: ""
3430
+ },
3431
+ identity: {
3432
+ provider: activeProvider.name,
3433
+ accountId: currentAccount.accountId ?? null,
3434
+ organizationId: currentAccount.organizationId ?? null,
3435
+ uniqueKey: ""
3436
+ }
3437
+ });
3438
+ }
3439
+ function buildManagedAccountDetails(currentAccount) {
3440
+ if (!currentAccount) {
3441
+ return {
3442
+ email: "none",
3443
+ uuid: "",
3444
+ display: {
3445
+ email: "none",
3446
+ accountName: null,
3447
+ organizationName: null,
3448
+ planType: null,
3449
+ role: null,
3450
+ label: "none"
3451
+ },
3452
+ identity: undefined,
3453
+ providerMetadata: undefined
3454
+ };
3455
+ }
3456
+ return {
3457
+ email: currentAccount.email,
3458
+ uuid: currentAccount.accountId ?? currentAccount.uniqueKey ?? "",
3459
+ display: {
3460
+ email: currentAccount.email,
3461
+ accountName: currentAccount.accountName ?? null,
3462
+ organizationName: currentAccount.organizationName ?? null,
3463
+ planType: currentAccount.planType ?? null,
3464
+ role: currentAccount.role ?? null,
3465
+ label: getCurrentAccountDisplayLabel(currentAccount)
3466
+ },
3467
+ identity: currentAccount.uniqueKey ? {
3468
+ provider: activeProvider.name,
3469
+ accountId: currentAccount.accountId ?? null,
3470
+ organizationId: currentAccount.organizationId ?? null,
3471
+ uniqueKey: currentAccount.uniqueKey
3472
+ } : undefined,
3473
+ providerMetadata: {
3474
+ organizationName: currentAccount.organizationName ?? null,
3475
+ planType: currentAccount.planType ?? null,
3476
+ role: currentAccount.role ?? null,
3477
+ accountName: currentAccount.accountName ?? null
3478
+ }
3479
+ };
3480
+ }
3195
3481
  function showProviderRequiredError(command) {
3196
3482
  console.error(`Error: ${command} requires provider prefix.`);
3197
3483
  console.error(`Try: caflip claude ${command} or caflip codex ${command}`);
@@ -3268,8 +3554,7 @@ async function resolveCliContext(parsed, deps = { resolveProviderForCommand }) {
3268
3554
  };
3269
3555
  }
3270
3556
  async function syncSequenceActiveAccount(seq) {
3271
- const currentEmail = getCurrentAccount();
3272
- const resolvedActive = resolveManagedAccountNumberForEmail(seq, currentEmail);
3557
+ const resolvedActive = resolveManagedAccountNumber(seq, getCurrentAccountIdentity());
3273
3558
  if (seq.activeAccountNumber !== resolvedActive) {
3274
3559
  seq.activeAccountNumber = resolvedActive;
3275
3560
  seq.lastUpdated = new Date().toISOString();
@@ -3289,22 +3574,26 @@ async function registerCurrentActiveAccount(options) {
3289
3574
  if (options?.expectedEmail && currentEmail !== options.expectedEmail) {
3290
3575
  throw new Error(`Active ${getProviderLabel()} account changed during login verification: expected ${options.expectedEmail}, got ${currentEmail}`);
3291
3576
  }
3577
+ if (currentAccount?.identityStatus === "ambiguous") {
3578
+ throw new Error(`${getProviderLabel()} current account is in an ambiguous workspace context. Please pick a single workspace first, then retry.`);
3579
+ }
3292
3580
  setupDirectories();
3293
3581
  await initSequenceFile(activeSequenceFile);
3294
3582
  const seq = await loadSequence(activeSequenceFile);
3295
3583
  await syncSequenceActiveAccount(seq);
3584
+ const currentAccountNum = resolveManagedAccount(seq, currentAccount);
3585
+ const currentDetails = buildManagedAccountDetails(currentAccount);
3296
3586
  if (options?.alias) {
3297
3587
  const result = validateAlias(options.alias);
3298
3588
  if (!result.valid) {
3299
3589
  throw new Error(result.reason);
3300
3590
  }
3301
3591
  const existingAliasTarget = findAccountByAlias(seq, options.alias);
3302
- const currentAccountNum = resolveAccountIdentifier(seq, currentEmail);
3303
3592
  if (existingAliasTarget && existingAliasTarget !== currentAccountNum) {
3304
3593
  throw new Error(`Alias "${options.alias}" is already in use`);
3305
3594
  }
3306
3595
  }
3307
- const existingAccountNum = resolveAccountIdentifier(seq, currentEmail);
3596
+ const existingAccountNum = currentAccountNum;
3308
3597
  if (existingAccountNum) {
3309
3598
  if (!options?.updateIfExists) {
3310
3599
  console.log(`Account ${currentEmail} is already managed.`);
@@ -3333,7 +3622,11 @@ async function registerCurrentActiveAccount(options) {
3333
3622
  ...seq.accounts,
3334
3623
  [existingAccountNum]: {
3335
3624
  ...seq.accounts[existingAccountNum],
3625
+ email: currentEmail,
3336
3626
  uuid,
3627
+ identity: currentDetails.identity ?? seq.accounts[existingAccountNum].identity,
3628
+ display: currentDetails.display,
3629
+ providerMetadata: currentDetails.providerMetadata ?? seq.accounts[existingAccountNum].providerMetadata,
3337
3630
  ...options?.alias ? { alias: options.alias } : {}
3338
3631
  }
3339
3632
  }
@@ -3352,7 +3645,10 @@ async function registerCurrentActiveAccount(options) {
3352
3645
  const updated = addAccountToSequence(seq, {
3353
3646
  email: currentEmail,
3354
3647
  uuid,
3355
- alias: options?.alias
3648
+ alias: options?.alias,
3649
+ identity: currentDetails.identity,
3650
+ display: currentDetails.display,
3651
+ providerMetadata: currentDetails.providerMetadata
3356
3652
  });
3357
3653
  const accountNum = String(updated.activeAccountNumber);
3358
3654
  await activeProvider.writeAccountAuth(accountNum, currentEmail, creds, activeCredentialsDir);
@@ -3380,17 +3676,27 @@ function getLoginPassthroughArgs(args) {
3380
3676
  return args.slice(passthroughIdx + 1);
3381
3677
  }
3382
3678
  async function performSwitch(seq, targetAccount, options) {
3679
+ const targetProvider = seq.accounts[targetAccount].identity?.provider;
3680
+ if (targetProvider) {
3681
+ setActiveProvider(targetProvider);
3682
+ }
3683
+ const providerName = targetProvider ?? activeProvider.name;
3684
+ const sequenceFile = getSequenceFile(providerName);
3685
+ const credentialsDir = getCredentialsDir(providerName);
3686
+ const configsDir = getConfigsDir(providerName);
3383
3687
  const targetEmail = seq.accounts[targetAccount].email;
3384
- const currentEmail = options?.currentEmail ?? getCurrentAccount();
3385
- const currentAccount = currentEmail === "none" ? null : resolveAccountIdentifier(seq, currentEmail);
3386
- if (currentEmail === targetEmail) {
3688
+ const observedCurrentIdentity = getCurrentAccountIdentity();
3689
+ const currentIdentity = options?.currentEmail && observedCurrentIdentity && observedCurrentIdentity.email !== options.currentEmail ? null : observedCurrentIdentity;
3690
+ const currentEmail = options?.currentEmail ?? currentIdentity?.email ?? "none";
3691
+ const currentAccount = resolveCurrentManagedAccountForSwitch(seq, currentIdentity, currentEmail);
3692
+ if (currentAccount === targetAccount) {
3387
3693
  const account = seq.accounts[targetAccount];
3388
3694
  const aliasStr2 = account.alias ? ` [${account.alias}]` : "";
3389
3695
  const displayLabel2 = getDisplayAccountLabel(seq, targetAccount);
3390
3696
  if (seq.activeAccountNumber !== Number(targetAccount)) {
3391
3697
  seq.activeAccountNumber = Number(targetAccount);
3392
3698
  seq.lastUpdated = new Date().toISOString();
3393
- await writeJsonAtomic2(activeSequenceFile, seq);
3699
+ await writeJsonAtomic2(sequenceFile, seq);
3394
3700
  }
3395
3701
  console.log(`Already using ${displayLabel2} (${account.email})${aliasStr2}`);
3396
3702
  return;
@@ -3404,17 +3710,17 @@ async function performSwitch(seq, targetAccount, options) {
3404
3710
  if (currentEmail !== "none" && currentAccount) {
3405
3711
  const currentCreds = await activeProvider.readActiveAuth();
3406
3712
  if (currentCreds) {
3407
- await activeProvider.writeAccountAuth(currentAccount, currentEmail, currentCreds, activeCredentialsDir);
3713
+ await activeProvider.writeAccountAuth(currentAccount, currentEmail, currentCreds, credentialsDir);
3408
3714
  }
3409
3715
  if (activeProvider.usesAccountConfig) {
3410
3716
  const currentConfig = await activeProvider.readActiveConfig();
3411
3717
  if (currentConfig) {
3412
- await activeProvider.writeAccountConfig(currentAccount, currentEmail, currentConfig, activeConfigsDir);
3718
+ await activeProvider.writeAccountConfig(currentAccount, currentEmail, currentConfig, configsDir);
3413
3719
  }
3414
3720
  }
3415
3721
  }
3416
- const targetCreds = await activeProvider.readAccountAuth(targetAccount, targetEmail, activeCredentialsDir);
3417
- const targetConfig = activeProvider.readAccountConfig(targetAccount, targetEmail, activeConfigsDir);
3722
+ const targetCreds = await activeProvider.readAccountAuth(targetAccount, targetEmail, credentialsDir);
3723
+ const targetConfig = activeProvider.readAccountConfig(targetAccount, targetEmail, configsDir);
3418
3724
  if (!targetCreds) {
3419
3725
  throw new Error(`Missing backup data for ${getDisplayAccountLabel(seq, targetAccount)}`);
3420
3726
  }
@@ -3427,10 +3733,23 @@ async function performSwitch(seq, targetAccount, options) {
3427
3733
  }
3428
3734
  seq.activeAccountNumber = Number(targetAccount);
3429
3735
  seq.lastUpdated = new Date().toISOString();
3430
- await writeJsonAtomic2(activeSequenceFile, seq);
3736
+ await writeJsonAtomic2(sequenceFile, seq);
3431
3737
  const alias = seq.accounts[targetAccount].alias;
3432
3738
  const aliasStr = alias ? ` [${alias}]` : "";
3433
3739
  const displayLabel = getDisplayAccountLabel(seq, targetAccount);
3740
+ const refreshedAccount = activeProvider.getCurrentAccount();
3741
+ if (refreshedAccount) {
3742
+ const refreshedDetails = buildManagedAccountDetails(refreshedAccount);
3743
+ seq.accounts[targetAccount] = {
3744
+ ...seq.accounts[targetAccount],
3745
+ email: refreshedDetails.email,
3746
+ uuid: refreshedDetails.uuid,
3747
+ display: refreshedDetails.display,
3748
+ identity: refreshedDetails.identity ?? seq.accounts[targetAccount].identity,
3749
+ providerMetadata: refreshedDetails.providerMetadata ?? seq.accounts[targetAccount].providerMetadata
3750
+ };
3751
+ await writeJsonAtomic2(sequenceFile, seq);
3752
+ }
3434
3753
  console.log(`Switched to ${displayLabel} (${targetEmail})${aliasStr}`);
3435
3754
  console.log(`
3436
3755
  Please restart ${getProviderLabel()} to use the new authentication.
@@ -3454,15 +3773,16 @@ async function getManagedAccountLinesForActiveProvider() {
3454
3773
  }
3455
3774
  const seq = await loadSequence(activeSequenceFile);
3456
3775
  await syncSequenceActiveAccount(seq);
3457
- const currentEmail = getCurrentAccount();
3776
+ const currentAccount = getCurrentAccountIdentity();
3777
+ const activeAccountNum = resolveManagedAccount(seq, currentAccount);
3458
3778
  return seq.sequence.map((num, index) => {
3459
3779
  const numStr = String(num);
3460
3780
  const account = seq.accounts[numStr];
3461
3781
  if (!account) {
3462
3782
  throw new Error(`Corrupt sequence data: missing account entry for id ${numStr}`);
3463
3783
  }
3464
- const isActive = account.email === currentEmail;
3465
- let line = ` ${index + 1}: ${account.email}`;
3784
+ const isActive = activeAccountNum === numStr;
3785
+ let line = ` ${index + 1}: ${getAccountDisplayLabel(account)}`;
3466
3786
  if (account.alias)
3467
3787
  line += ` [${account.alias}]`;
3468
3788
  if (isActive)
@@ -3566,6 +3886,8 @@ async function cmdStatus(options) {
3566
3886
  console.log(JSON.stringify({
3567
3887
  provider: activeProvider.name,
3568
3888
  email: summary.email === "none" ? null : summary.email,
3889
+ label: summary.email === "none" ? null : summary.label,
3890
+ organizationName: summary.organizationName,
3569
3891
  alias: summary.alias,
3570
3892
  managed: summary.managed
3571
3893
  }));
@@ -3573,29 +3895,37 @@ async function cmdStatus(options) {
3573
3895
  }
3574
3896
  if (summary.email === "none") {
3575
3897
  console.log("none");
3898
+ } else if (summary.alias) {
3899
+ console.log(`${summary.label} [${summary.alias}]`);
3576
3900
  } else {
3577
- if (summary.alias) {
3578
- console.log(`${summary.email} [${summary.alias}]`);
3579
- return;
3580
- }
3581
- console.log(summary.email);
3901
+ console.log(summary.label);
3582
3902
  }
3903
+ console.log(`managed accounts: ${summary.managedCount}`);
3583
3904
  }
3584
3905
  async function getStatusSummaryForActiveProvider() {
3585
- const email = getCurrentAccount();
3906
+ const currentAccount = getCurrentAccountIdentity();
3907
+ const email = currentAccount?.email ?? "none";
3586
3908
  let alias = null;
3587
3909
  let managed = false;
3910
+ let managedCount = 0;
3911
+ let label = getCurrentAccountDisplayLabel(currentAccount);
3912
+ let organizationName = currentAccount?.organizationName ?? null;
3588
3913
  if (email !== "none" && existsSync9(activeSequenceFile)) {
3589
3914
  const seq = await loadSequence(activeSequenceFile);
3590
- for (const account of Object.values(seq.accounts)) {
3591
- if (account.email === email) {
3592
- managed = true;
3593
- alias = account.alias ?? null;
3594
- break;
3595
- }
3596
- }
3915
+ managedCount = Object.keys(seq.accounts).length;
3916
+ const matchedAccountNum = resolveManagedAccount(seq, currentAccount);
3917
+ if (matchedAccountNum) {
3918
+ const account = seq.accounts[matchedAccountNum];
3919
+ managed = true;
3920
+ alias = account.alias ?? null;
3921
+ label = getAccountDisplayLabel(account);
3922
+ organizationName = account.display?.organizationName ?? organizationName;
3923
+ }
3924
+ } else if (existsSync9(activeSequenceFile)) {
3925
+ const seq = await loadSequence(activeSequenceFile);
3926
+ managedCount = Object.keys(seq.accounts).length;
3597
3927
  }
3598
- return { email, alias, managed };
3928
+ return { email, alias, managed, managedCount, label, organizationName };
3599
3929
  }
3600
3930
  async function withActiveProvider(provider, fn) {
3601
3931
  const previousProvider = activeProvider.name;
@@ -3645,13 +3975,12 @@ async function cmdStatusAllProviders() {
3645
3975
  console.log(summary.heading);
3646
3976
  if (summary.email === "none") {
3647
3977
  console.log(" none");
3648
- continue;
3649
- }
3650
- if (summary.alias) {
3651
- console.log(` ${summary.email} [${summary.alias}]`);
3652
- continue;
3978
+ } else if (summary.alias) {
3979
+ console.log(` ${summary.label} [${summary.alias}]`);
3980
+ } else {
3981
+ console.log(` ${summary.label}`);
3653
3982
  }
3654
- console.log(` ${summary.email}`);
3983
+ console.log(` managed accounts: ${summary.managedCount}`);
3655
3984
  }
3656
3985
  }
3657
3986
  async function cmdAlias(alias, identifier) {
@@ -3663,19 +3992,31 @@ async function cmdAlias(alias, identifier) {
3663
3992
  throw new Error(result.reason);
3664
3993
  }
3665
3994
  const seq = await loadSequence(activeSequenceFile);
3666
- if (identifier && /^\d+$/.test(identifier)) {
3667
- throw new Error("Alias target must be an email, not a number");
3668
- }
3669
3995
  const currentEmail = getCurrentAccount();
3670
- const accountNum = resolveAliasTargetAccount(seq, { identifier, currentEmail });
3671
- if (!accountNum) {
3672
- if (identifier) {
3996
+ const currentIdentity = getCurrentAccountIdentity();
3997
+ let accountNum = null;
3998
+ if (identifier) {
3999
+ const target = resolveAccountTarget(seq, identifier);
4000
+ if (target.status === "ambiguous") {
4001
+ throw new Error(`Multiple managed accounts match ${identifier}. Use account number or alias.`);
4002
+ }
4003
+ if (target.status === "missing") {
3673
4004
  throw new Error(`Account not found: ${identifier}`);
3674
- } else if (currentEmail === "none") {
4005
+ }
4006
+ accountNum = target.accountNum;
4007
+ } else {
4008
+ accountNum = resolveManagedAccount(seq, currentIdentity);
4009
+ if (!accountNum && currentIdentity?.email && currentIdentity.email !== "none") {
4010
+ if (hasMultipleProviderEmailMatches(seq, currentIdentity.email, activeProvider.name)) {
4011
+ throw new Error(`Multiple managed accounts match ${currentIdentity.email}. Use account number or alias.`);
4012
+ }
4013
+ }
4014
+ }
4015
+ if (!accountNum) {
4016
+ if (currentEmail === "none") {
3675
4017
  throw new Error(`No active ${getProviderLabel()} account found. Please log in first.`);
3676
- } else {
3677
- throw new Error(`Current account is not managed: ${currentEmail}`);
3678
4018
  }
4019
+ throw new Error(`Current account is not managed: ${currentEmail}`);
3679
4020
  }
3680
4021
  const updated = setAlias(seq, accountNum, alias);
3681
4022
  await writeJsonAtomic2(activeSequenceFile, updated);
@@ -3683,7 +4024,9 @@ async function cmdAlias(alias, identifier) {
3683
4024
  console.log(`Alias "${alias}" set for ${getDisplayAccountLabel(updated, accountNum)} (${account.email})`);
3684
4025
  }
3685
4026
  async function cmdInteractiveSwitch() {
3686
- const currentEmail = getCurrentAccount();
4027
+ const currentIdentity = getCurrentAccountIdentity();
4028
+ const currentEmail = currentIdentity?.email ?? "none";
4029
+ const currentLabel = getCurrentAccountDisplayLabel(currentIdentity);
3687
4030
  const hasSequence = existsSync9(activeSequenceFile);
3688
4031
  const seq = hasSequence ? await loadSequence(activeSequenceFile) : null;
3689
4032
  if (seq) {
@@ -3692,7 +4035,7 @@ async function cmdInteractiveSwitch() {
3692
4035
  if (!seq || seq.sequence.length === 0) {
3693
4036
  const emptyStateChoices = [
3694
4037
  {
3695
- name: `+ Add current logged-in account${currentEmail === "none" ? "" : ` (${currentEmail})`}`,
4038
+ name: `+ Add current logged-in account${currentEmail === "none" ? "" : ` (${currentLabel})`}`,
3696
4039
  value: ADD_CURRENT_ACCOUNT_CHOICE
3697
4040
  },
3698
4041
  { name: "Back", value: "__back__" }
@@ -3704,8 +4047,8 @@ async function cmdInteractiveSwitch() {
3704
4047
  await cmdAdd();
3705
4048
  return;
3706
4049
  }
3707
- const shouldOfferAddCurrent = currentEmail !== "none" && !accountExists(seq, currentEmail);
3708
- const extraChoices = shouldOfferAddCurrent ? [{ name: `+ Add current logged-in account (${currentEmail})`, value: ADD_CURRENT_ACCOUNT_CHOICE }] : [];
4050
+ const shouldOfferAddCurrent = currentEmail !== "none" && currentIdentity?.identityStatus !== "ambiguous" && !accountExists(seq, currentIdentity ?? currentEmail);
4051
+ const extraChoices = shouldOfferAddCurrent ? [{ name: `+ Add current logged-in account (${currentLabel})`, value: ADD_CURRENT_ACCOUNT_CHOICE }] : [];
3709
4052
  const selected = await pickAccount(seq, `caflip v${package_default.version} \u2014 Switch ${getProviderLabel()} account:`, undefined, extraChoices);
3710
4053
  if (selected === ADD_CURRENT_ACCOUNT_CHOICE) {
3711
4054
  await cmdAdd();
@@ -3723,7 +4066,7 @@ Usage:
3723
4066
  Commands:
3724
4067
  (no args) Interactive provider picker
3725
4068
  list List managed accounts for all providers
3726
- status Show current account for all providers
4069
+ status Show current active account for all providers
3727
4070
  add [--alias <name>] Pick provider, then add current account
3728
4071
  login [-- <args...>] Pick provider, then run provider login
3729
4072
  remove [<email>] Pick provider, then remove an account
@@ -3734,20 +4077,22 @@ Commands:
3734
4077
  <provider> login [-- <args...>] Run provider login and register session
3735
4078
  <provider> remove [<email>] Remove an account
3736
4079
  <provider> next Rotate to next account
3737
- <provider> status [--json] Show current account
3738
- <provider> alias <name> [<email>] Set alias for current or target account
4080
+ <provider> status [--json] Show current active account
4081
+ <provider> alias <name> [<account>] Set alias for current or target account
3739
4082
  help Show this help
3740
4083
 
3741
4084
  Examples:
3742
4085
  caflip Pick provider interactively
3743
4086
  caflip list List managed accounts for Claude and Codex
3744
- caflip status Show current account for Claude and Codex
4087
+ caflip status Show current active account for Claude and Codex
3745
4088
  caflip add Pick provider, then add current account
3746
4089
  caflip login Pick provider, then run provider login
3747
4090
  caflip remove Pick provider, then remove an account interactively
3748
4091
  caflip claude Pick Claude account interactively
3749
4092
  caflip claude work Switch Claude account by alias
3750
4093
  caflip claude add --alias personal Add current Claude account with alias
4094
+ caflip claude alias work Alias the current Claude account as "work"
4095
+ caflip claude alias work 2 Alias Claude Account-2 as "work"
3751
4096
  caflip claude login Run Claude login and register session
3752
4097
  caflip claude login -- --email me@example.com --sso
3753
4098
  Pass provider-specific flags after --
@@ -3755,8 +4100,12 @@ Examples:
3755
4100
  caflip codex list List managed Codex accounts
3756
4101
  caflip codex login -- --device-auth Run Codex login and register session
3757
4102
  caflip codex add --alias work Add current Codex account with alias
3758
- caflip codex alias work user@company.com
3759
- Set Codex alias for target email`);
4103
+ caflip codex alias work 2
4104
+ Set Codex alias for target account
4105
+
4106
+ Alias targets:
4107
+ <account> can be a list number, an existing alias, or an email when it matches exactly one managed account.
4108
+ Codex labels show workspace plans as email \xB7 plan(orgShortId); free shows email \xB7 free.`);
3760
4109
  }
3761
4110
  async function executeProviderCommand(command, args, provider, runWithLock) {
3762
4111
  switch (command) {
@@ -3798,7 +4147,7 @@ async function executeProviderCommand(command, args, provider, runWithLock) {
3798
4147
  break;
3799
4148
  case "alias": {
3800
4149
  if (!args[1]) {
3801
- console.error(`Usage: caflip ${provider} alias <name> [<email>]`);
4150
+ console.error(`Usage: caflip ${provider} alias <name> [<account>]`);
3802
4151
  process.exit(1);
3803
4152
  }
3804
4153
  await runWithLock(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "caflip",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "caflip": "bin/caflip"