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.
- package/LICENSE +201 -0
- package/README.md +95 -0
- package/TODO.md +11 -0
- package/commands/handoff.md +100 -0
- package/commands/make-release.md +75 -0
- package/commands/pickup.md +30 -0
- package/commands/update-changelog.md +78 -0
- package/package.json +22 -0
- package/pi-extensions/answer.ts +527 -0
- package/pi-extensions/codex-tuning.ts +632 -0
- package/pi-extensions/commit.ts +248 -0
- package/pi-extensions/cwd-history.ts +237 -0
- package/pi-extensions/issues.ts +548 -0
- package/pi-extensions/loop.ts +446 -0
- package/pi-extensions/qna.ts +167 -0
- package/pi-extensions/reveal.ts +689 -0
- package/pi-extensions/review.ts +807 -0
- package/pi-themes/armin.json +81 -0
- package/pi-themes/nightowl.json +82 -0
- package/skills/anachb/SKILL.md +183 -0
- package/skills/anachb/departures.sh +79 -0
- package/skills/anachb/disruptions.sh +53 -0
- package/skills/anachb/route.sh +87 -0
- package/skills/anachb/search.sh +43 -0
- package/skills/ghidra/SKILL.md +254 -0
- package/skills/ghidra/scripts/find-ghidra.sh +54 -0
- package/skills/ghidra/scripts/ghidra-analyze.sh +239 -0
- package/skills/ghidra/scripts/ghidra_scripts/ExportAll.java +278 -0
- package/skills/ghidra/scripts/ghidra_scripts/ExportCalls.java +148 -0
- package/skills/ghidra/scripts/ghidra_scripts/ExportDecompiled.java +84 -0
- package/skills/ghidra/scripts/ghidra_scripts/ExportFunctions.java +114 -0
- package/skills/ghidra/scripts/ghidra_scripts/ExportStrings.java +123 -0
- package/skills/ghidra/scripts/ghidra_scripts/ExportSymbols.java +135 -0
- package/skills/github/SKILL.md +47 -0
- package/skills/improve-skill/SKILL.md +155 -0
- package/skills/improve-skill/scripts/extract-session.js +349 -0
- package/skills/oebb-scotty/SKILL.md +429 -0
- package/skills/oebb-scotty/arrivals.sh +83 -0
- package/skills/oebb-scotty/departures.sh +83 -0
- package/skills/oebb-scotty/disruptions.sh +33 -0
- package/skills/oebb-scotty/search-station.sh +36 -0
- package/skills/oebb-scotty/trip.sh +119 -0
- package/skills/openscad/SKILL.md +232 -0
- package/skills/openscad/examples/parametric_box.scad +92 -0
- package/skills/openscad/examples/phone_stand.scad +95 -0
- package/skills/openscad/tools/common.sh +50 -0
- package/skills/openscad/tools/export-stl.sh +56 -0
- package/skills/openscad/tools/extract-params.sh +147 -0
- package/skills/openscad/tools/multi-preview.sh +68 -0
- package/skills/openscad/tools/preview.sh +74 -0
- package/skills/openscad/tools/render-with-params.sh +91 -0
- package/skills/openscad/tools/validate.sh +46 -0
- package/skills/pi-share/SKILL.md +105 -0
- package/skills/pi-share/fetch-session.mjs +322 -0
- package/skills/sentry/SKILL.md +239 -0
- package/skills/sentry/lib/auth.js +99 -0
- package/skills/sentry/scripts/fetch-event.js +329 -0
- package/skills/sentry/scripts/fetch-issue.js +356 -0
- package/skills/sentry/scripts/list-issues.js +239 -0
- package/skills/sentry/scripts/search-events.js +291 -0
- package/skills/sentry/scripts/search-logs.js +240 -0
- package/skills/tmux/SKILL.md +105 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/web-browser/SKILL.md +91 -0
- package/skills/web-browser/scripts/cdp.js +210 -0
- package/skills/web-browser/scripts/dismiss-cookies.js +373 -0
- package/skills/web-browser/scripts/eval.js +68 -0
- package/skills/web-browser/scripts/logs-tail.js +69 -0
- package/skills/web-browser/scripts/nav.js +65 -0
- package/skills/web-browser/scripts/net-summary.js +94 -0
- package/skills/web-browser/scripts/package-lock.json +33 -0
- package/skills/web-browser/scripts/package.json +6 -0
- package/skills/web-browser/scripts/pick.js +165 -0
- package/skills/web-browser/scripts/screenshot.js +52 -0
- package/skills/web-browser/scripts/start.js +80 -0
- package/skills/web-browser/scripts/watch.js +266 -0
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { SENTRY_API_BASE, getAuthToken, fetchJson, formatTimestamp } from "../lib/auth.js";
|
|
4
|
+
|
|
5
|
+
const LOG_FIELDS = [
|
|
6
|
+
"sentry.item_id",
|
|
7
|
+
"trace",
|
|
8
|
+
"sentry.severity",
|
|
9
|
+
"timestamp",
|
|
10
|
+
"message",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse a Sentry logs explorer URL
|
|
15
|
+
* Examples:
|
|
16
|
+
* https://earendil.sentry.io/explore/logs/?project=123&statsPeriod=14d
|
|
17
|
+
* https://sentry.io/organizations/myorg/explore/logs/?project=123
|
|
18
|
+
*/
|
|
19
|
+
function parseLogsUrl(urlStr) {
|
|
20
|
+
try {
|
|
21
|
+
const url = new URL(urlStr);
|
|
22
|
+
const params = url.searchParams;
|
|
23
|
+
const result = {};
|
|
24
|
+
|
|
25
|
+
// Extract org from subdomain (earendil.sentry.io) or path (/organizations/myorg/)
|
|
26
|
+
const subdomainMatch = url.hostname.match(/^([^.]+)\.sentry\.io$/);
|
|
27
|
+
if (subdomainMatch && subdomainMatch[1] !== "www") {
|
|
28
|
+
result.org = subdomainMatch[1];
|
|
29
|
+
} else {
|
|
30
|
+
const pathMatch = url.pathname.match(/\/organizations\/([^/]+)\//);
|
|
31
|
+
if (pathMatch) {
|
|
32
|
+
result.org = pathMatch[1];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Extract project ID
|
|
37
|
+
if (params.has("project")) {
|
|
38
|
+
result.project = params.get("project");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Extract time period
|
|
42
|
+
if (params.has("statsPeriod")) {
|
|
43
|
+
result.period = params.get("statsPeriod");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Extract query
|
|
47
|
+
if (params.has("logsQuery")) {
|
|
48
|
+
result.query = params.get("logsQuery");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return result;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function parseArgs(args) {
|
|
58
|
+
const options = {
|
|
59
|
+
org: null,
|
|
60
|
+
project: null,
|
|
61
|
+
query: null,
|
|
62
|
+
period: "24h",
|
|
63
|
+
limit: 100,
|
|
64
|
+
json: false,
|
|
65
|
+
help: false,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
for (let i = 0; i < args.length; i++) {
|
|
69
|
+
const arg = args[i];
|
|
70
|
+
|
|
71
|
+
if (arg === "--help" || arg === "-h") {
|
|
72
|
+
options.help = true;
|
|
73
|
+
} else if (arg === "--json") {
|
|
74
|
+
options.json = true;
|
|
75
|
+
} else if (arg === "--org" || arg === "-o") {
|
|
76
|
+
options.org = args[++i];
|
|
77
|
+
} else if (arg === "--project" || arg === "-p") {
|
|
78
|
+
options.project = args[++i];
|
|
79
|
+
} else if (arg === "--period" || arg === "-t") {
|
|
80
|
+
options.period = args[++i];
|
|
81
|
+
} else if (arg === "--limit" || arg === "-n") {
|
|
82
|
+
options.limit = parseInt(args[++i], 10);
|
|
83
|
+
} else if (!arg.startsWith("-")) {
|
|
84
|
+
// Check if it's a Sentry URL
|
|
85
|
+
if (arg.includes("sentry.io/") && arg.includes("/logs")) {
|
|
86
|
+
const urlOptions = parseLogsUrl(arg);
|
|
87
|
+
if (urlOptions) {
|
|
88
|
+
if (urlOptions.org) options.org = urlOptions.org;
|
|
89
|
+
if (urlOptions.project) options.project = urlOptions.project;
|
|
90
|
+
if (urlOptions.period) options.period = urlOptions.period;
|
|
91
|
+
if (urlOptions.query) options.query = urlOptions.query;
|
|
92
|
+
}
|
|
93
|
+
} else if (!options.query) {
|
|
94
|
+
options.query = arg;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return options;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function showHelp() {
|
|
103
|
+
console.log(`Usage: search-logs.js [query|url] [options]
|
|
104
|
+
|
|
105
|
+
Search for logs in Sentry.
|
|
106
|
+
|
|
107
|
+
Arguments:
|
|
108
|
+
query Search query (e.g., "level:error", "user.id:123")
|
|
109
|
+
url Sentry logs explorer URL (extracts org, project, period)
|
|
110
|
+
|
|
111
|
+
Options:
|
|
112
|
+
--org, -o <org> Organization slug (required unless URL provided)
|
|
113
|
+
--project, -p <p> Project slug or ID to filter by
|
|
114
|
+
--period, -t <p> Time period (default: 24h, e.g., 1h, 7d, 90d)
|
|
115
|
+
--limit, -n <n> Max results (default: 100, max: 1000)
|
|
116
|
+
--json Output raw JSON
|
|
117
|
+
-h, --help Show this help
|
|
118
|
+
|
|
119
|
+
Search Query Syntax:
|
|
120
|
+
level:error Filter by log level (trace, debug, info, warn, error, fatal)
|
|
121
|
+
message:*timeout* Search message text
|
|
122
|
+
trace:abc123 Filter by trace ID
|
|
123
|
+
project:my-project Filter by project slug
|
|
124
|
+
|
|
125
|
+
Combine filters: level:error message:*failed*
|
|
126
|
+
|
|
127
|
+
Examples:
|
|
128
|
+
search-logs.js --org myorg
|
|
129
|
+
search-logs.js "level:error" --org myorg --project backend
|
|
130
|
+
search-logs.js "message:*timeout*" --org myorg --period 7d
|
|
131
|
+
search-logs.js --org myorg --limit 50 --json
|
|
132
|
+
|
|
133
|
+
# Use a Sentry URL directly:
|
|
134
|
+
search-logs.js "https://myorg.sentry.io/explore/logs/?project=123&statsPeriod=7d"
|
|
135
|
+
`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function formatLogEntry(entry) {
|
|
139
|
+
const lines = [];
|
|
140
|
+
|
|
141
|
+
const ts = entry.timestamp || "N/A";
|
|
142
|
+
const severity = entry["sentry.severity"] || "info";
|
|
143
|
+
const message = entry.message || "(no message)";
|
|
144
|
+
const trace = entry.trace || null;
|
|
145
|
+
|
|
146
|
+
// Format timestamp for display
|
|
147
|
+
let displayTs = ts;
|
|
148
|
+
try {
|
|
149
|
+
const date = new Date(ts);
|
|
150
|
+
if (!isNaN(date.getTime())) {
|
|
151
|
+
displayTs = date.toISOString().replace("T", " ").slice(0, 19);
|
|
152
|
+
}
|
|
153
|
+
} catch {}
|
|
154
|
+
|
|
155
|
+
// Color-code severity in output
|
|
156
|
+
const severityDisplay = `[${severity.toUpperCase().padEnd(5)}]`;
|
|
157
|
+
|
|
158
|
+
lines.push(`${displayTs} ${severityDisplay} ${message}`);
|
|
159
|
+
|
|
160
|
+
if (trace) {
|
|
161
|
+
lines.push(` trace: ${trace}`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function formatOutput(data) {
|
|
168
|
+
if (!data.data || data.data.length === 0) {
|
|
169
|
+
return "No logs found matching your query.";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const lines = [];
|
|
173
|
+
lines.push(`Found ${data.data.length} log entries:\n`);
|
|
174
|
+
|
|
175
|
+
for (const entry of data.data) {
|
|
176
|
+
lines.push(formatLogEntry(entry));
|
|
177
|
+
lines.push("");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return lines.join("\n").trimEnd();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function main() {
|
|
184
|
+
const args = process.argv.slice(2);
|
|
185
|
+
const options = parseArgs(args);
|
|
186
|
+
|
|
187
|
+
if (options.help) {
|
|
188
|
+
showHelp();
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!options.org) {
|
|
193
|
+
console.error("Error: --org is required");
|
|
194
|
+
console.error("Run with --help for usage information");
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const token = getAuthToken();
|
|
199
|
+
|
|
200
|
+
// Build query parameters
|
|
201
|
+
const params = new URLSearchParams();
|
|
202
|
+
params.set("dataset", "logs");
|
|
203
|
+
params.set("statsPeriod", options.period);
|
|
204
|
+
params.set("per_page", Math.min(options.limit, 1000).toString());
|
|
205
|
+
params.set("sort", "-timestamp");
|
|
206
|
+
|
|
207
|
+
// Add fields
|
|
208
|
+
for (const field of LOG_FIELDS) {
|
|
209
|
+
params.append("field", field);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Build search query
|
|
213
|
+
const queryParts = [];
|
|
214
|
+
if (options.project) {
|
|
215
|
+
queryParts.push(`project:${options.project}`);
|
|
216
|
+
}
|
|
217
|
+
if (options.query) {
|
|
218
|
+
queryParts.push(options.query);
|
|
219
|
+
}
|
|
220
|
+
if (queryParts.length > 0) {
|
|
221
|
+
params.set("query", queryParts.join(" "));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const url = `${SENTRY_API_BASE}/organizations/${encodeURIComponent(options.org)}/events/?${params.toString()}`;
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const data = await fetchJson(url, token);
|
|
228
|
+
|
|
229
|
+
if (options.json) {
|
|
230
|
+
console.log(JSON.stringify(data, null, 2));
|
|
231
|
+
} else {
|
|
232
|
+
console.log(formatOutput(data));
|
|
233
|
+
}
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.error("Error:", err.message);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
main();
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tmux
|
|
3
|
+
description: "Remote control tmux sessions for interactive CLIs (python, gdb, etc.) by sending keystrokes and scraping pane output."
|
|
4
|
+
license: Vibecoded
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# tmux Skill
|
|
8
|
+
|
|
9
|
+
Use tmux as a programmable terminal multiplexer for interactive work. Works on Linux and macOS with stock tmux; avoid custom config by using a private socket.
|
|
10
|
+
|
|
11
|
+
## Quickstart (isolated socket)
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
SOCKET_DIR=${TMPDIR:-/tmp}/claude-tmux-sockets # well-known dir for all agent sockets
|
|
15
|
+
mkdir -p "$SOCKET_DIR"
|
|
16
|
+
SOCKET="$SOCKET_DIR/claude.sock" # keep agent sessions separate from your personal tmux
|
|
17
|
+
SESSION=claude-python # slug-like names; avoid spaces
|
|
18
|
+
tmux -S "$SOCKET" new -d -s "$SESSION" -n shell
|
|
19
|
+
tmux -S "$SOCKET" send-keys -t "$SESSION":0.0 -- 'python3 -q' Enter
|
|
20
|
+
tmux -S "$SOCKET" capture-pane -p -J -t "$SESSION":0.0 -S -200 # watch output
|
|
21
|
+
tmux -S "$SOCKET" kill-session -t "$SESSION" # clean up
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After starting a session ALWAYS tell the user how to monitor the session by giving them a command to copy paste:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
To monitor this session yourself:
|
|
28
|
+
tmux -S "$SOCKET" attach -t claude-lldb
|
|
29
|
+
|
|
30
|
+
Or to capture the output once:
|
|
31
|
+
tmux -S "$SOCKET" capture-pane -p -J -t claude-lldb:0.0 -S -200
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This must ALWAYS be printed right after a session was started and once again at the end of the tool loop. But the earlier you send it, the happier the user will be.
|
|
35
|
+
|
|
36
|
+
## Socket convention
|
|
37
|
+
|
|
38
|
+
- Agents MUST place tmux sockets under `CLAUDE_TMUX_SOCKET_DIR` (defaults to `${TMPDIR:-/tmp}/claude-tmux-sockets`) and use `tmux -S "$SOCKET"` so we can enumerate/clean them. Create the dir first: `mkdir -p "$CLAUDE_TMUX_SOCKET_DIR"`.
|
|
39
|
+
- Default socket path to use unless you must isolate further: `SOCKET="$CLAUDE_TMUX_SOCKET_DIR/claude.sock"`.
|
|
40
|
+
|
|
41
|
+
## Targeting panes and naming
|
|
42
|
+
|
|
43
|
+
- Target format: `{session}:{window}.{pane}`, defaults to `:0.0` if omitted. Keep names short (e.g., `claude-py`, `claude-gdb`).
|
|
44
|
+
- Use `-S "$SOCKET"` consistently to stay on the private socket path. If you need user config, drop `-f /dev/null`; otherwise `-f /dev/null` gives a clean config.
|
|
45
|
+
- Inspect: `tmux -S "$SOCKET" list-sessions`, `tmux -S "$SOCKET" list-panes -a`.
|
|
46
|
+
|
|
47
|
+
## Finding sessions
|
|
48
|
+
|
|
49
|
+
- List sessions on your active socket with metadata: `./scripts/find-sessions.sh -S "$SOCKET"`; add `-q partial-name` to filter.
|
|
50
|
+
- Scan all sockets under the shared directory: `./scripts/find-sessions.sh --all` (uses `CLAUDE_TMUX_SOCKET_DIR` or `${TMPDIR:-/tmp}/claude-tmux-sockets`).
|
|
51
|
+
|
|
52
|
+
## Sending input safely
|
|
53
|
+
|
|
54
|
+
- Prefer literal sends to avoid shell splitting: `tmux -L "$SOCKET" send-keys -t target -l -- "$cmd"`
|
|
55
|
+
- When composing inline commands, use single quotes or ANSI C quoting to avoid expansion: `tmux ... send-keys -t target -- $'python3 -m http.server 8000'`.
|
|
56
|
+
- To send control keys: `tmux ... send-keys -t target C-c`, `C-d`, `C-z`, `Escape`, etc.
|
|
57
|
+
|
|
58
|
+
## Watching output
|
|
59
|
+
|
|
60
|
+
- Capture recent history (joined lines to avoid wrapping artifacts): `tmux -L "$SOCKET" capture-pane -p -J -t target -S -200`.
|
|
61
|
+
- For continuous monitoring, poll with the helper script (below) instead of `tmux wait-for` (which does not watch pane output).
|
|
62
|
+
- You can also temporarily attach to observe: `tmux -L "$SOCKET" attach -t "$SESSION"`; detach with `Ctrl+b d`.
|
|
63
|
+
- When giving instructions to a user, **explicitly print a copy/paste monitor command** alongside the action don't assume they remembered the command.
|
|
64
|
+
|
|
65
|
+
## Spawning Processes
|
|
66
|
+
|
|
67
|
+
Some special rules for processes:
|
|
68
|
+
|
|
69
|
+
- when asked to debug, use lldb by default
|
|
70
|
+
- when starting a python interactive shell, always set the `PYTHON_BASIC_REPL=1` environment variable. This is very important as the non-basic console interferes with your send-keys.
|
|
71
|
+
|
|
72
|
+
## Synchronizing / waiting for prompts
|
|
73
|
+
|
|
74
|
+
- Use timed polling to avoid races with interactive tools. Example: wait for a Python prompt before sending code:
|
|
75
|
+
```bash
|
|
76
|
+
./scripts/wait-for-text.sh -t "$SESSION":0.0 -p '^>>>' -T 15 -l 4000
|
|
77
|
+
```
|
|
78
|
+
- For long-running commands, poll for completion text (`"Type quit to exit"`, `"Program exited"`, etc.) before proceeding.
|
|
79
|
+
|
|
80
|
+
## Interactive tool recipes
|
|
81
|
+
|
|
82
|
+
- **Python REPL**: `tmux ... send-keys -- 'python3 -q' Enter`; wait for `^>>>`; send code with `-l`; interrupt with `C-c`. Always with `PYTHON_BASIC_REPL`.
|
|
83
|
+
- **gdb**: `tmux ... send-keys -- 'gdb --quiet ./a.out' Enter`; disable paging `tmux ... send-keys -- 'set pagination off' Enter`; break with `C-c`; issue `bt`, `info locals`, etc.; exit via `quit` then confirm `y`.
|
|
84
|
+
- **Other TTY apps** (ipdb, psql, mysql, node, bash): same pattern—start the program, poll for its prompt, then send literal text and Enter.
|
|
85
|
+
|
|
86
|
+
## Cleanup
|
|
87
|
+
|
|
88
|
+
- Kill a session when done: `tmux -S "$SOCKET" kill-session -t "$SESSION"`.
|
|
89
|
+
- Kill all sessions on a socket: `tmux -S "$SOCKET" list-sessions -F '#{session_name}' | xargs -r -n1 tmux -S "$SOCKET" kill-session -t`.
|
|
90
|
+
- Remove everything on the private socket: `tmux -S "$SOCKET" kill-server`.
|
|
91
|
+
|
|
92
|
+
## Helper: wait-for-text.sh
|
|
93
|
+
|
|
94
|
+
`./scripts/wait-for-text.sh` polls a pane for a regex (or fixed string) with a timeout. Works on Linux/macOS with bash + tmux + grep.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
./scripts/wait-for-text.sh -t session:0.0 -p 'pattern' [-F] [-T 20] [-i 0.5] [-l 2000]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- `-t`/`--target` pane target (required)
|
|
101
|
+
- `-p`/`--pattern` regex to match (required); add `-F` for fixed string
|
|
102
|
+
- `-T` timeout seconds (integer, default 15)
|
|
103
|
+
- `-i` poll interval seconds (default 0.5)
|
|
104
|
+
- `-l` history lines to search from the pane (integer, default 1000)
|
|
105
|
+
- Exits 0 on first match, 1 on timeout. On failure prints the last captured text to stderr to aid debugging.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage: find-sessions.sh [-L socket-name|-S socket-path|-A] [-q pattern]
|
|
7
|
+
|
|
8
|
+
List tmux sessions on a socket (default tmux socket if none provided).
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
-L, --socket tmux socket name (passed to tmux -L)
|
|
12
|
+
-S, --socket-path tmux socket path (passed to tmux -S)
|
|
13
|
+
-A, --all scan all sockets under CLAUDE_TMUX_SOCKET_DIR
|
|
14
|
+
-q, --query case-insensitive substring to filter session names
|
|
15
|
+
-h, --help show this help
|
|
16
|
+
USAGE
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
socket_name=""
|
|
20
|
+
socket_path=""
|
|
21
|
+
query=""
|
|
22
|
+
scan_all=false
|
|
23
|
+
socket_dir="${CLAUDE_TMUX_SOCKET_DIR:-${TMPDIR:-/tmp}/claude-tmux-sockets}"
|
|
24
|
+
|
|
25
|
+
while [[ $# -gt 0 ]]; do
|
|
26
|
+
case "$1" in
|
|
27
|
+
-L|--socket) socket_name="${2-}"; shift 2 ;;
|
|
28
|
+
-S|--socket-path) socket_path="${2-}"; shift 2 ;;
|
|
29
|
+
-A|--all) scan_all=true; shift ;;
|
|
30
|
+
-q|--query) query="${2-}"; shift 2 ;;
|
|
31
|
+
-h|--help) usage; exit 0 ;;
|
|
32
|
+
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
|
|
33
|
+
esac
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
if [[ "$scan_all" == true && ( -n "$socket_name" || -n "$socket_path" ) ]]; then
|
|
37
|
+
echo "Cannot combine --all with -L or -S" >&2
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
if [[ -n "$socket_name" && -n "$socket_path" ]]; then
|
|
42
|
+
echo "Use either -L or -S, not both" >&2
|
|
43
|
+
exit 1
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
47
|
+
echo "tmux not found in PATH" >&2
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
list_sessions() {
|
|
52
|
+
local label="$1"; shift
|
|
53
|
+
local tmux_cmd=(tmux "$@")
|
|
54
|
+
|
|
55
|
+
if ! sessions="$("${tmux_cmd[@]}" list-sessions -F '#{session_name}\t#{session_attached}\t#{session_created_string}' 2>/dev/null)"; then
|
|
56
|
+
echo "No tmux server found on $label" >&2
|
|
57
|
+
return 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
if [[ -n "$query" ]]; then
|
|
61
|
+
sessions="$(printf '%s\n' "$sessions" | grep -i -- "$query" || true)"
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if [[ -z "$sessions" ]]; then
|
|
65
|
+
echo "No sessions found on $label"
|
|
66
|
+
return 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo "Sessions on $label:"
|
|
70
|
+
printf '%s\n' "$sessions" | while IFS=$'\t' read -r name attached created; do
|
|
71
|
+
attached_label=$([[ "$attached" == "1" ]] && echo "attached" || echo "detached")
|
|
72
|
+
printf ' - %s (%s, started %s)\n' "$name" "$attached_label" "$created"
|
|
73
|
+
done
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if [[ "$scan_all" == true ]]; then
|
|
77
|
+
if [[ ! -d "$socket_dir" ]]; then
|
|
78
|
+
echo "Socket directory not found: $socket_dir" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
shopt -s nullglob
|
|
83
|
+
sockets=("$socket_dir"/*)
|
|
84
|
+
shopt -u nullglob
|
|
85
|
+
|
|
86
|
+
if [[ "${#sockets[@]}" -eq 0 ]]; then
|
|
87
|
+
echo "No sockets found under $socket_dir" >&2
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
exit_code=0
|
|
92
|
+
for sock in "${sockets[@]}"; do
|
|
93
|
+
if [[ ! -S "$sock" ]]; then
|
|
94
|
+
continue
|
|
95
|
+
fi
|
|
96
|
+
list_sessions "socket path '$sock'" -S "$sock" || exit_code=$?
|
|
97
|
+
done
|
|
98
|
+
exit "$exit_code"
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
tmux_cmd=(tmux)
|
|
102
|
+
socket_label="default socket"
|
|
103
|
+
|
|
104
|
+
if [[ -n "$socket_name" ]]; then
|
|
105
|
+
tmux_cmd+=(-L "$socket_name")
|
|
106
|
+
socket_label="socket name '$socket_name'"
|
|
107
|
+
elif [[ -n "$socket_path" ]]; then
|
|
108
|
+
tmux_cmd+=(-S "$socket_path")
|
|
109
|
+
socket_label="socket path '$socket_path'"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
list_sessions "$socket_label" "${tmux_cmd[@]:1}"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
usage() {
|
|
5
|
+
cat <<'USAGE'
|
|
6
|
+
Usage: wait-for-text.sh -t target -p pattern [options]
|
|
7
|
+
|
|
8
|
+
Poll a tmux pane for text and exit when found.
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
-t, --target tmux target (session:window.pane), required
|
|
12
|
+
-p, --pattern regex pattern to look for, required
|
|
13
|
+
-F, --fixed treat pattern as a fixed string (grep -F)
|
|
14
|
+
-T, --timeout seconds to wait (integer, default: 15)
|
|
15
|
+
-i, --interval poll interval in seconds (default: 0.5)
|
|
16
|
+
-l, --lines number of history lines to inspect (integer, default: 1000)
|
|
17
|
+
-h, --help show this help
|
|
18
|
+
USAGE
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
target=""
|
|
22
|
+
pattern=""
|
|
23
|
+
grep_flag="-E"
|
|
24
|
+
timeout=15
|
|
25
|
+
interval=0.5
|
|
26
|
+
lines=1000
|
|
27
|
+
|
|
28
|
+
while [[ $# -gt 0 ]]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
-t|--target) target="${2-}"; shift 2 ;;
|
|
31
|
+
-p|--pattern) pattern="${2-}"; shift 2 ;;
|
|
32
|
+
-F|--fixed) grep_flag="-F"; shift ;;
|
|
33
|
+
-T|--timeout) timeout="${2-}"; shift 2 ;;
|
|
34
|
+
-i|--interval) interval="${2-}"; shift 2 ;;
|
|
35
|
+
-l|--lines) lines="${2-}"; shift 2 ;;
|
|
36
|
+
-h|--help) usage; exit 0 ;;
|
|
37
|
+
*) echo "Unknown option: $1" >&2; usage; exit 1 ;;
|
|
38
|
+
esac
|
|
39
|
+
done
|
|
40
|
+
|
|
41
|
+
if [[ -z "$target" || -z "$pattern" ]]; then
|
|
42
|
+
echo "target and pattern are required" >&2
|
|
43
|
+
usage
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if ! [[ "$timeout" =~ ^[0-9]+$ ]]; then
|
|
48
|
+
echo "timeout must be an integer number of seconds" >&2
|
|
49
|
+
exit 1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if ! [[ "$lines" =~ ^[0-9]+$ ]]; then
|
|
53
|
+
echo "lines must be an integer" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
58
|
+
echo "tmux not found in PATH" >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
# End time in epoch seconds (integer, good enough for polling)
|
|
63
|
+
start_epoch=$(date +%s)
|
|
64
|
+
deadline=$((start_epoch + timeout))
|
|
65
|
+
|
|
66
|
+
while true; do
|
|
67
|
+
# -J joins wrapped lines, -S uses negative index to read last N lines
|
|
68
|
+
pane_text="$(tmux capture-pane -p -J -t "$target" -S "-${lines}" 2>/dev/null || true)"
|
|
69
|
+
|
|
70
|
+
if printf '%s\n' "$pane_text" | grep $grep_flag -- "$pattern" >/dev/null 2>&1; then
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
now=$(date +%s)
|
|
75
|
+
if (( now >= deadline )); then
|
|
76
|
+
echo "Timed out after ${timeout}s waiting for pattern: $pattern" >&2
|
|
77
|
+
echo "Last ${lines} lines from $target:" >&2
|
|
78
|
+
printf '%s\n' "$pane_text" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
sleep "$interval"
|
|
83
|
+
done
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: web-browser
|
|
3
|
+
description: "Allows to interact with web pages by performing actions such as clicking buttons, filling out forms, and navigating links. It works by remote controlling Google Chrome or Chromium browsers using the Chrome DevTools Protocol (CDP). When Claude needs to browse the web, it can use this skill to do so."
|
|
4
|
+
license: Stolen from Mario
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Web Browser Skill
|
|
8
|
+
|
|
9
|
+
Minimal CDP tools for collaborative site exploration.
|
|
10
|
+
|
|
11
|
+
## Start Chrome
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
./scripts/start.js # Fresh profile
|
|
15
|
+
./scripts/start.js --profile # Copy your profile (cookies, logins)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Start Chrome on `:9222` with remote debugging.
|
|
19
|
+
|
|
20
|
+
## Navigate
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
./scripts/nav.js https://example.com
|
|
24
|
+
./scripts/nav.js https://example.com --new
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Navigate current tab or open new tab.
|
|
28
|
+
|
|
29
|
+
## Evaluate JavaScript
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
./scripts/eval.js 'document.title'
|
|
33
|
+
./scripts/eval.js 'document.querySelectorAll("a").length'
|
|
34
|
+
./scripts/eval.js 'JSON.stringify(Array.from(document.querySelectorAll("a")).map(a => ({ text: a.textContent.trim(), href: a.href })).filter(link => !link.href.startsWith("https://")))'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Execute JavaScript in active tab (async context). Be careful with string escaping, best to use single quotes.
|
|
38
|
+
|
|
39
|
+
## Screenshot
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
./scripts/screenshot.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Screenshot current viewport, returns temp file path
|
|
46
|
+
|
|
47
|
+
## Pick Elements
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
./scripts/pick.js "Click the submit button"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Interactive element picker. Click to select, Cmd/Ctrl+Click for multi-select, Enter to finish.
|
|
54
|
+
|
|
55
|
+
## Dismiss Cookie Dialogs
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
./scripts/dismiss-cookies.js # Accept cookies
|
|
59
|
+
./scripts/dismiss-cookies.js --reject # Reject cookies (where possible)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Automatically dismisses EU cookie consent dialogs.
|
|
63
|
+
|
|
64
|
+
Run after navigating to a page:
|
|
65
|
+
```bash
|
|
66
|
+
./scripts/nav.js https://example.com && ./scripts/dismiss-cookies.js
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Background Logging (Console + Errors + Network)
|
|
70
|
+
|
|
71
|
+
Automatically started by `start.js` and writes JSONL logs to:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
~/.cache/agent-web/logs/YYYY-MM-DD/<targetId>.jsonl
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Manually start:
|
|
78
|
+
```bash
|
|
79
|
+
./scripts/watch.js
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Tail latest log:
|
|
83
|
+
```bash
|
|
84
|
+
./scripts/logs-tail.js # dump current log and exit
|
|
85
|
+
./scripts/logs-tail.js --follow # keep following
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Summarize network responses:
|
|
89
|
+
```bash
|
|
90
|
+
./scripts/net-summary.js
|
|
91
|
+
```
|