import { sdk } from '../sdk' import { configFile } from '../file-models/config' const { InputSpec, Value, List } = sdk const inputSpec = InputSpec.of({ companies: Value.list( List.obj( { name: 'Portfolio Companies', description: 'One entry per portfolio company. The SLUG is the folder you drop its ' + 'decks into (inbox//2026-Q2-deck.pdf) and the key its running ' + 'scorecard ledger lives under. Pinned targets are the KPIs the scorer ' + 'holds the company to even when a deck goes quiet about them — pin the ' + 'profitability thresholds especially, since they carry the heaviest ' + 'weight (30 of the quant 60).', default: [], minLength: 0, maxLength: 64, }, { uniqueBy: 'slug', displayAs: '{{slug}}', spec: InputSpec.of({ slug: Value.text({ name: 'Slug', description: 'Folder name under the inbox and the ledger key. Stable — do not ' + 'rename once decks have been graded.', required: true, default: null, placeholder: 'acme-widgets', patterns: [ { regex: '^[a-z0-9][a-z0-9-]{0,40}$', description: 'Lowercase letters, numbers, dashes (max 41 chars).' }, ], }), name: Value.text({ name: 'Display Name (optional)', description: 'Shown on the dashboard and scorecards. Empty = the slug.', required: false, default: null, placeholder: 'Acme Widgets, Inc.', }), kpiAliases: Value.textarea({ name: 'KPI Aliases (optional)', description: 'One line per KPI: canonical=alias1;alias2 — maps the names a deck ' + 'uses onto the canonical KPI name, so "Adj. EBITDA" and "EBITDA ' + '(adj)" both land on the same pinned target.', required: false, default: null, minRows: 2, maxRows: 12, placeholder: 'ebitda=Adj. EBITDA;EBITDA (adj)\nmrr=Monthly Recurring Revenue;MRR', }), pinnedTargets: Value.list( List.obj( { name: 'Pinned KPI Targets', description: 'Targets graded every quarter regardless of what the deck ' + 'chooses to show. Mark profitability KPIs (EBITDA, net margin, ' + 'FCF...) so they score in the heavier profitability bucket.', default: [], minLength: 0, maxLength: 32, }, { uniqueBy: 'kpi', displayAs: '{{kpi}} → {{target}}', spec: InputSpec.of({ kpi: Value.text({ name: 'KPI Name', description: 'Canonical KPI name (see the aliases field above).', required: true, default: null, placeholder: 'ebitda', }), target: Value.number({ name: 'Target', description: 'The numeric target for the period.', required: true, default: null, integer: false, }), unit: Value.text({ name: 'Unit (optional)', description: 'For display only, e.g. "USD", "%", "customers".', required: false, default: null, placeholder: 'USD', }), direction: Value.select({ name: 'Direction', description: 'Whether hitting the target means being at-or-above it (revenue) or at-or-below it (churn, burn).', default: 'gte', values: { gte: 'At or above target', lte: 'At or below target', }, }), profitability: Value.toggle({ name: 'Profitability KPI', description: 'Score this KPI in the profitability bucket (heaviest weight) instead of the general KPI bucket.', default: false, }), }), }, ), ), }), }, ), ), }) export const configureCompanies = sdk.Action.withInput( 'configure-companies', async ({ effects }) => ({ name: 'Configure Companies', description: 'Define the portfolio companies, their inbox slugs, KPI aliases, and pinned KPI targets.', warning: null, allowedStatuses: 'any', group: 'Grading', visibility: 'enabled', }), inputSpec, async ({ effects }) => { const cfg = await configFile.read().const(effects) if (!cfg) return {} return { companies: cfg.companies.map((c) => ({ slug: c.slug, name: c.name || undefined, kpiAliases: c.kpiAliases || undefined, pinnedTargets: c.pinnedTargets.map((t) => ({ kpi: t.kpi, target: t.target, unit: t.unit || undefined, direction: t.direction, profitability: t.profitability, })), })), } }, async ({ effects, input }) => { await configFile.merge(effects, { companies: input.companies.map((c) => ({ slug: c.slug, name: c.name ?? '', kpiAliases: c.kpiAliases ?? '', pinnedTargets: c.pinnedTargets.map((t) => ({ kpi: t.kpi, target: t.target, unit: t.unit ?? '', direction: t.direction, profitability: t.profitability, })), })), }) return { version: '1', title: 'Companies Configured', message: 'Saved ' + input.companies.length + ' company(ies). Drop each company\'s ' + 'decks into inbox// (e.g. inbox/' + (input.companies[0]?.slug || 'acme-widgets') + '/2026-Q2-deck.pdf), then run "Grade Decks".', result: { type: 'single', value: input.companies.map((c) => c.slug).join(', ') || '(none)', copyable: false, qr: false, masked: false, }, } }, )