@apex-inc/mcp-server 0.27.4 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/add-journey-step.d.ts +29 -0
- package/dist/add-journey-step.d.ts.map +1 -0
- package/dist/add-journey-step.js +128 -0
- package/dist/add-journey-step.js.map +1 -0
- package/dist/adoption-compose.d.ts +7 -0
- package/dist/adoption-compose.d.ts.map +1 -1
- package/dist/adoption-compose.js +1 -0
- package/dist/adoption-compose.js.map +1 -1
- package/dist/tools.d.ts +211 -22
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +225 -44
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/skills/apex-adoption/SKILL.md +103 -81
package/dist/tools.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getPersonLabel, lowerPerson } from "./person-label.js";
|
|
|
9
9
|
import { notStartedExplanation, publishedExperimentLine, } from "./publish-communication-copy.js";
|
|
10
10
|
import { activationLiveLine, shouldRefuseActivationWiring, } from "./activation-wiring.js";
|
|
11
11
|
import { milestoneWriteBody, preflightAdoptionMilestone, } from "./adoption-compose.js";
|
|
12
|
+
import { applyAddJourneyStep } from "./add-journey-step.js";
|
|
12
13
|
const APEX = "Apex";
|
|
13
14
|
/**
|
|
14
15
|
* MOBX-006 — stable synthetic visitor id for agent-fired events.
|
|
@@ -4204,40 +4205,77 @@ Events fired through this tool carry a stable synthetic visitorId (mcp-agent-*,
|
|
|
4204
4205
|
},
|
|
4205
4206
|
},
|
|
4206
4207
|
add_journey_step: {
|
|
4207
|
-
description: `${APEX} — Append a
|
|
4208
|
+
description: `${APEX} — Append a Wait or Send to a draft Adaptive Journey. Prefer compose_adoption_milestone for a Milestone (one Wait until they finish, letters already on the arms). Wait mode duration (ISO, e.g. P1D) or until_event (trigger_contract_id + deadline_iso). Send arm event|deadline puts the letter on that Wait arm. Do not add_journey_step send on a compose-built path — use set_journey_send to point the existing Send.`,
|
|
4208
4209
|
schema: z.object({
|
|
4209
4210
|
journeyId: z.string(),
|
|
4210
4211
|
kind: z.enum(["wait", "send"]),
|
|
4211
|
-
|
|
4212
|
+
mode: z.enum(["duration", "until_event"]).optional().describe("wait only. Default duration."),
|
|
4213
|
+
durationIso: z.string().optional().describe("wait duration: ISO-8601, e.g. 'P1D' (1 day), 'PT2H' (2 hours)."),
|
|
4214
|
+
triggerContractId: z.string().optional().describe("wait until_event: the done-event trigger contract."),
|
|
4215
|
+
deadlineIso: z.string().optional().describe("wait until_event: hours or days as ISO-8601, e.g. 'PT1H', 'P3D'."),
|
|
4212
4216
|
commId: z.string().optional().describe("send only: the communication id to fire (create_communication or an existing comm)."),
|
|
4213
4217
|
commVersion: z.number().optional().describe("send only: pinned comm version (default 1)."),
|
|
4214
4218
|
channels: z.array(z.enum(["email", "in_app_push", "mobile_push", "web_push"])).optional().describe("send only: channels to fire (default ['email']). Use the SAME tokens as create_communication (email, in_app_push, mobile_push) so a comm's channels map cleanly onto the send step; web_push is send-only."),
|
|
4219
|
+
arm: z.enum(["event", "deadline"]).optional().describe("send only: put this Send on the until_event Wait's event or deadline arm."),
|
|
4215
4220
|
}),
|
|
4216
4221
|
handler: async (args) => {
|
|
4217
4222
|
try {
|
|
4218
|
-
|
|
4219
|
-
|
|
4223
|
+
const j = await apiGet(`/api/journeys/${encodeURIComponent(args.journeyId)}`);
|
|
4224
|
+
const newId = `step_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
4225
|
+
const applied = applyAddJourneyStep(j.steps ?? [], args, newId);
|
|
4226
|
+
if (!applied.ok) {
|
|
4227
|
+
return { content: [{ type: "text", text: `${APEX} ${applied.error}` }], isError: true };
|
|
4220
4228
|
}
|
|
4229
|
+
const updated = await apiPatch(`/api/journeys/${encodeURIComponent(args.journeyId)}`, { steps: applied.steps });
|
|
4230
|
+
if (!tenantOk(updated.workspaceKey))
|
|
4231
|
+
return tenantMismatch();
|
|
4232
|
+
return { content: [{ type: "text", text: `${APEX} Added ${args.kind} step ${applied.stepId} to journey ${args.journeyId}. Watch it update in the browser: ${journeyLink(args.journeyId)}` }] };
|
|
4233
|
+
}
|
|
4234
|
+
catch (err) {
|
|
4235
|
+
return { content: [{ type: "text", text: `${APEX} ${errMsg(err)}` }], isError: true };
|
|
4236
|
+
}
|
|
4237
|
+
},
|
|
4238
|
+
},
|
|
4239
|
+
set_journey_send: {
|
|
4240
|
+
description: `${APEX} — Point a draft journey's send step at a communication. Use after create_communication + edit_communication so the letter on the journey is yours, not the shared starter template. If the journey has one send, that send is updated. If it has several, pass step_id.`,
|
|
4241
|
+
schema: z.object({
|
|
4242
|
+
journeyId: z.string(),
|
|
4243
|
+
commId: z.string().describe("The communication id from create_communication."),
|
|
4244
|
+
stepId: z.string().optional().describe("Required when the journey has more than one send."),
|
|
4245
|
+
commVersion: z.number().optional().describe("Pinned comm version. Default 1."),
|
|
4246
|
+
}),
|
|
4247
|
+
handler: async (args) => {
|
|
4248
|
+
try {
|
|
4221
4249
|
const j = await apiGet(`/api/journeys/${encodeURIComponent(args.journeyId)}`);
|
|
4222
4250
|
const steps = [...(j.steps ?? [])];
|
|
4223
|
-
const
|
|
4224
|
-
|
|
4225
|
-
|
|
4251
|
+
const sends = steps.filter((s) => s.type === "send");
|
|
4252
|
+
const target = args.stepId
|
|
4253
|
+
? sends.find((s) => s.id === args.stepId)
|
|
4254
|
+
: sends.length === 1
|
|
4255
|
+
? sends[0]
|
|
4256
|
+
: undefined;
|
|
4257
|
+
if (!target) {
|
|
4258
|
+
return {
|
|
4259
|
+
content: [{
|
|
4260
|
+
type: "text",
|
|
4261
|
+
text: sends.length === 0
|
|
4262
|
+
? `${APEX} This journey has no send step. Use add_journey_step kind=send.`
|
|
4263
|
+
: `${APEX} This journey has ${sends.length} send steps. Pass step_id (${sends.map((s) => s.id).join(", ")}).`,
|
|
4264
|
+
}],
|
|
4265
|
+
isError: true,
|
|
4266
|
+
};
|
|
4226
4267
|
}
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
const newStep = args.kind === "wait"
|
|
4230
|
-
? { id: newId, type: "wait", label: "Wait", mode: "duration", duration: args.durationIso ?? "P1D", next: exitId }
|
|
4231
|
-
: { id: newId, type: "send", label: "Send", commId: args.commId, commVersion: args.commVersion ?? 1, channels: args.channels ?? ["email"], next: exitId };
|
|
4232
|
-
// Insert before the exit: rewire whatever currently points to exit.
|
|
4233
|
-
const pre = steps.find((s) => s.next === exitId);
|
|
4234
|
-
if (pre)
|
|
4235
|
-
pre.next = newId;
|
|
4236
|
-
steps.push(newStep);
|
|
4268
|
+
target.commId = args.commId;
|
|
4269
|
+
target.commVersion = args.commVersion ?? 1;
|
|
4237
4270
|
const updated = await apiPatch(`/api/journeys/${encodeURIComponent(args.journeyId)}`, { steps });
|
|
4238
4271
|
if (!tenantOk(updated.workspaceKey))
|
|
4239
4272
|
return tenantMismatch();
|
|
4240
|
-
return {
|
|
4273
|
+
return {
|
|
4274
|
+
content: [{
|
|
4275
|
+
type: "text",
|
|
4276
|
+
text: `${APEX} Send step ${String(target.id)} now uses ${args.commId}. View it: ${journeyLink(args.journeyId)}`,
|
|
4277
|
+
}],
|
|
4278
|
+
};
|
|
4241
4279
|
}
|
|
4242
4280
|
catch (err) {
|
|
4243
4281
|
return { content: [{ type: "text", text: `${APEX} ${errMsg(err)}` }], isError: true };
|
|
@@ -5448,6 +5486,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5448
5486
|
schema: z.object({
|
|
5449
5487
|
name: z.string().describe("Human label, e.g. 'Ran first report'"),
|
|
5450
5488
|
featureKey: z.string().describe("Stable feature key, e.g. 'reporting'"),
|
|
5489
|
+
actionLabel: z.string().optional().describe("Sentence verb: hasn't {action_label}. Never the raw event key."),
|
|
5451
5490
|
adoptedWhenKind: z.enum(["event", "trait"]).describe("How adoption is detected"),
|
|
5452
5491
|
eventName: z.string().optional().describe("event kind: the event whose firing means adopted"),
|
|
5453
5492
|
traitPath: z.string().optional().describe("trait kind: dotted trait path, e.g. 'plan'"),
|
|
@@ -5484,6 +5523,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5484
5523
|
schema: z.object({
|
|
5485
5524
|
milestoneId: z.string(),
|
|
5486
5525
|
name: z.string().optional(),
|
|
5526
|
+
actionLabel: z.string().optional().describe("Sentence verb: hasn't {action_label}."),
|
|
5487
5527
|
priority: z.number().optional(),
|
|
5488
5528
|
order: z.number().optional().describe("0-based sequence order. Prefer reorder_adoption_milestones to sequence the whole set."),
|
|
5489
5529
|
active: z.boolean().optional(),
|
|
@@ -5515,10 +5555,11 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5515
5555
|
},
|
|
5516
5556
|
},
|
|
5517
5557
|
compose_adoption_milestone: {
|
|
5518
|
-
description: `${APEX} — Create
|
|
5558
|
+
description: `${APEX} — Create a Milestone and stamp ONE Adaptive Journey (Trigger → Wait until they finish → Celebration and/or Nudge). scaffold="both" (default) includes both letters; "nudge" / "celebration" one letter; "none" is just-track. Unique Communication per Send — never reuse the shared catalog letters. New milestones default to enroll_mode="future". Journeys are drafts — preview, then publish_journey. A Milestone is NOT a Target.`,
|
|
5519
5559
|
schema: z.object({
|
|
5520
5560
|
name: z.string().describe("Human label, e.g. 'Started first experiment'"),
|
|
5521
5561
|
featureKey: z.string().describe("Stable feature key, e.g. 'experiments'"),
|
|
5562
|
+
actionLabel: z.string().optional().describe("Sentence verb: hasn't {action_label}."),
|
|
5522
5563
|
adoptedWhenKind: z.enum(["event", "trait"]).describe("How adoption is detected"),
|
|
5523
5564
|
eventName: z.string().optional().describe("event kind: the event whose firing means adopted"),
|
|
5524
5565
|
traitPath: z.string().optional().describe("trait kind: dotted trait path, e.g. 'plan'"),
|
|
@@ -5526,19 +5567,33 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5526
5567
|
traitOp: z.enum(["equals", "not_equals", "exists"]).optional(),
|
|
5527
5568
|
priority: z.number().optional().describe("Lower is nudged first. Default 100."),
|
|
5528
5569
|
order: z.number().optional().describe("0-based sequence order."),
|
|
5529
|
-
gapDays: z.number().optional().describe("Nudge window in days. Ignored when gap_hours is set. Default 3."),
|
|
5530
|
-
gapHours: z.number().optional().describe("Nudge window in hours.
|
|
5570
|
+
gapDays: z.number().optional().describe("First Nudge window in days. Ignored when gap_hours or nudge_clocks is set. Default 3."),
|
|
5571
|
+
gapHours: z.number().optional().describe("First Nudge window in hours. 0 = right away."),
|
|
5572
|
+
nudgeClocks: z
|
|
5573
|
+
.array(z.object({
|
|
5574
|
+
hours: z.number().optional(),
|
|
5575
|
+
days: z.number().optional(),
|
|
5576
|
+
}))
|
|
5577
|
+
.optional()
|
|
5578
|
+
.describe("Each clock is N hours or N days. First is the Wait deadline; later are extra Nudges."),
|
|
5531
5579
|
gapKind: z.enum(["days_since_signup", "event_not_fired"]).optional(),
|
|
5532
5580
|
sinceEventName: z.string().optional(),
|
|
5533
5581
|
enrollMode: z.enum(["all", "future"]).optional().describe("future (default) = only people who sign up from now on."),
|
|
5534
5582
|
active: z.boolean().optional().describe("Turn the milestone on. Default false."),
|
|
5535
|
-
scaffold: z.enum(["both", "celebration", "nudge", "none"]).optional().describe("Which
|
|
5536
|
-
journeyName: z.string().optional().describe("Name for
|
|
5583
|
+
scaffold: z.enum(["both", "celebration", "nudge", "none"]).optional().describe("Which letters to include. Default both."),
|
|
5584
|
+
journeyName: z.string().optional().describe("Name for the Adaptive Journey. Default is the Milestone name."),
|
|
5585
|
+
saveNotYetSegment: z.boolean().optional().describe("Save the live not-yet rule as a Segment."),
|
|
5586
|
+
saveDoneSegment: z.boolean().optional().describe("Save the live done rule as a Segment."),
|
|
5537
5587
|
}),
|
|
5538
5588
|
handler: async (args) => {
|
|
5539
5589
|
const warnings = await preflightAdoptionMilestone(args);
|
|
5590
|
+
const firstClock = args.nudgeClocks?.[0];
|
|
5540
5591
|
const created = await apiPost("/api/adoption/milestones", milestoneWriteBody({
|
|
5541
5592
|
...args,
|
|
5593
|
+
...(firstClock?.hours !== undefined ? { gapHours: firstClock.hours } : {}),
|
|
5594
|
+
...(firstClock?.days !== undefined && firstClock.hours === undefined
|
|
5595
|
+
? { gapDays: firstClock.days }
|
|
5596
|
+
: {}),
|
|
5542
5597
|
enrollMode: args.enrollMode ?? "future",
|
|
5543
5598
|
active: args.active ?? false,
|
|
5544
5599
|
priority: args.priority ?? 100,
|
|
@@ -5547,37 +5602,54 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5547
5602
|
if (!milestoneId) {
|
|
5548
5603
|
return { content: [{ type: "text", text: JSON.stringify(created, null, 2) }] };
|
|
5549
5604
|
}
|
|
5605
|
+
const persistSegments = async () => {
|
|
5606
|
+
if (!args.saveNotYetSegment && !args.saveDoneSegment) {
|
|
5607
|
+
return created.data?.milestone;
|
|
5608
|
+
}
|
|
5609
|
+
const patched = await apiPatch(`/api/adoption/milestones/${encodeURIComponent(milestoneId)}`, {
|
|
5610
|
+
saveNotYetSegment: args.saveNotYetSegment === true,
|
|
5611
|
+
saveDoneSegment: args.saveDoneSegment === true,
|
|
5612
|
+
});
|
|
5613
|
+
return patched.data?.milestone ?? created.data?.milestone;
|
|
5614
|
+
};
|
|
5550
5615
|
const scaffold = args.scaffold ?? "both";
|
|
5551
5616
|
if (scaffold === "none") {
|
|
5617
|
+
const milestone = await persistSegments();
|
|
5552
5618
|
return {
|
|
5553
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
5619
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
5620
|
+
milestone,
|
|
5621
|
+
journey_id: null,
|
|
5622
|
+
nudge_journey_id: null,
|
|
5623
|
+
celebration_journey_id: null,
|
|
5624
|
+
sends: [],
|
|
5625
|
+
warnings,
|
|
5626
|
+
}, null, 2) }],
|
|
5554
5627
|
};
|
|
5555
5628
|
}
|
|
5556
|
-
const
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
})
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
const
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
const
|
|
5570
|
-
? await scaffoldLeg("adoption-celebration", legName)
|
|
5571
|
-
: null;
|
|
5629
|
+
const journey = await apiPost("/api/journeys/from-template", {
|
|
5630
|
+
templateId: "adoption-nudge",
|
|
5631
|
+
milestoneId,
|
|
5632
|
+
scaffold,
|
|
5633
|
+
name: args.journeyName ?? args.name,
|
|
5634
|
+
...(args.nudgeClocks ? { nudgeClocks: args.nudgeClocks } : {}),
|
|
5635
|
+
});
|
|
5636
|
+
const journeyId = journey?.id ?? null;
|
|
5637
|
+
const sends = (journey?.sends ?? []).map((s) => ({
|
|
5638
|
+
role: s.role,
|
|
5639
|
+
step_id: s.stepId,
|
|
5640
|
+
comm_id: s.commId,
|
|
5641
|
+
}));
|
|
5642
|
+
const milestone = await persistSegments();
|
|
5572
5643
|
return {
|
|
5573
5644
|
content: [
|
|
5574
5645
|
{
|
|
5575
5646
|
type: "text",
|
|
5576
5647
|
text: JSON.stringify({
|
|
5577
|
-
milestone
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5648
|
+
milestone,
|
|
5649
|
+
journey_id: journeyId,
|
|
5650
|
+
nudge_journey_id: journeyId,
|
|
5651
|
+
celebration_journey_id: null,
|
|
5652
|
+
sends,
|
|
5581
5653
|
warnings,
|
|
5582
5654
|
}, null, 2),
|
|
5583
5655
|
},
|
|
@@ -5585,6 +5657,115 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5585
5657
|
};
|
|
5586
5658
|
},
|
|
5587
5659
|
},
|
|
5660
|
+
attach_adoption_milestone_still: {
|
|
5661
|
+
description: `${APEX} — Attach a still of the empty screen (not_yet) or the finished screen (done) to a Milestone. Same two pictures the shop uploads. Pass imageBase64 or a public url. A Milestone is NOT a Target.`,
|
|
5662
|
+
schema: z.object({
|
|
5663
|
+
milestoneId: z.string().describe("The Milestone to attach the still to"),
|
|
5664
|
+
state: z.enum(["not_yet", "done"]).describe("not_yet = empty screen; done = finished screen"),
|
|
5665
|
+
imageBase64: z.string().optional().describe("Base64-encoded PNG/JPEG."),
|
|
5666
|
+
url: z.string().url().optional().describe("Already-hosted public image URL."),
|
|
5667
|
+
viewport: z.enum(["desktop", "tablet", "mobile"]).optional(),
|
|
5668
|
+
format: z.enum(["png", "jpeg"]).optional(),
|
|
5669
|
+
label: z.string().optional(),
|
|
5670
|
+
}),
|
|
5671
|
+
handler: async (args) => {
|
|
5672
|
+
if (!args.imageBase64 && !args.url) {
|
|
5673
|
+
return {
|
|
5674
|
+
content: [{
|
|
5675
|
+
type: "text",
|
|
5676
|
+
text: "Provide either imageBase64 or url.",
|
|
5677
|
+
}],
|
|
5678
|
+
isError: true,
|
|
5679
|
+
};
|
|
5680
|
+
}
|
|
5681
|
+
const res = await apiPost(`/api/adoption/milestones/${encodeURIComponent(args.milestoneId)}/assets`, {
|
|
5682
|
+
state: args.state,
|
|
5683
|
+
source: args.imageBase64 ? "agent" : "upload",
|
|
5684
|
+
viewport: args.viewport ?? "desktop",
|
|
5685
|
+
imageBase64: args.imageBase64,
|
|
5686
|
+
url: args.url,
|
|
5687
|
+
format: args.format ?? "png",
|
|
5688
|
+
label: args.label,
|
|
5689
|
+
});
|
|
5690
|
+
const asset = res.data;
|
|
5691
|
+
return {
|
|
5692
|
+
content: [{
|
|
5693
|
+
type: "text",
|
|
5694
|
+
text: JSON.stringify({
|
|
5695
|
+
id: asset.id,
|
|
5696
|
+
state: asset.state,
|
|
5697
|
+
url: asset.url,
|
|
5698
|
+
milestone_id: args.milestoneId,
|
|
5699
|
+
}, null, 2),
|
|
5700
|
+
}],
|
|
5701
|
+
};
|
|
5702
|
+
},
|
|
5703
|
+
},
|
|
5704
|
+
recapture_adoption_milestone_stills: {
|
|
5705
|
+
description: `${APEX} — Capture the Not yet and Done stills from public pages on a named workspace host. Not localhost. A login wall fails honestly and writes nothing — attach a still yourself. Same two pictures the shop uploads.`,
|
|
5706
|
+
schema: z.object({
|
|
5707
|
+
milestoneId: z.string().describe("The Milestone to capture stills for"),
|
|
5708
|
+
notYetUrl: z.string().url().optional().describe("Public https URL of the empty screen."),
|
|
5709
|
+
doneUrl: z.string().url().optional().describe("Public https URL of the finished screen."),
|
|
5710
|
+
}),
|
|
5711
|
+
handler: async (args) => {
|
|
5712
|
+
if (!args.notYetUrl && !args.doneUrl) {
|
|
5713
|
+
return {
|
|
5714
|
+
content: [{ type: "text", text: "Name a public page for Not yet or Done." }],
|
|
5715
|
+
isError: true,
|
|
5716
|
+
};
|
|
5717
|
+
}
|
|
5718
|
+
try {
|
|
5719
|
+
const res = await apiPost(`/api/adoption/milestones/${encodeURIComponent(args.milestoneId)}/recapture`, {
|
|
5720
|
+
notYetUrl: args.notYetUrl,
|
|
5721
|
+
doneUrl: args.doneUrl,
|
|
5722
|
+
});
|
|
5723
|
+
if (res.error) {
|
|
5724
|
+
return {
|
|
5725
|
+
content: [{
|
|
5726
|
+
type: "text",
|
|
5727
|
+
text: res.error,
|
|
5728
|
+
}],
|
|
5729
|
+
isError: true,
|
|
5730
|
+
};
|
|
5731
|
+
}
|
|
5732
|
+
return {
|
|
5733
|
+
content: [{
|
|
5734
|
+
type: "text",
|
|
5735
|
+
text: JSON.stringify({
|
|
5736
|
+
captured: res.data?.captured ?? [],
|
|
5737
|
+
failed: res.data?.failed ?? [],
|
|
5738
|
+
milestone_id: args.milestoneId,
|
|
5739
|
+
}, null, 2),
|
|
5740
|
+
}],
|
|
5741
|
+
};
|
|
5742
|
+
}
|
|
5743
|
+
catch {
|
|
5744
|
+
return {
|
|
5745
|
+
content: [{
|
|
5746
|
+
type: "text",
|
|
5747
|
+
text: "We couldn’t open that page from Apex. It may need a sign-in. Attach a still yourself.",
|
|
5748
|
+
}],
|
|
5749
|
+
isError: true,
|
|
5750
|
+
};
|
|
5751
|
+
}
|
|
5752
|
+
},
|
|
5753
|
+
},
|
|
5754
|
+
list_adoption_milestone_stills: {
|
|
5755
|
+
description: `${APEX} — List the Not yet and Done stills attached to a Milestone. Empty means they have not attached one yet.`,
|
|
5756
|
+
schema: z.object({
|
|
5757
|
+
milestoneId: z.string().describe("The Milestone to list stills for"),
|
|
5758
|
+
}),
|
|
5759
|
+
handler: async ({ milestoneId }) => {
|
|
5760
|
+
const res = await apiGet(`/api/adoption/milestones/${encodeURIComponent(milestoneId)}/assets`);
|
|
5761
|
+
return {
|
|
5762
|
+
content: [{
|
|
5763
|
+
type: "text",
|
|
5764
|
+
text: JSON.stringify(res.data ?? [], null, 2),
|
|
5765
|
+
}],
|
|
5766
|
+
};
|
|
5767
|
+
},
|
|
5768
|
+
},
|
|
5588
5769
|
reorder_adoption_milestones: {
|
|
5589
5770
|
description: `${APEX} — Set the sequence order of adoption milestones in one call. Pass the milestone ids in the desired order; each gets order = its index. Ordering the full set unlocks the "Path" funnel in the account view (a step-by-step drop-off chart that is only honest over an explicitly-ordered set).`,
|
|
5590
5771
|
schema: z.object({
|