caflip 0.2.0 → 0.3.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.
- package/README.md +44 -3
- package/dist/cli.js +607 -197
- package/install.sh +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A fast account switcher for coding agents like Claude Code and Codex.
|
|
4
4
|
|
|
5
|
-

|
|
6
|
+
Pick provider first, then switch account.
|
|
6
7
|
|
|
7
8
|
caflip is built for one job: if you have multiple Claude or Codex accounts, switch between them quickly.
|
|
8
9
|
|
|
9
|
-
Today, caflip
|
|
10
|
+
Today, caflip supports both Claude Code and Codex accounts. Your skills, settings, themes, `CLAUDE.md`, MCP servers, keybindings, and all other configuration stay exactly the same while switching accounts.
|
|
10
11
|
|
|
11
12
|
Use case: you have personal/work Claude or Codex accounts and want to switch quickly without re-login flows every time.
|
|
12
13
|
|
|
@@ -16,7 +17,7 @@ Use case: you have personal/work Claude or Codex accounts and want to switch qui
|
|
|
16
17
|
| Platform | Credential Storage |
|
|
17
18
|
|---|---|
|
|
18
19
|
| macOS | System Keychain |
|
|
19
|
-
| Linux | `secret-tool`
|
|
20
|
+
| Linux | `CLAUDE_CONFIG_DIR/.credentials.json` when set, otherwise `~/.claude/.credentials.json`; `secret-tool` is kept as compatibility sync |
|
|
20
21
|
| WSL | Same as Linux |
|
|
21
22
|
| Windows | Not yet supported |
|
|
22
23
|
|
|
@@ -28,6 +29,12 @@ Use case: you have personal/work Claude or Codex accounts and want to switch qui
|
|
|
28
29
|
curl -fsSL https://raw.githubusercontent.com/LucienLee/caflip/main/install.sh | bash
|
|
29
30
|
```
|
|
30
31
|
|
|
32
|
+
To uninstall the standalone binary installed by this script:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
curl -fsSL https://raw.githubusercontent.com/LucienLee/caflip/main/uninstall.sh | bash
|
|
36
|
+
```
|
|
37
|
+
|
|
31
38
|
### Via npm (Node.js)
|
|
32
39
|
|
|
33
40
|
```bash
|
|
@@ -40,6 +47,13 @@ npm install -g caflip
|
|
|
40
47
|
bun install -g caflip
|
|
41
48
|
```
|
|
42
49
|
|
|
50
|
+
For package-manager installs, uninstall with the same package manager:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm uninstall -g caflip
|
|
54
|
+
bun remove -g caflip
|
|
55
|
+
```
|
|
56
|
+
|
|
43
57
|
### Local Development
|
|
44
58
|
|
|
45
59
|
```bash
|
|
@@ -49,6 +63,10 @@ bun run dev -- help
|
|
|
49
63
|
## Quick Start
|
|
50
64
|
|
|
51
65
|
```bash
|
|
66
|
+
# Show current account / managed accounts across both providers
|
|
67
|
+
caflip status
|
|
68
|
+
caflip list
|
|
69
|
+
|
|
52
70
|
# Add your first Claude account (must already be logged in)
|
|
53
71
|
caflip claude add --alias personal
|
|
54
72
|
|
|
@@ -69,6 +87,14 @@ caflip claude next
|
|
|
69
87
|
caflip codex add --alias codex-work
|
|
70
88
|
caflip codex list
|
|
71
89
|
caflip codex next
|
|
90
|
+
|
|
91
|
+
# Run official provider login through caflip, then register the session
|
|
92
|
+
caflip claude login
|
|
93
|
+
caflip codex login
|
|
94
|
+
|
|
95
|
+
# Pass provider-specific flags after --
|
|
96
|
+
caflip claude login -- --email lucien@aibor.io --sso
|
|
97
|
+
caflip codex login -- --device-auth
|
|
72
98
|
```
|
|
73
99
|
|
|
74
100
|
After switching, restart the target CLI (Claude Code or Codex) to pick up new authentication.
|
|
@@ -78,12 +104,15 @@ After switching, restart the target CLI (Claude Code or Codex) to pick up new au
|
|
|
78
104
|
| Command | Description |
|
|
79
105
|
|---|---|
|
|
80
106
|
| `caflip` | Interactive provider picker (Claude/Codex) |
|
|
107
|
+
| `caflip list` | List managed accounts for Claude and Codex |
|
|
108
|
+
| `caflip status` | Show current account for Claude and Codex |
|
|
81
109
|
| `caflip claude [command]` | Run command for Claude provider |
|
|
82
110
|
| `caflip codex [command]` | Run command for Codex provider |
|
|
83
111
|
| `caflip [provider]` | Interactive account picker for that provider |
|
|
84
112
|
| `caflip [provider] <alias>` | Switch by alias for that provider |
|
|
85
113
|
| `caflip [provider] list` | List managed accounts |
|
|
86
114
|
| `caflip [provider] add [--alias name]` | Add current account |
|
|
115
|
+
| `caflip [provider] login [-- <args...>]` | Run provider login and register the resulting session |
|
|
87
116
|
| `caflip [provider] remove [email]` | Remove an account |
|
|
88
117
|
| `caflip [provider] next` | Rotate to next account |
|
|
89
118
|
| `caflip [provider] status` | Show current account |
|
|
@@ -105,6 +134,14 @@ caflip codex alias work me@company.com
|
|
|
105
134
|
|
|
106
135
|
`remove` target accepts email only. Omit it to choose from the interactive picker.
|
|
107
136
|
|
|
137
|
+
`login` can be used without arguments for the default login flow. Pass provider-specific flags after `--`:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
caflip claude login
|
|
141
|
+
caflip claude login -- --email lucien@aibor.io --sso
|
|
142
|
+
caflip codex login -- --device-auth
|
|
143
|
+
```
|
|
144
|
+
|
|
108
145
|
## Shell Prompt Integration
|
|
109
146
|
|
|
110
147
|
Show the current account in your prompt:
|
|
@@ -119,6 +156,10 @@ Account data lives in:
|
|
|
119
156
|
- `~/.caflip-backup/claude/`
|
|
120
157
|
- `~/.caflip-backup/codex/`
|
|
121
158
|
|
|
159
|
+
On Linux and WSL, caflip follows Claude's config root for active Claude credentials and config:
|
|
160
|
+
- if `CLAUDE_CONFIG_DIR` is set, caflip reads `"$CLAUDE_CONFIG_DIR/.credentials.json"` and `"$CLAUDE_CONFIG_DIR/.claude.json"`
|
|
161
|
+
- otherwise it falls back to `~/.claude/.credentials.json` and then Claude's standard config lookup
|
|
162
|
+
|
|
122
163
|
## Credits
|
|
123
164
|
|
|
124
165
|
Inspired by [cc-account-switcher](https://github.com/ming86/cc-account-switcher).
|
package/dist/cli.js
CHANGED
|
@@ -181,11 +181,10 @@ var require_lib = __commonJS((exports, module) => {
|
|
|
181
181
|
});
|
|
182
182
|
|
|
183
183
|
// src/index.ts
|
|
184
|
-
import { existsSync as
|
|
184
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
185
185
|
|
|
186
186
|
// src/config.ts
|
|
187
187
|
import { homedir } from "os";
|
|
188
|
-
import { existsSync, readFileSync } from "fs";
|
|
189
188
|
import { join } from "path";
|
|
190
189
|
function getBackupDir(provider) {
|
|
191
190
|
return join(homedir(), ".caflip-backup", provider);
|
|
@@ -212,38 +211,26 @@ var RESERVED_COMMANDS = [
|
|
|
212
211
|
"add",
|
|
213
212
|
"remove",
|
|
214
213
|
"next",
|
|
214
|
+
"login",
|
|
215
215
|
"status",
|
|
216
216
|
"alias",
|
|
217
217
|
"claude",
|
|
218
218
|
"codex",
|
|
219
219
|
"help"
|
|
220
220
|
];
|
|
221
|
-
function getClaudeConfigPath() {
|
|
222
|
-
const primary = join(homedir(), ".claude", ".claude.json");
|
|
223
|
-
const fallback = join(homedir(), ".claude.json");
|
|
224
|
-
if (existsSync(primary)) {
|
|
225
|
-
try {
|
|
226
|
-
const content = JSON.parse(readFileSync(primary, "utf-8"));
|
|
227
|
-
if (content.oauthAccount) {
|
|
228
|
-
return primary;
|
|
229
|
-
}
|
|
230
|
-
} catch {}
|
|
231
|
-
}
|
|
232
|
-
return fallback;
|
|
233
|
-
}
|
|
234
221
|
|
|
235
222
|
// src/accounts.ts
|
|
236
|
-
import { existsSync as
|
|
223
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
237
224
|
|
|
238
225
|
// src/files.ts
|
|
239
|
-
import { existsSync
|
|
226
|
+
import { existsSync, mkdirSync, rmSync, chmodSync, renameSync, writeFileSync, readFileSync } from "fs";
|
|
240
227
|
import { dirname, join as join2 } from "path";
|
|
241
228
|
import { randomBytes } from "crypto";
|
|
242
229
|
async function writeJsonAtomic(filePath, data) {
|
|
243
230
|
const jsonStr = JSON.stringify(data, null, 2);
|
|
244
231
|
JSON.parse(jsonStr);
|
|
245
232
|
const dir = dirname(filePath);
|
|
246
|
-
if (!
|
|
233
|
+
if (!existsSync(dir)) {
|
|
247
234
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
248
235
|
}
|
|
249
236
|
chmodSync(dir, 448);
|
|
@@ -262,7 +249,7 @@ async function writeJsonAtomic(filePath, data) {
|
|
|
262
249
|
|
|
263
250
|
// src/accounts.ts
|
|
264
251
|
async function initSequenceFile(path) {
|
|
265
|
-
if (
|
|
252
|
+
if (existsSync2(path))
|
|
266
253
|
return;
|
|
267
254
|
const data = {
|
|
268
255
|
activeAccountNumber: null,
|
|
@@ -273,7 +260,7 @@ async function initSequenceFile(path) {
|
|
|
273
260
|
await writeJsonAtomic(path, data);
|
|
274
261
|
}
|
|
275
262
|
async function loadSequence(path) {
|
|
276
|
-
return JSON.parse(
|
|
263
|
+
return JSON.parse(readFileSync2(path, "utf-8"));
|
|
277
264
|
}
|
|
278
265
|
function getNextAccountNumber(seq) {
|
|
279
266
|
const keys = Object.keys(seq.accounts).map(Number);
|
|
@@ -422,14 +409,14 @@ function findAccountByAlias(seq, alias) {
|
|
|
422
409
|
}
|
|
423
410
|
|
|
424
411
|
// src/files.ts
|
|
425
|
-
import { existsSync as
|
|
412
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, rmSync as rmSync2, chmodSync as chmodSync2, renameSync as renameSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
|
|
426
413
|
import { dirname as dirname2, join as join3 } from "path";
|
|
427
414
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
428
415
|
async function writeJsonAtomic2(filePath, data) {
|
|
429
416
|
const jsonStr = JSON.stringify(data, null, 2);
|
|
430
417
|
JSON.parse(jsonStr);
|
|
431
418
|
const dir = dirname2(filePath);
|
|
432
|
-
if (!
|
|
419
|
+
if (!existsSync3(dir)) {
|
|
433
420
|
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
434
421
|
}
|
|
435
422
|
chmodSync2(dir, 448);
|
|
@@ -447,7 +434,7 @@ async function writeJsonAtomic2(filePath, data) {
|
|
|
447
434
|
}
|
|
448
435
|
function acquireLock(lockDir) {
|
|
449
436
|
const parentDir = dirname2(lockDir);
|
|
450
|
-
if (!
|
|
437
|
+
if (!existsSync3(parentDir)) {
|
|
451
438
|
mkdirSync2(parentDir, { recursive: true, mode: 448 });
|
|
452
439
|
}
|
|
453
440
|
try {
|
|
@@ -490,11 +477,11 @@ function writeLockOwner(lockDir) {
|
|
|
490
477
|
}
|
|
491
478
|
function isStaleLock(lockDir) {
|
|
492
479
|
const ownerPath = getLockOwnerPath(lockDir);
|
|
493
|
-
if (!
|
|
480
|
+
if (!existsSync3(ownerPath)) {
|
|
494
481
|
return true;
|
|
495
482
|
}
|
|
496
483
|
try {
|
|
497
|
-
const owner = JSON.parse(
|
|
484
|
+
const owner = JSON.parse(readFileSync3(ownerPath, "utf-8"));
|
|
498
485
|
if (!owner.pid || !Number.isInteger(owner.pid) || owner.pid <= 0) {
|
|
499
486
|
return true;
|
|
500
487
|
}
|
|
@@ -521,7 +508,7 @@ function isProcessAlive(pid) {
|
|
|
521
508
|
|
|
522
509
|
// src/config.ts
|
|
523
510
|
import { homedir as homedir2 } from "os";
|
|
524
|
-
import { existsSync as
|
|
511
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
525
512
|
import { join as join4 } from "path";
|
|
526
513
|
function getBackupDir2(provider) {
|
|
527
514
|
return join4(homedir2(), ".caflip-backup", provider);
|
|
@@ -548,6 +535,7 @@ var RESERVED_COMMANDS2 = [
|
|
|
548
535
|
"add",
|
|
549
536
|
"remove",
|
|
550
537
|
"next",
|
|
538
|
+
"login",
|
|
551
539
|
"status",
|
|
552
540
|
"alias",
|
|
553
541
|
"claude",
|
|
@@ -566,12 +554,23 @@ function detectPlatform() {
|
|
|
566
554
|
return "unknown";
|
|
567
555
|
}
|
|
568
556
|
}
|
|
569
|
-
function
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
557
|
+
function getClaudeConfigDir(env = process.env, home = homedir2()) {
|
|
558
|
+
const customDir = env.CLAUDE_CONFIG_DIR?.trim();
|
|
559
|
+
if (customDir) {
|
|
560
|
+
return customDir;
|
|
561
|
+
}
|
|
562
|
+
return join4(home, ".claude");
|
|
563
|
+
}
|
|
564
|
+
function getClaudeConfigPath(env = process.env, home = homedir2()) {
|
|
565
|
+
const customDir = env.CLAUDE_CONFIG_DIR?.trim();
|
|
566
|
+
if (customDir) {
|
|
567
|
+
return join4(customDir, ".claude.json");
|
|
568
|
+
}
|
|
569
|
+
const primary = join4(getClaudeConfigDir(env, home), ".claude.json");
|
|
570
|
+
const fallback = join4(home, ".claude.json");
|
|
571
|
+
if (existsSync4(primary)) {
|
|
573
572
|
try {
|
|
574
|
-
const content = JSON.parse(
|
|
573
|
+
const content = JSON.parse(readFileSync4(primary, "utf-8"));
|
|
575
574
|
if (content.oauthAccount) {
|
|
576
575
|
return primary;
|
|
577
576
|
}
|
|
@@ -618,7 +617,7 @@ function validateAlias(alias) {
|
|
|
618
617
|
// package.json
|
|
619
618
|
var package_default = {
|
|
620
619
|
name: "caflip",
|
|
621
|
-
version: "0.
|
|
620
|
+
version: "0.3.0",
|
|
622
621
|
type: "module",
|
|
623
622
|
bin: {
|
|
624
623
|
caflip: "bin/caflip"
|
|
@@ -2368,6 +2367,25 @@ async function confirmAction(message, promptConfirm = dist_default4) {
|
|
|
2368
2367
|
return wrapPromptCancellation(() => promptConfirm({ message, default: false }));
|
|
2369
2368
|
}
|
|
2370
2369
|
|
|
2370
|
+
// src/login/runner.ts
|
|
2371
|
+
import { spawn } from "child_process";
|
|
2372
|
+
async function runLoginCommand(command) {
|
|
2373
|
+
return await new Promise((resolve, reject) => {
|
|
2374
|
+
const proc = spawn(command[0], command.slice(1), {
|
|
2375
|
+
stdio: "inherit"
|
|
2376
|
+
});
|
|
2377
|
+
proc.on("error", (error) => {
|
|
2378
|
+
reject(error);
|
|
2379
|
+
});
|
|
2380
|
+
proc.on("close", (code, signal) => {
|
|
2381
|
+
resolve({
|
|
2382
|
+
exitCode: code ?? 1,
|
|
2383
|
+
signal
|
|
2384
|
+
});
|
|
2385
|
+
});
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2371
2389
|
// src/providers/types.ts
|
|
2372
2390
|
var SUPPORTED_PROVIDERS = ["claude", "codex"];
|
|
2373
2391
|
function isProviderName(value) {
|
|
@@ -2384,13 +2402,13 @@ function parseProviderArgs(args) {
|
|
|
2384
2402
|
}
|
|
2385
2403
|
|
|
2386
2404
|
// src/providers/claude.ts
|
|
2387
|
-
import { existsSync as
|
|
2405
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
2388
2406
|
|
|
2389
2407
|
// src/credentials.ts
|
|
2390
|
-
import { existsSync as
|
|
2408
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, mkdirSync as mkdirSync3, chmodSync as chmodSync3, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
2391
2409
|
import { join as join5 } from "path";
|
|
2392
2410
|
import { homedir as homedir3 } from "os";
|
|
2393
|
-
import { spawn, spawnSync } from "child_process";
|
|
2411
|
+
import { spawn as spawn2, spawnSync } from "child_process";
|
|
2394
2412
|
|
|
2395
2413
|
// src/validation.ts
|
|
2396
2414
|
var EMAIL_REGEX2 = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
@@ -2416,7 +2434,7 @@ async function runCommand(cmd) {
|
|
|
2416
2434
|
}
|
|
2417
2435
|
async function runCommandWithInput(cmd, input) {
|
|
2418
2436
|
return await new Promise((resolve, reject) => {
|
|
2419
|
-
const proc =
|
|
2437
|
+
const proc = spawn2(cmd[0], cmd.slice(1), {
|
|
2420
2438
|
stdio: ["pipe", "pipe", "pipe"]
|
|
2421
2439
|
});
|
|
2422
2440
|
let stdout = "";
|
|
@@ -2464,6 +2482,16 @@ function activeSecretToolAttrs() {
|
|
|
2464
2482
|
function backupSecretToolAttrs(accountNum, email) {
|
|
2465
2483
|
return ["service", "ccflip", "account", accountNum, "email", email];
|
|
2466
2484
|
}
|
|
2485
|
+
function getClaudeCredentialsDir(env = process.env, home = homedir3()) {
|
|
2486
|
+
const customDir = env.CLAUDE_CONFIG_DIR?.trim();
|
|
2487
|
+
if (customDir) {
|
|
2488
|
+
return customDir;
|
|
2489
|
+
}
|
|
2490
|
+
return join5(home, ".claude");
|
|
2491
|
+
}
|
|
2492
|
+
function getClaudeCredentialsPath(env = process.env, home = homedir3()) {
|
|
2493
|
+
return join5(getClaudeCredentialsDir(env, home), ".credentials.json");
|
|
2494
|
+
}
|
|
2467
2495
|
async function secretToolLookup(attrs) {
|
|
2468
2496
|
const result = await runCommand(["secret-tool", "lookup", ...attrs]);
|
|
2469
2497
|
if (result.exitCode === 0) {
|
|
@@ -2507,16 +2535,16 @@ async function readCredentials() {
|
|
|
2507
2535
|
}
|
|
2508
2536
|
case "linux":
|
|
2509
2537
|
case "wsl": {
|
|
2538
|
+
const credPath = getClaudeCredentialsPath();
|
|
2539
|
+
if (existsSync5(credPath)) {
|
|
2540
|
+
return readFileSync5(credPath, "utf-8");
|
|
2541
|
+
}
|
|
2510
2542
|
if (hasSecretTool()) {
|
|
2511
2543
|
const keyringValue = await secretToolLookup(activeSecretToolAttrs());
|
|
2512
2544
|
if (keyringValue) {
|
|
2513
2545
|
return keyringValue;
|
|
2514
2546
|
}
|
|
2515
2547
|
}
|
|
2516
|
-
const credPath = join5(homedir3(), ".claude", ".credentials.json");
|
|
2517
|
-
if (existsSync6(credPath)) {
|
|
2518
|
-
return readFileSync6(credPath, "utf-8");
|
|
2519
|
-
}
|
|
2520
2548
|
return "";
|
|
2521
2549
|
}
|
|
2522
2550
|
default:
|
|
@@ -2545,16 +2573,15 @@ async function writeCredentials(credentials) {
|
|
|
2545
2573
|
}
|
|
2546
2574
|
case "linux":
|
|
2547
2575
|
case "wsl": {
|
|
2548
|
-
|
|
2549
|
-
await secretToolStore(activeSecretToolAttrs(), credentials);
|
|
2550
|
-
return;
|
|
2551
|
-
}
|
|
2552
|
-
const claudeDir = join5(homedir3(), ".claude");
|
|
2576
|
+
const claudeDir = getClaudeCredentialsDir();
|
|
2553
2577
|
mkdirSync3(claudeDir, { recursive: true, mode: 448 });
|
|
2554
2578
|
chmodSync3(claudeDir, 448);
|
|
2555
|
-
const credPath =
|
|
2579
|
+
const credPath = getClaudeCredentialsPath();
|
|
2556
2580
|
writeFileSync3(credPath, credentials, { mode: 384 });
|
|
2557
2581
|
chmodSync3(credPath, 384);
|
|
2582
|
+
if (hasSecretTool()) {
|
|
2583
|
+
await secretToolStore(activeSecretToolAttrs(), credentials);
|
|
2584
|
+
}
|
|
2558
2585
|
break;
|
|
2559
2586
|
}
|
|
2560
2587
|
}
|
|
@@ -2576,11 +2603,11 @@ async function clearActiveCredentials() {
|
|
|
2576
2603
|
}
|
|
2577
2604
|
case "linux":
|
|
2578
2605
|
case "wsl": {
|
|
2606
|
+
const credPath = getClaudeCredentialsPath();
|
|
2607
|
+
rmSync3(credPath, { force: true });
|
|
2579
2608
|
if (hasSecretTool()) {
|
|
2580
2609
|
await secretToolClear(activeSecretToolAttrs());
|
|
2581
2610
|
}
|
|
2582
|
-
const credPath = join5(homedir3(), ".claude", ".credentials.json");
|
|
2583
|
-
rmSync3(credPath, { force: true });
|
|
2584
2611
|
break;
|
|
2585
2612
|
}
|
|
2586
2613
|
}
|
|
@@ -2617,8 +2644,8 @@ async function readAccountCredentials(accountNum, email, credentialsDir = CREDEN
|
|
|
2617
2644
|
}
|
|
2618
2645
|
}
|
|
2619
2646
|
const credFile = join5(credentialsDir, `.claude-credentials-${accountNum}-${email}.json`);
|
|
2620
|
-
if (
|
|
2621
|
-
return
|
|
2647
|
+
if (existsSync5(credFile)) {
|
|
2648
|
+
return readFileSync5(credFile, "utf-8");
|
|
2622
2649
|
}
|
|
2623
2650
|
return "";
|
|
2624
2651
|
}
|
|
@@ -2701,8 +2728,8 @@ function readAccountConfig(accountNum, email, configsDir) {
|
|
|
2701
2728
|
throw new Error(`Unsafe email for filename: ${email}`);
|
|
2702
2729
|
}
|
|
2703
2730
|
const configFile = join5(configsDir, `.claude-config-${accountNum}-${email}.json`);
|
|
2704
|
-
if (
|
|
2705
|
-
return
|
|
2731
|
+
if (existsSync5(configFile)) {
|
|
2732
|
+
return readFileSync5(configFile, "utf-8");
|
|
2706
2733
|
}
|
|
2707
2734
|
return "";
|
|
2708
2735
|
}
|
|
@@ -2725,6 +2752,191 @@ function deleteAccountConfig(accountNum, email, configsDir) {
|
|
|
2725
2752
|
rmSync3(configFile, { force: true });
|
|
2726
2753
|
}
|
|
2727
2754
|
|
|
2755
|
+
// src/login/runner.ts
|
|
2756
|
+
import { spawn as spawn3 } from "child_process";
|
|
2757
|
+
var DEFAULT_CAPTURE_TIMEOUT_MS = 5000;
|
|
2758
|
+
function normalizeOutput(value) {
|
|
2759
|
+
return value.trim();
|
|
2760
|
+
}
|
|
2761
|
+
async function runCapturedCommand(command, options) {
|
|
2762
|
+
return await new Promise((resolve, reject) => {
|
|
2763
|
+
const proc = spawn3(command[0], command.slice(1), {
|
|
2764
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2765
|
+
});
|
|
2766
|
+
let stdout = "";
|
|
2767
|
+
let stderr = "";
|
|
2768
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_CAPTURE_TIMEOUT_MS;
|
|
2769
|
+
const timer = setTimeout(() => {
|
|
2770
|
+
proc.kill("SIGTERM");
|
|
2771
|
+
}, timeoutMs);
|
|
2772
|
+
proc.stdout.on("data", (chunk) => {
|
|
2773
|
+
stdout += String(chunk);
|
|
2774
|
+
});
|
|
2775
|
+
proc.stderr.on("data", (chunk) => {
|
|
2776
|
+
stderr += String(chunk);
|
|
2777
|
+
});
|
|
2778
|
+
proc.on("error", (error) => {
|
|
2779
|
+
clearTimeout(timer);
|
|
2780
|
+
reject(error);
|
|
2781
|
+
});
|
|
2782
|
+
proc.on("close", (code, signal) => {
|
|
2783
|
+
clearTimeout(timer);
|
|
2784
|
+
if (signal === "SIGTERM") {
|
|
2785
|
+
resolve({
|
|
2786
|
+
exitCode: 124,
|
|
2787
|
+
stdout: normalizeOutput(stdout),
|
|
2788
|
+
stderr: normalizeOutput(stderr) || `command timed out after ${timeoutMs}ms`,
|
|
2789
|
+
signal
|
|
2790
|
+
});
|
|
2791
|
+
return;
|
|
2792
|
+
}
|
|
2793
|
+
resolve({
|
|
2794
|
+
exitCode: code ?? 1,
|
|
2795
|
+
stdout: normalizeOutput(stdout),
|
|
2796
|
+
stderr: normalizeOutput(stderr),
|
|
2797
|
+
signal
|
|
2798
|
+
});
|
|
2799
|
+
});
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
// src/providers/claude.ts
|
|
2804
|
+
function readClaudeConfigObject() {
|
|
2805
|
+
const configPath = getClaudeConfigPath();
|
|
2806
|
+
if (!existsSync6(configPath))
|
|
2807
|
+
return null;
|
|
2808
|
+
try {
|
|
2809
|
+
return JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
2810
|
+
} catch {
|
|
2811
|
+
return null;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
function getClaudeCurrentAccount() {
|
|
2815
|
+
const content = readClaudeConfigObject();
|
|
2816
|
+
const email = content?.oauthAccount?.emailAddress;
|
|
2817
|
+
if (typeof email !== "string" || !email) {
|
|
2818
|
+
return null;
|
|
2819
|
+
}
|
|
2820
|
+
const accountId = typeof content?.oauthAccount?.accountUuid === "string" ? content.oauthAccount.accountUuid : undefined;
|
|
2821
|
+
return { email, accountId };
|
|
2822
|
+
}
|
|
2823
|
+
function getClaudeCurrentAccountEmail() {
|
|
2824
|
+
return getClaudeCurrentAccount()?.email ?? "none";
|
|
2825
|
+
}
|
|
2826
|
+
async function readClaudeActiveConfig() {
|
|
2827
|
+
const configPath = getClaudeConfigPath();
|
|
2828
|
+
if (!existsSync6(configPath)) {
|
|
2829
|
+
return "";
|
|
2830
|
+
}
|
|
2831
|
+
try {
|
|
2832
|
+
return readFileSync6(configPath, "utf-8");
|
|
2833
|
+
} catch {
|
|
2834
|
+
return "";
|
|
2835
|
+
}
|
|
2836
|
+
}
|
|
2837
|
+
async function writeClaudeActiveConfig(raw) {
|
|
2838
|
+
let targetConfig;
|
|
2839
|
+
try {
|
|
2840
|
+
targetConfig = JSON.parse(raw);
|
|
2841
|
+
} catch {
|
|
2842
|
+
throw new Error("Invalid Claude config backup");
|
|
2843
|
+
}
|
|
2844
|
+
const oauthAccount = targetConfig.oauthAccount;
|
|
2845
|
+
if (!oauthAccount) {
|
|
2846
|
+
throw new Error("Invalid oauthAccount in backup");
|
|
2847
|
+
}
|
|
2848
|
+
const configPath = getClaudeConfigPath();
|
|
2849
|
+
let currentConfigObj = {};
|
|
2850
|
+
if (existsSync6(configPath)) {
|
|
2851
|
+
try {
|
|
2852
|
+
currentConfigObj = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
2853
|
+
} catch {
|
|
2854
|
+
currentConfigObj = {};
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
currentConfigObj.oauthAccount = oauthAccount;
|
|
2858
|
+
await writeJsonAtomic(configPath, currentConfigObj);
|
|
2859
|
+
}
|
|
2860
|
+
async function clearClaudeActiveConfig() {
|
|
2861
|
+
const configPath = getClaudeConfigPath();
|
|
2862
|
+
let configObj = {};
|
|
2863
|
+
if (existsSync6(configPath)) {
|
|
2864
|
+
try {
|
|
2865
|
+
configObj = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
2866
|
+
} catch {
|
|
2867
|
+
configObj = {};
|
|
2868
|
+
}
|
|
2869
|
+
}
|
|
2870
|
+
delete configObj.oauthAccount;
|
|
2871
|
+
await writeJsonAtomic(configPath, configObj);
|
|
2872
|
+
}
|
|
2873
|
+
async function verifyClaudeLogin(commandRunner = runCapturedCommand) {
|
|
2874
|
+
const result = await commandRunner(["claude", "auth", "status", "--json"]);
|
|
2875
|
+
if (result.exitCode !== 0) {
|
|
2876
|
+
return {
|
|
2877
|
+
ok: false,
|
|
2878
|
+
reason: result.stderr || "claude auth status failed"
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
let payload;
|
|
2882
|
+
try {
|
|
2883
|
+
payload = JSON.parse(result.stdout);
|
|
2884
|
+
} catch {
|
|
2885
|
+
return {
|
|
2886
|
+
ok: false,
|
|
2887
|
+
reason: "claude auth status returned invalid JSON"
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
if (payload.loggedIn !== true) {
|
|
2891
|
+
return {
|
|
2892
|
+
ok: false,
|
|
2893
|
+
reason: "claude auth status reported logged out",
|
|
2894
|
+
details: {
|
|
2895
|
+
loggedIn: payload.loggedIn ?? false
|
|
2896
|
+
}
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
if (!payload.email) {
|
|
2900
|
+
return {
|
|
2901
|
+
ok: false,
|
|
2902
|
+
reason: "claude auth status did not include an email"
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
return {
|
|
2906
|
+
ok: true,
|
|
2907
|
+
email: payload.email,
|
|
2908
|
+
details: {
|
|
2909
|
+
authMethod: payload.authMethod,
|
|
2910
|
+
orgId: payload.orgId,
|
|
2911
|
+
orgName: payload.orgName,
|
|
2912
|
+
subscriptionType: payload.subscriptionType
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
}
|
|
2916
|
+
var claudeLoginAdapter = {
|
|
2917
|
+
buildCommand: (passthroughArgs) => ["claude", "auth", "login", ...passthroughArgs],
|
|
2918
|
+
verifyLogin: verifyClaudeLogin
|
|
2919
|
+
};
|
|
2920
|
+
var claudeProvider = {
|
|
2921
|
+
name: "claude",
|
|
2922
|
+
login: claudeLoginAdapter,
|
|
2923
|
+
usesAccountConfig: true,
|
|
2924
|
+
getCurrentAccount: getClaudeCurrentAccount,
|
|
2925
|
+
getCurrentAccountEmail: getClaudeCurrentAccountEmail,
|
|
2926
|
+
readActiveAuth: readCredentials,
|
|
2927
|
+
writeActiveAuth: writeCredentials,
|
|
2928
|
+
clearActiveAuth: clearActiveCredentials,
|
|
2929
|
+
readActiveConfig: readClaudeActiveConfig,
|
|
2930
|
+
writeActiveConfig: writeClaudeActiveConfig,
|
|
2931
|
+
clearActiveConfig: clearClaudeActiveConfig,
|
|
2932
|
+
readAccountAuth: readAccountCredentials,
|
|
2933
|
+
writeAccountAuth: writeAccountCredentials,
|
|
2934
|
+
deleteAccountAuth: deleteAccountCredentials,
|
|
2935
|
+
readAccountConfig,
|
|
2936
|
+
writeAccountConfig,
|
|
2937
|
+
deleteAccountConfig
|
|
2938
|
+
};
|
|
2939
|
+
|
|
2728
2940
|
// src/providers/codex.ts
|
|
2729
2941
|
import { chmodSync as chmodSync4, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2730
2942
|
import { homedir as homedir4 } from "os";
|
|
@@ -2758,7 +2970,11 @@ async function readCodexActiveAuth() {
|
|
|
2758
2970
|
if (!existsSync7(authPath)) {
|
|
2759
2971
|
return "";
|
|
2760
2972
|
}
|
|
2761
|
-
|
|
2973
|
+
try {
|
|
2974
|
+
return readFileSync7(authPath, "utf-8");
|
|
2975
|
+
} catch {
|
|
2976
|
+
return "";
|
|
2977
|
+
}
|
|
2762
2978
|
}
|
|
2763
2979
|
async function writeCodexActiveAuth(raw) {
|
|
2764
2980
|
const codexDir = join6(process.env.HOME ?? homedir4(), ".codex");
|
|
@@ -2777,7 +2993,11 @@ async function readCodexAccountAuthBackup(accountNum, email, credentialsDir) {
|
|
|
2777
2993
|
if (!existsSync7(backupPath)) {
|
|
2778
2994
|
return "";
|
|
2779
2995
|
}
|
|
2780
|
-
|
|
2996
|
+
try {
|
|
2997
|
+
return readFileSync7(backupPath, "utf-8");
|
|
2998
|
+
} catch {
|
|
2999
|
+
return "";
|
|
3000
|
+
}
|
|
2781
3001
|
}
|
|
2782
3002
|
async function writeCodexAccountAuthBackup(accountNum, email, raw, credentialsDir) {
|
|
2783
3003
|
ensureBackupKeySafe(accountNum, email);
|
|
@@ -2821,45 +3041,79 @@ function getCodexCurrentAccount() {
|
|
|
2821
3041
|
return null;
|
|
2822
3042
|
}
|
|
2823
3043
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
return "none";
|
|
3044
|
+
function readCodexAuthFile() {
|
|
3045
|
+
const authPath = getCodexAuthPath();
|
|
3046
|
+
if (!existsSync7(authPath)) {
|
|
3047
|
+
return null;
|
|
3048
|
+
}
|
|
2830
3049
|
try {
|
|
2831
|
-
|
|
2832
|
-
return content?.oauthAccount?.emailAddress ?? "none";
|
|
3050
|
+
return JSON.parse(readFileSync7(authPath, "utf-8"));
|
|
2833
3051
|
} catch {
|
|
2834
|
-
return
|
|
3052
|
+
return null;
|
|
2835
3053
|
}
|
|
2836
3054
|
}
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
3055
|
+
async function verifyCodexLogin(commandRunner = runCapturedCommand) {
|
|
3056
|
+
const result = await commandRunner(["codex", "login", "status"]);
|
|
3057
|
+
if (result.exitCode !== 0) {
|
|
3058
|
+
return {
|
|
3059
|
+
ok: false,
|
|
3060
|
+
reason: result.stderr || "codex login status failed"
|
|
3061
|
+
};
|
|
3062
|
+
}
|
|
3063
|
+
const authFile = readCodexAuthFile();
|
|
3064
|
+
if (!authFile) {
|
|
3065
|
+
return {
|
|
3066
|
+
ok: false,
|
|
3067
|
+
reason: "codex auth file was missing or unreadable after successful login status"
|
|
3068
|
+
};
|
|
3069
|
+
}
|
|
3070
|
+
if (authFile.OPENAI_API_KEY) {
|
|
3071
|
+
return {
|
|
3072
|
+
ok: false,
|
|
3073
|
+
reason: "caflip does not support Codex API key login sessions",
|
|
3074
|
+
details: {
|
|
3075
|
+
authMode: authFile.auth_mode ?? "apikey"
|
|
3076
|
+
}
|
|
3077
|
+
};
|
|
3078
|
+
}
|
|
3079
|
+
if (authFile.auth_mode && authFile.auth_mode !== "chatgpt") {
|
|
3080
|
+
return {
|
|
3081
|
+
ok: false,
|
|
3082
|
+
reason: `caflip does not support Codex ${authFile.auth_mode} login sessions`,
|
|
3083
|
+
details: {
|
|
3084
|
+
authMode: authFile.auth_mode
|
|
3085
|
+
}
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
const currentAccount = getCodexCurrentAccount();
|
|
3089
|
+
if (!currentAccount?.email) {
|
|
3090
|
+
return {
|
|
3091
|
+
ok: false,
|
|
3092
|
+
reason: "codex auth file did not resolve a current account email"
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
return {
|
|
3096
|
+
ok: true,
|
|
3097
|
+
email: currentAccount.email,
|
|
3098
|
+
details: currentAccount.accountId ? { accountId: currentAccount.accountId } : undefined
|
|
3099
|
+
};
|
|
3100
|
+
}
|
|
3101
|
+
var codexLoginAdapter = {
|
|
3102
|
+
buildCommand: (passthroughArgs) => ["codex", "login", ...passthroughArgs],
|
|
3103
|
+
verifyLogin: verifyCodexLogin
|
|
2855
3104
|
};
|
|
2856
3105
|
var codexProvider = {
|
|
2857
3106
|
name: "codex",
|
|
3107
|
+
login: codexLoginAdapter,
|
|
3108
|
+
usesAccountConfig: false,
|
|
2858
3109
|
getCurrentAccount: getCodexCurrentAccount,
|
|
2859
3110
|
getCurrentAccountEmail: () => getCodexCurrentAccount()?.email ?? "none",
|
|
2860
3111
|
readActiveAuth: readCodexActiveAuth,
|
|
2861
3112
|
writeActiveAuth: writeCodexActiveAuth,
|
|
2862
3113
|
clearActiveAuth: clearCodexActiveAuth,
|
|
3114
|
+
readActiveConfig: async () => "",
|
|
3115
|
+
writeActiveConfig: async () => {},
|
|
3116
|
+
clearActiveConfig: async () => {},
|
|
2863
3117
|
readAccountAuth: readCodexAccountAuthBackup,
|
|
2864
3118
|
writeAccountAuth: writeCodexAccountAuthBackup,
|
|
2865
3119
|
deleteAccountAuth: deleteCodexAccountAuthBackup,
|
|
@@ -2878,7 +3132,7 @@ function getProvider(name) {
|
|
|
2878
3132
|
}
|
|
2879
3133
|
|
|
2880
3134
|
// src/meta.ts
|
|
2881
|
-
import { existsSync as
|
|
3135
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
2882
3136
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
2883
3137
|
import { homedir as homedir5 } from "os";
|
|
2884
3138
|
import { join as join7 } from "path";
|
|
@@ -2888,11 +3142,11 @@ function getMetaFilePath() {
|
|
|
2888
3142
|
}
|
|
2889
3143
|
function readCliMeta() {
|
|
2890
3144
|
const metaFile = getMetaFilePath();
|
|
2891
|
-
if (!
|
|
3145
|
+
if (!existsSync8(metaFile)) {
|
|
2892
3146
|
return { lastProvider: "claude" };
|
|
2893
3147
|
}
|
|
2894
3148
|
try {
|
|
2895
|
-
const parsed = JSON.parse(
|
|
3149
|
+
const parsed = JSON.parse(readFileSync8(metaFile, "utf-8"));
|
|
2896
3150
|
if (parsed.lastProvider === "codex") {
|
|
2897
3151
|
return { lastProvider: "codex" };
|
|
2898
3152
|
}
|
|
@@ -2937,18 +3191,10 @@ function getCurrentAccount() {
|
|
|
2937
3191
|
function getProviderLabel() {
|
|
2938
3192
|
return activeProvider.name === "codex" ? "Codex" : "Claude Code";
|
|
2939
3193
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
try {
|
|
2945
|
-
configObj = JSON.parse(readFileSync10(configPath, "utf-8"));
|
|
2946
|
-
} catch {
|
|
2947
|
-
configObj = {};
|
|
2948
|
-
}
|
|
2949
|
-
}
|
|
2950
|
-
delete configObj.oauthAccount;
|
|
2951
|
-
await writeJsonAtomic2(configPath, configObj);
|
|
3194
|
+
function showProviderRequiredError(command) {
|
|
3195
|
+
console.error(`Error: ${command} requires provider prefix.`);
|
|
3196
|
+
console.error(`Try: caflip claude ${command} or caflip codex ${command}`);
|
|
3197
|
+
process.exit(2);
|
|
2952
3198
|
}
|
|
2953
3199
|
async function syncSequenceActiveAccount(seq) {
|
|
2954
3200
|
const currentEmail = getCurrentAccount();
|
|
@@ -2960,6 +3206,108 @@ async function syncSequenceActiveAccount(seq) {
|
|
|
2960
3206
|
}
|
|
2961
3207
|
return seq;
|
|
2962
3208
|
}
|
|
3209
|
+
async function registerCurrentActiveAccount(options) {
|
|
3210
|
+
const currentAccount = activeProvider.getCurrentAccount();
|
|
3211
|
+
const currentEmail = currentAccount?.email ?? "none";
|
|
3212
|
+
if (currentEmail === "none") {
|
|
3213
|
+
throw new Error(`No active ${getProviderLabel()} account found. Please log in first.`);
|
|
3214
|
+
}
|
|
3215
|
+
if (!sanitizeEmailForFilename(currentEmail)) {
|
|
3216
|
+
throw new Error("Current account email is not safe for storage");
|
|
3217
|
+
}
|
|
3218
|
+
if (options?.expectedEmail && currentEmail !== options.expectedEmail) {
|
|
3219
|
+
throw new Error(`Active ${getProviderLabel()} account changed during login verification: expected ${options.expectedEmail}, got ${currentEmail}`);
|
|
3220
|
+
}
|
|
3221
|
+
setupDirectories();
|
|
3222
|
+
await initSequenceFile(activeSequenceFile);
|
|
3223
|
+
const seq = await loadSequence(activeSequenceFile);
|
|
3224
|
+
await syncSequenceActiveAccount(seq);
|
|
3225
|
+
if (options?.alias) {
|
|
3226
|
+
const result = validateAlias(options.alias);
|
|
3227
|
+
if (!result.valid) {
|
|
3228
|
+
throw new Error(result.reason);
|
|
3229
|
+
}
|
|
3230
|
+
const existingAliasTarget = findAccountByAlias(seq, options.alias);
|
|
3231
|
+
const currentAccountNum = resolveAccountIdentifier(seq, currentEmail);
|
|
3232
|
+
if (existingAliasTarget && existingAliasTarget !== currentAccountNum) {
|
|
3233
|
+
throw new Error(`Alias "${options.alias}" is already in use`);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
const existingAccountNum = resolveAccountIdentifier(seq, currentEmail);
|
|
3237
|
+
if (existingAccountNum) {
|
|
3238
|
+
if (!options?.updateIfExists) {
|
|
3239
|
+
console.log(`Account ${currentEmail} is already managed.`);
|
|
3240
|
+
return {
|
|
3241
|
+
action: "unchanged",
|
|
3242
|
+
accountNum: existingAccountNum,
|
|
3243
|
+
email: currentEmail
|
|
3244
|
+
};
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
const creds = await activeProvider.readActiveAuth();
|
|
3248
|
+
if (!creds) {
|
|
3249
|
+
throw new Error("No credentials found for current account");
|
|
3250
|
+
}
|
|
3251
|
+
const config = await activeProvider.readActiveConfig();
|
|
3252
|
+
let uuid = currentAccount?.accountId ?? "";
|
|
3253
|
+
if (activeProvider.usesAccountConfig && !config) {
|
|
3254
|
+
throw new Error("No config found for current account");
|
|
3255
|
+
}
|
|
3256
|
+
if (existingAccountNum) {
|
|
3257
|
+
const updatedSeq = {
|
|
3258
|
+
...seq,
|
|
3259
|
+
activeAccountNumber: Number(existingAccountNum),
|
|
3260
|
+
lastUpdated: new Date().toISOString(),
|
|
3261
|
+
accounts: {
|
|
3262
|
+
...seq.accounts,
|
|
3263
|
+
[existingAccountNum]: {
|
|
3264
|
+
...seq.accounts[existingAccountNum],
|
|
3265
|
+
uuid,
|
|
3266
|
+
...options?.alias ? { alias: options.alias } : {}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
await activeProvider.writeAccountAuth(existingAccountNum, currentEmail, creds, activeCredentialsDir);
|
|
3271
|
+
if (config) {
|
|
3272
|
+
await activeProvider.writeAccountConfig(existingAccountNum, currentEmail, config, activeConfigsDir);
|
|
3273
|
+
}
|
|
3274
|
+
await writeJsonAtomic2(activeSequenceFile, updatedSeq);
|
|
3275
|
+
return {
|
|
3276
|
+
action: "updated",
|
|
3277
|
+
accountNum: existingAccountNum,
|
|
3278
|
+
email: currentEmail
|
|
3279
|
+
};
|
|
3280
|
+
}
|
|
3281
|
+
const updated = addAccountToSequence(seq, {
|
|
3282
|
+
email: currentEmail,
|
|
3283
|
+
uuid,
|
|
3284
|
+
alias: options?.alias
|
|
3285
|
+
});
|
|
3286
|
+
const accountNum = String(updated.activeAccountNumber);
|
|
3287
|
+
await activeProvider.writeAccountAuth(accountNum, currentEmail, creds, activeCredentialsDir);
|
|
3288
|
+
if (config) {
|
|
3289
|
+
await activeProvider.writeAccountConfig(accountNum, currentEmail, config, activeConfigsDir);
|
|
3290
|
+
}
|
|
3291
|
+
await writeJsonAtomic2(activeSequenceFile, updated);
|
|
3292
|
+
return {
|
|
3293
|
+
action: "added",
|
|
3294
|
+
accountNum,
|
|
3295
|
+
email: currentEmail
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
function getLoginPassthroughArgs(args) {
|
|
3299
|
+
const passthroughIdx = args.indexOf("--");
|
|
3300
|
+
if (passthroughIdx === -1) {
|
|
3301
|
+
if (args.length > 0) {
|
|
3302
|
+
throw new Error("Provider login arguments must be passed after --");
|
|
3303
|
+
}
|
|
3304
|
+
return [];
|
|
3305
|
+
}
|
|
3306
|
+
if (passthroughIdx !== 0) {
|
|
3307
|
+
throw new Error("Provider login arguments must be passed after --");
|
|
3308
|
+
}
|
|
3309
|
+
return args.slice(passthroughIdx + 1);
|
|
3310
|
+
}
|
|
2963
3311
|
async function performSwitch(seq, targetAccount, options) {
|
|
2964
3312
|
const targetEmail = seq.accounts[targetAccount].email;
|
|
2965
3313
|
const currentEmail = options?.currentEmail ?? getCurrentAccount();
|
|
@@ -2987,9 +3335,8 @@ async function performSwitch(seq, targetAccount, options) {
|
|
|
2987
3335
|
if (currentCreds) {
|
|
2988
3336
|
await activeProvider.writeAccountAuth(currentAccount, currentEmail, currentCreds, activeCredentialsDir);
|
|
2989
3337
|
}
|
|
2990
|
-
if (activeProvider.
|
|
2991
|
-
const
|
|
2992
|
-
const currentConfig = existsSync10(configPath) ? readFileSync10(configPath, "utf-8") : "";
|
|
3338
|
+
if (activeProvider.usesAccountConfig) {
|
|
3339
|
+
const currentConfig = await activeProvider.readActiveConfig();
|
|
2993
3340
|
if (currentConfig) {
|
|
2994
3341
|
await activeProvider.writeAccountConfig(currentAccount, currentEmail, currentConfig, activeConfigsDir);
|
|
2995
3342
|
}
|
|
@@ -3000,23 +3347,12 @@ async function performSwitch(seq, targetAccount, options) {
|
|
|
3000
3347
|
if (!targetCreds) {
|
|
3001
3348
|
throw new Error(`Missing backup data for ${getDisplayAccountLabel(seq, targetAccount)}`);
|
|
3002
3349
|
}
|
|
3003
|
-
if (activeProvider.
|
|
3350
|
+
if (activeProvider.usesAccountConfig && !targetConfig) {
|
|
3004
3351
|
throw new Error(`Missing backup data for ${getDisplayAccountLabel(seq, targetAccount)}`);
|
|
3005
3352
|
}
|
|
3006
3353
|
await activeProvider.writeActiveAuth(targetCreds);
|
|
3007
|
-
if (
|
|
3008
|
-
|
|
3009
|
-
const oauthAccount = targetConfigObj.oauthAccount;
|
|
3010
|
-
if (!oauthAccount) {
|
|
3011
|
-
throw new Error("Invalid oauthAccount in backup");
|
|
3012
|
-
}
|
|
3013
|
-
const configPath = getClaudeConfigPath();
|
|
3014
|
-
let currentConfigObj = {};
|
|
3015
|
-
if (existsSync10(configPath)) {
|
|
3016
|
-
currentConfigObj = JSON.parse(readFileSync10(configPath, "utf-8"));
|
|
3017
|
-
}
|
|
3018
|
-
currentConfigObj.oauthAccount = oauthAccount;
|
|
3019
|
-
await writeJsonAtomic2(configPath, currentConfigObj);
|
|
3354
|
+
if (targetConfig) {
|
|
3355
|
+
await activeProvider.writeActiveConfig(targetConfig);
|
|
3020
3356
|
}
|
|
3021
3357
|
seq.activeAccountNumber = Number(targetAccount);
|
|
3022
3358
|
seq.lastUpdated = new Date().toISOString();
|
|
@@ -3030,16 +3366,25 @@ Please restart ${getProviderLabel()} to use the new authentication.
|
|
|
3030
3366
|
`);
|
|
3031
3367
|
}
|
|
3032
3368
|
async function cmdList() {
|
|
3033
|
-
|
|
3369
|
+
const lines = await getManagedAccountLinesForActiveProvider();
|
|
3370
|
+
if (!lines) {
|
|
3034
3371
|
const providerCmd = activeProvider.name === "codex" ? "caflip codex add" : "caflip claude add";
|
|
3035
3372
|
console.log(`No accounts managed yet. Run: ${providerCmd}`);
|
|
3036
3373
|
return;
|
|
3037
3374
|
}
|
|
3375
|
+
console.log("Accounts:");
|
|
3376
|
+
for (const line of lines) {
|
|
3377
|
+
console.log(line);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
async function getManagedAccountLinesForActiveProvider() {
|
|
3381
|
+
if (!existsSync9(activeSequenceFile)) {
|
|
3382
|
+
return null;
|
|
3383
|
+
}
|
|
3038
3384
|
const seq = await loadSequence(activeSequenceFile);
|
|
3039
3385
|
await syncSequenceActiveAccount(seq);
|
|
3040
3386
|
const currentEmail = getCurrentAccount();
|
|
3041
|
-
|
|
3042
|
-
seq.sequence.forEach((num, index) => {
|
|
3387
|
+
return seq.sequence.map((num, index) => {
|
|
3043
3388
|
const numStr = String(num);
|
|
3044
3389
|
const account = seq.accounts[numStr];
|
|
3045
3390
|
if (!account) {
|
|
@@ -3051,64 +3396,44 @@ async function cmdList() {
|
|
|
3051
3396
|
line += ` [${account.alias}]`;
|
|
3052
3397
|
if (isActive)
|
|
3053
3398
|
line += " (active)";
|
|
3054
|
-
|
|
3399
|
+
return line;
|
|
3055
3400
|
});
|
|
3056
3401
|
}
|
|
3057
3402
|
async function cmdAdd(alias) {
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
const currentAccount = activeProvider.getCurrentAccount();
|
|
3061
|
-
const currentEmail = currentAccount?.email ?? "none";
|
|
3062
|
-
if (currentEmail === "none") {
|
|
3063
|
-
throw new Error(`No active ${getProviderLabel()} account found. Please log in first.`);
|
|
3064
|
-
}
|
|
3065
|
-
if (!sanitizeEmailForFilename(currentEmail)) {
|
|
3066
|
-
throw new Error("Current account email is not safe for storage");
|
|
3067
|
-
}
|
|
3068
|
-
const seq = await loadSequence(activeSequenceFile);
|
|
3069
|
-
await syncSequenceActiveAccount(seq);
|
|
3070
|
-
if (accountExists(seq, currentEmail)) {
|
|
3071
|
-
console.log(`Account ${currentEmail} is already managed.`);
|
|
3403
|
+
const result = await registerCurrentActiveAccount({ alias, updateIfExists: false });
|
|
3404
|
+
if (result.action === "unchanged") {
|
|
3072
3405
|
return;
|
|
3073
3406
|
}
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
if (!result.valid) {
|
|
3077
|
-
throw new Error(result.reason);
|
|
3078
|
-
}
|
|
3079
|
-
if (findAccountByAlias(seq, alias)) {
|
|
3080
|
-
throw new Error(`Alias "${alias}" is already in use`);
|
|
3081
|
-
}
|
|
3082
|
-
}
|
|
3083
|
-
const creds = await activeProvider.readActiveAuth();
|
|
3084
|
-
if (!creds) {
|
|
3085
|
-
throw new Error("No credentials found for current account");
|
|
3086
|
-
}
|
|
3087
|
-
let config = "";
|
|
3088
|
-
let uuid = currentAccount?.accountId ?? "";
|
|
3089
|
-
if (activeProvider.name === "claude") {
|
|
3090
|
-
const configPath = getClaudeConfigPath();
|
|
3091
|
-
config = readFileSync10(configPath, "utf-8");
|
|
3092
|
-
const configObj = JSON.parse(config);
|
|
3093
|
-
uuid = configObj.oauthAccount?.accountUuid ?? "";
|
|
3094
|
-
}
|
|
3095
|
-
const updated = addAccountToSequence(seq, {
|
|
3096
|
-
email: currentEmail,
|
|
3097
|
-
uuid,
|
|
3098
|
-
alias
|
|
3099
|
-
});
|
|
3100
|
-
const accountNum = String(updated.activeAccountNumber);
|
|
3101
|
-
const displayLabel = getDisplayAccountLabel(updated, accountNum);
|
|
3102
|
-
await activeProvider.writeAccountAuth(accountNum, currentEmail, creds, activeCredentialsDir);
|
|
3103
|
-
if (config) {
|
|
3104
|
-
await activeProvider.writeAccountConfig(accountNum, currentEmail, config, activeConfigsDir);
|
|
3105
|
-
}
|
|
3106
|
-
await writeJsonAtomic2(activeSequenceFile, updated);
|
|
3407
|
+
const seq = await loadSequence(activeSequenceFile);
|
|
3408
|
+
const displayLabel = getDisplayAccountLabel(seq, result.accountNum);
|
|
3107
3409
|
const aliasStr = alias ? ` [${alias}]` : "";
|
|
3108
|
-
console.log(`Added ${displayLabel}: ${
|
|
3410
|
+
console.log(`Added ${displayLabel}: ${result.email}${aliasStr}`);
|
|
3411
|
+
}
|
|
3412
|
+
async function cmdLogin(args) {
|
|
3413
|
+
const passthroughArgs = getLoginPassthroughArgs(args);
|
|
3414
|
+
const loginCommand = activeProvider.login.buildCommand(passthroughArgs);
|
|
3415
|
+
const execution = await runLoginCommand(loginCommand);
|
|
3416
|
+
if (execution.exitCode !== 0) {
|
|
3417
|
+
throw new Error(`${getProviderLabel()} login failed`);
|
|
3418
|
+
}
|
|
3419
|
+
const verification = await activeProvider.login.verifyLogin();
|
|
3420
|
+
if (!verification.ok) {
|
|
3421
|
+
throw new Error(verification.reason);
|
|
3422
|
+
}
|
|
3423
|
+
const result = await registerCurrentActiveAccount({
|
|
3424
|
+
updateIfExists: true,
|
|
3425
|
+
expectedEmail: verification.email
|
|
3426
|
+
});
|
|
3427
|
+
const seq = await loadSequence(activeSequenceFile);
|
|
3428
|
+
const displayLabel = getDisplayAccountLabel(seq, result.accountNum);
|
|
3429
|
+
const verb = result.action === "added" ? "Added" : "Updated";
|
|
3430
|
+
console.log(`${verb} ${displayLabel}: ${result.email}`);
|
|
3431
|
+
}
|
|
3432
|
+
function validateLoginArgs(args) {
|
|
3433
|
+
getLoginPassthroughArgs(args);
|
|
3109
3434
|
}
|
|
3110
3435
|
async function cmdRemove(identifier) {
|
|
3111
|
-
if (!
|
|
3436
|
+
if (!existsSync9(activeSequenceFile)) {
|
|
3112
3437
|
throw new Error("No accounts managed yet");
|
|
3113
3438
|
}
|
|
3114
3439
|
const seq = await loadSequence(activeSequenceFile);
|
|
@@ -3144,9 +3469,7 @@ async function cmdRemove(identifier) {
|
|
|
3144
3469
|
await performSwitch(seq, action.targetAccountNumber);
|
|
3145
3470
|
} else if (action.type === "logout") {
|
|
3146
3471
|
await activeProvider.clearActiveAuth();
|
|
3147
|
-
|
|
3148
|
-
await clearActiveOAuthAccount();
|
|
3149
|
-
}
|
|
3472
|
+
await activeProvider.clearActiveConfig();
|
|
3150
3473
|
}
|
|
3151
3474
|
await activeProvider.deleteAccountAuth(accountNum, account.email, activeCredentialsDir);
|
|
3152
3475
|
activeProvider.deleteAccountConfig(accountNum, account.email, activeConfigsDir);
|
|
@@ -3154,7 +3477,7 @@ async function cmdRemove(identifier) {
|
|
|
3154
3477
|
console.log(`${getDisplayAccountLabel(seq, accountNum)} (${account.email}) has been removed`);
|
|
3155
3478
|
}
|
|
3156
3479
|
async function cmdNext() {
|
|
3157
|
-
if (!
|
|
3480
|
+
if (!existsSync9(activeSequenceFile)) {
|
|
3158
3481
|
throw new Error("No accounts managed yet");
|
|
3159
3482
|
}
|
|
3160
3483
|
const seq = await loadSequence(activeSequenceFile);
|
|
@@ -3166,11 +3489,32 @@ async function cmdNext() {
|
|
|
3166
3489
|
await performSwitch(seq, String(nextNum));
|
|
3167
3490
|
}
|
|
3168
3491
|
async function cmdStatus(options) {
|
|
3492
|
+
const summary = await getStatusSummaryForActiveProvider();
|
|
3169
3493
|
const jsonMode = options?.json ?? false;
|
|
3494
|
+
if (jsonMode) {
|
|
3495
|
+
console.log(JSON.stringify({
|
|
3496
|
+
provider: activeProvider.name,
|
|
3497
|
+
email: summary.email === "none" ? null : summary.email,
|
|
3498
|
+
alias: summary.alias,
|
|
3499
|
+
managed: summary.managed
|
|
3500
|
+
}));
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
if (summary.email === "none") {
|
|
3504
|
+
console.log("none");
|
|
3505
|
+
} else {
|
|
3506
|
+
if (summary.alias) {
|
|
3507
|
+
console.log(`${summary.email} [${summary.alias}]`);
|
|
3508
|
+
return;
|
|
3509
|
+
}
|
|
3510
|
+
console.log(summary.email);
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
async function getStatusSummaryForActiveProvider() {
|
|
3170
3514
|
const email = getCurrentAccount();
|
|
3171
3515
|
let alias = null;
|
|
3172
3516
|
let managed = false;
|
|
3173
|
-
if (email !== "none" &&
|
|
3517
|
+
if (email !== "none" && existsSync9(activeSequenceFile)) {
|
|
3174
3518
|
const seq = await loadSequence(activeSequenceFile);
|
|
3175
3519
|
for (const account of Object.values(seq.accounts)) {
|
|
3176
3520
|
if (account.email === email) {
|
|
@@ -3180,27 +3524,67 @@ async function cmdStatus(options) {
|
|
|
3180
3524
|
}
|
|
3181
3525
|
}
|
|
3182
3526
|
}
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3527
|
+
return { email, alias, managed };
|
|
3528
|
+
}
|
|
3529
|
+
async function withActiveProvider(provider, fn) {
|
|
3530
|
+
const previousProvider = activeProvider.name;
|
|
3531
|
+
setActiveProvider(provider);
|
|
3532
|
+
try {
|
|
3533
|
+
return await fn();
|
|
3534
|
+
} finally {
|
|
3535
|
+
setActiveProvider(previousProvider);
|
|
3191
3536
|
}
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3537
|
+
}
|
|
3538
|
+
async function cmdListAllProviders() {
|
|
3539
|
+
for (const [index, provider] of SUPPORTED_PROVIDERS.entries()) {
|
|
3540
|
+
const output = await withActiveProvider(provider, async () => {
|
|
3541
|
+
const heading = getProviderLabel();
|
|
3542
|
+
const lines = await getManagedAccountLinesForActiveProvider();
|
|
3543
|
+
if (!lines) {
|
|
3544
|
+
return {
|
|
3545
|
+
heading,
|
|
3546
|
+
lines: [`No accounts managed yet. Run: caflip ${provider} add`]
|
|
3547
|
+
};
|
|
3548
|
+
}
|
|
3549
|
+
return {
|
|
3550
|
+
heading,
|
|
3551
|
+
lines: ["Accounts:", ...lines.map((line) => line.slice(2))]
|
|
3552
|
+
};
|
|
3553
|
+
});
|
|
3554
|
+
if (index > 0) {
|
|
3555
|
+
console.log("");
|
|
3198
3556
|
}
|
|
3199
|
-
console.log(
|
|
3557
|
+
console.log(output.heading);
|
|
3558
|
+
for (const line of output.lines) {
|
|
3559
|
+
console.log(` ${line}`);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
}
|
|
3563
|
+
async function cmdStatusAllProviders() {
|
|
3564
|
+
for (const [index, provider] of SUPPORTED_PROVIDERS.entries()) {
|
|
3565
|
+
const summary = await withActiveProvider(provider, async () => {
|
|
3566
|
+
return {
|
|
3567
|
+
heading: getProviderLabel(),
|
|
3568
|
+
...await getStatusSummaryForActiveProvider()
|
|
3569
|
+
};
|
|
3570
|
+
});
|
|
3571
|
+
if (index > 0) {
|
|
3572
|
+
console.log("");
|
|
3573
|
+
}
|
|
3574
|
+
console.log(summary.heading);
|
|
3575
|
+
if (summary.email === "none") {
|
|
3576
|
+
console.log(" none");
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
if (summary.alias) {
|
|
3580
|
+
console.log(` ${summary.email} [${summary.alias}]`);
|
|
3581
|
+
continue;
|
|
3582
|
+
}
|
|
3583
|
+
console.log(` ${summary.email}`);
|
|
3200
3584
|
}
|
|
3201
3585
|
}
|
|
3202
3586
|
async function cmdAlias(alias, identifier) {
|
|
3203
|
-
if (!
|
|
3587
|
+
if (!existsSync9(activeSequenceFile)) {
|
|
3204
3588
|
throw new Error("No accounts managed yet");
|
|
3205
3589
|
}
|
|
3206
3590
|
const result = validateAlias(alias);
|
|
@@ -3229,7 +3613,7 @@ async function cmdAlias(alias, identifier) {
|
|
|
3229
3613
|
}
|
|
3230
3614
|
async function cmdInteractiveSwitch() {
|
|
3231
3615
|
const currentEmail = getCurrentAccount();
|
|
3232
|
-
const hasSequence =
|
|
3616
|
+
const hasSequence = existsSync9(activeSequenceFile);
|
|
3233
3617
|
const seq = hasSequence ? await loadSequence(activeSequenceFile) : null;
|
|
3234
3618
|
if (seq) {
|
|
3235
3619
|
await syncSequenceActiveAccount(seq);
|
|
@@ -3267,10 +3651,13 @@ Usage:
|
|
|
3267
3651
|
|
|
3268
3652
|
Commands:
|
|
3269
3653
|
(no args) Interactive provider picker
|
|
3654
|
+
list List managed accounts for all providers
|
|
3655
|
+
status Show current account for all providers
|
|
3270
3656
|
<provider> Interactive account picker for provider
|
|
3271
3657
|
<provider> <alias> Switch by alias for provider
|
|
3272
3658
|
<provider> list List all managed accounts
|
|
3273
3659
|
<provider> add [--alias <name>] Add current account
|
|
3660
|
+
<provider> login [-- <args...>] Run provider login and register session
|
|
3274
3661
|
<provider> remove [<email>] Remove an account
|
|
3275
3662
|
<provider> next Rotate to next account
|
|
3276
3663
|
<provider> status [--json] Show current account
|
|
@@ -3279,11 +3666,17 @@ Commands:
|
|
|
3279
3666
|
|
|
3280
3667
|
Examples:
|
|
3281
3668
|
caflip Pick provider interactively
|
|
3669
|
+
caflip list List managed accounts for Claude and Codex
|
|
3670
|
+
caflip status Show current account for Claude and Codex
|
|
3282
3671
|
caflip claude Pick Claude account interactively
|
|
3283
3672
|
caflip claude work Switch Claude account by alias
|
|
3284
3673
|
caflip claude add --alias personal Add current Claude account with alias
|
|
3674
|
+
caflip claude login Run Claude login and register session
|
|
3675
|
+
caflip claude login -- --email me@example.com --sso
|
|
3676
|
+
Pass provider-specific flags after --
|
|
3285
3677
|
caflip claude status --json Show Claude status as JSON
|
|
3286
3678
|
caflip codex list List managed Codex accounts
|
|
3679
|
+
caflip codex login -- --device-auth Run Codex login and register session
|
|
3287
3680
|
caflip codex add --alias work Add current Codex account with alias
|
|
3288
3681
|
caflip codex alias work user@company.com
|
|
3289
3682
|
Set Codex alias for target email`);
|
|
@@ -3312,11 +3705,10 @@ async function main() {
|
|
|
3312
3705
|
return await runWithLock(fn);
|
|
3313
3706
|
};
|
|
3314
3707
|
const isHelpCommand = command === "help" || command === "--help" || command === "-h";
|
|
3315
|
-
|
|
3708
|
+
const isProviderOptionalReadOnlyCommand = command === "list" || command === "status";
|
|
3709
|
+
if (!parsed.isProviderQualified && command && !isHelpCommand && !isProviderOptionalReadOnlyCommand) {
|
|
3316
3710
|
if (RESERVED_COMMANDS.includes(command)) {
|
|
3317
|
-
|
|
3318
|
-
console.error("Try: caflip claude list");
|
|
3319
|
-
process.exit(2);
|
|
3711
|
+
showProviderRequiredError(command);
|
|
3320
3712
|
} else {
|
|
3321
3713
|
console.error("Error: Alias requires provider prefix.");
|
|
3322
3714
|
console.error("Try: caflip claude <alias> or caflip codex <alias>");
|
|
@@ -3343,9 +3735,20 @@ async function main() {
|
|
|
3343
3735
|
return;
|
|
3344
3736
|
}
|
|
3345
3737
|
if (!provider) {
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
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);
|
|
3349
3752
|
}
|
|
3350
3753
|
setActiveProvider(provider);
|
|
3351
3754
|
switch (command) {
|
|
@@ -3375,12 +3778,19 @@ async function main() {
|
|
|
3375
3778
|
});
|
|
3376
3779
|
break;
|
|
3377
3780
|
}
|
|
3781
|
+
case "login": {
|
|
3782
|
+
validateLoginArgs(args.slice(1));
|
|
3783
|
+
await runWithLock(async () => {
|
|
3784
|
+
await cmdLogin(args.slice(1));
|
|
3785
|
+
});
|
|
3786
|
+
break;
|
|
3787
|
+
}
|
|
3378
3788
|
case "status":
|
|
3379
3789
|
await cmdStatus({ json: args.includes("--json") });
|
|
3380
3790
|
break;
|
|
3381
3791
|
case "alias": {
|
|
3382
3792
|
if (!args[1]) {
|
|
3383
|
-
console.error(
|
|
3793
|
+
console.error(`Usage: caflip ${provider} alias <name> [<email>]`);
|
|
3384
3794
|
process.exit(1);
|
|
3385
3795
|
}
|
|
3386
3796
|
await runWithLock(async () => {
|
|
@@ -3389,7 +3799,7 @@ async function main() {
|
|
|
3389
3799
|
break;
|
|
3390
3800
|
}
|
|
3391
3801
|
default: {
|
|
3392
|
-
if (
|
|
3802
|
+
if (existsSync9(activeSequenceFile)) {
|
|
3393
3803
|
const seq = await loadSequence(activeSequenceFile);
|
|
3394
3804
|
const accountNum = findAccountByAlias(seq, command);
|
|
3395
3805
|
if (accountNum) {
|
package/install.sh
CHANGED