@renbostudios/edge-ota 0.4.0 → 0.4.2

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/README.md CHANGED
@@ -1,112 +1,211 @@
1
- # @renbostudios/edge-ota CLI
1
+ <p align="center">
2
+ <img src="https://ota.renbo.site/og.png" alt="Edge OTA" width="100%">
3
+ </p>
2
4
 
3
- Zero-SDK, self-hostable OTA update CLI for Expo React Native. Push signed JavaScript bundles to any EdgeOTA server.
5
+ <h1 align="center">
6
+ <code>edge-ota</code>
7
+ </h1>
4
8
 
5
- ## Install
9
+ <p align="center">
10
+ <strong>Zero-SDK, self-hostable OTA update CLI for Expo React Native</strong><br>
11
+ Push signed JavaScript bundles to any EdgeOTA server.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@renbostudios/edge-ota"><img src="https://img.shields.io/npm/v/@renbostudios/edge-ota?style=flat-square&color=81C784" alt="npm version"></a>
16
+ <a href="https://github.com/renbostudios/edge-ota-cli/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@renbostudios/edge-ota?style=flat-square&color=FFAA00" alt="license"></a>
17
+ <a href="https://www.npmjs.com/package/@renbostudios/edge-ota"><img src="https://img.shields.io/npm/dm/@renbostudios/edge-ota?style=flat-square&color=81C784" alt="downloads"></a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://github.com/renbostudios/edge-ota-cli">GitHub</a> · <a href="https://ota.renbo.site">Dashboard</a> · <a href="https://ota.renbo.site/docs">Docs</a> · <a href="https://pd1mvpk7lt.ufs.sh/f/6uDGWrRxa3ipmhByNjc2TlHph0oA4dsWRFUuqZMan18Gwb7g">Video Tutorial</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ <br>
27
+
28
+ ```
29
+ ╔══════════════════════════════════════════════════════════════════╗
30
+ ║ ║
31
+ ║ ┌─┐┬┌─┬┌┬┐┌─┐┌─┐┬ ┬┌─┐┌┐┌┌┬┐┌─┐ ║
32
+ ║ └─┐││ ┤│││├┤ │ ┬├─┤├─┤│││ │ ┤ └─┐ ║
33
+ ║ └─┘┴└─┴┴ ┴└ └─┘┴ ┴┴ ┴┘└┘─┴┘└─┘└─┘ ║
34
+ ║ ║
35
+ ║ Push signed OTA bundles to the edge. ║
36
+ ║ Zero SDK. Zero egress. Full control. ║
37
+ ║ ║
38
+ ╚══════════════════════════════════════════════════════════════════╝
39
+ ```
40
+
41
+ <br>
42
+
43
+ ## ⚡ Quick Start
44
+
45
+ ```
46
+ ┌──────────────────────────────────────────────────────────────┐
47
+ │ STEP 01 │
48
+ │ Install the CLI globally │
49
+ └──────────────────────────────────────────────────────────────┘
50
+ ```
6
51
 
7
52
  ```bash
8
53
  npm install -g @renbostudios/edge-ota
9
54
  ```
10
55
 
11
- Requires Node.js 18+ and an Expo project with `expo-updates` installed.
12
-
13
- ## Quick Start
56
+ ```
57
+ ┌──────────────────────────────────────────────────────────────┐
58
+ │ STEP 02 │
59
+ │ Log in to your EdgeOTA account │
60
+ └──────────────────────────────────────────────────────────────┘
61
+ ```
14
62
 
15
63
  ```bash
16
- # 1. Log in to your EdgeOTA account
17
64
  edge-ota login
65
+ ```
66
+
67
+ ```
68
+ ┌──────────────────────────────────────────────────────────────┐
69
+ │ STEP 03 │
70
+ │ Register your project & generate signing keys │
71
+ └──────────────────────────────────────────────────────────────┘
72
+ ```
18
73
 
19
- # 2. Register your project and generate signing keys
74
+ ```bash
20
75
  edge-ota init
76
+ ```
77
+
78
+ ```
79
+ ┌──────────────────────────────────────────────────────────────┐
80
+ │ STEP 04 │
81
+ │ ⚠ REBUILD YOUR NATIVE APP (required!) │
82
+ └──────────────────────────────────────────────────────────────┘
83
+ ```
21
84
 
22
- # 3. Rebuild your native app (REQUIRED — see below)
85
+ ```bash
23
86
  npx expo prebuild --clean
24
87
  npx expo run:ios
25
88
  npx expo run:android
89
+ ```
90
+
91
+ ```
92
+ ┌──────────────────────────────────────────────────────────────┐
93
+ │ STEP 05 │
94
+ │ Push an OTA update 🚀 │
95
+ └──────────────────────────────────────────────────────────────┘
96
+ ```
26
97
 
27
- # 4. Push an OTA update
98
+ ```bash
28
99
  edge-ota push
29
100
  ```
30
101
 
102
+ > **📹 Prefer video?** Watch the full setup tutorial below:
103
+
104
+ <p align="center">
105
+ <video src="https://pd1mvpk7lt.ufs.sh/f/6uDGWrRxa3ipmhByNjc2TlHph0oA4dsWRFUuqZMan18Gwb7g" controls width="100%" style="max-width: 640px; border: 1px solid #333; border-radius: 4px; background: #000;">
106
+ Your browser does not support the video tag.
107
+ </video>
108
+ </p>
109
+
110
+ <br>
111
+
31
112
  ---
32
113
 
33
- ## Commands
114
+ <br>
115
+
116
+ ## 📦 Commands
34
117
 
35
118
  ### `edge-ota login`
36
119
 
37
- Authenticate with your EdgeOTA account. Credentials are stored globally at `~/.config/edge-ota/config.json`.
120
+ Authenticate with your EdgeOTA account. Credentials are stored globally — no per-project `.env` files needed.
38
121
 
39
122
  ```bash
40
- edge-ota login
41
- edge-ota login --server https://my-server.com # custom server
123
+ edge-ota login # default server
124
+ edge-ota login --server https://my-server.com # custom server
42
125
  ```
43
126
 
44
- You'll be prompted for your email and password. The session token is saved globally — no per-project `.env` files needed.
127
+ ```
128
+ ┌──────────────────────────────────────────────┐
129
+ │ 📁 Stored at: │
130
+ │ ~/.config/edge-ota/config.json │
131
+ │ │
132
+ │ Contains: auth token, email, server URL │
133
+ └──────────────────────────────────────────────┘
134
+ ```
135
+
136
+ ---
45
137
 
46
138
  ### `edge-ota logout`
47
139
 
48
- Remove stored credentials.
140
+ Remove stored credentials from this machine.
49
141
 
50
142
  ```bash
51
143
  edge-ota logout
52
144
  ```
53
145
 
146
+ ---
147
+
54
148
  ### `edge-ota init`
55
149
 
56
- Register your project on the EdgeOTA server, generate an ECDSA P-256 signing key pair, and configure `app.json` with the correct update URL.
150
+ The all-in-one setup command. Registers your project, generates ECDSA P-256 signing keys, and configures `app.json`.
57
151
 
58
152
  ```bash
59
- edge-ota init
60
- edge-ota init --server https://my-server.com # override server
153
+ edge-ota init # interactive setup
154
+ edge-ota init --server https://my-server.com # override server
61
155
  ```
62
156
 
63
- **What it does:**
157
+ **What happens under the hood:**
158
+
159
+ ```
160
+ ① Fetches your existing projects from the server
161
+ ② Lets you select an existing project or create a new one
162
+ ③ Generates an ECDSA P-256 key pair
163
+ ④ Registers the project with your public key
164
+ ⑤ Saves private key → ~/.config/edge-ota/keys/<projectId>.key
165
+ ⑥ Updates app.json with server URL, public key, and headers
166
+ ```
64
167
 
65
- 1. Fetches your existing projects from the server (if any)
66
- 2. Lets you select an existing project or create a new one
67
- 3. Generates an ECDSA P-256 key pair
68
- 4. Registers the project on the server with your public key
69
- 5. Saves the private key to `~/.config/edge-ota/keys/<projectId>.key`
70
- 6. Updates your `app.json` with:
71
- - `expo.updates.url` — points to your EdgeOTA server
72
- - `expo.updates.checkAutomatically` — set to `"ON_LOAD"`
73
- - `expo.updates.fallbackToCacheTimeout` — set to `30000`
74
- - `expo.updates.requestHeaders` — sets `expo-channel-name` to `"production"`
75
- - `expo.extra.edgeOtaServer` — the server URL for manual override
76
- - `expo.extra.edgeOtaPublicKey` — the generated public key
168
+ > ⚠ **After `init`, you MUST rebuild your native app.** See [Rebuild Required](#-rebuild-required) below.
77
169
 
78
- **After init, you MUST rebuild your native app.** The CLI will print a prominent warning with instructions. See the [Rebuild Required](#rebuild-required) section below.
170
+ ---
79
171
 
80
172
  ### `edge-ota push`
81
173
 
82
- Export your Expo bundle and publish an OTA update to the server.
174
+ Export your Expo bundle and publish an OTA update.
83
175
 
84
176
  ```bash
85
- edge-ota push # default: all platforms, production channel
86
- edge-ota push --channel staging # deploy to staging
87
- edge-ota push --platform ios # iOS only
88
- edge-ota push --platform android # Android only
89
- edge-ota push --skip-export # skip expo export, use existing ./dist
90
- edge-ota push --dry-run # build and sign, but don't upload
177
+ edge-ota push # all platforms, production channel
178
+ edge-ota push --channel staging # deploy to staging
179
+ edge-ota push --runtime 1.0.0 # target specific runtime version
180
+ edge-ota push --runtime 1.0.5,1.0.6,1.0.7 # target multiple runtime versions (matrix deployment)
181
+ edge-ota push --platform ios # iOS only
182
+ edge-ota push --platform android # Android only
183
+ edge-ota push --skip-export # use existing ./dist
184
+ edge-ota push --dry-run # build & sign, don't upload
91
185
  ```
92
186
 
93
- **What it does:**
187
+ **What happens under the hood:**
94
188
 
95
- 1. Runs `npx expo export` to create the JavaScript bundle (unless `--skip-export`)
96
- 2. Collects all assets from `./dist`
97
- 3. For each target platform (ios/android):
98
- - Locates the platform-specific bundle
99
- - Signs the payload with your ECDSA private key
100
- - Uploads the bundle + signature to the server
101
- 4. The server verifies the signature before storing the update
189
+ ```
190
+ ① Runs `npx expo export` to create the JS bundle
191
+ ② Collects all assets from ./dist
192
+ ③ For each platform (ios/android):
193
+ → Locates the platform-specific bundle
194
+ → Signs the payload with your ECDSA private key
195
+ → Uploads bundle + signature to the server
196
+ ④ Server verifies the signature before storing
197
+ ```
102
198
 
103
199
  **How it reads your config:**
104
200
 
105
- The CLI reads everything from `app.json` — no flags required for standard deploys:
106
- - **Server URL** — from `expo.updates.url` (or `expo.extra.edgeOtaServer`)
107
- - **Project ID** — parsed from the updates URL
108
- - **Runtime version** — from `expo.runtimeVersion` (string or policy object)
109
- - **Private key** — from `~/.config/edge-ota/keys/<projectId>.key`
201
+ | Source | What it reads |
202
+ |---|---|
203
+ | `expo.updates.url` | Server URL + Project ID |
204
+ | `expo.runtimeVersion` | Runtime version (string or policy) |
205
+ | `~/.config/edge-ota/keys/<id>.key` | ECDSA private key |
206
+ | `expo.extra.edgeOtaServer` | Server URL override |
207
+
208
+ ---
110
209
 
111
210
  ### `edge-ota status`
112
211
 
@@ -114,9 +213,11 @@ List recent deployments for the current project.
114
213
 
115
214
  ```bash
116
215
  edge-ota status
117
- edge-ota status --limit 20 # show more releases
216
+ edge-ota status --limit 20 # show more releases
118
217
  ```
119
218
 
219
+ ---
220
+
120
221
  ### `edge-ota keygen`
121
222
 
122
223
  Generate a new ECDSA P-256 key pair and print it to stdout. Does not write any files.
@@ -127,63 +228,74 @@ edge-ota keygen
127
228
 
128
229
  Useful for manual key rotation or generating keys for server-side signing.
129
230
 
231
+ <br>
232
+
130
233
  ---
131
234
 
132
- ## Rebuild Required
235
+ <br>
133
236
 
134
- After running `edge-ota init`, you **must** rebuild your native app. The `expo-updates` native module reads its configuration (server URL, runtime version) from the **native binary**, not from `app.json` at runtime.
237
+ ## 🔄 Rebuild Required
238
+
239
+ After running `edge-ota init`, you **must** rebuild your native app. The `expo-updates` native module reads its config from the **native binary**, not from `app.json` at runtime.
135
240
 
136
241
  ```bash
137
- # Regenerate native projects with fresh config
138
242
  npx expo prebuild --clean
139
-
140
- # Rebuild
141
243
  npx expo run:ios
142
244
  npx expo run:android
143
245
 
144
- # Or via EAS
246
+ # or via EAS
145
247
  eas build --profile production
146
248
  ```
147
249
 
148
- **If you skip this step**, the app will fail with:
149
-
150
250
  ```
151
- Failed to check for update
251
+ ╔══════════════════════════════════════════════════════════════════╗
252
+ ║ ⚠ SKIP THIS STEP AND YOU'LL SEE: ║
253
+ ║ ║
254
+ ║ "Failed to check for update" ║
255
+ ║ ║
256
+ ║ The native module has no valid server URL to reach because ║
257
+ ║ it was never embedded in the binary. ║
258
+ ╚══════════════════════════════════════════════════════════════════╝
152
259
  ```
153
260
 
154
- The native module has no valid server URL to reach because it was never embedded in the binary.
155
-
156
261
  You must also rebuild after any change to `expo.updates.url` or `expo.runtimeVersion` in `app.json`.
157
262
 
263
+ <br>
264
+
158
265
  ---
159
266
 
160
- ## How It Works
267
+ <br>
268
+
269
+ ## 🔁 How It Works
161
270
 
162
271
  ```
163
- Developer Machine EdgeOTA Server User's Phone
164
- ───────────────── ────────────── ────────────
165
- edge-ota push ─────────────► POST /api/updates
166
- (ECDSA verified)
167
- stores bundle
168
- ◄──────────── GET /api/updates
169
- (expo-updates SDK)
170
- ────────────► multipart manifest
171
- ◄──────────── GET /api/assets/:hash
172
- download bundle
173
- reload app ✓
272
+ Developer Machine EdgeOTA Server User's Phone
273
+ ───────────────── ────────────── ────────────
274
+
275
+ edge-ota push ──────────────► POST /api/updates
276
+ (ECDSA verified ✓)
277
+ (stores bundle)
278
+ ◄──────────── GET /api/updates
279
+ (expo-updates SDK)
280
+ ────────────► signed manifest
281
+ ◄──────────── GET /api/assets/:hash
282
+ (downloads bundle)
283
+ (reloads app ✓)
174
284
  ```
175
285
 
176
- 1. `edge-ota push` runs `expo export` and uploads the Hermes bundle + ECDSA signature to your server
177
- 2. The server verifies the ECDSA P-256 signature before storing anything
178
- 3. When users open the app, `expo-updates` checks your server for a new manifest
286
+ 1. `edge-ota push` runs `expo export` and uploads the Hermes bundle + ECDSA signature
287
+ 2. The server **verifies** the ECDSA P-256 signature before storing anything
288
+ 3. When users open the app, `expo-updates` checks for a new manifest
179
289
  4. The server responds with a signed multipart manifest (Expo Updates Protocol v1)
180
290
  5. `expo-updates` downloads and applies the update automatically
181
291
 
292
+ <br>
293
+
182
294
  ---
183
295
 
184
- ## What `init` Writes to `app.json`
296
+ <br>
185
297
 
186
- After running `edge-ota init`, your `app.json` will contain:
298
+ ## ⚙ What `init` Writes to `app.json`
187
299
 
188
300
  ```json
189
301
  {
@@ -205,68 +317,99 @@ After running `edge-ota init`, your `app.json` will contain:
205
317
  }
206
318
  ```
207
319
 
208
- The `runtimeVersion` field is **not modified** by the CLI — it must already exist in your `app.json`. It supports both static strings and policy objects:
320
+ > `runtimeVersion` is **not modified** by the CLI — it must already exist in your `app.json`. Supports both static strings and policy objects:
209
321
 
210
322
  ```json
211
- // Static (recommended)
212
323
  "runtimeVersion": "1.0.0"
213
-
214
- // Policy-based (auto-increments with expo.version)
324
+ ```
325
+ ```json
215
326
  "runtimeVersion": { "policy": "appVersion" }
216
327
  ```
217
328
 
329
+ <br>
330
+
218
331
  ---
219
332
 
220
- ## Global Storage
333
+ <br>
221
334
 
222
- All credentials and keys are stored outside your project directory:
335
+ ## 🗂 Global Storage
223
336
 
224
- | File | Contents |
225
- |---|---|
226
- | `~/.config/edge-ota/config.json` | Auth token, email, default server URL |
227
- | `~/.config/edge-ota/keys/<projectId>.key` | Private ECDSA signing key (per-project) |
228
- | `~/.config/edge-ota/update-check.json` | Auto-update check timestamp |
337
+ All credentials and keys are stored **outside** your project directory:
338
+
339
+ ```
340
+ ~/.config/edge-ota/
341
+ ├── config.json ← auth token, email, server URL
342
+ ├── keys/
343
+ │ └── <projectId>.key ← ECDSA private key (per-project)
344
+ └── update-check.json ← auto-update check timestamp
345
+ ```
229
346
 
230
- Nothing sensitive is ever stored in your project repository.
347
+ ```
348
+ ┌──────────────────────────────────────────────────────────────┐
349
+ │ 🔒 Nothing sensitive is ever stored in your project repo. │
350
+ └──────────────────────────────────────────────────────────────┘
351
+ ```
352
+
353
+ <br>
231
354
 
232
355
  ---
233
356
 
234
- ## Environment Variables
357
+ <br>
358
+
359
+ ## 🧪 Environment Variables
235
360
 
236
361
  | Variable | Purpose |
237
362
  |---|---|
238
- | `EDGE_OTA_TOKEN` | Override the auth token (useful for CI/CD) |
363
+ | `EDGE_OTA_TOKEN` | Override the auth token (for CI/CD pipelines) |
239
364
  | `EDGE_OTA_UPDATING` | Internal — prevents recursive auto-updates |
240
365
 
366
+ <br>
367
+
241
368
  ---
242
369
 
243
- ## Auto-Update
370
+ <br>
371
+
372
+ ## 🔄 Auto-Update
244
373
 
245
- The CLI checks for newer versions on npm every 15 minutes. If a newer version is found, it auto-installs globally. This check happens silently in the background and never blocks command execution.
374
+ The CLI checks for newer versions on npm every 15 minutes. If a newer version is found, it auto-installs globally. This happens silently in the background and never blocks command execution.
246
375
 
247
376
  To disable: set `EDGE_OTA_UPDATING=true` in your environment.
248
377
 
378
+ <br>
379
+
249
380
  ---
250
381
 
251
- ## Security
382
+ <br>
383
+
384
+ ## 🔐 Security
252
385
 
253
- - All bundles are signed with ECDSA P-256 before upload
254
- - The server rejects any bundle with an invalid or missing signature
255
- - Private keys are stored with `0600` permissions (owner read/write only)
256
- - Session tokens expire after 24 hours
257
- - Private keys never leave your machine
386
+ ```
387
+ ┌──────────────────────────────────────────────────────────────┐
388
+ │ │
389
+ │ ✅ ECDSA P-256 signing on every bundle upload │
390
+ │ ✅ Server rejects invalid or missing signatures │
391
+ │ ✅ Private keys stored with 0600 permissions │
392
+ │ ✅ Session tokens expire after 24 hours │
393
+ │ ✅ Private keys never leave your machine │
394
+ │ │
395
+ └──────────────────────────────────────────────────────────────┘
396
+ ```
397
+
398
+ <br>
258
399
 
259
400
  ---
260
401
 
261
- ## Server Setup
402
+ <br>
403
+
404
+ ## 🖥 Server Setup
262
405
 
263
- The EdgeOTA server is a standard Node.js/Express app. You can self-host it or use the managed cloud service.
406
+ The EdgeOTA server is a standard Node.js/Express app. Self-host it or use the managed cloud service at [ota.renbo.site](https://ota.renbo.site).
264
407
 
265
408
  ### Docker
266
409
 
267
410
  ```bash
268
- docker pull renbostudios/edge-ota-server:latest
269
- docker run -p 3000:3000 -e DATABASE_URL=... renbostudios/edge-ota-server
411
+ docker pull ghcr.io/itskodaaa/edge-ota-backend:latest
412
+ docker run -p 3000:3000 -e DATABASE_URL=... ghcr.io/itskodaaa/edge-ota-backend
270
413
  ```
271
414
 
272
415
  ### Docker Compose
@@ -286,13 +429,20 @@ pnpm install
286
429
  pnpm --filter @renbostudios/edge-ota-server-node dev
287
430
  ```
288
431
 
432
+ <br>
433
+
289
434
  ---
290
435
 
291
- ## Troubleshooting
436
+ <br>
292
437
 
293
- ### "Failed to check for update" on app launch
438
+ ## 🐛 Troubleshooting
294
439
 
295
- The `expo-updates` native module can't reach your server because the URL was never embedded in the binary. Fix: rebuild the native app after `edge-ota init`.
440
+ <details>
441
+ <summary><strong>"Failed to check for update" on app launch</strong></summary>
442
+
443
+ <br>
444
+
445
+ The `expo-updates` native module can't reach your server because the URL was never embedded in the binary.
296
446
 
297
447
  ```bash
298
448
  npx expo prebuild --clean
@@ -300,29 +450,73 @@ npx expo run:ios
300
450
  npx expo run:android
301
451
  ```
302
452
 
303
- ### Updates work on iOS but not Android
453
+ </details>
454
+
455
+ <details>
456
+ <summary><strong>Updates work on iOS but not Android</strong></summary>
304
457
 
305
- The Android native project may still have the default Expo Updates URL. Fix: regenerate and rebuild.
458
+ <br>
459
+
460
+ The Android native project may still have the default Expo Updates URL. Regenerate and rebuild:
306
461
 
307
462
  ```bash
308
463
  npx expo prebuild --clean
309
464
  npx expo run:android
310
465
  ```
311
466
 
312
- Verify by checking `android/app/src/main/AndroidManifest.xml` for the `EXPO_UPDATE_URL` meta-data entry — it should point to your EdgeOTA server.
467
+ Verify by checking `android/app/src/main/AndroidManifest.xml` for the `EXPO_UPDATE_URL` meta-data entry.
468
+
469
+ </details>
470
+
471
+ <details>
472
+ <summary><strong>Updates stopped working after a version bump</strong></summary>
313
473
 
314
- ### Updates stopped working after a version bump
474
+ <br>
315
475
 
316
476
  If `runtimeVersion` uses the `appVersion` policy, every version bump changes the runtime version. Push a new update after bumping, or switch to a static string.
317
477
 
318
- ### "No signing key found" error on push
478
+ </details>
479
+
480
+ <details>
481
+ <summary><strong>"No signing key found" error on push</strong></summary>
482
+
483
+ <br>
319
484
 
320
485
  Run `edge-ota init` to generate keys, or use `edge-ota keygen` to generate a new pair and register it manually.
321
486
 
487
+ </details>
488
+
489
+ <br>
490
+
491
+ ---
492
+
493
+ <br>
494
+
495
+ ## 📂 Project Structure
496
+
497
+ ```
498
+ edge-ota-cli/
499
+ ├── apps/
500
+ │ ├── cli/ ← CLI tool (you are here)
501
+ │ ├── server-node/ ← Express + TypeScript API
502
+ │ ├── worker-oss/ ← Cloudflare Worker (open source)
503
+ │ ├── dashboard/ ← SvelteKit frontend
504
+ │ └── core/ ← Shared crypto/manifest logic
505
+ └── packages/
506
+ └── core/ ← @renbostudios/edge-ota-core
507
+ ```
508
+
509
+ <br>
510
+
322
511
  ---
323
512
 
324
- ## Built by Renbo Studios
513
+ <br>
325
514
 
326
- [renbostudios.com](https://renbostudios.com) — Mobile App Development Studio
515
+ <p align="center">
516
+ <strong>Built by</strong><br>
517
+ <a href="https://renbostudios.com">Renbo Studios</a> — Mobile App Development Studio
518
+ </p>
327
519
 
328
- MIT License
520
+ <p align="center">
521
+ <sub>MIT License · <code>@renbostudios/edge-ota</code></sub>
522
+ </p>