File size: 5,180 Bytes
63458bf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | import "@std/dotenv/load";
import {
AnthropicModelProvider,
ZypherAgent,
createZypherContext,
} from "@corespeed/zypher";
import { eachValueFrom } from "rxjs-for-await";
function getRequiredEnv(name: string): string {
const value = Deno.env.get(name);
if (!value) {
throw new Error(`Environment variable ${name} is not set`);
}
return value;
}
const prompt = `You are a documentation extraction agent. Extract and format documentation as clean markdown.
RULES:
- Output ONLY the final markdown document
- Do NOT include meta-commentary like "I'll help you...", "Let me create...", "Perfect!", etc.
- Do NOT narrate what you're doing
- Start directly with the documentation title
PROCESS:
1. Crawl the main page (be fast - don't follow multiple links)
2. Extract: installation, key concepts, 1-2 code examples
3. Output the markdown document immediately
FORMAT:
# [Document Title]
> Source: [original URL]
[Your extracted content with headings, code blocks, and links]
Be concise and practical. Output the document, not commentary about creating it.`;
export class DocsExtractorAgent {
public zypher: ZypherAgent;
private constructor(agent: ZypherAgent) {
this.zypher = agent;
}
static async create() {
// Initialize the agent execution context
const workingDir = Deno.cwd();
const homeDir = Deno.env.get("HOME") || Deno.env.get("USERPROFILE") || workingDir;
const zypherContext = await createZypherContext(workingDir, {
zypherDir: `${homeDir}/.zypher`,
enableBuiltInTools: true, // Explicitly enable built-in tools
});
// Create the agent with Anthropic model provider
const agent = new ZypherAgent(
zypherContext,
new AnthropicModelProvider({
apiKey: getRequiredEnv("ANTHROPIC_API_KEY"),
}),
{
customInstructions: prompt,
},
);
// Register Firecrawl MCP server
try {
await agent.mcp.registerServer({
id: "firecrawl",
type: "command",
command: {
command: "npx",
args: ["-y", "firecrawl-mcp"],
env: {
FIRECRAWL_API_KEY: getRequiredEnv("FIRECRAWL_API_KEY"),
FIRECRAWL_TIMEOUT: "30000", // 30 second timeout
FIRECRAWL_MAX_DEPTH: "1", // Only crawl 1 level deep
},
},
});
console.log("✅ Registered Firecrawl MCP server");
} catch (error) {
console.error("⚠️ Failed to register Firecrawl MCP server:", error);
}
return new DocsExtractorAgent(agent);
}
async extract(docsUrl: string, extractionType: string, model: string = "claude-sonnet-4-20250514") {
const userPrompt = `Extract from ${docsUrl}: ${extractionType}
OUTPUT ONLY THE MARKDOWN DOCUMENT. No meta-commentary.
Start with:
# [Title for ${extractionType}]
> Source: ${docsUrl}
Then include:
- Installation/setup (if relevant)
- Key concepts
- 1-2 code examples
- Links to original docs
Keep it concise and practical. Output the document directly.`;
console.log(`🔍 Starting extraction from ${docsUrl}...`);
const event$ = this.zypher.runTask(userPrompt, model);
let responseText = "";
const toolUses: Array<{ name: string; input: unknown }> = [];
for await (const event of eachValueFrom(event$)) {
switch (event.type) {
case "text":
case "message":
if (event.content?.text) {
responseText += event.content.text;
} else if (typeof event.content === "string") {
responseText += event.content;
}
break;
case "tool_use":
case "tool_use_input":
if (event.content?.name) {
toolUses.push({
name: event.content.name,
input: event.content.input,
});
} else if (event.tool || event.name) {
const toolName = event.tool || event.name;
const toolInput = event.input || event.content;
toolUses.push({ name: toolName, input: toolInput });
}
break;
case "tool_result":
case "tool_use_approved":
default:
break;
}
}
console.log("✅ Extraction completed!");
// Save the response text to a file
try {
if (responseText) {
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `extraction-${timestamp}.md`;
const filepath = `./generated-docs/${filename}`;
await Deno.writeTextFile(filepath, responseText);
console.log(`📄 Saved: ${filename}`);
}
} catch (error) {
console.error("❌ Error saving file:", error);
}
return {
response: {
content: [
{
type: "text",
text: responseText,
},
],
},
toolUses,
};
}
async cleanup() {
}
}
// Terminal mode: Run agent in interactive CLI
if (import.meta.main) {
const { runAgentInTerminal } = await import("@corespeed/zypher");
const agent = await DocsExtractorAgent.create();
await runAgentInTerminal(agent.zypher, "claude-sonnet-4-20250514");
await agent.cleanup();
}
|