From carta-investors
Pull a ManCo budget from Carta via MCP and write to an Excel workbook with monthly amounts and subtotals.
How this skill is triggered — by the user, by Claude, or both
Slash command
/carta-investors:carta-fetch-budgetopusThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
[PATTERN carta-writing-style v0.0.2]
[PATTERN carta-writing-style v0.0.2] [PATTERN etiquette v0.0.6] [PATTERN text v0.0.8] [PATTERN tables v0.0.12] [PATTERN carta-watermark v0.0.10] [PATTERN base v0.1.0]
Pulls the budget for a management company directly from Carta via the
fa:list:budgets MCP command and lays it out as a single budget tab in
Excel. Only management companies carry budgets in Carta — funds and
SPVs do not — so the entity picker always lists the ManCo first.
Inlined here because the Carta CLI session-start hook does not run inside Claude for Excel. Audience is an accountant.
fa:list:budgets), UUIDs, raw JSON, or gate labels.X,XXX, negatives (X,XXX), totals
bolded — using the resolved currency's symbol, not always $. The workbook uses the
locale-specific token for the resolved presentation currency, which locks the
display to that currency regardless of the user's Excel locale. Derive the currency
from the data — never default to USD (see Hard rules).<citation:Sheet!Range>) in Claude for Excel mode, and a file://
path in Claude Code / Cowork mode. Never both.AskUserQuestion. Never
render the options as a bare code-fenced markdown list. The
AskUserQuestion tool is in allowed-tools; use it.Trigger this skill when the user asks for any of the following:
<ManCo>'s <year> budget from Carta"<ManCo> from Carta MCP"carta-create-budget.carta-budget-actuals.carta-budget-vs-actuals.carta-budget-scenarios.carta-consolidating-pnl. (If the user is
building a P&L and wants the Budget columns filled with Carta data,
carta-consolidating-pnl already integrates this skill's fetch logic in its
Gate 9 — do not duplicate the workflow.)Execute all gates silently. Do not narrate tool calls, intermediate results, or status updates. Only speak at explicit decision points: Gate 0.5 (if runtime is ambiguous), Gate 1 (destination chooser), Gate 2 (entity picker — if entity not already in context), Gate 3 (period picker), Gate 5 (approval), and Gate 7 (next-step menu).
Mandatory telemetry beacon — run this before any skip-gate check: If <SERVER> and <ENTITY_UUID> are already in context, call mcp__<SERVER>__set_context(firm_id=<ENTITY_UUID>, _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}) as your very first action. Do not skip this even when all data is already available — it records this skill invocation. If either is unknown, it fires in Gate 0 below.
Before Gate 0, check whether these context variables are already set from an earlier budgeting skill call in the same session:
<SERVER> — connected Carta MCP server prefix<ENTITY_NAME> and <ENTITY_UUID> — the resolved ManCo entity<RUNTIME> — excel-addin or local-fileIf all four are in context: skip Gates 0 and 0.5 entirely, and skip Gate 2 (<ENTITY_UUID> is already locked). Then run the remaining gates in full — do not stop after the period picker: Gate 1 (destination) → Gate 3 (period picker) → Gate 4 (fetch budget from Carta) → Gate 5 (pre-build review) → Gate 6 (write and brand) → Gate 7 (summary). Skipping Gates 4–7 would write empty or stale data without re-fetching.
If any is missing (fresh session or cold invocation): run Gates 0, 0.5, and 2 in order, then continue from Gate 1.
Do not ask "which firm?" when the entity is already established from the skill the user just ran.
refresh_mcp_connectors. Filter servers[] to name matching Carta / Carta (…) / carta with status: "connected". Drop failed.mcp__claude_ai_Carta__welcome(_instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}) and mcp__carta__welcome(_instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}). First success = <SERVER>.mcp__<SERVER>__* tool before welcome — every command is gated.If none connected, list failed connectors and stop. If multiple, default to Carta (production).
Resolve firm: if user named one → mcp__<SERVER>__list_contexts(firm_name="<firm>", _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}) → disambiguate via AskUserQuestion if multiple → mcp__<SERVER>__set_context(firm_id=<FIRM_UUID>, _instrumentation={"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}). Do not use call_tool for list_contexts or set_context — call the granular tools directly with _instrumentation as shown.
DWH param-name traps: dwh:execute:query takes sql: not query:. dwh:get:table_schema takes table_name: not table:. format accepts "ndjson" / "markdown", not "csv".
If no firm was named, defer to Gate 2.
This skill runs in two runtimes. Detect which one applies before asking where to write:
.xlsx files on disk.See carta-create-budget/SKILL.md Gate 0.5 for the heuristic. If unclear, ask
via AskUserQuestion. Store <RUNTIME> (excel-addin or local-file).
Branches by <RUNTIME>. Before showing any chooser, scan the
destination for an existing budget tab — if one is found, lead with
"update in place" instead of defaulting to a new tab.
A sheet counts as an "existing budget tab" when either is true:
Budget (case-insensitive), e.g.
Budget FY2026, 2026 Budget, MgmtCo Budget.Account in a label
column AND at least 6 month-like headers (Jan, Jan 2026,
2026-01, etc.) in the same row.Stop at the first match — the user can always pick another tab via the "choose a different tab" branch.
If <RUNTIME> is excel-addin:
<EXISTING_BUDGET_TABS>.<EXISTING_TAB_DATA[tab_name]>. Gate 5's
update-in-place matching uses this cached read — no second workbook
read is needed later.If <RUNTIME> is local-file:
Only scan if the user already supplied a workbook path in their prompt (otherwise there's nothing to scan yet — defer detection to the file they pick next).
uv run "${CLAUDE_PLUGIN_ROOT}/scripts/read_workbook.py" \
"<PATH>"
Apply the same heuristic to the JSON output. For each matched tab, store
its full content (all rows) from the read_workbook.py output as
<EXISTING_TAB_DATA[tab_name]> — Gate 5's update-in-place matching reuses
this cached read exactly as in the add-in path; no second read is needed.
If <EXISTING_BUDGET_TABS> is non-empty (excel-addin or local-file with a path):
Use AskUserQuestion:
I see a budget tab already in your workbook (
<existing tab name>). Want me to update it with Carta's data, or write the budget somewhere else?
| # | Option | What happens |
|---|---|---|
| 1 | Update <existing tab name> in place ← recommended | Refreshes the budget values on the existing tab — same accounts, same row positions; no new tab is created. |
| 2 | Add a new tab | Creates Budget FY<year> alongside the existing tab. |
| 3 | Pick a different existing tab to update | Lists all sheets in the workbook and asks which one. |
| 4 | Cancel | Stops the skill. |
If multiple existing budget tabs are found, list up to 3 of them as
option 1a / 1b / 1c (inside the same AskUserQuestion call) — never
silently pick one.
Update-in-place semantics (option 1):
<EXISTING_TAB_DATA[tab_name]> already read at Gate 1 — do not
re-read the workbook. Identify the label column (the column that
holds account names) and the month columns from that cached data. Treat
any cell where is_formula: true as load-bearing — subtotal / Net
Operating Income / FY-total rows stay formula-driven and are never
overwritten.gl_code or account_name) row already in the
sheet, write the new monthly budget values into the existing month
cells. The tab's row positions, section headers, formulas, and
formatting all stay put.Source: Carta Fund Admin (refreshed <ISO date>) so the user can see
when the values were last pulled.If <EXISTING_BUDGET_TABS> is empty:
Excel add-in runtime — empty-workbook shortcut: if the active workbook has one sheet, maxRows == 0, no other tabs (typically a fresh Book1.xlsx/Sheet1), skip the chooser. Announce the rename in one sentence — "I'll use the empty workbook you have open and rename Sheet1 to Budget FY<year>." — then proceed. The chooser only exists to protect non-empty state.
Otherwise, use AskUserQuestion with the default options:
Excel add-in runtime:
Where should I put the Carta budget?
Budget FY<year>.Local-file runtime:
Where should I write the budget file?
Budget FY<year>. After loading, re-run the budget-tab detection — if the loaded file has an existing budget tab, jump back to the "update in place" chooser above.If the user gave a path in the original prompt, skip the choice and use that path (but still run the detection on it).
Store <DESTINATION> for Gates 5–7.
Critical: in Carta, only management companies carry budgets. Funds
and SPVs return empty from fa:list:budgets.
Call read_skill(file_path="references/entity-picker.md") before proceeding. Do not reconstruct the picker logic from memory. Summary of the rule:
call_tool({"name": "fa__list__entities", "arguments": {"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}}}) against the active firm.(LLC|Management|Mgmt|ManCo|Capital, LLC) AND with no Fund /
Partners / SPV qualifier.← recommended), then other entities below it, then a final option
"None of these — let me type the name".AskUserQuestion.If the user already named the entity in their prompt and it resolves to exactly one ManCo, skip the picker. Otherwise always ask.
If a non-ManCo is picked, warn before fetching:
"Heads up — only management companies carry a budget in Carta. If I pull
<entity>, the result will likely be empty. Want me to pick the ManCo instead?"
Done when: <ENTITY_NAME> and <ENTITY_UUID> are locked.
In one AskUserQuestion call, ask for the period the prompt didn't
already specify. Offer smart defaults based on today's date —
compute year, half, and quarter labels dynamically, never copy the
example values below.
What period should I pull the budget for?
Example shape — rendered with today's date = May 2026. Substitute <CURRENT_YEAR> and <CURRENT_QUARTER> at runtime.
| # | Label | Date range |
|---|---|---|
| 1 ← recommended | Full year <CURRENT_YEAR> | Jan 1 – Dec 31, <CURRENT_YEAR> |
| 2 | H1 <CURRENT_YEAR> (Jan – Jun) | Jan 1 – Jun 30, <CURRENT_YEAR> |
| 3 | H2 <CURRENT_YEAR> (Jul – Dec) | Jul 1 – Dec 31, <CURRENT_YEAR> |
| 4 | <CURRENT_QUARTER> <CURRENT_YEAR> | (computed from today's date) |
| 5 | Custom range — I'll specify start / end month | — |
Always compute the current quarter label and year dynamically from today's date — never hardcode Q2 / 2026 / etc. Drop the H1 row once the current month is past June (H1 is fully in the past then — fold it into "Custom range"). Show the prior year as an option only if the user mentioned it in their prompt.
If the prompt already specified a year or range (e.g. "2026 budget", "H1"), store it directly and skip the question.
Store <BUDGET_YEAR>, <START_DATE> (<YEAR>-<MM>-01), <END_DATE>
(last day of <end_month>).
No tag breakdown available. The budget data from Carta (fa:list:budgets)
contains no reporting dimension — it returns one amount per account per
month. If the user asks for budget broken down by department, project
code, or any other tag, tell them in one sentence — "Carta's stored budget
isn't broken down by tag, so I can only return flat monthly totals." — and
offer to route them to a different skill via AskUserQuestion (never a
bare numbered list — see the UX Rules at the top of this skill).
Call AskUserQuestion with these exact parameters:
question: "Carta's stored budget has no tag dimension. How should I continue?"header: "Tag breakdown"multiSelect: falseoptions:
label: "Continue with the flat budget pull" — description: "No tag dimension; returns one amount per account per month."label: "Build a new tag-sliced budget from journal actuals instead" — description: "← recommended for tag breakdowns. Hands off to carta-create-budget's slice-by-tag mode; uses last year's actuals as the seed."If the user picks option 2, hand off via Skill('carta-investors:carta-create-budget')
with the slice-by-tag routing — that skill's slice-by-tag mode is the
canonical way to produce a budget broken down by reporting tag.
The ← recommended marker goes inside the description field of option 2, not as a suffix on the label. Bare-text numbered lists break the chooser UI in Claude for Excel and force the user to type the number — same rule as every other choice in this skill.
Call read_skill(file_path="references/fetch-budget-data.md") before issuing any MCP calls. Do not reconstruct the fetch contract from memory. Summary of the contract:
call_tool({"name": "fa__list__budgets", ...}) call per month for
every month in the requested window. For a full-year pull this is
exactly twelve calls (Jan 1–31, Feb 1–28/29, …, Dec 1–31). Issue all
twelve in one parallel batch and merge the row lists. If your runtime
limits concurrent tool calls, batch into two groups of six — but never
serialize further. Do not try a single annual or quarterly window
first — the MCP response truncates past ~40 KB and every wider window
the user has been observed needing has hit that cap.Verbatim call template — do not omit fund_uuid. The MCP rejects
the call with "missing required params: ['fund_uuid']" if the param
isn't passed. <ENTITY_UUID> is the UUID locked at the end of Gate 2.
call_tool({"name": "fa__list__budgets", "arguments": {
"fund_uuid": "<ENTITY_UUID>",
"start_date": "<YYYY-MM-01>",
"end_date": "<YYYY-MM-{28|29|30|31}>",
"_instrumentation": {"plugin": "carta-investors", "skills": ["carta-fetch-budget"]}
}})
The param is named fund_uuid for historical reasons but it accepts any
entity UUID returned by fa:list:entities. Pass the ManCo UUID Gate 2
resolved — never an empty value, never a name, never skip the param.
{account_id, account_name, account_type, amount, start_date} into a {account_type → account_name → {month: amount}}
map. Sum if multiple postings hit the same month + account.account_type ascending.Section mapping (by leading digit of account_type, same convention
as the other budgeting skills):
| Prefix | Section |
|---|---|
4xxx | Income |
5xxx / 6xxx / 7xxx / 8xxx | Expenses |
1xxx | Investments / Other |
| anything else | Other |
If the fetched dataset is empty (no rows): stop and tell the user
plainly — "I didn't find any budget rows in Carta for <ENTITY> for
<BUDGET_YEAR>. Common causes: the entity isn't a ManCo, or no budget
has been loaded into Carta yet for that year." Offer to retry against a
different entity or year.
Done when: the budget dataset is loaded into the
account_type → account_name → {month: amount} map and tagged by
section.
Branches by the Gate 1 write mode (new-tab, overwrite-tab,
new-workbook, or update-in-place).
new-tab, overwrite-tab, new-workbook)Present a plain-English preview before any write:
Ready to write Carta's 2026 budget for
Example Capital, LLC— please review.
- Source: Carta Fund Admin (live)
- Entity: Example Capital, LLC (ManCo)
- Period: Jan 2026 – Dec 2026
- Income accounts: 1
- Expense accounts: 47
- Sheet to write:
Budget FY2026in<DESTINATION>- Projected FY totals: Income 13,788,809 · Expenses 8,530,121 · Net Operating Income 5,258,689
update-in-place)Run the match step first (see Gate 1's "Update-in-place semantics") and classify every row:
Preview shape:
Ready to refresh
Budget FY2026in your open workbook with Carta's 2026 budget forExample Capital, LLC.
- Source: Carta Fund Admin (refreshed live)
- Existing tab:
Budget FY2026- Existing rows to refresh: 41 line items
- Cells to update: ~492 monthly amounts (excluding formula subtotals)
- Carta rows missing from your sheet: 6 line items (would be inserted if you approve)
- Sheet rows missing from Carta: 2 line items (will be left untouched)
Line item Old monthly avg New monthly avg Source Software (7005) $23,000 $25,000 Carta refresh … … … …
Account in Carta but missing from your sheet Section Projected FY Leased employee — guaranteed payments (7051) Operating Expenses $2,675,004 Investor relations — events (7203) Operating Expenses $12,504
If there are Carta rows missing from the sheet, ask via
AskUserQuestion whether to insert them (above the right section
subtotal, per the same logic as
references/fill-budget-columns.md step 4) or
skip them this run.
Output the preview above as a normal conversation message. Then call AskUserQuestion immediately after — the question field must be a single short sentence; never include preview content inside it.
question: "Approve writing this budget?" (Mode A) / "Approve refreshing in place?" (Mode B)header: "Approval"multiSelect: falseoptions:
If Edit, return to the right gate. Wait for explicit OK before writing.
Hard rule: no workbook-write tool (Excel-add-in cell write, execute_office_js that mutates state, write_workbook.py, or any equivalent) runs before this gate's AskUserQuestion returns the user's explicit "Approve and write" choice. If you catch yourself about to call a workbook-write tool without that approval recorded, stop and run this gate first.
Before calling execute_office_js with state-mutating code, setValues, write_workbook.py, or any other workbook-write tool, look back at your tool history. Find the most recent AskUserQuestion you sent. Does its answer literally include "Approve and write" or "Approve and refresh"? If NO, Gate 5 did not pass — send the Gate 5 approval menu now and wait for the explicit answer.
Do not interpret upstream answers as approval. A Gate 2 entity-pick response, a Gate 3 year-range answer, or any prior AskUserQuestion whose answer is not literally "Approve and write" / "Approve and refresh" does NOT clear this gate. Approval is the answer to the specific Gate 5 question — nothing else counts.
execute_office_js calls (excel-addin runtime)The most common failure mode is bundling cell writes + formatting + logo into one writeSheet(...) function — the model writes the cells, returns, and forgets the logo. Do not combine the cell-write call with the brand block in a single office.js block.
execute_office_js. Return.sheet.shapes.addImage(...)).CartaLogo exists.Returning from Call 1 does NOT finish Gate 6. The verification call must come last and must appear in your tool history.
Branches by the Gate 1 write mode. Before any write, call read_skill(file_path="references/branding-and-header.md"). Do not reconstruct the brand block or header band from memory — the file must be in your context before you generate any execute_office_js or write_workbook.py code. It defines the reserved 4-row metadata band (A1 entity / A2 descriptive title like "2026 Budget (from Carta Fund Admin)" / A3 source / A4 other context), Carta logo placement (column E, rows 1–3 height), the blobs.getText("assets/...") asset-loading pattern for Excel add-in (NOT Read), and the cell-comment pattern for any flagged rows. The tab is not "written" until it carries a CartaLogo shape.
This is the no-new-tab path. Apply the matched changes from Gate 5 directly to the existing tab:
write_cell (or the add-in equivalent)
for every (existing row × month) pair where Carta returned a value.
Hardcoded numbers, never formulas. Preserve every other cell on the
tab — labels, GL codes, section headers, formula subtotals, FY total
formulas, formatting.is_formula: true). The tab's =SUM(...) subtotals, Total Income,
Total Expenses, and Net Operating Income re-evaluate automatically.references/fill-budget-columns.md step 4.
After insertion, rewrite the affected section subtotal
=SUM(...) formulas so the new rows are included. Match the
formatting of neighboring rows (currency format, font, no fill).Source: Carta Fund Admin (refreshed <ISO date>).Budget FY<year>.Skip the layout section below — the tab already has its layout. Jump to Gate 7.
new-tab, overwrite-tab, new-workbook)Layout (4-row metadata band per branding-and-header.md):
| Row | Content |
|---|---|
| A1 | <ENTITY_NAME> — bold, size 10 |
| A2 | <BUDGET_YEAR> Budget (from Carta Fund Admin) — bold, size 10 |
| A3 | Source: Carta Fund Admin · fa:list:budgets — italic, size 10 |
| A4 | Amounts in <resolved_currency> — italic, size 10 (derive the currency from the data, never default to USD) |
| Row 5 | blank — breathing room between header band and column headers |
| Row 6 | Column headers starting in column A: Account | Jan <year> | Feb <year> | … | Dec <year> | FY <year> Total — bold, white-on-black, centered. No GL-code / "Account #" column. |
Body — for each section (Income → Expenses → Other), in this order:
Income,
Operating Expenses, Other). No cell borders.account_type.
Column A = account_name, columns B..M = monthly amounts (hardcoded
numbers). The GL code (account_type) is used only to sort and
section the rows — it is not written as a column.=SUM(<section_range>) per monthly column and for column N.After the last section:
Total Income — bold, top thin border, sums all Income subtotals.Total Expenses — bold, top thin + bottom medium border, sums all
Expense subtotals.Net Operating Income — bold, top thin + bottom medium border,
=<Total Income> - <Total Expenses> per monthly column and for column N.
Set numFmt="@" on the column-A label if it contains a slash.Column N = =SUM(B<row>:M<row>) for every account, subtotal, and total row.
Currency format (every numeric cell) — use the locale-specific token for the resolved currency, never a bare $ or "$" (renders in system locale on non-US installs):
[$$-en-US]#,##0.00_);([$$-en-US]#,##0.00);"-"[$€-x-euro2]#,##0.00_);([$€-x-euro2]#,##0.00);"-"[$£-en-GB]#,##0.00_);([$£-en-GB]#,##0.00);"-"[$CA$-en-CA]#,##0.00_);([$CA$-en-CA]#,##0.00);"-"Recalc + column widths (excel-addin): the last statements in the cell-write execute_office_js block, in this order — never a separate call:
context.application.calculationMode = Excel.CalculationMode.automatic;
context.workbook.application.calculate(Excel.CalculationType.full); // else =SUM cells stay 0 → render as "-" until edit+Enter
sheet.getRange("A:A").format.columnWidth = 180; // account-name column — fixed; prevents long names driving it too wide
sheet.getRange("B:N").format.autofitColumns(); // size amounts (B:M) + FY total (N) to REAL values (after recalc → no ####)
await context.sync();
Force the recalc before the autofit: without it the =SUM(...) subtotals / Total / NOI cells sit at 0 and the accounting format shows - (the user then has to edit+Enter each one); and autofitting before the recalc sizes the amount columns to the dash, so the real figures overflow as ####. In local-file mode, add a set_column_width op on column A (width 160), then an autofit_columns op over B:N. Do not call freeze_panes.
Column-width anti-pattern: Do NOT call autofitColumns() on a header-only range like B1:N1 — header rows are often empty at write time, leaving the amount columns too narrow for 5+ digit currency (####). Always autofit the full amount span (B:N) after the data is written. Full recipe: carta-create-budget/references/from-prior-actuals.md §6.
If <RUNTIME> is excel-addin: write via the Excel add-in's
runtime cell-write tools, applying the same number format.
If <RUNTIME> is local-file: build a JSON operations payload and
apply it:
uv run "${CLAUDE_PLUGIN_ROOT}/scripts/write_workbook.py" --stdin <<'JSON'
{
"workbook_path": "<DESTINATION>",
"create_if_missing": true,
"operations": [ ... ]
}
JSON
After running the brand block from branding-and-header.md, run this verification as a separate execute_office_js call before proceeding to Gate 7:
const tabs = ["<TAB_NAME_WRITTEN_THIS_RUN>"]; // substitute the actual tab name(s)
const result = {};
for (const tabName of tabs) {
const sheet = context.workbook.worksheets.getItem(tabName);
sheet.shapes.load("items/name");
await context.sync();
result[tabName] = sheet.shapes.items.map(s => s.name);
}
return result;
The result must show CartaLogo in every tab's shape list. If any tab returns [] or its shape list lacks CartaLogo, you have skipped the brand block for that tab — re-run it and re-verify. Do not start Gate 7 summary text until this verification returns CartaLogo on every tab. The verification call is observable evidence; without it in your tool history, Gate 6 is not complete.
Gate 7 precondition (DO NOT SKIP). Before sending the summary text below, scan your tool history. Three anchors MUST be present in that order:
AskUserQuestion whose answer included "Approve and write" or "Approve and refresh" — Gate 5 approval.sheet.shapes.addImage(base64) call for the tab the skill wrote/modified — Gate 6 branding.execute_office_js whose result showed CartaLogo on the tab — Gate 6 verification.If any anchor is missing, you have skipped a gate. Do NOT write "Carta logo placed at..." in the summary when no shapes.addImage call appears in your tool history — that's hallucinating completion. STOP, go back, run the missing gate, then return here.
One or two sentences confirming what got written. Wording branches on the Gate 1 write mode.
If <RUNTIME> is excel-addin:
Wrote Budget FY2026 for Example Capital, LLC — 1 income account, 47 expense accounts, source Carta Fund Admin (live). FY total: Income 13,788,809 · Expenses 8,530,121 · Net Operating Income 5,258,689.
If <RUNTIME> is local-file:
Wrote
Budget FY2026for Example Capital, LLC tofile:///path/to/budget.xlsx— 1 income account, 47 expense accounts, source Carta Fund Admin (live). FY total: Income 13,788,809 · Expenses 8,530,121 · Net Operating Income 5,258,689.
If <RUNTIME> is excel-addin:
Refreshed Budget FY2026 in place with Carta's 2026 budget for Example Capital, LLC — 492 cells updated across 41 line items, 6 new rows inserted (Operating Expenses), 2 sheet rows left untouched (no Carta budget for those accounts). Refreshed FY total: Income 13,788,809 · Expenses 8,530,121 · Net Operating Income 5,258,689.
If <RUNTIME> is local-file:
Refreshed
Budget FY2026infile:///path/to/budget.xlsxwith Carta's 2026 budget for Example Capital, LLC — 492 cells updated, 6 new rows inserted, 2 sheet rows left untouched. Refreshed FY total: Income 13,788,809 · Expenses 8,530,121 · Net Operating Income 5,258,689.
The next-step menu MUST be a single AskUserQuestion call with the options below as options entries. Never render them as a numbered markdown list, a bulleted list, or inline prose — bare-text menus break the chooser UI in Claude for Excel and force the user to type the number. The ← recommended marker goes inside the description field of one option, not as a suffix on the label.
Call AskUserQuestion with these exact parameters:
question: "What would you like to do next?"header: "Next step"multiSelect: falseoptions: the four label + description pairs above (place ← recommended in the description field of the recommended option, NOT in the label)DO NOT render the menu as inline markdown text, a numbered list, a bulleted list, or closing prose. If your response is about to contain 1. ..., 2. ..., 3. ..., 4. ... as a list at the end of the summary instead of an AskUserQuestion tool call, you have failed this gate — back up and invoke the tool.
When the user selects an option, immediately invoke the corresponding skill via Skill('<skill-name>') BEFORE doing any work. Do not freelance the output — load the downstream skill's SKILL.md so its gates, layout spec, branding rules, and approval flow apply. Routing:
| Option | Skill to invoke |
|---|---|
| 1 — Build the P&L with this budget pre-filled | Skill('carta-investors:carta-consolidating-pnl') |
| 2 — Refresh actuals against this budget | Skill('carta-investors:carta-budget-actuals') |
| 3 — Run a pacing analysis | Skill('carta-investors:carta-budget-vs-actuals') |
| 4 — I'm done | No invocation; close cleanly |
fa:list:budgets without fund_uuid — MCP rejects with "missing required params: ['fund_uuid']". Pass <ENTITY_UUID> (locked at end of Gate 2) as params["fund_uuid"].fa:list:budgets returned.carta-create-budget.)welcome, or the currency on the budget data); if it can't be resolved, ask the user. The A4 band reads Amounts in <resolved_currency>. Use the locale-specific format token — never a bare $ (renders as system symbol on non-US locales): USD [$$-en-US], EUR [$€-x-euro2], GBP [$£-en-GB], CAD [$CA$-en-CA].Worksheet.position in a separate context.sync(), never in the same statement as worksheets.add(). Create and activate first: const sh = sheets.add(name); sh.activate(); await context.sync();.range.merge(true) discards trailing cells. Insert a new row first.' or use numberFormat: "mmm yyyy" on a real date.style = "Continuous" then weight = "Thin". Never style: "Thin".references/branding-and-header.md for every tab. Rows 1–4 reserved, logo at column E, blobs.getText("assets/...") for asset access.Never auto-retry. Always surface the failure and let the user decide.
contexts:list returns no firm → echo name, ask for spelling. Don't silently near-match.fa:list:entities returns no ManCo → surface full entity list, let user pick. Flag that picked entity may not have a budget.fa:list:budgets returns empty → "No budget rows in Carta for <entity> <year>. Likely the entity isn't a ManCo, or no budget loaded yet." Offer retry.=SUM) → refresh lines, ask whether to recompute subtotals. Default to leaving alone if no reply..xlsx.McpAuthError) → prefix mismatch, NOT auth. Re-run refresh_mcp_connectors, probe matching prefix's welcome. Never tell user to re-auth without verifying.npx claudepluginhub anthropics/claude-plugins-official --plugin carta-investorsBuilds or restructures a fund/ManCo budget workbook in Excel from Carta prior-year actuals. Groups line items into sections with subtotals and applies inflation/contingency buffers.
Generates income statements, balance sheets, cash flows with period-over-period comparisons and variance analysis for monthly/quarterly P&L, budget vs actuals, and GAAP-compliant reporting.
Processes bookkeeper Excel/CSV exports into categorized spending, budget comparisons, variance analysis, runway calculations, and burn rate trends. Useful for month-end financial reviews.