name: mg-cli
description: >
Generate YAML configuration files and run CLI commands to onboard organizations into ModelGuide.
Use this skill when the user asks to set up an org, create agents, import SOPs, add connectors,
prepare onboarding YAML, provision a customer, seed demo data, or anything related to the mg CLI tool.
Also trigger when the user mentions "mg setup", "mg import", "mg add", "onboard", "provision org",
"create YAML for CLI", "prepare config files", or asks how to get a new organization running in ModelGuide.
ModelGuide CLI Onboarding Tool
The mg CLI is a thin orchestration layer over ModelGuide's service layer. It reads YAML files, validates them with Zod, and calls existing @features/* services in dependency order. All business logic lives in the services — the CLI handles parsing, validation, orchestration, and output.
When to Use This Skill
- User wants to onboard a new organization (customer, demo, test)
- User wants to prepare YAML config files for the CLI
- User wants to run individual CLI commands or the full
mg setuppipeline - User wants to import SOPs, guardrails, evals, sessions, or other entities
- User asks about available connectors, SOP templates, or schema fields
Workflow Overview
There are two ways to use the CLI:
Option A: Full Setup (recommended for new orgs)
Create a directory with YAML files anywhere on disk, then run one command:
cd modelguide-api
bun run src/cli/mg.ts setup /path/to/my-org/ # provision everything
bun run src/cli/mg.ts setup /path/to/my-org/ --dry-run # validate and preview without changes
The directory can live anywhere — it does not need to be inside the modelguide repo. Pass an absolute or relative path.
Required file: org.yaml
Optional files: users.yaml, secrets.yaml, connectors.yaml, agents.yaml, sops.yaml, guardrails.yaml, evals.yaml (or evals-*.yaml for multi-agent orgs), sessions.yaml
Flags:
--dry-run— validate all YAML files against schemas and print the plan without touching the database. Use this to verify files are correct before running for real.--skip-secrets— use placeholder values (useful for CI/testing)--skip-compile— skip agent compilation step--skip-evals— skip eval import--skip-sessions— skip demo session import
Option B: Individual Commands
Run each step separately (useful for adding to an existing org):
cd modelguide-api
bun run src/cli/mg.ts create-org --from /path/to/org.yaml
bun run src/cli/mg.ts add-users --org acme --from /path/to/users.yaml
bun run src/cli/mg.ts add-secrets --org acme --from /path/to/secrets.yaml
bun run src/cli/mg.ts add-connectors --org acme --from /path/to/connectors.yaml
bun run src/cli/mg.ts add-agents --org acme --from /path/to/agents.yaml
bun run src/cli/mg.ts import-sops --org acme /path/to/sops.yaml
bun run src/cli/mg.ts import-guardrails --org acme /path/to/guardrails.yaml
bun run src/cli/mg.ts import-evals --org acme /path/to/evals.yaml
bun run src/cli/mg.ts compile-agents --org acme
bun run src/cli/mg.ts import-sessions --org acme /path/to/sessions.yaml
Pipeline Dependency Order
The order matters because later steps reference entities created earlier:
1. org.yaml — organization (everything scoped to this)
2. users.yaml — users (agents need a createdBy user)
3. secrets.yaml — standalone secrets (connectors may reference these)
4. connectors.yaml — connectors + connector-scoped secrets
5. agents.yaml — agents + tool assignments (references connectors)
6. sops.yaml — SOPs (references agents + connector tools)
7. guardrails.yaml — guardrails (references agents)
8. evals.yaml — eval suites, evaluators, test cases (references agents + SOPs)
9. compile-agents — compiles each agent against its active SOPs (skipped with --skip-compile)
10. sessions.yaml — demo sessions (references agents)
The mg setup command handles this order automatically and threads an IdRegistry (slug-to-UUID map) across all steps so cross-references resolve without extra DB queries.
Idempotency
Re-running is safe:
- Orgs: upsert on slug (updates settings if exists, warns)
- Users/Agents/Connectors/SOPs/Guardrails: duplicate errors are caught and counted as "existing"
- Evals: suites deduped by (agent, SOP) pair; test cases by
externalIdin JSONB; eval configs by name - Sessions: deduped by
externalId(explicit or derived from payload hash) - Secrets: append-only (no stable dedup key — use
--skip-secretson re-runs)
Quick Schema Reference
Each YAML file has a specific structure. For the complete field-by-field reference with types, defaults, constraints, and edge cases, read references/schemas.md.
org.yaml
name: "Acme Corp"
slug: "acme" # lowercase + hyphens only
timezone: "America/Chicago" # optional
features: [voice-agents] # optional
demoEnabled: false # optional, default false
users.yaml
users:
- email: admin@acme.example.com
name: "Alice Admin"
role: admin # admin | support
secrets.yaml
secrets:
- name: OpenAI API Key
type: platform_api_key # api_key | oauth_token | credentials | platform_api_key | webhook_secret
scope: agent # connector | agent (optional)
# value: omitted = prompted interactively (or placeholder with --skip-secrets)
connectors.yaml
# Real connector — references a registered TypeScript manifest
connectors:
- name: "Acme Store"
slug: "acme_store" # lowercase + underscores
catalogSlug: "medusa" # must match a catalog entry — see references/catalog.md
config:
baseUrl: "https://api.acme.example.com"
secrets: # connector-scoped secrets created automatically
- field: "secretApiKey" # field name in connector config
name: "Acme Store API Key"
type: api_key
# Mocked connector — DB-driven fixtures, no TypeScript handler (ADR-013)
- name: "Bank Nowa Banking (Mock)"
slug: "banknowa_banking"
isMocked: true # switches schema branch
iconUrl: "/logos/bank-nowa.svg" # optional
tools: # inline tool defs — each returns `mock_response` verbatim
- name: "Verify Customer"
description: "Verify identity."
input_schema:
type: object
properties: { name: {type: string} }
required: [name]
mock_response:
success: true
customer_id: "CUST-001"
Edit mock_response in YAML and re-run mg add-connectors — existing tool rows are reconciled (no delete-then-reimport needed). See references/schemas.md for full field tables.
agents.yaml
agents:
- name: "Acme Voice Agent"
slug: "acme-voice-agent"
description: "Handles phone orders"
modality: voice # voice | text (default: voice)
platform: custom # custom | elevenlabs | livekit (default: custom)
tools:
- connectorSlug: "acme_store" # all tools from this connector
- connectorSlug: "acme_support"
toolSlugs: [create_ticket, get_ticket] # specific tools only
For platform: livekit (voice-test + outbound dispatch require this):
agents:
- name: "Acme Voice Agent"
slug: "acme-voice-agent"
modality: voice
platform: livekit
config:
# Only url + agentName are valid for livekit. llmModel is rejected
# (baked into the worker image).
url: "wss://your-project.livekit.cloud"
agentName: "acme_voice_agent" # must match the profile key in the worker's config/agents.yaml
tools:
- connectorSlug: "acme_store"
secrets:
# No `value:` → `mg setup` prompts once per field. These exact field
# names are read by agents.service.ts:getAgentSecretByType when
# dispatching the worker.
- field: livekit_api_key
name: "LiveKit API Key"
type: api_key
- field: livekit_api_secret
name: "LiveKit API Secret"
type: api_key
sops.yaml — two modes
Inline SOP (define steps directly):
sops:
- name: "Order Lookup"
slug: "order-lookup"
status: active # draft | active | archived (default: draft)
agents: ["acme-voice-agent"]
trigger:
type: intent_detected # see references/schemas.md for all trigger types
config:
patterns: ["where is my order", "track my order", "order status"]
steps:
- id: greet
instruction: "Greet and ask for order number"
required: true
- id: lookup
instruction: "Look up the order"
required: true
tool:
connectorSlug: "acme_store"
toolSlug: "get_order"
Template fork (fork from a global SOP template):
sops:
- name: "Order Lookup"
templateSlug: "order-lookup" # must match a template — see references/catalog.md
status: active
agents: ["acme-voice-agent"]
connectorMapping:
medusa: "acme_store" # maps template's catalog refs to org's connector slugs
Cannot specify both templateSlug and steps.
guardrails.yaml
guardrails:
- name: "No Medical Claims"
slug: "no-medical-claims"
content: |
Never claim any product treats, cures, or prevents a medical condition.
description: "FDA compliance"
config: { priority: critical, category: compliance }
agents: ["acme-voice-agent", "acme-chat-assistant"]
evals.yaml
One file per agent. For multi-agent orgs, use multiple files: evals-insurance.yaml, evals-booking.yaml, etc. The mg setup pipeline globs for evals*.yaml.
agentSlug: acme-voice-agent
evaluators:
- name: confirms-order-id
criterion: Agent confirms the order ID back to the customer
tags: [accuracy] # optional
- name: does-not-fabricate
criterion: Agent does NOT make up order details or tracking information
tags: [compliance, accuracy] # optional
test_cases:
- id: order-lookup-happy-path-01
sop_slug: order-lookup
scenario_key: order_status # optional
tags: [order-lookup, happy-path] # optional
evaluators: # references by name
- confirms-order-id
- does-not-fabricate
input:
customer_message: Hi, I placed an order last week, number ACM-12345.
conversation_history:
- role: assistant
content: Thanks for calling Acme Corp. How can I help you today?
Also supports standalone import via JSON (eval-scenarios.json) with --agent flag:
bun run src/cli/mg.ts import-evals --org acme --agent acme-voice-agent /path/to/eval-scenarios.json
sessions.yaml
sessions:
- agentSlug: "acme-voice-agent"
channel: voice # voice | web | api | slack | widget | sms | whatsapp | email
status: completed # active | completed | abandoned (default: completed)
userIdentifier: "sarah@example.com"
hoursAgo: 2 # how far back to timestamp messages (default: 1)
messages:
- role: user
content: "Hi, I want to check on my order ORD-1234."
- role: assistant
content: "Let me look that up for you."
feedback: # optional
verdict: good # good | bad
comment: "Very helpful"
source: customer # customer | support | system
links: # optional
- url: "https://store.acme.com/orders/1234"
title: "Order ORD-1234"
resourceType: "order"
How to Prepare Files for a New Organization
When the user describes their organization, follow this process:
- Gather requirements: What connectors do they need? What agents? What workflows (SOPs)?
- Create the directory: anywhere the user wants (e.g.,
~/onboarding/my-customer/) - Write files in dependency order: org → users → secrets → connectors → agents → sops → guardrails → evals → sessions
- Validate with dry-run:
cd modelguide-api && bun run src/cli/mg.ts setup /path/to/dir --dry-run - Run the import: add
--skip-secretsfor testing, or run without flags for production
For the full schema reference with every field, type, and constraint, read references/schemas.md.
For available connector catalog entries and SOP templates, read references/catalog.md.
For a complete working example (Acme Corp), read references/examples.md.
Running the CLI
All commands must be run from the modelguide-api/ directory:
cd modelguide-api
bun run src/cli/mg.ts <command> [options]
Running against Railway (from your local machine):
cd modelguide-api
railway run --service api -- sh -c \
'DATABASE_URL=postgresql://modelguide_app:$APP_DB_PASSWORD@$POSTGRES_TCP_PROXY_DOMAIN:$POSTGRES_TCP_PROXY_PORT/$PGDATABASE \
bun run src/cli/mg.ts setup /path/to/my-org/ --skip-secrets'
railway run injects all env vars (secrets, encryption keys, etc.). DATABASE_URL is overridden with the public TCP proxy since the private hostname isn't reachable locally. Requires TCP proxy vars from railway/DEPLOY.md step 6.
Common Patterns
Add a single agent to an existing org:
bun run src/cli/mg.ts add-agents --org acme name="New Agent" slug=new-agent modality=voice
Compile only one agent:
bun run src/cli/mg.ts compile-agents --org acme --agent acme-voice-agent
Import SOPs without activating (review first):
Set status: draft in sops.yaml, import, review in dashboard, then activate manually.
Re-run after fixing a YAML error: Safe to re-run — duplicates are skipped. Only new entities get created.