tau-all-agent 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/LICENSE +21 -0
- package/README.md +45 -0
- package/extensions/answer.ts +601 -0
- package/extensions/branch-term.ts +405 -0
- package/extensions/btw.ts +444 -0
- package/extensions/ghostty.ts +301 -0
- package/extensions/git-diff-stats.ts +277 -0
- package/extensions/git-pr-status.ts +286 -0
- package/extensions/insights.ts +2367 -0
- package/extensions/interlude.ts +144 -0
- package/extensions/loop.ts +529 -0
- package/extensions/memory.ts +1889 -0
- package/extensions/notify.ts +161 -0
- package/extensions/openai-fast.ts +227 -0
- package/extensions/openai-verbosity.ts +223 -0
- package/extensions/review.ts +4347 -0
- package/extensions/sandbox/index.ts +2578 -0
- package/extensions/telegram/README.md +81 -0
- package/extensions/telegram/daemon.mjs +1942 -0
- package/extensions/telegram/index.ts +1073 -0
- package/extensions/telegram/message-text.d.mts +1 -0
- package/extensions/telegram/message-text.mjs +16 -0
- package/extensions/usage/anthropic.ts +198 -0
- package/extensions/usage/github-copilot.ts +204 -0
- package/extensions/usage/google-gemini-cli.ts +232 -0
- package/extensions/usage/index.ts +2310 -0
- package/extensions/usage/minimax.ts +208 -0
- package/extensions/usage/openai-codex.ts +180 -0
- package/extensions/usage/openrouter.ts +168 -0
- package/extensions/usage/providers.ts +26 -0
- package/extensions/usage/shared.ts +156 -0
- package/extensions/usage/types.ts +53 -0
- package/extensions/usage/zai.ts +186 -0
- package/extensions/websearch/README.md +68 -0
- package/extensions/websearch/browser/chromium.ts +330 -0
- package/extensions/websearch/browser/discovery.ts +75 -0
- package/extensions/websearch/browser/firefox.ts +150 -0
- package/extensions/websearch/browser/sqlite.ts +38 -0
- package/extensions/websearch/config.ts +76 -0
- package/extensions/websearch/index.ts +312 -0
- package/extensions/websearch/normalize.ts +155 -0
- package/extensions/websearch/providers/anthropic.pi.ts +139 -0
- package/extensions/websearch/providers/gemini.browser.ts +185 -0
- package/extensions/websearch/providers/gemini.pi.ts +108 -0
- package/extensions/websearch/providers/openai-codex.browser.ts +77 -0
- package/extensions/websearch/providers/openai-codex.pi.ts +49 -0
- package/extensions/websearch/providers/openai-codex.shared.ts +123 -0
- package/extensions/websearch/providers/pi-model.shared.ts +86 -0
- package/extensions/websearch/providers/search-prompt.shared.ts +17 -0
- package/extensions/websearch/providers/shared.ts +76 -0
- package/extensions/websearch/types.ts +52 -0
- package/extensions/worktree.ts +2223 -0
- package/package.json +76 -0
- package/skills/browser-tools/SKILL.md +253 -0
- package/skills/browser-tools/scripts/browser-content.js +100 -0
- package/skills/browser-tools/scripts/browser-cookies.js +33 -0
- package/skills/browser-tools/scripts/browser-dismiss-cookies.js +455 -0
- package/skills/browser-tools/scripts/browser-eval.js +75 -0
- package/skills/browser-tools/scripts/browser-logs-tail.js +89 -0
- package/skills/browser-tools/scripts/browser-nav.js +28 -0
- package/skills/browser-tools/scripts/browser-net-summary.js +115 -0
- package/skills/browser-tools/scripts/browser-pick.js +133 -0
- package/skills/browser-tools/scripts/browser-screenshot.js +18 -0
- package/skills/browser-tools/scripts/browser-start.js +281 -0
- package/skills/browser-tools/scripts/browser-watch.js +321 -0
- package/skills/browser-tools/scripts/utils.js +60 -0
- package/skills/git-clean-history/SKILL.md +45 -0
- package/skills/git-commit/SKILL.md +55 -0
- package/skills/homeassistant-ops/SKILL.md +184 -0
- package/skills/homeassistant-ops/references/api.md +64 -0
- package/skills/homeassistant-ops/references/id_conventions.md +71 -0
- package/skills/homeassistant-ops/references/playbook.md +150 -0
- package/skills/homeassistant-ops/scripts/ha_ops.js +2942 -0
- package/skills/openscad/SKILL.md +149 -0
- package/skills/openscad/examples/parametric_box.scad +92 -0
- package/skills/openscad/examples/phone_stand.scad +95 -0
- package/skills/openscad/scripts/common.sh +49 -0
- package/skills/openscad/scripts/export-stl.sh +56 -0
- package/skills/openscad/scripts/extract-params.sh +147 -0
- package/skills/openscad/scripts/multi-preview.sh +68 -0
- package/skills/openscad/scripts/preview.sh +74 -0
- package/skills/openscad/scripts/render-with-params.sh +91 -0
- package/skills/openscad/scripts/validate.sh +46 -0
- package/skills/oracle/SKILL.md +64 -0
- package/skills/oracle/scripts/oracle +249 -0
- package/skills/oracle/scripts/oracle-bundle +227 -0
- package/skills/sentry/SKILL.md +198 -0
- package/skills/sentry/lib/auth.js +99 -0
- package/skills/sentry/scripts/fetch-event.js +325 -0
- package/skills/sentry/scripts/fetch-issue.js +368 -0
- package/skills/sentry/scripts/list-issues.js +245 -0
- package/skills/sentry/scripts/search-events.js +291 -0
- package/skills/sentry/scripts/search-logs.js +234 -0
- package/skills/update-changelog/SKILL.md +135 -0
- package/skills/web-design/SKILL.md +117 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Generate a preview PNG from an OpenSCAD file
|
|
3
|
+
# Usage: preview.sh input.scad output.png [options]
|
|
4
|
+
#
|
|
5
|
+
# Options:
|
|
6
|
+
# --camera=x,y,z,rx,ry,rz,dist Camera position
|
|
7
|
+
# --size=WxH Image size (default: 800x600)
|
|
8
|
+
# -D 'var=value' Set parameter value
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
+
source "$SCRIPT_DIR/common.sh"
|
|
14
|
+
|
|
15
|
+
check_openscad
|
|
16
|
+
|
|
17
|
+
if [ $# -lt 2 ]; then
|
|
18
|
+
echo "Usage: $0 input.scad output.png [--camera=...] [--size=WxH] [-D 'var=val']"
|
|
19
|
+
echo ""
|
|
20
|
+
echo "Camera format: x,y,z,rotx,roty,rotz,distance"
|
|
21
|
+
echo "Common cameras:"
|
|
22
|
+
echo " Isometric: --camera=0,0,0,55,0,25,200"
|
|
23
|
+
echo " Front: --camera=0,0,0,90,0,0,200"
|
|
24
|
+
echo " Top: --camera=0,0,0,0,0,0,200"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
INPUT="$1"
|
|
29
|
+
OUTPUT="$2"
|
|
30
|
+
shift 2
|
|
31
|
+
|
|
32
|
+
# Defaults
|
|
33
|
+
CAMERA="0,0,0,55,0,25,0"
|
|
34
|
+
SIZE="800,600"
|
|
35
|
+
DEFINES=()
|
|
36
|
+
|
|
37
|
+
# Parse options
|
|
38
|
+
while [ $# -gt 0 ]; do
|
|
39
|
+
case "$1" in
|
|
40
|
+
--camera=*)
|
|
41
|
+
CAMERA="${1#--camera=}"
|
|
42
|
+
;;
|
|
43
|
+
--size=*)
|
|
44
|
+
SIZE="${1#--size=}"
|
|
45
|
+
SIZE="${SIZE/x/,}"
|
|
46
|
+
;;
|
|
47
|
+
-D)
|
|
48
|
+
shift
|
|
49
|
+
DEFINES+=("-D" "$1")
|
|
50
|
+
;;
|
|
51
|
+
*)
|
|
52
|
+
echo "Unknown option: $1"
|
|
53
|
+
exit 1
|
|
54
|
+
;;
|
|
55
|
+
esac
|
|
56
|
+
shift
|
|
57
|
+
done
|
|
58
|
+
|
|
59
|
+
# Ensure output directory exists
|
|
60
|
+
mkdir -p "$(dirname "$OUTPUT")"
|
|
61
|
+
|
|
62
|
+
# Run OpenSCAD
|
|
63
|
+
echo "Rendering preview: $INPUT -> $OUTPUT"
|
|
64
|
+
$OPENSCAD \
|
|
65
|
+
--camera="$CAMERA" \
|
|
66
|
+
--imgsize="${SIZE}" \
|
|
67
|
+
--colorscheme="Tomorrow Night" \
|
|
68
|
+
--autocenter \
|
|
69
|
+
--viewall \
|
|
70
|
+
"${DEFINES[@]}" \
|
|
71
|
+
-o "$OUTPUT" \
|
|
72
|
+
"$INPUT"
|
|
73
|
+
|
|
74
|
+
echo "Preview saved to: $OUTPUT"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Render OpenSCAD with parameters from a JSON file
|
|
3
|
+
# Usage: render-with-params.sh input.scad params.json output.stl|output.png
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/common.sh"
|
|
9
|
+
|
|
10
|
+
check_openscad
|
|
11
|
+
|
|
12
|
+
if [ $# -lt 3 ]; then
|
|
13
|
+
echo "Usage: $0 input.scad params.json output.[stl|png]"
|
|
14
|
+
echo ""
|
|
15
|
+
echo "params.json format:"
|
|
16
|
+
echo ' {"width": 60, "height": 40, "include_lid": true}'
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
INPUT="$1"
|
|
21
|
+
PARAMS_FILE="$2"
|
|
22
|
+
OUTPUT="$3"
|
|
23
|
+
|
|
24
|
+
if [ ! -f "$INPUT" ]; then
|
|
25
|
+
echo "Error: Input file not found: $INPUT"
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
if [ ! -f "$PARAMS_FILE" ]; then
|
|
30
|
+
echo "Error: Params file not found: $PARAMS_FILE"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Build -D arguments from JSON
|
|
35
|
+
DEFINES=()
|
|
36
|
+
while IFS= read -r line; do
|
|
37
|
+
# Parse each key-value pair
|
|
38
|
+
key=$(echo "$line" | cut -d'=' -f1)
|
|
39
|
+
value=$(echo "$line" | cut -d'=' -f2-)
|
|
40
|
+
|
|
41
|
+
if [ -n "$key" ]; then
|
|
42
|
+
DEFINES+=("-D" "$key=$value")
|
|
43
|
+
fi
|
|
44
|
+
done < <(
|
|
45
|
+
# Use python or jq to parse JSON to key=value lines
|
|
46
|
+
if command -v python3 &> /dev/null; then
|
|
47
|
+
python3 -c "
|
|
48
|
+
import json
|
|
49
|
+
with open('$PARAMS_FILE') as f:
|
|
50
|
+
params = json.load(f)
|
|
51
|
+
for k, v in params.items():
|
|
52
|
+
if isinstance(v, bool):
|
|
53
|
+
print(f'{k}={str(v).lower()}')
|
|
54
|
+
elif isinstance(v, str):
|
|
55
|
+
print(f'{k}=\"{v}\"')
|
|
56
|
+
else:
|
|
57
|
+
print(f'{k}={v}')
|
|
58
|
+
"
|
|
59
|
+
elif command -v jq &> /dev/null; then
|
|
60
|
+
jq -r 'to_entries | .[] | "\(.key)=\(.value)"' "$PARAMS_FILE"
|
|
61
|
+
else
|
|
62
|
+
echo "Error: Requires python3 or jq to parse JSON"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
echo "Rendering with parameters from: $PARAMS_FILE"
|
|
68
|
+
echo "Parameters: ${DEFINES[*]}"
|
|
69
|
+
|
|
70
|
+
# Determine output type and set appropriate options
|
|
71
|
+
EXT="${OUTPUT##*.}"
|
|
72
|
+
case "$EXT" in
|
|
73
|
+
stl|STL)
|
|
74
|
+
$OPENSCAD "${DEFINES[@]}" -o "$OUTPUT" "$INPUT"
|
|
75
|
+
;;
|
|
76
|
+
png|PNG)
|
|
77
|
+
$OPENSCAD "${DEFINES[@]}" \
|
|
78
|
+
--camera="0,0,0,55,0,25,0" \
|
|
79
|
+
--imgsize="800,600" \
|
|
80
|
+
--colorscheme="Tomorrow Night" \
|
|
81
|
+
--autocenter --viewall \
|
|
82
|
+
-o "$OUTPUT" "$INPUT"
|
|
83
|
+
;;
|
|
84
|
+
*)
|
|
85
|
+
echo "Unsupported output format: $EXT"
|
|
86
|
+
echo "Supported: stl, png"
|
|
87
|
+
exit 1
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
|
|
91
|
+
echo "Output saved: $OUTPUT"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Validate an OpenSCAD file for syntax errors
|
|
3
|
+
# Usage: validate.sh input.scad
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
8
|
+
source "$SCRIPT_DIR/common.sh"
|
|
9
|
+
|
|
10
|
+
check_openscad
|
|
11
|
+
|
|
12
|
+
if [ $# -lt 1 ]; then
|
|
13
|
+
echo "Usage: $0 input.scad"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
INPUT="$1"
|
|
18
|
+
|
|
19
|
+
if [ ! -f "$INPUT" ]; then
|
|
20
|
+
echo "Error: File not found: $INPUT"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
echo "Validating: $INPUT"
|
|
25
|
+
|
|
26
|
+
# Create temp file for output
|
|
27
|
+
TEMP_OUTPUT=$(mktemp /tmp/openscad_validate.XXXXXX.echo)
|
|
28
|
+
trap "rm -f $TEMP_OUTPUT" EXIT
|
|
29
|
+
|
|
30
|
+
# Run OpenSCAD with echo output (fastest way to check syntax)
|
|
31
|
+
# Using --export-format=echo just parses and evaluates without rendering
|
|
32
|
+
if $OPENSCAD -o "$TEMP_OUTPUT" --export-format=echo "$INPUT" 2>&1; then
|
|
33
|
+
echo "✓ Syntax OK"
|
|
34
|
+
|
|
35
|
+
# Check for warnings in stderr
|
|
36
|
+
if [ -s "$TEMP_OUTPUT" ]; then
|
|
37
|
+
echo ""
|
|
38
|
+
echo "Echo output:"
|
|
39
|
+
cat "$TEMP_OUTPUT"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
exit 0
|
|
43
|
+
else
|
|
44
|
+
echo "✗ Validation failed"
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oracle
|
|
3
|
+
description: Get a second opinion by bundling a prompt + a curated file set, then asking a strong model from a different family through a separate Pi invocation.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Oracle
|
|
7
|
+
|
|
8
|
+
Use this skill when you want a second opinion from a capable model in a different family than the current session.
|
|
9
|
+
|
|
10
|
+
The oracle workflow bundles selected files into a standalone prompt, selects an alternate model with `pi --list-models`, and sends the bundle to `pi -p --no-tools`. It is read-only by default because the oracle receives the selected context directly and does not get tools.
|
|
11
|
+
|
|
12
|
+
## Model selection
|
|
13
|
+
|
|
14
|
+
The `./scripts/oracle` wrapper chooses the strongest known model it can find in the first usable family from this order:
|
|
15
|
+
|
|
16
|
+
| Current family | Oracle preference |
|
|
17
|
+
| ------------------ | ----------------------------------------------------- |
|
|
18
|
+
| OpenAI / Codex | Claude / Anthropic → Gemini / Google → OpenAI / Codex |
|
|
19
|
+
| Claude / Anthropic | OpenAI / Codex → Gemini / Google → Claude / Anthropic |
|
|
20
|
+
| Gemini / Google | OpenAI / Codex → Claude / Anthropic → Gemini / Google |
|
|
21
|
+
| Unknown | Claude / Anthropic → OpenAI / Codex → Gemini / Google |
|
|
22
|
+
|
|
23
|
+
It prefers models listed in Pi's `enabledModels`, then falls back to the best matching model shown by `pi --list-models`. Override only when needed with `--model provider/model`.
|
|
24
|
+
|
|
25
|
+
If the current model is not obvious to the script, pass it explicitly with `--current provider/model`. Otherwise it falls back to Pi's default model from `settings.json`.
|
|
26
|
+
|
|
27
|
+
## Workflow
|
|
28
|
+
|
|
29
|
+
1. Pick the smallest file set that contains the truth. Avoid secrets by default.
|
|
30
|
+
2. Preview selected files before sending.
|
|
31
|
+
3. Ask the oracle with a standalone prompt that states the task, constraints, and desired output format.
|
|
32
|
+
4. Treat the result as advice, not authority. Reason from first principles before applying recommendations.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
Run commands from this skill directory:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# Preview selected files
|
|
40
|
+
./scripts/oracle-bundle --dry-run -p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
41
|
+
|
|
42
|
+
# Preview the full bundle
|
|
43
|
+
./scripts/oracle-bundle -p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
44
|
+
|
|
45
|
+
# Show which oracle model would be selected
|
|
46
|
+
./scripts/oracle --list-models --current openai-codex/gpt-5.5
|
|
47
|
+
|
|
48
|
+
# Ask the automatically selected oracle model
|
|
49
|
+
./scripts/oracle --current openai-codex/gpt-5.5 \
|
|
50
|
+
-p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
51
|
+
|
|
52
|
+
# Override the oracle model when the automatic choice is wrong
|
|
53
|
+
./scripts/oracle --model github-copilot/claude-opus-4.7 \
|
|
54
|
+
-p "<task>" --file "src/**" --file "!**/*.test.*"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Tips
|
|
58
|
+
|
|
59
|
+
- Prefer a minimal file set over the whole repo.
|
|
60
|
+
- If you need diffs reviewed, paste the diff into the prompt or attach the diff file via `--file`.
|
|
61
|
+
- Make the prompt completely standalone: include intent, goals, constraints, error text, and whether you want a plan, review, pros/cons, or patch guidance.
|
|
62
|
+
- Never include secrets (`.env`, tokens, key files).
|
|
63
|
+
- For general code review, ask for feedback on correctness, consistency with existing code, simplicity, maintainability, performance, security, and edge cases.
|
|
64
|
+
- Oracle can be slow while it reasons. Allow it several minutes to process.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const THINKING_LEVEL = "xhigh";
|
|
9
|
+
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
|
|
11
|
+
const FAMILY_ORDER = {
|
|
12
|
+
openai: ["anthropic", "google", "openai"],
|
|
13
|
+
anthropic: ["openai", "google", "anthropic"],
|
|
14
|
+
google: ["openai", "anthropic", "google"],
|
|
15
|
+
unknown: ["anthropic", "openai", "google"],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const args = parseArgs(process.argv.slice(2));
|
|
19
|
+
const settings = readSettings();
|
|
20
|
+
const currentModel = args.currentModel ?? process.env.ORACLE_CURRENT_MODEL ?? defaultModel(settings);
|
|
21
|
+
const currentFamily = modelFamily(currentModel ?? "") ?? "unknown";
|
|
22
|
+
const availableModels = listPiModels();
|
|
23
|
+
const enabledModels = new Set((settings.enabledModels ?? []).map(normalizeModelSpec));
|
|
24
|
+
|
|
25
|
+
if (args.listModels) {
|
|
26
|
+
printModelPlan(availableModels, enabledModels, currentModel, currentFamily);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const oracleModel = args.model ?? selectOracleModel(availableModels, enabledModels, currentFamily);
|
|
31
|
+
if (!oracleModel) {
|
|
32
|
+
die("Could not find a suitable oracle model from `pi --list-models`.");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.error(
|
|
36
|
+
`[oracle] current=${currentModel ?? "unknown"} family=${currentFamily}; oracle=${oracleModel}`,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const bundle = run(path.join(SCRIPT_DIR, "oracle-bundle"), args.bundleArgs, {
|
|
40
|
+
cwd: repoRoot(),
|
|
41
|
+
encoding: "utf8",
|
|
42
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (bundle.status !== 0) {
|
|
46
|
+
process.stderr.write(bundle.stderr ?? "");
|
|
47
|
+
process.exit(bundle.status ?? 1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const pi = spawnSync("pi", ["-p", "--no-tools", "--model", oracleModel, "--thinking", THINKING_LEVEL], {
|
|
51
|
+
cwd: repoRoot(),
|
|
52
|
+
input: bundle.stdout,
|
|
53
|
+
stdio: ["pipe", "inherit", "inherit"],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (pi.error) die(pi.error.message);
|
|
57
|
+
process.exit(pi.status ?? 1);
|
|
58
|
+
|
|
59
|
+
function parseArgs(argv) {
|
|
60
|
+
const bundleArgs = [];
|
|
61
|
+
let currentModel;
|
|
62
|
+
let model;
|
|
63
|
+
let listModels = false;
|
|
64
|
+
|
|
65
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
66
|
+
const arg = argv[index];
|
|
67
|
+
if (arg === "--current" || arg === "--current-model") {
|
|
68
|
+
currentModel = requireValue(argv, (index += 1), arg);
|
|
69
|
+
} else if (arg === "--model" || arg === "--oracle-model") {
|
|
70
|
+
model = normalizeModelSpec(requireValue(argv, (index += 1), arg));
|
|
71
|
+
} else if (arg === "--list-models") {
|
|
72
|
+
listModels = true;
|
|
73
|
+
} else {
|
|
74
|
+
bundleArgs.push(arg);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { bundleArgs, currentModel: currentModel ? normalizeModelSpec(currentModel) : undefined, model, listModels };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function requireValue(argv, index, flag) {
|
|
82
|
+
const value = argv[index];
|
|
83
|
+
if (!value) die(`${flag} requires a value`);
|
|
84
|
+
return value;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readSettings() {
|
|
88
|
+
const agentDir = process.env.PI_CODING_AGENT_DIR ?? path.join(homedir(), ".pi", "agent");
|
|
89
|
+
const settingsPath = path.join(agentDir, "settings.json");
|
|
90
|
+
if (!existsSync(settingsPath)) return {};
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(`[oracle] warning: failed to parse ${settingsPath}: ${error.message}`);
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function defaultModel(settings) {
|
|
101
|
+
if (!settings.defaultProvider || !settings.defaultModel) return undefined;
|
|
102
|
+
return normalizeModelSpec(`${settings.defaultProvider}/${settings.defaultModel}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function listPiModels() {
|
|
106
|
+
const result = run("pi", ["--list-models"], { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
|
|
107
|
+
if (result.error) die(result.error.message);
|
|
108
|
+
if (result.status !== 0) die(result.stderr || "pi --list-models failed");
|
|
109
|
+
|
|
110
|
+
const models = [];
|
|
111
|
+
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
112
|
+
for (const line of output.split("\n")) {
|
|
113
|
+
const match = line.match(/^(\S+)\s+(\S+)\s+/);
|
|
114
|
+
if (!match || match[1] === "provider") continue;
|
|
115
|
+
models.push({ provider: match[1], model: match[2], spec: `${match[1]}/${match[2]}` });
|
|
116
|
+
}
|
|
117
|
+
return models;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function selectOracleModel(models, enabledModels, currentFamily) {
|
|
121
|
+
for (const family of FAMILY_ORDER[currentFamily] ?? FAMILY_ORDER.unknown) {
|
|
122
|
+
const best = bestModelForFamily(models, enabledModels, family);
|
|
123
|
+
if (best) return best.spec;
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function bestModelForFamily(models, enabledModels, family) {
|
|
129
|
+
return models
|
|
130
|
+
.map((model) => ({ model, score: scoreModel(model, enabledModels, family) }))
|
|
131
|
+
.filter((entry) => entry.score >= 0)
|
|
132
|
+
.sort((a, b) => b.score - a.score || a.model.spec.localeCompare(b.model.spec))[0]?.model;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function scoreModel(entry, enabledModels, targetFamily) {
|
|
136
|
+
if (modelFamily(entry.spec) !== targetFamily) return -1;
|
|
137
|
+
|
|
138
|
+
let score = enabledModels.has(normalizeModelSpec(entry.spec)) ? 10_000 : 0;
|
|
139
|
+
score += providerScore(entry.provider, targetFamily);
|
|
140
|
+
score += nameScore(entry.model, targetFamily);
|
|
141
|
+
return score;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function providerScore(provider, family) {
|
|
145
|
+
const normalized = provider.toLowerCase();
|
|
146
|
+
if (family === "openai") {
|
|
147
|
+
if (normalized === "openai-codex") return 700;
|
|
148
|
+
if (normalized === "openai") return 600;
|
|
149
|
+
if (normalized === "github-copilot") return 500;
|
|
150
|
+
if (normalized.includes("azure")) return 450;
|
|
151
|
+
if (normalized === "openrouter") return 300;
|
|
152
|
+
return 100;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (family === "anthropic") {
|
|
156
|
+
if (normalized === "github-copilot") return 700;
|
|
157
|
+
if (normalized === "anthropic") return 600;
|
|
158
|
+
if (normalized === "amazon-bedrock") return 450;
|
|
159
|
+
if (normalized === "openrouter") return 300;
|
|
160
|
+
return 100;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (family === "google") {
|
|
164
|
+
if (normalized === "google-gemini-cli") return 700;
|
|
165
|
+
if (normalized === "google") return 600;
|
|
166
|
+
if (normalized === "github-copilot") return 500;
|
|
167
|
+
if (normalized.includes("vertex")) return 450;
|
|
168
|
+
if (normalized === "openrouter") return 300;
|
|
169
|
+
return 100;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function nameScore(model, family) {
|
|
176
|
+
const normalized = model.toLowerCase();
|
|
177
|
+
|
|
178
|
+
if (family === "openai") {
|
|
179
|
+
if (/gpt-5\.5/.test(normalized)) return 1_000;
|
|
180
|
+
if (/gpt-5\.4/.test(normalized)) return 950;
|
|
181
|
+
if (/gpt-5\.3.*codex/.test(normalized)) return 925;
|
|
182
|
+
if (/gpt-5\.3/.test(normalized)) return 900;
|
|
183
|
+
if (/gpt-5\.2/.test(normalized)) return 850;
|
|
184
|
+
if (/gpt-5\.1/.test(normalized)) return 800;
|
|
185
|
+
if (/gpt-5/.test(normalized)) return 750;
|
|
186
|
+
if (/gpt-4\.1/.test(normalized)) return 500;
|
|
187
|
+
return 100;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (family === "anthropic") {
|
|
191
|
+
if (/claude[-_.]opus[-_.]4[-_.]7/.test(normalized)) return 1_000;
|
|
192
|
+
if (/claude[-_.]opus[-_.]4[-_.]6/.test(normalized)) return 950;
|
|
193
|
+
if (/claude[-_.]opus[-_.]4[-_.]5/.test(normalized)) return 900;
|
|
194
|
+
if (/claude[-_.]opus[-_.]4[-_.]1/.test(normalized)) return 850;
|
|
195
|
+
if (/claude[-_.]opus[-_.]4/.test(normalized)) return 800;
|
|
196
|
+
if (/claude[-_.]sonnet[-_.]4[-_.]6/.test(normalized)) return 700;
|
|
197
|
+
if (/claude[-_.]sonnet[-_.]4[-_.]5/.test(normalized)) return 650;
|
|
198
|
+
if (/claude[-_.]sonnet[-_.]4/.test(normalized)) return 600;
|
|
199
|
+
return 100;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (family === "google") {
|
|
203
|
+
if (/gemini[-_.]3\.1[-_.]pro/.test(normalized)) return 1_000;
|
|
204
|
+
if (/gemini[-_.]3[-_.]pro/.test(normalized)) return 950;
|
|
205
|
+
if (/gemini[-_.]2\.5[-_.]pro/.test(normalized)) return 800;
|
|
206
|
+
return 100;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function modelFamily(spec) {
|
|
213
|
+
const normalized = normalizeModelSpec(spec).toLowerCase();
|
|
214
|
+
if (!normalized) return undefined;
|
|
215
|
+
if (/(anthropic|claude)/.test(normalized)) return "anthropic";
|
|
216
|
+
if (/(google|gemini)/.test(normalized)) return "google";
|
|
217
|
+
if (/(openai|gpt|codex)/.test(normalized)) return "openai";
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeModelSpec(spec) {
|
|
222
|
+
return String(spec).trim().replace(/:(off|minimal|low|medium|high|xhigh)$/i, "");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function printModelPlan(models, enabledModels, currentModel, currentFamily) {
|
|
226
|
+
console.log(`Current model: ${currentModel ?? "unknown"}`);
|
|
227
|
+
console.log(`Current family: ${currentFamily}`);
|
|
228
|
+
console.log(`Family order: ${(FAMILY_ORDER[currentFamily] ?? FAMILY_ORDER.unknown).join(" -> ")}`);
|
|
229
|
+
console.log("");
|
|
230
|
+
|
|
231
|
+
for (const family of ["anthropic", "openai", "google"]) {
|
|
232
|
+
const best = bestModelForFamily(models, enabledModels, family);
|
|
233
|
+
console.log(`${family}: ${best?.spec ?? "not found"}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function repoRoot() {
|
|
238
|
+
const result = run("git", ["rev-parse", "--show-toplevel"], { encoding: "utf8" });
|
|
239
|
+
return result.status === 0 ? result.stdout.trim() : process.cwd();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function run(command, args, options = {}) {
|
|
243
|
+
return spawnSync(command, args, { ...options });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function die(message) {
|
|
247
|
+
console.error(`oracle: ${message}`);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|