stools-cli 0.1.0__tar.gz → 0.3.0__tar.gz

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.
@@ -1,3 +1,4 @@
1
1
  dist/
2
2
  *.egg-info/
3
3
  __pycache__/
4
+ .DS_Store
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stools-cli
3
- Version: 0.1.0
3
+ Version: 0.3.0
4
4
  Summary: CLI to extract structured data from Steam (reviews, game search) — designed for LLM agents
5
5
  Project-URL: Repository, https://github.com/aotarola/stools
6
6
  Author: aotarola
@@ -17,20 +17,29 @@ Classifier: Topic :: Utilities
17
17
  Requires-Python: >=3.9
18
18
  Description-Content-Type: text/markdown
19
19
 
20
- # stools
20
+ <p align="center">
21
+ <img src="assets/logo.png" alt="stools banner" width="150" />
22
+ </p>
21
23
 
22
- CLI tool to extract structured data from Steam. Outputs JSON to stdout, making it easy to pipe into other tools or consume from LLM agents.
24
+ <h1 align="center">stools</h1>
25
+
26
+ <p align="center">
27
+ CLI tool to extract structured data from Steam. Outputs JSON to stdout, making it easy to pipe into other tools or consume from LLM agents.
28
+ </p>
23
29
 
24
30
  ## Installation
25
31
 
32
+ ### macOS (Homebrew)
33
+
26
34
  ```bash
35
+ brew install pipx
27
36
  pipx install stools-cli
28
37
  ```
29
38
 
30
- Or with pip:
39
+ ### Other platforms
31
40
 
32
41
  ```bash
33
- pip install stools-cli
42
+ python3 -m pip install stools-cli
34
43
  ```
35
44
 
36
45
  Requires Python 3.9+. No external dependencies.
@@ -45,11 +54,11 @@ Search Steam for games by name.
45
54
  stools search <query> [--limit N] [--fields f1,f2,...]
46
55
  ```
47
56
 
48
- | Argument | Description |
49
- |---------------|----------------------------------------------------------|
50
- | `query` | Search term (e.g. `megaman`, `dark souls`) |
51
- | `--limit N` | Max results to return. Default: `10` |
52
- | `--fields` | Comma-separated fields to keep per result (e.g. `app_id,name`) |
57
+ | Argument | Description |
58
+ | ----------- | -------------------------------------------------------------- |
59
+ | `query` | Search term (e.g. `megaman`, `dark souls`) |
60
+ | `--limit N` | Max results to return. Default: `10` |
61
+ | `--fields` | Comma-separated fields to keep per result (e.g. `app_id,name`) |
53
62
 
54
63
  #### Output schema
55
64
 
@@ -88,16 +97,18 @@ stools reviews "$APP_ID"
88
97
  Fetch all reviews for a Steam game.
89
98
 
90
99
  ```
91
- stools reviews <app_id_or_url> [--language CODE] [--limit N] [--fields f1,f2,...] [--output FILE]
100
+ stools reviews <app_id_or_url> [--language CODE] [--review-type TYPE] [--limit N] [--offset N] [--fields f1,f2,...] [--output FILE]
92
101
  ```
93
102
 
94
- | Argument | Description |
95
- |----------------|-----------------------------------------------------------------------------|
96
- | `app` | Steam app ID (`1005310`) or store URL (`https://store.steampowered.com/app/1005310/Snake_vs_Snake/`) |
97
- | `--language` | Filter by language code: `english`, `spanish`, `french`, `german`, etc. Default: `all` |
98
- | `--limit N` | Max reviews to return. `0` = all. Default: `0` |
99
- | `--fields` | Comma-separated fields to keep per review (e.g. `review,voted_up`) |
100
- | `--output FILE`| Write JSON to a file instead of stdout |
103
+ | Argument | Description |
104
+ | --------------- | ---------------------------------------------------------------------------------------------------- |
105
+ | `app` | Steam app ID (`1005310`) or store URL (`https://store.steampowered.com/app/1005310/Snake_vs_Snake/`) |
106
+ | `--language` | Filter by language code: `english`, `spanish`, `french`, `german`, etc. Default: `all` |
107
+ | `--review-type` | Filter by sentiment: `all`, `positive`, `negative`. Default: `all` |
108
+ | `--limit N` | Max reviews to return. `0` = all. Default: `0` |
109
+ | `--offset N` | Skip the first N reviews. Use with `--limit` to paginate. Default: `0` |
110
+ | `--fields` | Comma-separated fields to keep per review (e.g. `review,voted_up`) |
111
+ | `--output FILE` | Write JSON to a file instead of stdout |
101
112
 
102
113
  #### Output schema
103
114
 
@@ -147,8 +158,16 @@ stools reviews 1005310
147
158
  # Fetch only English reviews, save to file
148
159
  stools reviews 1005310 --language english --output reviews.json
149
160
 
150
- # Fetch first 10 reviews, no progress output
161
+ # Fetch first 10 reviews
151
162
  stools reviews 1005310 --limit 10
163
+
164
+ # Paginate through reviews
165
+ stools reviews 730 --limit 50
166
+ stools reviews 730 --limit 50 --offset 50
167
+
168
+ # Get only negative reviews
169
+ stools reviews 730 --review-type negative --fields review,voted_up
170
+
152
171
  # Use a full Steam URL
153
172
  stools reviews "https://store.steampowered.com/app/1005310/Snake_vs_Snake/"
154
173
 
@@ -158,21 +177,38 @@ stools reviews 1005310 --fields review,voted_up
158
177
 
159
178
  ## Exit codes
160
179
 
161
- | Code | Meaning |
162
- |------|--------------------|
163
- | `0` | Success |
164
- | `1` | Usage / input error|
165
- | `2` | Network / API error|
180
+ | Code | Meaning |
181
+ | ---- | ------------------- |
182
+ | `0` | Success |
183
+ | `1` | Usage / input error |
184
+ | `2` | Network / API error |
166
185
 
167
186
  ## Agent integration
168
187
 
169
- stools ships as a [Claude Code plugin](https://docs.anthropic.com/en/docs/claude-code). To use it in your agent:
188
+ stools bundles a [SKILL.md](skills/stools/SKILL.md) following the [agentskills.io](https://agentskills.io) open standard.
189
+
190
+ ### Claude Code
191
+
192
+ Via the plugin marketplace (installs the skill and keeps it updated):
170
193
 
171
194
  ```bash
172
195
  claude install-plugin aotarola/stools
173
196
  ```
174
197
 
175
- This registers the `stools` CLI as a skill that Claude Code can discover and invoke automatically. See [`skills/stools/SKILL.md`](skills/stools/SKILL.md) for the agent-facing documentation.
198
+ Or via the CLI:
199
+
200
+ ```bash
201
+ stools install-skill claude # → ~/.claude/skills/stools/SKILL.md
202
+ ```
203
+
204
+ ### Hermes / Codex
205
+
206
+ ```bash
207
+ stools install-skill hermes # → ~/.hermes/skills/stools/SKILL.md
208
+ stools install-skill codex # → ~/.agents/skills/stools/SKILL.md
209
+ ```
210
+
211
+ Once installed, the agent can discover and use `stools` automatically.
176
212
 
177
213
  ## Design notes
178
214
 
@@ -0,0 +1,199 @@
1
+ <p align="center">
2
+ <img src="assets/logo.png" alt="stools banner" width="150" />
3
+ </p>
4
+
5
+ <h1 align="center">stools</h1>
6
+
7
+ <p align="center">
8
+ CLI tool to extract structured data from Steam. Outputs JSON to stdout, making it easy to pipe into other tools or consume from LLM agents.
9
+ </p>
10
+
11
+ ## Installation
12
+
13
+ ### macOS (Homebrew)
14
+
15
+ ```bash
16
+ brew install pipx
17
+ pipx install stools-cli
18
+ ```
19
+
20
+ ### Other platforms
21
+
22
+ ```bash
23
+ python3 -m pip install stools-cli
24
+ ```
25
+
26
+ Requires Python 3.9+. No external dependencies.
27
+
28
+ ## Commands
29
+
30
+ ### `stools search`
31
+
32
+ Search Steam for games by name.
33
+
34
+ ```
35
+ stools search <query> [--limit N] [--fields f1,f2,...]
36
+ ```
37
+
38
+ | Argument | Description |
39
+ | ----------- | -------------------------------------------------------------- |
40
+ | `query` | Search term (e.g. `megaman`, `dark souls`) |
41
+ | `--limit N` | Max results to return. Default: `10` |
42
+ | `--fields` | Comma-separated fields to keep per result (e.g. `app_id,name`) |
43
+
44
+ #### Output schema
45
+
46
+ ```json
47
+ {
48
+ "query": "megaman",
49
+ "total_results": 10,
50
+ "results": [
51
+ {
52
+ "app_id": 742300,
53
+ "name": "Mega Man 11",
54
+ "price": { "currency": "USD", "initial": 2999, "final": 2999 },
55
+ "platforms": { "windows": true, "mac": false, "linux": false },
56
+ "metascore": "80"
57
+ }
58
+ ]
59
+ }
60
+ ```
61
+
62
+ #### Examples
63
+
64
+ ```bash
65
+ # Search for a game
66
+ stools search "megaman"
67
+
68
+ # Get top 3 results, only app_id and name
69
+ stools search "dark souls" --limit 3 --fields app_id,name
70
+
71
+ # Search then fetch reviews for the first result
72
+ APP_ID=$(stools search "snake vs snake" 2>/dev/null | jq '.results[0].app_id')
73
+ stools reviews "$APP_ID"
74
+ ```
75
+
76
+ ### `stools reviews`
77
+
78
+ Fetch all reviews for a Steam game.
79
+
80
+ ```
81
+ stools reviews <app_id_or_url> [--language CODE] [--review-type TYPE] [--limit N] [--offset N] [--fields f1,f2,...] [--output FILE]
82
+ ```
83
+
84
+ | Argument | Description |
85
+ | --------------- | ---------------------------------------------------------------------------------------------------- |
86
+ | `app` | Steam app ID (`1005310`) or store URL (`https://store.steampowered.com/app/1005310/Snake_vs_Snake/`) |
87
+ | `--language` | Filter by language code: `english`, `spanish`, `french`, `german`, etc. Default: `all` |
88
+ | `--review-type` | Filter by sentiment: `all`, `positive`, `negative`. Default: `all` |
89
+ | `--limit N` | Max reviews to return. `0` = all. Default: `0` |
90
+ | `--offset N` | Skip the first N reviews. Use with `--limit` to paginate. Default: `0` |
91
+ | `--fields` | Comma-separated fields to keep per review (e.g. `review,voted_up`) |
92
+ | `--output FILE` | Write JSON to a file instead of stdout |
93
+
94
+ #### Output schema
95
+
96
+ ```json
97
+ {
98
+ "app_id": 1005310,
99
+ "query_summary": {
100
+ "num_reviews": 33,
101
+ "review_score": 7,
102
+ "review_score_desc": "Positive",
103
+ "total_positive": 33,
104
+ "total_negative": 0,
105
+ "total_reviews": 33
106
+ },
107
+ "total_fetched": 33,
108
+ "reviews": [
109
+ {
110
+ "recommendationid": "210668843",
111
+ "author": {
112
+ "steamid": "76561198079866033",
113
+ "personaname": "username",
114
+ "num_reviews": 32,
115
+ "playtime_forever": 1554,
116
+ "playtime_at_review": 1554
117
+ },
118
+ "language": "english",
119
+ "review": "Review text here.",
120
+ "timestamp_created": 1764106395,
121
+ "timestamp_updated": 1764106395,
122
+ "voted_up": true,
123
+ "votes_up": 0,
124
+ "votes_funny": 0,
125
+ "steam_purchase": true,
126
+ "received_for_free": false,
127
+ "written_during_early_access": false
128
+ }
129
+ ]
130
+ }
131
+ ```
132
+
133
+ #### Examples
134
+
135
+ ```bash
136
+ # Fetch all reviews for a game
137
+ stools reviews 1005310
138
+
139
+ # Fetch only English reviews, save to file
140
+ stools reviews 1005310 --language english --output reviews.json
141
+
142
+ # Fetch first 10 reviews
143
+ stools reviews 1005310 --limit 10
144
+
145
+ # Paginate through reviews
146
+ stools reviews 730 --limit 50
147
+ stools reviews 730 --limit 50 --offset 50
148
+
149
+ # Get only negative reviews
150
+ stools reviews 730 --review-type negative --fields review,voted_up
151
+
152
+ # Use a full Steam URL
153
+ stools reviews "https://store.steampowered.com/app/1005310/Snake_vs_Snake/"
154
+
155
+ # Get only review text and sentiment
156
+ stools reviews 1005310 --fields review,voted_up
157
+ ```
158
+
159
+ ## Exit codes
160
+
161
+ | Code | Meaning |
162
+ | ---- | ------------------- |
163
+ | `0` | Success |
164
+ | `1` | Usage / input error |
165
+ | `2` | Network / API error |
166
+
167
+ ## Agent integration
168
+
169
+ stools bundles a [SKILL.md](skills/stools/SKILL.md) following the [agentskills.io](https://agentskills.io) open standard.
170
+
171
+ ### Claude Code
172
+
173
+ Via the plugin marketplace (installs the skill and keeps it updated):
174
+
175
+ ```bash
176
+ claude install-plugin aotarola/stools
177
+ ```
178
+
179
+ Or via the CLI:
180
+
181
+ ```bash
182
+ stools install-skill claude # → ~/.claude/skills/stools/SKILL.md
183
+ ```
184
+
185
+ ### Hermes / Codex
186
+
187
+ ```bash
188
+ stools install-skill hermes # → ~/.hermes/skills/stools/SKILL.md
189
+ stools install-skill codex # → ~/.agents/skills/stools/SKILL.md
190
+ ```
191
+
192
+ Once installed, the agent can discover and use `stools` automatically.
193
+
194
+ ## Design notes
195
+
196
+ - **JSON only**: all output goes to stdout as JSON; errors go to stderr.
197
+ - **No dependencies**: uses only Python standard library.
198
+ - **Subcommand structure**: `stools <command>` — designed to grow with more Steam data commands.
199
+ - **Agent-ready**: includes `.claude-plugin/` manifest and `skills/` for automatic discovery by LLM agents.
Binary file
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "stools-cli"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "CLI to extract structured data from Steam (reviews, game search) — designed for LLM agents"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -1,11 +1,17 @@
1
+ ---
2
+ name: stools
3
+ description: Search Steam games and fetch reviews as structured JSON. Use when the user asks about Steam game reviews, ratings, or wants to look up a game on Steam.
4
+ license: MIT
5
+ compatibility: Requires Python 3.9+ and stools-cli installed via pip or pipx.
6
+ metadata:
7
+ author: aotarola
8
+ version: "0.1.0"
9
+ ---
10
+
1
11
  # stools — Steam data extraction CLI
2
12
 
3
13
  You have access to `stools`, a CLI that fetches structured data from Steam. All output is JSON on stdout. Errors go to stderr.
4
14
 
5
- ## Requirements
6
-
7
- Python 3.9+. No external dependencies.
8
-
9
15
  ## Typical Workflow
10
16
 
11
17
  When a user asks about a Steam game by name (not by app ID or URL):
@@ -60,14 +66,16 @@ stools search <query> [--limit N] [--fields f1,f2,...]
60
66
  Fetch all reviews for a Steam game.
61
67
 
62
68
  ```
63
- stools reviews <app_id_or_url> [--language CODE] [--limit N] [--fields f1,f2,...] [--output FILE]
69
+ stools reviews <app_id_or_url> [--language CODE] [--review-type TYPE] [--limit N] [--offset N] [--fields f1,f2,...] [--output FILE]
64
70
  ```
65
71
 
66
72
  **Arguments:**
67
73
 
68
74
  - `app` (required): Steam app ID (e.g. `1005310`) or full store URL
69
75
  - `--language CODE`: Filter by language — `english`, `spanish`, `french`, `german`, etc. Default: `all`
76
+ - `--review-type TYPE`: Filter by sentiment — `all`, `positive`, `negative`. Default: `all`
70
77
  - `--limit N`: Max reviews to return. `0` = all. Default: `0`
78
+ - `--offset N`: Skip the first N reviews. Use with `--limit` to paginate. Default: `0`
71
79
  - `--fields f1,f2,...`: Comma-separated fields to keep per review. Available: `recommendationid`, `author`, `language`, `review`, `timestamp_created`, `timestamp_updated`, `voted_up`, `votes_up`, `votes_funny`, `steam_purchase`, `received_for_free`, `written_during_early_access`
72
80
  - `--output FILE`: Write JSON to a file instead of stdout
73
81
 
@@ -122,6 +130,13 @@ stools reviews 1005310 --language english --fields review,voted_up
122
130
  # Get a sample of 10 reviews
123
131
  stools reviews 1005310 --limit 10 --fields review,voted_up
124
132
 
133
+ # Paginate: first 50, then next 50
134
+ stools reviews 730 --limit 50 --fields review,voted_up
135
+ stools reviews 730 --limit 50 --offset 50 --fields review,voted_up
136
+
137
+ # Get only negative reviews
138
+ stools reviews 730 --review-type negative --fields review,voted_up
139
+
125
140
  # Save full reviews to file
126
141
  stools reviews 1005310 --output reviews.json
127
142
  ```
@@ -129,7 +144,8 @@ stools reviews 1005310 --output reviews.json
129
144
  ## Tips
130
145
 
131
146
  - Use `--fields` to reduce output size. For disambiguation, `--fields app_id,name` is usually enough.
132
- - Use `--limit` to fetch a small sample before pulling all reviews for large games.
147
+ - Use `--limit` and `--offset` to paginate through large review sets in batches.
148
+ - Use `--review-type negative` to focus on criticism without pulling all reviews.
133
149
  - The `reviews` command accepts full Steam URLs, so you can pass URLs directly from the user.
134
150
  - Timestamps are Unix epoch seconds.
135
151
  - Prices are in cents (e.g. `2999` = $29.99).
@@ -1,8 +1,9 @@
1
1
  """stools — CLI to extract structured data from Steam.
2
2
 
3
3
  Usage:
4
- stools search <query> [--limit <n>]
5
- stools reviews <app_id_or_url> [--language <code>] [--limit <n>] [--output <file>]
4
+ stools search <query> [--limit <n>] [--fields <f1,f2,...>]
5
+ stools reviews <app_id_or_url> [--language <code>] [--limit <n>] [--fields <f1,f2,...>] [--output <file>]
6
+ stools install-skill <claude|hermes|codex>
6
7
 
7
8
  All output is JSON on stdout. Errors go to stderr.
8
9
  Exit codes: 0 = success, 1 = usage error, 2 = network/API error.
@@ -10,14 +11,17 @@ Exit codes: 0 = success, 1 = usage error, 2 = network/API error.
10
11
 
11
12
  import argparse
12
13
  import json
14
+ import os
13
15
  import re
16
+ import shutil
14
17
  import sys
15
18
  import time
16
19
  import urllib.error
17
20
  import urllib.parse
18
21
  import urllib.request
22
+ from pathlib import Path
19
23
 
20
- VERSION = "0.1.0"
24
+ VERSION = "0.3.0"
21
25
 
22
26
 
23
27
  def pick_fields(items: list[dict], fields: list[str]) -> list[dict]:
@@ -42,18 +46,28 @@ def parse_app_id(value: str) -> int:
42
46
  sys.exit(1)
43
47
 
44
48
 
45
- def fetch_reviews(app_id: int, language: str = "all", limit: int = 0) -> dict:
49
+ def fetch_reviews(
50
+ app_id: int,
51
+ language: str = "all",
52
+ review_type: str = "all",
53
+ limit: int = 0,
54
+ offset: int = 0,
55
+ ) -> dict:
46
56
  """Fetch reviews for a Steam app, paginating automatically."""
57
+ total_needed = limit + offset if limit else 0
47
58
  all_reviews: list[dict] = []
48
59
  cursor = "*"
49
60
  query_summary = None
50
61
  per_page = 100
51
62
 
52
63
  while True:
53
- if limit and len(all_reviews) >= limit:
64
+ if total_needed and len(all_reviews) >= total_needed:
54
65
  break
55
66
 
56
- batch_size = min(per_page, limit - len(all_reviews)) if limit else per_page
67
+ if total_needed:
68
+ batch_size = min(per_page, total_needed - len(all_reviews))
69
+ else:
70
+ batch_size = per_page
57
71
 
58
72
  params = urllib.parse.urlencode({
59
73
  "json": 1,
@@ -61,13 +75,13 @@ def fetch_reviews(app_id: int, language: str = "all", limit: int = 0) -> dict:
61
75
  "cursor": cursor,
62
76
  "filter": "recent",
63
77
  "language": language,
64
- "review_type": "all",
78
+ "review_type": review_type,
65
79
  "purchase_type": "all",
66
80
  })
67
81
  url = f"https://store.steampowered.com/appreviews/{app_id}?{params}"
68
82
 
69
83
  try:
70
- req = urllib.request.Request(url, headers={"User-Agent": "stools/0.1.0"})
84
+ req = urllib.request.Request(url, headers={"User-Agent": "stools/0.2.0"})
71
85
  with urllib.request.urlopen(req, timeout=30) as resp:
72
86
  data = json.loads(resp.read().decode())
73
87
  except urllib.error.URLError as e:
@@ -93,6 +107,8 @@ def fetch_reviews(app_id: int, language: str = "all", limit: int = 0) -> dict:
93
107
 
94
108
  time.sleep(0.5)
95
109
 
110
+ # Apply offset and limit
111
+ all_reviews = all_reviews[offset:]
96
112
  if limit:
97
113
  all_reviews = all_reviews[:limit]
98
114
 
@@ -140,6 +156,33 @@ def fetch_search(query: str, limit: int = 10) -> dict:
140
156
  }
141
157
 
142
158
 
159
+ SKILL_TARGETS = {
160
+ "claude": Path.home() / ".claude" / "skills" / "stools",
161
+ "hermes": Path.home() / ".hermes" / "skills" / "stools",
162
+ "codex": Path.home() / ".agents" / "skills" / "stools",
163
+ }
164
+
165
+
166
+ def get_bundled_skill_dir() -> Path:
167
+ """Return the path to the bundled skill directory inside the package."""
168
+ return Path(__file__).parent / "skill"
169
+
170
+
171
+ def cmd_install_skill(args: argparse.Namespace) -> None:
172
+ agent = args.agent
173
+ target = SKILL_TARGETS[agent]
174
+ source = get_bundled_skill_dir()
175
+ skill_file = source / "SKILL.md"
176
+
177
+ if not skill_file.exists():
178
+ print(json.dumps({"error": "Bundled SKILL.md not found."}), file=sys.stderr)
179
+ sys.exit(1)
180
+
181
+ target.mkdir(parents=True, exist_ok=True)
182
+ shutil.copy2(skill_file, target / "SKILL.md")
183
+ print(json.dumps({"installed": str(target / "SKILL.md"), "agent": agent}))
184
+
185
+
143
186
  def cmd_search(args: argparse.Namespace) -> None:
144
187
  result = fetch_search(args.query, limit=args.limit)
145
188
  if args.fields:
@@ -149,7 +192,13 @@ def cmd_search(args: argparse.Namespace) -> None:
149
192
 
150
193
  def cmd_reviews(args: argparse.Namespace) -> None:
151
194
  app_id = parse_app_id(args.app)
152
- result = fetch_reviews(app_id, language=args.language, limit=args.limit)
195
+ result = fetch_reviews(
196
+ app_id,
197
+ language=args.language,
198
+ review_type=args.review_type,
199
+ limit=args.limit,
200
+ offset=args.offset,
201
+ )
153
202
  if args.fields:
154
203
  result["reviews"] = pick_fields(result["reviews"], args.fields)
155
204
 
@@ -169,6 +218,19 @@ def main() -> None:
169
218
  parser.add_argument("--version", action="version", version=f"stools {VERSION}")
170
219
  subparsers = parser.add_subparsers(dest="command", required=True)
171
220
 
221
+ # --- install-skill subcommand ---
222
+ install_parser = subparsers.add_parser(
223
+ "install-skill",
224
+ help="Install the stools skill for an AI agent.",
225
+ description="Copy the stools SKILL.md to the skills directory of the specified agent.",
226
+ )
227
+ install_parser.add_argument(
228
+ "agent",
229
+ choices=["claude", "hermes", "codex"],
230
+ help="Target agent: claude (~/.claude/skills/stools), hermes (~/.hermes/skills/stools), codex (~/.agents/skills/stools)",
231
+ )
232
+ install_parser.set_defaults(func=cmd_install_skill)
233
+
172
234
  # --- search subcommand ---
173
235
  search_parser = subparsers.add_parser(
174
236
  "search",
@@ -216,6 +278,12 @@ def main() -> None:
216
278
  metavar="CODE",
217
279
  help="Filter by language: english, spanish, french, german, etc. (default: all)",
218
280
  )
281
+ reviews_parser.add_argument(
282
+ "--review-type",
283
+ default="all",
284
+ choices=["all", "positive", "negative"],
285
+ help="Filter by review type (default: all)",
286
+ )
219
287
  reviews_parser.add_argument(
220
288
  "--limit",
221
289
  type=int,
@@ -223,6 +291,13 @@ def main() -> None:
223
291
  metavar="N",
224
292
  help="Max number of reviews to fetch. 0 = all (default: 0)",
225
293
  )
294
+ reviews_parser.add_argument(
295
+ "--offset",
296
+ type=int,
297
+ default=0,
298
+ metavar="N",
299
+ help="Skip the first N reviews. Use with --limit to paginate (default: 0)",
300
+ )
226
301
  reviews_parser.add_argument(
227
302
  "--output",
228
303
  metavar="FILE",
@@ -0,0 +1,151 @@
1
+ ---
2
+ name: stools
3
+ description: Search Steam games and fetch reviews as structured JSON. Use when the user asks about Steam game reviews, ratings, or wants to look up a game on Steam.
4
+ license: MIT
5
+ compatibility: Requires Python 3.9+ and stools-cli installed via pip or pipx.
6
+ metadata:
7
+ author: aotarola
8
+ version: "0.1.0"
9
+ ---
10
+
11
+ # stools — Steam data extraction CLI
12
+
13
+ You have access to `stools`, a CLI that fetches structured data from Steam. All output is JSON on stdout. Errors go to stderr.
14
+
15
+ ## Typical Workflow
16
+
17
+ When a user asks about a Steam game by name (not by app ID or URL):
18
+
19
+ 1. Run `stools search "<game name>" --fields app_id,name` to find matching games.
20
+ 2. If multiple results match, present the options to the user and ask which one they mean.
21
+ 3. Use the `app_id` from the chosen result to run `stools reviews <app_id>`.
22
+
23
+ When the user provides a Steam URL or app ID directly, skip to step 3.
24
+
25
+ Use `--fields` on both commands to request only the data you need and keep context small.
26
+
27
+ ## Available Commands
28
+
29
+ ### `stools search`
30
+
31
+ Search Steam for games by name.
32
+
33
+ ```
34
+ stools search <query> [--limit N] [--fields f1,f2,...]
35
+ ```
36
+
37
+ **Arguments:**
38
+
39
+ - `query` (required): Search term (e.g. `"megaman"`, `"dark souls"`, `"snake"`)
40
+ - `--limit N`: Max results to return. Default: `10`
41
+ - `--fields f1,f2,...`: Comma-separated fields to keep per result. Available: `app_id`, `name`, `price`, `platforms`, `metascore`
42
+
43
+ **Output:**
44
+
45
+ ```json
46
+ {
47
+ "query": "megaman",
48
+ "total_results": 10,
49
+ "results": [
50
+ {
51
+ "app_id": 742300,
52
+ "name": "Mega Man 11"
53
+ }
54
+ ]
55
+ }
56
+ ```
57
+
58
+ **Key fields:**
59
+
60
+ - `results[].app_id`: Use this to call `stools reviews`
61
+ - `results[].name`: Display name to show the user
62
+ - `results[].price.final`: Price in cents (divide by 100 for dollars)
63
+
64
+ ### `stools reviews`
65
+
66
+ Fetch all reviews for a Steam game.
67
+
68
+ ```
69
+ stools reviews <app_id_or_url> [--language CODE] [--review-type TYPE] [--limit N] [--offset N] [--fields f1,f2,...] [--output FILE]
70
+ ```
71
+
72
+ **Arguments:**
73
+
74
+ - `app` (required): Steam app ID (e.g. `1005310`) or full store URL
75
+ - `--language CODE`: Filter by language — `english`, `spanish`, `french`, `german`, etc. Default: `all`
76
+ - `--review-type TYPE`: Filter by sentiment — `all`, `positive`, `negative`. Default: `all`
77
+ - `--limit N`: Max reviews to return. `0` = all. Default: `0`
78
+ - `--offset N`: Skip the first N reviews. Use with `--limit` to paginate. Default: `0`
79
+ - `--fields f1,f2,...`: Comma-separated fields to keep per review. Available: `recommendationid`, `author`, `language`, `review`, `timestamp_created`, `timestamp_updated`, `voted_up`, `votes_up`, `votes_funny`, `steam_purchase`, `received_for_free`, `written_during_early_access`
80
+ - `--output FILE`: Write JSON to a file instead of stdout
81
+
82
+ **Output:**
83
+
84
+ ```json
85
+ {
86
+ "app_id": 1005310,
87
+ "query_summary": {
88
+ "num_reviews": 33,
89
+ "review_score": 7,
90
+ "review_score_desc": "Positive",
91
+ "total_positive": 33,
92
+ "total_negative": 0,
93
+ "total_reviews": 33
94
+ },
95
+ "total_fetched": 33,
96
+ "reviews": [
97
+ {
98
+ "review": "The review text content.",
99
+ "voted_up": true
100
+ }
101
+ ]
102
+ }
103
+ ```
104
+
105
+ **Key fields:**
106
+
107
+ - `query_summary.review_score_desc`: Overall rating label (e.g. "Positive", "Mixed", "Overwhelmingly Positive")
108
+ - `query_summary.total_positive` / `total_negative`: Vote counts
109
+ - `reviews[].voted_up`: `true` = positive, `false` = negative
110
+ - `reviews[].review`: The full review text
111
+ - `reviews[].playtime_forever`: Total playtime in minutes
112
+ - `reviews[].playtime_at_review`: Playtime at time of review in minutes
113
+
114
+ ## Exit Codes
115
+
116
+ `0` = success, `1` = input error, `2` = network/API error.
117
+
118
+ ## Examples
119
+
120
+ ```bash
121
+ # Search for a game (minimal output)
122
+ stools search "snake vs snake" --fields app_id,name
123
+
124
+ # Fetch reviews — just text and sentiment
125
+ stools reviews 1005310 --fields review,voted_up
126
+
127
+ # Get only English reviews
128
+ stools reviews 1005310 --language english --fields review,voted_up
129
+
130
+ # Get a sample of 10 reviews
131
+ stools reviews 1005310 --limit 10 --fields review,voted_up
132
+
133
+ # Paginate: first 50, then next 50
134
+ stools reviews 730 --limit 50 --fields review,voted_up
135
+ stools reviews 730 --limit 50 --offset 50 --fields review,voted_up
136
+
137
+ # Get only negative reviews
138
+ stools reviews 730 --review-type negative --fields review,voted_up
139
+
140
+ # Save full reviews to file
141
+ stools reviews 1005310 --output reviews.json
142
+ ```
143
+
144
+ ## Tips
145
+
146
+ - Use `--fields` to reduce output size. For disambiguation, `--fields app_id,name` is usually enough.
147
+ - Use `--limit` and `--offset` to paginate through large review sets in batches.
148
+ - Use `--review-type negative` to focus on criticism without pulling all reviews.
149
+ - The `reviews` command accepts full Steam URLs, so you can pass URLs directly from the user.
150
+ - Timestamps are Unix epoch seconds.
151
+ - Prices are in cents (e.g. `2999` = $29.99).
@@ -1,163 +0,0 @@
1
- # stools
2
-
3
- CLI tool to extract structured data from Steam. Outputs JSON to stdout, making it easy to pipe into other tools or consume from LLM agents.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- pipx install stools-cli
9
- ```
10
-
11
- Or with pip:
12
-
13
- ```bash
14
- pip install stools-cli
15
- ```
16
-
17
- Requires Python 3.9+. No external dependencies.
18
-
19
- ## Commands
20
-
21
- ### `stools search`
22
-
23
- Search Steam for games by name.
24
-
25
- ```
26
- stools search <query> [--limit N] [--fields f1,f2,...]
27
- ```
28
-
29
- | Argument | Description |
30
- |---------------|----------------------------------------------------------|
31
- | `query` | Search term (e.g. `megaman`, `dark souls`) |
32
- | `--limit N` | Max results to return. Default: `10` |
33
- | `--fields` | Comma-separated fields to keep per result (e.g. `app_id,name`) |
34
-
35
- #### Output schema
36
-
37
- ```json
38
- {
39
- "query": "megaman",
40
- "total_results": 10,
41
- "results": [
42
- {
43
- "app_id": 742300,
44
- "name": "Mega Man 11",
45
- "price": { "currency": "USD", "initial": 2999, "final": 2999 },
46
- "platforms": { "windows": true, "mac": false, "linux": false },
47
- "metascore": "80"
48
- }
49
- ]
50
- }
51
- ```
52
-
53
- #### Examples
54
-
55
- ```bash
56
- # Search for a game
57
- stools search "megaman"
58
-
59
- # Get top 3 results, only app_id and name
60
- stools search "dark souls" --limit 3 --fields app_id,name
61
-
62
- # Search then fetch reviews for the first result
63
- APP_ID=$(stools search "snake vs snake" 2>/dev/null | jq '.results[0].app_id')
64
- stools reviews "$APP_ID"
65
- ```
66
-
67
- ### `stools reviews`
68
-
69
- Fetch all reviews for a Steam game.
70
-
71
- ```
72
- stools reviews <app_id_or_url> [--language CODE] [--limit N] [--fields f1,f2,...] [--output FILE]
73
- ```
74
-
75
- | Argument | Description |
76
- |----------------|-----------------------------------------------------------------------------|
77
- | `app` | Steam app ID (`1005310`) or store URL (`https://store.steampowered.com/app/1005310/Snake_vs_Snake/`) |
78
- | `--language` | Filter by language code: `english`, `spanish`, `french`, `german`, etc. Default: `all` |
79
- | `--limit N` | Max reviews to return. `0` = all. Default: `0` |
80
- | `--fields` | Comma-separated fields to keep per review (e.g. `review,voted_up`) |
81
- | `--output FILE`| Write JSON to a file instead of stdout |
82
-
83
- #### Output schema
84
-
85
- ```json
86
- {
87
- "app_id": 1005310,
88
- "query_summary": {
89
- "num_reviews": 33,
90
- "review_score": 7,
91
- "review_score_desc": "Positive",
92
- "total_positive": 33,
93
- "total_negative": 0,
94
- "total_reviews": 33
95
- },
96
- "total_fetched": 33,
97
- "reviews": [
98
- {
99
- "recommendationid": "210668843",
100
- "author": {
101
- "steamid": "76561198079866033",
102
- "personaname": "username",
103
- "num_reviews": 32,
104
- "playtime_forever": 1554,
105
- "playtime_at_review": 1554
106
- },
107
- "language": "english",
108
- "review": "Review text here.",
109
- "timestamp_created": 1764106395,
110
- "timestamp_updated": 1764106395,
111
- "voted_up": true,
112
- "votes_up": 0,
113
- "votes_funny": 0,
114
- "steam_purchase": true,
115
- "received_for_free": false,
116
- "written_during_early_access": false
117
- }
118
- ]
119
- }
120
- ```
121
-
122
- #### Examples
123
-
124
- ```bash
125
- # Fetch all reviews for a game
126
- stools reviews 1005310
127
-
128
- # Fetch only English reviews, save to file
129
- stools reviews 1005310 --language english --output reviews.json
130
-
131
- # Fetch first 10 reviews, no progress output
132
- stools reviews 1005310 --limit 10
133
- # Use a full Steam URL
134
- stools reviews "https://store.steampowered.com/app/1005310/Snake_vs_Snake/"
135
-
136
- # Get only review text and sentiment
137
- stools reviews 1005310 --fields review,voted_up
138
- ```
139
-
140
- ## Exit codes
141
-
142
- | Code | Meaning |
143
- |------|--------------------|
144
- | `0` | Success |
145
- | `1` | Usage / input error|
146
- | `2` | Network / API error|
147
-
148
- ## Agent integration
149
-
150
- stools ships as a [Claude Code plugin](https://docs.anthropic.com/en/docs/claude-code). To use it in your agent:
151
-
152
- ```bash
153
- claude install-plugin aotarola/stools
154
- ```
155
-
156
- This registers the `stools` CLI as a skill that Claude Code can discover and invoke automatically. See [`skills/stools/SKILL.md`](skills/stools/SKILL.md) for the agent-facing documentation.
157
-
158
- ## Design notes
159
-
160
- - **JSON only**: all output goes to stdout as JSON; errors go to stderr.
161
- - **No dependencies**: uses only Python standard library.
162
- - **Subcommand structure**: `stools <command>` — designed to grow with more Steam data commands.
163
- - **Agent-ready**: includes `.claude-plugin/` manifest and `skills/` for automatic discovery by LLM agents.
File without changes