diff --git a/README.md b/README.md index 0312477..af073d9 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,8 @@ From Bash, bootstrap the profile with one command: ``` The bootstrap is safe to rerun. It installs the unpinned top-level Pi package sources, -merges only SecStack-managed package entries and shell-path configuration into the -SecStack profile's `settings.json` and creates a profile-local Python environment. It links the +merges only SecStack-managed package entries, shell-path configuration, and `quietStartup` +preference into the SecStack profile's `settings.json` and creates a profile-local Python environment. It links the profile's `SYSTEM.md` to the installed SecStack package, so `pi update --extensions` updates the research-agent identity and prompt envelope. It does not install coding-task guidance or link global `AGENTS.md` or `APPEND_SYSTEM.md` files into the profile. diff --git a/context/MAP.md b/context/MAP.md index f8130f8..b482163 100644 --- a/context/MAP.md +++ b/context/MAP.md @@ -35,16 +35,16 @@ The bootstrap configures the isolated profile at `~/.pi/secstack-agent`. Its - `agent-browser`. Pi installs and updates each source independently. The bootstrap manages only those package -entries, the profile's Pi-managed shell path, the profile-local Python environment, and a -relative `SYSTEM.md` symlink into the installed SecStack package. The system-prompt-viewer -package is the sole owner of the `/system-context` overlay. SecStack's +entries, the profile's Pi-managed shell path, the `quietStartup` preference, the profile-local +Python environment, and a relative `SYSTEM.md` symlink into the installed SecStack package. The +system-prompt-viewer package is the sole owner of the `/system-context` overlay. SecStack's `restore-tool-envelope` extension restores Pi's live tool snippets and guidelines after `SYSTEM.md` replaces Pi's coding-agent identity. Its `startup-header` extension replaces -Pi's built-in header with a responsive, theme-adaptive SecStack research mark. Coding-task -guidance files are not linked, and the filtered `pi-setup` package contributes no skills, -including `repo-nav` and `bootstrap-docs`. Research runs in the primary Pi context; -no sub-agent package is installed. Authentication, model selection, provider configuration, -sessions, and unrelated settings remain profile-local and untouched. +Pi's built-in header with a responsive, theme-adaptive SecStack research mark and interactive +research pipeline. Coding-task guidance files are not linked, and the filtered `pi-setup` package +contributes no skills, including `repo-nav` and `bootstrap-docs`. Research runs in the primary +Pi context; no sub-agent package is installed. Authentication, model selection, provider +configuration, sessions, and unrelated settings remain profile-local and untouched. ## Skill internals diff --git a/extensions/startup-header.ts b/extensions/startup-header.ts index 1e0f183..bc99cdd 100644 --- a/extensions/startup-header.ts +++ b/extensions/startup-header.ts @@ -4,85 +4,163 @@ import type { } from "@earendil-works/pi-coding-agent"; import { truncateToWidth } from "@earendil-works/pi-tui"; -const WIDE_HEADER_WIDTH = 50; -const COMPACT_HEADER_WIDTH = 38; +const WIDE_HEADER_WIDTH = 42; +const COMPACT_HEADER_WIDTH = 34; function fit(lines: string[], width: number): string[] { return lines.map((line) => truncateToWidth(line, width, "")); } -function renderWide(theme: Theme, width: number): string[] { - const border = (text: string) => theme.fg("borderMuted", text); - const ink = (text: string) => theme.fg("text", text); - const copy = (text: string) => theme.fg("muted", text); +function renderWide(theme: Theme, width: number, showPipeline: boolean): string[] { + const g = (text: string) => theme.fg("success", text); + const gold = (text: string) => theme.fg("warning", text); const mark = (text: string) => theme.fg("accent", text); + const ink = (text: string) => theme.bold(theme.fg("text", text)); + const tree = (text: string) => theme.fg("dim", text); + const copy = (text: string) => theme.fg("muted", text); - return fit( - [ - "", - border("┌─────────────────────┐"), - `${border("│")} ${ink(theme.bold("FORM 10-K · ITEM 7"))} ${border("◢│")}`, - `${border("│")} ${copy("━━━━━━━━━━━━━━━ ")} ${border("│")} ${mark("┏")}`, - `${border("│")} ${copy("━━━━━━━━━━ ")} ${border("│")} ${mark("┃")} ${ink(theme.bold("S E C S T A C K"))}`, - `${border("│")} ${mark("███████████████")} ${border("│")}${mark("━━┫━━")} ${mark(theme.bold("EVIDENCE → JUDGMENT"))}`, - `${border("│")} ${copy("━━━━━━━━━━━━━━━━ ")} ${border("│")} ${mark("┃")}`, - `${border("│")} ${copy("━━━━━━━━━━━ ")} ${border("│")} ${mark("┗")}`, - border("└─────────────────────┘"), - "", - ], - Math.min(width, WIDE_HEADER_WIDTH), - ); + const corner = `${g("[")}${gold("$")}${g("]")}`; + const billTop = `${g("╔═")}${corner}${g("════════════════")}${corner}${g("═╗")}`; + const billMid = `${g("║")} ${ink("SECSTACK")} ${g("║")}`; + const billBot = `${g("╚═")}${corner}${g("════════════════")}${corner}${g("═╝")}`; + + const lines = [ + "", + ` ${mark("▄█▄")}`, + ` ${mark("┌─────┐")} ${billTop}`, + ` ${mark("│ ")}${gold("█ █")}${mark(" │")} ${billMid}`, + ` ${mark("┌┴─────┴┐")} ${billBot}`, + ` ${mark("▀▀▀▀▀▀▀▀▀▀▀")}`, + ]; + + if (showPipeline) { + if (width >= 77) { + lines.push( + "", + ` ${tree("Research Pipeline:")}`, + ` ${tree("├─ ")}${gold("[SIGNALS]")} ${copy("Insider clusters · 13D blocks · Market screens · Themes")}`, + ` ${tree("├─ ")}${theme.fg("mdCode", "[EDGAR]")} ${copy("10-K/Q/8-K · XBRL financials · 13F holders · Proxies")}`, + ` ${tree("├─ ")}${mark("[MARKET]")} ${copy("Live quotes · Peer comps · Trailing returns · Transcripts")}`, + ` ${tree("├─ ")}${theme.fg("mdHeading", "[ANALYSIS]")} ${copy("Normalized FCFF · Reverse DCF / EPV · Moats · Pre-mortem")}`, + ` ${tree("└─ ")}${theme.fg("syntaxType", "[VIC PITCH]")} ${copy("Norbert Lou VIC style · Hard arithmetic · Falsification")}`, + ); + } else { + lines.push( + "", + ` ${tree("Research Pipeline:")}`, + ` ${tree("├─ ")}${gold("[SIGNALS]")} ${copy("Insider clusters · 13Ds · Screens")}`, + ` ${tree("├─ ")}${theme.fg("mdCode", "[EDGAR]")} ${copy("10-K/Q/8-K · XBRL · 13F holders")}`, + ` ${tree("├─ ")}${mark("[MARKET]")} ${copy("Quotes · Peer comps · Transcripts")}`, + ` ${tree("├─ ")}${theme.fg("mdHeading", "[ANALYSIS]")} ${copy("Normalized FCFF · DCF · Pre-mortem")}`, + ` ${tree("└─ ")}${theme.fg("syntaxType", "[VIC PITCH]")} ${copy("Norbert Lou VIC pitch · Math")}`, + ); + } + } + + lines.push(""); + return fit(lines, width); } -function renderCompact(theme: Theme, width: number): string[] { - const border = (text: string) => theme.fg("borderMuted", text); - const ink = (text: string) => theme.fg("text", text); - const copy = (text: string) => theme.fg("muted", text); +function renderCompact(theme: Theme, width: number, showPipeline: boolean): string[] { + const g = (text: string) => theme.fg("success", text); + const gold = (text: string) => theme.fg("warning", text); const mark = (text: string) => theme.fg("accent", text); + const ink = (text: string) => theme.bold(theme.fg("text", text)); + const tree = (text: string) => theme.fg("dim", text); + const copy = (text: string) => theme.fg("muted", text); - return fit( - [ - "", - `${border("┌───────────────┐")} ${ink(theme.bold("SECSTACK"))}`, - `${border("│")} ${ink(theme.bold("10-K · ITEM 7"))} ${border("◢│")}`, - `${border("│")} ${copy("━━━━━━━━━━ ")} ${border("│")} ${mark("┓")}`, - `${border("│")} ${mark("██████████")} ${border("│")}${mark("━━┫")}`, - `${border("│")} ${copy("━━━━━━━━━━━━ ")} ${border("│")} ${mark("┛")}`, - `${border("└───────────────┘")} ${mark("EVIDENCE → JUDGMENT")}`, + const corner = `${g("[")}${gold("$")}${g("]")}`; + const billTop = `${g("╔")}${corner}${g("═══════════")}${corner}${g("╗")}`; + const billMid = `${g("║")} ${ink("SECSTACK")} ${g("║")}`; + const billBot = `${g("╚")}${corner}${g("═══════════")}${corner}${g("╝")}`; + + const lines = [ + "", + ` ${mark("▄█▄")}`, + ` ${mark("┌─────┐")} ${billTop}`, + ` ${mark("│ ")}${gold("█ █")}${mark(" │")} ${billMid}`, + ` ${mark("┌┴─────┴┐")} ${billBot}`, + ` ${mark("▀▀▀▀▀▀▀▀▀▀▀")}`, + ]; + + if (showPipeline) { + lines.push( "", - ], - Math.min(width, COMPACT_HEADER_WIDTH), - ); + ` ${tree("Research Pipeline:")}`, + ` ${tree("├─ ")}${gold("[SIGNALS]")} ${copy("Insiders · 13Ds · Screens")}`, + ` ${tree("├─ ")}${theme.fg("mdCode", "[EDGAR]")} ${copy("10-K/Q · XBRL · 13F")}`, + ` ${tree("├─ ")}${mark("[MARKET]")} ${copy("Quotes · Comps · Calls")}`, + ` ${tree("├─ ")}${theme.fg("mdHeading", "[ANALYSIS]")} ${copy("FCFF · DCF · Moats")}`, + ` ${tree("└─ ")}${theme.fg("syntaxType", "[VIC PITCH]")} ${copy("Norbert Lou pitch")}`, + ); + } + + lines.push(""); + return fit(lines, width); } -function renderNarrow(theme: Theme, width: number): string[] { - const ink = (text: string) => theme.fg("text", text); - const mark = (text: string) => theme.fg("accent", text); +function renderNarrow(theme: Theme, width: number, showPipeline: boolean): string[] { + const g = (text: string) => theme.fg("success", text); + const gold = (text: string) => theme.fg("warning", text); + const ink = (text: string) => theme.bold(theme.fg("text", text)); + const copy = (text: string) => theme.fg("muted", text); + const tree = (text: string) => theme.fg("dim", text); - return fit( - [ - "", - ink(theme.bold("SECSTACK")), - `${mark("▰")} ${ink("10-K · ITEM 7")} ${mark("→")} ${mark(theme.bold("JUDGMENT"))}`, + const corner = `${g("[")}${gold("$")}${g("]")}`; + const lines = [ + "", + `${corner} ${ink("SECSTACK")} ${corner}`, + copy("Bottom-Up Equity Research"), + ]; + + if (showPipeline) { + lines.push( "", - ], - width, - ); + tree("• Signals & Discovery"), + tree("• EDGAR Filings & 13F"), + tree("• Market & Transcripts"), + tree("• Bottom-Up Valuation"), + tree("• Norbert Lou VIC Pitch"), + ); + } + + lines.push(""); + return fit(lines, width); } -export function renderStartupHeader(theme: Theme, width: number): string[] { - if (width >= WIDE_HEADER_WIDTH) return renderWide(theme, width); - if (width >= COMPACT_HEADER_WIDTH) return renderCompact(theme, width); - return renderNarrow(theme, width); +export function renderStartupHeader( + theme: Theme, + width: number, + showPipeline: boolean = true, +): string[] { + if (width >= WIDE_HEADER_WIDTH) return renderWide(theme, width, showPipeline); + if (width >= COMPACT_HEADER_WIDTH) return renderCompact(theme, width, showPipeline); + return renderNarrow(theme, width, showPipeline); } export default function startupHeaderExtension(pi: ExtensionAPI) { pi.on("session_start", (_event, ctx) => { if (ctx.mode !== "tui") return; - ctx.ui.setHeader((_tui, theme) => ({ - render: (width) => renderStartupHeader(theme, width), + let showPipeline = true; + + ctx.ui.setHeader((tui, theme) => ({ + render: (width) => renderStartupHeader(theme, width, showPipeline), invalidate() {}, + setExpanded(toolsExpanded: boolean) { + // Invert: when tools are expanded for deeper inspection, collapse the + // header to yield vertical screen space. When tools are collapsed, + // show the full research capability pipeline. + const next = !toolsExpanded; + if (showPipeline === next) return; + showPipeline = next; + tui.requestRender(); + }, })); }); + + pi.on("session_shutdown", (_event, ctx) => { + if (ctx.mode !== "tui") return; + ctx.ui.setHeader(undefined); + }); } diff --git a/scripts/bootstrap.mjs b/scripts/bootstrap.mjs index b8116d0..eae4218 100644 --- a/scripts/bootstrap.mjs +++ b/scripts/bootstrap.mjs @@ -4,7 +4,7 @@ * SecStack profile. * * This script deliberately changes only the SecStack profile's package list, - * shell command prefix, and optional Bash launcher. + * shell command prefix, quietStartup setting, and optional Bash launcher. */ import { execFileSync } from "node:child_process"; import { @@ -151,6 +151,10 @@ function mergeSettings() { : pathCommand; } + // Silence Pi's default startup banner and raw resource listing so SecStack's + // custom startup header and interactive capability pipeline take center stage. + settings.quietStartup = true; + const temp = join(agentDir, `.settings.${process.pid}.tmp`); writeFileSync(temp, `${JSON.stringify(settings, null, 2)}\n`, "utf8"); renameSync(temp, settingsPath);