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:
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Configuration, read once at boot and validated loudly.
|
||||
*
|
||||
* A misconfigured deployment should fail to start with a clear message rather
|
||||
* than start successfully and behave subtly wrong. The warnings below are the
|
||||
* cases where PIG *can* run but an operator almost certainly did not intend
|
||||
* the resulting behaviour.
|
||||
*/
|
||||
import { z } from 'zod';
|
||||
|
||||
const schema = z.object({
|
||||
DATABASE_URL: z.string().min(1, 'DATABASE_URL is required.'),
|
||||
|
||||
SUPABASE_URL: z.string().url().optional(),
|
||||
SUPABASE_ANON_KEY: z.string().optional(),
|
||||
SUPABASE_SERVICE_KEY: z.string().optional(),
|
||||
|
||||
PIG_PORT: z.coerce.number().int().positive().default(8920),
|
||||
PIG_PUBLIC_URL: z.string().default('http://localhost:8920'),
|
||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||
|
||||
PIG_ADMIN_EMAILS: z.string().default(''),
|
||||
PIG_INVITE_CODE: z.string().optional(),
|
||||
|
||||
PRIME_API_KEY: z.string().optional(),
|
||||
PRIME_API_BASE: z.string().default('https://api.primeintellect.ai'),
|
||||
PRIME_SYNC_ENABLED: z.coerce.boolean().default(false),
|
||||
PRIME_SYNC_INTERVAL_MINUTES: z.coerce.number().int().positive().default(30),
|
||||
|
||||
PIGGY_ENABLED: z.coerce.boolean().default(false),
|
||||
ANTHROPIC_API_KEY: z.string().optional(),
|
||||
PIGGY_MODEL: z.string().default('claude-sonnet-5'),
|
||||
PIGGY_LEASE_SECONDS: z.coerce.number().int().positive().default(300),
|
||||
|
||||
SLACK_BOT_TOKEN: z.string().optional(),
|
||||
SLACK_SIGNING_SECRET: z.string().optional(),
|
||||
BUZZ_RELAY_URL: z.string().optional(),
|
||||
});
|
||||
|
||||
export type Config = z.infer<typeof schema> & {
|
||||
adminEmails: string[];
|
||||
isProduction: boolean;
|
||||
};
|
||||
|
||||
export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
|
||||
const parsed = schema.safeParse(env);
|
||||
if (!parsed.success) {
|
||||
const issues = parsed.error.issues.map((i) => ` ${i.path.join('.')}: ${i.message}`);
|
||||
throw new Error(`Invalid configuration:\n${issues.join('\n')}`);
|
||||
}
|
||||
|
||||
const adminEmails = parsed.data.PIG_ADMIN_EMAILS.split(',')
|
||||
.map((e) => e.trim().toLowerCase())
|
||||
.filter(Boolean);
|
||||
|
||||
const config: Config = {
|
||||
...parsed.data,
|
||||
adminEmails,
|
||||
isProduction: parsed.data.NODE_ENV === 'production',
|
||||
};
|
||||
|
||||
warnOnFootguns(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
function warnOnFootguns(config: Config): void {
|
||||
const warn = (message: string) => console.warn(`[pig] WARNING: ${message}`);
|
||||
|
||||
if (config.adminEmails.length === 0) {
|
||||
// Safe default, but worth saying out loud: with no admins nobody can
|
||||
// manage invites or settings through the UI.
|
||||
warn('PIG_ADMIN_EMAILS is empty — no user will have platform-admin rights.');
|
||||
}
|
||||
|
||||
if (!config.SUPABASE_URL) {
|
||||
warn(
|
||||
'SUPABASE_URL is not set — authentication is DISABLED and every request ' +
|
||||
'runs as the development user. Never do this in production.',
|
||||
);
|
||||
}
|
||||
|
||||
if (config.isProduction && !config.SUPABASE_URL) {
|
||||
throw new Error(
|
||||
'Refusing to start: NODE_ENV=production with no SUPABASE_URL would serve ' +
|
||||
'the entire CRM unauthenticated.',
|
||||
);
|
||||
}
|
||||
|
||||
if (config.PRIME_SYNC_ENABLED && !config.PRIME_API_KEY) {
|
||||
warn('PRIME_SYNC_ENABLED is on but PRIME_API_KEY is unset — sync will not run.');
|
||||
}
|
||||
|
||||
if (config.PIGGY_ENABLED && !config.ANTHROPIC_API_KEY) {
|
||||
warn('PIGGY_ENABLED is on but ANTHROPIC_API_KEY is unset — the agent will idle.');
|
||||
}
|
||||
|
||||
if (config.SUPABASE_SERVICE_KEY) {
|
||||
// Present legitimately for admin provisioning, but it is the most powerful
|
||||
// credential in the deployment and most installs do not need it.
|
||||
warn(
|
||||
'SUPABASE_SERVICE_KEY is set. It is only needed for administrative user ' +
|
||||
'provisioning; unset it if you are not using that.',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user