@gkoreli/ghx 0.2.0 → 0.2.1
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/README.md +3 -2
- package/SKILL.md +11 -2
- package/ghx +56 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,7 @@ If you have `gh` CLI working, ghx works too — same prerequisites.
|
|
|
42
42
|
```bash
|
|
43
43
|
# Search repos — name, stars, language, README preview in 1 call
|
|
44
44
|
ghx repos "react state management"
|
|
45
|
+
ghx repos "playwright mcp" --limit 5
|
|
45
46
|
|
|
46
47
|
# Explore a repo — branch, file tree, and README in 1 API call
|
|
47
48
|
ghx explore plausible/analytics
|
|
@@ -60,7 +61,7 @@ ghx read plausible/analytics --lines 42-80 lib/plausible/stats/query.ex
|
|
|
60
61
|
|
|
61
62
|
# Search code (AND matching, shows matching lines, token-protected)
|
|
62
63
|
ghx search "useState repo:facebook/react"
|
|
63
|
-
ghx search "path:llms.txt extension:txt"
|
|
64
|
+
ghx search "path:llms.txt extension:txt" --limit 10
|
|
64
65
|
|
|
65
66
|
# Full recursive tree
|
|
66
67
|
ghx tree plausible/analytics assets/js
|
|
@@ -68,7 +69,7 @@ ghx tree plausible/analytics assets/js
|
|
|
68
69
|
|
|
69
70
|
## Why
|
|
70
71
|
|
|
71
|
-
AI agents exploring GitHub face a reliability gap: *"Did I find nothing because nothing exists, or because I used the tool wrong?"* ghx eliminates this with smart defaults — AND matching instead of exact phrase, README previews instead of bare names, matching context instead of bare paths. The right behavior is the default behavior.
|
|
72
|
+
AI agents exploring GitHub face a reliability gap: *"Did I find nothing because nothing exists, or because I used the tool wrong?"* ghx eliminates this with smart defaults — AND matching instead of exact phrase, README previews instead of bare names, matching context instead of bare paths. Unknown flags are rejected loudly (not silently absorbed into queries). The right behavior is the default behavior, and the wrong behavior is impossible.
|
|
72
73
|
|
|
73
74
|
| Tool | Files per call | Matching context | Smart defaults | Dependencies |
|
|
74
75
|
|------|---------------|-----------------|---------------|-------------|
|
package/SKILL.md
CHANGED
|
@@ -22,12 +22,17 @@ ghx read <owner/repo> <f1> [f2] [f3] # Read 1-10 files in 1 API call (Grap
|
|
|
22
22
|
ghx read <owner/repo> --map <f1> [f2] # Structural map: signatures, imports, types (~92% token reduction)
|
|
23
23
|
ghx read <owner/repo> --grep "pat" <f> # Read file, show only matching lines (2 lines context)
|
|
24
24
|
ghx read <owner/repo> --lines 42-80 <f> # Read specific line range
|
|
25
|
-
ghx repos "<query>" # Search repos with README preview
|
|
26
|
-
ghx
|
|
25
|
+
ghx repos "<query>" # Search repos with README preview (default: 10 results)
|
|
26
|
+
ghx repos "<query>" --limit 5 # Limit repo results (max: 20)
|
|
27
|
+
ghx search "<query>" # Code search (AND matching, default: 30 results)
|
|
28
|
+
ghx search "<query>" --limit 10 # Limit code search results (max: 100)
|
|
27
29
|
ghx search --full "<query>" # Code search without line truncation (for minified files)
|
|
28
30
|
ghx tree <owner/repo> [path] # Full recursive tree listing
|
|
29
31
|
```
|
|
30
32
|
|
|
33
|
+
**Exit codes:** 0 = results returned, 1 = no results (query valid), 2 = usage error (bad flags/args).
|
|
34
|
+
**Flag safety:** Unknown flags always error (exit 2). Never silently absorbed into queries.
|
|
35
|
+
|
|
31
36
|
## Chain of Thought: Progressive Disclosure
|
|
32
37
|
|
|
33
38
|
**Always start surgical, escalate only when needed.** This mirrors how developers work: scan structure → identify interesting files → read specific sections.
|
|
@@ -157,6 +162,8 @@ AND matching is almost always what agents want. `gh search code "useState fetchD
|
|
|
157
162
|
|
|
158
163
|
10. **`gh search repos` and `gh search code` use different rate limit pools.** Repo search: 30/min (generous). Code search: 10/min (restrictive). Don't assume one rate limit applies to both.
|
|
159
164
|
|
|
165
|
+
11. **Unknown flags are rejected, not silently absorbed.** `ghx search "query" --json` exits 2 with a clear error. This is intentional — silent flag absorption was the #1 cause of agent failures (flags like `--limit` would get concatenated into the query string, corrupting it). If you get exit 2, check your flags.
|
|
166
|
+
|
|
160
167
|
## Anti-Patterns
|
|
161
168
|
|
|
162
169
|
- ❌ `web_fetch`/`web_search` on github.com — returns HTML noise, wastes thousands of tokens for zero useful information
|
|
@@ -176,6 +183,8 @@ AND matching is almost always what agents want. `gh search code "useState fetchD
|
|
|
176
183
|
- **Batch file reads.** `ghx read owner/repo f1 f2 f3` = 1 API call. Three separate reads = 3 calls.
|
|
177
184
|
- **Map before reading.** `ghx read --map` first to understand structure, then `--grep` or `--lines` for specifics.
|
|
178
185
|
- **Refine search, don't paginate.** If `ghx search` shows "201472 results (showing 30)", add qualifiers (`repo:`, `language:`, `path:`) — don't try to page through. 9 req/min rate limit makes pagination expensive.
|
|
186
|
+
- **Use `--limit` to control token budget.** `ghx repos "query" --limit 5` for quick checks, `--limit 15` for thorough discovery. `ghx search "query" --limit 10` when you only need top results.
|
|
187
|
+
- **Check exit codes.** 0 = got results, 1 = no results (query was valid, broaden it), 2 = usage error (fix your command).
|
|
179
188
|
- **Use `gh api --cache 1h`** for repeated lookups when using raw `gh` commands.
|
|
180
189
|
- **Use `--json fields --jq 'expr'`** on `gh` commands to get structured output and reduce noise.
|
|
181
190
|
- **Piped output is machine-formatted.** Tab-delimited, no truncation, no color codes — agents always get clean output.
|
package/ghx
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
set -euo pipefail
|
|
7
7
|
|
|
8
|
+
VERSION="0.2.1"
|
|
9
|
+
|
|
8
10
|
cmd="${1:-help}"
|
|
9
11
|
shift || true
|
|
10
12
|
|
|
@@ -73,13 +75,14 @@ read)
|
|
|
73
75
|
--grep) grep_pattern="$2"; shift 2 ;;
|
|
74
76
|
--lines) line_range="$2"; shift 2 ;;
|
|
75
77
|
--map) map_mode=1; shift ;;
|
|
78
|
+
--*) echo "ghx read: unknown flag '$1'" >&2; exit 2 ;;
|
|
76
79
|
*) files+=("$1"); shift ;;
|
|
77
80
|
esac
|
|
78
81
|
done
|
|
79
82
|
|
|
80
83
|
if [[ ${#files[@]} -eq 0 ]]; then
|
|
81
84
|
echo "Usage: ghx read <owner/repo> <path1> [path2] [--grep pattern] [--lines N-M]" >&2
|
|
82
|
-
exit
|
|
85
|
+
exit 2
|
|
83
86
|
fi
|
|
84
87
|
|
|
85
88
|
# Get default branch
|
|
@@ -136,28 +139,37 @@ read)
|
|
|
136
139
|
search)
|
|
137
140
|
# Parse flags
|
|
138
141
|
full_mode=""
|
|
142
|
+
limit=""
|
|
139
143
|
args=()
|
|
140
|
-
|
|
141
|
-
case "$
|
|
142
|
-
--full) full_mode=1 ;;
|
|
143
|
-
|
|
144
|
+
while [[ $# -gt 0 ]]; do
|
|
145
|
+
case "$1" in
|
|
146
|
+
--full) full_mode=1; shift ;;
|
|
147
|
+
--limit) [[ $# -lt 2 ]] && { echo "ghx search: --limit requires a value" >&2; exit 2; }
|
|
148
|
+
limit="$2"; shift 2 ;;
|
|
149
|
+
--*) echo "ghx search: unknown flag '$1'" >&2; exit 2 ;;
|
|
150
|
+
*) args+=("$1"); shift ;;
|
|
144
151
|
esac
|
|
145
152
|
done
|
|
146
153
|
query="${args[*]}"
|
|
147
154
|
if [[ -z "$query" ]]; then
|
|
148
|
-
echo "Usage: ghx search <query> [--full]" >&2
|
|
155
|
+
echo "Usage: ghx search <query> [--full] [--limit N]" >&2
|
|
149
156
|
echo "Examples: 'useState repo:owner/repo' | 'path:src/ extension:tsx language:typescript'" >&2
|
|
150
|
-
exit
|
|
157
|
+
exit 2
|
|
158
|
+
fi
|
|
159
|
+
# Validate and clamp limit (API max: 100)
|
|
160
|
+
if [[ -n "$limit" ]]; then
|
|
161
|
+
[[ "$limit" =~ ^[0-9]+$ ]] || { echo "ghx search: --limit must be a number, got '$limit'" >&2; exit 2; }
|
|
162
|
+
(( limit > 100 )) && { echo "⚠ --limit clamped to 100 (API max)" >&2; limit=100; }
|
|
151
163
|
fi
|
|
152
164
|
|
|
153
165
|
# Prerequisite checks
|
|
154
166
|
if ! command -v gh &>/dev/null; then
|
|
155
167
|
echo "ghx requires the GitHub CLI (gh). Install: https://cli.github.com" >&2
|
|
156
|
-
exit
|
|
168
|
+
exit 2
|
|
157
169
|
fi
|
|
158
170
|
if ! gh auth status &>/dev/null; then
|
|
159
171
|
echo "GitHub code search requires authentication. Run: gh auth login" >&2
|
|
160
|
-
exit
|
|
172
|
+
exit 2
|
|
161
173
|
fi
|
|
162
174
|
|
|
163
175
|
# Warn on web-only qualifiers (they silently become literal text in REST API)
|
|
@@ -168,9 +180,10 @@ search)
|
|
|
168
180
|
done
|
|
169
181
|
|
|
170
182
|
# Search with text_matches for matching context
|
|
183
|
+
per_page="${limit:-30}"
|
|
171
184
|
response=$(gh api /search/code --method GET \
|
|
172
185
|
-H "Accept: application/vnd.github.text-match+json" \
|
|
173
|
-
-f q="$query" 2>&1) || {
|
|
186
|
+
-f q="$query" -f per_page="$per_page" 2>&1) || {
|
|
174
187
|
echo "$response" >&2
|
|
175
188
|
exit 1
|
|
176
189
|
}
|
|
@@ -182,6 +195,7 @@ search)
|
|
|
182
195
|
echo "$total results (showing $count)" >&2
|
|
183
196
|
[[ "$incomplete" == "true" ]] && echo "⚠ Results may be incomplete (query timed out)" >&2
|
|
184
197
|
[[ "$total" -gt 1000 ]] 2>/dev/null && echo "⚠ Query too broad — add repo:, language:, or path: to narrow" >&2
|
|
198
|
+
[[ "$count" -eq 0 ]] && exit 1
|
|
185
199
|
|
|
186
200
|
# Output: repo path: matching context from fragment
|
|
187
201
|
# Default: 200 char window centered on the match (prevents minified JS token explosion)
|
|
@@ -233,15 +247,29 @@ search)
|
|
|
233
247
|
|
|
234
248
|
# ─── repos: search repos with README preview in 1 GraphQL call ───
|
|
235
249
|
repos)
|
|
236
|
-
|
|
250
|
+
limit=""
|
|
251
|
+
args=()
|
|
252
|
+
while [[ $# -gt 0 ]]; do
|
|
253
|
+
case "$1" in
|
|
254
|
+
--limit) [[ $# -lt 2 ]] && { echo "ghx repos: --limit requires a value" >&2; exit 2; }
|
|
255
|
+
limit="$2"; shift 2 ;;
|
|
256
|
+
--*) echo "ghx repos: unknown flag '$1'" >&2; exit 2 ;;
|
|
257
|
+
*) args+=("$1"); shift ;;
|
|
258
|
+
esac
|
|
259
|
+
done
|
|
260
|
+
query="${args[*]}"
|
|
237
261
|
if [[ -z "$query" ]]; then
|
|
238
|
-
echo "Usage: ghx repos <query>" >&2
|
|
239
|
-
exit
|
|
262
|
+
echo "Usage: ghx repos <query> [--limit N]" >&2
|
|
263
|
+
exit 2
|
|
240
264
|
fi
|
|
265
|
+
# Validate and clamp limit (GraphQL max: 100, but README fetching makes >20 expensive)
|
|
266
|
+
first="${limit:-10}"
|
|
267
|
+
[[ "$first" =~ ^[0-9]+$ ]] || { echo "ghx repos: --limit must be a number, got '$first'" >&2; exit 2; }
|
|
268
|
+
(( first > 20 )) && { echo "⚠ --limit clamped to 20 (README fetching makes larger values slow)" >&2; first=20; }
|
|
241
269
|
|
|
242
270
|
response=$(gh api graphql -f query='
|
|
243
271
|
{
|
|
244
|
-
search(query: "'"$query"'", type: REPOSITORY, first:
|
|
272
|
+
search(query: "'"$query"'", type: REPOSITORY, first: '"$first"') {
|
|
245
273
|
repositoryCount
|
|
246
274
|
nodes {
|
|
247
275
|
... on Repository {
|
|
@@ -258,6 +286,8 @@ repos)
|
|
|
258
286
|
}')
|
|
259
287
|
|
|
260
288
|
echo "$response" | jq -r '.data.search.repositoryCount | "\(.) repos found"' >&2
|
|
289
|
+
count=$(echo "$response" | jq '.data.search.nodes | length')
|
|
290
|
+
[[ "$count" -eq 0 ]] && exit 1
|
|
261
291
|
echo "$response" | jq -r '
|
|
262
292
|
.data.search.nodes[] |
|
|
263
293
|
.nameWithOwner as $name |
|
|
@@ -313,6 +343,10 @@ tree)
|
|
|
313
343
|
;;
|
|
314
344
|
|
|
315
345
|
# ─── help ───
|
|
346
|
+
version|-v|--version)
|
|
347
|
+
echo "ghx $VERSION"
|
|
348
|
+
;;
|
|
349
|
+
|
|
316
350
|
help|*)
|
|
317
351
|
cat <<'EOF'
|
|
318
352
|
ghx — GitHub code exploration for agents and humans
|
|
@@ -320,8 +354,8 @@ ghx — GitHub code exploration for agents and humans
|
|
|
320
354
|
Commands:
|
|
321
355
|
ghx explore <owner/repo> [path] Branch + tree + README in 1 API call
|
|
322
356
|
ghx read <owner/repo> <f1> [f2..] Read 1-10 files in 1 API call
|
|
323
|
-
ghx repos <query>
|
|
324
|
-
ghx search "<query>"
|
|
357
|
+
ghx repos <query> [--limit N] Search repos with README preview (default: 10)
|
|
358
|
+
ghx search "<query>" [--limit N] Code search (AND matching, default: 30)
|
|
325
359
|
ghx search --full "<query>" Code search without line truncation
|
|
326
360
|
ghx skill Output SKILL.md (for agent context injection)
|
|
327
361
|
ghx tree <owner/repo> [path] Full recursive tree listing
|
|
@@ -329,6 +363,12 @@ Commands:
|
|
|
329
363
|
Read flags:
|
|
330
364
|
--grep <pattern> Filter output to matching lines (case-insensitive, 2 lines context)
|
|
331
365
|
--lines <N-M> Extract specific line range
|
|
366
|
+
--map Structural signatures only (~92% token reduction)
|
|
367
|
+
|
|
368
|
+
Exit codes:
|
|
369
|
+
0 Success with results
|
|
370
|
+
1 No results (query valid, nothing matched)
|
|
371
|
+
2 Usage error (bad flags, missing args)
|
|
332
372
|
|
|
333
373
|
Examples:
|
|
334
374
|
ghx explore plausible/analytics
|