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.
- {stools_cli-0.1.0 → stools_cli-0.3.0}/.gitignore +1 -0
- {stools_cli-0.1.0 → stools_cli-0.3.0}/PKG-INFO +62 -26
- stools_cli-0.3.0/README.md +199 -0
- stools_cli-0.3.0/assets/logo.png +0 -0
- {stools_cli-0.1.0 → stools_cli-0.3.0}/pyproject.toml +1 -1
- {stools_cli-0.1.0 → stools_cli-0.3.0}/skills/stools/SKILL.md +22 -6
- {stools_cli-0.1.0 → stools_cli-0.3.0}/src/stools_cli/cli.py +84 -9
- stools_cli-0.3.0/src/stools_cli/skill/SKILL.md +151 -0
- stools_cli-0.1.0/README.md +0 -163
- {stools_cli-0.1.0 → stools_cli-0.3.0}/.claude-plugin/marketplace.json +0 -0
- {stools_cli-0.1.0 → stools_cli-0.3.0}/.github/workflows/publish.yml +0 -0
- {stools_cli-0.1.0 → stools_cli-0.3.0}/LICENSE +0 -0
- {stools_cli-0.1.0 → stools_cli-0.3.0}/src/stools_cli/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stools-cli
|
|
3
|
-
Version: 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
|
-
|
|
20
|
+
<p align="center">
|
|
21
|
+
<img src="assets/logo.png" alt="stools banner" width="150" />
|
|
22
|
+
</p>
|
|
21
23
|
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
| `query`
|
|
51
|
-
| `--limit N`
|
|
52
|
-
| `--fields`
|
|
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
|
|
95
|
-
|
|
96
|
-
| `app`
|
|
97
|
-
| `--language`
|
|
98
|
-
| `--
|
|
99
|
-
| `--
|
|
100
|
-
| `--
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
@@ -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`
|
|
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.
|
|
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(
|
|
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
|
|
64
|
+
if total_needed and len(all_reviews) >= total_needed:
|
|
54
65
|
break
|
|
55
66
|
|
|
56
|
-
|
|
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":
|
|
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.
|
|
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(
|
|
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).
|
stools_cli-0.1.0/README.md
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|