@feastalytics/cli 0.1.8 → 0.1.9
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/cli.js +204 -15
- package/feast/SKILL.md +15 -14
- package/feast/references/domains.md +15 -3
- package/feast/references/workflows/ads.md +50 -0
- package/feast/references/workflows/automations.md +1 -1
- package/feast/references/workflows/campaigns.md +4 -15
- package/feast/references/workflows/creators.md +17 -2
- package/feast/references/workflows/facebook.md +5 -5
- package/feast/references/workflows/funnels.md +6 -3
- package/feast/references/workflows/guests.md +11 -1
- package/feast/references/workflows/members-program.md +6 -5
- package/feast/references/workflows/onboarding.md +18 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8955,7 +8955,7 @@ var CLI_MANIFEST = {
|
|
|
8955
8955
|
{
|
|
8956
8956
|
"id": "applyAutomationTemplate",
|
|
8957
8957
|
"domain": "automations",
|
|
8958
|
-
"description": "Applies an automation template and creates automations. Only use when the campaign or members program has no existing flows; otherwise
|
|
8958
|
+
"description": "Applies an automation template and creates automations. Only use when the campaign or members program has no existing flows; otherwise edit the existing flows through the draft loop (createAutomationDraft + stageAutomationEdits) or batchEditAutomations.",
|
|
8959
8959
|
"type": "mutation",
|
|
8960
8960
|
"path": [
|
|
8961
8961
|
"api",
|
|
@@ -10742,7 +10742,7 @@ var CLI_MANIFEST = {
|
|
|
10742
10742
|
{
|
|
10743
10743
|
"id": "cloneCampaign",
|
|
10744
10744
|
"domain": "campaigns",
|
|
10745
|
-
"description": "Clones an existing campaign including its funnel screens, automations, and offers. Requires sourceCampaignId, newCampaignName, and referrer (subdomain from organization.subdomains2).",
|
|
10745
|
+
"description": "Clones an existing campaign including its funnel screens, automations, and offers. Requires sourceCampaignId, newCampaignName, and referrer (subdomain from organization.subdomains2). Cloned automations keep the source campaign's reservation links \u2014 after cloning, rewrite any reservation link in the new campaign's automations to the new campaign's shorthand.",
|
|
10746
10746
|
"type": "mutation",
|
|
10747
10747
|
"path": [
|
|
10748
10748
|
"api",
|
|
@@ -10772,6 +10772,30 @@ var CLI_MANIFEST = {
|
|
|
10772
10772
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
10773
10773
|
}
|
|
10774
10774
|
},
|
|
10775
|
+
{
|
|
10776
|
+
"id": "countParentAutomationRecipients",
|
|
10777
|
+
"domain": "automations",
|
|
10778
|
+
"description": "How many distinct members already received an automation \u2014 the audience size a receiveAutomation-triggered child would reach if backfilled with applyToHistorical: true. Call this before any backfill, tell the user the number, and warn when it exceeds 1000; only backfill after they confirm. Read-only and safe to call while deciding.",
|
|
10779
|
+
"type": "query",
|
|
10780
|
+
"path": [
|
|
10781
|
+
"api",
|
|
10782
|
+
"automation",
|
|
10783
|
+
"countParentAutomationRecipients"
|
|
10784
|
+
],
|
|
10785
|
+
"inputJsonSchema": {
|
|
10786
|
+
"type": "object",
|
|
10787
|
+
"properties": {
|
|
10788
|
+
"parentAutomationId": {
|
|
10789
|
+
"type": "string"
|
|
10790
|
+
}
|
|
10791
|
+
},
|
|
10792
|
+
"required": [
|
|
10793
|
+
"parentAutomationId"
|
|
10794
|
+
],
|
|
10795
|
+
"additionalProperties": false,
|
|
10796
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
10797
|
+
}
|
|
10798
|
+
},
|
|
10775
10799
|
{
|
|
10776
10800
|
"id": "createAutomationDraft",
|
|
10777
10801
|
"domain": "automations",
|
|
@@ -12905,7 +12929,7 @@ var CLI_MANIFEST = {
|
|
|
12905
12929
|
{
|
|
12906
12930
|
"id": "createCreativeStrategy",
|
|
12907
12931
|
"domain": "creators",
|
|
12908
|
-
"description": "Generate a creator strategy. An awareness strategy is built from a fixed template and is saved before this returns, with generationStatus complete and a null jobId. A CTA strategy is generated by an LLM in the background \u2014 this returns immediately with generationStatus generating, so poll getCreativeStrategy with the returned strategyId until it reads complete or failed before using the brief. The jobId and jobType that come back track the same run, but
|
|
12932
|
+
"description": "Generate a creator strategy. An awareness strategy is built from a fixed template and is saved before this returns, with generationStatus complete and a null jobId. A CTA strategy is generated by an LLM in the background \u2014 this returns immediately with generationStatus generating, so poll getCreativeStrategy with the returned strategyId until it reads complete or failed before using the brief. The jobId and jobType that come back track the same run through getJob, but getCreativeStrategy is the simpler poll \u2014 reach for getJob only when the strategy reads failed and you want the job's errorMessage. Edit the result with updateCreativeStrategy. Passing a strategyId that is not a draft is rejected rather than overwritten.",
|
|
12909
12933
|
"type": "mutation",
|
|
12910
12934
|
"path": [
|
|
12911
12935
|
"api",
|
|
@@ -12941,7 +12965,6 @@ var CLI_MANIFEST = {
|
|
|
12941
12965
|
}
|
|
12942
12966
|
},
|
|
12943
12967
|
"required": [
|
|
12944
|
-
"restaurantName",
|
|
12945
12968
|
"restaurantNeighborhood",
|
|
12946
12969
|
"differentiatorsText",
|
|
12947
12970
|
"mustTryItemsText"
|
|
@@ -13055,6 +13078,31 @@ var CLI_MANIFEST = {
|
|
|
13055
13078
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
13056
13079
|
}
|
|
13057
13080
|
},
|
|
13081
|
+
{
|
|
13082
|
+
"id": "createInfluencerPayout",
|
|
13083
|
+
"domain": "creators",
|
|
13084
|
+
"description": "Charges the organization's card to pay a creator's content bonus. NEVER call this on your own initiative or as part of an automated flow \u2014 every call needs the client's explicit, fresh approval to pay this specific creator, given to you directly; a standing instruction or an inferred intent does not count. The endpoint enforces its own preconditions and refuses otherwise: the visit must have a content submission approved as a paid ad (decideCreatorSubmission with approvalType 'ad' \u2014 organic approvals earn no payout), and no payout may already exist for the visit in any active status \u2014 one payout per visit, so a second call while one is pending, funding, onboarding, or paid is rejected. The bonus amount comes from the location's board config, grossed up so the org covers the Stripe fee. After the charge, Stripe webhooks carry it to the creator (FUNDED \u2192 onboarding if needed \u2192 PAID) with no further action from you; follow progress in queryData creators.creatorPayout.",
|
|
13085
|
+
"type": "mutation",
|
|
13086
|
+
"path": [
|
|
13087
|
+
"api",
|
|
13088
|
+
"stripe",
|
|
13089
|
+
"createInfluencerPayout"
|
|
13090
|
+
],
|
|
13091
|
+
"inputJsonSchema": {
|
|
13092
|
+
"type": "object",
|
|
13093
|
+
"properties": {
|
|
13094
|
+
"eventId": {
|
|
13095
|
+
"type": "string",
|
|
13096
|
+
"description": "The creator visit's eventId (creatorVisitApplication.eventId) whose approved paid-ad submission is being paid out."
|
|
13097
|
+
}
|
|
13098
|
+
},
|
|
13099
|
+
"required": [
|
|
13100
|
+
"eventId"
|
|
13101
|
+
],
|
|
13102
|
+
"additionalProperties": false,
|
|
13103
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
13104
|
+
}
|
|
13105
|
+
},
|
|
13058
13106
|
{
|
|
13059
13107
|
"id": "createMembersProgramReward",
|
|
13060
13108
|
"domain": "membersProgram",
|
|
@@ -13264,7 +13312,7 @@ var CLI_MANIFEST = {
|
|
|
13264
13312
|
{
|
|
13265
13313
|
"id": "deleteFunnel",
|
|
13266
13314
|
"domain": "campaigns",
|
|
13267
|
-
"description": "Tears down a campaign's funnel: deletes the campaign-specific screens and resets the campaign override (clears initialScreenId, postSignupScreen, overrideByScreenId, and variants), returning the campaign to the choose-template state. Inverse of
|
|
13315
|
+
"description": "Tears down a campaign's funnel: deletes the campaign-specific screens and resets the campaign override (clears initialScreenId, postSignupScreen, overrideByScreenId, and variants), returning the campaign to the choose-template state. Inverse of applyFunnelTemplate.",
|
|
13268
13316
|
"type": "mutation",
|
|
13269
13317
|
"path": [
|
|
13270
13318
|
"api",
|
|
@@ -13604,6 +13652,34 @@ var CLI_MANIFEST = {
|
|
|
13604
13652
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
13605
13653
|
}
|
|
13606
13654
|
},
|
|
13655
|
+
{
|
|
13656
|
+
"id": "getJob",
|
|
13657
|
+
"domain": "core",
|
|
13658
|
+
"description": "Poll one background job by id. Any tool that queues work returns a { jobId, jobType } pair \u2014 publishAds and the CTA path of createCreativeStrategy both do \u2014 and both values are required here, because a job id alone is not addressable. Returns { job: null } while the row hasn't landed yet, so treat null as in-flight and keep polling; status moves PENDING, RUNNING, then COMPLETED or FAILED with an errorMessage. A publish job's output reports each declared effect as done, skipped or error with a human-readable detail \u2014 including whether the program's approver was texted \u2014 and that outcome is reported nowhere else, so read it rather than assuming the effects ran. The job's input and per-step generated text are stripped by default; includeFullPayload returns both.",
|
|
13659
|
+
"type": "query",
|
|
13660
|
+
"path": [
|
|
13661
|
+
"api",
|
|
13662
|
+
"jobs",
|
|
13663
|
+
"get"
|
|
13664
|
+
],
|
|
13665
|
+
"inputJsonSchema": {
|
|
13666
|
+
"type": "object",
|
|
13667
|
+
"properties": {
|
|
13668
|
+
"jobType": {},
|
|
13669
|
+
"jobId": {
|
|
13670
|
+
"type": "string"
|
|
13671
|
+
},
|
|
13672
|
+
"includeFullPayload": {
|
|
13673
|
+
"type": "boolean"
|
|
13674
|
+
}
|
|
13675
|
+
},
|
|
13676
|
+
"required": [
|
|
13677
|
+
"jobId"
|
|
13678
|
+
],
|
|
13679
|
+
"additionalProperties": false,
|
|
13680
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
13681
|
+
}
|
|
13682
|
+
},
|
|
13607
13683
|
{
|
|
13608
13684
|
"id": "getMediaUploadUrl",
|
|
13609
13685
|
"domain": "core",
|
|
@@ -13973,7 +14049,7 @@ var CLI_MANIFEST = {
|
|
|
13973
14049
|
{
|
|
13974
14050
|
"id": "listAutomationTemplates",
|
|
13975
14051
|
"domain": "automations",
|
|
13976
|
-
"description": "Lists available automation templates for retention (members program) or acquisition (campaign) systems. Use
|
|
14052
|
+
"description": "Lists available automation templates for retention (members program) or acquisition (campaign) systems. Use listTemplateAutomations to preview before applying. Only apply templates when the campaign or members program has no existing flows.",
|
|
13977
14053
|
"type": "query",
|
|
13978
14054
|
"path": [
|
|
13979
14055
|
"api",
|
|
@@ -14013,7 +14089,7 @@ var CLI_MANIFEST = {
|
|
|
14013
14089
|
{
|
|
14014
14090
|
"id": "listCampaigns",
|
|
14015
14091
|
"domain": "core",
|
|
14016
|
-
"description": "
|
|
14092
|
+
"description": "The organization's acquisition campaigns, newest first. Start here to resolve a campaignId: the `id` field (a UUID) is what every other campaign tool takes, NOT the nested Meta campaign id. Also carries each campaign's name, shorthand (used in reservation links), publish state, and referrers. Read one campaign's full configuration with getCampaign.",
|
|
14017
14093
|
"type": "query",
|
|
14018
14094
|
"path": [
|
|
14019
14095
|
"api",
|
|
@@ -14023,6 +14099,34 @@ var CLI_MANIFEST = {
|
|
|
14023
14099
|
],
|
|
14024
14100
|
"inputJsonSchema": null
|
|
14025
14101
|
},
|
|
14102
|
+
{
|
|
14103
|
+
"id": "listCreatives",
|
|
14104
|
+
"domain": "creators",
|
|
14105
|
+
"description": "The generated recruitment creatives for an organization, optionally narrowed to one offer. Each carries an imageKey resolving to the rendered variant that was picked, and selectedImageUrl for the same object as a URL \u2014 pass imageKey to planAds as a libraryAsset reference rather than choosing among the composite fields yourself. imageUrl is the base render and is not the ad asset. staleCreativeIds lists creatives generated from an older version of their offer, and is only populated when offerId is given.",
|
|
14106
|
+
"type": "query",
|
|
14107
|
+
"path": [
|
|
14108
|
+
"api",
|
|
14109
|
+
"dfy",
|
|
14110
|
+
"listCreatives"
|
|
14111
|
+
],
|
|
14112
|
+
"inputJsonSchema": {
|
|
14113
|
+
"anyOf": [
|
|
14114
|
+
{
|
|
14115
|
+
"not": {}
|
|
14116
|
+
},
|
|
14117
|
+
{
|
|
14118
|
+
"type": "object",
|
|
14119
|
+
"properties": {
|
|
14120
|
+
"offerId": {
|
|
14121
|
+
"type": "string"
|
|
14122
|
+
}
|
|
14123
|
+
},
|
|
14124
|
+
"additionalProperties": false
|
|
14125
|
+
}
|
|
14126
|
+
],
|
|
14127
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
14128
|
+
}
|
|
14129
|
+
},
|
|
14026
14130
|
{
|
|
14027
14131
|
"id": "listCreatorApplications",
|
|
14028
14132
|
"domain": "creators",
|
|
@@ -14241,6 +14345,46 @@ var CLI_MANIFEST = {
|
|
|
14241
14345
|
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
14242
14346
|
}
|
|
14243
14347
|
},
|
|
14348
|
+
{
|
|
14349
|
+
"id": "markCreativesPublished",
|
|
14350
|
+
"domain": "creators",
|
|
14351
|
+
"description": "Record which Meta ad a recruitment creative was published in. The linkRecruitmentOffer effect on publishAds records this as part of the publish, so after an effect-declared publish there is nothing left to mark \u2014 call this only when ads were created outside publishAds or a publish's effect reported an error. Pass every creative that went into the ad \u2014 a dynamic ad rotates several at once, so they all take the same fbAdId. Without a record the creatives keep offering to be published again.",
|
|
14352
|
+
"type": "mutation",
|
|
14353
|
+
"path": [
|
|
14354
|
+
"api",
|
|
14355
|
+
"dfy",
|
|
14356
|
+
"markCreativesPublished"
|
|
14357
|
+
],
|
|
14358
|
+
"inputJsonSchema": {
|
|
14359
|
+
"type": "object",
|
|
14360
|
+
"properties": {
|
|
14361
|
+
"creatives": {
|
|
14362
|
+
"type": "array",
|
|
14363
|
+
"items": {
|
|
14364
|
+
"type": "object",
|
|
14365
|
+
"properties": {
|
|
14366
|
+
"creativeId": {
|
|
14367
|
+
"type": "string"
|
|
14368
|
+
},
|
|
14369
|
+
"fbAdId": {
|
|
14370
|
+
"type": "string"
|
|
14371
|
+
}
|
|
14372
|
+
},
|
|
14373
|
+
"required": [
|
|
14374
|
+
"creativeId",
|
|
14375
|
+
"fbAdId"
|
|
14376
|
+
],
|
|
14377
|
+
"additionalProperties": false
|
|
14378
|
+
}
|
|
14379
|
+
}
|
|
14380
|
+
},
|
|
14381
|
+
"required": [
|
|
14382
|
+
"creatives"
|
|
14383
|
+
],
|
|
14384
|
+
"additionalProperties": false,
|
|
14385
|
+
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
14386
|
+
}
|
|
14387
|
+
},
|
|
14244
14388
|
{
|
|
14245
14389
|
"id": "planAds",
|
|
14246
14390
|
"domain": "ads",
|
|
@@ -14284,7 +14428,7 @@ var CLI_MANIFEST = {
|
|
|
14284
14428
|
{
|
|
14285
14429
|
"id": "populateCampaign",
|
|
14286
14430
|
"domain": "campaigns",
|
|
14287
|
-
"description": "Finalizes a campaign that was created with isCreating true. Optionally attaches a promotion/offer image to the campaign. Does NOT set up funnel screens \u2014 the user picks a
|
|
14431
|
+
"description": "Finalizes a campaign that was created with isCreating true. Optionally attaches a promotion/offer image to the campaign. Does NOT set up funnel screens \u2014 follow with applyFunnelTemplate, or the user picks a template in the funnel editor.",
|
|
14288
14432
|
"type": "mutation",
|
|
14289
14433
|
"path": [
|
|
14290
14434
|
"api",
|
|
@@ -14380,7 +14524,7 @@ var CLI_MANIFEST = {
|
|
|
14380
14524
|
{
|
|
14381
14525
|
"id": "publishAds",
|
|
14382
14526
|
"domain": "ads",
|
|
14383
|
-
"description": "Publish a plan produced by planAds. Pass back the variables, overrides and planHash that planAds returned, unchanged. The server re-derives the tree and refuses to publish if it no longer matches the hash, so re-plan and show the human the difference if that happens. This returns as soon as the work is queued \u2014 poll
|
|
14527
|
+
"description": "Publish a plan produced by planAds. Declare what should be recorded once the ads exist through effects, and the worker runs them as part of the job \u2014 a recruitment publish must pass linkRecruitmentOffer with its offerId and creativeIds, which stamps the creatives, stamps the offer that the monthly sourcing cap and the dashboard's spend both read, and texts the program's approver that sourcing is live; omitting it is refused rather than silently skipped. Doing it afterwards through a separate call is a step that can be missed, and missing it is silent. Pass back the variables, overrides and planHash that planAds returned, unchanged. The server re-derives the tree and refuses to publish if it no longer matches the hash, so re-plan and show the human the difference if that happens. This returns as soon as the work is queued \u2014 poll getJob with the returned jobId and jobType to follow it, and read the job's effect outcomes rather than assuming they ran. Everything is created paused; use setAdCampaignStatus to start it. Requires an explicit confirm.",
|
|
14384
14528
|
"type": "mutation",
|
|
14385
14529
|
"path": [
|
|
14386
14530
|
"api",
|
|
@@ -14420,11 +14564,56 @@ var CLI_MANIFEST = {
|
|
|
14420
14564
|
"type": "boolean",
|
|
14421
14565
|
"const": true
|
|
14422
14566
|
},
|
|
14423
|
-
"
|
|
14424
|
-
"type":
|
|
14425
|
-
|
|
14426
|
-
"
|
|
14427
|
-
|
|
14567
|
+
"effects": {
|
|
14568
|
+
"type": "array",
|
|
14569
|
+
"items": {
|
|
14570
|
+
"anyOf": [
|
|
14571
|
+
{
|
|
14572
|
+
"type": "object",
|
|
14573
|
+
"properties": {
|
|
14574
|
+
"type": {
|
|
14575
|
+
"type": "string",
|
|
14576
|
+
"const": "linkFeastCampaign"
|
|
14577
|
+
},
|
|
14578
|
+
"campaignId": {
|
|
14579
|
+
"type": "string",
|
|
14580
|
+
"minLength": 1
|
|
14581
|
+
}
|
|
14582
|
+
},
|
|
14583
|
+
"required": [
|
|
14584
|
+
"type",
|
|
14585
|
+
"campaignId"
|
|
14586
|
+
],
|
|
14587
|
+
"additionalProperties": false
|
|
14588
|
+
},
|
|
14589
|
+
{
|
|
14590
|
+
"type": "object",
|
|
14591
|
+
"properties": {
|
|
14592
|
+
"type": {
|
|
14593
|
+
"type": "string",
|
|
14594
|
+
"const": "linkRecruitmentOffer"
|
|
14595
|
+
},
|
|
14596
|
+
"offerId": {
|
|
14597
|
+
"type": "string",
|
|
14598
|
+
"minLength": 1
|
|
14599
|
+
},
|
|
14600
|
+
"creativeIds": {
|
|
14601
|
+
"type": "array",
|
|
14602
|
+
"items": {
|
|
14603
|
+
"type": "string",
|
|
14604
|
+
"minLength": 1
|
|
14605
|
+
}
|
|
14606
|
+
}
|
|
14607
|
+
},
|
|
14608
|
+
"required": [
|
|
14609
|
+
"type",
|
|
14610
|
+
"offerId",
|
|
14611
|
+
"creativeIds"
|
|
14612
|
+
],
|
|
14613
|
+
"additionalProperties": false
|
|
14614
|
+
}
|
|
14615
|
+
]
|
|
14616
|
+
}
|
|
14428
14617
|
},
|
|
14429
14618
|
"reviewedPlan": {}
|
|
14430
14619
|
},
|
|
@@ -20205,7 +20394,7 @@ Example - opted-in members with more than 5 visits, newest first:
|
|
|
20205
20394
|
{
|
|
20206
20395
|
"id": "updateAutomationFlow",
|
|
20207
20396
|
"domain": "automations",
|
|
20208
|
-
"description": "Update a flow's metadata (the group that holds automations). Pass flowId plus the new title (required), and optionally description and isCampaignCheckDisabled. Does not touch the automations inside the flow \u2014
|
|
20397
|
+
"description": "Update a flow's metadata (the group that holds automations). Pass flowId plus the new title (required), and optionally description and isCampaignCheckDisabled. Does not touch the automations inside the flow \u2014 stage those through the draft loop, or batchEditAutomations for an immediate live write.",
|
|
20209
20398
|
"type": "mutation",
|
|
20210
20399
|
"path": [
|
|
20211
20400
|
"api",
|
package/feast/SKILL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: feast
|
|
3
|
-
description: Operate a Feastalytics organization from the terminal —
|
|
3
|
+
description: Operate a Feastalytics organization from the terminal — campaigns, automations, funnels, members-program rewards, the wallet pass, creator sourcing, Meta ads, onboarding, and read-only data queries — via the `feast` CLI. Use this skill whenever the user wants to inspect or change Feastalytics data outside the dashboard — "list my campaigns", "create an automation for org X", "approve this creator", "publish the recruitment ad", "query my guests", "update the members program", or any request to script/batch/automate Feastalytics operations. Reach for it even when the user doesn't say "CLI" — if the task is reading or changing Feastalytics data, this is the tool.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Feast CLI
|
|
7
7
|
|
|
8
|
-
Drive the Feastalytics platform from the terminal. The `feast` CLI exposes the same tool surface the in-app AI agent uses (campaigns, automations,
|
|
8
|
+
Drive the Feastalytics platform from the terminal. The `feast` CLI exposes the same tool surface the in-app AI agent uses (campaigns, automations, funnels, members program, creator sourcing, Meta ads, onboarding, data queries) as plain commands that hit the production API as the logged-in user.
|
|
9
9
|
|
|
10
10
|
The CLI is the source of truth for *which* tools exist and *what* they accept — always discover that at runtime rather than assuming, because the tool set grows as new endpoints are tagged. Your job is to pick the right tool, scope it to the right organization, and hand it valid input.
|
|
11
11
|
|
|
@@ -79,7 +79,7 @@ Query tools (listing, describing, reading) are safe and read-only. Mutation tool
|
|
|
79
79
|
- Before running one, the CLI resolves the organization server-side and prints its name, so a wrong `--org` shows up as the wrong restaurant rather than an opaque id. Read that line.
|
|
80
80
|
- **There is no confirmation prompt.** A mutation runs the moment you call it. Nothing asks twice, and nothing undoes it.
|
|
81
81
|
|
|
82
|
-
That last point matters most for the tools that reach the real world rather than just the database. Buying a phone number bills the account. Approving a creator visit or deciding a submission sends that person a text immediately and cannot be recalled. Publishing a campaign puts it live, and pricing a recurring promotion creates real Stripe products. Saving automation edits changes what guests receive. Treat those as irreversible, and get the user's intent straight *before* the call, because there is no gate after it.
|
|
82
|
+
That last point matters most for the tools that reach the real world rather than just the database. Buying a phone number bills the account. Approving a creator visit or deciding a submission sends that person a text immediately and cannot be recalled. Paying a creator's bonus charges the organization's card. Publishing a campaign puts it live, and pricing a recurring promotion creates real Stripe products. Activating a Meta campaign spends real ad budget. Saving automation edits changes what guests receive. Treat those as irreversible, and get the user's intent straight *before* the call, because there is no gate after it. (The one schema-level exception: `publishAds` requires `confirm: true` in its input — but that's you confirming, not the CLI asking.)
|
|
83
83
|
|
|
84
84
|
Prefer reading before writing: e.g. `listCampaigns` to find the right `campaignId` before `updateCampaign`, or `describe`/`listAutomationFlows` before creating a flow.
|
|
85
85
|
|
|
@@ -97,18 +97,19 @@ Many tasks are multi-step and have a required ordering the app normally enforces
|
|
|
97
97
|
|
|
98
98
|
| Doing this | Read |
|
|
99
99
|
|---|---|
|
|
100
|
-
| Creating, cloning or configuring a campaign;
|
|
100
|
+
| Creating, cloning or configuring a campaign; promotions | `references/workflows/campaigns.md` |
|
|
101
101
|
| Anything touching automations — creating, editing, simulating, promoting a draft | `references/workflows/automations.md` |
|
|
102
102
|
| Editing funnel screens, applying a funnel template, staging a new screen | `references/workflows/funnels.md` |
|
|
103
103
|
| Writing Meta ad copy — guest-facing or creator recruitment | `references/workflows/facebook.md` |
|
|
104
|
-
|
|
|
104
|
+
| Publishing, pausing, budgeting or diagnosing Meta ads | `references/workflows/ads.md` |
|
|
105
|
+
| Creator sourcing — approving applicants, reviewing content, creatives, payouts | `references/workflows/creators.md` |
|
|
105
106
|
| Members-program rewards; reading or saving the wallet pass configuration | `references/workflows/members-program.md` |
|
|
106
|
-
| Working the onboarding taskboard; brand identity | `references/workflows/onboarding.md` |
|
|
107
|
-
| Searching guests/members
|
|
107
|
+
| Working the onboarding taskboard; brand identity; phone, media, invites, billing | `references/workflows/onboarding.md` |
|
|
108
|
+
| Searching guests/members; querying anything via the data catalog | `references/workflows/guests.md` |
|
|
108
109
|
|
|
109
|
-
Read more than one when a task spans them — a new campaign usually means `campaigns.md` plus `automations.md` and `funnels.md`.
|
|
110
|
+
Read more than one when a task spans them — a new campaign usually means `campaigns.md` plus `automations.md` and `funnels.md`, and publishing a recruitment ad means `creators.md` plus `facebook.md` and `ads.md`.
|
|
110
111
|
|
|
111
|
-
Some things are deliberately **not exposed**: replying to a guest or a creator by SMS, firing an automation at a live member,
|
|
112
|
+
Some things are deliberately **not exposed**: replying to a guest or a creator by SMS, firing an automation at a live member, pass image generation, ad-copy generation (write it yourself), and publishing creator content as partnership ads. The workflow files say which. 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.
|
|
112
113
|
|
|
113
114
|
## Link to what you touched
|
|
114
115
|
|
|
@@ -120,13 +121,13 @@ That file has the dashboard routes with their panel and tab names, the guest-fac
|
|
|
120
121
|
|
|
121
122
|
## Worked example
|
|
122
123
|
|
|
123
|
-
User: "add a
|
|
124
|
+
User: "add a Free Dessert reward members can redeem for 100 points in my Plum Vietnamese org."
|
|
124
125
|
|
|
125
126
|
```bash
|
|
126
|
-
feast whoami
|
|
127
|
-
feast describe
|
|
128
|
-
feast call
|
|
129
|
-
feast call
|
|
127
|
+
feast whoami # find the Plum Vietnamese org id
|
|
128
|
+
feast describe createMembersProgramReward # learn the input shape (type: item vs name)
|
|
129
|
+
feast call listMembersProgramRewards --org <orgId> # avoid duplicating an existing reward or catalog item
|
|
130
|
+
feast call createMembersProgramReward --org <orgId> --input '{"type":"name","name":"Free Dessert","pointsCost":100}'
|
|
130
131
|
```
|
|
131
132
|
|
|
132
133
|
The pattern generalizes: identify the org, learn the tool, resolve any referenced ids, then act.
|
|
@@ -23,13 +23,25 @@ Typical flow: `createCampaign` (set `isCreating: true` if you'll finish it with
|
|
|
23
23
|
- **Authoring:** `createAutomationFlow` makes a flow; `batchEditAutomations` creates/updates/deletes automations in one atomic batch (create ops **require** a `flowId`); `updateAutomationFlow` / `deleteAutomationFlow` manage the flow itself; `simulateAutomations` dry-runs a flow with no real sends. See `workflows/automations.md` for the ordering and the trigger/condition/send-time rules.
|
|
24
24
|
- Templates: `listAutomationTemplates` → `listTemplateAutomations` (preview) → `applyAutomationTemplate`. Only apply a template to a campaign/members-program that has no existing flows.
|
|
25
25
|
|
|
26
|
-
## Offers
|
|
26
|
+
## Offers and promotions
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Promotions live on the campaign record (`getCampaign` / `updateCampaign`), and real menu items come from `queryData` on `interface.catalogItem`.
|
|
29
29
|
|
|
30
30
|
## Members program (retention)
|
|
31
31
|
|
|
32
|
-
The retention counterpart to campaigns: rewards and pass configuration for returning guests. Members-program automations are the flows with no `campaignId` (`scope: "membersProgram"` above).
|
|
32
|
+
The retention counterpart to campaigns: rewards and pass configuration for returning guests. Members-program automations are the flows with no `campaignId` (`scope: "membersProgram"` above). Rewards are fully manageable (`listMembersProgramRewards` / `createMembersProgramReward` / `updateMembersProgramReward` / `deleteMembersProgramReward`), and the wallet pass is a read-modify-write document (`getPassConfiguration` / `updatePassConfiguration`) — see `workflows/members-program.md`.
|
|
33
|
+
|
|
34
|
+
## Creator sourcing
|
|
35
|
+
|
|
36
|
+
Restaurants recruit local content creators to visit and post. One config per location (`getInfluencerBoardConfig`), an approval queue of applications, content review, and bonus payouts — see `workflows/creators.md`. Recruitment *ads* publish through the Meta ads surface (`workflows/ads.md`).
|
|
37
|
+
|
|
38
|
+
## Meta ads
|
|
39
|
+
|
|
40
|
+
A template-driven publish pipeline: `listAdTemplates` → `planAds` → `publishAds` → `getJob` → `setAdCampaignStatus`, plus `ads_*` tools for reading and steering what's already on the ad account — see `workflows/ads.md`.
|
|
41
|
+
|
|
42
|
+
## The data catalog
|
|
43
|
+
|
|
44
|
+
`describeData` / `queryData` expose a read-only, org-scoped query surface over the data model — guests, orders, menu items, texts, creator visits, payouts. When no purpose-built tool answers a read question, the catalog usually does; `describeData` with no arguments is the index.
|
|
33
45
|
|
|
34
46
|
---
|
|
35
47
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Publishing and steering Meta ads
|
|
2
|
+
|
|
3
|
+
> Part of the Feastalytics CLI workflows. Confirm a tool exists with `feast tools` before relying on it, and get its exact fields from `feast describe <tool>` — this file gives the *meaning* and *ordering* the schema can't.
|
|
4
|
+
|
|
5
|
+
Publishing is CLI-drivable end to end: resolve a template, plan, publish, activate. The planner is the only door in — **never hand-assemble Meta campaign parameters**; `publishAds` re-derives everything from the template variables and refuses anything else.
|
|
6
|
+
|
|
7
|
+
For the *words* in the ads, read `facebook.md` first — copywriting is its own discipline with its own failure modes.
|
|
8
|
+
|
|
9
|
+
### The model: template → plan → publish → activate
|
|
10
|
+
|
|
11
|
+
- A **template** is a server-owned recipe. `listAdTemplates` returns each one with its variables, budget range, and which plan paths may be overridden. Each variable that names a `producedBy` tool is telling you exactly where its value comes from — treat that as the shopping list.
|
|
12
|
+
- **`planAds`** resolves template + variables into the exact tree of campaigns, ad sets and ads that would be created. It creates nothing on Meta and changes no Feastalytics data. It returns the tree, a `planHash`, the fully defaulted variables, and validation issues.
|
|
13
|
+
- **`publishAds`** takes those variables, overrides and hash back *unchanged*, re-derives the tree server-side, and refuses on a mismatch — so a stale plan fails loudly instead of publishing something the human never saw. Everything is created **paused**.
|
|
14
|
+
- **`setAdCampaignStatus`** `ACTIVE` starts a campaign Feastalytics published, cascading to every ad set and ad. This is the moment real money starts moving — explicit user confirmation first, every time.
|
|
15
|
+
|
|
16
|
+
### The loop
|
|
17
|
+
|
|
18
|
+
1. `listAdTemplates` — pick the template, read each variable's `producedBy`.
|
|
19
|
+
2. Gather variables with those tools: `ads_get_ad_accounts`, `ads_get_user_pages`, `ads_get_ig_accounts`, `listCreatives`, `getCampaign`, etc. Prefer a Page with `usedByOrganization: true` — the token reaches other businesses' Pages and nothing stops you publishing from the wrong one.
|
|
20
|
+
3. `planAds` — fix every issue with severity `error` and re-plan. Summarize the resulting tree (campaign name, budget, targeting, ad count) for the user before going further; the plan is the thing they're approving.
|
|
21
|
+
4. `publishAds` with the returned `variables`, `overrides` and `planHash` unchanged, plus:
|
|
22
|
+
- `confirm: true` — the schema demands it; this is the only tool with a schema-level confirm.
|
|
23
|
+
- an `idempotencyKey` you generate. Reuse the same key when retrying the *same* publish — a duplicate key returns the earlier job instead of publishing twice. Never reuse one for a new publish.
|
|
24
|
+
- `effects` — see below.
|
|
25
|
+
5. Poll `getJob` with the returned `jobId` + `jobType` until `COMPLETED` or `FAILED`. `{ job: null }` means not landed yet — keep polling. **Read the job's effect outcomes** — each declared effect reports `done`, `skipped` or `error` with a human-readable detail, and effect failures do not fail the job (the ads already exist by then), so this is the only place you find out.
|
|
26
|
+
6. `setAdCampaignStatus` to go live, after the user says go. Check the preflight counts in the response.
|
|
27
|
+
|
|
28
|
+
### Effects: the write-back is declared, not called afterwards
|
|
29
|
+
|
|
30
|
+
Bookkeeping that must happen once the ads exist travels *inside* the publish as `effects`, and the worker runs it as part of the job — because a follow-up call you're supposed to remember is a follow-up call that gets missed, silently.
|
|
31
|
+
|
|
32
|
+
- **A recruitment publish must declare `linkRecruitmentOffer`** with its `offerId` and `creativeIds` — the server refuses the publish without it. The effect stamps the creatives as published (no separate `markCreativesPublished` call needed), stamps the offer that the monthly sourcing cap and the dashboard's spend both read, and texts the program's approver that sourcing is live.
|
|
33
|
+
- **`linkFeastCampaign`** records the published Meta campaign onto a Feast campaign, which is what makes its ads panel and KPIs see the spend.
|
|
34
|
+
|
|
35
|
+
`markCreativesPublished` is the fallback for ads created outside `publishAds`, or for repairing a publish whose effect reported `error`.
|
|
36
|
+
|
|
37
|
+
### Which template
|
|
38
|
+
|
|
39
|
+
- **`directOffer`** — guest-facing offer ads for a campaign. Copy rules: the `adCopy` half of `facebook.md`.
|
|
40
|
+
- **`recruitment`** — creator-recruitment ads. An always-on trickle with an enforced budget floor and ceiling. Creatives come from `createRecruitmentCreatives` → `listCreatives` (pass each creative's `imageKey` as a `libraryAsset` reference); copy rules: the `recruitmentAdCopy` half of `facebook.md`; program context: `creators.md`.
|
|
41
|
+
- **`addAds`** — add fresh creatives to an ad set that is already running. Copy the settings the new ads must match from an existing ad via `ads_get_ad_entities` — its description carries the exact field-by-field recipe, and Meta will happily publish a mismatched ad rather than reject it.
|
|
42
|
+
|
|
43
|
+
### Reading and steering what's live
|
|
44
|
+
|
|
45
|
+
- `ads_get_ad_entities` — read campaigns/ad sets/ads on an account, creatives attached. The diagnostic read for everything below.
|
|
46
|
+
- `ads_update_entity` — rename, re-budget, or pause. Budgets are integer cents and **replace** the current value; read first, confirm the number with the human. Creatives are immutable at Meta — new copy or media means a new ad (the `addAds` template).
|
|
47
|
+
- `ads_activate_entity` — go-live for structures Feastalytics did *not* publish. No cascade: activate top-down and check `willDeliver`; a child under a paused parent is live in name only. For campaigns Feastalytics published, `setAdCampaignStatus` cascades and is the right tool.
|
|
48
|
+
- `ads_get_datasets` / `ads_create_dataset` — pixel checks and creation. The pixel a campaign should optimise against is the one its funnel actually fires (from the layout config), not whichever pixel looks plausible on the account. After creating one, write its id back with `updateBrandIdentity` — creation alone connects nothing.
|
|
49
|
+
|
|
50
|
+
> **Not exposed:** ad-copy generation (write it yourself — `facebook.md`), creative *content* editing on Meta (immutable there), and publishing creator content as partnership ads.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Part of the Feastalytics CLI workflows. Confirm a tool exists with `feast tools` before relying on it, and get its exact fields from `feast describe <tool>` — this file gives the *meaning* and *ordering* the schema can't.
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**Fully authorable** (create / edit / delete / dry-run). This is the richest workflow — the ordering is simpler than the app's, but the domain rules below are what separate a professional flow from a carrier-blocked mess. Follow them when creating, and use them as a checklist when reviewing.
|
|
6
6
|
|
|
7
7
|
### The model: automations live inside flows
|
|
8
8
|
|
|
@@ -26,21 +26,10 @@ The one-shot text→campaign endpoints (`createWithOffer` / `parseCampaignDescri
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Offers and promotions
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
2. `dfyGetMenuHierarchy` with that `locationId` — browse real menu items and prices.
|
|
35
|
-
3. `dfyListOffers` — see the current backlog; avoid duplicates.
|
|
36
|
-
4. `dfyCreateOffer` — create it. `dfyUpdateOffer` / `dfyDeleteOffer` to revise.
|
|
37
|
-
|
|
38
|
-
Every offer picks one **framework**:
|
|
39
|
-
|
|
40
|
-
- **`free`** — give away a low-cost item (appetizer, side, drink, small dessert) with no purchase. Maximizes signups. `offerPrice: null`; `items` = the single free item at its menu price. Headline: "A Complimentary [Item]" / "Free [Item]".
|
|
41
|
-
- **`combo`** — bundle to lift the ticket. *Pattern A* "Buy X, Get Y Free" (`offerPrice` = purchased item only) — best for quick-service. *Pattern B* fixed-price bundle "[Item] & [Item] for $XX" (`offerPrice` = bundle price) — preserves brand equity for upscale.
|
|
42
|
-
- **`experience`** — a curated multi-item tasting/pairing/prix-fixe with **no discount** (`offerPrice` = sum of item prices). MUST have 2+ items.
|
|
43
|
-
|
|
44
|
-
`dfyCreateOffer` needs `name` (internal label, no restaurant name), `headline` (states what the guest gets + price, using real item names), a short `description` (context the headline can't carry), `framework`, `items` (`[{name, price}]` from the menu), `offerPrice`, and `locationId`. Always frame as "offers," never "discounts" or "deals."
|
|
31
|
+
- A campaign's **promotions** are part of the campaign record: read them with `getCampaign`, edit them with `updateCampaign` (including a promotion's `staffInstructions`, and prices — noting the Stripe-products warning in `updateCampaign`'s description).
|
|
32
|
+
- **Real menu data** for grounding any offer or promotion copy comes from `queryData` on `interface.catalogItem` — POS-agnostic, hierarchical via `parentId`/`catalogItemLink`.
|
|
33
|
+
- When you write guest-facing offer language anywhere, frame it as an "offer," never a "discount" or "deal."
|
|
45
34
|
|
|
46
35
|
---
|
|
@@ -50,6 +50,17 @@ A window's `block` is one of two shapes: `once`, with a `utcStart` and `utcEnd`;
|
|
|
50
50
|
|
|
51
51
|
`updateCreativeStrategy` is the revision step. Two things to get right: omitting `strategyId` **creates a new strategy** instead of editing the one you meant, and it replaces the fields you send rather than merging them — so read first, apply your edits to the full `concepts` array, and send the whole thing back. Generating into a strategy that isn't still a draft is rejected rather than silently overwritten.
|
|
52
52
|
|
|
53
|
+
### Recruitment creatives and the recruitment ad
|
|
54
|
+
|
|
55
|
+
The ads that bring applicants in are CLI-drivable end to end:
|
|
56
|
+
|
|
57
|
+
1. `createRecruitmentCreatives` with the `campaignId` — it resolves (or creates) the campaign's recruitment offer itself, which is what groups the creatives and carries the monthly sourcing cap. Each run calls an image model per missing type; `force` deletes and regenerates the whole set, so don't pass it casually.
|
|
58
|
+
2. `listCreatives` — each creative's `imageKey` is the reference `planAds` takes as a `libraryAsset`; `staleCreativeIds` flags creatives generated from an older version of their offer.
|
|
59
|
+
3. Publish through the `recruitment` template in `ads.md`, declaring the **`linkRecruitmentOffer` effect** — the publish is refused without it. The effect stamps the creatives, links the offer (which the sourcing cap and dashboard spend read), and texts the program's approver that sourcing is live.
|
|
60
|
+
4. Copy rules for the ad live in `facebook.md` (`recruitmentAdCopy` — the creator-facing half; conflating it with guest copy is the classic failure).
|
|
61
|
+
|
|
62
|
+
`markCreativesPublished` is only the fallback for recording ads created outside `publishAds` or repairing an effect that reported `error`.
|
|
63
|
+
|
|
53
64
|
### The decision loop
|
|
54
65
|
|
|
55
66
|
1. `listCreatorApplications` — the approval queue, newest first, across every location. Takes no arguments. Use this rather than querying the data model: it carries **`instagramFollowerCount`**, which is usually the deciding factor and isn't reachable any other way.
|
|
@@ -60,6 +71,10 @@ A window's `block` is one of two shapes: `once`, with a `utcStart` and `utcEnd`;
|
|
|
60
71
|
4. `listCreatorSubmissions` with `{ "status": "submitted" }` (and `"revision_requested"`) — the content review queue. Submissions are stored outside the queryable data model, so this tool is the only way to read them.
|
|
61
72
|
5. `decideCreatorSubmission` — `approved`, `rejected`, `revision_requested`, or `under_review`. **This texts the creator too.** `revision_requested` sends your `feedbackMessage` verbatim plus a resubmit link, so write it as something the creator will read, not an internal note. Approving queues their bonus payout. `approvalType` defaults to `"ad"` (the content may run in paid ads) and is rejected when the board's bonus is $0 — use `"organic"` when it's just for their own channels.
|
|
62
73
|
|
|
74
|
+
### Paying the bonus
|
|
75
|
+
|
|
76
|
+
`createInfluencerPayout` charges the organization's card and starts the creator's bonus on its way. **Never call it on your own initiative** — every call needs the client's explicit, fresh approval to pay this specific creator; a standing instruction doesn't count. The endpoint enforces its own preconditions (an ad-approved submission, no payout already active for the visit — one per visit), the amount comes from the board config grossed up to cover the Stripe fee, and after the charge Stripe webhooks carry it to the creator with no further action from you. Follow progress in `queryData` `creators.creatorPayout`, joined to the visit on `visitEventId`.
|
|
77
|
+
|
|
63
78
|
### Conversations
|
|
64
79
|
|
|
65
80
|
`listCreatorConversations` is the "who is waiting on a reply" queue: every creator's SMS thread with `hasUnread`, the last message body and direction, and a derived `visitStatus` chip that's more reliable than reading raw columns.
|
|
@@ -68,8 +83,8 @@ A window's `block` is one of two shapes: `once`, with a `utcStart` and `utcEnd`;
|
|
|
68
83
|
|
|
69
84
|
### Everything else: queryData
|
|
70
85
|
|
|
71
|
-
The `creators` schema exposes `creator` (the person, one row shared across all their applications)
|
|
86
|
+
The `creators` schema exposes `creator` (the person, one row shared across all their applications), `creatorVisitApplication` (one application/visit), and `creatorPayout` (one initiated bonus payout, joined to the visit on `visitEventId`). Join person to visit on `creator.influencerId = creatorVisitApplication.userId`. Use it for anything the tools above don't answer — no-shows, per-location counts, repeat creators, payout history. Content submissions are **not** in the catalog; `listCreatorSubmissions` is the only read.
|
|
72
87
|
|
|
73
|
-
> **Not exposed:**
|
|
88
|
+
> **Not exposed:** replying to a creator's texts or marking a conversation read (the dashboard owns creator messaging), the dashboard's launch-program button itself (its bookkeeping rides the recruitment publish effect — see above), and publishing a creator's submitted content as a partnership ad.
|
|
74
89
|
|
|
75
90
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Part of the Feastalytics CLI workflows. Confirm a tool exists with `feast tools` before relying on it, and get its exact fields from `feast describe <tool>` — this file gives the *meaning* and *ordering* the schema can't.
|
|
4
4
|
|
|
5
|
-
Two jobs live under Meta ads: **writing the ad copy** and **publishing the ad**.
|
|
5
|
+
Two jobs live under Meta ads: **writing the ad copy** and **publishing the ad**. This file is the copywriting half; the publish loop (templates, planning, effects, activation) lives in `ads.md`.
|
|
6
6
|
|
|
7
7
|
**You write the copy yourself.** The dashboard has a "generate copy" button behind an LLM call; there is no CLI equivalent and you shouldn't want one, because it would be you calling an HTTP endpoint in order to call a model. The copy lands on a plain field of the campaign record, so saving it is trivial and covered at the bottom of this file. Everything between here and there is the part that's actually hard.
|
|
8
8
|
|
|
@@ -34,7 +34,7 @@ Generic copy is the failure mode, and specifics are the entire job. The differen
|
|
|
34
34
|
1. `getOrganization` — brand name, cuisine, and the subdomains in `subdomains2[].subdomain`.
|
|
35
35
|
2. `getCampaign` — `name`, `description`, `bannerConfig`, `promotions`, `referrers`, `shorthand`.
|
|
36
36
|
3. `listFunnelScreens` `{ "referrer": "<subdomain>", "campaignId": "<id>" }` — **the actual landing-page copy the guest sees after the click.** This is your source of truth for congruence: the trip from ad to landing page should feel like one continuous thing, not a bait-and-switch. Copy that promises something the landing page doesn't deliver burns the click.
|
|
37
|
-
4. `
|
|
37
|
+
4. `queryData` on `interface.catalogItem` — real menu item names and real prices, not approximations of them. It's hierarchical; walk the tree with `parentId` or `catalogItemLink`.
|
|
38
38
|
|
|
39
39
|
The landing page URL is `https://{referrer}.feastalytics.com/campaign/{campaignId}`, using a referrer from the campaign's own `referrers` rather than just the org's first subdomain.
|
|
40
40
|
|
|
@@ -185,7 +185,7 @@ That's a customer offer wearing a creator ad's clothes. Every one of "free meal"
|
|
|
185
185
|
|
|
186
186
|
`foodCreditCents`, `creatorPayoutCents` and `minFollowerCount` on `recruitmentAdCopy` record the terms your copy actually stated. The dashboard compares them against the live creator board config and flags the copy as drifted when they diverge — so if you write "$30 tab" and leave them unset, nobody finds out when the credit later changes to $50 and the ad starts lying.
|
|
187
187
|
|
|
188
|
-
**
|
|
188
|
+
**Read the creator board config with `getInfluencerBoardConfig` first** — the dining credit, the bonus and the follower minimum live there and nowhere else. Write those exact numbers into the copy, and mirror them into these fields (in **cents** for the two money fields). Don't guess them, and don't ask the user for numbers the config already has.
|
|
189
189
|
|
|
190
190
|
The creator landing page is `/creator-landing` on the org's subdomain with `orgId`, `locId`, `campaignId` and UTM params — fiddly enough that you should reuse the existing `recruitmentAdCopy.landingPageUrl` when the campaign already has copy, rather than reconstructing it.
|
|
191
191
|
|
|
@@ -199,10 +199,10 @@ One `updateCampaign` call writes `adCopy` or `recruitmentAdCopy`. Run `feast des
|
|
|
199
199
|
|
|
200
200
|
## Publishing
|
|
201
201
|
|
|
202
|
-
**
|
|
202
|
+
**CLI-drivable — read `ads.md`.** The loop is `listAdTemplates` → gather variables → `planAds` → `publishAds` (with its effects) → `getJob` → `setAdCampaignStatus`, and that file carries the ordering, the idempotency-key discipline, and the effect declarations that make the publish self-bookkeeping.
|
|
203
203
|
|
|
204
204
|
---
|
|
205
205
|
|
|
206
|
-
> **Not exposed:** ad copy generation (write it yourself, per above)
|
|
206
|
+
> **Not exposed:** ad copy generation (write it yourself, per above). Read `references/links.md` before writing any dashboard link you hand over.
|
|
207
207
|
|
|
208
208
|
---
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Applying a funnel template** expands a whole screen tree server-side in one call: `applyFunnelTemplate` (needs the campaign's funnel unset — a fresh campaign — and resolves the referrer from the campaign). `deleteFunnel` tears one down.
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Individual funnel screens are edited** 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`.
|
|
8
8
|
|
|
9
9
|
### The loop
|
|
10
10
|
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
- `{ "type": "create", "renderable": { "id": "<new-uuid>", ... }, "targetId": "<sibling id>", "position": "before" | "after" | "inside" }` — generate a fresh UUID.
|
|
16
16
|
- `{ "type": "delete", "id": "<renderableId>" }` and `{ "type": "move", "id": "...", "targetId": "...", "position": "..." }`.
|
|
17
17
|
**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`.
|
|
18
|
-
4. **Preview**
|
|
19
|
-
|
|
18
|
+
4. **Preview** — two ways, use whichever fits how you can look at things:
|
|
19
|
+
- `previewFunnelDraft` renders every screen to a **PDF** (one screen per page, mobile viewport) and returns a short-lived download URL — the option that works when you can read files but not browse.
|
|
20
|
+
- The live preview page `https://{referrer}.feastalytics.com/preview/{draftId}/{campaignId}` (drop `/{campaignId}` for a members-program draft) renders the funnel as a flow diagram with the edits applied — the link to hand the user.
|
|
21
|
+
Iterate: re-run `listFunnelScreens` **with the `draftId`** to read the funnel *with* the staged edits, stage more, re-preview — until it's right.
|
|
22
|
+
5. **`saveFunnelEdits`** `{ "draftId": "..." }` — **promotes to prod, with no confirmation prompt**: applies the draft's edits to the live funnel and marks the draft `promoted`. To abandon instead, `discardFunnelDraft`.
|
|
20
23
|
|
|
21
24
|
`saveFunnelEdits` can also take an inline `{ "referrer", "campaignId", "edits": [ { "screenId", "edit" } ] }` array instead of a `draftId` — a one-shot save with no persisted draft (you lose the preview step, so prefer the draft loop when the change is visual).
|
|
22
25
|
|
|
@@ -2,9 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
> Part of the Feastalytics CLI workflows. Confirm a tool exists with `feast tools` before relying on it, and get its exact fields from `feast describe <tool>` — this file gives the *meaning* and *ordering* the schema can't.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`searchUsers` returns a page of recent member activity — one event per member, each carrying the member's `serialNumber` plus the event (type, time, related object).
|
|
6
6
|
|
|
7
7
|
- Filter with `query` (free-text name), `eventTypes` (e.g. `sentText`, `receivedText`, `scan`, `order`, `rewardAwarded`, `rewardRedeemed`, `checkout`, the `*Attribution` types), `progressMinBound`/`progressMaxBound` (visit-count range), `isUnread: true` (members with unanswered inbound texts), `orderBy` (ASC|DESC by event time).
|
|
8
8
|
- Paginate with `limit` (default 100) and `cursor` (pass back the `cursor` from the previous call; an undefined cursor means no more pages).
|
|
9
9
|
|
|
10
10
|
**Replying by SMS is NOT exposed, deliberately.** The send primitive enforces opt-out, quiet-hours, dedup, and rate limits *downstream* (not at the endpoint), and opt-in is currently gated only by a UI control. If a reply capability is ever exposed, it must run with confirmation and must not bypass those guardrails. For now, tell the user that replying to guests is done in the app.
|
|
11
|
+
|
|
12
|
+
## Everything else: the data catalog
|
|
13
|
+
|
|
14
|
+
`searchUsers` answers "recent activity, one event per member." Every other read question about guests — and about orders, menu items, texts, reservations, creator visits, payouts — goes through **`describeData` → `queryData`**:
|
|
15
|
+
|
|
16
|
+
- `describeData` with no arguments returns the index of every queryable object type plus the full query grammar; narrowed by schema or object type it returns full column detail. Never guess column names.
|
|
17
|
+
- `queryData` is read-only and always scoped to the calling organization — never filter on organizationId yourself.
|
|
18
|
+
- Six schemas: `interface` (POS-agnostic orders, order items, menu `catalogItem`s, `location`s, reservations — same shape whichever POS the org runs), `core` (guests/members), `events` (user events), `texting` (SMS logs), `creators` (visits and payouts), `attribution` (campaign attribution). Prefer `interface` for anything POS-shaped.
|
|
19
|
+
|
|
20
|
+
Typical uses: visit counts and cohorts, order history for one guest, menu items with real prices for grounding copy, text delivery history, creator payout status.
|
|
@@ -8,18 +8,19 @@
|
|
|
8
8
|
The retention counterpart to campaigns — flows with no `campaignId`.
|
|
9
9
|
|
|
10
10
|
- **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.
|
|
11
|
-
- **Rewards are
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
11
|
+
- **Rewards are fully manageable.** `listMembersProgramRewards` returns every reward with its catalog item's `name`, `staffInstructions`, `pointsCost` and `source` — the item is resolved across the Feast, Toast, Square and Clover catalogs, and a `null` name means it no longer exists in any of them.
|
|
12
|
+
- **Creating takes one of two shapes.** `{ "type": "item", "itemId" }` promotes an existing catalog item — prefer it whenever the item already exists in the POS. `{ "type": "name", "name" }` looks the name up across all four catalogs and creates a new Feast item only if nothing matches; **the match is exact, so a near-miss silently duplicates a menu item the restaurant already has** — check `listMembersProgramRewards` or the catalog first. A name shared by several items returns a CONFLICT listing candidates so you can pass `itemId` instead. `staffInstructions` only exist on Feast items and are rejected for POS-sourced ones.
|
|
13
|
+
- **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. To find orphans, cross-reference `listAutomations` for `awardReward` actions carrying the reward's `itemId`.
|
|
14
|
+
- `updateMembersProgramReward` corrects a reward in place — `pointsCost: null` converts a points reward into an automation-granted one. `deleteMembersProgramReward` is the orphan cleanup; it leaves the catalog item alone (it may be a real menu item) and doesn't claw back anything already redeemed.
|
|
14
15
|
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
## Wallet pass configuration
|
|
18
19
|
|
|
19
|
-
The pass (the wallet membership card) is
|
|
20
|
+
The pass (the wallet membership card) is read and written as a whole document.
|
|
20
21
|
|
|
21
22
|
- **`getPassConfiguration`** `{}` — returns the latest live configuration: `sections`, `features`, `locations`, `metadata`, and its `version`.
|
|
22
|
-
- **`updatePassConfiguration`** — **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 —
|
|
23
|
+
- **`updatePassConfiguration`** — **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 — there is no confirmation prompt, so treat it with the same care as a live send.
|
|
23
24
|
- Pass **image generation** (punch-card strips etc.) is not exposed — image workflows still need the app.
|
|
24
25
|
|
|
25
26
|
---
|
|
@@ -8,12 +8,27 @@
|
|
|
8
8
|
`getTaskboard` is the single "what needs fixing or finishing" surface: one `entries` list discriminated by `kind`. `task` entries are the org's onboarding tasks; `issue` entries are live-computed misconfigurations with a `fixHint`. Scope with `{"scope":{"type":"onboarding"}}` for tasks only, `{"type":"task","task":{"taskId":"..."}}` for one task, or leave the default `all`.
|
|
9
9
|
|
|
10
10
|
- **Start from `completionInstructions`, not guesswork.** Every task entry says exactly what completes it and whether it needs a human in a browser. Trust it over inferring from the task name.
|
|
11
|
-
- **Split the work accordingly.**
|
|
11
|
+
- **Split the work accordingly.** Campaigns, automations, funnel fixes, rewards, brand identity, the phone number, image uploads and the onboarding form are all completable through CLI tools — do them. Tasks that need OAuth (Facebook, POS), physical device setup, or in-restaurant staff training cannot be: hand the user that task's **`completionUrl`** — a page where they complete exactly that task. Paste the URL directly in your reply; in the dashboard chat it opens the task next to the conversation, and in a terminal it's clickable.
|
|
12
12
|
- **Never claim a task complete or try to mark one.** Statuses are derived from live data by a recompute (triggered by every taskboard read, ~30s lag) — do the underlying work, then re-read the taskboard to confirm the checkmark flipped.
|
|
13
13
|
- Working through onboarding = repeat: `getTaskboard` (scope `onboarding`) → do the CLI-doable incomplete required tasks → hand over completionUrls for the rest → re-read to verify.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## The onboarding form
|
|
16
|
+
|
|
17
|
+
Some tasks read self-reported answers rather than observed data — launch date, funnel direction, per-step `isComplete` markers. `getOnboardingForm` reads them (`null` when the org has no form yet); `updateOnboardingForm` writes them. **Nested step objects are replaced, not merged** — read first and send back the whole step you're editing (`data` is the exception and is merged). Setting `pos.details.type` to `"other"` provisions a manual-entry POS location as a side effect.
|
|
18
|
+
|
|
19
|
+
Two POS setup tasks complete off `updateOrganization` instead: `staffInstructions.scan` completes *Members Program Visits POS setup*, and `.prepaid` is additionally required for *Campaign POS setup* when the promotion allows pre-pay. `staffInstructions` is replaced wholesale — send every key you want to keep.
|
|
16
20
|
|
|
17
21
|
## Establishing brand identity
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
1. `searchGooglePlaces` — resolve the restaurant to its Google Place. Include the city in the query; only trust `confidentMatch` when it's non-null, otherwise show the candidates and let the customer pick.
|
|
24
|
+
2. `createBrandIdentity` — the branded site: a subdomain, layout config, and a full default screen tree. The subdomain is claimed **across all organizations** and gates everything funnel-shaped downstream, so confirm the name with the customer first. Get `logoUrl` via `getMediaUploadUrl`.
|
|
25
|
+
3. `updateBrandIdentity` — business data, theme, tracking pixel IDs, OpenTable links, and the Google Place link. It deep-merges, so send only what you're changing. **Setting `googleConfig.placeId` enqueues a review/photo scrape; changing an existing placeId orphans everything scraped under the old one** — confirm before replacing.
|
|
26
|
+
|
|
27
|
+
Browser-only: the brand *import* intelligence — auto-extracting a usable palette and logo from a scraped site lives in the app, not the API. If the customer wants that flow, hand them the dashboard.
|
|
28
|
+
|
|
29
|
+
## Plumbing the taskboard leans on
|
|
30
|
+
|
|
31
|
+
- **Phone number** — `searchAvailablePhoneNumbers` (free, search by the restaurant's own postal code or coordinates — proximity beats a memorable area code) then `purchaseAndConfigurePhoneNumber`, which **bills the account irreversibly**. Establish where the restaurant actually is before buying.
|
|
32
|
+
- **Media** — `getMediaUploadUrl` (PUT the bytes to the presigned URL, then reference the returned key), `listMedia`, `deleteMedia`. This is how logos and offer images get in from the CLI.
|
|
33
|
+
- **Team** — `inviteUser` sends a real email immediately and **defaults to OWNER** (full billing access) — always pass `role` explicitly; VIEWER is read-only, SCANNER is for staff running the scanner app.
|
|
34
|
+
- **Billing** — `getBillingStatus`, read-only: `hasAccess` answers "can they use the product," `needsPayment` flags the states worth acting on. Every billing write stays in the dashboard.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feastalytics/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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": {
|