mitsupi 1.0.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.
Files changed (77) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +95 -0
  3. package/TODO.md +11 -0
  4. package/commands/handoff.md +100 -0
  5. package/commands/make-release.md +75 -0
  6. package/commands/pickup.md +30 -0
  7. package/commands/update-changelog.md +78 -0
  8. package/package.json +22 -0
  9. package/pi-extensions/answer.ts +527 -0
  10. package/pi-extensions/codex-tuning.ts +632 -0
  11. package/pi-extensions/commit.ts +248 -0
  12. package/pi-extensions/cwd-history.ts +237 -0
  13. package/pi-extensions/issues.ts +548 -0
  14. package/pi-extensions/loop.ts +446 -0
  15. package/pi-extensions/qna.ts +167 -0
  16. package/pi-extensions/reveal.ts +689 -0
  17. package/pi-extensions/review.ts +807 -0
  18. package/pi-themes/armin.json +81 -0
  19. package/pi-themes/nightowl.json +82 -0
  20. package/skills/anachb/SKILL.md +183 -0
  21. package/skills/anachb/departures.sh +79 -0
  22. package/skills/anachb/disruptions.sh +53 -0
  23. package/skills/anachb/route.sh +87 -0
  24. package/skills/anachb/search.sh +43 -0
  25. package/skills/ghidra/SKILL.md +254 -0
  26. package/skills/ghidra/scripts/find-ghidra.sh +54 -0
  27. package/skills/ghidra/scripts/ghidra-analyze.sh +239 -0
  28. package/skills/ghidra/scripts/ghidra_scripts/ExportAll.java +278 -0
  29. package/skills/ghidra/scripts/ghidra_scripts/ExportCalls.java +148 -0
  30. package/skills/ghidra/scripts/ghidra_scripts/ExportDecompiled.java +84 -0
  31. package/skills/ghidra/scripts/ghidra_scripts/ExportFunctions.java +114 -0
  32. package/skills/ghidra/scripts/ghidra_scripts/ExportStrings.java +123 -0
  33. package/skills/ghidra/scripts/ghidra_scripts/ExportSymbols.java +135 -0
  34. package/skills/github/SKILL.md +47 -0
  35. package/skills/improve-skill/SKILL.md +155 -0
  36. package/skills/improve-skill/scripts/extract-session.js +349 -0
  37. package/skills/oebb-scotty/SKILL.md +429 -0
  38. package/skills/oebb-scotty/arrivals.sh +83 -0
  39. package/skills/oebb-scotty/departures.sh +83 -0
  40. package/skills/oebb-scotty/disruptions.sh +33 -0
  41. package/skills/oebb-scotty/search-station.sh +36 -0
  42. package/skills/oebb-scotty/trip.sh +119 -0
  43. package/skills/openscad/SKILL.md +232 -0
  44. package/skills/openscad/examples/parametric_box.scad +92 -0
  45. package/skills/openscad/examples/phone_stand.scad +95 -0
  46. package/skills/openscad/tools/common.sh +50 -0
  47. package/skills/openscad/tools/export-stl.sh +56 -0
  48. package/skills/openscad/tools/extract-params.sh +147 -0
  49. package/skills/openscad/tools/multi-preview.sh +68 -0
  50. package/skills/openscad/tools/preview.sh +74 -0
  51. package/skills/openscad/tools/render-with-params.sh +91 -0
  52. package/skills/openscad/tools/validate.sh +46 -0
  53. package/skills/pi-share/SKILL.md +105 -0
  54. package/skills/pi-share/fetch-session.mjs +322 -0
  55. package/skills/sentry/SKILL.md +239 -0
  56. package/skills/sentry/lib/auth.js +99 -0
  57. package/skills/sentry/scripts/fetch-event.js +329 -0
  58. package/skills/sentry/scripts/fetch-issue.js +356 -0
  59. package/skills/sentry/scripts/list-issues.js +239 -0
  60. package/skills/sentry/scripts/search-events.js +291 -0
  61. package/skills/sentry/scripts/search-logs.js +240 -0
  62. package/skills/tmux/SKILL.md +105 -0
  63. package/skills/tmux/scripts/find-sessions.sh +112 -0
  64. package/skills/tmux/scripts/wait-for-text.sh +83 -0
  65. package/skills/web-browser/SKILL.md +91 -0
  66. package/skills/web-browser/scripts/cdp.js +210 -0
  67. package/skills/web-browser/scripts/dismiss-cookies.js +373 -0
  68. package/skills/web-browser/scripts/eval.js +68 -0
  69. package/skills/web-browser/scripts/logs-tail.js +69 -0
  70. package/skills/web-browser/scripts/nav.js +65 -0
  71. package/skills/web-browser/scripts/net-summary.js +94 -0
  72. package/skills/web-browser/scripts/package-lock.json +33 -0
  73. package/skills/web-browser/scripts/package.json +6 -0
  74. package/skills/web-browser/scripts/pick.js +165 -0
  75. package/skills/web-browser/scripts/screenshot.js +52 -0
  76. package/skills/web-browser/scripts/start.js +80 -0
  77. package/skills/web-browser/scripts/watch.js +266 -0
@@ -0,0 +1,322 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Fetch and parse pi-share (shittycodingagent.ai/buildwithpi.ai/buildwithpi.com) session exports.
4
+ *
5
+ * Usage:
6
+ * node fetch-session.mjs <url-or-gist-id> [--header] [--entries] [--system] [--tools] [--human-summary] [--no-cache]
7
+ *
8
+ * Options:
9
+ * (no flag) Output full session data JSON
10
+ * --header Output just the session header
11
+ * --entries Output entries as JSON lines (one per line)
12
+ * --system Output the system prompt
13
+ * --tools Output tool definitions
14
+ * --human-summary Summarize what the human did in this session (uses haiku-4-5)
15
+ * --no-cache Bypass cache and fetch fresh
16
+ */
17
+
18
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'fs';
19
+ import { tmpdir } from 'os';
20
+ import { join } from 'path';
21
+ import { spawnSync } from 'child_process';
22
+
23
+ const CACHE_DIR = join(tmpdir(), 'pi-share-cache');
24
+
25
+ const args = process.argv.slice(2);
26
+ const input = args.find(a => !a.startsWith('--'));
27
+ const flags = new Set(args.filter(a => a.startsWith('--')));
28
+
29
+ if (!input) {
30
+ console.error('Usage: node fetch-session.mjs <url-or-gist-id> [--header|--entries|--system|--tools]');
31
+ process.exit(1);
32
+ }
33
+
34
+ // Cache functions
35
+ function getCachePath(gistId) {
36
+ return join(CACHE_DIR, `${gistId}.json`);
37
+ }
38
+
39
+ function readCache(gistId) {
40
+ const path = getCachePath(gistId);
41
+ if (existsSync(path)) {
42
+ return JSON.parse(readFileSync(path, 'utf-8'));
43
+ }
44
+ return null;
45
+ }
46
+
47
+ function writeCache(gistId, data) {
48
+ mkdirSync(CACHE_DIR, { recursive: true });
49
+ writeFileSync(getCachePath(gistId), JSON.stringify(data));
50
+ }
51
+
52
+ // Extract gist ID from URL or use directly
53
+ function extractGistId(input) {
54
+ // Handle full URLs like https://shittycodingagent.ai/session/?<id>
55
+ const queryMatch = input.match(/[?&]([a-f0-9]{32})/i);
56
+ if (queryMatch) return queryMatch[1];
57
+
58
+ // Handle path-based URLs like https://buildwithpi.ai/session/<id>
59
+ const pathMatch = input.match(/\/session\/?([a-f0-9]{32})/i);
60
+ if (pathMatch) return pathMatch[1];
61
+
62
+ // Handle direct gist ID
63
+ if (/^[a-f0-9]{32}$/i.test(input)) return input;
64
+
65
+ // Handle gist.github.com URLs
66
+ const gistMatch = input.match(/gist\.github\.com\/[^/]+\/([a-f0-9]+)/i);
67
+ if (gistMatch) return gistMatch[1];
68
+
69
+ throw new Error(`Cannot extract gist ID from: ${input}`);
70
+ }
71
+
72
+ // Fetch session HTML from gist
73
+ async function fetchSessionHtml(gistId) {
74
+ const gistRes = await fetch(`https://api.github.com/gists/${gistId}`);
75
+ if (!gistRes.ok) {
76
+ if (gistRes.status === 404) throw new Error('Session not found (gist deleted or invalid ID)');
77
+ throw new Error(`GitHub API error: ${gistRes.status}`);
78
+ }
79
+
80
+ const gist = await gistRes.json();
81
+ const file = gist.files?.['session.html'];
82
+ if (!file) {
83
+ const available = Object.keys(gist.files || {}).join(', ') || 'none';
84
+ throw new Error(`No session.html in gist. Available: ${available}`);
85
+ }
86
+
87
+ // Fetch raw content if truncated
88
+ if (file.truncated && file.raw_url) {
89
+ const rawRes = await fetch(file.raw_url);
90
+ if (!rawRes.ok) throw new Error('Failed to fetch raw content');
91
+ return rawRes.text();
92
+ }
93
+
94
+ return file.content;
95
+ }
96
+
97
+ // Extract base64 session data from HTML
98
+ function extractSessionData(html) {
99
+ // New format: <script id="session-data" type="application/json">BASE64</script>
100
+ const match = html.match(/<script[^>]*id="session-data"[^>]*>([^<]+)<\/script>/);
101
+ if (match) {
102
+ const base64 = match[1].trim();
103
+ const json = Buffer.from(base64, 'base64').toString('utf-8');
104
+ return JSON.parse(json);
105
+ }
106
+
107
+ throw new Error('No session data found in HTML. This may be an older export format without embedded data.');
108
+ }
109
+
110
+ // Truncate text to maxLen, adding ellipsis if truncated
111
+ function truncate(text, maxLen = 150) {
112
+ if (!text || text.length <= maxLen) return text;
113
+ return text.slice(0, maxLen) + '...';
114
+ }
115
+
116
+ // Extract condensed session data for human summary
117
+ function extractForSummary(data) {
118
+ const turns = [];
119
+ let turnNumber = 0;
120
+
121
+ for (const entry of data.entries) {
122
+ if (entry.type !== 'message') continue;
123
+
124
+ const msg = entry.message;
125
+ if (!msg || !msg.role) continue;
126
+
127
+ if (msg.role === 'user') {
128
+ turnNumber++;
129
+ // Extract user text
130
+ const textParts = (msg.content || [])
131
+ .filter(c => c.type === 'text')
132
+ .map(c => c.text)
133
+ .join('\n');
134
+
135
+ if (textParts.trim()) {
136
+ turns.push({
137
+ turn: turnNumber,
138
+ role: 'human',
139
+ text: textParts
140
+ });
141
+ }
142
+ } else if (msg.role === 'assistant') {
143
+ // Extract condensed assistant info: brief text + tool summary
144
+ const textParts = [];
145
+ const toolCalls = [];
146
+
147
+ for (const block of (msg.content || [])) {
148
+ if (block.type === 'text' && block.text) {
149
+ // Just first 200 chars of assistant text for context
150
+ textParts.push(truncate(block.text, 200));
151
+ } else if (block.type === 'toolCall') {
152
+ // Condense tool call: name + truncated key info
153
+ let summary = block.toolName;
154
+ if (block.args) {
155
+ if (block.args.path) {
156
+ summary += `: ${truncate(block.args.path, 100)}`;
157
+ } else if (block.args.command) {
158
+ summary += `: ${truncate(block.args.command, 100)}`;
159
+ } else {
160
+ // Generic args truncation
161
+ const argsStr = JSON.stringify(block.args);
162
+ summary += `: ${truncate(argsStr, 100)}`;
163
+ }
164
+ }
165
+ toolCalls.push(summary);
166
+ }
167
+ }
168
+
169
+ if (textParts.length || toolCalls.length) {
170
+ turns.push({
171
+ turn: turnNumber,
172
+ role: 'assistant',
173
+ text: textParts.length ? textParts[0] : null,
174
+ tools: toolCalls.length ? toolCalls : null
175
+ });
176
+ }
177
+ } else if (msg.role === 'toolResult') {
178
+ // Just note if there was an error
179
+ const hasError = (msg.content || []).some(c => c.isError);
180
+ if (hasError) {
181
+ turns.push({
182
+ turn: turnNumber,
183
+ role: 'tool_error',
184
+ text: 'Tool returned an error'
185
+ });
186
+ }
187
+ }
188
+ }
189
+
190
+ return {
191
+ sessionId: data.header?.id,
192
+ timestamp: data.header?.timestamp,
193
+ cwd: data.header?.cwd,
194
+ turns
195
+ };
196
+ }
197
+
198
+ // Format condensed data as text for the summarizer
199
+ function formatForSummary(condensed) {
200
+ const lines = [];
201
+
202
+ lines.push(`Session: ${condensed.sessionId || 'unknown'}`);
203
+ lines.push(`Time: ${condensed.timestamp || 'unknown'}`);
204
+ lines.push(`Directory: ${condensed.cwd || 'unknown'}`);
205
+ lines.push('');
206
+ lines.push('=== Conversation ===');
207
+ lines.push('');
208
+
209
+ for (const turn of condensed.turns) {
210
+ if (turn.role === 'human') {
211
+ lines.push(`[Turn ${turn.turn}] HUMAN:`);
212
+ lines.push(turn.text);
213
+ lines.push('');
214
+ } else if (turn.role === 'assistant') {
215
+ lines.push(`[Turn ${turn.turn}] ASSISTANT (condensed):`);
216
+ if (turn.text) {
217
+ lines.push(` Response: ${turn.text}`);
218
+ }
219
+ if (turn.tools && turn.tools.length) {
220
+ lines.push(` Tools used: ${turn.tools.join(', ')}`);
221
+ }
222
+ lines.push('');
223
+ } else if (turn.role === 'tool_error') {
224
+ lines.push(`[Turn ${turn.turn}] ⚠️ Tool error occurred`);
225
+ lines.push('');
226
+ }
227
+ }
228
+
229
+ return lines.join('\n');
230
+ }
231
+
232
+ // Generate human summary using haiku via pi
233
+ async function generateHumanSummary(data) {
234
+ const condensed = extractForSummary(data);
235
+ const formatted = formatForSummary(condensed);
236
+
237
+ const prompt = `You are analyzing a coding agent session transcript. Your task is to summarize what the HUMAN did, not what the AI agent did.
238
+
239
+ Focus on:
240
+ 1. What was the human's initial goal/request?
241
+ 2. How many times did they have to re-prompt or steer the agent?
242
+ 3. What kind of steering did they do? (corrections, clarifications, changes of direction, expressing frustration, etc.)
243
+ 4. Did the human have to intervene when things went wrong?
244
+ 5. How specific vs vague were their instructions?
245
+
246
+ Write a ~300 word summary in third person ("The user asked...", "They then had to clarify...").
247
+ Include a brief note about what domain/tools were involved for context, but keep focus on the human's actions and experience.
248
+
249
+ Here is the condensed session transcript:
250
+
251
+ ${formatted}`;
252
+
253
+ try {
254
+ const result = spawnSync('pi', [
255
+ '--provider', 'anthropic',
256
+ '--model', 'claude-haiku-4-5',
257
+ '--no-tools',
258
+ '--no-session',
259
+ '-p',
260
+ prompt
261
+ ], {
262
+ encoding: 'utf-8',
263
+ maxBuffer: 10 * 1024 * 1024,
264
+ timeout: 60000
265
+ });
266
+
267
+ if (result.error) {
268
+ throw result.error;
269
+ }
270
+ if (result.status !== 0) {
271
+ throw new Error(result.stderr || 'pi command failed');
272
+ }
273
+
274
+ return result.stdout.trim();
275
+ } catch (err) {
276
+ throw new Error(`Failed to generate summary: ${err.message}`);
277
+ }
278
+ }
279
+
280
+ // Main
281
+ async function main() {
282
+ try {
283
+ const gistId = extractGistId(input);
284
+
285
+ // Check cache first (unless --no-cache)
286
+ let data = null;
287
+ if (!flags.has('--no-cache')) {
288
+ data = readCache(gistId);
289
+ }
290
+
291
+ if (!data) {
292
+ const html = await fetchSessionHtml(gistId);
293
+ data = extractSessionData(html);
294
+ writeCache(gistId, data);
295
+ }
296
+
297
+ if (flags.has('--header')) {
298
+ console.log(JSON.stringify(data.header));
299
+ } else if (flags.has('--entries')) {
300
+ // Output as JSON lines - one entry per line
301
+ for (const entry of data.entries) {
302
+ console.log(JSON.stringify(entry));
303
+ }
304
+ } else if (flags.has('--system')) {
305
+ console.log(data.systemPrompt || '');
306
+ } else if (flags.has('--tools')) {
307
+ console.log(JSON.stringify(data.tools || []));
308
+ } else if (flags.has('--human-summary')) {
309
+ // Generate human-centric summary using haiku
310
+ const summary = await generateHumanSummary(data);
311
+ console.log(summary);
312
+ } else {
313
+ // Default: full session data
314
+ console.log(JSON.stringify(data));
315
+ }
316
+ } catch (err) {
317
+ console.error(err.message);
318
+ process.exit(1);
319
+ }
320
+ }
321
+
322
+ main();
@@ -0,0 +1,239 @@
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 Skill
7
+
8
+ Access Sentry data via the API for debugging and investigation. Uses auth token from `~/.sentryclirc`.
9
+
10
+ ## Quick Reference
11
+
12
+ | Task | Command |
13
+ |------|---------|
14
+ | Find errors on a date | `search-events.js --org X --start 2025-12-23T15:00:00 --level error` |
15
+ | List open issues | `list-issues.js --org X --status unresolved` |
16
+ | Get issue details | `fetch-issue.js <issue-id-or-url> --latest` |
17
+ | Get event details | `fetch-event.js <event-id> --org X --project Y` |
18
+ | Search logs | `search-logs.js --org X --project Y "level:error"` |
19
+
20
+ ## Common Debugging Workflows
21
+
22
+ ### "What went wrong at this time?"
23
+
24
+ Find events around a specific timestamp:
25
+
26
+ ```bash
27
+ # Find all events in a 2-hour window
28
+ ./scripts/search-events.js --org myorg --project backend \
29
+ --start 2025-12-23T15:00:00 --end 2025-12-23T17:00:00
30
+
31
+ # Filter to just errors
32
+ ./scripts/search-events.js --org myorg --start 2025-12-23T15:00:00 \
33
+ --level error
34
+
35
+ # Find a specific transaction type
36
+ ./scripts/search-events.js --org myorg --start 2025-12-23T15:00:00 \
37
+ --transaction process-incoming-email
38
+ ```
39
+
40
+ ### "What errors have occurred recently?"
41
+
42
+ ```bash
43
+ # List unresolved errors from last 24 hours
44
+ ./scripts/list-issues.js --org myorg --status unresolved --level error --period 24h
45
+
46
+ # Find high-frequency issues
47
+ ./scripts/list-issues.js --org myorg --query "times_seen:>50" --sort freq
48
+
49
+ # Issues affecting users
50
+ ./scripts/list-issues.js --org myorg --query "is:unresolved has:user" --sort user
51
+ ```
52
+
53
+ ### "Get details about a specific issue/event"
54
+
55
+ ```bash
56
+ # Get issue with latest stack trace
57
+ ./scripts/fetch-issue.js 5765604106 --latest
58
+ ./scripts/fetch-issue.js https://sentry.io/organizations/myorg/issues/123/ --latest
59
+ ./scripts/fetch-issue.js MYPROJ-123 --org myorg --latest
60
+
61
+ # Get specific event with all breadcrumbs
62
+ ./scripts/fetch-event.js abc123def456 --org myorg --project backend --breadcrumbs
63
+ ```
64
+
65
+ ### "Find events with a specific tag"
66
+
67
+ ```bash
68
+ # Find by custom tag (e.g., thread_id, user_id)
69
+ ./scripts/search-events.js --org myorg --tag thread_id:th_abc123
70
+
71
+ # Find by user email
72
+ ./scripts/search-events.js --org myorg --query "user.email:*@example.com"
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Fetch Issue
78
+
79
+ ```bash
80
+ ./scripts/fetch-issue.js <issue-id-or-url> [options]
81
+ ```
82
+
83
+ Get details about a specific issue (grouped error).
84
+
85
+ **Accepts:**
86
+ - Issue ID: `5765604106`
87
+ - Issue URL: `https://sentry.io/organizations/sentry/issues/5765604106/`
88
+ - New URL format: `https://myorg.sentry.io/issues/5765604106/`
89
+ - Short ID: `JAVASCRIPT-ABC` (requires `--org` flag)
90
+
91
+ **Options:**
92
+ - `--latest` - Include the latest event with full stack trace
93
+ - `--org <org>` - Organization slug (for short IDs)
94
+ - `--json` - Output raw JSON
95
+
96
+ **Output includes:**
97
+ - Title, culprit, status, level
98
+ - First/last seen timestamps
99
+ - Event count and user impact
100
+ - Tags and environment info
101
+ - With `--latest`: stack trace, request details, breadcrumbs, runtime context
102
+
103
+ ---
104
+
105
+ ## Fetch Event
106
+
107
+ ```bash
108
+ ./scripts/fetch-event.js <event-id> --org <org> --project <project> [options]
109
+ ```
110
+
111
+ Get full details of a specific event by its ID.
112
+
113
+ **Options:**
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
+ **Output includes:**
121
+ - Timestamp, project, title, message
122
+ - All tags
123
+ - Context (runtime, browser, OS, trace info)
124
+ - Request details
125
+ - Exception with stack trace
126
+ - Breadcrumbs
127
+ - Spans (with `--spans`)
128
+
129
+ ---
130
+
131
+ ## Search Events
132
+
133
+ ```bash
134
+ ./scripts/search-events.js [options]
135
+ ```
136
+
137
+ Search for events (transactions, errors) using Sentry Discover.
138
+
139
+ **Time Range Options:**
140
+ - `--period, -t <period>` - Relative time (24h, 7d, 14d)
141
+ - `--start <datetime>` - Start time (ISO 8601: 2025-12-23T15:00:00)
142
+ - `--end <datetime>` - End time (ISO 8601)
143
+
144
+ **Filter Options:**
145
+ - `--org, -o <org>` - Organization slug (required)
146
+ - `--project, -p <project>` - Project slug or ID
147
+ - `--query, -q <query>` - Discover search query
148
+ - `--transaction <name>` - Transaction name filter
149
+ - `--tag <key:value>` - Tag filter (repeatable)
150
+ - `--level <level>` - Level filter (error, warning, info)
151
+ - `--limit, -n <n>` - Max results (default: 25, max: 100)
152
+ - `--fields <fields>` - Comma-separated fields to include
153
+
154
+ **Query Syntax:**
155
+ ```
156
+ transaction:process-* Wildcard transaction match
157
+ level:error Filter by level
158
+ user.email:foo@bar.com Filter by user
159
+ environment:production Filter by environment
160
+ has:stack.filename Has stack trace
161
+ ```
162
+
163
+ ---
164
+
165
+ ## List Issues
166
+
167
+ ```bash
168
+ ./scripts/list-issues.js [options]
169
+ ```
170
+
171
+ List and search issues (grouped errors) in a project.
172
+
173
+ **Options:**
174
+ - `--org, -o <org>` - Organization slug (required)
175
+ - `--project, -p <project>` - Project slug (repeatable)
176
+ - `--query, -q <query>` - Issue search query
177
+ - `--status <status>` - unresolved, resolved, ignored
178
+ - `--level <level>` - error, warning, info, fatal
179
+ - `--period, -t <period>` - Time period (default: 14d)
180
+ - `--limit, -n <n>` - Max results (default: 25)
181
+ - `--sort <sort>` - date, new, priority, freq, user
182
+ - `--json` - Output raw JSON
183
+
184
+ **Query Syntax:**
185
+ ```
186
+ is:unresolved Status filter
187
+ is:assigned Has assignee
188
+ assigned:me Assigned to current user
189
+ level:error Level filter
190
+ firstSeen:+7d First seen > 7 days ago
191
+ lastSeen:-24h Last seen within 24h
192
+ times_seen:>100 Event count filter
193
+ has:user Has user context
194
+ error.handled:0 Unhandled errors only
195
+ ```
196
+
197
+ ---
198
+
199
+ ## Search Logs
200
+
201
+ ```bash
202
+ ./scripts/search-logs.js [query|url] [options]
203
+ ```
204
+
205
+ Search for logs in Sentry's Logs Explorer.
206
+
207
+ **Options:**
208
+ - `--org, -o <org>` - Organization slug (required unless URL provided)
209
+ - `--project, -p <project>` - Filter by project slug or ID
210
+ - `--period, -t <period>` - Time period (default: 24h)
211
+ - `--limit, -n <n>` - Max results (default: 100, max: 1000)
212
+ - `--json` - Output raw JSON
213
+
214
+ **Query Syntax:**
215
+ ```
216
+ level:error Filter by level (trace, debug, info, warn, error, fatal)
217
+ message:*timeout* Search message text with wildcards
218
+ trace:abc123 Filter by trace ID
219
+ project:my-project Filter by project slug
220
+ ```
221
+
222
+ **Accepts Sentry URLs:**
223
+ ```bash
224
+ ./scripts/search-logs.js "https://myorg.sentry.io/explore/logs/?project=123&statsPeriod=7d"
225
+ ```
226
+
227
+ ---
228
+
229
+ ## Tips for Debugging
230
+
231
+ 1. **Start broad, then narrow down**: Use `search-events.js` with a time range first, then drill into specific events
232
+
233
+ 2. **Use breadcrumbs**: The `--breadcrumbs` flag on `fetch-event.js` shows the full history of what happened before an error
234
+
235
+ 3. **Look for patterns**: Use `list-issues.js --sort freq` to find frequently occurring problems
236
+
237
+ 4. **Check related events**: If you find one event, look for others with the same transaction name or trace ID
238
+
239
+ 5. **Tags are your friend**: Custom tags like `thread_id`, `user_id`, `request_id` help correlate events
@@ -0,0 +1,99 @@
1
+ import { readFileSync, existsSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+
5
+ export const SENTRY_API_BASE = "https://sentry.io/api/0";
6
+
7
+ // Cache for project slug -> ID resolution
8
+ const projectIdCache = new Map();
9
+
10
+ /**
11
+ * Get auth token from ~/.sentryclirc
12
+ * @returns {string} The auth token
13
+ */
14
+ export function getAuthToken() {
15
+ const rcPath = join(homedir(), ".sentryclirc");
16
+ if (!existsSync(rcPath)) {
17
+ console.error("Error: ~/.sentryclirc not found");
18
+ console.error("Run 'sentry-cli login' to authenticate");
19
+ process.exit(1);
20
+ }
21
+
22
+ const content = readFileSync(rcPath, "utf-8");
23
+ const match = content.match(/token\s*=\s*(.+)/);
24
+ if (!match) {
25
+ console.error("Error: No token found in ~/.sentryclirc");
26
+ process.exit(1);
27
+ }
28
+ return match[1].trim();
29
+ }
30
+
31
+ /**
32
+ * Fetch JSON from a Sentry API endpoint
33
+ * @param {string} url - The full URL to fetch
34
+ * @param {string} token - The auth token
35
+ * @returns {Promise<any>} The parsed JSON response
36
+ */
37
+ export async function fetchJson(url, token) {
38
+ const res = await fetch(url, {
39
+ headers: {
40
+ Authorization: `Bearer ${token}`,
41
+ },
42
+ });
43
+
44
+ if (!res.ok) {
45
+ const text = await res.text();
46
+ throw new Error(`API error ${res.status}: ${text}`);
47
+ }
48
+
49
+ return res.json();
50
+ }
51
+
52
+ /**
53
+ * Format a timestamp for display
54
+ * @param {string|number} ts - Timestamp (ISO string or unix)
55
+ * @returns {string} Formatted timestamp
56
+ */
57
+ export function formatTimestamp(ts) {
58
+ if (!ts) return "N/A";
59
+ try {
60
+ const date = new Date(ts);
61
+ if (isNaN(date.getTime())) return ts;
62
+ return date.toLocaleString();
63
+ } catch {
64
+ return ts;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Resolve a project slug to its numeric ID.
70
+ * If the input is already a numeric ID, returns it as-is.
71
+ * @param {string} org - Organization slug
72
+ * @param {string} project - Project slug or numeric ID
73
+ * @param {string} token - Auth token
74
+ * @returns {Promise<string>} The numeric project ID
75
+ */
76
+ export async function resolveProjectId(org, project, token) {
77
+ // If already numeric, return as-is
78
+ if (/^\d+$/.test(project)) {
79
+ return project;
80
+ }
81
+
82
+ // Check cache
83
+ const cacheKey = `${org}/${project}`;
84
+ if (projectIdCache.has(cacheKey)) {
85
+ return projectIdCache.get(cacheKey);
86
+ }
87
+
88
+ // Fetch project details to get the ID
89
+ const url = `${SENTRY_API_BASE}/projects/${encodeURIComponent(org)}/${encodeURIComponent(project)}/`;
90
+ const data = await fetchJson(url, token);
91
+
92
+ if (!data || !data.id) {
93
+ throw new Error(`Project '${project}' not found in organization '${org}'`);
94
+ }
95
+
96
+ const id = String(data.id);
97
+ projectIdCache.set(cacheKey, id);
98
+ return id;
99
+ }