Skip to main content

ONE BINARY. ONE INTERFACE.

Identity, governance, deployment, jobs, logs, and MCP server lifecycle live behind one CLI. Agents call the same commands a human types. A profile switch sends the next command to a different environment. The audit trail does not care which caller it was.

Eight Domains, One Mental Model

Identity lives in one tool. Governance lives in another. Deployment is a script. Analytics is a dashboard. The MCP servers have a console of their own. Every operator action crosses tool boundaries, and nothing shares an audit trail. The cost is not the tools. The cost is the seams between them.

The systemprompt CLI collapses those seams into one binary with one grammar: systemprompt <domain> <command> [args]. Eight domains live behind it. core owns skills, content, files, contexts, plugins, and hooks. infra runs services, databases, jobs, and logs with request tracing. admin handles users, RBAC, agent orchestration, and runtime configuration. cloud covers OAuth login, profile switching, tenant provisioning, deployment, and secrets. analytics exposes conversations, agents, tools, requests, sessions, content, traffic, costs, and an overview. web manages content types, templates, assets, and sitemaps. plugins lists extensions, discovers MCP capabilities, and calls MCP tools directly. build compiles the Rust workspace and the MCP extension binaries.

Every level uses the same clap derive pattern, so --help works at every depth. The Cli struct in args.rs defines eight subcommand variants and nothing else; the eight domain modules under commands/ own their own subtrees. That is the whole top-level surface.

  • One Grammar at Every Depthsystemprompt <domain> <command> [args], all the way down. The Cli struct in args.rs has eight subcommand variants. There is no second pattern to learn.
  • Help Generated From the Type System — Every command, argument, and option is a clap derive. --help at any level renders from the same definitions the parser uses. The CLI describes itself to humans and to agents reading --json.
  • Typed Output for PipelinesCommandResult wraps every command in a typed envelope with artifact type and rendering hints. CI scripts and agents parse the same shape every command emits.

Agents Call the Same Interface as Humans

Most agent integrations end up as a parallel API. The agent gets a thin wrapper with fewer permissions, a separate audit log, and a different failure mode. Two months in, the wrapper has drifted from the real CLI, and the audit trail has two formats no compliance officer wants to reconcile.

Two MCP servers in this codebase expose the same operations a human would type. The systemprompt MCP server proxies CLI commands. The marketplace MCP server registers 24 tools across skills, agents, plugins, MCP servers, secrets, and analytics, each with typed input and output schemas. Both servers gate every tool call through enforce_rbac_from_registry in their handle_tool_call implementations. The middleware, the role model, and the audit record are the same for an agent caller and a human caller.

Tool discovery is runtime, not documentation. The marketplace server's list_tools() returns the registry built in tools/registry.rs; an agent reads it and learns what is callable on this deployment. Add a tool, rebuild, and the agent picks it up on the next list_tools call.

  • One RBAC Path for Both Callers — Both MCP servers route every tool call through enforce_rbac_from_registry before dispatch. Agent and human requests share one permission model and one audit record.
  • 24 Tools, Typed Schemas — Skills, agents, plugins, MCP servers, secrets, and analytics. Each tool ships JSON Schema for input and output. Agents bind to the schema, not to a hand-written guide.
  • Discovery Lives in the Serverlist_tools() returns whatever the registry holds at startup. Add a tool to tools/registry.rs, rebuild, and connected agents see it on their next discovery call.

Same Binary, Every Environment

Run a command against the wrong environment once and the lesson sticks. Most CLIs make that easy: a stale shell variable, a forgotten kubectl context, an SSH tunnel left open. The blast radius lives in operator memory, not in the tool.

The systemprompt CLI resolves a target through one explicit cascade. resolve_session in cli/src/session/resolution/mod.rs checks config.profile_override (the --profile flag) first, then SYSTEMPROMPT_PROFILE in the environment, then the active session in SessionStore, and only then the default ProfileBootstrap. There is one function and four cases in order. The order is in source.

A profile is a YAML file under .systemprompt/profiles/<name>/profile.yaml holding the database URL, server endpoints, secrets, and runtime settings for a target. Switching profiles changes the next command's destination. The audit record format does not change with the destination, so a query against local PostgreSQL and a query against production end up in the same shape on the way out.

  • One Function, Four Cases, In Orderresolve_session consults --profile, then SYSTEMPROMPT_PROFILE, then the active session in SessionStore, then ProfileBootstrap. The order is fixed in source, not in shell convention.
  • Profiles Are Files, Not Flags — Every target lives in .systemprompt/profiles/<name>/profile.yaml with its own database URL, endpoints, and secrets. Reviewing what production points at is reading one file.
  • OAuth, Not SSHcloud auth handles login and token refresh against the cloud control plane. No SSH tunnel, no bastion, no copy-paste of credentials into shell history.

Interchangeable Components

An extension is a Rust type that implements the Extension trait in shared/extension/src/traits.rs. The trait declares many methods (schemas, router, jobs, page data providers, page prerenderers, component renderers, site auth, roles, migrations, required assets, and more) and almost all of them ship with a default. An extension overrides only the methods it actually contributes. WebExtension overrides routes, jobs, providers, and assets. EmailExtension overrides far less. Both register the same way and are discovered the same way at startup.

Three tiers cover the integration patterns. Library extensions compile into the binary. WebExtension, MarketplaceExtension, and EmailExtension each call register_extension!(), which submits them to the inventory crate's linker-time collection. CLI extensions ship as separate binaries discovered through a manifest.yaml; the CLI invokes them as subprocesses. MCP extensions run as HTTP servers that implement ServerHandler.

Registration requires no central list. src/lib.rs defines __force_extension_link(), which uses core::hint::black_box to keep the linker from stripping the inventory submissions. Add an extension to the workspace, call the macro in its crate, rebuild. Remove it and rebuild. There is no registry file to edit. build.rs parses every YAML under services/ at compile time, so a malformed config never reaches a running binary.

  • One Trait, Many DefaultsExtension declares the full surface and ships defaults for almost every method. An extension overrides only the methods that pull weight.
  • Three Tiers, One Discovery Path — Library (compiled in via register_extension!), CLI (subprocess via manifest.yaml), MCP (HTTP via ServerHandler). The host loads each tier without manual wiring.
  • Linker-Time Registrationinventory collects extensions through the linker. __force_extension_link blocks stripping. Adding an extension is adding a crate, not editing a registry.

Configuration as Code, Deployed Instantly

The operator problem with most stacks is not configuration. It is the gap between configuration and effect. A YAML file lives in one repo, a deployment lives in another, a job runner is somewhere else, and the audit log of who flipped what lives in a fourth place. Reconciling them is the work.

In this codebase, declarative state lives under services/. Agents in services/agents/, MCP servers in services/mcp/, skills in services/skills/, web navigation and homepage and features in services/web/config/. build.rs parses every file at compile time, so a malformed YAML never reaches a running binary. systemprompt core skills sync, systemprompt core content publish, and systemprompt admin agents sync apply changes against the active profile, idempotently.

Background work is part of the same binary, not a separate runner. Each extension contributes a list of jobs from its fn jobs() implementation. The web extension returns fifteen jobs covering content ingestion, CSS bundling, asset copying, prerendering, sitemap, llms.txt, robots.txt, redirects, the publish pipeline, analytics aggregation, daily traffic and activity reports, retention cleanup, the Slack gateway, and admin traffic reports. The marketplace extension returns nine more covering admin CSS bundling, gamification, secret migration, Paddle plan seeding, Anthropic plugin imports, usage retention, daily summaries, achievement emails, and session analysis backfill. Each job implements the Job trait with a name and a cron schedule and runs on the same audit trail as a CLI command.

  • YAML In, Live Changes Out — Edit a file under services/, run the matching sync command. build.rs validates every YAML at compile time, so a broken file never ships.
  • Jobs Live in the BinaryWebExtension::jobs returns 15 jobs. MarketplaceExtension::jobs returns 9. Each implements Job with a cron schedule and runs in-process. No sidecar runner.
  • Logs and Tracing in the Same CLIinfra logs covers stream, list, search, summary, export, and full audit reconstruction. systemprompt infra logs audit <id> --full rebuilds an AI request from one terminal.

Founder-led. Self-service first.

No sales team. No demo theatre. The template is free to evaluate — if it solves your problem, we talk.

Who we are

One founder, one binary, full IP ownership. Every line of Rust, every governance rule, every MCP integration — written in-house. Two years of building AI governance infrastructure from first principles. No venture capital dictating roadmap. No advisory board approving features.

How to engage

One binary. One CLI. Eight domains.

Clone the template. Build the binary. Run identity, governance, jobs, deployment, and MCP lifecycle from one terminal.