tau-coding-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 +42 -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/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 +72 -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/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,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
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -eo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
5
|
+
|
|
6
|
+
MAX_BYTES=$((1024 * 1024))
|
|
7
|
+
DRY_RUN=0
|
|
8
|
+
PROMPT=""
|
|
9
|
+
FILE_SELECTORS=()
|
|
10
|
+
|
|
11
|
+
usage() {
|
|
12
|
+
cat <<'EOF'
|
|
13
|
+
Usage:
|
|
14
|
+
oracle-bundle -p "<task>" --file "path|dir|glob" [--file "..."] [--dry-run] [--max-bytes N]
|
|
15
|
+
|
|
16
|
+
Notes:
|
|
17
|
+
- Runs from the git repo root when inside a repo; otherwise uses $PWD.
|
|
18
|
+
- --file accepts paths, dirs, or glob-like patterns (string globs).
|
|
19
|
+
- Exclude patterns by prefixing with "!" (e.g. --file "!**/*.test.*").
|
|
20
|
+
EOF
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
trim() {
|
|
24
|
+
local s="$1"
|
|
25
|
+
s="${s#"${s%%[!$' \t\r\n']*}"}"
|
|
26
|
+
s="${s%"${s##*[!$' \t\r\n']}"}"
|
|
27
|
+
printf '%s' "$s"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
has_glob_chars() {
|
|
31
|
+
case "$1" in
|
|
32
|
+
*'*'* | *'?'* | *'['* | *']'* ) return 0 ;;
|
|
33
|
+
* ) return 1 ;;
|
|
34
|
+
esac
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
file_size_bytes() {
|
|
38
|
+
local path="$1"
|
|
39
|
+
local size=""
|
|
40
|
+
size="$(stat -f%z "$path" 2>/dev/null || true)"
|
|
41
|
+
if [[ -n "$size" ]]; then
|
|
42
|
+
printf '%s' "$size"
|
|
43
|
+
return 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
size="$(stat -c%s "$path" 2>/dev/null || true)"
|
|
47
|
+
if [[ -n "$size" ]]; then
|
|
48
|
+
printf '%s' "$size"
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
wc -c <"$path" | tr -d ' '
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
path_has_ignored_dir() {
|
|
56
|
+
local rel="$1"
|
|
57
|
+
case "$rel" in
|
|
58
|
+
.git/* | */.git/*) return 0 ;;
|
|
59
|
+
node_modules/* | */node_modules/*) return 0 ;;
|
|
60
|
+
dist/* | */dist/*) return 0 ;;
|
|
61
|
+
build/* | */build/*) return 0 ;;
|
|
62
|
+
coverage/* | */coverage/*) return 0 ;;
|
|
63
|
+
.next/* | */.next/*) return 0 ;;
|
|
64
|
+
.turbo/* | */.turbo/*) return 0 ;;
|
|
65
|
+
tmp/* | */tmp/*) return 0 ;;
|
|
66
|
+
*) return 1 ;;
|
|
67
|
+
esac
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
matches_any_pattern() {
|
|
71
|
+
local rel="$1"
|
|
72
|
+
shift
|
|
73
|
+
local pat=""
|
|
74
|
+
for pat in "$@"; do
|
|
75
|
+
# shellcheck disable=SC2053
|
|
76
|
+
[[ "$rel" == $pat ]] && return 0
|
|
77
|
+
done
|
|
78
|
+
return 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
while [[ $# -gt 0 ]]; do
|
|
82
|
+
case "$1" in
|
|
83
|
+
-p | --prompt)
|
|
84
|
+
PROMPT="${2-}"
|
|
85
|
+
shift 2
|
|
86
|
+
;;
|
|
87
|
+
--file | -f)
|
|
88
|
+
FILE_SELECTORS+=("${2-}")
|
|
89
|
+
shift 2
|
|
90
|
+
;;
|
|
91
|
+
--max-bytes)
|
|
92
|
+
MAX_BYTES="${2-}"
|
|
93
|
+
shift 2
|
|
94
|
+
;;
|
|
95
|
+
--dry-run)
|
|
96
|
+
DRY_RUN=1
|
|
97
|
+
shift
|
|
98
|
+
;;
|
|
99
|
+
-h | --help)
|
|
100
|
+
usage
|
|
101
|
+
exit 0
|
|
102
|
+
;;
|
|
103
|
+
*)
|
|
104
|
+
echo "error: unknown argument: $1" >&2
|
|
105
|
+
usage >&2
|
|
106
|
+
exit 2
|
|
107
|
+
;;
|
|
108
|
+
esac
|
|
109
|
+
done
|
|
110
|
+
|
|
111
|
+
if [[ -z "$PROMPT" && ! -t 0 ]]; then
|
|
112
|
+
PROMPT="$(cat)"
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
if [[ -z "$(trim "$PROMPT")" ]]; then
|
|
116
|
+
echo "error: prompt is empty (use -p/--prompt)" >&2
|
|
117
|
+
exit 2
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
cd "${ROOT_DIR}"
|
|
121
|
+
|
|
122
|
+
selectors=()
|
|
123
|
+
for raw in "${FILE_SELECTORS[@]-}"; do
|
|
124
|
+
IFS=',' read -r -a parts <<<"$raw"
|
|
125
|
+
for part in "${parts[@]}"; do
|
|
126
|
+
part="$(trim "$part")"
|
|
127
|
+
[[ -n "$part" ]] && selectors+=("$part")
|
|
128
|
+
done
|
|
129
|
+
done
|
|
130
|
+
|
|
131
|
+
include_patterns=()
|
|
132
|
+
exclude_patterns=()
|
|
133
|
+
|
|
134
|
+
for sel in "${selectors[@]}"; do
|
|
135
|
+
is_exclude=0
|
|
136
|
+
if [[ "$sel" == !* ]]; then
|
|
137
|
+
is_exclude=1
|
|
138
|
+
sel="${sel:1}"
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
sel="$(trim "$sel")"
|
|
142
|
+
sel="${sel%/}"
|
|
143
|
+
if [[ -z "$sel" ]]; then
|
|
144
|
+
continue
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if [[ "$sel" == /* ]]; then
|
|
148
|
+
if [[ "$sel" == "${ROOT_DIR}/"* ]]; then
|
|
149
|
+
sel="${sel#"${ROOT_DIR}/"}"
|
|
150
|
+
else
|
|
151
|
+
echo "error: absolute paths must be under repo root: ${sel}" >&2
|
|
152
|
+
exit 2
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
|
|
156
|
+
if [[ -e "$sel" && -d "$sel" ]] && ! has_glob_chars "$sel"; then
|
|
157
|
+
sel="${sel}/**"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
if [[ $is_exclude -eq 1 ]]; then
|
|
161
|
+
exclude_patterns+=("$sel")
|
|
162
|
+
else
|
|
163
|
+
include_patterns+=("$sel")
|
|
164
|
+
fi
|
|
165
|
+
done
|
|
166
|
+
|
|
167
|
+
if [[ "${#include_patterns[@]}" -eq 0 ]]; then
|
|
168
|
+
echo "error: no include patterns provided (need at least one non-! --file)" >&2
|
|
169
|
+
exit 2
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
all_files=()
|
|
173
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
174
|
+
while IFS= read -r f; do
|
|
175
|
+
[[ -n "$f" ]] && all_files+=("$f")
|
|
176
|
+
done < <(git ls-files -co --exclude-standard)
|
|
177
|
+
else
|
|
178
|
+
while IFS= read -r f; do
|
|
179
|
+
f="${f#./}"
|
|
180
|
+
[[ -n "$f" ]] && all_files+=("$f")
|
|
181
|
+
done < <(find . -type f -print)
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
selected=()
|
|
185
|
+
skipped=()
|
|
186
|
+
|
|
187
|
+
for rel in "${all_files[@]}"; do
|
|
188
|
+
[[ -z "$rel" ]] && continue
|
|
189
|
+
path_has_ignored_dir "$rel" && continue
|
|
190
|
+
|
|
191
|
+
if ! matches_any_pattern "$rel" "${include_patterns[@]}"; then
|
|
192
|
+
continue
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
if [[ "${#exclude_patterns[@]}" -gt 0 ]] && matches_any_pattern "$rel" "${exclude_patterns[@]}"; then
|
|
196
|
+
continue
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
if [[ ! -f "$rel" ]]; then
|
|
200
|
+
continue
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
size="$(file_size_bytes "$rel")"
|
|
204
|
+
if [[ "$size" -gt "$MAX_BYTES" ]]; then
|
|
205
|
+
skipped+=("${rel} (too large: ${size} bytes)")
|
|
206
|
+
continue
|
|
207
|
+
fi
|
|
208
|
+
|
|
209
|
+
selected+=("$rel")
|
|
210
|
+
done
|
|
211
|
+
|
|
212
|
+
if [[ "$DRY_RUN" -eq 1 ]]; then
|
|
213
|
+
printf '%s\n' "${selected[@]}" | LC_ALL=C sort
|
|
214
|
+
if [[ "${#skipped[@]}" -gt 0 ]]; then
|
|
215
|
+
printf '\nSkipped:\n' >&2
|
|
216
|
+
printf '%s\n' "${skipped[@]}" >&2
|
|
217
|
+
fi
|
|
218
|
+
exit 0
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
printf '# Task\n\n%s\n\n# Files\n' "$(trim "$PROMPT")"
|
|
222
|
+
printf '%s\n' "${selected[@]}" | LC_ALL=C sort | while IFS= read -r rel; do
|
|
223
|
+
[[ -z "$rel" ]] && continue
|
|
224
|
+
printf '\n## %s\n\n```\n' "$rel"
|
|
225
|
+
cat "$rel"
|
|
226
|
+
printf '\n```\n'
|
|
227
|
+
done
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sentry
|
|
3
|
+
description: "Fetch and analyze Sentry issues, events, transactions, and logs. Helps agents debug errors, find root causes, and understand what happened at specific times."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sentry
|
|
7
|
+
|
|
8
|
+
Access Sentry data via the Sentry API for debugging and investigation.
|
|
9
|
+
|
|
10
|
+
## Requirements
|
|
11
|
+
|
|
12
|
+
A Sentry auth token available to the scripts (by default they read `~/.sentryclirc`).
|
|
13
|
+
|
|
14
|
+
## Quick reference
|
|
15
|
+
|
|
16
|
+
Run these scripts from this skill directory.
|
|
17
|
+
|
|
18
|
+
| Task | Command |
|
|
19
|
+
| --------------------- | -------------------------------------------------------------------------------- |
|
|
20
|
+
| Find errors on a date | `"./scripts/search-events.js" --org X --start 2025-12-23T15:00:00 --level error` |
|
|
21
|
+
| List open issues | `"./scripts/list-issues.js" --org X --status unresolved` |
|
|
22
|
+
| Get issue details | `"./scripts/fetch-issue.js" <issue-id-or-url> --latest` |
|
|
23
|
+
| Get event details | `"./scripts/fetch-event.js" <event-id> --org X --project Y` |
|
|
24
|
+
| Search logs | `"./scripts/search-logs.js" --org X --project Y "level:error"` |
|
|
25
|
+
|
|
26
|
+
## Common debugging workflows
|
|
27
|
+
|
|
28
|
+
### “What went wrong at this time?”
|
|
29
|
+
|
|
30
|
+
Find events around a specific timestamp:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Find all events in a 2-hour window
|
|
34
|
+
"./scripts/search-events.js" --org myorg --project backend \
|
|
35
|
+
--start 2025-12-23T15:00:00 --end 2025-12-23T17:00:00
|
|
36
|
+
|
|
37
|
+
# Filter to just errors
|
|
38
|
+
"./scripts/search-events.js" --org myorg --start 2025-12-23T15:00:00 \
|
|
39
|
+
--level error
|
|
40
|
+
|
|
41
|
+
# Find a specific transaction type
|
|
42
|
+
"./scripts/search-events.js" --org myorg --start 2025-12-23T15:00:00 \
|
|
43
|
+
--transaction process-incoming-email
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### “What errors have occurred recently?”
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# List unresolved errors from last 24 hours
|
|
50
|
+
"./scripts/list-issues.js" --org myorg --status unresolved --level error --period 24h
|
|
51
|
+
|
|
52
|
+
# Find high-frequency issues
|
|
53
|
+
"./scripts/list-issues.js" --org myorg --query "times_seen:>50" --sort freq
|
|
54
|
+
|
|
55
|
+
# Issues affecting users
|
|
56
|
+
"./scripts/list-issues.js" --org myorg --query "is:unresolved has:user" --sort user
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### “Get details about a specific issue/event”
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Get issue with latest stack trace
|
|
63
|
+
"./scripts/fetch-issue.js" 5765604106 --latest
|
|
64
|
+
"./scripts/fetch-issue.js" https://sentry.io/organizations/myorg/issues/123/ --latest
|
|
65
|
+
"./scripts/fetch-issue.js" MYPROJ-123 --org myorg --latest
|
|
66
|
+
|
|
67
|
+
# Get specific event with all breadcrumbs
|
|
68
|
+
"./scripts/fetch-event.js" abc123def456 --org myorg --project backend --breadcrumbs
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### “Find events with a specific tag”
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# Find by custom tag (e.g., thread_id, user_id)
|
|
75
|
+
"./scripts/search-events.js" --org myorg --tag thread_id:th_abc123
|
|
76
|
+
|
|
77
|
+
# Find by user email
|
|
78
|
+
"./scripts/search-events.js" --org myorg --query "user.email:*@example.com"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Fetch issue
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
"./scripts/fetch-issue.js" <issue-id-or-url> [options]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Get details about a specific issue (grouped error).
|
|
90
|
+
|
|
91
|
+
**Accepts:**
|
|
92
|
+
|
|
93
|
+
- Issue ID: `5765604106`
|
|
94
|
+
- Issue URL: `https://sentry.io/organizations/sentry/issues/5765604106/`
|
|
95
|
+
- New URL format: `https://myorg.sentry.io/issues/5765604106/`
|
|
96
|
+
- Short ID: `JAVASCRIPT-ABC` (requires `--org`)
|
|
97
|
+
|
|
98
|
+
**Options:**
|
|
99
|
+
|
|
100
|
+
- `--latest` include the latest event with full stack trace
|
|
101
|
+
- `--org <org>` organization slug (for short IDs)
|
|
102
|
+
- `--json` output raw JSON
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Fetch event
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
"./scripts/fetch-event.js" <event-id> --org <org> --project <project> [options]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Options:**
|
|
113
|
+
|
|
114
|
+
- `--org, -o <org>` organization slug (required)
|
|
115
|
+
- `--project, -p <project>` project slug (required)
|
|
116
|
+
- `--breadcrumbs, -b` show all breadcrumbs (default: last 30)
|
|
117
|
+
- `--spans` show span tree for transactions
|
|
118
|
+
- `--json` output raw JSON
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Search events (Discover)
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
"./scripts/search-events.js" [options]
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Time range options:**
|
|
129
|
+
|
|
130
|
+
- `--period, -t <period>` relative time (24h, 7d, 14d)
|
|
131
|
+
- `--start <datetime>` start time (ISO 8601: 2025-12-23T15:00:00)
|
|
132
|
+
- `--end <datetime>` end time (ISO 8601)
|
|
133
|
+
|
|
134
|
+
**Filter options:**
|
|
135
|
+
|
|
136
|
+
- `--org, -o <org>` organization slug (required)
|
|
137
|
+
- `--project, -p <project>` project slug or ID
|
|
138
|
+
- `--query, -q <query>` Discover search query
|
|
139
|
+
- `--transaction <name>` transaction name filter
|
|
140
|
+
- `--tag <key:value>` tag filter (repeatable)
|
|
141
|
+
- `--level <level>` level filter (error, warning, info)
|
|
142
|
+
- `--limit, -n <n>` max results (default: 25, max: 100)
|
|
143
|
+
- `--fields <fields>` comma-separated fields to include
|
|
144
|
+
|
|
145
|
+
**Query syntax (Discover):**
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
transaction:process-* Wildcard transaction match
|
|
149
|
+
level:error Filter by level
|
|
150
|
+
user.email:foo@bar.com Filter by user
|
|
151
|
+
environment:production Filter by environment
|
|
152
|
+
has:stack.filename Has stack trace
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## List issues
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
"./scripts/list-issues.js" [options]
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Options:**
|
|
164
|
+
|
|
165
|
+
- `--org, -o <org>` organization slug (required)
|
|
166
|
+
- `--project, -p <project>` project slug (repeatable)
|
|
167
|
+
- `--query, -q <query>` issue search query
|
|
168
|
+
- `--status <status>` unresolved, resolved, ignored
|
|
169
|
+
- `--level <level>` error, warning, info, fatal
|
|
170
|
+
- `--period, -t <period>` time period (default: 14d)
|
|
171
|
+
- `--limit, -n <n>` max results (default: 25)
|
|
172
|
+
- `--sort <sort>` date, new, priority, freq, user
|
|
173
|
+
- `--json` output raw JSON
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Search logs (Logs Explorer)
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
"./scripts/search-logs.js" [query|url] [options]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Options:**
|
|
184
|
+
|
|
185
|
+
- `--org, -o <org>` organization slug (required unless URL provided)
|
|
186
|
+
- `--project, -p <project>` filter by project slug or ID
|
|
187
|
+
- `--period, -t <period>` time period (default: 24h)
|
|
188
|
+
- `--limit, -n <n>` max results (default: 100, max: 1000)
|
|
189
|
+
- `--json` output raw JSON
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Tips
|
|
194
|
+
|
|
195
|
+
1. Start broad (time window + simple query), then drill into a single event/issue.
|
|
196
|
+
2. Use `--breadcrumbs` on `fetch-event.js` for the full lead-up to an error.
|
|
197
|
+
3. Use `list-issues.js --sort freq` to find recurring problems.
|
|
198
|
+
4. Use tags (`request_id`, `user_id`, etc.) to correlate events.
|