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,291 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { SENTRY_API_BASE, getAuthToken, fetchJson, resolveProjectId } from "../lib/auth.js";
|
|
4
|
+
|
|
5
|
+
const HELP = `Usage: search-events.js [options]
|
|
6
|
+
|
|
7
|
+
Search for events (transactions, errors) in Sentry Discover.
|
|
8
|
+
|
|
9
|
+
Options:
|
|
10
|
+
--org, -o <org> Organization slug (required)
|
|
11
|
+
--project, -p <project> Project slug or ID
|
|
12
|
+
--query, -q <query> Search query (Discover syntax)
|
|
13
|
+
--period, -t <period> Time period (default: 24h, e.g., 1h, 7d, 14d)
|
|
14
|
+
--start <datetime> Start time (ISO 8601, e.g., 2025-12-23T15:00:00)
|
|
15
|
+
--end <datetime> End time (ISO 8601)
|
|
16
|
+
--transaction <name> Filter by transaction name
|
|
17
|
+
--tag <key:value> Filter by tag (can be repeated)
|
|
18
|
+
--level <level> Filter by level (error, warning, info)
|
|
19
|
+
--limit, -n <n> Max results (default: 25, max: 100)
|
|
20
|
+
--fields <fields> Comma-separated fields to include
|
|
21
|
+
--json Output raw JSON
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
|
|
24
|
+
Common Fields:
|
|
25
|
+
id, title, timestamp, transaction, message, level, environment,
|
|
26
|
+
user.email, user.id, tags[key], http.method, http.url
|
|
27
|
+
|
|
28
|
+
Query Syntax (Discover):
|
|
29
|
+
transaction:process-* Match transaction names with wildcards
|
|
30
|
+
level:error Filter by log level
|
|
31
|
+
user.email:foo@bar.com Filter by user email
|
|
32
|
+
environment:production Filter by environment
|
|
33
|
+
has:stack.filename Events with stack traces
|
|
34
|
+
!has:user Events without user
|
|
35
|
+
|
|
36
|
+
Date Range Examples:
|
|
37
|
+
--period 7d Last 7 days
|
|
38
|
+
--start 2025-12-23T15:00:00 From specific time to now
|
|
39
|
+
--start 2025-12-23T15:00:00 --end 2025-12-23T18:00:00 Specific range
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
# Find all transactions for a transaction name
|
|
43
|
+
search-events.js --org myorg --project backend --transaction process-incoming-email
|
|
44
|
+
|
|
45
|
+
# Find errors in the last 7 days
|
|
46
|
+
search-events.js --org myorg --query "level:error" --period 7d
|
|
47
|
+
|
|
48
|
+
# Find events around a specific time
|
|
49
|
+
search-events.js --org myorg --start 2025-12-23T15:00:00 --end 2025-12-23T17:00:00
|
|
50
|
+
|
|
51
|
+
# Search with a specific tag
|
|
52
|
+
search-events.js --org myorg --tag thread_id:th_abc123
|
|
53
|
+
|
|
54
|
+
# Get more fields
|
|
55
|
+
search-events.js --org myorg --fields "id,title,timestamp,user.email"
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
function parseArgs(args) {
|
|
59
|
+
const options = {
|
|
60
|
+
org: null,
|
|
61
|
+
project: null,
|
|
62
|
+
query: null,
|
|
63
|
+
period: null,
|
|
64
|
+
start: null,
|
|
65
|
+
end: null,
|
|
66
|
+
transaction: null,
|
|
67
|
+
tags: [],
|
|
68
|
+
level: null,
|
|
69
|
+
limit: 25,
|
|
70
|
+
fields: ["id", "title", "timestamp", "transaction", "message"],
|
|
71
|
+
json: false,
|
|
72
|
+
help: false,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < args.length; i++) {
|
|
76
|
+
const arg = args[i];
|
|
77
|
+
|
|
78
|
+
switch (arg) {
|
|
79
|
+
case "--help":
|
|
80
|
+
case "-h":
|
|
81
|
+
options.help = true;
|
|
82
|
+
break;
|
|
83
|
+
case "--json":
|
|
84
|
+
options.json = true;
|
|
85
|
+
break;
|
|
86
|
+
case "--org":
|
|
87
|
+
case "-o":
|
|
88
|
+
options.org = args[++i];
|
|
89
|
+
break;
|
|
90
|
+
case "--project":
|
|
91
|
+
case "-p":
|
|
92
|
+
options.project = args[++i];
|
|
93
|
+
break;
|
|
94
|
+
case "--query":
|
|
95
|
+
case "-q":
|
|
96
|
+
options.query = args[++i];
|
|
97
|
+
break;
|
|
98
|
+
case "--period":
|
|
99
|
+
case "-t":
|
|
100
|
+
options.period = args[++i];
|
|
101
|
+
break;
|
|
102
|
+
case "--start":
|
|
103
|
+
options.start = args[++i];
|
|
104
|
+
break;
|
|
105
|
+
case "--end":
|
|
106
|
+
options.end = args[++i];
|
|
107
|
+
break;
|
|
108
|
+
case "--transaction":
|
|
109
|
+
options.transaction = args[++i];
|
|
110
|
+
break;
|
|
111
|
+
case "--tag":
|
|
112
|
+
options.tags.push(args[++i]);
|
|
113
|
+
break;
|
|
114
|
+
case "--level":
|
|
115
|
+
options.level = args[++i];
|
|
116
|
+
break;
|
|
117
|
+
case "--limit":
|
|
118
|
+
case "-n":
|
|
119
|
+
options.limit = parseInt(args[++i], 10);
|
|
120
|
+
break;
|
|
121
|
+
case "--fields":
|
|
122
|
+
options.fields = args[++i].split(",").map((f) => f.trim());
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Default to 24h if no time range specified
|
|
128
|
+
if (!options.period && !options.start) {
|
|
129
|
+
options.period = "24h";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return options;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function formatEvent(event, fields) {
|
|
136
|
+
const lines = [];
|
|
137
|
+
|
|
138
|
+
const id = event.id || event["event.type"] || "?";
|
|
139
|
+
const ts = event.timestamp || "N/A";
|
|
140
|
+
const title = event.title || event.transaction || event.message || "(no title)";
|
|
141
|
+
const transaction = event.transaction || "";
|
|
142
|
+
|
|
143
|
+
// Format timestamp
|
|
144
|
+
let displayTs = ts;
|
|
145
|
+
try {
|
|
146
|
+
const date = new Date(ts);
|
|
147
|
+
if (!isNaN(date.getTime())) {
|
|
148
|
+
displayTs = date.toISOString().replace("T", " ").slice(0, 19);
|
|
149
|
+
}
|
|
150
|
+
} catch {}
|
|
151
|
+
|
|
152
|
+
lines.push(`[${displayTs}] ${title}`);
|
|
153
|
+
|
|
154
|
+
if (transaction && transaction !== title) {
|
|
155
|
+
lines.push(` transaction: ${transaction}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (event.message && event.message !== title) {
|
|
159
|
+
lines.push(` message: ${event.message}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Show any extra fields the user requested
|
|
163
|
+
for (const field of fields) {
|
|
164
|
+
if (["id", "title", "timestamp", "transaction", "message"].includes(field)) continue;
|
|
165
|
+
const value = event[field];
|
|
166
|
+
if (value !== undefined && value !== null && value !== "") {
|
|
167
|
+
lines.push(` ${field}: ${value}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
lines.push(` id: ${id}`);
|
|
172
|
+
|
|
173
|
+
return lines.join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function formatOutput(data, fields) {
|
|
177
|
+
if (!data.data || data.data.length === 0) {
|
|
178
|
+
return "No events found matching your query.";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const lines = [];
|
|
182
|
+
lines.push(`Found ${data.data.length} events:\n`);
|
|
183
|
+
|
|
184
|
+
for (const event of data.data) {
|
|
185
|
+
lines.push(formatEvent(event, fields));
|
|
186
|
+
lines.push("");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return lines.join("\n").trimEnd();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function main() {
|
|
193
|
+
const args = process.argv.slice(2);
|
|
194
|
+
const options = parseArgs(args);
|
|
195
|
+
|
|
196
|
+
if (options.help) {
|
|
197
|
+
console.log(HELP);
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!options.org) {
|
|
202
|
+
console.error("Error: --org is required");
|
|
203
|
+
console.error("Run with --help for usage information");
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const token = getAuthToken();
|
|
208
|
+
|
|
209
|
+
// Build query parameters
|
|
210
|
+
const params = new URLSearchParams();
|
|
211
|
+
params.set("dataset", "discover");
|
|
212
|
+
|
|
213
|
+
// Time range
|
|
214
|
+
if (options.start) {
|
|
215
|
+
params.set("start", options.start);
|
|
216
|
+
if (options.end) {
|
|
217
|
+
params.set("end", options.end);
|
|
218
|
+
} else {
|
|
219
|
+
// If only start, use current time as end
|
|
220
|
+
params.set("end", new Date().toISOString());
|
|
221
|
+
}
|
|
222
|
+
} else if (options.period) {
|
|
223
|
+
params.set("statsPeriod", options.period);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
params.set("per_page", Math.min(options.limit, 100).toString());
|
|
227
|
+
params.set("sort", "-timestamp");
|
|
228
|
+
|
|
229
|
+
// Add fields
|
|
230
|
+
for (const field of options.fields) {
|
|
231
|
+
params.append("field", field);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Always include project.name for context
|
|
235
|
+
if (!options.fields.includes("project.name")) {
|
|
236
|
+
params.append("field", "project.name");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Build search query
|
|
240
|
+
const queryParts = [];
|
|
241
|
+
|
|
242
|
+
if (options.project) {
|
|
243
|
+
const projectId = await resolveProjectId(options.org, options.project, token);
|
|
244
|
+
params.set("project", projectId);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (options.query) {
|
|
248
|
+
queryParts.push(options.query);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (options.transaction) {
|
|
252
|
+
queryParts.push(`transaction:${options.transaction}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (options.level) {
|
|
256
|
+
queryParts.push(`level:${options.level}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
for (const tag of options.tags) {
|
|
260
|
+
// Handle tags[key]:value format
|
|
261
|
+
if (tag.includes(":")) {
|
|
262
|
+
const [key, value] = tag.split(":", 2);
|
|
263
|
+
if (key.startsWith("tags[")) {
|
|
264
|
+
queryParts.push(`${key}:${value}`);
|
|
265
|
+
} else {
|
|
266
|
+
queryParts.push(`tags[${key}]:${value}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (queryParts.length > 0) {
|
|
272
|
+
params.set("query", queryParts.join(" "));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const url = `${SENTRY_API_BASE}/organizations/${encodeURIComponent(options.org)}/events/?${params.toString()}`;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const data = await fetchJson(url, token);
|
|
279
|
+
|
|
280
|
+
if (options.json) {
|
|
281
|
+
console.log(JSON.stringify(data, null, 2));
|
|
282
|
+
} else {
|
|
283
|
+
console.log(formatOutput(data, options.fields));
|
|
284
|
+
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error("Error:", err.message);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
main();
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { SENTRY_API_BASE, getAuthToken, fetchJson } from "../lib/auth.js";
|
|
4
|
+
|
|
5
|
+
const LOG_FIELDS = ["sentry.item_id", "trace", "sentry.severity", "timestamp", "message"];
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Parse a Sentry logs explorer URL
|
|
9
|
+
* Examples:
|
|
10
|
+
* https://earendil.sentry.io/explore/logs/?project=123&statsPeriod=14d
|
|
11
|
+
* https://sentry.io/organizations/myorg/explore/logs/?project=123
|
|
12
|
+
*/
|
|
13
|
+
function parseLogsUrl(urlStr) {
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(urlStr);
|
|
16
|
+
const params = url.searchParams;
|
|
17
|
+
const result = {};
|
|
18
|
+
|
|
19
|
+
// Extract org from subdomain (earendil.sentry.io) or path (/organizations/myorg/)
|
|
20
|
+
const subdomainMatch = url.hostname.match(/^([^.]+)\.sentry\.io$/);
|
|
21
|
+
if (subdomainMatch && subdomainMatch[1] !== "www") {
|
|
22
|
+
result.org = subdomainMatch[1];
|
|
23
|
+
} else {
|
|
24
|
+
const pathMatch = url.pathname.match(/\/organizations\/([^/]+)\//);
|
|
25
|
+
if (pathMatch) {
|
|
26
|
+
result.org = pathMatch[1];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Extract project ID
|
|
31
|
+
if (params.has("project")) {
|
|
32
|
+
result.project = params.get("project");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Extract time period
|
|
36
|
+
if (params.has("statsPeriod")) {
|
|
37
|
+
result.period = params.get("statsPeriod");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Extract query
|
|
41
|
+
if (params.has("logsQuery")) {
|
|
42
|
+
result.query = params.get("logsQuery");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseArgs(args) {
|
|
52
|
+
const options = {
|
|
53
|
+
org: null,
|
|
54
|
+
project: null,
|
|
55
|
+
query: null,
|
|
56
|
+
period: "24h",
|
|
57
|
+
limit: 100,
|
|
58
|
+
json: false,
|
|
59
|
+
help: false,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < args.length; i++) {
|
|
63
|
+
const arg = args[i];
|
|
64
|
+
|
|
65
|
+
if (arg === "--help" || arg === "-h") {
|
|
66
|
+
options.help = true;
|
|
67
|
+
} else if (arg === "--json") {
|
|
68
|
+
options.json = true;
|
|
69
|
+
} else if (arg === "--org" || arg === "-o") {
|
|
70
|
+
options.org = args[++i];
|
|
71
|
+
} else if (arg === "--project" || arg === "-p") {
|
|
72
|
+
options.project = args[++i];
|
|
73
|
+
} else if (arg === "--period" || arg === "-t") {
|
|
74
|
+
options.period = args[++i];
|
|
75
|
+
} else if (arg === "--limit" || arg === "-n") {
|
|
76
|
+
options.limit = parseInt(args[++i], 10);
|
|
77
|
+
} else if (!arg.startsWith("-")) {
|
|
78
|
+
// Check if it's a Sentry URL
|
|
79
|
+
if (arg.includes("sentry.io/") && arg.includes("/logs")) {
|
|
80
|
+
const urlOptions = parseLogsUrl(arg);
|
|
81
|
+
if (urlOptions) {
|
|
82
|
+
if (urlOptions.org) options.org = urlOptions.org;
|
|
83
|
+
if (urlOptions.project) options.project = urlOptions.project;
|
|
84
|
+
if (urlOptions.period) options.period = urlOptions.period;
|
|
85
|
+
if (urlOptions.query) options.query = urlOptions.query;
|
|
86
|
+
}
|
|
87
|
+
} else if (!options.query) {
|
|
88
|
+
options.query = arg;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return options;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function showHelp() {
|
|
97
|
+
console.log(`Usage: search-logs.js [query|url] [options]
|
|
98
|
+
|
|
99
|
+
Search for logs in Sentry.
|
|
100
|
+
|
|
101
|
+
Arguments:
|
|
102
|
+
query Search query (e.g., "level:error", "user.id:123")
|
|
103
|
+
url Sentry logs explorer URL (extracts org, project, period)
|
|
104
|
+
|
|
105
|
+
Options:
|
|
106
|
+
--org, -o <org> Organization slug (required unless URL provided)
|
|
107
|
+
--project, -p <p> Project slug or ID to filter by
|
|
108
|
+
--period, -t <p> Time period (default: 24h, e.g., 1h, 7d, 90d)
|
|
109
|
+
--limit, -n <n> Max results (default: 100, max: 1000)
|
|
110
|
+
--json Output raw JSON
|
|
111
|
+
-h, --help Show this help
|
|
112
|
+
|
|
113
|
+
Search Query Syntax:
|
|
114
|
+
level:error Filter by log level (trace, debug, info, warn, error, fatal)
|
|
115
|
+
message:*timeout* Search message text
|
|
116
|
+
trace:abc123 Filter by trace ID
|
|
117
|
+
project:my-project Filter by project slug
|
|
118
|
+
|
|
119
|
+
Combine filters: level:error message:*failed*
|
|
120
|
+
|
|
121
|
+
Examples:
|
|
122
|
+
search-logs.js --org myorg
|
|
123
|
+
search-logs.js "level:error" --org myorg --project backend
|
|
124
|
+
search-logs.js "message:*timeout*" --org myorg --period 7d
|
|
125
|
+
search-logs.js --org myorg --limit 50 --json
|
|
126
|
+
|
|
127
|
+
# Use a Sentry URL directly:
|
|
128
|
+
search-logs.js "https://myorg.sentry.io/explore/logs/?project=123&statsPeriod=7d"
|
|
129
|
+
`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatLogEntry(entry) {
|
|
133
|
+
const lines = [];
|
|
134
|
+
|
|
135
|
+
const ts = entry.timestamp || "N/A";
|
|
136
|
+
const severity = entry["sentry.severity"] || "info";
|
|
137
|
+
const message = entry.message || "(no message)";
|
|
138
|
+
const trace = entry.trace || null;
|
|
139
|
+
|
|
140
|
+
// Format timestamp for display
|
|
141
|
+
let displayTs = ts;
|
|
142
|
+
try {
|
|
143
|
+
const date = new Date(ts);
|
|
144
|
+
if (!isNaN(date.getTime())) {
|
|
145
|
+
displayTs = date.toISOString().replace("T", " ").slice(0, 19);
|
|
146
|
+
}
|
|
147
|
+
} catch {}
|
|
148
|
+
|
|
149
|
+
// Color-code severity in output
|
|
150
|
+
const severityDisplay = `[${severity.toUpperCase().padEnd(5)}]`;
|
|
151
|
+
|
|
152
|
+
lines.push(`${displayTs} ${severityDisplay} ${message}`);
|
|
153
|
+
|
|
154
|
+
if (trace) {
|
|
155
|
+
lines.push(` trace: ${trace}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return lines.join("\n");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function formatOutput(data) {
|
|
162
|
+
if (!data.data || data.data.length === 0) {
|
|
163
|
+
return "No logs found matching your query.";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const lines = [];
|
|
167
|
+
lines.push(`Found ${data.data.length} log entries:\n`);
|
|
168
|
+
|
|
169
|
+
for (const entry of data.data) {
|
|
170
|
+
lines.push(formatLogEntry(entry));
|
|
171
|
+
lines.push("");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return lines.join("\n").trimEnd();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function main() {
|
|
178
|
+
const args = process.argv.slice(2);
|
|
179
|
+
const options = parseArgs(args);
|
|
180
|
+
|
|
181
|
+
if (options.help) {
|
|
182
|
+
showHelp();
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!options.org) {
|
|
187
|
+
console.error("Error: --org is required");
|
|
188
|
+
console.error("Run with --help for usage information");
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const token = getAuthToken();
|
|
193
|
+
|
|
194
|
+
// Build query parameters
|
|
195
|
+
const params = new URLSearchParams();
|
|
196
|
+
params.set("dataset", "logs");
|
|
197
|
+
params.set("statsPeriod", options.period);
|
|
198
|
+
params.set("per_page", Math.min(options.limit, 1000).toString());
|
|
199
|
+
params.set("sort", "-timestamp");
|
|
200
|
+
|
|
201
|
+
// Add fields
|
|
202
|
+
for (const field of LOG_FIELDS) {
|
|
203
|
+
params.append("field", field);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Build search query
|
|
207
|
+
const queryParts = [];
|
|
208
|
+
if (options.project) {
|
|
209
|
+
queryParts.push(`project:${options.project}`);
|
|
210
|
+
}
|
|
211
|
+
if (options.query) {
|
|
212
|
+
queryParts.push(options.query);
|
|
213
|
+
}
|
|
214
|
+
if (queryParts.length > 0) {
|
|
215
|
+
params.set("query", queryParts.join(" "));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const url = `${SENTRY_API_BASE}/organizations/${encodeURIComponent(options.org)}/events/?${params.toString()}`;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const data = await fetchJson(url, token);
|
|
222
|
+
|
|
223
|
+
if (options.json) {
|
|
224
|
+
console.log(JSON.stringify(data, null, 2));
|
|
225
|
+
} else {
|
|
226
|
+
console.log(formatOutput(data));
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
console.error("Error:", err.message);
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
main();
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update-changelog
|
|
3
|
+
description: "Update CHANGELOG.md following Keep a Changelog (https://keepachangelog.com/en/1.1.0/)"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Update Changelog (Keep a Changelog)
|
|
7
|
+
|
|
8
|
+
Update the repository changelog with user-facing changes that landed since the last release.
|
|
9
|
+
|
|
10
|
+
This skill is **explicitly based on** Keep a Changelog v1.1.0:
|
|
11
|
+
https://keepachangelog.com/en/1.1.0/
|
|
12
|
+
|
|
13
|
+
## Rules (non-negotiable)
|
|
14
|
+
|
|
15
|
+
- **Do not add installation instructions** to the changelog.
|
|
16
|
+
- Only include **notable, user-visible** changes.
|
|
17
|
+
- **Never add raw commit SHAs**. Prefer PR numbers (e.g. `#123`) and/or issue IDs.
|
|
18
|
+
- Add entries **only under `Unreleased`** (unless you are also cutting a release and moving items into a versioned section).
|
|
19
|
+
- Preserve the project’s existing formatting where possible, but align new content to Keep a Changelog.
|
|
20
|
+
|
|
21
|
+
## File to edit
|
|
22
|
+
|
|
23
|
+
- Prefer `CHANGELOG.md`.
|
|
24
|
+
- If missing, use `CHANGELOG`.
|
|
25
|
+
|
|
26
|
+
## Step-by-step
|
|
27
|
+
|
|
28
|
+
### 1) Identify the baseline (last released version)
|
|
29
|
+
|
|
30
|
+
Pick a baseline tag/version to compare against.
|
|
31
|
+
|
|
32
|
+
- If the project uses git tags:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git describe --tags --abbrev=0
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- If tags are missing/inconsistent, use the newest release section in the changelog as the baseline.
|
|
39
|
+
|
|
40
|
+
### 2) Collect candidate changes
|
|
41
|
+
|
|
42
|
+
Gather commits/PRs since the baseline and identify user-facing changes.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git log <baseline>..HEAD --oneline
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If you have PR metadata available (e.g., via GitHub), use it to improve wording and include PR numbers.
|
|
49
|
+
|
|
50
|
+
### 3) Ensure the changelog structure matches Keep a Changelog
|
|
51
|
+
|
|
52
|
+
At minimum, Keep a Changelog expects:
|
|
53
|
+
|
|
54
|
+
- A top `Unreleased` section
|
|
55
|
+
- Optional subsections under `Unreleased` (and under each release):
|
|
56
|
+
- `Added`, `Changed`, `Deprecated`, `Removed`, `Fixed`, `Security`
|
|
57
|
+
|
|
58
|
+
If `Unreleased` exists but is missing subsections, create only the ones you need for the new entries.
|
|
59
|
+
|
|
60
|
+
### 4) Add entries under `Unreleased`
|
|
61
|
+
|
|
62
|
+
Classify each change into one of the standard headings:
|
|
63
|
+
|
|
64
|
+
- **Added**: new features
|
|
65
|
+
- **Changed**: changes in existing functionality (including behavior changes)
|
|
66
|
+
- **Deprecated**: soon-to-be removed features
|
|
67
|
+
- **Removed**: removed features
|
|
68
|
+
- **Fixed**: bug fixes
|
|
69
|
+
- **Security**: vulnerability fixes
|
|
70
|
+
|
|
71
|
+
Write entries as **consistent bullet points**:
|
|
72
|
+
|
|
73
|
+
- Start with the category verb (Added/Changed/Deprecated/Removed/Fixed/Security) _only via the section heading_, not inside each bullet.
|
|
74
|
+
- Each bullet should be a single, past-tense sentence fragment.
|
|
75
|
+
- Prefer: `- Added … (#123)` / `- Fixed … (#456)` style (consistent grammar).
|
|
76
|
+
|
|
77
|
+
### 5) Keep it user-facing
|
|
78
|
+
|
|
79
|
+
Include:
|
|
80
|
+
|
|
81
|
+
- visible behavior changes
|
|
82
|
+
- new CLI flags/API additions
|
|
83
|
+
- bug fixes with clear impact
|
|
84
|
+
- security fixes (without leaking sensitive details)
|
|
85
|
+
|
|
86
|
+
Exclude (unless they change user-visible behavior):
|
|
87
|
+
|
|
88
|
+
- pure refactors
|
|
89
|
+
- internal cleanup
|
|
90
|
+
- dependency bumps with no user impact
|
|
91
|
+
- typo-only doc edits
|
|
92
|
+
|
|
93
|
+
### 6) Links (only if the file already uses them)
|
|
94
|
+
|
|
95
|
+
Keep a Changelog commonly includes link references at the bottom, e.g.:
|
|
96
|
+
|
|
97
|
+
- `[Unreleased]: <compare link>`
|
|
98
|
+
- `[1.2.3]: <compare link>`
|
|
99
|
+
|
|
100
|
+
If the project already uses these, update them accordingly (don’t introduce link refs if the changelog doesn’t use them).
|
|
101
|
+
|
|
102
|
+
## Example (consistent Keep a Changelog format)
|
|
103
|
+
|
|
104
|
+
```markdown
|
|
105
|
+
## [Unreleased]
|
|
106
|
+
|
|
107
|
+
### Added
|
|
108
|
+
|
|
109
|
+
- Added widget-level caching for faster dashboard loads. (#123)
|
|
110
|
+
|
|
111
|
+
### Changed
|
|
112
|
+
|
|
113
|
+
- Changed default retry policy to exponential backoff. (#140)
|
|
114
|
+
|
|
115
|
+
### Fixed
|
|
116
|
+
|
|
117
|
+
- Fixed crash when importing a config with empty sections. (#155)
|
|
118
|
+
|
|
119
|
+
## [1.4.0] - 2026-02-01
|
|
120
|
+
|
|
121
|
+
### Added
|
|
122
|
+
|
|
123
|
+
- Added support for exporting reports as CSV. (#110)
|
|
124
|
+
|
|
125
|
+
### Fixed
|
|
126
|
+
|
|
127
|
+
- Fixed incorrect timezone handling in scheduled jobs. (#117)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Quality checklist
|
|
131
|
+
|
|
132
|
+
- Entries are under **`Unreleased`** and categorized correctly.
|
|
133
|
+
- Wording is consistent (same tense/style across bullets).
|
|
134
|
+
- No installation instructions, no commit SHAs.
|
|
135
|
+
- The changelog remains easy to scan and matches the repo’s established conventions.
|