@narumitw/pi-firecrawl 0.13.0 → 0.13.1
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 +5 -3
- package/package.json +1 -1
- package/src/client.ts +99 -0
- package/src/firecrawl.ts +26 -792
- package/src/settings.ts +188 -0
- package/src/tool-selector.ts +330 -0
- package/src/tools.ts +186 -0
package/README.md
CHANGED
|
@@ -111,8 +111,7 @@ instead of enabling tools by itself. A valid saved selection is restored on Pi s
|
|
|
111
111
|
Compatibility: older versions used `pi-firecrawl-settings.json`. During the migration window,
|
|
112
112
|
a legacy-only file is automatically migrated to `pi-firecrawl.json` with a warning. If both
|
|
113
113
|
files exist, `pi-firecrawl.json` wins and the legacy file is ignored. The legacy filename is
|
|
114
|
-
deprecated and
|
|
115
|
-
or at the next major release.
|
|
114
|
+
deprecated and will be removed in a future major release.
|
|
116
115
|
|
|
117
116
|
## 🚀 Examples
|
|
118
117
|
|
|
@@ -159,13 +158,16 @@ Start a crawl with markdown extraction:
|
|
|
159
158
|
```txt
|
|
160
159
|
extensions/pi-firecrawl/
|
|
161
160
|
├── src/
|
|
162
|
-
│
|
|
161
|
+
│ ├── firecrawl.ts # Pi entrypoint and command orchestration
|
|
162
|
+
│ └── *.ts # Package-local client, settings, selector, and tool modules
|
|
163
163
|
├── README.md
|
|
164
164
|
├── LICENSE
|
|
165
165
|
├── tsconfig.json
|
|
166
166
|
└── package.json
|
|
167
167
|
```
|
|
168
168
|
|
|
169
|
+
Only `firecrawl.ts` is a Pi entrypoint; the other source modules are internal.
|
|
170
|
+
|
|
169
171
|
## 🔎 Keywords
|
|
170
172
|
|
|
171
173
|
Pi extension, Pi coding agent, Firecrawl, web scraping, web crawling, URL discovery, web search, markdown extraction, AI research agent, TypeScript Pi tools.
|
package/package.json
CHANGED
package/src/client.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_API_URL = "https://api.firecrawl.dev/v1";
|
|
4
|
+
const STATUS_KEY = "firecrawl";
|
|
5
|
+
|
|
6
|
+
let apiUrl = normalizeApiUrl(process.env.FIRECRAWL_API_URL ?? process.env.FIRECRAWL_BASE_URL);
|
|
7
|
+
|
|
8
|
+
export function configuredApiUrl() {
|
|
9
|
+
return apiUrl;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function resetConfiguredApiUrl() {
|
|
13
|
+
apiUrl = normalizeApiUrl(process.env.FIRECRAWL_API_URL ?? process.env.FIRECRAWL_BASE_URL);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function firecrawlRequest(
|
|
17
|
+
method: "GET" | "POST",
|
|
18
|
+
path: string,
|
|
19
|
+
body: unknown,
|
|
20
|
+
signal?: AbortSignal,
|
|
21
|
+
) {
|
|
22
|
+
const apiKey = getApiKey();
|
|
23
|
+
const response = await fetch(`${apiUrl}${path}`, {
|
|
24
|
+
method,
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${apiKey}`,
|
|
27
|
+
...(body === undefined ? {} : { "Content-Type": "application/json" }),
|
|
28
|
+
},
|
|
29
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
30
|
+
signal,
|
|
31
|
+
});
|
|
32
|
+
const responseText = await response.text();
|
|
33
|
+
const payload = parseResponseBody(responseText);
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`Firecrawl ${method} ${path} failed (${response.status}): ${formatPayload(payload)}`);
|
|
36
|
+
}
|
|
37
|
+
return payload;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getApiKey() {
|
|
41
|
+
const apiKey = process.env.FIRECRAWL_API_KEY?.trim();
|
|
42
|
+
if (!apiKey) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
"FIRECRAWL_API_KEY is not configured. Set it in your shell or Pi environment, then retry once.",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return apiKey;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function hasApiKey() {
|
|
51
|
+
return Boolean(process.env.FIRECRAWL_API_KEY?.trim());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function normalizeApiUrl(value: string | undefined) {
|
|
55
|
+
return (value?.trim() || DEFAULT_API_URL).replace(/\/+$/, "");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function parseResponseBody(responseText: string) {
|
|
59
|
+
if (!responseText) return {};
|
|
60
|
+
try {
|
|
61
|
+
return JSON.parse(responseText) as unknown;
|
|
62
|
+
} catch {
|
|
63
|
+
return responseText;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function formatPayload(payload: unknown) {
|
|
68
|
+
return typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function jsonResult(payload: unknown) {
|
|
72
|
+
return {
|
|
73
|
+
content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }],
|
|
74
|
+
details: payload,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function withStatus<T>(
|
|
79
|
+
ctx: Pick<ExtensionContext, "ui">,
|
|
80
|
+
status: string,
|
|
81
|
+
callback: () => Promise<T>,
|
|
82
|
+
) {
|
|
83
|
+
ctx.ui.setStatus(STATUS_KEY, status);
|
|
84
|
+
try {
|
|
85
|
+
return await callback();
|
|
86
|
+
} finally {
|
|
87
|
+
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function cleanObject<T>(value: T): T {
|
|
92
|
+
if (Array.isArray(value)) return value.map(cleanObject) as T;
|
|
93
|
+
if (!value || typeof value !== "object") return value;
|
|
94
|
+
return Object.fromEntries(
|
|
95
|
+
Object.entries(value as Record<string, unknown>)
|
|
96
|
+
.filter(([, item]) => item !== undefined)
|
|
97
|
+
.map(([key, item]) => [key, cleanObject(item)]),
|
|
98
|
+
) as T;
|
|
99
|
+
}
|