Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Optional: install these if your config uses document sources or local embeddings
# RUN npm install pdf-parse mammoth # For type: document (PDF/DOCX)
# RUN npm install @xenova/transformers # For embedding.provider: local
COPY --from=build /app/dist/ ./dist/
COPY deploy/copilotkit-docs.yaml ./copilotkit-docs.yaml
COPY deploy/pathfinder-docs.yaml ./pathfinder-docs.yaml
Expand Down
17 changes: 16 additions & 1 deletion docs/deploy/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,22 @@ <h2>Environment Variables</h2>
</table>

<h3 id="optional-dependencies">Optional dependencies</h3>
<p>For <code>document</code> source type (PDF/DOCX), install optional peer dependencies: <code>npm install pdf-parse mammoth</code></p>
<p>Some features require extra packages that are not bundled by default. Install only the ones your config needs:</p>
<table class="env-table">
<thead>
<tr>
<th>Package</th>
<th>When required</th>
<th>Install</th>
</tr>
</thead>
<tbody>
<tr><td><code>pdf-parse</code></td><td>Any source has <code>type: document</code> with <code>*.pdf</code> file patterns</td><td><code>npm install pdf-parse</code></td></tr>
<tr><td><code>mammoth</code></td><td>Any source has <code>type: document</code> with <code>*.docx</code> file patterns</td><td><code>npm install mammoth</code></td></tr>
<tr><td><code>@xenova/transformers</code></td><td><code>embedding.provider</code> is <code>local</code></td><td><code>npm install @xenova/transformers</code></td></tr>
</tbody>
</table>
<p>Run <code>pathfinder validate</code> to detect missing optional dependencies and get install instructions.</p>

<h3 id="health-endpoint">Health endpoint</h3>
<p>Pathfinder exposes <code>GET /health</code> for monitoring. It returns JSON with uptime, indexing status, chunk counts per source, and index state (last indexed time, commit SHA, errors). Use it for load balancer health checks and deployment verification.</p>
Expand Down
76 changes: 55 additions & 21 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@
--warning: #ffaa00;
--mono: "JetBrains Mono", "SF Mono", "Fira Code", monospace;
--sans: "Instrument Sans", -apple-system, system-ui, sans-serif;
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}
/* Animations */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in { opacity: 0; transform: translateY(30px); transition: opacity 0.8s var(--ease-out-expo), transform 0.8s var(--ease-out-expo); }
.fade-in.visible { opacity: 1; transform: translateY(0); }
* { margin:0; padding:0; box-sizing:border-box; }
html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; font-size: 16px; }
body { background: var(--bg-deep); color: var(--text-primary); font-family: var(--sans); line-height: 1.6; overflow-x:hidden; }
Expand Down Expand Up @@ -66,14 +74,14 @@

/* Hero */
.hero { padding: 120px 0 3rem; max-width: 1120px; margin: 0 auto; padding-left: 2rem; padding-right: 2rem; }
.badge { display: inline-flex; align-items:center; gap: 8px; padding: 6px 16px; border: 1px solid var(--border); border-radius: 100px; font-family: var(--mono); font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 32px; }
.badge { display: inline-flex; align-items:center; gap: 8px; padding: 6px 16px; border: 1px solid var(--border); border-radius: 100px; font-family: var(--mono); font-size: 0.75rem; color: var(--text-secondary); margin-bottom: 32px; animation: fadeUp 0.8s var(--ease-out-expo) both; }
.badge .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); animation: pulse 2s infinite; }
@keyframes pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }
.hero h1 { font-size: clamp(2.5rem, 6vw, 4.5rem); font-weight: 700; line-height: 1.08; margin-bottom: 24px; letter-spacing: -0.03em; }
.hero h1 { font-size: clamp(2.5rem, 6vw, 4.5rem); font-weight: 700; line-height: 1.08; margin-bottom: 24px; letter-spacing: -0.03em; animation: fadeUp 0.8s var(--ease-out-expo) 0.1s both; }
.hero-break { display: none; }
@media (min-width: 769px) { .hero-break { display: block; } }
.hero h1 .accent { color: var(--accent); }
.hero .subtitle { font-size: 1.2rem; color: var(--text-secondary); max-width: 700px; line-height: 1.7; margin-bottom: 40px; }
.hero .subtitle { font-size: 1.2rem; color: var(--text-secondary); max-width: 700px; line-height: 1.7; margin-bottom: 40px; animation: fadeUp 0.8s var(--ease-out-expo) 0.2s both; }

/* Install box */
.install-box { display: inline-flex; flex-direction: column; gap: 0; background: var(--bg-surface); border: 1px solid var(--border); border-radius: 8px; padding: 12px 20px; font-family: var(--mono); font-size: 0.9rem; margin-bottom: 2.5rem; }
Expand Down Expand Up @@ -104,7 +112,7 @@

/* Feature cards (3x3 grid) */
.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
.feature-card { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 12px; padding: 2rem; box-shadow: 0 4px 16px rgba(0,0,0,0.2); transition: transform 0.2s, border-color 0.2s, box-shadow 0.2s; }
.feature-card { background: var(--bg-surface); border: 1px solid var(--border); border-radius: 12px; padding: 2rem; box-shadow: 0 4px 16px rgba(0,0,0,0.2); transition: all 0.3s var(--ease-out-expo); }
.feature-card:hover { transform: translateY(-4px); border-color: var(--accent-dim); box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
.feature-card .icon { font-size: 1.75rem; margin-bottom: 1rem; }
.feature-card h3 { font-size: 1.05rem; font-weight: 600; margin-bottom: 0.5rem; }
Expand Down Expand Up @@ -197,6 +205,9 @@
<a href="/" class="nav-brand"><span>$</span> pathfinder</a>
<div class="nav-links">
<a href="/" class="active">Home</a>
<a href="#features">Features</a>
<a href="#comparison">Comparison</a>
<a href="#migration">Switch</a>
<a href="/usage">Docs</a>
<a href="https://github.com/CopilotKit/pathfinder" class="gh-btn" target="_blank"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg> GitHub</a>
</div>
Expand All @@ -210,6 +221,9 @@

<div class="mobile-menu">
<a href="/" class="active">Home</a>
<a href="#features">Features</a>
<a href="#comparison">Comparison</a>
<a href="#migration">Switch</a>
<a href="/usage">Docs</a>
<a href="https://github.com/CopilotKit/pathfinder" class="gh-btn" target="_blank"><svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg> GitHub</a>
</div>
Expand Down Expand Up @@ -269,51 +283,51 @@ <h1>The <span class="accent">knowledge server</span><br class="hero-break"> for
</section>

<section class="features" id="features">
<h2>What's in the box</h2>
<p class="section-sub">A complete system enabling agents to discover and learn what's important.</p>
<h2 class="fade-in">What's in the box</h2>
<p class="section-sub fade-in">A complete system enabling agents to discover and learn what's important.</p>

<div class="feature-grid">
<div class="feature-card">
<div class="feature-card fade-in">
<div class="icon">🔍</div>
<h3>Semantic + Filesystem</h3>
<p>Agents choose the right tool: vector search for conceptual questions, grep and cat for precise lookups. Both paradigms, composable.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.08s">
<div class="icon">💬</div>
<h3>Automatic Q&amp;A Extraction</h3>
<p>Slack threads and Discord forums are distilled into Q&amp;A pairs automatically. Forum posts need no LLM — they're already questions and answers.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.16s">
<div class="icon">🔗</div>
<h3>Cross-Source Intelligence</h3>
<p>Search docs and Q&amp;A together. Grep misses suggest semantic search. related finds similar files across sources.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.24s">
<div class="icon">📋</div>
<h3>Knowledge on Tap</h3>
<p>/faq.txt and /llms.txt give agents instant context at session start. No search required — your best answers are already loaded.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.32s">
<div class="icon">🚀</div>
<h3>Zero-Infra Mode</h3>
<p>Bash-only tools work with no database, no API keys. Add RAG when you're ready — it's just config.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.4s">
<div class="icon">🔄</div>
<h3>Always Current</h3>
<p>Webhook-triggered reindexing on every push. Nightly full reindex on schedule. Confidence thresholds adjust without re-processing.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.48s">
<div class="icon">🔌</div>
<h3>Pluggable Embeddings</h3>
<p>OpenAI, Ollama, or local transformers.js — choose your embedding provider. Run fully local with no API keys, or use the cloud. Switch with one config line.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.56s">
<div class="icon">📄</div>
<h3>PDF &amp; DOCX Support</h3>
<p>Index binary documents directly — no conversion needed. Page-break and section-aware chunking extracts structure from PDFs and Word docs.</p>
</div>
<div class="feature-card">
<div class="feature-card fade-in" style="transition-delay: 0.64s">
<div class="icon">📊</div>
<h3>Analytics Dashboard</h3>
<p>See what agents search for, which queries return empty, and where your knowledge gaps are. Built-in dashboard at <code>/analytics</code>.</p>
Expand All @@ -322,10 +336,10 @@ <h3>Analytics Dashboard</h3>
</section>

<section class="section-comparison" id="comparison">
<h2>How Pathfinder compares</h2>
<p class="section-sub">See how Pathfinder stacks up against other knowledge tools for AI agents.</p>
<h2 class="fade-in">How Pathfinder compares</h2>
<p class="section-sub fade-in">See how Pathfinder stacks up against other knowledge tools for AI agents.</p>

<div class="comparison-wrap">
<div class="comparison-wrap fade-in">
<table class="comp-matrix">
<thead>
<tr>
Expand Down Expand Up @@ -464,9 +478,9 @@ <h2>How Pathfinder compares</h2>
</section>

<section class="migration" id="migration">
<h2>Switch to Pathfinder</h2>
<p class="section-sub">Migration guides for every platform. Most take less than 15 minutes.</p>
<div style="display:flex; flex-wrap:wrap; gap:12px; justify-content:center;">
<h2 class="fade-in">Switch to Pathfinder</h2>
<p class="section-sub fade-in">Migration guides for every platform. Most take less than 15 minutes.</p>
<div class="fade-in" style="display:flex; flex-wrap:wrap; gap:12px; justify-content:center;">
<a href="/migrate-from-mintlify" class="cta-btn">From Mintlify &#8594;</a>
<a href="/migrate-from-gitbook" class="cta-btn">From GitBook &#8594;</a>
<a href="/migrate-from-ragdocs" class="cta-btn">From mcp-ragdocs &#8594;</a>
Expand Down Expand Up @@ -577,5 +591,25 @@ <h2>Switch to Pathfinder</h2>
});
})();
</script>
<script>
// Scroll-triggered fade-in via IntersectionObserver
(function () {
var observer = new IntersectionObserver(
function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.1, rootMargin: "0px 0px -40px 0px" },
);

document.querySelectorAll(".fade-in").forEach(function (el) {
observer.observe(el);
});
})();
</script>
</body>
</html>
161 changes: 161 additions & 0 deletions src/__tests__/validate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,121 @@ describe("validateConfig", () => {
expect(openaiEnv?.required).toBe(true);
});

// ── Optional dependency checks ──────────────────────────────────────────

describe("optional dependency checks", () => {
it("reports missing pdf-parse for PDF document sources", async () => {
(getServerConfig as ReturnType<typeof vi.fn>).mockReturnValue({
server: { name: "test", version: "1.0" },
sources: [
{
name: "pdf-docs",
type: "document",
path: "./docs",
file_patterns: ["**/*.pdf"],
chunk: {},
},
],
tools: [
{
name: "c",
type: "collect",
description: "collect",
response: "Collected",
schema: { field1: { type: "string" } },
},
],
});

const result = await validateConfig();
expect(
result.errors.some(
(e) =>
e.includes("Missing optional dependency: pdf-parse") &&
e.includes("npm install pdf-parse"),
),
).toBe(true);
});

it("reports missing mammoth for DOCX document sources", async () => {
(getServerConfig as ReturnType<typeof vi.fn>).mockReturnValue({
server: { name: "test", version: "1.0" },
sources: [
{
name: "docx-docs",
type: "document",
path: "./docs",
file_patterns: ["**/*.docx"],
chunk: {},
},
],
tools: [
{
name: "c",
type: "collect",
description: "collect",
response: "Collected",
schema: { field1: { type: "string" } },
},
],
});

const result = await validateConfig();
expect(
result.errors.some(
(e) =>
e.includes("Missing optional dependency: mammoth") &&
e.includes("npm install mammoth"),
),
).toBe(true);
});

it("reports missing @xenova/transformers for local embedding provider", async () => {
(getServerConfig as ReturnType<typeof vi.fn>).mockReturnValue({
server: { name: "test", version: "1.0" },
sources: [
{
name: "docs",
type: "markdown",
path: "./docs",
file_patterns: ["**/*.md"],
chunk: {},
},
],
tools: [
{
name: "c",
type: "collect",
description: "collect",
response: "Collected",
schema: { field1: { type: "string" } },
},
],
embedding: {
provider: "local",
model: "Xenova/all-MiniLM-L6-v2",
dimensions: 384,
},
});

const result = await validateConfig();
expect(
result.errors.some(
(e) =>
e.includes("Missing optional dependency: @xenova/transformers") &&
e.includes("npm install @xenova/transformers"),
),
).toBe(true);
});

it("does not report optional dep errors when no document sources or local embeddings", async () => {
const result = await validateConfig();
expect(
result.errors.some((e) => e.includes("Missing optional dependency")),
).toBe(false);
});
});

// ── Source validation ────────────────────────────────────────────────────

describe("source validation", () => {
Expand Down Expand Up @@ -946,6 +1061,52 @@ describe("formatValidationResult", () => {
expect(output).toContain("- Error three");
});

it("formats optional dependency warnings separately from hard errors", () => {
const result: ValidationResult = {
configValid: true,
envVars: [],
sources: [],
tools: [],
errors: [
"Missing optional dependency: pdf-parse — Required for PDF document sources. Install: npm install pdf-parse",
"Missing required environment variable: DATABASE_URL",
],
};

const output = formatValidationResult(result);
expect(output).toContain("Optional Dependencies:");
expect(output).toContain("pdf-parse");
expect(output).toContain("2 error(s) found");
expect(output).toContain(
"- Missing required environment variable: DATABASE_URL",
);
// The optional dep error should appear in the Optional Dependencies section, not in the error list
const lines = output.split("\n");
const errorListLines = lines.filter(
(l) => l.startsWith(" - ") && l.includes("Missing required"),
);
expect(errorListLines).toHaveLength(1);
});

it("formats result with only optional dep warnings as no hard errors", () => {
const result: ValidationResult = {
configValid: true,
envVars: [],
sources: [],
tools: [],
errors: [
"Missing optional dependency: mammoth — Required for DOCX document sources. Install: npm install mammoth",
],
};

const output = formatValidationResult(result);
expect(output).toContain("Optional Dependencies:");
expect(output).toContain(
"1 optional dependency warning(s), no hard errors",
);
expect(output).not.toContain("error(s) found");
});

it("includes section headers", () => {
const result: ValidationResult = {
configValid: true,
Expand Down
Loading
Loading