feat(landing): dev-focused rebuild — pinned examples, animated architecture, real cast#945
Conversation
Adds scripts/gen-landing-snippets.ts that walks examples/<id>/ for the 8 dev-focused use cases (legal, finance, sales, delivery, leadership, agent-community, ecommerce, market) and emits src/generated/use-case-snippets.json with the raw source of lobu.toml, the entities + watchers sections of models/schema.yaml, and the first reaction/connector TS file when present. Wired into both prebuild and a new predev script so the manifest is fresh whenever the landing page boots.
…lighting New component renders Snippet objects from the use-case manifest with: - tab header (filename + optional badge) - syntax-highlighted body (regex tokenizers per language, no extra deps) - optional maxLines cap with a fade gradient - footer line-count + 'See on GitHub' link Adds dark code-panel color tokens to globals.css tuned to harmonize with the existing warm cream/orange page palette.
Each example now ships one realistic *.connector.ts modeled on examples/lobu-crm/connectors/funnel-form.connector.ts: - legal: docusign-envelopes (envelope status transitions) - finance: quickbooks-transactions (bank-feed transactions) - sales: salesforce-pipeline (opportunity changes) - delivery: shopify-orders (order updates) - leadership: linear-cycles (active-cycle issue transitions) - agent-community: discourse-posts (forum posts) - ecommerce: stripe-charges (charges + refunds) - market: exa-news-feed (Exa neural-search articles) The landing page reads these via gen-landing-snippets.ts so each use-case pivot lands real connector code on screen.
Replaces the 3-stage HeroProductCard flow with a Notion-style dev-focused home page mirroring the structure in tmp/lobu-mockup.html: Hero (copy-prompt CTA + Claude Code terminal demo) -> 3-box ArchitectureDiagram (External / Lobu / Memory) -> 'TypeScript + Postgres' pull-quote -> 'Three shapes' use-case grid -> Use-case pivot strip (8 tabs) -> Connectors (code: connectorTs) -> Memory (code: memorySchemaYaml) -> Watchers (code: watcherYaml + WebhookFanDiagram) -> Agents (code: agentToml + reactionTs) -> Run anywhere (3 cards) -> CTA + latest blog + footer New components: - WebhookFanDiagram: SVG fan-in/fan-out built from a per-use-case triggers + actions table; highlights one hot path - ArchitectureDiagram rewritten as three monospace boxes - LandingPage drives 4 product sections from generated/use-case-snippets.json (real example/<id>/ source) Preserves Nav, Footer, CTA, LatestBlogPosts.
…ne tabs Architecture diagram now reads 'external platforms / lobu / knowledge graph' — three boxes on the same surface, same radius, same border, all lowercase monospace. Lobu box has a slightly stronger border to emphasise the central role; no shadows, no badges, no inner box outlines (gateway/workers are just labelled lists). WebhookFanDiagram is now a flat 3-column SVG (trigger rows / center webhook / action rows). One trigger + one action highlighted with the foreground colour, others sit in the muted border colour. No gradients, no glow, no accent pills. LandingPage: - Use-case pivot becomes a Notion-style bottom-underline tab strip. Active tab: bold foreground text + 2px underline; inactive: muted. - Drop drop-shadows from hero terminal, use-case grid cards, and run-anywhere cards. - Callout simplified to a left-border-only quote (no fill). CodeBlock loses its drop-shadow + accent-pill badge; the badge slot becomes a muted lowercase comment in the tab. Drops and the fade-gradient truncation pattern (round 3 wants every code block to render its source in full). Drops the now-unused --color-landing-callout-* tokens from globals.css.
Adds optional `forceTheme` prop to BaseLayout, threaded into the inline theme script as an early branch. When set, the script applies the matching palette and returns before checking prefers-color-scheme. Home page (`/`) opts into `forceTheme="light"` so the warm cream + orange palette renders regardless of OS dark-mode. Other pages (docs, blog, etc.) continue to honour the OS preference.
knip confirms these have no remaining importers anywhere in the package (MDX, Astro pages, other components): - ArchitectureSection.tsx (replaced by ArchitectureDiagram inline in LandingPage) - FeatureBlock.tsx (the old Memory + Skills feature rows) - FeatureGraphics.tsx (SharedMemoryGraphic + SkillsGraphic) - HeroProductCard.tsx (3-stage hero card flow) - HeroSection.tsx (the surrounding hero shell) No backwards-compat shims per repo conventions — if it's unused, delete it.
…scroll Round 3 requirement: every landing code block renders its source in full. gen-landing-snippets.ts now does the trimming so the runtime can drop maxLines + the fade-truncation pattern. Per-snippet extraction rules: - lobu.toml -> trimAgentToml() keeps the [agents.<id>] table, the first [[agents.<id>.providers]] block, and [memory]. Drops the dir= line, network rules, and the leading docstring comment. (Fixed the section regex to allow hyphens in agent ids: [agent-community], [vc-tracking].) - models/schema.yaml entities slice -> first 2 entities with description / icon / color / x-* / enum / namespace / normalize stripped; properties capped at 4 keys per entity, with a trailing '# ...N more' comment when pruned. - models/schema.yaml watchers slice -> first watcher only; block-scalar prompts collapsed to a single-line literal; sprawling extraction_schema bodies trimmed to type + required + first 4 property names. Line counts (was -> now): agentToml: 24 -> 16 memory: 73-393 -> 27-37 watcher: 31-222 -> 11-24 reaction: unchanged (~40) connector: unchanged (handled separately) Connectors still warn at 143-166 lines — addressed in the next commit by slimming the source files themselves.
…scroll Each *.connector.ts now under ~50 lines (was 140-170) so the landing code block renders the full source in one visible chunk on a normal viewport. Dropped: - file-level docstring headers - separate Config / Checkpoint interface aliases - explicit configSchema / optionsSchema / eventKinds / metadataSchema payloads (the connector definition reduces to key + name + description + authSchema + a minimal feeds map) - AbortController + per-request fetch timeout (one fetch, one response) - standalone helper methods + stripHtml utility (inlined where used) - the no-op execute() override (default implementation suffices) - multi-line type aliases collapsed to a single line The trimmed files still typecheck against @lobu/connector-sdk and implement a real sync() that fetches, maps to events, and returns a checkpoint — picking option (b) from the round-3 brief so there's one source of truth, not a hand-curated landing-slim variant on the side. Per-snippet line counts after this commit: 38-46 (was 143-166). Two stragglers (leadership 45, market 46) still render without an internal scrollbar at 1280x800; the gen-landing-snippets script warns on anything > 40 to make future drift visible.
…tors Biome's pre-commit hook reflowed the single-line type aliases and inline authSchema fields in the 8 connector files back into multi-line form, bumping each connector from ~40 lines to 48-60. The runtime CodeBlock still renders without an internal scrollbar at 1280x800 but the JSON manifest needs to match the on-disk source.
Absorb the landing's SETUP_PROMPT interview into the skill so installing the skill is sufficient for an agent (Claude Code, Cursor, etc.) to walk a new user from zero to a working Lobu agent. Standardises on `npx @lobu/cli@latest init` (not the contributor monorepo clone). Four phases: environment (Node 22-24, Postgres+pgvector, provider key), interview (6 questions, one at a time, no batching), scaffold (CLI init + targeted edits), verify (concrete search_memory check + admin UI URL).
…8, connectors ~32
Round 4 brief said snippets are still too long. Re-tuned both the
generator and the connector source files:
gen-landing-snippets.ts:
- trimAgentToml() drops blank-line separators between sections and the
[memory] description / [agents] description / dir fields. Result: a
flat 11-line table (was 13 with blanks, 16 with descriptions).
- compressEntities() now emits ONE entity with at most 3 properties,
each rendered as a single-line `<name>: { type: <t> }` shorthand,
dropping all nested x-table / enum / description metadata. Result:
10 lines (was 27-37).
- compressWatcher() renders slug + agent + on|schedule + a single-line
prompt + extraction_schema.type + required (a single bracketed list,
not the full property graph). Only the FIRST top-level `required:`
is harvested — nested required: arrays inside object properties
no longer leak into the snippet (fixed market). Result: 8 lines
(was 11-24).
- Fixed indent math: children of '- slug: …' live at baseIndent+2 (the
source uses 2-space YAML indent), and the body of metadata_schema /
extraction_schema lives at +4 / +6 respectively.
Connector source files (all 8):
- One-line `import { ConnectorRuntime, type SyncContext }`
- `readonly definition` without explicit ConnectorDefinition typing,
with the inline payload narrowed to key + name + version + authSchema
+ a single-feed feeds map. No description, no eventKinds, no
configSchema, no optionsSchema, no per-field metadataSchema.
- `sync(ctx: SyncContext)` — no Promise<SyncResult> annotation; ctx
fields accessed directly. Inline anonymous response types.
- Minimal no-op execute() so ConnectorRuntime's abstract contract is
satisfied with one line.
Result: connectors land at 31-38 lines (was 48-60 after round 3,
148-166 originally). Budget bumped to 40 to account for biome's
preferred line wrapping.
…l format Round 4 follow-up: after biome reformatted the round-4 connector rewrites back from ~32 to 46-60 lines (it expanded inline anonymous response types and the authSchema methods array onto separate lines), add the file-level pragma // biome-ignore-all format at line 1 of every connector so biome leaves the compact layout alone on subsequent runs. Also replace the per-call inline response types with a single `as any` cast on the json() result, then narrow back at the destructure site (`const records: any[] = …`). Loses some compile-time type info on the remote response but the events the connector emits are still strongly typed via the connector-sdk contract. Per-connector line counts on disk: 32-34 (was 46-60).
Each primitive section (Connectors / Memory / Watchers / Agents) now pairs the code block with a faked terminal transcript on the same side. The terminal shows what running the primitive looks like for the active use case — e.g. for sales the connectors terminal reads: $ lobu run → connector salesforce-pipeline ✓ → synced 3 opportunities 142 ms → event salesforce.opp.updated #acme-q4 New files: - TerminalPanel.tsx — flat dark panel with the dot title bar and a hand-built sequence of tagged segments (plain / muted / accent / string / key / green). Same styling as the hero terminal. - terminal-outputs.ts — 8 use cases × 4 sections of transcripts, authored via small tagged helpers (`p`, `muted`, `accent`, …) so the data block stays compact. LandingPage changes: - All 4 product sections now pull the per-section terminal from TERMINAL_OUTPUTS[useCaseId][section] and render it under the CodeBlock. - Drops the Callout component (no longer used) and the now-orphaned WebhookFanDiagram (the watcher terminal already shows trigger → watcher → extracted → event textually). - AgentsSection drops the inline reaction.ts CodeBlock — viewers can reach it via the connector's 'see on github' link if they want to read the full file.
Hero now mounts a playing asciinema cast instead of a hand-coded React
terminal — frees up that real estate for an actual recording.
Wiring:
- BaseLayout.astro takes a new loadAsciinema?: boolean prop. When true
it ships the vendored CSS in <head> and the standalone .min.js bundle
before </body>. Files live in packages/landing/public/asciinema-player/
(added with -f to defeat the packages/**/*.js gitignore rule).
- biome ignore extended with !**/packages/landing/public/** so the
pre-commit hook doesn't try to lint the minified player.
- index.astro passes loadAsciinema={true} so the player only ships on
the home page; docs / blog don't pay for the bundle.
- LandingPage HeroAsciinema() renders a flat dark container with a
ref callback that calls AsciinemaPlayer.create("/casts/setup.cast",
node, { autoPlay, loop, idleTimeLimit, fit, theme }) once on mount.
Guarded via dataset.asciinemaMounted so HMR doesn't double-mount.
- public/casts/setup.cast — placeholder cast (asciicast v2) that
walks the same scaffold flow the old React terminal showed. Drop
the real recording at this path when ready.
Drops the HeroTerminalDemo function entirely; restores the period on
the hero headline that biome lost in the prior reflow.
The skill (`/plugin install lobu`) absorbs the env-setup + interview +
scaffold flow that was previously inlined in the hero's copy-prompt
button. Landing's prompt is now a three-step ask:
1. install the skill
2. run the skill's onboarding interview (asks what / who / data /
channels / schedule)
3. scaffold + boot locally and show the written memory event
Plus a one-paragraph orientation line + repo / docs URLs so an
unprimed Claude / Cursor / opencode session still gets enough context.
Drops the 30-line hand-rolled phased prompt — the skill is the source
of truth for project conventions now.
…block No behavior change; biome's pre-commit hook reflowed long single-line expressions over multiple lines in three files left uncommitted from round 4. Committing so subsequent diffs stay readable.
Round 5 user feedback. Tightens prose where the rhythm read like AI
output without losing the underlying claim.
- Drop the entire PullQuote section ('If you know TypeScript and
Postgres…' + 'No new language…') — call site and component both gone.
- UseCaseGrid heading: 'Three shapes. One platform.' → 'Three ways
teams ship Lobu agents.'
- Connectors SectionHeading: 'One event stream for everything your
team produces.' → 'One typed event stream from every source.'
- Hero subhead: drops the three-parallel-sentence cadence; adds
'search and cite' as the concrete capability.
- UseCase card 1 (For your team): swaps the 'One-click "Add to Slack"
wires the bot live' marketing line for the matter-of-fact 'Add the
bot to Slack from the admin UI'.
- UseCase card 2 (For your org): replaces 'Dreaming watchers deepen
the graph nightly. No manual ETL.' with the more concrete 'Watchers
run on cron to add structure overnight, so the schema grows with
your data.'
- Watchers feature list 'Dreaming' item: drops 'while your team
sleeps'; collapses 'yesterday's events' to 'the previous day's
events'.
- Agents feature list 'Ship anywhere' item: replaces the empty
one-liner with the actual list (Slack/Telegram/Discord/Teams/
WhatsApp/HTTP/MCP) + 'Same lobu.toml'.
Hero H1 ('Proactive AI agents on self-building knowledge graph') and
the watchers callout (already deleted in round 4) left as-is per the
amended brief.
- Drop `forceTheme="light"` on the home page; honor user's system preference like the rest of the site. - Re-remove the period from the hero headline that the round-5 copy pass reverted.
Each primitive section's right column was a faked terminal transcript. Round-6 swap puts the tables that used to live in the deleted HeroProductCard (round 2, commit e661f98) back in their place — same data shape (LandingUseCaseDefinition), same flat composition rules from round 2 (no shadows, no gradients, lowercase-mono headers, --color-page-border + --color-page-surface). New file packages/landing/src/components/AppShellPanels.tsx (439 lines) exports four panels, each wired into the matching section in LandingPage: - ConnectorsPanel -> ConnectorsSection (collapsible per-connector rows with sample connections under the active row; dedupes by brand-key so github.com + .githubusercontent.com collapse) - MemoryPanel -> MemorySection (entity-type chips strip on top, then first 4 records from useCase.memory.recordTree.children) - WatchersPanel -> WatchersSection (slug + entity + status badge + cron schedule + last run; reuses buildWatcherRows shape from the deleted file) - AgentsPanel -> AgentsSection (agent name + channel + skills chips + status; synthesized from skills.agentId / skills.skills / memory.watcher.name) Each panel wraps in a shared PanelFrame that gives the dot-titlebar look (accent eyebrow on the left, /route on the right) without re-introducing the dark code-bg from TerminalPanel. Watchers section keeps WatchersPanel only — the WebhookFanDiagram had already been deleted in round 4 commit 558d7b2, and the table tells the trigger/entity/schedule story cleanly enough on its own. Drops terminal-outputs.ts + TerminalPanel.tsx entirely; no section uses them anymore.
…amples User feedback: card 3 was off-positioning (Lobu = org/team backend, not B2B SaaS LLM-embed). New framing: every card is a technical shape of a Lobu agent, each backed by a real examples/<id>/ project. - SectionHeading: 'Three ways teams ship Lobu agents.' -> 'Three shapes. Three working examples.' - Card 1: Reactive bot / A chat-driven agent. -> office-bot example - Card 2: Cron digest / A dreaming watcher. -> finance example - Card 3: Event automation / A connector -> watcher -> reaction pipeline. -> sales example Snippet styling unchanged (same monospace mini-block, same code-bg + comment/key/string tokens). Links keep target=_blank + rel=noopener noreferrer as before.
User: 'maybe let's just remove for now.' The synthesized rows (buildSampleConnections -> Albert Lund / 2m ago / lobu-prod.workspace) felt dishonest as a Lobu Cloud preview; we may swap in real screenshots later. - Drop imports for ConnectorsPanel / MemoryPanel / WatchersPanel / AgentsPanel + the landingUseCases import that fed them. - Strip the panel render from each of the 4 product sections; right column is now just the CodeBlock. ProductGrid layout (text + code, alternating reverse) unchanged. - Delete packages/landing/src/components/AppShellPanels.tsx (439 lines). knip on packages/landing reports only pre-existing orphans (functions/*, starlight-*.css, asciinema-player.css) — no leftover panel callers.
User: 'above agents maybe we can have one more section called skills.' Lands SkillsSection (reverse grid, code on left, text on right) so the new section flow reads: Hero -> Architecture -> UseCaseGrid -> Pivot -> Connectors -> Memory -> Watchers -> Skills -> Agents -> Run anywhere -> CTA -> Posts Section content: - Eyebrow: 'Skills' - Heading: 'Bundle tools, packages, and policy into one drop-in.' - Lede + 4-item FeatureList (Instructions / Tools / Network / Packages) - ProductLink 'Read the skills guide' -> /getting-started/ Code panel: - Static CodeSnippet (not pivoted — only lobu-crm + office-bot ship SKILL.md today). Holds the YAML frontmatter + '# CRM operations' + first paragraph of examples/lobu-crm/agents/crm/skills/crm-ops/SKILL.md. - tabLabel = agents/crm/skills/crm-ops/SKILL.md - badge = 'skill' - githubUrl points at the file on main. CodeBlock now supports markdown language: - Tokenizer recognises the leading '---' frontmatter fences, frontmatter 'key: value' pairs (key + string colour), ATX headings (# … through ###### …), and inline backtick code spans. Everything else falls through as plain text.
The crm-ops/SKILL.md frontmatter only carried name+description, which
didn't back the section's 'bundle tools, packages, and policy' pitch.
Swap to office-bot/agents/food-ordering/skills/deliveroo-order — its
frontmatter exercises every promised field:
- nixPackages: [chromium]
- network.allow (registry.npmjs.org, playwright.azureedge.net)
- network.judge (deliveroo.co.uk, deliveroo.com)
- judges.default — plain-English egress policy that allows reads +
basket mutations and DENIES checkout/payment
Description and judges.default policy trimmed to keep the snippet at
19 lines (was 30+ in source). H1 + first paragraph dropped — the
frontmatter alone makes the pitch and there's no room to scroll on the
right column.
tabLabel updated to agents/food-ordering/skills/deliveroo-order/SKILL.md;
githubUrl points at the file on main; language stays markdown so the
tokenizer keeps colouring frontmatter keys, the --- fences, and the
inline backtick code spans (`lobu apply`, etc.).
Body lede tweaked: 'instructions, tools, network, and packages' ->
'instructions, tools, packages, and a per-domain LLM egress policy'
so the prose matches what the new snippet actually demonstrates.
User: the 8-tab pivot at the top forced the reader to scroll back to
remember which use case they picked. Pin every primitive section to a
canonical example instead.
- Delete PIVOT_USE_CASES constant + UseCasePivot component + its
render call.
- Delete activeUseCaseId state, defaultUseCaseId prop, the
LandingUseCaseId / DEFAULT_LANDING_USE_CASE_ID imports.
- Replace with a module-level PINNED_USE_CASE = 'sales' and a single
derived 'active' snippet bundle, fed to every section.
- Drop the now-unused useCaseId field from ProductSectionProps; sections
take just { useCase }.
Following commits (Task B + C) will move the snippet manifest to per-
section pinned files instead of per-use-case bundles.
Round 10 Task B + amendment. New manifest shape:
{
connector: Snippet, // examples/lobu-crm/connectors/funnel-form.connector.ts
memorySchema: Snippet, // examples/sales/models/schema.yaml (entities slice)
watcher: Snippet, // examples/sales/models/schema.yaml (watchers slice)
reaction: Snippet, // examples/sales/models/reactions/account-health-monitor.reaction.ts
agentToml: Snippet, // examples/sales/lobu.toml
examples: ExampleEntry[] // every examples/*/lobu.toml — globbed, not hardcoded
}
Each ExampleEntry has slug + label + description + githubUrl. Label
comes from [agents.<id>] name; description from [memory] description
(falling back to [agents.<id>] description); both pulled by a small
readExampleMeta() parser. Missing description -> null (component
renders the card without a description line).
Drops the round-4 use-case slicing — no more buildForUseCase(useCase),
no more USE_CASES enum. Per-snippet trimming logic kept intact for the
pinned files. Output file path renamed to landing-snippets.json so the
landing import switches cleanly in Task C.
Also slims examples/lobu-crm/connectors/funnel-form.connector.ts from
179 lines to 39 (same // biome-ignore-all format + as any pattern used
for the 8 round-4 connectors) so the pinned connector snippet fits the
landing without scroll.
12 examples land in the manifest (agent-community, atlas, delivery,
ecommerce, finance, leadership, legal, lobu-crm, market, office-bot,
personal-finance, sales) — every directory under examples/ that has a
lobu.toml. Sorted alphabetically by slug.
…le footer links Task C of round 10. - Switch import: ../generated/use-case-snippets.json -> ../generated/landing-snippets.json (flat pinned-snippet shape from Task B). - Drop UseCaseSnippets / ProductSectionProps types; sections take no props and reach into snippets.<key> directly. - New ExampleFooterLink helper renders 'Full example: examples/<slug> →' in muted text with the slug in monospace + accent on hover. - ConnectorsSection: pulls snippets.connector (funnel-form), footer -> examples/lobu-crm. - MemorySection: pulls snippets.memorySchema (sales entities), footer -> examples/sales. - WatchersSection: TWO code blocks stacked — watcher YAML, then a one-line label 'When the watcher extracts data, this reaction runs:', then snippets.reaction (account-health-monitor.reaction.ts). Footer -> examples/sales. - SkillsSection: footer -> examples/office-bot. - AgentsSection: drops the reaction.ts CodeBlock (it lives with the watcher now), keeps just snippets.agentToml. Footer -> examples/sales. Each section's right column now reads top-to-bottom: code -> (optional extras) -> footer link, so the reader always knows which example they're looking at.
…isted) Task D of round 10 + amendment. - Drop UseCaseGrid (3 'shape' cards) entirely. - New BrowseExamplesSection renders one card per examples/ project from snippets.examples (populated by glob in gen-landing-snippets.ts in Task B). Today: 12 cards (agent-community, atlas, delivery, ecommerce, finance, leadership, legal, lobu-crm, market, office-bot, personal-finance, sales), all auto-sorted by slug. - Section position moves down: between AgentsSection and RunAnywhereSection, so the reader sees the primitives first and the catalog last. - Card shape: flat (border + bg-surface, no shadow), the whole card is one <a target=_blank href=githubUrl>, monospace slug at top, optional one-line description from lobu.toml below. - Grid: 1 column on mobile, 2 at sm, 3 at md+. - Section header: eyebrow 'Browse the repo' + heading 'Examples' + sub-lede. Per the amendment: no hardcoded list — gen-landing-snippets emits the examples[] array so adding a new project under examples/ just lands a new card without touching this file.
Replaces the hand-scripted 9-frame placeholder with a real asciicast of `lobu init my-agent` followed by `ls` + `cat lobu.toml`. Output is genuine CLI output; the "npx @lobu/cli@latest …" header is printf'd for marketing readability (locally-installed `lobu` is what actually runs to avoid the bunx download noise in the cast).
End the cast right after `lobu.toml is valid` instead of running on into the `lobu run` env-bug debug spiral (which ends in Claude hallucinating a bot response in the summary). Honest beat: scaffold the project, validate it, done. `lobu chat` end-to-end demo will return once the CLI bug-fix branch lands + we record cleanly with fixed env.
This reverts commit 514a9db.
End the cast right after Claude's 'Validated successfully' status — before it gets into the lobu run env-bug debug spiral that ends with a hallucinated bot response in the summary. Honest beat: paste prompt → Claude scaffolds → schema written → lobu validate succeeds → end. The lobu chat end-to-end demo returns once the CLI bug-fix branch lands and we record with a stable BETTER_AUTH_SECRET env.
The previous Linear GraphQL connector showed a wall of minified GraphQL as a template string that's cryptic for a developer who hasn't worked with Linear. Stripe charges is universally recognized and the snippet shape is plain REST fetch + JSON.map, much easier to read at a glance. Also update the Connectors-section footer link to point at examples/ecommerce.
Adds guide-style entries under getting-started/ and full type references under reference/ for both @lobu/connector-sdk and the reaction surface, points the landing's Connectors/Watchers ProductLinks at them, and surfaces them from the REST API reference splash + platforms/rest-api.
📝 WalkthroughWalkthroughAdds multiple Connector SDK example runtimes, a manifest-driven landing redesign (snippet generator, CodeBlock, ArchitectureDiagram, LandingPage), asciinema assets and layout/theme updates, and Connector/Reaction SDK getting-started and reference documentation. ChangesExample Connector Implementations
Landing Page Redesign
SDK Documentation
Estimated code review effort: Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (1)
packages/landing/scripts/gen-landing-snippets.ts (1)
536-541: 💤 Low valueHardcoded policy summary replaces actual content.
The collapsed
default:value is a fixed string regardless of what the actual skill policy says. This is fine for demo purposes but could be misleading if the pinned skill's policy differs significantly. Consider extracting the first sentence from the actual block scalar instead.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/scripts/gen-landing-snippets.ts` around lines 536 - 541, The code always inserts a hardcoded default policy string when matching the YAML block scalar (regex assigned to blockScalar) which can misrepresent the actual skill policy; instead, parse the matched block scalar lines following the detected indent (use baseIndent and the surrounding lines or the original block scalar content) and extract the first sentence of that block scalar to produce the summary inserted by out.push for the default: entry, preserving indentation via " ".repeat(baseIndent) so the inserted line reflects the real policy rather than a fixed message.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@examples/agent-community/connectors/discourse-posts.connector.ts`:
- Around line 14-16: The current cursor/window logic uses before=${cursor + 50}
and then filters id > cursor which can permanently skip posts if >50 new posts
arrive; replace that with a proper "since" style fetch and pagination: request
posts using after=${cursor} (or iterate fetching pages until no posts > cursor)
and remove the id>cursor post-filter, updating the checkpoint to the highest
returned post id (reference variables: cursor, ctx.checkpoint, the fetch call).
Also include the configured API key from your connector auth (use
ctx.secrets?.api_key or the authSchema key) in the request (e.g., add an Api-Key
header or query param when calling fetch against ctx.config.base_url) so
authentication is actually sent. Ensure to update the fetch invocation and
checkpoint handling in the same function accordingly.
- Around line 9-15: The sync method is calling fetch without using the declared
api_key in authSchema, so private Discourse instances will fail; update the
sync(ctx: SyncContext) implementation to read the configured api_key (from the
connector auth/secrets available on ctx, e.g. ctx.config.auth or ctx.secrets)
and attach it to the request—either by adding an Api-Key header or appending
?api_key=... to the URL—when calling
fetch(`${ctx.config.base_url}/posts.json?...`) so the declared authSchema is
actually applied; ensure the key name matches "api_key" and handle missing key
gracefully.
In `@examples/delivery/connectors/shopify-orders.connector.ts`:
- Around line 9-16: The sync function is not using the access_token declared in
authSchema; update sync to read the token from ctx.auth or ctx.config (where
auth is stored) and include it as the Authorization header (Bearer
<access_token>) or as X-Shopify-Access-Token per Shopify API when performing
fetch to orders.json; modify the fetch call in sync to add the appropriate
header and ensure it throws or logs a clear error if access_token is missing,
referencing authSchema and the sync function so reviewers can find the change.
In `@examples/ecommerce/connectors/stripe-charges.connector.ts`:
- Around line 15-16: The current single-page fetch using
fetch(`https://api.stripe.com/v1/charges?limit=100&created[gt]=${cursor}`) and
then advancing the checkpoint to the last returned record can drop records if
more than 100 charges exist; modify the connector to either (A) iterate
pagination until all pages are retrieved (use Stripe's next_page/has_more or the
response.data + starting_after/limit pattern) before sorting and advancing the
checkpoint, ensuring the loop references the same fetch call and the
response.data handling, or (B) switch the checkpoint to a cursor-based/offset
approach (store Stripe's starting_after/last_id or a composite timestamp+id)
instead of just the last record's created timestamp so no records are skipped
when updating the checkpoint. Ensure the code paths that read `cursor`, call
`fetch(...)`, and update the checkpoint (where the last record's created
timestamp is used) are updated accordingly.
- Around line 9-15: The fetch call in the sync method is missing the Stripe API
credential declared in authSchema: grab the secret_key from the connector
context (e.g., from ctx.connectorAuth or ctx.auth depending on how credentials
are stored in this runtime) and include it in the request as an Authorization:
Bearer header when calling fetch in sync; update the fetch invocation used in
sync (and preserve the existing cursor logic) to pass headers: { Authorization:
`Bearer ${secret_key}` } so Stripe requests authenticate properly.
In `@examples/finance/connectors/quickbooks-transactions.connector.ts`:
- Around line 14-16: The current checkpoint (ctx.checkpoint?.last_txn_date) and
query (TxnDate > since with MAXRESULTS 500) can skip same-day overflow; change
the checkpoint to include sub-day granularity (e.g., last_txn_datetime or a
tuple {last_txn_date, last_txn_id}) and update the query construction in this
connector where variables since, q and the fetch call are created: use a
timestamp field (MetaData.LastUpdatedTime or TxnDate with time) or a composite
filter to break ties (e.g., fetch transactions where TxnDate > last_date OR
(TxnDate = last_date AND Id > last_id)) so the next run includes remaining
same-day records, and when persisting the checkpoint save both the
datetime/timestamp or datetime+id so subsequent queries use the finer-grained
checkpoint.
- Around line 9-16: The fetch to QuickBooks in the async sync(ctx: SyncContext)
function is missing the OAuth Authorization header; update the fetch call that
builds the query URL for ctx.config.realm_id to include headers: pass
Authorization: Bearer ${ctx.credentials?.accessToken} (or
ctx.credentials.accessToken) and appropriate Accept/Content-Type as needed so
the request uses the OAuth token from SyncContext credentials; ensure you
reference the existing q variable and keep encodeURIComponent(q) when adding
headers to the same fetch invocation.
In `@examples/leadership/connectors/linear-cycles.connector.ts`:
- Around line 11-17: In the sync(ctx: SyncContext) function the GraphQL POST to
"https://api.linear.app/graphql" omits the OAuth token; read
ctx.credentials?.accessToken and add an Authorization: Bearer <token> header
when present (merge with existing "Content-Type" header) before calling fetch so
the Linear API request is authenticated.
In `@examples/legal/connectors/docusign-envelopes.connector.ts`:
- Around line 9-16: The sync method is calling the DocuSign API without using
the OAuth token declared in authSchema; update the fetch in sync to include an
Authorization header using ctx.credentials?.accessToken (e.g., headers: {
Authorization: `Bearer ${ctx.credentials?.accessToken}` }) and preserve existing
parameters (from_date, count) and base URL handling; ensure you handle missing
accessToken gracefully (throw or log) before making the request.
In `@examples/market/connectors/exa-news-feed.connector.ts`:
- Around line 15-16: The authSchema definition is incorrect and the connector
doesn't send the API key: change the authSchema object to use type: "env_keys"
and use key (not name) for the field definition (i.e., adjust the authSchema
entry for the Exa API key), and update the sync/fetch logic to read the
env-based credential instead of using ctx.credentials (which is OAuth-only) —
use the SDK's env_keys access pattern (or SyncContext.connection_credentials /
equivalent once exposed) to add the Authorization header to the POST fetch to
https://api.exa.ai/search so the API key is sent with each request.
In `@packages/landing/public/asciinema-player/asciinema-player.css`:
- Line 2: Update the CSS variable --term-font-family to quote all font family
names that currently are unquoted (e.g., wrap Menlo, Consolas, monospace entries
in quotes) and replace any deprecated word-wrap declarations with overflow-wrap
to satisfy Stylelint; search for the --term-font-family declaration and any
occurrences of word-wrap in asciinema-player.css and update the identifiers
accordingly.
In `@packages/landing/src/components/LandingPage.tsx`:
- Around line 346-417: HeroAsciinema's ref stores a cleanup function on
typedNode.__lobuHeroCleanup but never calls it, so timers and listeners leak;
update the ref callback inside HeroAsciinema to, before attaching a new
instance, check if an existing node was previously mounted (i.e., when ref is
called with null on unmount or when replacing the node) and invoke its
__lobuHeroCleanup if present, and also ensure you clear __lobuHeroCleanup after
calling it; reference the typedNode.__lobuHeroCleanup property and the ref
callback in HeroAsciinema to perform this cleanup invocation.
In `@packages/landing/src/components/Nav.tsx`:
- Around line 294-299: Remove the unused prop "currentPath": delete it from the
NavProps type and drop it from the Nav component signature (remove the default
"_currentPath = '/'"), leaving Nav as function Nav() and preserving the internal
state (e.g., openId via useState). Then remove the prop from every .astro
callsite (delete currentPath={Astro.url.pathname} occurrences) so callers match
the new signature, and run the type checker to ensure no remaining references.
In `@packages/landing/src/content/docs/getting-started/connector-sdk.md`:
- Around line 203-210: The fenced code block that shows the project tree (the
triple-backtick block starting at the snippet with "my-agent/") is missing a
language tag and triggers MD040; update that block to use the text language tag
by changing the opening fence from ``` to ```text so the tree snippet is fenced
as plain text.
In `@packages/landing/src/content/docs/getting-started/reaction-sdk.md`:
- Around line 100-109: The fenced filesystem snippet starting at the code block
should specify a language to satisfy markdownlint MD040; update the opening
fence from ``` to ```text so the block (showing the my-agent/ tree with
lobu.toml, models/, watchers/, reactions/, etc.) is explicitly marked as text.
In `@packages/landing/src/content/docs/platforms/rest-api.md`:
- Around line 24-25: Change the link text that reads "`@lobu/reaction-sdk`" to
make it clear this is a docs/reference page rather than an installable
package—e.g., replace the visible text "`@lobu/reaction-sdk`" with "Reaction SDK
reference" (while keeping the same href `/reference/reaction-sdk/`) so readers
don’t confuse the link label with an npm package; leave the
"`@lobu/connector-sdk`" text as-is unless you want it similarly clarified.
In `@packages/landing/src/content/docs/reference/connector-sdk.md`:
- Line 180: The in-page fragment link `#authartifact` in the sentence
referencing `AuthArtifact` is invalid because the actual section is a combined
heading; update the documentation so the fragment resolves: either add a
dedicated heading `### AuthArtifact` (so the `#authartifact` anchor becomes
valid) or change the link target to the exact generated anchor for the combined
heading (replace `#authartifact` with the correct anchor text). Ensure the
sentence still mentions `authenticate(ctx)` and streams `AuthArtifact`s and that
the link points to the newly created `AuthArtifact` heading or the correct
combined-heading anchor.
In `@skills/lobu/SKILL.md`:
- Line 34: Update the onboarding text that currently references "connector.ts"
to use the canonical pattern "connectors/<name>.connector.ts" so it matches the
rest of the guide; replace the occurrence of connector.ts in the SKILL.md
sentence with connectors/<name>.connector.ts and ensure wording remains
consistent with the later reference to connectors/<name>.connector.ts.
---
Nitpick comments:
In `@packages/landing/scripts/gen-landing-snippets.ts`:
- Around line 536-541: The code always inserts a hardcoded default policy string
when matching the YAML block scalar (regex assigned to blockScalar) which can
misrepresent the actual skill policy; instead, parse the matched block scalar
lines following the detected indent (use baseIndent and the surrounding lines or
the original block scalar content) and extract the first sentence of that block
scalar to produce the summary inserted by out.push for the default: entry,
preserving indentation via " ".repeat(baseIndent) so the inserted line reflects
the real policy rather than a fixed message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: c7d2574e-b10c-4720-ad63-dd46052e45f7
⛔ Files ignored due to path filters (4)
packages/landing/public/asciinema-player/asciinema-player-ui.min.jsis excluded by!**/*.min.jspackages/landing/public/asciinema-player/asciinema-player-worker.min.jsis excluded by!**/*.min.jspackages/landing/public/asciinema-player/asciinema-player.min.jsis excluded by!**/*.min.jspackages/landing/src/generated/landing-snippets.jsonis excluded by!**/generated/**
📒 Files selected for processing (36)
config/biome.config.jsonexamples/agent-community/connectors/discourse-posts.connector.tsexamples/delivery/connectors/shopify-orders.connector.tsexamples/ecommerce/connectors/stripe-charges.connector.tsexamples/finance/connectors/quickbooks-transactions.connector.tsexamples/leadership/connectors/linear-cycles.connector.tsexamples/legal/connectors/docusign-envelopes.connector.tsexamples/lobu-crm/connectors/funnel-form.connector.tsexamples/market/connectors/exa-news-feed.connector.tsexamples/sales/connectors/salesforce-pipeline.connector.tspackages/landing/astro.config.mjspackages/landing/package.jsonpackages/landing/public/asciinema-player/asciinema-player.csspackages/landing/public/casts/claude.castpackages/landing/scripts/gen-landing-snippets.tspackages/landing/src/components/ArchitectureDiagram.tsxpackages/landing/src/components/ArchitectureSection.tsxpackages/landing/src/components/CodeBlock.tsxpackages/landing/src/components/FeatureBlock.tsxpackages/landing/src/components/FeatureGraphics.tsxpackages/landing/src/components/HeroProductCard.tsxpackages/landing/src/components/HeroSection.tsxpackages/landing/src/components/LandingPage.tsxpackages/landing/src/components/Nav.tsxpackages/landing/src/content/docs/getting-started/connector-sdk.mdpackages/landing/src/content/docs/getting-started/reaction-sdk.mdpackages/landing/src/content/docs/platforms/rest-api.mdpackages/landing/src/content/docs/reference/connector-sdk.mdpackages/landing/src/content/docs/reference/reaction-sdk.mdpackages/landing/src/globals.csspackages/landing/src/layouts/BaseLayout.astropackages/landing/src/pages/for/[useCase].astropackages/landing/src/pages/index.astropackages/landing/src/pages/reference/api-reference.astropackages/landing/src/styles/theme-script.tsskills/lobu/SKILL.md
💤 Files with no reviewable changes (4)
- packages/landing/src/components/ArchitectureSection.tsx
- packages/landing/src/components/HeroSection.tsx
- packages/landing/src/components/FeatureBlock.tsx
- packages/landing/src/components/FeatureGraphics.tsx
| const cursor = (ctx.checkpoint as any)?.last_post_id ?? 0; | ||
| const r = await fetch(`${ctx.config.base_url}/posts.json?before=${cursor + 50}`); | ||
| const posts: any[] = ((await r.json() as any).latest_posts ?? []).filter((p: any) => p.id > cursor).sort((a: any, b: any) => a.id - b.id); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "discourse-posts.connector.ts" -type fRepository: lobu-ai/lobu
Length of output: 123
🏁 Script executed:
cat -n ./examples/agent-community/connectors/discourse-posts.connector.tsRepository: lobu-ai/lobu
Length of output: 1596
🏁 Script executed:
# Check if there are other files in the connector directory to understand the pattern
find ./examples/agent-community/connectors -type f -name "*.ts" | head -5Repository: lobu-ai/lobu
Length of output: 123
🌐 Web query:
Discourse API posts.json before parameter pagination behavior
💡 Result:
In Discourse’s posts.json (the “list latest posts across topics” API), pagination is done with the query parameter before (a cursor), not with a page= offset. The docs say: “Load posts with an id lower than this value. Useful for pagination,” and the parameter table lists before for get/posts.json. [1] So the intended usage is: - Start with no before parameter (you get the most recent posts). - For the next page, send before= where is the last (oldest) post id you received, and Discourse returns posts with ids lower than that value. [1] If you instead try page=2 with /posts.json, you may find it doesn’t work; a Discourse Meta discussion notes you might need to use /latest.json?page=2 rather than /posts.json (because /posts.json isn’t the right endpoint for page-style pagination). [2] Example (conceptual): - First call: https:///posts.json - Next call: https:///posts.json?before=<oldest_post_id_from_previous_batch> That’s the “before” behavior for posts.json pagination in Discourse.
Citations:
- 1: https://docs.discourse.org/
- 2: https://meta.discourse.org/t/latest-posts-api-pagination/88589?tl=en
🏁 Script executed:
# Check file context - is this an example or production code?
head -5 ./examples/agent-community/connectors/discourse-posts.connector.ts && \
# Check if there's a README or other docs explaining this
find ./examples/agent-community -name "*.md" -o -name "README*" | head -5Repository: lobu-ai/lobu
Length of output: 481
Cursor window logic skips posts and auth key is unused.
Line 15 fetches posts with before=${cursor + 50}, and Line 16 filters to id > cursor. If more than 50 posts arrive between runs, those beyond the window are never read. Additionally, the api_key declared in the authSchema (line 9) is never used in the fetch request—add the API key to the request headers or query parameters.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/agent-community/connectors/discourse-posts.connector.ts` around
lines 14 - 16, The current cursor/window logic uses before=${cursor + 50} and
then filters id > cursor which can permanently skip posts if >50 new posts
arrive; replace that with a proper "since" style fetch and pagination: request
posts using after=${cursor} (or iterate fetching pages until no posts > cursor)
and remove the id>cursor post-filter, updating the checkpoint to the highest
returned post id (reference variables: cursor, ctx.checkpoint, the fetch call).
Also include the configured API key from your connector auth (use
ctx.secrets?.api_key or the authSchema key) in the request (e.g., add an Api-Key
header or query param when calling fetch against ctx.config.base_url) so
authentication is actually sent. Ensure to update the fetch invocation and
checkpoint handling in the same function accordingly.
| authSchema: { methods: [{ type: "env" as const, fields: [{ name: "access_token" }] }] }, | ||
| feeds: { orders: { key: "orders", name: "Order updates" } }, | ||
| }; | ||
|
|
||
| async sync(ctx: SyncContext) { | ||
| const since = (ctx.checkpoint as any)?.updated_at_min ?? "2000-01-01T00:00:00Z"; | ||
| const r = await fetch(`https://${ctx.config.shop}/admin/api/2024-10/orders.json?status=any&updated_at_min=${encodeURIComponent(since)}&limit=100`); | ||
| const orders: any[] = ((await r.json() as any).orders ?? []).sort((a: any, b: any) => new Date(a.updated_at).getTime() - new Date(b.updated_at).getTime()); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n examples/delivery/connectors/shopify-orders.connector.tsRepository: lobu-ai/lobu
Length of output: 1697
access_token is defined in authSchema but never used in the API call.
Line 9 declares env auth, but line 15 sends the Shopify Admin API request without the token, leaving sync unwired to its own auth contract.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/delivery/connectors/shopify-orders.connector.ts` around lines 9 -
16, The sync function is not using the access_token declared in authSchema;
update sync to read the token from ctx.auth or ctx.config (where auth is stored)
and include it as the Authorization header (Bearer <access_token>) or as
X-Shopify-Access-Token per Shopify API when performing fetch to orders.json;
modify the fetch call in sync to add the appropriate header and ensure it throws
or logs a clear error if access_token is missing, referencing authSchema and the
sync function so reviewers can find the change.
| const r = await fetch(`https://api.stripe.com/v1/charges?limit=100&created[gt]=${cursor}`); | ||
| const data: any[] = ((await r.json() as any).data ?? []).sort((a: any, b: any) => a.created - b.created); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n examples/ecommerce/connectors/stripe-charges.connector.ts | head -40Repository: lobu-ai/lobu
Length of output: 1642
Single-page sync with advancing checkpoint risks dropping charges.
The code fetches only one page of 100 records per sync (line 15), then advances the checkpoint to the timestamp of the last record in that page (line 25). If more than 100 charges exist between the previous checkpoint and the API response, the pagination will silently skip any records beyond the first 100 on the next sync. For example, if charges 1–100 are fetched and the newest has created=1100, the next sync will query created[gt]=1100, dropping charges 101–100 that were never fetched in the first request. This can cause financial records to be permanently dropped from the sync.
Either fetch all available records in a single sync by handling pagination, or use a more granular checkpoint strategy (e.g., offset-based or cursor-based pagination with explicit continuation handling).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@examples/ecommerce/connectors/stripe-charges.connector.ts` around lines 15 -
16, The current single-page fetch using
fetch(`https://api.stripe.com/v1/charges?limit=100&created[gt]=${cursor}`) and
then advancing the checkpoint to the last returned record can drop records if
more than 100 charges exist; modify the connector to either (A) iterate
pagination until all pages are retrieved (use Stripe's next_page/has_more or the
response.data + starting_after/limit pattern) before sorting and advancing the
checkpoint, ensuring the loop references the same fetch call and the
response.data handling, or (B) switch the checkpoint to a cursor-based/offset
approach (store Stripe's starting_after/last_id or a composite timestamp+id)
instead of just the last record's created timestamp so no records are skipped
when updating the checkpoint. Ensure the code paths that read `cursor`, call
`fetch(...)`, and update the checkpoint (where the last record's created
timestamp is used) are updated accordingly.
| ``` | ||
| my-agent/ | ||
| ├── lobu.toml | ||
| ├── connectors/ | ||
| │ ├── funnel-form.connector.ts | ||
| │ └── stripe-charges.connector.ts | ||
| └── agents/my-agent/... | ||
| ``` |
There was a problem hiding this comment.
Add a language tag to the fenced tree block.
Line 203 opens a fenced block without a language, which triggers MD040 and can fail docs lint pipelines. Use text for this tree snippet.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 203-203: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/landing/src/content/docs/getting-started/connector-sdk.md` around
lines 203 - 210, The fenced code block that shows the project tree (the
triple-backtick block starting at the snippet with "my-agent/") is missing a
language tag and triggers MD040; update that block to use the text language tag
by changing the opening fence from ``` to ```text so the tree snippet is fenced
as plain text.
| ``` | ||
| my-agent/ | ||
| ├── lobu.toml | ||
| ├── models/ | ||
| │ ├── watchers/ | ||
| │ │ └── account-health-monitor.yaml | ||
| │ └── reactions/ | ||
| │ └── account-health-monitor.reaction.ts | ||
| └── agents/my-agent/... | ||
| ``` |
There was a problem hiding this comment.
Specify a language for the filesystem snippet block.
Line 100 starts a fenced block without a language. Add text to satisfy markdownlint MD040.
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 100-100: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/landing/src/content/docs/getting-started/reaction-sdk.md` around
lines 100 - 109, The fenced filesystem snippet starting at the code block should
specify a language to satisfy markdownlint MD040; update the opening fence from
``` to ```text so the block (showing the my-agent/ tree with lobu.toml, models/,
watchers/, reactions/, etc.) is explicitly marked as text.
| } | ||
| ``` | ||
|
|
||
| Connectors declaring this method must implement `authenticate(ctx)` and stream [`AuthArtifact`s](#authartifact). |
There was a problem hiding this comment.
Fix broken in-page fragment link for AuthArtifact.
Line 180 links to #authartifact, but the section is a combined heading, so this fragment is invalid (MD051). Point to the actual generated anchor (or split out a dedicated ### AuthArtifact heading and keep #authartifact).
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 180-180: Link fragments should be valid
(MD051, link-fragments)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/landing/src/content/docs/reference/connector-sdk.md` at line 180,
The in-page fragment link `#authartifact` in the sentence referencing
`AuthArtifact` is invalid because the actual section is a combined heading;
update the documentation so the fragment resolves: either add a dedicated
heading `### AuthArtifact` (so the `#authartifact` anchor becomes valid) or
change the link target to the exact generated anchor for the combined heading
(replace `#authartifact` with the correct anchor text). Ensure the sentence
still mentions `authenticate(ctx)` and streams `AuthArtifact`s and that the link
points to the newly created `AuthArtifact` heading or the correct
combined-heading anchor.
| 1. **What is the agent for?** One sentence. (Example: "Triage support emails and draft replies for the on-call engineer.") | ||
| 2. **Who uses it?** Just you, your team, or each of your customers (multi-tenant)? | ||
| 3. **What does it need to remember?** Translate the answer into 1–3 entity types. (Example: "support tickets, customers, recurring issues.") | ||
| 4. **Where does the data come from?** One source to start. (Slack, Gmail, GitHub, Linear, Stripe, a custom webhook, a CSV — pick one.) If the user names a source Lobu does not ship a bundled connector for, plan to write a custom `connector.ts`. |
There was a problem hiding this comment.
Use the canonical connector filename pattern in onboarding text.
Line 34 says connector.ts, but this guide later standardizes on connectors/<name>.connector.ts (Line 52). Align Line 34 to the same pattern to avoid scaffolding mistakes.
🧰 Tools
🪛 LanguageTool
[style] ~34-~34: It’s more common nowadays to write this noun as one word.
Context: ...stom webhook, a CSV — pick one.) If the user names a source Lobu does not ship a bundled c...
(RECOMMENDED_COMPOUNDS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/lobu/SKILL.md` at line 34, Update the onboarding text that currently
references "connector.ts" to use the canonical pattern
"connectors/<name>.connector.ts" so it matches the rest of the guide; replace
the occurrence of connector.ts in the SKILL.md sentence with
connectors/<name>.connector.ts and ensure wording remains consistent with the
later reference to connectors/<name>.connector.ts.
|
There was a problem hiding this comment.
♻️ Duplicate comments (2)
packages/landing/src/content/docs/getting-started/reaction-sdk.md (1)
124-133:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSpecify a language for the tree code block.
Line 124 starts a fenced block without a language tag. Please change it to
```textto resolve MD040.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/src/content/docs/getting-started/reaction-sdk.md` around lines 124 - 133, The fenced code block that shows the directory tree (the block beginning with "my-agent/" in reaction-sdk.md) is missing a language tag; update the opening fence from ``` to ```text so the tree is marked as plain text and resolves MD040 (look for the code block containing "lobu.toml" and the models/reactions/watchers entries).packages/landing/src/content/docs/getting-started/connector-sdk.md (1)
305-312:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd a language tag to the filesystem tree fence.
Line 305 opens a fenced block without a language. Use
text(i.e.,```text) to satisfy MD040 and keep docs lint clean.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/src/content/docs/getting-started/connector-sdk.md` around lines 305 - 312, The fenced code block showing the filesystem tree starting with "my-agent/" uses an unlabeled triple-backtick; change its opening fence from ``` to ```text so the block becomes a text-labeled fence (e.g., ```text) to satisfy MD040 and keep the docs lint clean; update the fence that encloses the tree (the block that contains "my-agent/ ├── lobu.toml ...") accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@packages/landing/src/content/docs/getting-started/connector-sdk.md`:
- Around line 305-312: The fenced code block showing the filesystem tree
starting with "my-agent/" uses an unlabeled triple-backtick; change its opening
fence from ``` to ```text so the block becomes a text-labeled fence (e.g.,
```text) to satisfy MD040 and keep the docs lint clean; update the fence that
encloses the tree (the block that contains "my-agent/ ├── lobu.toml ...")
accordingly.
In `@packages/landing/src/content/docs/getting-started/reaction-sdk.md`:
- Around line 124-133: The fenced code block that shows the directory tree (the
block beginning with "my-agent/" in reaction-sdk.md) is missing a language tag;
update the opening fence from ``` to ```text so the tree is marked as plain text
and resolves MD040 (look for the code block containing "lobu.toml" and the
models/reactions/watchers entries).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 75898b1c-2e10-4440-86bf-9cc7914a7016
📒 Files selected for processing (3)
packages/landing/src/content/docs/getting-started/connector-sdk.mdpackages/landing/src/content/docs/getting-started/reaction-sdk.mdpackages/landing/src/content/docs/reference/reaction-sdk.md
✅ Files skipped from review due to trivial changes (1)
- packages/landing/src/content/docs/reference/reaction-sdk.md
…k contract - auth: type 'env' -> 'env_keys', field 'name' -> 'key', add label/secret/required - read secret key from ctx.config.secret_key (env_keys land in ctx.config) - send Authorization: Bearer header so the call actually authenticates - emit required payload_text on every EventEnvelope - replace any-casts with typed Checkpoint + StripeCharge + EventEnvelope[] Regenerated landing-snippets.json from the fixed source so the landing page code panel ships the working version.
Per the runtime (packages/connector-worker/src/executor/child-runner.ts), env_keys values land in `ctx.config` under the field's `key`, not on `ctx.credentials`. `ctx.credentials` is OAuth-only. - getting-started/connector-sdk.md: read PAT from ctx.config.token; clarify the SyncContext table and the inline auth-models note; apply the same fix to the assign_issue execute() example. - reference/connector-sdk.md: add an explicit 'where credentials land' paragraph after SyncContext mapping each auth method to the right slot.
There is no @lobu/reaction-sdk npm package — reactions are part of @lobu/connector-sdk. Keep reactions as a first-class concept (own docs page, sidebar entry, arch pill) but drop language that implies a separate package. - ArchitectureDiagram: pill label 'reactions' (was '@lobu/reaction-sdk') with caption 'from @lobu/connector-sdk'. - astro.config.mjs: sidebar labels 'Reaction SDK' / '@lobu/reaction-sdk' -> 'Reactions'. URL slugs unchanged. - reference/api-reference.astro: quick-link 'Reactions' (was '@lobu/reaction-sdk'). - getting-started/reaction-sdk.md: title 'Reactions'; open with the explicit 'part of @lobu/connector-sdk' framing. - reference/reaction-sdk.md: title 'Reactions'; clarify all types come from @lobu/connector-sdk. - platforms/rest-api.md + connector-sdk guide/reference cross-links: relabel 'Reactions' with parenthetical 'part of @lobu/connector-sdk'.
The hero terminal cast was mounted into an empty div with no role, no label, no fallback text. Screen-reader users and JS-disabled visitors got nothing. - Wrap the player in role='img' + aria-label describing the demo. - Add a sr-only <p> sibling summarising what the asciicast contains, so assistive tech and no-JS visitors still get the gist even when the asciinema-player script fails to mount (JS error, blocked CDN). - Split the wrapper / mount-target so the player's DOM rewrites don't clobber the sr-only summary.
Visitors could read the entities-table widget and run-anywhere terminal cards as a snapshot of a real customer's data. - ArchitectureDiagram entities table: 'acme corp' / 'jane doe' / 'q3 review' -> 'Customer A/B/C'. Reads as a schema preview. - LandingPage Run-anywhere local card: pid=72341 / 2 entities / 1 armed -> pid=<n> / N entities / N armed. - LandingPage Run-anywhere cloud card: org=acme / region=us-east-1 / 1 deployed / acme.lobu.run -> org=<your-org> / region=<your-region> / N deployed / <your-org>.lobu.run. Biome reformatted nesting inside HeroAsciinema after the prior commit nested the player <div>; carried that whitespace-only change in this commit.
|
previous findings resolved — looks clean to merge |
There was a problem hiding this comment.
♻️ Duplicate comments (4)
examples/ecommerce/connectors/stripe-charges.connector.ts (1)
20-33:⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy liftPaginate Stripe results before checkpoint advancement to avoid dropped events.
Line 20 fetches only one page (
limit=100), while Line 33 advances the checkpoint to the newestcreatedin that page. If >100 charges exist between runs (or multiple charges share the boundary timestamp), some events can be skipped permanently.Use
has_more+starting_afterpaging and advance a composite checkpoint (timestamp + stable tie-breaker id) after all pages are processed.Stripe Charges List API pagination semantics: confirm `has_more`, `starting_after`, and ordering guarantees when using `created[gt]` filters.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/ecommerce/connectors/stripe-charges.connector.ts` around lines 20 - 33, The current fetch in stripe-charges.connector.ts only retrieves one page and advances the checkpoint to the newest created in that page (using data.at(-1)?.created), which can drop events; change the logic in the connector that builds events (the fetch/const r, parsing into data, events mapping, and return checkpoint) to loop with Stripe pagination (inspect response.has_more and use starting_after with the last charge.id) accumulating all pages into a single data/events array before returning, and return a composite checkpoint object (e.g., { last_created: <timestamp>, last_id: <stable_charge_id> } ) so ties at the same timestamp are resolved deterministically when advancing the checkpoint. Ensure you use the charge.id as the stable tie-breaker and only advance the checkpoint after all pages are processed.packages/landing/src/content/docs/getting-started/reaction-sdk.md (1)
124-124:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winSpecify a language for the project-tree fenced block.
Line 124 starts a fenced block without a language (MD040).
Suggested fix
-``` +```text my-agent/ ├── lobu.toml ├── models/🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/src/content/docs/getting-started/reaction-sdk.md` at line 124, The fenced code block that begins with ``` is missing a language label (MD040); update the fence used for the project-tree snippet to include a language identifier (e.g., change the opening fence from ``` to ```text) so the block is explicitly marked as plain text for the project-tree content in reaction-sdk.md.packages/landing/src/content/docs/getting-started/connector-sdk.md (1)
310-310:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd a language tag to the filesystem tree code fence.
The fence opened at Line 310 is missing a language identifier (MD040).
Suggested fix
-``` +```text my-agent/ ├── lobu.toml ├── connectors/🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/src/content/docs/getting-started/connector-sdk.md` at line 310, The code fence that renders the filesystem tree (the opening triple-backtick immediately before the "my-agent/" tree block) is missing a language tag; update the opening fence from ``` to ```text so the block is explicitly marked as plain text (look for the fence preceding the "my-agent/" / connectors/ tree in connector-sdk.md and change the fence to ```text).packages/landing/src/content/docs/reference/connector-sdk.md (1)
180-180:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winFix invalid in-page fragment link for
AuthArtifact.Line 180 links to
#authartifact, but that anchor does not exist for the combined heading (MD051).Suggested fix
-Connectors declaring this method must implement `authenticate(ctx)` and stream [`AuthArtifact`s](`#authartifact`). +Connectors declaring this method must implement `authenticate(ctx)` and stream [`AuthArtifact`s](`#authcontext--authartifact--authresult`).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/landing/src/content/docs/reference/connector-sdk.md` at line 180, The in-page link to AuthArtifact is using an invalid anchor `#authartifact`; update the link in the sentence that mentions "stream [`AuthArtifact`s](`#authartifact`)" to use the correct generated heading anchor for the "AuthArtifact" heading (replace `#authartifact` with the proper anchor, e.g. `#auth-artifact`), ensuring the link matches the combined heading slug for the AuthArtifact section and verifies it resolves in the rendered doc; keep the surrounding text and the reference to authenticate(ctx) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In `@examples/ecommerce/connectors/stripe-charges.connector.ts`:
- Around line 20-33: The current fetch in stripe-charges.connector.ts only
retrieves one page and advances the checkpoint to the newest created in that
page (using data.at(-1)?.created), which can drop events; change the logic in
the connector that builds events (the fetch/const r, parsing into data, events
mapping, and return checkpoint) to loop with Stripe pagination (inspect
response.has_more and use starting_after with the last charge.id) accumulating
all pages into a single data/events array before returning, and return a
composite checkpoint object (e.g., { last_created: <timestamp>, last_id:
<stable_charge_id> } ) so ties at the same timestamp are resolved
deterministically when advancing the checkpoint. Ensure you use the charge.id as
the stable tie-breaker and only advance the checkpoint after all pages are
processed.
In `@packages/landing/src/content/docs/getting-started/connector-sdk.md`:
- Line 310: The code fence that renders the filesystem tree (the opening
triple-backtick immediately before the "my-agent/" tree block) is missing a
language tag; update the opening fence from ``` to ```text so the block is
explicitly marked as plain text (look for the fence preceding the "my-agent/" /
connectors/ tree in connector-sdk.md and change the fence to ```text).
In `@packages/landing/src/content/docs/getting-started/reaction-sdk.md`:
- Line 124: The fenced code block that begins with ``` is missing a language
label (MD040); update the fence used for the project-tree snippet to include a
language identifier (e.g., change the opening fence from ``` to ```text) so the
block is explicitly marked as plain text for the project-tree content in
reaction-sdk.md.
In `@packages/landing/src/content/docs/reference/connector-sdk.md`:
- Line 180: The in-page link to AuthArtifact is using an invalid anchor
`#authartifact`; update the link in the sentence that mentions "stream
[`AuthArtifact`s](`#authartifact`)" to use the correct generated heading anchor
for the "AuthArtifact" heading (replace `#authartifact` with the proper anchor,
e.g. `#auth-artifact`), ensuring the link matches the combined heading slug for
the AuthArtifact section and verifies it resolves in the rendered doc; keep the
surrounding text and the reference to authenticate(ctx) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 134d6fa6-d855-45d4-93c2-8bc324f9f4f8
⛔ Files ignored due to path filters (1)
packages/landing/src/generated/landing-snippets.jsonis excluded by!**/generated/**
📒 Files selected for processing (10)
examples/ecommerce/connectors/stripe-charges.connector.tspackages/landing/astro.config.mjspackages/landing/src/components/ArchitectureDiagram.tsxpackages/landing/src/components/LandingPage.tsxpackages/landing/src/content/docs/getting-started/connector-sdk.mdpackages/landing/src/content/docs/getting-started/reaction-sdk.mdpackages/landing/src/content/docs/platforms/rest-api.mdpackages/landing/src/content/docs/reference/connector-sdk.mdpackages/landing/src/content/docs/reference/reaction-sdk.mdpackages/landing/src/pages/reference/api-reference.astro
✅ Files skipped from review due to trivial changes (1)
- packages/landing/src/content/docs/reference/reaction-sdk.md
🚧 Files skipped from review as they are similar to previous changes (4)
- packages/landing/src/content/docs/platforms/rest-api.md
- packages/landing/src/pages/reference/api-reference.astro
- packages/landing/astro.config.mjs
- packages/landing/src/components/LandingPage.tsx
Summary
Full rebuild of the lobu.ai home page across ~60 commits, in iterative rounds. Targets developers reading code, not operators picking use cases.
Highlights
Hero
Architecture diagram (animated, flat)
Sections (pinned to real examples)
Browse-examples grid — 12 cards auto-globbed from `examples/*/lobu.toml`. Drop a new example, the grid grows.
SDK docs — new `docs/getting-started/connector-sdk.md` + `reaction-sdk.md` (guides) and matching `docs/reference/` entries; sidebar updated; ProductLinks rewired.
Other
What's deferred
Test plan
Summary by CodeRabbit
New Features
Documentation
Chores