From 6b28e1d6c8befb2eda102851741f59d47b34cdd1 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Mon, 27 Jul 2026 06:22:51 -0700 Subject: [PATCH] feat: add the logo, and point people at the plugin before they mint a token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Logo: the app had no icon at all — public/ held only .gitkeep and the page emitted no , so browsers requested /favicon.ico, got a 404 and showed a blank tab. app/icon.svg is picked up automatically by the App Router; public/logo.svg is a currentColor variant for in-app use. The mark is three retrieval signals converging on a single memory, which is what the search actually does (vector + full-text + tags fused by RRF) and what the product does (many sessions, one store). Checked at 16px: the outer strokes are held at equal opacity because asymmetry read as a rendering artifact rather than as ranking. Tokens page: reframed so a bearer token is the exception rather than the default. A token is a credential to store and rotate; the plugin just signs you in. The install hint renders only when PLUGIN_MARKETPLACE_URL is set — a copyable command pointing nowhere is worse than no command. Co-Authored-By: Claude Opus 5 (1M context) --- .env.example | 6 +++ README.md | 2 + .../web/app/(authed)/settings/tokens/page.tsx | 45 ++++++++++++++++++- apps/web/app/icon.svg | 16 +++++++ apps/web/lib/env.ts | 8 ++++ apps/web/public/logo.svg | 14 ++++++ docker-compose.yml | 2 + 7 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 apps/web/app/icon.svg create mode 100644 apps/web/public/logo.svg diff --git a/.env.example b/.env.example index 1560832..f60be24 100644 --- a/.env.example +++ b/.env.example @@ -40,6 +40,12 @@ OIDC_CLIENT_ID_WEB=replace-me OIDC_CLIENT_SECRET_WEB=replace-me OIDC_CLIENT_ID_MCP=replace-me OIDC_AUDIENCE=shared-memory + +# Marketplace this instance's Claude Code plugin is published from. When set, +# the CLI tokens page shows the one-command plugin install so people only mint +# a bearer token when a browser sign-in genuinely isn't possible. +#PLUGIN_MARKETPLACE_URL=https://your-git-host/you/shared-memory.git +#PLUGIN_MARKETPLACE_NAME=shared-memory # Scope whose IdP mapping emits `aud: `. Advertised in # /.well-known/oauth-protected-resource so MCP clients request it — without # that, Authentik never evaluates the mapping and every token 401s with diff --git a/README.md b/README.md index 5e2c24e..c6ea95b 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,8 @@ Copy `.env.example` and fill in the values below. | `OIDC_CLIENT_ID_MCP` | both | Client ID of the MCP resource-server client in your IdP. | | `OIDC_AUDIENCE` | both | Audience string the MCP access token must carry in its `aud` claim. Recommended: `shared-memory`. | | `OIDC_ISSUER_MCP` | optional | Issuer of MCP access tokens when the MCP endpoint is a separate IdP application (Authentik stamps each app's tokens with its own slug). Defaults to `OIDC_ISSUER`. | +| `PLUGIN_MARKETPLACE_URL` | optional | Marketplace URL for this instance's plugin. Shown as a one-command install on the CLI tokens page. Hidden when unset. | +| `PLUGIN_MARKETPLACE_NAME` | optional | Marketplace name used in `shared-memory@`. Defaults to `shared-memory`. | | `OIDC_AUDIENCE_SCOPE` | optional | Name of the IdP scope whose mapping emits that `aud` claim. Advertised in `scopes_supported` so clients request it. Defaults to `aud-`. | | `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | both | Local Postgres credentials. | | `NEXTAUTH_SECRET` | both | Session-cookie signing key. Generate with `openssl rand -base64 32`. | diff --git a/apps/web/app/(authed)/settings/tokens/page.tsx b/apps/web/app/(authed)/settings/tokens/page.tsx index 25044c8..94829ab 100644 --- a/apps/web/app/(authed)/settings/tokens/page.tsx +++ b/apps/web/app/(authed)/settings/tokens/page.tsx @@ -108,6 +108,44 @@ async function revokeTokenAction(formData: FormData) { revalidatePath("/settings/tokens"); } +/** + * Points people at the plugin before they mint a token they don't need. + * + * Rendered only when this instance knows which marketplace it's published + * from — showing a copyable command that points nowhere is worse than showing + * nothing. + */ +function PluginHint({ + marketplaceUrl, + marketplaceName, +}: { + marketplaceUrl: string | undefined; + marketplaceName: string; +}) { + if (!marketplaceUrl) return null; + return ( + + + If this machine has a browser, install the plugin instead + + +

+ The plugin signs you in through {" "} + your usual login, so there's no + token to copy, store, or rotate. Generate a token below only when a + browser sign-in isn't possible. +

+
+          {[
+            `claude plugin marketplace add ${marketplaceUrl}`,
+            `claude plugin install shared-memory@${marketplaceName}`,
+          ].join("\n")}
+        
+
+
+ ); +} + export default async function TokensPage() { const session = await auth(); const userId = session!.user.id; @@ -144,7 +182,12 @@ export default async function TokensPage() { + + diff --git a/apps/web/app/icon.svg b/apps/web/app/icon.svg new file mode 100644 index 0000000..6150af7 --- /dev/null +++ b/apps/web/app/icon.svg @@ -0,0 +1,16 @@ + + shared-memory + + + + + + + + + diff --git a/apps/web/lib/env.ts b/apps/web/lib/env.ts index 9609460..a9c7f02 100644 --- a/apps/web/lib/env.ts +++ b/apps/web/lib/env.ts @@ -67,6 +67,13 @@ const envSchema = z.object({ // every issued CLI token at once. CLI_TOKEN_SECRET: z.string().min(32, "CLI_TOKEN_SECRET must be at least 32 chars"), + // Plugin marketplace this instance is published from. When set, the CLI + // tokens page shows the one-command plugin install, so people only mint a + // bearer token when their machine genuinely can't complete a browser + // sign-in. Left unset, that hint is hidden rather than shown wrong. + PLUGIN_MARKETPLACE_URL: optional(z.string().url()), + PLUGIN_MARKETPLACE_NAME: z.string().min(1).default("shared-memory"), + // Behavior flags ALLOW_INSECURE_HTTP: Bool.optional().default(false), }); @@ -110,6 +117,7 @@ function buildPhaseStub(): Env { EMBEDDING_DIM: 384, NEXTAUTH_SECRET: "build-phase-secret-not-used-at-runtime-xxxxxxxx", CLI_TOKEN_SECRET: "build-phase-secret-not-used-at-runtime-xxxxxxxx", + PLUGIN_MARKETPLACE_NAME: "shared-memory", ALLOW_INSECURE_HTTP: false, }; } diff --git a/apps/web/public/logo.svg b/apps/web/public/logo.svg new file mode 100644 index 0000000..2d0a8c2 --- /dev/null +++ b/apps/web/public/logo.svg @@ -0,0 +1,14 @@ + + shared-memory + + + + + + + + diff --git a/docker-compose.yml b/docker-compose.yml index 02f6a2a..dd72ed0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -117,6 +117,8 @@ services: # added to .env but not listed here never reaches the container. OIDC_ISSUER_MCP: ${OIDC_ISSUER_MCP:-} OIDC_AUDIENCE_SCOPE: ${OIDC_AUDIENCE_SCOPE:-} + PLUGIN_MARKETPLACE_URL: ${PLUGIN_MARKETPLACE_URL:-} + PLUGIN_MARKETPLACE_NAME: ${PLUGIN_MARKETPLACE_NAME:-shared-memory} DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB} -- 2.52.0