@agentrysh/mcp 0.0.9 → 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,412 @@ 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
+ },
124
+ {
125
+ name: "agentry_configure_session_replay",
126
+ description: "Enable / disable / customize PostHog session replay for the user's project. " +
127
+ "Session replay is OFF by default — agentry users opt in. Recordings eat significant " +
128
+ "storage, so pick a strategy that matches the customer's debugging needs:" +
129
+ "" +
130
+ " - 'off' — disable. No new sessions recorded." +
131
+ " - 'all' — 100% sampling. Heavy storage cost; only pick if the customer's " +
132
+ " traffic is low AND they want every session recorded." +
133
+ " - 'sampled' — random sample at sample_rate (0–1, default 0.1 = 10%). Good " +
134
+ " balance of coverage + cost for most apps." +
135
+ " - 'url_scoped' — record only sessions that hit specific URLs (e.g. /checkout/*). " +
136
+ " Pass url_triggers: [{url, matching}]. Best for funnel debugging." +
137
+ " - 'errors_only' — record nothing by default, but the customer's app calls " +
138
+ " `posthog.startSessionRecording()` from captureError (or wherever " +
139
+ " they want). Cheapest; recording starts JUST IN TIME when something " +
140
+ " breaks. After picking this, ALSO wire the call into the customer's " +
141
+ " agentry helper (drop_in_helper from agentry_install_guide)." +
142
+ "" +
143
+ "Workflow: ASK THE USER first which strategy they want and what retention. Then call this " +
144
+ "tool with the answer. If 'errors_only', also edit the agentry helper to call " +
145
+ "posthog.startSessionRecording() inside captureError. After this, recordings show up in " +
146
+ "PostHog's Replay tab — call agentry_session_replay_status to get the deep-link URL.",
147
+ inputSchema: {
148
+ type: "object",
149
+ properties: {
150
+ project_id: { type: "string", description: "Project id. Defaults to local default." },
151
+ strategy: {
152
+ type: "string",
153
+ enum: ["off", "all", "sampled", "url_scoped", "errors_only"],
154
+ },
155
+ sample_rate: {
156
+ type: "number",
157
+ description: "0–1; only used when strategy='sampled'. Default 0.1 (10%).",
158
+ },
159
+ retention_days: {
160
+ type: "number",
161
+ description: "How many days to retain recordings. Storage-bounded. 30 / 90 / 365.",
162
+ },
163
+ min_duration_ms: {
164
+ type: "number",
165
+ description: "Drop recordings shorter than this (ms). 0 = keep all. Useful for skipping bounces. " +
166
+ "Default unset.",
167
+ },
168
+ url_triggers: {
169
+ type: "array",
170
+ items: {
171
+ type: "object",
172
+ properties: {
173
+ url: { type: "string" },
174
+ matching: { type: "string", enum: ["exact", "regex"] },
175
+ },
176
+ required: ["url"],
177
+ additionalProperties: false,
178
+ },
179
+ description: "When strategy='url_scoped': pages where recording should start.",
180
+ },
181
+ },
182
+ required: ["strategy"],
183
+ additionalProperties: false,
184
+ },
185
+ },
186
+ {
187
+ name: "agentry_session_replay_status",
188
+ description: "Get the current session-replay configuration for the user's project AND a deep-link " +
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. " +
385
+ "" +
386
+ "Surveys are created in DRAFT — pass start_date (ISO) to launch immediately, or call " +
387
+ "PATCH /surveys/:id with {start_date} later.",
388
+ inputSchema: {
389
+ type: "object",
390
+ properties: {
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)." },
447
+ },
448
+ additionalProperties: false,
449
+ },
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
+ },
60
466
  {
61
467
  name: "agentry_repair_analytics",
62
468
  description: "Re-attempt PostHog provisioning for the authenticated user. Idempotent — if the user " +
@@ -831,6 +1237,12 @@ function persistKeyResponse(cfg, resp) {
831
1237
  const next = {
832
1238
  ...cfg,
833
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
+ : {}),
834
1246
  };
835
1247
  saveConfig(next);
836
1248
  return next;
@@ -901,6 +1313,130 @@ export async function dispatchTool(name, args) {
901
1313
  return await handleRotateKey();
902
1314
  case "agentry_repair_analytics":
903
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
+ });
1332
+ case "agentry_configure_session_replay":
1333
+ return await handleConfigureSessionReplay({
1334
+ project_id: a.project_id ? String(a.project_id) : undefined,
1335
+ strategy: String(a.strategy ?? ""),
1336
+ sample_rate: typeof a.sample_rate === "number" ? a.sample_rate : undefined,
1337
+ retention_days: typeof a.retention_days === "number" ? a.retention_days : undefined,
1338
+ min_duration_ms: typeof a.min_duration_ms === "number" ? a.min_duration_ms : undefined,
1339
+ url_triggers: Array.isArray(a.url_triggers)
1340
+ ? a.url_triggers
1341
+ : undefined,
1342
+ });
1343
+ case "agentry_session_replay_status":
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
+ });
904
1440
  case "agentry_list_projects":
905
1441
  return await handleListProjects();
906
1442
  case "agentry_create_project":
@@ -1299,6 +1835,306 @@ async function handleRepairAnalytics() {
1299
1835
  };
1300
1836
  }
1301
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
+ }
1897
+ async function handleConfigureSessionReplay(input) {
1898
+ const cfg = loadConfig();
1899
+ const picked = pickProject(cfg, input.project_id);
1900
+ if (!picked) {
1901
+ return {
1902
+ error: {
1903
+ code: "no_project",
1904
+ message: "No project_id given and no default project set.",
1905
+ next_action: "Pass project_id, or call agentry_create_project.",
1906
+ },
1907
+ };
1908
+ }
1909
+ return await api.configureSessionReplay(cfg, picked.id, {
1910
+ strategy: input.strategy,
1911
+ sample_rate: input.sample_rate,
1912
+ retention_days: input.retention_days,
1913
+ min_duration_ms: input.min_duration_ms,
1914
+ url_triggers: input.url_triggers,
1915
+ });
1916
+ }
1917
+ async function handleSessionReplayStatus(projectId) {
1918
+ const cfg = loadConfig();
1919
+ const picked = pickProject(cfg, projectId);
1920
+ if (!picked) {
1921
+ return {
1922
+ error: {
1923
+ code: "no_project",
1924
+ message: "No project_id given and no default project set.",
1925
+ next_action: "Pass project_id, or call agentry_create_project.",
1926
+ },
1927
+ };
1928
+ }
1929
+ return await api.getSessionReplayStatus(cfg, picked.id);
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
+ }
1302
2138
  async function handleListProjects() {
1303
2139
  const cfg = loadConfig();
1304
2140
  if (!cfg.api_key) {