omnilane 0.7.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/SECURITY.md ADDED
@@ -0,0 +1,37 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ | Version | Security fixes |
6
+ |---|---|
7
+ | 0.5.x | Supported |
8
+ | 0.4.x and older | Upgrade to the latest release first |
9
+
10
+ ## Reporting a vulnerability
11
+
12
+ As of 2026-07-15, this repository does not have GitHub private vulnerability
13
+ reporting enabled. Do not publish exploit details, tokens, prompts, model
14
+ outputs, local paths, or other sensitive evidence in a public issue.
15
+
16
+ Open a minimal [security coordination issue](https://github.com/Seraphim0916/omnilane/issues/new)
17
+ that contains only:
18
+
19
+ - the affected released version;
20
+ - the broad component, such as installer, dispatcher, jobs CLI, or Live UI;
21
+ - a request for a private contact channel.
22
+
23
+ Wait for the maintainer to establish a private channel before sharing the
24
+ reproduction or impact details. Maintainers should enable GitHub private
25
+ vulnerability reporting when a stable private disclosure workflow is ready.
26
+
27
+ Useful reports include a minimal reproduction, expected and actual behavior,
28
+ impact, platform, and a proposed mitigation if available. Remove credentials,
29
+ cookies, provider responses, and proprietary source code.
30
+
31
+ ## Security-relevant scope
32
+
33
+ Examples include shell or routing injection, path traversal, unsafe installer
34
+ replacement, cross-user prompt or result disclosure, loopback authentication
35
+ bypass, unsafe symlink handling, and worker processes that survive a declared
36
+ timeout. Model answer quality and upstream provider availability are normally
37
+ not security vulnerabilities in Omnilane itself.
package/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.7.0
package/bin/omnilane ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ # omnilane — global entrypoint so commands work from any directory.
4
+
5
+ SELF="${BASH_SOURCE[0]}"
6
+ # A relative readlink target (how npm links global bins) resolves against the
7
+ # symlink's own directory, not the caller's cwd.
8
+ while [[ -L "$SELF" ]]; do
9
+ TARGET="$(readlink "$SELF")"
10
+ case "$TARGET" in
11
+ /*) SELF="$TARGET" ;;
12
+ *) SELF="$(dirname "$SELF")/$TARGET" ;;
13
+ esac
14
+ done
15
+ REPO="$(cd "$(dirname "$SELF")/.." && pwd)"
16
+
17
+ cmd="${1:-help}"; shift || true
18
+ case "$cmd" in
19
+ version|--version|-V)
20
+ [[ -r "$REPO/VERSION" ]] || {
21
+ echo "omnilane: VERSION file is unavailable" >&2
22
+ exit 1
23
+ }
24
+ version="$(<"$REPO/VERSION")"
25
+ [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
26
+ echo "omnilane: invalid VERSION file" >&2
27
+ exit 1
28
+ }
29
+ printf 'omnilane %s\n' "$version"
30
+ ;;
31
+ list) exec bash "$REPO/scripts/dispatch.sh" --list ;;
32
+ route|dispatch) exec bash "$REPO/scripts/dispatch.sh" "$@" ;;
33
+ jobs) exec bash "$REPO/scripts/jobs.sh" "$@" ;;
34
+ completion)
35
+ [[ $# -eq 1 ]] || {
36
+ echo "usage: omnilane completion bash|zsh" >&2
37
+ exit 2
38
+ }
39
+ case "$1" in
40
+ bash) cat "$REPO/completions/omnilane.bash" ;;
41
+ zsh) cat "$REPO/completions/_omnilane" ;;
42
+ *) echo "usage: omnilane completion bash|zsh" >&2; exit 2 ;;
43
+ esac
44
+ ;;
45
+ release-audit) exec bash "$REPO/scripts/release-audit.sh" "$@" ;;
46
+ doctor) exec bash "$REPO/scripts/doctor.sh" "$@" ;;
47
+ ui)
48
+ command -v python3 >/dev/null 2>&1 || {
49
+ echo "omnilane: ui requires Python 3.9 or newer" >&2
50
+ exit 1
51
+ }
52
+ exec python3 "$REPO/scripts/ui.py" "$@"
53
+ ;;
54
+ configure) exec bash "$REPO/scripts/configure.sh" "$@" ;;
55
+ help|--help|-h)
56
+ cat <<'EOF'
57
+ omnilane — one routing table, every harness
58
+
59
+ omnilane version installed release version
60
+ omnilane list effective routing table
61
+ omnilane route [--vendor V] [flags] LANE "TASK"
62
+ dispatch or consult a model
63
+ omnilane jobs [--json] COMMAND [ARGS] list, status, result, stats, wait, or audit
64
+ omnilane completion bash|zsh print a shell completion script
65
+ omnilane release-audit [flags] offline, read-only release gate
66
+ omnilane doctor [--json] read-only local health report
67
+ omnilane ui start|status|url|stop
68
+ omnilane configure interactive lane menu
69
+ EOF
70
+ ;;
71
+ *) echo "omnilane: unknown command '$cmd' (try: omnilane help)" >&2; exit 2 ;;
72
+ esac
@@ -0,0 +1,114 @@
1
+ #compdef omnilane
2
+ # Zsh completion for omnilane. Reads bounded names only; never dispatches.
3
+
4
+ _omnilane_repo() {
5
+ if [[ -n "${OMNILANE_COMPLETION_REPO:-}" && -r "$OMNILANE_COMPLETION_REPO/routing.yaml" ]]; then
6
+ print -r -- "$OMNILANE_COMPLETION_REPO"
7
+ return 0
8
+ fi
9
+ local exe target
10
+ exe="${commands[omnilane]:-}" || return 1
11
+ while [[ -L "$exe" ]]; do
12
+ target="$(readlink "$exe")" || return 1
13
+ if [[ "$target" = /* ]]; then exe="$target"; else exe="${exe:h}/$target"; fi
14
+ done
15
+ (cd "${exe:h}/.." 2>/dev/null && pwd -P)
16
+ }
17
+
18
+ _omnilane_lanes() {
19
+ local repo home file
20
+ local -a files
21
+ repo="$(_omnilane_repo)" || return 0
22
+ home="${OMNILANE_HOME:-$HOME/.omnilane}"
23
+ files=()
24
+ for file in "$home/routing.local.yaml" "$repo/routing.yaml"; do
25
+ [[ -f "$file" && ! -L "$file" ]] && files+=("$file")
26
+ done
27
+ (( ${#files} )) || return 0
28
+ awk '
29
+ /^[a-z][a-z0-9-]*:[[:space:]]/ {
30
+ lane=$1; sub(/:$/, "", lane)
31
+ if (!seen[lane]++) print lane
32
+ }
33
+ ' "${files[@]}"
34
+ }
35
+
36
+ _omnilane_job_ids() {
37
+ local home root dir id count=0
38
+ home="${OMNILANE_HOME:-$HOME/.omnilane}"
39
+ root="$home/jobs"
40
+ [[ -d "$root" && ! -L "$root" ]] || return 0
41
+ for dir in "$root"/*(N/); do
42
+ [[ -L "$dir" ]] && continue
43
+ id="${dir:t}"
44
+ if [[ "$id" =~ '^[0-9]{8}-[0-9]{6}-[0-9]+-[0-9]+$' ]]; then
45
+ print -r -- "$id"
46
+ (( count += 1 ))
47
+ (( count < 1000 )) || break
48
+ fi
49
+ done
50
+ }
51
+
52
+ _omnilane() {
53
+ local command="${words[2]:-}" sub sub_index=3
54
+ local -a lanes ids
55
+ if (( CURRENT == 2 )); then
56
+ _values 'command' version list route dispatch jobs doctor release-audit ui configure completion help
57
+ return
58
+ fi
59
+ case "$command" in
60
+ route|dispatch)
61
+ lanes=("${(@f)$(_omnilane_lanes)}")
62
+ _arguments \
63
+ '--background[run in background]' \
64
+ '--dry-run[resolve and print the plan without side effects]' \
65
+ '--mode[execution mode]:mode:(advise work)' \
66
+ '--workdir[target directory]:directory:_directories' \
67
+ '--vendor[vendor override]:vendor:(codex claude grok gemini exec vote)' \
68
+ '--model[model override]:model:' \
69
+ '--effort[reasoning effort]:effort:(low medium high xhigh max)' \
70
+ '--timeout[per-call seconds]:seconds:' \
71
+ '--job-timeout[whole-job seconds]:seconds:' \
72
+ "1:lane:($lanes)" '2:task:'
73
+ ;;
74
+ jobs)
75
+ sub="${words[3]:-}"
76
+ if [[ "$sub" == "--json" ]]; then
77
+ sub_index=4
78
+ sub="${words[4]:-}"
79
+ fi
80
+ if (( CURRENT == sub_index )); then
81
+ _values 'job command' list status result tail retry stats wait audit prune help
82
+ elif (( CURRENT == sub_index + 1 )) &&
83
+ [[ "$sub" == status || "$sub" == result || "$sub" == wait ||
84
+ "$sub" == tail || "$sub" == retry ]]; then
85
+ ids=("${(@f)$(_omnilane_job_ids)}")
86
+ _values 'job id' "$ids[@]"
87
+ elif [[ "$sub" == list || "$sub" == status || "$sub" == result ]]; then
88
+ _values 'option' --json
89
+ elif [[ "$sub" == tail ]]; then
90
+ _arguments '--lines[output lines to show]:count:'
91
+ elif [[ "$sub" == retry ]]; then
92
+ _arguments '--background[re-dispatch in the background]'
93
+ elif [[ "$sub" == wait ]]; then
94
+ _arguments '--timeout[maximum wait seconds]:seconds:'
95
+ elif [[ "$sub" == stats || "$sub" == audit ]]; then
96
+ _arguments '--last[maximum jobs]:count:' '--json[versioned metadata output]'
97
+ elif [[ "$sub" == prune ]]; then
98
+ _arguments '--keep[completed jobs to retain]:count:' \
99
+ '--older-than[completed jobs older than N days]:days:' \
100
+ '--apply[delete eligible jobs]'
101
+ fi
102
+ ;;
103
+ doctor) _values 'option' --json ;;
104
+ release-audit)
105
+ _arguments '--target[release version]:version:' '--allow-dirty[permit a dirty worktree]' \
106
+ '--require-tag[require an annotated tag at HEAD]' '--manifest[include tracked inventory]' \
107
+ '--json[versioned JSON output]'
108
+ ;;
109
+ ui) _values 'ui command' start status url stop ;;
110
+ completion) _values 'shell' bash zsh ;;
111
+ esac
112
+ }
113
+
114
+ (( $+functions[compdef] )) && compdef _omnilane omnilane
@@ -0,0 +1,123 @@
1
+ # Bash completion for omnilane. This file reads routing/job names only; it never
2
+ # invokes dispatch, provider CLIs, or the executable machine-local overlay.
3
+
4
+ _omnilane_repo() {
5
+ if [[ -n "${OMNILANE_COMPLETION_REPO:-}" && -r "$OMNILANE_COMPLETION_REPO/routing.yaml" ]]; then
6
+ printf '%s\n' "$OMNILANE_COMPLETION_REPO"
7
+ return 0
8
+ fi
9
+ local exe target
10
+ exe="$(command -v omnilane 2>/dev/null)" || return 1
11
+ while [[ -L "$exe" ]]; do
12
+ target="$(readlink "$exe")" || return 1
13
+ case "$target" in
14
+ /*) exe="$target" ;;
15
+ *) exe="$(dirname "$exe")/$target" ;;
16
+ esac
17
+ done
18
+ (cd "$(dirname "$exe")/.." 2>/dev/null && pwd -P)
19
+ }
20
+
21
+ _omnilane_lanes() {
22
+ local repo home file
23
+ local files=()
24
+ repo="$(_omnilane_repo)" || return 0
25
+ home="${OMNILANE_HOME:-$HOME/.omnilane}"
26
+ for file in "$home/routing.local.yaml" "$repo/routing.yaml"; do
27
+ [[ -f "$file" && ! -L "$file" ]] && files+=("$file")
28
+ done
29
+ [[ ${#files[@]} -gt 0 ]] || return 0
30
+ awk '
31
+ /^[a-z][a-z0-9-]*:[[:space:]]/ {
32
+ lane=$1; sub(/:$/, "", lane)
33
+ if (!seen[lane]++) print lane
34
+ }
35
+ ' "${files[@]}"
36
+ }
37
+
38
+ _omnilane_job_ids() {
39
+ local home root dir id count=0
40
+ home="${OMNILANE_HOME:-$HOME/.omnilane}"
41
+ root="$home/jobs"
42
+ [[ -d "$root" && ! -L "$root" ]] || return 0
43
+ for dir in "$root"/*; do
44
+ [[ -d "$dir" && ! -L "$dir" ]] || continue
45
+ id="${dir##*/}"
46
+ if [[ "$id" =~ ^[0-9]{8}-[0-9]{6}-[0-9]+-[0-9]+$ ]]; then
47
+ printf '%s\n' "$id"
48
+ ((count += 1))
49
+ [[ "$count" -lt 1000 ]] || break
50
+ fi
51
+ done
52
+ }
53
+
54
+ _omnilane() {
55
+ local cur prev command sub words sub_index reply_line
56
+ COMPREPLY=()
57
+ cur="${COMP_WORDS[COMP_CWORD]}"
58
+ prev="${COMP_WORDS[COMP_CWORD-1]:-}"
59
+ command="${COMP_WORDS[1]:-}"
60
+ if [[ "$COMP_CWORD" -eq 1 ]]; then
61
+ words="version list route dispatch jobs doctor release-audit ui configure completion help"
62
+ else
63
+ case "$command" in
64
+ route|dispatch)
65
+ case "$prev" in
66
+ --mode) words="advise work" ;;
67
+ --vendor) words="codex claude grok gemini exec vote" ;;
68
+ --effort) words="low medium high xhigh max" ;;
69
+ --workdir)
70
+ COMPREPLY=()
71
+ while IFS= read -r reply_line; do
72
+ COMPREPLY+=("$reply_line")
73
+ done < <(compgen -d -- "$cur")
74
+ return ;;
75
+ --model|--timeout|--job-timeout) return ;;
76
+ *) words="--background --dry-run --help --mode --workdir --vendor --model --effort --timeout --job-timeout $(_omnilane_lanes)" ;;
77
+ esac
78
+ ;;
79
+ jobs)
80
+ sub="${COMP_WORDS[2]:-}"
81
+ sub_index=2
82
+ if [[ "$sub" == "--json" ]]; then
83
+ sub_index=3
84
+ sub="${COMP_WORDS[3]:-}"
85
+ fi
86
+ if [[ "$COMP_CWORD" -eq "$sub_index" ]]; then
87
+ words="list status result tail retry stats wait audit prune help"
88
+ elif [[ "$COMP_CWORD" -eq $((sub_index + 1)) &&
89
+ ( "$sub" == status || "$sub" == result || "$sub" == wait ||
90
+ "$sub" == tail || "$sub" == retry ) ]]; then
91
+ words="$(_omnilane_job_ids)"
92
+ elif [[ "$sub" == list ]]; then
93
+ words="--json"
94
+ elif [[ "$sub" == status || "$sub" == result ]]; then
95
+ words="--json"
96
+ elif [[ "$sub" == tail ]]; then
97
+ words="--lines"
98
+ elif [[ "$sub" == retry ]]; then
99
+ words="--background"
100
+ elif [[ "$sub" == wait ]]; then
101
+ words="--timeout"
102
+ elif [[ "$sub" == stats || "$sub" == audit ]]; then
103
+ words="--last --json"
104
+ elif [[ "$sub" == prune ]]; then
105
+ words="--keep --older-than --apply"
106
+ else
107
+ return
108
+ fi
109
+ ;;
110
+ doctor) words="--json" ;;
111
+ release-audit) words="--target --allow-dirty --require-tag --manifest --json" ;;
112
+ ui) words="start status url stop" ;;
113
+ completion) words="bash zsh" ;;
114
+ *) return ;;
115
+ esac
116
+ fi
117
+ COMPREPLY=()
118
+ while IFS= read -r reply_line; do
119
+ COMPREPLY+=("$reply_line")
120
+ done < <(compgen -W "$words" -- "$cur")
121
+ }
122
+
123
+ complete -F _omnilane omnilane
@@ -0,0 +1,37 @@
1
+ # Copy to ~/.omnilane/local.sh — sourced by every runner. Never committed.
2
+ # Use it for machine-specific binaries, proxies, and auth. Examples:
3
+
4
+ # Absolute paths when the CLIs are not on the runners' PATH.
5
+ # Keep the ${VAR:-...} form so per-call env overrides still win:
6
+ # CLAUDE_BIN="${CLAUDE_BIN:-/opt/homebrew/bin/claude}"
7
+ # CODEX_BIN="${CODEX_BIN:-$HOME/.local/bin/codex}"
8
+ # GROK_BIN="${GROK_BIN:-/usr/local/bin/grok}"
9
+ # AGY_BIN="${AGY_BIN:-/usr/local/bin/agy}"
10
+
11
+ # Route a vendor CLI through a local proxy:
12
+ # export ANTHROPIC_BASE_URL=http://127.0.0.1:8080
13
+
14
+ # Watchdog cap for EACH CLI invocation (seconds), not a whole-job budget —
15
+ # a retrying vendor (grok) or the vote panel makes several calls. Global default:
16
+ # export OMNILANE_TIMEOUT=600
17
+ # Per-lane override — lane upper-cased with "-" turned into "_". A one-off
18
+ # `--timeout SEC` on the dispatch call still wins over both of these:
19
+ # export OMNILANE_TIMEOUT_TRIAGE=600
20
+ # export OMNILANE_TIMEOUT_HARD_JUDGMENT=1200
21
+ # export OMNILANE_TIMEOUT_LONG_CONTEXT=1800
22
+
23
+ # Grok retries an empty response; keep the explicit cost bound between 1 and 20.
24
+ # export OMNILANE_GROK_MAX_ATTEMPTS=5
25
+
26
+ # Optional whole-job fuse: lock wait + every retry/voter/round share this cap.
27
+ # Disabled by default, except non-Git Codex work automatically reuses its
28
+ # resolved per-call timeout (up to 999999999 seconds). A one-off
29
+ # `--job-timeout SEC` wins over these values.
30
+ # The automatic exception warns and falls back to the existing per-call
31
+ # watchdog path if the bundled Perl supervisor is unavailable; it never makes
32
+ # non-Git work Git-only.
33
+ # For a deep full-repository audit, 2–4 hours is a starting range, not a default:
34
+ # export OMNILANE_JOB_TIMEOUT=10800
35
+ # export OMNILANE_JOB_TIMEOUT_HARD_JUDGMENT=14400
36
+ # Pair it with a per-call guard such as:
37
+ # export OMNILANE_TIMEOUT_HARD_JUDGMENT=1800
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "omnilane",
3
+ "version": "0.7.0",
4
+ "description": "One routing table, every harness — classify subtasks into lanes and dispatch each lane to the best vendor's agentic CLI (Codex, Claude, Gemini, Grok) using your existing subscription logins.",
5
+ "bin": {
6
+ "omnilane": "bin/omnilane"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "scripts/",
11
+ "!scripts/__pycache__",
12
+ "!**/*.pyc",
13
+ "completions/",
14
+ "ui/",
15
+ "routing.yaml",
16
+ "routing.local.yaml.example",
17
+ "local.sh.example",
18
+ "VERSION",
19
+ "CHANGELOG.md",
20
+ "README.md",
21
+ "README.zh-TW.md",
22
+ "README.zh-CN.md",
23
+ "README.ja.md",
24
+ "README.ko.md",
25
+ "SECURITY.md"
26
+ ],
27
+ "os": [
28
+ "darwin",
29
+ "linux"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/Seraphim0916/omnilane.git"
34
+ },
35
+ "homepage": "https://github.com/Seraphim0916/omnilane#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/Seraphim0916/omnilane/issues"
38
+ },
39
+ "keywords": [
40
+ "agentic",
41
+ "cli",
42
+ "llm",
43
+ "routing",
44
+ "dispatch",
45
+ "codex",
46
+ "claude",
47
+ "gemini",
48
+ "grok",
49
+ "multi-model",
50
+ "orchestration"
51
+ ],
52
+ "license": "MIT",
53
+ "author": "Seraphim0916"
54
+ }
@@ -0,0 +1,41 @@
1
+ # Copy to ~/.omnilane/routing.local.yaml — lines here override routing.yaml.
2
+ # Same format: lane: vendor model effort (first match per lane wins)
3
+
4
+ # Example: send triage to a cheaper local setup and disable the grok lane.
5
+ # triage: codex gpt-5.6-luna low
6
+ # live-search: off - -
7
+
8
+ # consult is a multi-vendor direct-target chain. configure.sh intentionally
9
+ # skips it because that menu writes one candidate per lane. If overriding it,
10
+ # retain every vendor you want to address by name:
11
+ # consult: codex gpt-5.6-sol max | claude claude-opus-4-8 high | grok grok-4.5 - | gemini "Gemini 3.1 Pro (High)" -
12
+
13
+ # ── Starter profiles ─────────────────────────────────────────────
14
+ # Uncomment ONE block that matches what you actually subscribe to.
15
+ # You never need every vendor — unmatched lanes fall back down the chain
16
+ # in routing.yaml automatically.
17
+
18
+ # Profile: Claude-only (no Codex/Gemini/Grok) — route everything to Claude.
19
+ # hardest-coding: claude claude-opus-4-8 high
20
+ # bulk-mechanical: claude claude-sonnet-5 high
21
+ # triage: claude claude-haiku-4-5 -
22
+ # hard-judgment: claude claude-opus-4-8 high
23
+ # ui-draft: claude claude-opus-4-8 high
24
+ # long-context: claude claude-opus-4-8 high
25
+ # fast-agentic: claude claude-sonnet-5 high
26
+ # live-search: off - -
27
+ # coding-overflow: off - -
28
+
29
+ # Profile: Claude Code (Fable 5) as the main loop — let Fable keep judgment/taste,
30
+ # push only coding volume out to Codex.
31
+ # hard-judgment: claude claude-fable-5 high
32
+ # taste-final: claude claude-fable-5 high
33
+
34
+ # Profile: Codex-heavy (Sol main) — keep the hard lanes on Codex, Claude for taste.
35
+ # taste-final: claude claude-opus-4-8 high
36
+ # long-context: codex gpt-5.6-sol high
37
+
38
+ # Profile: no Codex subscription — promote Claude/Gemini into the coding lanes.
39
+ # hardest-coding: claude claude-opus-4-8 high
40
+ # bulk-mechanical: gemini "Gemini 3.5 Flash (High)" -
41
+ # triage: gemini "Gemini 3.5 Flash (Low)" -
package/routing.yaml ADDED
@@ -0,0 +1,34 @@
1
+ # omnilane — single routing table shared by every harness (CC / Codex / Grok / Antigravity)
2
+ # Format: one lane per line -> lane: vendor model effort [| vendor model effort | off]
3
+ # vendor = codex | claude | grok | gemini
4
+ # effort = codex reasoning effort / claude --effort / agy thinking suffix / (grok: ignored)
5
+ # Fallback chain: the first candidate whose vendor CLI is installed wins, so the
6
+ # same table degrades gracefully when you only subscribe to one or two vendors.
7
+ # Override any line in ~/.omnilane/routing.local.yaml (same format; local wins).
8
+ # Provenance (audited 2026-07-12): defaults follow Artificial Analysis data, 2026-07
9
+ # snapshot. Verified against AA site records + vendor pricing pages: Intelligence &
10
+ # Coding indexes and 7:2:1 blended prices all match (AA field price1mBlended7To2To1);
11
+ # coding cost-per-task is chart-only (not independently reconstructed). Prices are
12
+ # standard short-context API tier — on subscription CLIs treat $ as relative ranking.
13
+ # Your own job outcomes (~/.omnilane/jobs/) outrank these priors; edit lanes to match.
14
+
15
+ hardest-coding: codex gpt-5.6-sol xhigh | claude claude-opus-4-8 high # AA coding 78.35; xhigh beats max (77.39) at lower cost — verified
16
+ bulk-mechanical: codex gpt-5.6-terra max | claude claude-sonnet-5 high | gemini "Gemini 3.5 Flash (High)" - # Terra 76.66 ~= Fable-class at a fraction of the cost
17
+ triage: codex gpt-5.6-luna medium | gemini "Gemini 3.5 Flash (Low)" - | claude claude-haiku-4-5 - # high-volume scans
18
+ hard-judgment: codex gpt-5.6-sol max | claude claude-opus-4-8 high # Sol max Intelligence 58.9 — second only to Fable 5 (59.9, fallback composite)
19
+ taste-final: claude claude-opus-4-8 high | codex gpt-5.6-sol max # user-facing prose, prompt/doc polish, Chinese phrasing, style arbitration
20
+ consult: codex gpt-5.6-sol max | claude claude-opus-4-8 high | grok grok-4.5 - | gemini "Gemini 3.1 Pro (High)" - # direct named-model consultation; use --vendor to prevent fallback
21
+ ui-draft: codex gpt-5.6-sol xhigh | claude claude-opus-4-8 high # only with a design system / reference images; open-ended visual taste -> taste-final
22
+ long-context: gemini "Gemini 3.1 Pro (High)" - | claude claude-opus-4-8 high | codex gpt-5.6-sol high # 1M-token synthesis ONLY — never agentic loops (AA agentic 21.4)
23
+ fast-agentic: gemini "Gemini 3.5 Flash (High)" - | codex gpt-5.6-luna high # speed + agentic tool loops
24
+ live-search: grok grok-4.5 - | off # native X/web search lane; no real substitute
25
+ coding-overflow: grok grok-4.5 - | off # codex-quota relief valve: mid-tier coding (AA 72.45); verify factual claims (hallucination 0.54)
26
+ arbitrate: off - - # opinion panel is OPT-IN: it costs one call per voter per round.
27
+ # Enable: `arbitrate: vote codex,claude,grok -` (any 1-4 of codex/claude/grok/gemini)
28
+ # Debate round (each voter rebuts the others): set the effort field to 2.
29
+ # Custom gate: `arbitrate: exec /path/to/script -`
30
+ # Claude Fable 5 (claude-fable-5) is deliberately absent from the defaults: the top Claude tier
31
+ # is usually the MAIN LOOP itself, not a dispatched worker, and it prices above Opus. If you want
32
+ # to route to it anyway, pick it in the configurator or set e.g.
33
+ # taste-final: claude claude-fable-5 high
34
+ # in ~/.omnilane/routing.local.yaml.
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ # omnilane interactive lane configurator — writes ~/.omnilane/routing.local.yaml.
4
+ # Pure bash + a tty, no extra dependencies. Prefer editing routing.local.yaml
5
+ # by hand for scripted/non-interactive setups.
6
+
7
+ source "$(dirname "${BASH_SOURCE[0]}")/lib/common.sh"
8
+ source "$(dirname "${BASH_SOURCE[0]}")/lib/i18n.sh"
9
+
10
+ LOCAL_FILE="$OMNILANE_HOME/routing.local.yaml"
11
+
12
+ # Curated suggestions only — "c" always allows free text so new models work.
13
+ CODEX_MODELS=("gpt-5.6-sol" "gpt-5.6-terra" "gpt-5.6-luna")
14
+ CODEX_EFFORTS=("xhigh" "max" "ultra" "high" "medium" "low" "minimal" "none")
15
+ CLAUDE_MODELS=("claude-opus-4-8" "claude-fable-5" "claude-sonnet-5" "claude-haiku-4-5")
16
+ CLAUDE_EFFORTS=("max" "xhigh" "high" "medium" "low" "-")
17
+ GEMINI_MODELS=("Gemini 3.1 Pro (High)" "Gemini 3.1 Pro (Low)" "Gemini 3.5 Flash (High)" "Gemini 3.5 Flash (Medium)" "Gemini 3.5 Flash (Low)")
18
+ GROK_MODELS=("grok-4.5" "grok-4.3")
19
+
20
+ custom_value_is_safe() {
21
+ case "$1" in
22
+ *'$'*|*'`'*|*'"'*|*'\'*|*'#'*|*'|'*|*$'\r'*) return 1 ;;
23
+ *) return 0 ;;
24
+ esac
25
+ }
26
+
27
+ warn_unsafe_value() {
28
+ echo 'omnilane: unsafe custom value (not allowed: $ ` " \ # |)' >&2
29
+ }
30
+
31
+ pick() { # title, options... -> prints the chosen value
32
+ local title="$1"; shift
33
+ local opts=("$@") i choice
34
+ echo "$title" >&2
35
+ for i in "${!opts[@]}"; do printf ' %d) %s\n' "$((i + 1))" "${opts[$i]}" >&2; done
36
+ printf '%s\n' "$(msg cfg_custom)" >&2
37
+ while true; do
38
+ read -rp "> " choice || choice=""
39
+ [[ -z "$choice" ]] && { echo "$(msg cfg_aborted)" >&2; exit 1; }
40
+ if [[ "$choice" == "c" ]]; then
41
+ read -rp "$(msg cfg_custom_value)" choice || choice=""
42
+ if [[ -n "$choice" ]] && custom_value_is_safe "$choice"; then
43
+ printf '%s' "$choice"; return
44
+ fi
45
+ [[ -n "$choice" ]] && warn_unsafe_value
46
+ continue
47
+ fi
48
+ if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#opts[@]} )); then
49
+ printf '%s' "${opts[$((choice - 1))]}"; return
50
+ fi
51
+ echo "$(msgf cfg_pick_or_c "${#opts[@]}")" >&2
52
+ done
53
+ }
54
+
55
+ LANES=()
56
+ while IFS= read -r line; do
57
+ if [[ "$line" =~ ^([a-z][a-z0-9-]*): ]]; then
58
+ lane="${BASH_REMATCH[1]}"
59
+ [[ "$lane" == "consult" ]] || LANES+=("$lane")
60
+ fi
61
+ done < "$OMNILANE_REPO/routing.yaml"
62
+
63
+ echo "$(msg cfg_title)"
64
+ bash "$OMNILANE_REPO/scripts/dispatch.sh" --list
65
+ echo
66
+
67
+ OVERRIDES=()
68
+ while true; do
69
+ echo "$(msg cfg_pick_lane)"
70
+ for i in "${!LANES[@]}"; do printf ' %d) %s\n' "$((i + 1))" "${LANES[$i]}"; done
71
+ read -rp "lane> " n || n=""
72
+ [[ -z "$n" ]] && break
73
+ if ! [[ "$n" =~ ^[0-9]+$ ]] || (( n < 1 || n > ${#LANES[@]} )); then
74
+ echo "$(msgf cfg_pick_range "${#LANES[@]}")"; continue
75
+ fi
76
+ lane="${LANES[$((n - 1))]}"
77
+ vendor="$(pick "$(msgf cfg_vendor_for "$lane")" codex claude grok gemini "vote (multi-model panel)" "exec (your own script/gate)" off)"
78
+ [[ "$vendor" == exec* ]] && vendor="exec"
79
+ [[ "$vendor" == vote* ]] && vendor="vote"
80
+ if [[ "$vendor" == "off" ]]; then
81
+ OVERRIDES+=("$lane: off - -")
82
+ echo "-> $lane: off"; echo; continue
83
+ fi
84
+ case "$vendor" in
85
+ codex) model="$(pick "$(msg cfg_model)" "${CODEX_MODELS[@]}")"; effort="$(pick "$(msg cfg_effort)" "${CODEX_EFFORTS[@]}")" ;;
86
+ claude) model="$(pick "$(msg cfg_model)" "${CLAUDE_MODELS[@]}")"; effort="$(pick "$(msg cfg_effort)" "${CLAUDE_EFFORTS[@]}")" ;;
87
+ gemini) model="$(pick "$(msg cfg_model)" "${GEMINI_MODELS[@]}")"; effort="-" ;;
88
+ grok) model="$(pick "$(msg cfg_model)" "${GROK_MODELS[@]}")"; effort="-" ;;
89
+ vote) while true; do
90
+ count="$(pick "$(msg cfg_voters_count)" "1" "2" "3" "4")"
91
+ [[ "$count" =~ ^[1-4]$ ]] && break
92
+ echo "$(msg cfg_voters_range)" >&2
93
+ done
94
+ model=""; remaining=(codex claude grok gemini)
95
+ for ((s = 1; s <= count; s++)); do
96
+ v="$(pick "$(msgf cfg_voter_n "$s")" "${remaining[@]}")"
97
+ model+="${model:+,}$v"
98
+ next=()
99
+ for r in "${remaining[@]}"; do [[ "$r" == "$v" ]] || next+=("$r"); done
100
+ # next is empty once every vendor is picked; the bare expansion
101
+ # is an unbound-variable error under set -u on Bash 3.2.
102
+ remaining=(${next[@]+"${next[@]}"})
103
+ done
104
+ effort="$(pick "$(msg cfg_rounds)" "1" "2")"
105
+ [[ "$effort" == "1" ]] && effort="-" ;;
106
+ exec) read -rp "$(msg cfg_exec_path)" model || model=""
107
+ [[ -n "$model" ]] || { echo "$(msg cfg_empty_path)"; continue; }
108
+ effort="-" ;;
109
+ *) model="$(pick "$(msg cfg_model)" "custom")"; effort="-" ;;
110
+ esac
111
+ if ! custom_value_is_safe "$vendor" || ! custom_value_is_safe "$model" ||
112
+ ! custom_value_is_safe "$effort" ||
113
+ [[ "$vendor" == *[[:space:]]* || "$effort" == *[[:space:]]* ]]; then
114
+ warn_unsafe_value; continue
115
+ fi
116
+ [[ "$model" == *[[:space:]]* ]] && model="\"$model\""
117
+ OVERRIDES+=("$lane: $vendor $model $effort")
118
+ echo "-> $lane: $vendor $model $effort"
119
+ echo
120
+ done
121
+
122
+ [[ ${#OVERRIDES[@]} -gt 0 ]] || { echo "$(msg cfg_no_changes)"; exit 0; }
123
+
124
+ mkdir -p "$OMNILANE_HOME"
125
+ [[ -f "$LOCAL_FILE" ]] && cp "$LOCAL_FILE" "$LOCAL_FILE.bak"
126
+ {
127
+ echo "# written by scripts/configure.sh on $(date +%F) — first match per lane wins"
128
+ printf '%s\n' "${OVERRIDES[@]}"
129
+ # keep earlier customizations below (new lines above shadow same-lane old ones)
130
+ [[ -f "$LOCAL_FILE.bak" ]] && grep -v '^#' "$LOCAL_FILE.bak" || true
131
+ } > "$LOCAL_FILE"
132
+
133
+ echo "$(msgf cfg_wrote "$LOCAL_FILE")"
134
+ bash "$OMNILANE_REPO/scripts/dispatch.sh" --list