The owner asked for this after failing to find it, which is the whole argument: a gear in a header is where a settings control is easy to write and not where anybody looks for one. The bottom of a sidebar is its conventional home, and a labelled row rather than an icon means it says what it is. The form opens ABOVE the button so the control does not move when you use it. A disclosure that walks away from the pointer as it expands is the small kind of wrong that makes a thing feel unfinished. `aria-expanded` on the button, because it is a disclosure and a screen reader has no other way to know the state of one. The error path still force-opens it - "no account is mapped to you" is the state every user starts in, and the fix is in that form.
75 lines
3.5 KiB
TypeScript
75 lines
3.5 KiB
TypeScript
import type { PluginManifest } from '@@/plugins/manifest';
|
|
|
|
/**
|
|
* Karakeep — your bookmarks, behind an Officer session.
|
|
*
|
|
* ── The model, which is Vault's ──
|
|
*
|
|
* One Karakeep per Officer install, pointed at by the owner. Officer stores no bookmarks: Karakeep owns
|
|
* those. What this plugin owns is the CREDENTIAL and the door. Where Vault brokers one account, this maps
|
|
* every Officer user onto a Karakeep account of their own — `db/schema.ts`, and
|
|
* `plugins/USER_PROVISIONING.md` for the contract that governs creating one.
|
|
*
|
|
* ── Why Karakeep and not Linkwarden, which this was first built against ──
|
|
*
|
|
* Worth recording, because the plugin is otherwise the same shape and somebody will wonder.
|
|
*
|
|
* Linkwarden authenticates its extension through NextAuth: host-scoped session cookies, CSRF checked
|
|
* against the host, and absolute URLs baked from `NEXTAUTH_URL`. Proxying that means the upstream has to
|
|
* be told the address the CLIENT uses, which is not the address the sidecar dials, and getting it wrong
|
|
* bounces the extension out of the proxy mid-login. It cost a NEXTAUTH_URL change, a lost csrf cookie and
|
|
* a container recreate, and it retired the upstream's own web login as the price of going through Officer.
|
|
*
|
|
* Karakeep's extension holds an API KEY and sends it as a header. Nothing is host-pinned, so none of the
|
|
* above exists: the same key works at any address, and the upstream stays reachable on its own.
|
|
*
|
|
* ── The extension, and why one prefix is enough ──
|
|
*
|
|
* It calls `${address}/api/trpc`, so pointing it at `https://<officer>/api/karakeep/kk` puts everything
|
|
* it needs under one declared path. No root-path claims, and no header routing.
|
|
*
|
|
* A bare Officer origin — the Bitwarden arrangement, where you paste the Officer URL and nothing else —
|
|
* is possible here and is NOT declared yet. It would need `protocolHeaders`, and Karakeep's extension can
|
|
* carry one because its settings take arbitrary `customHeaders`. That is a real option rather than a
|
|
* wish, and it is left until the path form is proven, because it asks the owner to configure a header
|
|
* whose purpose is invisible from where they are typing it.
|
|
*
|
|
* ── No `osDependencies` ──
|
|
*
|
|
* Nothing shells out. The sidecar is a Bun HTTP proxy and the upstream is reached with `fetch`.
|
|
*/
|
|
export const manifest: PluginManifest = {
|
|
publisher: 'officerdev',
|
|
version: '2026.09.05.04',
|
|
platform: '>=1.0.0',
|
|
|
|
label: 'Karakeep',
|
|
summary: 'Your bookmarks, behind an Officer session — saved, archived and searchable',
|
|
tags: ['Personal', 'Degoogling'],
|
|
icon: 'Bookmark',
|
|
color: '#14b8a6',
|
|
|
|
permissions: [
|
|
{
|
|
key: 'karakeep',
|
|
label: 'Karakeep',
|
|
description: 'Your saved bookmarks, lists and their archived pages',
|
|
/*
|
|
Reading and saving is every user's. Pointing the install at a server and creating accounts on it
|
|
is not — `/config` holds the address the whole install uses and `/accounts` provisions against an
|
|
admin credential. Both are prefix-matched, so the trees beneath them are covered.
|
|
*/
|
|
ownerOnly: ['/config', '/accounts'],
|
|
},
|
|
],
|
|
|
|
/*
|
|
The extension's door, served without an Officer session.
|
|
|
|
A stock Karakeep extension carries its own API key and has nowhere to put an Officer JWT. Same
|
|
argument as Vault's `/bw`: it is the same exposure through one address instead of two, and it is what
|
|
lets Karakeep stop being reachable on its own.
|
|
*/
|
|
unauthenticatedPaths: ['/kk/*'],
|
|
};
|