@f5xc-salesdemos/xcsh 14.1.1 → 14.2.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@f5xc-salesdemos/xcsh",
4
- "version": "14.1.1",
4
+ "version": "14.2.1",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/f5xc-salesdemos/xcsh",
7
7
  "author": "Can Boluk",
@@ -46,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.16.1",
48
48
  "@mozilla/readability": "^0.6",
49
- "@f5xc-salesdemos/xcsh-stats": "14.1.1",
50
- "@f5xc-salesdemos/pi-agent-core": "14.1.1",
51
- "@f5xc-salesdemos/pi-ai": "14.1.1",
52
- "@f5xc-salesdemos/pi-natives": "14.1.1",
53
- "@f5xc-salesdemos/pi-tui": "14.1.1",
54
- "@f5xc-salesdemos/pi-utils": "14.1.1",
49
+ "@f5xc-salesdemos/xcsh-stats": "14.2.1",
50
+ "@f5xc-salesdemos/pi-agent-core": "14.2.1",
51
+ "@f5xc-salesdemos/pi-ai": "14.2.1",
52
+ "@f5xc-salesdemos/pi-natives": "14.2.1",
53
+ "@f5xc-salesdemos/pi-tui": "14.2.1",
54
+ "@f5xc-salesdemos/pi-utils": "14.2.1",
55
55
  "@sinclair/typebox": "^0.34",
56
56
  "@xterm/headless": "^6.0",
57
57
  "ajv": "^8.18",
@@ -1579,6 +1579,7 @@ export const SETTINGS_SCHEMA = {
1579
1579
  type: "enum",
1580
1580
  values: [
1581
1581
  "auto",
1582
+ "firecrawl",
1582
1583
  "exa",
1583
1584
  "brave",
1584
1585
  "jina",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "colors": {
17
17
  "accent": "f5Red",
18
- "border": "subtleGray",
18
+ "border": "f5Red",
19
19
  "borderAccent": "f5Red",
20
20
  "borderMuted": "f5Red",
21
21
  "success": "signalGreen",
@@ -58,12 +58,12 @@
58
58
  "syntaxType": "coolGray",
59
59
  "syntaxOperator": "coolGray",
60
60
  "syntaxPunctuation": "coolGray",
61
- "thinkingOff": "#4a5058",
62
- "thinkingMinimal": "#5a6068",
63
- "thinkingLow": "#6a7078",
64
- "thinkingMedium": "coolGray",
65
- "thinkingHigh": "coolGray",
66
- "thinkingXhigh": "warmAmber",
61
+ "thinkingOff": "f5Red",
62
+ "thinkingMinimal": "f5Red",
63
+ "thinkingLow": "f5Red",
64
+ "thinkingMedium": "f5Red",
65
+ "thinkingHigh": "f5Red",
66
+ "thinkingXhigh": "f5Red",
67
67
  "bashMode": "signalGreen",
68
68
  "statusLineBg": "deepCharcoal",
69
69
  "statusLineSep": "subtleGray",
@@ -3,6 +3,7 @@ import type { SearchProvider } from "./providers/base";
3
3
  import { BraveProvider } from "./providers/brave";
4
4
  import { CodexProvider } from "./providers/codex";
5
5
  import { ExaProvider } from "./providers/exa";
6
+ import { FirecrawlProvider } from "./providers/firecrawl";
6
7
  import { GeminiProvider } from "./providers/gemini";
7
8
  import { JinaProvider } from "./providers/jina";
8
9
  import { KagiProvider } from "./providers/kagi";
@@ -30,10 +31,12 @@ const SEARCH_PROVIDERS: Record<SearchProviderId, SearchProvider> = {
30
31
  tavily: new TavilyProvider(),
31
32
  parallel: new ParallelProvider(),
32
33
  kagi: new KagiProvider(),
34
+ firecrawl: new FirecrawlProvider(),
33
35
  synthetic: new SyntheticProvider(),
34
36
  } as const;
35
37
 
36
38
  export const SEARCH_PROVIDER_ORDER: SearchProviderId[] = [
39
+ "firecrawl",
37
40
  "tavily",
38
41
  "perplexity",
39
42
  "brave",
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Firecrawl Web Search Provider
3
+ *
4
+ * Calls a local Firecrawl /v1/search API (backed by SearXNG) and maps
5
+ * results into the unified SearchResponse shape used by the web search tool.
6
+ *
7
+ * Set FIRECRAWL_SEARCH_URL to the Firecrawl base URL (e.g. http://localhost:3002).
8
+ */
9
+ import type { SearchResponse, SearchSource } from "../../../web/search/types";
10
+ import { SearchProviderError } from "../../../web/search/types";
11
+ import { clampNumResults, dateToAgeSeconds } from "../utils";
12
+ import type { SearchParams } from "./base";
13
+ import { SearchProvider } from "./base";
14
+
15
+ const DEFAULT_NUM_RESULTS = 5;
16
+ const MAX_NUM_RESULTS = 20;
17
+
18
+ export interface FirecrawlSearchParams {
19
+ query: string;
20
+ num_results?: number;
21
+ signal?: AbortSignal;
22
+ }
23
+
24
+ interface FirecrawlResultItem {
25
+ title?: string | null;
26
+ url?: string | null;
27
+ markdown?: string | null;
28
+ metadata?: {
29
+ publishedTime?: string | null;
30
+ [key: string]: unknown;
31
+ } | null;
32
+ }
33
+
34
+ interface FirecrawlSearchResponse {
35
+ data?: FirecrawlResultItem[];
36
+ }
37
+
38
+ /** Return the configured Firecrawl base URL, or null if unset. */
39
+ export function getFirecrawlUrl(): string | null {
40
+ return process.env.FIRECRAWL_SEARCH_URL ?? null;
41
+ }
42
+
43
+ async function callFirecrawlSearch(baseUrl: string, params: FirecrawlSearchParams): Promise<FirecrawlSearchResponse> {
44
+ const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
45
+
46
+ const response = await fetch(`${baseUrl}/v1/search`, {
47
+ method: "POST",
48
+ headers: { "Content-Type": "application/json" },
49
+ body: JSON.stringify({
50
+ query: params.query,
51
+ limit: numResults,
52
+ scrapeOptions: { formats: ["markdown"], onlyMainContent: true },
53
+ }),
54
+ signal: params.signal,
55
+ });
56
+
57
+ if (!response.ok) {
58
+ const errorText = await response.text();
59
+ throw new SearchProviderError(
60
+ "firecrawl",
61
+ `Firecrawl API error (${response.status}): ${errorText.trim() || response.statusText}`,
62
+ response.status,
63
+ );
64
+ }
65
+
66
+ return (await response.json()) as FirecrawlSearchResponse;
67
+ }
68
+
69
+ /** Execute a web search via the local Firecrawl/SearXNG instance. */
70
+ export async function searchFirecrawl(params: FirecrawlSearchParams): Promise<SearchResponse> {
71
+ const baseUrl = getFirecrawlUrl();
72
+ if (!baseUrl) {
73
+ throw new Error(
74
+ "FIRECRAWL_SEARCH_URL is not configured. Set it to the Firecrawl base URL (e.g. http://localhost:3002).",
75
+ );
76
+ }
77
+
78
+ const numResults = clampNumResults(params.num_results, DEFAULT_NUM_RESULTS, MAX_NUM_RESULTS);
79
+ const response = await callFirecrawlSearch(baseUrl, params);
80
+ const sources: SearchSource[] = [];
81
+
82
+ for (const item of response.data ?? []) {
83
+ if (!item.url) continue;
84
+ sources.push({
85
+ title: item.title ?? item.url,
86
+ url: item.url,
87
+ snippet: item.markdown?.trim() || undefined,
88
+ publishedDate: item.metadata?.publishedTime ?? undefined,
89
+ ageSeconds: dateToAgeSeconds(item.metadata?.publishedTime ?? undefined),
90
+ });
91
+ }
92
+
93
+ return {
94
+ provider: "firecrawl",
95
+ sources: sources.slice(0, numResults),
96
+ };
97
+ }
98
+
99
+ /** Search provider for local Firecrawl/SearXNG. */
100
+ export class FirecrawlProvider extends SearchProvider {
101
+ readonly id = "firecrawl" as const;
102
+ readonly label = "Firecrawl";
103
+
104
+ isAvailable() {
105
+ return !!getFirecrawlUrl();
106
+ }
107
+
108
+ search(params: SearchParams): Promise<SearchResponse> {
109
+ return searchFirecrawl({
110
+ query: params.query,
111
+ num_results: params.numSearchResults ?? params.limit,
112
+ signal: params.signal,
113
+ });
114
+ }
115
+ }
@@ -18,6 +18,7 @@ export type SearchProviderId =
18
18
  | "tavily"
19
19
  | "parallel"
20
20
  | "kagi"
21
+ | "firecrawl"
21
22
  | "synthetic";
22
23
 
23
24
  export function isSearchProviderId(value: string): value is SearchProviderId {
@@ -34,6 +35,7 @@ export function isSearchProviderId(value: string): value is SearchProviderId {
34
35
  "tavily",
35
36
  "parallel",
36
37
  "kagi",
38
+ "firecrawl",
37
39
  "synthetic",
38
40
  ].includes(value);
39
41
  }