import type { PluginManifest } from '@@/plugins/manifest'; /** * OpenSEO — self-hosted keyword research, rank tracking and site audits, inside Officer. * * ── It points at an instance; it does not own one ── * * OpenSEO is a container, and a plugin has no primitive for "bring a container": `osDependencies` * installs OS packages. So this follows the shape `jellyfin`, `gitea` and `proxy-manager` already use — * a stored connection to a service running somewhere, with the credential write-only across the * boundary. * * A decision to revisit rather than a permanent one. `todo/infra/opm-officer-s-own-process-manager` is * heading toward the Docker network, and on the day a plugin can declare a container, the connection * becomes something this provisions rather than something somebody types. * * ── The permission split, and why it is not the same as proxy-manager's ── * * Proxy Manager carves out `/_config` because its credential can redirect any hostname on the machine. * The shape here is the same and the reason is different: OpenSEO self-hosts with * `AUTH_MODE=local_noauth`, so ANY reachable request to it is unauthenticated by design — the whole of * Officer's gate is the authentication. A member who could read the connection could reach the instance * directly and bypass this plugin entirely. * * So `/_config` is owner-only, and the pass-through is the only door. Nothing here may publish * OpenSEO's own port, and that is not a hardening measure — it is the security model upstream * documents: *"only expose it behind your own auth-protected reverse proxy, tunnel, or private * network."* * * ── Why the tools are not reimplemented here ── * * OpenSEO ships its own MCP server at `/mcp`. Most integrations need Officer to invent the agent-facing * surface; this one arrives with one, built for exactly this kind of client. Wrapping it in * Officer-native tools is a real option and an open decision — but inventing a second vocabulary for * tools that already exist would be work whose only product is drift. */ export const manifest: PluginManifest = { publisher: 'officerdev', version: '0.1.0', platform: '>=1.0.0', label: 'OpenSEO', summary: 'Keyword research, rank tracking, competitors, backlinks and site audits — self-hosted', /* `Marketing` and `Research` would both be more accurate and neither exists — the union is closed, and extending it is a platform change rather than a plugin's to make. `Development` is the least wrong of what is there: this is a tool about websites, used by whoever builds them. Worth revisiting if the store ever grows a marketing shelf. */ tags: ['Development', 'Homelab'], icon: 'TrendingUp', color: '#22c55e', permissions: [ { key: 'openseo', label: 'OpenSEO', description: 'Keyword research, rank tracking, competitor and backlink data, and site audits', /* The connection form, the probe, and the DataForSEO key. Owner-only by PREFIX, so `/_config/test` is covered too. That matters here for the same reason it does in proxy-manager: the test request carries a credential in its body, and a member who can call it has an oracle pointed at somebody else's key. */ ownerOnly: ['/_config'], }, ], }; export default manifest;