@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 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
- # 1. Login with your Claude account (one-time)
11
- npx @suncreation/crush-auth-proxy setup-token
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
- # 3. Configure Crush (see below)
14
+ That's it! Now use Crush:
17
15
 
18
- # 4. Use Crush normally
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-token` | OAuth login opens browser, saves token locally |
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
 
@@ -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-token OAuth login (run this first!)
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
- 1. npx @suncreation/crush-auth-proxy setup-token
90
- 2. Add to ~/.config/crush/crush.json:
91
- {
92
- "providers": {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@suncreation/crush-auth-proxy",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Claude Code Auth Proxy for Crush CLI — Use your Claude Max subscription (OAuth) instead of API keys",
5
5
  "type": "module",
6
6
  "bin": {