@andrej7510/ai-router 1.0.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 +105 -0
- package/ai.js +287 -0
- package/claude-load +1 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# ai-router
|
|
2
|
+
|
|
3
|
+
CLI orchestrator that automatically routes tasks to **OpenAI Codex** or **Anthropic Claude** based on task complexity — and shifts more work to Codex when Claude's context window is running high.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g ai-router
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires `codex` and `claude` CLIs to be installed and authenticated:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @openai/codex
|
|
15
|
+
npm install -g @anthropic-ai/claude-code
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## How it works
|
|
19
|
+
|
|
20
|
+
Every task is scored 0–100 using keyword and length heuristics. Routing happens in two layers:
|
|
21
|
+
|
|
22
|
+
**1. Intent detection (takes priority)**
|
|
23
|
+
|
|
24
|
+
The task is matched against intent patterns first. Each intent maps to the most appropriate Claude model:
|
|
25
|
+
|
|
26
|
+
| Intent | Trigger keywords | Model |
|
|
27
|
+
|--------|-----------------|-------|
|
|
28
|
+
| `plan` | design, architect, brainstorm, strategy, roadmap, scaffold | `claude-opus-4-5` |
|
|
29
|
+
| `security` | security, encrypt, auth, vulnerability, threat, audit, pentest | `claude-opus-4-5` |
|
|
30
|
+
| `debug` | why, broken, not working, crash, investigate, root cause | `claude-sonnet-4-6` |
|
|
31
|
+
| `explain` | explain, how does, analyse, understand, overview, summarize | `claude-sonnet-4-6` |
|
|
32
|
+
| `edit` | add, fix, rename, change, update, remove, refactor, extract | `claude-haiku-4-5` |
|
|
33
|
+
| `test` / `format` | unit test, lint, typo, format, mock, coverage | `claude-haiku-4-5` |
|
|
34
|
+
|
|
35
|
+
**2. Score-based fallback (no intent detected)**
|
|
36
|
+
|
|
37
|
+
| Score | Model |
|
|
38
|
+
|-------|-------|
|
|
39
|
+
| ≥ 80 | `claude-opus-4-5` |
|
|
40
|
+
| 60–79 | `claude-sonnet-4-6` |
|
|
41
|
+
| < 60 | `claude-haiku-4-5` |
|
|
42
|
+
|
|
43
|
+
Tasks that score below the routing threshold go to **Codex** regardless of intent.
|
|
44
|
+
|
|
45
|
+
**3. Load-shedding**
|
|
46
|
+
|
|
47
|
+
The routing threshold shifts automatically when Claude's context usage is high:
|
|
48
|
+
|
|
49
|
+
| Claude load | Threshold | Effect |
|
|
50
|
+
|-------------|-----------|--------|
|
|
51
|
+
| < 80% | 40 | Normal routing |
|
|
52
|
+
| ≥ 80% | 60 | Codex handles medium-complexity tasks |
|
|
53
|
+
| ≥ 90% | 80 | Almost everything goes to Codex |
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Auto-route based on complexity
|
|
59
|
+
ai "add a null check to the confirm handler" # → Codex
|
|
60
|
+
ai "why is the serial handshake timing out" # → Claude
|
|
61
|
+
ai "brainstorm an encryption strategy" # → Claude
|
|
62
|
+
|
|
63
|
+
# Force a specific tool
|
|
64
|
+
ai --codex "write a unit test for deriveDigest"
|
|
65
|
+
ai --claude "design the auth flow"
|
|
66
|
+
|
|
67
|
+
# Preview routing without running
|
|
68
|
+
ai --dry "your task here"
|
|
69
|
+
|
|
70
|
+
# Set Claude context usage (0–100)
|
|
71
|
+
ai --set-load 85
|
|
72
|
+
|
|
73
|
+
# Show current load + threshold
|
|
74
|
+
ai --status
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Load management
|
|
78
|
+
|
|
79
|
+
When your Claude session is approaching its context limit, tell the router:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ai --set-load 85 # Claude at 85% → threshold raises to 60
|
|
83
|
+
ai --set-load 92 # Claude at 92% → threshold raises to 80
|
|
84
|
+
ai --set-load 0 # Back to normal
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The load value is stored in `~/.ai-router/claude-load` and persists across sessions.
|
|
88
|
+
|
|
89
|
+
## Routing examples
|
|
90
|
+
|
|
91
|
+
| Task | Score | Intent | Model | At 85% | At 92% |
|
|
92
|
+
|------|-------|--------|-------|--------|--------|
|
|
93
|
+
| `rename function` | 10 | edit | — | Codex | Codex |
|
|
94
|
+
| `add a null check` | 20 | edit | — | Codex | Codex |
|
|
95
|
+
| `explain the escrow flow` | 42 | explain | `sonnet-4-6` | Codex | Codex |
|
|
96
|
+
| `debug serial timeout` | 74 | debug | `sonnet-4-6` | `sonnet-4-6` | Codex |
|
|
97
|
+
| `design an encryption strategy` | 86 | security | `opus-4-5` | `opus-4-5` | `opus-4-5` |
|
|
98
|
+
| `architect the payment flow` | 78 | plan | `opus-4-5` | `opus-4-5` | Codex |
|
|
99
|
+
| `why is the handshake failing` | 62 | debug | `sonnet-4-6` | `sonnet-4-6` | Codex |
|
|
100
|
+
|
|
101
|
+
## Future improvements
|
|
102
|
+
|
|
103
|
+
- Per-project `.ai-router.json` config (custom patterns + thresholds)
|
|
104
|
+
- Auto-detect Claude context usage via API
|
|
105
|
+
- Support additional AI CLI tools
|
package/ai.js
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ai — Orchestrator that routes tasks to Codex or Claude based on complexity
|
|
4
|
+
* and task intent, picking the cheapest Claude model that can do the job.
|
|
5
|
+
*
|
|
6
|
+
* Intent → model mapping:
|
|
7
|
+
* plan / strategy / architecture → PLAN model (most powerful)
|
|
8
|
+
* security / crypto / audit → SECURITY model
|
|
9
|
+
* debug / investigate → DEBUG model
|
|
10
|
+
* explain / analyse → EXPLAIN model
|
|
11
|
+
* edit / refactor / simple fix → EDIT model
|
|
12
|
+
* test / format / typo → EDIT model (cheapest)
|
|
13
|
+
* (no intent detected, fallback) → score-based (haiku / sonnet / opus)
|
|
14
|
+
*
|
|
15
|
+
* Load-shedding thresholds:
|
|
16
|
+
* Claude < 80% → threshold 40 (normal)
|
|
17
|
+
* Claude ≥ 80% → threshold 60 (Codex picks up medium tasks)
|
|
18
|
+
* Claude ≥ 90% → threshold 80 (almost everything → Codex)
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* ai "your task" → auto-route
|
|
22
|
+
* ai --codex "..." → force Codex
|
|
23
|
+
* ai --claude "..." → force Claude
|
|
24
|
+
* ai --dry "..." → show routing decision only
|
|
25
|
+
* ai --set-load 85 → tell router Claude is at 85% context usage
|
|
26
|
+
* ai --status → show current load + model table
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// ── Model map — update IDs here when new versions drop ───────────────────────
|
|
30
|
+
const MODELS = {
|
|
31
|
+
plan: "claude-opus-4-5", // planning, architecture, strategy
|
|
32
|
+
security: "claude-opus-4-5", // security, crypto, audits — high stakes
|
|
33
|
+
debug: "claude-sonnet-4-6", // debugging, investigation
|
|
34
|
+
explain: "claude-sonnet-4-6", // explanations, analysis
|
|
35
|
+
edit: "claude-haiku-4-5", // simple edits, tests, formatting
|
|
36
|
+
// score-based fallbacks (when no intent detected)
|
|
37
|
+
high: "claude-opus-4-5", // score ≥ 80
|
|
38
|
+
mid: "claude-sonnet-4-6", // score 60–79
|
|
39
|
+
low: "claude-haiku-4-5", // score < 60 (but above threshold)
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
import { execFileSync, execSync } from "child_process";
|
|
43
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
44
|
+
import { join } from "path";
|
|
45
|
+
import { homedir } from "os";
|
|
46
|
+
|
|
47
|
+
const LOAD_FILE = join(homedir(), ".ai-router", "claude-load");
|
|
48
|
+
|
|
49
|
+
// ── Claude load helpers ───────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function readLoad() {
|
|
52
|
+
if (!existsSync(LOAD_FILE)) return 0;
|
|
53
|
+
const val = parseInt(readFileSync(LOAD_FILE, "utf8").trim(), 10);
|
|
54
|
+
return isNaN(val) ? 0 : Math.max(0, Math.min(100, val));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeLoad(pct) {
|
|
58
|
+
writeFileSync(LOAD_FILE, String(pct), "utf8");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function thresholdForLoad(load) {
|
|
62
|
+
if (load >= 90) return 80; // near full — only very complex tasks reach Claude
|
|
63
|
+
if (load >= 80) return 60; // high load — Codex handles medium tasks too
|
|
64
|
+
return 40; // normal
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function loadLabel(load) {
|
|
68
|
+
if (load >= 90) return "CRITICAL (≥90%)";
|
|
69
|
+
if (load >= 80) return "HIGH (≥80%)";
|
|
70
|
+
return "NORMAL";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Intent detector ───────────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
const INTENT_PATTERNS = {
|
|
76
|
+
plan: /\b(plan|design|architect|brainstorm|strategy|roadmap|outline|structure|organiz|scaffold)\b/i,
|
|
77
|
+
security: /\b(security|encrypt|decrypt|auth(?:entication|oriz)?|vulnerabilit|threat|attack|ecdh|tls|aes|csrf|xss|injection|audit|pentest|harden)\b/i,
|
|
78
|
+
debug: /\b(debug|why (is|does|isn.t|doesn.t)|broken|not work(?:ing)?|doesn.t work|failing|crash(?:ing)?|error|investigate|trace|root cause|symptom)\b/i,
|
|
79
|
+
explain: /\b(explain|how does|what (is|does|are)|understand|analyse|analyze|overview|summarize|describe|clarify)\b/i,
|
|
80
|
+
edit: /\b(add|fix|rename|change|update|remove|delete|insert|set|replace|convert|refactor|extract|move|split|merge|cleanup|simplify)\b/i,
|
|
81
|
+
test: /\b(unit test|test for|write (a )?test|spec|coverage|mock|stub|format|lint|typo|comment|docstring)\b/i,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Detect the primary intent of a task string.
|
|
86
|
+
* Returns the first matching intent key, or null if none match.
|
|
87
|
+
* Priority order: plan > security > debug > explain > edit > test
|
|
88
|
+
*/
|
|
89
|
+
function detectIntent(task) {
|
|
90
|
+
for (const [intent, pattern] of Object.entries(INTENT_PATTERNS)) {
|
|
91
|
+
if (pattern.test(task)) return intent;
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Pick the Claude model for a task.
|
|
98
|
+
* Intent takes priority; score-based fallback when intent is ambiguous.
|
|
99
|
+
*/
|
|
100
|
+
function claudeModel(task, s) {
|
|
101
|
+
const intent = detectIntent(task);
|
|
102
|
+
if (intent === "plan") return { model: MODELS.plan, intent };
|
|
103
|
+
if (intent === "security") return { model: MODELS.security, intent };
|
|
104
|
+
if (intent === "debug") return { model: MODELS.debug, intent };
|
|
105
|
+
if (intent === "explain") return { model: MODELS.explain, intent };
|
|
106
|
+
if (intent === "edit") return { model: MODELS.edit, intent };
|
|
107
|
+
if (intent === "test") return { model: MODELS.edit, intent }; // haiku is fine
|
|
108
|
+
// fallback: score bands
|
|
109
|
+
const model = s >= 80 ? MODELS.high : s >= 60 ? MODELS.mid : MODELS.low;
|
|
110
|
+
return { model, intent: null };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Complexity scorer ─────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
const COMPLEX_PATTERNS = [
|
|
116
|
+
// Intent: understand, plan, analyse
|
|
117
|
+
/\b(why|how does|explain|understand|analyse|analyze|brainstorm|design|architect|plan|strategy)\b/i,
|
|
118
|
+
// Scope: whole system, many files
|
|
119
|
+
/\b(refactor|rewrite|migrate|reorgani[sz]e|across|system|all files?)\b/i,
|
|
120
|
+
// Debugging unknown issues
|
|
121
|
+
/\b(debug|broken|not work|doesn.t work|failing|crash|error|issue|investigate)\b/i,
|
|
122
|
+
// Security / crypto
|
|
123
|
+
/\b(security|encrypt|decrypt|auth|vulnerabilit|threat|attack|ecdh|tls|aes)\b/i,
|
|
124
|
+
// Open-ended / soft
|
|
125
|
+
/\b(best (way|practice)|should (i|we)|what (if|about)|trade.?off|compare|vs\.?|pros? and cons?)\b/i,
|
|
126
|
+
// Multi-step
|
|
127
|
+
/\b(first .* then|step[- ]by[- ]step|and (also|then)|multiple)\b/i,
|
|
128
|
+
];
|
|
129
|
+
|
|
130
|
+
const SIMPLE_PATTERNS = [
|
|
131
|
+
// Narrow scope
|
|
132
|
+
/\b(add|fix|rename|change|update|remove|delete|insert|set|replace|convert)\b/i,
|
|
133
|
+
// File-local
|
|
134
|
+
/\b(function|variable|parameter|field|method|class|import|type|interface|const|let|var)\b/i,
|
|
135
|
+
// Tests / formatting
|
|
136
|
+
/\b(unit test|test for|format|lint|typo|comment|docstring|log (line|statement))\b/i,
|
|
137
|
+
// Short task marker
|
|
138
|
+
/^.{0,60}$/,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
function score(task) {
|
|
142
|
+
let s = 50;
|
|
143
|
+
for (const p of COMPLEX_PATTERNS) if (p.test(task)) s += 12;
|
|
144
|
+
for (const p of SIMPLE_PATTERNS) if (p.test(task)) s -= 10;
|
|
145
|
+
if (task.length > 200) s += 15;
|
|
146
|
+
if (task.length > 100) s += 8;
|
|
147
|
+
if (task.length < 60) s -= 10;
|
|
148
|
+
return Math.max(0, Math.min(100, s));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── CLI arg parsing ───────────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
const args = process.argv.slice(2);
|
|
154
|
+
|
|
155
|
+
let forceCodex = false;
|
|
156
|
+
let forceClaude = false;
|
|
157
|
+
let dryRun = false;
|
|
158
|
+
let setLoad = null;
|
|
159
|
+
let showStatus = false;
|
|
160
|
+
const taskParts = [];
|
|
161
|
+
|
|
162
|
+
for (let i = 0; i < args.length; i++) {
|
|
163
|
+
const arg = args[i];
|
|
164
|
+
if (arg === "--codex") { forceCodex = true; continue; }
|
|
165
|
+
if (arg === "--claude") { forceClaude = true; continue; }
|
|
166
|
+
if (arg === "--dry") { dryRun = true; continue; }
|
|
167
|
+
if (arg === "--status") { showStatus = true; continue; }
|
|
168
|
+
if (arg === "--set-load") { setLoad = parseInt(args[++i], 10); continue; }
|
|
169
|
+
taskParts.push(arg);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ── --set-load ────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
if (setLoad !== null) {
|
|
175
|
+
if (isNaN(setLoad) || setLoad < 0 || setLoad > 100) {
|
|
176
|
+
console.error("Usage: ai --set-load <0-100>");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
writeLoad(setLoad);
|
|
180
|
+
const threshold = thresholdForLoad(setLoad);
|
|
181
|
+
console.log(`\n┌─ AI Router — Claude load updated ────────────────`);
|
|
182
|
+
console.log(`│ Claude usage: ${setLoad}% [${loadLabel(setLoad)}]`);
|
|
183
|
+
console.log(`│ Threshold: ${threshold} (score < ${threshold} → Codex)`);
|
|
184
|
+
console.log(`└──────────────────────────────────────────────────\n`);
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── --status ──────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
if (showStatus) {
|
|
191
|
+
const load = readLoad();
|
|
192
|
+
const threshold = thresholdForLoad(load);
|
|
193
|
+
console.log(`\n┌─ AI Router — Status ──────────────────────────────`);
|
|
194
|
+
console.log(`│ Claude usage: ${load}% [${loadLabel(load)}]`);
|
|
195
|
+
console.log(`│ Threshold: ${threshold} (score < ${threshold} → Codex)`);
|
|
196
|
+
console.log(`│`);
|
|
197
|
+
console.log(`│ Intent-based routing (takes priority over score):`);
|
|
198
|
+
console.log(`│ plan → ${MODELS.plan}`);
|
|
199
|
+
console.log(`│ security → ${MODELS.security}`);
|
|
200
|
+
console.log(`│ debug → ${MODELS.debug}`);
|
|
201
|
+
console.log(`│ explain → ${MODELS.explain}`);
|
|
202
|
+
console.log(`│ edit → ${MODELS.edit}`);
|
|
203
|
+
console.log(`│ test → ${MODELS.edit}`);
|
|
204
|
+
console.log(`│`);
|
|
205
|
+
console.log(`│ Score fallback (no intent detected):`);
|
|
206
|
+
console.log(`│ score ≥ 80 → ${MODELS.high}`);
|
|
207
|
+
console.log(`│ score 60–79 → ${MODELS.mid}`);
|
|
208
|
+
console.log(`│ score < 60 → ${MODELS.low}`);
|
|
209
|
+
console.log(`│`);
|
|
210
|
+
console.log(`│ Update with: ai --set-load <0-100>`);
|
|
211
|
+
console.log(`└──────────────────────────────────────────────────\n`);
|
|
212
|
+
process.exit(0);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── Require a task ────────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
const task = taskParts.join(" ").trim();
|
|
218
|
+
|
|
219
|
+
if (!task) {
|
|
220
|
+
console.error(`
|
|
221
|
+
ai — AI task router (Codex vs Claude)
|
|
222
|
+
|
|
223
|
+
Usage:
|
|
224
|
+
ai "your task here"
|
|
225
|
+
ai --codex "force codex"
|
|
226
|
+
ai --claude "force claude"
|
|
227
|
+
ai --dry "show routing only, don't run"
|
|
228
|
+
ai --set-load 85 set Claude context usage (0-100)
|
|
229
|
+
ai --status show current load + threshold
|
|
230
|
+
`);
|
|
231
|
+
process.exit(1);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── Route ─────────────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
const load = readLoad();
|
|
237
|
+
const THRESHOLD = thresholdForLoad(load);
|
|
238
|
+
const loadNote = load >= 80 ? ` ⚡ Claude at ${load}% — threshold lowered to ${THRESHOLD}` : "";
|
|
239
|
+
|
|
240
|
+
let tool;
|
|
241
|
+
let reason;
|
|
242
|
+
let model = null; // only set when routing to Claude
|
|
243
|
+
let intent = null;
|
|
244
|
+
|
|
245
|
+
const s = score(task);
|
|
246
|
+
|
|
247
|
+
if (forceCodex) {
|
|
248
|
+
tool = "codex";
|
|
249
|
+
reason = "forced via --codex";
|
|
250
|
+
} else if (forceClaude) {
|
|
251
|
+
tool = "claude";
|
|
252
|
+
({ model, intent } = claudeModel(task, s));
|
|
253
|
+
reason = `forced via --claude`;
|
|
254
|
+
} else {
|
|
255
|
+
if (s >= THRESHOLD) {
|
|
256
|
+
tool = "claude";
|
|
257
|
+
({ model, intent } = claudeModel(task, s));
|
|
258
|
+
reason = `score ${s}/100 ≥ ${THRESHOLD}`;
|
|
259
|
+
} else {
|
|
260
|
+
tool = "codex";
|
|
261
|
+
reason = `score ${s}/100 < ${THRESHOLD}`;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const intentLabel = intent ? ` [${intent}]` : "";
|
|
266
|
+
console.log(`\n┌─ AI Router ──────────────────────────────────────`);
|
|
267
|
+
console.log(`│ Task: ${task.slice(0, 72)}${task.length > 72 ? "…" : ""}`);
|
|
268
|
+
console.log(`│ Route: ${tool.toUpperCase()}${model ? ` → ${model}` : ""}${intentLabel} (${reason})`);
|
|
269
|
+
if (loadNote) console.log(`│ ${loadNote}`);
|
|
270
|
+
console.log(`└──────────────────────────────────────────────────\n`);
|
|
271
|
+
|
|
272
|
+
if (dryRun) process.exit(0);
|
|
273
|
+
|
|
274
|
+
// ── Execute ───────────────────────────────────────────────────────────────────
|
|
275
|
+
|
|
276
|
+
const cmd = tool === "codex" ? "codex" : "claude";
|
|
277
|
+
const argv = tool === "codex"
|
|
278
|
+
? ["exec", "--sandbox", "workspace-write", task]
|
|
279
|
+
: ["--model", model, task];
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
execFileSync(cmd, argv, { stdio: "inherit" });
|
|
283
|
+
} catch (e) {
|
|
284
|
+
if (e.status) process.exit(e.status);
|
|
285
|
+
console.error(e.message);
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
package/claude-load
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@andrej7510/ai-router",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI that routes tasks to OpenAI Codex or Claude based on complexity, with automatic load-shedding when Claude context is high",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai": "ai.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"ai",
|
|
11
|
+
"claude",
|
|
12
|
+
"codex",
|
|
13
|
+
"openai",
|
|
14
|
+
"anthropic",
|
|
15
|
+
"cli",
|
|
16
|
+
"orchestration",
|
|
17
|
+
"routing"
|
|
18
|
+
],
|
|
19
|
+
"author": "andrej7510",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
}
|
|
24
|
+
}
|