a chat under the lists, and the skill that makes it worth having

the layout gains a third node across the bottom, a third of the height. a
conversation reads badly in a narrow column and the two lists do not, so the
chat gets width rather than a column of its own.

it is officerdev/chat, the platform's own panel, not a simplified one of ours.
seedAppRegistry merges the platform's panels and every plugin's into one
registry, so naming it is all it takes — and a second chat maintained here
would be the one that falls behind.

the skill is deliberately NOT scoped to these routes. that was considered and
dropped once it was clear what a skill costs: compileSkills emits a name, a
description and a path, and the body is only read if the agent decides it is
relevant. two lines in every conversation. scoping would have meant a platform
change to buy back nothing.

(affordances are the opposite — loaded in full, every conversation — so this
plugin ships none. by docs/affordances.md's own test the 404 quirk does not
qualify: it CAN be fixed instead, and the platform route already fixes it.)

what the skill carries: the routes, that meta.nginx_online is the field for
"is it down", that renewal is at 30 days so under 14 is a real failure worth
raising unprompted and nothing else will raise it, that the 404 has already
been interpreted before it reaches you, and that disabling a host takes a
domain off the internet so it is confirmed and never inferred. plus what is
not built, so it does not get offered.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: 656f5c85-e49a-4f1f-b9f3-042a04b4d043
This commit is contained in:
2026-08-25 17:17:32 +00:00
co-authored by Claude Opus 5
parent 3d02cb6229
commit b8fd43d7ab
2 changed files with 125 additions and 5 deletions
+94
View File
@@ -0,0 +1,94 @@
---
name: Proxy Manager
description: Use when asked which domains this machine serves, where a hostname forwards to, whether a site is up, or when a TLS certificate expires — and before taking a proxy host in or out of service. Carries the routes, the one 404 that means something else, and what this surface deliberately cannot do.
version: 1
---
Officer proxies through **Nginx Proxy Manager**, and this plugin is how you reach it. Everything below is
`/api/proxy-manager/...`, authenticated as the signed-in account like any other platform route.
**Go through the platform, never at NPM directly.** The admin credential lives encrypted on the server and
is never handed out — not to you, not to the owner. A route here already holds it, already translates NPM's
odder answers, and is the only path that works for an account that is not the owner.
## What is running, and where it goes
```
GET /api/proxy-manager/hosts proxy hosts, sorted by first domain
GET /api/proxy-manager/hosts/:id one host, with its certificate and access list expanded
GET /api/proxy-manager/redirections redirection hosts
GET /api/proxy-manager/streams TCP/UDP streams
GET /api/proxy-manager/dead-hosts domains served deliberately as nothing (NPM calls them 404 hosts)
GET /api/proxy-manager/access-lists with their items and clients
GET /api/proxy-manager/status is a connection configured, and does it answer
```
A host carries `domain_names`, `forward_scheme`, `forward_host`, `forward_port`, `enabled`, `ssl_forced`,
`certificate_id` (0 means none), `access_list_id`, and `meta.nginx_online` — which is `false` when nginx
itself reports the site down, and is the field to check when somebody says a site is broken.
**This is the honest answer to "what domains does this machine serve".** Better than any document: a
hand-maintained list in a repository is right on the day it is written, and this is what nginx is actually
configured with right now.
## Certificates
```
GET /api/proxy-manager/certificates
```
Sorted by expiry rather than name, with `daysUntilExpiry` computed server-side. That ordering is the point:
a list by name is a reference, a list by expiry answers the only question anyone has.
**Read the numbers the way Let's Encrypt behaves.** Renewal runs at 30 days, so under 30 is normal and not
worth mentioning. Under 14 means renewal has actually failed and the domain goes dark at zero — that is
worth raising unprompted. `expires_on` is null for some uploaded certificates, and `daysUntilExpiry` is
then null too; that is unknown, not urgent.
**Nothing warns anybody.** No notification exists yet. If you notice a certificate inside 14 days, say so
in the conversation — there is no other mechanism that will.
## The one write
```
POST /api/proxy-manager/hosts/:id/enable
POST /api/proxy-manager/hosts/:id/disable
```
Taking a host in or out of service. **It is the only write this surface has**, deliberately: it is
reversible in one click, and creating, editing and deleting hosts are not. Do not look for a create route —
there isn't one, and its absence is a decision rather than a gap.
**Disabling a host takes a domain off the internet.** Confirm before you do it, name the domains, and do not
infer it from something adjacent. "Is `foo.example.com` up?" is a question about `meta.nginx_online`, not an
instruction to change anything.
For anything this cannot do — a new proxy host, a certificate to issue, an access list to edit — say so and
point at NPM's own UI. Do not attempt it another way.
## The 404 that is not a 404
**NPM answers an unauthenticated read with 404, not 401.** Its router only registers a resource once the
request carries a token, so "not signed in" and "no such endpoint" are the same response.
The plugin already knows this: it re-mints its token and retries once, and if a 404 survives that it reports
the credential rather than the route. So a failure reaching you through these routes has already been
interpreted — **read the message, do not re-diagnose it as a missing endpoint.**
It is not a rule about the whole API, only about the routes that need it. `GET /nginx/certificates/dns-providers`,
for instance, answers a proper 403.
## Two states that are not failures
- **409 with `unconfigured`** — no NPM connection is stored. Nothing is broken; the owner has not connected
one. Only the owner can, at `/proxy-manager?config=1`.
- **403 on anything under `/_config`** — the connection settings are owner-only, including the test. It is
an NPM admin login and NPM has no read-only role, so anyone holding it can redirect any hostname this
machine serves. Reading the hosts and certificates is grantable; the credential is not.
## What is coming, so you do not promise it
Handing DNS and certificates over properly — a domain registry, provider API keys in the secret store, and
one request that turns *"expose sub.example.com on the private network over HTTPS"* into a DNS record, a
certificate and a proxy host. **None of it is built.** `plugins/proxy-manager/README.md` has the design.
Until it exists, do not offer to create a host or issue a certificate.