{"version":3,"file":"skillsOverlay.mjs","names":[],"sources":["../../../src/tui/skillsOverlay.ts"],"sourcesContent":["/**\n * Skills: the `SPC e s` overlay.\n *\n * A tree of the skills this session loaded on the left, the selected skill's\n * frontmatter and body on the right. Everything listed is loaded, so the\n * surface answers \"what do I have, and what does it cost\" rather than doubling\n * as a domain browser.\n *\n * The tree is flat and always open: groups and owners are headings, only skills\n * take the selection. Rendering is pure: the catalogue is resolved once by the\n * caller and passed in, so `render(width)` returns lines with no side effects\n * and the whole surface can be asserted as text without a live terminal.\n */\n\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport {\n  DOOM_FULLSCREEN_UI_OPTIONS,\n  DOOM_OVERLAY_ACCENT,\n  DoomOverlay,\n  type DoomOverlayChrome,\n  type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/doom-overlay';\nimport { fitStyledLine, formatTokens } from '@agimon-ai/doompi-ui/rendering';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\n\nimport type { SkillCatalog, SkillEntry } from '../services/skillCatalog';\n\ntype ThemeColor = Parameters<Theme['fg']>[0];\ntype ThemeBg = Parameters<Theme['bg']>[0];\n\nconst MIN_WIDTH = 60;\n/** A third for the tree, two thirds for the skill being read. */\nconst TREE_PANE_RATIO = 1 / 3;\n/** One blank column each side of the pane divider so neither column touches it. */\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 20;\nconst LABEL_COLUMN = 13;\n/** Ceiling on the columns a row's right-hand tag may take from its label. */\nconst TAG_SHARE = 0.4;\nconst INDENT = 2;\nconst ELLIPSIS = '…';\nconst SELECTION_MARKER = '›';\nconst RULE = '─';\nconst KEY_TAB = '\\t';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_BACKSPACE = '\\x7f';\nconst FILTER_KEY = '/';\nconst BODY_PAGE = 10;\n\nconst TITLE = 'SKILLS';\nconst BREADCRUMB = 'SPC › e / extension › s / skills';\nconst HEADING = 'CATALOG';\nconst EMPTY_TREE = 'No skills discovered.';\nconst NO_SELECTION = 'Select a skill to read it.';\nconst UNREADABLE = 'Could not read SKILL.md.';\n\nconst TREE_HINTS: readonly (readonly [string, string])[] = [\n  ['↑↓', 'select'],\n  ['enter', 'use skill'],\n  ['tab', 'detail pane'],\n  ['/', 'filter'],\n  ['esc', 'close'],\n];\nconst BODY_HINTS: readonly (readonly [string, string])[] = [\n  ['pgup/pgdn', 'scroll'],\n  ['tab', 'tree'],\n  ['esc', 'close'],\n];\nconst FILTER_HINTS: readonly (readonly [string, string])[] = [\n  ['type', 'to filter'],\n  ['enter', 'keep'],\n  ['esc', 'clear'],\n];\n\nexport type SkillsOverlayResult = { kind: 'invoke'; skill: SkillEntry } | undefined;\n\nexport interface SkillsOverlayOptions {\n  catalog: SkillCatalog;\n  /** Repo root, so paths render relative rather than as absolute noise. */\n  repoRoot: string;\n  readFile?: (filePath: string) => string;\n}\n\ntype RowKind = 'group' | 'owner' | 'skill';\n\ninterface TreeRow {\n  kind: RowKind;\n  depth: number;\n  label: string;\n  tag: string;\n  /** Only a skill row carries one; group and owner rows are headings. */\n  skill?: SkillEntry;\n}\n\nfunction fitRow(text: string, width: number): string {\n  return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction wrap(text: string, width: number): string[] {\n  if (width <= 0) return [];\n  const lines: string[] = [];\n  let current = '';\n  for (const word of text.split(/\\s+/).filter(Boolean)) {\n    if (current.length === 0) current = word;\n    else if (visibleWidth(current) + 1 + visibleWidth(word) <= width) current += ` ${word}`;\n    else {\n      lines.push(current);\n      current = word;\n    }\n  }\n  if (current.length > 0) lines.push(current);\n  return lines;\n}\n\n/** Drops YAML frontmatter so the preview starts at the prose, as Pi does. */\nexport function skillBody(content: string): string {\n  const match = content.match(/^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n?([\\s\\S]*)$/);\n  return (match ? (match[1] ?? '') : content).replace(/^\\s+/, '');\n}\n\nfunction matches(skill: SkillEntry, query: string): boolean {\n  const needle = query.toLowerCase();\n  return skill.name.toLowerCase().includes(needle) || skill.description.toLowerCase().includes(needle);\n}\n\nexport class SkillsOverlayComponent extends DoomOverlay {\n  private readonly options: SkillsOverlayOptions;\n  private readonly done: (result: SkillsOverlayResult) => void;\n  private readonly readFile: (filePath: string) => string;\n  private readonly bodyCache = new Map<string, string[]>();\n\n  /** Index into the skill rows, not into every row: headings are not selectable. */\n  private selected = 0;\n  private focus: 'tree' | 'body' = 'tree';\n  private filtering = false;\n  private query = '';\n  private bodyOffset = 0;\n\n  constructor(\n    tui: DoomOverlayTui,\n    theme: Theme,\n    options: SkillsOverlayOptions,\n    done: (result: SkillsOverlayResult) => void,\n  ) {\n    super(tui, theme);\n    this.options = options;\n    this.done = done;\n    this.readFile = options.readFile ?? ((filePath) => fs.readFileSync(filePath, 'utf8'));\n  }\n\n  /** Every row, honouring the active filter. The tree does not fold. */\n  private rows(): TreeRow[] {\n    const query = this.query.trim();\n    const rows: TreeRow[] = [];\n    for (const group of this.options.catalog.groups) {\n      const owners = query\n        ? group.owners\n            .map((owner) => ({ ...owner, skills: owner.skills.filter((skill) => matches(skill, query)) }))\n            .filter((owner) => owner.skills.length > 0)\n        : group.owners;\n      if (owners.length === 0) continue;\n      const count = owners.reduce((total, owner) => total + owner.skills.length, 0);\n      rows.push({ kind: 'group', depth: 0, label: group.label, tag: String(count) });\n      for (const owner of owners) {\n        rows.push({ kind: 'owner', depth: 1, label: owner.owner, tag: String(owner.skills.length) });\n        for (const skill of owner.skills) {\n          rows.push({ kind: 'skill', depth: 2, label: skill.name, tag: '', skill });\n        }\n      }\n    }\n    return rows;\n  }\n\n  /**\n   * Where the skill rows sit in `rows()`.\n   *\n   * The selection indexes this list rather than `rows()`, which is what keeps\n   * headings unselectable without the key handler having to hunt past them, and\n   * what puts the opening selection on a skill rather than on the first group.\n   */\n  private selectable(rows: TreeRow[]): number[] {\n    const indexes: number[] = [];\n    for (const [index, row] of rows.entries()) if (row.skill) indexes.push(index);\n    return indexes;\n  }\n\n  private currentRow(): TreeRow | undefined {\n    const rows = this.rows();\n    const indexes = this.selectable(rows);\n    if (indexes.length === 0) return undefined;\n    return rows[indexes[Math.min(this.selected, indexes.length - 1)] as number];\n  }\n\n  private move(delta: number): void {\n    const count = this.selectable(this.rows()).length;\n    if (count === 0) return;\n    this.selected = Math.max(0, Math.min(count - 1, this.selected + delta));\n    this.bodyOffset = 0;\n    this.invalidate();\n  }\n\n  private bodyLines(skill: SkillEntry, width: number): string[] {\n    let raw = this.bodyCache.get(skill.filePath);\n    if (!raw) {\n      let source: string;\n      try {\n        source = skillBody(this.readFile(skill.filePath));\n      } catch {\n        source = UNREADABLE;\n      }\n      raw = source.split(/\\r?\\n/);\n      this.bodyCache.set(skill.filePath, raw);\n    }\n    // Wrapped at render width rather than at read time, so a resize reflows the\n    // same cached file instead of re-reading it.\n    return raw.flatMap((line) => (line.trim() === '' ? [''] : wrap(line, width)));\n  }\n\n  handleInput(data: string): void {\n    if (this.filtering) {\n      this.handleFilterInput(data);\n      return;\n    }\n\n    if (matchesKey(data, 'escape')) {\n      // The filter clears before the overlay closes: escaping straight out of a\n      // narrowed tree loses the search with no way to see what it hid.\n      if (this.query) {\n        this.query = '';\n        this.selected = 0;\n        this.invalidate();\n        return;\n      }\n      this.done(undefined);\n      return;\n    }\n    if (data === KEY_TAB || matchesKey(data, 'tab')) {\n      this.focus = this.focus === 'tree' ? 'body' : 'tree';\n      this.invalidate();\n      return;\n    }\n    if (matchesKey(data, 'pageUp')) {\n      this.bodyOffset = Math.max(0, this.bodyOffset - BODY_PAGE);\n      this.invalidate();\n      return;\n    }\n    if (matchesKey(data, 'pageDown')) {\n      this.bodyOffset += BODY_PAGE;\n      this.invalidate();\n      return;\n    }\n    if (this.focus === 'body') return;\n\n    if (data === FILTER_KEY) {\n      this.filtering = true;\n      this.invalidate();\n      return;\n    }\n    if (matchesKey(data, 'up') || matchesKey(data, 'k')) {\n      this.move(-1);\n      return;\n    }\n    if (matchesKey(data, 'down') || matchesKey(data, 'j')) {\n      this.move(1);\n      return;\n    }\n\n    if (data === KEY_ENTER || data === KEY_NEWLINE || matchesKey(data, 'enter')) {\n      const skill = this.currentRow()?.skill;\n      if (skill) this.done({ kind: 'invoke', skill });\n    }\n  }\n\n  private handleFilterInput(data: string): void {\n    if (matchesKey(data, 'escape')) {\n      this.filtering = false;\n      this.query = '';\n      this.selected = 0;\n      this.invalidate();\n      return;\n    }\n    if (data === KEY_ENTER || data === KEY_NEWLINE) {\n      this.filtering = false;\n      this.invalidate();\n      return;\n    }\n    if (data === KEY_BACKSPACE) this.query = this.query.slice(0, -1);\n    // Control sequences are dropped rather than appended: an arrow key would\n    // otherwise land in the query as a raw escape sequence.\n    else if (data.length === 1 && data >= ' ') this.query += data;\n    this.selected = 0;\n    this.invalidate();\n  }\n\n  protected getChrome(): DoomOverlayChrome {\n    const { skillCount, promptTokens, bodyTokens } = this.options.catalog;\n    const sources = this.options.catalog.groups.filter((group) => group.owners.length > 0).length;\n    const hints = this.filtering ? FILTER_HINTS : this.focus === 'body' ? BODY_HINTS : TREE_HINTS;\n    return {\n      title: TITLE,\n      accent: DOOM_OVERLAY_ACCENT,\n      breadcrumb: BREADCRUMB,\n      // Two token figures, because they answer different questions: what the\n      // catalogue costs on every request, and what reading all of it would cost.\n      headerRight: `${skillCount} skills · ${sources} sources · ${formatTokens(promptTokens)} always-on · ${formatTokens(bodyTokens)} full`,\n      footer: this.hintLine(hints),\n    };\n  }\n\n  private badge(text: string, background: ThemeBg, colour: ThemeColor = 'text'): string {\n    return this.theme.bg(background, this.theme.fg(colour, ` ${text} `));\n  }\n\n  /** Footer hints as capped keys: `[ enter ] use skill`, per the mockup. */\n  private hintLine(hints: readonly (readonly [string, string])[]): string {\n    return hints\n      .map(([key, label]) => `${this.badge(key, 'selectedBg')}${this.theme.fg('dim', ` ${label}`)}`)\n      .join(this.theme.fg('dim', '   '));\n  }\n\n  protected renderBody(width: number, height: number): string[] {\n    if (width < MIN_WIDTH) return this.renderStacked(width, height);\n\n    const treeWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * TREE_PANE_RATIO));\n    const detailWidth = Math.max(1, width - treeWidth - 1);\n    const treeContent = Math.max(1, treeWidth - PANE_GUTTER);\n    const detailContent = Math.max(1, detailWidth - PANE_GUTTER);\n\n    const left = this.treeLines(treeContent, height);\n    const right = this.detailLines(detailContent, height);\n    const divider = this.theme.fg('borderMuted', '│');\n    return Array.from({ length: height }, (_, index) => {\n      const row = `${fitRow(left[index] ?? '', treeContent)} ${divider} ${fitRow(right[index] ?? '', detailContent)}`;\n      return truncateToWidth(row, width, ELLIPSIS);\n    });\n  }\n\n  private renderStacked(width: number, height: number): string[] {\n    const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n    const bottomHeight = Math.max(0, height - topHeight - 1);\n    const top = this.treeLines(width, topHeight);\n    const bottom = this.detailLines(width, bottomHeight);\n    return [\n      ...top.slice(0, topHeight),\n      ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n      this.theme.fg('borderMuted', RULE.repeat(width)),\n      ...bottom.slice(0, bottomHeight),\n    ];\n  }\n\n  private treeLines(width: number, height: number): string[] {\n    const heading = this.filtering || this.query ? `${HEADING}  ${FILTER_KEY}${this.query}` : HEADING;\n    const lines = [this.theme.bold(this.theme.fg('accent', heading)), ''];\n    const rows = this.rows();\n    if (rows.length === 0) {\n      lines.push(this.theme.fg('dim', EMPTY_TREE));\n      return lines;\n    }\n\n    const budget = Math.max(1, height - lines.length);\n    const indexes = this.selectable(rows);\n    // No skill rows means the filter matched nothing, so nothing is highlighted.\n    const selected = indexes[Math.min(this.selected, indexes.length - 1)] ?? -1;\n    const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, rows.length - budget)));\n    for (const [offset, row] of rows.slice(start, start + budget).entries()) {\n      const isSelected = start + offset === selected;\n      const marker = isSelected ? this.theme.fg('accent', SELECTION_MARKER) : ' ';\n      const indent = ' '.repeat(row.depth * INDENT);\n      // Three tiers rather than two: the group heading leads, the owner beneath\n      // it recedes to a caption, and the skills it holds carry the reading\n      // weight because they are the only rows that do anything.\n      const colour: ThemeColor = row.kind === 'group' ? 'accent' : row.kind === 'owner' ? 'dim' : 'text';\n      let label = this.theme.fg(colour, `${indent}${row.label}`);\n      if (row.kind === 'group' || isSelected) label = this.theme.bold(label);\n      // The tag is capped before it claims columns: an owner in many domains\n      // carries a long `+a,b,c` hint that would otherwise leave the name with\n      // no room at all and render the row as a bare count.\n      const tag = row.tag ? truncateToWidth(row.tag, Math.floor(width * TAG_SHARE), ELLIPSIS) : '';\n      const tagWidth = tag ? visibleWidth(tag) + 1 : 0;\n      const body = fitRow(`${marker} ${label}`, Math.max(1, width - tagWidth));\n      const line = tag ? `${body} ${this.theme.fg('dim', tag)}` : body;\n      lines.push(isSelected ? this.theme.bg('selectedBg', fitRow(line, width)) : line);\n    }\n    return lines;\n  }\n\n  private field(label: string, value: string, colour: ThemeColor, width: number): string[] {\n    const valueWidth = Math.max(1, width - LABEL_COLUMN);\n    return wrap(value, valueWidth).map(\n      (line, index) =>\n        `${this.theme.fg('dim', fitRow(index === 0 ? label : '', LABEL_COLUMN))}${this.theme.fg(colour, line)}`,\n    );\n  }\n\n  private detailLines(width: number, height: number): string[] {\n    const skill = this.currentRow()?.skill;\n    if (!skill) return ['', '', this.theme.fg('dim', NO_SELECTION)];\n\n    const relative = path.relative(this.options.repoRoot, skill.filePath) || skill.filePath;\n    const lines = [\n      this.theme.bold(this.theme.fg('accent', skill.name)),\n      '',\n      ...this.field('description', skill.description, 'text', width),\n      ...this.field('source', `${skill.group} › ${skill.owner}`, 'success', width),\n      ...this.field('path', relative, 'text', width),\n      ...this.field(\n        'invocable',\n        skill.modelInvocable ? `model  ·  /skill:${skill.name}` : `/skill:${skill.name} only`,\n        'success',\n        width,\n      ),\n    ];\n\n    const body = this.bodyLines(skill, width);\n    const remaining = Math.max(0, height - lines.length - 2);\n    // Clamped here rather than in the key handler, because how far the body can\n    // scroll depends on the width it has just wrapped to.\n    this.bodyOffset = Math.min(this.bodyOffset, Math.max(0, body.length - remaining));\n    const shown = body.slice(this.bodyOffset, this.bodyOffset + remaining);\n    const position =\n      body.length > remaining\n        ? `lines ${this.bodyOffset + 1}-${this.bodyOffset + shown.length} / ${body.length}`\n        : 'SKILL.md';\n    const focus = this.focus === 'body' ? this.theme.fg('accent', ' ◀ focused') : '';\n\n    lines.push('', `${this.theme.fg('dim', position)}${focus}`);\n    for (const line of shown) lines.push(this.theme.fg('dim', line));\n    return lines;\n  }\n}\n\nexport async function openSkillsOverlay(\n  ctx: ExtensionContext,\n  options: SkillsOverlayOptions,\n): Promise<SkillsOverlayResult> {\n  return ctx.ui.custom<SkillsOverlayResult>(\n    (tui, theme, _keybindings, done) => new SkillsOverlayComponent(tui, theme, options, done),\n    DOOM_FULLSCREEN_UI_OPTIONS,\n  );\n}\n"],"mappings":"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"}