omnigateway 0.4.11 → 0.4.13
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 +86 -238
- package/bin/omni.js +910 -384
- package/gateway.js +906 -355
- package/package.json +1 -1
- package/public/assets/{CopyValue-By5BAgv7.js → CopyValue-C2DkO9Yz.js} +1 -1
- package/public/assets/{Rack-fDZClWZL.js → Rack-HM36SprU.js} +1 -1
- package/public/assets/{Toggle-ClEeOh0F.js → Toggle-CRrH9nDt.js} +1 -1
- package/public/assets/{TokenBreakdown-D70VeZ2N.js → TokenBreakdown-C2NsJnqO.js} +1 -1
- package/public/assets/{_app-CDPKnQ72.js → _app-DVpIsUid.js} +1 -1
- package/public/assets/{_app.accounts-Dz0ox4Br.js → _app.accounts-64d_cxir.js} +2 -2
- package/public/assets/{_app.console-hPUmmYjp.js → _app.console-BBuBmLRI.js} +1 -1
- package/public/assets/{_app.database-DGtP4NdG.js → _app.database-J9Ujio8l.js} +1 -1
- package/public/assets/{_app.index-COcCAmbH.js → _app.index-BX4yKEXO.js} +1 -1
- package/public/assets/{_app.keys-yTXTbKTA.js → _app.keys-2sKwhZJc.js} +1 -1
- package/public/assets/{_app.logs-BhMvO_Fx.js → _app.logs-DuHXYrZU.js} +1 -1
- package/public/assets/{_app.models-0S3rurIl.js → _app.models-B0kTfPtn.js} +1 -1
- package/public/assets/{_app.plugins._pluginId-B-ZDQNPU.js → _app.plugins._pluginId-BOXRBToU.js} +1 -1
- package/public/assets/{_app.settings-DTnFwTeA.js → _app.settings-DoaOUmc1.js} +1 -1
- package/public/assets/{_app.usage-C2-VLiq9.js → _app.usage-B5sxB40E.js} +1 -1
- package/public/assets/{index-COwhxKU7.js → index-iCfSaRSG.js} +2 -2
- package/public/assets/{login-DjfMomJJ.js → login-B9CJqU--.js} +1 -1
- package/public/assets/plus-DKapAGoR.js +1 -0
- package/public/assets/{trash-2-U2ViYZum.js → trash-2-DwMK-JrZ.js} +1 -1
- package/public/index.html +1 -1
- package/public/assets/plus-UDQ_BTEr.js +0 -1
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ graph TD
|
|
|
81
81
|
dashboard -. "types only" .-> store
|
|
82
82
|
control --> router
|
|
83
83
|
control --> ratelimit
|
|
84
|
-
router
|
|
84
|
+
router -. "types + 2 pure helpers,<br/>via /types subpath" .-> store
|
|
85
85
|
router --> providers
|
|
86
86
|
store --> rtk
|
|
87
87
|
store --> ratelimit
|
|
@@ -107,6 +107,12 @@ The dashboard's edges are dotted because they are type-level only.
|
|
|
107
107
|
`@omni/providers/catalog` is deliberately kept import-free so model lists can be
|
|
108
108
|
bundled into the browser without dragging in the HTTP client.
|
|
109
109
|
|
|
110
|
+
The router's dotted edge into `@omni/store` is nearly the same story: its
|
|
111
|
+
package-root imports are all `import type`. Two pure arithmetic helpers,
|
|
112
|
+
`durationFor` and `cacheReadRate`, do run at runtime — imported through the
|
|
113
|
+
`@omni/store/types` leaf subpath, so neither SQLite nor crypto enters the
|
|
114
|
+
router's module graph and the no-I/O rule holds.
|
|
115
|
+
|
|
110
116
|
The gateway and the CLI are two front ends over the same `@omni/control`
|
|
111
117
|
functions. The CLI does not call the running server's API — it opens the same
|
|
112
118
|
SQLite file directly, which is why it still works when the gateway is down.
|
|
@@ -192,22 +198,12 @@ omni models catalog # what is available
|
|
|
192
198
|
omni models put fast --from-catalog anthropic:claude-sonnet-5
|
|
193
199
|
```
|
|
194
200
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
target's price to match the tier you are actually paying.
|
|
202
|
-
|
|
203
|
-
**A note on `kilo-auto/*` pricing.** Kilo's `frontier`, `balanced`, and
|
|
204
|
-
`efficient` routers choose an upstream model per request, and Kilo states no
|
|
205
|
-
rate for them. The catalog records zero, which the router reads as *unpriced*
|
|
206
|
-
and leaves out of its cost ranking — the same stored figure `kilo-auto/free`
|
|
207
|
-
carries because it genuinely is free. So a `kilo-auto` target seeded from the
|
|
208
|
-
catalog is not free, it is unranked: cost never counts for or against it. If you
|
|
209
|
-
want one ranked against your other accounts, set a real `costPerMTok` on the
|
|
210
|
-
saved target for the tier you expect it to land in.
|
|
201
|
+
Two catalog pricing caveats: xAI doubles its rate at or above 200K context —
|
|
202
|
+
the higher rate applies to every token, but a target holds one flat price, so
|
|
203
|
+
edit the saved target if you run grok long-context; and Kilo's `kilo-auto/*`
|
|
204
|
+
routers carry no published rate, so the router treats them as unpriced rather
|
|
205
|
+
than free — set a real `costPerMTok` on the saved target to have one ranked.
|
|
206
|
+
Details in [docs/adding-a-provider.md](docs/adding-a-provider.md).
|
|
211
207
|
|
|
212
208
|
Mint a key for your client. **It is printed once and stored only as a hash:**
|
|
213
209
|
|
|
@@ -308,23 +304,13 @@ anthropic-ratelimit-requests-remaining: 1841 x-ratelimit-remaining-requests:
|
|
|
308
304
|
anthropic-ratelimit-requests-reset: 2026-08-19T14:32:07Z x-ratelimit-reset-requests: 4h51m22s
|
|
309
305
|
```
|
|
310
306
|
|
|
311
|
-
`requests-remaining` counts the request
|
|
312
|
-
|
|
313
|
-
written when the header goes out, so its token cost is not yet known and
|
|
314
|
-
subtracting anything would be an invented number rather than a measured one.
|
|
315
|
-
|
|
307
|
+
`requests-remaining` counts the request being answered; `tokens-remaining` does
|
|
308
|
+
not and cannot — the response is still being written when the header goes out.
|
|
316
309
|
Where a key has several windows on one dimension, the headers report the one
|
|
317
|
-
**nearest exhaustion
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
defines a header for them and a number no client parses is noise in every
|
|
322
|
-
response.
|
|
323
|
-
|
|
324
|
-
A refusal is `429` with `Retry-After` in seconds, alongside the usual error body.
|
|
325
|
-
The wait is computed from the oldest request still inside the window that
|
|
326
|
-
refused you, so a weekly ceiling tells you when a slot actually frees rather than
|
|
327
|
-
parking you for seven days.
|
|
310
|
+
**nearest exhaustion**, not the reassuring ones. `spend` and `concurrency`
|
|
311
|
+
render on neither dialect, because no vendor defines a header for them. A
|
|
312
|
+
refusal is `429` with `Retry-After` in seconds, computed from the oldest request
|
|
313
|
+
still inside the window that refused you.
|
|
328
314
|
|
|
329
315
|
### Tools and routing
|
|
330
316
|
|
|
@@ -407,29 +393,14 @@ start` supervises the process itself with a pidfile under
|
|
|
407
393
|
|
|
408
394
|
### Restarting and stopping from the console
|
|
409
395
|
|
|
410
|
-
The console can restart and shut down the gateway
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
omnigateway.service` — because the unit `omni service install` writes sets
|
|
419
|
-
`Restart=on-failure`, and a handled `SIGTERM` exits cleanly, which systemd
|
|
420
|
-
reads as success. A gateway that killed itself would stop and stay stopped.
|
|
421
|
-
- **In a container**, it depends on the container's restart policy, and that
|
|
422
|
-
policy cannot be read from inside the container. The console says so instead
|
|
423
|
-
of promising a restart it has no way to verify. Run with
|
|
424
|
-
`--restart unless-stopped` if you want the control to mean what it says.
|
|
425
|
-
- **With no supervisor** — `omni start` with no unit installed — the control is
|
|
426
|
-
disabled, and the section names the supervisor it found. Nothing is watching
|
|
427
|
-
the process, so an exit is simply the end of it. Use `omni restart` from a
|
|
428
|
-
terminal on the machine.
|
|
429
|
-
|
|
430
|
-
Shutdown is offered in every shape, because stopping is the point of it. In a
|
|
431
|
-
container it is a one-way door: stopping the only process takes the console that
|
|
432
|
-
would have restarted it, so bringing the gateway back needs access to the host.
|
|
396
|
+
The console can restart and shut down the gateway from the foot of its sidebar.
|
|
397
|
+
Restart works under systemd — the gateway asks the manager rather than
|
|
398
|
+
signalling itself, because a handled SIGTERM exits cleanly, which systemd's
|
|
399
|
+
`Restart=on-failure` reads as success — reports uncertainty in a container,
|
|
400
|
+
whose restart policy cannot be read from inside, and disables itself with no
|
|
401
|
+
supervisor; use `omni restart` from a terminal there. Shutdown is offered in
|
|
402
|
+
every shape. In a container it is a one-way door: bring the process back from
|
|
403
|
+
the host.
|
|
433
404
|
|
|
434
405
|
## Configuration
|
|
435
406
|
|
|
@@ -488,39 +459,24 @@ deliberately edited on the Database screen instead; see
|
|
|
488
459
|
|
|
489
460
|
## Recording bodies
|
|
490
461
|
|
|
491
|
-
By default the gateway records no prompts and no responses.
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
**Per-key opt-out.** A gateway key can be created with *Never record this key's
|
|
511
|
-
bodies* — `omni keys create --no-bodies` — and it is then never captured whatever
|
|
512
|
-
the setting says. Use it for a client whose payloads must not be retained. The
|
|
513
|
-
choice is made when the key is issued and cannot be reversed afterwards; reissue
|
|
514
|
-
the key instead. `omni keys list` and the console's Keys screen both show which
|
|
515
|
-
keys are exempt, so an audit does not have to go through the database.
|
|
516
|
-
|
|
517
|
-
**What is captured.** What arrived at `/v1/*` and what was returned, plus the
|
|
518
|
-
request and response of every provider attempt, in dispatch order. The client
|
|
519
|
-
request is the conversation *before* RTK compression and each attempt request is
|
|
520
|
-
the one *after* it, so an artifact is the only place you can read what a filter
|
|
521
|
-
actually removed. The console labels which side is which, and so does the CLI.
|
|
522
|
-
|
|
523
|
-
**Reading them.** Expand a row on the console's Logs screen, or from a terminal:
|
|
462
|
+
By default the gateway records no prompts and no responses. For incident
|
|
463
|
+
forensics, capture is opt-in and needs **two independent keys, both required**:
|
|
464
|
+
`OMNI_BODY_LOGGING_ALLOWED=1` read at boot, plus the **Capture request and
|
|
465
|
+
response bodies** setting (console Settings, or `omni settings set
|
|
466
|
+
bodyLoggingEnabled true`). An admin session alone cannot start recording your
|
|
467
|
+
users' prompts; with the variable unset the console says the switch does
|
|
468
|
+
nothing rather than letting you flip it. Capture can be toggled mid-incident;
|
|
469
|
+
turning it off stops new capture and does not delete what was written.
|
|
470
|
+
|
|
471
|
+
A gateway key created with `--no-bodies` is never captured whatever the setting
|
|
472
|
+
says — made at issue time, not reversible afterwards; reissue instead. Raw SSE
|
|
473
|
+
frames are captured separately, under `bodyLoggingCaptureStreamChunks`, and are
|
|
474
|
+
far the larger store.
|
|
475
|
+
|
|
476
|
+
What is captured: what arrived at `/v1/*` and what was returned, plus every
|
|
477
|
+
provider attempt in dispatch order — the client side pre-RTK, attempts post-RTK,
|
|
478
|
+
labelled as such in console and CLI. Headers are never captured, at any layer.
|
|
479
|
+
Read them from the console's Logs screen or:
|
|
524
480
|
|
|
525
481
|
```bash
|
|
526
482
|
omni bodies req_550e8400-… # the frame: state, size, one line per attempt
|
|
@@ -528,79 +484,24 @@ omni bodies req_550e8400-… --full # the payloads themselves
|
|
|
528
484
|
omni bodies req_550e8400-… --json # the artifact, for a script
|
|
529
485
|
```
|
|
530
486
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
**What is never captured.** Headers, at any layer — every provider authenticates
|
|
551
|
-
through headers, so that is where the tokens are. That one is a guarantee: the
|
|
552
|
-
capture layer is never handed a header list, so no provider, present or future,
|
|
553
|
-
can opt its own in.
|
|
554
|
-
|
|
555
|
-
**Masking is best-effort, and a body corpus is sensitive even after it.** Bodies
|
|
556
|
-
are masked before they are written, replacing bearer tokens, `sk-`/`ak-`/`pk-`
|
|
557
|
-
prefixed keys, the well-known vendor prefixes (`ghp_` and the rest of GitHub's,
|
|
558
|
-
`github_pat_`, `AIza`, `GOCSPX-`, `xai-`), and any long opaque token with elided
|
|
559
|
-
forms. Two things follow, and both matter before you turn capture on:
|
|
560
|
-
|
|
561
|
-
- It costs fidelity. The length rule has no idea what it is looking at, so it
|
|
562
|
-
also elides base64 image data, content hashes, and minified source. That is
|
|
563
|
-
deliberate: a corpus that leaks a live credential is the worse failure.
|
|
564
|
-
- It does not catch everything. The length rule is tuned to base64url, so a
|
|
565
|
-
standard-base64 secret or an AWS secret access key can slip through it on a
|
|
566
|
-
`+` or a `/`, and a credential shorter than forty-one characters or exactly
|
|
567
|
-
forty characters long — an Azure OpenAI key, for instance — is out of its reach
|
|
568
|
-
entirely. The prefix rules exist precisely because the length rule cannot be
|
|
569
|
-
the whole answer, and between them they are a reduction in exposure, not a
|
|
570
|
-
guarantee of none.
|
|
571
|
-
|
|
572
|
-
Treat the artifact tree as you would treat the prompts themselves: it is
|
|
573
|
-
encrypted at rest, it belongs on a volume you control, and it is not something to
|
|
574
|
-
copy into a ticket.
|
|
575
|
-
|
|
576
|
-
Nothing changes about stdout. Prompts and responses never reach the log, the
|
|
577
|
-
journal, or the Console screen; capture is a separate encrypted store.
|
|
578
|
-
|
|
579
|
-
**Where it goes.** `request_bodies/YYYY/MM/DD/<requestId>.json.enc` beside the
|
|
580
|
-
database file, encrypted with AES-256-GCM under `OMNI_ENCRYPTION_KEY`, the same
|
|
581
|
-
key as your provider credentials. Copies taken without the key yield nothing.
|
|
582
|
-
Changing the key invalidates every artifact already written.
|
|
583
|
-
|
|
584
|
-
**Bounds.** Two limits, because either alone fails:
|
|
585
|
-
|
|
586
|
-
- Bodies expire on the same **log retention** window as request rows, swept
|
|
587
|
-
hourly, file and row deleted together.
|
|
588
|
-
- A hard cap of **100,000 body rows**, oldest pruned first. The window is what
|
|
589
|
-
you reason about; the row cap is what actually bounds disk, because a week's
|
|
590
|
-
window over sustained traffic bounds nothing.
|
|
591
|
-
|
|
592
|
-
Individual payloads are bounded structurally rather than by byte offset — strings
|
|
593
|
-
past 64 KB, arrays to their last 24 items, nesting past 6 levels, objects to 80
|
|
594
|
-
keys — so a stored artifact is always valid JSON. An artifact still over 512 KB
|
|
595
|
-
after that has its bodies replaced by a marker recording why.
|
|
596
|
-
|
|
597
|
-
**Sizing.** 512 KB is a *plaintext* cap and encryption emits hex, so one artifact
|
|
598
|
-
can reach ~1 MB on disk: with the 100,000-row cap the corpus worst case is about
|
|
599
|
-
**100 GB**, not 50. Most artifacts are a few kilobytes, but that is the number to
|
|
600
|
-
size a volume against. The same cap applies per body held in memory while a
|
|
601
|
-
request is in flight, one per side per attempt — so ~512 KB × (attempts + 1) per
|
|
602
|
-
captured request. Multiply by your concurrency before enabling this on a small
|
|
603
|
-
box.
|
|
487
|
+
The bare command prints only the frame, never conversations — asking costs one
|
|
488
|
+
flag. A missing artifact answers rather than errors: `not captured`, `captured,
|
|
489
|
+
then lost` (retention or the row cap), or `captured, but unreadable` (usually a
|
|
490
|
+
changed `OMNI_ENCRYPTION_KEY`). There is no command to delete a captured body;
|
|
491
|
+
a second path that erases forensic evidence on request loses incident records.
|
|
492
|
+
|
|
493
|
+
Artifacts live at `request_bodies/YYYY/MM/DD/<requestId>.json.enc` beside the
|
|
494
|
+
database, AES-256-GCM under `OMNI_ENCRYPTION_KEY`; changing that key invalidates
|
|
495
|
+
every artifact. Bounds: log-retention expiry plus a hard **100,000-row cap**, so
|
|
496
|
+
capture is forensics, not an archive — size a volume against roughly 100 GB
|
|
497
|
+
worst case, though most artifacts are kilobytes.
|
|
498
|
+
|
|
499
|
+
Masking is best-effort — bearer tokens, vendor-prefixed keys, long opaque
|
|
500
|
+
tokens are elided before write — a reduction in exposure, not a guarantee, and
|
|
501
|
+
it costs fidelity. Treat the tree as you would the prompts themselves: encrypted
|
|
502
|
+
at rest, on a volume you control, never pasted into a ticket.
|
|
503
|
+
[ARCHITECTURE.md](ARCHITECTURE.md#body-capture-forensics) documents the storage
|
|
504
|
+
format, structural bounds, and masking rules.
|
|
604
505
|
|
|
605
506
|
## Snapshots and restore
|
|
606
507
|
|
|
@@ -621,12 +522,10 @@ captured-body tree out of step with the table: files the restored database has n
|
|
|
621
522
|
row for are collected by the hourly sweep, and a row whose file is gone reads back
|
|
622
523
|
as `captured, then lost`.
|
|
623
524
|
|
|
624
|
-
**A snapshot does carry secrets
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
holding both the file and the key holds your provider accounts. Downloads are
|
|
629
|
-
never cached, and each one is recorded in the gateway's log.
|
|
525
|
+
**A snapshot does carry secrets** — encrypted provider credentials and gateway
|
|
526
|
+
key hashes — inert only because `OMNI_ENCRYPTION_KEY` is not in the file.
|
|
527
|
+
Anyone holding both the file and the key holds your provider accounts; treat a
|
|
528
|
+
downloaded snapshot as the database itself.
|
|
630
529
|
|
|
631
530
|
**Retention** bounds the directory: at most `keepLatest` snapshots are kept, and
|
|
632
531
|
nothing older than `maxAgeDays` — 5 and 30 by default. Both bounds have to pass,
|
|
@@ -642,23 +541,14 @@ It is the undo.
|
|
|
642
541
|
|
|
643
542
|
**Restoring from the console** happens inside the running gateway. Client traffic
|
|
644
543
|
on `/v1/*` is refused with a retryable 503 while the file is replaced; `/api/*`
|
|
645
|
-
and `/health` keep answering
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
a
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
**A restore ends by rebuilding the hourly usage rollup, and that step blocks.**
|
|
654
|
-
The rollup is what rate limits count their 5h and 1w windows from, and no file an
|
|
655
|
-
operator hands over says whether its counters agree with its rows — so it is
|
|
656
|
-
recomputed rather than trusted. `bun:sqlite` is synchronous, so the grouped scan
|
|
657
|
-
holds the event loop: roughly 0.4 s per 500k request-log rows, 1.6 s per 2M, and
|
|
658
|
-
about 6.5 s at 8M. `/api/*` and `/health` do not answer during it. It is the last
|
|
659
|
-
thing a restore does, after the swap has already succeeded, and a failure is
|
|
660
|
-
logged rather than raised — the database is live either way, and `omni doctor`
|
|
661
|
-
reports a rollup that disagrees with its rows.
|
|
544
|
+
and `/health` keep answering. The screen also uploads a database file from
|
|
545
|
+
elsewhere, up to 2 GiB — bring `OMNI_ENCRYPTION_KEY` with it, or the credentials
|
|
546
|
+
in it are unreadable. The file is integrity-checked before anything is touched,
|
|
547
|
+
and a copy of what was there is taken first. A restore ends by rebuilding the
|
|
548
|
+
usage rollup, which briefly blocks even `/api/*`: roughly 0.4 s per 500k
|
|
549
|
+
request-log rows, 1.6 s at 2M, 6.5 s at 8M. A failure is logged rather than
|
|
550
|
+
raised — the database is live either way, and `omni doctor` reports a rollup
|
|
551
|
+
that disagrees with its rows.
|
|
662
552
|
|
|
663
553
|
**`omni db restore <id>` refuses while a gateway is running** against that
|
|
664
554
|
installation, and there is no override flag. A second process can open its own
|
|
@@ -743,12 +633,9 @@ Worth knowing before you deploy it:
|
|
|
743
633
|
fonts, no third-party origins. A plugin may declare outbound origins of its
|
|
744
634
|
own, and `omni plugin verify <id>` shows exactly which ones it asked for — as
|
|
745
635
|
does its manifest, which is a plain file you can read before installing.
|
|
746
|
-
- **Plugins run inside the gateway process, with its privileges
|
|
747
|
-
capability context
|
|
748
|
-
|
|
749
|
-
key and decrypted provider credentials, and can reach past the context if it
|
|
750
|
-
wants to. Install plugins you wrote or audited, and read a plugin's manifest —
|
|
751
|
-
it lists every capability and outbound origin it asked for.
|
|
636
|
+
- **Plugins run inside the gateway process, with its privileges** — the
|
|
637
|
+
capability context is a guardrail, not a sandbox. Read the
|
|
638
|
+
[security note](#plugins) before installing one you did not write.
|
|
752
639
|
|
|
753
640
|
## Plugins
|
|
754
641
|
|
|
@@ -837,60 +724,21 @@ the plugin.
|
|
|
837
724
|
|
|
838
725
|
### Installing on a machine with no checkout
|
|
839
726
|
|
|
840
|
-
A published plugin installs by name
|
|
841
|
-
toolchain:
|
|
727
|
+
A published plugin installs by name — no checkout, no build toolchain:
|
|
842
728
|
|
|
843
729
|
```bash
|
|
844
730
|
omni plugin install omnigateway-plugin-example
|
|
845
731
|
omni plugin verify example && omni restart
|
|
846
732
|
```
|
|
847
733
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
For a plugin you build yourself and do not publish, ship the tarball and install
|
|
853
|
-
from the path:
|
|
854
|
-
|
|
855
|
-
```bash
|
|
856
|
-
# wherever you build — a workstation, CI
|
|
857
|
-
bun run build # your plugin's own build
|
|
858
|
-
tar -czf my-plugin.tgz -C dist my-plugin
|
|
859
|
-
|
|
860
|
-
# on the host
|
|
861
|
-
scp my-plugin.tgz gateway-host:/tmp/
|
|
862
|
-
ssh gateway-host 'omni plugin install /tmp/my-plugin.tgz && omni plugin verify my-plugin && omni restart'
|
|
863
|
-
```
|
|
864
|
-
|
|
865
|
-
Whatever you pack, the manifest must sit at the **root** of the archive once one
|
|
866
|
-
wrapping directory is stripped. A build that nests it — `dist/my-plugin/omni-plugin.json`
|
|
867
|
-
inside a tarball made from the repository root — is refused with "has no
|
|
868
|
-
omni-plugin.json at its root", and that is the most common way a plugin that
|
|
869
|
-
builds fine turns out not to install.
|
|
870
|
-
|
|
871
|
-
Plaintext `http://` is refused outright and always will be: what arrives over
|
|
872
|
-
that fetch is code the gateway process will `import`, so anyone between you and
|
|
873
|
-
the host would be choosing what the gateway runs. Silently upgrading to `https://`
|
|
874
|
-
would be worse — it would install *something* from a URL you did not type.
|
|
875
|
-
|
|
876
|
-
**In Docker**, the image carries the gateway only, so a plugin arrives on a
|
|
877
|
-
volume rather than through the CLI. Mount it at `<root>/plugins/<id>` — the same
|
|
878
|
-
layout `install` writes — and restart the container:
|
|
879
|
-
|
|
880
|
-
```bash
|
|
881
|
-
docker run --rm -p 9000:9000 \
|
|
882
|
-
-e OMNI_ENCRYPTION_KEY="$OMNI_ENCRYPTION_KEY" \
|
|
883
|
-
-v omnigateway-data:/data \
|
|
884
|
-
-v "$PWD/pokemon:/data/plugins/pokemon" \
|
|
885
|
-
omnigateway
|
|
886
|
-
```
|
|
734
|
+
Building and shipping your own plugin — tarball layout, the manifest-at-root
|
|
735
|
+
rule, why plaintext `http://` stays refused, Docker mounting — is covered in
|
|
736
|
+
[docs/writing-a-plugin.md](docs/writing-a-plugin.md).
|
|
887
737
|
|
|
888
|
-
**
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
names the mount. Keeping a plugin's code immutable while its cache stays
|
|
893
|
-
writable is not expressible today; mount the directory read-write.
|
|
738
|
+
**In Docker**, mount the plugin at `<root>/plugins/<id>` on a volume — the same
|
|
739
|
+
layout `install` writes — and restart the container; read-write, not `:ro`,
|
|
740
|
+
because a plugin declaring `files` writes its cache inside its own directory.
|
|
741
|
+
See [docs/writing-a-plugin.md](docs/writing-a-plugin.md).
|
|
894
742
|
|
|
895
743
|
Removing one keeps its data:
|
|
896
744
|
|