@agentrysh/mcp 0.0.10 → 0.0.12

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
@@ -57,6 +57,70 @@ export const TOOL_DESCRIPTORS = [
57
57
  additionalProperties: false,
58
58
  },
59
59
  },
60
+ {
61
+ name: "agentry_publish_query",
62
+ description: "Mint a public-fetchable URL for a specific recipe + params combination. The returned " +
63
+ "URL can be embedded in a PUBLIC dashboard (your marketing site, a customer-facing " +
64
+ "metrics page, etc.) — visitors fetch the rendered query results without an account, " +
65
+ "credential, or session." +
66
+ "" +
67
+ "Auth model: the URL embeds the user's `agp_…` PUBLIC key (auto-minted at login alongside " +
68
+ "the private `agk_…`). agp_ is read-only AND can only fetch publications you explicitly " +
69
+ "created. Even if the URL leaks to the entire internet, the worst case is that the SAME " +
70
+ "(recipe + params) query you already chose to make public can be re-fetched. No other " +
71
+ "data is reachable." +
72
+ "" +
73
+ "Workflow: ASK the user what to publish (which metric, which params), call this tool, " +
74
+ "embed the returned `public_url?key=<agp_…>` in their page. CORS is open. Revoke with " +
75
+ "agentry_revoke_publication.",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ project_id: { type: "string" },
80
+ recipe_id: {
81
+ type: "string",
82
+ description: "ID from agentry_list_recipes. Bound to this publication permanently — to change " +
83
+ "the recipe, revoke + republish.",
84
+ },
85
+ params: {
86
+ type: "object",
87
+ description: "Recipe params (matches recipe.params schema). Bound to this publication.",
88
+ },
89
+ description: {
90
+ type: "string",
91
+ description: "What this dashboard widget shows — for your own future reference in " +
92
+ "agentry_list_publications.",
93
+ },
94
+ },
95
+ required: ["recipe_id"],
96
+ additionalProperties: false,
97
+ },
98
+ },
99
+ {
100
+ name: "agentry_list_publications",
101
+ description: "List active public-fetchable query publications for this project. Returns each one's " +
102
+ "public_url + last_used_at. Use this to audit what's currently exposed publicly.",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: { project_id: { type: "string" } },
106
+ additionalProperties: false,
107
+ },
108
+ },
109
+ {
110
+ name: "agentry_revoke_publication",
111
+ description: "Revoke a public-fetchable query publication. The public_url will start returning 410 " +
112
+ "(Gone). Use when the dashboard widget is decommissioned or the embedded URL leaks " +
113
+ "somewhere unintended.",
114
+ inputSchema: {
115
+ type: "object",
116
+ properties: {
117
+ project_id: { type: "string" },
118
+ publication_id: { type: "string" },
119
+ },
120
+ required: ["publication_id"],
121
+ additionalProperties: false,
122
+ },
123
+ },
60
124
  {
61
125
  name: "agentry_configure_session_replay",
62
126
  description: "Enable / disable / customize PostHog session replay for the user's project. " +
@@ -122,20 +186,283 @@ export const TOOL_DESCRIPTORS = [
122
186
  {
123
187
  name: "agentry_session_replay_status",
124
188
  description: "Get the current session-replay configuration for the user's project AND a deep-link " +
125
- "URL into PostHog's Replay tab for viewing recordings. " +
189
+ "URL into PostHog's Replay tab. For programmatic recording retrieval (returning the " +
190
+ "list of recordings or player URLs), call agentry_list_session_replays / " +
191
+ "agentry_get_session_replay — both work as of 2026-05-15 (master Personal API Key " +
192
+ "now has session_recording:read scope).",
193
+ inputSchema: {
194
+ type: "object",
195
+ properties: {
196
+ project_id: { type: "string" },
197
+ },
198
+ additionalProperties: false,
199
+ },
200
+ },
201
+ // ---------------------------------------------------------------------------
202
+ // PostHog per-user-team CRUD: feature flags, cohorts, surveys, session
203
+ // recordings retrieval. Each wraps an /api/projects/<team_id>/<resource>/
204
+ // endpoint on the user's per-user PostHog team. Master Personal API Key
205
+ // has `*` scope as of 2026-05-15 so all of these are live.
206
+ // ---------------------------------------------------------------------------
207
+ {
208
+ name: "agentry_list_feature_flags",
209
+ description: "List feature flags on the user's PostHog project. Use this to inspect what flags " +
210
+ "exist before creating new ones, or to find a flag's id to update/delete it. Each " +
211
+ "flag has: id, key, name, active, filters (rollout rules), created_at.",
212
+ inputSchema: {
213
+ type: "object",
214
+ properties: {
215
+ project_id: { type: "string" },
216
+ limit: { type: "number", description: "Max flags to return (default 100, max 200)." },
217
+ },
218
+ additionalProperties: false,
219
+ },
220
+ },
221
+ {
222
+ name: "agentry_get_feature_flag",
223
+ description: "Fetch a single feature flag's full configuration (filters, variants, conditions).",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ project_id: { type: "string" },
228
+ flag_id: {
229
+ type: "string",
230
+ description: "Numeric id from agentry_list_feature_flags (NOT the flag's key string).",
231
+ },
232
+ },
233
+ required: ["flag_id"],
234
+ additionalProperties: false,
235
+ },
236
+ },
237
+ {
238
+ name: "agentry_create_feature_flag",
239
+ description: "Create a new feature flag. Two shapes supported:" +
240
+ "" +
241
+ " - Simple: pass {key, name?, active?, rollout_percentage?} — single-group filter at " +
242
+ " the given % rollout (0-100). Default: active=true, rollout=100." +
243
+ "" +
244
+ " - Advanced: pass {key, name?, active?, filters} — `filters` is PostHog's raw filter " +
245
+ " object ({groups: [{properties: [...], rollout_percentage}], multivariate?, …}). " +
246
+ " Use this for property-targeted rules, multi-variant flags, or cohort-scoped flags.",
247
+ inputSchema: {
248
+ type: "object",
249
+ properties: {
250
+ project_id: { type: "string" },
251
+ key: { type: "string", description: "Stable slug used in code (e.g. `new-checkout-flow`)." },
252
+ name: { type: "string", description: "Human label (defaults to key)." },
253
+ active: { type: "boolean", description: "Default true." },
254
+ rollout_percentage: { type: "number", description: "0–100 (simple shape)." },
255
+ filters: { type: "object", description: "Raw PostHog filter object (advanced shape)." },
256
+ },
257
+ required: ["key"],
258
+ additionalProperties: false,
259
+ },
260
+ },
261
+ {
262
+ name: "agentry_update_feature_flag",
263
+ description: "Patch a feature flag. Toggle on/off via {active}, change rollout via {rollout_percentage}, " +
264
+ "rename via {name}, or replace targeting with {filters}.",
265
+ inputSchema: {
266
+ type: "object",
267
+ properties: {
268
+ project_id: { type: "string" },
269
+ flag_id: { type: "string" },
270
+ active: { type: "boolean" },
271
+ name: { type: "string" },
272
+ rollout_percentage: { type: "number" },
273
+ filters: { type: "object" },
274
+ },
275
+ required: ["flag_id"],
276
+ additionalProperties: false,
277
+ },
278
+ },
279
+ {
280
+ name: "agentry_delete_feature_flag",
281
+ description: "Soft-delete a feature flag (sets deleted=true; recoverable in PostHog's web UI).",
282
+ inputSchema: {
283
+ type: "object",
284
+ properties: {
285
+ project_id: { type: "string" },
286
+ flag_id: { type: "string" },
287
+ },
288
+ required: ["flag_id"],
289
+ additionalProperties: false,
290
+ },
291
+ },
292
+ {
293
+ name: "agentry_list_cohorts",
294
+ description: "List cohorts (dynamic user segments) on the user's PostHog project. Cohorts are " +
295
+ "groups of users matching a filter (e.g. 'users who did event X in last 30 days'). " +
296
+ "Used by feature-flag targeting and HogQL queries.",
297
+ inputSchema: {
298
+ type: "object",
299
+ properties: {
300
+ project_id: { type: "string" },
301
+ },
302
+ additionalProperties: false,
303
+ },
304
+ },
305
+ {
306
+ name: "agentry_get_cohort",
307
+ description: "Fetch a single cohort's definition (filters, last calculation time, count).",
308
+ inputSchema: {
309
+ type: "object",
310
+ properties: {
311
+ project_id: { type: "string" },
312
+ cohort_id: { type: "string" },
313
+ },
314
+ required: ["cohort_id"],
315
+ additionalProperties: false,
316
+ },
317
+ },
318
+ {
319
+ name: "agentry_create_cohort",
320
+ description: "Create a cohort. Two shapes supported:" +
321
+ "" +
322
+ " - Simple: {name, event, days?} — users who fired `event` at least once in the last " +
323
+ " N days (days defaults to 30)." +
324
+ "" +
325
+ " - Advanced: {name, groups} — `groups` is PostHog's raw cohort-group filter array, " +
326
+ " for property-targeted or multi-condition cohorts.",
327
+ inputSchema: {
328
+ type: "object",
329
+ properties: {
330
+ project_id: { type: "string" },
331
+ name: { type: "string" },
332
+ event: { type: "string", description: "Event name (simple shape)." },
333
+ days: { type: "number", description: "Lookback window in days (default 30, simple shape)." },
334
+ groups: { type: "array", description: "Raw PostHog cohort-group filters (advanced shape)." },
335
+ },
336
+ required: ["name"],
337
+ additionalProperties: false,
338
+ },
339
+ },
340
+ {
341
+ name: "agentry_delete_cohort",
342
+ description: "Soft-delete a cohort (recoverable in PostHog's web UI).",
343
+ inputSchema: {
344
+ type: "object",
345
+ properties: {
346
+ project_id: { type: "string" },
347
+ cohort_id: { type: "string" },
348
+ },
349
+ required: ["cohort_id"],
350
+ additionalProperties: false,
351
+ },
352
+ },
353
+ {
354
+ name: "agentry_list_surveys",
355
+ description: "List surveys on the user's PostHog project. A survey is a popup/banner/widget the " +
356
+ "customer's PostHog-JS-enabled site renders to ask users a question (NPS, CSAT, " +
357
+ "free-text). Responses land as `survey sent` events.",
358
+ inputSchema: {
359
+ type: "object",
360
+ properties: {
361
+ project_id: { type: "string" },
362
+ },
363
+ additionalProperties: false,
364
+ },
365
+ },
366
+ {
367
+ name: "agentry_get_survey",
368
+ description: "Fetch a single survey's definition. To read responses, query HogQL: " +
369
+ "`SELECT properties FROM events WHERE event = 'survey sent' AND properties.\\$survey_id = '<id>'`.",
370
+ inputSchema: {
371
+ type: "object",
372
+ properties: {
373
+ project_id: { type: "string" },
374
+ survey_id: { type: "string" },
375
+ },
376
+ required: ["survey_id"],
377
+ additionalProperties: false,
378
+ },
379
+ },
380
+ {
381
+ name: "agentry_create_survey",
382
+ description: "Create a survey. Quick shape: {name, question, question_type?} for a single-question " +
383
+ "popover (question_type defaults to 'open' — also 'rating', 'single_choice', " +
384
+ "'multiple_choice', 'link'). Advanced: pass {name, questions: [...]} for multi-question. " +
126
385
  "" +
127
- "agentry's MCP can't currently retrieve recording bytes programmatically the master " +
128
- "Personal API Key needs `session_recording:read` scope expanded. As an interim, this " +
129
- "tool returns `web_ui_url` — paste into a browser to view recordings. Coming: " +
130
- "agentry_get_session_replays(case_id) returning playable URLs once scope is widened.",
386
+ "Surveys are created in DRAFT pass start_date (ISO) to launch immediately, or call " +
387
+ "PATCH /surveys/:id with {start_date} later.",
131
388
  inputSchema: {
132
389
  type: "object",
133
390
  properties: {
134
391
  project_id: { type: "string" },
392
+ name: { type: "string" },
393
+ type: {
394
+ type: "string",
395
+ enum: ["popover", "widget", "button", "api"],
396
+ description: "Render style. Default 'popover'.",
397
+ },
398
+ question: { type: "string", description: "Single-question quick shape." },
399
+ question_type: {
400
+ type: "string",
401
+ enum: ["open", "rating", "single_choice", "multiple_choice", "link"],
402
+ },
403
+ questions: { type: "array", description: "Multi-question array (advanced shape)." },
404
+ description: { type: "string" },
405
+ linked_flag_id: { type: "number" },
406
+ targeting_flag_id: { type: "number" },
407
+ conditions: { type: "object" },
408
+ appearance: { type: "object" },
409
+ start_date: { type: "string", description: "ISO timestamp to launch immediately." },
410
+ },
411
+ required: ["name"],
412
+ additionalProperties: false,
413
+ },
414
+ },
415
+ {
416
+ name: "agentry_delete_survey",
417
+ description: "Delete a survey (PostHog hard-deletes survey rows on DELETE).",
418
+ inputSchema: {
419
+ type: "object",
420
+ properties: {
421
+ project_id: { type: "string" },
422
+ survey_id: { type: "string" },
423
+ },
424
+ required: ["survey_id"],
425
+ additionalProperties: false,
426
+ },
427
+ },
428
+ {
429
+ name: "agentry_list_session_replays",
430
+ description: "List session recordings on the user's PostHog project. Use this to find replays " +
431
+ "linked to a user (distinct_id) or a time range — e.g. when investigating an error, " +
432
+ "filter by the affected user's distinct_id to find the recording leading up to it. " +
433
+ "" +
434
+ "Note: session replay must be ENABLED first (call agentry_configure_session_replay). " +
435
+ "Recordings are only captured while a strategy is on.",
436
+ inputSchema: {
437
+ type: "object",
438
+ properties: {
439
+ project_id: { type: "string" },
440
+ distinct_id: {
441
+ type: "string",
442
+ description: "Filter to one user (e.g. from a case's affected_users).",
443
+ },
444
+ date_from: { type: "string", description: "ISO timestamp lower bound." },
445
+ date_to: { type: "string", description: "ISO timestamp upper bound." },
446
+ limit: { type: "number", description: "Max recordings (default 25, max 100)." },
135
447
  },
136
448
  additionalProperties: false,
137
449
  },
138
450
  },
451
+ {
452
+ name: "agentry_get_session_replay",
453
+ 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
+ inputSchema: {
457
+ type: "object",
458
+ properties: {
459
+ project_id: { type: "string" },
460
+ replay_id: { type: "string" },
461
+ },
462
+ required: ["replay_id"],
463
+ additionalProperties: false,
464
+ },
465
+ },
139
466
  {
140
467
  name: "agentry_repair_analytics",
141
468
  description: "Re-attempt PostHog provisioning for the authenticated user. Idempotent — if the user " +
@@ -910,6 +1237,12 @@ function persistKeyResponse(cfg, resp) {
910
1237
  const next = {
911
1238
  ...cfg,
912
1239
  api_key: resp.api_key,
1240
+ // Persist the agp_ public key too (returned by login response). Only
1241
+ // overwrite if the new response provides one; rotation/repair paths
1242
+ // don't necessarily return public keys.
1243
+ ...(resp.public_api_key && !resp.public_api_key.startsWith("(redacted")
1244
+ ? { public_api_key: resp.public_api_key }
1245
+ : {}),
913
1246
  };
914
1247
  saveConfig(next);
915
1248
  return next;
@@ -980,6 +1313,22 @@ export async function dispatchTool(name, args) {
980
1313
  return await handleRotateKey();
981
1314
  case "agentry_repair_analytics":
982
1315
  return await handleRepairAnalytics();
1316
+ case "agentry_publish_query":
1317
+ return await handlePublishQuery({
1318
+ project_id: a.project_id ? String(a.project_id) : undefined,
1319
+ recipe_id: String(a.recipe_id ?? ""),
1320
+ params: a.params && typeof a.params === "object"
1321
+ ? a.params
1322
+ : undefined,
1323
+ description: a.description ? String(a.description) : undefined,
1324
+ });
1325
+ case "agentry_list_publications":
1326
+ return await handleListPublications(a.project_id ? String(a.project_id) : undefined);
1327
+ case "agentry_revoke_publication":
1328
+ return await handleRevokePublication({
1329
+ project_id: a.project_id ? String(a.project_id) : undefined,
1330
+ publication_id: String(a.publication_id ?? ""),
1331
+ });
983
1332
  case "agentry_configure_session_replay":
984
1333
  return await handleConfigureSessionReplay({
985
1334
  project_id: a.project_id ? String(a.project_id) : undefined,
@@ -993,6 +1342,101 @@ export async function dispatchTool(name, args) {
993
1342
  });
994
1343
  case "agentry_session_replay_status":
995
1344
  return await handleSessionReplayStatus(a.project_id ? String(a.project_id) : undefined);
1345
+ case "agentry_list_feature_flags":
1346
+ return await handleListFeatureFlags({
1347
+ project_id: a.project_id ? String(a.project_id) : undefined,
1348
+ limit: typeof a.limit === "number" ? a.limit : undefined,
1349
+ });
1350
+ case "agentry_get_feature_flag":
1351
+ return await handleGetFeatureFlag({
1352
+ project_id: a.project_id ? String(a.project_id) : undefined,
1353
+ flag_id: String(a.flag_id ?? ""),
1354
+ });
1355
+ case "agentry_create_feature_flag":
1356
+ return await handleCreateFeatureFlag({
1357
+ project_id: a.project_id ? String(a.project_id) : undefined,
1358
+ key: String(a.key ?? ""),
1359
+ name: a.name ? String(a.name) : undefined,
1360
+ active: typeof a.active === "boolean" ? a.active : undefined,
1361
+ rollout_percentage: typeof a.rollout_percentage === "number" ? a.rollout_percentage : undefined,
1362
+ filters: a.filters && typeof a.filters === "object" ? a.filters : undefined,
1363
+ });
1364
+ case "agentry_update_feature_flag":
1365
+ return await handleUpdateFeatureFlag({
1366
+ project_id: a.project_id ? String(a.project_id) : undefined,
1367
+ flag_id: String(a.flag_id ?? ""),
1368
+ active: typeof a.active === "boolean" ? a.active : undefined,
1369
+ name: a.name ? String(a.name) : undefined,
1370
+ rollout_percentage: typeof a.rollout_percentage === "number" ? a.rollout_percentage : undefined,
1371
+ filters: a.filters && typeof a.filters === "object" ? a.filters : undefined,
1372
+ });
1373
+ case "agentry_delete_feature_flag":
1374
+ return await handleDeleteFeatureFlag({
1375
+ project_id: a.project_id ? String(a.project_id) : undefined,
1376
+ flag_id: String(a.flag_id ?? ""),
1377
+ });
1378
+ case "agentry_list_cohorts":
1379
+ return await handleListCohorts(a.project_id ? String(a.project_id) : undefined);
1380
+ case "agentry_get_cohort":
1381
+ return await handleGetCohort({
1382
+ project_id: a.project_id ? String(a.project_id) : undefined,
1383
+ cohort_id: String(a.cohort_id ?? ""),
1384
+ });
1385
+ case "agentry_create_cohort":
1386
+ return await handleCreateCohort({
1387
+ project_id: a.project_id ? String(a.project_id) : undefined,
1388
+ name: String(a.name ?? ""),
1389
+ event: a.event ? String(a.event) : undefined,
1390
+ days: typeof a.days === "number" ? a.days : undefined,
1391
+ groups: Array.isArray(a.groups) ? a.groups : undefined,
1392
+ });
1393
+ case "agentry_delete_cohort":
1394
+ return await handleDeleteCohort({
1395
+ project_id: a.project_id ? String(a.project_id) : undefined,
1396
+ cohort_id: String(a.cohort_id ?? ""),
1397
+ });
1398
+ case "agentry_list_surveys":
1399
+ return await handleListSurveys(a.project_id ? String(a.project_id) : undefined);
1400
+ case "agentry_get_survey":
1401
+ return await handleGetSurvey({
1402
+ project_id: a.project_id ? String(a.project_id) : undefined,
1403
+ survey_id: String(a.survey_id ?? ""),
1404
+ });
1405
+ case "agentry_create_survey":
1406
+ return await handleCreateSurvey({
1407
+ project_id: a.project_id ? String(a.project_id) : undefined,
1408
+ name: String(a.name ?? ""),
1409
+ type: a.type ? String(a.type) : undefined,
1410
+ question: a.question ? String(a.question) : undefined,
1411
+ question_type: a.question_type
1412
+ ? String(a.question_type)
1413
+ : undefined,
1414
+ questions: Array.isArray(a.questions) ? a.questions : undefined,
1415
+ description: a.description ? String(a.description) : undefined,
1416
+ linked_flag_id: typeof a.linked_flag_id === "number" ? a.linked_flag_id : undefined,
1417
+ targeting_flag_id: typeof a.targeting_flag_id === "number" ? a.targeting_flag_id : undefined,
1418
+ conditions: a.conditions && typeof a.conditions === "object" ? a.conditions : undefined,
1419
+ appearance: a.appearance && typeof a.appearance === "object" ? a.appearance : undefined,
1420
+ start_date: a.start_date ? String(a.start_date) : undefined,
1421
+ });
1422
+ case "agentry_delete_survey":
1423
+ return await handleDeleteSurvey({
1424
+ project_id: a.project_id ? String(a.project_id) : undefined,
1425
+ survey_id: String(a.survey_id ?? ""),
1426
+ });
1427
+ case "agentry_list_session_replays":
1428
+ return await handleListSessionReplays({
1429
+ project_id: a.project_id ? String(a.project_id) : undefined,
1430
+ distinct_id: a.distinct_id ? String(a.distinct_id) : undefined,
1431
+ date_from: a.date_from ? String(a.date_from) : undefined,
1432
+ date_to: a.date_to ? String(a.date_to) : undefined,
1433
+ limit: typeof a.limit === "number" ? a.limit : undefined,
1434
+ });
1435
+ case "agentry_get_session_replay":
1436
+ return await handleGetSessionReplay({
1437
+ project_id: a.project_id ? String(a.project_id) : undefined,
1438
+ replay_id: String(a.replay_id ?? ""),
1439
+ });
996
1440
  case "agentry_list_projects":
997
1441
  return await handleListProjects();
998
1442
  case "agentry_create_project":
@@ -1391,6 +1835,65 @@ async function handleRepairAnalytics() {
1391
1835
  };
1392
1836
  }
1393
1837
  }
1838
+ async function handlePublishQuery(input) {
1839
+ const cfg = loadConfig();
1840
+ const picked = pickProject(cfg, input.project_id);
1841
+ if (!picked) {
1842
+ return {
1843
+ error: {
1844
+ code: "no_project",
1845
+ message: "No project_id given and no default project set.",
1846
+ next_action: "Pass project_id, or call agentry_create_project.",
1847
+ },
1848
+ };
1849
+ }
1850
+ const resp = await api.publishQuery(cfg, picked.id, {
1851
+ recipe_id: input.recipe_id,
1852
+ params: input.params,
1853
+ description: input.description,
1854
+ });
1855
+ // Help the agent: fetch the user's agp_ key from the local config and
1856
+ // append it as a query param to the public URL.
1857
+ const agp = cfg.public_api_key ?? null;
1858
+ const embeddableUrl = agp ? `${resp.public_url}?key=${agp}` : resp.public_url;
1859
+ return {
1860
+ ...resp,
1861
+ embeddable_url: embeddableUrl,
1862
+ next_action: agp
1863
+ ? "Embed embeddable_url in the public dashboard. CORS is open. Revoke any time with " +
1864
+ "agentry_revoke_publication if it's leaked or no longer needed."
1865
+ : "Your agp_ public key isn't cached locally yet — call agentry_login to mint it. Once " +
1866
+ "minted, paste it as ?key=<agp_…> on the public_url to embed.",
1867
+ };
1868
+ }
1869
+ async function handleListPublications(projectId) {
1870
+ const cfg = loadConfig();
1871
+ const picked = pickProject(cfg, projectId);
1872
+ if (!picked) {
1873
+ return {
1874
+ error: {
1875
+ code: "no_project",
1876
+ message: "No project_id given and no default project set.",
1877
+ next_action: "Pass project_id.",
1878
+ },
1879
+ };
1880
+ }
1881
+ return await api.listPublications(cfg, picked.id);
1882
+ }
1883
+ async function handleRevokePublication(input) {
1884
+ const cfg = loadConfig();
1885
+ const picked = pickProject(cfg, input.project_id);
1886
+ if (!picked) {
1887
+ return {
1888
+ error: {
1889
+ code: "no_project",
1890
+ message: "No project_id given and no default project set.",
1891
+ next_action: "Pass project_id.",
1892
+ },
1893
+ };
1894
+ }
1895
+ return await api.revokePublication(cfg, picked.id, input.publication_id);
1896
+ }
1394
1897
  async function handleConfigureSessionReplay(input) {
1395
1898
  const cfg = loadConfig();
1396
1899
  const picked = pickProject(cfg, input.project_id);
@@ -1425,6 +1928,213 @@ async function handleSessionReplayStatus(projectId) {
1425
1928
  }
1426
1929
  return await api.getSessionReplayStatus(cfg, picked.id);
1427
1930
  }
1931
+ // ---------------------------------------------------------------------------
1932
+ // PostHog per-user-team CRUD handlers.
1933
+ // Shared helper for the "no project" error envelope to keep handlers terse.
1934
+ // ---------------------------------------------------------------------------
1935
+ function pickOrError(cfg, projectId) {
1936
+ const picked = pickProject(cfg, projectId);
1937
+ if (!picked) {
1938
+ return {
1939
+ ok: false,
1940
+ error: {
1941
+ error: {
1942
+ code: "no_project",
1943
+ message: "No project_id given and no default project set.",
1944
+ next_action: "Pass project_id, or call agentry_create_project.",
1945
+ },
1946
+ },
1947
+ };
1948
+ }
1949
+ return { ok: true, id: picked.id };
1950
+ }
1951
+ async function handleListFeatureFlags(input) {
1952
+ const cfg = loadConfig();
1953
+ const r = pickOrError(cfg, input.project_id);
1954
+ if (!r.ok)
1955
+ return r.error;
1956
+ return await api.listFeatureFlags(cfg, r.id, { limit: input.limit });
1957
+ }
1958
+ async function handleGetFeatureFlag(input) {
1959
+ if (!input.flag_id) {
1960
+ return { error: { code: "invalid_payload", message: "flag_id is required.", next_action: "Pass flag_id (numeric, from agentry_list_feature_flags)." } };
1961
+ }
1962
+ const cfg = loadConfig();
1963
+ const r = pickOrError(cfg, input.project_id);
1964
+ if (!r.ok)
1965
+ return r.error;
1966
+ return await api.getFeatureFlag(cfg, r.id, input.flag_id);
1967
+ }
1968
+ async function handleCreateFeatureFlag(input) {
1969
+ if (!input.key) {
1970
+ return { error: { code: "invalid_payload", message: "key is required.", next_action: "Pass key (slug, e.g. 'new-checkout-flow')." } };
1971
+ }
1972
+ const cfg = loadConfig();
1973
+ const r = pickOrError(cfg, input.project_id);
1974
+ if (!r.ok)
1975
+ return r.error;
1976
+ return await api.createFeatureFlag(cfg, r.id, {
1977
+ key: input.key,
1978
+ name: input.name,
1979
+ active: input.active,
1980
+ rollout_percentage: input.rollout_percentage,
1981
+ filters: input.filters,
1982
+ });
1983
+ }
1984
+ async function handleUpdateFeatureFlag(input) {
1985
+ if (!input.flag_id) {
1986
+ return { error: { code: "invalid_payload", message: "flag_id is required.", next_action: "Pass flag_id (numeric)." } };
1987
+ }
1988
+ const cfg = loadConfig();
1989
+ const r = pickOrError(cfg, input.project_id);
1990
+ if (!r.ok)
1991
+ return r.error;
1992
+ return await api.updateFeatureFlag(cfg, r.id, input.flag_id, {
1993
+ active: input.active,
1994
+ name: input.name,
1995
+ rollout_percentage: input.rollout_percentage,
1996
+ filters: input.filters,
1997
+ });
1998
+ }
1999
+ async function handleDeleteFeatureFlag(input) {
2000
+ if (!input.flag_id) {
2001
+ return { error: { code: "invalid_payload", message: "flag_id is required.", next_action: "Pass flag_id (numeric)." } };
2002
+ }
2003
+ const cfg = loadConfig();
2004
+ const r = pickOrError(cfg, input.project_id);
2005
+ if (!r.ok)
2006
+ return r.error;
2007
+ return await api.deleteFeatureFlag(cfg, r.id, input.flag_id);
2008
+ }
2009
+ async function handleListCohorts(projectId) {
2010
+ const cfg = loadConfig();
2011
+ const r = pickOrError(cfg, projectId);
2012
+ if (!r.ok)
2013
+ return r.error;
2014
+ return await api.listCohorts(cfg, r.id);
2015
+ }
2016
+ async function handleGetCohort(input) {
2017
+ if (!input.cohort_id) {
2018
+ return { error: { code: "invalid_payload", message: "cohort_id is required.", next_action: "Pass cohort_id (numeric)." } };
2019
+ }
2020
+ const cfg = loadConfig();
2021
+ const r = pickOrError(cfg, input.project_id);
2022
+ if (!r.ok)
2023
+ return r.error;
2024
+ return await api.getCohort(cfg, r.id, input.cohort_id);
2025
+ }
2026
+ async function handleCreateCohort(input) {
2027
+ if (!input.name) {
2028
+ return { error: { code: "invalid_payload", message: "name is required.", next_action: "Pass cohort name." } };
2029
+ }
2030
+ if (!input.event && (!input.groups || input.groups.length === 0)) {
2031
+ return {
2032
+ error: {
2033
+ code: "invalid_payload",
2034
+ message: "Cohort body must include 'event' (simple shape) or 'groups' (advanced shape).",
2035
+ next_action: "Pass event='signup_completed' (last N days) or pass groups: [...PostHog filter format].",
2036
+ },
2037
+ };
2038
+ }
2039
+ const cfg = loadConfig();
2040
+ const r = pickOrError(cfg, input.project_id);
2041
+ if (!r.ok)
2042
+ return r.error;
2043
+ const body = input.groups
2044
+ ? { name: input.name, groups: input.groups }
2045
+ : { name: input.name, event: input.event, days: input.days };
2046
+ return await api.createCohort(cfg, r.id, body);
2047
+ }
2048
+ async function handleDeleteCohort(input) {
2049
+ if (!input.cohort_id) {
2050
+ return { error: { code: "invalid_payload", message: "cohort_id is required.", next_action: "Pass cohort_id (numeric)." } };
2051
+ }
2052
+ const cfg = loadConfig();
2053
+ const r = pickOrError(cfg, input.project_id);
2054
+ if (!r.ok)
2055
+ return r.error;
2056
+ return await api.deleteCohort(cfg, r.id, input.cohort_id);
2057
+ }
2058
+ async function handleListSurveys(projectId) {
2059
+ const cfg = loadConfig();
2060
+ const r = pickOrError(cfg, projectId);
2061
+ if (!r.ok)
2062
+ return r.error;
2063
+ return await api.listSurveys(cfg, r.id);
2064
+ }
2065
+ async function handleGetSurvey(input) {
2066
+ if (!input.survey_id) {
2067
+ return { error: { code: "invalid_payload", message: "survey_id is required.", next_action: "Pass survey_id." } };
2068
+ }
2069
+ const cfg = loadConfig();
2070
+ const r = pickOrError(cfg, input.project_id);
2071
+ if (!r.ok)
2072
+ return r.error;
2073
+ return await api.getSurvey(cfg, r.id, input.survey_id);
2074
+ }
2075
+ async function handleCreateSurvey(input) {
2076
+ if (!input.name) {
2077
+ return { error: { code: "invalid_payload", message: "name is required.", next_action: "Pass survey name." } };
2078
+ }
2079
+ if (!input.question && (!input.questions || input.questions.length === 0)) {
2080
+ return {
2081
+ error: {
2082
+ code: "invalid_payload",
2083
+ message: "Survey must include 'question' (simple) or 'questions' (multi).",
2084
+ next_action: "Pass question='How likely are you to recommend us?' OR questions: [{type, question}, ...]",
2085
+ },
2086
+ };
2087
+ }
2088
+ const cfg = loadConfig();
2089
+ const r = pickOrError(cfg, input.project_id);
2090
+ if (!r.ok)
2091
+ return r.error;
2092
+ return await api.createSurvey(cfg, r.id, {
2093
+ name: input.name,
2094
+ type: input.type,
2095
+ question: input.question,
2096
+ question_type: input.question_type,
2097
+ questions: input.questions,
2098
+ description: input.description,
2099
+ linked_flag_id: input.linked_flag_id,
2100
+ targeting_flag_id: input.targeting_flag_id,
2101
+ conditions: input.conditions,
2102
+ appearance: input.appearance,
2103
+ start_date: input.start_date,
2104
+ });
2105
+ }
2106
+ async function handleDeleteSurvey(input) {
2107
+ if (!input.survey_id) {
2108
+ return { error: { code: "invalid_payload", message: "survey_id is required.", next_action: "Pass survey_id." } };
2109
+ }
2110
+ const cfg = loadConfig();
2111
+ const r = pickOrError(cfg, input.project_id);
2112
+ if (!r.ok)
2113
+ return r.error;
2114
+ return await api.deleteSurvey(cfg, r.id, input.survey_id);
2115
+ }
2116
+ async function handleListSessionReplays(input) {
2117
+ const cfg = loadConfig();
2118
+ const r = pickOrError(cfg, input.project_id);
2119
+ if (!r.ok)
2120
+ return r.error;
2121
+ return await api.listSessionReplays(cfg, r.id, {
2122
+ distinctId: input.distinct_id,
2123
+ dateFrom: input.date_from,
2124
+ dateTo: input.date_to,
2125
+ limit: input.limit,
2126
+ });
2127
+ }
2128
+ async function handleGetSessionReplay(input) {
2129
+ if (!input.replay_id) {
2130
+ return { error: { code: "invalid_payload", message: "replay_id is required.", next_action: "Pass replay_id from agentry_list_session_replays." } };
2131
+ }
2132
+ const cfg = loadConfig();
2133
+ const r = pickOrError(cfg, input.project_id);
2134
+ if (!r.ok)
2135
+ return r.error;
2136
+ return await api.getSessionReplay(cfg, r.id, input.replay_id);
2137
+ }
1428
2138
  async function handleListProjects() {
1429
2139
  const cfg = loadConfig();
1430
2140
  if (!cfg.api_key) {