From e3d9e236ef0fc840b0a306f843cf8d0b69f681d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Thu, 28 May 2026 17:41:12 +0100 Subject: [PATCH 1/2] feat(landing): use-case-specific /for/ pages with collapsible code tabs - Replace alternating two-column section grid on /for/ pages with a single "What ships" section showing 4-5 collapsed tab cards (lobu.config.ts / Connectors / Memory / Watchers / Skills). Clicking a card reveals a full-width code panel below. First tab preselected. - Per-use-case snippet generation: gen-landing-snippets.ts now emits agentConfig/reaction/skill snippets per example dir; sections with no source file (e.g. legal has no reaction or skill) hide cleanly instead of falling back to sales. - Architecture diagram entity rows + Connectors column derived from landingUseCases[slug].memory.highlights / .howItWorks.connect.chips, so /for/legal shows "Redwood NDA / contract" and "File upload / Drive / Research" instead of generic "Customer A / company". - Hero: replace "View on GitHub" secondary CTA with "Talk to the founder" reusing the existing cal.com modal. - Homepage Browse Examples: curated 6 cards routing to /for/ plus a single "Browse all on GitHub" link. - CodeBlock gains maxHeight prop to bound long snippets. - Pull shared GitHub URLs into packages/landing/src/lib/urls.ts. --- .../landing/scripts/gen-landing-snippets.ts | 42 +- .../src/components/ArchitectureDiagram.tsx | 115 +++- packages/landing/src/components/CodeBlock.tsx | 13 +- packages/landing/src/components/Footer.tsx | 2 +- .../landing/src/components/LandingPage.tsx | 651 +++++++----------- packages/landing/src/components/Nav.tsx | 2 +- .../src/components/ServerlessSection.tsx | 3 +- .../src/generated/landing-snippets.json | 103 ++- packages/landing/src/lib/urls.ts | 4 + packages/landing/src/use-case-showcases.ts | 50 ++ 10 files changed, 544 insertions(+), 441 deletions(-) create mode 100644 packages/landing/src/lib/urls.ts diff --git a/packages/landing/scripts/gen-landing-snippets.ts b/packages/landing/scripts/gen-landing-snippets.ts index 76af0194a..f850fd4f1 100644 --- a/packages/landing/scripts/gen-landing-snippets.ts +++ b/packages/landing/scripts/gen-landing-snippets.ts @@ -84,6 +84,9 @@ type UseCaseSnippets = { connector: Snippet; memorySchema: Snippet; watcher: Snippet; + agentConfig: Snippet; + reaction: Snippet | null; + skill: Snippet | null; }; type LandingSnippets = { @@ -442,6 +445,27 @@ function findConnectorFile(slug: string): { rel: string } { return { rel: file }; } +function findReactionFile(slug: string): string | null { + const exampleDir = resolve(examplesDir, slug); + const file = readdirSync(exampleDir).find((f) => f.endsWith(".reaction.ts")); + return file ?? null; +} + +// First-party skills only — under examples//skills//SKILL.md. +// Workspace-bundled .skills/ and node_modules SKILL.md are noise. +function findSkillFile(slug: string): string | null { + const skillsDir = resolve(examplesDir, slug, "skills"); + if (!existsSync(skillsDir)) return null; + const names = readdirSync(skillsDir, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => d.name); + for (const name of names) { + const rel = `skills/${name}/SKILL.md`; + if (existsSync(resolve(examplesDir, slug, rel))) return rel; + } + return null; +} + function buildUseCases(): Record { const out: Record = {}; for (const slug of USE_CASE_SLUGS) { @@ -454,7 +478,23 @@ function buildUseCases(): Record { ); const memorySchema = configSnippet(slug, entitySlice); const watcher = configSnippet(slug, watcherSlice); - out[slug] = { connector, memorySchema, watcher }; + const agentConfig = configSnippet(slug, agentConfigSlice); + const reactionRel = findReactionFile(slug); + const reaction = reactionRel + ? fileSnippet(slug, reactionRel, "typescript") + : null; + const skillRel = findSkillFile(slug); + const skill = skillRel + ? fileSnippet(slug, skillRel, "markdown", trimSkillMarkdown) + : null; + out[slug] = { + connector, + memorySchema, + watcher, + agentConfig, + reaction, + skill, + }; } return out; } diff --git a/packages/landing/src/components/ArchitectureDiagram.tsx b/packages/landing/src/components/ArchitectureDiagram.tsx index fe1533a8c..a250befe7 100644 --- a/packages/landing/src/components/ArchitectureDiagram.tsx +++ b/packages/landing/src/components/ArchitectureDiagram.tsx @@ -1,6 +1,31 @@ // biome-ignore-all format: stays compact for the landing-page panel +import { GITHUB_CONNECTORS_TREE_URL } from "../lib/urls"; +import { + type ArchitectureEntityRow, + getArchitectureConnectorChips, + getArchitectureEntityRows, +} from "../use-case-showcases"; import { messagingChannels } from "./platforms"; +const GENERIC_CONNECTOR_ROWS: ReadonlyArray<{ + label: string; + href: string; +}> = [ + { label: "50+ built-in connectors", href: GITHUB_CONNECTORS_TREE_URL }, + { label: "any MCP server", href: "/guides/mcp-proxy/" }, + { label: "custom via connector-sdk", href: "/getting-started/connector-sdk/" }, +]; + +// Fallback entity rows when no use case is active (the homepage). Labelled +// generically so the widget reads as a schema preview, not a snapshot of a +// real customer's data. /for/ pages pass `slug` and get vertical- +// specific rows via getArchitectureEntityRows. +const GENERIC_ROWS: readonly ArchitectureEntityRow[] = [ + ["Customer A", "company", "2d"], + ["Customer B", "person", "5h"], + ["Customer C", "meeting", "1h"], +]; + /** * Architecture diagram: layered stream story (NOT cycling pairs). * @@ -22,11 +47,19 @@ import { messagingChannels } from "./platforms"; /* Component */ /* -------------------------------------------------------------------------- */ -export function ArchitectureDiagram() { +export function ArchitectureDiagram({ slug }: { slug?: string } = {}) { + const rows = getArchitectureEntityRows(slug) ?? GENERIC_ROWS; + const chips = getArchitectureConnectorChips(slug); + const connectorRows = chips + ? chips.map((label) => ({ + label, + href: "/getting-started/connector-sdk/", + })) + : GENERIC_CONNECTOR_ROWS; return (
- +
); @@ -60,7 +93,15 @@ function Header() { ); } -function DiagramBoard() { +type ConnectorRow = { label: string; href: string }; + +function DiagramBoard({ + rows, + connectorRows, +}: { + rows: readonly ArchitectureEntityRow[]; + connectorRows: readonly ConnectorRow[]; +}) { return (
- +
); @@ -84,24 +125,36 @@ function DiagramBoard() { /* Desktop board: three column layered story */ /* -------------------------------------------------------------------------- */ -function DesktopBoard() { +function DesktopBoard({ + rows, + connectorRows, +}: { + rows: readonly ArchitectureEntityRow[]; + connectorRows: readonly ConnectorRow[]; +}) { return (
- + - +
); } -function MobileBoard() { +function MobileBoard({ + rows, + connectorRows, +}: { + rows: readonly ArchitectureEntityRow[]; + connectorRows: readonly ConnectorRow[]; +}) { return (
- + - +
@@ -112,31 +165,33 @@ function MobileBoard() { /* Columns */ /* -------------------------------------------------------------------------- */ -function ConnectorsColumn() { +function ConnectorsColumn({ + connectorRows, +}: { + connectorRows: readonly ConnectorRow[]; +}) { return (
- - - + {connectorRows.map((row) => ( + + ))}
); } -function MemoryColumn() { +function MemoryColumn({ + rows, +}: { + rows: readonly ArchitectureEntityRow[]; +}) { return (
- +
); @@ -319,15 +374,13 @@ function StreamBox({ label }: { label: string }) { * monospace dashes/blocks so the table reads as a schema preview rather * than fake data. Sits in the same box family as StreamBox. */ -function EntitiesTable() { +function EntitiesTable({ + rows, +}: { + rows: readonly ArchitectureEntityRow[]; +}) { const HEADERS = ["name", "type", "updated"] as const; - // Placeholder rows, labelled generically so the widget reads as a - // schema preview, not a snapshot of a real customer's data. - const ROWS: ReadonlyArray = [ - ["Customer A", "company", "2d"], - ["Customer B", "person", "5h"], - ["Customer C", "meeting", "1h"], - ]; + const ROWS = rows; return (
             
               {lines.map((toks, idx) => (
diff --git a/packages/landing/src/components/Footer.tsx b/packages/landing/src/components/Footer.tsx
index 63e5a2996..5226aa8aa 100644
--- a/packages/landing/src/components/Footer.tsx
+++ b/packages/landing/src/components/Footer.tsx
@@ -1,4 +1,4 @@
-const GITHUB_URL = "https://github.com/lobu-ai/lobu";
+import { GITHUB_URL } from "../lib/urls";
 
 type FooterColumn = {
   heading: string;
diff --git a/packages/landing/src/components/LandingPage.tsx b/packages/landing/src/components/LandingPage.tsx
index 27260c09c..2bbd90fbf 100644
--- a/packages/landing/src/components/LandingPage.tsx
+++ b/packages/landing/src/components/LandingPage.tsx
@@ -1,12 +1,14 @@
 import { useState } from "preact/hooks";
 import connectorsManifest from "../generated/connectors.json";
 import snippetsManifest from "../generated/landing-snippets.json";
+import { GITHUB_CONNECTORS_BLOB_URL, GITHUB_EXAMPLES_URL } from "../lib/urls";
 import { getLobuBaseUrl } from "../use-case-showcases";
 import { ArchitectureDiagram } from "./ArchitectureDiagram";
 import { CodeBlock, type CodeSnippet } from "./CodeBlock";
 import { CTA } from "./CTA";
 import { LatestBlogPosts, type LatestBlogPost } from "./LatestBlogPosts";
 import { ProactiveLoop } from "./ProactiveLoop";
+import { ScheduleCallButton, ScheduleCallIcon } from "./ScheduleDialog";
 
 type ExampleEntry = {
   slug: string;
@@ -19,6 +21,9 @@ type UseCaseSnippets = {
   connector: CodeSnippet;
   memorySchema: CodeSnippet;
   watcher: CodeSnippet;
+  agentConfig: CodeSnippet;
+  reaction: CodeSnippet | null;
+  skill: CodeSnippet | null;
 };
 
 type LandingSnippets = {
@@ -34,8 +39,6 @@ type LandingSnippets = {
 
 const snippets = snippetsManifest as LandingSnippets;
 
-const EXAMPLE_BASE_URL = "https://github.com/lobu-ai/lobu/tree/main/examples";
-
 const SETUP_PROMPT = `I want to build a Lobu agent with you. Lobu is an open-source, event-sourced backend for AI agents: connectors emit events, memory keeps a structured knowledge graph, and agents react in real time and run on a schedule. Set it up with me end to end.
 
 1. Interview me, one question at a time. Wait for my answer before the next. Don't batch them, don't guess, and don't fake any credentials:
@@ -55,8 +58,6 @@ const SETUP_PROMPT = `I want to build a Lobu agent with you. Lobu is an open-sou
 
 Repo: https://github.com/lobu-ai/lobu. Docs: https://lobu.ai/docs/`;
 
-const GITHUB_URL = "https://github.com/lobu-ai/lobu";
-
 // The canonical "test it" command, kept in sync with InstallSection.
 const QUICKSTART_CMD = "npx @lobu/cli@latest init my-agent";
 
@@ -73,6 +74,11 @@ export function LandingPage(props: {
   const connectorSnippet = uc?.connector ?? snippets.connector;
   const memorySchemaSnippet = uc?.memorySchema ?? snippets.memorySchema;
   const watcherSnippet = uc?.watcher ?? snippets.watcher;
+  const agentConfigSnippet = uc?.agentConfig ?? snippets.agentConfig;
+  // null means the example for this use case has no reaction / skill — hide
+  // that panel rather than substituting a foreign example's code.
+  const reactionSnippet = uc ? uc.reaction : snippets.reaction;
+  const skillSnippet = uc ? uc.skill : snippets.skill;
 
   // The canonical homepage stays benefit-led: outcome artifact + a plain
   // 3-step explanation, with the deep code living in the docs. The
@@ -93,19 +99,17 @@ export function LandingPage(props: {
       ) : (
         <>
           
-            
+            
           
-          
-          
-          
-          
-          
         
       )}
       
@@ -226,20 +230,17 @@ function Hero() {
                 : "Copy setup prompt"}
             
           
-          
-            
-            View on GitHub
-          
+            
+            Talk to the founder
+          
         
- Browse the repo - Examples + Solutions + + Pick a use case to see it end to end. +

- Clone any one, run{" "} - lobu apply, and you - have a working agent. + Each page walks through the connectors, memory shape, and watchers + for one team — and ships as a working example you can{" "} + lobu apply.

- {examples.map((ex) => ( + {featured.map((ex) => ( - examples/{ex.slug} + {ex.label} {ex.description ? ( ))}
+
); @@ -527,7 +540,7 @@ function ExampleFooterLink({ slug }: { slug: string }) { return ( - - Connectors - - One typed event stream from every source. - -

- Three ways in: a built-in connector, your own in TypeScript with{" "} - @lobu/connector-sdk, or - any MCP server wrapped as a connector. -

- - On-device collection: paired Chrome and macOS - connectors capture local context no cloud agent can see. - , - <> - Multi-tenant OAuth: each user signs in with their own - account; workers never see the token. - , - <> - Durable checkpointing: connectors resume from the last - cursor after restart. No missed events. - , - ]} - /> - - Read the connector-sdk docs - - - - } - code={ -
- - -
- } - /> - - ); -} - -const CONNECTOR_SRC_BASE = - "https://github.com/lobu-ai/lobu/blob/main/packages/connectors/src"; - // Renders every built-in connector as a chip linking to its source file. // The list is generated from packages/connectors by scripts/gen-connectors.ts, // so adding a connector surfaces it here automatically. @@ -690,7 +642,7 @@ function ConnectorChips() { {connectorsManifest.map((c) => (
- - Memory - - An event-sourced database for AI agents. - -

- Declare entity types in TypeScript. Lobu stores them as - append-only events with full audit. Multi-tenant by default, - agents see only their scope. -

- - Entity types & relationships: declare what your - agent should remember; link entities with typed relations. - , - <> - Append-only: every change is a new event. Tombstones - supersede; nothing is destroyed. - , - <> - Per-user / per-org isolation: your agents only see the - memory they're scoped to. - , - ]} - /> - - Read the memory guide - - - } - code={ -
- - -
- } - /> - - ); -} +/* -------------------------------------------------------------------------- */ +/* Use-case showcase: the four/five product primitives as collapsed tabs. */ +/* Each tab card shows a compact pitch; clicking one reveals its code panel */ +/* below at full width. Skills tab is omitted when the active use case's */ +/* example has no SKILL.md. */ +/* -------------------------------------------------------------------------- */ -function WatchersSection({ - watcher, +type ShowcaseTab = { + id: string; + eyebrow: string; + blurb: string; + primary: { snippet: CodeSnippet; badge: string; maxHeight: string }; + secondary?: { + snippet: CodeSnippet; + badge: string; + intro: string; + maxHeight: string; + }; + docHref: string; + docLabel: string; + showConnectorChips?: boolean; +}; + +function UseCaseShowcaseSection({ slug, + agentConfig, + connector, + memorySchema, + watcher, + reaction, + skill, }: { - watcher: CodeSnippet; slug: string; + agentConfig: CodeSnippet; + connector: CodeSnippet; + memorySchema: CodeSnippet; + watcher: CodeSnippet; + reaction: CodeSnippet | null; + skill: CodeSnippet | null; }) { - return ( - - - Watchers - - Turn events into memory. With prompts. - -

- A watcher is a prompt +{" "} - extraction_schema. Lobu - runs the LLM, validates, and persists the output to memory.{" "} - No application code for extraction: fire on events, or run - on cron. -

- - Reactive: fires on the event stream (e.g.{" "} - - linear.issue.created - - ). - , - <> - Dreaming: runs on cron. Aggregates the previous day's - events into higher-level entities. - , - <> - No-code ETL: the prompt is your transformation; the - schema is your output type. - , - ]} - /> -
- - Watchers guide - - - Reaction SDK docs - -
- - - } - code={ -
-

- Reactions run code when memory changes. For example: -

- - -
- } - /> -
- ); -} - -/* -------------------------------------------------------------------------- */ -/* Skills section: snippet is the YAML frontmatter of the sales account-brief */ -/* SKILL.md, trimmed at build time by gen-landing-snippets.ts (it exercises */ -/* every field the pitch promises: nixPackages, network.allow, network.judge, */ -/* judges). */ -/* -------------------------------------------------------------------------- */ + const tabs: ShowcaseTab[] = [ + { + id: "config", + eyebrow: "lobu.config.ts", + blurb: + "One typed file declares the agent and wires entities, watchers, connectors, and skills.", + primary: { + snippet: agentConfig, + badge: "lobu.config.ts", + maxHeight: "36rem", + }, + docHref: "/getting-started/", + docLabel: "Agents guide", + }, + { + id: "connectors", + eyebrow: "Connectors", + blurb: + "Built-in, MCP, or a custom *.connector.ts — one typed event stream from every source.", + primary: { + snippet: connector, + badge: "typescript", + maxHeight: "36rem", + }, + docHref: "/getting-started/connector-sdk/", + docLabel: "Connector SDK docs", + showConnectorChips: true, + }, + { + id: "memory", + eyebrow: "Memory", + blurb: + "Declare entity types in TypeScript. Lobu stores them as append-only events with full audit.", + primary: { + snippet: memorySchema, + badge: "entities", + maxHeight: "36rem", + }, + docHref: "/getting-started/memory/", + docLabel: "Memory guide", + }, + { + id: "watchers", + eyebrow: "Watchers", + blurb: + "Prompt + extraction schema. The LLM runs, validates, and writes typed memory — no ETL code.", + primary: { + snippet: watcher, + badge: "reactive + dreaming", + maxHeight: "26rem", + }, + secondary: reaction + ? { + snippet: reaction, + badge: "optional · typescript", + intro: "Reactions run code when memory changes:", + maxHeight: "26rem", + } + : undefined, + docHref: "/getting-started/memory/", + docLabel: "Watchers guide", + }, + ]; + if (skill) { + tabs.push({ + id: "skills", + eyebrow: "Skills", + blurb: + "A SKILL.md folder: instructions, TS tools, Nix packages, and per-domain egress policy.", + primary: { snippet: skill, badge: "skill", maxHeight: "32rem" }, + docHref: "/getting-started/", + docLabel: "Skills guide", + }); + } -function SkillsSection() { - return ( - - - Skills - - Bundle tools, packages, and policy into one drop-in. - -

- A skill is a folder with a{" "} - SKILL.md. Drop it in{" "} - skills/ or{" "} - - agents/<id>/skills/ - - , lobu apply picks it - up. The agent gets instructions, tools, packages, and a per-domain - LLM egress policy in one shot. -

- - Instructions: markdown describing when the agent should - use this skill. - , - <> - Tools: TypeScript functions the agent calls. - Auto-registered as MCP tools. - , - <> - Network: allowed domains + per-domain LLM egress judge - in YAML. - , - <> - Packages: Nix packages (git, jq, etc.) merged into the - worker env. - , - ]} - /> - - Read the skills guide - - - } - code={ -
- -

- Plus the markdown body, instructions for when and how the agent - should use this skill. -

- -
- } - /> -
- ); -} + const [activeId, setActiveId] = useState(tabs[0].id); + const active = tabs.find((t) => t.id === activeId) ?? null; -function AgentsSection() { return ( - - lobu.config.ts - One typed file wires it together. -

+ What ships + + One typed file wires it together. + +

+ Pick a piece to see the code for this use case. Click again to hide. +

+ + +
- } - code={ -
- - + + {tab.eyebrow} + + + {tab.blurb} + + + {isOpen ? "hide code ▾" : "show code ▸"} + + + ); + })} +
+ + {active ? ( +
+ + {active.secondary ? ( + <> +

+ {active.secondary.intro} +

+ + + ) : null} +
+ {active.docLabel} +
- } - /> + {active.showConnectorChips ? : null} +
+ ) : null} ); } diff --git a/packages/landing/src/components/Nav.tsx b/packages/landing/src/components/Nav.tsx index 1483a8aca..3a8879643 100644 --- a/packages/landing/src/components/Nav.tsx +++ b/packages/landing/src/components/Nav.tsx @@ -1,7 +1,7 @@ import { useEffect, useRef, useState } from "preact/hooks"; +import { GITHUB_URL } from "../lib/urls"; import { getLobuLoginUrl } from "../use-case-showcases"; -const GITHUB_URL = "https://github.com/lobu-ai/lobu"; const GITHUB_STARS_BADGE = "https://img.shields.io/github/stars/lobu-ai/lobu?style=social"; diff --git a/packages/landing/src/components/ServerlessSection.tsx b/packages/landing/src/components/ServerlessSection.tsx index 384d92970..0d6c5db1e 100644 --- a/packages/landing/src/components/ServerlessSection.tsx +++ b/packages/landing/src/components/ServerlessSection.tsx @@ -1,9 +1,8 @@ import { useState } from "preact/hooks"; +import { GITHUB_URL } from "../lib/urls"; import { ModeCard, modes } from "./InstallSection"; import { ScheduleCallButton, ScheduleCallIcon } from "./ScheduleDialog"; -const GITHUB_URL = "https://github.com/lobu-ai/lobu"; - const resources = [ { label: "vCPU", diff --git a/packages/landing/src/generated/landing-snippets.json b/packages/landing/src/generated/landing-snippets.json index f92e32650..08b090a03 100644 --- a/packages/landing/src/generated/landing-snippets.json +++ b/packages/landing/src/generated/landing-snippets.json @@ -128,7 +128,15 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/legal/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n secret,\n} from \"@lobu/cli/config\";\nimport type DocuSignEnvelopesConnector from \"./docusign-envelopes.connector.ts\";\n\nconst legalReview = defineAgent({\n id: \"legal-review\",\n name: \"legal-review\",\n description:\n \"Review contracts, summarize risk, and surface missing protections\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst contractReviewTracker = defineWatcher({\n agent: legalReview,\n slug: \"contract-review-tracker\",\n name: \"Contract review tracker\",\n schedule: \"0 8 * * 1-5\",\n notification: { priority: \"high\" },\n tags: [\"legal\", \"contract\", \"daily\"],\n minCooldownSeconds: 1800,\n reactionsGuidance:\n \"For any contract with `status: needs_counsel`, route an entity-scoped event\\nto the assigned reviewer. For contracts >90 days unsigned, escalate to the\\ncounterparty owner; never auto-resolve risk items.\\n\",\n prompt:\n \"Review active contracts for approaching deadlines, unsigned agreements, and unresolved risk items. Flag any clauses that still need counsel approval.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\n \"pending_contracts\",\n \"unresolved_risks\",\n \"approaching_deadlines\",\n ],\n properties: {\n pending_contracts: { type: \"array\", items: { type: \"string\" } },\n unresolved_risks: { type: \"array\", items: { type: \"string\" } },\n approaching_deadlines: { type: \"array\", items: { type: \"string\" } },\n flagged_clauses: { type: \"array\", items: { type: \"string\" } },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./docusign-envelopes.connector.ts\"\n ),\n ],\n org: \"legal-review\",\n orgName: \"Legal\",\n orgDescription:\n \"Review contracts, summarize risk, and surface missing protections\",\n agents: [legalReview],\n entities: [clause, contract, counterparty, risk],\n relationships: [belongsToCounterparty, containsClause, createsRisk],\n watchers: [contractReviewTracker],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/legal/lobu.config.ts", + "language": "typescript" + }, + "reaction": null, + "skill": null }, "finance": { "connector": { @@ -148,7 +156,20 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/finance/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n reactionFromFile,\n secret,\n} from \"@lobu/cli/config\";\nimport type QuickBooksTransactionsConnector from \"./quickbooks-transactions.connector.ts\";\nimport type reconciliationMonitorReaction from \"./reconciliation-monitor.reaction.ts\";\n\nconst finance = defineAgent({\n id: \"finance\",\n name: \"finance\",\n description:\n \"Help finance teams reconcile data, explain variance, and prepare reporting runs\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst reconciliationMonitor = defineWatcher({\n agent: finance,\n slug: \"reconciliation-monitor\",\n name: \"Reconciliation monitor\",\n schedule: \"0 6 * * 1-5\",\n notification: { priority: \"high\", channel: \"both\" },\n tags: [\"finance\", \"reconciliation\", \"daily\"],\n minCooldownSeconds: 3600,\n reaction: reactionFromFile(\n \"./reconciliation-monitor.reaction.ts\"\n ),\n prompt:\n \"Check accounts for unreconciled transactions, new variances, and approaching reporting deadlines. Lead with exceptions that need review.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\"unreconciled_count\", \"new_variances\", \"approaching_deadlines\"],\n properties: {\n unreconciled_count: { type: \"integer\" },\n new_variances: { type: \"array\", items: { type: \"string\" } },\n approaching_deadlines: { type: \"array\", items: { type: \"string\" } },\n payment_risks: { type: \"array\", items: { type: \"string\" } },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./quickbooks-transactions.connector.ts\"\n ),\n ],\n org: \"finance\",\n orgName: \"Finance\",\n orgDescription:\n \"Help finance teams reconcile data, explain variance, and prepare reporting runs\",\n agents: [finance],\n entities: [account, report, transaction, variance],\n relationships: [createsVariance, reconcilesTo, summarizedIn],\n watchers: [reconciliationMonitor],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/finance/lobu.config.ts", + "language": "typescript" + }, + "reaction": { + "code": "/**\n * Reaction for the `reconciliation-monitor` watcher.\n *\n * Persists variance events when unreconciled transactions or new anomalies\n * are detected during the daily reconciliation pass.\n */\nimport type { ReactionClient, ReactionContext } from \"@lobu/connector-sdk\";\n\ninterface ReconciliationData {\n unreconciled_count: number;\n new_variances: string[];\n approaching_deadlines: string[];\n payment_risks?: string[];\n}\n\nexport default async (\n ctx: ReactionContext,\n client: ReactionClient\n): Promise => {\n const data = ctx.extracted_data as unknown as ReconciliationData;\n\n const hasIssues =\n data.unreconciled_count > 0 ||\n (data.new_variances?.length ?? 0) > 0 ||\n (data.approaching_deadlines?.length ?? 0) > 0;\n\n if (!hasIssues) return;\n\n const parts: string[] = [];\n if (data.unreconciled_count > 0) {\n parts.push(`${data.unreconciled_count} unreconciled transactions`);\n }\n if (data.new_variances?.length) {\n parts.push(`Variances: ${data.new_variances.join(\"; \")}`);\n }\n if (data.approaching_deadlines?.length) {\n parts.push(`Deadlines: ${data.approaching_deadlines.join(\"; \")}`);\n }\n\n await client.knowledge.save({\n entity_ids: ctx.entities.map((e) => e.id),\n content: parts.join(\"\\n\"),\n semantic_type: \"reconciliation_alert\",\n metadata: {\n window_id: ctx.window.id,\n unreconciled_count: data.unreconciled_count,\n variance_count: data.new_variances?.length ?? 0,\n },\n });\n};", + "path": "reconciliation-monitor.reaction.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/finance/reconciliation-monitor.reaction.ts", + "language": "typescript" + }, + "skill": null }, "sales": { "connector": { @@ -168,6 +189,24 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/sales/lobu.config.ts", "language": "typescript" + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n reactionFromFile,\n secret,\n} from \"@lobu/cli/config\";\nimport type SalesforcePipelineConnector from \"./salesforce-pipeline.connector.ts\";\nimport type accountHealthMonitorReaction from \"./account-health-monitor.reaction.ts\";\n\nconst sales = defineAgent({\n id: \"sales\",\n name: \"sales\",\n description:\n \"Help revenue teams track account health, rollout progress, and renewal signals\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst accountHealthMonitor = defineWatcher({\n agent: sales,\n slug: \"account-health-monitor\",\n name: \"Account health monitor\",\n schedule: \"0 */12 * * *\",\n notification: { priority: \"high\", channel: \"both\" },\n tags: [\"sales\", \"health\", \"renewals\"],\n minCooldownSeconds: 1800,\n reaction: reactionFromFile(\n \"./account-health-monitor.reaction.ts\"\n ),\n prompt:\n \"Poll CRM data for tracked accounts. Track expansion progress, risk level changes, and renewal timeline.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\n \"risk_level\",\n \"expansion_status\",\n \"renewal_blockers\",\n \"activity_delta\",\n ],\n properties: {\n risk_level: { type: \"string\" },\n expansion_status: { type: \"string\" },\n renewal_blockers: { type: \"array\", items: { type: \"string\" } },\n activity_delta: { type: \"string\" },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./salesforce-pipeline.connector.ts\"\n ),\n ],\n org: \"sales\",\n orgName: \"Sales\",\n orgDescription:\n \"Help revenue teams track account health, rollout progress, and renewal signals\",\n agents: [sales],\n entities: [organization, product, region, renewalRisk, team],\n relationships: [affects, expandedInto, runs],\n watchers: [accountHealthMonitor],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/sales/lobu.config.ts", + "language": "typescript" + }, + "reaction": { + "code": "/**\n * Reaction for the `account-health-monitor` watcher.\n *\n * When the watcher detects a material risk-level change on a tracked account,\n * persist a `health_change` event so the renewal-risk view + weekly digest\n * have a stable record without re-extracting from the CRM stream.\n */\nimport type { ReactionClient, ReactionContext } from \"@lobu/connector-sdk\";\n\ninterface HealthData {\n account_changes?: Array<{\n account: string;\n previous_risk: \"low\" | \"medium\" | \"high\";\n current_risk: \"low\" | \"medium\" | \"high\";\n signals: string[];\n }>;\n}\n\nconst RISK_ORDER = { low: 0, medium: 1, high: 2 } as const;\n\nexport default async (\n ctx: ReactionContext,\n client: ReactionClient\n): Promise => {\n const data = ctx.extracted_data as HealthData;\n const changes = data.account_changes ?? [];\n const escalations = changes.filter(\n (c) => RISK_ORDER[c.current_risk] > RISK_ORDER[c.previous_risk]\n );\n if (escalations.length === 0) return;\n\n for (const c of escalations) {\n await client.knowledge.save({\n entity_ids: ctx.entities.map((e) => e.id),\n content: `Account ${c.account}: risk ${c.previous_risk} → ${c.current_risk}\\nSignals: ${c.signals.join(\"; \")}`,\n semantic_type: \"health_change\",\n metadata: {\n account: c.account,\n from: c.previous_risk,\n to: c.current_risk,\n window_id: ctx.window.id,\n },\n });\n }\n};", + "path": "account-health-monitor.reaction.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/sales/account-health-monitor.reaction.ts", + "language": "typescript" + }, + "skill": { + "code": "---\nname: account-brief\ndescription: Build a pre-renewal brief for a tracked account from its recent public news and announcements. Use before a renewal call or QBR, after the account-health watcher flags a risk. Reading public news is allowed; do not log in, submit forms, or touch any CRM write endpoint.\nnixPackages:\n - jq\nnetwork:\n allow:\n - .reuters.com\n - .apnews.com\n judge:\n - domain: newsapi.org\n judge: news-read\n - domain: .newsapi.org\n judge: news-read\njudges:\n news-read: >\n Allow GET reads of public news and headlines. Deny logins, posting,\n and any account, billing, or write action. Fail closed if unclear.\n---", + "path": "skills/account-brief/SKILL.md", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/sales/skills/account-brief/SKILL.md", + "language": "markdown" } }, "delivery": { @@ -188,7 +227,15 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/delivery/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n secret,\n} from \"@lobu/cli/config\";\nimport type ShopifyOrdersConnector from \"./shopify-orders.connector.ts\";\n\nconst delivery = defineAgent({\n id: \"delivery\",\n name: \"delivery\",\n description:\n \"Help delivery teams keep milestones, blockers, owners, and artifacts aligned\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst phoenixRolloutTracker = defineWatcher({\n agent: delivery,\n slug: \"phoenix-rollout-tracker\",\n name: \"Phoenix rollout tracker\",\n schedule: \"0 9 * * 1\",\n notification: { priority: \"high\", channel: \"both\" },\n tags: [\"delivery\", \"weekly\", \"rollout\"],\n minCooldownSeconds: 3600,\n prompt:\n \"Check project blockers, milestone progress, and generate the weekly risk summary for leadership.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\n \"blockers_resolved\",\n \"milestone_state\",\n \"new_risks\",\n \"risk_summary\",\n ],\n properties: {\n blockers_resolved: { type: \"array\", items: { type: \"string\" } },\n milestone_state: { type: \"string\" },\n new_risks: { type: \"array\", items: { type: \"string\" } },\n risk_summary: { type: \"string\" },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./shopify-orders.connector.ts\"\n ),\n ],\n org: \"delivery\",\n orgName: \"Delivery\",\n orgDescription:\n \"Help delivery teams keep milestones, blockers, owners, and artifacts aligned\",\n agents: [delivery],\n entities: [blocker, document, milestone, stakeholder],\n relationships: [blockedBy, documentedIn, ownedBy],\n watchers: [phoenixRolloutTracker],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/delivery/lobu.config.ts", + "language": "typescript" + }, + "reaction": null, + "skill": null }, "market": { "connector": { @@ -208,7 +255,20 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/market/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n reactionFromFile,\n secret,\n} from \"@lobu/cli/config\";\nimport type ExaNewsFeedConnector from \"./exa-news-feed.connector.ts\";\nimport type founderActivityTrackerReaction from \"./founder-activity-tracker.reaction.ts\";\n\nconst vcTracking = defineAgent({\n id: \"vc-tracking\",\n name: \"vc-tracking\",\n description:\n \"Track companies, founders, and investment opportunities for venture firms\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst founderActivityTracker = defineWatcher({\n agent: vcTracking,\n slug: \"founder-activity-tracker\",\n name: \"Founder Activity Tracker\",\n schedule: \"0 10 * * *\",\n notification: { priority: \"normal\" },\n tags: [\"vc\", \"founders\", \"daily\"],\n minCooldownSeconds: 600,\n reaction: reactionFromFile(\n \"./founder-activity-tracker.reaction.ts\"\n ),\n prompt:\n \"You are a venture capital analyst tracking the public activity of startup founders in your portfolio.\\n\\n## Founders\\n{{#each entities}}\\n- {{name}} ({{entity_type}}, ID: {{id}})\\n{{/each}}\\n\\n## Recent Founder Activity\\n{{#if sources.founder_posts}}\\n{{sources.founder_posts}}\\n{{/if}}\\n\\n---\\n\\nProduce a structured founder activity report:\\n1. **Executive Summary**: 2-3 sentence overview of founder activity and signals.\\n2. **Per-Founder Analysis**: For each active founder, summarize their messaging themes, engagement level, and signals about company direction.\\n3. **Cross-Portfolio Patterns**: Themes multiple founders discuss.\\n4. **Notable Signals**: Flag potential announcements, strategic shifts, or concerns.\\n\\nBe specific and cite actual tweets/posts as evidence.\\n\",\n sources: {\n founder_posts:\n \"SELECT id, title, payload_text, author_name, source_url, occurred_at, score, origin_type, connector_key FROM events WHERE connector_key IN ('x') AND origin_type IN ('tweet', 'reply') ORDER BY occurred_at DESC LIMIT 300\\n\",\n },\n reactionsGuidance:\n \"When a founder signals hiring activity, fundraising, or pivots, flag for the investment team.\\nTrack founders going quiet as a potential concern.\\nAlert on any public statements about competitors or market conditions.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\"summary\", \"founders\", \"notable_signals\"],\n properties: {\n summary: { type: \"string\" },\n founders: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"name\", \"company\", \"activity_level\", \"themes\"],\n properties: {\n name: { type: \"string\" },\n company: { type: \"string\" },\n activity_level: {\n type: \"string\",\n enum: [\"high\", \"medium\", \"low\", \"inactive\"],\n },\n themes: { type: \"array\", items: { type: \"string\" } },\n sentiment: {\n type: \"string\",\n enum: [\"bullish\", \"neutral\", \"cautious\", \"concerned\"],\n },\n signals: { type: \"array\", items: { type: \"string\" } },\n notable_posts: { type: \"array\", items: { type: \"string\" } },\n },\n },\n },\n cross_patterns: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n theme: { type: \"string\" },\n founders_involved: { type: \"array\", items: { type: \"string\" } },\n },\n },\n },\n notable_signals: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"signal\", \"founder\", \"impact\"],\n properties: {\n signal: { type: \"string\" },\n founder: { type: \"string\" },\n impact: { type: \"string\", enum: [\"high\", \"medium\", \"low\"] },\n },\n },\n },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./exa-news-feed.connector.ts\"\n ),\n ],\n org: \"market\",\n orgName: \"Market\",\n orgDescription:\n \"Track companies, founders, and investment opportunities for venture firms\",\n agents: [vcTracking],\n entities: [\n company,\n founder,\n fundRound,\n investor,\n jobPosting,\n product,\n sector,\n ],\n relationships: [\n educatedAt,\n foundedBy,\n headquarteredIn,\n inIndustry,\n inSector,\n investedIn,\n mentions,\n operatesIn,\n previouslyAt,\n primaryRelationshipOwner,\n roundLedBy,\n roundOf,\n sourcedBy,\n usesTechnology,\n worksAt,\n ],\n watchers: [founderActivityTracker, opportunityMatcher],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/market/lobu.config.ts", + "language": "typescript" + }, + "reaction": { + "code": "/**\n * Reaction for the `founder-activity-tracker` watcher.\n *\n * Records notable public activity (tweets, blog posts, hiring posts, fundraise\n * rumors) as `founder_activity` events. The opportunity-matcher watcher reads\n * these events to suggest cross-portfolio introductions.\n */\nimport type { ReactionContext } from \"@lobu/connector-sdk\";\n\ninterface FounderActivityData {\n signals?: Array<{\n founder: string;\n activity_type: string;\n summary: string;\n importance?: \"low\" | \"medium\" | \"high\";\n }>;\n}\n\nexport default async (ctx: ReactionContext, client: any): Promise => {\n const data = ctx.extracted_data as FounderActivityData;\n const signals = data.signals ?? [];\n // High-importance only — low-noise channel for the intel feed.\n const notable = signals.filter((s) => s.importance === \"high\");\n if (notable.length === 0) return;\n\n for (const s of notable) {\n await client.knowledge.save({\n entity_ids: ctx.entities.map((e) => e.id),\n content: `${s.founder} — ${s.activity_type}: ${s.summary}`,\n semantic_type: \"founder_activity\",\n metadata: {\n founder: s.founder,\n activity_type: s.activity_type,\n importance: s.importance,\n window_id: ctx.window.id,\n },\n });\n }\n};", + "path": "founder-activity-tracker.reaction.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/market/founder-activity-tracker.reaction.ts", + "language": "typescript" + }, + "skill": null }, "agent-community": { "connector": { @@ -228,7 +288,20 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/agent-community/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n reactionFromFile,\n secret,\n} from \"@lobu/cli/config\";\nimport type DiscoursePostsConnector from \"./discourse-posts.connector.ts\";\nimport type opportunityMatcherReaction from \"./opportunity-matcher.reaction.ts\";\n\nconst agentCommunity = defineAgent({\n id: \"agent-community\",\n name: \"agent-community\",\n description:\n \"Discover aligned members, explain why they should meet, and draft warm introductions\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst opportunityMatcher = defineWatcher({\n agent: agentCommunity,\n slug: \"opportunity-matcher\",\n name: \"Opportunity matcher\",\n schedule: \"0 */12 * * *\",\n notification: { priority: \"normal\" },\n tags: [\"community\", \"matching\"],\n minCooldownSeconds: 300,\n reaction: reactionFromFile(\n \"./opportunity-matcher.reaction.ts\"\n ),\n prompt:\n \"Monitor connected profiles, newsletters, websites, and member updates for new launches, posts, hiring signals, funding news, and project changes. Identify which members are likely to care, explain why, and queue approved intro or outreach drafts.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\"signals\"],\n properties: {\n signals: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n type: { type: \"string\" },\n source: { type: \"string\" },\n related_topics: { type: \"array\", items: { type: \"string\" } },\n interested_members: { type: \"array\", items: { type: \"string\" } },\n reason: { type: \"string\" },\n suggested_action: { type: \"string\" },\n },\n },\n },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./discourse-posts.connector.ts\"\n ),\n ],\n org: \"agent-community\",\n orgName: \"Agent Community\",\n orgDescription:\n \"Discover aligned members, explain why they should meet, and draft warm introductions\",\n agents: [agentCommunity],\n entities: [match, post, topic],\n relationships: [interestedIn, introducedTo, matchesWith, writesAbout],\n watchers: [opportunityMatcher],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/agent-community/lobu.config.ts", + "language": "typescript" + }, + "reaction": { + "code": "/**\n * Reaction for the `opportunity-matcher` watcher.\n *\n * Runs every 12h after the LLM scans member activity and produces a list of\n * suggested matches. Persists each match as a `community_match` event so\n * downstream consumers (intro-drafting agents, weekly digest, audit log) can\n * iterate over a single source of truth instead of re-running the matcher.\n */\nimport type { ReactionContext } from \"@lobu/connector-sdk\";\n\ninterface MatchData {\n signals?: Array<{\n member_a: string;\n member_b: string;\n reason: string;\n confidence?: number;\n }>;\n}\n\nexport default async (ctx: ReactionContext, client: any): Promise => {\n const data = ctx.extracted_data as MatchData;\n const signals = data.signals ?? [];\n if (signals.length === 0) return;\n\n for (const s of signals) {\n await client.knowledge.save({\n entity_ids: ctx.entities.map((e) => e.id),\n content: `Match: ${s.member_a} ↔ ${s.member_b} — ${s.reason}`,\n semantic_type: \"community_match\",\n metadata: {\n member_a: s.member_a,\n member_b: s.member_b,\n confidence: s.confidence ?? null,\n window_id: ctx.window.id,\n },\n });\n }\n};", + "path": "opportunity-matcher.reaction.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/agent-community/opportunity-matcher.reaction.ts", + "language": "typescript" + }, + "skill": null }, "ecommerce": { "connector": { @@ -248,7 +321,15 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/ecommerce/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n secret,\n} from \"@lobu/cli/config\";\nimport type StripeChargesConnector from \"./stripe-charges.connector.ts\";\n\nconst ecommerceOps = defineAgent({\n id: \"ecommerce-ops\",\n name: \"ecommerce-ops\",\n description:\n \"Manage subscriptions, process order changes, and resolve customer requests\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst customerActivityTracker = defineWatcher({\n agent: ecommerceOps,\n slug: \"customer-activity-tracker\",\n name: \"Customer activity tracker\",\n schedule: \"0 */6 * * *\",\n notification: { priority: \"normal\" },\n tags: [\"ecommerce\", \"customer-ops\"],\n minCooldownSeconds: 300,\n prompt:\n \"Monitor customers for new orders, subscription changes, delivery requests, and support interactions.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\n \"subscription_status\",\n \"pending_changes\",\n \"recent_orders\",\n \"communication_preferences\",\n \"open_requests\",\n ],\n properties: {\n subscription_status: { type: \"string\" },\n pending_changes: { type: \"array\", items: { type: \"string\" } },\n recent_orders: { type: \"array\", items: { type: \"string\" } },\n communication_preferences: { type: \"string\" },\n open_requests: { type: \"array\", items: { type: \"string\" } },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./stripe-charges.connector.ts\"\n ),\n ],\n org: \"ecommerce\",\n orgName: \"Ecommerce\",\n orgDescription:\n \"Manage subscriptions, process order changes, and resolve customer requests\",\n agents: [ecommerceOps],\n entities: [customer, order, product, subscription],\n relationships: [hasPreference, placedOrder, subscribedTo],\n watchers: [customerActivityTracker],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/ecommerce/lobu.config.ts", + "language": "typescript" + }, + "reaction": null, + "skill": null }, "leadership": { "connector": { @@ -268,7 +349,15 @@ "path": "lobu.config.ts", "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/leadership/lobu.config.ts", "language": "typescript" - } + }, + "agentConfig": { + "code": "import {\n connectorFromFile,\n defineAgent,\n defineConfig,\n defineEntityType,\n defineRelationshipType,\n defineWatcher,\n secret,\n} from \"@lobu/cli/config\";\nimport type LinearCyclesConnector from \"./linear-cycles.connector.ts\";\n\nconst leadership = defineAgent({\n id: \"leadership\",\n name: \"leadership\",\n description:\n \"Help leadership teams turn memos, decisions, and board materials into reusable operating context\",\n dir: \".\",\n providers: [\n {\n id: \"anthropic\",\n model: \"claude/sonnet-4-5\",\n key: secret(\"ANTHROPIC_API_KEY\"),\n },\n ],\n network: {\n allowed: [\n \"github.com\",\n \".github.com\",\n \".githubusercontent.com\",\n \"registry.npmjs.org\",\n \".npmjs.org\",\n ],\n },\n});\n\n// entity types and relationships defined here…\n\nconst boardActionTracker = defineWatcher({\n agent: leadership,\n slug: \"board-action-tracker\",\n name: \"Board action tracker\",\n schedule: \"0 8 * * *\",\n notification: { priority: \"high\", channel: \"both\" },\n tags: [\"leadership\", \"daily\", \"board\"],\n agentKind: \"notifier\",\n prompt:\n \"Track board action items: check task delivery status, blocker resolution progress, and approaching deadlines for the next board packet.\\n\",\n extractionSchema: {\n type: \"object\",\n required: [\n \"action_items\",\n \"blocked_items\",\n \"deadlines_approaching\",\n \"completion_status\",\n ],\n properties: {\n action_items: { type: \"array\", items: { type: \"string\" } },\n blocked_items: { type: \"array\", items: { type: \"string\" } },\n deadlines_approaching: { type: \"array\", items: { type: \"string\" } },\n completion_status: { type: \"string\" },\n },\n },\n});\n\nexport default defineConfig({\n connectors: [\n connectorFromFile(\n \"./linear-cycles.connector.ts\"\n ),\n ],\n org: \"leadership\",\n orgName: \"Leadership\",\n orgDescription:\n \"Turn memos, decisions, and board materials into reusable operating context\",\n agents: [leadership],\n entities: [decision, document, region, risk, task],\n relationships: [approved, assigned, blockedBy],\n watchers: [boardActionTracker],\n});", + "path": "lobu.config.ts", + "githubUrl": "https://github.com/lobu-ai/lobu/blob/main/examples/leadership/lobu.config.ts", + "language": "typescript" + }, + "reaction": null, + "skill": null } } } diff --git a/packages/landing/src/lib/urls.ts b/packages/landing/src/lib/urls.ts new file mode 100644 index 000000000..dc9d83ec6 --- /dev/null +++ b/packages/landing/src/lib/urls.ts @@ -0,0 +1,4 @@ +export const GITHUB_URL = "https://github.com/lobu-ai/lobu"; +export const GITHUB_EXAMPLES_URL = `${GITHUB_URL}/tree/main/examples`; +export const GITHUB_CONNECTORS_TREE_URL = `${GITHUB_URL}/tree/main/packages/connectors/src`; +export const GITHUB_CONNECTORS_BLOB_URL = `${GITHUB_URL}/blob/main/packages/connectors/src`; diff --git a/packages/landing/src/use-case-showcases.ts b/packages/landing/src/use-case-showcases.ts index 05acadb3c..bac6645b5 100644 --- a/packages/landing/src/use-case-showcases.ts +++ b/packages/landing/src/use-case-showcases.ts @@ -2127,6 +2127,56 @@ function getLobuOrgSlug(useCaseId?: LandingUseCaseId) { return "lobuOrg" in def ? def.lobuOrg : undefined; } +export type ArchitectureEntityRow = readonly [ + name: string, + type: string, + updated: string, +]; + +// The 3 rows shown in the architecture diagram's "entities" table for a given +// use case. Derived from the first highlights in landingUseCases[slug].memory +// (the same data the /for/ hero copy is built from), so adding a new +// use case automatically yields a vertical-specific preview instead of the +// generic "Customer A / company" placeholder. Returns null when the slug is +// unknown — the diagram falls back to its built-in generic rows. +const FALLBACK_UPDATED: readonly string[] = ["2d", "5h", "1h"]; + +export function getArchitectureEntityRows( + slug?: string +): readonly ArchitectureEntityRow[] | null { + if (!slug) return null; + const def = (landingUseCases as Record)[ + slug + ]; + if (!def) return null; + const rows: ArchitectureEntityRow[] = []; + for (const h of def.memory.highlights) { + if (rows.length >= 3) break; + rows.push([h.value, h.label.toLowerCase(), FALLBACK_UPDATED[rows.length]]); + } + return rows.length === 3 ? rows : null; +} + +// 3 connector source labels for the architecture diagram's Connectors column. +// Pulled from the use case's own `connect.chips` list (the same data the +// /for/ hero copy describes), so legal shows "File upload / Drive / +// Research" instead of the generic "50+ built-in connectors" placeholder. +// Returns null when the slug is unknown — the diagram falls back to its +// generic labels. +export function getArchitectureConnectorChips( + slug?: string +): readonly string[] | null { + if (!slug) return null; + const def = (landingUseCases as Record)[ + slug + ]; + if (!def) return null; + const chips = def.memory.howItWorks + .find((p) => p.id === "connect") + ?.chips?.slice(0, 3); + return chips && chips.length === 3 ? chips : null; +} + export function getLobuMcpUrl() { return `${LOBU_APP_BASE_URL}/mcp`; } From 216102d85b4f53b392339306b99fe33bc0e46425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20Emre=20Kabakc=C4=B1?= Date: Thu, 28 May 2026 17:47:30 +0100 Subject: [PATCH 2/2] chore(landing): drop unused ProductGrid/FeatureList helpers --- .../landing/src/components/LandingPage.tsx | 51 ------------------- 1 file changed, 51 deletions(-) diff --git a/packages/landing/src/components/LandingPage.tsx b/packages/landing/src/components/LandingPage.tsx index 2bbd90fbf..c57f01ef9 100644 --- a/packages/landing/src/components/LandingPage.tsx +++ b/packages/landing/src/components/LandingPage.tsx @@ -557,57 +557,6 @@ function ExampleFooterLink({ slug }: { slug: string }) { ); } -function ProductGrid(props: { - reverse?: boolean; - text: preact.ComponentChildren; - code: preact.ComponentChildren; -}) { - return ( -
- {props.reverse ? ( - <> -
{props.code}
-
{props.text}
- - ) : ( - <> -
{props.text}
-
{props.code}
- - )} -
- ); -} - -function FeatureList(props: { items: Array }) { - return ( -
    - {props.items.map((item, i) => ( -
  • - - {item} -
  • - ))} -
- ); -} - function ProductLink(props: { href: string; children: preact.ComponentChildren;