diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 0000000..bcc67c6
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
+ "name": "carta-plugins",
+ "description": "Official Carta plugins for Claude Code",
+ "owner": {
+ "name": "Carta Engineering",
+ "email": "engineering@carta.com"
+ },
+ "plugins": [
+ {
+ "name": "carta-cap-table",
+ "source": "./plugins/carta-cap-table",
+ "description": "Carta Cap Table plugin — skills and hooks for querying cap tables, grants, SAFEs, 409A valuations, waterfall scenarios, and more",
+ "category": "productivity"
+ }
+ ]
+}
diff --git a/plugins/carta-cap-table/.claude-plugin/plugin.json b/plugins/carta-cap-table/.claude-plugin/plugin.json
new file mode 100644
index 0000000..e550f3f
--- /dev/null
+++ b/plugins/carta-cap-table/.claude-plugin/plugin.json
@@ -0,0 +1,25 @@
+{
+ "name": "carta-cap-table",
+ "displayName": "Carta Cap Table",
+ "version": "0.6.1",
+ "description": "Carta Cap Table plugin — skills and hooks for querying cap tables, grants, SAFEs, 409A valuations, waterfall scenarios, and more",
+ "author": {
+ "name": "Carta Engineering",
+ "email": "engineering@carta.com"
+ },
+ "homepage": "https://github.com/carta/plugins",
+ "repository": "https://github.com/carta/plugins",
+ "keywords": [
+ "cap_table",
+ "mcp",
+ "carta",
+ "equity",
+ "grants"
+ ],
+ "mcpServers": {
+ "carta": {
+ "type": "http",
+ "url": "https://mcp.app.carta.com/mcp"
+ }
+ }
+}
diff --git a/plugins/carta-cap-table/README.md b/plugins/carta-cap-table/README.md
new file mode 100644
index 0000000..b30319c
--- /dev/null
+++ b/plugins/carta-cap-table/README.md
@@ -0,0 +1,53 @@
+# Carta Cap Table
+
+Claude Code plugin that gives Claude access to Carta cap table data.
+
+## How it works
+
+This plugin provides **skills** that teach Claude how to use the Carta MCP server effectively — querying cap tables, modeling rounds, detecting portfolio alerts, and more.
+
+The MCP server handles authentication via OAuth:
+1. On first use, Claude opens a browser to the Carta login page
+2. You log in with your Carta credentials
+3. Carta issues an access token that Claude uses for all subsequent requests
+
+## Installation
+
+Install from the `carta/plugins` marketplace:
+
+```
+/plugin install carta-cap-table@carta-plugins
+```
+
+This registers the Carta MCP server (`https://mcp.app.carta.com/mcp`) and loads all skills automatically.
+
+After installing, restart Claude Code and run `/mcp` to complete OAuth authentication.
+
+## Try it out
+
+- "What Carta cap table data do I have access to?"
+- "Show me the ownership breakdown for [company]"
+- "Who are the stakeholders in [company]?"
+- "What SAFEs are outstanding for [company]?"
+- "When does the 409A expire for [company]?"
+- "Model a Series B at $80M pre-money with a $20M raise"
+
+## Skills
+
+| Skill | Description |
+|-------|-------------|
+| `client-triggers` | Surface time-based BD triggers across the portfolio. Use when asked about client outreach, which clients closed a round recently, stale cap tables, pending grants, tombstones, weekly deals, or BD triggers. |
+| `conversion-calculator` | Calculate SAFE and convertible note conversion into equity. Use when asked about SAFE conversion, note conversion, conversion shares, or how instruments convert in a round. |
+| `discover-commands` | Find the right carta-cap-table command when no other skill matches. Use when unsure which command to call, exploring available data, or when the user's request doesn't match a specific skill. |
+| `grant-vesting` | Fetch vesting schedule for a specific option grant. Use when asked about vesting details, cliff dates, vesting progress, or unvested shares for a particular grant. |
+| `list-convertible-notes` | Fetch all convertible instruments (SAFEs and convertible debt) for a company. Use when asked about convertible notes, SAFEs, convertible debt, note terms, caps, discounts, or maturity dates. |
+| `list-safes` | Fetch all SAFEs for a company. Use when asked about SAFEs, simple agreements for future equity, SAFE terms, valuation caps, or discounts. |
+| `market-benchmarks` | Analyze cap structure patterns across the portfolio as market benchmarks. Use when asked about market benchmarks, typical option pool sizes, average SAFE terms, what's normal for a Series A, cap structure patterns, or portfolio-wide statistics. |
+| `ownership` | Ownership structure by share class, voting rights, and liquidation seniority. Use when asked about ownership breakdown, preferred vs common holders, voting power, protective provisions, or consent requirements. |
+| `portfolio-alerts` | Detect red flags and time-sensitive issues across portfolio companies. Use when asked to flag problems, find expiring items, or audit portfolio health. |
+| `portfolio-query` | Query cap table data for one or more companies. Use when asked about cap tables, ownership breakdown, share classes, stakeholder holdings, portfolio-wide analysis, comparing companies, or finding patterns across multiple entities. |
+| `pro-forma-model` | Model a pro-forma financing round to show dilution impact. Use when asked to model a Series A/B/C, new round, or show how a round would affect ownership. |
+| `round-history` | Fetch financing round history for a company. Use when asked about funding rounds, capital raised, or financing history. |
+| `stakeholders` | List stakeholders for a company. Use when asked who the stakeholders are, stakeholder list, shareholders, investors, or holders. |
+| `valuation-history` | Fetch 409A valuation history for a company. Use when asked about 409A valuations, FMV, exercise prices, or valuation expiration dates. |
+| `waterfall-scenarios` | Fetch saved waterfall / exit scenario models for a company. Use when asked about liquidation preferences, exit payouts, return multiples, or waterfall analysis. |
diff --git a/plugins/carta-cap-table/hooks/hooks.json b/plugins/carta-cap-table/hooks/hooks.json
new file mode 100644
index 0000000..6ac68ea
--- /dev/null
+++ b/plugins/carta-cap-table/hooks/hooks.json
@@ -0,0 +1,27 @@
+{
+ "hooks": {
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/inject-skill-context.js",
+ "timeout": 5
+ }
+ ]
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "mcp__carta.*__fetch",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/hooks/warn-empty-fetch.js",
+ "timeout": 5
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/plugins/carta-cap-table/scripts/hooks/inject-skill-context.js b/plugins/carta-cap-table/scripts/hooks/inject-skill-context.js
new file mode 100644
index 0000000..85b3690
--- /dev/null
+++ b/plugins/carta-cap-table/scripts/hooks/inject-skill-context.js
@@ -0,0 +1,29 @@
+#!/usr/bin/env node
+/**
+ * SessionStart hook: inject skill-first reminder into every session.
+ *
+ * Ensures Claude loads the relevant carta-cap-table skill before making
+ * any tool calls, even in subagents that don't inherit session context.
+ */
+
+let inputData = '';
+process.stdin.on('data', chunk => (inputData += chunk));
+
+process.stdin.on('end', () => {
+ let hookEventName = 'SessionStart';
+ try {
+ const input = JSON.parse(inputData);
+ hookEventName = input.hook_event_name || hookEventName;
+ } catch {}
+
+ const output = {
+ hookSpecificOutput: {
+ hookEventName,
+ additionalContext:
+ 'You have carta-cap-table tools available. Before ANY tool call, invoke the matching Skill(\'carta-cap-table:...\') first. The skill defines what to fetch, what inputs are required, and how to present results. If no skill matches, invoke Skill(\'carta-cap-table:discover-commands\') to find the right command via discover(). IMPORTANT: Skill is a deferred tool — if its schema is not yet loaded, you MUST call ToolSearch with query "select:Skill" first, then invoke the Skill tool.',
+ },
+ };
+
+ process.stdout.write(JSON.stringify(output));
+ process.exit(0);
+});
diff --git a/plugins/carta-cap-table/scripts/hooks/warn-empty-fetch.js b/plugins/carta-cap-table/scripts/hooks/warn-empty-fetch.js
new file mode 100755
index 0000000..6416d37
--- /dev/null
+++ b/plugins/carta-cap-table/scripts/hooks/warn-empty-fetch.js
@@ -0,0 +1,51 @@
+#!/usr/bin/env node
+/**
+ * PostToolUse Hook: Warn on empty fetch() responses
+ *
+ * When carta-cap-table's fetch() returns a _warning (empty data),
+ * outputs a reminder to stderr (exit 2) which gets fed to Claude.
+ */
+
+let inputData = '';
+process.stdin.on('data', chunk => (inputData += chunk));
+
+process.stdin.on('end', () => {
+ try {
+ const input = JSON.parse(inputData);
+ const { tool_input, tool_response } = input;
+
+ // Extract the result string from the MCP response
+ let resultStr = tool_response?.result || tool_response;
+ if (Array.isArray(resultStr) && resultStr[0]?.type === 'text') {
+ resultStr = resultStr[0].text;
+ } else if (resultStr?.content && Array.isArray(resultStr.content)) {
+ resultStr = resultStr.content[0]?.text || resultStr;
+ }
+
+ // Parse JSON and check for _warning
+ let parsed;
+ try {
+ parsed = typeof resultStr === 'string' ? JSON.parse(resultStr) : resultStr;
+ } catch {
+ process.exit(0);
+ return;
+ }
+
+ if (parsed && parsed._warning) {
+ const command = tool_input?.command || 'unknown command';
+ // Exit 2 = stderr fed back to Claude
+ process.stderr.write(
+ `⚠️ EMPTY DATA: ${command} returned no results. ` +
+ `Per the golden rule, any values you derive from this gap are BEST EFFORT. ` +
+ `Tell the user what's missing and ask before computing substitutes.`
+ );
+ process.exit(2);
+ return;
+ }
+
+ process.exit(0);
+ } catch (err) {
+ // Never block on hook errors
+ process.exit(0);
+ }
+});
diff --git a/plugins/carta-cap-table/skills/client-triggers/SKILL.md b/plugins/carta-cap-table/skills/client-triggers/SKILL.md
new file mode 100644
index 0000000..16138ac
--- /dev/null
+++ b/plugins/carta-cap-table/skills/client-triggers/SKILL.md
@@ -0,0 +1,106 @@
+---
+name: client-triggers
+description: Surface time-based BD triggers across the portfolio. Use when asked about client outreach, which clients closed a round recently, stale cap tables, pending grants, tombstones, weekly deals, or BD triggers.
+---
+
+# Client Triggers
+
+Scan the portfolio for actionable outreach triggers: recent round closes, expiring 409As, and companies with pending grants but no current valuation.
+
+## Prerequisites
+
+No inputs required — this skill loops the full portfolio automatically. Cap at 20 companies.
+
+## Commands
+
+- `list_accounts` — get all portfolio companies
+- `fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})` — financing history per company
+- `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` — 409A status (optional)
+
+## Key Fields
+
+From financing history:
+- `issue_date`: date of issuance
+- `round_name`: name of the round (e.g. "Series A")
+- `is_grant`: true if this is a grant issuance (not a priced round)
+
+From 409A FMVs:
+- `expiration_date`: when the valuation expires
+- `price`: FMV per share
+
+## How to Present
+
+See Step 4 below. Group by trigger type. Omit sections with no results.
+
+## Step 1 — Get Portfolio
+
+Call `list_accounts`. Filter to accounts where `id` starts with `corporation_pk:`. Extract the numeric corporation IDs (up to 20).
+
+## Step 2 — Collect Data Per Company
+
+For each company, fetch in sequence:
+
+**Financing history:**
+- `fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})`
+- Key fields: `issue_date`, `round_name`, `is_grant`
+- Group by `round_name`, find max `issue_date` per round → "last round date"
+
+**409A status** (optional, only if checking valuation triggers):
+- `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})`
+- Key fields: `expiration_date`, `price` (FMV per share)
+- Find the most recent valuation (sort by `effective_date` desc)
+
+## Step 3 — Classify Triggers
+
+### Recent Closes
+Companies whose last round closed within N days (default 90, or user-specified):
+- Trigger: `last_round_date ≥ today - N days`
+- Action: Congratulations / cap table review outreach
+
+### Stale 409A
+Companies with an expired or near-expiry 409A (within 60 days):
+- Trigger: `expiration_date ≤ today + 60 days` or no 409A on file
+- Action: Renewal reminder outreach
+
+### Pending Grants (no current 409A)
+Companies that issued grants recently (within 90 days) but have no valid 409A:
+- Trigger: recent `is_grant=true` entry + no active 409A
+- Action: Urgency outreach — grants issued without a current FMV create 409A exposure
+
+## Step 4 — Present Results
+
+Group output by trigger type. Example:
+
+---
+
+**Recent Closes (last 90 days)**
+
+| Company | Round | Close Date | Days Ago |
+|---------|-------|------------|----------|
+| Acme Inc | Series B | 2026-01-15 | 63 days |
+
+**Stale or Expiring 409As**
+
+| Company | Last 409A | Expiration | Status |
+|---------|-----------|------------|--------|
+| Beta Corp | $2.50 | 2026-04-01 | Expiring soon |
+| Gamma LLC | — | — | No 409A on file |
+
+**Grants Issued Without Current 409A**
+
+| Company | Last Grant Date | 409A Status |
+|---------|----------------|-------------|
+| Delta Co | 2026-02-20 | Expired |
+
+---
+
+If no triggers found in a category, omit that section.
+
+## Parameters
+
+If the user specifies a time window (e.g., "last 60 days", "last 6 months"), use that instead of the default 90 days.
+
+## Best Effort
+
+- **Computed:** trigger classification (recent close / stale 409A / pending grants) and time-window detection are heuristic
+- **Authoritative:** round close dates, 409A expiration dates, and grant issuance dates come directly from Carta
diff --git a/plugins/carta-cap-table/skills/conversion-calculator/SKILL.md b/plugins/carta-cap-table/skills/conversion-calculator/SKILL.md
new file mode 100644
index 0000000..8e71bc1
--- /dev/null
+++ b/plugins/carta-cap-table/skills/conversion-calculator/SKILL.md
@@ -0,0 +1,124 @@
+---
+name: conversion-calculator
+description: Calculate SAFE and convertible note conversion into equity. Use when asked about SAFE conversion, note conversion, conversion shares, or how instruments convert in a round.
+---
+
+# Conversion Calculator
+
+Calculate how SAFEs and convertible notes convert into equity at a given round price or valuation.
+
+## When to Use
+
+- "How many shares would the SAFEs convert into at a $50M pre?"
+- "Calculate SAFE conversions for the Series A"
+- "What happens to the convertible notes if we raise at $10/share?"
+- "Show me the conversion math for all outstanding instruments"
+
+## Prerequisites
+
+You need:
+1. `corporation_id` — get from `list_accounts`
+2. Round terms — user must provide at least a **pre-money valuation** or **price per share**
+
+If neither is provided, ask before proceeding:
+> "What pre-money valuation or price per share should I use for the conversion calculation?"
+
+**Subagent prohibition:** Do NOT delegate this skill to a background agent if the round valuation or price per share is missing. A subagent cannot ask the user for input. If these are absent and you are considering dispatching an agent, stop — ask the user directly first, then proceed.
+
+## Data Retrieval
+
+- `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` — SAFEs + convertible notes
+- `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})` — current fully diluted count
+- `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` — current FMV (for reference)
+
+## Key Fields
+
+From convertible instruments:
+- `is_debt`: false = SAFE, true = convertible note
+- `dollar_amount`: principal investment amount
+- `price_cap`: valuation cap
+- `discount_percent`: discount rate (e.g. `"20.00"` = 20%)
+- `interest_rate`: annual interest rate (notes only)
+- `total_with_interest`: principal + accrued interest (use this for note conversions)
+- `has_most_favored_nation_clause`: MFN SAFE — converts at best subsequent terms
+- `status_explanation`: filter to "Outstanding" only
+
+## Step 1: Gather Instrument Data
+
+1. `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` — SAFEs + notes (filter to `status_explanation: "Outstanding"`)
+2. `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})` — get current fully diluted share count from `totals.total_fully_diluted`
+3. `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` — current FMV for context
+
+## Step 2: Conversion Math
+
+### SAFE Conversion
+
+For each SAFE, compute shares under both methods, use the one giving MORE shares:
+
+**Cap conversion:**
+```
+cap_price = valuation_cap / pre_money_fully_diluted_shares
+shares = investment_amount / cap_price
+```
+
+**Discount conversion:**
+```
+discount_price = round_price_per_share * (1 - discount_rate)
+shares = investment_amount / discount_price
+```
+
+**No cap, no discount (MFN):**
+- Converts at the round price (or the best terms of a subsequent SAFE)
+```
+shares = investment_amount / round_price_per_share
+```
+
+### Convertible Note Conversion
+
+Same as SAFE but:
+- Use `total_with_interest` (principal + accrued interest) instead of investment amount
+- If `interest_rate` and `maturity_date` are available and `total_with_interest` is not, calculate:
+ ```
+ years = (conversion_date - issue_date) / 365
+ total = principal * (1 + interest_rate * years)
+ ```
+
+## Step 3: Present Results
+
+### Per-Instrument Table
+
+| Instrument | Investor | Amount | Accrued Interest | Total | Cap | Discount | Method Used | Price/Share | Shares |
+|-----------|----------|--------|-----------------|-------|-----|----------|-------------|-------------|--------|
+| SAFE-1 | Investor A | $500,000 | — | $500,000 | $6M | 20% | Cap | $0.60 | 833,333 |
+| SAFE-2 | Investor B | $250,000 | — | $250,000 | $8M | — | Cap | $0.80 | 312,500 |
+| CN-1 | Investor C | $500,000 | $240,164 | $740,164 | $8M | 20% | Discount | $0.88 | 840,914 |
+
+### Summary
+
+```
+Round price per share: $1.10
+Pre-money fully diluted: 10,000,000 shares
+
+SAFE conversions: 1,145,833 shares ($750,000 invested)
+ - Effective avg price: $0.65/share (41% discount to round price)
+
+Note conversions: 840,914 shares ($500,000 principal + $240,164 interest)
+ - Effective avg price: $0.88/share (20% discount to round price)
+
+Total conversion shares: 1,986,747
+Post-conversion fully diluted: 11,986,747
+```
+
+## Important Notes
+
+- Always show which conversion method (cap vs discount) was more favorable and used
+- If a SAFE has both cap and discount, compute both and pick the one yielding more shares
+- For MFN SAFEs, note that they take the best terms of any subsequent SAFE
+- Accrued interest on notes can significantly increase the conversion amount — always account for it
+- State the assumed conversion date (today or the round close date)
+- This is an estimate — actual conversion depends on legal documents. Recommend review by counsel.
+
+## Best Effort
+
+- **Computed:** SAFE/note conversion share counts, cap-vs-discount method selection, effective price per share — these are estimates derived from instrument terms and user-supplied round price
+- **Authoritative:** instrument terms (cap, discount, principal, accrued interest) come directly from Carta
diff --git a/plugins/carta-cap-table/skills/discover-commands/SKILL.md b/plugins/carta-cap-table/skills/discover-commands/SKILL.md
new file mode 100644
index 0000000..d863013
--- /dev/null
+++ b/plugins/carta-cap-table/skills/discover-commands/SKILL.md
@@ -0,0 +1,38 @@
+---
+name: discover-commands
+description: Find the right carta-cap-table command when no other skill matches. Use when unsure which command to call, exploring available data, or when the user's request doesn't match a specific skill.
+---
+
+# Discover Commands
+
+Use the `discover()` tool to find available commands when no specific skill covers the user's request.
+
+## When to Use
+
+- No other carta-cap-table skill matches the user's request
+- User asks "what can you do?" or "what data is available?"
+- You're unsure which command to call
+
+## Step 1 — Search for Relevant Commands
+
+```
+discover(search="")
+```
+
+Use a keyword that captures the user's intent (e.g. "valuation", "grant", "safe", "stakeholder").
+
+## Step 2 — Pick the Best Match
+
+Review the returned commands. Each has:
+- `command`: the name to pass to `fetch()`
+- `description`: what it returns
+- `required_params`: what you need to provide
+- `help`: detailed field descriptions and caveats
+
+## Step 3 — Execute
+
+```
+fetch("", { ...params })
+```
+
+You still need `corporation_id` for most commands — get it from `list_accounts` if you don't have it.
diff --git a/plugins/carta-cap-table/skills/grant-vesting/SKILL.md b/plugins/carta-cap-table/skills/grant-vesting/SKILL.md
new file mode 100644
index 0000000..c4de369
--- /dev/null
+++ b/plugins/carta-cap-table/skills/grant-vesting/SKILL.md
@@ -0,0 +1,36 @@
+---
+name: grant-vesting
+description: Fetch vesting schedule for a specific option grant. Use when asked about vesting details, cliff dates, vesting progress, or unvested shares for a particular grant.
+---
+
+# Grant Vesting Data
+
+Fetch the full vesting schedule for an option grant and present it with useful context, not just the raw table.
+
+## Prerequisites
+
+You need:
+1. `corporation_id` — get from `list_accounts` if you don't have it
+2. `grant_id` — identify via the grants list using the holder's name or grant number
+
+## Workflow
+
+1. **Identify the grant**: Call `fetch("cap_table:list:grants", {"corporation_id": corporation_id, "search": ""})`. If multiple grants are returned, ask the user which one, or pick the most relevant based on context.
+2. **Fetch vesting data**: Call `fetch("cap_table:get:grant_vesting", {"corporation_id": corporation_id, "grant_id": grant_id})`.
+3. **Present with context** — see below.
+
+## How to Present
+
+The tool returns a formatted summary and vesting events table. Lead with a one-sentence plain-English summary before showing the table:
+
+- **Pre-cliff**: how long until the cliff, how many shares vest at cliff
+- **Partially vested**: what % has vested, what the ongoing cadence is (monthly/quarterly), when fully vested
+- **Fully vested**: confirm and note if any shares remain unexercised
+
+Flag anything time-sensitive:
+- Cliff date within the next 90 days
+- Grant expiring soon
+- Large unvested block concentrated at a future date
+- **Deep in-the-money grants**: if the current 409A FMV is available and the spread between exercise price and FMV exceeds 10×, flag it. Note that holders face significant ordinary income (NSO) or AMT (ISO) exposure at exercise, and recommend the company consider a tender offer, early exercise program, or liquidity event planning.
+
+Then show the formatted table from the tool.
diff --git a/plugins/carta-cap-table/skills/list-convertible-notes/SKILL.md b/plugins/carta-cap-table/skills/list-convertible-notes/SKILL.md
new file mode 100644
index 0000000..f50c6a9
--- /dev/null
+++ b/plugins/carta-cap-table/skills/list-convertible-notes/SKILL.md
@@ -0,0 +1,58 @@
+---
+name: list-convertible-notes
+description: Fetch all convertible instruments (SAFEs and convertible debt) for a company. Use when asked about convertible notes, SAFEs, convertible debt, note terms, caps, discounts, or maturity dates.
+---
+
+# List Convertible Notes
+
+Fetch all convertible instruments for a company.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Data Fetching
+
+```
+fetch("cap_table:list:convertible_notes", {"corporation_id": corporation_id})
+```
+
+Optional params:
+- `page`, `pageSize`: pagination
+- `search`: text search
+- `statusExplanation`: filter by status (e.g. "Outstanding", "Converted", "Canceled")
+
+## Key Fields
+
+- `is_debt`: `false` = SAFE, `true` = convertible note
+- `dollar_amount`: investment amount
+- `price_cap`: valuation cap
+- `discount_percent`: discount rate
+- `interest_rate`: annual interest rate (convertible debt only)
+- `maturity_date`: when the note matures
+- `total_with_interest`: principal + accrued interest
+- `status_explanation`: "Outstanding", "Converted", "Canceled"
+- `note_block`: groups notes by round/tranche (e.g. "2013 SAFE", "Bridge 2014")
+- `name`: investor name
+- `label`: security label (e.g. "SAFE-1", "CN-1")
+
+## How to Present
+
+1. Separate into SAFEs (`is_debt: false`) and Convertible Notes (`is_debt: true`)
+2. Filter out canceled/converted unless specifically asked
+3. For outstanding instruments, check if `maturity_date` is approaching (within 90 days) or past
+
+**SAFEs:**
+
+| Investor | Amount | Val. Cap | Discount | MFN | Status |
+|----------|--------|----------|----------|-----|--------|
+| Investor A | $500,000 | $6,000,000 | 20% | No | Outstanding |
+
+**Convertible Notes:**
+
+| Investor | Amount | Val. Cap | Discount | Interest | Maturity | Total w/ Interest | Status |
+|----------|--------|----------|----------|----------|----------|-------------------|--------|
+| Investor B | $500,000 | $8,000,000 | 20% | 6% | 11/05/2019 | $740,164 | Converted |
+
+4. Group by `note_block` if there are multiple tranches
+5. Show totals: total outstanding amount, total with interest
diff --git a/plugins/carta-cap-table/skills/list-safes/SKILL.md b/plugins/carta-cap-table/skills/list-safes/SKILL.md
new file mode 100644
index 0000000..c21dd12
--- /dev/null
+++ b/plugins/carta-cap-table/skills/list-safes/SKILL.md
@@ -0,0 +1,41 @@
+---
+name: list-safes
+description: Fetch all SAFEs for a company. Use when asked about SAFEs, simple agreements for future equity, SAFE terms, valuation caps, or discounts.
+---
+
+# List SAFEs
+
+Fetch all SAFEs for a company.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## When to Use This Skill
+
+Use this skill for SAFEs only. If you need both SAFEs and convertible notes together (e.g. for the conversion-calculator or pro-forma-model skills), use `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` instead — that returns both types in one call and avoids a redundant API request. Do not call both `list-safes` and `list-convertible-notes` for the same query.
+
+## Data Fetching
+
+```
+fetch("cap_table:list:safes", {"corporation_id": corporation_id})
+```
+
+## Key Fields
+
+- `investor_name` or `investor.name`: investor name
+- `investment_amount` or `amount`: dollar amount invested
+- `valuation_cap` or `cap`: valuation cap
+- `discount_rate` or `discount`: discount percentage
+- `mfn`: boolean, most favored nation clause
+- `status`: "Outstanding", "Converted", etc.
+
+## How to Present
+
+Format as a table:
+
+| Investor | Amount | Val. Cap | Discount | MFN | Status |
+|----------|--------|----------|----------|-----|--------|
+| Investor A | $500,000 | $6,000,000 | 20% | No | Outstanding |
+
+Show totals: total outstanding amount, count by status.
diff --git a/plugins/carta-cap-table/skills/market-benchmarks/SKILL.md b/plugins/carta-cap-table/skills/market-benchmarks/SKILL.md
new file mode 100644
index 0000000..bde1cfe
--- /dev/null
+++ b/plugins/carta-cap-table/skills/market-benchmarks/SKILL.md
@@ -0,0 +1,110 @@
+---
+name: market-benchmarks
+description: Analyze cap structure patterns across the portfolio as market benchmarks. Use when asked about market benchmarks, typical option pool sizes, average SAFE terms, what's normal for a Series A, cap structure patterns, or portfolio-wide statistics.
+---
+
+# Market Benchmarks
+
+Compute portfolio-wide benchmarks from your own Carta data: option pool sizes, SAFE valuation caps, and round sizes. Useful for sanity-checking a new deal's terms against your existing portfolio.
+
+> **Note:** This reflects your firm's portfolio, not Carta-wide market data. Present results as "portfolio benchmarks" not "market data."
+
+## Prerequisites
+
+No inputs required — this skill loops the full portfolio automatically. Cap at 20 companies.
+
+## Commands
+
+- `list_accounts` — get all portfolio companies
+- `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})` — option pool data
+- `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` — SAFE/note terms
+- `fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})` — round sizes
+
+## Key Fields
+
+From cap table (option pool):
+- `option_plans[].authorized_shares`: shares authorized per plan
+- `totals.total_fully_diluted`: total fully diluted share count
+
+From convertible notes:
+- `price_cap`: SAFE/note valuation cap
+- `discount_percent`: discount rate (e.g. `"20.00"` = 20%)
+- `interest_rate`: annual interest rate
+- `is_debt`: false = SAFE, true = convertible note
+
+From financing history:
+- `round_name`: round name
+- `cash_paid`: amount paid per security (sum by round_name for round total)
+- `issue_date`: close date
+
+## How to Present
+
+See Step 4 below. Present as benchmark tables grouped by metric.
+
+## Step 1 — Get Portfolio
+
+Call `list_accounts`. Filter to `corporation_pk:` accounts. Extract up to 20 numeric corporation IDs.
+
+## Step 2 — Collect Data Per Company
+
+For each company, fetch in sequence:
+
+**Cap table by share class** (for option pool %):
+- `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})`
+- From `option_plans[]`: sum `authorized_shares` across all plans
+- From `totals.total_fully_diluted`: compute option pool % = option_pool_authorized / total_fully_diluted
+
+**SAFE / convertible note terms:**
+- `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})`
+- Key fields per instrument: `price_cap`, `discount_percent`, `interest_rate`, `is_debt` (false=SAFE, true=note)
+- Collect all valuation caps for SAFEs
+
+**Financing history** (for round sizes):
+- `fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})`
+- Key fields: `round_name`, `amount_raised`, `issue_date`
+- Identify the most recent priced round and its size
+
+## Step 3 — Compute Summary Statistics
+
+For each metric, compute across companies that have data:
+- **Median**, **min**, **max**
+- Skip companies with no data for a given metric (don't count as zero)
+
+Metrics:
+- Option pool % (fully diluted)
+- SAFE valuation cap
+- Last priced round size
+
+## Step 4 — Present Results
+
+**Portfolio Benchmarks (N companies)**
+
+**Option Pool Size (% Fully Diluted)**
+| Metric | Value |
+|--------|-------|
+| Median | 12.5% |
+| Range | 8% – 20% |
+| Companies with data | 14 |
+
+**SAFE Valuation Caps**
+| Metric | Value |
+|--------|-------|
+| Median | $8,000,000 |
+| Range | $3M – $25M |
+| SAFEs analyzed | 28 |
+
+**Last Priced Round Size**
+| Metric | Value |
+|--------|-------|
+| Median | $5,000,000 |
+| Range | $500K – $30M |
+| Companies with priced rounds | 10 |
+
+---
+
+If the user asks about a specific company ("how does Acme's option pool compare?"), show that company's value alongside the portfolio median.
+
+## Best Effort
+
+- **Computed:** median, min, max summary statistics and company-vs-portfolio comparisons are derived from the portfolio sample
+- **Authoritative:** per-company option pool sizes, SAFE terms, and round sizes come directly from Carta
diff --git a/plugins/carta-cap-table/skills/ownership/SKILL.md b/plugins/carta-cap-table/skills/ownership/SKILL.md
new file mode 100644
index 0000000..f4c6701
--- /dev/null
+++ b/plugins/carta-cap-table/skills/ownership/SKILL.md
@@ -0,0 +1,74 @@
+---
+name: ownership
+description: Ownership structure by share class, voting rights, and liquidation seniority. Use when asked about ownership breakdown, preferred vs common holders, voting power, protective provisions, or consent requirements.
+---
+
+# Ownership Structure
+
+Surface which preferred stockholders hold voting power and would typically need to consent for major corporate actions (financing rounds, M&A, charter amendments).
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Data Retrieval
+
+1. `fetch("cap_table:get:rights_and_preferences", { corporation_id })`
+2. `fetch("cap_table:get:cap_table_by_stakeholder", { corporation_id })`
+
+## Key Fields
+
+From share classes:
+- `name`: class name (e.g. "Series A Preferred")
+- `stock_type`: "PREFERRED" or "COMMON"
+- `votes_per_share`: voting weight (0 = non-voting)
+- `seniority`: liquidation seniority rank (higher = senior)
+
+From cap table by stakeholder:
+- Per-stakeholder share counts and ownership % broken down by share class
+
+## Step 1 — Fetch Share Classes
+
+```
+fetch("cap_table:get:rights_and_preferences", { corporation_id })
+```
+
+## Step 2 — Fetch Cap Table by Stakeholder
+
+```
+fetch("cap_table:get:cap_table_by_stakeholder", { corporation_id })
+```
+
+This returns per-stakeholder ownership broken down by share class.
+
+## How to Present
+
+See Step 4 below for table format.
+
+## Step 3 — Identify Preferred Holders
+
+From the share class data, identify preferred classes (stock_type = "PREFERRED" or votes_per_share > 0).
+
+From the cap table, identify stakeholders holding preferred shares. Sort by:
+1. Seniority (most senior first)
+2. Fully diluted ownership % (descending)
+
+## Step 4 — Present Results
+
+**Preferred Stockholders with Voting Rights**
+
+| Holder | Share Class | Shares | FD % | Votes/Share | Seniority |
+|--------|-------------|--------|------|-------------|-----------|
+| ... | ... | ... | ... | ... | ... |
+
+**Common Stockholders** (if relevant — founders, employees)
+
+| Holder | Shares | FD % |
+|--------|--------|------|
+| ... | ... | ... |
+
+## Important Note
+
+> Carta surfaces share ownership and voting structure, but does **not** expose actual consent thresholds or protective provision terms — those live in the Stockholders' Agreement and Certificate of Incorporation. This data identifies *who* holds voting preferred shares; an attorney must interpret *what* approvals are required and at what thresholds.
+
+If the user asks about specific thresholds (e.g., "do we need 60% of Series A to approve?"), acknowledge the limitation and recommend reviewing the governing documents.
diff --git a/plugins/carta-cap-table/skills/portfolio-alerts/SKILL.md b/plugins/carta-cap-table/skills/portfolio-alerts/SKILL.md
new file mode 100644
index 0000000..0f507c8
--- /dev/null
+++ b/plugins/carta-cap-table/skills/portfolio-alerts/SKILL.md
@@ -0,0 +1,124 @@
+---
+name: portfolio-alerts
+description: Detect red flags and time-sensitive issues across portfolio companies. Use when asked to flag problems, find expiring items, or audit portfolio health.
+---
+
+# Portfolio Alerts
+
+Scan multiple companies for red flags and time-sensitive issues. Builds on the `portfolio-query` pattern.
+
+## When to Use
+
+- "Flag any red flags across my portfolio"
+- "Which companies need attention?"
+- "Are any 409As expiring soon?"
+- "Which companies have low option pools?"
+- "Any SAFEs or notes approaching maturity?"
+
+## Prerequisites
+
+No inputs required — this skill loops the full portfolio. Call `list_accounts` to get all `corporation_pk` accounts automatically.
+
+## Commands
+
+Depending on the check:
+- `list_accounts` — get all portfolio companies
+- `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` — 409A expiry check
+- `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})` — option pool check
+- `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` — note maturity check
+- `fetch("cap_table:list:safes", {"corporation_id": corporation_id})` — SAFE exposure check
+
+## Key Fields
+
+From 409A: `expiration_date`, `price`, `effective_date`
+From cap table option plans: `available_ownership`, `name`
+From convertible notes: `maturity_date`, `status_explanation`, `is_debt`, `dollar_amount`, `total_with_interest`
+
+## How It Works
+
+1. Call `list_accounts` to get all `corporation_pk` accounts
+2. For each company, run the relevant checks
+3. Aggregate findings by severity: critical > warning > info
+4. Present a summary dashboard
+
+## Alert Checks
+
+Run whichever checks are relevant to the user's question. If they say "all red flags", run all of them.
+
+### 1. Expiring 409A Valuations
+
+```
+fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})
+```
+- **Critical**: no 409A on file at all — treat as missing/unknown FMV; flag for immediate follow-up if the company issues options
+- **Critical**: expiration_date is in the past
+- **Warning**: expiration_date is within 90 days of today
+- **Info**: expiration_date is within 180 days
+
+Companies with no 409A data should never be silently skipped — always include them in the output as a distinct category.
+
+### 2. Low Option Pool
+
+```
+fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})
+```
+- **Critical**: option plan available_ownership < 2%
+- **Warning**: option plan available_ownership < 5%
+- **Info**: option plan available_ownership < 10%
+
+### 3. SAFEs/Notes Approaching Maturity
+
+```
+fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})
+```
+- Filter to `status_explanation: "Outstanding"` and `is_debt: true` (notes have maturity dates)
+- **Critical**: maturity_date is in the past
+- **Warning**: maturity_date is within 90 days
+- **Info**: maturity_date is within 180 days
+
+### 4. Large Unconverted SAFE Exposure
+
+```
+fetch("cap_table:list:safes", {"corporation_id": corporation_id})
+```
+- Sum outstanding SAFE amounts
+- **Warning**: total outstanding SAFEs > 20% of last known valuation cap
+- Present total SAFE exposure per company
+
+## Presentation
+
+### Summary Dashboard
+
+```
+Portfolio Health Check — 12 companies scanned
+
+Critical (2):
+ - Beta Inc: 409A EXPIRED (expired 2025-01-14, 63 days ago)
+ - Gamma Corp: Option pool at 1.2% available
+
+Warning (3):
+ - Acme Corp: 409A expires in 37 days (04/24/2025)
+ - Delta LLC: Convertible note matures in 45 days
+ - Epsilon Inc: Option pool at 4.1% available
+
+Healthy (7): Alpha, Zeta, Eta, Theta, Iota, Kappa, Lambda
+```
+
+### Detail Table (for specific checks)
+
+| Company | Issue | Severity | Details | Action Needed |
+|---------|-------|----------|---------|---------------|
+| Beta Inc | 409A Expired | Critical | Expired 01/14/2025 | Order new 409A |
+| Acme Corp | 409A Expiring | Warning | Expires 04/24/2025 (37 days) | Schedule valuation |
+
+## Important Notes
+
+- Be mindful of rate limits — if > 20 companies, ask the user to narrow scope
+- Some companies may error (permissions, incomplete setup) — skip gracefully and note which failed
+- Always show the scan date and count: "Scanned 12 companies on 2025-03-18"
+- Sort by severity (critical first), then by urgency (nearest deadline first)
+
+## Best Effort
+
+- **Computed:** severity classification (critical/warning/info thresholds) and aggregated health summary are heuristic, not Carta-defined
+- **Authoritative:** 409A expiration dates, option pool percentages, and note maturity dates come directly from Carta
diff --git a/plugins/carta-cap-table/skills/portfolio-query/SKILL.md b/plugins/carta-cap-table/skills/portfolio-query/SKILL.md
new file mode 100644
index 0000000..e947d42
--- /dev/null
+++ b/plugins/carta-cap-table/skills/portfolio-query/SKILL.md
@@ -0,0 +1,138 @@
+---
+name: portfolio-query
+description: Query cap table data for one or more companies. Use when asked about cap tables, ownership breakdown, share classes, stakeholder holdings, portfolio-wide analysis, comparing companies, or finding patterns across multiple entities.
+---
+
+# Portfolio Query
+
+Fetch and present cap table data for a single company or across multiple companies.
+
+## When to Use
+
+- "Show me the cap table for Acme Corp"
+- "What's the ownership breakdown?"
+- "Who are the shareholders?"
+- "Which companies have expiring 409As?"
+- "Show me cap tables for all my portfolio companies"
+- "Which companies have SAFEs outstanding?"
+- "Compare option pool sizes across my portfolio"
+- "Flag any red flags across my companies"
+
+## Single-Company Cap Table
+
+For a single company, fetch both views and present with a bar chart:
+
+```
+fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})
+fetch("cap_table:get:cap_table_by_stakeholder", {"corporation_id": corporation_id})
+```
+
+**Present in this exact order — do not skip any step:**
+
+1. **Summary line**: total outstanding shares, total fully diluted shares, total cash raised
+2. **Share class table**: share class name, outstanding shares, outstanding %, FD shares, FD %, cash raised
+3. **Option pool**: authorized vs issued/outstanding, available shares and %
+4. **Top stakeholders table**: name, outstanding shares, outstanding %, FD %, cash raised
+5. **ASCII bar chart** — REQUIRED, always render this, no exceptions
+
+**Bar chart — MANDATORY. You MUST render this after the tables. Do not skip it.**
+
+Use the stakeholder data to render an ASCII ownership bar chart. Steps:
+1. Collect each stakeholder's fully diluted % (or outstanding % if FD not available)
+2. Find the max value — that stakeholder gets 40 █ blocks
+3. All others: `bar_width = round(pct / max_pct * 40)`, minimum 1 block
+4. Left-align labels in a fixed-width column (pad to the longest label length + 2 spaces)
+5. Immediately after the label padding, place the █ blocks — no spaces between label and blocks
+6. After the blocks, one space then the percentage
+
+Example output (you MUST produce something like this):
+```
+Ownership (Fully Diluted)
+
+Option Pool ████████████████████████████████████████ 71.8%
+Lead Investor ████████ 7.8%
+Founder A ████ 4.0%
+Founder B ████ 3.9%
+Other Investor ███ 2.6%
+Others ████ 9.9%
+```
+
+## Prerequisites
+
+- Single company: resolve `corporation_id` from `list_accounts`
+- Multi-company: loop all `corporation_pk` accounts from `list_accounts`
+
+## Commands
+
+- `list_accounts` — get all accessible entities
+- Then per-company commands depending on the query (see reference below)
+
+### Command Reference
+
+| Data | Command |
+|---|---|
+| 409A valuations | `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` |
+| SAFEs & convertible notes | `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` |
+| Pro-forma models | `fetch("cap_table:get:pro_forma_models", {"corporation_id": corporation_id})` |
+| Cap table by share class | `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})` |
+| Option grants | `fetch("cap_table:list:grants", {"corporation_id": corporation_id})` |
+
+## Key Fields
+
+From `list_accounts`:
+- `id`: format `corporation_pk:` — extract the number for company-level commands
+- `accountType`: `"company"` accounts work with all commands; `"portfolio"` and `"investment firm"` are organizations, not companies
+- `name`: company display name
+
+## How It Works
+
+There is no single "portfolio query" endpoint. Instead:
+
+1. Call `list_accounts` to get all accessible entities
+2. Filter to the relevant account type (`company`, `fund`, `investment firm`, `law firm`, `portfolio`)
+3. Extract `corporation_id` from each account's `id` field (strip the `corporation_pk:` or `organization_pk:` prefix)
+4. Loop through each corporation, calling the relevant command per company
+5. Aggregate and present the results
+
+## Important
+
+- Only `corporation_pk` accounts work with company-level commands (cap table, grants, SAFEs, valuations). `organization_pk` accounts are portfolios/firms.
+- Be mindful of rate — don't call 50+ companies at once. Ask the user to narrow down if the list is large.
+- Some companies may return errors (permissions, setup state) — skip them gracefully and note which ones failed.
+
+## Example: Find Expiring 409As
+
+```
+1. list_accounts → get all corporation_pk accounts
+2. For each corporation_id:
+ fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})
+3. For each result, check if the most recent expiration_date is within 90 days of today
+4. Present:
+```
+
+| Company | Current FMV | Effective | Expires | Days Left |
+|---------|------------|-----------|---------|-----------|
+| Acme Corp | $12.61 | 04/25/2024 | 04/24/2025 | 37 |
+| Beta Inc | $5.00 | 01/15/2024 | 01/14/2025 | EXPIRED |
+
+## Example: Portfolio Option Pool Health
+
+```
+1. list_accounts → get all corporation_pk accounts
+2. For each corporation_id:
+ fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})
+3. Extract option plan available_ownership percentage
+4. Flag companies with available pool < 5%
+```
+
+## Presentation Tips
+
+- Always show company name alongside the data
+- Sort by urgency/severity (e.g. most expired first)
+- Group results: "needs attention" vs "healthy"
+- Include a count summary: "3 of 12 companies need attention"
+
+## Best Effort
+
+- **Computed:** cross-company comparisons, pattern detection, and urgency/severity classifications are derived by Claude
+- **Authoritative:** per-company data (409A dates, cap tables, SAFEs, grants) comes directly from Carta
diff --git a/plugins/carta-cap-table/skills/pro-forma-model/SKILL.md b/plugins/carta-cap-table/skills/pro-forma-model/SKILL.md
new file mode 100644
index 0000000..b07b535
--- /dev/null
+++ b/plugins/carta-cap-table/skills/pro-forma-model/SKILL.md
@@ -0,0 +1,186 @@
+---
+name: pro-forma-model
+description: Model a pro-forma financing round to show dilution impact. Use when asked to model a Series A/B/C, new round, or show how a round would affect ownership.
+---
+
+# Pro-Forma Round Modeling
+
+Model the dilution impact of a new financing round using cap table data, SAFE/note conversion terms, and round parameters.
+
+## When to Use
+
+- "Model a Series B at $80M pre-money"
+- "What does dilution look like if we raise $20M at $100M pre?"
+- "How would a 10% option pool increase affect ownership?"
+- "Show me pro-forma ownership after converting SAFEs into a Series A"
+
+## Required Inputs — MUST BLOCK
+
+**STOP. Do not compute ANY pro-forma values, scenarios, or estimates without ALL required inputs. Do not model multiple scenarios as a substitute for asking. Do not proceed "in the meantime". Ask first, compute after.**
+
+| Input | Example | Required? |
+|---|---|---|
+| Pre-money valuation | "$80M pre-money" | Yes |
+| New investment amount (raise size) | "raising $15M" | **Yes — ALWAYS ask if not given** |
+| Option pool target | "10% post-money pool" | No — skip pool math if omitted |
+
+If the raise size is missing, you MUST call AskUserQuestion BEFORE any computation:
+AskUserQuestion("How much are you raising in this round? I need the raise amount before I can compute any pro-forma numbers.")
+
+**Even in multi-part requests** (e.g. "get me cap table AND pro-forma"), complete the other parts but BLOCK on the pro-forma until raise size is confirmed. Do not guess. Do not model scenarios at common amounts. Ask.
+
+**Subagent prohibition:** Do NOT delegate this skill to a background agent if the raise size is missing. A subagent cannot ask the user for input.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Data Retrieval
+
+Fetch saved pro-forma round models:
+
+```
+fetch("cap_table:get:pro_forma_models", {"corporation_id": corporation_id})
+```
+
+Returns saved pro-forma round models. If none exist, compute manually (see Option 2 below).
+
+## Key Fields
+
+From saved models:
+- `name`: round name (e.g. "Series A")
+- `inputs.pre_money_valuation`: pre-money valuation used
+- `inputs.new_investment_amount`: round size
+- `inputs.post_round_option_pool.value`: target option pool %
+- `outputs.post_money_valuation`: computed post-money valuation
+- `outputs.post_round_share_price`: new share price
+- `outputs.post_round_option_pool_share_count_increase`: shares added to pool
+- `investments[]`: per-investor breakdown
+- `convertibles[]`: SAFEs/notes included in the model
+
+## Option 1: Use Saved Pro-Forma Models
+
+```
+fetch("cap_table:get:pro_forma_models", {"corporation_id": corporation_id})
+```
+
+This returns any saved pro-forma round models. If the company has modeled rounds in Carta, use these directly.
+
+## Option 2: Compute from Cap Table Data (BEST EFFORT)
+
+When no saved models match the requested terms, **tell the user before computing**:
+- State what the saved models contain (if any) and why they don't match
+- Explicitly say: "I can compute a best-effort estimate from the cap table data. This is my math, not Carta's — it may not match actual round terms. Want me to proceed?"
+- Only compute after the user confirms (or if they explicitly asked for a custom scenario)
+
+When computing manually, **prefix the results with a prominent disclaimer**:
+> ⚠️ **Best-effort estimate** — computed from cap table data, not from a saved Carta model. Verify with counsel before relying on these numbers.
+
+### Step 1: Gather Current State
+
+1. `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})` — get current authorized/outstanding/fully-diluted shares
+2. `fetch("cap_table:get:convertible_notes", {"corporation_id": corporation_id})` — get SAFEs and convertible notes (filter to `status_explanation: "Outstanding"`)
+3. `fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})` — get current FMV
+
+### Step 2: Apply Round Math
+
+Given user inputs:
+- **Pre-money valuation** (e.g. $80M)
+- **Round size** (e.g. $15M)
+- **Option pool increase** (e.g. 10% post-money target)
+
+Before computing, check available pool shares. From `fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})`, compare `option_plans[].authorized_shares` vs `option_plans[].fully_diluted_shares` — the difference is currently available (unissued) shares. Report both the authorized pool size and the available-for-future-grants amount separately. If the available pool is nearly exhausted (< 5% of authorized), flag it — even a large authorized pool means little if it's mostly issued/vested.
+
+Calculate using this EXACT formula. Do not deviate or simplify.
+
+**Option pool shuffle** — the pool increase is carved from pre-money, meaning it inflates the denominator for price-per-share. This is circular (pool depends on post-FD, post-FD depends on pool), so solve iteratively:
+
+```
+Inputs:
+ pre_money = user-provided (e.g. $80,000,000)
+ raise = user-provided (e.g. $20,000,000)
+ pool_pct = user-provided (e.g. 0.10 for 10%)
+ pre_fd = from cap table total_fully_diluted
+ existing_pool = from cap table option_plans fully_diluted_shares
+ conversion_shares = SAFE + note conversions (see below)
+
+Step A — Solve for new pool shares (iterative):
+ Start with: new_pool_shares = 0
+ Repeat until stable (max 20 iterations):
+ adjusted_pre_fd = pre_fd + new_pool_shares + conversion_shares
+ price_per_share = pre_money / adjusted_pre_fd
+ new_round_shares = raise / price_per_share
+ post_fd = adjusted_pre_fd + new_round_shares
+ new_pool_shares = (post_fd * pool_pct) - existing_pool
+ if new_pool_shares < 0: new_pool_shares = 0
+
+Step B — Final values:
+ post_money = pre_money + raise
+ price_per_share = pre_money / (pre_fd + new_pool_shares + conversion_shares)
+ new_round_shares = raise / price_per_share
+ post_fd = pre_fd + new_pool_shares + conversion_shares + new_round_shares
+
+Verify: new_round_shares / post_fd should equal raise / post_money (investor ownership = raise / post-money). If not, recheck the math.
+
+SAFE conversions (only if status = "Outstanding"):
+ Cap: shares = investment / (cap / pre_fd)
+ Discount: shares = investment / (price_per_share * (1 - discount))
+ Use whichever gives MORE shares to the holder.
+
+Note conversions (only if status = "Outstanding"):
+ Same as SAFE but use total_with_interest instead of investment.
+```
+
+
+### Step 3: Present Results
+
+Show a before/after ownership table:
+
+| Stakeholder Group | Pre-Round Shares | Pre-Round % | Post-Round Shares | Post-Round % | Dilution |
+|-------------------|-----------------|-------------|-------------------|--------------|----------|
+| Founders | 5,000,000 | 50.0% | 5,000,000 | 35.7% | -14.3% |
+| Series A Investors | 2,000,000 | 20.0% | 2,000,000 | 14.3% | -5.7% |
+| SAFE Conversions | — | — | 800,000 | 5.7% | +5.7% |
+| Series B (new) | — | — | 1,875,000 | 13.4% | +13.4% |
+| Option Pool | 3,000,000 | 30.0% | 4,325,000 | 30.9% | +0.9% |
+| **Total** | **10,000,000** | **100%** | **14,000,000** | **100%** | |
+
+Also show:
+- Price per share
+- Pre-money and post-money valuation
+- Total SAFE/note conversion shares
+- New option pool shares added
+
+After the table, render an ASCII bar chart of post-round ownership by stakeholder group.
+Sort descending by post-round ownership. Scale bars to max width 40 chars.
+
+**Bar chart format rules:**
+- Left-align labels in a fixed-width column (pad with spaces to the longest label length + 2)
+- Immediately after the label column, fill blocks: `bar_width = round(pct / max_pct * 40)`
+- After the blocks, add a single space then the percentage
+- Never insert spaces between the label column and the blocks — the blocks must start right after label padding
+- Minimum 1 block for any non-zero value
+- Always pin "Others", "Other", or any catch-all group to the bottom of the chart, regardless of its ownership size
+
+```
+Post-Round Ownership
+
+Option Pool (total) ████████████████████████████████████████ 64.4%
+Series B (new) ████████████ 14.2%
+Common Stock ████ 7.6%
+Series A Preferred ███ 4.5%
+Series B Preferred ██ 3.7%
+Series C Preferred ██ 2.7%
+Series Seed Pref. █ 1.7%
+Series D Preferred █ 1.2%
+```
+
+Also render this same chart for cap-table-by-stakeholder views when showing current ownership
+(pre-round). Use the same format rules.
+
+## Important Notes
+
+- Always state your assumptions clearly (e.g. "assuming all SAFEs convert at their caps")
+- If SAFE terms are ambiguous, show both cap and discount scenarios
+- Option pool shuffle: the pool increase typically comes from pre-money (dilutes existing holders, not new investors). Clarify this.
+- This is an estimate — actual round math depends on legal terms. Recommend verifying with counsel.
diff --git a/plugins/carta-cap-table/skills/round-history/SKILL.md b/plugins/carta-cap-table/skills/round-history/SKILL.md
new file mode 100644
index 0000000..f7a4274
--- /dev/null
+++ b/plugins/carta-cap-table/skills/round-history/SKILL.md
@@ -0,0 +1,93 @@
+---
+name: round-history
+description: Fetch financing round history for a company. Use when asked about funding rounds, capital raised, or financing history.
+---
+
+# Round History
+
+Fetch financing history and summarize by round, or use the cap table by share class for a quick overview.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Option 1: Detailed History (per-security)
+
+```
+fetch("cap_table:get:financing_history", {"corporation_id": corporation_id})
+```
+
+This is the same data that powers the in-app "Financing History" tab.
+
+## Key Fields
+
+- `round_name`: name of the financing round (e.g. "Series A Preferred")
+- `issue_date`: date the security was issued
+- `cash_paid`: amount paid by the investor for this security
+- `quantity`: number of shares issued
+- `issue_price`: price per share
+- `stakeholder_name`: investor name
+- `label`: security label (e.g. "PB-9")
+- `is_grant`: true if this is an option grant (not a priced round)
+- `is_canceled`: true if the security was canceled — exclude from aggregates
+- `is_converted`: true if the security converted (e.g. SAFE → preferred)
+
+### Response Format
+
+```json
+{
+ "count": 120,
+ "results": [
+ {
+ "id": 666,
+ "pk_key": "certificate_pk",
+ "stakeholder_name": "Janet Sugiyama",
+ "currency": "USD",
+ "label": "PB-9",
+ "round_name": "Series B Preferred",
+ "quantity": 180000.0,
+ "issue_date": "2014-09-14",
+ "cash_paid": 219600.0,
+ "issue_price": "1.22",
+ "is_grant": false,
+ "is_canceled": false,
+ "is_converted": false
+ }
+ ]
+}
+```
+
+### How to Present
+
+1. Group results by `round_name`
+2. For each round, aggregate: total `cash_paid`, total `quantity`, count of investors, earliest `issue_date`
+3. Use `issue_price` from any non-canceled entry as the price per share
+4. Filter out entries where `is_canceled` is true
+5. Present as:
+
+| Round | Close Date | Price/Share | Investors | Shares Issued | Cash Raised |
+|-------|-----------|-------------|-----------|---------------|-------------|
+| Series Seed Preferred | 2013-06-30 | $0.27 | 5 | 1,422,435 | $383,380 |
+| Series A Preferred | 2013-11-15 | $0.44 | 8 | 3,697,191 | $1,645,250 |
+
+6. After the table, render an ASCII bar chart of cash raised per round (chronological order).
+ Scale bars to max width 40 chars. Exclude rounds with zero cash raised (e.g. option grants).
+
+```
+Cash Raised by Round
+
+Series Seed Preferred ████ $383K
+Series A Preferred ████████████████ $1.6M
+Series B Preferred ████████████████████████████████████████ $3.7M
+```
+
+ Each bar width = (cash_raised / max_cash_raised) * 40, rounded to nearest integer.
+ Format large numbers as $XM or $XK for readability.
+
+## Option 2: Quick Summary (from cap table)
+
+```
+fetch("cap_table:get:cap_table_by_share_class", {"corporation_id": corporation_id})
+```
+
+Each preferred share class represents a round. Faster but less detail: no individual investors, no issue dates, no price per share.
diff --git a/plugins/carta-cap-table/skills/stakeholders/SKILL.md b/plugins/carta-cap-table/skills/stakeholders/SKILL.md
new file mode 100644
index 0000000..9e6eeeb
--- /dev/null
+++ b/plugins/carta-cap-table/skills/stakeholders/SKILL.md
@@ -0,0 +1,52 @@
+---
+name: stakeholders
+description: List stakeholders for a company. Use when asked who the stakeholders are, stakeholder list, shareholders, investors, or holders.
+---
+
+# Stakeholders
+
+List stakeholders (shareholders, investors, employees) for a company.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Which Command to Use
+
+- **"Who are the stakeholders?"** → `cap_table:get:stakeholders` (names, emails, roles — no ownership data)
+- **"What does everyone own?"** → `cap_table:get:cap_table_by_stakeholder` (holdings, ownership %, FD shares)
+
+Default to `cap_table:get:stakeholders` unless the user asks about ownership, shares, or percentages.
+
+## Data Retrieval
+
+```
+fetch("cap_table:get:stakeholders", { corporation_id })
+```
+
+Supports `search` param to filter by name or email.
+
+## Key Fields
+
+- `full_name`: stakeholder display name
+- `email`: contact email
+- `event_relationship`: role (e.g. founder, employee, investor)
+- `kind`: stakeholder type
+
+## Step 1 — Fetch Stakeholders
+
+```
+fetch("cap_table:get:stakeholders", { corporation_id })
+```
+
+## Step 2 — Present Results
+
+| Name | Email | Role | Type |
+|------|-------|------|------|
+| ... | ... | ... | ... |
+
+If the user then asks about ownership, follow up with:
+
+```
+fetch("cap_table:get:cap_table_by_stakeholder", { corporation_id })
+```
diff --git a/plugins/carta-cap-table/skills/valuation-history/SKILL.md b/plugins/carta-cap-table/skills/valuation-history/SKILL.md
new file mode 100644
index 0000000..fbef846
--- /dev/null
+++ b/plugins/carta-cap-table/skills/valuation-history/SKILL.md
@@ -0,0 +1,74 @@
+---
+name: valuation-history
+description: Fetch 409A valuation history for a company. Use when asked about 409A valuations, FMV, exercise prices, or valuation expiration dates.
+---
+
+# 409A Valuation History
+
+Fetch 409A fair market value (FMV) history for a company.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Data Fetching
+
+```
+fetch("cap_table:get:409a_valuations", {"corporation_id": corporation_id})
+```
+
+Optional params:
+- `share_class_id`: filter to a specific share class
+- `report_id`: filter to a specific valuation report
+
+## Key Fields
+
+- `effective_date`: date the 409A valuation became effective
+- `expiration_date`: date the valuation expires (typically 1 year after effective)
+- `stale_date`: date after which the valuation is considered stale (if applicable)
+- `price`: FMV per share as a string (e.g. `"12.610000000000"`)
+- `name`: share class name (e.g. "Common")
+- `common`: true if this is the common stock FMV
+- `report_id`: ID of the 409A report
+- `share_class_id`: ID of the share class
+
+## Response Format
+
+JSON array of FMV records:
+```json
+[
+ {
+ "id": 484,
+ "effective_date": "04/25/2024",
+ "expiration_date": "04/24/2025",
+ "stale_date": null,
+ "price": "12.610000000000",
+ "report_id": 472,
+ "share_class_id": 9,
+ "name": "Common",
+ "common": true,
+ "corporation_id": 7
+ }
+]
+```
+
+## How to Present
+
+1. Sort by `effective_date` descending (most recent first)
+2. The **most recent** entry is the current 409A — highlight it
+3. Format `price` as currency (e.g. "$12.61/share"), trimming trailing zeros
+4. Check if `expiration_date` is within 90 days of today — **flag as a time-sensitive action item**, not just a data point: bold it, call out the exact days remaining, and recommend initiating renewal immediately (especially if a financing round is in progress, as closing will likely push past the expiry date)
+5. Check if `expiration_date` is in the past — flag as "expired"
+6. For history, show a table:
+
+| Effective | Expires | FMV/Share | Share Class | Status |
+|-----------|---------|-----------|-------------|--------|
+| 04/25/2024 | 04/24/2025 | $12.61 | Common | Current |
+| 03/31/2023 | 04/24/2024 | $10.33 | Common | Expired |
+
+7. Do not render a bar chart for FMV history — values in mature companies cluster near the
+ maximum, making bars uninformative (all bars look the same width). The table is sufficient.
+ Instead, after the table, add a one-line trend summary:
+ > FMV has grown **Nx since YYYY**, with [acceleration/steady growth] since [year].
+
+8. If multiple share classes exist, group by share class name in the table.
diff --git a/plugins/carta-cap-table/skills/waterfall-scenarios/SKILL.md b/plugins/carta-cap-table/skills/waterfall-scenarios/SKILL.md
new file mode 100644
index 0000000..70afbd4
--- /dev/null
+++ b/plugins/carta-cap-table/skills/waterfall-scenarios/SKILL.md
@@ -0,0 +1,53 @@
+---
+name: waterfall-scenarios
+description: Fetch saved waterfall / exit scenario models for a company. Use when asked about liquidation preferences, exit payouts, return multiples, or waterfall analysis.
+---
+
+# Waterfall Scenarios
+
+Fetch saved exit scenario models and present them with meaningful context, not just the per-holder table.
+
+## Prerequisites
+
+You need the `corporation_id`. Get it from `list_accounts` if you don't have it.
+
+## Workflow
+
+```
+fetch("cap_table:get:waterfall_scenarios", {"corporation_id": corporation_id})
+```
+
+The command returns each completed (status == "DONE", non-draft) scenario with a per-holder breakdown of cost basis, payout value, share count, and return multiple.
+
+## How to Present
+
+Don't just show the table — frame each scenario:
+
+- **Lead with the exit value and what it means**: who gets paid out, at what multiple, and whether any holders are underwater
+- **Highlight the biggest winners and losers** by return multiple — a 1.0x return means a holder barely breaks even; anything below that means a loss
+- **If there are multiple scenarios**, compare them: how do payouts shift as exit value changes? At what exit value does the common stack start to see meaningful returns?
+- **Note liquidation preference effects**: if preferred holders take a large share at lower exit values, say so plainly
+
+After the per-holder table, render an ASCII bar chart of payout by holder (sorted descending by payout).
+Scale bars to max width 40 chars:
+
+```
+Payout Distribution — $50M Exit
+
+Lead Investor ████████████████████████████████████████ $18.2M 3.7x
+Founder ████████████████████ $9.1M 1.8x
+Common Holders ██████████ $4.5M 0.9x
+```
+
+Each bar width = (value_of_holdings / max_value) * 40. Show return multiple after the dollar amount.
+
+Flag anything notable:
+- Any holder with return multiple < 1.0x (loss scenario)
+- Large gap between pref payout and common payout at a given exit value
+- Scenarios that are very close in exit value but have very different common distributions
+
+## Custom Exit Values
+
+If the user asks to model a specific exit value not in the saved scenarios:
+
+> "There's no saved model at that exit value. To model a custom exit, create a new scenario in Carta's scenario modeling tool, then come back and I'll pull it up."