@nekosuneprojects/warframe-api 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +298 -298
  3. package/data/characters/amesha.json +226 -226
  4. package/data/characters/ash.json +1354 -1354
  5. package/data/characters/ashprime.json +1994 -1994
  6. package/data/characters/atlas.json +916 -916
  7. package/data/characters/atlasprime.json +1632 -1632
  8. package/data/characters/banshee.json +1036 -1036
  9. package/data/characters/bansheeprime.json +1750 -1750
  10. package/data/characters/baruuk.json +555 -555
  11. package/data/characters/baruukprime.json +1298 -1298
  12. package/data/characters/bonewidow.json +354 -354
  13. package/data/characters/caliban.json +434 -434
  14. package/data/characters/calibanprime.json +683 -683
  15. package/data/characters/chroma.json +1344 -1344
  16. package/data/characters/chromaprime.json +1977 -1977
  17. package/data/characters/citrine.json +220 -220
  18. package/data/characters/citrineprime.json +62 -62
  19. package/data/characters/cyte09.json +218 -218
  20. package/data/characters/dagath.json +194 -194
  21. package/data/characters/dante.json +223 -223
  22. package/data/characters/elytron.json +300 -300
  23. package/data/characters/ember.json +1209 -1209
  24. package/data/characters/emberprime.json +1678 -1678
  25. package/data/characters/equinox.json +958 -958
  26. package/data/characters/equinoxprime.json +1644 -1644
  27. package/data/characters/excalibur.json +1603 -1603
  28. package/data/characters/excaliburprime.json +1524 -1524
  29. package/data/characters/excaliburumbra.json +397 -397
  30. package/data/characters/follie.json +246 -246
  31. package/data/characters/frost.json +1330 -1330
  32. package/data/characters/frostprime.json +1686 -1686
  33. package/data/characters/gara.json +925 -925
  34. package/data/characters/garaprime.json +1494 -1494
  35. package/data/characters/garuda.json +851 -851
  36. package/data/characters/garudaprime.json +1449 -1449
  37. package/data/characters/gauss.json +506 -506
  38. package/data/characters/gaussprime.json +1077 -1077
  39. package/data/characters/grendel.json +463 -463
  40. package/data/characters/grendelprime.json +772 -772
  41. package/data/characters/gyre.json +381 -381
  42. package/data/characters/gyreprime.json +403 -403
  43. package/data/characters/harrow.json +916 -916
  44. package/data/characters/harrowprime.json +1700 -1700
  45. package/data/characters/helminth.json +220 -220
  46. package/data/characters/hildryn.json +627 -627
  47. package/data/characters/hildrynprime.json +996 -996
  48. package/data/characters/hydroid.json +1028 -1028
  49. package/data/characters/hydroidprime.json +1621 -1621
  50. package/data/characters/inaros.json +976 -976
  51. package/data/characters/inarosprime.json +1721 -1721
  52. package/data/characters/itzal.json +465 -465
  53. package/data/characters/ivara.json +1349 -1349
  54. package/data/characters/ivaraprime.json +1921 -1921
  55. package/data/characters/jade.json +240 -240
  56. package/data/characters/khora.json +804 -804
  57. package/data/characters/khoraprime.json +1403 -1403
  58. package/data/characters/koumei.json +221 -221
  59. package/data/characters/kullervo.json +191 -191
  60. package/data/characters/lavos.json +511 -511
  61. package/data/characters/lavosprime.json +711 -711
  62. package/data/characters/limbo.json +1363 -1363
  63. package/data/characters/limboprime.json +2084 -2084
  64. package/data/characters/loki.json +1209 -1209
  65. package/data/characters/lokiprime.json +1457 -1457
  66. package/data/characters/mag.json +2089 -2089
  67. package/data/characters/magprime.json +2532 -2532
  68. package/data/characters/mesa.json +1195 -1195
  69. package/data/characters/mesaprime.json +2059 -2059
  70. package/data/characters/mirage.json +1561 -1561
  71. package/data/characters/mirageprime.json +2257 -2257
  72. package/data/characters/narin.json +43 -43
  73. package/data/characters/nekros.json +1235 -1235
  74. package/data/characters/nekrosprime.json +1882 -1882
  75. package/data/characters/nezha.json +868 -868
  76. package/data/characters/nezhaprime.json +1695 -1695
  77. package/data/characters/nidus.json +1027 -1027
  78. package/data/characters/nidusprime.json +1706 -1706
  79. package/data/characters/nokko.json +308 -308
  80. package/data/characters/nova.json +1226 -1226
  81. package/data/characters/novaprime.json +1667 -1667
  82. package/data/characters/nyx.json +1610 -1610
  83. package/data/characters/nyxprime.json +1967 -1967
  84. package/data/characters/oberon.json +1249 -1249
  85. package/data/characters/oberonprime.json +1872 -1872
  86. package/data/characters/octavia.json +1049 -1049
  87. package/data/characters/octaviaprime.json +1735 -1735
  88. package/data/characters/odonata.json +336 -336
  89. package/data/characters/odonataprime.json +787 -787
  90. package/data/characters/oraxia.json +246 -246
  91. package/data/characters/orionsirius.json +134 -134
  92. package/data/characters/protea.json +521 -521
  93. package/data/characters/proteaprime.json +992 -992
  94. package/data/characters/qorvex.json +214 -214
  95. package/data/characters/revenant.json +931 -931
  96. package/data/characters/revenantprime.json +1494 -1494
  97. package/data/characters/rhino.json +1344 -1344
  98. package/data/characters/rhinoprime.json +1675 -1675
  99. package/data/characters/saryn.json +1210 -1210
  100. package/data/characters/sarynprime.json +2042 -2042
  101. package/data/characters/sevagoth.json +478 -478
  102. package/data/characters/sevagothprime.json +910 -910
  103. package/data/characters/siriusorion.json +247 -247
  104. package/data/characters/styanax.json +320 -320
  105. package/data/characters/styanaxprime.json +321 -321
  106. package/data/characters/temple.json +271 -271
  107. package/data/characters/titania.json +1227 -1227
  108. package/data/characters/titaniaprime.json +2000 -2000
  109. package/data/characters/trinity.json +1097 -1097
  110. package/data/characters/trinityprime.json +1849 -1849
  111. package/data/characters/uriel.json +215 -215
  112. package/data/characters/valkyr.json +1165 -1165
  113. package/data/characters/valkyrprime.json +1969 -1969
  114. package/data/characters/vauban.json +1067 -1067
  115. package/data/characters/vaubanprime.json +1614 -1614
  116. package/data/characters/voidrig.json +355 -355
  117. package/data/characters/volt.json +1324 -1324
  118. package/data/characters/voltprime.json +1843 -1843
  119. package/data/characters/voruna.json +293 -293
  120. package/data/characters/vorunaprime.json +431 -431
  121. package/data/characters/wisp.json +824 -824
  122. package/data/characters/wispprime.json +908 -908
  123. package/data/characters/wukong.json +927 -927
  124. package/data/characters/wukongprime.json +1754 -1754
  125. package/data/characters/xaku.json +620 -620
  126. package/data/characters/xakuprime.json +879 -879
  127. package/data/characters/yareli.json +409 -409
  128. package/data/characters/yareliprime.json +683 -683
  129. package/data/characters/zephyr.json +1149 -1149
  130. package/data/characters/zephyrprime.json +1864 -1864
  131. package/data/codes/warframe.json +20824 -20824
  132. package/data/meta.json +14 -14
  133. package/index.js +11 -11
  134. package/package.json +50 -50
  135. package/src/catalog.js +105 -105
  136. package/src/client-error.js +9 -9
  137. package/src/data-source.js +7 -7
  138. package/src/data-store.js +138 -138
  139. package/src/provider.js +22 -22
  140. package/src/tracker.js +115 -115
  141. package/src/warframe-client.js +57 -57
  142. package/src/warframe-codes.js +82 -82
  143. package/src/wfcd.js +93 -93
package/README.md CHANGED
@@ -1,298 +1,298 @@
1
- # @nekosuneprojects/warframe-api
2
-
3
- **Warframe live-game information, fully local game/character JSON, and redeem-code/glyph records.** A CommonJS npm package by NekoSuneVR, currently version **1.0.0**.
4
-
5
- - Live data: [WarframeStat.us](https://api.warframestat.us/) — public community API for worldstate, equipment, drops, rivens and supported profiles (not an official Digital Extremes API).
6
- - Local character data: **129 bundled JSON files** from [game-tracker → JsonData → games/warframe](https://github.com/NekoSuneProjectsNPM/game-tracker/tree/JsonData/games/warframe), including `meta.json`, **Narin** and **Citrine Prime** (Update 44).
7
- - Local codes: **one bundled `codes/games/warframe.json` snapshot** from [JsonData](https://github.com/NekoSuneProjectsNPM/game-tracker/blob/JsonData/codes/games/warframe.json), containing 503 `Active`, 32 `Expired`, and 0 `Unknown` entries at the time of integration. Status information is a *snapshot*, not a promise that any code is still redeemable.
8
- - Optional separate GitHub synchronization for characters and codes. Offline queries work immediately using bundled JSON, with no API key or download required.
9
- - Direct API URL is fixed in the package. **Do not configure a custom API URL.** The documented WarframeStat.us public endpoints do not require an API key.
10
-
11
- ## Installation
12
-
13
- Once manually released to npmjs.org:
14
-
15
- ```bash
16
- npm install @nekosuneprojects/warframe-api
17
- ```
18
-
19
- **Status:** The source is on the `warframe-api` branch. Creating this branch does not publish the npm package. GitHub Actions publishing remains manual and uses the committed `package.json` version exactly.
20
-
21
- ## Initialise a Warframe tracker
22
-
23
- ```js
24
- const Warframe = require('@nekosuneprojects/warframe-api');
25
-
26
- const warframeData = Warframe({
27
- platform: 'pc',
28
- language: 'en',
29
- timeoutMs: 15000,
30
- // Optional for live GitHub sync only:
31
- cacheDir: './storage/warframe',
32
- githubToken: process.env.GITHUB_DATA_TOKEN
33
- });
34
- ```
35
-
36
- No credential is required for bundled JSON or the public WarframeStat.us API. A **GitHub read-only Contents token** may be required to update JSON from the `JsonData` branch if the GitHub repository is private. Store it in an environment variable, not in source control.
37
-
38
- ### Local game metadata — `About()`
39
-
40
- ```js
41
- const aboutWarframe = await warframeData.About();
42
- console.log(aboutWarframe.name);
43
- console.log(aboutWarframe.Platform);
44
- console.log(aboutWarframe.developers);
45
- ```
46
-
47
- Reads the full `meta.json`, including developer, publisher, genre, description and supported platforms. The stored field names are preserved unchanged.
48
-
49
- ### Local Warframe characters — `search()` and `character()`
50
-
51
- ```js
52
- const matches = await warframeData.search('Ash');
53
- // Each match contains an id, name, type and path.
54
- // Select an exact result: Ash vs Ash Prime.
55
-
56
- const normalAsh = await warframeData.character('ash');
57
- const primeAsh = await warframeData.character('ashprime');
58
- console.log(normalAsh.abilities, primeAsh.armor);
59
- ```
60
-
61
- Search scans local JSON filenames, names, aliases, and nested strings. Results are objects with `query`, `total`, `results`, `message`. A search with no results returns an empty list and a not-found message. Exact character lookups return the **full JSON record**, including nested details and abilities, or throw `NOT_FOUND`.
62
-
63
- Convenience methods: `warframeData.record(category,id)`, `warframeData.item(id)`, `warframeData.map(id)`, `warframeData.data.get(relativePath)`, `warframeData.data.list()`. If that category is not present in the Warframe JSON source, exact lookup returns `NOT_FOUND`.
64
-
65
- ## Warframe game codes and creator glyphs
66
-
67
- The codes source has a different structure from character records: it is a **single JSON object with `Active`, `Expired`, and `Unknown` arrays**. Individual entries include `id`, `game`, `code` (sometimes `null`), `category`, `status`, `verificationStatus`, `items`, `expiry`, `redeemUrl`, `claimType`, `requirements` and source metadata.
68
-
69
- **Some active creator-glyph entries have `code: null`.** They describe giveaways, social links or eligibility requirements, not text redeem codes. The module preserves them, and `hasCode` allows filtering.
70
-
71
- ```js
72
- // Full, original grouped JSON:
73
- const grouped = await warframeData.codes.raw();
74
-
75
- // Read lists with optional filters:
76
- const active = await warframeData.codes.active();
77
- const expired = await warframeData.codes.expired();
78
- const unknown = await warframeData.codes.unknown();
79
-
80
- // Actual text codes only:
81
- const activeRedeemCodes = await warframeData.codes.active({
82
- hasCode: true
83
- });
84
-
85
- // Creator-glyph acquisition instructions without text codes:
86
- const glyphInstructions = await warframeData.codes.active({
87
- category: 'creator-glyph',
88
- hasCode: false,
89
- limit: 20
90
- });
91
-
92
- // Search by code, item name, ID, type, or notes:
93
- const codeMatches = await warframeData.codes.search('QUINCY');
94
-
95
- // Get one full code/glyph entry by text code or numeric ID:
96
- const quincy = await warframeData.codes.get('1999-QUINCY');
97
- const sameEntry = await warframeData.code(quincy.id);
98
-
99
- const summary = await warframeData.codes.summary();
100
- console.log(summary);
101
- ```
102
-
103
- | Method | Description |
104
- | --- | --- |
105
- | `codes.raw()` | Full `Active` / `Expired` / `Unknown` JSON |
106
- | `codes.list({status,category,claimType,hasCode,limit})` | Flatten entries and apply filters |
107
- | `codes.active(options)` | Entries marked active in the stored dataset |
108
- | `codes.expired(options)` | Entries marked expired in the stored dataset |
109
- | `codes.unknown(options)` | Entries with unknown status |
110
- | `codes.search(query,options)` | Search code strings, IDs, reward names, notes and categories |
111
- | `codes.get(codeOrId)` | Return a complete unique record; throws `NOT_FOUND` or `AMBIGUOUS` |
112
- | `code(codeOrId)` | Game-level alias for `codes.get` |
113
- | `codes.summary()` | Counts by status and whether an actual code string exists |
114
- | `codes.sync()` | Explicitly refresh only `JsonData/codes/games/warframe.json` |
115
-
116
- **Data freshness:** An `active` label reflects the supplied JSON snapshot and its verification timestamps, not a live check of redemption availability. For real-world usage, inspect `expiry`, `verificationStatus`, `lastVerifiedAt`, `requirements` and `sourceUrls`.
117
-
118
- ### Missing Warframe JSON and automatic retrieval on exact lookups
119
-
120
- The bundled character snapshot already includes Narin and Citrine Prime. If another character is added to `JsonData` later and is not bundled or cached, an exact `character(id)` lookup will attempt one GitHub synchronization and then read the newly downloaded record:
121
-
122
- ```js
123
- // Character is fetched from JsonData if not found locally:
124
- const futureCharacter = await warframeData.character('futurecharacterid');
125
-
126
- // Explicitly discover new files in search results:
127
- const matches = await warframeData.search('Future', { refresh: true });
128
-
129
- // Skip GitHub if you require strictly offline behaviour:
130
- const onlyLocal = await warframeData.search('Ash');
131
- const character = await warframeData.character('ash', { refresh: false });
132
- ```
133
-
134
- Existing local records are read immediately without network requests. For updates to *existing* records, call `data.sync()` explicitly (or use `data.ensure('characters/ash.json', { refresh: true })`). This keeps offline usage fast and avoids silently making network requests for every search. Missing files, changed files, and missing cached copies are downloaded using GitHub SHA comparisons; successfully cached data survives offline operation. A private repository still requires a read token to fetch additions.
135
-
136
- ### Separate incremental JSON synchronization
137
-
138
- ```js
139
- // Characters and meta.json (games/warframe/)
140
- const characterSync = await warframeData.data.sync();
141
-
142
- // Warframe codes only (codes/games/warframe.json)
143
- const codeSync = await warframeData.codes.sync();
144
-
145
- console.log(characterSync, codeSync);
146
- ```
147
-
148
- Synchronization compares GitHub blob SHA values, downloads changed JSON only and caches it atomically. Unchanged files are skipped; existing local data remains available offline. A successful sync can refresh data without publishing a new npm version. The two sync methods use separate cache directories, so their manifests do not conflict. **No timer or automatic background synchronization is started.**
149
-
150
- When the source is private, pass a suitable `githubToken` option. GitHub API rate limits, internet access, and repository permissions apply. Failed updates do not invalidate previously bundled data.
151
-
152
- ## Live WarframeStat.us endpoints
153
-
154
- ```js
155
- const alerts = await warframeData.api.alerts();
156
- const fissures = await warframeData.api.fissures();
157
- const invasions = await warframeData.api.invasions();
158
- const sortie = await warframeData.api.sortie();
159
- const cycles = await warframeData.api.cetusCycle();
160
- const trader = await warframeData.api.voidTrader();
161
- const liveWorldstate = await warframeData.api.worldstate();
162
-
163
- const liveWarframes = await warframeData.api.warframes();
164
- const weapon = await warframeData.api.weapon('Soma');
165
- const drops = await warframeData.api.drops();
166
-
167
- const playerProfile = await warframeData.api.profile('PlayerName');
168
- const playerStats = await warframeData.api.profileStats('PlayerName');
169
- ```
170
-
171
- All live methods are grouped under `api` so the live equipment endpoints never overwrite offline JSON methods. Network requests use the built-in WarframeStat.us URL; availability and response content depend on the upstream service.
172
-
173
- ### Full method reference
174
-
175
- | Category | Exported methods |
176
- | --- | --- |
177
- | Live worldstate | `api.worldstate()`, `api.worldstateField(field,options)` |
178
- | Worldstate convenience methods | `api.alerts()`, `api.arbitration()`, `api.archonHunt()`, `api.cambionCycle()`, `api.cetusCycle()`, `api.conclaveChallenges()`, `api.constructionProgress()`, `api.dailyDeals()`, `api.darvo()`, `api.earthCycle()`, `api.events()`, `api.fissures()`, `api.flashSales()`, `api.globalUpgrades()`, `api.invasions()`, `api.kuva()`, `api.news()`, `api.nightwave()`, `api.persistentEnemies()`, `api.sentientOutposts()`, `api.simaris()`, `api.sortie()`, `api.steelPath()`, `api.syndicateMissions()`, `api.thermia()`, `api.vallisCycle()`, `api.voidTrader()`, `api.weeklyChallenges()` |
179
- | Equipment and static item data | `api.warframes()`, `api.weapons()`, `api.items()`, `api.mods()`, `api.warframe(name)`, `api.weapon(name)`, `api.item(name)`, `api.mod(name)` |
180
- | Equipment search | `api.searchWarframes(query)`, `api.searchWeapons(query)`, `api.searchItems(query)`, `api.searchMods(query)`, `api.list(kind)`, `api.get(kind,name)`, `api.search(kind,query)` |
181
- | Drops and rivens | `api.drops()`, `api.searchDrops(query,options)`, `api.rivens()`, `api.searchRivens(weapon)` |
182
- | Player profile | `api.profile(playerId)`, `api.profileStats(playerId)`, `api.profileXP(playerId)`, `getPlayerStats(playerId)` |
183
- | Other static and feeds | `api.staticData(key)`, `api.searchStatic(key,query)`, `api.arcanes()`, `api.synthTargets()`, `api.rss()`, `api.twitter()`, `api.wfinfoFilteredItems()`, `api.wfinfoPrices()` |
184
- | Diagnostics | `api.health()` |
185
- | WFCD datasets | `wfcd.drops.*`, `wfcd.items.*`, `wfcd.relics.*`, `wfcd.patchlogs.*` |
186
- | Optional WFCD modules | `market(options)`, `createUpdateMonitor(options)` |
187
- | Offline game and characters | `About()`, `search(query,{refresh})`, `character(id,{refresh})`, `record(category,id)`, `data.get(path)`, `data.ensure(path,{refresh})`, `data.list()`, `data.sync()` |
188
- | Offline codes | `codes.raw()`, `codes.list()`, `codes.active()`, `codes.expired()`, `codes.unknown()`, `codes.search()`, `codes.get()`, `codes.summary()`, `codes.sync()`, `code(codeOrId)` |
189
-
190
- Some community API endpoints may return 404, change behaviour, or be temporarily unavailable. HTTP failures are surfaced as `TrackerError` with `status` when available. Check [WarframeStat.us docs](https://docs.warframestat.us/) for current upstream availability.
191
-
192
- ## Warframe Community Developers (WFCD) integrations
193
-
194
- Four upstream WFCD datasets are available through fixed, on-demand GitHub sources with **persistent disk caching and SHA verification**. Data is downloaded only when requested, not at package startup. These repositories are third-party community sources and may contain upstream-stale records. The default package does not bulk-download all their assets.
195
-
196
- | WFCD project | Integrated access | What it provides |
197
- | --- | --- | --- |
198
- | [warframe-drop-data](https://github.com/WFCD/warframe-drop-data) | `wfcd.drops` | Drop tables, relic drops, mission rewards, bounty rewards, build metadata |
199
- | [warframe-items](https://github.com/WFCD/warframe-items) | `wfcd.items` | Compiled items, Warframes, weapons, mods, arcanes, crafting data |
200
- | [warframe-relic-data](https://github.com/WFCD/warframe-relic-data) | `wfcd.relics` | Relic JSON from the upstream `development` branch; the upstream project warns its data may be stale |
201
- | [warframe-patchlogs](https://github.com/WFCD/warframe-patchlogs) | `wfcd.patchlogs` | Parsed patch note JSON |
202
- | [warframe-nexus-query](https://github.com/WFCD/warframe-nexus-query) | `market()` | Optional market price query library using Warframe.Market |
203
- | [warframe-updates](https://github.com/WFCD/warframe-updates) | `createUpdateMonitor()` | Optional event-driven patch deployment monitor |
204
-
205
- ```js
206
- const Warframe = require('@nekosuneprojects/warframe-api');
207
- const warframeData = Warframe({ cacheDir: './storage/warframe' });
208
-
209
- // Directly read individual WFCD JSON files; cached locally after first use.
210
- const dropInfo = await warframeData.wfcd.drops.info();
211
- const drops = await warframeData.wfcd.drops.all();
212
- const missionRewards = await warframeData.wfcd.drops.missionRewards();
213
-
214
- const allWarframes = await warframeData.wfcd.items.warframes();
215
- const weapons = await warframeData.wfcd.items.weapons();
216
- const mods = await warframeData.wfcd.items.mods();
217
- const arcanes = await warframeData.wfcd.items.arcanes();
218
-
219
- const relics = await warframeData.wfcd.relics.all();
220
- const patchlogs = await warframeData.wfcd.patchlogs.all();
221
-
222
- // Refresh a particular upstream file by checking its Git blob hash:
223
- const latestWarframes = await warframeData.wfcd.items.refresh('Warframes.json');
224
- ```
225
-
226
- Each dataset also exposes `get(filename)`, `refresh(filename)`, and `files`, a list of supported JSON filenames. Filename selection is restricted to verified source directories and known files, not arbitrary URL inputs. Cached files remain usable offline; `refresh()` needs GitHub connectivity. Some categories, particularly large item data files, may require significant download bandwidth and memory.
227
-
228
- ### Optional WFCD market pricing and patch update monitoring
229
-
230
- The two upstream libraries are **optional**, not npm dependencies bundled into this project. Install them separately in the consuming application:
231
-
232
- ```bash
233
- npm install warframe-nexus-query warframe-updates
234
- ```
235
-
236
- ```js
237
- // Market query package is loaded on demand (ESM dynamic import).
238
- const marketQuery = await warframeData.market({ logger: console });
239
- const pricing = await marketQuery.priceCheckQuery('loki prime', 'pc');
240
- await marketQuery.stopUpdating();
241
-
242
- // Patch monitor is an EventEmitter supplied by the upstream package.
243
- // It may poll periodically; stop/cleanup according to the upstream API.
244
- const patchMonitor = warframeData.createUpdateMonitor({ interval: 60000 });
245
- patchMonitor.on('update', update => console.log(update));
246
- ```
247
-
248
- **Compatibility note:** The two optional integrations wrap the documented upstream constructors and require those packages to be installed. Their runtime compatibility and live availability are not yet end-to-end verified. The other four WFCD sources do **not** require an additional npm dependency. No third-party source code is bundled into this package.
249
-
250
- ## Supported runtime and packaging
251
-
252
- - Node.js 18+; `require()` CommonJS; native `fetch`.
253
- - `platform`: `pc`, `ps4`, `psn`, `xb1`, `swi`, `ns`. Default `pc`.
254
- - `language`: two-letter language code. Default `en`.
255
- - `timeoutMs`: positive integer, default `15000`.
256
- - `cacheDir`: optional writable disk path for character cache; codes use a separate `codes/` subdirectory.
257
- - `githubToken`: optional GitHub Contents read token for private `JsonData` sync.
258
- - `fetchImpl`: optional mocked fetch implementation for tests.
259
- - Package entry `index.js`, license MIT, author NekoSuneVR.
260
- - WFCD datasets are optional on-demand downloads; market and update monitor require separate installation of their upstream packages.
261
- - No npm dependency on `gaming-infos`; all bundled JSON comes from the **JsonData branch in this repository**.
262
-
263
- ### Manual publishing: no RELEASE_TOKEN required
264
-
265
- The **Manually publish tracker version** action runs only when started with **Run workflow**. Select a tracker branch (never `main`, `TEMPLATE`, or `JsonData`) and choose `both`, `npmjs`, or `github`. All jobs use that branch's **exact committed `package.json` version**, with no automatic version bump.
266
-
267
- The publishing jobs now run **directly inside the manual workflow**. It uses the built-in `GITHUB_TOKEN` to publish GitHub Packages and create the GitHub Release, so no `RELEASE_TOKEN` secret is required. npmjs publishing still requires repository secret `NPM_TOKEN` with correct permissions and any npm 2FA/trusted publishing configuration. GitHub-hosted `ubuntu-latest` runners are used; no self-hosted runner is needed. The GitHub package namespace must have suitable rights for the package scope.
268
-
269
- The separate older `release: published` workflows remain available for releases explicitly created outside this manual workflow. Releases created here with `GITHUB_TOKEN` will **not retrigger them**, preventing duplicate uploads.
270
-
271
- If one registry succeeds and another fails, its package version may already be published. Choose only the failed registry on a subsequent manual run rather than trying to republish the same version to both.
272
-
273
- ## Development, validation and release
274
-
275
- ```bash
276
- npm ci
277
- npm run check
278
- npm test
279
- npm pack --dry-run
280
- ```
281
-
282
- Normal pushes do **not** publish anything. The manual GitHub Actions workflow validates the selected branch, the `package-lock.json` and tests; it creates a release for the exact `package.json` version. Choose `warframe-api` only after verifying the package.
283
-
284
- [Manual publish workflow](https://github.com/NekoSuneProjectsNPM/game-tracker/actions/workflows/publish.yml) · [Source branch](https://github.com/NekoSuneProjectsNPM/game-tracker/tree/warframe-api) · [Issues](https://github.com/NekoSuneProjectsNPM/game-tracker/issues)
285
-
286
- Copyright © 2026 NekoSuneVR · MIT
287
-
288
- ## Registry authentication and retrying a failed manual publish
289
-
290
- The manual action has **registry** (both/npmjs/github) and **npm_auth** (token/trusted) selections. It runs on GitHub-hosted Ubuntu runners and uses the exact selected tracker's `package.json` version.
291
-
292
- - **npmjs.org package:** `@nekosuneprojects/<tracker-branch>` (unchanged).
293
- - **GitHub Packages package:** `@nekosuneprojectsnpm/<tracker-branch>` (temporarily renamed only in the GitHub Packages publish job, matching repository organisation `NekoSuneProjectsNPM`). The committed package metadata keeps its npmjs name.
294
- - **npm_auth = token:** Configure `NPM_TOKEN` in GitHub Actions secrets with a granular npm publishing token permitted to bypass interactive 2FA for CI. A standard token that returns `EOTP` cannot complete unattended GitHub Actions publishing; replace it or choose trusted publishing. Do not share the token.
295
- - **npm_auth = trusted:** Configure npmjs [Trusted publishing](https://docs.npmjs.com/trusted-publishers/) for GitHub organisation/user `NekoSuneProjectsNPM`, repository `game-tracker`, workflow filename `publish.yml`, allowing direct `npm publish`. This uses OIDC (`id-token: write`) and Node.js 24 on GitHub-hosted runners; `NPM_TOKEN` is ignored in this mode. For an unpublished package, first publication may require a properly permissioned npm token before package settings permit Trusted Publisher configuration.
296
-
297
- A failed job does not mean the package was published: check the registry package listing. If only one registry succeeded, re-run with **registry = npmjs** or **registry = github** to avoid re-publishing an existing immutable version. The workflow does not automatically bump any version and does not publish on pushes.
298
-
1
+ # @nekosuneprojects/warframe-api
2
+
3
+ **Warframe live-game information, fully local game/character JSON, and redeem-code/glyph records.** A CommonJS npm package by NekoSuneVR, currently version **1.0.0**.
4
+
5
+ - Live data: [WarframeStat.us](https://api.warframestat.us/) — public community API for worldstate, equipment, drops, rivens and supported profiles (not an official Digital Extremes API).
6
+ - Local character data: **129 bundled JSON files** from [game-tracker → JsonData → games/warframe](https://github.com/NekoSuneProjectsNPM/game-tracker/tree/JsonData/games/warframe), including `meta.json`, **Narin** and **Citrine Prime** (Update 44).
7
+ - Local codes: **one bundled `codes/games/warframe.json` snapshot** from [JsonData](https://github.com/NekoSuneProjectsNPM/game-tracker/blob/JsonData/codes/games/warframe.json), containing 503 `Active`, 32 `Expired`, and 0 `Unknown` entries at the time of integration. Status information is a *snapshot*, not a promise that any code is still redeemable.
8
+ - Optional separate GitHub synchronization for characters and codes. Offline queries work immediately using bundled JSON, with no API key or download required.
9
+ - Direct API URL is fixed in the package. **Do not configure a custom API URL.** The documented WarframeStat.us public endpoints do not require an API key.
10
+
11
+ ## Installation
12
+
13
+ Once manually released to npmjs.org:
14
+
15
+ ```bash
16
+ npm install @nekosuneprojects/warframe-api
17
+ ```
18
+
19
+ **Status:** The source is on the `warframe-api` branch. Creating this branch does not publish the npm package. GitHub Actions publishing remains manual and uses the committed `package.json` version exactly.
20
+
21
+ ## Initialise a Warframe tracker
22
+
23
+ ```js
24
+ const Warframe = require('@nekosuneprojects/warframe-api');
25
+
26
+ const warframeData = Warframe({
27
+ platform: 'pc',
28
+ language: 'en',
29
+ timeoutMs: 15000,
30
+ // Optional for live GitHub sync only:
31
+ cacheDir: './storage/warframe',
32
+ githubToken: process.env.GITHUB_DATA_TOKEN
33
+ });
34
+ ```
35
+
36
+ No credential is required for bundled JSON or the public WarframeStat.us API. A **GitHub read-only Contents token** may be required to update JSON from the `JsonData` branch if the GitHub repository is private. Store it in an environment variable, not in source control.
37
+
38
+ ### Local game metadata — `About()`
39
+
40
+ ```js
41
+ const aboutWarframe = await warframeData.About();
42
+ console.log(aboutWarframe.name);
43
+ console.log(aboutWarframe.Platform);
44
+ console.log(aboutWarframe.developers);
45
+ ```
46
+
47
+ Reads the full `meta.json`, including developer, publisher, genre, description and supported platforms. The stored field names are preserved unchanged.
48
+
49
+ ### Local Warframe characters — `search()` and `character()`
50
+
51
+ ```js
52
+ const matches = await warframeData.search('Ash');
53
+ // Each match contains an id, name, type and path.
54
+ // Select an exact result: Ash vs Ash Prime.
55
+
56
+ const normalAsh = await warframeData.character('ash');
57
+ const primeAsh = await warframeData.character('ashprime');
58
+ console.log(normalAsh.abilities, primeAsh.armor);
59
+ ```
60
+
61
+ Search scans local JSON filenames, names, aliases, and nested strings. Results are objects with `query`, `total`, `results`, `message`. A search with no results returns an empty list and a not-found message. Exact character lookups return the **full JSON record**, including nested details and abilities, or throw `NOT_FOUND`.
62
+
63
+ Convenience methods: `warframeData.record(category,id)`, `warframeData.item(id)`, `warframeData.map(id)`, `warframeData.data.get(relativePath)`, `warframeData.data.list()`. If that category is not present in the Warframe JSON source, exact lookup returns `NOT_FOUND`.
64
+
65
+ ## Warframe game codes and creator glyphs
66
+
67
+ The codes source has a different structure from character records: it is a **single JSON object with `Active`, `Expired`, and `Unknown` arrays**. Individual entries include `id`, `game`, `code` (sometimes `null`), `category`, `status`, `verificationStatus`, `items`, `expiry`, `redeemUrl`, `claimType`, `requirements` and source metadata.
68
+
69
+ **Some active creator-glyph entries have `code: null`.** They describe giveaways, social links or eligibility requirements, not text redeem codes. The module preserves them, and `hasCode` allows filtering.
70
+
71
+ ```js
72
+ // Full, original grouped JSON:
73
+ const grouped = await warframeData.codes.raw();
74
+
75
+ // Read lists with optional filters:
76
+ const active = await warframeData.codes.active();
77
+ const expired = await warframeData.codes.expired();
78
+ const unknown = await warframeData.codes.unknown();
79
+
80
+ // Actual text codes only:
81
+ const activeRedeemCodes = await warframeData.codes.active({
82
+ hasCode: true
83
+ });
84
+
85
+ // Creator-glyph acquisition instructions without text codes:
86
+ const glyphInstructions = await warframeData.codes.active({
87
+ category: 'creator-glyph',
88
+ hasCode: false,
89
+ limit: 20
90
+ });
91
+
92
+ // Search by code, item name, ID, type, or notes:
93
+ const codeMatches = await warframeData.codes.search('QUINCY');
94
+
95
+ // Get one full code/glyph entry by text code or numeric ID:
96
+ const quincy = await warframeData.codes.get('1999-QUINCY');
97
+ const sameEntry = await warframeData.code(quincy.id);
98
+
99
+ const summary = await warframeData.codes.summary();
100
+ console.log(summary);
101
+ ```
102
+
103
+ | Method | Description |
104
+ | --- | --- |
105
+ | `codes.raw()` | Full `Active` / `Expired` / `Unknown` JSON |
106
+ | `codes.list({status,category,claimType,hasCode,limit})` | Flatten entries and apply filters |
107
+ | `codes.active(options)` | Entries marked active in the stored dataset |
108
+ | `codes.expired(options)` | Entries marked expired in the stored dataset |
109
+ | `codes.unknown(options)` | Entries with unknown status |
110
+ | `codes.search(query,options)` | Search code strings, IDs, reward names, notes and categories |
111
+ | `codes.get(codeOrId)` | Return a complete unique record; throws `NOT_FOUND` or `AMBIGUOUS` |
112
+ | `code(codeOrId)` | Game-level alias for `codes.get` |
113
+ | `codes.summary()` | Counts by status and whether an actual code string exists |
114
+ | `codes.sync()` | Explicitly refresh only `JsonData/codes/games/warframe.json` |
115
+
116
+ **Data freshness:** An `active` label reflects the supplied JSON snapshot and its verification timestamps, not a live check of redemption availability. For real-world usage, inspect `expiry`, `verificationStatus`, `lastVerifiedAt`, `requirements` and `sourceUrls`.
117
+
118
+ ### Missing Warframe JSON and automatic retrieval on exact lookups
119
+
120
+ The bundled character snapshot already includes Narin and Citrine Prime. If another character is added to `JsonData` later and is not bundled or cached, an exact `character(id)` lookup will attempt one GitHub synchronization and then read the newly downloaded record:
121
+
122
+ ```js
123
+ // Character is fetched from JsonData if not found locally:
124
+ const futureCharacter = await warframeData.character('futurecharacterid');
125
+
126
+ // Explicitly discover new files in search results:
127
+ const matches = await warframeData.search('Future', { refresh: true });
128
+
129
+ // Skip GitHub if you require strictly offline behaviour:
130
+ const onlyLocal = await warframeData.search('Ash');
131
+ const character = await warframeData.character('ash', { refresh: false });
132
+ ```
133
+
134
+ Existing local records are read immediately without network requests. For updates to *existing* records, call `data.sync()` explicitly (or use `data.ensure('characters/ash.json', { refresh: true })`). This keeps offline usage fast and avoids silently making network requests for every search. Missing files, changed files, and missing cached copies are downloaded using GitHub SHA comparisons; successfully cached data survives offline operation. A private repository still requires a read token to fetch additions.
135
+
136
+ ### Separate incremental JSON synchronization
137
+
138
+ ```js
139
+ // Characters and meta.json (games/warframe/)
140
+ const characterSync = await warframeData.data.sync();
141
+
142
+ // Warframe codes only (codes/games/warframe.json)
143
+ const codeSync = await warframeData.codes.sync();
144
+
145
+ console.log(characterSync, codeSync);
146
+ ```
147
+
148
+ Synchronization compares GitHub blob SHA values, downloads changed JSON only and caches it atomically. Unchanged files are skipped; existing local data remains available offline. A successful sync can refresh data without publishing a new npm version. The two sync methods use separate cache directories, so their manifests do not conflict. **No timer or automatic background synchronization is started.**
149
+
150
+ When the source is private, pass a suitable `githubToken` option. GitHub API rate limits, internet access, and repository permissions apply. Failed updates do not invalidate previously bundled data.
151
+
152
+ ## Live WarframeStat.us endpoints
153
+
154
+ ```js
155
+ const alerts = await warframeData.api.alerts();
156
+ const fissures = await warframeData.api.fissures();
157
+ const invasions = await warframeData.api.invasions();
158
+ const sortie = await warframeData.api.sortie();
159
+ const cycles = await warframeData.api.cetusCycle();
160
+ const trader = await warframeData.api.voidTrader();
161
+ const liveWorldstate = await warframeData.api.worldstate();
162
+
163
+ const liveWarframes = await warframeData.api.warframes();
164
+ const weapon = await warframeData.api.weapon('Soma');
165
+ const drops = await warframeData.api.drops();
166
+
167
+ const playerProfile = await warframeData.api.profile('PlayerName');
168
+ const playerStats = await warframeData.api.profileStats('PlayerName');
169
+ ```
170
+
171
+ All live methods are grouped under `api` so the live equipment endpoints never overwrite offline JSON methods. Network requests use the built-in WarframeStat.us URL; availability and response content depend on the upstream service.
172
+
173
+ ### Full method reference
174
+
175
+ | Category | Exported methods |
176
+ | --- | --- |
177
+ | Live worldstate | `api.worldstate()`, `api.worldstateField(field,options)` |
178
+ | Worldstate convenience methods | `api.alerts()`, `api.arbitration()`, `api.archonHunt()`, `api.cambionCycle()`, `api.cetusCycle()`, `api.conclaveChallenges()`, `api.constructionProgress()`, `api.dailyDeals()`, `api.darvo()`, `api.earthCycle()`, `api.events()`, `api.fissures()`, `api.flashSales()`, `api.globalUpgrades()`, `api.invasions()`, `api.kuva()`, `api.news()`, `api.nightwave()`, `api.persistentEnemies()`, `api.sentientOutposts()`, `api.simaris()`, `api.sortie()`, `api.steelPath()`, `api.syndicateMissions()`, `api.thermia()`, `api.vallisCycle()`, `api.voidTrader()`, `api.weeklyChallenges()` |
179
+ | Equipment and static item data | `api.warframes()`, `api.weapons()`, `api.items()`, `api.mods()`, `api.warframe(name)`, `api.weapon(name)`, `api.item(name)`, `api.mod(name)` |
180
+ | Equipment search | `api.searchWarframes(query)`, `api.searchWeapons(query)`, `api.searchItems(query)`, `api.searchMods(query)`, `api.list(kind)`, `api.get(kind,name)`, `api.search(kind,query)` |
181
+ | Drops and rivens | `api.drops()`, `api.searchDrops(query,options)`, `api.rivens()`, `api.searchRivens(weapon)` |
182
+ | Player profile | `api.profile(playerId)`, `api.profileStats(playerId)`, `api.profileXP(playerId)`, `getPlayerStats(playerId)` |
183
+ | Other static and feeds | `api.staticData(key)`, `api.searchStatic(key,query)`, `api.arcanes()`, `api.synthTargets()`, `api.rss()`, `api.twitter()`, `api.wfinfoFilteredItems()`, `api.wfinfoPrices()` |
184
+ | Diagnostics | `api.health()` |
185
+ | WFCD datasets | `wfcd.drops.*`, `wfcd.items.*`, `wfcd.relics.*`, `wfcd.patchlogs.*` |
186
+ | Optional WFCD modules | `market(options)`, `createUpdateMonitor(options)` |
187
+ | Offline game and characters | `About()`, `search(query,{refresh})`, `character(id,{refresh})`, `record(category,id)`, `data.get(path)`, `data.ensure(path,{refresh})`, `data.list()`, `data.sync()` |
188
+ | Offline codes | `codes.raw()`, `codes.list()`, `codes.active()`, `codes.expired()`, `codes.unknown()`, `codes.search()`, `codes.get()`, `codes.summary()`, `codes.sync()`, `code(codeOrId)` |
189
+
190
+ Some community API endpoints may return 404, change behaviour, or be temporarily unavailable. HTTP failures are surfaced as `TrackerError` with `status` when available. Check [WarframeStat.us docs](https://docs.warframestat.us/) for current upstream availability.
191
+
192
+ ## Warframe Community Developers (WFCD) integrations
193
+
194
+ Four upstream WFCD datasets are available through fixed, on-demand GitHub sources with **persistent disk caching and SHA verification**. Data is downloaded only when requested, not at package startup. These repositories are third-party community sources and may contain upstream-stale records. The default package does not bulk-download all their assets.
195
+
196
+ | WFCD project | Integrated access | What it provides |
197
+ | --- | --- | --- |
198
+ | [warframe-drop-data](https://github.com/WFCD/warframe-drop-data) | `wfcd.drops` | Drop tables, relic drops, mission rewards, bounty rewards, build metadata |
199
+ | [warframe-items](https://github.com/WFCD/warframe-items) | `wfcd.items` | Compiled items, Warframes, weapons, mods, arcanes, crafting data |
200
+ | [warframe-relic-data](https://github.com/WFCD/warframe-relic-data) | `wfcd.relics` | Relic JSON from the upstream `development` branch; the upstream project warns its data may be stale |
201
+ | [warframe-patchlogs](https://github.com/WFCD/warframe-patchlogs) | `wfcd.patchlogs` | Parsed patch note JSON |
202
+ | [warframe-nexus-query](https://github.com/WFCD/warframe-nexus-query) | `market()` | Optional market price query library using Warframe.Market |
203
+ | [warframe-updates](https://github.com/WFCD/warframe-updates) | `createUpdateMonitor()` | Optional event-driven patch deployment monitor |
204
+
205
+ ```js
206
+ const Warframe = require('@nekosuneprojects/warframe-api');
207
+ const warframeData = Warframe({ cacheDir: './storage/warframe' });
208
+
209
+ // Directly read individual WFCD JSON files; cached locally after first use.
210
+ const dropInfo = await warframeData.wfcd.drops.info();
211
+ const drops = await warframeData.wfcd.drops.all();
212
+ const missionRewards = await warframeData.wfcd.drops.missionRewards();
213
+
214
+ const allWarframes = await warframeData.wfcd.items.warframes();
215
+ const weapons = await warframeData.wfcd.items.weapons();
216
+ const mods = await warframeData.wfcd.items.mods();
217
+ const arcanes = await warframeData.wfcd.items.arcanes();
218
+
219
+ const relics = await warframeData.wfcd.relics.all();
220
+ const patchlogs = await warframeData.wfcd.patchlogs.all();
221
+
222
+ // Refresh a particular upstream file by checking its Git blob hash:
223
+ const latestWarframes = await warframeData.wfcd.items.refresh('Warframes.json');
224
+ ```
225
+
226
+ Each dataset also exposes `get(filename)`, `refresh(filename)`, and `files`, a list of supported JSON filenames. Filename selection is restricted to verified source directories and known files, not arbitrary URL inputs. Cached files remain usable offline; `refresh()` needs GitHub connectivity. Some categories, particularly large item data files, may require significant download bandwidth and memory.
227
+
228
+ ### Optional WFCD market pricing and patch update monitoring
229
+
230
+ The two upstream libraries are **optional**, not npm dependencies bundled into this project. Install them separately in the consuming application:
231
+
232
+ ```bash
233
+ npm install warframe-nexus-query warframe-updates
234
+ ```
235
+
236
+ ```js
237
+ // Market query package is loaded on demand (ESM dynamic import).
238
+ const marketQuery = await warframeData.market({ logger: console });
239
+ const pricing = await marketQuery.priceCheckQuery('loki prime', 'pc');
240
+ await marketQuery.stopUpdating();
241
+
242
+ // Patch monitor is an EventEmitter supplied by the upstream package.
243
+ // It may poll periodically; stop/cleanup according to the upstream API.
244
+ const patchMonitor = warframeData.createUpdateMonitor({ interval: 60000 });
245
+ patchMonitor.on('update', update => console.log(update));
246
+ ```
247
+
248
+ **Compatibility note:** The two optional integrations wrap the documented upstream constructors and require those packages to be installed. Their runtime compatibility and live availability are not yet end-to-end verified. The other four WFCD sources do **not** require an additional npm dependency. No third-party source code is bundled into this package.
249
+
250
+ ## Supported runtime and packaging
251
+
252
+ - Node.js 18+; `require()` CommonJS; native `fetch`.
253
+ - `platform`: `pc`, `ps4`, `psn`, `xb1`, `swi`, `ns`. Default `pc`.
254
+ - `language`: two-letter language code. Default `en`.
255
+ - `timeoutMs`: positive integer, default `15000`.
256
+ - `cacheDir`: optional writable disk path for character cache; codes use a separate `codes/` subdirectory.
257
+ - `githubToken`: optional GitHub Contents read token for private `JsonData` sync.
258
+ - `fetchImpl`: optional mocked fetch implementation for tests.
259
+ - Package entry `index.js`, license MIT, author NekoSuneVR.
260
+ - WFCD datasets are optional on-demand downloads; market and update monitor require separate installation of their upstream packages.
261
+ - No npm dependency on `gaming-infos`; all bundled JSON comes from the **JsonData branch in this repository**.
262
+
263
+ ### Manual publishing: no RELEASE_TOKEN required
264
+
265
+ The **Manually publish tracker version** action runs only when started with **Run workflow**. Select a tracker branch (never `main`, `TEMPLATE`, or `JsonData`) and choose `both`, `npmjs`, or `github`. All jobs use that branch's **exact committed `package.json` version**, with no automatic version bump.
266
+
267
+ The publishing jobs now run **directly inside the manual workflow**. It uses the built-in `GITHUB_TOKEN` to publish GitHub Packages and create the GitHub Release, so no `RELEASE_TOKEN` secret is required. npmjs publishing still requires repository secret `NPM_TOKEN` with correct permissions and any npm 2FA/trusted publishing configuration. GitHub-hosted `ubuntu-latest` runners are used; no self-hosted runner is needed. The GitHub package namespace must have suitable rights for the package scope.
268
+
269
+ The separate older `release: published` workflows remain available for releases explicitly created outside this manual workflow. Releases created here with `GITHUB_TOKEN` will **not retrigger them**, preventing duplicate uploads.
270
+
271
+ If one registry succeeds and another fails, its package version may already be published. Choose only the failed registry on a subsequent manual run rather than trying to republish the same version to both.
272
+
273
+ ## Development, validation and release
274
+
275
+ ```bash
276
+ npm ci
277
+ npm run check
278
+ npm test
279
+ npm pack --dry-run
280
+ ```
281
+
282
+ Normal pushes do **not** publish anything. The manual GitHub Actions workflow validates the selected branch, the `package-lock.json` and tests; it creates a release for the exact `package.json` version. Choose `warframe-api` only after verifying the package.
283
+
284
+ [Manual publish workflow](https://github.com/NekoSuneProjectsNPM/game-tracker/actions/workflows/publish.yml) · [Source branch](https://github.com/NekoSuneProjectsNPM/game-tracker/tree/warframe-api) · [Issues](https://github.com/NekoSuneProjectsNPM/game-tracker/issues)
285
+
286
+ Copyright © 2026 NekoSuneVR · MIT
287
+
288
+ ## Registry authentication and retrying a failed manual publish
289
+
290
+ The manual action has **registry** (both/npmjs/github) and **npm_auth** (token/trusted) selections. It runs on GitHub-hosted Ubuntu runners and uses the exact selected tracker's `package.json` version.
291
+
292
+ - **npmjs.org package:** `@nekosuneprojects/<tracker-branch>` (unchanged).
293
+ - **GitHub Packages package:** `@nekosuneprojectsnpm/<tracker-branch>` (temporarily renamed only in the GitHub Packages publish job, matching repository organisation `NekoSuneProjectsNPM`). The committed package metadata keeps its npmjs name.
294
+ - **npm_auth = token:** Configure `NPM_TOKEN` in GitHub Actions secrets with a granular npm publishing token permitted to bypass interactive 2FA for CI. A standard token that returns `EOTP` cannot complete unattended GitHub Actions publishing; replace it or choose trusted publishing. Do not share the token.
295
+ - **npm_auth = trusted:** Configure npmjs [Trusted publishing](https://docs.npmjs.com/trusted-publishers/) for GitHub organisation/user `NekoSuneProjectsNPM`, repository `game-tracker`, workflow filename `publish.yml`, allowing direct `npm publish`. This uses OIDC (`id-token: write`) and Node.js 24 on GitHub-hosted runners; `NPM_TOKEN` is ignored in this mode. For an unpublished package, first publication may require a properly permissioned npm token before package settings permit Trusted Publisher configuration.
296
+
297
+ A failed job does not mean the package was published: check the registry package listing. If only one registry succeeded, re-run with **registry = npmjs** or **registry = github** to avoid re-publishing an existing immutable version. The workflow does not automatically bump any version and does not publish on pushes.
298
+