This commit is contained in:
20
packages/shared/package.json
Normal file
20
packages/shared/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@waggle/shared",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "node ../../node_modules/vitest/vitest.mjs run --root ../.. --config vitest.config.ts packages/shared/tests"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.24.0"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts"
|
||||
}
|
||||
66
packages/shared/src/command-intent.ts
Normal file
66
packages/shared/src/command-intent.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
// Natural-Language Command Bar (Ctrl+K Tier 1 intent layer).
|
||||
//
|
||||
// The contract returned by `POST /api/command/interpret`. The resolver maps a
|
||||
// plain-language request onto the CLOSED action registry (server-side) and
|
||||
// returns one structured `InterpretResult`. The LLM never emits an endpoint,
|
||||
// route, or free code — it picks a registry action id; the SERVER derives the
|
||||
// executable shape below. See docs/nl-command-bar/STEP0-AND-DESIGN.md.
|
||||
|
||||
import type { RiskLevel } from './risk.js';
|
||||
import type { Tier } from './tiers.js';
|
||||
|
||||
/** The five resolution outcomes. `plan` is typed but NOT executed in v1. */
|
||||
export type InterpretKind = 'action' | 'plan' | 'clarify' | 'tier_gated' | 'none';
|
||||
|
||||
/**
|
||||
* A single resolved, server-validated action. Exactly one of `navigate` /
|
||||
* `endpoint` is set — both are derived server-side from the registry, never
|
||||
* supplied by the model.
|
||||
*/
|
||||
export interface ResolvedAction {
|
||||
/** Registry action id (closed set), e.g. 'create_workspace'. */
|
||||
id: string;
|
||||
/** Human-readable summary for the preview / approval surface. */
|
||||
label: string;
|
||||
/** Validated params the action was built from (for display + audit). */
|
||||
params: Record<string, unknown>;
|
||||
/** True → the frontend MUST render the approval surface before executing. */
|
||||
sideEffect: boolean;
|
||||
/** Risk badge for the approval surface (consistent with confirmation.ts). */
|
||||
riskLevel: RiskLevel;
|
||||
/** Navigation target — reuses CommandResult nav semantics (`onNavigate`). */
|
||||
navigate?: { type: string; id: string };
|
||||
/** Server-derived endpoint for a create / side-effect execute. */
|
||||
endpoint?: { method: 'POST' | 'PATCH' | 'DELETE'; path: string; body?: Record<string, unknown> };
|
||||
}
|
||||
|
||||
/** The single structured result of an interpret call. */
|
||||
export interface InterpretResult {
|
||||
kind: InterpretKind;
|
||||
/** kind === 'action'. */
|
||||
action?: ResolvedAction;
|
||||
/** kind === 'plan' — typed only; v1 downgrades to clarify/single-action. */
|
||||
steps?: ResolvedAction[];
|
||||
/** kind === 'clarify'. */
|
||||
question?: string;
|
||||
options?: string[];
|
||||
/** kind === 'tier_gated'. */
|
||||
capability?: string;
|
||||
requiredTier?: Tier;
|
||||
actualTier?: Tier;
|
||||
/** User-facing message (kind 'none', and a hint on others). */
|
||||
message?: string;
|
||||
/**
|
||||
* True when resolution failed/degraded (no key, LLM error, parse failure):
|
||||
* the frontend should fall back to Tier 0 results.
|
||||
*/
|
||||
fallback?: boolean;
|
||||
}
|
||||
|
||||
/** Request body for `POST /api/command/interpret`. */
|
||||
export interface InterpretRequest {
|
||||
text: string;
|
||||
workspaceId?: string;
|
||||
/** Lightweight client hints (current route, etc.). Optional. */
|
||||
context?: Record<string, unknown>;
|
||||
}
|
||||
173
packages/shared/src/connector-recommendations.ts
Normal file
173
packages/shared/src/connector-recommendations.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Persona-aware connector recommendations.
|
||||
*
|
||||
* Replaces the old "5 skills + 3 connectors at Essential" capability cap
|
||||
* (per the user's pushback in 2026-05-08 session: hiding 145 of 148
|
||||
* connectors didn't simplify, it crippled). Instead the catalog stays
|
||||
* fully reachable — but the connector-landing surface, onboarding step,
|
||||
* and chat hints SHOW different defaults based on the user's persona +
|
||||
* template, on the principle that a Sales rep wants HubSpot/Salesforce
|
||||
* up front while a Coder wants GitHub/Linear/Slack.
|
||||
*
|
||||
* This module is the single source of truth: server (for API), web
|
||||
* (for ConnectorsApp landing tile), and onboarding (for the "which
|
||||
* tools?" step) all import from here. Every connector ID listed below
|
||||
* is validated against `mcp-catalog.ts` by the unit test
|
||||
* `connector-recommendations.test.ts`.
|
||||
*
|
||||
* Shape:
|
||||
* recommendConnectors(personaId) → { primary: string[], secondary: string[] }
|
||||
* - `primary` — 3–5 IDs shown as the immediate "start here" tile
|
||||
* - `secondary` — additional context-relevant IDs shown one tier down
|
||||
*
|
||||
* Capability is NEVER reduced: all 148 connectors remain reachable via
|
||||
* search + browse-all in ConnectorsApp regardless of recommendation.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Universal-default connectors used as fallback for any persona without
|
||||
* a specific override (e.g. general-purpose, planner, verifier,
|
||||
* coordinator). Picked because these three are the most commonly-used
|
||||
* tools across professional roles globally.
|
||||
*/
|
||||
const UNIVERSAL_PRIMARY = ['gdrive-mcp', 'gmail-mcp', 'notion-mcp'] as const;
|
||||
|
||||
/** Secondary defaults that round out the universal set without overwhelming. */
|
||||
const UNIVERSAL_SECONDARY = ['slack-mcp', 'microsoft-365'] as const;
|
||||
|
||||
export interface ConnectorRecommendation {
|
||||
/** 3–5 IDs to display in the immediate connector landing tile. */
|
||||
primary: readonly string[];
|
||||
/** Additional IDs that show in a "also useful for your role" section. */
|
||||
secondary: readonly string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-persona connector recommendations.
|
||||
*
|
||||
* The keys are the canonical persona IDs from
|
||||
* `packages/agent/src/persona-data.ts`. Adding a new persona without
|
||||
* updating this map is allowed — `recommendConnectors` falls back to
|
||||
* the universal defaults so the surface never breaks.
|
||||
*/
|
||||
export const CONNECTOR_RECOMMENDATIONS: Readonly<Record<string, ConnectorRecommendation>> = {
|
||||
// ── Universal / reasoning personas — use the universal defaults ──────
|
||||
'general-purpose': { primary: UNIVERSAL_PRIMARY, secondary: UNIVERSAL_SECONDARY },
|
||||
'planner': { primary: UNIVERSAL_PRIMARY, secondary: UNIVERSAL_SECONDARY },
|
||||
'verifier': { primary: UNIVERSAL_PRIMARY, secondary: UNIVERSAL_SECONDARY },
|
||||
'coordinator': { primary: UNIVERSAL_PRIMARY, secondary: UNIVERSAL_SECONDARY },
|
||||
|
||||
// ── Knowledge-tier personas ──────────────────────────────────────────
|
||||
'researcher': {
|
||||
primary: ['perplexity', 'exa', 'gdrive-mcp', 'notion-mcp', 'gmail-mcp'],
|
||||
secondary: ['firecrawl', 'tavily', 'brave-search', 'jina'],
|
||||
},
|
||||
'writer': {
|
||||
primary: ['gdrive-mcp', 'notion-mcp', 'gmail-mcp', 'figma'],
|
||||
secondary: ['confluence-mcp', 'microsoft-365'],
|
||||
},
|
||||
'analyst': {
|
||||
primary: ['bigquery', 'postgres', 'gdrive-mcp', 'notion-mcp', 'excel'],
|
||||
secondary: ['snowflake', 'clickhouse', 'duckdb', 'airtable-mcp'],
|
||||
},
|
||||
'coder': {
|
||||
primary: ['github-mcp', 'slack-mcp', 'linear-mcp', 'sentry', 'gdrive-mcp'],
|
||||
secondary: ['gitlab-mcp', 'docker', 'vercel', 'datadog', 'postgres'],
|
||||
},
|
||||
|
||||
// ── Domain-tier personas ─────────────────────────────────────────────
|
||||
'project-manager': {
|
||||
primary: ['linear-mcp', 'jira-mcp', 'slack-mcp', 'notion-mcp', 'gdrive-mcp'],
|
||||
secondary: ['asana-mcp', 'monday-mcp', 'clickup', 'atlassian'],
|
||||
},
|
||||
'executive-assistant': {
|
||||
primary: ['gmail-mcp', 'gdrive-mcp', 'notion-mcp', 'slack-mcp'],
|
||||
secondary: ['microsoft-365', 'asana-mcp'],
|
||||
},
|
||||
'sales-rep': {
|
||||
primary: ['hubspot-mcp', 'salesforce-mcp', 'slack-mcp', 'gmail-mcp', 'notion-mcp'],
|
||||
secondary: ['linear-mcp', 'gdrive-mcp', 'airtable-mcp'],
|
||||
},
|
||||
'marketer': {
|
||||
primary: ['hubspot-mcp', 'notion-mcp', 'gdrive-mcp', 'slack-mcp', 'figma'],
|
||||
secondary: ['airtable-mcp', 'salesforce-mcp', 'firecrawl'],
|
||||
},
|
||||
'product-manager-senior': {
|
||||
primary: ['linear-mcp', 'jira-mcp', 'github-mcp', 'figma', 'slack-mcp', 'notion-mcp'],
|
||||
secondary: ['atlassian', 'sentry', 'gdrive-mcp'],
|
||||
},
|
||||
'hr-manager': {
|
||||
primary: ['gmail-mcp', 'gdrive-mcp', 'slack-mcp', 'notion-mcp'],
|
||||
secondary: ['microsoft-365', 'asana-mcp', 'airtable-mcp'],
|
||||
},
|
||||
'legal-professional': {
|
||||
primary: ['gdrive-mcp', 'notion-mcp', 'gmail-mcp'],
|
||||
secondary: ['microsoft-365', 'box', 'confluence-mcp'],
|
||||
},
|
||||
'finance-owner': {
|
||||
primary: ['stripe-mcp', 'gdrive-mcp', 'excel', 'gmail-mcp', 'notion-mcp'],
|
||||
secondary: ['microsoft-365', 'bigquery', 'airtable-mcp'],
|
||||
},
|
||||
'consultant': {
|
||||
primary: ['notion-mcp', 'gdrive-mcp', 'gmail-mcp', 'slack-mcp', 'figma'],
|
||||
secondary: ['microsoft-365', 'confluence-mcp', 'airtable-mcp'],
|
||||
},
|
||||
'support-agent': {
|
||||
primary: ['zendesk', 'intercom', 'slack-mcp', 'gmail-mcp', 'notion-mcp'],
|
||||
secondary: ['linear-mcp', 'jira-mcp', 'gdrive-mcp'],
|
||||
},
|
||||
'ops-manager': {
|
||||
primary: ['notion-mcp', 'slack-mcp', 'gdrive-mcp', 'atlassian'],
|
||||
secondary: ['asana-mcp', 'monday-mcp', 'jira-mcp', 'airtable-mcp'],
|
||||
},
|
||||
'data-engineer': {
|
||||
primary: ['bigquery', 'postgres', 'snowflake', 'github-mcp', 'slack-mcp'],
|
||||
secondary: ['clickhouse', 'duckdb', 'mongodb', 'gdrive-mcp'],
|
||||
},
|
||||
'recruiter': {
|
||||
primary: ['gmail-mcp', 'gdrive-mcp', 'slack-mcp', 'notion-mcp'],
|
||||
secondary: ['microsoft-365', 'airtable-mcp'],
|
||||
// Note: 'linkedin' would be the obvious add here but is not in the
|
||||
// MCP catalog as of 2026-05-08. When/if added, slot it ahead of
|
||||
// airtable-mcp in secondary.
|
||||
},
|
||||
'creative-director': {
|
||||
primary: ['figma', 'gdrive-mcp', 'slack-mcp', 'notion-mcp'],
|
||||
secondary: ['microsoft-365', 'asana-mcp'],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the recommended connectors for a persona.
|
||||
*
|
||||
* Falls back to universal defaults for unknown persona IDs (custom
|
||||
* personas, future additions, typos) so the surface never goes empty.
|
||||
*/
|
||||
export function recommendConnectors(personaId: string): ConnectorRecommendation {
|
||||
const exact = CONNECTOR_RECOMMENDATIONS[personaId];
|
||||
if (exact) return exact;
|
||||
return { primary: UNIVERSAL_PRIMARY, secondary: UNIVERSAL_SECONDARY };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience: flatten a recommendation into a single ranked list
|
||||
* (primary first, then secondary). Useful for the onboarding step's
|
||||
* "pick your tools" multi-select where ordering matters more than
|
||||
* sectioning.
|
||||
*/
|
||||
export function flattenRecommendation(rec: ConnectorRecommendation): string[] {
|
||||
return [...rec.primary, ...rec.secondary];
|
||||
}
|
||||
|
||||
/** All connector IDs referenced anywhere in this file. Used by the test
|
||||
* suite to verify catalog membership without enumerating personas. */
|
||||
export function allReferencedConnectorIds(): string[] {
|
||||
const seen = new Set<string>();
|
||||
for (const id of UNIVERSAL_PRIMARY) seen.add(id);
|
||||
for (const id of UNIVERSAL_SECONDARY) seen.add(id);
|
||||
for (const rec of Object.values(CONNECTOR_RECOMMENDATIONS)) {
|
||||
for (const id of rec.primary) seen.add(id);
|
||||
for (const id of rec.secondary) seen.add(id);
|
||||
}
|
||||
return [...seen].sort();
|
||||
}
|
||||
24
packages/shared/src/constants.ts
Normal file
24
packages/shared/src/constants.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
// @waggle/shared — Constants for M3 Team Pilot
|
||||
|
||||
export const TEAM_ROLES = ['owner', 'admin', 'member'] as const;
|
||||
export const TASK_STATUSES = ['open', 'claimed', 'in_progress', 'done', 'cancelled'] as const;
|
||||
export const TASK_PRIORITIES = ['critical', 'high', 'normal', 'low'] as const;
|
||||
export const MESSAGE_TYPES = ['broadcast', 'request', 'response'] as const;
|
||||
export const JOB_TYPES = ['chat', 'task', 'cron', 'waggle'] as const;
|
||||
export const JOB_STATUSES = ['queued', 'running', 'completed', 'failed'] as const;
|
||||
export const AGENT_GROUP_STRATEGIES = ['parallel', 'sequential', 'coordinator'] as const;
|
||||
export const RESOURCE_TYPES = ['model_recipe', 'skill', 'tool_config', 'prompt_template'] as const;
|
||||
export const SUGGESTION_TYPES = ['dashboard', 'cron', 'share', 'skill', 'upgrade'] as const;
|
||||
|
||||
export const MAX_SUGGESTIONS_PER_INTERACTION = 1;
|
||||
export const SCOUT_DEFAULT_INTERVAL_MS = 24 * 60 * 60 * 1000; // daily
|
||||
export const SUBCONSCIOUS_INTERACTION_THRESHOLD = 10; // reflect every N tasks
|
||||
export const HIVE_MIND_CRON = '0 9 * * 1'; // weekly Monday 9am
|
||||
|
||||
/**
|
||||
* W2G — the prefix the chat route prepends to a persisted assistant turn when
|
||||
* generation fails. Shared so the server (persist) and the web client (decode a
|
||||
* reloaded failed turn back into an error block) can never drift. The bare error
|
||||
* message follows the prefix, matching the live SSE 'error' event payload.
|
||||
*/
|
||||
export const GENERATION_FAILED_PREFIX = 'Generation failed: ';
|
||||
11
packages/shared/src/index.ts
Normal file
11
packages/shared/src/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// @waggle/shared
|
||||
export * from './types.js';
|
||||
export * from './schemas.js';
|
||||
export * from './constants.js';
|
||||
export * from './tiers.js';
|
||||
export * from './mcp-catalog.js';
|
||||
export * from './connector-recommendations.js';
|
||||
export * from './tool-detection.js';
|
||||
export * from './risk.js';
|
||||
export * from './command-intent.js';
|
||||
export * from './loop-templates.js';
|
||||
144
packages/shared/src/loop-templates.ts
Normal file
144
packages/shared/src/loop-templates.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Knowledge-worker Loop templates — preset, report-only (L1) automations that
|
||||
* ride the `job_type:'loop'` executor (packages/server/src/local/loop-executor.ts).
|
||||
*
|
||||
* Each template is a ready-to-POST automation: a sensible default cadence plus
|
||||
* a `jobConfig` matching the executor's LoopSpec ({ prompt, query?, rubric? }).
|
||||
* They translate the recurring rhythms of knowledge work — the morning brief,
|
||||
* the weekly digest, the "who have I gone quiet on" sweep — into scheduled,
|
||||
* memory-grounded reports. All are report-only: a Loop tick generates a report
|
||||
* grounded in the workspace's memory and notifies; it never sends, writes, or
|
||||
* acts on an external system.
|
||||
*
|
||||
* Cadences are daily/weekly by design — a Loop is several LLM round-trips, so
|
||||
* tight crons are wasteful (the executor also enforces a per-loop cost floor).
|
||||
*/
|
||||
|
||||
export interface LoopTemplate {
|
||||
/** Stable id (used as the picker key). */
|
||||
id: string;
|
||||
/** User-facing title — becomes the automation name. */
|
||||
name: string;
|
||||
/** One-line description shown in the picker. */
|
||||
description: string;
|
||||
/** lucide-react icon name for the UI to resolve (falls back gracefully). */
|
||||
icon: string;
|
||||
/** The knowledge-worker role this rhythm belongs to (picker grouping hint). */
|
||||
role: string;
|
||||
/** Default cron expression (5-field). Daily/weekly by design. */
|
||||
defaultCron: string;
|
||||
/** Ready-to-persist loop config — shape matches the executor's LoopSpec. */
|
||||
jobConfig: {
|
||||
prompt: string;
|
||||
query?: string;
|
||||
rubric?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const LOOP_TEMPLATES: LoopTemplate[] = [
|
||||
{
|
||||
id: 'daily-desk-brief',
|
||||
name: 'Daily Desk Brief',
|
||||
description: 'Each morning, a short brief of what needs your attention today, grounded in this workspace.',
|
||||
icon: 'Sunrise',
|
||||
role: 'Executive assistant',
|
||||
defaultCron: '0 8 * * *',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'Brief me on what needs my attention today in this workspace. Pull from recent memory: ' +
|
||||
'open tasks, commitments I made, threads awaiting a reply, and anything time-sensitive. ' +
|
||||
'Lead with the 3 most important items. Be concise.',
|
||||
query: 'open tasks commitments awaiting reply deadlines today',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'weekly-wins',
|
||||
name: 'Weekly Wins Digest',
|
||||
description: 'A Friday digest of what shipped, closed, or moved this week — ready to share or paste into a status update.',
|
||||
icon: 'Trophy',
|
||||
role: 'Project manager',
|
||||
defaultCron: '0 16 * * 5',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'Summarise what shipped, closed, or moved forward in this workspace over the past week. ' +
|
||||
'Group by theme. Write it as a short status update I could share with my team or manager.',
|
||||
query: 'shipped completed closed progress this week milestones',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'relationship-decay',
|
||||
name: 'Relationship Decay Sweep',
|
||||
description: 'Weekly: contacts and deals you have gone quiet on, ranked by importance, so nothing slips.',
|
||||
icon: 'HeartHandshake',
|
||||
role: 'Sales / Account management',
|
||||
defaultCron: '0 9 * * 1',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'From this workspace\'s memory, identify the contacts, accounts, or deals I have gone quiet on. ' +
|
||||
'Rank them by importance and how long since the last touch. For each, suggest a one-line reason to reconnect. ' +
|
||||
'Report only — do not draft or send anything.',
|
||||
query: 'contacts accounts deals last contact follow up gone quiet',
|
||||
rubric: 'Reward concrete, specific contacts grounded in memory with a clear last-touch rationale. ' +
|
||||
'Penalise vague or invented names.',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'inbound-triage',
|
||||
name: 'Inbound Triage Report',
|
||||
description: 'Weekday mornings: classify and summarise new inbound items (requests, leads, tickets) — propose-only.',
|
||||
icon: 'Inbox',
|
||||
role: 'Support / Operations',
|
||||
defaultCron: '0 7 * * 1-5',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'Triage the new inbound items captured in this workspace since the last run. ' +
|
||||
'Classify each (e.g. request, lead, question, FYI), flag anything urgent, and suggest who or what should handle it. ' +
|
||||
'This is a report and proposal only — take no action.',
|
||||
query: 'new inbound requests tickets leads questions to triage',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'renewal-radar',
|
||||
name: 'Renewal & Expiry Radar',
|
||||
description: 'Weekly scan for contracts, licenses, or subscriptions coming up for renewal or expiry.',
|
||||
icon: 'CalendarClock',
|
||||
role: 'Operations / Legal / Finance',
|
||||
defaultCron: '0 9 * * 1',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'Scan this workspace\'s memory for contracts, licenses, subscriptions, or commitments with an upcoming ' +
|
||||
'renewal, expiry, or review date. List them soonest-first with the date and what action they need. ' +
|
||||
'If you cannot determine a date, say so rather than guessing.',
|
||||
query: 'contract license subscription renewal expiry review date deadline',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'commitment-tracker',
|
||||
name: 'Commitment Tracker',
|
||||
description: 'End of each weekday: surface the promises you made ("I\'ll send X by Friday") so none are dropped.',
|
||||
icon: 'CheckSquare',
|
||||
role: 'Executive assistant',
|
||||
defaultCron: '0 17 * * 1-5',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'Review this workspace\'s recent memory for commitments I made — things I said I would do, send, or follow up on. ' +
|
||||
'List each with who it is owed to and any deadline. Flag any that look overdue. Report only.',
|
||||
query: 'I will send follow up by promised commitment owe deadline',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'competitor-watch',
|
||||
name: 'Competitor Watch Digest',
|
||||
description: 'Weekly: what changed about the competitors and market signals tracked in this workspace.',
|
||||
icon: 'Telescope',
|
||||
role: 'Marketing / Product',
|
||||
defaultCron: '0 9 * * 1',
|
||||
jobConfig: {
|
||||
prompt:
|
||||
'Summarise what is new or changed about the competitors and market signals tracked in this workspace ' +
|
||||
'since the previous run. Emphasise concrete, sourced developments. If nothing materially changed, say so.',
|
||||
query: 'competitor market signal launch pricing announcement change',
|
||||
rubric: 'Reward concrete, novel, sourced items grounded in memory. Penalise speculation or repetition of prior reports.',
|
||||
},
|
||||
},
|
||||
];
|
||||
319
packages/shared/src/mcp-catalog.ts
Normal file
319
packages/shared/src/mcp-catalog.ts
Normal file
@@ -0,0 +1,319 @@
|
||||
/**
|
||||
* MCP Server Registry — curated catalog of community MCP servers.
|
||||
*
|
||||
* Sources cross-referenced:
|
||||
* - modelcontextprotocol/servers (official Anthropic reference)
|
||||
* - punkpeye/awesome-mcp-servers (77K stars)
|
||||
* - appcypher/awesome-mcp-servers
|
||||
* - tolkonepiu/best-of-mcp-servers (450 ranked, 34 categories)
|
||||
*
|
||||
* Deduplication is enforced at module load time via `assertCatalogUnique`
|
||||
* at the bottom of this file — adding a server with a colliding normalized
|
||||
* id or url will crash the build.
|
||||
*
|
||||
* Organized by category for the ConnectorsApp MCP tab.
|
||||
*/
|
||||
|
||||
export interface McpServer {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
author: string;
|
||||
category: string;
|
||||
url: string;
|
||||
installCmd: string;
|
||||
capabilities: string[];
|
||||
official?: boolean;
|
||||
logo?: string; // Emoji or brand initial
|
||||
}
|
||||
|
||||
export const MCP_CATEGORIES = [
|
||||
'Database', 'Files', 'Web', 'Code', 'Communication',
|
||||
'Productivity', 'Analytics', 'Cloud', 'DevTools',
|
||||
'Business', 'AI & ML', 'Security', 'Media', 'Utilities',
|
||||
] as const;
|
||||
|
||||
export const CATEGORY_EMOJI: Record<string, string> = {
|
||||
'Database': '\u{1F4BE}', // floppy
|
||||
'Files': '\u{1F4C1}', // folder
|
||||
'Web': '\u{1F310}', // globe
|
||||
'Code': '\u{1F4BB}', // laptop
|
||||
'Communication': '\u{1F4AC}', // speech
|
||||
'Productivity': '\u{1F4CB}', // clipboard
|
||||
'Analytics': '\u{1F4CA}', // chart
|
||||
'Cloud': '\u{2601}', // cloud
|
||||
'DevTools': '\u{1F527}', // wrench
|
||||
'Business': '\u{1F4BC}', // briefcase
|
||||
'AI & ML': '\u{1F916}', // robot
|
||||
'Security': '\u{1F512}', // lock
|
||||
'Media': '\u{1F3A8}', // palette
|
||||
'Utilities': '\u{2699}', // gear
|
||||
};
|
||||
|
||||
export const MCP_CATALOG: McpServer[] = [
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// DATABASE (12)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'postgres', name: 'PostgreSQL', description: 'Query databases, inspect schemas, run migrations', author: 'MCP', category: 'Database', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/postgres', installCmd: 'npx @modelcontextprotocol/server-postgres', capabilities: ['query', 'schema', 'migrations'], official: true, logo: 'PG' },
|
||||
{ id: 'sqlite', name: 'SQLite', description: 'Read and query SQLite databases', author: 'MCP', category: 'Database', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite', installCmd: 'npx @modelcontextprotocol/server-sqlite', capabilities: ['query', 'schema'], official: true, logo: 'SQ' },
|
||||
{ id: 'mysql', name: 'MySQL', description: 'Query MySQL/MariaDB databases', author: 'Community', category: 'Database', url: 'https://github.com/benborla/mcp-server-mysql', installCmd: 'npx mcp-server-mysql', capabilities: ['query', 'schema', 'tables'] },
|
||||
{ id: 'mongodb', name: 'MongoDB', description: 'Query MongoDB collections, inspect schemas', author: 'Community', category: 'Database', url: 'https://github.com/mongodb-labs/mongodb-mcp-server', installCmd: 'npx mongodb-mcp-server', capabilities: ['query', 'aggregate', 'collections'] },
|
||||
{ id: 'redis', name: 'Redis', description: 'Interact with Redis key-value store', author: 'Community', category: 'Database', url: 'https://github.com/redis/mcp-redis', installCmd: 'npx @mcp/redis-server', capabilities: ['get', 'set', 'query'] },
|
||||
{ id: 'neo4j', name: 'Neo4j', description: 'Query graph databases with Cypher', author: 'Community', category: 'Database', url: 'https://github.com/neo4j-contrib/mcp-neo4j', installCmd: 'npx mcp-neo4j', capabilities: ['cypher', 'nodes', 'relations'] },
|
||||
{ id: 'supabase', name: 'Supabase', description: 'Manage Supabase projects, query data, auth', author: 'Community', category: 'Database', url: 'https://github.com/supabase-community/supabase-mcp', installCmd: 'npx supabase-mcp-server', capabilities: ['query', 'auth', 'storage', 'functions'] },
|
||||
{ id: 'neon', name: 'Neon', description: 'Serverless Postgres — create databases, query, branch', author: 'Community', category: 'Database', url: 'https://github.com/neondatabase/mcp-server-neon', installCmd: 'npx mcp-server-neon', capabilities: ['query', 'branches', 'databases'] },
|
||||
{ id: 'qdrant', name: 'Qdrant', description: 'Vector database — search, upsert, collections', author: 'Community', category: 'Database', url: 'https://github.com/qdrant/mcp-server-qdrant', installCmd: 'npx mcp-server-qdrant', capabilities: ['search', 'upsert', 'collections'] },
|
||||
{ id: 'turso', name: 'Turso', description: 'Edge SQLite database (libSQL)', author: 'Community', category: 'Database', url: 'https://github.com/turso-extended/mcp-server-turso', installCmd: 'npx mcp-server-turso', capabilities: ['query', 'schema'] },
|
||||
{ id: 'planetscale', name: 'PlanetScale', description: 'MySQL-compatible serverless database', author: 'Community', category: 'Database', url: 'https://github.com/planetscale/mcp-server', installCmd: 'npx @planetscale/mcp-server', capabilities: ['query', 'schema', 'branches'] },
|
||||
{ id: 'clickhouse', name: 'ClickHouse', description: 'Analytics database — fast SQL queries', author: 'Community', category: 'Database', url: 'https://github.com/ClickHouse/mcp-server', installCmd: 'npx @clickhouse/mcp-server', capabilities: ['query', 'tables', 'analytics'] },
|
||||
{ id: 'bigquery', name: 'BigQuery', description: 'Google BigQuery — serverless data warehouse and analytics', author: 'Community', category: 'Database', url: 'https://github.com/LucasHild/mcp-server-bigquery', installCmd: 'npx mcp-server-bigquery', capabilities: ['query', 'schema', 'datasets'] },
|
||||
{ id: 'snowflake', name: 'Snowflake', description: 'Cloud data warehouse — read/write with insight tracking', author: 'Community', category: 'Database', url: 'https://github.com/Snowflake-Labs/mcp', installCmd: 'uvx mcp-snowflake', capabilities: ['query', 'schema', 'warehouses'] },
|
||||
{ id: 'duckdb', name: 'DuckDB', description: 'In-process analytical SQL database', author: 'Community', category: 'Database', url: 'https://github.com/ktanaka101/mcp-server-duckdb', installCmd: 'uvx mcp-server-duckdb', capabilities: ['query', 'schema', 'analytics'] },
|
||||
{ id: 'couchbase', name: 'Couchbase', description: 'Distributed NoSQL — natural language querying', author: 'Community', category: 'Database', url: 'https://github.com/Couchbase-Ecosystem/mcp-server-couchbase', installCmd: 'npx mcp-server-couchbase', capabilities: ['query', 'buckets', 'n1ql'] },
|
||||
{ id: 'tidb', name: 'TiDB', description: 'Distributed MySQL-compatible serverless database', author: 'Community', category: 'Database', url: 'https://github.com/pingcap/pytidb', installCmd: 'uvx pytidb-mcp', capabilities: ['query', 'schema', 'vector_search'] },
|
||||
{ id: 'excel', name: 'Microsoft Excel', description: 'Read/write Excel workbooks — cells, worksheets, charts', author: 'Community', category: 'Database', url: 'https://github.com/haris-musa/excel-mcp-server', installCmd: 'uvx excel-mcp-server', capabilities: ['read', 'write', 'charts', 'pivot'] },
|
||||
{ id: 'nocodb', name: 'NocoDB', description: 'Open-source Airtable alternative — records, tables, views', author: 'Community', category: 'Database', url: 'https://github.com/edwinbernadus/nocodb-mcp-server', installCmd: 'npx nocodb-mcp-server', capabilities: ['read', 'write', 'tables'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// FILES & STORAGE (8)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'filesystem', name: 'Filesystem', description: 'Read, write, search files on local filesystem', author: 'MCP', category: 'Files', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem', installCmd: 'npx @modelcontextprotocol/server-filesystem /path', capabilities: ['read', 'write', 'search', 'directory'], official: true },
|
||||
{ id: 'gdrive-mcp', name: 'Google Drive', description: 'Search and read Google Drive files', author: 'MCP', category: 'Files', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/gdrive', installCmd: 'npx @modelcontextprotocol/server-gdrive', capabilities: ['search', 'read', 'list'], official: true },
|
||||
{ id: 's3', name: 'AWS S3', description: 'List, read, and manage S3 objects', author: 'Community', category: 'Files', url: 'https://github.com/aws/mcp-server-s3', installCmd: 'npx @aws/mcp-server-s3', capabilities: ['list', 'read', 'write'] },
|
||||
{ id: 'onedrive-mcp', name: 'OneDrive', description: 'Access Microsoft OneDrive files', author: 'Community', category: 'Files', url: 'https://github.com/microsoft/mcp-server-onedrive', installCmd: 'npx mcp-server-onedrive', capabilities: ['read', 'list', 'search'] },
|
||||
{ id: 'box', name: 'Box', description: 'Enterprise file sharing and management', author: 'Community', category: 'Files', url: 'https://github.com/box/mcp-server-box', installCmd: 'npx @box/mcp-server', capabilities: ['read', 'upload', 'search'] },
|
||||
{ id: 'dropbox-mcp', name: 'Dropbox', description: 'Access Dropbox files and folders', author: 'Community', category: 'Files', url: 'https://github.com/dropbox/mcp-server', installCmd: 'npx mcp-server-dropbox', capabilities: ['read', 'list', 'search'] },
|
||||
{ id: 'minio', name: 'MinIO', description: 'S3-compatible object storage', author: 'Community', category: 'Files', url: 'https://github.com/minio/mcp-server-minio', installCmd: 'npx mcp-server-minio', capabilities: ['list', 'read', 'write', 'buckets'] },
|
||||
{ id: 'gcs', name: 'Google Cloud Storage', description: 'Access GCS buckets and objects', author: 'Community', category: 'Files', url: 'https://github.com/GoogleCloudPlatform/mcp-server-gcs', installCmd: 'npx mcp-server-gcs', capabilities: ['list', 'read', 'write'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// WEB & SEARCH (10)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'brave-search', name: 'Brave Search', description: 'Web and local search via Brave API', author: 'MCP', category: 'Web', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/brave-search', installCmd: 'npx @modelcontextprotocol/server-brave-search', capabilities: ['web_search', 'local_search'], official: true },
|
||||
{ id: 'fetch', name: 'Fetch', description: 'Fetch URLs and convert to markdown', author: 'MCP', category: 'Web', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/fetch', installCmd: 'npx @modelcontextprotocol/server-fetch', capabilities: ['fetch', 'convert'], official: true },
|
||||
{ id: 'puppeteer', name: 'Puppeteer', description: 'Browser automation — navigate, screenshot, interact', author: 'MCP', category: 'Web', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/puppeteer', installCmd: 'npx @modelcontextprotocol/server-puppeteer', capabilities: ['navigate', 'screenshot', 'click'], official: true },
|
||||
{ id: 'playwright-mcp', name: 'Playwright', description: 'Cross-browser automation and testing', author: 'Community', category: 'Web', url: 'https://github.com/playwright-community/mcp-server-playwright', installCmd: 'npx @playwright/mcp-server', capabilities: ['navigate', 'screenshot', 'test'] },
|
||||
{ id: 'tavily', name: 'Tavily', description: 'AI-optimized web search API', author: 'Community', category: 'Web', url: 'https://github.com/tavily-ai/mcp-server-tavily', installCmd: 'npx mcp-server-tavily', capabilities: ['search', 'extract'] },
|
||||
{ id: 'exa', name: 'Exa', description: 'Neural search engine — semantic web search', author: 'Community', category: 'Web', url: 'https://github.com/exa-labs/exa-mcp-server', installCmd: 'npx exa-mcp-server', capabilities: ['search', 'contents', 'similar'] },
|
||||
{ id: 'firecrawl', name: 'Firecrawl', description: 'Web scraping and crawling with markdown output', author: 'Community', category: 'Web', url: 'https://github.com/firecrawl/mcp-server-firecrawl', installCmd: 'npx mcp-server-firecrawl', capabilities: ['scrape', 'crawl', 'extract'] },
|
||||
{ id: 'serper', name: 'Serper', description: 'Google Search API results', author: 'Community', category: 'Web', url: 'https://github.com/nichochar/mcp-server-serper', installCmd: 'npx mcp-server-serper', capabilities: ['search', 'news', 'images'] },
|
||||
{ id: 'browserbase', name: 'Browserbase', description: 'Cloud browser automation platform', author: 'Community', category: 'Web', url: 'https://github.com/browserbase/mcp-server', installCmd: 'npx @browserbase/mcp-server', capabilities: ['navigate', 'screenshot', 'session'] },
|
||||
{ id: 'jina', name: 'Jina Reader', description: 'Extract content from any URL as clean text', author: 'Community', category: 'Web', url: 'https://github.com/jina-ai/mcp-server', installCmd: 'npx mcp-server-jina', capabilities: ['read', 'extract', 'summarize'] },
|
||||
{ id: 'perplexity', name: 'Perplexity', description: 'Perplexity AI — real-time web search with citations', author: 'Community', category: 'Web', url: 'https://github.com/ppl-ai/modelcontextprotocol', installCmd: 'npx @perplexity-ai/mcp-server', capabilities: ['search', 'ask', 'citations'] },
|
||||
{ id: 'kagi', name: 'Kagi Search', description: 'Privacy-focused premium search engine', author: 'Community', category: 'Web', url: 'https://github.com/kagisearch/kagimcp', installCmd: 'uvx kagimcp', capabilities: ['search', 'summarize', 'universal'] },
|
||||
{ id: 'searxng', name: 'SearXNG', description: 'Self-hosted privacy-respecting metasearch engine', author: 'Community', category: 'Web', url: 'https://github.com/ihor-sokoliuk/mcp-searxng', installCmd: 'npx mcp-searxng', capabilities: ['search', 'images', 'news'] },
|
||||
{ id: 'apify', name: 'Apify', description: 'Web scraping platform — run 4000+ pre-built actors', author: 'Community', category: 'Web', url: 'https://github.com/apify/actors-mcp-server', installCmd: 'npx @apify/actors-mcp-server', capabilities: ['scrape', 'crawl', 'actors'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CODE & DEVTOOLS (12)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'github-mcp', name: 'GitHub', description: 'Repos, issues, PRs, code search, actions', author: 'MCP', category: 'Code', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/github', installCmd: 'npx @modelcontextprotocol/server-github', capabilities: ['repos', 'issues', 'prs', 'search', 'actions'], official: true },
|
||||
{ id: 'gitlab-mcp', name: 'GitLab', description: 'Projects, issues, merge requests, pipelines', author: 'Community', category: 'Code', url: 'https://github.com/theanhne/mcp-server-gitlab', installCmd: 'npx mcp-server-gitlab', capabilities: ['projects', 'issues', 'mrs', 'pipelines'] },
|
||||
{ id: 'sentry', name: 'Sentry', description: 'Error tracking — issues, events, releases', author: 'MCP', category: 'DevTools', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sentry', installCmd: 'npx @modelcontextprotocol/server-sentry', capabilities: ['issues', 'events', 'projects'], official: true },
|
||||
{ id: 'docker', name: 'Docker', description: 'Manage containers, images, compose stacks', author: 'Community', category: 'DevTools', url: 'https://github.com/docker/mcp-server-docker', installCmd: 'npx mcp-server-docker', capabilities: ['containers', 'images', 'compose', 'logs'] },
|
||||
{ id: 'kubernetes', name: 'Kubernetes', description: 'Manage K8s clusters, pods, deployments', author: 'Community', category: 'DevTools', url: 'https://github.com/kubernetes/mcp-server', installCmd: 'npx mcp-server-kubernetes', capabilities: ['pods', 'deployments', 'services', 'logs'] },
|
||||
{ id: 'vercel', name: 'Vercel', description: 'Deployments, domains, environment variables', author: 'Community', category: 'DevTools', url: 'https://github.com/vercel/mcp-server', installCmd: 'npx @vercel/mcp-server', capabilities: ['deployments', 'domains', 'env', 'logs'] },
|
||||
{ id: 'npm', name: 'npm', description: 'Search packages, view details, check versions', author: 'Community', category: 'Code', url: 'https://github.com/nichochar/mcp-server-npm', installCmd: 'npx mcp-server-npm', capabilities: ['search', 'info', 'versions'] },
|
||||
{ id: 'grafana', name: 'Grafana', description: 'Query dashboards, alerts, and datasources', author: 'Community', category: 'DevTools', url: 'https://github.com/grafana/mcp-server-grafana', installCmd: 'npx mcp-server-grafana', capabilities: ['dashboards', 'alerts', 'queries'] },
|
||||
{ id: 'datadog', name: 'Datadog', description: 'Metrics, logs, monitors, and incidents', author: 'Community', category: 'DevTools', url: 'https://github.com/DataDog/mcp-server', installCmd: 'npx @datadog/mcp-server', capabilities: ['metrics', 'logs', 'monitors'] },
|
||||
{ id: 'circleci', name: 'CircleCI', description: 'Pipelines, jobs, artifacts', author: 'Community', category: 'DevTools', url: 'https://github.com/CircleCI-Public/mcp-server-circleci', installCmd: 'npx mcp-server-circleci', capabilities: ['pipelines', 'jobs', 'artifacts'] },
|
||||
{ id: 'terraform', name: 'Terraform', description: 'Infrastructure as code — plan, apply, state', author: 'Community', category: 'DevTools', url: 'https://github.com/hashicorp/mcp-server-terraform', installCmd: 'npx mcp-server-terraform', capabilities: ['plan', 'state', 'modules'] },
|
||||
{ id: 'cloudflare', name: 'Cloudflare', description: 'Workers, DNS, KV, R2, analytics', author: 'Community', category: 'DevTools', url: 'https://github.com/cloudflare/mcp-server-cloudflare', installCmd: 'npx @cloudflare/mcp-server', capabilities: ['workers', 'dns', 'kv', 'r2'] },
|
||||
{ id: 'git', name: 'Git', description: 'Read, search, and manipulate local Git repositories', author: 'MCP', category: 'Code', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/git', installCmd: 'uvx mcp-server-git', capabilities: ['log', 'diff', 'status', 'blame'], official: true },
|
||||
{ id: 'postman', name: 'Postman', description: 'API development — collections, requests, environments', author: 'Community', category: 'DevTools', url: 'https://github.com/shannonlal/mcp-postman', installCmd: 'npx mcp-postman', capabilities: ['collections', 'requests', 'environments'] },
|
||||
{ id: 'pulumi', name: 'Pulumi', description: 'Infrastructure as code with real languages — preview, up, state', author: 'Community', category: 'DevTools', url: 'https://github.com/pulumi/mcp-server', installCmd: 'npx @pulumi/mcp-server', capabilities: ['preview', 'up', 'state', 'stacks'] },
|
||||
{ id: 'gitkraken', name: 'GitKraken', description: 'Git client — workspaces, PRs, issues across platforms', author: 'Community', category: 'DevTools', url: 'https://github.com/gitkraken/gk-cli', installCmd: 'gk mcp', capabilities: ['workspaces', 'prs', 'issues', 'focus'] },
|
||||
{ id: 'semgrep', name: 'Semgrep', description: 'Static analysis — security and code quality scans', author: 'Community', category: 'DevTools', url: 'https://github.com/semgrep/mcp', installCmd: 'uvx semgrep-mcp', capabilities: ['scan', 'rules', 'findings'] },
|
||||
{ id: 'e2b', name: 'E2B', description: 'Secure cloud sandboxes — run untrusted code in isolated containers', author: 'Community', category: 'DevTools', url: 'https://github.com/e2b-dev/mcp-server', installCmd: 'npx @e2b/mcp-server', capabilities: ['run', 'files', 'sandbox'] },
|
||||
{ id: 'skyvern', name: 'Skyvern', description: 'Browser automation powered by LLMs — navigate, fill forms, scrape', author: 'Community', category: 'DevTools', url: 'https://github.com/Skyvern-AI/skyvern/tree/main/integrations/mcp', installCmd: 'uvx skyvern-mcp', capabilities: ['navigate', 'fill', 'scrape', 'vision'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// COMMUNICATION (8)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'slack-mcp', name: 'Slack', description: 'Read/send messages, manage channels, search', author: 'MCP', category: 'Communication', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/slack', installCmd: 'npx @modelcontextprotocol/server-slack', capabilities: ['read', 'send', 'channels', 'search'], official: true },
|
||||
{ id: 'gmail-mcp', name: 'Gmail', description: 'Read, search, and send emails', author: 'Community', category: 'Communication', url: 'https://github.com/nichochar/mcp-server-gmail', installCmd: 'npx mcp-server-gmail', capabilities: ['read', 'search', 'send'] },
|
||||
{ id: 'discord-mcp', name: 'Discord', description: 'Read/send messages, manage channels', author: 'Community', category: 'Communication', url: 'https://github.com/discord/mcp-server', installCmd: 'npx mcp-server-discord', capabilities: ['read', 'send', 'channels'] },
|
||||
{ id: 'teams-mcp', name: 'Microsoft Teams', description: 'Messages, channels, meetings', author: 'Community', category: 'Communication', url: 'https://github.com/microsoft/mcp-server-teams', installCmd: 'npx mcp-server-teams', capabilities: ['messages', 'channels', 'meetings'] },
|
||||
{ id: 'telegram', name: 'Telegram', description: 'Send/receive Telegram messages', author: 'Community', category: 'Communication', url: 'https://github.com/nichochar/mcp-server-telegram', installCmd: 'npx mcp-server-telegram', capabilities: ['send', 'receive', 'groups'] },
|
||||
{ id: 'whatsapp', name: 'WhatsApp', description: 'Send WhatsApp messages via Business API', author: 'Community', category: 'Communication', url: 'https://github.com/nichochar/mcp-server-whatsapp', installCmd: 'npx mcp-server-whatsapp', capabilities: ['send', 'templates'] },
|
||||
{ id: 'twilio', name: 'Twilio', description: 'SMS, voice calls, WhatsApp messaging', author: 'Community', category: 'Communication', url: 'https://github.com/twilio/mcp-server', installCmd: 'npx @twilio/mcp-server', capabilities: ['sms', 'voice', 'whatsapp'] },
|
||||
{ id: 'sendgrid', name: 'SendGrid', description: 'Transactional and marketing email', author: 'Community', category: 'Communication', url: 'https://github.com/sendgrid/mcp-server', installCmd: 'npx mcp-server-sendgrid', capabilities: ['send', 'templates', 'stats'] },
|
||||
{ id: 'bluesky', name: 'Bluesky', description: 'Post, read, and search the Bluesky social network (AT Protocol)', author: 'Community', category: 'Communication', url: 'https://github.com/semioz/bluesky-mcp', installCmd: 'npx bluesky-mcp', capabilities: ['post', 'feed', 'search', 'follow'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PRODUCTIVITY (12)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'notion-mcp', name: 'Notion', description: 'Pages, databases, search, create content', author: 'Community', category: 'Productivity', url: 'https://github.com/makenotion/notion-mcp-server', installCmd: 'npx @notionhq/mcp-server', capabilities: ['search', 'read', 'create', 'databases'] },
|
||||
{ id: 'linear-mcp', name: 'Linear', description: 'Issues, projects, cycles, teams', author: 'Community', category: 'Productivity', url: 'https://github.com/linear/mcp-server-linear', installCmd: 'npx mcp-server-linear', capabilities: ['issues', 'projects', 'cycles'] },
|
||||
{ id: 'jira-mcp', name: 'Jira', description: 'Issues, projects, sprints, boards', author: 'Community', category: 'Productivity', url: 'https://github.com/atlassian/mcp-server-jira', installCmd: 'npx mcp-server-jira', capabilities: ['issues', 'search', 'projects', 'sprints'] },
|
||||
{ id: 'confluence-mcp', name: 'Confluence', description: 'Read and search Confluence pages', author: 'Community', category: 'Productivity', url: 'https://github.com/atlassian/mcp-server-confluence', installCmd: 'npx mcp-server-confluence', capabilities: ['search', 'read', 'spaces'] },
|
||||
{ id: 'asana-mcp', name: 'Asana', description: 'Tasks, projects, teams, workspaces', author: 'Community', category: 'Productivity', url: 'https://github.com/asana/mcp-server', installCmd: 'npx mcp-server-asana', capabilities: ['tasks', 'projects', 'teams'] },
|
||||
{ id: 'todoist', name: 'Todoist', description: 'Task management — projects, tasks, labels', author: 'Community', category: 'Productivity', url: 'https://github.com/doist/mcp-server-todoist', installCmd: 'npx mcp-server-todoist', capabilities: ['tasks', 'projects', 'labels'] },
|
||||
{ id: 'google-calendar', name: 'Google Calendar', description: 'Events, scheduling, availability', author: 'Community', category: 'Productivity', url: 'https://github.com/nichochar/mcp-server-gcal', installCmd: 'npx mcp-server-gcal', capabilities: ['events', 'create', 'availability'] },
|
||||
{ id: 'google-docs', name: 'Google Docs', description: 'Read and edit Google Docs', author: 'Community', category: 'Productivity', url: 'https://github.com/nichochar/mcp-server-gdocs', installCmd: 'npx mcp-server-gdocs', capabilities: ['read', 'edit', 'create'] },
|
||||
{ id: 'google-sheets', name: 'Google Sheets', description: 'Read, write, and query spreadsheets', author: 'Community', category: 'Productivity', url: 'https://github.com/nichochar/mcp-server-gsheets', installCmd: 'npx mcp-server-gsheets', capabilities: ['read', 'write', 'query'] },
|
||||
{ id: 'obsidian-mcp', name: 'Obsidian', description: 'Read and search Obsidian vaults', author: 'Community', category: 'Productivity', url: 'https://github.com/obsidian-community/mcp-server', installCmd: 'npx mcp-server-obsidian', capabilities: ['read', 'search', 'backlinks'] },
|
||||
{ id: 'monday-mcp', name: 'monday.com', description: 'Boards, items, updates, automations', author: 'Community', category: 'Productivity', url: 'https://github.com/mondaycom/mcp-server', installCmd: 'npx mcp-server-monday', capabilities: ['boards', 'items', 'updates'] },
|
||||
{ id: 'clickup', name: 'ClickUp', description: 'Tasks, spaces, lists, docs', author: 'Community', category: 'Productivity', url: 'https://github.com/clickup/mcp-server', installCmd: 'npx mcp-server-clickup', capabilities: ['tasks', 'spaces', 'docs'] },
|
||||
{ id: 'atlassian', name: 'Atlassian', description: 'Unified Jira + Confluence access across Cloud and Server', author: 'Community', category: 'Productivity', url: 'https://github.com/sooperset/mcp-atlassian', installCmd: 'uvx mcp-atlassian', capabilities: ['jira', 'confluence', 'search', 'issues'] },
|
||||
{ id: 'make', name: 'Make', description: 'Run Make.com (Integromat) scenarios — automation orchestration', author: 'Community', category: 'Productivity', url: 'https://github.com/integromat/make-mcp-server', installCmd: 'npx @make/mcp-server', capabilities: ['scenarios', 'triggers', 'webhooks'] },
|
||||
{ id: 'pipedream', name: 'Pipedream', description: 'Workflow automation with 2000+ integrations', author: 'Community', category: 'Productivity', url: 'https://github.com/PipedreamHQ/pipedream/tree/master/modelcontextprotocol', installCmd: 'npx @pipedream/mcp-server', capabilities: ['workflows', 'apps', 'triggers'] },
|
||||
{ id: 'microsoft-365', name: 'Microsoft 365', description: 'Full M365 suite via Graph API — mail, files, calendar, Excel', author: 'Community', category: 'Productivity', url: 'https://github.com/softeria/ms-365-mcp-server', installCmd: 'npx ms-365-mcp-server', capabilities: ['mail', 'files', 'calendar', 'excel'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// BUSINESS & CRM (8)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'stripe-mcp', name: 'Stripe', description: 'Payments, subscriptions, customers, invoices', author: 'Community', category: 'Business', url: 'https://github.com/stripe/agent-toolkit', installCmd: 'npx @stripe/mcp-server', capabilities: ['payments', 'customers', 'subscriptions', 'invoices'] },
|
||||
{ id: 'salesforce-mcp', name: 'Salesforce', description: 'CRM — accounts, contacts, opportunities', author: 'Community', category: 'Business', url: 'https://github.com/salesforce/mcp-server', installCmd: 'npx mcp-server-salesforce', capabilities: ['accounts', 'contacts', 'opportunities', 'soql'] },
|
||||
{ id: 'hubspot-mcp', name: 'HubSpot', description: 'CRM, marketing, sales, service hub', author: 'Community', category: 'Business', url: 'https://github.com/hubspot/mcp-server', installCmd: 'npx mcp-server-hubspot', capabilities: ['contacts', 'deals', 'tickets', 'email'] },
|
||||
{ id: 'shopify', name: 'Shopify', description: 'Products, orders, customers, inventory', author: 'Community', category: 'Business', url: 'https://github.com/shopify/mcp-server', installCmd: 'npx mcp-server-shopify', capabilities: ['products', 'orders', 'customers'] },
|
||||
{ id: 'airtable-mcp', name: 'Airtable', description: 'Bases, tables, records, views', author: 'Community', category: 'Business', url: 'https://github.com/airtable/mcp-server', installCmd: 'npx mcp-server-airtable', capabilities: ['records', 'tables', 'views'] },
|
||||
{ id: 'intercom', name: 'Intercom', description: 'Customer messaging, tickets, articles', author: 'Community', category: 'Business', url: 'https://github.com/intercom/mcp-server', installCmd: 'npx mcp-server-intercom', capabilities: ['conversations', 'contacts', 'articles'] },
|
||||
{ id: 'zendesk', name: 'Zendesk', description: 'Support tickets, users, organizations', author: 'Community', category: 'Business', url: 'https://github.com/zendesk/mcp-server', installCmd: 'npx mcp-server-zendesk', capabilities: ['tickets', 'users', 'search'] },
|
||||
{ id: 'freshdesk', name: 'Freshdesk', description: 'Help desk — tickets, contacts, knowledge base', author: 'Community', category: 'Business', url: 'https://github.com/nichochar/mcp-server-freshdesk', installCmd: 'npx mcp-server-freshdesk', capabilities: ['tickets', 'contacts', 'kb'] },
|
||||
{ id: 'chargebee', name: 'Chargebee', description: 'Subscription billing — customers, invoices, plans', author: 'Community', category: 'Business', url: 'https://github.com/chargebee/agentkit/tree/main/modelcontextprotocol', installCmd: 'npx @chargebee/mcp-server', capabilities: ['subscriptions', 'invoices', 'customers'] },
|
||||
{ id: 'google-ads', name: 'Google Ads', description: 'Campaigns, keywords, performance reports', author: 'Community', category: 'Business', url: 'https://github.com/gomarble-ai/google-ads-mcp-server', installCmd: 'npx google-ads-mcp', capabilities: ['campaigns', 'keywords', 'reports'] },
|
||||
{ id: 'facebook-ads', name: 'Facebook Ads', description: 'Meta ad accounts, campaigns, creatives, insights', author: 'Community', category: 'Business', url: 'https://github.com/gomarble-ai/facebook-ads-mcp-server', installCmd: 'npx facebook-ads-mcp', capabilities: ['campaigns', 'creatives', 'insights'] },
|
||||
{ id: 'google-maps', name: 'Google Maps', description: 'Geocoding, places, directions, distance matrix', author: 'Community', category: 'Business', url: 'https://github.com/cablate/mcp-google-map', installCmd: 'npx mcp-google-map', capabilities: ['geocode', 'places', 'directions'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CLOUD & INFRASTRUCTURE (8)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'aws', name: 'AWS', description: 'EC2, Lambda, CloudWatch, IAM, and more', author: 'Community', category: 'Cloud', url: 'https://github.com/aws/mcp-server-aws', installCmd: 'npx @aws/mcp-server', capabilities: ['ec2', 'lambda', 'cloudwatch', 'iam'] },
|
||||
{ id: 'gcp', name: 'Google Cloud', description: 'Compute, BigQuery, Cloud Run, IAM', author: 'Community', category: 'Cloud', url: 'https://github.com/GoogleCloudPlatform/mcp-server', installCmd: 'npx mcp-server-gcp', capabilities: ['compute', 'bigquery', 'run', 'iam'] },
|
||||
{ id: 'azure', name: 'Azure', description: 'VMs, Functions, CosmosDB, Active Directory', author: 'Community', category: 'Cloud', url: 'https://github.com/microsoft/mcp-server-azure', installCmd: 'npx mcp-server-azure', capabilities: ['vms', 'functions', 'cosmosdb'] },
|
||||
{ id: 'fly', name: 'Fly.io', description: 'Deploy and manage Fly.io applications', author: 'Community', category: 'Cloud', url: 'https://github.com/fly-io/mcp-server', installCmd: 'npx mcp-server-fly', capabilities: ['deploy', 'machines', 'secrets'] },
|
||||
{ id: 'railway', name: 'Railway', description: 'Deploy apps, manage services and databases', author: 'Community', category: 'Cloud', url: 'https://github.com/railwayapp/mcp-server', installCmd: 'npx mcp-server-railway', capabilities: ['deploy', 'services', 'variables'] },
|
||||
{ id: 'render', name: 'Render', description: 'Web services, databases, cron jobs', author: 'Community', category: 'Cloud', url: 'https://github.com/render-oss/mcp-server', installCmd: 'npx mcp-server-render', capabilities: ['services', 'databases', 'deploys'] },
|
||||
{ id: 'digitalocean', name: 'DigitalOcean', description: 'Droplets, databases, Kubernetes', author: 'Community', category: 'Cloud', url: 'https://github.com/digitalocean/mcp-server', installCmd: 'npx mcp-server-digitalocean', capabilities: ['droplets', 'databases', 'k8s'] },
|
||||
{ id: 'hetzner', name: 'Hetzner', description: 'Servers, networks, firewalls', author: 'Community', category: 'Cloud', url: 'https://github.com/nichochar/mcp-server-hetzner', installCmd: 'npx mcp-server-hetzner', capabilities: ['servers', 'networks', 'firewalls'] },
|
||||
{ id: 'edgeone', name: 'EdgeOne Pages', description: 'Tencent EdgeOne — deploy static sites to global edge', author: 'Community', category: 'Cloud', url: 'https://github.com/TencentEdgeOne/edgeone-pages-mcp', installCmd: 'npx edgeone-pages-mcp', capabilities: ['deploy', 'domains', 'edge'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// AI & ML (8)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'openai-mcp', name: 'OpenAI', description: 'Chat, embeddings, image generation, moderation', author: 'Community', category: 'AI & ML', url: 'https://github.com/openai/mcp-server', installCmd: 'npx mcp-server-openai', capabilities: ['chat', 'embeddings', 'images'] },
|
||||
{ id: 'huggingface', name: 'Hugging Face', description: 'Models, datasets, spaces, inference', author: 'Community', category: 'AI & ML', url: 'https://github.com/huggingface/mcp-server', installCmd: 'npx mcp-server-huggingface', capabilities: ['models', 'datasets', 'inference'] },
|
||||
{ id: 'replicate', name: 'Replicate', description: 'Run ML models via API — image, video, audio', author: 'Community', category: 'AI & ML', url: 'https://github.com/replicate/mcp-server', installCmd: 'npx mcp-server-replicate', capabilities: ['predict', 'models', 'deployments'] },
|
||||
{ id: 'stability', name: 'Stability AI', description: 'Image generation — Stable Diffusion API', author: 'Community', category: 'AI & ML', url: 'https://github.com/stability-ai/mcp-server', installCmd: 'npx mcp-server-stability', capabilities: ['generate', 'edit', 'upscale'] },
|
||||
{ id: 'langchain', name: 'LangChain', description: 'Chain tools, retrievers, and agents', author: 'Community', category: 'AI & ML', url: 'https://github.com/langchain-ai/mcp-server', installCmd: 'npx mcp-server-langchain', capabilities: ['chains', 'retrievers', 'tools'] },
|
||||
{ id: 'pinecone', name: 'Pinecone', description: 'Vector database for embeddings search', author: 'Community', category: 'AI & ML', url: 'https://github.com/pinecone-io/mcp-server', installCmd: 'npx mcp-server-pinecone', capabilities: ['upsert', 'query', 'namespaces'] },
|
||||
{ id: 'weaviate', name: 'Weaviate', description: 'Vector search engine with ML models', author: 'Community', category: 'AI & ML', url: 'https://github.com/weaviate/mcp-server', installCmd: 'npx mcp-server-weaviate', capabilities: ['search', 'objects', 'schema'] },
|
||||
{ id: 'elevenlabs-mcp', name: 'ElevenLabs', description: 'Text-to-speech, voice cloning', author: 'Community', category: 'AI & ML', url: 'https://github.com/elevenlabs/mcp-server', installCmd: 'npx mcp-server-elevenlabs', capabilities: ['tts', 'voices', 'clone'] },
|
||||
{ id: 'llamacloud', name: 'LlamaCloud', description: 'LlamaIndex managed RAG — parse, index, query documents', author: 'Community', category: 'AI & ML', url: 'https://github.com/run-llama/mcp-server-llamacloud', installCmd: 'npx mcp-server-llamacloud', capabilities: ['parse', 'index', 'query'] },
|
||||
{ id: 'fastmcp', name: 'FastMCP', description: 'Framework for building MCP servers in Python — meta server', author: 'Community', category: 'AI & ML', url: 'https://github.com/jlowin/fastmcp', installCmd: 'uvx fastmcp', capabilities: ['framework', 'tools', 'prompts'] },
|
||||
{ id: 'opik', name: 'Opik', description: 'Comet ML — LLM observability, traces, evals', author: 'Community', category: 'AI & ML', url: 'https://github.com/comet-ml/opik-mcp', installCmd: 'npx @comet/opik-mcp', capabilities: ['traces', 'evals', 'datasets'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// ANALYTICS (6)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'posthog', name: 'PostHog', description: 'Product analytics, feature flags, experiments', author: 'Community', category: 'Analytics', url: 'https://github.com/PostHog/mcp-server', installCmd: 'npx mcp-server-posthog', capabilities: ['events', 'funnels', 'feature_flags'] },
|
||||
{ id: 'amplitude', name: 'Amplitude', description: 'Product analytics and user behavior', author: 'Community', category: 'Analytics', url: 'https://github.com/amplitude/mcp-server', installCmd: 'npx mcp-server-amplitude', capabilities: ['events', 'cohorts', 'charts'] },
|
||||
{ id: 'mixpanel', name: 'Mixpanel', description: 'Event analytics, funnels, retention', author: 'Community', category: 'Analytics', url: 'https://github.com/mixpanel/mcp-server', installCmd: 'npx mcp-server-mixpanel', capabilities: ['events', 'funnels', 'reports'] },
|
||||
{ id: 'plausible', name: 'Plausible', description: 'Privacy-focused web analytics', author: 'Community', category: 'Analytics', url: 'https://github.com/plausible/mcp-server', installCmd: 'npx mcp-server-plausible', capabilities: ['stats', 'pages', 'sources'] },
|
||||
{ id: 'prometheus', name: 'Prometheus', description: 'Metrics, alerts, targets', author: 'Community', category: 'Analytics', url: 'https://github.com/prometheus/mcp-server', installCmd: 'npx mcp-server-prometheus', capabilities: ['query', 'alerts', 'targets'] },
|
||||
{ id: 'google-analytics', name: 'Google Analytics', description: 'GA4 reports, realtime, audiences', author: 'Community', category: 'Analytics', url: 'https://github.com/nichochar/mcp-server-ga4', installCmd: 'npx mcp-server-ga4', capabilities: ['reports', 'realtime', 'audiences'] },
|
||||
{ id: 'tinybird', name: 'Tinybird', description: 'Real-time analytics — ClickHouse-powered data pipelines', author: 'Community', category: 'Analytics', url: 'https://github.com/tinybirdco/mcp-tinybird', installCmd: 'npx @tinybird/mcp-server', capabilities: ['query', 'pipes', 'data_sources'] },
|
||||
{ id: 'victoriametrics', name: 'VictoriaMetrics', description: 'High-performance time-series database — queries, alerts', author: 'Community', category: 'Analytics', url: 'https://github.com/VictoriaMetrics-Community/mcp-victoriametrics', installCmd: 'npx mcp-victoriametrics', capabilities: ['query', 'metrics', 'alerts'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SECURITY (4)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'vault', name: 'HashiCorp Vault', description: 'Secrets management — read, list, manage', author: 'Community', category: 'Security', url: 'https://github.com/hashicorp/mcp-server-vault', installCmd: 'npx mcp-server-vault', capabilities: ['secrets', 'policies', 'auth'] },
|
||||
{ id: 'snyk', name: 'Snyk', description: 'Security scanning — vulnerabilities, licenses', author: 'Community', category: 'Security', url: 'https://github.com/snyk/mcp-server', installCmd: 'npx @snyk/mcp-server', capabilities: ['scan', 'vulnerabilities', 'licenses'] },
|
||||
{ id: 'onepassword', name: '1Password', description: 'Password and secret management', author: 'Community', category: 'Security', url: 'https://github.com/1Password/mcp-server', installCmd: 'npx mcp-server-1password', capabilities: ['items', 'vaults', 'secrets'] },
|
||||
{ id: 'bitwarden', name: 'Bitwarden', description: 'Password manager — items, folders, organizations', author: 'Community', category: 'Security', url: 'https://github.com/bitwarden/mcp-server', installCmd: 'npx mcp-server-bitwarden', capabilities: ['items', 'folders', 'generate'] },
|
||||
{ id: 'keycloak', name: 'Keycloak', description: 'Identity and access management — users, roles, realms', author: 'Community', category: 'Security', url: 'https://github.com/idoyudha/mcp-keycloak', installCmd: 'npx mcp-keycloak', capabilities: ['users', 'roles', 'realms', 'sso'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// MEDIA (6)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'figma', name: 'Figma', description: 'Read designs, components, variables, comments', author: 'Community', category: 'Media', url: 'https://github.com/nichochar/mcp-server-figma', installCmd: 'npx mcp-server-figma', capabilities: ['files', 'components', 'comments'] },
|
||||
{ id: 'canva', name: 'Canva', description: 'Designs, templates, brand assets', author: 'Community', category: 'Media', url: 'https://github.com/canva/mcp-server', installCmd: 'npx mcp-server-canva', capabilities: ['designs', 'templates', 'export'] },
|
||||
{ id: 'youtube', name: 'YouTube', description: 'Video search, transcripts, channel data', author: 'Community', category: 'Media', url: 'https://github.com/nichochar/mcp-server-youtube', installCmd: 'npx mcp-server-youtube', capabilities: ['search', 'transcripts', 'channels'] },
|
||||
{ id: 'spotify', name: 'Spotify', description: 'Search tracks, playlists, playback control', author: 'Community', category: 'Media', url: 'https://github.com/nichochar/mcp-server-spotify', installCmd: 'npx mcp-server-spotify', capabilities: ['search', 'playlists', 'playback'] },
|
||||
{ id: 'unsplash', name: 'Unsplash', description: 'Search and download stock photos', author: 'Community', category: 'Media', url: 'https://github.com/nichochar/mcp-server-unsplash', installCmd: 'npx mcp-server-unsplash', capabilities: ['search', 'download', 'collections'] },
|
||||
{ id: 'dall-e', name: 'DALL-E', description: 'AI image generation via OpenAI', author: 'Community', category: 'Media', url: 'https://github.com/nichochar/mcp-server-dalle', installCmd: 'npx mcp-server-dalle', capabilities: ['generate', 'edit', 'variations'] },
|
||||
{ id: 'videodb', name: 'VideoDB', description: 'Serverless video database — index, search, stream, auto-edit', author: 'Community', category: 'Media', url: 'https://github.com/video-db/agent-toolkit/tree/main/modelcontextprotocol', installCmd: 'npx @videodb/mcp-server', capabilities: ['index', 'search', 'stream', 'edit'] },
|
||||
{ id: 'echarts', name: 'Apache ECharts', description: 'Generate charts from data — bar, line, pie, heatmap, radar', author: 'Community', category: 'Media', url: 'https://github.com/hustcc/mcp-echarts', installCmd: 'npx mcp-echarts', capabilities: ['charts', 'visualization', 'export'] },
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// UTILITIES (8)
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
{ id: 'memory-mcp', name: 'Memory', description: 'Persistent key-value memory for agents', author: 'MCP', category: 'Utilities', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/memory', installCmd: 'npx @modelcontextprotocol/server-memory', capabilities: ['store', 'retrieve', 'search'], official: true },
|
||||
{ id: 'time', name: 'Time', description: 'Current time, timezone conversions, countdowns', author: 'MCP', category: 'Utilities', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/time', installCmd: 'npx @modelcontextprotocol/server-time', capabilities: ['current', 'convert', 'diff'], official: true },
|
||||
{ id: 'sequentialthinking', name: 'Sequential Thinking', description: 'Chain-of-thought reasoning with revision', author: 'MCP', category: 'Utilities', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/sequentialthinking', installCmd: 'npx @modelcontextprotocol/server-sequentialthinking', capabilities: ['think', 'revise'], official: true },
|
||||
{ id: 'context7', name: 'Context7', description: 'Library documentation lookup', author: 'Community', category: 'Utilities', url: 'https://github.com/upstash/context7', installCmd: 'npx @upstash/context7-mcp', capabilities: ['docs', 'examples', 'api_ref'] },
|
||||
{ id: 'magic-mcp', name: 'Magic MCP', description: 'Generate UI components and previews', author: 'Community', category: 'Utilities', url: 'https://github.com/nichochar/magic-mcp', installCmd: 'npx magic-mcp', capabilities: ['generate_ui', 'preview'] },
|
||||
{ id: 'mcp-shell', name: 'Shell', description: 'Safe shell command runner with allowlists', author: 'Community', category: 'Utilities', url: 'https://github.com/nichochar/mcp-server-shell', installCmd: 'npx mcp-server-shell', capabilities: ['run', 'scripts'] },
|
||||
{ id: 'everything', name: 'Everything', description: 'MCP protocol test server — all resource types', author: 'MCP', category: 'Utilities', url: 'https://github.com/modelcontextprotocol/servers/tree/main/src/everything', installCmd: 'npx @modelcontextprotocol/server-everything', capabilities: ['resources', 'tools', 'prompts'], official: true },
|
||||
{ id: 'calculator', name: 'Calculator', description: 'Math operations, unit conversions', author: 'Community', category: 'Utilities', url: 'https://github.com/nichochar/mcp-server-calculator', installCmd: 'npx mcp-server-calculator', capabilities: ['math', 'convert', 'statistics'] },
|
||||
{ id: 'gitingest', name: 'GitIngest', description: 'Turn any Git repo into prompt-friendly context summaries', author: 'Community', category: 'Utilities', url: 'https://github.com/cyclotruc/gitingest', installCmd: 'uvx gitingest-mcp', capabilities: ['summarize', 'ingest', 'context'] },
|
||||
{ id: 'xcode', name: 'Xcode', description: 'Drive Xcode builds, simulators, and iOS/macOS projects', author: 'Community', category: 'Utilities', url: 'https://github.com/ShenghaiWang/xcodebuild', installCmd: 'npx xcodebuild-mcp', capabilities: ['build', 'test', 'simulator'] },
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// Dedup guard — normalize ids + url check, fires at module load time.
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
/**
|
||||
* Normalize an MCP server identifier or name to a canonical dedup key.
|
||||
* Strips common MCP affixes, npm scopes, and punctuation so that
|
||||
* "GitHub" / "github-mcp" / "mcp-server-github" / "@modelcontextprotocol/server-github"
|
||||
* all collapse to the same key ("github").
|
||||
*
|
||||
* Used both by the internal dedup guard and by any caller that needs to
|
||||
* match a server name against an external source (awesome-list, composio, etc).
|
||||
*/
|
||||
export function normalizeMcpId(raw: string): string {
|
||||
return raw
|
||||
.toLowerCase()
|
||||
.replace(/^@[^/]+\//, '') // strip npm scope
|
||||
.replace(/[.()[\]/,]/g, ' ') // punctuation → space
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\bmcp[- ]?server\b/g, '')
|
||||
.replace(/\bserver[- ]?mcp\b/g, '')
|
||||
.replace(/\bmcp\b/g, '')
|
||||
.replace(/^server[- ]/, '') // leading "server-" (post-scope)
|
||||
.replace(/[- ]server$/, '') // trailing "-server"
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-+|-+$/g, '')
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforces catalog uniqueness. Throws a descriptive error if two servers
|
||||
* collide on either:
|
||||
* 1. normalized id (catches "github-mcp" + "github" + "mcp-server-github")
|
||||
* 2. repository url (catches accidental copy-paste of an existing entry)
|
||||
*
|
||||
* Runs once at module load so Vite / tsc builds fail fast when a contributor
|
||||
* adds a duplicate. O(n) with early exit.
|
||||
*/
|
||||
function assertCatalogUnique(catalog: readonly McpServer[]): void {
|
||||
const seenIds = new Map<string, string>();
|
||||
const seenUrls = new Map<string, string>();
|
||||
for (const server of catalog) {
|
||||
const normalizedId = normalizeMcpId(server.id);
|
||||
const existingId = seenIds.get(normalizedId);
|
||||
if (existingId && existingId !== server.id) {
|
||||
throw new Error(
|
||||
`[mcp-registry] duplicate server id: "${server.id}" collides with ` +
|
||||
`"${existingId}" (normalized: "${normalizedId}"). ` +
|
||||
`If these are genuinely different servers, rename one so their ` +
|
||||
`normalized keys don't collide.`
|
||||
);
|
||||
}
|
||||
seenIds.set(normalizedId, server.id);
|
||||
const existingUrl = seenUrls.get(server.url);
|
||||
if (existingUrl && existingUrl !== server.id) {
|
||||
throw new Error(
|
||||
`[mcp-registry] duplicate repository url: ` +
|
||||
`"${server.id}" and "${existingUrl}" both point at ${server.url}`
|
||||
);
|
||||
}
|
||||
seenUrls.set(server.url, server.id);
|
||||
}
|
||||
}
|
||||
|
||||
assertCatalogUnique(MCP_CATALOG);
|
||||
82
packages/shared/src/risk.ts
Normal file
82
packages/shared/src/risk.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Canonical risk / approval / audit taxonomy (P7/D15 Track A, binding A1).
|
||||
*
|
||||
* Before this module the vocabulary was redeclared in five places that had
|
||||
* drifted apart — trust-model.ts (RiskLevel without 'critical'), confirmation.ts
|
||||
* (a hardcoded ApprovalClass mapper), install-audit.ts (the widest set, plus a
|
||||
* hand-duplicated SQLite CHECK in hive-mind-core/schema.ts), and
|
||||
* team-capability-governance.ts (a 4th set keyed on 'none'). A producer narrower
|
||||
* than its store meant a `critical` risk could only ever be hand-written, and the
|
||||
* FE modal couldn't represent the `critical`/`blocked` levels the audit recorded.
|
||||
*
|
||||
* This is the SINGLE SOURCE. Each enum is the WIDEST of the prior sets so no
|
||||
* producer/store mismatch remains. Every other module imports from here.
|
||||
*
|
||||
* Two axes, one rule:
|
||||
* - `RiskLevel` = how dangerous the action is (severity → display colour).
|
||||
* - `ApprovalClass` = how strongly to gate it (standard < elevated < critical < blocked).
|
||||
* They are NOT the same axis; surfaces that collapsed them must keep them distinct.
|
||||
*
|
||||
* Each type is derived from a runtime `const` array so the array can also drive
|
||||
* the SQLite CHECK constraints (A3) and parity tests, with zero chance of the
|
||||
* type and the value list drifting.
|
||||
*/
|
||||
|
||||
/** Severity of an action. Widest prior set = install-audit's (adds 'critical'). */
|
||||
export const RISK_LEVELS = ['low', 'medium', 'high', 'critical'] as const;
|
||||
export type RiskLevel = (typeof RISK_LEVELS)[number];
|
||||
|
||||
/** Gating strength. Widest prior set = install-audit's (adds 'blocked'). */
|
||||
export const APPROVAL_CLASSES = ['standard', 'elevated', 'critical', 'blocked'] as const;
|
||||
export type ApprovalClass = (typeof APPROVAL_CLASSES)[number];
|
||||
|
||||
/** Provenance of a capability. Widest prior set = install-audit's (adds 'security-gate'). */
|
||||
export const TRUST_SOURCES = [
|
||||
'builtin', // First-party, ships with Waggle
|
||||
'starter_pack', // Curated starter skills
|
||||
'local_user', // User-created via create_skill
|
||||
'third_party_verified', // Verified registry
|
||||
'third_party_unverified', // Unknown registry source
|
||||
'unknown', // No provenance information
|
||||
'security-gate', // Set by the SecurityGate scan (blocked/flagged)
|
||||
] as const;
|
||||
export type TrustSource = (typeof TRUST_SOURCES)[number];
|
||||
|
||||
/** How a risk assessment was derived. */
|
||||
export const ASSESSMENT_MODES = ['declared', 'heuristic', 'mixed'] as const;
|
||||
export type AssessmentMode = (typeof ASSESSMENT_MODES)[number];
|
||||
|
||||
/** Lifecycle action recorded in install_audit. Includes 'uninstalled' (P5/D4). */
|
||||
export const AUDIT_ACTIONS = [
|
||||
'proposed', 'approved', 'installed', 'rejected', 'failed', 'blocked', 'uninstalled',
|
||||
] as const;
|
||||
export type AuditAction = (typeof AUDIT_ACTIONS)[number];
|
||||
|
||||
/** Kind of capability an audit row concerns. */
|
||||
export const AUDIT_CAPABILITY_TYPES = [
|
||||
'native', 'skill', 'plugin', 'mcp', 'connector', 'marketplace',
|
||||
] as const;
|
||||
export type AuditCapabilityType = (typeof AUDIT_CAPABILITY_TYPES)[number];
|
||||
|
||||
/** Who initiated an audited action. */
|
||||
export const AUDIT_INITIATORS = ['agent', 'user', 'system'] as const;
|
||||
export type AuditInitiator = (typeof AUDIT_INITIATORS)[number];
|
||||
|
||||
/** Ascending severity order — index = rank. Used to compare/sort risk levels so a
|
||||
* `critical` always outranks `low` (the team-governance bug was a `?? 0` that
|
||||
* sorted `critical` BELOW `low`). */
|
||||
export function riskRank(level: RiskLevel): number {
|
||||
return RISK_LEVELS.indexOf(level);
|
||||
}
|
||||
|
||||
/** True when `level` meets or exceeds `threshold` on the canonical severity scale. */
|
||||
export function riskAtLeast(level: RiskLevel, threshold: RiskLevel): boolean {
|
||||
return riskRank(level) >= riskRank(threshold);
|
||||
}
|
||||
|
||||
/** Build a SQLite `CHECK (col IN (...))` clause body from a const value list, so
|
||||
* the DDL constraint and the TS union can never drift (A3 single-sources both
|
||||
* install-audit.ts and hive-mind-core/schema.ts from these arrays). */
|
||||
export function sqlInList(values: readonly string[]): string {
|
||||
return values.map((v) => `'${v}'`).join(', ');
|
||||
}
|
||||
138
packages/shared/src/schemas.ts
Normal file
138
packages/shared/src/schemas.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
// @waggle/shared — Zod validation schemas for API requests
|
||||
|
||||
import { z } from 'zod';
|
||||
import { AGENT_RUN_STATES } from './types.js';
|
||||
|
||||
export const createTeamSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
slug: z.string().min(1).max(50).regex(/^[a-z0-9-]+$/),
|
||||
});
|
||||
|
||||
export const inviteMemberSchema = z.object({
|
||||
email: z.string().email(),
|
||||
role: z.enum(['admin', 'member']),
|
||||
});
|
||||
|
||||
export const updateMemberSchema = z.object({
|
||||
role: z.enum(['admin', 'member']).optional(),
|
||||
roleDescription: z.string().max(500).optional(),
|
||||
interests: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const createTaskSchema = z.object({
|
||||
title: z.string().min(1).max(200),
|
||||
description: z.string().max(5000).optional(),
|
||||
priority: z.enum(['critical', 'high', 'normal', 'low']).default('normal'),
|
||||
parentTaskId: z.string().uuid().optional(),
|
||||
});
|
||||
|
||||
export const updateTaskSchema = z.object({
|
||||
title: z.string().min(1).max(200).optional(),
|
||||
description: z.string().max(5000).optional(),
|
||||
status: z.enum(['open', 'claimed', 'in_progress', 'done', 'cancelled']).optional(),
|
||||
priority: z.enum(['critical', 'high', 'normal', 'low']).optional(),
|
||||
assignedTo: z.string().uuid().nullable().optional(),
|
||||
});
|
||||
|
||||
export const sendMessageSchema = z.object({
|
||||
type: z.enum(['broadcast', 'request', 'response']),
|
||||
subtype: z.enum([
|
||||
'knowledge_check', 'task_delegation', 'skill_request', 'model_recommendation',
|
||||
'knowledge_match', 'task_claim', 'discovery', 'routed_share', 'skill_share', 'model_recipe',
|
||||
]),
|
||||
content: z.record(z.unknown()),
|
||||
referenceId: z.string().uuid().optional(),
|
||||
routing: z.array(z.object({ userId: z.string().uuid(), reason: z.string() })).optional(),
|
||||
});
|
||||
|
||||
// UX-Refactor Phase 3 (PRD §15.5): shared enum fragments for the Agent entity.
|
||||
// Ref-id arrays use plain min(1) strings — workspace/agent ids in this repo are
|
||||
// NOT all UUIDs (cron ids are numeric, artifact ids are `art_${uuid}`).
|
||||
// status derives from the §14.5 AGENT_RUN_STATES tuple in types.ts (the
|
||||
// vocabulary the sidecar agents-store actually persists) — single source.
|
||||
const agentTypeEnum = z.enum(['personal', 'workspace', 'team', 'autonomous']);
|
||||
const autonomyLevelEnum = z.enum(['manual', 'guided', 'medium', 'high']);
|
||||
const scopeEnum = z.enum(['personal', 'workspace', 'team', 'organization']);
|
||||
const agentStatusEnum = z.enum(AGENT_RUN_STATES);
|
||||
|
||||
// NOTE: this schema is consumed by the Clerk-gated CLOUD route
|
||||
// (packages/server/src/routes/agents.ts). The cloud AgentService persists only
|
||||
// the legacy fields (name/role/systemPrompt/model/tools/config/teamId) — the
|
||||
// §15.5 fields below validate but are NOT stored there yet. The local sidecar
|
||||
// surface (local/routes/agents.ts) is the §15.5 system of record.
|
||||
export const createAgentSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
role: z.string().max(500).optional(),
|
||||
systemPrompt: z.string().max(10000).optional(),
|
||||
model: z.string().min(1).default('claude-haiku-4-5'),
|
||||
tools: z.array(z.string()).default([]),
|
||||
config: z.record(z.unknown()).default({}),
|
||||
teamId: z.string().uuid().optional(),
|
||||
// §15.5 optional Agent-entity fields (Phase 3) — all optional for back-compat.
|
||||
type: agentTypeEnum.optional(),
|
||||
goal: z.string().max(4000).optional(),
|
||||
description: z.string().max(2000).optional(),
|
||||
personaId: z.string().min(1).max(200).optional(),
|
||||
autonomyLevel: autonomyLevelEnum.optional(),
|
||||
workspaceIds: z.array(z.string().min(1)).optional(),
|
||||
memoryScopes: z.array(scopeEnum).optional(),
|
||||
skillIds: z.array(z.string().min(1)).optional(),
|
||||
connectorIds: z.array(z.string().min(1)).optional(),
|
||||
mcpIds: z.array(z.string().min(1)).optional(),
|
||||
permissions: z.record(z.unknown()).optional(),
|
||||
status: agentStatusEnum.optional(),
|
||||
});
|
||||
|
||||
// NOTE (Phase 3A review): speculative updateAgent/createSkill/updateSkill/
|
||||
// createAutomation/updateAutomation schemas were removed here — no route
|
||||
// consumed them and their shapes contradicted the implemented wire contracts
|
||||
// (the local routes validate inline; skills mandate steps[]; automations use a
|
||||
// nested trigger object). Re-add a schema only together with a route that
|
||||
// parses with it.
|
||||
|
||||
export const createAgentGroupSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
description: z.string().max(500).optional(),
|
||||
strategy: z.enum(['parallel', 'sequential', 'coordinator']),
|
||||
members: z.array(z.object({
|
||||
agentId: z.string().uuid(),
|
||||
roleInGroup: z.enum(['lead', 'worker']).default('worker'),
|
||||
executionOrder: z.number().int().min(0).default(0),
|
||||
})),
|
||||
});
|
||||
|
||||
export const createEntitySchema = z.object({
|
||||
entityType: z.string().min(1).max(100),
|
||||
name: z.string().min(1).max(200),
|
||||
properties: z.record(z.unknown()).default({}),
|
||||
validFrom: z.string().datetime().optional(),
|
||||
validTo: z.string().datetime().optional(),
|
||||
});
|
||||
|
||||
export const createRelationSchema = z.object({
|
||||
sourceId: z.string().uuid(),
|
||||
targetId: z.string().uuid(),
|
||||
relationType: z.string().min(1).max(100),
|
||||
confidence: z.number().min(0).max(1).default(1.0),
|
||||
properties: z.record(z.unknown()).default({}),
|
||||
});
|
||||
|
||||
export const createResourceSchema = z.object({
|
||||
resourceType: z.enum(['model_recipe', 'skill', 'tool_config', 'prompt_template']),
|
||||
name: z.string().min(1).max(200),
|
||||
description: z.string().max(1000).optional(),
|
||||
config: z.record(z.unknown()),
|
||||
});
|
||||
|
||||
export const createCronSchema = z.object({
|
||||
name: z.string().min(1).max(200),
|
||||
cronExpr: z.string().min(1),
|
||||
jobType: z.string().min(1),
|
||||
jobConfig: z.record(z.unknown()).default({}),
|
||||
});
|
||||
|
||||
export const queueJobSchema = z.object({
|
||||
jobType: z.enum(['chat', 'task', 'cron', 'waggle', 'group']),
|
||||
input: z.record(z.unknown()),
|
||||
teamId: z.string().uuid().optional(),
|
||||
});
|
||||
244
packages/shared/src/tiers.ts
Normal file
244
packages/shared/src/tiers.ts
Normal file
@@ -0,0 +1,244 @@
|
||||
/**
|
||||
* Tier Architecture — canonical tier definitions, capabilities, and enforcement.
|
||||
*
|
||||
* CANONICAL TIER NAMES: TRIAL, FREE, TEAMS, ENTERPRISE (4-tier)
|
||||
* These are the only valid values for the Tier type.
|
||||
* Note: FREE displays as "Solo" (see TIER_LABELS); the stored value stays `FREE`.
|
||||
*
|
||||
* Pricing (confirmed July 5, 2026):
|
||||
* TRIAL — $0 / 15 days (all features unlocked)
|
||||
* FREE — Solo — $0 forever (all personal features)
|
||||
* TEAMS — $49/mo per seat (shared workspaces, WaggleDance, governance)
|
||||
* ENTERPRISE — Consultative (KVARK sovereign on-prem)
|
||||
*
|
||||
* Strategy: Memory + Harvest is free forever (lock-in moat).
|
||||
* Agents are free (they generate memory).
|
||||
* Skills/connectors are the upgrade trigger.
|
||||
* Trial → Free fallback after 15 days (painful but not destructive).
|
||||
*/
|
||||
|
||||
export const TIERS = ['TRIAL', 'FREE', 'TEAMS', 'ENTERPRISE'] as const;
|
||||
export type Tier = typeof TIERS[number];
|
||||
|
||||
export const TRIAL_DURATION_DAYS = 15;
|
||||
|
||||
/**
|
||||
* Read an env var safely from either Node (`process.env`) or the browser
|
||||
* (where `process` is undefined). Used by shared tier config so this module
|
||||
* can be imported by both the sidecar and the web bundle.
|
||||
*/
|
||||
const readEnv = (key: string): string | null => {
|
||||
const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process;
|
||||
return proc?.env?.[key] ?? null;
|
||||
};
|
||||
|
||||
export type EmbeddingProviderType = 'inprocess' | 'ollama' | 'voyage' | 'openai' | 'litellm' | 'mock';
|
||||
export type ExportFormat = 'txt' | 'md' | 'pdf' | 'json';
|
||||
|
||||
export interface TierCapabilities {
|
||||
connectorLimit: number;
|
||||
workspaceLimit: number;
|
||||
embeddingProviders: EmbeddingProviderType[];
|
||||
embeddingQuotaPerMonth: number;
|
||||
messageHistoryLimit: number;
|
||||
spawnAgents: boolean;
|
||||
customSkills: boolean;
|
||||
teamSkillLibrary: boolean;
|
||||
cloudSync: boolean;
|
||||
exportFormats: ExportFormat[];
|
||||
teamMembersLimit: number;
|
||||
sharedWorkspaces: boolean;
|
||||
adminPanel: boolean;
|
||||
auditLog: 'none' | 'basic' | 'full';
|
||||
selfHosted: boolean;
|
||||
managedModelPool: boolean;
|
||||
priorityModels: boolean;
|
||||
kvarkCta: 'none' | 'subtle' | 'active';
|
||||
stripePriceId: string | null;
|
||||
}
|
||||
|
||||
export const TIER_CAPABILITIES: Record<Tier, TierCapabilities> = {
|
||||
TRIAL: {
|
||||
connectorLimit: -1,
|
||||
workspaceLimit: -1,
|
||||
embeddingProviders: ['inprocess', 'mock', 'ollama', 'voyage', 'openai', 'litellm'],
|
||||
embeddingQuotaPerMonth: -1,
|
||||
messageHistoryLimit: -1,
|
||||
spawnAgents: true,
|
||||
customSkills: true,
|
||||
teamSkillLibrary: true,
|
||||
cloudSync: true,
|
||||
exportFormats: ['txt', 'md', 'pdf', 'json'],
|
||||
teamMembersLimit: -1,
|
||||
sharedWorkspaces: true,
|
||||
adminPanel: true,
|
||||
auditLog: 'full',
|
||||
selfHosted: false,
|
||||
managedModelPool: true,
|
||||
priorityModels: true,
|
||||
kvarkCta: 'subtle',
|
||||
stripePriceId: null,
|
||||
},
|
||||
FREE: {
|
||||
connectorLimit: -1,
|
||||
workspaceLimit: -1,
|
||||
embeddingProviders: ['inprocess', 'mock', 'ollama', 'voyage', 'openai'],
|
||||
embeddingQuotaPerMonth: -1,
|
||||
messageHistoryLimit: -1,
|
||||
spawnAgents: true,
|
||||
customSkills: true,
|
||||
teamSkillLibrary: false,
|
||||
cloudSync: false,
|
||||
exportFormats: ['txt', 'md', 'pdf', 'json'],
|
||||
teamMembersLimit: 1,
|
||||
sharedWorkspaces: false,
|
||||
adminPanel: false,
|
||||
auditLog: 'basic',
|
||||
selfHosted: false,
|
||||
managedModelPool: false,
|
||||
priorityModels: false,
|
||||
kvarkCta: 'subtle',
|
||||
stripePriceId: null,
|
||||
},
|
||||
TEAMS: {
|
||||
connectorLimit: -1,
|
||||
workspaceLimit: -1,
|
||||
embeddingProviders: ['inprocess', 'mock', 'ollama', 'voyage', 'openai', 'litellm'],
|
||||
embeddingQuotaPerMonth: -1,
|
||||
messageHistoryLimit: -1,
|
||||
spawnAgents: true,
|
||||
customSkills: true,
|
||||
teamSkillLibrary: true,
|
||||
cloudSync: true,
|
||||
exportFormats: ['txt', 'md', 'pdf', 'json'],
|
||||
teamMembersLimit: -1,
|
||||
sharedWorkspaces: true,
|
||||
adminPanel: true,
|
||||
auditLog: 'full',
|
||||
selfHosted: true,
|
||||
managedModelPool: true,
|
||||
priorityModels: true,
|
||||
kvarkCta: 'active',
|
||||
stripePriceId: readEnv('STRIPE_PRICE_TEAMS'),
|
||||
},
|
||||
ENTERPRISE: {
|
||||
connectorLimit: -1,
|
||||
workspaceLimit: -1,
|
||||
embeddingProviders: ['inprocess', 'mock', 'ollama', 'voyage', 'openai', 'litellm'],
|
||||
embeddingQuotaPerMonth: -1,
|
||||
messageHistoryLimit: -1,
|
||||
spawnAgents: true,
|
||||
customSkills: true,
|
||||
teamSkillLibrary: true,
|
||||
cloudSync: true,
|
||||
exportFormats: ['txt', 'md', 'pdf', 'json'],
|
||||
teamMembersLimit: -1,
|
||||
sharedWorkspaces: true,
|
||||
adminPanel: true,
|
||||
auditLog: 'full',
|
||||
selfHosted: true,
|
||||
managedModelPool: true,
|
||||
priorityModels: true,
|
||||
kvarkCta: 'none',
|
||||
stripePriceId: null,
|
||||
},
|
||||
};
|
||||
|
||||
// Tier ordering — higher index = more capable
|
||||
// TRIAL has max capabilities but is time-limited, so it ranks above TEAMS
|
||||
const TIER_ORDER: Record<Tier, number> = {
|
||||
FREE: 0, TEAMS: 2, ENTERPRISE: 3, TRIAL: 3,
|
||||
};
|
||||
|
||||
/** Map legacy tier names to new canonical names. */
|
||||
const LEGACY_TIER_MAP: Record<string, Tier> = {
|
||||
solo: 'FREE',
|
||||
basic: 'FREE',
|
||||
pro: 'FREE',
|
||||
business: 'TEAMS',
|
||||
enterprise: 'ENTERPRISE',
|
||||
trial: 'TRIAL',
|
||||
};
|
||||
|
||||
/** Display names — single source of truth for user-facing tier labels. */
|
||||
export const TIER_LABELS: Record<Tier, string> = {
|
||||
TRIAL: 'Trial',
|
||||
FREE: 'Solo',
|
||||
TEAMS: 'Team',
|
||||
ENTERPRISE: 'Enterprise',
|
||||
};
|
||||
|
||||
/** Get the user-facing display name for a tier. */
|
||||
export function tierLabel(t: Tier): string {
|
||||
return TIER_LABELS[t];
|
||||
}
|
||||
|
||||
/** Parse a tier string (handles both legacy and canonical names). */
|
||||
export function parseTier(raw: string): Tier | null {
|
||||
const upper = raw.toUpperCase();
|
||||
if (TIERS.includes(upper as Tier)) return upper as Tier;
|
||||
return LEGACY_TIER_MAP[raw.toLowerCase()] ?? null;
|
||||
}
|
||||
|
||||
/** Check if a trial has expired. `trialStartedAt` is an ISO date string. */
|
||||
export function isTrialExpired(trialStartedAt: string | null | undefined): boolean {
|
||||
if (!trialStartedAt) return true;
|
||||
const start = new Date(trialStartedAt).getTime();
|
||||
if (isNaN(start)) return true;
|
||||
const now = Date.now();
|
||||
const elapsed = now - start;
|
||||
return elapsed > TRIAL_DURATION_DAYS * 24 * 60 * 60 * 1000;
|
||||
}
|
||||
|
||||
/** Get the effective tier — downgrades TRIAL to FREE if expired. */
|
||||
export function getEffectiveTier(tier: Tier, trialStartedAt?: string | null): Tier {
|
||||
if (tier === 'TRIAL' && isTrialExpired(trialStartedAt)) return 'FREE';
|
||||
return tier;
|
||||
}
|
||||
|
||||
/** Days remaining in trial (0 if expired or not on trial). */
|
||||
export function trialDaysRemaining(trialStartedAt: string | null | undefined): number {
|
||||
if (!trialStartedAt) return 0;
|
||||
const start = new Date(trialStartedAt).getTime();
|
||||
if (isNaN(start)) return 0;
|
||||
const elapsed = Date.now() - start;
|
||||
const remaining = TRIAL_DURATION_DAYS - elapsed / (24 * 60 * 60 * 1000);
|
||||
return Math.max(0, Math.ceil(remaining));
|
||||
}
|
||||
|
||||
export class TierError extends Error {
|
||||
constructor(
|
||||
public readonly required: Tier,
|
||||
public readonly actual: Tier,
|
||||
) {
|
||||
super(`Tier insufficient: requires ${required}, actual is ${actual}`);
|
||||
this.name = 'TierError';
|
||||
}
|
||||
}
|
||||
|
||||
export function tierSatisfies(actual: Tier, required: Tier): boolean {
|
||||
return TIER_ORDER[actual] >= TIER_ORDER[required];
|
||||
}
|
||||
|
||||
export function assertTierCapability(actual: Tier, required: Tier): void {
|
||||
if (!tierSatisfies(actual, required)) {
|
||||
throw new TierError(required, actual);
|
||||
}
|
||||
}
|
||||
|
||||
export function getCapabilities(tier: Tier): TierCapabilities {
|
||||
return TIER_CAPABILITIES[tier];
|
||||
}
|
||||
|
||||
export function hasCapability<K extends keyof TierCapabilities>(
|
||||
tier: Tier,
|
||||
capability: K,
|
||||
minimumValue?: TierCapabilities[K],
|
||||
): boolean {
|
||||
const cap = TIER_CAPABILITIES[tier][capability];
|
||||
if (minimumValue === undefined) return Boolean(cap);
|
||||
if (typeof cap === 'number' && typeof minimumValue === 'number') {
|
||||
return cap === -1 || cap >= minimumValue;
|
||||
}
|
||||
return cap === minimumValue;
|
||||
}
|
||||
262
packages/shared/src/tool-detection.ts
Normal file
262
packages/shared/src/tool-detection.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/**
|
||||
* Tool Detection — shared types for AI-OS Phase 0.
|
||||
*
|
||||
* Mission: report which external AI tools are installed on the user's
|
||||
* machine, with version and hive-mind hook status. Foundation for the
|
||||
* launcher dock (Phase 2), hook auto-installer UX (Phase 2), and the
|
||||
* Mission Control inventory tile (Phase 4).
|
||||
*
|
||||
* The detection itself runs in the sidecar (agent package); these types
|
||||
* live in @waggle/shared so both the sidecar and the web bundle can
|
||||
* consume the same envelope without duplicating shapes.
|
||||
*
|
||||
* Out of scope for these types:
|
||||
* - Hook installation (Phase 2)
|
||||
* - Tool spawn / process management (Phase 2)
|
||||
* - Workspace context injection (Phase 2)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Canonical AI-tool IDs that hive-mind has shipped hook packages for.
|
||||
* See packages/hive-mind-hooks-* for the matching installers.
|
||||
*/
|
||||
export const SUPPORTED_TOOLS = [
|
||||
'claude-code',
|
||||
'claude-desktop',
|
||||
'cursor',
|
||||
'codex',
|
||||
'codex-desktop',
|
||||
'hermes',
|
||||
'openclaw',
|
||||
] as const;
|
||||
|
||||
export type ToolId = typeof SUPPORTED_TOOLS[number];
|
||||
|
||||
/**
|
||||
* AI-OS #5 — how a tool is detected. `path` = PATH lookup by binary name (CLI
|
||||
* tools; the only kind a third-party JSON adapter may declare). `candidates` =
|
||||
* platform-branching candidate-path probe, resolved by built-in agent code
|
||||
* (GUI/desktop apps) — not expressible declaratively, so built-in only.
|
||||
*/
|
||||
export type ToolDetectSpec =
|
||||
| { kind: 'path'; binaryName: string }
|
||||
| { kind: 'candidates' };
|
||||
|
||||
export type ExternalToolAccess = 'read-only' | 'workspace-write' | 'native';
|
||||
export type ToolPromptTransport = 'stdin' | 'arg' | 'temp-file';
|
||||
export type ToolOutputDialect =
|
||||
| 'claude-stream-json'
|
||||
| 'codex-jsonl'
|
||||
| 'hermes-text'
|
||||
| 'openclaw-json'
|
||||
| 'text'
|
||||
| 'json'
|
||||
| 'jsonl';
|
||||
export type ToolWorkspaceBinding = 'cwd' | 'flag' | 'managed-agent';
|
||||
|
||||
/** Declarative, shell-free contract for one capturable agent task. */
|
||||
export interface ToolTaskSpec {
|
||||
argvTemplate: readonly string[];
|
||||
resumeArgvTemplate?: readonly string[];
|
||||
accessArgs: Partial<Record<ExternalToolAccess, readonly string[]>>;
|
||||
promptTransport: ToolPromptTransport;
|
||||
outputDialect: ToolOutputDialect;
|
||||
workspaceBinding: ToolWorkspaceBinding;
|
||||
permissionModes: readonly ExternalToolAccess[];
|
||||
resumable: boolean;
|
||||
}
|
||||
|
||||
export interface ToolCapabilities {
|
||||
interactiveLaunch: boolean;
|
||||
headlessTask: boolean;
|
||||
structuredProgress: boolean;
|
||||
resumable: boolean;
|
||||
liveWaggleDance: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI-OS #5 — declarative descriptor for one external tool. The single source of
|
||||
* truth for the per-tool facts that used to be duplicated across SUPPORTED_TOOLS
|
||||
* / LAUNCH_COHORT / TOOL_DISPLAY_NAMES / HOOK_POINTER_BY_TOOL / HOOKS_COHORT /
|
||||
* the per-tool detectors. Built-ins live in BUILTIN_TOOL_MANIFESTS; third-party
|
||||
* adapters are loaded (data-only) from ~/.waggle/adapters/*.json.
|
||||
*/
|
||||
export interface ToolManifest {
|
||||
id: string;
|
||||
displayName: string;
|
||||
launchable: boolean;
|
||||
hookCapable: boolean;
|
||||
hookPointer: string;
|
||||
detect: ToolDetectSpec;
|
||||
/**
|
||||
* Declarative inline-prompt arg template for THIRD-PARTY path adapters
|
||||
* (e.g. ['--print', '{prompt}']). Built-ins keep their logic in
|
||||
* launcher-prompt-args.ts. Captured in v1; application is a fast-follow.
|
||||
*/
|
||||
promptArgTemplate?: string[];
|
||||
/** Explicit capability split: opening an app is not the same as running a task. */
|
||||
capabilities?: ToolCapabilities;
|
||||
/** Present only when the adapter has a verified, capturable headless lane. */
|
||||
task?: ToolTaskSpec;
|
||||
/** true = first-party (the 7); false/absent = loaded third-party. */
|
||||
builtin?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The canonical 7 built-in tools — the source of truth for their per-tool data.
|
||||
* SUPPORTED_TOOLS (above) stays the `as const` type anchor; the cohort/name/
|
||||
* pointer consts derive from these manifests.
|
||||
*/
|
||||
export const BUILTIN_TOOL_MANIFESTS: readonly ToolManifest[] = [
|
||||
{
|
||||
id: 'claude-code', displayName: 'Claude Code', launchable: true, hookCapable: true,
|
||||
hookPointer: '.claude/hive-mind-install.json', detect: { kind: 'path', binaryName: 'claude' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: true, structuredProgress: true, resumable: true, liveWaggleDance: false },
|
||||
task: {
|
||||
argvTemplate: ['-p', '--safe-mode', '--disable-slash-commands', '--no-session-persistence', '--max-budget-usd', '0.25', '--input-format', 'text', '--output-format', 'stream-json', '--verbose', '{accessArgs}'],
|
||||
resumeArgvTemplate: ['-p', '--safe-mode', '--disable-slash-commands', '--resume', '{sessionId}', '--max-budget-usd', '0.25', '--input-format', 'text', '--output-format', 'stream-json', '--verbose', '{accessArgs}'],
|
||||
accessArgs: {
|
||||
'read-only': ['--permission-mode', 'plan'],
|
||||
'workspace-write': ['--permission-mode', 'acceptEdits'],
|
||||
native: [],
|
||||
},
|
||||
promptTransport: 'stdin', outputDialect: 'claude-stream-json', workspaceBinding: 'cwd',
|
||||
permissionModes: ['read-only', 'workspace-write', 'native'], resumable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'claude-desktop', displayName: 'Claude Desktop', launchable: true, hookCapable: true,
|
||||
hookPointer: '.waggle/claude-desktop/hive-mind-install.json', detect: { kind: 'candidates' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: false, structuredProgress: false, resumable: false, liveWaggleDance: false },
|
||||
},
|
||||
{
|
||||
id: 'cursor', displayName: 'Cursor', launchable: true, hookCapable: true,
|
||||
hookPointer: '.cursor/hive-mind-install.json', detect: { kind: 'candidates' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: false, structuredProgress: false, resumable: false, liveWaggleDance: false },
|
||||
},
|
||||
{
|
||||
id: 'codex', displayName: 'Codex CLI', launchable: true, hookCapable: true,
|
||||
hookPointer: '.codex/hive-mind-install.json', detect: { kind: 'path', binaryName: 'codex' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: true, structuredProgress: true, resumable: true, liveWaggleDance: false },
|
||||
task: {
|
||||
argvTemplate: ['{accessArgs}', 'exec', '--ignore-user-config', '--ignore-rules', '--ephemeral', '--skip-git-repo-check', '--json', '--color', 'never', '-C', '{workspacePath}', '-'],
|
||||
resumeArgvTemplate: ['{accessArgs}', 'exec', '--ignore-user-config', '--ignore-rules', '--ephemeral', '--skip-git-repo-check', 'resume', '{sessionId}', '--json', '--color', 'never', '-C', '{workspacePath}', '-'],
|
||||
accessArgs: {
|
||||
'read-only': ['--ask-for-approval', 'never', '--sandbox', 'read-only'],
|
||||
'workspace-write': ['--ask-for-approval', 'never', '--sandbox', 'workspace-write'],
|
||||
native: [],
|
||||
},
|
||||
promptTransport: 'stdin', outputDialect: 'codex-jsonl', workspaceBinding: 'flag',
|
||||
permissionModes: ['read-only', 'workspace-write', 'native'], resumable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'codex-desktop', displayName: 'Codex Desktop', launchable: true, hookCapable: true,
|
||||
hookPointer: '.codex/hive-mind-install.json', detect: { kind: 'candidates' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: false, structuredProgress: false, resumable: false, liveWaggleDance: false },
|
||||
},
|
||||
{
|
||||
id: 'hermes', displayName: 'Hermes Agent', launchable: true, hookCapable: true,
|
||||
hookPointer: '.hermes/hive-mind-install.json', detect: { kind: 'path', binaryName: 'hermes' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: true, structuredProgress: false, resumable: true, liveWaggleDance: false },
|
||||
task: {
|
||||
argvTemplate: ['chat', '-q', '{prompt}', '-Q', '--source', 'tool', '--ignore-rules', '--max-turns', '12', '--checkpoints'],
|
||||
resumeArgvTemplate: ['chat', '--resume', '{sessionId}', '-q', '{prompt}', '-Q', '--source', 'tool', '--ignore-rules', '--max-turns', '12', '--checkpoints'],
|
||||
accessArgs: { native: [] }, promptTransport: 'arg', outputDialect: 'hermes-text', workspaceBinding: 'cwd',
|
||||
permissionModes: ['native'], resumable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'openclaw', displayName: 'OpenClaw', launchable: true, hookCapable: true,
|
||||
hookPointer: '.openclaw/hive-mind-install.json', detect: { kind: 'path', binaryName: 'openclaw' }, builtin: true,
|
||||
capabilities: { interactiveLaunch: true, headlessTask: true, structuredProgress: true, resumable: true, liveWaggleDance: false },
|
||||
task: {
|
||||
argvTemplate: ['agent', '--agent', '{agentId}', '--session-key', 'agent:{agentId}:waggle:{runId}', '--message-file', '{promptFile}', '--json', '--timeout', '{timeoutSeconds}'],
|
||||
resumeArgvTemplate: ['agent', '--agent', '{agentId}', '--session-key', 'agent:{agentId}:waggle:{sessionId}', '--message-file', '{promptFile}', '--json', '--timeout', '{timeoutSeconds}'],
|
||||
accessArgs: { native: [] }, promptTransport: 'temp-file', outputDialect: 'openclaw-json', workspaceBinding: 'managed-agent',
|
||||
permissionModes: ['native'], resumable: true,
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Tools the launcher dock + hook installer support end-to-end.
|
||||
*
|
||||
* Phase 1 shipped with 3 entries (Claude Code, Cursor, Claude
|
||||
* Desktop — D3). Phase 4 extends to all 7 because (a) each tool
|
||||
* already has a published hook-installer package
|
||||
* (@waggle/hive-mind-hooks-<id>), and (b) the marginal cost per
|
||||
* additional detector is one PATH lookup or candidate-path entry.
|
||||
*/
|
||||
export const LAUNCH_COHORT: readonly ToolId[] =
|
||||
BUILTIN_TOOL_MANIFESTS.filter((m) => m.launchable).map((m) => m.id as ToolId);
|
||||
|
||||
/**
|
||||
* AI-OS #5 — apply a third-party adapter's `promptArgTemplate` to a prompt by
|
||||
* substituting every `{prompt}` placeholder in each entry. Built-ins use their
|
||||
* own promptArgsForTool (web); this is the declarative form for loaded adapters.
|
||||
*/
|
||||
export function applyPromptArgTemplate(template: readonly string[], prompt: string): string[] {
|
||||
return template.map((a) => a.split('{prompt}').join(prompt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-tool human-readable display name. Centralized so the launcher
|
||||
* UI, sidecar logs, and KVARK governance reports all agree.
|
||||
*/
|
||||
export const TOOL_DISPLAY_NAMES = Object.fromEntries(
|
||||
BUILTIN_TOOL_MANIFESTS.map((m) => [m.id, m.displayName]),
|
||||
) as Record<ToolId, string>;
|
||||
|
||||
/**
|
||||
* Result of detecting a single tool on the user's machine.
|
||||
*
|
||||
* Field semantics:
|
||||
* - `installed` : true iff the binary was found at a known path.
|
||||
* - `installedPath` : absolute path to the binary, or null.
|
||||
* - `version` : version string (best-effort; null if exec failed).
|
||||
* - `hooksInstalled` : true iff a hive-mind hook pointer file
|
||||
* was found AND its referenced backup file still exists
|
||||
* (so a partially-rolled-back install reports false).
|
||||
* - `hookPointerPath` : the pointer file we read (or attempted),
|
||||
* for diagnostics.
|
||||
* - `diagnostic` : optional human-readable reason for any
|
||||
* partial-failure case (e.g. "claude-code installed but --version
|
||||
* returned non-zero").
|
||||
*/
|
||||
export interface DetectedTool {
|
||||
/** Tool id — a built-in ToolId or a loaded third-party adapter id (#5). */
|
||||
id: string;
|
||||
displayName: string;
|
||||
/** True when the tool manifest allows launching from the dock. */
|
||||
launchable?: boolean;
|
||||
/** True when the tool manifest declares hook support. */
|
||||
hookCapable?: boolean;
|
||||
/** True for the built-in seven tools; false for loaded adapters. */
|
||||
builtin?: boolean;
|
||||
/** True when the manifest can accept the launch prompt inline. */
|
||||
acceptsInlinePrompt?: boolean;
|
||||
/** Canonical split between opening the app and running a captured task. */
|
||||
capabilities?: ToolCapabilities;
|
||||
/** Access modes supported by the manifest's captured-task contract. */
|
||||
permissionModes?: readonly ExternalToolAccess[];
|
||||
installed: boolean;
|
||||
installedPath: string | null;
|
||||
version: string | null;
|
||||
hooksInstalled: boolean;
|
||||
hookPointerPath: string | null;
|
||||
diagnostic?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Top-level detection envelope. Stable shape across all callers
|
||||
* (sidecar route, web UI, Tauri command).
|
||||
*/
|
||||
export interface ToolDetectionResult {
|
||||
/** Platform the detection ran on. */
|
||||
platform: NodeJS.Platform | 'other';
|
||||
/** ISO timestamp at which the detection completed. */
|
||||
detectedAt: string;
|
||||
/** Per-tool detection result. Registry order: built-ins first, then adapters. */
|
||||
tools: DetectedTool[];
|
||||
}
|
||||
780
packages/shared/src/types.ts
Normal file
780
packages/shared/src/types.ts
Normal file
@@ -0,0 +1,780 @@
|
||||
// @waggle/shared — Domain types for M3 Team Pilot
|
||||
|
||||
// === Auth & Users ===
|
||||
export interface User {
|
||||
id: string;
|
||||
clerkId: string;
|
||||
displayName: string;
|
||||
email: string;
|
||||
avatarUrl: string | null;
|
||||
mindPath: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// === Teams ===
|
||||
export interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
ownerId: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export type TeamRole = 'owner' | 'admin' | 'member';
|
||||
|
||||
export interface TeamMember {
|
||||
teamId: string;
|
||||
userId: string;
|
||||
role: TeamRole;
|
||||
roleDescription: string | null;
|
||||
interests: string[] | null;
|
||||
joinedAt: Date;
|
||||
}
|
||||
|
||||
// === Agent Configuration ===
|
||||
// UX-Refactor Phase 3 (PRD §15.5, gate B3): the legacy 10 members stay untouched
|
||||
// (cloud callers depend on userId/role/systemPrompt/config); the optional fields
|
||||
// below carry the Agent-entity vocabulary. Semantics: `tools` remains the raw
|
||||
// tool allowlist while skillIds/connectorIds/mcpIds are entity references;
|
||||
// `role` stays the free-text display label while `personaId` is the canonical
|
||||
// persona reference. lastRunAt/successRate are DERIVED at read from
|
||||
// execution_traces (B3) — route layers must not persist them.
|
||||
export interface AgentDef {
|
||||
id: string;
|
||||
userId: string;
|
||||
teamId: string | null;
|
||||
name: string;
|
||||
role: string | null;
|
||||
systemPrompt: string | null;
|
||||
model: string;
|
||||
tools: string[];
|
||||
config: Record<string, unknown>;
|
||||
createdAt: Date;
|
||||
type?: AgentType;
|
||||
goal?: string;
|
||||
description?: string;
|
||||
personaId?: string;
|
||||
autonomyLevel?: AutonomyLevel;
|
||||
workspaceIds?: string[];
|
||||
memoryScopes?: Scope[];
|
||||
skillIds?: string[];
|
||||
connectorIds?: string[];
|
||||
mcpIds?: string[];
|
||||
permissions?: Record<string, unknown>;
|
||||
status?: AgentRunState;
|
||||
/** ISO timestamp — derived at read, never persisted (B3). */
|
||||
lastRunAt?: string;
|
||||
/** 0-1 — derived at read from execution_traces outcomes (B3). */
|
||||
successRate?: number;
|
||||
}
|
||||
|
||||
export type AgentGroupStrategy = 'parallel' | 'sequential' | 'coordinator';
|
||||
|
||||
export interface AgentGroup {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
strategy: AgentGroupStrategy;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface AgentGroupMember {
|
||||
groupId: string;
|
||||
agentId: string;
|
||||
roleInGroup: 'lead' | 'worker';
|
||||
executionOrder: number;
|
||||
}
|
||||
|
||||
// === Tasks ===
|
||||
export type TaskStatus = 'open' | 'claimed' | 'in_progress' | 'done' | 'cancelled';
|
||||
export type TaskPriority = 'critical' | 'high' | 'normal' | 'low';
|
||||
|
||||
export interface Task {
|
||||
id: string;
|
||||
teamId: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
status: TaskStatus;
|
||||
priority: TaskPriority;
|
||||
createdBy: string;
|
||||
assignedTo: string | null;
|
||||
parentTaskId: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* AI-OS #6 — durable "why" injected into the agent system prompt each run
|
||||
* (the purpose above the current turn; complements recall + live awareness).
|
||||
* All levels optional. Today `project` (workspace) and `goal` (agent goal) are
|
||||
* populated; `mission` (no workspace-charter field yet) and `task` (already in
|
||||
* the self-awareness section) are reserved/omitted.
|
||||
*/
|
||||
export interface GoalAncestry {
|
||||
mission?: string;
|
||||
project?: string;
|
||||
goal?: string;
|
||||
task?: string;
|
||||
}
|
||||
|
||||
// === Waggle Dance Messages ===
|
||||
export type MessageType = 'broadcast' | 'request' | 'response';
|
||||
export type MessageSubtype =
|
||||
| 'knowledge_check' | 'task_delegation' | 'skill_request'
|
||||
| 'model_recommendation' | 'knowledge_match' | 'task_claim'
|
||||
| 'discovery' | 'routed_share' | 'skill_share' | 'model_recipe';
|
||||
|
||||
export interface WaggleMessage {
|
||||
id: string;
|
||||
teamId: string;
|
||||
senderId: string;
|
||||
type: MessageType;
|
||||
subtype: MessageSubtype;
|
||||
content: Record<string, unknown>;
|
||||
referenceId: string | null;
|
||||
routing: Array<{ userId: string; reason: string }> | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === Team Knowledge Graph ===
|
||||
export interface TeamEntity {
|
||||
id: string;
|
||||
teamId: string;
|
||||
entityType: string;
|
||||
name: string;
|
||||
properties: Record<string, unknown>;
|
||||
sharedBy: string;
|
||||
validFrom: Date;
|
||||
validTo: Date | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface TeamRelation {
|
||||
id: string;
|
||||
teamId: string;
|
||||
sourceId: string;
|
||||
targetId: string;
|
||||
relationType: string;
|
||||
confidence: number;
|
||||
properties: Record<string, unknown>;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === Team Resources ===
|
||||
export type ResourceType = 'model_recipe' | 'skill' | 'tool_config' | 'prompt_template';
|
||||
|
||||
export interface TeamResource {
|
||||
id: string;
|
||||
teamId: string;
|
||||
resourceType: ResourceType;
|
||||
name: string;
|
||||
description: string | null;
|
||||
config: Record<string, unknown>;
|
||||
sharedBy: string;
|
||||
rating: number;
|
||||
useCount: number;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === Jobs ===
|
||||
export type JobType = 'chat' | 'task' | 'cron' | 'waggle';
|
||||
export type JobStatus = 'queued' | 'running' | 'completed' | 'failed';
|
||||
|
||||
export interface AgentJob {
|
||||
id: string;
|
||||
teamId: string;
|
||||
userId: string;
|
||||
jobType: JobType;
|
||||
status: JobStatus;
|
||||
input: Record<string, unknown>;
|
||||
output: Record<string, unknown> | null;
|
||||
startedAt: Date | null;
|
||||
completedAt: Date | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === Cron ===
|
||||
export interface CronSchedule {
|
||||
id: string;
|
||||
teamId: string;
|
||||
createdBy: string;
|
||||
name: string;
|
||||
cronExpr: string;
|
||||
jobType: string;
|
||||
jobConfig: Record<string, unknown>;
|
||||
enabled: boolean;
|
||||
lastRunAt: Date | null;
|
||||
nextRunAt: Date | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === Intelligence ===
|
||||
export type ScoutSource = 'marketplace' | 'mcp_registry' | 'model_provider' | 'team';
|
||||
export type ScoutCategory = 'skill' | 'mcp' | 'model' | 'feature' | 'practice';
|
||||
export type FindingStatus = 'new' | 'presented' | 'adopted' | 'dismissed';
|
||||
|
||||
export interface ScoutFinding {
|
||||
id: string;
|
||||
userId: string | null;
|
||||
teamId: string | null;
|
||||
source: ScoutSource;
|
||||
category: ScoutCategory;
|
||||
title: string;
|
||||
summary: string | null;
|
||||
relevanceScore: number;
|
||||
url: string | null;
|
||||
status: FindingStatus;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export type SuggestionType = 'dashboard' | 'cron' | 'share' | 'skill' | 'upgrade';
|
||||
export type SuggestionStatus = 'pending' | 'accepted' | 'dismissed' | 'snoozed';
|
||||
|
||||
export interface ProactivePattern {
|
||||
id: string;
|
||||
name: string;
|
||||
trigger: Record<string, unknown>;
|
||||
suggestionType: SuggestionType;
|
||||
template: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface SuggestionEntry {
|
||||
id: string;
|
||||
userId: string;
|
||||
patternId: string;
|
||||
context: Record<string, unknown>;
|
||||
status: SuggestionStatus;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === Audit ===
|
||||
export interface AuditEntry {
|
||||
id: string;
|
||||
userId: string;
|
||||
teamId: string | null;
|
||||
agentName: string;
|
||||
actionType: string;
|
||||
description: string;
|
||||
beforeState: Record<string, unknown> | null;
|
||||
afterState: Record<string, unknown> | null;
|
||||
requiresApproval: boolean;
|
||||
approved: boolean | null;
|
||||
approvedBy: string | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
// === WebSocket Events ===
|
||||
export type WsClientEvent =
|
||||
| { type: 'authenticate'; token: string }
|
||||
| { type: 'join_team'; teamSlug: string }
|
||||
| { type: 'send_message'; teamSlug: string; messageType: MessageType; subtype: MessageSubtype; content: Record<string, unknown> };
|
||||
|
||||
export type WsServerEvent =
|
||||
| { type: 'waggle_message'; message: WaggleMessage }
|
||||
| { type: 'task_update'; task: Task }
|
||||
| { type: 'agent_status'; userId: string; status: 'running' | 'idle' | 'completed' }
|
||||
| { type: 'suggestion'; suggestion: SuggestionEntry }
|
||||
| { type: 'scout_finding'; finding: ScoutFinding }
|
||||
| { type: 'job_progress'; jobId: string; progress: Record<string, unknown> };
|
||||
|
||||
// ─── Connector Types ────────────────────────────────────────────────────
|
||||
|
||||
/** Connector credential type stored in vault */
|
||||
export interface ConnectorCredential {
|
||||
type: 'api_key' | 'oauth2' | 'bearer' | 'basic';
|
||||
/** For oauth2: access token */
|
||||
accessToken?: string;
|
||||
/** For oauth2: refresh token */
|
||||
refreshToken?: string;
|
||||
/** ISO timestamp when accessToken expires */
|
||||
expiresAt?: string;
|
||||
/** OAuth scopes granted */
|
||||
scopes?: string[];
|
||||
/** For api_key/bearer: the key or token value */
|
||||
apiKey?: string;
|
||||
/** For basic: username */
|
||||
username?: string;
|
||||
}
|
||||
|
||||
/** Connector status in the system */
|
||||
export type ConnectorStatus = 'connected' | 'disconnected' | 'expired' | 'error';
|
||||
|
||||
/** Rich action metadata for SDK-backed connectors */
|
||||
export interface ConnectorActionMeta {
|
||||
name: string;
|
||||
description: string;
|
||||
riskLevel: 'low' | 'medium' | 'high';
|
||||
}
|
||||
|
||||
/** Connector definition — what the user sees */
|
||||
export interface ConnectorDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
/** Which service this connects to */
|
||||
service: string;
|
||||
/** What auth method is needed */
|
||||
authType: 'api_key' | 'oauth2' | 'bearer' | 'basic';
|
||||
/** Whether credentials exist in vault */
|
||||
status: ConnectorStatus;
|
||||
/** What the connector can do */
|
||||
capabilities: ('read' | 'write' | 'search')[];
|
||||
/** Which substrate manages this connector */
|
||||
substrate: 'waggle' | 'kvark';
|
||||
/** Agent tools this connector provides when connected */
|
||||
tools: string[];
|
||||
/** Connector-specific config */
|
||||
config?: Record<string, unknown>;
|
||||
/** Rich action metadata (optional — available when SDK connector is loaded) */
|
||||
actions?: ConnectorActionMeta[];
|
||||
/** CDN URL for SVG logo */
|
||||
logoUrl?: string;
|
||||
/** Connector category */
|
||||
category?: 'productivity' | 'development' | 'crm' | 'data' | 'communication' | 'storage' | 'integration';
|
||||
/** 1-2 sentences: what credential is needed and where to get it */
|
||||
setupGuide?: string;
|
||||
/** When the connector was last manually synced (C16). GET /api/connectors
|
||||
* enriches each definition with this from the vault stamp. */
|
||||
lastSyncAt?: string;
|
||||
}
|
||||
|
||||
/** Connector health for cockpit display */
|
||||
export interface ConnectorHealth {
|
||||
id: string;
|
||||
name: string;
|
||||
status: ConnectorStatus;
|
||||
lastChecked: string;
|
||||
error?: string;
|
||||
tokenExpiresAt?: string;
|
||||
/** When the connector was last manually synced (C16: sync-now = health
|
||||
* re-probe + stamp). Stamped by POST /api/connectors/:id/sync; merged into
|
||||
* GET /api/connectors/:id/health (and the fallback path) from the vault. */
|
||||
lastSyncAt?: string;
|
||||
}
|
||||
|
||||
// === UX-Refactor vocabulary (PRD §15.2) ===
|
||||
// Domain literal unions for the workspace-first Agent Desktop refactor.
|
||||
// Single source of truth — the sidecar route layer and apps/web both import these
|
||||
// (no per-file union duplication; see docs/ux-refactor/deltas/shared-types-delta.md §0).
|
||||
export type WorkspaceType =
|
||||
| 'project' | 'client' | 'research' | 'personal' | 'team' | 'organization';
|
||||
export type Scope = 'personal' | 'workspace' | 'team' | 'organization';
|
||||
/** 0-100 confidence score for a memory / provenance signal. */
|
||||
export type Confidence = number;
|
||||
|
||||
export type MemoryKind =
|
||||
| 'fact' | 'decision' | 'task' | 'preference'
|
||||
| 'strategy' | 'learning' | 'goal' | 'entity';
|
||||
export type ArtifactKind =
|
||||
| 'document' | 'presentation' | 'spreadsheet' | 'dashboard'
|
||||
| 'research' | 'code' | 'media' | 'design' | 'other';
|
||||
export type AgentType = 'personal' | 'workspace' | 'team' | 'autonomous';
|
||||
export type AutonomyLevel = 'manual' | 'guided' | 'medium' | 'high';
|
||||
/** PRD §14.5 agent lifecycle states — SINGLE source of truth. The sidecar
|
||||
* store (packages/server/src/local/agents-store.ts) and the FE view-model
|
||||
* (apps/web/src/lib/types.ts) re-export this union; do not redeclare it.
|
||||
* Declared as a const tuple so schemas.ts derives `agentStatusEnum` from it
|
||||
* (z.enum) — the type and the runtime list cannot drift. */
|
||||
export const AGENT_RUN_STATES = [
|
||||
'draft', 'idle', 'running', 'paused', 'failed',
|
||||
'waiting_for_approval', 'completed', 'archived',
|
||||
] as const;
|
||||
export type AgentRunState = (typeof AGENT_RUN_STATES)[number];
|
||||
|
||||
/**
|
||||
* Canonical runtime record for work performed inside the Room. This is
|
||||
* intentionally separate from `AgentRunState` above: that union describes the
|
||||
* lifecycle of a saved Agent blueprint, while collaboration runs are concrete
|
||||
* executions by Waggle agents or external tools.
|
||||
*/
|
||||
export const COLLABORATION_RUN_SOURCES = [
|
||||
'external_tool', 'fleet', 'chat_subagent', 'workflow', 'agent_group',
|
||||
] as const;
|
||||
export type CollaborationRunSource = (typeof COLLABORATION_RUN_SOURCES)[number];
|
||||
|
||||
export const COLLABORATION_RUN_STATUSES = [
|
||||
'queued', 'starting', 'running', 'waiting_for_approval', 'paused',
|
||||
'cancelling', 'completed', 'failed', 'cancelled', 'interrupted',
|
||||
] as const;
|
||||
export type CollaborationRunStatus = (typeof COLLABORATION_RUN_STATUSES)[number];
|
||||
|
||||
export const COLLABORATION_RUN_CONTROLS = ['cancel', 'pause', 'resume', 'message'] as const;
|
||||
export type CollaborationRunControl = (typeof COLLABORATION_RUN_CONTROLS)[number];
|
||||
|
||||
export interface CollaborationRunExecutor {
|
||||
kind: 'external_tool' | 'waggle_agent' | 'coordinator';
|
||||
toolId?: string;
|
||||
pid?: number;
|
||||
agentId?: string;
|
||||
personaId?: string;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
export interface CollaborationRunProgress {
|
||||
message: string;
|
||||
phase?: string;
|
||||
current?: number;
|
||||
total?: number;
|
||||
}
|
||||
|
||||
export interface CollaborationRunResult {
|
||||
summary?: string;
|
||||
sessionId?: string;
|
||||
traceId?: string;
|
||||
artifacts?: string[];
|
||||
exitCode?: number | null;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface CollaborationRunMetrics {
|
||||
toolsUsed?: string[];
|
||||
inputTokens?: number;
|
||||
outputTokens?: number;
|
||||
costUsd?: number;
|
||||
}
|
||||
|
||||
export interface CollaborationRunMemoryRefs {
|
||||
status: 'pending' | 'complete' | 'partial' | 'failed';
|
||||
personalFrameIds: number[];
|
||||
workspaceFrameIds: Record<string, number[]>;
|
||||
}
|
||||
|
||||
export interface CollaborationRunCapabilities {
|
||||
cancel: boolean;
|
||||
pause: boolean;
|
||||
resume: boolean;
|
||||
message: boolean;
|
||||
}
|
||||
|
||||
export interface CollaborationRunAttribution {
|
||||
routeDecisionId: string;
|
||||
/** Absent when the run was dispatched without a memory brief. */
|
||||
briefHash?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One executable leaf is bound to exactly one `workspaceId`. A Room/root run
|
||||
* may coordinate several leaves and therefore carries only `workspaceIds`.
|
||||
*/
|
||||
interface CollaborationRunBase {
|
||||
schemaVersion: 1;
|
||||
id: string;
|
||||
roomId: string;
|
||||
rootRunId: string;
|
||||
source: CollaborationRunSource;
|
||||
executor: CollaborationRunExecutor;
|
||||
title: string;
|
||||
task: string;
|
||||
attribution?: CollaborationRunAttribution;
|
||||
status: CollaborationRunStatus;
|
||||
progress?: CollaborationRunProgress;
|
||||
result?: CollaborationRunResult;
|
||||
metrics?: CollaborationRunMetrics;
|
||||
memoryRefs: CollaborationRunMemoryRefs;
|
||||
capabilities: CollaborationRunCapabilities;
|
||||
revision: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
startedAt?: string;
|
||||
completedAt?: string;
|
||||
}
|
||||
|
||||
/** A Room is the durable coordination root and may span many workspaces. */
|
||||
export interface CollaborationRoomRun extends CollaborationRunBase {
|
||||
kind: 'room';
|
||||
parentRunId: null;
|
||||
workspaceIds: string[];
|
||||
}
|
||||
|
||||
/** An executable participant is always bound to one concrete workspace. */
|
||||
export interface CollaborationWorkerRun extends CollaborationRunBase {
|
||||
kind: 'worker';
|
||||
parentRunId: string;
|
||||
workspaceId: string;
|
||||
retryOfRunId?: string;
|
||||
}
|
||||
|
||||
export type CollaborationRun = CollaborationRoomRun | CollaborationWorkerRun;
|
||||
|
||||
export interface CollaborationRunEvent {
|
||||
seq: number;
|
||||
type: 'upsert';
|
||||
run: CollaborationRun;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export interface CollaborationRunSnapshot {
|
||||
lastSeq: number;
|
||||
runs: CollaborationRun[];
|
||||
}
|
||||
/** PRD §15.2 / §12.13 extension domains (B7 ratified: drop 'external_tool' —
|
||||
* external tools surface via connectors/MCPs — and add 'agent'). Const tuple
|
||||
* so the Extend routes validate the `type` facet against the runtime list. */
|
||||
export const EXTENSION_TYPES = [
|
||||
'skill', 'agent', 'connector', 'mcp', 'model', 'template',
|
||||
] as const;
|
||||
export type ExtensionType = (typeof EXTENSION_TYPES)[number];
|
||||
|
||||
/**
|
||||
* Installed MCP-server instance state (shared-types-delta §8b). The CATALOG
|
||||
* entry stays `McpServer` (mcp-catalog.ts) — `McpInstance.id` references
|
||||
* `McpServer.id` for catalog installs, or the custom server name for
|
||||
* user-added servers (POST /api/mcps). Produced by GET /api/mcps.
|
||||
*
|
||||
* v1 carries ONLY fields the route actually emits. The delta's
|
||||
* version/riskLevel/permissions/lastUsedAt fields are deferred (no producer
|
||||
* yet); per-tool `permissions` granularity is deferred together with C19's
|
||||
* single-workspaceId scope model — PATCH /api/mcps/:id/permissions accepts
|
||||
* `{ workspaceId }` or `{ scope: 'personal' }` only.
|
||||
*/
|
||||
export interface McpInstance {
|
||||
id: string;
|
||||
name: string;
|
||||
status: 'installed' | 'running' | 'stopped' | 'error';
|
||||
/** Locality (§17.3): 'workspace' when pinned to a workspaceId, else 'personal'. */
|
||||
scope: Scope;
|
||||
/** Workspace/agent ids this server is pinned to (C19: single workspaceId v1). */
|
||||
connectedTo?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* PRD §15.3 workspace contract — the normalized shape the sidecar route layer
|
||||
* exposes to the new UI. The PERSISTED struct lives in `@waggle/hive-mind-core`
|
||||
* (`workspace-manager.ts` `WorkspaceConfig`, a superset carrying legacy fields).
|
||||
* A route-layer normalizer (Phase 1) bridges the struct to this contract, filling
|
||||
* defaults for pre-V2 workspaces (type from templateId/group, status 'active',
|
||||
* updatedAt from created). Kept separate (not `extends`) so the additive fields on
|
||||
* the persistence struct stay optional and existing workspace literals don't break.
|
||||
*/
|
||||
export interface WorkspaceConfigV2 {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
type: WorkspaceType;
|
||||
group: string;
|
||||
icon?: string;
|
||||
status: 'active' | 'paused' | 'archived';
|
||||
model?: string;
|
||||
personaId?: string;
|
||||
templateId?: string;
|
||||
tools?: string[];
|
||||
skills?: string[];
|
||||
agentIds?: string[];
|
||||
connectorIds?: string[];
|
||||
mcpIds?: string[];
|
||||
storageType?: 'virtual' | 'local' | 'team';
|
||||
storagePath?: string;
|
||||
teamId?: string;
|
||||
teamRole?: 'owner' | 'admin' | 'member' | 'viewer';
|
||||
riskLevel?: 'minimal' | 'limited' | 'high-risk' | 'unacceptable';
|
||||
created: string;
|
||||
updatedAt: string;
|
||||
lastActiveAt?: string;
|
||||
}
|
||||
|
||||
// === UX-Refactor Command vocabulary (PRD §12.3 / shared-types-delta §9) ===
|
||||
// Command Center (Ctrl+K) result/command shapes. Single source of truth — the
|
||||
// sidecar `command.ts` route layer and apps/web both import these. See
|
||||
// docs/ux-refactor/deltas/shared-types-delta.md §9.
|
||||
|
||||
/** The six verb sections of the Command Center (PRD §12.3). */
|
||||
export type CommandCategory =
|
||||
| 'search' | 'launch' | 'create' | 'run' | 'navigate' | 'extend';
|
||||
|
||||
/**
|
||||
* Every searchable object class the palette federates over (PRD §12.3 FR:
|
||||
* "search across workspaces, memory, artifacts, sessions, people, agents,
|
||||
* skills, commands, connectors, MCPs"). Artifact/agent rows are gated until
|
||||
* those screens (S05/S09) land — the type carries them so the union is stable.
|
||||
*/
|
||||
export type CommandResultType =
|
||||
| 'workspace' | 'memory' | 'artifact' | 'session' | 'person'
|
||||
| 'agent' | 'skill' | 'command' | 'connector' | 'mcp' | 'automation';
|
||||
|
||||
/**
|
||||
* What a `run`/`create`/`navigate`/`extend` result does when executed. A
|
||||
* Navigate result carries a `route`; a server-dispatched action carries an
|
||||
* `endpoint` + `payload`. All optional so a pure Search result needs none.
|
||||
*/
|
||||
export interface CommandAction {
|
||||
route?: string;
|
||||
endpoint?: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** One row in the Command Center result list. */
|
||||
export interface CommandResult {
|
||||
id: string;
|
||||
type: CommandResultType;
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
category: CommandCategory;
|
||||
icon?: string;
|
||||
/** §12.3 permission-gated → renders the approval prompt before execution. */
|
||||
requiresApproval?: boolean;
|
||||
action?: CommandAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* The execute request the palette posts to `POST /api/command/execute`. A
|
||||
* structured command resolves via `id`; a natural-language command rides in
|
||||
* `input` (PRD §12.3 "natural-language command input").
|
||||
*/
|
||||
export interface Command {
|
||||
id?: string;
|
||||
input?: string;
|
||||
category?: CommandCategory;
|
||||
type?: CommandResultType;
|
||||
workspaceId?: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// === UX-Refactor Memory entity (PRD §15.4 / shared-types-delta §3b) ===
|
||||
// The normalized contract the sidecar `memory.ts` route layer exposes to the
|
||||
// Memory Center. The PERSISTED row lives in `@waggle/hive-mind-core`
|
||||
// (`memory_frames`); a route-layer normalizer (Phase 2B) projects the row +
|
||||
// its JSON `metadata` blob into this shape. `kind/confidence/scope/status/
|
||||
// sourceId/tags/evidence/related*` ride `memory_frames.metadata` (added by the
|
||||
// idempotent ADD-COLUMN migration M1 in Phase 2B); the base columns map 1:1
|
||||
// (`content`, `created_at`, `last_accessed`, `importance`, `source`).
|
||||
|
||||
/**
|
||||
* Memory lifecycle state (PRD §12.4 / §14.4), reconciled with the Phase-2 gate
|
||||
* ratifications (2026-06-09):
|
||||
* - `unreviewed` — freshly imported, pending non-blocking review (C33: import
|
||||
* commits immediately but lands unreviewed; Memory Center "needs review" filter).
|
||||
* - `archived` — reversible soft-status (A8 Archive).
|
||||
* - `deprecated` — superseded (A8 Deprecate; mirrors the existing `importance`).
|
||||
* - `low_confidence` / `conflict` — surfaced for review; may be derived at
|
||||
* recall-time (S04 C10) rather than persisted.
|
||||
* - Delete is a HARD delete (A8) — there is no `trash`/tombstone state in v1.
|
||||
*/
|
||||
export type MemoryStatus =
|
||||
| 'active' | 'unreviewed' | 'low_confidence' | 'conflict' | 'deprecated' | 'archived';
|
||||
|
||||
export interface Memory {
|
||||
id: string;
|
||||
kind: MemoryKind;
|
||||
title: string;
|
||||
content: string;
|
||||
scope: Scope;
|
||||
workspaceId?: string;
|
||||
teamId?: string | null;
|
||||
/** Provenance class — maps from `memory_frames.source` (FrameSource). */
|
||||
source: string;
|
||||
sourceId?: string | null;
|
||||
sourceUrl?: string | null;
|
||||
/** #7: whether a verbatim raw_archive row is linked (metadata.archiveUids), i.e.
|
||||
* whether "View original source" can actually load. Distinct from sourceId — an
|
||||
* auto-synced harvest summary carries a sourceId but no archive link, so the
|
||||
* affordance gates on THIS, not on sourceId presence. */
|
||||
hasOriginalSource?: boolean;
|
||||
/** 0-100; B2 heuristic at import (source-trust × adapter × dedup). */
|
||||
confidence?: Confidence;
|
||||
/** Reuses the substrate `Importance` union (`frames.ts`). */
|
||||
importance: 'critical' | 'important' | 'normal' | 'temporary' | 'deprecated';
|
||||
evidence?: string[];
|
||||
tags?: string[];
|
||||
relatedMemoryIds?: string[];
|
||||
relatedArtifactIds?: string[];
|
||||
status: MemoryStatus;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
lastAccessedAt?: string;
|
||||
}
|
||||
|
||||
// === UX-Refactor Artifact entity (PRD §15.6 / shared-types-delta §4a) ===
|
||||
// Artifacts are first-class produced OUTCOMES (decks/docs/sheets/dashboards/
|
||||
// research), NOT raw file attachments. Per the Phase-2 gate ratification (A6),
|
||||
// the backing store is a per-workspace `artifacts.json` index over the existing
|
||||
// StorageProvider (NO new SQLite table) — assigned a stable id + status/tags/
|
||||
// relations. Classification rule: an artifact is an EXPLICIT produced output
|
||||
// (generated doc or user-promoted file), not every ingested input.
|
||||
|
||||
export type ArtifactStatus = 'draft' | 'ready' | 'in_review' | 'final' | 'archived';
|
||||
|
||||
export interface Artifact {
|
||||
id: string;
|
||||
title: string;
|
||||
kind: ArtifactKind;
|
||||
workspaceId: string;
|
||||
teamId?: string | null;
|
||||
createdBy: string;
|
||||
/** agent | user | import | automation */
|
||||
source: string;
|
||||
status: ArtifactStatus;
|
||||
/** Pre-archive status, stashed by the server on Archive so Unarchive restores
|
||||
* the prior lifecycle state faithfully (A8 reversibility), not a flat 'draft'. */
|
||||
prevStatus?: ArtifactStatus;
|
||||
mimeType?: string;
|
||||
/** StorageProvider path (virtual | local | team). */
|
||||
storagePath?: string;
|
||||
previewUrl?: string;
|
||||
tags?: string[];
|
||||
relatedMemoryIds?: string[];
|
||||
relatedSessionIds?: string[];
|
||||
relatedTaskIds?: string[];
|
||||
relatedAgentIds?: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
// === Federated "search-related" envelope (S05 headline, PRD §16.6 / line 532) ===
|
||||
// The Artifact Center's defining endpoint returns an artifact PLUS its related
|
||||
// memories/sessions/tasks/agents — "outcomes with relations, not files". Memories
|
||||
// reuse the canonical `Memory` shape; the other three are lightweight references
|
||||
// (the full Session/Task/Agent contracts are not part of this envelope on purpose).
|
||||
|
||||
export interface RelatedRef {
|
||||
id: string;
|
||||
title: string;
|
||||
/** Owning workspace, when the item is workspace-scoped. */
|
||||
workspaceId?: string;
|
||||
/** Short text excerpt for display, when available. */
|
||||
snippet?: string;
|
||||
/** Sub-classification (e.g. session status, task state, agent type). */
|
||||
kind?: string;
|
||||
}
|
||||
|
||||
export interface RelatedSearchResult {
|
||||
artifacts: Artifact[];
|
||||
memories: Memory[];
|
||||
sessions: RelatedRef[];
|
||||
tasks: RelatedRef[];
|
||||
agents: RelatedRef[];
|
||||
}
|
||||
|
||||
// NOTE (Phase 3A review): a speculative shared `Skill` interface was removed
|
||||
// here — no route produces it (GET /api/skills returns {name,length,preview};
|
||||
// POST /api/skills/create takes {name,description,steps[],tools,category}).
|
||||
// Re-introduce a Skill contract only together with a route that emits it.
|
||||
|
||||
// === UX-Refactor Automation entity (PRD §16.10 / shared-types-delta §7, Phase 3) ===
|
||||
// "Automations" is the PRD-vocabulary alias over the existing cron substrate
|
||||
// (B4 — alias, never rename; `/api/cron/*` callers keep working). Trigger/
|
||||
// condition/actions ride the existing `cron_schedules.job_config` TEXT blob —
|
||||
// NO migration. C24: schedule-only triggers v1 ('event' stays in the union but
|
||||
// is rejected by the route layer). C25: `condition` is an ADVISORY string —
|
||||
// no evaluation engine in v1.
|
||||
|
||||
export type AutomationTriggerType = 'schedule' | 'event' | 'manual';
|
||||
|
||||
export interface Automation {
|
||||
id: string;
|
||||
name: string;
|
||||
triggerType: AutomationTriggerType;
|
||||
/** Cron expression when triggerType === 'schedule'. */
|
||||
schedule?: string;
|
||||
/** Advisory only (C25) — stored, surfaced, never evaluated in v1. */
|
||||
condition?: string;
|
||||
actions: string[];
|
||||
agentId?: string;
|
||||
notify?: boolean;
|
||||
workspaceId: string;
|
||||
status: 'active' | 'paused' | 'running' | 'failed';
|
||||
lastRun?: string;
|
||||
nextRun?: string;
|
||||
}
|
||||
113
packages/shared/tests/connector-recommendations.test.ts
Normal file
113
packages/shared/tests/connector-recommendations.test.ts
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* Persona-aware connector recommendations.
|
||||
*
|
||||
* Two layers of guards:
|
||||
* 1. SHAPE — primary lengths in the agreed 3–5 range, no duplicates,
|
||||
* fallback for unknown personas, every onboarding persona has a
|
||||
* matching map entry.
|
||||
* 2. CATALOG MEMBERSHIP — every connector ID referenced by the
|
||||
* recommendations actually exists in mcp-catalog.ts. This is the
|
||||
* bug class we fix here: typos / stale IDs / forward-references
|
||||
* that ship a recommendation pointing nowhere.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
recommendConnectors,
|
||||
flattenRecommendation,
|
||||
allReferencedConnectorIds,
|
||||
CONNECTOR_RECOMMENDATIONS,
|
||||
} from '../src/connector-recommendations.js';
|
||||
import { MCP_CATALOG } from '../src/mcp-catalog.js';
|
||||
|
||||
const CATALOG_IDS = new Set(MCP_CATALOG.map(s => s.id));
|
||||
|
||||
describe('recommendConnectors', () => {
|
||||
it('returns universal defaults for an unknown persona id', () => {
|
||||
const r = recommendConnectors('not-a-real-persona-id');
|
||||
expect(r.primary).toContain('gdrive-mcp');
|
||||
expect(r.primary).toContain('gmail-mcp');
|
||||
expect(r.primary).toContain('notion-mcp');
|
||||
});
|
||||
|
||||
it('returns the persona-specific recommendation when the id matches', () => {
|
||||
const sales = recommendConnectors('sales-rep');
|
||||
expect(sales.primary).toContain('hubspot-mcp');
|
||||
expect(sales.primary).toContain('salesforce-mcp');
|
||||
|
||||
const coder = recommendConnectors('coder');
|
||||
expect(coder.primary).toContain('github-mcp');
|
||||
expect(coder.primary).toContain('linear-mcp');
|
||||
|
||||
const consultant = recommendConnectors('consultant');
|
||||
expect(consultant.primary).toContain('notion-mcp');
|
||||
expect(consultant.primary).toContain('gdrive-mcp');
|
||||
});
|
||||
|
||||
it('always returns a non-empty primary list (never strands the UI on empty)', () => {
|
||||
const personaIds = [...Object.keys(CONNECTOR_RECOMMENDATIONS), 'unknown-persona', ''];
|
||||
for (const id of personaIds) {
|
||||
const r = recommendConnectors(id);
|
||||
expect(r.primary.length).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('CONNECTOR_RECOMMENDATIONS shape', () => {
|
||||
it('every recommendation has 3-6 primary entries (Ljiljana-bar cognitive ceiling)', () => {
|
||||
for (const [personaId, rec] of Object.entries(CONNECTOR_RECOMMENDATIONS)) {
|
||||
expect(rec.primary.length, `persona "${personaId}" primary out of range`).toBeGreaterThanOrEqual(3);
|
||||
expect(rec.primary.length, `persona "${personaId}" primary out of range`).toBeLessThanOrEqual(6);
|
||||
}
|
||||
});
|
||||
|
||||
it('no recommendation has duplicate ids within primary or secondary', () => {
|
||||
for (const [personaId, rec] of Object.entries(CONNECTOR_RECOMMENDATIONS)) {
|
||||
const allIds = [...rec.primary, ...rec.secondary];
|
||||
const dupes = allIds.filter((id, i) => allIds.indexOf(id) !== i);
|
||||
expect(dupes, `persona "${personaId}" has duplicate connector ids: ${dupes.join(', ')}`).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it('every recommended id resolves to a real entry in mcp-catalog.ts', () => {
|
||||
const referenced = allReferencedConnectorIds();
|
||||
const missing = referenced.filter(id => !CATALOG_IDS.has(id));
|
||||
expect(missing, `connector recommendation references ids not in mcp-catalog.ts: ${missing.join(', ')}`).toEqual([]);
|
||||
});
|
||||
|
||||
it('covers every persona id from persona-data.ts (no recommendation drift after a persona is added)', () => {
|
||||
// The persona list is duplicated here intentionally — importing from
|
||||
// packages/agent would couple shared->agent, which we don't want for
|
||||
// a leaf data module. If a persona is added in persona-data.ts but
|
||||
// not here, this test fails and the fix is a one-line entry.
|
||||
const ALL_PERSONA_IDS = [
|
||||
'researcher', 'writer', 'analyst', 'coder',
|
||||
'project-manager', 'executive-assistant', 'sales-rep', 'marketer',
|
||||
'product-manager-senior', 'hr-manager', 'legal-professional',
|
||||
'finance-owner', 'consultant',
|
||||
'general-purpose', 'planner', 'verifier', 'coordinator',
|
||||
'support-agent', 'ops-manager', 'data-engineer', 'recruiter',
|
||||
'creative-director',
|
||||
];
|
||||
const recommendedIds = new Set(Object.keys(CONNECTOR_RECOMMENDATIONS));
|
||||
const missingFromMap = ALL_PERSONA_IDS.filter(id => !recommendedIds.has(id));
|
||||
expect(missingFromMap, `personas without a CONNECTOR_RECOMMENDATIONS entry: ${missingFromMap.join(', ')}`).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('flattenRecommendation', () => {
|
||||
it('returns primary IDs in order, then secondary', () => {
|
||||
const flat = flattenRecommendation({
|
||||
primary: ['a', 'b', 'c'],
|
||||
secondary: ['d', 'e'],
|
||||
});
|
||||
expect(flat).toEqual(['a', 'b', 'c', 'd', 'e']);
|
||||
});
|
||||
|
||||
it('preserves primary-first ordering for a real persona', () => {
|
||||
const flat = flattenRecommendation(recommendConnectors('coder'));
|
||||
// First five must be the primary list (anchored to github-mcp on top
|
||||
// because that's the most-load-bearing connector for the coder persona).
|
||||
expect(flat[0]).toBe('github-mcp');
|
||||
});
|
||||
});
|
||||
41
packages/shared/tests/loop-templates.test.ts
Normal file
41
packages/shared/tests/loop-templates.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { LOOP_TEMPLATES } from '../src/loop-templates.js';
|
||||
|
||||
describe('LOOP_TEMPLATES (knowledge-worker Loop catalog)', () => {
|
||||
it('ships a useful set of templates', () => {
|
||||
expect(LOOP_TEMPLATES.length).toBeGreaterThanOrEqual(5);
|
||||
});
|
||||
|
||||
it('every template id is unique', () => {
|
||||
const ids = LOOP_TEMPLATES.map(t => t.id);
|
||||
expect(new Set(ids).size).toBe(ids.length);
|
||||
});
|
||||
|
||||
it('every template has a non-empty name, description, role and prompt', () => {
|
||||
for (const t of LOOP_TEMPLATES) {
|
||||
expect(t.name.trim().length, t.id).toBeGreaterThan(0);
|
||||
expect(t.description.trim().length, t.id).toBeGreaterThan(0);
|
||||
expect(t.role.trim().length, t.id).toBeGreaterThan(0);
|
||||
expect(t.jobConfig.prompt.trim().length, t.id).toBeGreaterThan(0);
|
||||
}
|
||||
});
|
||||
|
||||
it('every defaultCron is a valid 5-field cron expression', () => {
|
||||
for (const t of LOOP_TEMPLATES) {
|
||||
const fields = t.defaultCron.trim().split(/\s+/);
|
||||
expect(fields.length, `${t.id}: "${t.defaultCron}"`).toBe(5);
|
||||
for (const f of fields) {
|
||||
expect(/^[\d*,/-]+$/.test(f), `${t.id} field "${f}"`).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('cadences are daily-or-slower — never a minute/sub-hourly wildcard', () => {
|
||||
// A Loop is several LLM round-trips; a '*' or '*/n' minute field would burn
|
||||
// tokens every minute. Templates must use a concrete minute.
|
||||
for (const t of LOOP_TEMPLATES) {
|
||||
const minute = t.defaultCron.trim().split(/\s+/)[0];
|
||||
expect(minute === '*' || minute.includes('/'), `${t.id} fires too often (minute="${minute}")`).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
54
packages/shared/tests/risk.test.ts
Normal file
54
packages/shared/tests/risk.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
RISK_LEVELS, APPROVAL_CLASSES, TRUST_SOURCES, ASSESSMENT_MODES,
|
||||
AUDIT_ACTIONS, AUDIT_CAPABILITY_TYPES, AUDIT_INITIATORS,
|
||||
riskRank, riskAtLeast, sqlInList,
|
||||
} from '../src/risk.js';
|
||||
|
||||
describe('canonical risk taxonomy (A1) — widest-set parity', () => {
|
||||
it('RiskLevel adopts the audit set (adds critical)', () => {
|
||||
expect([...RISK_LEVELS]).toEqual(['low', 'medium', 'high', 'critical']);
|
||||
});
|
||||
it('ApprovalClass adopts the audit set (adds blocked)', () => {
|
||||
expect([...APPROVAL_CLASSES]).toEqual(['standard', 'elevated', 'critical', 'blocked']);
|
||||
});
|
||||
it('TrustSource is the 7-value set incl. security-gate', () => {
|
||||
expect([...TRUST_SOURCES]).toEqual([
|
||||
'builtin', 'starter_pack', 'local_user', 'third_party_verified',
|
||||
'third_party_unverified', 'unknown', 'security-gate',
|
||||
]);
|
||||
});
|
||||
it('AssessmentMode is declared/heuristic/mixed', () => {
|
||||
expect([...ASSESSMENT_MODES]).toEqual(['declared', 'heuristic', 'mixed']);
|
||||
});
|
||||
it('AuditAction includes uninstalled (P5/D4)', () => {
|
||||
expect([...AUDIT_ACTIONS]).toEqual([
|
||||
'proposed', 'approved', 'installed', 'rejected', 'failed', 'blocked', 'uninstalled',
|
||||
]);
|
||||
});
|
||||
it('AuditCapabilityType + AuditInitiator match the store', () => {
|
||||
expect([...AUDIT_CAPABILITY_TYPES]).toEqual(['native', 'skill', 'plugin', 'mcp', 'connector', 'marketplace']);
|
||||
expect([...AUDIT_INITIATORS]).toEqual(['agent', 'user', 'system']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('risk ordering helpers', () => {
|
||||
it('riskRank orders ascending, critical highest', () => {
|
||||
expect(riskRank('low')).toBe(0);
|
||||
expect(riskRank('critical')).toBe(3);
|
||||
expect(riskRank('critical')).toBeGreaterThan(riskRank('low'));
|
||||
});
|
||||
it('riskAtLeast compares on the canonical scale (critical >= low, not below)', () => {
|
||||
expect(riskAtLeast('critical', 'low')).toBe(true);
|
||||
expect(riskAtLeast('low', 'high')).toBe(false);
|
||||
expect(riskAtLeast('high', 'high')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sqlInList — CHECK-constraint single source', () => {
|
||||
it('quotes and comma-joins for a SQLite IN clause', () => {
|
||||
expect(sqlInList(AUDIT_ACTIONS)).toBe(
|
||||
"'proposed', 'approved', 'installed', 'rejected', 'failed', 'blocked', 'uninstalled'",
|
||||
);
|
||||
});
|
||||
});
|
||||
89
packages/shared/tests/schemas.test.ts
Normal file
89
packages/shared/tests/schemas.test.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
createTeamSchema,
|
||||
createTaskSchema,
|
||||
sendMessageSchema,
|
||||
createAgentSchema,
|
||||
createAgentGroupSchema,
|
||||
} from '../src/schemas.js';
|
||||
|
||||
describe('createTeamSchema', () => {
|
||||
it('accepts valid team', () => {
|
||||
const result = createTeamSchema.safeParse({ name: 'Marketing', slug: 'marketing' });
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects empty name', () => {
|
||||
const result = createTeamSchema.safeParse({ name: '', slug: 'ok' });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects invalid slug characters', () => {
|
||||
const result = createTeamSchema.safeParse({ name: 'Ok', slug: 'Has Spaces' });
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createTaskSchema', () => {
|
||||
it('accepts valid task with defaults', () => {
|
||||
const result = createTaskSchema.safeParse({ title: 'Research competitors' });
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.priority).toBe('normal');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('sendMessageSchema', () => {
|
||||
it('accepts valid waggle dance message', () => {
|
||||
const result = sendMessageSchema.safeParse({
|
||||
type: 'request',
|
||||
subtype: 'knowledge_check',
|
||||
content: { topic: 'competitor pricing', scope: 'Product Line X' },
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects invalid subtype', () => {
|
||||
const result = sendMessageSchema.safeParse({
|
||||
type: 'broadcast',
|
||||
subtype: 'invalid_type',
|
||||
content: {},
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAgentSchema', () => {
|
||||
it('accepts agent with defaults', () => {
|
||||
const result = createAgentSchema.safeParse({ name: 'web-searcher' });
|
||||
expect(result.success).toBe(true);
|
||||
if (result.success) {
|
||||
expect(result.data.model).toBe('claude-haiku-4-5');
|
||||
expect(result.data.tools).toEqual([]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAgentGroupSchema', () => {
|
||||
it('accepts valid agent group', () => {
|
||||
const result = createAgentGroupSchema.safeParse({
|
||||
name: 'Research Team',
|
||||
strategy: 'parallel',
|
||||
members: [
|
||||
{ agentId: '00000000-0000-0000-0000-000000000001', roleInGroup: 'lead' },
|
||||
{ agentId: '00000000-0000-0000-0000-000000000002' },
|
||||
],
|
||||
});
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects invalid strategy', () => {
|
||||
const result = createAgentGroupSchema.safeParse({
|
||||
name: 'Bad',
|
||||
strategy: 'random',
|
||||
members: [],
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
});
|
||||
41
packages/shared/tests/tool-manifests.test.ts
Normal file
41
packages/shared/tests/tool-manifests.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
BUILTIN_TOOL_MANIFESTS, SUPPORTED_TOOLS, LAUNCH_COHORT, TOOL_DISPLAY_NAMES,
|
||||
applyPromptArgTemplate,
|
||||
} from '../src/tool-detection.js';
|
||||
|
||||
describe('BUILTIN_TOOL_MANIFESTS', () => {
|
||||
it('has one manifest per supported tool, ids matching SUPPORTED_TOOLS', () => {
|
||||
expect(BUILTIN_TOOL_MANIFESTS.map((m) => m.id).sort()).toEqual([...SUPPORTED_TOOLS].sort());
|
||||
});
|
||||
it('marks every built-in as builtin:true and launchable', () => {
|
||||
for (const m of BUILTIN_TOOL_MANIFESTS) {
|
||||
expect(m.builtin).toBe(true);
|
||||
expect(m.launchable).toBe(true);
|
||||
}
|
||||
});
|
||||
it('every built-in is hook-capable', () => {
|
||||
expect(BUILTIN_TOOL_MANIFESTS.filter((m) => !m.hookCapable)).toEqual([]);
|
||||
});
|
||||
it('derives TOOL_DISPLAY_NAMES + LAUNCH_COHORT from the manifests (unchanged values)', () => {
|
||||
expect(TOOL_DISPLAY_NAMES['claude-code']).toBe('Claude Code');
|
||||
expect(TOOL_DISPLAY_NAMES['codex']).toBe('Codex CLI');
|
||||
expect([...LAUNCH_COHORT].sort()).toEqual([...SUPPORTED_TOOLS].sort());
|
||||
});
|
||||
it('claude-code detects by PATH binary "claude" (not its id)', () => {
|
||||
const cc = BUILTIN_TOOL_MANIFESTS.find((m) => m.id === 'claude-code')!;
|
||||
expect(cc.detect).toEqual({ kind: 'path', binaryName: 'claude' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('applyPromptArgTemplate (#5 fast-follow)', () => {
|
||||
it('substitutes {prompt} in each template entry', () => {
|
||||
expect(applyPromptArgTemplate(['--print', '{prompt}'], 'hello')).toEqual(['--print', 'hello']);
|
||||
});
|
||||
it('substitutes within an entry and across multiple entries', () => {
|
||||
expect(applyPromptArgTemplate(['-m', 'msg={prompt}', '{prompt}'], 'hi')).toEqual(['-m', 'msg=hi', 'hi']);
|
||||
});
|
||||
it('leaves entries without the placeholder untouched', () => {
|
||||
expect(applyPromptArgTemplate(['--yes', '--fast'], 'hi')).toEqual(['--yes', '--fast']);
|
||||
});
|
||||
});
|
||||
21
packages/shared/tsconfig.json
Normal file
21
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist", "tests"]
|
||||
}
|
||||
Reference in New Issue
Block a user