-
Notifications
You must be signed in to change notification settings - Fork 20
feat(landing): dev-focused rebuild — pinned examples, animated architecture, real cast #945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a16c054
38e110b
f36b86f
6a4c2a0
8f553a9
f074f3b
e661f98
2e51bbc
940cf81
d6d825c
8e5e6bc
484024f
f54b7e4
3a62d1e
558d7b2
dfa0591
d31b647
df754fc
7431d87
303c8d8
71087d7
0e8a5ac
fd61cea
b1ea643
68c5c33
f4a48f3
2444a60
7a2e869
16490a6
9320f5e
e022428
1af120d
72edda7
bc670ba
756d659
c4740d6
5f10ef0
7e11f7e
13c68ec
670d98b
4308594
1b34205
7bcfbfd
45fce74
72c0ba2
9e1d7db
1712b0f
2414d63
97976ba
77063c3
4e85ac0
cdf7d48
8b23a9d
808361d
514a9db
2370627
ca600e5
be188e9
aae8df8
008f05d
79d450b
b298664
b760a85
d5a9ea8
371cc9f
314e219
6f9f3d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| // biome-ignore-all format: stays compact for the landing-page code panel | ||
| import { ConnectorRuntime, type SyncContext } from "@lobu/connector-sdk"; | ||
|
|
||
| export default class DiscoursePostsConnector extends ConnectorRuntime { | ||
| readonly definition = { | ||
| key: "discourse-posts", | ||
| name: "Discourse posts", | ||
| version: "1.0.0", | ||
| authSchema: { methods: [{ type: "env" as const, fields: [{ name: "api_key" }] }] }, | ||
| feeds: { posts: { key: "posts", name: "Forum posts" } }, | ||
| }; | ||
|
|
||
| async sync(ctx: SyncContext) { | ||
| 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); | ||
|
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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:
💡 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:
🏁 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 🤖 Prompt for AI Agents |
||
| return { | ||
| events: posts.map((p) => ({ | ||
| origin_id: String(p.id), | ||
| origin_type: "post_created", | ||
| title: p.topic_title ?? `Post by ${p.username}`, | ||
| author_name: p.username, | ||
| source_url: `${ctx.config.base_url}/t/${p.topic_slug}/${p.topic_id}/${p.id}`, | ||
| occurred_at: new Date(p.created_at), | ||
| })), | ||
| checkpoint: { last_post_id: posts.at(-1)?.id ?? cursor }, | ||
| }; | ||
| } | ||
|
|
||
| async execute() { | ||
| return { success: false, error: "no actions" }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // biome-ignore-all format: stays compact for the landing-page code panel | ||
| import { ConnectorRuntime, type SyncContext } from "@lobu/connector-sdk"; | ||
|
|
||
| export default class ShopifyOrdersConnector extends ConnectorRuntime { | ||
| readonly definition = { | ||
| key: "shopify-orders", | ||
| name: "Shopify orders", | ||
| version: "1.0.0", | ||
| 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()); | ||
|
Comment on lines
+9
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n examples/delivery/connectors/shopify-orders.connector.tsRepository: lobu-ai/lobu Length of output: 1697
Line 9 declares env auth, but line 15 sends the Shopify Admin API request without the token, leaving 🤖 Prompt for AI Agents |
||
| return { | ||
| events: orders.map((o) => ({ | ||
| origin_id: `${o.id}:${o.updated_at}`, | ||
| origin_type: "order_updated", | ||
| title: `Order ${o.name} — ${o.fulfillment_status ?? "unfulfilled"}`, | ||
| source_url: `https://${ctx.config.shop}/admin/orders/${o.id}`, | ||
| occurred_at: new Date(o.updated_at), | ||
| })), | ||
| checkpoint: { updated_at_min: orders.at(-1)?.updated_at ?? since }, | ||
| }; | ||
| } | ||
|
|
||
| async execute() { | ||
| return { success: false, error: "no actions" }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| // biome-ignore-all format: stays compact for the landing-page code panel | ||
| import { ConnectorRuntime, type ConnectorDefinition, type EventEnvelope, type SyncContext, type SyncResult } from "@lobu/connector-sdk"; | ||
|
|
||
| interface StripeCharge { id: string; amount: number; currency: string; created: number; refunded: boolean } | ||
| interface Checkpoint { last_created: number } | ||
|
|
||
| export default class StripeChargesConnector extends ConnectorRuntime { | ||
| readonly definition: ConnectorDefinition = { | ||
| key: "stripe-charges", | ||
| name: "Stripe charges", | ||
| version: "1.0.0", | ||
| // Stripe secret key collected per connection; exposed to sync() as ctx.config.secret_key. | ||
| authSchema: { methods: [{ type: "env_keys", fields: [{ key: "secret_key", label: "Stripe secret key", secret: true, required: true }] }] }, | ||
| feeds: { charges: { key: "charges", name: "Charges" } }, | ||
| }; | ||
|
|
||
| async sync(ctx: SyncContext): Promise<SyncResult> { | ||
| const cursor = ((ctx.checkpoint ?? {}) as Partial<Checkpoint>).last_created ?? 0; | ||
| const secretKey = String(ctx.config.secret_key ?? ""); | ||
| const r = await fetch(`https://api.stripe.com/v1/charges?limit=100&created[gt]=${cursor}`, { | ||
| headers: { Authorization: `Bearer ${secretKey}` }, | ||
| }); | ||
| if (!r.ok) throw new Error(`Stripe ${r.status}: ${await r.text()}`); | ||
| const data = (((await r.json()) as { data?: StripeCharge[] }).data ?? []).sort((a, b) => a.created - b.created); | ||
| const events: EventEnvelope[] = data.map((c) => ({ | ||
| origin_id: c.refunded ? `${c.id}:refund` : c.id, | ||
| origin_type: c.refunded ? "charge_refunded" : "charge_succeeded", | ||
| title: `${c.refunded ? "Refund" : "Charge"} — ${(c.amount / 100).toFixed(2)} ${c.currency.toUpperCase()}`, | ||
| payload_text: `${c.refunded ? "Refund" : "Charge"} of ${(c.amount / 100).toFixed(2)} ${c.currency.toUpperCase()} (stripe id ${c.id})`, | ||
| source_url: `https://dashboard.stripe.com/payments/${c.id}`, | ||
| occurred_at: new Date(c.created * 1000), | ||
| })); | ||
| return { events, checkpoint: { last_created: data.at(-1)?.created ?? cursor } satisfies Checkpoint }; | ||
| } | ||
|
|
||
| async execute() { | ||
| return { success: false, error: "no actions" }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // biome-ignore-all format: stays compact for the landing-page code panel | ||
| import { ConnectorRuntime, type SyncContext } from "@lobu/connector-sdk"; | ||
|
|
||
| export default class QuickBooksTransactionsConnector extends ConnectorRuntime { | ||
| readonly definition = { | ||
| key: "quickbooks-transactions", | ||
| name: "QuickBooks transactions", | ||
| version: "1.0.0", | ||
| authSchema: { methods: [{ type: "oauth" as const, provider: "intuit" }] }, | ||
| feeds: { transactions: { key: "transactions", name: "Posted transactions" } }, | ||
| }; | ||
|
|
||
| async sync(ctx: SyncContext) { | ||
| const since = (ctx.checkpoint as any)?.last_txn_date ?? "1970-01-01"; | ||
| const q = `SELECT * FROM Transaction WHERE TxnDate > '${since}' ORDERBY TxnDate ASC MAXRESULTS 500`; | ||
| const r = await fetch(`https://quickbooks.api.intuit.com/v3/company/${ctx.config.realm_id}/query?query=${encodeURIComponent(q)}`); | ||
|
Comment on lines
+9
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate and examine the file
find . -type f -name "quickbooks-transactions.connector.ts" | head -5Repository: lobu-ai/lobu Length of output: 123 🏁 Script executed: # Once found, examine the full file
cat -n examples/finance/connectors/quickbooks-transactions.connector.tsRepository: lobu-ai/lobu Length of output: 1643 🏁 Script executed: # Search for SyncContext definition in connector-sdk
rg "type SyncContext|interface SyncContext|class SyncContext" -A 20Repository: lobu-ai/lobu Length of output: 50370 🏁 Script executed: # Look for other connector examples that might show proper OAuth usage
find . -type f -name "*.connector.ts" | head -10Repository: lobu-ai/lobu Length of output: 602 🏁 Script executed: # Check if there are any other connectors using oauth
rg "oauth.*provider" --type tsRepository: lobu-ai/lobu Length of output: 7716 🏁 Script executed: # Check another OAuth connector to see the proper pattern
cat -n examples/sales/connectors/salesforce-pipeline.connector.ts | head -30Repository: lobu-ai/lobu Length of output: 1574 🏁 Script executed: # Search for how credentials are used in the connector runtime
rg "credentials" packages/connector-sdk/src/connector-types.ts -A 5 -B 5Repository: lobu-ai/lobu Length of output: 2366 🏁 Script executed: # Check for fetch wrapper or middleware that might inject auth
rg "Authorization.*accessToken|accessToken.*Authorization" -A 3 -B 3Repository: lobu-ai/lobu Length of output: 8101 Add Authorization header to fetch request using credentials from context. Line 16 declares OAuth for Intuit but sends the request without passing credentials. The 🤖 Prompt for AI Agents
Comment on lines
+14
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n examples/finance/connectors/quickbooks-transactions.connector.tsRepository: lobu-ai/lobu Length of output: 1643 Use timestamp or sequence granularity for checkpoint to avoid skipping same-day transaction overflow. With 🤖 Prompt for AI Agents |
||
| const txns: any[] = (await r.json() as any).QueryResponse?.Transaction ?? []; | ||
| return { | ||
| events: txns.map((t) => ({ | ||
| origin_id: t.Id, | ||
| origin_type: "transaction_posted", | ||
| title: `${t.AccountRef?.name ?? "Bank"} — $${t.Amount.toFixed(2)}`, | ||
| occurred_at: new Date(`${t.TxnDate}T00:00:00Z`), | ||
| })), | ||
| checkpoint: { last_txn_date: txns.at(-1)?.TxnDate ?? since }, | ||
| }; | ||
| } | ||
|
|
||
| async execute() { | ||
| return { success: false, error: "no actions" }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // biome-ignore-all format: stays compact for the landing-page code panel | ||
| import { ConnectorRuntime, type SyncContext } from "@lobu/connector-sdk"; | ||
|
|
||
| const QUERY = `query($t:ID!,$a:DateTimeOrDuration!){issues(first:100,filter:{team:{id:{eq:$t}},updatedAt:{gt:$a},cycle:{isActive:{eq:true}}},orderBy:updatedAt){nodes{id identifier title url updatedAt state{name}}}}`; | ||
|
|
||
| export default class LinearCyclesConnector extends ConnectorRuntime { | ||
| readonly definition = { | ||
| key: "linear-cycles", | ||
| name: "Linear cycles", | ||
| version: "1.0.0", | ||
| authSchema: { methods: [{ type: "oauth" as const, provider: "linear" }] }, | ||
| feeds: { cycle_issues: { key: "cycle_issues", name: "Cycle issues" } }, | ||
| }; | ||
|
|
||
| async sync(ctx: SyncContext) { | ||
| const since = (ctx.checkpoint as any)?.updated_at ?? "2000-01-01T00:00:00Z"; | ||
| const r = await fetch("https://api.linear.app/graphql", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query: QUERY, variables: { t: ctx.config.team_id, a: since } }) }); | ||
|
Comment on lines
+11
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n examples/leadership/connectors/linear-cycles.connector.tsRepository: lobu-ai/lobu Length of output: 1846 🏁 Script executed: find . -type f -name "*.ts" -o -name "*.tsx" | xargs grep -l "SyncContext" | head -20Repository: lobu-ai/lobu Length of output: 1179 🏁 Script executed: rg "class.*Connector.*extends ConnectorRuntime" -A 20 | head -100Repository: lobu-ai/lobu Length of output: 13551 🏁 Script executed: cat -n packages/connector-sdk/src/connector-types.ts | head -150Repository: lobu-ai/lobu Length of output: 5887 🏁 Script executed: rg "interface SyncContext|type SyncContext" -A 15 packages/connector-sdk/Repository: lobu-ai/lobu Length of output: 1560 🏁 Script executed: rg "interface SyncCredentials|type SyncCredentials" -A 10 packages/connector-sdk/Repository: lobu-ai/lobu Length of output: 776 🏁 Script executed: rg "ctx\.credentials" examples/ packages/connectors/ -B 2 -A 2Repository: lobu-ai/lobu Length of output: 7199 Add Authorization header using OAuth credentials from context. Line 11 declares Linear OAuth, but Line 17 executes the GraphQL request without applying credentials. Every OAuth-enabled connector in the codebase extracts the access token from Add the access token to the Authorization header: 🤖 Prompt for AI Agents |
||
| const issues: any[] = ((await r.json()) as any).data?.issues?.nodes ?? []; | ||
| return { | ||
| events: issues.map((i) => ({ | ||
| origin_id: `${i.id}:${i.state.name}`, | ||
| origin_type: "issue_state_changed", | ||
| title: `${i.identifier} ${i.title} → ${i.state.name}`, | ||
| source_url: i.url, | ||
| occurred_at: new Date(i.updatedAt), | ||
| })), | ||
| checkpoint: { updated_at: issues.at(-1)?.updatedAt ?? since }, | ||
| }; | ||
| } | ||
|
|
||
| async execute() { | ||
| return { success: false, error: "no actions" }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| // biome-ignore-all format: stays compact for the landing-page code panel | ||
| import { ConnectorRuntime, type SyncContext } from "@lobu/connector-sdk"; | ||
|
|
||
| export default class DocuSignEnvelopesConnector extends ConnectorRuntime { | ||
| readonly definition = { | ||
| key: "docusign-envelopes", | ||
| name: "DocuSign envelopes", | ||
| version: "1.0.0", | ||
| authSchema: { methods: [{ type: "oauth" as const, provider: "docusign" }] }, | ||
| feeds: { envelopes: { key: "envelopes", name: "Envelope status changes" } }, | ||
| }; | ||
|
|
||
| async sync(ctx: SyncContext) { | ||
| const since = (ctx.checkpoint as any)?.last_status_changed ?? "2000-01-01T00:00:00Z"; | ||
| const base = String(ctx.config.base_path ?? "https://www.docusign.net/restapi").replace(/\/$/, ""); | ||
| const r = await fetch(`${base}/v2.1/accounts/${ctx.config.account_id}/envelopes?from_date=${encodeURIComponent(since)}&count=100`); | ||
|
Comment on lines
+9
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n examples/legal/connectors/docusign-envelopes.connector.tsRepository: lobu-ai/lobu Length of output: 1799 🏁 Script executed: find . -type f -name "*.connector.ts" | head -10Repository: lobu-ai/lobu Length of output: 602 🏁 Script executed: rg "authSchema.*oauth" --type ts -A 5 -B 2 | head -60Repository: lobu-ai/lobu Length of output: 3328 🏁 Script executed: rg "class.*extends ConnectorRuntime" --type ts -l | head -5Repository: lobu-ai/lobu Length of output: 256 🏁 Script executed: cat -n examples/sales/connectors/salesforce-pipeline.connector.ts | head -30Repository: lobu-ai/lobu Length of output: 1574 🏁 Script executed: cat -n examples/finance/connectors/quickbooks-transactions.connector.ts | head -30Repository: lobu-ai/lobu Length of output: 1623 🏁 Script executed: cat -n examples/leadership/connectors/linear-cycles.connector.ts | head -30Repository: lobu-ai/lobu Length of output: 1740 🏁 Script executed: find . -type f -name "*.ts" -path "*/connector-sdk/*" | head -20Repository: lobu-ai/lobu Length of output: 992 🏁 Script executed: rg "type SyncContext" --type ts -B 2 -A 10 | head -50Repository: lobu-ai/lobu Length of output: 2842 🏁 Script executed: cat -n packages/connector-sdk/src/connector-types.ts | head -100Repository: lobu-ai/lobu Length of output: 3933 🏁 Script executed: cat -n packages/connector-sdk/src/types.ts | head -100Repository: lobu-ai/lobu Length of output: 3987 🏁 Script executed: rg "interface SyncContext" --type ts -B 2 -A 20Repository: lobu-ai/lobu Length of output: 4026 🏁 Script executed: rg "export.*SyncContext" --type ts -B 2 -A 15 | head -80Repository: lobu-ai/lobu Length of output: 3356 Add Authorization header using OAuth credentials from context.
🤖 Prompt for AI Agents |
||
| const envelopes: any[] = ((await r.json() as any).envelopes ?? []).sort((a: any, b: any) => new Date(a.statusChangedDateTime).getTime() - new Date(b.statusChangedDateTime).getTime()); | ||
| return { | ||
| events: envelopes.map((e) => ({ | ||
| origin_id: `${e.envelopeId}:${e.status}`, | ||
| origin_type: "envelope_status_changed", | ||
| title: `${e.emailSubject ?? e.envelopeId} → ${e.status}`, | ||
| occurred_at: new Date(e.statusChangedDateTime), | ||
| })), | ||
| checkpoint: { last_status_changed: envelopes.at(-1)?.statusChangedDateTime ?? since }, | ||
| }; | ||
| } | ||
|
|
||
| async execute() { | ||
| return { success: false, error: "no actions" }; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.