@agentrysh/mcp 0.0.12 → 0.0.14

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/dist/tools.js CHANGED
@@ -16,12 +16,14 @@ export const TOOL_DESCRIPTORS = [
16
16
  },
17
17
  {
18
18
  name: "agentry_login",
19
- description: "Authenticate the user via GitHub device flow. Returns an API key, stored locally. " +
19
+ description: "Authenticate the user via the agentry sign-in page (GitHub, Google, or magic link — " +
20
+ "user picks any provider). Returns an API key, stored locally. " +
20
21
  "" +
21
22
  "RECOMMENDED two-call sequence for interactive sessions: " +
22
- " 1. Call with mode='start_only' → returns user_code + verification_uri + device_code. " +
23
- " Show the user the code and URL (DO NOT ask them to confirm authorization — they'll " +
24
- " just open the URL, paste the code, and you'll poll). " +
23
+ " 1. Call with mode='start_only' → returns user_code + verification_uri + device_code " +
24
+ " (plus verification_uri_complete with the code pre-filled). Show the user the URL " +
25
+ " and code (DO NOT ask them to confirm authorization — they'll open the URL, sign in " +
26
+ " with their provider of choice, and you'll poll). " +
25
27
  " 2. IMMEDIATELY call again with mode='full' + the device_code from step 1. " +
26
28
  " This blocks and auto-polls every ~5s for up to timeout_seconds (default 300 = 5min). " +
27
29
  " Returns the api_key when the user authorizes, or status='expired'/'denied' on failure. " +
@@ -451,8 +453,8 @@ export const TOOL_DESCRIPTORS = [
451
453
  {
452
454
  name: "agentry_get_session_replay",
453
455
  description: "Fetch a single session recording's metadata + player URL. Open `player_url` in a " +
454
- "browser to watch. For programmatic DOM-event inspection (the snapshot data), call " +
455
- "the snapshots subresource directly via curl using the agentry-injected master key.",
456
+ "browser to watch. For programmatic DOM-event inspection (the rrweb snapshots), " +
457
+ "call agentry_get_replay_snapshots.",
456
458
  inputSchema: {
457
459
  type: "object",
458
460
  properties: {
@@ -463,17 +465,163 @@ export const TOOL_DESCRIPTORS = [
463
465
  additionalProperties: false,
464
466
  },
465
467
  },
468
+ {
469
+ name: "agentry_get_replay_snapshots",
470
+ description: "Fetch the rrweb-format DOM snapshots for one recording. Each snapshot has type " +
471
+ "(Meta / FullSnapshot / IncrementalSnapshot — values 4 / 2 / 3) and timestamp. " +
472
+ "For agent inspection: filter type=3 events for user actions (clicks, scrolls, " +
473
+ "inputs). Useful for reconstructing exactly what the user did before an error.",
474
+ inputSchema: {
475
+ type: "object",
476
+ properties: {
477
+ project_id: { type: "string" },
478
+ replay_id: { type: "string" },
479
+ source: {
480
+ type: "string",
481
+ enum: ["realtime", "blob"],
482
+ description: "PostHog snapshot source. 'realtime' for live, 'blob' for archived.",
483
+ },
484
+ },
485
+ required: ["replay_id"],
486
+ additionalProperties: false,
487
+ },
488
+ },
489
+ {
490
+ name: "agentry_evaluate_feature_flag",
491
+ description: "Evaluate feature flags for one user (distinct_id). Two shapes:" +
492
+ "" +
493
+ " - Pass `key` to get a single flag's value (boolean / variant string / null if " +
494
+ " not active for this user)." +
495
+ " - Omit `key` to get a map of all currently-active flags for the user." +
496
+ "" +
497
+ "Use this when investigating a bug — 'is this user in the new checkout flow?' is " +
498
+ "one call away. Pass `person_properties` to override the stored properties for " +
499
+ "what-if evaluation.",
500
+ inputSchema: {
501
+ type: "object",
502
+ properties: {
503
+ project_id: { type: "string" },
504
+ distinct_id: { type: "string", description: "Stable per-user id, same as case.affected_users[].distinct_id." },
505
+ key: { type: "string", description: "Flag key. Omit to get all flags." },
506
+ person_properties: { type: "object", description: "Override stored properties for what-if." },
507
+ groups: { type: "object", description: "Group analytics overrides if your flag targets groups." },
508
+ },
509
+ required: ["distinct_id"],
510
+ additionalProperties: false,
511
+ },
512
+ },
513
+ {
514
+ name: "agentry_get_distinct_id_summary",
515
+ description: "One-call user dossier. Returns: PostHog person properties, total event count, " +
516
+ "first/last seen, last 20 events, recent session recordings (if replay is on), and " +
517
+ "a deep-link URL into PostHog's persons UI. Use this when investigating a case — " +
518
+ "the affected_users[].distinct_id from agentry_get_case lets you build the full " +
519
+ "picture without 3+ separate HogQL calls.",
520
+ inputSchema: {
521
+ type: "object",
522
+ properties: {
523
+ project_id: { type: "string" },
524
+ distinct_id: { type: "string" },
525
+ },
526
+ required: ["distinct_id"],
527
+ additionalProperties: false,
528
+ },
529
+ },
530
+ {
531
+ name: "agentry_survey_responses",
532
+ description: "Roll-up of survey responses. Returns: the survey definition (questions + choice " +
533
+ "labels), a response_distribution (counts per choice), the last 50 free-text " +
534
+ "responses with timestamps, and a web UI deep-link. Saves the agent from composing " +
535
+ "HogQL with $survey_response property unpacking.",
536
+ inputSchema: {
537
+ type: "object",
538
+ properties: {
539
+ project_id: { type: "string" },
540
+ survey_id: { type: "string" },
541
+ },
542
+ required: ["survey_id"],
543
+ additionalProperties: false,
544
+ },
545
+ },
546
+ {
547
+ name: "agentry_create_ab_test",
548
+ description: "Composite tool: creates a multivariate feature flag AND returns the bound " +
549
+ "conversion-rate HogQL query an agent can run later via agentry_analytics_query. " +
550
+ "" +
551
+ "Pass `name`, `success_event` (e.g. 'checkout_completed'), and `variants` (array of " +
552
+ "{key} — auto-split rollout if rollout_percentage not given; must sum to 100 if " +
553
+ "any are explicit). Optional flag_key (default: slugified name)." +
554
+ "" +
555
+ "Returns: flag_id, conversion_query string, web_ui_url. Recommended: ≥1000 users " +
556
+ "per variant before drawing conclusions.",
557
+ inputSchema: {
558
+ type: "object",
559
+ properties: {
560
+ project_id: { type: "string" },
561
+ name: { type: "string" },
562
+ flag_key: { type: "string", description: "Override the slugified name." },
563
+ success_event: { type: "string", description: "The event whose conversion you're measuring." },
564
+ variants: {
565
+ type: "array",
566
+ items: {
567
+ type: "object",
568
+ properties: {
569
+ key: { type: "string" },
570
+ name: { type: "string" },
571
+ rollout_percentage: { type: "number" },
572
+ },
573
+ },
574
+ description: "Min 2 variants. Auto-split if rollout_percentage omitted.",
575
+ },
576
+ },
577
+ required: ["name", "success_event", "variants"],
578
+ additionalProperties: false,
579
+ },
580
+ },
581
+ {
582
+ name: "agentry_recent_changes",
583
+ description: "Read the agent-mutation audit log. Returns every flag/cohort/survey/publication/" +
584
+ "session-replay mutation the agent (or anyone with your `agk_` key) has made in the " +
585
+ "last N hours." +
586
+ "" +
587
+ "Default window is 24 hours. Pass `hours` to widen — 48, 72, 168 (week), max 720 (30d)." +
588
+ "Pass `action_prefix` (e.g. 'feature_flag.' / 'cohort.' / 'survey.' / 'publication.' / " +
589
+ "'session_replay.') or `resource_type` to filter. Use this as a periodic " +
590
+ "'what-did-the-agent-do' check when leaving agents to run unattended.",
591
+ inputSchema: {
592
+ type: "object",
593
+ properties: {
594
+ hours: { type: "number", description: "Lookback window. 1..720. Default 24." },
595
+ action_prefix: {
596
+ type: "string",
597
+ description: "Filter by action prefix (e.g. 'feature_flag.' matches both create + delete).",
598
+ },
599
+ resource_type: {
600
+ type: "string",
601
+ enum: ["feature_flag", "cohort", "survey", "publication", "session_replay", "ab_test"],
602
+ },
603
+ project_id: { type: "string", description: "Restrict to one project." },
604
+ limit: { type: "number", description: "Max rows (default 200, max 500)." },
605
+ },
606
+ additionalProperties: false,
607
+ },
608
+ },
466
609
  {
467
610
  name: "agentry_repair_analytics",
468
- description: "Re-attempt PostHog provisioning for the authenticated user. Idempotent — if the user " +
469
- "already has a PostHog project, returns its id without recreating. Use this when " +
611
+ description: "Re-attempt PostHog provisioning for one project. Idempotent — if the project already " +
612
+ "has a PostHog team binding, returns its id without recreating. Use this when " +
470
613
  "agentry_verify_install reports analytics ❌ with reason 'no_posthog_project' OR when a " +
471
614
  "/v1/track/ call returns 503 with that code. " +
472
615
  "" +
473
616
  "DO NOT re-run agentry_login for this failure mode — that mints a new api_key and " +
474
- "churns the user's local config. This tool runs only the provisioning step that was " +
475
- "supposed to happen at login.",
476
- inputSchema: { type: "object", properties: {}, additionalProperties: false },
617
+ "churns the user's local config. This tool repairs the project-scoped analytics binding only.",
618
+ inputSchema: {
619
+ type: "object",
620
+ properties: {
621
+ project_id: { type: "string", description: "Project to repair. Defaults to local default_project_id." },
622
+ },
623
+ additionalProperties: false,
624
+ },
477
625
  },
478
626
  {
479
627
  name: "agentry_rotate_key",
@@ -1198,6 +1346,59 @@ export const TOOL_DESCRIPTORS = [
1198
1346
  additionalProperties: false,
1199
1347
  },
1200
1348
  },
1349
+ {
1350
+ name: "agentry_invite_teammate",
1351
+ description: "Mint a one-shot invite URL granting a teammate access to a project. Owner-only. " +
1352
+ "The recipient opens the URL, signs in with any provider (GitHub / Google / magic link), " +
1353
+ "and gets their own agentry account + API key automatically scoped to this project — they " +
1354
+ "do NOT use your API key. " +
1355
+ "" +
1356
+ "After this returns, share `invite_url` with your teammate (Slack, email — manual for v1). " +
1357
+ "Invite expires in 7 days, single-use.",
1358
+ inputSchema: {
1359
+ type: "object",
1360
+ properties: {
1361
+ project_id: { type: "string", description: "Defaults to the local default project." },
1362
+ email: {
1363
+ type: "string",
1364
+ description: "Optional — shown on the invite landing page as a hint to the recipient. " +
1365
+ "Does NOT restrict who can consume the invite; the link is the credential.",
1366
+ },
1367
+ role: {
1368
+ type: "string",
1369
+ enum: ["member", "owner"],
1370
+ description: "Defaults to 'member'. 'owner' is rare — only add other owners deliberately.",
1371
+ },
1372
+ },
1373
+ additionalProperties: false,
1374
+ },
1375
+ },
1376
+ {
1377
+ name: "agentry_list_members",
1378
+ description: "List members of a project + any pending (un-consumed, un-expired) invites. " +
1379
+ "Any member can see the full roster; remove members via agentry_remove_member.",
1380
+ inputSchema: {
1381
+ type: "object",
1382
+ properties: {
1383
+ project_id: { type: "string", description: "Defaults to the local default project." },
1384
+ },
1385
+ additionalProperties: false,
1386
+ },
1387
+ },
1388
+ {
1389
+ name: "agentry_remove_member",
1390
+ description: "Remove a teammate from a project. Owner-only. Cannot remove the last remaining owner, " +
1391
+ "and an owner cannot remove themselves (transfer ownership first — TBD).",
1392
+ inputSchema: {
1393
+ type: "object",
1394
+ properties: {
1395
+ project_id: { type: "string", description: "Defaults to the local default project." },
1396
+ user_id: { type: "string", description: "The user_id from agentry_list_members." },
1397
+ },
1398
+ required: ["user_id"],
1399
+ additionalProperties: false,
1400
+ },
1401
+ },
1201
1402
  ];
1202
1403
  // ---------------------------------------------------------------------------
1203
1404
  // Helpers shared across tool handlers
@@ -1312,7 +1513,7 @@ export async function dispatchTool(name, args) {
1312
1513
  case "agentry_rotate_key":
1313
1514
  return await handleRotateKey();
1314
1515
  case "agentry_repair_analytics":
1315
- return await handleRepairAnalytics();
1516
+ return await handleRepairAnalytics(a.project_id ? String(a.project_id) : undefined);
1316
1517
  case "agentry_publish_query":
1317
1518
  return await handlePublishQuery({
1318
1519
  project_id: a.project_id ? String(a.project_id) : undefined,
@@ -1437,6 +1638,52 @@ export async function dispatchTool(name, args) {
1437
1638
  project_id: a.project_id ? String(a.project_id) : undefined,
1438
1639
  replay_id: String(a.replay_id ?? ""),
1439
1640
  });
1641
+ case "agentry_get_replay_snapshots":
1642
+ return await handleGetReplaySnapshots({
1643
+ project_id: a.project_id ? String(a.project_id) : undefined,
1644
+ replay_id: String(a.replay_id ?? ""),
1645
+ source: a.source ? String(a.source) : undefined,
1646
+ });
1647
+ case "agentry_evaluate_feature_flag":
1648
+ return await handleEvaluateFeatureFlag({
1649
+ project_id: a.project_id ? String(a.project_id) : undefined,
1650
+ distinct_id: String(a.distinct_id ?? ""),
1651
+ key: a.key ? String(a.key) : undefined,
1652
+ person_properties: a.person_properties && typeof a.person_properties === "object"
1653
+ ? a.person_properties
1654
+ : undefined,
1655
+ groups: a.groups && typeof a.groups === "object"
1656
+ ? a.groups
1657
+ : undefined,
1658
+ });
1659
+ case "agentry_get_distinct_id_summary":
1660
+ return await handleGetDistinctIdSummary({
1661
+ project_id: a.project_id ? String(a.project_id) : undefined,
1662
+ distinct_id: String(a.distinct_id ?? ""),
1663
+ });
1664
+ case "agentry_survey_responses":
1665
+ return await handleSurveyResponses({
1666
+ project_id: a.project_id ? String(a.project_id) : undefined,
1667
+ survey_id: String(a.survey_id ?? ""),
1668
+ });
1669
+ case "agentry_create_ab_test":
1670
+ return await handleCreateAbTest({
1671
+ project_id: a.project_id ? String(a.project_id) : undefined,
1672
+ name: String(a.name ?? ""),
1673
+ flag_key: a.flag_key ? String(a.flag_key) : undefined,
1674
+ success_event: String(a.success_event ?? ""),
1675
+ variants: Array.isArray(a.variants)
1676
+ ? a.variants
1677
+ : [],
1678
+ });
1679
+ case "agentry_recent_changes":
1680
+ return await handleRecentChanges({
1681
+ hours: typeof a.hours === "number" ? a.hours : undefined,
1682
+ action_prefix: a.action_prefix ? String(a.action_prefix) : undefined,
1683
+ resource_type: a.resource_type ? String(a.resource_type) : undefined,
1684
+ project_id: a.project_id ? String(a.project_id) : undefined,
1685
+ limit: typeof a.limit === "number" ? a.limit : undefined,
1686
+ });
1440
1687
  case "agentry_list_projects":
1441
1688
  return await handleListProjects();
1442
1689
  case "agentry_create_project":
@@ -1634,6 +1881,19 @@ export async function dispatchTool(name, args) {
1634
1881
  kind: a.kind ? String(a.kind) : undefined,
1635
1882
  resolved: typeof a.resolved === "boolean" ? a.resolved : undefined,
1636
1883
  });
1884
+ case "agentry_invite_teammate":
1885
+ return await handleInviteTeammate({
1886
+ project_id: a.project_id ? String(a.project_id) : undefined,
1887
+ email: a.email ? String(a.email) : undefined,
1888
+ role: a.role === "owner" ? "owner" : "member",
1889
+ });
1890
+ case "agentry_list_members":
1891
+ return await handleListMembers(a.project_id ? String(a.project_id) : undefined);
1892
+ case "agentry_remove_member":
1893
+ return await handleRemoveMember({
1894
+ project_id: a.project_id ? String(a.project_id) : undefined,
1895
+ user_id: String(a.user_id ?? ""),
1896
+ });
1637
1897
  default:
1638
1898
  return {
1639
1899
  error: {
@@ -1663,7 +1923,13 @@ function handleStatus() {
1663
1923
  project_count: projectIds.length,
1664
1924
  projects: projectIds.map((id) => {
1665
1925
  const p = cfg.projects[id];
1666
- return { id, name: p.name, local_path: p.local_path };
1926
+ return {
1927
+ id,
1928
+ name: p.name,
1929
+ local_path: p.local_path,
1930
+ analytics_ready: p.analytics_ready ?? null,
1931
+ posthog_project_id: p.posthog_project_id ?? null,
1932
+ };
1667
1933
  }),
1668
1934
  onboarding: hint,
1669
1935
  next_steps: [hint.message, hint.next_action],
@@ -1673,14 +1939,16 @@ async function handleLogin(input) {
1673
1939
  const cfg = loadConfig();
1674
1940
  if (input.mode === "start_only") {
1675
1941
  const start = await api.startDeviceFlow(cfg);
1942
+ const openUrl = start.verification_uri_complete ?? start.verification_uri;
1676
1943
  return {
1677
1944
  mode: "start_only",
1678
1945
  verification_uri: start.verification_uri,
1946
+ verification_uri_complete: start.verification_uri_complete,
1679
1947
  user_code: start.user_code,
1680
1948
  device_code: start.device_code,
1681
1949
  interval: start.interval,
1682
1950
  expires_in: start.expires_in,
1683
- next_action: `Show the user: "Open ${start.verification_uri} and enter the code ${start.user_code}." ` +
1951
+ next_action: `Show the user a clickable link: ${openUrl} one click takes them to sign-in, no code entry. ` +
1684
1952
  `Then IMMEDIATELY call agentry_login again with mode='full' and device_code='${start.device_code}'. ` +
1685
1953
  `That call will block and auto-poll for up to ${start.expires_in}s — DO NOT ask the user to confirm authorization before polling.`,
1686
1954
  };
@@ -1779,7 +2047,7 @@ async function handleLogin(input) {
1779
2047
  device_code: deviceCode,
1780
2048
  next_action: `Timed out after ${input.timeout_seconds ?? 300}s. ` +
1781
2049
  (verificationUri && userCode
1782
- ? `Confirm the user opened ${verificationUri} and entered ${userCode}, `
2050
+ ? `Confirm the user opened ${verificationUri}?code=${userCode}, `
1783
2051
  : "Confirm the user has authorized, ") +
1784
2052
  `then call agentry_login again with mode='full' and device_code='${deviceCode}' to resume polling.`,
1785
2053
  };
@@ -1808,7 +2076,7 @@ async function handleRotateKey() {
1808
2076
  "New key stored locally. Old key is revoked — update any places it was pasted (CI envs, etc).",
1809
2077
  };
1810
2078
  }
1811
- async function handleRepairAnalytics() {
2079
+ async function handleRepairAnalytics(projectId) {
1812
2080
  const cfg = loadConfig();
1813
2081
  if (!cfg.api_key) {
1814
2082
  return {
@@ -1819,9 +2087,32 @@ async function handleRepairAnalytics() {
1819
2087
  },
1820
2088
  };
1821
2089
  }
2090
+ const picked = pickProject(cfg, projectId);
2091
+ if (!picked || !picked.project) {
2092
+ return {
2093
+ error: {
2094
+ code: "no_project",
2095
+ message: "No local project selected for analytics repair.",
2096
+ next_action: "Create a project first or pass a project_id from agentry_list_projects.",
2097
+ },
2098
+ };
2099
+ }
1822
2100
  try {
1823
- const resp = await api.repairAnalyticsBackend(cfg);
1824
- return resp;
2101
+ const resp = await api.repairAnalyticsBackend(cfg, picked.id);
2102
+ const updatedProject = {
2103
+ ...picked.project,
2104
+ analytics_ready: resp.posthog_project_id !== null,
2105
+ posthog_project_id: resp.posthog_project_id,
2106
+ };
2107
+ const nextCfg = {
2108
+ ...cfg,
2109
+ projects: {
2110
+ ...cfg.projects,
2111
+ [picked.id]: updatedProject,
2112
+ },
2113
+ };
2114
+ saveConfig(nextCfg);
2115
+ return { project_id: picked.id, ...resp };
1825
2116
  }
1826
2117
  catch (err) {
1827
2118
  return {
@@ -1829,7 +2120,7 @@ async function handleRepairAnalytics() {
1829
2120
  code: "repair_failed",
1830
2121
  message: err instanceof Error ? err.message : String(err),
1831
2122
  next_action: "Upstream PostHog provisioning failed. If the error mentions 5xx / timeouts / rate " +
1832
- "limits, wait 30–60s and call agentry_repair_analytics again. Errors and deploys are " +
2123
+ "limits, wait 30–60s and call agentry_repair_analytics again for the same project. Errors and deploys are " +
1833
2124
  "unaffected; only analytics ingest needs PostHog.",
1834
2125
  },
1835
2126
  };
@@ -2135,6 +2426,91 @@ async function handleGetSessionReplay(input) {
2135
2426
  return r.error;
2136
2427
  return await api.getSessionReplay(cfg, r.id, input.replay_id);
2137
2428
  }
2429
+ async function handleGetReplaySnapshots(input) {
2430
+ if (!input.replay_id) {
2431
+ return { error: { code: "invalid_payload", message: "replay_id is required.", next_action: "Pass replay_id from agentry_list_session_replays." } };
2432
+ }
2433
+ const cfg = loadConfig();
2434
+ const r = pickOrError(cfg, input.project_id);
2435
+ if (!r.ok)
2436
+ return r.error;
2437
+ return await api.getReplaySnapshots(cfg, r.id, input.replay_id, input.source);
2438
+ }
2439
+ async function handleEvaluateFeatureFlag(input) {
2440
+ if (!input.distinct_id) {
2441
+ return { error: { code: "invalid_payload", message: "distinct_id is required.", next_action: "Pass the user identifier (same as case.affected_users[].distinct_id)." } };
2442
+ }
2443
+ const cfg = loadConfig();
2444
+ const r = pickOrError(cfg, input.project_id);
2445
+ if (!r.ok)
2446
+ return r.error;
2447
+ return await api.evaluateFeatureFlag(cfg, r.id, {
2448
+ distinct_id: input.distinct_id,
2449
+ key: input.key,
2450
+ person_properties: input.person_properties,
2451
+ groups: input.groups,
2452
+ });
2453
+ }
2454
+ async function handleGetDistinctIdSummary(input) {
2455
+ if (!input.distinct_id) {
2456
+ return { error: { code: "invalid_payload", message: "distinct_id is required.", next_action: "Pass the user identifier." } };
2457
+ }
2458
+ const cfg = loadConfig();
2459
+ const r = pickOrError(cfg, input.project_id);
2460
+ if (!r.ok)
2461
+ return r.error;
2462
+ return await api.getDistinctIdSummary(cfg, r.id, input.distinct_id);
2463
+ }
2464
+ async function handleSurveyResponses(input) {
2465
+ if (!input.survey_id) {
2466
+ return { error: { code: "invalid_payload", message: "survey_id is required.", next_action: "Pass survey_id from agentry_list_surveys." } };
2467
+ }
2468
+ const cfg = loadConfig();
2469
+ const r = pickOrError(cfg, input.project_id);
2470
+ if (!r.ok)
2471
+ return r.error;
2472
+ return await api.getSurveyResponses(cfg, r.id, input.survey_id);
2473
+ }
2474
+ async function handleCreateAbTest(input) {
2475
+ if (!input.name) {
2476
+ return { error: { code: "invalid_payload", message: "name is required.", next_action: "Pass an A/B test name." } };
2477
+ }
2478
+ if (!input.success_event) {
2479
+ return { error: { code: "invalid_payload", message: "success_event is required.", next_action: "Pass the conversion event name (e.g. 'checkout_completed')." } };
2480
+ }
2481
+ if (!Array.isArray(input.variants) || input.variants.length < 2) {
2482
+ return { error: { code: "invalid_payload", message: "variants must be ≥2 entries.", next_action: "Pass variants: [{key:'control'},{key:'treatment'}]." } };
2483
+ }
2484
+ const cfg = loadConfig();
2485
+ const r = pickOrError(cfg, input.project_id);
2486
+ if (!r.ok)
2487
+ return r.error;
2488
+ return await api.createAbTest(cfg, r.id, {
2489
+ name: input.name,
2490
+ flag_key: input.flag_key,
2491
+ success_event: input.success_event,
2492
+ variants: input.variants,
2493
+ });
2494
+ }
2495
+ async function handleRecentChanges(input) {
2496
+ const cfg = loadConfig();
2497
+ if (!cfg.api_key) {
2498
+ return {
2499
+ error: {
2500
+ code: "no_key",
2501
+ message: "No API key on file.",
2502
+ next_action: "Call agentry_login.",
2503
+ },
2504
+ };
2505
+ }
2506
+ return await api.listRecentChanges(cfg, {
2507
+ hours: input.hours,
2508
+ actionPrefix: input.action_prefix,
2509
+ resourceType: input.resource_type,
2510
+ projectId: input.project_id,
2511
+ limit: input.limit,
2512
+ });
2513
+ }
2138
2514
  async function handleListProjects() {
2139
2515
  const cfg = loadConfig();
2140
2516
  if (!cfg.api_key) {
@@ -2153,6 +2529,8 @@ async function handleListProjects() {
2153
2529
  ...p,
2154
2530
  local_path: local?.local_path ?? null,
2155
2531
  dsn_known_locally: Boolean(local?.dsn),
2532
+ analytics_ready: local?.analytics_ready ?? null,
2533
+ posthog_project_id: local?.posthog_project_id ?? null,
2156
2534
  is_default: cfg.default_project_id === p.id,
2157
2535
  };
2158
2536
  });
@@ -2197,6 +2575,8 @@ async function handleCreateProject(input) {
2197
2575
  dsn: resp.dsn,
2198
2576
  local_path: input.local_path ?? null,
2199
2577
  default_branch: resp.default_branch ?? input.default_branch ?? "main",
2578
+ analytics_ready: resp.analytics?.ready,
2579
+ posthog_project_id: resp.analytics?.posthog_project_id ?? null,
2200
2580
  };
2201
2581
  const nextCfg = {
2202
2582
  ...cfg,
@@ -2225,6 +2605,7 @@ async function handleCreateProject(input) {
2225
2605
  logs_url: resp.logs_url,
2226
2606
  analytics_url: resp.analytics_url,
2227
2607
  deploys_url: resp.deploys_url,
2608
+ analytics: resp.analytics,
2228
2609
  },
2229
2610
  install_snippet: install,
2230
2611
  next_action: resp.next_action ??
@@ -3038,17 +3419,17 @@ async function handleVerifyInstall(input) {
3038
3419
  const analyticsDetail = checks.analytics?.detail ?? "";
3039
3420
  const noPosthogProject = !checks.analytics?.ok &&
3040
3421
  (analyticsDetail.includes("no_posthog_project") ||
3041
- analyticsDetail.includes("user has no PostHog project provisioned"));
3422
+ analyticsDetail.includes("project has no PostHog project provisioned"));
3042
3423
  const baseAction = failed.length === 0
3043
3424
  ? "Install verified. Errors land in agentry_list_cases; analytics flow to PostHog; deploys via agentry_list_deploys."
3044
3425
  : noPosthogProject && failed.length === 1
3045
3426
  ? "Analytics is the only failed signal AND the cause is missing PostHog provisioning " +
3046
- "(first-login provisioning was best-effort and failed). Call agentry_repair_analytics " +
3427
+ "(the project binding is missing). Call agentry_repair_analytics " +
3047
3428
  "— it's idempotent and runs the same provisioning step. Then re-run agentry_verify_install. " +
3048
3429
  "DO NOT re-run agentry_login for this."
3049
3430
  : `Install incomplete. Failed signal types: ${failed.join(", ")}. ` +
3050
3431
  (noPosthogProject
3051
- ? "Analytics failed because the user has no PostHog project provisioned — " +
3432
+ ? "Analytics failed because the project has no PostHog project provisioned — " +
3052
3433
  "call agentry_repair_analytics, then re-run verify. "
3053
3434
  : "") +
3054
3435
  "For each failed type, re-read its corresponding step in agentry_install_guide and fix.";
@@ -3502,4 +3883,61 @@ async function handleListFeedback(input) {
3502
3883
  const resp = await api.listFeedback(cfg, opts);
3503
3884
  return resp;
3504
3885
  }
3886
+ // ---------------------------------------------------------------------------
3887
+ // Team invites + member management (Phase 4)
3888
+ // ---------------------------------------------------------------------------
3889
+ async function handleInviteTeammate(input) {
3890
+ const cfg = loadConfig();
3891
+ const picked = pickProject(cfg, input.project_id);
3892
+ if (!picked) {
3893
+ return {
3894
+ error: {
3895
+ code: "no_project",
3896
+ message: "No project_id given and no default project set.",
3897
+ next_action: "Pass project_id, or call agentry_create_project.",
3898
+ },
3899
+ };
3900
+ }
3901
+ const body = { role: input.role };
3902
+ if (input.email)
3903
+ body.email = input.email;
3904
+ return await api.inviteTeammate(cfg, picked.id, body);
3905
+ }
3906
+ async function handleListMembers(projectId) {
3907
+ const cfg = loadConfig();
3908
+ const picked = pickProject(cfg, projectId);
3909
+ if (!picked) {
3910
+ return {
3911
+ error: {
3912
+ code: "no_project",
3913
+ message: "No project_id given and no default project set.",
3914
+ next_action: "Pass project_id.",
3915
+ },
3916
+ };
3917
+ }
3918
+ return await api.listMembers(cfg, picked.id);
3919
+ }
3920
+ async function handleRemoveMember(input) {
3921
+ const cfg = loadConfig();
3922
+ const picked = pickProject(cfg, input.project_id);
3923
+ if (!picked) {
3924
+ return {
3925
+ error: {
3926
+ code: "no_project",
3927
+ message: "No project_id given and no default project set.",
3928
+ next_action: "Pass project_id.",
3929
+ },
3930
+ };
3931
+ }
3932
+ if (!input.user_id) {
3933
+ return {
3934
+ error: {
3935
+ code: "invalid_payload",
3936
+ message: "user_id is required.",
3937
+ next_action: "Call agentry_list_members to find the user_id to remove.",
3938
+ },
3939
+ };
3940
+ }
3941
+ return await api.removeMember(cfg, picked.id, input.user_id);
3942
+ }
3505
3943
  //# sourceMappingURL=tools.js.map