{"version":3,"file":"index.mjs","names":[],"sources":["../../../tooling/shared/src/config.ts","../../../tooling/shared/src/statusline.ts","../../../tooling/shared/src/ui.ts","../src/converter.ts","../src/fetch.ts","../src/index.ts"],"sourcesContent":["import { getAgentDir } from '@earendil-works/pi-coding-agent'\nimport { Effect, FileSystem, Path, Schema } from 'effect'\n\n/**\n * Loads an extension's JSON config from `<agent-dir>/extensions/<name>.json` and\n * validates it with `schema`.\n */\nexport const loadExtensionConfig = Effect.fnUntraced(function* <\n  S extends Schema.Top,\n>(schema: S, name: string) {\n  const fs = yield* FileSystem.FileSystem\n  const path = yield* Path.Path\n\n  const file = path.join(getAgentDir(), 'extensions', `${name}.json`)\n  return yield* fs.readFileString(file).pipe(\n    Effect.flatMap(Schema.decodeEffect(Schema.fromJsonString(schema))),\n    Effect.tapError((error) =>\n      error._tag === 'PlatformError' && error.reason._tag === 'NotFound'\n        ? Effect.void\n        : Effect.logWarning(\n            `Could not load ${name} config. [${error._tag}]: ${error.message}`,\n          ),\n    ),\n  )\n})\n","import type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent'\nimport { Array, Order, pipe, String } from 'effect'\n\n/**\n * One entry on the shared status line. `text` must be plain (un-styled) text —\n * the whole row is dimmed uniformly so segments from different plugins share\n * one look.\n */\nexport interface StatuslineSegment {\n  readonly text: string\n  readonly align: 'left' | 'right'\n}\n\nconst WIDGET_KEY = 'pi-plugins:statusline'\n\n// Each plugin bundles its own copy of this module; `Symbol.for` on\n// `globalThis` gives them all the same registry inside one pi process.\nconst REGISTRY_KEY = Symbol.for('@pi-plugins/statusline-registry')\n\ntype Registry = Map<string, StatuslineSegment>\n\nfunction registry(): Registry {\n  const store = globalThis as { [REGISTRY_KEY]?: Registry }\n  store[REGISTRY_KEY] ??= new Map()\n  return store[REGISTRY_KEY]\n}\n\nconst byKey = Order.mapInput(\n  Order.String,\n  ([key]: readonly [string, StatuslineSegment]) => key,\n)\n\nfunction side(segments: Registry, align: StatuslineSegment['align']): string {\n  return pipe(\n    Array.fromIterable(segments),\n    Array.filter(([, segment]) => segment.align === align),\n    Array.sortBy(byKey),\n    Array.map(([, segment]) => segment.text),\n    Array.join(' · '),\n  )\n}\n\n/** Terminal columns of plain text (code points, no ANSI). */\nfunction textWidth(text: string): number {\n  return Array.fromIterable(text).length\n}\n\nfunction truncate(text: string, columns: number): string {\n  return pipe(Array.fromIterable(text), Array.take(columns), Array.join(''))\n}\n\n/**\n * Lays out `left ... right` across `width`: left indented one column to match\n * pi's built-in widget rows, right flush against the terminal edge to match\n * pi's footer. Falls back to one truncated `left · right` run when too narrow.\n */\nfunction renderLine(segments: Registry, width: number, theme: Theme): string {\n  const left = side(segments, 'left')\n  const right = side(segments, 'right')\n\n  const margin = Math.min(width, 1)\n  const inner = width - margin\n  const gap = inner - textWidth(left) - textWidth(right)\n  const minGap = String.isNonEmpty(left) && String.isNonEmpty(right) ? 2 : 0\n\n  const line =\n    String.isNonEmpty(right) && gap >= minGap\n      ? `${left}${' '.repeat(gap)}${right}`\n      : truncate(\n          pipe([left, right], Array.filter(String.isNonEmpty), Array.join(' · ')),\n          Math.max(inner, 0),\n        )\n\n  return ' '.repeat(margin) + theme.fg('dim', line)\n}\n\n/**\n * Adds, replaces (`segment`), or removes (`undefined`) a keyed segment on a\n * single status line above the editor shared across pi-plugins extensions,\n * instead of each plugin stacking its own widget row.\n */\nexport function setStatuslineSegment(\n  ctx: ExtensionContext,\n  key: string,\n  segment: StatuslineSegment | undefined,\n): void {\n  const segments = registry()\n  if (segment === undefined) {\n    segments.delete(key)\n  } else {\n    segments.set(key, segment)\n  }\n\n  if (!ctx.hasUI) {\n    return\n  }\n\n  if (segments.size === 0) {\n    ctx.ui.setWidget(WIDGET_KEY, undefined)\n    return\n  }\n\n  ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => ({\n    render: (width: number) => [renderLine(segments, width, theme)],\n    invalidate: () => {},\n  }))\n}\n","import type {\n  AgentToolResult,\n  Theme,\n  TruncationResult,\n} from '@earendil-works/pi-coding-agent'\nimport {\n  DEFAULT_MAX_BYTES,\n  DEFAULT_MAX_LINES,\n  formatSize,\n  keyHint,\n} from '@earendil-works/pi-coding-agent'\n\n/**\n * Wraps text into at most `maxLines` preview lines of `width` characters,\n * marking the last line with `...` when the text was cut off.\n */\nexport function previewLines(\n  text: string,\n  maxLines: number,\n  width: number,\n): string[] {\n  const lines: string[] = []\n  outer: for (const line of text.split('\\n')) {\n    for (let i = 0; i === 0 || i < line.length; i += width) {\n      lines.push(line.slice(i, i + width))\n      if (lines.length > maxLines) {\n        break outer\n      }\n    }\n  }\n  if (lines.length <= maxLines) {\n    return lines\n  }\n  const preview = lines.slice(0, maxLines)\n  preview[maxLines - 1] += '...'\n  return preview\n}\n\n/** Joins all text blocks of a tool result into one string, stripping carriage returns. */\nexport function getTextOutput(\n  result: Pick<AgentToolResult<unknown>, 'content'>,\n): string {\n  return result.content\n    .filter((content) => content.type === 'text')\n    .map((content) => content.text.replace(/\\r/g, ''))\n    .join('\\n')\n}\n\n/**\n * Renders a `header` above its `content`. Trailing blank lines are dropped and\n * the body is capped to a 10-line preview unless `expanded`; when the preview\n * hides lines a \"... (N more lines, ... to expand)\" hint is appended. A\n * `truncation` notice footer is added when provided.\n */\nexport function renderExpandableText({\n  header,\n  content,\n  expanded,\n  theme,\n  truncation,\n}: {\n  header: string\n  content: string\n  expanded: boolean\n  theme: Theme\n  truncation?: TruncationResult\n}): string {\n  const lines = content.split('\\n')\n  while (lines.length > 0 && lines[lines.length - 1] === '') {\n    lines.pop()\n  }\n\n  const maxLines = expanded ? lines.length : 10\n  const displayLines = lines.slice(0, maxLines)\n  const remaining = lines.length - maxLines\n\n  let text = header\n\n  if (displayLines.length > 0) {\n    text += `\\n${displayLines\n      .map((line) => theme.fg('toolOutput', line.replace(/\\t/g, '   ')))\n      .join('\\n')}`\n  } else {\n    text += `\\n${theme.fg('dim', '(empty response)')}`\n  }\n\n  if (remaining > 0) {\n    text += `${theme.fg('muted', `\\n... (${remaining} more lines,`)} ${keyHint(\n      'app.tools.expand',\n      'to expand',\n    )})`\n  }\n\n  if (truncation) {\n    const notice = formatTruncationNotice(truncation)\n    if (notice) {\n      text += `\\n${theme.fg('warning', notice)}`\n    }\n  }\n\n  return text\n}\n\n/**\n * Builds a human-readable notice describing how a result was truncated (by line\n * or byte limit), or an empty string when it was not truncated.\n */\nexport function formatTruncationNotice(truncation: TruncationResult): string {\n  let result = ''\n\n  if (truncation.truncated) {\n    if (truncation.firstLineExceedsLimit) {\n      result = `[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`\n    }\n\n    if (truncation.truncatedBy === 'lines') {\n      result = `[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`\n    } else if (truncation.truncatedBy === 'bytes') {\n      result = `[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`\n    }\n  }\n\n  return result\n}\n","import { Defuddle } from 'defuddle/node'\nimport { Context, Effect, Layer, Schema } from 'effect'\n\nexport class HtmlConverterError extends Schema.TaggedErrorClass<HtmlConverterError>()(\n  '@pi-plugins/webfetch/HtmlConverterError',\n  {\n    message: Schema.String,\n    cause: Schema.optional(Schema.Defect()),\n  },\n) {}\n\nexport interface HtmlConverterService {\n  toMarkdown: (\n    html: string,\n    url: string,\n  ) => Effect.Effect<string, HtmlConverterError>\n}\n\nexport class HtmlConverter extends Context.Service<\n  HtmlConverter,\n  HtmlConverterService\n>()('@pi-plugins/webfetch/HtmlConverter', {\n  make: Effect.succeed({\n    toMarkdown: Effect.fn(\n      function* (html: string, url: string) {\n        const response = yield* Effect.tryPromise({\n          try: () => Defuddle(html, url, { markdown: true }),\n          catch: (cause) =>\n            new HtmlConverterError({\n              message: 'Failed to convert HTML to Markdown',\n              cause,\n            }),\n        })\n        return response.content\n      },\n      Effect.withSpan('HtmlConverter.toMarkdown', (_, url) => ({\n        attributes: {\n          url: url,\n        },\n      })),\n    ),\n  }),\n}) {\n  static readonly layer = Layer.effect(this, this.make)\n}\n","import { Context, Duration, Effect, Layer, Schedule } from 'effect'\nimport type { TimeoutError } from 'effect/Cause'\nimport {\n  FetchHttpClient,\n  HttpClient,\n  type HttpClientError,\n  HttpClientRequest,\n} from 'effect/unstable/http'\nimport { HtmlConverter, HtmlConverterError } from './converter'\n\nexport type WebFetchFormat = 'markdown' | 'html'\n\nconst ACCEPT_HEADERS: Record<WebFetchFormat, string> = {\n  markdown:\n    'text/markdown;q=1.0, text/x-markdown;q=0.9, text/plain;q=0.8, text/html;q=0.7, */*;q=0.1',\n  html: 'text/html;q=1.0, application/xhtml+xml;q=0.9, text/plain;q=0.8, text/markdown;q=0.7, */*;q=0.1',\n}\n\nconst BROWSER_HEADERS = {\n  'Accept-Language': 'en-US,en;q=0.9',\n  'User-Agent':\n    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',\n}\n\nfunction shouldConvertToMarkdown(\n  contentType: string | undefined,\n  content: string,\n): boolean {\n  if (contentType !== undefined) {\n    const mediaType = contentType.split(';', 1)[0]?.trim().toLowerCase()\n    return mediaType === 'text/html' || mediaType === 'application/xhtml+xml'\n  }\n  const prefix = content.trimStart().slice(0, 1024)\n  return /^(?:<!doctype html|<html|<\\?xml[\\s\\S]*<html)/i.test(prefix)\n}\n\ninterface WebFetchService {\n  fetch: (options: {\n    url: string\n    format: WebFetchFormat\n    timeout: Duration.Input\n  }) => Effect.Effect<\n    string,\n    HtmlConverterError | HttpClientError.HttpClientError | TimeoutError\n  >\n}\n\nexport class WebFetch extends Context.Service<WebFetch, WebFetchService>()(\n  '@pi-plugins/webfetch/WebFetch',\n  {\n    make: Effect.gen(function* () {\n      const converter = yield* HtmlConverter\n      const http = (yield* HttpClient.HttpClient).pipe(\n        HttpClient.mapRequest((request) =>\n          HttpClientRequest.setHeaders(request, BROWSER_HEADERS),\n        ),\n        HttpClient.filterStatusOk,\n        HttpClient.retryTransient({\n          times: 3,\n          schedule: Schedule.jittered(Schedule.exponential('1 second')),\n        }),\n      )\n\n      const fetch = Effect.fn(\n        function* (options: {\n          url: string\n          format: WebFetchFormat\n          timeout: Duration.Input\n        }) {\n          const response = yield* http.get(options.url, {\n            headers: {\n              Accept: ACCEPT_HEADERS[options.format],\n            },\n          })\n          const contentType = response.headers['content-type']\n          const raw = yield* response.text\n\n          if (\n            options.format === 'markdown' &&\n            shouldConvertToMarkdown(contentType, raw)\n          ) {\n            return yield* converter.toMarkdown(raw, options.url)\n          }\n\n          return raw\n        },\n        (_, options) =>\n          _.pipe(\n            Effect.timeout(options.timeout),\n            Effect.withSpan('WebFetch.fetch', {\n              attributes: { url: options.url, format: options.format },\n            }),\n          ),\n      )\n\n      return { fetch } as const\n    }),\n  },\n) {\n  static readonly layer = Layer.effect(this, this.make).pipe(\n    Layer.provide([HtmlConverter.layer, FetchHttpClient.layer]),\n  )\n}\n","import type { ExtensionAPI, TruncationResult } from '@earendil-works/pi-coding-agent'\nimport { truncateHead } from '@earendil-works/pi-coding-agent'\nimport { Text } from '@earendil-works/pi-tui'\nimport { formatTruncationNotice, renderExpandableText } from '@pi-plugins/shared'\nimport { Duration, Effect, Number } from 'effect'\nimport { Type, type Static } from 'typebox'\nimport { WebFetch, type WebFetchFormat } from './fetch'\n\nconst DEFAULT_TIMEOUT_SECONDS = 30\nconst MAX_TIMEOUT_SECONDS = 120\n\nconst webFetchSchema = Type.Object({\n  url: Type.String({\n    description: 'The URL to fetch content from',\n  }),\n  format: Type.Optional(\n    Type.Unsafe<'markdown' | 'html'>({\n      type: 'string',\n      enum: ['markdown', 'html'],\n      description: \"The format to return the content in ('markdown' or 'html').\",\n      default: 'markdown',\n    }),\n  ),\n  timeout: Type.Optional(\n    Type.Number({\n      description: 'Timeout in seconds.',\n      default: DEFAULT_TIMEOUT_SECONDS,\n      minimum: 1,\n      maximum: MAX_TIMEOUT_SECONDS,\n    }),\n  ),\n})\n\nexport type WebFetchInput = Static<typeof webFetchSchema>\n\ninterface WebFetchDetails {\n  truncation: TruncationResult\n}\n\nexport default function webFetch(pi: ExtensionAPI) {\n  pi.registerTool<typeof webFetchSchema, WebFetchDetails>({\n    name: 'web_fetch',\n    label: 'WebFetch',\n    description:\n      'Fetch an HTTP(S) page and return its content as Markdown (default) or the raw HTML.',\n    promptSnippet: 'Fetch HTTP(S) pages as Markdown or raw HTML.',\n    parameters: webFetchSchema,\n    async execute(_toolCallId, params, signal) {\n      const format: WebFetchFormat = params.format ?? 'markdown'\n      const timeoutSeconds = Number.clamp(\n        params.timeout ?? DEFAULT_TIMEOUT_SECONDS,\n        { minimum: 1, maximum: MAX_TIMEOUT_SECONDS },\n      )\n\n      const program = Effect.gen(function* () {\n        const webfetch = yield* WebFetch\n        return yield* webfetch.fetch({\n          url: params.url,\n          format,\n          timeout: Duration.seconds(timeoutSeconds),\n        })\n      }).pipe(Effect.provide(WebFetch.layer))\n\n      const content = await Effect.runPromise(program, { signal })\n      const truncation = truncateHead(content)\n\n      return {\n        content: [\n          {\n            type: 'text' as const,\n            text: truncation.truncated\n              ? `${truncation.content}\\n\\n${formatTruncationNotice(truncation)}`\n              : truncation.content,\n          },\n        ],\n        details: {\n          truncation,\n        },\n      }\n    },\n    renderResult({ details }, { expanded }, theme, context) {\n      const format = context.args.format ?? 'markdown'\n      const header = `${theme.fg('success', '✓')} ${theme.fg('accent', context.args.url)} ${theme.fg(\n        'muted',\n        `(${format})`,\n      )}`\n\n      const text = new Text('', 0, 0)\n      text.setText(\n        renderExpandableText({\n          header,\n          content: details.truncation.content,\n          expanded,\n          theme,\n          truncation: details.truncation,\n        }),\n      )\n      return text\n    },\n  })\n}\n"],"mappings":"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"}