@feastalytics/cli 0.1.3 → 0.1.5

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/feast/SKILL.md CHANGED
@@ -91,7 +91,15 @@ For the domain-specific meaning of fields — how automations chain, what a funn
91
91
 
92
92
  Many tasks are multi-step and have a required ordering the app normally enforces. The most important rule: **automations live inside flows — always find a flow (`listAutomationFlows`) or create one (`createAutomationFlow`) before adding automations; never create an orphan automation.** The same "resolve the parent/ids first, then act" shape recurs across campaigns, funnels, and offers.
93
93
 
94
- For the ordered steps and domain rules of each common workflow, read `references/workflows.md`. It covers what's **fully doable** — creating/cloning a campaign, authoring automations end-to-end (create/edit/delete/simulate, with the trigger, condition, send-time, and chaining rules that make a flow professional), editing funnel screens (the draft → preview → promote loop), creating offers, and exploring users — and what's **not yet exposed** — members-program reward creation, brand identity, and replying to guests by SMS. Don't fabricate a call for a workflow whose tools aren't listed by `feast tools`; tell the user that part isn't available yet.
94
+ For the ordered steps and domain rules of each common workflow, read `references/workflows.md`. It covers what's **fully doable** — creating/cloning a campaign, authoring automations end-to-end (draft-first: stage edits on a draft, dry-run them with `simulateAutomations`, hand the user the preview link, then promote), editing funnel screens (the draft → preview → promote loop, including staging brand-new screens), reading and saving the wallet pass configuration (full-document save — read, modify, save the whole thing), listing/creating members-program rewards, creating offers, and exploring users — and what's **not yet exposed** — reward update/delete, pass image generation, brand identity, and replying to guests by SMS. Don't fabricate a call for a workflow whose tools aren't listed by `feast tools`; tell the user that part isn't available yet.
95
+
96
+ ## Link to what you touched
97
+
98
+ Work you do through the CLI lands somewhere in the product, and a link is a cheap thing to offer — so offer them freely. After a turn where you created, changed, or published something, close with a short markdown list: where to see it, where to edit it, where to preview it. Not because anyone has to go check your work, but because opening the thing is usually the next step anyway. When someone asks where a thing lives or how to set it up, lead with the link rather than click-by-click directions.
99
+
100
+ **You don't know these URLs — read `references/links.md` before you write one.** The dashboard's shape is not the one you'd extrapolate from the guest-facing links elsewhere in this skill, so a URL that looks obviously right is the exact case to check. A wrong link is worse than no link: it looks authoritative and 404s.
101
+
102
+ That file has the dashboard routes with their panel and tab names, the guest-facing pages on the organization's own subdomain, the preview route that completes the funnel draft loop, and which query params actually suppress analytics versus merely tagging a visit as a preview.
95
103
 
96
104
  ## Worked example
97
105
 
@@ -0,0 +1,80 @@
1
+ # Linking to your work
2
+
3
+ Most of what you create or change through the CLI has a stable URL in the product. Handing one over is cheap and saves the user hunting through the dashboard for the thing you just made — opening it is usually their next step anyway.
4
+
5
+ So offer links freely: after a turn where you created, changed, or published something, close with a short markdown list — usually two to four, covering where to see it, where to edit it, and where to preview it. When someone asks where a thing lives or how to set it up, lead with the link rather than describing where to click. It's an affordance, not a checkpoint; nobody has to go verify your work.
6
+
7
+ You can build almost every URL below from ids you already have. `<organizationId>` is the same value you pass to `--org`. Campaign, flow and draft ids come back from the tool call you just made. Only `<subdomain>` needs a lookup.
8
+
9
+ Angle brackets below mark a value **you** substitute. A finished link contains no brackets, no braces and no backticks — if you emit `{{...}}` or a bare `<campaignId>`, the link is broken.
10
+
11
+ ## Dashboard
12
+
13
+ Everything an authenticated user sees hangs off `https://feastalytics.com/<organizationId>/app`. Note the shape: the organization id is a **path segment**, not a subdomain — there is no `app.feastalytics.com`.
14
+
15
+ ```
16
+ / home
17
+ /campaigns every acquisition campaign
18
+ /campaigns/<campaignId> one campaign
19
+ /campaigns/<campaignId>?panel=funnel-v2 that campaign's funnel editor
20
+ /members-program?panel=<panel> the members program
21
+ /members-program?panel=funnel the members program funnel editor
22
+ /automation/<flowId> one automation flow
23
+ /settings/<tab> settings
24
+ /chats guest conversations
25
+ /orders /activity /guest-journey
26
+ ```
27
+
28
+ Campaign panels: `overview`, `funnel-v2`, `automations`, `promotions`, `metrics`, `ads`, `orders`, `reservations`, `subscriptions`, `creative-strategy`. With no `?panel=`, an unpublished campaign opens on `funnel-v2` and a published one on `overview`.
29
+
30
+ Members-program panels: `overview`, `funnel`, `automations`, `pass-builder`, `rewards`.
31
+
32
+ Settings tabs: `account`, `general`, `members`, `integrations`, `notifications`, `usage`, `scanning`, `texting`, `subscription`. Point people at `/settings/integrations` when a task needs a POS or Meta connection you can't make for them.
33
+
34
+ Funnels are always edited inside a panel, never on a page of their own — a campaign's `funnel-v2` panel, or the members program's `funnel` panel.
35
+
36
+ ### Automation previews
37
+
38
+ These two hang off the **root**, not off `/<organizationId>/app` — the organization id is the first path segment:
39
+
40
+ ```
41
+ https://feastalytics.com/automation-preview/<organizationId>/<flowId>
42
+ https://feastalytics.com/automation-preview/<organizationId>/<flowId>?draftId=<draftId>
43
+ ```
44
+
45
+ Without `draftId` it dry-runs the live flow as a text-message thread. With one, the same page diffs the draft's staged changes against live — added messages tinted, removed struck through, edited showing the old copy above the new — which is the link to hand someone before you promote.
46
+
47
+ A `flowId` contains `:` and `;` and **must be percent-encoded** in the path. Easier: `createAutomationDraft` and `stageAutomationEdits` both return `previewUrls`, already built and encoded, one per flow the draft touches. Use those rather than assembling your own.
48
+
49
+ ## Public pages
50
+
51
+ The guest-facing site lives on the organization's own subdomain, `https://<subdomain>.feastalytics.com`:
52
+
53
+ ```
54
+ /campaign/<campaignId> the live campaign funnel
55
+ / the live members program funnel
56
+ /preview/<draftId> an unsaved funnel draft, as a tree of every screen
57
+ /preview/<draftId>/<campaignId> the same draft, scoped to one campaign
58
+ ```
59
+
60
+ Get `<subdomain>` from `loadCurrentOrganization` → `organization.subdomains2[].subdomain`. When the link is about a campaign, pick the subdomain matching that campaign's `referrers` rather than the first one. The funnel draft tools (`createFunnelDraft`, `getFunnelDraft`) already return the draft's `referrer`, so use that instead of looking it up again.
61
+
62
+ The `/preview/<draftId>` route is the payoff of the draft → preview → promote loop in `workflows.md`: it renders every screen of the staged funnel as a tree, so it's the right link to hand over after `stageFunnelEdit` and before `saveFunnelEdits`. It stops working once the draft is discarded or expires.
63
+
64
+ ## Two query params that don't do what they look like
65
+
66
+ `utm_source=PREVIEW` only *tags* a visit as a preview so it can be filtered out of reporting later. The visit is still counted. The in-app preview panes append it, so use it when you want to match what the app does.
67
+
68
+ `internal_qa=1` is what actually suppresses analytics, and it sticks for that browser until cleared with `internal_qa=0`. Use it when the point is to look at a live page without being counted. `qa=1`, `review=1` and `campaign_review=1` suppress the current page only.
69
+
70
+ ## Worked example
71
+
72
+ After creating a campaign and applying a funnel template. Every id below is substituted — this is what a finished message looks like, with nothing left to fill in:
73
+
74
+ ```markdown
75
+ Done — "Fall Prix Fixe" is live as a draft.
76
+
77
+ - [Open the campaign](https://feastalytics.com/3e8cb27c-6e54-444b-859f-66dbae0e711b/app/campaigns/e8ccc852-6555-4a9a-b48b-127d687bb34a)
78
+ - [Edit the funnel](https://feastalytics.com/3e8cb27c-6e54-444b-859f-66dbae0e711b/app/campaigns/e8ccc852-6555-4a9a-b48b-127d687bb34a?panel=funnel-v2)
79
+ - [See it as a guest](https://melbourneseafoodstation.feastalytics.com/campaign/e8ccc852-6555-4a9a-b48b-127d687bb34a?utm_source=PREVIEW)
80
+ ```
@@ -39,18 +39,26 @@ The one-shot text→campaign endpoints (`createWithOffer` / `parseCampaignDescri
39
39
  - A **flow** groups automations by a shared trigger, and belongs to *either* a campaign *or* the members program — never both.
40
40
  - **The rule that matters most: every automation needs a `flowId`. `batchEditAutomations` throws on a create op without one.** So you must resolve the flow *before* creating. Never invent a flowId.
41
41
 
42
- ### The CLI loop (simpler than the app — no navigate/stage/save split)
42
+ ### The CLI loop: draft → stage → share → save
43
43
 
44
- Unlike the in-app agent (which scaffolds an automation, then edits it, then explicitly saves), the CLI sends a **complete** automation in one operation and it persists immediately.
44
+ Automations have a staging tier, and it is the default path. Changes accumulate on a **draft** — an off-prod overlay — until someone explicitly promotes them. A draft carries a link that renders the change as a text-message thread with the edits highlighted, which is what you hand a human to look at before anything reaches a real guest.
45
45
 
46
46
  1. `listAutomationFlows` — find an existing flow. Pass `{ "campaignId": "<id>" }` for a campaign's flows, or `{ "scope": "membersProgram" }` for members-program flows. Reuse a matching flow when one fits.
47
47
  2. If none fits, `createAutomationFlow` to make one. If the campaign/members-program has **no flows at all**, strongly prefer `applyAutomationTemplate` (then customize) over building from scratch. Only apply a template when there are no existing flows.
48
48
  3. `listAutomations` with `{ "flowId": "<id>" }` to see the automations already in that flow before editing (omit the input to list every automation in the org).
49
- 4. `batchEditAutomations` with a list of `operations`:
50
- - `{ "type": "create", "automation": { ...full automation..., "flowId": "<id>" } }` — generate a fresh UUID for the automation's id, set the `flowId`, and include triggers, conditions, actions, send time, and a descriptive title all at once. The server validates it and (create ops) requires the flowId.
49
+ 4. `createAutomationDraft` with a short `title` describing the change in the user's terms ("Shorten the day-3 nudge") — that title is what the reviewer sees. Keep the returned `draftId`; **there is no way to list drafts, so if you lose it the draft is unreachable.**
50
+ 5. `stageAutomationEdits` with `{ "draftId": "<id>", "operations": [...] }`. The ops are exactly the ones `batchEditAutomations` takes:
51
+ - `{ "type": "create", "automation": { ...full automation..., "flowId": "<id>" } }` — generate a fresh UUID for the automation's id, set the `flowId`, and include triggers, conditions, actions, send time, and a descriptive title all at once. Create ops require the flowId.
51
52
  - `{ "type": "update", "automationId": "<id>", "automation": { ...changed fields... } }`
52
- - `{ "type": "delete", "automationId": "<id>" }` — blocked if the automation already has sends.
53
- 5. `simulateAutomations` — dry-run the flow against a synthetic event timeline with **no real sends**, to confirm the right automations fire before or after you save. It can also preview un-saved `edits` before you commit them.
53
+ - `{ "type": "delete", "automationId": "<id>" }` — blocked at save time if the automation already has sends.
54
+ Call it repeatedly to build a change up; ops append in order.
55
+ 6. `simulateAutomations` with `{ "flowId": "<id>", "edits": <the draft's operations> }` — dry-run against a synthetic event timeline with **no real sends** and confirm the right automations fire. If the simulation surprises you, stage a correction rather than promoting and patching live.
56
+ 7. **Give the user the `previewUrls` from the draft** and let them look before you promote. Each entry is one flow's before/after view. Don't promote unprompted work on the user's behalf — staging exists so a human sees the change first.
57
+ 8. `saveAutomationEdits` with `{ "draftId": "<id>" }` — this is the write to production. It refuses if any automation the draft touches was changed by someone else since you staged, naming which; re-stage against the current state rather than retrying.
58
+
59
+ `discardAutomationDraft` throws a draft away without promoting. `getAutomationDraft` re-reads one by id. Drafts expire after 14 days.
60
+
61
+ **`batchEditAutomations` still exists and writes straight to production in one call.** Reach for it only when the user explicitly wants an immediate live change and has said so — not as a shortcut past the review step.
54
62
 
55
63
  `updateAutomationFlow` renames/retitles a flow; `deleteAutomationFlow` removes a flow and its automations (blocked at ≥20 sends — turn it off instead).
56
64
 
@@ -142,8 +150,19 @@ Every offer picks one **framework**:
142
150
  The retention counterpart to campaigns — flows with no `campaignId`.
143
151
 
144
152
  - **Automations are authorable** (see the automations workflow): use `listAutomationFlows` with `{ "scope": "membersProgram" }`, then the same create/edit loop. Remember the members-program 30-day `awardReward` default.
145
- - **Creating members-program rewards themselves is NOT exposed.** Reward creation in the app writes a catalog item + reward link through generic object-query mutations with no dedicated tool, and orchestrates catalog-item-create-or-reuse plus awarding-automation scaffolding client-side. There's no tagged `createReward`. If asked, tell the user this needs the app (or a new endpoint).
146
- - **Pass builder** (wallet pass design) is not exposed to the CLI.
153
+ - **Rewards are now readable and creatable.** `listMembersProgramRewards` returns every reward with its catalog `name`, `staffInstructions`, and `pointsCost` (fields are `null` when unset). `createMembersProgramReward` takes `{ "name", "staffInstructions"?, "pointsCost"? }` and does the catalog-item dance for you: an existing catalog item with that exact name is reused (updating its `staffInstructions` if you passed any), otherwise one is created, then the reward row is linked to it.
154
+ - **The `pointsCost` fork matters.** A reward with `pointsCost` set is redeemed *by the member with points* and is never auto-awarded. Omit `pointsCost` for automation-awarded rewards — and then actually pair the reward with an `awardReward` automation (see the automations workflow), or it will never reach anyone. `listMembersProgramRewards` deliberately has no `hasAutomation` flag: cross-reference `listAutomations` for `awardReward` actions carrying the reward's `itemId` to find orphans.
155
+ - Reward **update/delete are not exposed** yet — those still need the app.
156
+
157
+ ---
158
+
159
+ ## Wallet pass configuration
160
+
161
+ The pass (the wallet membership card) is now CLI-readable and -writable as a whole document.
162
+
163
+ - **`readPassConfiguration`** `{}` — returns the latest live configuration: `sections`, `features`, `locations`, `metadata`, and its `version`.
164
+ - **`savePassConfiguration`** — **a full-document save, not a patch.** Anything you omit is dropped from the new version. The only safe workflow is read → modify the returned document → save the complete result. Saving appends a new version (history is preserved server-side), and a visible change triggers a re-push of the pass to every member's wallet — so this confirms before running and deserves the same care as a live send.
165
+ - Pass **image generation** (punch-card strips etc.) is not exposed — image workflows still need the app.
147
166
 
148
167
  ---
149
168
 
@@ -160,7 +179,7 @@ The retention counterpart to campaigns — flows with no `campaignId`.
160
179
 
161
180
  **Applying a funnel template** expands a whole screen tree server-side in one call: `chooseFunnelTemplate` (needs the campaign's funnel unset — a fresh campaign — and resolves the referrer from the campaign). `deleteFunnel` tears one down.
162
181
 
163
- **Editing individual funnel screens is now CLI-drivable** through a **draft → preview → promote** loop. You never apply edits locally: you stage them on an off-prod draft, preview the result at a stable URL, then save. Tools: `listFunnelScreens`, `createFunnelDraft`, `stageFunnelEdit`, `getFunnelDraft`, `listFunnelDrafts`, `discardFunnelDraft`, `saveFunnelEdits`.
182
+ **Editing individual funnel screens is now CLI-drivable** through a **draft → preview → promote** loop. You never apply edits locally: you stage them on an off-prod draft, preview the result at a stable URL, then save. Tools: `listFunnelScreens`, `createFunnelDraft`, `stageFunnelEdit`, `stageFunnelScreen`, `getFunnelDraft`, `listFunnelDrafts`, `discardFunnelDraft`, `saveFunnelEdits`.
164
183
 
165
184
  ### The loop
166
185
 
@@ -170,6 +189,7 @@ The retention counterpart to campaigns — flows with no `campaignId`.
170
189
  - `{ "type": "update", "id": "<renderableId>", "renderable": { ...clone of what you read, with your changes... } }` — keep the same `id`.
171
190
  - `{ "type": "create", "renderable": { "id": "<new-uuid>", ... }, "targetId": "<sibling id>", "position": "before" | "after" | "inside" }` — generate a fresh UUID.
172
191
  - `{ "type": "delete", "id": "<renderableId>" }` and `{ "type": "move", "id": "...", "targetId": "...", "position": "..." }`.
192
+ **Need a brand-new screen?** `stageFunnelScreen` `{ "draftId": "...", "title": "...", "description"? }` stages an empty screen on the draft and returns its **permanent `screenId`** — the id is assigned at staging time, not at save, so you can immediately `stageFunnelEdit` content into it and reference it from navigation/buttons on other screens; nothing gets re-keyed at promote. On a campaign draft the screen is campaign-scoped unless you pass `"campaignScoped": false`. The screen only exists on the draft (and in draft-scoped `listFunnelScreens`/previews) until `saveFunnelEdits` promotes it, which reports it in `createdScreenIds`.
173
193
  4. **Preview** (no CLI call): open `https://{referrer}.feastalytics.com/preview/{draftId}/{campaignId}` (drop `/{campaignId}` for a members-program draft). It renders the whole funnel as a flow diagram with the draft's edits applied. Screenshot it, then iterate — re-run `listFunnelScreens` **with the `draftId`** to read the funnel *with* the staged edits, stage more, re-preview — until it's right.
174
194
  5. **`saveFunnelEdits`** `{ "draftId": "..." }` — **promotes to prod** (mutation, confirms first): applies the draft's edits to the live funnel and marks the draft `promoted`. To abandon instead, `discardFunnelDraft`.
175
195
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feastalytics/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Command-line client for the Feastalytics platform — list, create, and update campaigns, automations, offers, and members-program rewards from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {