Files
karakeep/manifest.ts
T
pastilhas d882e349a9 the grid never noticed the crawler finishing
It loaded once. A bookmark is not finished when it is saved - Karakeep crawls it
afterwards, and the picture, description and favicon arrive minutes later - so
the panel sat on "fetching…" while the work quietly completed behind it.

Worse, the page most likely to be full of unfinished ones is the FIRST page.
Newest first, and the crawler works through the backlog, so a fresh import shows
exactly the page with the least on it. Measured while diagnosing: 79 of 97
crawled, and all 16 pending sitting on page one - which reads as a stalled
crawler and is the opposite.

Polls every five seconds while anything is pending, through the same code path as
a first load so a refresh cannot drift from it. Self-limiting and needs no
switch: it re-arms only while something is actually pending, so a settled library
makes no requests at all.
2026-09-05 21:45:07 +01:00

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.03',
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/*'],
};