@suncreation/crush-auth-proxy 1.0.0 → 1.1.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 +22 -25
- package/bin/crush-auth-proxy.mjs +209 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,23 +7,27 @@ This proxy sits between Crush and Anthropic's API, injecting Claude Code OAuth c
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
#
|
|
11
|
-
npx @suncreation/crush-auth-proxy setup
|
|
12
|
-
|
|
13
|
-
# 2. Start the proxy daemon
|
|
14
|
-
npx @suncreation/crush-auth-proxy start
|
|
10
|
+
# One command does everything: login + crush config + auto-start
|
|
11
|
+
npx @suncreation/crush-auth-proxy setup
|
|
12
|
+
```
|
|
15
13
|
|
|
16
|
-
|
|
14
|
+
That's it! Now use Crush:
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
```bash
|
|
19
17
|
crush run --model anthropic/claude-opus-4-6 "Hello!"
|
|
20
18
|
```
|
|
21
19
|
|
|
20
|
+
The proxy auto-starts on boot. No manual configuration needed.
|
|
21
|
+
|
|
22
22
|
## Commands
|
|
23
23
|
|
|
24
24
|
| Command | Description |
|
|
25
25
|
|---------|-------------|
|
|
26
|
-
| `setup
|
|
26
|
+
| `setup` | **All-in-one**: OAuth login + crush.json config + auto-start service |
|
|
27
|
+
| `setup-token` | OAuth login only — opens browser, saves token locally |
|
|
28
|
+
| `setup-crush` | Configure `crush.json` only (adds anthropic provider) |
|
|
29
|
+
| `install-service` | Install macOS auto-start service (launchd) |
|
|
30
|
+
| `uninstall-service` | Remove auto-start service |
|
|
27
31
|
| `start` | Start proxy as background daemon (port 18080) |
|
|
28
32
|
| `stop` | Stop the daemon |
|
|
29
33
|
| `restart` | Restart the daemon |
|
|
@@ -37,23 +41,6 @@ crush run --model anthropic/claude-opus-4-6 "Hello!"
|
|
|
37
41
|
| `--port <n>` | Custom port (default: 18080) |
|
|
38
42
|
| `--foreground` | Run in foreground instead of daemon |
|
|
39
43
|
|
|
40
|
-
## Crush Configuration
|
|
41
|
-
|
|
42
|
-
Create or edit `~/.config/crush/crush.json`:
|
|
43
|
-
|
|
44
|
-
```json
|
|
45
|
-
{
|
|
46
|
-
"providers": {
|
|
47
|
-
"anthropic": {
|
|
48
|
-
"id": "anthropic",
|
|
49
|
-
"type": "anthropic",
|
|
50
|
-
"base_url": "http://127.0.0.1:18080",
|
|
51
|
-
"api_key": "proxy-handled"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
44
|
## How It Works
|
|
58
45
|
|
|
59
46
|
The proxy replicates Claude Code's authentication mechanism:
|
|
@@ -71,13 +58,23 @@ All config is stored in `~/.config/crush-auth-proxy/`:
|
|
|
71
58
|
```
|
|
72
59
|
~/.config/crush-auth-proxy/
|
|
73
60
|
├── claude-oauth-token.json # Your OAuth tokens (chmod 0600)
|
|
61
|
+
├── crush-auth-proxy.mjs # Stable copy for auto-start service
|
|
74
62
|
├── proxy.pid # Daemon PID
|
|
75
63
|
└── proxy.log # Daemon logs
|
|
76
64
|
```
|
|
77
65
|
|
|
66
|
+
## Uninstall
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx @suncreation/crush-auth-proxy uninstall-service
|
|
70
|
+
npx @suncreation/crush-auth-proxy stop
|
|
71
|
+
rm -rf ~/.config/crush-auth-proxy
|
|
72
|
+
```
|
|
73
|
+
|
|
78
74
|
## Requirements
|
|
79
75
|
|
|
80
76
|
- Node.js >= 18
|
|
77
|
+
- macOS (for auto-start via launchd; proxy itself works on any OS)
|
|
81
78
|
- A Claude Max subscription (claude.ai account)
|
|
82
79
|
- [Crush CLI](https://github.com/charmbracelet/crush) installed
|
|
83
80
|
|
package/bin/crush-auth-proxy.mjs
CHANGED
|
@@ -70,7 +70,11 @@ Uses your Claude Max subscription via OAuth so you don't need
|
|
|
70
70
|
a separate Anthropic API key with Crush.
|
|
71
71
|
|
|
72
72
|
COMMANDS:
|
|
73
|
-
setup-
|
|
73
|
+
setup All-in-one: login + crush config + auto-start
|
|
74
|
+
setup-token OAuth login only
|
|
75
|
+
setup-crush Configure crush.json only
|
|
76
|
+
install-service Install macOS auto-start (launchd)
|
|
77
|
+
uninstall-service Remove auto-start service
|
|
74
78
|
start Start proxy as background daemon
|
|
75
79
|
stop Stop the background daemon
|
|
76
80
|
restart Restart the daemon
|
|
@@ -85,21 +89,11 @@ OPTIONS:
|
|
|
85
89
|
ENVIRONMENT:
|
|
86
90
|
PORT Override default port (18080)
|
|
87
91
|
|
|
88
|
-
QUICK START:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
"anthropic": {
|
|
94
|
-
"id": "anthropic",
|
|
95
|
-
"type": "anthropic",
|
|
96
|
-
"base_url": "http://127.0.0.1:18080",
|
|
97
|
-
"api_key": "proxy-handled"
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
3. npx @suncreation/crush-auth-proxy start
|
|
102
|
-
4. crush run --model anthropic/claude-opus-4-6 "hello"
|
|
92
|
+
QUICK START (recommended):
|
|
93
|
+
npx @suncreation/crush-auth-proxy setup
|
|
94
|
+
|
|
95
|
+
That's it! Sets up OAuth, crush.json, and auto-start in one step.
|
|
96
|
+
Then just use: crush run --model anthropic/claude-opus-4-6 "hello"
|
|
103
97
|
|
|
104
98
|
CONFIG DIR: ~/.config/crush-auth-proxy/
|
|
105
99
|
`);
|
|
@@ -349,6 +343,205 @@ async function getValidToken() {
|
|
|
349
343
|
return token;
|
|
350
344
|
}
|
|
351
345
|
|
|
346
|
+
// ── Crush Config Helper ────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
const CRUSH_CONFIG_DIR = path.join(os.homedir(), ".config", "crush");
|
|
349
|
+
const CRUSH_CONFIG_FILE = path.join(CRUSH_CONFIG_DIR, "crush.json");
|
|
350
|
+
|
|
351
|
+
function setupCrushConfig(port = DEFAULT_PORT) {
|
|
352
|
+
fs.mkdirSync(CRUSH_CONFIG_DIR, { recursive: true });
|
|
353
|
+
|
|
354
|
+
let config = {};
|
|
355
|
+
try {
|
|
356
|
+
config = JSON.parse(fs.readFileSync(CRUSH_CONFIG_FILE, "utf8"));
|
|
357
|
+
} catch {}
|
|
358
|
+
|
|
359
|
+
if (!config.providers) config.providers = {};
|
|
360
|
+
config.providers.anthropic = {
|
|
361
|
+
id: "anthropic",
|
|
362
|
+
type: "anthropic",
|
|
363
|
+
base_url: `http://127.0.0.1:${port}`,
|
|
364
|
+
api_key: "proxy-handled",
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
// Preserve $schema if present
|
|
368
|
+
if (!config.$schema) config.$schema = "https://charm.land/crush.json";
|
|
369
|
+
|
|
370
|
+
fs.writeFileSync(CRUSH_CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
371
|
+
console.log(`[setup] crush.json updated: ${CRUSH_CONFIG_FILE}`);
|
|
372
|
+
console.log(`[setup] anthropic provider → http://127.0.0.1:${port}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ── Launchd Service Helper ─────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
const LAUNCHD_LABEL = "com.suncreation.crush-auth-proxy";
|
|
378
|
+
const LAUNCHD_PLIST = path.join(
|
|
379
|
+
os.homedir(),
|
|
380
|
+
"Library",
|
|
381
|
+
"LaunchAgents",
|
|
382
|
+
`${LAUNCHD_LABEL}.plist`
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
function getNodePath() {
|
|
386
|
+
try {
|
|
387
|
+
return execSync("which node", { encoding: "utf8" }).trim();
|
|
388
|
+
} catch {
|
|
389
|
+
return process.execPath;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function installLaunchd(port = DEFAULT_PORT) {
|
|
394
|
+
const nodePath = getNodePath();
|
|
395
|
+
// Copy script to stable config dir so launchd always has a valid path
|
|
396
|
+
const stablePath = path.join(CONFIG_DIR, "crush-auth-proxy.mjs");
|
|
397
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
398
|
+
fs.copyFileSync(__filename, stablePath);
|
|
399
|
+
const scriptPath = stablePath;
|
|
400
|
+
|
|
401
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
402
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
403
|
+
<plist version="1.0">
|
|
404
|
+
<dict>
|
|
405
|
+
<key>Label</key>
|
|
406
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
407
|
+
<key>ProgramArguments</key>
|
|
408
|
+
<array>
|
|
409
|
+
<string>${nodePath}</string>
|
|
410
|
+
<string>${scriptPath}</string>
|
|
411
|
+
<string>--foreground</string>
|
|
412
|
+
<string>--port</string>
|
|
413
|
+
<string>${port}</string>
|
|
414
|
+
</array>
|
|
415
|
+
<key>RunAtLoad</key>
|
|
416
|
+
<true/>
|
|
417
|
+
<key>KeepAlive</key>
|
|
418
|
+
<true/>
|
|
419
|
+
<key>StandardOutPath</key>
|
|
420
|
+
<string>${LOG_FILE}</string>
|
|
421
|
+
<key>StandardErrorPath</key>
|
|
422
|
+
<string>${LOG_FILE}</string>
|
|
423
|
+
<key>EnvironmentVariables</key>
|
|
424
|
+
<dict>
|
|
425
|
+
<key>PATH</key>
|
|
426
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
427
|
+
</dict>
|
|
428
|
+
</dict>
|
|
429
|
+
</plist>`;
|
|
430
|
+
|
|
431
|
+
fs.mkdirSync(path.dirname(LAUNCHD_PLIST), { recursive: true });
|
|
432
|
+
fs.writeFileSync(LAUNCHD_PLIST, plist);
|
|
433
|
+
console.log(`[setup] Launchd plist installed: ${LAUNCHD_PLIST}`);
|
|
434
|
+
|
|
435
|
+
// Unload old, load new
|
|
436
|
+
try { execSync(`launchctl unload "${LAUNCHD_PLIST}" 2>/dev/null`); } catch {}
|
|
437
|
+
try {
|
|
438
|
+
execSync(`launchctl load "${LAUNCHD_PLIST}"`);
|
|
439
|
+
console.log("[setup] Service loaded — proxy will auto-start on boot");
|
|
440
|
+
} catch (e) {
|
|
441
|
+
console.error("[setup] Warning: launchctl load failed:", e.message);
|
|
442
|
+
console.log("[setup] You may need to load manually:");
|
|
443
|
+
console.log(` launchctl load "${LAUNCHD_PLIST}"`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function uninstallLaunchd() {
|
|
448
|
+
try { execSync(`launchctl unload "${LAUNCHD_PLIST}" 2>/dev/null`); } catch {}
|
|
449
|
+
try {
|
|
450
|
+
fs.unlinkSync(LAUNCHD_PLIST);
|
|
451
|
+
console.log(`[setup] Service removed: ${LAUNCHD_PLIST}`);
|
|
452
|
+
} catch {
|
|
453
|
+
console.log("[setup] No launchd service found");
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// ── Command: setup (all-in-one) ────────────────────────────────
|
|
458
|
+
|
|
459
|
+
if (command === "setup") {
|
|
460
|
+
console.log("[setup] ── crush-auth-proxy full setup ──\n");
|
|
461
|
+
|
|
462
|
+
// Step 1: Token
|
|
463
|
+
let token = loadToken();
|
|
464
|
+
if (token && token.expires_at > Date.now()) {
|
|
465
|
+
console.log(`[setup] ✓ Existing token found (expires: ${new Date(token.expires_at).toLocaleString()})`);
|
|
466
|
+
} else {
|
|
467
|
+
console.log("[setup] Step 1/3: OAuth login...");
|
|
468
|
+
try {
|
|
469
|
+
token = await doOAuthLogin();
|
|
470
|
+
console.log("[setup] ✓ Login successful!\n");
|
|
471
|
+
} catch (e) {
|
|
472
|
+
console.error("[setup] ✗ Login failed:", e.message);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Step 2: Crush config
|
|
478
|
+
console.log("[setup] Step 2/3: Configuring crush.json...");
|
|
479
|
+
setupCrushConfig(DEFAULT_PORT);
|
|
480
|
+
console.log("");
|
|
481
|
+
|
|
482
|
+
// Step 3: Launchd auto-start
|
|
483
|
+
console.log("[setup] Step 3/3: Installing auto-start service...");
|
|
484
|
+
// Stop any existing daemon first
|
|
485
|
+
const pid = getRunningPid();
|
|
486
|
+
if (pid) {
|
|
487
|
+
try { process.kill(pid, "SIGTERM"); } catch {}
|
|
488
|
+
try { fs.unlinkSync(PID_FILE); } catch {}
|
|
489
|
+
await sleepMs(500);
|
|
490
|
+
}
|
|
491
|
+
installLaunchd(DEFAULT_PORT);
|
|
492
|
+
|
|
493
|
+
await sleepMs(1500);
|
|
494
|
+
console.log("");
|
|
495
|
+
|
|
496
|
+
// Verify
|
|
497
|
+
try {
|
|
498
|
+
const health = execSync(`curl -s http://127.0.0.1:${DEFAULT_PORT}/health`, {
|
|
499
|
+
timeout: 3000,
|
|
500
|
+
}).toString();
|
|
501
|
+
console.log(`[setup] ✓ Proxy running on http://127.0.0.1:${DEFAULT_PORT} (health: ${health})`);
|
|
502
|
+
} catch {
|
|
503
|
+
console.log(`[setup] ⚠ Proxy may still be starting — check: crush-auth-proxy status`);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
console.log(`
|
|
507
|
+
── Setup complete! ──
|
|
508
|
+
|
|
509
|
+
You can now use Crush with Claude Max:
|
|
510
|
+
crush run --model anthropic/claude-opus-4-6 "hello"
|
|
511
|
+
|
|
512
|
+
The proxy auto-starts on boot. Manage with:
|
|
513
|
+
npx @suncreation/crush-auth-proxy status
|
|
514
|
+
npx @suncreation/crush-auth-proxy stop
|
|
515
|
+
npx @suncreation/crush-auth-proxy logs
|
|
516
|
+
`);
|
|
517
|
+
process.exit(0);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ── Command: setup-crush (just crush.json) ─────────────────────
|
|
521
|
+
|
|
522
|
+
if (command === "setup-crush") {
|
|
523
|
+
setupCrushConfig(DEFAULT_PORT);
|
|
524
|
+
process.exit(0);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ── Command: install-service ───────────────────────────────────
|
|
528
|
+
|
|
529
|
+
if (command === "install-service") {
|
|
530
|
+
if (os.platform() !== "darwin") {
|
|
531
|
+
console.error("[setup] install-service is macOS only (launchd)");
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
installLaunchd(DEFAULT_PORT);
|
|
535
|
+
process.exit(0);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ── Command: uninstall-service ─────────────────────────────────
|
|
539
|
+
|
|
540
|
+
if (command === "uninstall-service") {
|
|
541
|
+
uninstallLaunchd();
|
|
542
|
+
process.exit(0);
|
|
543
|
+
}
|
|
544
|
+
|
|
352
545
|
// ── Command: setup-token ───────────────────────────────────────
|
|
353
546
|
|
|
354
547
|
if (command === "setup-token") {
|
package/package.json
CHANGED