@mrclrchtr/supi-web 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,9 @@
1
1
  # @mrclrchtr/supi-web
2
2
 
3
- Fetch web pages as clean Markdown and query library documentation via [Context7](https://context7.com) for the [pi coding agent](https://github.com/earendil-works/pi).
3
+ Adds web-page fetching and library-documentation lookup tools to the [pi coding agent](https://github.com/earendil-works/pi).
4
4
 
5
5
  ## Install
6
6
 
7
- Included in `@mrclrchtr/supi`, or install standalone:
8
-
9
7
  ```bash
10
8
  pi install npm:@mrclrchtr/supi-web
11
9
  ```
@@ -16,90 +14,79 @@ For local development:
16
14
  pi install ./packages/supi-web
17
15
  ```
18
16
 
19
- After editing the source, run `/reload` to pick up changes.
20
-
21
- ## Package surfaces
22
-
23
- - `@mrclrchtr/supi-web/api` — programmatic exports (conversion helpers, fetch utilities, tool factories)
24
- - `@mrclrchtr/supi-web/extension` — aggregated pi extension entrypoint that registers all three tools
25
-
26
- `pi.extensions` still points at the real file path `./src/extension.ts` inside the package. The `/api` and `/extension` paths are consumer-facing package exports, not manifest aliases.
17
+ After editing the source, run `/reload`.
27
18
 
28
- ## What it adds
19
+ ## What you get
29
20
 
30
- Registers three agent-callable tools:
21
+ After install, pi gets three tools:
31
22
 
32
- | Tool | Purpose |
33
- |------|---------|
34
- | `web_fetch_md` | Fetch an `http(s)` URL and return clean Markdown |
35
- | `web_docs_search` | Search Context7 for libraries by name |
36
- | `web_docs_fetch` | Retrieve up-to-date documentation for a specific library via Context7 |
23
+ - `web_fetch_md` fetch a web page and convert it to Markdown
24
+ - `web_docs_search` — search Context7 for a library ID
25
+ - `web_docs_fetch` fetch up-to-date documentation for a specific Context7 library
37
26
 
38
- ## web_fetch_md Web Page to Markdown
27
+ ## Choose the right tool
39
28
 
40
- ### Parameters
29
+ ### `web_fetch_md`
41
30
 
42
- | Parameter | Type | Default | Description |
43
- |-----------|------|---------|-------------|
44
- | `url` | `string` | **required** | `http://` or `https://` URL to fetch |
45
- | `output_mode` | `"auto" \| "inline" \| "file"` | `"auto"` | `auto` returns inline if ≤15,000 chars, else writes to temp file |
46
- | `abs_links` | `boolean` | `true` | Absolutize relative links and image sources |
47
- | `timeout_ms` | `number` | `30000` | Fetch timeout in milliseconds |
31
+ Use this for general web pages.
48
32
 
49
- ### Content negotiation
33
+ Important behavior:
50
34
 
51
- The tool tries multiple strategies to get clean Markdown:
35
+ - accepts only real `http://` or `https://` URLs
36
+ - defaults to `output_mode: auto`
37
+ - returns Markdown inline when the result is at most **15,000 characters**
38
+ - otherwise writes the Markdown to a temporary `.md` file and returns the file path
39
+ - absolutizes links and image URLs by default
40
+ - wraps plain-text responses as fenced code blocks instead of pretending they are prose
52
41
 
53
- 1. **HEAD negotiation** checks `content-type` for Markdown
54
- 2. **Sniffing** — range GET of first 8KB to detect Markdown/plain text vs HTML
55
- 3. **Sibling probing** — tries `.md` / `.markdown` variants (e.g. `page.md` for `page.html`)
56
- 4. **HTML conversion** — full GET → JSDOM + Readability + Turndown → clean Markdown
42
+ The fetch pipeline tries several strategies in order:
57
43
 
58
- ## web_docs_search Library Lookup
44
+ 1. Markdown-aware content negotiation
45
+ 2. content sniffing
46
+ 3. sibling `.md` / `.markdown` probing
47
+ 4. HTML fetch followed by Readability + Turndown conversion
59
48
 
60
- Searches Context7's library index and returns a Markdown table of matching libraries with metadata.
49
+ ### `web_docs_search`
61
50
 
62
- ### Parameters
51
+ Use this when you need a Context7 library ID first.
63
52
 
64
- | Parameter | Type | Default | Description |
65
- |-----------|------|---------|-------------|
66
- | `library_name` | `string` | **required** | Library name to search for (e.g. `"react"`, `"next.js"`, `"fastapi"`) |
67
- | `query` | `string` | **required** | What the agent is trying to do — used for relevance ranking |
53
+ It returns a Markdown table of matching libraries with fields such as:
68
54
 
69
- ### Output
55
+ - ID
56
+ - name
57
+ - description
58
+ - trust score
59
+ - benchmark score
60
+ - snippet count
61
+ - versions
70
62
 
71
- Returns a Markdown table with columns: Name, ID, Description, Trust Score, Benchmark Score, Snippet Count, Versions. The agent picks a library ID from these results to pass to `web_docs_fetch`.
63
+ ### `web_docs_fetch`
72
64
 
73
- ## web_docs_fetch Documentation Retrieval
65
+ Use this when you already know the Context7 library ID and want current docs or snippets for a specific question.
74
66
 
75
- Fetches up-to-date documentation context for a specific library via Context7's API.
67
+ - `library_id` is required
68
+ - `query` is required
69
+ - `raw: true` returns JSON-serialized snippet objects instead of plain text Markdown
76
70
 
77
- ### Parameters
71
+ ## Context7 notes
78
72
 
79
- | Parameter | Type | Default | Description |
80
- |-----------|------|---------|-------------|
81
- | `library_id` | `string` | **required** | Context7 library ID (e.g. `/facebook/react`, `/vercel/next.js/v15.1.8`) |
82
- | `query` | `string` | **required** | Specific question about the library |
83
- | `raw` | `boolean` | `false` | When `true`, returns JSON-serialized snippet objects instead of plain text Markdown |
73
+ `web_docs_search` and `web_docs_fetch` use Context7 through `@upstash/context7-sdk`.
84
74
 
85
- ### API Key (optional)
75
+ If `CONTEXT7_API_KEY` is set, the SDK will use it automatically.
86
76
 
87
- The tool reads the `CONTEXT7_API_KEY` environment variable automatically when set. Without a key, it works with lower rate limits. Get a key at [context7.com/dashboard](https://context7.com/dashboard).
88
-
89
- ### Source files
77
+ ## Package surfaces
90
78
 
91
- - `src/api.ts` — public package exports
92
- - `src/extension.ts` — aggregated extension entrypoint
93
- - `src/web.ts` — `web_fetch_md` tool registration
94
- - `src/docs.ts` — `web_docs_search` + `web_docs_fetch` tool registration
95
- - `src/context7-client.ts` — thin wrapper around `@upstash/context7-sdk`
96
- - `src/fetch.ts` — HTTP fetch logic
97
- - `src/convert.ts` — HTML to Markdown conversion
79
+ - `@mrclrchtr/supi-web/api` — conversion helpers, fetch helpers, and extension exports
80
+ - `@mrclrchtr/supi-web/extension` — extension entrypoint that registers all three tools
98
81
 
99
- ## Commands
82
+ ## Source
100
83
 
101
- ```bash
102
- pnpm vitest run packages/supi-web/
103
- pnpm exec tsc --noEmit -p packages/supi-web/tsconfig.json
104
- pnpm exec biome check packages/supi-web/
105
- ```
84
+ - `src/web.ts` — `web_fetch_md`
85
+ - `src/docs.ts` `web_docs_search` and `web_docs_fetch`
86
+ - `src/fetch.ts` HTTP fetching and negotiation
87
+ - `src/convert.ts` HTML-to-Markdown conversion
88
+ - `src/context7-client.ts` — Context7 client wrapper
89
+ - `src/temp-file.ts` — temp-file output helper
90
+ - `src/tool/web-fetch-md-guidance.ts` — model-facing prompt surfaces for `web_fetch_md`
91
+ - `src/tool/web-docs-search-guidance.ts` — model-facing prompt surfaces for `web_docs_search`
92
+ - `src/tool/web-docs-fetch-guidance.ts` — model-facing prompt surfaces for `web_docs_fetch`
@@ -1,65 +1,78 @@
1
1
  # @mrclrchtr/supi-core
2
2
 
3
- Shared infrastructure for SuPi packages.
3
+ Shared infrastructure for SuPi extensions.
4
+
5
+ This package is mainly for extension authors. It gives you a common config system, settings plumbing, context helpers, registries, and a small extension surface that registers `/supi-settings`.
4
6
 
5
7
  ## Install
6
8
 
7
- Use it as a dependency in another extension package:
9
+ ### As a dependency for another extension
8
10
 
9
11
  ```bash
10
12
  pnpm add @mrclrchtr/supi-core
11
13
  ```
12
14
 
13
- ## Package role
14
-
15
- `@mrclrchtr/supi-core` now has two explicit surfaces:
15
+ ### As a pi package
16
16
 
17
- - `@mrclrchtr/supi-core/api` — shared library helpers for other SuPi packages
18
- - `@mrclrchtr/supi-core/extension` — a minimal pi extension that registers `/supi-settings`
19
-
20
- `pi.extensions` still points at the real file path `./src/extension.ts` inside the package. The `/api` and `/extension` paths are consumer-facing package exports, not manifest aliases.
17
+ ```bash
18
+ pi install npm:@mrclrchtr/supi-core
19
+ ```
21
20
 
22
- ## What it provides
21
+ Installing it as a pi package adds the minimal `/supi-settings` extension surface.
23
22
 
24
- Current exports cover:
23
+ ## Package surfaces
25
24
 
26
- - shared config loading, scoped reads, writes, and key removal
27
- - config-backed settings registration helpers for `/supi-settings`
28
- - the shared settings registry, overlay UI, and `registerSettingsCommand()` helper
29
- - XML `<extension-context>` wrapping plus context-message utilities
30
- - context-provider and debug-event registries reused across SuPi packages
31
- - project root and path helpers reused by packages such as `supi-lsp`
25
+ - `@mrclrchtr/supi-core/api` reusable helpers for other packages and extensions
26
+ - `@mrclrchtr/supi-core/extension` minimal pi extension that registers `/supi-settings`
32
27
 
33
- ## Config system
28
+ ## What you get from the API
34
29
 
35
- Config resolution order:
30
+ ### Config helpers
36
31
 
37
- ```text
38
- defaults <- global <- project
39
- ```
32
+ - `loadSupiConfig()` — merged config with resolution order `defaults <- global <- project`
33
+ - `loadSupiConfigForScope()` load one scope at a time for settings UIs
34
+ - `writeSupiConfig()` — persist values
35
+ - `removeSupiConfigKey()` — remove a key or override
40
36
 
41
37
  Config file locations:
42
38
 
43
39
  - global: `~/.pi/agent/supi/config.json`
44
40
  - project: `.pi/supi/config.json`
45
41
 
46
- Main helpers:
42
+ ### Settings helpers
43
+
44
+ - `registerSettings()` — register an arbitrary settings section
45
+ - `registerConfigSettings()` — register a config-backed settings section with scoped persistence helpers
46
+ - `registerSettingsCommand()` — register `/supi-settings`
47
+ - `openSettingsOverlay()` — open the shared settings UI directly
48
+ - `createInputSubmenu()` — helper for simple text-entry submenus
49
+
50
+ The built-in settings UI supports:
47
51
 
48
- - `loadSupiConfig()` — effective merged config (`defaults <- global <- project`)
49
- - `loadSupiConfigForScope()` raw single-scope config for settings UIs (`defaults <- selected scope`)
50
- - `writeSupiConfig()`
51
- - `removeSupiConfigKey()`
52
- - `registerConfigSettings()`
52
+ - project/global scope toggle
53
+ - grouped extension sections
54
+ - searchable setting lists
53
55
 
54
- ## Context and settings helpers
56
+ ### Context helpers
55
57
 
56
- - `wrapExtensionContext()`
58
+ - `wrapExtensionContext()` — wrap injected text in SuPi's `<extension-context>` tag
57
59
  - `findLastUserMessageIndex()`
58
60
  - `getContextToken()`
61
+ - `getPromptContent()`
59
62
  - `pruneAndReorderContextMessages()`
60
- - `registerSettings()`
61
- - `registerSettingsCommand()`
62
- - `openSettingsOverlay()`
63
+ - `restorePromptContent()`
64
+
65
+ ### Shared registries
66
+
67
+ - context-provider registry for `/supi-context`
68
+ - debug-event registry for producers that want shared debug capture
69
+ - settings registry used by `/supi-settings`
70
+
71
+ ### Project and session helpers
72
+
73
+ - project-root detection and directory walking helpers such as `findProjectRoot()` and `walkProject()`
74
+ - active-branch session helper: `getActiveBranchEntries()`
75
+ - terminal helpers such as `formatTitle()`, `signalWaiting()`, and `signalDone()`
63
76
 
64
77
  ## Example
65
78
 
@@ -80,17 +93,15 @@ registerConfigSettings({
80
93
  });
81
94
 
82
95
  const message = wrapExtensionContext("my-extension", "hello", {
83
- turn: 1,
84
96
  file: "CLAUDE.md",
97
+ turn: 1,
85
98
  });
86
99
  ```
87
100
 
88
- ## Requirements
89
-
90
- - `@earendil-works/pi-coding-agent`
91
- - `@earendil-works/pi-tui`
92
-
93
101
  ## Source
94
102
 
95
- - Library surface: `src/api.ts`
96
- - Extension surface: `src/extension.ts`
103
+ - `src/api.ts` — exported library surface
104
+ - `src/extension.ts` — minimal `/supi-settings` entrypoint
105
+ - `src/config.ts` — shared config loading and writing
106
+ - `src/config-settings.ts` — config-backed settings registration helper
107
+ - `src/settings-ui.ts` — shared settings overlay
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-core",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "SuPi core — shared infrastructure for SuPi extensions (XML context tags, config system)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -2,30 +2,30 @@
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // and settings registry for supi-wide TUI settings.
4
4
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config.ts";
5
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
6
  export {
7
7
  loadSupiConfig,
8
8
  loadSupiConfigForScope,
9
9
  removeSupiConfigKey,
10
10
  writeSupiConfig,
11
- } from "./config.ts";
12
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config-settings.ts";
13
- export { registerConfigSettings } from "./config-settings.ts";
14
- export type { ContextMessageLike } from "./context-messages.ts";
11
+ } from "./config/config.ts";
12
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
13
+ export { registerConfigSettings } from "./config/config-settings.ts";
14
+ export type { ContextMessageLike } from "./context/context-messages.ts";
15
15
  export {
16
16
  findLastUserMessageIndex,
17
17
  getContextToken,
18
18
  getPromptContent,
19
19
  pruneAndReorderContextMessages,
20
20
  restorePromptContent,
21
- } from "./context-messages.ts";
22
- export type { ContextProvider } from "./context-provider-registry.ts";
21
+ } from "./context/context-messages.ts";
22
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
23
23
  export {
24
24
  clearRegisteredContextProviders,
25
25
  getRegisteredContextProviders,
26
26
  registerContextProvider,
27
- } from "./context-provider-registry.ts";
28
- export { wrapExtensionContext } from "./context-tag.ts";
27
+ } from "./context/context-provider-registry.ts";
28
+ export { wrapExtensionContext } from "./context/context-tag.ts";
29
29
  export type {
30
30
  DebugAgentAccess,
31
31
  DebugEvent,
@@ -64,14 +64,14 @@ export {
64
64
  walkProject,
65
65
  } from "./project-roots.ts";
66
66
  export { getActiveBranchEntries } from "./session-utils.ts";
67
- export { registerSettingsCommand } from "./settings-command.ts";
68
- export type { SettingsScope, SettingsSection } from "./settings-registry.ts";
67
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
68
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
69
69
  export {
70
70
  clearRegisteredSettings,
71
71
  getRegisteredSettings,
72
72
  registerSettings,
73
- } from "./settings-registry.ts";
74
- export { createInputSubmenu, openSettingsOverlay } from "./settings-ui.ts";
73
+ } from "./settings/settings-registry.ts";
74
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
75
75
  export type { TitleTarget } from "./terminal.ts";
76
76
  export {
77
77
  DONE_SYMBOL,
@@ -2,9 +2,9 @@
2
2
  // Wraps registerSettings() and centralizes selected-scope loading + scoped persistence.
3
3
 
4
4
  import type { SettingItem } from "@earendil-works/pi-tui";
5
+ import type { SettingsScope } from "../settings/settings-registry.ts";
6
+ import { registerSettings } from "../settings/settings-registry.ts";
5
7
  import { loadSupiConfigForScope, removeSupiConfigKey, writeSupiConfig } from "./config.ts";
6
- import type { SettingsScope } from "./settings-registry.ts";
7
- import { registerSettings } from "./settings-registry.ts";
8
8
 
9
9
  export interface ConfigSettingsHelpers {
10
10
  /** Write a key to the selected scope's config section. */
@@ -3,7 +3,7 @@
3
3
  // Extensions declare context data providers via `registerContextProvider()` during their
4
4
  // factory function. The `/supi-context` command reads them via `getRegisteredContextProviders()`.
5
5
 
6
- import { createRegistry } from "./registry-utils.ts";
6
+ import { createRegistry } from "../registry-utils.ts";
7
7
 
8
8
  export interface ContextProvider {
9
9
  /** Unique identifier — e.g. "rtk" */
@@ -1 +1 @@
1
- export { registerSettingsCommand as default } from "./settings-command.ts";
1
+ export { registerSettingsCommand as default } from "./settings/settings-command.ts";
@@ -2,30 +2,30 @@
2
2
  // Provides XML context tag wrapping, unified config system, context-message utilities,
3
3
  // and settings registry for supi-wide TUI settings.
4
4
 
5
- export type { SupiConfigLocation, SupiConfigOptions } from "./config.ts";
5
+ export type { SupiConfigLocation, SupiConfigOptions } from "./config/config.ts";
6
6
  export {
7
7
  loadSupiConfig,
8
8
  loadSupiConfigForScope,
9
9
  removeSupiConfigKey,
10
10
  writeSupiConfig,
11
- } from "./config.ts";
12
- export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config-settings.ts";
13
- export { registerConfigSettings } from "./config-settings.ts";
14
- export type { ContextMessageLike } from "./context-messages.ts";
11
+ } from "./config/config.ts";
12
+ export type { ConfigSettingsHelpers, ConfigSettingsOptions } from "./config/config-settings.ts";
13
+ export { registerConfigSettings } from "./config/config-settings.ts";
14
+ export type { ContextMessageLike } from "./context/context-messages.ts";
15
15
  export {
16
16
  findLastUserMessageIndex,
17
17
  getContextToken,
18
18
  getPromptContent,
19
19
  pruneAndReorderContextMessages,
20
20
  restorePromptContent,
21
- } from "./context-messages.ts";
22
- export type { ContextProvider } from "./context-provider-registry.ts";
21
+ } from "./context/context-messages.ts";
22
+ export type { ContextProvider } from "./context/context-provider-registry.ts";
23
23
  export {
24
24
  clearRegisteredContextProviders,
25
25
  getRegisteredContextProviders,
26
26
  registerContextProvider,
27
- } from "./context-provider-registry.ts";
28
- export { wrapExtensionContext } from "./context-tag.ts";
27
+ } from "./context/context-provider-registry.ts";
28
+ export { wrapExtensionContext } from "./context/context-tag.ts";
29
29
  export type {
30
30
  DebugAgentAccess,
31
31
  DebugEvent,
@@ -64,14 +64,14 @@ export {
64
64
  walkProject,
65
65
  } from "./project-roots.ts";
66
66
  export { getActiveBranchEntries } from "./session-utils.ts";
67
- export { registerSettingsCommand } from "./settings-command.ts";
68
- export type { SettingsScope, SettingsSection } from "./settings-registry.ts";
67
+ export { registerSettingsCommand } from "./settings/settings-command.ts";
68
+ export type { SettingsScope, SettingsSection } from "./settings/settings-registry.ts";
69
69
  export {
70
70
  clearRegisteredSettings,
71
71
  getRegisteredSettings,
72
72
  registerSettings,
73
- } from "./settings-registry.ts";
74
- export { createInputSubmenu, openSettingsOverlay } from "./settings-ui.ts";
73
+ } from "./settings/settings-registry.ts";
74
+ export { createInputSubmenu, openSettingsOverlay } from "./settings/settings-ui.ts";
75
75
  export type { TitleTarget } from "./terminal.ts";
76
76
  export {
77
77
  DONE_SYMBOL,
@@ -4,7 +4,7 @@
4
4
  // factory function. The generic settings UI reads them via `getRegisteredSettings()`.
5
5
 
6
6
  import type { SettingItem } from "@earendil-works/pi-tui";
7
- import { createRegistry } from "./registry-utils.ts";
7
+ import { createRegistry } from "../registry-utils.ts";
8
8
 
9
9
  export type SettingsScope = "project" | "global";
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrclrchtr/supi-web",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "SuPi Web extension — fetch web pages as clean Markdown (web_fetch_md) and library docs via Context7 (web_docs_search, web_docs_fetch)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "@mozilla/readability": "^0.6.0",
26
26
  "turndown": "^7.2.0",
27
27
  "turndown-plugin-gfm": "^1.0.2",
28
- "@mrclrchtr/supi-core": "1.3.0"
28
+ "@mrclrchtr/supi-core": "1.4.0"
29
29
  },
30
30
  "bundledDependencies": [
31
31
  "@mrclrchtr/supi-core"
@@ -44,7 +44,8 @@
44
44
  },
45
45
  "pi": {
46
46
  "extensions": [
47
- "./src/extension.ts"
47
+ "./src/extension.ts",
48
+ "node_modules/@mrclrchtr/supi-core/src/extension.ts"
48
49
  ]
49
50
  },
50
51
  "main": "src/api.ts",
package/src/docs.ts CHANGED
@@ -8,43 +8,29 @@
8
8
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
9
  import { Type } from "typebox";
10
10
  import { Context7Error, getContext, searchLibrary } from "./context7-client.ts";
11
+ import {
12
+ promptGuidelines as fetchPromptGuidelines,
13
+ promptSnippet as fetchPromptSnippet,
14
+ toolDescription as fetchToolDescription,
15
+ } from "./tool/web-docs-fetch-guidance.ts";
16
+ import {
17
+ promptGuidelines as searchPromptGuidelines,
18
+ promptSnippet as searchPromptSnippet,
19
+ toolDescription as searchToolDescription,
20
+ } from "./tool/web-docs-search-guidance.ts";
11
21
 
12
22
  const SEARCH_TOOL_NAME = "web_docs_search";
13
23
  const SEARCH_TOOL_LABEL = "Web Docs Search";
14
24
  const FETCH_TOOL_NAME = "web_docs_fetch";
15
25
  const FETCH_TOOL_LABEL = "Web Docs Fetch";
16
26
 
17
- const SEARCH_TOOL_DESCRIPTION = `Search for libraries via Context7. Returns a Markdown table of matching libraries with metadata (ID, name, description, trust score, benchmark score, snippet count, versions). Use the library ID from results with web_docs_fetch to retrieve documentation.`;
18
-
19
- const FETCH_TOOL_DESCRIPTION = `Retrieve documentation context for a specific library via Context7. Returns up-to-date code snippets and documentation prose as Markdown, tailored to the query. Use web_docs_search first to find the library ID. Set raw=true to get JSON-serialized snippet objects instead of plain text. Requires a Context7 library ID (e.g. /facebook/react, /vercel/next.js).`;
20
-
21
- const SEARCH_PROMPT_SNIPPET =
22
- "web_docs_search — search Context7 for libraries matching a name, returns library IDs for use with web_docs_fetch";
23
-
24
- const FETCH_PROMPT_SNIPPET =
25
- "web_docs_fetch — retrieve up-to-date documentation context from Context7 for a specific library";
26
-
27
- const SEARCH_PROMPT_GUIDELINES = [
28
- "Use web_docs_search to find Context7 library IDs by name before calling web_docs_fetch.",
29
- "Pass a descriptive query along with the library name for better relevance ranking.",
30
- "Review the search results carefully — pick the library ID that best matches the user's need.",
31
- ];
32
-
33
- const FETCH_PROMPT_GUIDELINES = [
34
- "Use web_docs_fetch to get up-to-date, version-specific documentation for any library.",
35
- "The library_id must be a Context7 library ID like /facebook/react or /vercel/next.js.",
36
- "Set raw=true only when you need structured JSON snippets instead of plain text Markdown.",
37
- "If the library ID is unknown, call web_docs_search first to find it.",
38
- "Prefer descriptive, specific queries over vague ones for better results.",
39
- ];
40
-
41
27
  export default function docsExtension(pi: ExtensionAPI): void {
42
28
  pi.registerTool({
43
29
  name: SEARCH_TOOL_NAME,
44
30
  label: SEARCH_TOOL_LABEL,
45
- description: SEARCH_TOOL_DESCRIPTION,
46
- promptSnippet: SEARCH_PROMPT_SNIPPET,
47
- promptGuidelines: SEARCH_PROMPT_GUIDELINES,
31
+ description: searchToolDescription,
32
+ promptSnippet: searchPromptSnippet,
33
+ promptGuidelines: searchPromptGuidelines,
48
34
  parameters: Type.Object({
49
35
  library_name: Type.String({
50
36
  description: "Library name to search for (e.g. react, next.js, fastapi)",
@@ -59,9 +45,9 @@ export default function docsExtension(pi: ExtensionAPI): void {
59
45
  pi.registerTool({
60
46
  name: FETCH_TOOL_NAME,
61
47
  label: FETCH_TOOL_LABEL,
62
- description: FETCH_TOOL_DESCRIPTION,
63
- promptSnippet: FETCH_PROMPT_SNIPPET,
64
- promptGuidelines: FETCH_PROMPT_GUIDELINES,
48
+ description: fetchToolDescription,
49
+ promptSnippet: fetchPromptSnippet,
50
+ promptGuidelines: fetchPromptGuidelines,
65
51
  parameters: Type.Object({
66
52
  library_id: Type.String({
67
53
  description:
@@ -0,0 +1,14 @@
1
+ // Prompt guidance and tool description for the web_docs_fetch tool.
2
+
3
+ export const toolDescription = `Retrieve up-to-date documentation context for a specific library via Context7. Returns Markdown documentation and code snippets tailored to the query, or JSON-serialized snippet objects when \`raw: true\`. Use web_docs_fetch after web_docs_search or whenever the exact Context7 library ID is already known. Requires a Context7 library ID (e.g. /facebook/react, /vercel/next.js).`;
4
+
5
+ export const promptSnippet =
6
+ "web_docs_fetch — retrieve focused, up-to-date Context7 docs for a known library ID";
7
+
8
+ export const promptGuidelines = [
9
+ "Use web_docs_fetch after web_docs_search, or use web_docs_fetch directly when the exact Context7 `library_id` is already known.",
10
+ "Use web_docs_fetch only with Context7 library IDs such as `/facebook/react` or `/vercel/next.js`.",
11
+ "Use web_docs_fetch with a specific, task-oriented `query` to retrieve focused version-aware documentation instead of a vague broad dump.",
12
+ "Use web_docs_fetch with `raw: true` only when you need structured JSON snippet objects instead of Markdown documentation.",
13
+ "Call web_docs_search before web_docs_fetch when the correct `library_id` is unknown or ambiguous.",
14
+ ];
@@ -0,0 +1,12 @@
1
+ // Prompt guidance and tool description for the web_docs_search tool.
2
+
3
+ export const toolDescription = `Search Context7 for library IDs and metadata before fetching docs. Returns a Markdown table of matching libraries (ID, name, description, trust score, benchmark score, snippet count, versions). Use web_docs_search when you know the library name but not the exact Context7 library ID.`;
4
+
5
+ export const promptSnippet =
6
+ "web_docs_search — find Context7 library IDs and candidate versions before calling web_docs_fetch";
7
+
8
+ export const promptGuidelines = [
9
+ "Use web_docs_search before web_docs_fetch when you do not already know the exact Context7 `library_id`.",
10
+ "Use web_docs_search with both `library_name` and a descriptive `query` so Context7 can rank results for the user's actual task.",
11
+ "Review web_docs_search results carefully and choose the `library_id` that best matches the user's framework, package scope, and version needs.",
12
+ ];
@@ -0,0 +1,44 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ // Prompt guidance and tool description for the web_fetch_md tool.
4
+
5
+ const INLINE_MAX_CHARS = 15_000;
6
+
7
+ export const toolDescription = `Fetch a web page and convert it to clean Markdown for LLM ingestion.
8
+
9
+ Use web_fetch_md when the user provides a public URL or asks you to inspect public web content. Only accepts real \`http://\` or \`https://\` URLs. If the page is access-controlled (login, paywall, private content), stop and ask the user for an allowed source or exported content.
10
+
11
+ Output modes:
12
+ - \`auto\` (default): returns Markdown inline if ≤${INLINE_MAX_CHARS.toLocaleString()} characters; otherwise writes to a temporary file and returns the path.
13
+ - \`inline\`: always returns Markdown inline.
14
+ - \`file\`: always writes to a temporary file and returns the path.
15
+
16
+ Links and images are absolutized by default. Use \`abs_links: false\` to keep them as-is.`;
17
+
18
+ export const promptSnippet =
19
+ "web_fetch_md — fetch a public URL and convert the response to clean Markdown for LLM ingestion";
20
+
21
+ function isGhAvailable(): boolean {
22
+ try {
23
+ const result = spawnSync("gh", ["--version"], { stdio: "ignore" });
24
+ return result.status === 0;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ export function buildPromptGuidelines(): string[] {
31
+ const guidelines = [
32
+ "Use web_fetch_md to fetch a public web page and convert it to clean Markdown for the model.",
33
+ "Use web_fetch_md only with real `http://` or `https://` URLs; if a page is access-controlled, stop and ask the user for an allowed source or exported content.",
34
+ "Use web_fetch_md with `output_mode: auto` unless you have a specific reason to force inline output or a temp file path.",
35
+ "Use web_fetch_md with `output_mode: inline` only when you need the Markdown directly in context, and use web_fetch_md with `output_mode: file` when you explicitly want a saved temp file path.",
36
+ "Use web_fetch_md with `abs_links: false` only when relative links or images are intentionally desired.",
37
+ ];
38
+ if (isGhAvailable()) {
39
+ guidelines.push(
40
+ "Use bash with the `gh` CLI instead of web_fetch_md for GitHub URLs when `gh` is available.",
41
+ );
42
+ }
43
+ return guidelines;
44
+ }
package/src/web.ts CHANGED
@@ -2,55 +2,21 @@
2
2
  * SuPi Web extension entry point — registers the `web_fetch_md` tool with pi.
3
3
  */
4
4
 
5
- import { spawnSync } from "node:child_process";
6
5
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
7
6
  import { Type } from "typebox";
8
7
  import { htmlToMarkdown, wrapAsCodeBlock } from "./convert.ts";
9
8
  import { FetchError, fetchWithNegotiation, isValidHttpUrl } from "./fetch.ts";
10
9
  import { writeTempFile } from "./temp-file.ts";
10
+ import {
11
+ buildPromptGuidelines,
12
+ promptSnippet,
13
+ toolDescription,
14
+ } from "./tool/web-fetch-md-guidance.ts";
11
15
 
12
16
  const TOOL_NAME = "web_fetch_md";
13
17
  const TOOL_LABEL = "Web Fetch";
14
18
  const INLINE_MAX_CHARS = 15_000;
15
19
 
16
- const TOOL_DESCRIPTION = `Fetch a web page and convert it to clean Markdown for LLM ingestion.
17
-
18
- Only accepts real \`http://\` or \`https://\` URLs. If the page is access-controlled (login, paywall, private content), stop and ask the user for an allowed source or exported content.
19
-
20
- Output modes:
21
- - \`auto\` (default): returns Markdown inline if ≤${INLINE_MAX_CHARS.toLocaleString()} characters; otherwise writes to a temporary file and returns the path.
22
- - \`inline\`: always returns Markdown inline.
23
- - \`file\`: always writes to a temporary file and returns the path.
24
-
25
- Links and images are absolutized by default. Use \`abs_links: false\` to keep them as-is.`;
26
-
27
- const PROMPT_SNIPPET =
28
- "web_fetch_md — fetch a URL and convert it to clean Markdown suitable for LLM ingestion.";
29
-
30
- function isGhAvailable(): boolean {
31
- try {
32
- const result = spawnSync("gh", ["--version"], { stdio: "ignore" });
33
- return result.status === 0;
34
- } catch {
35
- return false;
36
- }
37
- }
38
-
39
- function buildPromptGuidelines(): string[] {
40
- const guidelines = [
41
- "Use web_fetch_md to fetch web pages and convert them to clean Markdown for LLM ingestion.",
42
- "Only accept real `http://` or `https://` URLs; stop and ask the user for an allowed source if the page is access-controlled.",
43
- "Prefer `output_mode: auto` (default) so large pages are written to temp files instead of flooding the context window.",
44
- "Set `abs_links: false` only when relative links are intentional (e.g., local documentation).",
45
- ];
46
- if (isGhAvailable()) {
47
- guidelines.push(
48
- "For GitHub URLs (e.g., repos, issues, PRs, releases), prefer the `gh` CLI via `bash` over this tool.",
49
- );
50
- }
51
- return guidelines;
52
- }
53
-
54
20
  const OutputModeEnum = Type.Union(
55
21
  [Type.Literal("auto"), Type.Literal("inline"), Type.Literal("file")],
56
22
  { default: "auto", description: "Output mode: auto, inline, or file" },
@@ -60,8 +26,8 @@ export default function webExtension(pi: ExtensionAPI): void {
60
26
  pi.registerTool({
61
27
  name: TOOL_NAME,
62
28
  label: TOOL_LABEL,
63
- description: TOOL_DESCRIPTION,
64
- promptSnippet: PROMPT_SNIPPET,
29
+ description: toolDescription,
30
+ promptSnippet,
65
31
  promptGuidelines: buildPromptGuidelines(),
66
32
  parameters: Type.Object({
67
33
  url: Type.String({ description: "http(s) URL to fetch" }),