feat(F-001): scaffold modular monolith skeleton with boundary checker
- TypeScript + Fastify skeleton under project/ (src/modules, shared, infrastructure, app) - scripts/check-module-boundaries.mjs enforcing module public-API rules (tested with fixtures) - GET /health endpoint, error envelope without stack leakage - specs/F-001-scaffold (SPEC/DESIGN/TASKS/TESTS), spec/tech.md dependency justification - 30-ticket MercadoDeVida roadmap in backlog/features.json, spec/roadmap.md - All gates approved: reviewer, security, qa; verify.sh green
This commit is contained in:
56
platforms/pi/extensions/orquestra-web-fetch.ts
Normal file
56
platforms/pi/extensions/orquestra-web-fetch.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
||||
import { Type } from "typebox";
|
||||
|
||||
function textFromHtml(html: string): { title: string; content: string } {
|
||||
const title = html.match(/<title[^>]*>([^<]+)<\/title>/i)?.[1]?.trim() || "No title";
|
||||
const content = html
|
||||
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, " ")
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, " ")
|
||||
.replace(/<[^>]+>/g, " ")
|
||||
.replace(/ /g, " ")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, '"')
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
return { title, content };
|
||||
}
|
||||
|
||||
async function fetchText(url: string, signal?: AbortSignal) {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 30000);
|
||||
const activeSignal = signal ?? controller.signal;
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
signal: activeSignal,
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 Orquestra/1.0",
|
||||
},
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||
return textFromHtml(await response.text());
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
export default function orquestraWebFetch(pi: ExtensionAPI) {
|
||||
pi.registerTool({
|
||||
name: "orquestra_web_fetch",
|
||||
label: "Orquestra Web Fetch",
|
||||
description: "Fetch a web page and return extracted text using a simple non-browser fetch.",
|
||||
parameters: Type.Object({
|
||||
url: Type.String({ description: "URL to fetch" }),
|
||||
limit: Type.Optional(Type.Integer({ minimum: 500, maximum: 20000, description: "Max characters to return" })),
|
||||
}),
|
||||
async execute(_toolCallId, params, signal) {
|
||||
const { title, content } = await fetchText(params.url, signal);
|
||||
const limit = params.limit ?? 8000;
|
||||
return {
|
||||
content: [{ type: "text", text: `Title: ${title}\n\nContent:\n${content.slice(0, limit)}` }],
|
||||
details: { title, url: params.url, characters: content.length },
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user