@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.
Files changed (61) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +371 -0
  3. package/dist/cache/uploads.js +48 -0
  4. package/dist/cache/uploads.js.map +1 -0
  5. package/dist/cli.js +196 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/commands/doctor.js +89 -0
  8. package/dist/commands/doctor.js.map +1 -0
  9. package/dist/commands/init.js +168 -0
  10. package/dist/commands/init.js.map +1 -0
  11. package/dist/commands/install-sdk.js +121 -0
  12. package/dist/commands/install-sdk.js.map +1 -0
  13. package/dist/commands/list.js +67 -0
  14. package/dist/commands/list.js.map +1 -0
  15. package/dist/commands/new.js +177 -0
  16. package/dist/commands/new.js.map +1 -0
  17. package/dist/commands/update.js +116 -0
  18. package/dist/commands/update.js.map +1 -0
  19. package/dist/commands/upgrade.js +93 -0
  20. package/dist/commands/upgrade.js.map +1 -0
  21. package/dist/commands/upload-all.js +250 -0
  22. package/dist/commands/upload-all.js.map +1 -0
  23. package/dist/commands/upload.js +205 -0
  24. package/dist/commands/upload.js.map +1 -0
  25. package/dist/config/schema.js +76 -0
  26. package/dist/config/schema.js.map +1 -0
  27. package/dist/config/store.js +70 -0
  28. package/dist/config/store.js.map +1 -0
  29. package/dist/manifest/builder.js +84 -0
  30. package/dist/manifest/builder.js.map +1 -0
  31. package/dist/manifest/itemTypes.js +44 -0
  32. package/dist/manifest/itemTypes.js.map +1 -0
  33. package/dist/manifest/writer.js +38 -0
  34. package/dist/manifest/writer.js.map +1 -0
  35. package/dist/sdk/installer.js +59 -0
  36. package/dist/sdk/installer.js.map +1 -0
  37. package/dist/sdk/locator.js +160 -0
  38. package/dist/sdk/locator.js.map +1 -0
  39. package/dist/steam/client.js +163 -0
  40. package/dist/steam/client.js.map +1 -0
  41. package/dist/steam/mock.js +51 -0
  42. package/dist/steam/mock.js.map +1 -0
  43. package/dist/steam/types.js +7 -0
  44. package/dist/steam/types.js.map +1 -0
  45. package/dist/ui/BulkUploadProgress.js +21 -0
  46. package/dist/ui/BulkUploadProgress.js.map +1 -0
  47. package/dist/ui/InitWizard.js +67 -0
  48. package/dist/ui/InitWizard.js.map +1 -0
  49. package/dist/ui/NewSkinWizard.js +52 -0
  50. package/dist/ui/NewSkinWizard.js.map +1 -0
  51. package/dist/ui/UploadProgress.js +46 -0
  52. package/dist/ui/UploadProgress.js.map +1 -0
  53. package/dist/utils/iconValidator.js +68 -0
  54. package/dist/utils/iconValidator.js.map +1 -0
  55. package/dist/utils/slugify.js +13 -0
  56. package/dist/utils/slugify.js.map +1 -0
  57. package/dist/utils/updateCheck.js +93 -0
  58. package/dist/utils/updateCheck.js.map +1 -0
  59. package/dist/version.js +9 -0
  60. package/dist/version.js.map +1 -0
  61. 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.tsx"],"names":[],"mappings":"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"}