@blackasteroid/riu 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +371 -0
- package/dist/cache/uploads.js +48 -0
- package/dist/cache/uploads.js.map +1 -0
- package/dist/cli.js +196 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.js +89 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +168 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/install-sdk.js +121 -0
- package/dist/commands/install-sdk.js.map +1 -0
- package/dist/commands/list.js +67 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/new.js +177 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/update.js +116 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/upgrade.js +93 -0
- package/dist/commands/upgrade.js.map +1 -0
- package/dist/commands/upload-all.js +250 -0
- package/dist/commands/upload-all.js.map +1 -0
- package/dist/commands/upload.js +205 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/config/schema.js +76 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.js +70 -0
- package/dist/config/store.js.map +1 -0
- package/dist/manifest/builder.js +84 -0
- package/dist/manifest/builder.js.map +1 -0
- package/dist/manifest/itemTypes.js +44 -0
- package/dist/manifest/itemTypes.js.map +1 -0
- package/dist/manifest/writer.js +38 -0
- package/dist/manifest/writer.js.map +1 -0
- package/dist/sdk/installer.js +59 -0
- package/dist/sdk/installer.js.map +1 -0
- package/dist/sdk/locator.js +160 -0
- package/dist/sdk/locator.js.map +1 -0
- package/dist/steam/client.js +163 -0
- package/dist/steam/client.js.map +1 -0
- package/dist/steam/mock.js +51 -0
- package/dist/steam/mock.js.map +1 -0
- package/dist/steam/types.js +7 -0
- package/dist/steam/types.js.map +1 -0
- package/dist/ui/BulkUploadProgress.js +21 -0
- package/dist/ui/BulkUploadProgress.js.map +1 -0
- package/dist/ui/InitWizard.js +67 -0
- package/dist/ui/InitWizard.js.map +1 -0
- package/dist/ui/NewSkinWizard.js +52 -0
- package/dist/ui/NewSkinWizard.js.map +1 -0
- package/dist/ui/UploadProgress.js +46 -0
- package/dist/ui/UploadProgress.js.map +1 -0
- package/dist/utils/iconValidator.js +68 -0
- package/dist/utils/iconValidator.js.map +1 -0
- package/dist/utils/slugify.js +13 -0
- package/dist/utils/slugify.js.map +1 -0
- package/dist/utils/updateCheck.js +93 -0
- package/dist/utils/updateCheck.js.map +1 -0
- package/dist/version.js +9 -0
- package/dist/version.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pablo Morales / BlackAsteroid
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# rust-icon-uploader (`riu`)
|
|
2
|
+
|
|
3
|
+
Automate uploading **icon-only Rust workshop skins** from the command line.
|
|
4
|
+
|
|
5
|
+
`riu` generates the right `manifest.txt`, validates your icon PNG, and pushes the workshop item to Steam — interactively (Ink TUI) or headless via JSON (agent-friendly). Every command supports `--dry-run` against a mock Steam client so you can exercise the whole pipeline without `libsteam_api.dylib`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Why this exists
|
|
10
|
+
|
|
11
|
+
Most Rust items aren't on Facepunch's official skinnable list (UAV, Patrol Signal, etc.), so you can't make traditional texture skins for them. But there's a workaround: you can upload a workshop item with an **empty `Textures` block** and Rust will use the workshop preview image as the in-game inventory icon.
|
|
12
|
+
|
|
13
|
+
Doing this by hand is tedious. There's a Windows-only Unity tool called *Rust Custom Icons* that automates the upload, but if you're on macOS or want to script it from CI / an agent, you're stuck. `riu` is the cross-platform replacement — Node.js, no Bun, no Zig, no Windows.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone git@github.com:BlackAsteroid/rust-icon-uploader.git
|
|
21
|
+
cd rust-icon-uploader
|
|
22
|
+
npm install
|
|
23
|
+
npm run build
|
|
24
|
+
npm link # makes the `riu` command available globally
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Requires **Node.js 20+**.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Prerequisites for real uploads
|
|
32
|
+
|
|
33
|
+
`--dry-run` works on any machine. For real uploads you need:
|
|
34
|
+
|
|
35
|
+
1. **Steam client running**, logged into the account that owns Rust (AppID 252490).
|
|
36
|
+
2. **`libsteam_api.dylib`** — `riu init` and `riu install-sdk` will **auto-detect and copy this from your local Rust install** (it ships as `RustClient.app/Contents/PlugIns/libsteam_api.bundle`, a flat universal Mach-O). You don't need to download anything from Valve.
|
|
37
|
+
|
|
38
|
+
If you don't have Rust installed locally, you can also:
|
|
39
|
+
- Install Spacewar (free, AppID 480) which ships the SDK reference binaries
|
|
40
|
+
- Pass `--from <path>` to `riu install-sdk` with a manually-acquired dylib
|
|
41
|
+
- Register as a Steamworks partner (free) and download the SDK from <https://partner.steamgames.com>
|
|
42
|
+
|
|
43
|
+
Run `riu doctor` after setup to verify everything is wired up.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# 1. one-time setup — auto-detects libsteam_api.dylib from your Rust install
|
|
51
|
+
riu init # interactive: only asks for SteamID
|
|
52
|
+
# or headless:
|
|
53
|
+
riu init --json '{"authorId":"76561198xxxxxxxxx"}' --force
|
|
54
|
+
|
|
55
|
+
# 2. verify the environment
|
|
56
|
+
riu doctor
|
|
57
|
+
|
|
58
|
+
# 3. generate a skin folder from one of the example icons
|
|
59
|
+
riu new
|
|
60
|
+
|
|
61
|
+
# 4. upload it (use --dry-run first to confirm without touching Steam)
|
|
62
|
+
riu upload ./skins/my-uav-skin --dry-run
|
|
63
|
+
riu upload ./skins/my-uav-skin
|
|
64
|
+
|
|
65
|
+
# 5. see what you've uploaded
|
|
66
|
+
riu list
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Command reference
|
|
72
|
+
|
|
73
|
+
All commands accept `--json [payload]` for headless / agent use and `--dry-run` to use the mock Steam client.
|
|
74
|
+
|
|
75
|
+
### `riu init`
|
|
76
|
+
|
|
77
|
+
One-time setup wizard. Asks for SDK path, SteamID64, default tags, etc., and writes them to `~/Library/Application Support/rust-icon-uploader-nodejs/config.json` (macOS).
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# interactive
|
|
81
|
+
riu init
|
|
82
|
+
|
|
83
|
+
# headless (for agents / CI)
|
|
84
|
+
riu init --json '{
|
|
85
|
+
"steamSdkPath": "/Users/me/steamworks_sdk",
|
|
86
|
+
"authorId": "76561198194158447",
|
|
87
|
+
"defaultTags": ["Version3", "Skin"]
|
|
88
|
+
}' --force
|
|
89
|
+
|
|
90
|
+
# show current config
|
|
91
|
+
riu init --show
|
|
92
|
+
riu init --show --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Flags:
|
|
96
|
+
- `--show` print current config
|
|
97
|
+
- `--force` overwrite existing config
|
|
98
|
+
- `--dry-run` validate without writing
|
|
99
|
+
- `--json [payload]` headless mode (payload required for write, omit for `--show`)
|
|
100
|
+
|
|
101
|
+
### `riu install-sdk`
|
|
102
|
+
|
|
103
|
+
Auto-locates `libsteam_api.dylib` (or `.bundle`) from a local Steam game install and copies it to `~/.riu/sdk/libsteam_api.dylib`. Updates the existing config to point at the managed location.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
riu install-sdk # auto-locate and install
|
|
107
|
+
riu install-sdk --dry-run --json # show what would be installed
|
|
108
|
+
riu install-sdk --from /path/to/libsteam_api.dylib # explicit source
|
|
109
|
+
riu install-sdk --force # reinstall over existing
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This command is run automatically by `riu init` if no `steamSdkPath` is provided. Useful as a standalone after installing Rust on a new machine.
|
|
113
|
+
|
|
114
|
+
### `riu doctor`
|
|
115
|
+
|
|
116
|
+
Diagnostic check: verifies config exists, dylib is present, Steam is running, SteamID format is valid, AppID is set. Exits non-zero if any check fails.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
riu doctor
|
|
120
|
+
riu doctor --json
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### `riu new`
|
|
124
|
+
|
|
125
|
+
Generates a complete skin folder (`manifest.txt` + `icon.png`).
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# interactive Ink wizard
|
|
129
|
+
riu new
|
|
130
|
+
|
|
131
|
+
# headless
|
|
132
|
+
riu new --json '{
|
|
133
|
+
"itemType": "Grenade",
|
|
134
|
+
"title": "UAV Custom Icon",
|
|
135
|
+
"description": "BusinessCore UAV signal",
|
|
136
|
+
"tags": ["Version3", "Skin"],
|
|
137
|
+
"iconPath": "/path/to/uav.png",
|
|
138
|
+
"outputDir": "./skins/uav"
|
|
139
|
+
}'
|
|
140
|
+
|
|
141
|
+
# validate without writing
|
|
142
|
+
riu new --json '{...}' --dry-run
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Icon validation** (sharp): PNG, square, dimensions in {256, 512, 1024, 2048}, ≤1MB. RGBA preferred.
|
|
146
|
+
|
|
147
|
+
**Known item types**: Grenade (UAV / Patrol Signal), Rock, Pistol, Rifle, Shotgun, SMG, Bow, Sword, Spear, Knife, Axe, Hammer, Tool, Hat, Shirt, Pants, Boots, Gloves, Container, SleepingBag, Furniture. Pass `"allowCustomItemType": true` in the payload to use a string outside this list.
|
|
148
|
+
|
|
149
|
+
### `riu upload [skin-dir]`
|
|
150
|
+
|
|
151
|
+
Uploads a single skin folder to the workshop.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# positional arg
|
|
155
|
+
riu upload ./skins/uav
|
|
156
|
+
|
|
157
|
+
# with overrides
|
|
158
|
+
riu upload ./skins/uav --title "Custom UAV v2" --tags "Version3,Skin,UAV"
|
|
159
|
+
|
|
160
|
+
# headless JSON
|
|
161
|
+
riu upload --json '{
|
|
162
|
+
"skinDir": "./skins/uav",
|
|
163
|
+
"title": "UAV Custom Icon",
|
|
164
|
+
"description": "...",
|
|
165
|
+
"tags": ["Version3","Skin"]
|
|
166
|
+
}'
|
|
167
|
+
|
|
168
|
+
# dry run with mock client (no Steam contact)
|
|
169
|
+
riu upload ./skins/uav --dry-run
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Interactive mode shows a staged Ink progress UI (validating → creating → configuring → uploading → submitting → done). JSON mode skips the UI and emits a single result document.
|
|
173
|
+
|
|
174
|
+
### `riu upload-all <parent-dir>`
|
|
175
|
+
|
|
176
|
+
Bulk-uploads every skin folder under a parent directory in sequence.
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
riu upload-all ./skins
|
|
180
|
+
riu upload-all ./skins --dry-run --json
|
|
181
|
+
riu upload-all ./skins --filter Grenade --limit 5
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Resume support**: on success, the returned `PublishedFileId` is written back into `manifest.txt`. On re-run, any folder whose manifest already has `PublishedFileId` is skipped without contacting Steam — so partial-failure recovery is automatic.
|
|
185
|
+
|
|
186
|
+
**JSON mode**: emits an NDJSON event stream so agents can consume progress live:
|
|
187
|
+
|
|
188
|
+
```jsonl
|
|
189
|
+
{"event":"start","total":3,"dryRun":true}
|
|
190
|
+
{"event":"item","index":1,"title":"uav","status":"uploading"}
|
|
191
|
+
{"event":"item","index":1,"title":"uav","status":"success","publishedFileId":"9000000001","url":"..."}
|
|
192
|
+
{"event":"item","index":2,"title":"patrol","status":"skipped","reason":"already published"}
|
|
193
|
+
{"event":"summary","total":3,"success":2,"skipped":1,"failed":0}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Flags:
|
|
197
|
+
- `--filter <itemType>` only upload skins matching the category
|
|
198
|
+
- `--limit <n>` cap number of uploads (testing)
|
|
199
|
+
- `--continue-on-error` keep going if one fails (default: true)
|
|
200
|
+
- `--dry-run` mock client
|
|
201
|
+
- `--json` NDJSON output
|
|
202
|
+
|
|
203
|
+
### `riu list`
|
|
204
|
+
|
|
205
|
+
Show skins uploaded via this tool (read from local cache).
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
riu list
|
|
209
|
+
riu list --json
|
|
210
|
+
riu list --filter Grenade
|
|
211
|
+
riu list --include-dry-run
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
By default, dry-run uploads are filtered out so the list reflects real workshop items only.
|
|
215
|
+
|
|
216
|
+
### `riu update <published-file-id>`
|
|
217
|
+
|
|
218
|
+
Re-upload the icon for an existing workshop item.
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# uses cached skinDir if available
|
|
222
|
+
riu update 3248306153 --note "icon refresh"
|
|
223
|
+
|
|
224
|
+
# or specify the folder explicitly
|
|
225
|
+
riu update 3248306153 --skin-dir ./skins/uav --note "tweaked colors"
|
|
226
|
+
|
|
227
|
+
# headless
|
|
228
|
+
riu update --json '{
|
|
229
|
+
"publishedFileId": "3248306153",
|
|
230
|
+
"skinDir": "./skins/uav",
|
|
231
|
+
"note": "icon refresh"
|
|
232
|
+
}'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Agent / scripting integration
|
|
238
|
+
|
|
239
|
+
`--json` makes every command machine-consumable. The pattern most agents will want is:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
riu init --force --json '{"steamSdkPath":"...","authorId":"..."}'
|
|
243
|
+
riu new --json '{"itemType":"Grenade","title":"...","iconPath":"...","outputDir":"./skin-x"}'
|
|
244
|
+
riu upload ./skin-x --json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Or all-at-once from a folder of pre-generated skins:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
riu upload-all ./generated-skins --json | while read line; do
|
|
251
|
+
echo "$line" | jq -r '. | "\(.event): \(.title // "")"'
|
|
252
|
+
done
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
**Exit codes**:
|
|
256
|
+
- `0` success
|
|
257
|
+
- `1` runtime error (missing config, Steam init failed, network)
|
|
258
|
+
- `2` validation error (bad input, malformed JSON, schema failure)
|
|
259
|
+
|
|
260
|
+
**Error shape** in JSON mode:
|
|
261
|
+
```json
|
|
262
|
+
{"status":"error","error":{"message":"...","field":"steamSdkPath"}}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Mock / dry-run mode
|
|
268
|
+
|
|
269
|
+
`--dry-run` swaps the real Steam client for a `MockSteamClient` that:
|
|
270
|
+
- Does **not** load `libsteam_api.dylib` (works on any machine)
|
|
271
|
+
- Does **not** require Steam to be running
|
|
272
|
+
- Returns synthetic `9_xxx_xxx_xxx` PublishedFileIds
|
|
273
|
+
- Writes cache entries with `dryRun: true` so `riu list` filters them by default
|
|
274
|
+
|
|
275
|
+
This means **`scripts/e2e-dry-run.sh` works on any machine, in CI, anywhere** — no Steam SDK required. Useful for:
|
|
276
|
+
- CI smoke tests
|
|
277
|
+
- Agent tool development
|
|
278
|
+
- Validating manifests before a real run
|
|
279
|
+
- Demos
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## How the icon-only skin trick works
|
|
284
|
+
|
|
285
|
+
A normal Rust workshop skin replaces an item's textures (diffuse, normal, etc.) by setting non-empty entries in the manifest's `Textures` dictionary. Rust's workshop client downloads those textures, applies them to the item's material at runtime, and the item *looks* different in-world.
|
|
286
|
+
|
|
287
|
+
For items NOT on Facepunch's skinnable list, that texture-replacement path doesn't work — Rust has no skinnable material for them. But the workshop client still downloads the **preview image** of every workshop item the player is subscribed to and uses it as the inventory icon. So if you upload a workshop item with:
|
|
288
|
+
|
|
289
|
+
```json
|
|
290
|
+
{
|
|
291
|
+
"Version": 3,
|
|
292
|
+
"ItemType": "Grenade",
|
|
293
|
+
"Groups": [{
|
|
294
|
+
"Textures": {}, ← empty, no texture overrides
|
|
295
|
+
"Floats": {...},
|
|
296
|
+
"Colors": {...}
|
|
297
|
+
}]
|
|
298
|
+
}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
…Rust will recognize the workshop item, see no textures to apply, and just use the **preview image** as the in-game icon for items of that ItemType. That's the entire trick.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Project layout
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
src/
|
|
309
|
+
├── cli.tsx # commander entrypoint
|
|
310
|
+
├── commands/ # one file per CLI command
|
|
311
|
+
│ ├── init.tsx
|
|
312
|
+
│ ├── doctor.tsx
|
|
313
|
+
│ ├── new.tsx
|
|
314
|
+
│ ├── upload.tsx
|
|
315
|
+
│ ├── list.tsx
|
|
316
|
+
│ ├── update.tsx
|
|
317
|
+
│ └── upload-all.tsx
|
|
318
|
+
├── ui/ # Ink components
|
|
319
|
+
│ ├── InitWizard.tsx
|
|
320
|
+
│ ├── NewSkinWizard.tsx
|
|
321
|
+
│ ├── UploadProgress.tsx
|
|
322
|
+
│ └── BulkUploadProgress.tsx
|
|
323
|
+
├── steam/ # Steam UGC layer (mockable)
|
|
324
|
+
│ ├── types.ts
|
|
325
|
+
│ ├── client.ts # real, wraps steamworks-ffi-node
|
|
326
|
+
│ └── mock.ts # fake, used by --dry-run
|
|
327
|
+
├── manifest/ # pure functions, unit tested
|
|
328
|
+
│ ├── itemTypes.ts
|
|
329
|
+
│ ├── builder.ts
|
|
330
|
+
│ ├── writer.ts
|
|
331
|
+
│ └── __tests__/
|
|
332
|
+
├── config/ # conf-backed user config + Zod schema
|
|
333
|
+
├── cache/ # uploads.json local cache
|
|
334
|
+
└── utils/ # iconValidator, slugify
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Development
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
npm run dev -- new # run via tsx without building
|
|
343
|
+
npm run build # tsc → dist/
|
|
344
|
+
npm run typecheck # tsc --noEmit
|
|
345
|
+
npm test # node:test unit tests
|
|
346
|
+
bash scripts/e2e-dry-run.sh # full dry-run smoke test
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Test config gets stored in a temp dir via `RIU_CONFIG_DIR` so it never touches your real config.
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
## Troubleshooting
|
|
354
|
+
|
|
355
|
+
**`Steamworks SDK not ready: no Steam library found`**
|
|
356
|
+
The path you gave `riu init` doesn't contain `libsteam_api.dylib`. Run `riu doctor` for a clear report.
|
|
357
|
+
|
|
358
|
+
**`Steam init failed`**
|
|
359
|
+
Steam isn't running, or the account isn't logged in, or it doesn't own AppID 252490 (Rust). The `steam_appid.txt` file is automatically managed in cwd — leftover copies are deleted on `riu shutdown`.
|
|
360
|
+
|
|
361
|
+
**`createItem returned null`**
|
|
362
|
+
Steam refused to create the workshop item. Common cause: the account hasn't accepted the workshop legal agreement. Visit any workshop submission page in a browser to accept it.
|
|
363
|
+
|
|
364
|
+
**`icon validation failed: must be square`**
|
|
365
|
+
Resize your PNG to 256/512/1024/2048 px. Sharp can do it: `npx sharp-cli resize 512 512 < bad.png > good.png`.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## License
|
|
370
|
+
|
|
371
|
+
MIT
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Local cache of skins uploaded via this tool. Lives at <cacheDir>/uploads.json.
|
|
2
|
+
//
|
|
3
|
+
// Used by `riu list` (display) and `riu update` (look up skin folder by file
|
|
4
|
+
// ID). Cache is append-only — failures don't pollute it because we only
|
|
5
|
+
// write after a successful upload.
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
function cacheFilePath(cacheDir) {
|
|
9
|
+
return join(cacheDir, "uploads.json");
|
|
10
|
+
}
|
|
11
|
+
export function readCache(cacheDir) {
|
|
12
|
+
const path = cacheFilePath(cacheDir);
|
|
13
|
+
if (!existsSync(path)) {
|
|
14
|
+
return { uploads: [] };
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
const raw = readFileSync(path, "utf8");
|
|
18
|
+
const parsed = JSON.parse(raw);
|
|
19
|
+
if (!parsed.uploads || !Array.isArray(parsed.uploads)) {
|
|
20
|
+
return { uploads: [] };
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return { uploads: [] };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function appendUpload(cacheDir, entry) {
|
|
29
|
+
const path = cacheFilePath(cacheDir);
|
|
30
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
31
|
+
const cache = readCache(cacheDir);
|
|
32
|
+
cache.uploads.push(entry);
|
|
33
|
+
writeFileSync(path, JSON.stringify(cache, null, 2) + "\n", "utf8");
|
|
34
|
+
}
|
|
35
|
+
export function findUpload(cacheDir, publishedFileId) {
|
|
36
|
+
const cache = readCache(cacheDir);
|
|
37
|
+
return cache.uploads.find((u) => u.publishedFileId === publishedFileId);
|
|
38
|
+
}
|
|
39
|
+
export function updateUploadTimestamp(cacheDir, publishedFileId) {
|
|
40
|
+
const path = cacheFilePath(cacheDir);
|
|
41
|
+
const cache = readCache(cacheDir);
|
|
42
|
+
const entry = cache.uploads.find((u) => u.publishedFileId === publishedFileId);
|
|
43
|
+
if (entry) {
|
|
44
|
+
entry.uploadedAt = new Date().toISOString();
|
|
45
|
+
writeFileSync(path, JSON.stringify(cache, null, 2) + "\n", "utf8");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=uploads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uploads.js","sourceRoot":"","sources":["../../src/cache/uploads.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,EAAE;AACF,6EAA6E;AAC7E,wEAAwE;AACxE,mCAAmC;AAEnC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAoB1C,SAAS,aAAa,CAAC,QAAgB;IACrC,OAAO,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,KAAmB;IAChE,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,eAAuB;IAClE,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,KAAK,eAAe,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,eAAuB;IAC7E,MAAM,IAAI,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,KAAK,eAAe,CAAC,CAAC;IAC/E,IAAI,KAAK,EAAE,CAAC;QACV,KAAK,CAAC,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5C,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { VERSION } from "./version.js";
|
|
4
|
+
import { maybeRunUpdateCheck } from "./utils/updateCheck.js";
|
|
5
|
+
// Background update check (skipped automatically for --json, --version, --help,
|
|
6
|
+
// and the `upgrade` subcommand). Notice prints to stderr if an update is found.
|
|
7
|
+
maybeRunUpdateCheck(process.argv);
|
|
8
|
+
function notImplemented(name, opts, payload) {
|
|
9
|
+
const msg = {
|
|
10
|
+
command: name,
|
|
11
|
+
status: "not_implemented",
|
|
12
|
+
note: "scaffold only — implementation lands in a follow-up issue",
|
|
13
|
+
options: opts,
|
|
14
|
+
payload: payload ?? null,
|
|
15
|
+
};
|
|
16
|
+
if (opts.json) {
|
|
17
|
+
process.stdout.write(JSON.stringify(msg) + "\n");
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(`[${name}] not implemented yet`);
|
|
21
|
+
if (opts.dryRun)
|
|
22
|
+
console.log(" (dry-run mode)");
|
|
23
|
+
if (payload !== undefined)
|
|
24
|
+
console.log(" payload:", payload);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function parseJsonFlag(raw) {
|
|
28
|
+
if (raw === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
// `--json` (no value) → boolean true; `--json '{...}'` → parsed object
|
|
31
|
+
if (raw === "" || raw === "true")
|
|
32
|
+
return true;
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(raw);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// commander gave us a non-JSON string → treat as boolean true
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const program = new Command();
|
|
42
|
+
program
|
|
43
|
+
.name("riu")
|
|
44
|
+
.description("Rust workshop icon uploader — automate icon-only skin uploads")
|
|
45
|
+
.version(VERSION, "-v, --version", "print version");
|
|
46
|
+
program
|
|
47
|
+
.command("init")
|
|
48
|
+
.description("one-time setup wizard")
|
|
49
|
+
.option("--json [payload]", "JSON config payload OR flag for JSON output")
|
|
50
|
+
.option("--dry-run", "validate without writing config")
|
|
51
|
+
.option("--show", "print current config and exit")
|
|
52
|
+
.option("--force", "overwrite existing config without prompting")
|
|
53
|
+
.action(async (rawOpts) => {
|
|
54
|
+
const parsed = parseJsonFlag(rawOpts.json);
|
|
55
|
+
const { runInit } = await import("./commands/init.js");
|
|
56
|
+
await runInit({
|
|
57
|
+
jsonFlag: rawOpts.json !== undefined,
|
|
58
|
+
jsonPayload: typeof parsed === "object" && parsed !== null ? parsed : undefined,
|
|
59
|
+
show: rawOpts.show ?? false,
|
|
60
|
+
force: rawOpts.force ?? false,
|
|
61
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
program
|
|
65
|
+
.command("upgrade")
|
|
66
|
+
.description("upgrade riu to the latest version on npm")
|
|
67
|
+
.option("--check", "only check for updates, don't install")
|
|
68
|
+
.option("--dry-run", "show what would be run without installing")
|
|
69
|
+
.option("--json", "structured JSON output")
|
|
70
|
+
.action(async (rawOpts) => {
|
|
71
|
+
const { runUpgrade } = await import("./commands/upgrade.js");
|
|
72
|
+
await runUpgrade({
|
|
73
|
+
check: rawOpts.check ?? false,
|
|
74
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
75
|
+
json: rawOpts.json ?? false,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
program
|
|
79
|
+
.command("install-sdk")
|
|
80
|
+
.description("auto-locate libsteam_api.dylib from a local Steam game install and copy it to ~/.riu/sdk/")
|
|
81
|
+
.option("--from <path>", "install from an explicit path instead of auto-locating")
|
|
82
|
+
.option("--force", "reinstall even if SDK is already present")
|
|
83
|
+
.option("--dry-run", "show what would be installed without copying")
|
|
84
|
+
.option("--json", "structured JSON output")
|
|
85
|
+
.action(async (rawOpts) => {
|
|
86
|
+
const { runInstallSdk } = await import("./commands/install-sdk.js");
|
|
87
|
+
runInstallSdk({
|
|
88
|
+
from: rawOpts.from,
|
|
89
|
+
force: rawOpts.force ?? false,
|
|
90
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
91
|
+
json: rawOpts.json ?? false,
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
program
|
|
95
|
+
.command("doctor")
|
|
96
|
+
.description("validate Steam SDK + dylib + auth + Rust running")
|
|
97
|
+
.option("--json", "structured JSON output")
|
|
98
|
+
.action(async (rawOpts) => {
|
|
99
|
+
const { runDoctor } = await import("./commands/doctor.js");
|
|
100
|
+
runDoctor({ json: rawOpts.json ?? false });
|
|
101
|
+
});
|
|
102
|
+
program
|
|
103
|
+
.command("new")
|
|
104
|
+
.description("generate a skin folder (manifest.txt + icon.png)")
|
|
105
|
+
.option("--json [payload]", "JSON payload OR flag for JSON output")
|
|
106
|
+
.option("--dry-run", "validate but don't write files")
|
|
107
|
+
.action(async (rawOpts) => {
|
|
108
|
+
const parsed = parseJsonFlag(rawOpts.json);
|
|
109
|
+
const { runNew } = await import("./commands/new.js");
|
|
110
|
+
await runNew({
|
|
111
|
+
jsonFlag: rawOpts.json !== undefined,
|
|
112
|
+
jsonPayload: typeof parsed === "object" && parsed !== null ? parsed : undefined,
|
|
113
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
program
|
|
117
|
+
.command("upload")
|
|
118
|
+
.description("upload a single skin folder to the Rust workshop")
|
|
119
|
+
.argument("[skin-dir]", "path to skin folder containing manifest.txt + icon.png")
|
|
120
|
+
.option("--json [payload]", "JSON payload OR flag for JSON output")
|
|
121
|
+
.option("--dry-run", "use mock Steam client, don't actually upload")
|
|
122
|
+
.option("--title <title>", "override workshop item title")
|
|
123
|
+
.option("--description <desc>", "override workshop item description")
|
|
124
|
+
.option("--tags <tags>", "comma-separated tags")
|
|
125
|
+
.action(async (skinDir, rawOpts) => {
|
|
126
|
+
const parsed = parseJsonFlag(rawOpts.json);
|
|
127
|
+
const { runUpload } = await import("./commands/upload.js");
|
|
128
|
+
await runUpload({
|
|
129
|
+
jsonFlag: rawOpts.json !== undefined,
|
|
130
|
+
jsonPayload: typeof parsed === "object" && parsed !== null ? parsed : undefined,
|
|
131
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
132
|
+
title: rawOpts.title,
|
|
133
|
+
description: rawOpts.description,
|
|
134
|
+
tags: rawOpts.tags,
|
|
135
|
+
positionalSkinDir: skinDir,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
program
|
|
139
|
+
.command("upload-all")
|
|
140
|
+
.description("bulk upload a parent folder of skin folders")
|
|
141
|
+
.argument("<parent-dir>", "directory containing one subfolder per skin")
|
|
142
|
+
.option("--json", "NDJSON event stream output")
|
|
143
|
+
.option("--dry-run", "use mock Steam client")
|
|
144
|
+
.option("--continue-on-error", "keep going if one skin fails", true)
|
|
145
|
+
.option("--limit <n>", "only upload first N skins", (v) => parseInt(v, 10))
|
|
146
|
+
.option("--filter <itemType>", "only upload skins matching this itemType")
|
|
147
|
+
.action(async (parentDir, rawOpts) => {
|
|
148
|
+
const { runUploadAll } = await import("./commands/upload-all.js");
|
|
149
|
+
await runUploadAll({
|
|
150
|
+
parentDir,
|
|
151
|
+
jsonFlag: rawOpts.json ?? false,
|
|
152
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
153
|
+
continueOnError: rawOpts.continueOnError ?? true,
|
|
154
|
+
limit: rawOpts.limit,
|
|
155
|
+
filter: rawOpts.filter,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
program
|
|
159
|
+
.command("list")
|
|
160
|
+
.description("list skins uploaded via this tool")
|
|
161
|
+
.option("--json", "structured JSON output")
|
|
162
|
+
.option("--filter <itemType>", "only show skins matching this itemType")
|
|
163
|
+
.option("--include-dry-run", "include dry-run uploads in the output")
|
|
164
|
+
.action(async (rawOpts) => {
|
|
165
|
+
const { runList } = await import("./commands/list.js");
|
|
166
|
+
runList({
|
|
167
|
+
json: rawOpts.json ?? false,
|
|
168
|
+
filter: rawOpts.filter,
|
|
169
|
+
includeDryRun: rawOpts.includeDryRun ?? false,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
program
|
|
173
|
+
.command("update")
|
|
174
|
+
.description("re-upload icon for an existing workshop item")
|
|
175
|
+
.argument("<published-file-id>", "PublishedFileId from a previous upload")
|
|
176
|
+
.option("--skin-dir <dir>", "skin folder if not in local cache")
|
|
177
|
+
.option("--note <note>", "change note for the update", "icon update")
|
|
178
|
+
.option("--json [payload]", "JSON payload OR flag for JSON output")
|
|
179
|
+
.option("--dry-run", "use mock Steam client")
|
|
180
|
+
.action(async (publishedFileId, rawOpts) => {
|
|
181
|
+
const parsed = parseJsonFlag(rawOpts.json);
|
|
182
|
+
const { runUpdate } = await import("./commands/update.js");
|
|
183
|
+
await runUpdate({
|
|
184
|
+
jsonFlag: rawOpts.json !== undefined,
|
|
185
|
+
jsonPayload: typeof parsed === "object" && parsed !== null ? parsed : undefined,
|
|
186
|
+
dryRun: rawOpts.dryRun ?? false,
|
|
187
|
+
publishedFileId,
|
|
188
|
+
skinDir: rawOpts.skinDir,
|
|
189
|
+
note: rawOpts.note ?? "icon update",
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
193
|
+
process.stderr.write(`error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|
|
196
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"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"}
|