Add Prime Intellect client, API, and MCP server

packages/prime — a hand-written typed client, because the first-party SDK is
Python only. Deliberately narrow: PIG reads availability and nothing else, and
the key it holds should be scoped so it could not provision even if the code
tried. Rate limits are undocumented upstream, so it backs off empirically with
full jitter and honours Retry-After. Unknown fields survive in `raw` rather
than being dropped.

apps/api — Hono, with authentication and authorization kept firmly apart. A
verified JWT proves someone has an account in the identity project, which may
be shared with other applications; it does NOT prove they belong here. Access
requires a row in PIG's own users table, and a token without one gets 403
needs_profile rather than entry.

The capacity service is the business logic: availability counts sold and held
separately, so a live hold removes inventory from everyone else's availability
without inflating utilisation. Expired holds are ignored at read time, so the
numbers stay right even when the sweeper is behind. Matching treats
interconnect as a hard filter and excludes Unknown as well as Ethernet —
unverified is not the same as adequate.

apps/mcp — nine tools over stdio, so a team member drives PIG from Claude
Code, Codex, prime-agent, or a Buzz agent. It holds an API key and calls the
same HTTP API the browser does, with no database credentials, so an agent can
never reach further than the person it acts for. Results are formatted as
prose rather than raw JSON.

Theme preferences live in the database rather than localStorage, so a chosen
accent follows someone from laptop to phone. Status colours stay independent
of the accent: if "at risk" re-tinted to whatever a user picked, the signal
would be gone.

Note on the SDK import: its package exports use a `./*` wildcard whose types
entry resolves server/mcp.js to server/mcp.js.d.ts, which does not exist. The
runtime specifier must keep the .js suffix, so the types are mapped via
tsconfig paths rather than by writing an import that would fail at runtime.

Verified: all five packages typecheck; the MCP server constructs and registers
its tools.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 19:02:45 -07:00
parent d36762f264
commit 7aeec0c632
21 changed files with 3997 additions and 2 deletions
+1
View File
@@ -1,2 +1,3 @@
export * from './ontology';
export * from './margin';
export * from './theme';
+124
View File
@@ -0,0 +1,124 @@
/**
* The PIG palette.
*
* Users pick an accent and the whole interface re-tints from it. The accent is
* stored as a KEY rather than a hex string, for two reasons: the palette can be
* retuned centrally without migrating anyone's saved preference, and nobody can
* choose a colour that is illegible against the surfaces.
*
* Every accent below is specified as HSL triples for light and dark surfaces
* independently. A colour that reads well on white is usually too dark on
* near-black, so the dark variants are lifted in lightness and slightly
* desaturated — the same hue, tuned twice, rather than one value used in both
* places and looking wrong in one of them.
*
* Contrast: every `fg` is chosen to clear WCAG AA (4.5:1) against its own
* surface, and `on` is the text colour that clears AA against the accent itself
* when used as a solid fill.
*/
export interface AccentDefinition {
key: string;
label: string;
/** Light-mode: accent as HSL channels, `H S% L%`, for CSS `hsl(var(--x))`. */
light: { accent: string; fg: string; on: string; subtle: string };
/** Dark-mode equivalents. */
dark: { accent: string; fg: string; on: string; subtle: string };
}
/**
* `pig` is the default and the brand: near-black in light mode, near-white in
* dark. The mascot is a black-and-white pig, so the product's own accent is
* monochrome and every other choice is the user's personality rather than ours.
*/
export const ACCENTS: AccentDefinition[] = [
{
key: 'pig',
label: 'Pig',
light: {
accent: '240 6% 10%',
fg: '240 6% 10%',
on: '0 0% 100%',
subtle: '240 5% 96%',
},
dark: {
accent: '0 0% 98%',
fg: '0 0% 98%',
on: '240 6% 10%',
subtle: '240 4% 16%',
},
},
{
key: 'rose',
label: 'Rose',
light: { accent: '346 77% 50%', fg: '346 77% 42%', on: '0 0% 100%', subtle: '346 77% 97%' },
dark: { accent: '346 84% 62%', fg: '346 90% 72%', on: '346 90% 12%', subtle: '346 40% 18%' },
},
{
key: 'amber',
label: 'Amber',
light: { accent: '32 95% 44%', fg: '28 80% 36%', on: '0 0% 100%', subtle: '38 92% 95%' },
dark: { accent: '38 92% 58%', fg: '43 96% 68%', on: '26 83% 12%', subtle: '30 40% 18%' },
},
{
key: 'emerald',
label: 'Emerald',
light: { accent: '160 84% 32%', fg: '161 88% 26%', on: '0 0% 100%', subtle: '152 76% 96%' },
dark: { accent: '158 64% 48%', fg: '156 72% 62%', on: '160 90% 10%', subtle: '158 35% 16%' },
},
{
key: 'sky',
label: 'Sky',
light: { accent: '201 90% 40%', fg: '202 90% 33%', on: '0 0% 100%', subtle: '204 94% 96%' },
dark: { accent: '199 89% 58%', fg: '198 93% 68%', on: '202 90% 10%', subtle: '200 40% 17%' },
},
{
key: 'violet',
label: 'Violet',
light: { accent: '262 83% 55%', fg: '263 70% 46%', on: '0 0% 100%', subtle: '270 100% 97%' },
dark: { accent: '258 90% 70%', fg: '255 92% 78%', on: '264 80% 12%', subtle: '260 35% 20%' },
},
{
key: 'slate',
label: 'Slate',
light: { accent: '215 25% 35%', fg: '215 25% 28%', on: '0 0% 100%', subtle: '210 40% 96%' },
dark: { accent: '213 27% 74%', fg: '214 32% 82%', on: '215 28% 12%', subtle: '215 20% 18%' },
},
];
export const ACCENT_KEYS = ACCENTS.map((a) => a.key);
export const DEFAULT_ACCENT = 'pig';
export const THEME_MODES = ['light', 'dark', 'system'] as const;
export type ThemeMode = (typeof THEME_MODES)[number];
export const DEFAULT_THEME_MODE: ThemeMode = 'system';
export function getAccent(key: string | null | undefined): AccentDefinition {
return ACCENTS.find((a) => a.key === key) ?? ACCENTS[0]!;
}
export function isValidAccent(key: string): boolean {
return ACCENT_KEYS.includes(key);
}
export function isValidThemeMode(mode: string): mode is ThemeMode {
return (THEME_MODES as readonly string[]).includes(mode);
}
/**
* Semantic colours for pipeline stages and health states.
*
* Deliberately independent of the user's accent: if "at risk" re-tinted to
* whatever someone picked, a violet enthusiast would see warnings in violet and
* the signal would be gone. Status colour must mean the same thing for
* everyone.
*/
export const STATUS_COLORS = {
positive: { light: '160 84% 32%', dark: '158 64% 52%' },
warning: { light: '32 95% 44%', dark: '38 92% 60%' },
danger: { light: '0 72% 45%', dark: '0 84% 65%' },
info: { light: '201 90% 40%', dark: '199 89% 60%' },
neutral: { light: '240 4% 46%', dark: '240 5% 65%' },
} as const;
export type StatusColor = keyof typeof STATUS_COLORS;