@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.
Files changed (4) hide show
  1. package/README.md +3 -2
  2. package/SKILL.md +11 -2
  3. package/ghx +56 -16
  4. 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 in 1 GraphQL call
26
- ghx search "<query>" # Code search (REST API, AND matching, shows matching lines)
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 1
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
- for arg in "$@"; do
141
- case "$arg" in
142
- --full) full_mode=1 ;;
143
- *) args+=("$arg") ;;
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 1
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 1
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 1
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
- query="$*"
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 1
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: 5) {
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> Search repos with README preview in 1 GraphQL call
324
- ghx search "<query>" Code search (AND matching, qualifiers: repo: path: language: extension: filename:)
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gkoreli/ghx",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "GitHub code exploration for agents and humans. Batch file reads, code maps, search — all via gh CLI.",
5
5
  "bin": {
6
6
  "ghx": "./ghx"