* docs: restructure README.md — 2,539 → 209 lines (#247) - Cut from 2,539 lines / 73 sections to 209 lines / 18 sections - Consolidated 4 install methods into one unified section - Moved all skill details to domain-level READMEs (linked from table) - Front-loaded value prop and keywords for SEO - Added POWERFUL tier highlight section - Added skill-security-auditor showcase section - Removed stale Q4 2025 roadmap, outdated ROI claims, duplicate content - Fixed all internal links - Clean heading hierarchy (H2 for main sections only) Closes #233 Co-authored-by: Leo <leo@openclaw.ai> * fix: enhance 5 skills with scripts, references, and Anthropic best practices (#248) * fix(skill): enhance git-worktree-manager with scripts, references, and Anthropic best practices * fix(skill): enhance mcp-server-builder with scripts, references, and Anthropic best practices * fix(skill): enhance changelog-generator with scripts, references, and Anthropic best practices * fix(skill): enhance ci-cd-pipeline-builder with scripts, references, and Anthropic best practices * fix(skill): enhance prompt-engineer-toolkit with scripts, references, and Anthropic best practices * docs: update README, CHANGELOG, and plugin metadata * fix: correct marketing plugin count, expand thin references --------- Co-authored-by: Leo <leo@openclaw.ai> * ci: Add VirusTotal security scan for skills (#252) * Dev (#231) * Improve senior-fullstack skill description and workflow validation - Expand frontmatter description with concrete actions and trigger clauses - Add validation steps to scaffolding workflow (verify scaffold succeeded) - Add re-run verification step to audit workflow (confirm P0 fixes) * chore: sync codex skills symlinks [automated] * fix(skill): normalize senior-fullstack frontmatter to inline format Normalize YAML description from block scalar (>) to inline single-line format matching all other 50+ skills. Align frontmatter trigger phrases with the body's Trigger Phrases section to eliminate duplication. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(ci): add GITHUB_TOKEN to checkout + restore corrupted skill descriptions - Add token: ${{ secrets.GITHUB_TOKEN }} to actions/checkout@v4 in sync-codex-skills.yml so git-auto-commit-action can push back to branch (fixes: fatal: could not read Username, exit 128) - Restore correct description for incident-commander (was: 'Skill from engineering-team') - Restore correct description for senior-fullstack (was: '>') * fix(ci): pass PROJECTS_TOKEN to fix automated commits + remove duplicate checkout Fixes PROJECTS_TOKEN passthrough for git-auto-commit-action and removes duplicate checkout step in pr-issue-auto-close workflow. * fix(ci): remove stray merge conflict marker in sync-codex-skills.yml (#221) Co-authored-by: Leo <leo@leo-agent-server> * fix(ci): fix workflow errors + add OpenClaw support (#222) * feat: add 20 new practical skills for professional Claude Code users New skills across 5 categories: Engineering (12): - git-worktree-manager: Parallel dev with port isolation & env sync - ci-cd-pipeline-builder: Generate GitHub Actions/GitLab CI from stack analysis - mcp-server-builder: Build MCP servers from OpenAPI specs - changelog-generator: Conventional commits to structured changelogs - pr-review-expert: Blast radius analysis & security scan for PRs - api-test-suite-builder: Auto-generate test suites from API routes - env-secrets-manager: .env management, leak detection, rotation workflows - database-schema-designer: Requirements to migrations & types - codebase-onboarding: Auto-generate onboarding docs from codebase - performance-profiler: Node/Python/Go profiling & optimization - runbook-generator: Operational runbooks from codebase analysis - monorepo-navigator: Turborepo/Nx/pnpm workspace management Engineering Team (2): - stripe-integration-expert: Subscriptions, webhooks, billing patterns - email-template-builder: React Email/MJML transactional email systems Product Team (3): - saas-scaffolder: Full SaaS project generation from product brief - landing-page-generator: High-converting landing pages with copy frameworks - competitive-teardown: Structured competitive product analysis Business Growth (1): - contract-and-proposal-writer: Contracts, SOWs, NDAs per jurisdiction Marketing (1): - prompt-engineer-toolkit: Systematic prompt development & A/B testing Designed for daily professional use and commercial distribution. * chore: sync codex skills symlinks [automated] * docs: update README with 20 new skills, counts 65→86, new skills section * docs: add commercial distribution plan (Stan Store + Gumroad) * docs: rewrite CHANGELOG.md with v2.0.0 release (65 skills, 9 domains) (#226) * docs: rewrite CHANGELOG.md with v2.0.0 release (65 skills, 9 domains) - Consolidate 191 commits since v1.0.2 into proper v2.0.0 entry - Document 12 POWERFUL-tier skills, 37 refactored skills - Add new domains: business-growth, finance - Document Codex support and marketplace integration - Update version history summary table - Clean up [Unreleased] to only planned work * docs: add 24 POWERFUL-tier skills to plugin, fix counts to 85 across all docs - Add engineering-advanced-skills plugin (24 POWERFUL-tier skills) to marketplace.json - Add 13 missing skills to CHANGELOG v2.0.0 (agent-workflow-designer, api-test-suite-builder, changelog-generator, ci-cd-pipeline-builder, codebase-onboarding, database-schema-designer, env-secrets-manager, git-worktree-manager, mcp-server-builder, monorepo-navigator, performance-profiler, pr-review-expert, runbook-generator) - Fix skill count: 86→85 (excl sample-skill) across README, CHANGELOG, marketplace.json - Fix stale 53→85 references in README - Add engineering-advanced-skills install command to README - Update marketplace.json version to 2.0.0 --------- Co-authored-by: Leo <leo@openclaw.ai> * feat: add skill-security-auditor POWERFUL-tier skill (#230) Security audit and vulnerability scanner for AI agent skills before installation. Scans for: - Code execution risks (eval, exec, os.system, subprocess shell injection) - Data exfiltration (outbound HTTP, credential harvesting, env var extraction) - Prompt injection in SKILL.md (system override, role hijack, safety bypass) - Dependency supply chain (typosquatting, unpinned versions, runtime installs) - File system abuse (boundary violations, binaries, symlinks, hidden files) - Privilege escalation (sudo, SUID, cron manipulation, shell config writes) - Obfuscation (base64, hex encoding, chr chains, codecs) Produces clear PASS/WARN/FAIL verdict with per-finding remediation guidance. Supports local dirs, git repo URLs, JSON output, strict mode, and CI/CD integration. Includes: - scripts/skill_security_auditor.py (1049 lines, zero dependencies) - references/threat-model.md (complete attack vector documentation) - SKILL.md with usage guide and report format Tested against: rag-architect (PASS), agent-designer (PASS), senior-secops (FAIL - correctly flagged eval/exec patterns). Co-authored-by: Leo <leo@openclaw.ai> * docs: add skill-security-auditor to marketplace, README, and CHANGELOG - Add standalone plugin entry for skill-security-auditor in marketplace.json - Update engineering-advanced-skills plugin description to include it - Update skill counts: 85→86 across README, CHANGELOG, marketplace - Add install command to README Quick Install section - Add to CHANGELOG [Unreleased] section --------- Co-authored-by: Baptiste Fernandez <fernandez.baptiste1@gmail.com> Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Leo <leo@leo-agent-server> Co-authored-by: Leo <leo@openclaw.ai> * Dev (#249) * docs: restructure README.md — 2,539 → 209 lines (#247) - Cut from 2,539 lines / 73 sections to 209 lines / 18 sections - Consolidated 4 install methods into one unified section - Moved all skill details to domain-level READMEs (linked from table) - Front-loaded value prop and keywords for SEO - Added POWERFUL tier highlight section - Added skill-security-auditor showcase section - Removed stale Q4 2025 roadmap, outdated ROI claims, duplicate content - Fixed all internal links - Clean heading hierarchy (H2 for main sections only) Closes #233 Co-authored-by: Leo <leo@openclaw.ai> * fix: enhance 5 skills with scripts, references, and Anthropic best practices (#248) * fix(skill): enhance git-worktree-manager with scripts, references, and Anthropic best practices * fix(skill): enhance mcp-server-builder with scripts, references, and Anthropic best practices * fix(skill): enhance changelog-generator with scripts, references, and Anthropic best practices * fix(skill): enhance ci-cd-pipeline-builder with scripts, references, and Anthropic best practices * fix(skill): enhance prompt-engineer-toolkit with scripts, references, and Anthropic best practices * docs: update README, CHANGELOG, and plugin metadata * fix: correct marketing plugin count, expand thin references --------- Co-authored-by: Leo <leo@openclaw.ai> --------- Co-authored-by: Leo <leo@openclaw.ai> * Dev (#250) * docs: restructure README.md — 2,539 → 209 lines (#247) - Cut from 2,539 lines / 73 sections to 209 lines / 18 sections - Consolidated 4 install methods into one unified section - Moved all skill details to domain-level READMEs (linked from table) - Front-loaded value prop and keywords for SEO - Added POWERFUL tier highlight section - Added skill-security-auditor showcase section - Removed stale Q4 2025 roadmap, outdated ROI claims, duplicate content - Fixed all internal links - Clean heading hierarchy (H2 for main sections only) Closes #233 Co-authored-by: Leo <leo@openclaw.ai> * fix: enhance 5 skills with scripts, references, and Anthropic best practices (#248) * fix(skill): enhance git-worktree-manager with scripts, references, and Anthropic best practices * fix(skill): enhance mcp-server-builder with scripts, references, and Anthropic best practices * fix(skill): enhance changelog-generator with scripts, references, and Anthropic best practices * fix(skill): enhance ci-cd-pipeline-builder with scripts, references, and Anthropic best practices * fix(skill): enhance prompt-engineer-toolkit with scripts, references, and Anthropic best practices * docs: update README, CHANGELOG, and plugin metadata * fix: correct marketing plugin count, expand thin references --------- Co-authored-by: Leo <leo@openclaw.ai> --------- Co-authored-by: Leo <leo@openclaw.ai> * ci: add VirusTotal security scan for skills - Scans changed skill directories on PRs to dev/main - Scans all skills on release publish - Posts scan results as PR comment with analysis links - Rate-limited to 4 req/min (free tier compatible) - Appends VirusTotal links to release body on publish * fix: resolve YAML lint errors in virustotal workflow - Add document start marker (---) - Quote 'on' key for truthy lint rule - Remove trailing spaces - Break long lines under 160 char limit --------- Co-authored-by: Baptiste Fernandez <fernandez.baptiste1@gmail.com> Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Leo <leo@leo-agent-server> Co-authored-by: Leo <leo@openclaw.ai> * feat: add playwright-pro plugin — production-grade Playwright testing toolkit (#254) Complete Claude Code plugin with: - 9 skills (/pw:init, generate, review, fix, migrate, coverage, testrail, browserstack, report) - 3 specialized agents (test-architect, test-debugger, migration-planner) - 55 test case templates across 11 categories (auth, CRUD, checkout, search, forms, dashboard, settings, onboarding, notifications, API, accessibility) - TestRail MCP server (TypeScript) — 8 tools for bidirectional sync - BrowserStack MCP server (TypeScript) — 7 tools for cross-browser testing - Smart hooks (auto-validate tests, auto-detect Playwright projects) - 6 curated reference docs (golden rules, locators, assertions, fixtures, pitfalls, flaky tests) - Leverages Claude Code built-ins (/batch, /debug, Explore subagent) - Zero-config for core features; TestRail/BrowserStack via env vars - Both TypeScript and JavaScript support throughout Co-authored-by: Leo <leo@openclaw.ai> * feat: add playwright-pro to marketplace registry (#256) - New plugin: playwright-pro (9 skills, 3 agents, 55 templates, 2 MCP servers) - Install: /plugin install playwright-pro@claude-code-skills - Total marketplace plugins: 17 Co-authored-by: Leo <leo@openclaw.ai> * fix: integrate playwright-pro across all platforms (#258) - Add root SKILL.md for OpenClaw and ClawHub compatibility - Add to README: Skills Overview table, install section, badge count - Regenerate .codex/skills-index.json with playwright-pro entry - Add .codex/skills/playwright-pro symlink for Codex CLI - Fix YAML frontmatter (single-line description for index parsing) Platforms verified: - Claude Code: marketplace.json ✅ (merged in PR #256) - Codex CLI: symlink + skills-index.json ✅ - OpenClaw: SKILL.md auto-discovered by install script ✅ - ClawHub: published as playwright-pro@1.1.0 ✅ Co-authored-by: Leo <leo@openclaw.ai> * docs: update CLAUDE.md — reflect 87 skills across 9 domains Sync CLAUDE.md with actual repository state: add Engineering POWERFUL tier (25 skills), update all skill counts, add plugin registry references, and replace stale sprint section with v2.0.0 version info. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs: mention Claude Code in project description Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add self-improving-agent plugin — auto-memory curation for Claude Code (#260) New plugin: engineering-team/self-improving-agent/ - 5 skills: /si:review, /si:promote, /si:extract, /si:status, /si:remember - 2 agents: memory-analyst, skill-extractor - 1 hook: PostToolUse error capture (zero overhead on success) - 3 reference docs: memory architecture, promotion rules, rules directory patterns - 2 templates: rule template, skill template - 20 files, 1,829 lines Integrates natively with Claude Code's auto-memory (v2.1.32+). Reads from ~/.claude/projects/<path>/memory/ — no duplicate storage. Promotes proven patterns from MEMORY.md to CLAUDE.md or .claude/rules/. Also: - Added to marketplace.json (18 plugins total) - Added to README (Skills Overview + install section) - Updated badge count to 88+ - Regenerated .codex/skills-index.json + symlink Co-authored-by: Leo <leo@openclaw.ai> * feat: C-Suite expansion — 8 new executive advisory roles (2→10) (#264) * feat: C-Suite expansion — 8 new executive advisory roles Add COO, CPO, CMO, CFO, CRO, CISO, CHRO advisors and Executive Mentor. Expands C-level advisory from 2 to 10 roles with 74 total files. Each role includes: - SKILL.md (lean, <5KB, ~1200 tokens for context efficiency) - Reference docs (loaded on demand, not at startup) - Python analysis scripts (stdlib only, runnable CLI) Executive Mentor features /em: slash commands (challenge, board-prep, hard-call, stress-test, postmortem) with devil's advocate agent. 21 Python tools, 24 reference frameworks, 28,379 total lines. All SKILL.md files combined: ~17K tokens (8.5% of 200K context window). Badge: 88 → 116 skills * feat: C-Suite orchestration layer + 18 complementary skills ORCHESTRATION (new): - cs-onboard: Founder interview → company-context.md - chief-of-staff: Routing, synthesis, inter-agent orchestration - board-meeting: 6-phase multi-agent deliberation protocol - decision-logger: Two-layer memory (raw transcripts + approved decisions) - agent-protocol: Inter-agent invocation with loop prevention - context-engine: Company context loading + anonymization CROSS-CUTTING CAPABILITIES (new): - board-deck-builder: Board/investor update assembly - scenario-war-room: Cascading multi-variable what-if modeling - competitive-intel: Systematic competitor tracking + battlecards - org-health-diagnostic: Cross-functional health scoring (8 dimensions) - ma-playbook: M&A strategy (acquiring + being acquired) - intl-expansion: International market entry frameworks CULTURE & COLLABORATION (new): - culture-architect: Values → behaviors, culture code, health assessment - company-os: EOS/Scaling Up operating system selection + implementation - founder-coach: Founder development, delegation, blind spots - strategic-alignment: Strategy cascade, silo detection, alignment scoring - change-management: ADKAR-based change rollout framework - internal-narrative: One story across employees/investors/customers UPGRADES TO EXISTING ROLES: - All 10 roles get reasoning technique directives - All 10 roles get company-context.md integration - All 10 roles get board meeting isolation rules - CEO gets stage-adaptive temporal horizons (seed→C) Key design decisions: - Two-layer memory prevents hallucinated consensus from rejected ideas - Phase 2 isolation: agents think independently before cross-examination - Executive Mentor (The Critic) sees all perspectives, others don't - 25 Python tools total (stdlib only, no dependencies) 52 new files, 10 modified, 10,862 new lines. Total C-suite ecosystem: 134 files, 39,131 lines. * fix: connect all dots — Chief of Staff routes to all 28 skills - Added complementary skills registry to routing-matrix.md - Chief of Staff SKILL.md now lists all 28 skills in ecosystem - Added integration tables to scenario-war-room and competitive-intel - Badge: 116 → 134 skills - README: C-Level Advisory count 10 → 28 Quality audit passed: ✅ All 10 roles: company-context, reasoning, isolation, invocation ✅ All 6 phases in board meeting ✅ Two-layer memory with DO_NOT_RESURFACE ✅ Loop prevention (no self-invoke, max depth 2, no circular) ✅ All /em: commands present ✅ All complementary skills cross-reference roles ✅ Chief of Staff routes to every skill in ecosystem * refactor: CEO + CTO advisors upgraded to C-suite parity Both roles now match the structural standard of all new roles: - CEO: 11.7KB → 6.8KB SKILL.md (heavy content stays in references) - CTO: 10KB → 7.2KB SKILL.md (heavy content stays in references) Added to both: - Integration table (who they work with and when) - Key diagnostic questions - Structured metrics dashboard table - Consistent section ordering (Keywords → Quick Start → Responsibilities → Questions → Metrics → Red Flags → Integration → Reasoning → Context) CEO additions: - Stage-adaptive temporal horizons (seed=3m/6m/12m → B+=1y/3y/5y) - Cross-references to culture-architect and board-deck-builder CTO additions: - Key Questions section (7 diagnostic questions) - Structured metrics table (DORA + debt + team + architecture + cost) - Cross-references to all peer roles All 10 roles now pass structural parity: ✅ Keywords ✅ QuickStart ✅ Questions ✅ Metrics ✅ RedFlags ✅ Integration * feat: add proactive triggers + output artifacts to all 10 roles Every C-suite role now specifies: - Proactive Triggers: 'surface these without being asked' — context-driven early warnings that make advisors proactive, not reactive - Output Artifacts: concrete deliverables per request type (what you ask → what you get) CEO: runway alerts, board prep triggers, strategy review nudges CTO: deploy frequency monitoring, tech debt thresholds, bus factor flags COO: blocker detection, scaling threshold warnings, cadence gaps CPO: retention curve monitoring, portfolio dog detection, research gaps CMO: CAC trend monitoring, positioning gaps, budget staleness CFO: runway forecasting, burn multiple alerts, scenario planning gaps CRO: NRR monitoring, pipeline coverage, pricing review triggers CISO: audit overdue alerts, compliance gaps, vendor risk CHRO: retention risk, comp band gaps, org scaling thresholds Executive Mentor: board prep triggers, groupthink detection, hard call surfacing This transforms the C-suite from reactive advisors into proactive partners. * feat: User Communication Standard — structured output for all roles Defines 3 output formats in agent-protocol/SKILL.md: 1. Standard Output: Bottom Line → What → Why → How to Act → Risks → Your Decision 2. Proactive Alert: What I Noticed → Why It Matters → Action → Urgency (🔴🟡⚪) 3. Board Meeting: Decision Required → Perspectives → Agree/Disagree → Critic → Action Items 10 non-negotiable rules: - Bottom line first, always - Results and decisions only (no process narration) - What + Why + How for every finding - Actions have owners and deadlines ('we should consider' is banned) - Decisions framed as options with trade-offs - Founder is the highest authority — roles recommend, founder decides - Risks are concrete (if X → Y, costs $Z) - Max 5 bullets per section - No jargon without explanation - Silence over fabricated updates All 10 roles reference this standard. Chief of Staff enforces it as a quality gate. Board meeting Phase 4 uses the Board Meeting Output format. * feat: Internal Quality Loop — verification before delivery No role presents to the founder without passing verification: Step 1: Self-Verification (every role, every time) - Source attribution: where did each data point come from? - Assumption audit: [VERIFIED] vs [ASSUMED] tags on every finding - Confidence scoring: 🟢 high / 🟡 medium / 🔴 low per finding - Contradiction check against company-context + decision log - 'So what?' test: every finding needs a business consequence Step 2: Peer Verification (cross-functional) - Financial claims → CFO validates math - Revenue projections → CRO validates pipeline backing - Technical feasibility → CTO validates - People/hiring impact → CHRO validates - Skip for single-domain, low-stakes questions Step 3: Critic Pre-Screen (high-stakes only) - Irreversible decisions, >20% runway impact, strategy changes - Executive Mentor finds weakest point before founder sees it - Suspicious consensus triggers mandatory pre-screen Step 4: Course Correction (after founder feedback) - Approve → log + assign actions - Modify → re-verify changed parts - Reject → DO_NOT_RESURFACE + learn why - 30/60/90 day post-decision review Board meeting contributions now require self-verified format with confidence tags and source attribution on every finding. * fix: resolve PR review issues 1, 4, and minor observation Issue 1: c-level-advisor/CLAUDE.md — completely rewritten - Was: 2 skills (CEO, CTO only), dated Nov 2025 - Now: full 28-skill ecosystem map with architecture diagram, all roles/orchestration/cross-cutting/culture skills listed, design decisions, integration with other domains Issue 4: Root CLAUDE.md — updated all stale counts - 87 → 134 skills across all 3 references - C-Level: 2 → 33 (10 roles + 5 mentor commands + 18 complementary) - Tool count: 160+ → 185+ - Reference count: 200+ → 250+ Minor observation: Documented plugin.json convention - Explained in c-level-advisor/CLAUDE.md that only executive-mentor has plugin.json because only it has slash commands (/em: namespace) - Other skills are invoked by name through Chief of Staff or directly Also fixed: README.md 88+ → 134 in two places (first line + skills section) * fix: update all plugin/index registrations for 28-skill C-suite 1. c-level-advisor/.claude-plugin/plugin.json — v2.0.0 - Was: 2 skills, generic description - Now: all 28 skills listed with descriptions, all 25 scripts, namespace 'cs', full ecosystem description 2. .codex/skills-index.json — added 18 complementary skills - Was: 10 roles only - Now: 28 total c-level entries (10 roles + 6 orchestration + 6 cross-cutting + 6 culture) - Each with full description for skill discovery 3. .claude-plugin/marketplace.json — updated c-level-skills entry - Was: generic 2-skill description - Now: v2.0.0, full 28-skill ecosystem description, skills_count: 28, scripts_count: 25 * feat: add root SKILL.md for c-level-advisor ClawHub package --------- Co-authored-by: Leo <leo@openclaw.ai> * chore: sync codex skills symlinks [automated] --------- Co-authored-by: Leo <leo@openclaw.ai> Co-authored-by: Baptiste Fernandez <fernandez.baptiste1@gmail.com> Co-authored-by: alirezarezvani <5697919+alirezarezvani@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Leo <leo@leo-agent-server>
491 lines
18 KiB
Python
491 lines
18 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Fundraising Model
|
|
==================
|
|
Cap table management, dilution modeling, and multi-round scenario planning.
|
|
Know exactly what you're giving up before you walk into any negotiation.
|
|
|
|
Covers:
|
|
- Cap table state at each round
|
|
- Dilution per shareholder per round
|
|
- Option pool shuffle impact
|
|
- Multi-round projections (Seed → A → B → C)
|
|
- Return scenarios at different exit valuations
|
|
|
|
Usage:
|
|
python fundraising_model.py
|
|
python fundraising_model.py --exit 150 # model at $150M exit
|
|
python fundraising_model.py --csv
|
|
|
|
Stdlib only. No dependencies.
|
|
"""
|
|
|
|
import argparse
|
|
import csv
|
|
import io
|
|
import sys
|
|
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Data structures
|
|
# ---------------------------------------------------------------------------
|
|
|
|
@dataclass
|
|
class Shareholder:
|
|
"""A shareholder in the cap table."""
|
|
name: str
|
|
share_class: str # "common", "preferred", "option"
|
|
shares: float
|
|
invested: float = 0.0 # total cash invested
|
|
is_option_pool: bool = False
|
|
|
|
|
|
@dataclass
|
|
class RoundConfig:
|
|
"""Configuration for a financing round."""
|
|
name: str # e.g. "Series A"
|
|
pre_money_valuation: float
|
|
investment_amount: float
|
|
new_option_pool_pct: float = 0.0 # % of POST-money to allocate to new options
|
|
option_pool_pre_round: bool = True # True = pool created before round (dilutes founders)
|
|
lead_investor_name: str = "New Investor"
|
|
share_price_override: Optional[float] = None # if None, computed from valuation
|
|
|
|
|
|
@dataclass
|
|
class CapTableEntry:
|
|
"""A row in the cap table at a point in time."""
|
|
name: str
|
|
share_class: str
|
|
shares: float
|
|
pct_ownership: float
|
|
invested: float
|
|
is_option_pool: bool = False
|
|
|
|
|
|
@dataclass
|
|
class RoundResult:
|
|
"""Snapshot of cap table after a round closes."""
|
|
round_name: str
|
|
pre_money_valuation: float
|
|
investment_amount: float
|
|
post_money_valuation: float
|
|
price_per_share: float
|
|
new_shares_issued: float
|
|
option_pool_shares_created: float
|
|
total_shares: float
|
|
cap_table: list[CapTableEntry]
|
|
|
|
|
|
@dataclass
|
|
class ExitAnalysis:
|
|
"""Proceeds to each shareholder at an exit."""
|
|
exit_valuation: float
|
|
shareholder: str
|
|
shares: float
|
|
ownership_pct: float
|
|
proceeds_common: float # if all preferred converts to common
|
|
invested: float
|
|
moic: float # multiple on invested capital (for investors)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Core cap table engine
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class CapTable:
|
|
"""Manages a cap table through multiple rounds."""
|
|
|
|
def __init__(self):
|
|
self.shareholders: list[Shareholder] = []
|
|
self._total_shares: float = 0.0
|
|
|
|
def add_shareholder(self, sh: Shareholder) -> None:
|
|
self.shareholders.append(sh)
|
|
self._total_shares += sh.shares
|
|
|
|
def total_shares(self) -> float:
|
|
return sum(s.shares for s in self.shareholders)
|
|
|
|
def snapshot(self, label: str = "") -> list[CapTableEntry]:
|
|
total = self.total_shares()
|
|
return [
|
|
CapTableEntry(
|
|
name=s.name,
|
|
share_class=s.share_class,
|
|
shares=s.shares,
|
|
pct_ownership=s.shares / total if total > 0 else 0,
|
|
invested=s.invested,
|
|
is_option_pool=s.is_option_pool,
|
|
)
|
|
for s in self.shareholders
|
|
]
|
|
|
|
def execute_round(self, config: RoundConfig) -> RoundResult:
|
|
"""
|
|
Execute a financing round:
|
|
1. (Optional) Create option pool pre-round (dilutes existing shareholders)
|
|
2. Issue new shares to investor at round price
|
|
Returns a RoundResult with full cap table snapshot.
|
|
"""
|
|
current_total = self.total_shares()
|
|
|
|
# Step 1: Option pool shuffle (if pre-round)
|
|
option_pool_shares_created = 0.0
|
|
if config.new_option_pool_pct > 0 and config.option_pool_pre_round:
|
|
# Target: post-round option pool = new_option_pool_pct of total post-money shares
|
|
# Solve: pool_shares / (current_total + pool_shares + new_investor_shares) = target_pct
|
|
# This requires iteration because new_investor_shares also depends on pool_shares
|
|
# Simplification: create pool based on post-round total (slightly approximated)
|
|
target_post_round_pct = config.new_option_pool_pct
|
|
post_money = config.pre_money_valuation + config.investment_amount
|
|
|
|
# Estimate shares per dollar (price per share)
|
|
price_per_share = config.pre_money_valuation / current_total
|
|
new_investor_shares_estimate = config.investment_amount / price_per_share
|
|
|
|
# Pool shares needed so that pool / total_post = target_pct
|
|
total_post_estimate = current_total + new_investor_shares_estimate
|
|
pool_shares_needed = (target_post_round_pct * total_post_estimate) / (1 - target_post_round_pct)
|
|
|
|
# Check if existing pool is sufficient
|
|
existing_pool = next(
|
|
(s.shares for s in self.shareholders if s.is_option_pool), 0
|
|
)
|
|
additional_pool_needed = max(0, pool_shares_needed - existing_pool)
|
|
|
|
if additional_pool_needed > 0:
|
|
option_pool_shares_created = additional_pool_needed
|
|
# Add to existing pool or create new
|
|
pool_sh = next((s for s in self.shareholders if s.is_option_pool), None)
|
|
if pool_sh:
|
|
pool_sh.shares += additional_pool_needed
|
|
else:
|
|
self.shareholders.append(Shareholder(
|
|
name="Option Pool",
|
|
share_class="option",
|
|
shares=additional_pool_needed,
|
|
is_option_pool=True,
|
|
))
|
|
|
|
# Step 2: Price per share (after pool creation)
|
|
current_total_post_pool = self.total_shares()
|
|
if config.share_price_override:
|
|
price_per_share = config.share_price_override
|
|
else:
|
|
price_per_share = config.pre_money_valuation / current_total_post_pool
|
|
|
|
# Step 3: New shares for investor
|
|
new_shares = config.investment_amount / price_per_share
|
|
|
|
# Step 4: Add investor to cap table
|
|
self.shareholders.append(Shareholder(
|
|
name=config.lead_investor_name,
|
|
share_class="preferred",
|
|
shares=new_shares,
|
|
invested=config.investment_amount,
|
|
))
|
|
|
|
post_money = config.pre_money_valuation + config.investment_amount
|
|
total_post = self.total_shares()
|
|
|
|
return RoundResult(
|
|
round_name=config.name,
|
|
pre_money_valuation=config.pre_money_valuation,
|
|
investment_amount=config.investment_amount,
|
|
post_money_valuation=post_money,
|
|
price_per_share=price_per_share,
|
|
new_shares_issued=new_shares,
|
|
option_pool_shares_created=option_pool_shares_created,
|
|
total_shares=total_post,
|
|
cap_table=self.snapshot(),
|
|
)
|
|
|
|
def analyze_exit(self, exit_valuation: float) -> list[ExitAnalysis]:
|
|
"""
|
|
Simple exit analysis: all preferred converts to common, proceeds split pro-rata.
|
|
(Does not model liquidation preferences — see fundraising_playbook.md for that.)
|
|
"""
|
|
total = self.total_shares()
|
|
price_per_share = exit_valuation / total
|
|
results = []
|
|
for s in self.shareholders:
|
|
if s.is_option_pool:
|
|
continue # unissued options don't receive proceeds
|
|
proceeds = s.shares * price_per_share
|
|
moic = proceeds / s.invested if s.invested > 0 else 0.0
|
|
results.append(ExitAnalysis(
|
|
exit_valuation=exit_valuation,
|
|
shareholder=s.name,
|
|
shares=s.shares,
|
|
ownership_pct=s.shares / total,
|
|
proceeds_common=proceeds,
|
|
invested=s.invested,
|
|
moic=moic,
|
|
))
|
|
return sorted(results, key=lambda x: x.proceeds_common, reverse=True)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Reporting
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def fmt(value: float, prefix: str = "$") -> str:
|
|
if value == float("inf"):
|
|
return "∞"
|
|
if abs(value) >= 1_000_000:
|
|
return f"{prefix}{value/1_000_000:.2f}M"
|
|
if abs(value) >= 1_000:
|
|
return f"{prefix}{value/1_000:.0f}K"
|
|
return f"{prefix}{value:.2f}"
|
|
|
|
|
|
def print_round_result(result: RoundResult, prev_cap_table: Optional[list[CapTableEntry]] = None) -> None:
|
|
print(f"\n{'='*70}")
|
|
print(f" {result.round_name.upper()}")
|
|
print(f"{'='*70}")
|
|
print(f" Pre-money valuation: {fmt(result.pre_money_valuation)}")
|
|
print(f" Investment: {fmt(result.investment_amount)}")
|
|
print(f" Post-money valuation: {fmt(result.post_money_valuation)}")
|
|
print(f" Price per share: {fmt(result.price_per_share, '$')}")
|
|
print(f" New shares issued: {result.new_shares_issued:,.0f}")
|
|
if result.option_pool_shares_created > 0:
|
|
print(f" Option pool created: {result.option_pool_shares_created:,.0f} shares")
|
|
print(f" ⚠️ Pool created pre-round: dilutes existing shareholders, not new investor")
|
|
print(f" Total shares post: {result.total_shares:,.0f}")
|
|
|
|
print(f"\n {'Shareholder':<22} {'Shares':>12} {'Ownership':>10} {'Invested':>10} {'Δ Ownership':>12}")
|
|
print(" " + "-"*68)
|
|
|
|
prev_map = {e.name: e.pct_ownership for e in prev_cap_table} if prev_cap_table else {}
|
|
|
|
for entry in result.cap_table:
|
|
delta = ""
|
|
if entry.name in prev_map:
|
|
change = (entry.pct_ownership - prev_map[entry.name]) * 100
|
|
delta = f"{change:+.1f}pp"
|
|
elif not entry.is_option_pool:
|
|
delta = "new"
|
|
|
|
invested_str = fmt(entry.invested) if entry.invested > 0 else "-"
|
|
print(
|
|
f" {entry.name:<22} {entry.shares:>12,.0f} "
|
|
f"{entry.pct_ownership*100:>9.2f}% {invested_str:>10} {delta:>12}"
|
|
)
|
|
|
|
|
|
def print_exit_analysis(results: list[ExitAnalysis], exit_valuation: float) -> None:
|
|
print(f"\n{'='*70}")
|
|
print(f" EXIT ANALYSIS @ {fmt(exit_valuation)} (all preferred converts to common)")
|
|
print(f"{'='*70}")
|
|
print(f"\n {'Shareholder':<22} {'Ownership':>10} {'Proceeds':>12} {'Invested':>10} {'MOIC':>8}")
|
|
print(" " + "-"*65)
|
|
for r in results:
|
|
moic_str = f"{r.moic:.1f}x" if r.moic > 0 else "n/a"
|
|
invested_str = fmt(r.invested) if r.invested > 0 else "-"
|
|
print(
|
|
f" {r.shareholder:<22} {r.ownership_pct*100:>9.2f}% "
|
|
f"{fmt(r.proceeds_common):>12} {invested_str:>10} {moic_str:>8}"
|
|
)
|
|
print(f"\n Note: Does not model liquidation preferences.")
|
|
print(f" Participating preferred reduces founder proceeds in most real exits.")
|
|
print(f" See references/fundraising_playbook.md for full liquidation waterfall.")
|
|
|
|
|
|
def print_dilution_summary(rounds: list[RoundResult]) -> None:
|
|
print(f"\n{'='*70}")
|
|
print(f" DILUTION SUMMARY — FOUNDER PERSPECTIVE")
|
|
print(f"{'='*70}")
|
|
|
|
# Find all founders (common shareholders who aren't investors or option pool)
|
|
founder_names = []
|
|
for entry in rounds[0].cap_table:
|
|
if entry.share_class == "common" and not entry.is_option_pool:
|
|
founder_names.append(entry.name)
|
|
|
|
if not founder_names:
|
|
print(" No common shareholders found in initial cap table.")
|
|
return
|
|
|
|
header = f" {'Round':<16}" + "".join(f" {n:<16}" for n in founder_names) + f" {'Total Inv':>12}"
|
|
print(header)
|
|
print(" " + "-" * (16 + 18 * len(founder_names) + 14))
|
|
|
|
for result in rounds:
|
|
cap_map = {e.name: e for e in result.cap_table}
|
|
total_invested = sum(e.invested for e in result.cap_table if not e.is_option_pool)
|
|
row = f" {result.round_name:<16}"
|
|
for name in founder_names:
|
|
pct = cap_map[name].pct_ownership * 100 if name in cap_map else 0
|
|
row += f" {pct:>6.2f}% "
|
|
row += f" {fmt(total_invested):>12}"
|
|
print(row)
|
|
|
|
|
|
def export_csv_rounds(rounds: list[RoundResult]) -> str:
|
|
buf = io.StringIO()
|
|
writer = csv.writer(buf)
|
|
writer.writerow(["Round", "Shareholder", "Share Class", "Shares", "Ownership Pct",
|
|
"Invested", "Pre Money", "Post Money", "Price Per Share"])
|
|
for r in rounds:
|
|
for entry in r.cap_table:
|
|
writer.writerow([
|
|
r.round_name, entry.name, entry.share_class,
|
|
round(entry.shares, 0), round(entry.pct_ownership * 100, 4),
|
|
round(entry.invested, 2), round(r.pre_money_valuation, 0),
|
|
round(r.post_money_valuation, 0), round(r.price_per_share, 4),
|
|
])
|
|
return buf.getvalue()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Sample data: typical two-founder Series A/B/C startup
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def build_sample_model() -> tuple[CapTable, list[RoundResult]]:
|
|
"""
|
|
Sample company:
|
|
- 2 founders, started with 10M shares each
|
|
- 1M shares for early advisor
|
|
- Raises Pre-seed → Seed → Series A → Series B → Series C
|
|
"""
|
|
cap = CapTable()
|
|
SHARES_PER_FOUNDER = 4_000_000
|
|
SHARES_ADVISOR = 200_000
|
|
|
|
# Founding state
|
|
cap.add_shareholder(Shareholder("Founder A (CEO)", "common", SHARES_PER_FOUNDER))
|
|
cap.add_shareholder(Shareholder("Founder B (CTO)", "common", SHARES_PER_FOUNDER))
|
|
cap.add_shareholder(Shareholder("Advisor", "common", SHARES_ADVISOR))
|
|
|
|
rounds: list[RoundResult] = []
|
|
prev_cap = cap.snapshot()
|
|
|
|
# Round 1: Pre-seed — $500K at $4.5M pre, 10% option pool created
|
|
r1 = cap.execute_round(RoundConfig(
|
|
name="Pre-seed",
|
|
pre_money_valuation=4_500_000,
|
|
investment_amount=500_000,
|
|
new_option_pool_pct=0.10,
|
|
option_pool_pre_round=True,
|
|
lead_investor_name="Angel Syndicate",
|
|
))
|
|
rounds.append(r1)
|
|
prev_r1 = r1.cap_table[:]
|
|
|
|
# Round 2: Seed — $2M at $9M pre, expand option pool to 12%
|
|
r2 = cap.execute_round(RoundConfig(
|
|
name="Seed",
|
|
pre_money_valuation=9_000_000,
|
|
investment_amount=2_000_000,
|
|
new_option_pool_pct=0.12,
|
|
option_pool_pre_round=True,
|
|
lead_investor_name="Seed Fund",
|
|
))
|
|
rounds.append(r2)
|
|
|
|
# Round 3: Series A — $12M at $38M pre, refresh option pool to 15%
|
|
r3 = cap.execute_round(RoundConfig(
|
|
name="Series A",
|
|
pre_money_valuation=38_000_000,
|
|
investment_amount=12_000_000,
|
|
new_option_pool_pct=0.15,
|
|
option_pool_pre_round=True,
|
|
lead_investor_name="Series A Fund",
|
|
))
|
|
rounds.append(r3)
|
|
|
|
# Round 4: Series B — $25M at $95M pre, refresh pool to 12%
|
|
r4 = cap.execute_round(RoundConfig(
|
|
name="Series B",
|
|
pre_money_valuation=95_000_000,
|
|
investment_amount=25_000_000,
|
|
new_option_pool_pct=0.12,
|
|
option_pool_pre_round=True,
|
|
lead_investor_name="Series B Fund",
|
|
))
|
|
rounds.append(r4)
|
|
|
|
# Round 5: Series C — $40M at $185M pre, refresh pool to 10%
|
|
r5 = cap.execute_round(RoundConfig(
|
|
name="Series C",
|
|
pre_money_valuation=185_000_000,
|
|
investment_amount=40_000_000,
|
|
new_option_pool_pct=0.10,
|
|
option_pool_pre_round=True,
|
|
lead_investor_name="Series C Fund",
|
|
))
|
|
rounds.append(r5)
|
|
|
|
return cap, rounds
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Entry point
|
|
# ---------------------------------------------------------------------------
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description="Fundraising Model — Cap Table & Dilution")
|
|
parser.add_argument("--exit", type=float, default=250.0,
|
|
help="Exit valuation in $M for return analysis (default: 250)")
|
|
parser.add_argument("--csv", action="store_true", help="Export round data as CSV to stdout")
|
|
args = parser.parse_args()
|
|
|
|
exit_valuation = args.exit * 1_000_000
|
|
|
|
print("\n" + "="*70)
|
|
print(" FUNDRAISING MODEL — CAP TABLE & DILUTION ANALYSIS")
|
|
print(" Sample Company: Two-founder SaaS startup")
|
|
print(" Pre-seed → Seed → Series A → Series B → Series C")
|
|
print("="*70)
|
|
|
|
cap, rounds = build_sample_model()
|
|
|
|
# Print each round
|
|
prev = None
|
|
for r in rounds:
|
|
print_round_result(r, prev)
|
|
prev = r.cap_table
|
|
|
|
# Dilution summary table
|
|
print_dilution_summary(rounds)
|
|
|
|
# Exit analysis at specified valuation
|
|
exit_results = cap.analyze_exit(exit_valuation)
|
|
print_exit_analysis(exit_results, exit_valuation)
|
|
|
|
# Also print at 2x and 5x for sensitivity
|
|
print("\n Exit Sensitivity — Founder A Proceeds:")
|
|
print(f" {'Exit Valuation':<20} {'Founder A %':>12} {'Founder A $':>14} {'MOIC':>8}")
|
|
print(" " + "-"*56)
|
|
for mult in [0.5, 1.0, 1.5, 2.0, 3.0, 5.0]:
|
|
val = rounds[-1].post_money_valuation * mult
|
|
ex = cap.analyze_exit(val)
|
|
founder_a = next((r for r in ex if r.shareholder == "Founder A (CEO)"), None)
|
|
if founder_a:
|
|
print(f" {fmt(val):<20} {founder_a.ownership_pct*100:>11.2f}% "
|
|
f"{fmt(founder_a.proceeds_common):>14} {'n/a':>8}")
|
|
|
|
print("\n Key Takeaways:")
|
|
final = rounds[-1].cap_table
|
|
total = sum(e.shares for e in final)
|
|
founder_a_final = next((e for e in final if e.name == "Founder A (CEO)"), None)
|
|
if founder_a_final:
|
|
print(f" Founder A final ownership: {founder_a_final.pct_ownership*100:.2f}%")
|
|
total_raised = sum(e.invested for e in final)
|
|
print(f" Total capital raised: {fmt(total_raised)}")
|
|
print(f" Total shares outstanding: {total:,.0f}")
|
|
print(f" Final post-money: {fmt(rounds[-1].post_money_valuation)}")
|
|
print("\n Run with --exit <$M> to model proceeds at different exit valuations.")
|
|
print(" Example: python fundraising_model.py --exit 500")
|
|
|
|
if args.csv:
|
|
print("\n\n--- CSV EXPORT ---\n")
|
|
sys.stdout.write(export_csv_rounds(rounds))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|