@apex-inc/mcp-server 0.27.5 → 0.28.1
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/README.md +21 -0
- 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/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/screenshot-cli.d.ts +35 -3
- package/dist/screenshot-cli.d.ts.map +1 -1
- package/dist/screenshot-cli.js +126 -38
- package/dist/screenshot-cli.js.map +1 -1
- package/dist/tools.d.ts +179 -26
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +182 -47
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
- package/skills/apex-adoption/SKILL.md +122 -81
- package/skills/apex-experimentation/SKILL.md +1 -1
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,31 @@ 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
|
-
if (args.kind === "send" && !args.commId) {
|
|
4219
|
-
return { content: [{ type: "text", text: `${APEX} A send step needs commId.` }], isError: true };
|
|
4220
|
-
}
|
|
4221
4223
|
const j = await apiGet(`/api/journeys/${encodeURIComponent(args.journeyId)}`);
|
|
4222
|
-
const steps = [...(j.steps ?? [])];
|
|
4223
|
-
const exit = steps.find((s) => s.type === "exit");
|
|
4224
|
-
if (!exit) {
|
|
4225
|
-
return { content: [{ type: "text", text: `${APEX} Journey has no exit step; cannot insert.` }], isError: true };
|
|
4226
|
-
}
|
|
4227
|
-
const exitId = exit.id;
|
|
4228
4224
|
const newId = `step_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
|
|
4229
|
-
const
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
const
|
|
4234
|
-
if (pre)
|
|
4235
|
-
pre.next = newId;
|
|
4236
|
-
steps.push(newStep);
|
|
4237
|
-
const updated = await apiPatch(`/api/journeys/${encodeURIComponent(args.journeyId)}`, { steps });
|
|
4225
|
+
const applied = applyAddJourneyStep(j.steps ?? [], args, newId);
|
|
4226
|
+
if (!applied.ok) {
|
|
4227
|
+
return { content: [{ type: "text", text: `${APEX} ${applied.error}` }], isError: true };
|
|
4228
|
+
}
|
|
4229
|
+
const updated = await apiPatch(`/api/journeys/${encodeURIComponent(args.journeyId)}`, { steps: applied.steps });
|
|
4238
4230
|
if (!tenantOk(updated.workspaceKey))
|
|
4239
4231
|
return tenantMismatch();
|
|
4240
|
-
return { content: [{ type: "text", text: `${APEX} Added ${args.kind} step to journey ${args.journeyId}. Watch it update in the browser: ${journeyLink(args.journeyId)}` }] };
|
|
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)}` }] };
|
|
4241
4233
|
}
|
|
4242
4234
|
catch (err) {
|
|
4243
4235
|
return { content: [{ type: "text", text: `${APEX} ${errMsg(err)}` }], isError: true };
|
|
@@ -5494,6 +5486,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5494
5486
|
schema: z.object({
|
|
5495
5487
|
name: z.string().describe("Human label, e.g. 'Ran first report'"),
|
|
5496
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."),
|
|
5497
5490
|
adoptedWhenKind: z.enum(["event", "trait"]).describe("How adoption is detected"),
|
|
5498
5491
|
eventName: z.string().optional().describe("event kind: the event whose firing means adopted"),
|
|
5499
5492
|
traitPath: z.string().optional().describe("trait kind: dotted trait path, e.g. 'plan'"),
|
|
@@ -5530,6 +5523,7 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5530
5523
|
schema: z.object({
|
|
5531
5524
|
milestoneId: z.string(),
|
|
5532
5525
|
name: z.string().optional(),
|
|
5526
|
+
actionLabel: z.string().optional().describe("Sentence verb: hasn't {action_label}."),
|
|
5533
5527
|
priority: z.number().optional(),
|
|
5534
5528
|
order: z.number().optional().describe("0-based sequence order. Prefer reorder_adoption_milestones to sequence the whole set."),
|
|
5535
5529
|
active: z.boolean().optional(),
|
|
@@ -5561,10 +5555,11 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5561
5555
|
},
|
|
5562
5556
|
},
|
|
5563
5557
|
compose_adoption_milestone: {
|
|
5564
|
-
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.`,
|
|
5565
5559
|
schema: z.object({
|
|
5566
5560
|
name: z.string().describe("Human label, e.g. 'Started first experiment'"),
|
|
5567
5561
|
featureKey: z.string().describe("Stable feature key, e.g. 'experiments'"),
|
|
5562
|
+
actionLabel: z.string().optional().describe("Sentence verb: hasn't {action_label}."),
|
|
5568
5563
|
adoptedWhenKind: z.enum(["event", "trait"]).describe("How adoption is detected"),
|
|
5569
5564
|
eventName: z.string().optional().describe("event kind: the event whose firing means adopted"),
|
|
5570
5565
|
traitPath: z.string().optional().describe("trait kind: dotted trait path, e.g. 'plan'"),
|
|
@@ -5572,19 +5567,33 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5572
5567
|
traitOp: z.enum(["equals", "not_equals", "exists"]).optional(),
|
|
5573
5568
|
priority: z.number().optional().describe("Lower is nudged first. Default 100."),
|
|
5574
5569
|
order: z.number().optional().describe("0-based sequence order."),
|
|
5575
|
-
gapDays: z.number().optional().describe("Nudge window in days. Ignored when gap_hours is set. Default 3."),
|
|
5576
|
-
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."),
|
|
5577
5579
|
gapKind: z.enum(["days_since_signup", "event_not_fired"]).optional(),
|
|
5578
5580
|
sinceEventName: z.string().optional(),
|
|
5579
5581
|
enrollMode: z.enum(["all", "future"]).optional().describe("future (default) = only people who sign up from now on."),
|
|
5580
5582
|
active: z.boolean().optional().describe("Turn the milestone on. Default false."),
|
|
5581
|
-
scaffold: z.enum(["both", "celebration", "nudge", "none"]).optional().describe("Which
|
|
5582
|
-
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."),
|
|
5583
5587
|
}),
|
|
5584
5588
|
handler: async (args) => {
|
|
5585
5589
|
const warnings = await preflightAdoptionMilestone(args);
|
|
5590
|
+
const firstClock = args.nudgeClocks?.[0];
|
|
5586
5591
|
const created = await apiPost("/api/adoption/milestones", milestoneWriteBody({
|
|
5587
5592
|
...args,
|
|
5593
|
+
...(firstClock?.hours !== undefined ? { gapHours: firstClock.hours } : {}),
|
|
5594
|
+
...(firstClock?.days !== undefined && firstClock.hours === undefined
|
|
5595
|
+
? { gapDays: firstClock.days }
|
|
5596
|
+
: {}),
|
|
5588
5597
|
enrollMode: args.enrollMode ?? "future",
|
|
5589
5598
|
active: args.active ?? false,
|
|
5590
5599
|
priority: args.priority ?? 100,
|
|
@@ -5593,37 +5602,54 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5593
5602
|
if (!milestoneId) {
|
|
5594
5603
|
return { content: [{ type: "text", text: JSON.stringify(created, null, 2) }] };
|
|
5595
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
|
+
};
|
|
5596
5615
|
const scaffold = args.scaffold ?? "both";
|
|
5597
5616
|
if (scaffold === "none") {
|
|
5617
|
+
const milestone = await persistSegments();
|
|
5598
5618
|
return {
|
|
5599
|
-
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) }],
|
|
5600
5627
|
};
|
|
5601
5628
|
}
|
|
5602
|
-
const
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
})
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
const
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
const
|
|
5616
|
-
? await scaffoldLeg("adoption-celebration", legName)
|
|
5617
|
-
: 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();
|
|
5618
5643
|
return {
|
|
5619
5644
|
content: [
|
|
5620
5645
|
{
|
|
5621
5646
|
type: "text",
|
|
5622
5647
|
text: JSON.stringify({
|
|
5623
|
-
milestone
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5648
|
+
milestone,
|
|
5649
|
+
journey_id: journeyId,
|
|
5650
|
+
nudge_journey_id: journeyId,
|
|
5651
|
+
celebration_journey_id: null,
|
|
5652
|
+
sends,
|
|
5627
5653
|
warnings,
|
|
5628
5654
|
}, null, 2),
|
|
5629
5655
|
},
|
|
@@ -5631,6 +5657,115 @@ _Suggest the next step the user should tackle based on what's incomplete in the
|
|
|
5631
5657
|
};
|
|
5632
5658
|
},
|
|
5633
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. For a signed-in product page, capture on their machine with \`npx @apex-inc/mcp-server screenshot --milestone … --state not_yet|done --url … --storage-state <login file>\` — do not use hosted recapture. 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. For a signed-in product page, sit it on their machine: \`npx @apex-inc/mcp-server screenshot --milestone … --storage-state <login file>\`. 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
|
+
},
|
|
5634
5769
|
reorder_adoption_milestones: {
|
|
5635
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).`,
|
|
5636
5771
|
schema: z.object({
|