@leadbay/mcp 0.21.3 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.22.0 — 2026-06-20
4
+
5
+ Headless artifact SDK + two always-on read tools, so the user's Claude (cowork) can build interactive HTML artifacts that call Leadbay.
6
+
7
+ - **New package `@leadbay/components`** — vanilla view-models (`lb.field` / `action` / `resource` / `list`, plus domain helpers `outreach`, `note`, `like`/`dislike`, `leadHistory`, `leadProfile`, `callList`, `enrichment`, `teamActivity`) that own a control's data lifecycle: populate-from-API, value/loading/error, validation, polling, request sequencing, a 30s call timeout, and the `report_outreach` verification + `_triggered_by` footguns. The agent owns 100% of markup; the library renders nothing. The build emits a minified runtime into core; a `components:check` drift guard fails CI if the committed runtime goes stale.
8
+ - **`leadbay_artifact_kit`** (always-on read) — returns the runtime string + a usage guide the agent reads to assemble an artifact. Lives in `tools/` (granular-shaped) so a kit fetch carries no `_triggered_by` mandate.
9
+ - **`leadbay_team_activity`** (always-on read) — per-rep activity leaderboard + activity trend for a look-back window, wrapping `/kpi/users` + `/kpi/trends` (the web Dashboard-Manager data). Admins get the whole org; non-admins are scoped to themselves by the backend.
10
+
11
+ Validated by agent-dogfood tests: an independent agent builds a working cold-call sheet + a manager dashboard from the usage guide alone, and jsdom asserts the real tool calls fire with the right args. (Core 0.8.4, components 0.3.1.)
12
+
3
13
  ## 0.21.3 — 2026-06-19
4
14
 
5
15
  Kills the 401 startup "reconnect Leadbay" hallucination — the assistant told users to re-authenticate on a connection that actually worked (product#3761). Fixed at every layer that produced or surfaced the spurious 401:
package/dist/bin.js CHANGED
@@ -5163,6 +5163,7 @@ var init_composite_file_names = __esm({
5163
5163
  "leadbay_resolve_import_rows",
5164
5164
  "leadbay_scan_portfolio_signals",
5165
5165
  "leadbay_seed_candidates",
5166
+ "leadbay_team_activity",
5166
5167
  "leadbay_tour_plan"
5167
5168
  ]);
5168
5169
  }
@@ -5467,7 +5468,7 @@ var init_notifications = __esm({
5467
5468
  });
5468
5469
 
5469
5470
  // ../core/dist/tool-descriptions.generated.js
5470
- var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_pin_contact, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_contact, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
5471
+ var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_artifact_kit, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_pin_contact, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_contact, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_team_activity, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
5471
5472
  var init_tool_descriptions_generated = __esm({
5472
5473
  "../core/dist/tool-descriptions.generated.js"() {
5473
5474
  "use strict";
@@ -5863,6 +5864,53 @@ WHEN TO USE: after leadbay_refine_prompt returns \`status='clarification_pending
5863
5864
  WHEN NOT TO USE: to set a brand-new prompt \u2014 use leadbay_refine_prompt.
5864
5865
 
5865
5866
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
5867
+ `;
5868
+ leadbay_artifact_kit = `## WHEN TO USE
5869
+
5870
+ Trigger phrases: "build me a dashboard", "build a call sheet", "interactive artifact", "make a page with buttons", "build an artifact to work my leads", "interactive lead triage board", "a page with buttons that log my calls".
5871
+
5872
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
5873
+
5874
+ Do NOT use for: "show me today's leads" \u2192 \`leadbay_pull_leads\`; "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "log that I emailed" \u2192 \`leadbay_report_outreach\`.
5875
+
5876
+ Prefer when: the user wants a clickable/interactive HTML artifact whose controls call Leadbay tools \u2014 not a one-off data answer
5877
+
5878
+ Examples that SHOULD invoke this tool:
5879
+ - "Build me an interactive call sheet for these leads."
5880
+ - "Make an artifact with buttons to log call outcomes per lead."
5881
+ - "I want a clickable lead-triage board I can work down."
5882
+
5883
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
5884
+ - "Show me today's leads."
5885
+ - "Which leads should I follow up with this week?"
5886
+ - "Log that I emailed Acme's CTO."
5887
+
5888
+ ## RENDER (quick)
5889
+
5890
+ Do NOT render this tool's result as prose or a table. It returns
5891
+ \`runtime\` (a JS string) + \`usage_guide\` (the recipe) + \`version\`. Inline
5892
+ \`runtime\` as ONE \`<script>\` (it self-attaches \`window.LeadbayArtifacts\`),
5893
+ build view-models with \`lb.field/action/resource/list\` + the domain helpers,
5894
+ bind them to your own HTML, and pass the tools you call as the artifact's
5895
+ \`mcp_tools\`. READ \`usage_guide\` first. The artifact is the answer.
5896
+
5897
+ ---
5898
+
5899
+ Hands you everything to build an interactive HTML **artifact** the user runs inside their own Claude (cowork): headless **domain view-models** + a markdown usage guide. A view-model owns a control's whole data lifecycle \u2014 populate a dropdown's options from a Leadbay call, hold the value, expose loading/error, validate, encapsulate the API call + business rules. You own 100% of the markup, layout, and style. The library renders nothing.
5900
+
5901
+ Returns \`{ runtime, usage_guide, version }\`:
5902
+ - \`runtime\` \u2014 a minified, zero-dependency vanilla IIFE that self-attaches \`window.LeadbayArtifacts\` (call it \`lb\`). Inline once as \`<script>\`.
5903
+ - \`usage_guide\` \u2014 the full recipe: \`lb.field\` / \`lb.action\` / the \`lb.bind*\` sugar, a copy-paste cold-call call sheet, and the write-call rules. READ IT before building.
5904
+
5905
+ The model \u2014 two layers. Primitives: \`lb.field\` (value + API-populated options), \`lb.action\` (write/submit), \`lb.resource\` (load-on-click / poll-until-done / \`.refresh()\`), \`lb.list\` (paginated rows); each exposes \`.loading/.error/.subscribe\`, and you bind them to YOUR native elements with \`lb.bindSelect/bindValue/bindAction\` (no style imposed). Domain components (pre-wired, footguns baked in): \`lb.campaigns\`, \`lb.outreach\`, \`lb.note\`, \`lb.like/dislike\`, \`lb.leadHistory\`, \`lb.leadProfile\`, \`lb.callList\`, \`lb.enrichment\` (launch + poll), \`lb.teamActivity\` (manager leaderboard + trend). TanStack-Query's headless-view-model separation, vanilla (cowork is inline-only \u2014 no React).
5906
+
5907
+ Canonical uses: a cold-call sheet (\`lb.callList\` + per-row \`lb.outreach\`/\`lb.leadHistory\`); a manager dashboard (\`lb.teamActivity\` \u2192 leaderboard table + Chart.js trend); a live enrichment view (\`lb.enrichment\` \u2192 progress + refresh). Live auto-poll is host-dependent \u2014 always wire a Refresh.
5908
+
5909
+ Write-call footguns (in the guide, repeated because they bite): for \`leadbay_report_outreach\` (status/disposition) the \`args\` MUST include \`verification:{source:"user_confirmed",ref:"\u2026"}\` AND \`_triggered_by:"<the user's request>"\`, or the call is rejected. \`leadbay_add_leads_to_campaign\` needs \`_triggered_by\` too. \`leadbay_add_note\`/\`leadbay_like_lead\`/\`leadbay_dislike_lead\` need only their own args. Snoozing (pushback) and standalone status are advanced-gated \u2014 not callable from a default artifact; use \`report_outreach\`'s \`epilogue_status\` for outcomes.
5910
+
5911
+ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or call sheet that DOES things (not just displays data).
5912
+
5913
+ WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
5866
5914
  `;
5867
5915
  leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Returns the \`bulk_id\`, progress per lead (done/total enrichable contacts), and overall progress. When \`include_contacts=true\` (opt-in), includes each contact's email/phone/job_title/enrichment.done.
5868
5916
 
@@ -8867,6 +8915,50 @@ WHEN TO USE: low-level.
8867
8915
  WHEN NOT TO USE: from agent flow \u2014 use leadbay_refine_prompt, which polls for follow-up clarifications.
8868
8916
 
8869
8917
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
8918
+ `;
8919
+ leadbay_team_activity = `## WHEN TO USE
8920
+
8921
+ Trigger phrases: "how is my team doing", "team activity", "top performers", "rep leaderboard", "manager dashboard", "who's most active this week", "activity by rep".
8922
+
8923
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
8924
+
8925
+ Do NOT use for: "show me leads" \u2192 \`leadbay_pull_leads\`; "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "how is this campaign progressing" \u2192 \`leadbay_campaign_progression\`.
8926
+
8927
+ Prefer when: a manager wants team-wide / per-rep activity aggregates, not a lead list
8928
+
8929
+ Examples that SHOULD invoke this tool:
8930
+ - "How is my team doing this month?"
8931
+ - "Who are my top performers in the last two weeks?"
8932
+ - "Show me activity by rep."
8933
+
8934
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
8935
+ - "Show me today's leads."
8936
+ - "How is the Q3 Outbound campaign progressing?"
8937
+ - "Which leads should I follow up with?"
8938
+
8939
+ ## RENDER (quick)
8940
+
8941
+ Per-rep leaderboard table (sort by total_activities desc) + an activity
8942
+ time-series. Columns worth showing: name, total_activities, notes,
8943
+ contacts_added, meetings_or_interest, lost. Feeds a manager dashboard
8944
+ artifact (table + Chart.js line of \`trend\`).
8945
+
8946
+ ---
8947
+
8948
+ Manager / team KPIs \u2014 the data behind the web app's Dashboard-Manager screen, exposed so an agent (or an artifact) can render a team dashboard.
8949
+
8950
+ Returns \`{ range, reps, trend }\`:
8951
+ - \`range\` \u2014 \`{ from, to, periodicity }\` (the resolved look-back window).
8952
+ - \`reps\` \u2014 per-rep activity, sorted by \`total_activities\` desc (the leaderboard). Each: \`{ user_id, name, email, total_activities, likes, saves, website_clicks, exported, profile_views, contacts_added, contacts_purchased, notes, meetings_or_interest, could_not_reach, lost, still_chasing }\`. The \`epilogue_*\`-derived fields (\`meetings_or_interest\`, \`lost\`, \u2026) are the outcome signal.
8953
+ - \`trend\` \u2014 \`[{ date, count }]\` activity over time for the window (feed a chart).
8954
+
8955
+ Params: \`weeks\` (default 4) OR explicit \`from\`/\`to\` (YYYY-MM-DD); \`periodicity\` (DAILY|WEEKLY, default WEEKLY); \`user_ids\` (omit for the whole team). Admins get the whole org; non-admins are scoped to themselves by the backend.
8956
+
8957
+ Wraps \`GET /kpi/users\` + \`GET /kpi/trends\`. Quota / remaining capacity for the org lives on leadbay_account_status (don't duplicate it here).
8958
+
8959
+ WHEN TO USE: a manager wants to see team-wide or per-rep activity (leaderboard, who's active, activity trend).
8960
+
8961
+ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_followups) or one campaign's funnel (leadbay_campaign_progression).
8870
8962
  `;
8871
8963
  leadbay_tour_plan = `## WHEN TO USE
8872
8964
 
@@ -15652,7 +15744,13 @@ var init_research_lead_by_id = __esm({
15652
15744
  },
15653
15745
  agent_memory: { type: "object" }
15654
15746
  },
15655
- additionalProperties: false
15747
+ // _meta is an open envelope: the MCP server layer injects
15748
+ // additional keys (latency_ms, notifications, update_available)
15749
+ // after the composite returns. Mirror the sibling tools
15750
+ // (leadbay_pull_leads et al.) and leave _meta open so those
15751
+ // server-side additions don't fail outputSchema validation
15752
+ // with -32602 (data/_meta must NOT have additional properties).
15753
+ additionalProperties: true
15656
15754
  }
15657
15755
  },
15658
15756
  required: [
@@ -21405,6 +21503,84 @@ var init_report_friction = __esm({
21405
21503
  }
21406
21504
  });
21407
21505
 
21506
+ // ../core/dist/composite/team-activity.js
21507
+ function mapRep(r) {
21508
+ const n = (v) => v ?? 0;
21509
+ return {
21510
+ user_id: r.user?.id,
21511
+ name: r.user?.name ?? null,
21512
+ email: r.user?.email ?? null,
21513
+ total_activities: n(r.total_activities),
21514
+ likes: n(r.likes),
21515
+ saves: n(r.saves),
21516
+ website_clicks: n(r.website),
21517
+ exported: n(r.exported),
21518
+ profile_views: n(r.lead_profile_views),
21519
+ contacts_added: n(r.create_lead_contact),
21520
+ contacts_purchased: n(r.purchase_lead_contact),
21521
+ notes: n(r.create_lead_note),
21522
+ // Epilogue outcomes logged in the window — the "deals" signal.
21523
+ meetings_or_interest: n(r.epilogue_interest_validated_or_meeting_planed),
21524
+ could_not_reach: n(r.epilogue_could_not_reach_still_trying),
21525
+ lost: n(r.epilogue_not_interested_lost),
21526
+ still_chasing: n(r.epilogue_still_chasing)
21527
+ };
21528
+ }
21529
+ var DAY_MS, ymd, teamActivity;
21530
+ var init_team_activity = __esm({
21531
+ "../core/dist/composite/team-activity.js"() {
21532
+ "use strict";
21533
+ init_tool_descriptions_generated();
21534
+ DAY_MS = 864e5;
21535
+ ymd = (d) => d.toISOString().slice(0, 10);
21536
+ teamActivity = {
21537
+ name: "leadbay_team_activity",
21538
+ annotations: {
21539
+ title: "Team activity + per-rep KPIs",
21540
+ readOnlyHint: true,
21541
+ destructiveHint: false,
21542
+ idempotentHint: true,
21543
+ openWorldHint: true
21544
+ },
21545
+ description: leadbay_team_activity,
21546
+ write: false,
21547
+ inputSchema: {
21548
+ type: "object",
21549
+ properties: {
21550
+ weeks: { type: "number", description: "Look-back window in weeks (default 4). Ignored if from/to given." },
21551
+ from: { type: "string", description: "ISO date YYYY-MM-DD (start). Overrides weeks." },
21552
+ to: { type: "string", description: "ISO date YYYY-MM-DD (end). Overrides weeks." },
21553
+ periodicity: { type: "string", enum: ["DAILY", "WEEKLY"], description: "Trend bucketing (default WEEKLY)." },
21554
+ user_ids: {
21555
+ type: "array",
21556
+ items: { type: "string" },
21557
+ description: "Specific rep user-ids; omit for the whole team."
21558
+ }
21559
+ },
21560
+ additionalProperties: false
21561
+ },
21562
+ execute: async (client, params, _ctx) => {
21563
+ const periodicity = params.periodicity ?? "WEEKLY";
21564
+ const to = params.to ?? ymd(/* @__PURE__ */ new Date());
21565
+ const from = params.from ?? ymd(new Date(Date.parse(to) - Math.max(1, params.weeks ?? 4) * 7 * DAY_MS));
21566
+ const ids = params.user_ids && params.user_ids.length > 0 ? params.user_ids.join(",") : "ALL";
21567
+ const qs = `from=${from}&to=${to}&periodicity=${periodicity}&user_ids=${encodeURIComponent(ids)}`;
21568
+ const [usersRaw, trendRaw] = await Promise.all([
21569
+ client.request("GET", `/kpi/users?${qs}`),
21570
+ client.request("GET", `/kpi/trends?${qs}`)
21571
+ ]);
21572
+ const reps = (usersRaw ?? []).map(mapRep).sort((a, b) => b.total_activities - a.total_activities);
21573
+ return {
21574
+ range: { from, to, periodicity },
21575
+ reps,
21576
+ trend: (trendRaw ?? []).map((t) => ({ date: t.date, count: t.count ?? 0 })),
21577
+ _meta: { region: client.region }
21578
+ };
21579
+ }
21580
+ };
21581
+ }
21582
+ });
21583
+
21408
21584
  // ../core/dist/tools/send-feedback.js
21409
21585
  var MESSAGE_MAX, sendFeedback;
21410
21586
  var init_send_feedback = __esm({
@@ -21489,6 +21665,55 @@ var init_send_feedback = __esm({
21489
21665
  }
21490
21666
  });
21491
21667
 
21668
+ // ../core/dist/artifact-runtime.generated.js
21669
+ var ARTIFACT_KIT_VERSION, ARTIFACT_RUNTIME, ARTIFACT_USAGE_GUIDE;
21670
+ var init_artifact_runtime_generated = __esm({
21671
+ "../core/dist/artifact-runtime.generated.js"() {
21672
+ "use strict";
21673
+ ARTIFACT_KIT_VERSION = "0.3.1";
21674
+ ARTIFACT_RUNTIME = '"use strict";(()=>{var _=Object.defineProperty;var k=(e,t,n)=>t in e?_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var o=(e,t,n)=>k(e,typeof t!="symbol"?t+"":t,n);var T="0.3.1",c=class extends Error{constructor(n,i={}){super(n);o(this,"code");o(this,"raw");this.name="LbError",this.code=i.code,this.raw=i.raw}},p=null,f=3e4;function E(){let e=globalThis.cowork;return e&&typeof e.callMcpTool=="function"?(t,n)=>e.callMcpTool(t,n):null}function v(e){if(e&&typeof e=="object"&&"content"in e){let t=e.content;if(Array.isArray(t)&&t[0]&&typeof t[0].text=="string")return t[0].text}return null}function y(e){if(!e||typeof e!="object")return e;let t=e;if(t.isError)throw new c(v(e)??"tool call failed",{raw:e});if("structuredContent"in t&&t.structuredContent!=null)return t.structuredContent;let n=v(e);if(n!=null)try{return JSON.parse(n)}catch{return n}return e}function L(e){return e instanceof Error?e.message:String(e)}function m(e){let t=e instanceof c?e.code:void 0;return{message:L(e),unavailable:t==="unavailable",code:t}}function S(e={}){p=e.call??null,f=e.timeoutMs??3e4}async function w(e,t){if(!f||f<=0)return e;let n,i=new Promise((r,s)=>{n=setTimeout(()=>s(new c(`"${t}" timed out after ${f}ms`,{code:"timeout"})),f)});try{return await Promise.race([e,i])}finally{n&&clearTimeout(n)}}async function a(e,t={}){if(p)return y(await w(Promise.resolve(p(e,t)),e));let n=E();if(!n)throw new c("Leadbay bridge unavailable (window.cowork absent)",{code:"unavailable"});return y(await w(Promise.resolve(n(e,t)),e))}var d=class{constructor(){o(this,"subs",new Set)}subscribe(t){return this.subs.add(t),t(this),()=>this.subs.delete(t)}emit(){for(let t of this.subs)t(this)}};function A(e){return Array.isArray(e)?e.map(t=>t&&typeof t=="object"?t:{value:t,label:String(t)}):[]}var h=class extends d{constructor(n={}){super();o(this,"kind");o(this,"value");o(this,"options",[]);o(this,"loading",!1);o(this,"error",null);o(this,"ready",!1);o(this,"cfg");o(this,"depUnsubs",[]);o(this,"seq",0);this.cfg=n,this.kind=n.kind,this.value=n.value??"";for(let i of n.dependsOn??[]){let r=i.value;this.depUnsubs.push(i.subscribe(()=>{i.value!==r&&(r=i.value,this.cfg.load&&this.load())}))}n.load&&(n.autoLoad??!0)&&this.load()}async load(){if(!this.cfg.load)return;let n=++this.seq;this.loading=!0,this.error=null,this.emit();try{let i=await this.cfg.load();if(n!==this.seq)return;this.options=this.cfg.options?this.cfg.options(i):A(i),this.ready=!0;let r=this.value==null?"":String(this.value);this.options.length&&(r===""||!this.options.some(s=>String(s.value)===r))&&(this.value=this.options[0].value)}catch(i){if(n!==this.seq)return;this.options=[],this.error=m(i)}finally{n===this.seq&&(this.loading=!1,this.emit())}}setValue(n){this.value=n;let i=this.validate();this.error=i?{message:i,unavailable:!1}:null,this.emit()}validate(){return this.cfg.validate?this.cfg.validate(this.value):null}get valid(){return this.validate()==null}reset(){this.value=this.cfg.value??"",this.error=null,this.emit()}dispose(){for(let n of this.depUnsubs)n();this.depUnsubs=[]}},l=class extends d{constructor(n){super();o(this,"loading",!1);o(this,"error",null);o(this,"lastResult",null);o(this,"cfg");this.cfg=n}async run(){if(this.loading)return;for(let i of this.cfg.fields??[]){let r=i.validate();if(r!=null){this.error={message:r,unavailable:!1},this.emit();return}}if(this.cfg.confirm&&typeof globalThis.confirm=="function"&&!globalThis.confirm(this.cfg.confirm))return;this.loading=!0,this.error=null,this.emit();let n;try{let i=typeof this.cfg.args=="function"?this.cfg.args():this.cfg.args??{};n=await a(this.cfg.tool,i)}catch(i){this.error=m(i),this.loading=!1,this.emit(),this.cfg.onError?.(this.error);return}return this.lastResult=n,this.loading=!1,this.emit(),this.cfg.onSuccess?.(n),n}reset(){this.error=null,this.lastResult=null,this.emit()}},u=class extends d{constructor(n){super();o(this,"data",null);o(this,"loading",!1);o(this,"refreshing",!1);o(this,"error",null);o(this,"done",!1);o(this,"cfg");o(this,"timer",null);o(this,"seq",0);this.cfg=n,(n.autoLoad??!0)&&this.load()}async load(){this.clearTimer();let n=++this.seq;this.data==null?this.loading=!0:this.refreshing=!0,this.error=null,this.emit();try{let r=await this.cfg.load();if(n!==this.seq)return;this.data=r,this.done=this.cfg.until?this.cfg.until(r):!0,this.cfg.pollEvery&&!this.done&&(this.timer=setTimeout(()=>void this.load(),this.cfg.pollEvery))}catch(r){if(n!==this.seq)return;this.error=m(r)}finally{n===this.seq&&(this.loading=!1,this.refreshing=!1,this.emit())}}refresh(){return this.load()}stop(){this.clearTimer()}clearTimer(){this.timer&&(clearTimeout(this.timer),this.timer=null)}},b=class extends d{constructor(n){super();o(this,"items",[]);o(this,"page",0);o(this,"pageSize");o(this,"total",0);o(this,"loading",!1);o(this,"error",null);o(this,"cfg");o(this,"seq",0);this.cfg=n,this.pageSize=n.pageSize??20,(n.autoLoad??!0)&&this.loadPage(0)}async loadPage(n){let i=++this.seq;this.loading=!0,this.error=null,this.emit();try{let r=await this.cfg.load({page:n,pageSize:this.pageSize});if(i!==this.seq)return;this.items=r.items??[],this.total=r.total??this.items.length,this.page=n}catch(r){if(i!==this.seq)return;this.error=m(r)}finally{i===this.seq&&(this.loading=!1,this.emit())}}next(){return this.loadPage(this.page+1)}prev(){return this.loadPage(Math.max(0,this.page-1))}get hasMore(){return(this.page+1)*this.pageSize<this.total}};function x(e,t){let n=t.error?.unavailable?"unavailable":t.loading?"loading":t.error?"error":"ready";e.setAttribute("data-lb-state",n),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")}function C(e,t){let n=()=>t.setValue(e.value);e.addEventListener("change",n);let i=t.subscribe(()=>{x(e,t),e.disabled=t.loading,e.innerHTML="";for(let r of t.options){let s=document.createElement("option");s.value=String(r.value),s.textContent=r.label,e.appendChild(s)}e.value=t.value==null?"":String(t.value)});return()=>{e.removeEventListener("change",n),i()}}function I(e,t){let n=e.type==="checkbox",i=e.tagName==="SELECT"?"change":"input",r=()=>t.setValue(n?e.checked:e.value);e.addEventListener(i,r);let s=t.subscribe(()=>{if(n)e.checked=!!t.value;else{let g=t.value==null?"":String(t.value);e.value!==g&&(e.value=g)}e.setAttribute("data-lb-state",t.error?"error":"ready"),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")});return()=>{e.removeEventListener(i,r),s()}}function R(e,t){let n=r=>{r.preventDefault(),t.run()};e.addEventListener("click",n);let i=t.subscribe(()=>{let r=t.error?.unavailable?"unavailable":t.loading?"loading":t.error?"error":t.lastResult!=null?"success":"idle";e.setAttribute("data-lb-state",r),"disabled"in e&&(e.disabled=t.loading),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")});return()=>{e.removeEventListener("click",n),i()}}var O=["STILL_CHASING","COULD_NOT_REACH_STILL_TRYING","INTEREST_VALIDATED_OR_MEETING_PLANED","NOT_INTERESTED_LOST"];function P(e){return new h({kind:"select",load:()=>a("leadbay_list_campaigns",{_triggered_by:e}),options:t=>(t?.campaigns??[]).map(i=>{let r=i?.campaign??i;return r?.id?{value:r.id,label:r.name??r.ai_generated_name??String(r.id)}:null}).filter(i=>i!=null)})}function M(e){return new l({tool:"leadbay_report_outreach",fields:e.note?[e.note]:[],args:()=>({lead_id:e.leadId,...e.status?{epilogue_status:e.status.value}:{},note:e.note?e.note.value:"",verification:{source:"user_confirmed",ref:e.ref??"logged from artifact"},_triggered_by:e.ask})})}function F(e){return new l({tool:"leadbay_add_note",fields:[e.note],args:()=>({leadId:e.leadId,note:e.note.value})})}function q(e){return new l({tool:"leadbay_like_lead",args:{lead_id:e}})}function H(e){return new l({tool:"leadbay_dislike_lead",args:{lead_id:e}})}function z(e,t){return new u({autoLoad:!1,load:()=>a("leadbay_account_history",{leadId:e,_triggered_by:t})})}function N(e,t){return new u({autoLoad:!1,load:()=>a("leadbay_research_lead_by_id",{leadId:e,_triggered_by:t})})}function U(e){let t=null;return new u({pollEvery:e.pollEvery??4e3,until:n=>!!n?.all_done,load:async()=>{if(!t){let n=await a("leadbay_enrich_titles",{...e.leadIds?{leadIds:e.leadIds}:{},titles:e.titles,email:e.email??!0,phone:e.phone??!1,_triggered_by:e.ask});if(t=n?.bulk_id??null,!t)return{all_done:!0,no_job:!0,mode:n?.mode,preview:n?.preview}}return a("leadbay_bulk_enrich_status",{bulk_id:t,_triggered_by:e.ask})}})}function D(e){let t=e.source??"followups";return new b({pageSize:e.pageSize??20,load:async({page:n,pageSize:i})=>{let s=t==="campaign"?await a("leadbay_campaign_call_sheet",{campaign_id:e.campaignId,page:n,count:i,_triggered_by:e.ask}):await a("leadbay_pull_followups",{page:n,count:i,...e.city?{city:e.city}:{},_triggered_by:e.ask}),g=s.leads??s.items??[];return{items:g,total:s.total_leads??s.pagination?.total??g.length}}})}function V(e){return new u({load:()=>a("leadbay_team_activity",{weeks:e.weeks??4,_triggered_by:e.ask})})}var j={VERSION:T,configure:S,call:a,field:e=>new h(e),action:e=>new l(e),resource:e=>new u(e),list:e=>new b(e),bindSelect:C,bindValue:I,bindAction:R,campaigns:P,outreach:M,note:F,like:q,dislike:H,leadHistory:z,leadProfile:N,enrichment:U,callList:D,teamActivity:V,EPILOGUE_STATUSES:O};typeof globalThis<"u"&&(globalThis.LeadbayArtifacts=j);})();';
21675
+ ARTIFACT_USAGE_GUIDE = '# Leadbay Artifact Kit \u2014 headless domain components\n\nYou are building a single-file HTML **artifact** the user runs inside cowork. This\nkit gives you **headless view-models** that own a control\'s whole data lifecycle \u2014\nload/populate from a Leadbay call, hold value/state, poll, validate, and\nencapsulate the API call + business rules. **You own 100% of markup/layout/style.**\nThe library renders nothing. Inline the runtime once as a `<script>`; it exposes\none global `window.LeadbayArtifacts` (call it `lb`). Vanilla, no React, no build.\n\nPass every tool you use as the artifact\'s `mcp_tools` so the host permits it.\n\n## Two layers\n\n**Primitives** (generic):\n- `lb.field({ load, options, value, validate, dependsOn })` \u2014 a value + optionally\n API-populated options. `.value/.setValue/.options/.loading/.error/.valid/.subscribe`.\n- `lb.action({ tool, args, fields, confirm, onSuccess, onError })` \u2014 a write/submit.\n `.run()/.loading/.error/.lastResult/.subscribe`.\n- `lb.resource({ load, pollEvery?, until?, autoLoad? })` \u2014 one read that may change:\n load-on-click or poll-until-`until`. `.data/.loading/.refreshing/.error/.done/.load()/.refresh()/.stop()/.subscribe`.\n- `lb.list({ load, pageSize })` \u2014 paginated rows. `.items/.page/.total/.loading/.loadPage(n)/.next()/.prev()/.hasMore/.subscribe`.\n\n`.error` is `{ message, unavailable } | null`. `subscribe(cb)` fires immediately\nthen on every change \u2014 render your own DOM from it.\n\n**Domain components** (pre-wired \u2014 bake in the tool name, arg shape, and footguns):\n\n| Call | Returns | For |\n|---|---|---|\n| `lb.campaigns(ask)` | field | a campaign `<select>`, options from `leadbay_list_campaigns` |\n| `lb.outreach({leadId, ask, status?, note?})` | action | log a call \u2192 `report_outreach` (verification + `_triggered_by` baked in) |\n| `lb.note({leadId, note})` | action | add a note \u2192 `add_note` |\n| `lb.like(leadId)` / `lb.dislike(leadId)` | action | taste signal |\n| `lb.leadHistory(leadId, ask)` | resource (lazy) | notes + activities + engagement \u2192 `account_history` |\n| `lb.leadProfile(leadId, ask)` | resource (lazy) | full lead profile \u2192 `research_lead_by_id` |\n| `lb.callList({source:\'followups\'\\|\'campaign\', campaignId?, city?, ask})` | list | a cold-call list (Monitor or a campaign) |\n| `lb.enrichment({leadIds, titles, ask, pollEvery?})` | resource (polling) | launch + watch contact enrichment |\n| `lb.teamActivity({weeks, ask})` | resource | manager leaderboard + activity trend \u2192 `leadbay_team_activity` |\n\n`lb.EPILOGUE_STATUSES` = the 4 disposition values\n(`STILL_CHASING`, `COULD_NOT_REACH_STILL_TRYING`, `INTEREST_VALIDATED_OR_MEETING_PLANED`, `NOT_INTERESTED_LOST`).\n\n**Binding sugar** (optional; binds a view-model to YOUR native element, no style):\n`lb.bindSelect(selectEl, field)` (populates options + value), `lb.bindValue(inputEl, field)`,\n`lb.bindAction(buttonEl, action)`. They set `data-lb-state`\n(`ready|loading|error|success|unavailable`) + `data-lb-error` on your element as\nstyling hooks. For lists/resources, use `.subscribe()` and render yourself.\n\n`ask` is the user\'s request this artifact serves \u2014 it becomes `_triggered_by`.\n\n## Recipe: cold-call sheet (one row per lead)\n\n```js\nconst lb = window.LeadbayArtifacts; lb.configure();\nconst ASK = "<the user\'s request>";\n\nconst list = lb.callList({ source: "campaign", campaignId: CID, ask: ASK });\nlist.subscribe((l) => renderRows(l.items, l.loading)); // your render\n\n// per lead row (call when you build a row):\nfunction wireRow(lead, els) {\n const status = lb.field({ value: "STILL_CHASING" }); // static-enum <select>\n const note = lb.field({ validate: (v) => (v && v.trim() ? null : "Add a note") });\n lb.bindValue(els.status, status);\n lb.bindValue(els.note, note);\n lb.bindAction(els.log, lb.outreach({ leadId: lead.id, ask: ASK, status, note }));\n lb.bindAction(els.like, lb.like(lead.id));\n\n const history = lb.leadHistory(lead.id, ASK); // lazy\n history.subscribe((h) => renderHistory(els.history, h));\n els.expand.onclick = () => history.load(); // load on click\n}\n```\n\n## Recipe: manager dashboard\n\n```js\nconst team = lb.teamActivity({ weeks: 4, ask: ASK });\nteam.subscribe((t) => {\n if (t.loading) showSpinner();\n if (t.data) {\n renderLeaderboard(t.data.reps); // sorted by total_activities; cols: name, notes, meetings_or_interest, lost\u2026\n renderTrendChart(t.data.trend); // [{date,count}] \u2192 Chart.js (allowed from CDN)\n }\n});\nrefreshBtn.onclick = () => team.refresh();\n```\n\n## Recipe: live enrichment\n\n```js\nconst job = lb.enrichment({ leadIds: [LEAD], titles: ["CEO", "VP Sales"], ask: ASK });\njob.subscribe((j) => {\n const p = j.data && j.data.overall_progress; // {done,total,done_ratio}\n renderBar(p);\n if (j.done) renderContacts(j.data.leads); // enriched contacts\n});\nrefreshBtn.onclick = () => job.refresh();\n```\n\n## Write-call rules\n\nThe domain factories handle these for you. If you hand-roll an action:\n`leadbay_report_outreach` args MUST include `verification:{source:"user_confirmed", ref}`\nAND `_triggered_by`; `leadbay_add_leads_to_campaign` needs `_triggered_by`;\n`add_note`/`like_lead`/`dislike_lead` take only their own args. `epilogue_status` is\none of `lb.EPILOGUE_STATUSES`. Snoozing (pushback) and org WON/LOST status are\nadvanced-gated \u2014 not callable from a default artifact; use the epilogue values.\n\n## Degradation + live updates\n\nIf the host bridge is absent, a view-model\'s `.error` is set with `.error.unavailable\n=== true` (bind helpers set `data-lb-state="unavailable"`) \u2014 nothing throws. Every\ncall also has a **30s timeout** (configurable via `lb.configure({ timeoutMs })`): a\nhost call that never settles becomes `.error` with `code:"timeout"`, so a control is\nnever stuck loading forever \u2014 always render the `.error` branch so the user can retry.\nAuto-poll (`pollEvery`) depends on the cowork host serving FRESH reads; `.refresh()`\nis the guaranteed manual path \u2014 always wire a Refresh control for polling resources.';
21676
+ }
21677
+ });
21678
+
21679
+ // ../core/dist/tools/artifact-kit.js
21680
+ var artifactKit;
21681
+ var init_artifact_kit = __esm({
21682
+ "../core/dist/tools/artifact-kit.js"() {
21683
+ "use strict";
21684
+ init_tool_descriptions_generated();
21685
+ init_artifact_runtime_generated();
21686
+ artifactKit = {
21687
+ name: "leadbay_artifact_kit",
21688
+ annotations: {
21689
+ title: "Artifact component kit",
21690
+ readOnlyHint: true,
21691
+ destructiveHint: false,
21692
+ idempotentHint: true,
21693
+ openWorldHint: false
21694
+ },
21695
+ description: leadbay_artifact_kit,
21696
+ write: false,
21697
+ inputSchema: {
21698
+ type: "object",
21699
+ properties: {},
21700
+ additionalProperties: false
21701
+ },
21702
+ // No outputSchema by design: the return is three opaque strings. Declaring a
21703
+ // schema would enroll the tool in the output-schema-conformance drift-catcher
21704
+ // (an existing test file we don't modify). The server still emits the
21705
+ // plain-object return as structuredContent: { runtime, usage_guide, version }.
21706
+ execute: async (_client, _params, _ctx) => {
21707
+ return {
21708
+ version: ARTIFACT_KIT_VERSION,
21709
+ runtime: ARTIFACT_RUNTIME,
21710
+ usage_guide: ARTIFACT_USAGE_GUIDE
21711
+ };
21712
+ }
21713
+ };
21714
+ }
21715
+ });
21716
+
21492
21717
  // ../core/dist/index.js
21493
21718
  var dist_exports = {};
21494
21719
  __export(dist_exports, {
@@ -21519,6 +21744,7 @@ __export(dist_exports, {
21519
21744
  answerClarification: () => answerClarification,
21520
21745
  appendEntry: () => appendEntry,
21521
21746
  appendTombstone: () => appendTombstone,
21747
+ artifactKit: () => artifactKit,
21522
21748
  assertSafeInsight: () => assertSafeInsight,
21523
21749
  bulkEnrichStatus: () => bulkEnrichStatus,
21524
21750
  bulkQualifyLeads: () => bulkQualifyLeads,
@@ -21624,6 +21850,7 @@ __export(dist_exports, {
21624
21850
  setEpilogueStatus: () => setEpilogueStatus,
21625
21851
  setPushback: () => setPushback,
21626
21852
  setUserPrompt: () => setUserPrompt,
21853
+ teamActivity: () => teamActivity,
21627
21854
  toInboxEntry: () => toInboxEntry,
21628
21855
  tools: () => tools,
21629
21856
  tourPlan: () => tourPlan,
@@ -21731,7 +21958,9 @@ var init_dist = __esm({
21731
21958
  init_answer_clarification();
21732
21959
  init_report_outreach();
21733
21960
  init_report_friction();
21961
+ init_team_activity();
21734
21962
  init_send_feedback();
21963
+ init_artifact_kit();
21735
21964
  init_bulk_store();
21736
21965
  agentMemoryTools = [
21737
21966
  agentMemoryRecall,
@@ -21816,6 +22045,11 @@ var init_dist = __esm({
21816
22045
  // answer to "which of my leads have signal X" that previously forced a
21817
22046
  // per-lead research_lead_by_id loop (issue #3704).
21818
22047
  scanPortfolioSignals,
22048
+ // Team activity — manager-facing per-rep leaderboard + activity trend (wraps
22049
+ // /kpi/users + /kpi/trends, the web Dashboard-Manager surface). Read-only;
22050
+ // always exposed so a manager artifact can render team KPIs. Non-admins are
22051
+ // scoped to themselves by the backend.
22052
+ teamActivity,
21819
22053
  recallOrderedTitles,
21820
22054
  accountStatus,
21821
22055
  bulkEnrichStatus,
@@ -21854,7 +22088,13 @@ var init_dist = __esm({
21854
22088
  // every tool response regardless of write gating; without ack the agent
21855
22089
  // sees the same entries on every call forever. Pairing the surfacing
21856
22090
  // channel with the clearing tool is non-optional.
21857
- acknowledgeNotification
22091
+ acknowledgeNotification,
22092
+ // Artifact kit — ALWAYS exposed, read-only. Returns the headless
22093
+ // @leadbay/components runtime + usage guide the agent uses to BUILD an
22094
+ // interactive HTML artifact (cold-call sheet, lead-triage board) whose
22095
+ // buttons call Leadbay writes. No backend call; granular-shaped (lives in
22096
+ // tools/) so it carries no _triggered_by mandate for a kit fetch.
22097
+ artifactKit
21858
22098
  ];
21859
22099
  compositeWriteTools = [
21860
22100
  bulkQualifyLeads,
@@ -26184,7 +26424,7 @@ var OAUTH_BASE_URLS = {
26184
26424
  fr: "https://staging.api.leadbay.app"
26185
26425
  }
26186
26426
  };
26187
- var VERSION = "0.21.3";
26427
+ var VERSION = "0.22.0";
26188
26428
  var HELP = `
26189
26429
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
26190
26430
 
@@ -6333,6 +6333,7 @@ var COMPOSITE_FILE_TOOL_NAMES = /* @__PURE__ */ new Set([
6333
6333
  "leadbay_resolve_import_rows",
6334
6334
  "leadbay_scan_portfolio_signals",
6335
6335
  "leadbay_seed_candidates",
6336
+ "leadbay_team_activity",
6336
6337
  "leadbay_tour_plan"
6337
6338
  ]);
6338
6339
 
@@ -6733,6 +6734,53 @@ WHEN NOT TO USE: to set a brand-new prompt \u2014 use leadbay_refine_prompt.
6733
6734
 
6734
6735
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
6735
6736
  `;
6737
+ var leadbay_artifact_kit = `## WHEN TO USE
6738
+
6739
+ Trigger phrases: "build me a dashboard", "build a call sheet", "interactive artifact", "make a page with buttons", "build an artifact to work my leads", "interactive lead triage board", "a page with buttons that log my calls".
6740
+
6741
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
6742
+
6743
+ Do NOT use for: "show me today's leads" \u2192 \`leadbay_pull_leads\`; "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "log that I emailed" \u2192 \`leadbay_report_outreach\`.
6744
+
6745
+ Prefer when: the user wants a clickable/interactive HTML artifact whose controls call Leadbay tools \u2014 not a one-off data answer
6746
+
6747
+ Examples that SHOULD invoke this tool:
6748
+ - "Build me an interactive call sheet for these leads."
6749
+ - "Make an artifact with buttons to log call outcomes per lead."
6750
+ - "I want a clickable lead-triage board I can work down."
6751
+
6752
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
6753
+ - "Show me today's leads."
6754
+ - "Which leads should I follow up with this week?"
6755
+ - "Log that I emailed Acme's CTO."
6756
+
6757
+ ## RENDER (quick)
6758
+
6759
+ Do NOT render this tool's result as prose or a table. It returns
6760
+ \`runtime\` (a JS string) + \`usage_guide\` (the recipe) + \`version\`. Inline
6761
+ \`runtime\` as ONE \`<script>\` (it self-attaches \`window.LeadbayArtifacts\`),
6762
+ build view-models with \`lb.field/action/resource/list\` + the domain helpers,
6763
+ bind them to your own HTML, and pass the tools you call as the artifact's
6764
+ \`mcp_tools\`. READ \`usage_guide\` first. The artifact is the answer.
6765
+
6766
+ ---
6767
+
6768
+ Hands you everything to build an interactive HTML **artifact** the user runs inside their own Claude (cowork): headless **domain view-models** + a markdown usage guide. A view-model owns a control's whole data lifecycle \u2014 populate a dropdown's options from a Leadbay call, hold the value, expose loading/error, validate, encapsulate the API call + business rules. You own 100% of the markup, layout, and style. The library renders nothing.
6769
+
6770
+ Returns \`{ runtime, usage_guide, version }\`:
6771
+ - \`runtime\` \u2014 a minified, zero-dependency vanilla IIFE that self-attaches \`window.LeadbayArtifacts\` (call it \`lb\`). Inline once as \`<script>\`.
6772
+ - \`usage_guide\` \u2014 the full recipe: \`lb.field\` / \`lb.action\` / the \`lb.bind*\` sugar, a copy-paste cold-call call sheet, and the write-call rules. READ IT before building.
6773
+
6774
+ The model \u2014 two layers. Primitives: \`lb.field\` (value + API-populated options), \`lb.action\` (write/submit), \`lb.resource\` (load-on-click / poll-until-done / \`.refresh()\`), \`lb.list\` (paginated rows); each exposes \`.loading/.error/.subscribe\`, and you bind them to YOUR native elements with \`lb.bindSelect/bindValue/bindAction\` (no style imposed). Domain components (pre-wired, footguns baked in): \`lb.campaigns\`, \`lb.outreach\`, \`lb.note\`, \`lb.like/dislike\`, \`lb.leadHistory\`, \`lb.leadProfile\`, \`lb.callList\`, \`lb.enrichment\` (launch + poll), \`lb.teamActivity\` (manager leaderboard + trend). TanStack-Query's headless-view-model separation, vanilla (cowork is inline-only \u2014 no React).
6775
+
6776
+ Canonical uses: a cold-call sheet (\`lb.callList\` + per-row \`lb.outreach\`/\`lb.leadHistory\`); a manager dashboard (\`lb.teamActivity\` \u2192 leaderboard table + Chart.js trend); a live enrichment view (\`lb.enrichment\` \u2192 progress + refresh). Live auto-poll is host-dependent \u2014 always wire a Refresh.
6777
+
6778
+ Write-call footguns (in the guide, repeated because they bite): for \`leadbay_report_outreach\` (status/disposition) the \`args\` MUST include \`verification:{source:"user_confirmed",ref:"\u2026"}\` AND \`_triggered_by:"<the user's request>"\`, or the call is rejected. \`leadbay_add_leads_to_campaign\` needs \`_triggered_by\` too. \`leadbay_add_note\`/\`leadbay_like_lead\`/\`leadbay_dislike_lead\` need only their own args. Snoozing (pushback) and standalone status are advanced-gated \u2014 not callable from a default artifact; use \`report_outreach\`'s \`epilogue_status\` for outcomes.
6779
+
6780
+ WHEN TO USE: the user asks for a clickable / interactive artifact, dashboard, or call sheet that DOES things (not just displays data).
6781
+
6782
+ WHEN NOT TO USE: the user wants a plain data answer (route to leadbay_pull_leads / leadbay_pull_followups) or to log a single real outreach you just did (leadbay_report_outreach).
6783
+ `;
6736
6784
  var leadbay_bulk_enrich_status = `Check status + per-lead contacts for a bulk enrichment you previously launched via leadbay_enrich_titles. Returns the \`bulk_id\`, progress per lead (done/total enrichable contacts), and overall progress. When \`include_contacts=true\` (opt-in), includes each contact's email/phone/job_title/enrichment.done.
6737
6785
 
6738
6786
  WHEN TO USE: poll this after leadbay_enrich_titles returns a \`bulk_id\`. Default \`include_contacts=false\` for cheap status polls; set \`include_contacts=true\` once \`all_done\` flips for the final read.
@@ -9737,6 +9785,50 @@ WHEN NOT TO USE: from agent flow \u2014 use leadbay_refine_prompt, which polls f
9737
9785
 
9738
9786
  This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
9739
9787
  `;
9788
+ var leadbay_team_activity = `## WHEN TO USE
9789
+
9790
+ Trigger phrases: "how is my team doing", "team activity", "top performers", "rep leaderboard", "manager dashboard", "who's most active this week", "activity by rep".
9791
+
9792
+ **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
9793
+
9794
+ Do NOT use for: "show me leads" \u2192 \`leadbay_pull_leads\`; "leads I should follow up with" \u2192 \`leadbay_pull_followups\`; "how is this campaign progressing" \u2192 \`leadbay_campaign_progression\`.
9795
+
9796
+ Prefer when: a manager wants team-wide / per-rep activity aggregates, not a lead list
9797
+
9798
+ Examples that SHOULD invoke this tool:
9799
+ - "How is my team doing this month?"
9800
+ - "Who are my top performers in the last two weeks?"
9801
+ - "Show me activity by rep."
9802
+
9803
+ Examples that should NOT invoke this tool (sound similar, route elsewhere):
9804
+ - "Show me today's leads."
9805
+ - "How is the Q3 Outbound campaign progressing?"
9806
+ - "Which leads should I follow up with?"
9807
+
9808
+ ## RENDER (quick)
9809
+
9810
+ Per-rep leaderboard table (sort by total_activities desc) + an activity
9811
+ time-series. Columns worth showing: name, total_activities, notes,
9812
+ contacts_added, meetings_or_interest, lost. Feeds a manager dashboard
9813
+ artifact (table + Chart.js line of \`trend\`).
9814
+
9815
+ ---
9816
+
9817
+ Manager / team KPIs \u2014 the data behind the web app's Dashboard-Manager screen, exposed so an agent (or an artifact) can render a team dashboard.
9818
+
9819
+ Returns \`{ range, reps, trend }\`:
9820
+ - \`range\` \u2014 \`{ from, to, periodicity }\` (the resolved look-back window).
9821
+ - \`reps\` \u2014 per-rep activity, sorted by \`total_activities\` desc (the leaderboard). Each: \`{ user_id, name, email, total_activities, likes, saves, website_clicks, exported, profile_views, contacts_added, contacts_purchased, notes, meetings_or_interest, could_not_reach, lost, still_chasing }\`. The \`epilogue_*\`-derived fields (\`meetings_or_interest\`, \`lost\`, \u2026) are the outcome signal.
9822
+ - \`trend\` \u2014 \`[{ date, count }]\` activity over time for the window (feed a chart).
9823
+
9824
+ Params: \`weeks\` (default 4) OR explicit \`from\`/\`to\` (YYYY-MM-DD); \`periodicity\` (DAILY|WEEKLY, default WEEKLY); \`user_ids\` (omit for the whole team). Admins get the whole org; non-admins are scoped to themselves by the backend.
9825
+
9826
+ Wraps \`GET /kpi/users\` + \`GET /kpi/trends\`. Quota / remaining capacity for the org lives on leadbay_account_status (don't duplicate it here).
9827
+
9828
+ WHEN TO USE: a manager wants to see team-wide or per-rep activity (leaderboard, who's active, activity trend).
9829
+
9830
+ WHEN NOT TO USE: the user wants a lead list (leadbay_pull_leads / leadbay_pull_followups) or one campaign's funnel (leadbay_campaign_progression).
9831
+ `;
9740
9832
  var leadbay_tour_plan = `## WHEN TO USE
9741
9833
 
9742
9834
  Trigger phrases: "visiting <city> in <N> days", "field tour in <city>", "plan a tour in <city>", "who should I meet in <city>", "customers plus prospects in <city>", "tour itinerary".
@@ -15995,7 +16087,13 @@ var researchLeadById = {
15995
16087
  },
15996
16088
  agent_memory: { type: "object" }
15997
16089
  },
15998
- additionalProperties: false
16090
+ // _meta is an open envelope: the MCP server layer injects
16091
+ // additional keys (latency_ms, notifications, update_available)
16092
+ // after the composite returns. Mirror the sibling tools
16093
+ // (leadbay_pull_leads et al.) and leave _meta open so those
16094
+ // server-side additions don't fail outputSchema validation
16095
+ // with -32602 (data/_meta must NOT have additional properties).
16096
+ additionalProperties: true
15999
16097
  }
16000
16098
  },
16001
16099
  required: [
@@ -20960,6 +21058,77 @@ var reportFriction = {
20960
21058
  }
20961
21059
  };
20962
21060
 
21061
+ // ../core/dist/composite/team-activity.js
21062
+ var DAY_MS = 864e5;
21063
+ var ymd = (d) => d.toISOString().slice(0, 10);
21064
+ function mapRep(r) {
21065
+ const n = (v) => v ?? 0;
21066
+ return {
21067
+ user_id: r.user?.id,
21068
+ name: r.user?.name ?? null,
21069
+ email: r.user?.email ?? null,
21070
+ total_activities: n(r.total_activities),
21071
+ likes: n(r.likes),
21072
+ saves: n(r.saves),
21073
+ website_clicks: n(r.website),
21074
+ exported: n(r.exported),
21075
+ profile_views: n(r.lead_profile_views),
21076
+ contacts_added: n(r.create_lead_contact),
21077
+ contacts_purchased: n(r.purchase_lead_contact),
21078
+ notes: n(r.create_lead_note),
21079
+ // Epilogue outcomes logged in the window — the "deals" signal.
21080
+ meetings_or_interest: n(r.epilogue_interest_validated_or_meeting_planed),
21081
+ could_not_reach: n(r.epilogue_could_not_reach_still_trying),
21082
+ lost: n(r.epilogue_not_interested_lost),
21083
+ still_chasing: n(r.epilogue_still_chasing)
21084
+ };
21085
+ }
21086
+ var teamActivity = {
21087
+ name: "leadbay_team_activity",
21088
+ annotations: {
21089
+ title: "Team activity + per-rep KPIs",
21090
+ readOnlyHint: true,
21091
+ destructiveHint: false,
21092
+ idempotentHint: true,
21093
+ openWorldHint: true
21094
+ },
21095
+ description: leadbay_team_activity,
21096
+ write: false,
21097
+ inputSchema: {
21098
+ type: "object",
21099
+ properties: {
21100
+ weeks: { type: "number", description: "Look-back window in weeks (default 4). Ignored if from/to given." },
21101
+ from: { type: "string", description: "ISO date YYYY-MM-DD (start). Overrides weeks." },
21102
+ to: { type: "string", description: "ISO date YYYY-MM-DD (end). Overrides weeks." },
21103
+ periodicity: { type: "string", enum: ["DAILY", "WEEKLY"], description: "Trend bucketing (default WEEKLY)." },
21104
+ user_ids: {
21105
+ type: "array",
21106
+ items: { type: "string" },
21107
+ description: "Specific rep user-ids; omit for the whole team."
21108
+ }
21109
+ },
21110
+ additionalProperties: false
21111
+ },
21112
+ execute: async (client, params, _ctx) => {
21113
+ const periodicity = params.periodicity ?? "WEEKLY";
21114
+ const to = params.to ?? ymd(/* @__PURE__ */ new Date());
21115
+ const from = params.from ?? ymd(new Date(Date.parse(to) - Math.max(1, params.weeks ?? 4) * 7 * DAY_MS));
21116
+ const ids = params.user_ids && params.user_ids.length > 0 ? params.user_ids.join(",") : "ALL";
21117
+ const qs = `from=${from}&to=${to}&periodicity=${periodicity}&user_ids=${encodeURIComponent(ids)}`;
21118
+ const [usersRaw, trendRaw] = await Promise.all([
21119
+ client.request("GET", `/kpi/users?${qs}`),
21120
+ client.request("GET", `/kpi/trends?${qs}`)
21121
+ ]);
21122
+ const reps = (usersRaw ?? []).map(mapRep).sort((a, b) => b.total_activities - a.total_activities);
21123
+ return {
21124
+ range: { from, to, periodicity },
21125
+ reps,
21126
+ trend: (trendRaw ?? []).map((t) => ({ date: t.date, count: t.count ?? 0 })),
21127
+ _meta: { region: client.region }
21128
+ };
21129
+ }
21130
+ };
21131
+
20963
21132
  // ../core/dist/tools/send-feedback.js
20964
21133
  var MESSAGE_MAX = 4e3;
20965
21134
  var sendFeedback = {
@@ -21037,6 +21206,41 @@ var sendFeedback = {
21037
21206
  }
21038
21207
  };
21039
21208
 
21209
+ // ../core/dist/artifact-runtime.generated.js
21210
+ var ARTIFACT_KIT_VERSION = "0.3.1";
21211
+ var ARTIFACT_RUNTIME = '"use strict";(()=>{var _=Object.defineProperty;var k=(e,t,n)=>t in e?_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n;var o=(e,t,n)=>k(e,typeof t!="symbol"?t+"":t,n);var T="0.3.1",c=class extends Error{constructor(n,i={}){super(n);o(this,"code");o(this,"raw");this.name="LbError",this.code=i.code,this.raw=i.raw}},p=null,f=3e4;function E(){let e=globalThis.cowork;return e&&typeof e.callMcpTool=="function"?(t,n)=>e.callMcpTool(t,n):null}function v(e){if(e&&typeof e=="object"&&"content"in e){let t=e.content;if(Array.isArray(t)&&t[0]&&typeof t[0].text=="string")return t[0].text}return null}function y(e){if(!e||typeof e!="object")return e;let t=e;if(t.isError)throw new c(v(e)??"tool call failed",{raw:e});if("structuredContent"in t&&t.structuredContent!=null)return t.structuredContent;let n=v(e);if(n!=null)try{return JSON.parse(n)}catch{return n}return e}function L(e){return e instanceof Error?e.message:String(e)}function m(e){let t=e instanceof c?e.code:void 0;return{message:L(e),unavailable:t==="unavailable",code:t}}function S(e={}){p=e.call??null,f=e.timeoutMs??3e4}async function w(e,t){if(!f||f<=0)return e;let n,i=new Promise((r,s)=>{n=setTimeout(()=>s(new c(`"${t}" timed out after ${f}ms`,{code:"timeout"})),f)});try{return await Promise.race([e,i])}finally{n&&clearTimeout(n)}}async function a(e,t={}){if(p)return y(await w(Promise.resolve(p(e,t)),e));let n=E();if(!n)throw new c("Leadbay bridge unavailable (window.cowork absent)",{code:"unavailable"});return y(await w(Promise.resolve(n(e,t)),e))}var d=class{constructor(){o(this,"subs",new Set)}subscribe(t){return this.subs.add(t),t(this),()=>this.subs.delete(t)}emit(){for(let t of this.subs)t(this)}};function A(e){return Array.isArray(e)?e.map(t=>t&&typeof t=="object"?t:{value:t,label:String(t)}):[]}var h=class extends d{constructor(n={}){super();o(this,"kind");o(this,"value");o(this,"options",[]);o(this,"loading",!1);o(this,"error",null);o(this,"ready",!1);o(this,"cfg");o(this,"depUnsubs",[]);o(this,"seq",0);this.cfg=n,this.kind=n.kind,this.value=n.value??"";for(let i of n.dependsOn??[]){let r=i.value;this.depUnsubs.push(i.subscribe(()=>{i.value!==r&&(r=i.value,this.cfg.load&&this.load())}))}n.load&&(n.autoLoad??!0)&&this.load()}async load(){if(!this.cfg.load)return;let n=++this.seq;this.loading=!0,this.error=null,this.emit();try{let i=await this.cfg.load();if(n!==this.seq)return;this.options=this.cfg.options?this.cfg.options(i):A(i),this.ready=!0;let r=this.value==null?"":String(this.value);this.options.length&&(r===""||!this.options.some(s=>String(s.value)===r))&&(this.value=this.options[0].value)}catch(i){if(n!==this.seq)return;this.options=[],this.error=m(i)}finally{n===this.seq&&(this.loading=!1,this.emit())}}setValue(n){this.value=n;let i=this.validate();this.error=i?{message:i,unavailable:!1}:null,this.emit()}validate(){return this.cfg.validate?this.cfg.validate(this.value):null}get valid(){return this.validate()==null}reset(){this.value=this.cfg.value??"",this.error=null,this.emit()}dispose(){for(let n of this.depUnsubs)n();this.depUnsubs=[]}},l=class extends d{constructor(n){super();o(this,"loading",!1);o(this,"error",null);o(this,"lastResult",null);o(this,"cfg");this.cfg=n}async run(){if(this.loading)return;for(let i of this.cfg.fields??[]){let r=i.validate();if(r!=null){this.error={message:r,unavailable:!1},this.emit();return}}if(this.cfg.confirm&&typeof globalThis.confirm=="function"&&!globalThis.confirm(this.cfg.confirm))return;this.loading=!0,this.error=null,this.emit();let n;try{let i=typeof this.cfg.args=="function"?this.cfg.args():this.cfg.args??{};n=await a(this.cfg.tool,i)}catch(i){this.error=m(i),this.loading=!1,this.emit(),this.cfg.onError?.(this.error);return}return this.lastResult=n,this.loading=!1,this.emit(),this.cfg.onSuccess?.(n),n}reset(){this.error=null,this.lastResult=null,this.emit()}},u=class extends d{constructor(n){super();o(this,"data",null);o(this,"loading",!1);o(this,"refreshing",!1);o(this,"error",null);o(this,"done",!1);o(this,"cfg");o(this,"timer",null);o(this,"seq",0);this.cfg=n,(n.autoLoad??!0)&&this.load()}async load(){this.clearTimer();let n=++this.seq;this.data==null?this.loading=!0:this.refreshing=!0,this.error=null,this.emit();try{let r=await this.cfg.load();if(n!==this.seq)return;this.data=r,this.done=this.cfg.until?this.cfg.until(r):!0,this.cfg.pollEvery&&!this.done&&(this.timer=setTimeout(()=>void this.load(),this.cfg.pollEvery))}catch(r){if(n!==this.seq)return;this.error=m(r)}finally{n===this.seq&&(this.loading=!1,this.refreshing=!1,this.emit())}}refresh(){return this.load()}stop(){this.clearTimer()}clearTimer(){this.timer&&(clearTimeout(this.timer),this.timer=null)}},b=class extends d{constructor(n){super();o(this,"items",[]);o(this,"page",0);o(this,"pageSize");o(this,"total",0);o(this,"loading",!1);o(this,"error",null);o(this,"cfg");o(this,"seq",0);this.cfg=n,this.pageSize=n.pageSize??20,(n.autoLoad??!0)&&this.loadPage(0)}async loadPage(n){let i=++this.seq;this.loading=!0,this.error=null,this.emit();try{let r=await this.cfg.load({page:n,pageSize:this.pageSize});if(i!==this.seq)return;this.items=r.items??[],this.total=r.total??this.items.length,this.page=n}catch(r){if(i!==this.seq)return;this.error=m(r)}finally{i===this.seq&&(this.loading=!1,this.emit())}}next(){return this.loadPage(this.page+1)}prev(){return this.loadPage(Math.max(0,this.page-1))}get hasMore(){return(this.page+1)*this.pageSize<this.total}};function x(e,t){let n=t.error?.unavailable?"unavailable":t.loading?"loading":t.error?"error":"ready";e.setAttribute("data-lb-state",n),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")}function C(e,t){let n=()=>t.setValue(e.value);e.addEventListener("change",n);let i=t.subscribe(()=>{x(e,t),e.disabled=t.loading,e.innerHTML="";for(let r of t.options){let s=document.createElement("option");s.value=String(r.value),s.textContent=r.label,e.appendChild(s)}e.value=t.value==null?"":String(t.value)});return()=>{e.removeEventListener("change",n),i()}}function I(e,t){let n=e.type==="checkbox",i=e.tagName==="SELECT"?"change":"input",r=()=>t.setValue(n?e.checked:e.value);e.addEventListener(i,r);let s=t.subscribe(()=>{if(n)e.checked=!!t.value;else{let g=t.value==null?"":String(t.value);e.value!==g&&(e.value=g)}e.setAttribute("data-lb-state",t.error?"error":"ready"),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")});return()=>{e.removeEventListener(i,r),s()}}function R(e,t){let n=r=>{r.preventDefault(),t.run()};e.addEventListener("click",n);let i=t.subscribe(()=>{let r=t.error?.unavailable?"unavailable":t.loading?"loading":t.error?"error":t.lastResult!=null?"success":"idle";e.setAttribute("data-lb-state",r),"disabled"in e&&(e.disabled=t.loading),t.error?e.setAttribute("data-lb-error",t.error.message):e.removeAttribute("data-lb-error")});return()=>{e.removeEventListener("click",n),i()}}var O=["STILL_CHASING","COULD_NOT_REACH_STILL_TRYING","INTEREST_VALIDATED_OR_MEETING_PLANED","NOT_INTERESTED_LOST"];function P(e){return new h({kind:"select",load:()=>a("leadbay_list_campaigns",{_triggered_by:e}),options:t=>(t?.campaigns??[]).map(i=>{let r=i?.campaign??i;return r?.id?{value:r.id,label:r.name??r.ai_generated_name??String(r.id)}:null}).filter(i=>i!=null)})}function M(e){return new l({tool:"leadbay_report_outreach",fields:e.note?[e.note]:[],args:()=>({lead_id:e.leadId,...e.status?{epilogue_status:e.status.value}:{},note:e.note?e.note.value:"",verification:{source:"user_confirmed",ref:e.ref??"logged from artifact"},_triggered_by:e.ask})})}function F(e){return new l({tool:"leadbay_add_note",fields:[e.note],args:()=>({leadId:e.leadId,note:e.note.value})})}function q(e){return new l({tool:"leadbay_like_lead",args:{lead_id:e}})}function H(e){return new l({tool:"leadbay_dislike_lead",args:{lead_id:e}})}function z(e,t){return new u({autoLoad:!1,load:()=>a("leadbay_account_history",{leadId:e,_triggered_by:t})})}function N(e,t){return new u({autoLoad:!1,load:()=>a("leadbay_research_lead_by_id",{leadId:e,_triggered_by:t})})}function U(e){let t=null;return new u({pollEvery:e.pollEvery??4e3,until:n=>!!n?.all_done,load:async()=>{if(!t){let n=await a("leadbay_enrich_titles",{...e.leadIds?{leadIds:e.leadIds}:{},titles:e.titles,email:e.email??!0,phone:e.phone??!1,_triggered_by:e.ask});if(t=n?.bulk_id??null,!t)return{all_done:!0,no_job:!0,mode:n?.mode,preview:n?.preview}}return a("leadbay_bulk_enrich_status",{bulk_id:t,_triggered_by:e.ask})}})}function D(e){let t=e.source??"followups";return new b({pageSize:e.pageSize??20,load:async({page:n,pageSize:i})=>{let s=t==="campaign"?await a("leadbay_campaign_call_sheet",{campaign_id:e.campaignId,page:n,count:i,_triggered_by:e.ask}):await a("leadbay_pull_followups",{page:n,count:i,...e.city?{city:e.city}:{},_triggered_by:e.ask}),g=s.leads??s.items??[];return{items:g,total:s.total_leads??s.pagination?.total??g.length}}})}function V(e){return new u({load:()=>a("leadbay_team_activity",{weeks:e.weeks??4,_triggered_by:e.ask})})}var j={VERSION:T,configure:S,call:a,field:e=>new h(e),action:e=>new l(e),resource:e=>new u(e),list:e=>new b(e),bindSelect:C,bindValue:I,bindAction:R,campaigns:P,outreach:M,note:F,like:q,dislike:H,leadHistory:z,leadProfile:N,enrichment:U,callList:D,teamActivity:V,EPILOGUE_STATUSES:O};typeof globalThis<"u"&&(globalThis.LeadbayArtifacts=j);})();';
21212
+ var ARTIFACT_USAGE_GUIDE = '# Leadbay Artifact Kit \u2014 headless domain components\n\nYou are building a single-file HTML **artifact** the user runs inside cowork. This\nkit gives you **headless view-models** that own a control\'s whole data lifecycle \u2014\nload/populate from a Leadbay call, hold value/state, poll, validate, and\nencapsulate the API call + business rules. **You own 100% of markup/layout/style.**\nThe library renders nothing. Inline the runtime once as a `<script>`; it exposes\none global `window.LeadbayArtifacts` (call it `lb`). Vanilla, no React, no build.\n\nPass every tool you use as the artifact\'s `mcp_tools` so the host permits it.\n\n## Two layers\n\n**Primitives** (generic):\n- `lb.field({ load, options, value, validate, dependsOn })` \u2014 a value + optionally\n API-populated options. `.value/.setValue/.options/.loading/.error/.valid/.subscribe`.\n- `lb.action({ tool, args, fields, confirm, onSuccess, onError })` \u2014 a write/submit.\n `.run()/.loading/.error/.lastResult/.subscribe`.\n- `lb.resource({ load, pollEvery?, until?, autoLoad? })` \u2014 one read that may change:\n load-on-click or poll-until-`until`. `.data/.loading/.refreshing/.error/.done/.load()/.refresh()/.stop()/.subscribe`.\n- `lb.list({ load, pageSize })` \u2014 paginated rows. `.items/.page/.total/.loading/.loadPage(n)/.next()/.prev()/.hasMore/.subscribe`.\n\n`.error` is `{ message, unavailable } | null`. `subscribe(cb)` fires immediately\nthen on every change \u2014 render your own DOM from it.\n\n**Domain components** (pre-wired \u2014 bake in the tool name, arg shape, and footguns):\n\n| Call | Returns | For |\n|---|---|---|\n| `lb.campaigns(ask)` | field | a campaign `<select>`, options from `leadbay_list_campaigns` |\n| `lb.outreach({leadId, ask, status?, note?})` | action | log a call \u2192 `report_outreach` (verification + `_triggered_by` baked in) |\n| `lb.note({leadId, note})` | action | add a note \u2192 `add_note` |\n| `lb.like(leadId)` / `lb.dislike(leadId)` | action | taste signal |\n| `lb.leadHistory(leadId, ask)` | resource (lazy) | notes + activities + engagement \u2192 `account_history` |\n| `lb.leadProfile(leadId, ask)` | resource (lazy) | full lead profile \u2192 `research_lead_by_id` |\n| `lb.callList({source:\'followups\'\\|\'campaign\', campaignId?, city?, ask})` | list | a cold-call list (Monitor or a campaign) |\n| `lb.enrichment({leadIds, titles, ask, pollEvery?})` | resource (polling) | launch + watch contact enrichment |\n| `lb.teamActivity({weeks, ask})` | resource | manager leaderboard + activity trend \u2192 `leadbay_team_activity` |\n\n`lb.EPILOGUE_STATUSES` = the 4 disposition values\n(`STILL_CHASING`, `COULD_NOT_REACH_STILL_TRYING`, `INTEREST_VALIDATED_OR_MEETING_PLANED`, `NOT_INTERESTED_LOST`).\n\n**Binding sugar** (optional; binds a view-model to YOUR native element, no style):\n`lb.bindSelect(selectEl, field)` (populates options + value), `lb.bindValue(inputEl, field)`,\n`lb.bindAction(buttonEl, action)`. They set `data-lb-state`\n(`ready|loading|error|success|unavailable`) + `data-lb-error` on your element as\nstyling hooks. For lists/resources, use `.subscribe()` and render yourself.\n\n`ask` is the user\'s request this artifact serves \u2014 it becomes `_triggered_by`.\n\n## Recipe: cold-call sheet (one row per lead)\n\n```js\nconst lb = window.LeadbayArtifacts; lb.configure();\nconst ASK = "<the user\'s request>";\n\nconst list = lb.callList({ source: "campaign", campaignId: CID, ask: ASK });\nlist.subscribe((l) => renderRows(l.items, l.loading)); // your render\n\n// per lead row (call when you build a row):\nfunction wireRow(lead, els) {\n const status = lb.field({ value: "STILL_CHASING" }); // static-enum <select>\n const note = lb.field({ validate: (v) => (v && v.trim() ? null : "Add a note") });\n lb.bindValue(els.status, status);\n lb.bindValue(els.note, note);\n lb.bindAction(els.log, lb.outreach({ leadId: lead.id, ask: ASK, status, note }));\n lb.bindAction(els.like, lb.like(lead.id));\n\n const history = lb.leadHistory(lead.id, ASK); // lazy\n history.subscribe((h) => renderHistory(els.history, h));\n els.expand.onclick = () => history.load(); // load on click\n}\n```\n\n## Recipe: manager dashboard\n\n```js\nconst team = lb.teamActivity({ weeks: 4, ask: ASK });\nteam.subscribe((t) => {\n if (t.loading) showSpinner();\n if (t.data) {\n renderLeaderboard(t.data.reps); // sorted by total_activities; cols: name, notes, meetings_or_interest, lost\u2026\n renderTrendChart(t.data.trend); // [{date,count}] \u2192 Chart.js (allowed from CDN)\n }\n});\nrefreshBtn.onclick = () => team.refresh();\n```\n\n## Recipe: live enrichment\n\n```js\nconst job = lb.enrichment({ leadIds: [LEAD], titles: ["CEO", "VP Sales"], ask: ASK });\njob.subscribe((j) => {\n const p = j.data && j.data.overall_progress; // {done,total,done_ratio}\n renderBar(p);\n if (j.done) renderContacts(j.data.leads); // enriched contacts\n});\nrefreshBtn.onclick = () => job.refresh();\n```\n\n## Write-call rules\n\nThe domain factories handle these for you. If you hand-roll an action:\n`leadbay_report_outreach` args MUST include `verification:{source:"user_confirmed", ref}`\nAND `_triggered_by`; `leadbay_add_leads_to_campaign` needs `_triggered_by`;\n`add_note`/`like_lead`/`dislike_lead` take only their own args. `epilogue_status` is\none of `lb.EPILOGUE_STATUSES`. Snoozing (pushback) and org WON/LOST status are\nadvanced-gated \u2014 not callable from a default artifact; use the epilogue values.\n\n## Degradation + live updates\n\nIf the host bridge is absent, a view-model\'s `.error` is set with `.error.unavailable\n=== true` (bind helpers set `data-lb-state="unavailable"`) \u2014 nothing throws. Every\ncall also has a **30s timeout** (configurable via `lb.configure({ timeoutMs })`): a\nhost call that never settles becomes `.error` with `code:"timeout"`, so a control is\nnever stuck loading forever \u2014 always render the `.error` branch so the user can retry.\nAuto-poll (`pollEvery`) depends on the cowork host serving FRESH reads; `.refresh()`\nis the guaranteed manual path \u2014 always wire a Refresh control for polling resources.';
21213
+
21214
+ // ../core/dist/tools/artifact-kit.js
21215
+ var artifactKit = {
21216
+ name: "leadbay_artifact_kit",
21217
+ annotations: {
21218
+ title: "Artifact component kit",
21219
+ readOnlyHint: true,
21220
+ destructiveHint: false,
21221
+ idempotentHint: true,
21222
+ openWorldHint: false
21223
+ },
21224
+ description: leadbay_artifact_kit,
21225
+ write: false,
21226
+ inputSchema: {
21227
+ type: "object",
21228
+ properties: {},
21229
+ additionalProperties: false
21230
+ },
21231
+ // No outputSchema by design: the return is three opaque strings. Declaring a
21232
+ // schema would enroll the tool in the output-schema-conformance drift-catcher
21233
+ // (an existing test file we don't modify). The server still emits the
21234
+ // plain-object return as structuredContent: { runtime, usage_guide, version }.
21235
+ execute: async (_client, _params, _ctx) => {
21236
+ return {
21237
+ version: ARTIFACT_KIT_VERSION,
21238
+ runtime: ARTIFACT_RUNTIME,
21239
+ usage_guide: ARTIFACT_USAGE_GUIDE
21240
+ };
21241
+ }
21242
+ };
21243
+
21040
21244
  // ../core/dist/index.js
21041
21245
  var agentMemoryTools = [
21042
21246
  agentMemoryRecall,
@@ -21121,6 +21325,11 @@ var compositeReadTools = [
21121
21325
  // answer to "which of my leads have signal X" that previously forced a
21122
21326
  // per-lead research_lead_by_id loop (issue #3704).
21123
21327
  scanPortfolioSignals,
21328
+ // Team activity — manager-facing per-rep leaderboard + activity trend (wraps
21329
+ // /kpi/users + /kpi/trends, the web Dashboard-Manager surface). Read-only;
21330
+ // always exposed so a manager artifact can render team KPIs. Non-admins are
21331
+ // scoped to themselves by the backend.
21332
+ teamActivity,
21124
21333
  recallOrderedTitles,
21125
21334
  accountStatus,
21126
21335
  bulkEnrichStatus,
@@ -21159,7 +21368,13 @@ var compositeReadTools = [
21159
21368
  // every tool response regardless of write gating; without ack the agent
21160
21369
  // sees the same entries on every call forever. Pairing the surfacing
21161
21370
  // channel with the clearing tool is non-optional.
21162
- acknowledgeNotification
21371
+ acknowledgeNotification,
21372
+ // Artifact kit — ALWAYS exposed, read-only. Returns the headless
21373
+ // @leadbay/components runtime + usage guide the agent uses to BUILD an
21374
+ // interactive HTML artifact (cold-call sheet, lead-triage board) whose
21375
+ // buttons call Leadbay writes. No backend call; granular-shaped (lives in
21376
+ // tools/) so it carries no _triggered_by mandate for a kit fetch.
21377
+ artifactKit
21163
21378
  ];
21164
21379
  var compositeWriteTools = [
21165
21380
  bulkQualifyLeads,
@@ -22616,7 +22831,7 @@ function parseWriteEnv(env = process.env) {
22616
22831
  }
22617
22832
 
22618
22833
  // src/http-server.ts
22619
- var VERSION = true ? "0.21.3" : "0.0.0-dev";
22834
+ var VERSION = true ? "0.22.0" : "0.0.0-dev";
22620
22835
  var PORT = Number(process.env.PORT ?? 8080);
22621
22836
  var HOST = process.env.HOST ?? "0.0.0.0";
22622
22837
  var sseSessions = /* @__PURE__ */ new Map();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.21.3",
3
+ "version": "0.22.0",
4
4
  "mcpName": "io.github.leadbay/leadbay-mcp",
5
5
  "description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
6
6
  "type": "module",