@alexeiled/claude-router 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/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@alexeiled/claude-router",
3
+ "version": "0.1.0",
4
+ "description": "Claude Code plugin: a local gateway that selects a model and an effort level for each user turn with a TypeSafe Jev Choice.",
5
+ "license": "MIT",
6
+ "author": "Alexei Ledenev",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/alexei-led/claude-router.git"
10
+ },
11
+ "homepage": "https://github.com/alexei-led/claude-router#readme",
12
+ "type": "module",
13
+ "engines": {
14
+ "node": ">=22"
15
+ },
16
+ "files": [
17
+ ".claude-plugin/plugin.json",
18
+ "hooks",
19
+ "lib",
20
+ "scripts",
21
+ "skills",
22
+ "docs",
23
+ "README.md"
24
+ ],
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "scripts": {
29
+ "test": "node --test --test-timeout=8000 test/*.test.mjs",
30
+ "check": "biome check .",
31
+ "validate": "claude plugin validate .",
32
+ "format": "biome format --write .",
33
+ "pack:dry": "npm pack --dry-run",
34
+ "prepublishOnly": "npm run check && npm test"
35
+ },
36
+ "devDependencies": {
37
+ "@biomejs/biome": "2.5.14"
38
+ }
39
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+ // SessionStart hook: start the gateway daemon when nothing answers on its port. Detached, so it outlives the session.
3
+ import { spawn } from 'node:child_process';
4
+ import { mkdirSync, openSync } from 'node:fs';
5
+ import { connect } from 'node:net';
6
+ import { dirname, join } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { loadRuntime } from '../lib/runtime.mjs';
9
+
10
+ const env = process.env;
11
+ const { config, dataDir } = loadRuntime(env);
12
+ const port = config.gateway.port;
13
+
14
+ function probe() {
15
+ return new Promise((resolve) => {
16
+ const socket = connect({ host: '127.0.0.1', port });
17
+ socket.once('connect', () => {
18
+ socket.destroy();
19
+ resolve(true);
20
+ });
21
+ socket.once('error', () => resolve(false));
22
+ });
23
+ }
24
+
25
+ const configured = (env.ANTHROPIC_BASE_URL ?? '').includes(`127.0.0.1:${port}`);
26
+ if (!configured)
27
+ process.stdout.write(
28
+ 'router: Claude Code does not use the gateway yet. Run /router:setup, then restart Claude Code.\n',
29
+ );
30
+
31
+ if (!(await probe())) {
32
+ mkdirSync(dataDir, { recursive: true });
33
+ const log = openSync(join(dataDir, 'gateway.log'), 'a');
34
+ // Claude Code exports the plugin option as CLAUDE_PLUGIN_OPTION_<KEY>; the gateway reads one name only.
35
+ const childEnv = {
36
+ ...env,
37
+ TYPESAFE_API_KEY: env.TYPESAFE_API_KEY || env.CLAUDE_PLUGIN_OPTION_TYPESAFE_API_KEY || '',
38
+ };
39
+ const child = spawn(process.execPath, [join(dirname(fileURLToPath(import.meta.url)), 'gateway.mjs')], {
40
+ detached: true,
41
+ stdio: ['ignore', log, log],
42
+ env: childEnv,
43
+ });
44
+ child.unref();
45
+ // Wait for the listener so the session's first request finds it.
46
+ let up = false;
47
+ for (let i = 0; i < 20 && !up; i += 1) {
48
+ await new Promise((r) => setTimeout(r, 100));
49
+ up = await probe();
50
+ }
51
+ process.stdout.write(
52
+ `router: gateway on 127.0.0.1:${port} ${up ? 'started' : 'not answering yet'} (pid ${child.pid})\n`,
53
+ );
54
+ }
55
+ process.exit(0);
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ // Daemon entry: local gateway on 127.0.0.1:<gateway.port>. Started by the SessionStart hook or by hand.
3
+ import { createGateway } from '../lib/gateway.mjs';
4
+ import { Router } from '../lib/router.mjs';
5
+ import { loadRuntime } from '../lib/runtime.mjs';
6
+
7
+ const { config, dataDir } = loadRuntime(process.env);
8
+ const router = new Router({ config, fetchFn: globalThis.fetch, dataDir });
9
+ const server = createGateway({ router, onError: (error) => process.stderr.write(`router: ${error.message}\n`) });
10
+ server.listen(config.gateway.port, '127.0.0.1', () =>
11
+ process.stdout.write(`router: listening on http://127.0.0.1:${config.gateway.port} as ${config.gateway.alias}\n`),
12
+ );
@@ -0,0 +1,4 @@
1
+ #!/bin/sh
2
+ # Print model, session effort, per-turn effort, active skill and block types per assistant line.
3
+ # Usage: spike-check.sh ~/.claude/projects/<project>/<session>.jsonl
4
+ jq -r 'select(.type=="assistant") | [.timestamp[11:19], .message.model, (.effort // "-"), (.perTurnEffort // "-"), (.attributionSkill // "-"), ([.message.content[]?.type] | join(","))] | @tsv' "$1"
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: high
3
+ disable-model-invocation: true
4
+ description: Manual pin: run this turn on fable at xhigh effort. Use as /router:high <prompt>.
5
+ model: fable
6
+ effort: xhigh
7
+ ---
8
+ Routing tier applied. Continue with the user's request as written. Do not mention the router.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: low
3
+ disable-model-invocation: true
4
+ description: Manual pin: run this turn on sonnet. Use as /router:low <prompt>.
5
+ model: sonnet
6
+ ---
7
+ Routing tier applied. Continue with the user's request as written. Do not mention the router.
@@ -0,0 +1,8 @@
1
+ ---
2
+ name: medium
3
+ disable-model-invocation: true
4
+ description: Manual pin: run this turn on opus at high effort. Use as /router:medium <prompt>.
5
+ model: opus
6
+ effort: high
7
+ ---
8
+ Routing tier applied. Continue with the user's request as written. Do not mention the router.
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: micro
3
+ disable-model-invocation: true
4
+ description: Manual pin: run this turn on haiku. Use as /router:micro <prompt>.
5
+ model: haiku
6
+ ---
7
+ Routing tier applied. Continue with the user's request as written. Do not mention the router.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: setup
3
+ description: Point Claude Code at the router gateway. Adds `model` and `ANTHROPIC_BASE_URL` to the user settings.
4
+ disable-model-invocation: true
5
+ allowed-tools: Read, Edit, Write
6
+ ---
7
+ Configure Claude Code for the router gateway. Do these steps:
8
+
9
+ 1. Read `~/.claude/settings.json`. If the file does not exist, start from `{}`.
10
+ 2. Set the key `model` to `"router"`.
11
+ 3. Set the key `env.ANTHROPIC_BASE_URL` to `"http://127.0.0.1:43170"`. If `~/.claude/router.json` sets `gateway.port`, use that port.
12
+ 4. Keep every other key unchanged. Write the file.
13
+ 5. Tell the user: restart Claude Code, then the router serves each turn. To stop the routing, remove the two keys.
14
+
15
+ Do not change any other file.