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,349 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Extract session transcript from Claude Code, Pi, or Codex session files.
5
+ *
6
+ * Usage:
7
+ * ./extract-session.js [session-path]
8
+ * ./extract-session.js --agent claude|pi|codex [--cwd /path/to/dir]
9
+ *
10
+ * If no arguments, auto-detects based on current working directory.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const os = require('os');
16
+
17
+ // Parse arguments
18
+ const args = process.argv.slice(2);
19
+ let sessionPath = null;
20
+ let agent = null;
21
+ let cwd = process.cwd();
22
+
23
+ for (let i = 0; i < args.length; i++) {
24
+ if (args[i] === '--agent' && args[i + 1]) {
25
+ agent = args[++i];
26
+ } else if (args[i] === '--cwd' && args[i + 1]) {
27
+ cwd = args[++i];
28
+ } else if (!args[i].startsWith('-')) {
29
+ sessionPath = args[i];
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Encode CWD for session path lookup
35
+ */
36
+ function encodeCwd(cwd, style) {
37
+ if (style === 'pi') {
38
+ // Pi uses: --<cwd-without-leading-slash-with-slashes-as-dashes>--
39
+ // e.g., /Users/mitsuhiko/Development/myproject -> --Users-mitsuhiko-Development-myproject--
40
+ const safePath = `--${cwd.replace(/^[/\\]/, '').replace(/[/\\:]/g, '-')}--`;
41
+ return safePath;
42
+ }
43
+ // Claude Code: just replace / with -
44
+ return cwd.replace(/\//g, '-');
45
+ }
46
+
47
+ /**
48
+ * Find the most recent session file in a directory
49
+ */
50
+ function findMostRecentSession(dir) {
51
+ if (!fs.existsSync(dir)) return null;
52
+
53
+ const files = fs.readdirSync(dir)
54
+ .filter(f => f.endsWith('.jsonl'))
55
+ .map(f => ({
56
+ name: f,
57
+ path: path.join(dir, f),
58
+ mtime: fs.statSync(path.join(dir, f)).mtime
59
+ }))
60
+ .sort((a, b) => b.mtime - a.mtime);
61
+
62
+ return files.length > 0 ? files[0].path : null;
63
+ }
64
+
65
+ /**
66
+ * Find Codex session matching CWD
67
+ */
68
+ function findCodexSession(targetCwd) {
69
+ const baseDir = path.join(os.homedir(), '.codex', 'sessions');
70
+ if (!fs.existsSync(baseDir)) return null;
71
+
72
+ // Find all session files, sorted by mtime
73
+ const allSessions = [];
74
+
75
+ function walkDir(dir) {
76
+ if (!fs.existsSync(dir)) return;
77
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
78
+ const fullPath = path.join(dir, entry.name);
79
+ if (entry.isDirectory()) {
80
+ walkDir(fullPath);
81
+ } else if (entry.name.endsWith('.jsonl')) {
82
+ allSessions.push({
83
+ path: fullPath,
84
+ mtime: fs.statSync(fullPath).mtime
85
+ });
86
+ }
87
+ }
88
+ }
89
+
90
+ walkDir(baseDir);
91
+ allSessions.sort((a, b) => b.mtime - a.mtime);
92
+
93
+ // Find most recent matching CWD
94
+ for (const session of allSessions.slice(0, 50)) { // Check last 50
95
+ try {
96
+ const firstLine = fs.readFileSync(session.path, 'utf8').split('\n')[0];
97
+ const data = JSON.parse(firstLine);
98
+ if (data.payload?.cwd === targetCwd) {
99
+ return session.path;
100
+ }
101
+ } catch (e) {
102
+ // Skip invalid files
103
+ }
104
+ }
105
+
106
+ return null;
107
+ }
108
+
109
+ /**
110
+ * Auto-detect session based on CWD
111
+ */
112
+ function autoDetectSession(cwd) {
113
+ // Try Claude Code first
114
+ const claudePath = path.join(os.homedir(), '.claude', 'projects', encodeCwd(cwd, 'claude'));
115
+ let session = findMostRecentSession(claudePath);
116
+ if (session) return { agent: 'claude', path: session };
117
+
118
+ // Try Pi
119
+ const piPath = path.join(os.homedir(), '.pi', 'agent', 'sessions', encodeCwd(cwd, 'pi'));
120
+ session = findMostRecentSession(piPath);
121
+ if (session) return { agent: 'pi', path: session };
122
+
123
+ // Try Codex
124
+ session = findCodexSession(cwd);
125
+ if (session) return { agent: 'codex', path: session };
126
+
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Parse Claude Code session format
132
+ */
133
+ function parseClaudeSession(content) {
134
+ const messages = [];
135
+ const lines = content.trim().split('\n');
136
+
137
+ for (const line of lines) {
138
+ try {
139
+ const entry = JSON.parse(line);
140
+ if (entry.message?.role && entry.message?.content) {
141
+ const msg = entry.message;
142
+ messages.push({
143
+ role: msg.role,
144
+ content: extractContent(msg.content),
145
+ timestamp: entry.timestamp
146
+ });
147
+ }
148
+ } catch (e) {
149
+ // Skip invalid lines
150
+ }
151
+ }
152
+
153
+ return messages;
154
+ }
155
+
156
+ /**
157
+ * Parse Pi session format
158
+ */
159
+ function parsePiSession(content) {
160
+ const messages = [];
161
+ const lines = content.trim().split('\n');
162
+
163
+ for (const line of lines) {
164
+ try {
165
+ const entry = JSON.parse(line);
166
+ if (entry.type === 'message' && entry.message?.role) {
167
+ messages.push({
168
+ role: entry.message.role,
169
+ content: extractContent(entry.message.content),
170
+ timestamp: entry.timestamp
171
+ });
172
+ }
173
+ } catch (e) {
174
+ // Skip invalid lines
175
+ }
176
+ }
177
+
178
+ return messages;
179
+ }
180
+
181
+ /**
182
+ * Parse Codex session format
183
+ */
184
+ function parseCodexSession(content) {
185
+ const messages = [];
186
+ const lines = content.trim().split('\n');
187
+
188
+ for (const line of lines) {
189
+ try {
190
+ const entry = JSON.parse(line);
191
+ if (entry.type === 'response_item' && entry.payload?.role) {
192
+ const payload = entry.payload;
193
+ messages.push({
194
+ role: payload.role,
195
+ content: extractContent(payload.content),
196
+ timestamp: entry.timestamp
197
+ });
198
+ }
199
+ } catch (e) {
200
+ // Skip invalid lines
201
+ }
202
+ }
203
+
204
+ return messages;
205
+ }
206
+
207
+ /**
208
+ * Extract text content from various content formats
209
+ */
210
+ function extractContent(content) {
211
+ if (typeof content === 'string') return content;
212
+ if (!Array.isArray(content)) return JSON.stringify(content);
213
+
214
+ const parts = [];
215
+ for (const item of content) {
216
+ if (typeof item === 'string') {
217
+ parts.push(item);
218
+ } else if (item.type === 'text') {
219
+ parts.push(item.text);
220
+ } else if (item.type === 'input_text') {
221
+ parts.push(item.text);
222
+ } else if (item.type === 'tool_use') {
223
+ parts.push(`[Tool: ${item.name}]\n${JSON.stringify(item.input, null, 2)}`);
224
+ } else if (item.type === 'tool_result') {
225
+ const result = typeof item.content === 'string'
226
+ ? item.content
227
+ : JSON.stringify(item.content);
228
+ // Truncate long tool results
229
+ const truncated = result.length > 500
230
+ ? result.slice(0, 500) + '\n[... truncated ...]'
231
+ : result;
232
+ parts.push(`[Tool Result]\n${truncated}`);
233
+ } else {
234
+ parts.push(`[${item.type}]`);
235
+ }
236
+ }
237
+
238
+ return parts.join('\n');
239
+ }
240
+
241
+ /**
242
+ * Format messages as readable transcript
243
+ */
244
+ function formatTranscript(messages, maxMessages = 100) {
245
+ const recent = messages.slice(-maxMessages);
246
+ const lines = [];
247
+
248
+ for (const msg of recent) {
249
+ const role = msg.role.toUpperCase();
250
+ lines.push(`\n### ${role}:\n`);
251
+ lines.push(msg.content);
252
+ }
253
+
254
+ if (messages.length > maxMessages) {
255
+ lines.unshift(`\n[... ${messages.length - maxMessages} earlier messages omitted ...]\n`);
256
+ }
257
+
258
+ return lines.join('\n');
259
+ }
260
+
261
+ // Main
262
+ async function main() {
263
+ let result;
264
+
265
+ if (sessionPath) {
266
+ // Explicit path provided
267
+ if (!fs.existsSync(sessionPath)) {
268
+ console.error(`Session file not found: ${sessionPath}`);
269
+ process.exit(1);
270
+ }
271
+ // Guess agent from path
272
+ if (sessionPath.includes('.claude')) {
273
+ result = { agent: 'claude', path: sessionPath };
274
+ } else if (sessionPath.includes('.pi')) {
275
+ result = { agent: 'pi', path: sessionPath };
276
+ } else if (sessionPath.includes('.codex')) {
277
+ result = { agent: 'codex', path: sessionPath };
278
+ } else {
279
+ // Default to Claude format
280
+ result = { agent: 'claude', path: sessionPath };
281
+ }
282
+ } else if (agent) {
283
+ // Agent specified, find session for that agent
284
+ if (agent === 'claude') {
285
+ const dir = path.join(os.homedir(), '.claude', 'projects', encodeCwd(cwd, 'claude'));
286
+ const session = findMostRecentSession(dir);
287
+ if (!session) {
288
+ console.error(`No Claude Code session found for: ${cwd}`);
289
+ process.exit(1);
290
+ }
291
+ result = { agent: 'claude', path: session };
292
+ } else if (agent === 'pi') {
293
+ const dir = path.join(os.homedir(), '.pi', 'agent', 'sessions', encodeCwd(cwd, 'pi'));
294
+ const session = findMostRecentSession(dir);
295
+ if (!session) {
296
+ console.error(`No Pi session found for: ${cwd}`);
297
+ process.exit(1);
298
+ }
299
+ result = { agent: 'pi', path: session };
300
+ } else if (agent === 'codex') {
301
+ const session = findCodexSession(cwd);
302
+ if (!session) {
303
+ console.error(`No Codex session found for: ${cwd}`);
304
+ process.exit(1);
305
+ }
306
+ result = { agent: 'codex', path: session };
307
+ } else {
308
+ console.error(`Unknown agent: ${agent}`);
309
+ process.exit(1);
310
+ }
311
+ } else {
312
+ // Auto-detect
313
+ result = autoDetectSession(cwd);
314
+ if (!result) {
315
+ console.error(`No session found for: ${cwd}`);
316
+ console.error('Try specifying --agent claude|pi|codex or provide a session path directly.');
317
+ process.exit(1);
318
+ }
319
+ }
320
+
321
+ // Read and parse session
322
+ const content = fs.readFileSync(result.path, 'utf8');
323
+
324
+ let messages;
325
+ switch (result.agent) {
326
+ case 'claude':
327
+ messages = parseClaudeSession(content);
328
+ break;
329
+ case 'pi':
330
+ messages = parsePiSession(content);
331
+ break;
332
+ case 'codex':
333
+ messages = parseCodexSession(content);
334
+ break;
335
+ }
336
+
337
+ // Output metadata and transcript
338
+ console.log(`# Session Transcript`);
339
+ console.log(`Agent: ${result.agent}`);
340
+ console.log(`File: ${result.path}`);
341
+ console.log(`Messages: ${messages.length}`);
342
+ console.log('');
343
+ console.log(formatTranscript(messages));
344
+ }
345
+
346
+ main().catch(e => {
347
+ console.error(e.message);
348
+ process.exit(1);
349
+ });