arbiter-cli 0.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/index.mjs +129 -0
- package/package.json +12 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Arbiter CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx arbiter-cli init Set up Arbiter in current project
|
|
8
|
+
* npx arbiter-cli status Check connection to Arbiter
|
|
9
|
+
* npx arbiter-cli stats View your savings
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs'
|
|
13
|
+
import { resolve } from 'path'
|
|
14
|
+
|
|
15
|
+
const ARBITER_URL = 'https://arbiter-proxy-long-summit-6283.fly.dev'
|
|
16
|
+
const ENV_LINE = `OPENAI_BASE_URL=${ARBITER_URL}/v1`
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2)
|
|
19
|
+
const command = args[0] || 'init'
|
|
20
|
+
|
|
21
|
+
const colors = {
|
|
22
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
23
|
+
cyan: (s) => `\x1b[36m${s}\x1b[0m`,
|
|
24
|
+
dim: (s) => `\x1b[2m${s}\x1b[0m`,
|
|
25
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
26
|
+
yellow: (s) => `\x1b[33m${s}\x1b[0m`,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
console.log('')
|
|
30
|
+
console.log(colors.bold(' Arbiter') + colors.dim(' — AI inference optimization'))
|
|
31
|
+
console.log('')
|
|
32
|
+
|
|
33
|
+
if (command === 'init') {
|
|
34
|
+
init()
|
|
35
|
+
} else if (command === 'status') {
|
|
36
|
+
await status()
|
|
37
|
+
} else if (command === 'stats') {
|
|
38
|
+
await stats()
|
|
39
|
+
} else {
|
|
40
|
+
console.log(` Commands:`)
|
|
41
|
+
console.log(` ${colors.cyan('init')} Set up Arbiter in current project`)
|
|
42
|
+
console.log(` ${colors.cyan('status')} Check connection to Arbiter`)
|
|
43
|
+
console.log(` ${colors.cyan('stats')} View your cost savings`)
|
|
44
|
+
console.log('')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function init() {
|
|
48
|
+
const envPath = resolve(process.cwd(), '.env')
|
|
49
|
+
const gitignorePath = resolve(process.cwd(), '.gitignore')
|
|
50
|
+
|
|
51
|
+
// Check if already configured
|
|
52
|
+
if (existsSync(envPath)) {
|
|
53
|
+
const content = readFileSync(envPath, 'utf-8')
|
|
54
|
+
if (content.includes('OPENAI_BASE_URL') && content.includes('arbiter')) {
|
|
55
|
+
console.log(colors.green(' ✓ Already configured.'))
|
|
56
|
+
console.log(colors.dim(` ${envPath}`))
|
|
57
|
+
console.log('')
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Add to .env
|
|
63
|
+
const line = `\n# Arbiter — route all OpenAI calls through the optimizer\n${ENV_LINE}\n`
|
|
64
|
+
|
|
65
|
+
if (existsSync(envPath)) {
|
|
66
|
+
appendFileSync(envPath, line)
|
|
67
|
+
console.log(colors.green(' ✓ Added to existing .env'))
|
|
68
|
+
} else {
|
|
69
|
+
writeFileSync(envPath, `# Arbiter — route all OpenAI calls through the optimizer\n${ENV_LINE}\nOPENAI_API_KEY=sk-your-openrouter-key\n`)
|
|
70
|
+
console.log(colors.green(' ✓ Created .env'))
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Make sure .env is in .gitignore
|
|
74
|
+
if (existsSync(gitignorePath)) {
|
|
75
|
+
const gi = readFileSync(gitignorePath, 'utf-8')
|
|
76
|
+
if (!gi.includes('.env')) {
|
|
77
|
+
appendFileSync(gitignorePath, '\n.env\n')
|
|
78
|
+
console.log(colors.green(' ✓ Added .env to .gitignore'))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log('')
|
|
83
|
+
console.log(` ${colors.bold('Done.')} All OpenAI SDK calls in this project now route through Arbiter.`)
|
|
84
|
+
console.log('')
|
|
85
|
+
console.log(colors.dim(' No code changes needed. The OpenAI SDK reads OPENAI_BASE_URL automatically.'))
|
|
86
|
+
console.log(colors.dim(` Proxy: ${ARBITER_URL}/v1`))
|
|
87
|
+
console.log(colors.dim(' Dashboard: ' + ARBITER_URL + '/dashboard'))
|
|
88
|
+
console.log('')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function status() {
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(`${ARBITER_URL}/health`)
|
|
94
|
+
const data = await res.json()
|
|
95
|
+
console.log(colors.green(' ✓ Connected'))
|
|
96
|
+
console.log(colors.dim(` Status: ${data.status}`))
|
|
97
|
+
console.log(colors.dim(` Version: ${data.version}`))
|
|
98
|
+
console.log(colors.dim(` Models: ${data.models}`))
|
|
99
|
+
console.log(colors.dim(` Requests served: ${data.requests}`))
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.log(colors.yellow(' ✗ Cannot reach Arbiter'))
|
|
102
|
+
console.log(colors.dim(` ${e.message}`))
|
|
103
|
+
}
|
|
104
|
+
console.log('')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function stats() {
|
|
108
|
+
try {
|
|
109
|
+
const res = await fetch(`${ARBITER_URL}/api/analytics`)
|
|
110
|
+
const data = await res.json()
|
|
111
|
+
console.log(` ${colors.bold('Savings Report')}`)
|
|
112
|
+
console.log('')
|
|
113
|
+
console.log(` Requests routed: ${colors.cyan(data.total_requests)}`)
|
|
114
|
+
console.log(` Actual cost: ${colors.cyan('$' + data.total_actual_cost.toFixed(4))}`)
|
|
115
|
+
console.log(` Would have cost: ${colors.dim('$' + data.total_baseline_cost.toFixed(4))}`)
|
|
116
|
+
console.log(` ${colors.green('Saved: $' + data.total_savings.toFixed(4) + ' (' + data.savings_pct + '%)')}`)
|
|
117
|
+
console.log('')
|
|
118
|
+
if (data.models_used) {
|
|
119
|
+
console.log(` Models used:`)
|
|
120
|
+
for (const [model, count] of Object.entries(data.models_used)) {
|
|
121
|
+
console.log(` ${colors.dim(model.split('/').pop())}: ${count} requests`)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.log(colors.yellow(' ✗ Cannot reach Arbiter'))
|
|
126
|
+
console.log(colors.dim(` ${e.message}`))
|
|
127
|
+
}
|
|
128
|
+
console.log('')
|
|
129
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "arbiter-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Set up Arbiter in any project with one command",
|
|
5
|
+
"bin": {
|
|
6
|
+
"arbiter": "./index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": ["llm", "proxy", "openai", "cost", "optimization"],
|
|
10
|
+
"author": "Muhammad Saqib",
|
|
11
|
+
"license": "MIT"
|
|
12
|
+
}
|