@koda-sl/baker-cli 0.432.0-dev.795979397 → 0.433.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -32
- package/dist/{chunk-PHLFNYGQ.js → chunk-4JBT2CSW.js} +4 -4
- package/dist/{chunk-GTHLEQHU.js → chunk-7BQIIJLV.js} +6 -27
- package/dist/chunk-7BQIIJLV.js.map +1 -0
- package/dist/{chunk-GCTR4MDE.js → chunk-LDS7LUKK.js} +2 -2
- package/dist/{chunk-FQOKJYKN.js → chunk-SPPJRGQF.js} +13 -4
- package/dist/chunk-SPPJRGQF.js.map +1 -0
- package/dist/{chunk-Q6F5UNOU.js → chunk-X46KRGZU.js} +8 -5
- package/dist/{chunk-Q6F5UNOU.js.map → chunk-X46KRGZU.js.map} +1 -1
- package/dist/{chunk-RFZYRG2Q.js → chunk-YBJ7KJNA.js} +11 -3
- package/dist/chunk-YBJ7KJNA.js.map +1 -0
- package/dist/cli.js +43651 -43303
- package/dist/cli.js.map +1 -1
- package/dist/{client-GS7T7ETG.js → client-HQ7LO4CO.js} +4 -4
- package/dist/engine/index.js +3 -3
- package/dist/{env-SL22S6CM.js → env-M4DKUWLD.js} +4 -2
- package/dist/output-T4MY3I56.js +22 -0
- package/dist/{shared-XR54KFMH.js → shared-RZMKZE4W.js} +6 -6
- package/package.json +1 -1
- package/dist/chunk-FQOKJYKN.js.map +0 -1
- package/dist/chunk-GTHLEQHU.js.map +0 -1
- package/dist/chunk-RFZYRG2Q.js.map +0 -1
- package/dist/output-OFDKEPPJ.js +0 -18
- /package/dist/{chunk-PHLFNYGQ.js.map → chunk-4JBT2CSW.js.map} +0 -0
- /package/dist/{chunk-GCTR4MDE.js.map → chunk-LDS7LUKK.js.map} +0 -0
- /package/dist/{client-GS7T7ETG.js.map → client-HQ7LO4CO.js.map} +0 -0
- /package/dist/{env-SL22S6CM.js.map → env-M4DKUWLD.js.map} +0 -0
- /package/dist/{output-OFDKEPPJ.js.map → output-T4MY3I56.js.map} +0 -0
- /package/dist/{shared-XR54KFMH.js.map → shared-RZMKZE4W.js.map} +0 -0
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ export BAKER_CHAT_ID="<chat-id>"
|
|
|
46
46
|
- `BAKER_GSC_SITE_URL` — default GSC site URL. Used when `--site-url` is not passed. If neither is set and exactly one site is verified, it is auto-selected.
|
|
47
47
|
- `BAKER_X_ADS_ACCOUNT_ID` — default X Ads account ID (base36). Used when `--account-id` is not passed. If neither is set and exactly one X Ads account is connected, it is auto-selected.
|
|
48
48
|
- `BAKER_CHAT_ID` — chat context for staged Work Action commands and staged Scheduled agent create/update/delete/unstage. `scheduled-agents run` does not require it. When set, it is also sent as an `x-baker-chat-id` header on every request, so library additions that apply immediately (images, videos, followed brands) are listed in that chat's changes as already applied.
|
|
49
|
+
- `BAKER_THREAD_ID` — set per turn by the sandbox. Sent as an `x-baker-thread-id` header alongside the chat header, so the tools a command reports or changes are those of whoever sent this turn, even while someone else writes in another thread of the same chat.
|
|
49
50
|
|
|
50
51
|
## Debug logging
|
|
51
52
|
|
|
@@ -1116,10 +1117,24 @@ baker ads linkedin analytics --level campaign --campaign-id 1234 \
|
|
|
1116
1117
|
# "Top companies seeing this campaign" (ABM feedback loop) — org names auto-resolved
|
|
1117
1118
|
baker ads linkedin top-companies --campaign-id 1234 --last-days 30
|
|
1118
1119
|
|
|
1120
|
+
# "Which companies are our ads reaching?" — one row per company over the whole period,
|
|
1121
|
+
# most engaged first ({org, name, impressions, clicks, engagements, costLocal}).
|
|
1122
|
+
# Defaults: last 12 calendar months, the only selected ad account, top 100 (--full for all).
|
|
1123
|
+
baker ads linkedin companies --account-id 1234
|
|
1124
|
+
baker ads linkedin companies --page 2 # companies 101-200, most engaged first
|
|
1125
|
+
baker ads linkedin companies --by month --min-impressions 50 # months[] per company
|
|
1126
|
+
baker ads linkedin companies --campaign-id 1,2 --by campaign # campaigns[] per company
|
|
1127
|
+
baker ads linkedin companies --with-domains --stats --output csv # + domain/domainSource; read-quality stats
|
|
1128
|
+
|
|
1119
1129
|
# "Who are these org URNs?" — resolve org URNs to company names (probe leakage)
|
|
1120
1130
|
baker ads linkedin resolve --urns urn:li:organization:17719,urn:li:organization:19022
|
|
1121
1131
|
baker ads linkedin resolve --ids 17719,19022 --output csv
|
|
1122
1132
|
|
|
1133
|
+
# "What are these companies' website domains?" — for matching against CRM companies.
|
|
1134
|
+
# Per org: vanityName (LinkedIn page), website, domain, domainSource (website | subdomain | none);
|
|
1135
|
+
# ids LinkedIn doesn't know land in `errors`. Shortlinks are never followed.
|
|
1136
|
+
baker ads linkedin resolve --ids 1035,1337 --with-domains
|
|
1137
|
+
|
|
1123
1138
|
# "Who is in our audience by industry/seniority/function/title — all at once?"
|
|
1124
1139
|
baker ads linkedin demographics --campaign-id 1234 --last-days 30
|
|
1125
1140
|
|
|
@@ -1188,6 +1203,7 @@ creatives # default ACTIVE intended-status
|
|
|
1188
1203
|
|
|
1189
1204
|
analytics # see "Smart defaults" above (3-axis: level × intent × pivot)
|
|
1190
1205
|
demographics # sweep all firmographic pivots in one call
|
|
1206
|
+
companies # which companies the ads reached, one row per company (--by month|campaign, --with-domains)
|
|
1191
1207
|
top-companies # pivot=MEMBER_COMPANY shortcut (ABM)
|
|
1192
1208
|
conversation # pivot=CONVERSATION_NODE_BUTTON shortcut (Sponsored Messaging)
|
|
1193
1209
|
|
|
@@ -1207,6 +1223,7 @@ audit --format md # deliverable-ready markdown table
|
|
|
1207
1223
|
campaign-groups create|update|pause|resume|duplicate # staged writes (see below)
|
|
1208
1224
|
campaigns create|update|pause|resume|archive|duplicate
|
|
1209
1225
|
campaigns url-params <ad-set-id> # UTMs for every ad in the ad set
|
|
1226
|
+
campaigns exclude-companies <ad-set-id> --org <ids> # stop an ad set reaching these companies
|
|
1210
1227
|
account url-params # UTMs for every ad in the whole account
|
|
1211
1228
|
url-params [--ad-sets] # read what is tagged now, and what lands twice
|
|
1212
1229
|
creatives create|update|pause|resume|duplicate
|
|
@@ -1247,9 +1264,16 @@ baker ads linkedin account url-params --param "utm_source=linkedin&utm_medium=pa
|
|
|
1247
1264
|
baker ads linkedin campaigns update 123456 --audience-expansion off --lan off
|
|
1248
1265
|
baker ads linkedin campaigns update 123456 --bid 7.50 --daily-budget 100 --currency USD
|
|
1249
1266
|
|
|
1250
|
-
# Matched audiences (CSV with header; email columns SHA-256 hashed locally)
|
|
1267
|
+
# Matched audiences (CSV with header; email columns SHA-256 hashed locally).
|
|
1268
|
+
# Company lists take companyName,domain,organizationUrn — organizationUrn matches the company page exactly
|
|
1251
1269
|
baker ads linkedin audiences create --name "ABM Tier-1" --type company-list --list-file accounts.csv
|
|
1252
1270
|
|
|
1271
|
+
# Stop an ad set reaching customers / open deals — add-only; at publish Baker re-reads the ad set
|
|
1272
|
+
# and merges into its targeting as it stands then. --org takes company ids, ≤200 per call;
|
|
1273
|
+
# a longer list goes into a company-list audience passed as --segment
|
|
1274
|
+
baker ads linkedin campaigns exclude-companies 123456 --org 1035,1441
|
|
1275
|
+
baker ads linkedin campaigns exclude-companies 123456 --segment urn:li:dmpSegment:42
|
|
1276
|
+
|
|
1253
1277
|
# Conversions with playbook-default windows
|
|
1254
1278
|
baker ads linkedin conversions create --name "Demo booked" --type LEAD --method INSIGHT_TAG \
|
|
1255
1279
|
--post-click-window 30 --view-window 7
|
|
@@ -1311,7 +1335,7 @@ Each finding: `{id, area, check, status, severity, evidence, fix: {explanation,
|
|
|
1311
1335
|
- OAuth tokens auto-refresh server-side. On 401, reconnect LinkedIn from dashboard → integrations.
|
|
1312
1336
|
- Account scoping: backend rejects calls against an account not in the picker selection.
|
|
1313
1337
|
- Pinned API version: `Linkedin-Version: 202606` (declared in `oauth/constants.ts`). Bump as a coordinated change — LinkedIn deprecates monthly with a ~12-month support window. The floor is `TRACKING_PARAM_DYNAMIC_MIN_API_VERSION` (`CREATIVE_NAME` needs 202606), asserted in `ads/linkedin/utils.test.ts`.
|
|
1314
|
-
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, bid-pricing/forecast 6h, audience-size 1h.
|
|
1338
|
+
- Cache TTLs: accounts/account-detail 1h, listings 30m, analytics 15m–6h (depends on date range), facets 7d, urn-resolve 1h, org domains 7d, bid-pricing/forecast 6h, audience-size 1h.
|
|
1315
1339
|
- `--no-cache` on any command skips both cache layers (the CLI's own and the server's).
|
|
1316
1340
|
|
|
1317
1341
|
#### LinkedIn-specific gotchas
|
|
@@ -1440,7 +1464,7 @@ Shared flags: `--days <n>` (default 30) or `--start-date` / `--end-date` (`YYYY-
|
|
|
1440
1464
|
|
|
1441
1465
|
**`tracking` answers "can this account be reported on at all", and it reads the ad account to do it.** `analytics ads` can only describe parameters that have already arrived, which leaves two blind spots that mislead in the expensive direction: a platform with no traffic in the window looks exactly like a platform whose ads carry nothing, and a dimension arriving under a name nobody has mapped looks exactly like one nothing carries. `baker analytics tracking` reads the mapping, the traffic **and** the client's own Google tracking templates / Meta URL parameters, so it can answer for a platform that has not had a click yet. Every dimension comes back in one of four states, and they are four different jobs: `covered` is done; `unmapped` means the URLs already carry it under a name Baker does not read, so run the `mapCommand` in the response — free, and it repairs the traffic already collected; `missing` means nothing carries it; `unknown` means no visit arrived *and* the account could not be read, which is not a finding and must never be reported as untagged.
|
|
1442
1466
|
|
|
1443
|
-
**`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write.
|
|
1467
|
+
**`fix` says which level to put the missing pairs at, and who can write it.** Not a field name — the levels that platform accepts tagging at, widest first (the widest is the one to prefer: it covers campaigns nobody has created yet), which of them Baker can stage and which the user has to do by hand, and how each field treats a value already in it. That last part is the one people skip and it differs per platform: a Google **campaign** final URL suffix *replaces* the account one rather than merging, LinkedIn's `url-params` call *sets the ad set's whole parameter set* and Baker cannot read back what is in it, and a Meta creative is immutable so a live ad cannot be retagged through Baker at all. `suffix` is only ever the **missing** pairs, so the rule everywhere is the same: read what is there, work out what is missing, write the union. Where `fix` says Baker has no write surface — Microsoft, TikTok, Reddit, Pinterest, Snapchat — that is a request to hand the user with the exact string and the exact screen, never a reason to stop on the platforms it can write. ChatGPT Ads is one Baker can write: its campaign and ad group `--tracking-template` (`baker ads chatgpt campaigns|ad-groups update`), which should carry `campaign_id={campaign_id}&ad_group_id={ad_group_id}&ad_id={ad_id}` — an id that only rides in `utm_campaign` or `utm_content` is reported as that UTM and never as the campaign or the ad, so `--platform chatgpt --role adgroup_id` has nothing to split by.
|
|
1444
1468
|
|
|
1445
1469
|
**The one recommendation about the ad account itself.** `baker analytics ads --platform <p>` warns when that platform's traffic carries no campaign id, ad group id or ad id — the three tiers every "which ad worked" question is grouped by — and returns the exact URL suffix, in the platform's own macro language, that would start sending the missing ones. It is the only mapping gap that cannot be fixed afterwards: naming a parameter Baker already captures corrects the history, because roles are resolved when a report is read, but a parameter the ads never sent has no stored values for a meaning to apply to. The hint stays quiet for a platform with little traffic, and defers to the untagged-account finding rather than repeating it.
|
|
1446
1470
|
|
|
@@ -1803,22 +1827,6 @@ baker gsc sitemaps --site-url "https://example.com/"
|
|
|
1803
1827
|
|
|
1804
1828
|
---
|
|
1805
1829
|
|
|
1806
|
-
### Making ads (`baker creatives brief` / `ideas` / `make`)
|
|
1807
|
-
|
|
1808
|
-
Ads start from what customers said. Three commands carry the process; the agent writes the ideas and the copy.
|
|
1809
|
-
|
|
1810
|
-
```bash
|
|
1811
|
-
baker creatives brief [--adset <id>] [--account-id act_<id>] # one read: viewer, account bottleneck, what works, taste, customer quotes, ideas on file
|
|
1812
|
-
baker creatives ideas [--full] # check and rank src/creatives/_ideas/*.md; build_next
|
|
1813
|
-
baker creatives make <idea> [--slug <s>] [--adset <id>] [--reference <adId>] [--spec <beats.json>] [--from-idea]
|
|
1814
|
-
baker creatives learn [--days <n>] [--dry-run] # what each idea's ads did, written back onto the idea
|
|
1815
|
-
```
|
|
1816
|
-
|
|
1817
|
-
- `brief` returns a verdict, not raw numbers: `account.bottleneck` is `hook`, `after_click`, `none` or `too_little_data`. A part that cannot be read says `unavailable`. `market` says what competitors' ads already say (the followed brands, else the library), how sophisticated the market is, and which awareness stages nobody speaks to.
|
|
1818
|
-
- `ideas` is free and local. An idea's `evidence` resolves to `src/content/insights/` entries, or it is an `explore` bet. Numbers must be in the Knowledge or our own customers' quotes (a Reddit, forum or web quote never sources a claim), no two ideas may share an angle and format, and a review or testimonial format needs a quote from our own customer.
|
|
1819
|
-
- `learn` pools each built idea's creatives, judges them against the rest of the account's cost per result and writes `status` (`winner`, `failed`, `inconclusive`, `testing`) and a frozen `result` onto the idea. `ideas` then warns when a new idea repeats a failed one.
|
|
1820
|
-
- `make` builds one idea. A static searches the idea's container for ads made for our viewer, checks the closest three with `winning-ads fit` and scaffolds from the first that fits. A video idea takes its beats spec. Either way it records the idea on `_definition.md`, sets `prompt.json`'s `message` and syncs the definition. Drafted copy lines claiming a figure the company never wrote come back first in `hints`. When no proven ad fits (or with `--from-idea`), a static starts from the idea itself: `prompt.json` holds the message and placeholders, and `canvas run` refuses to render until the scene and copy are written.
|
|
1821
|
-
|
|
1822
1830
|
### Competitive Intelligence (`baker research`)
|
|
1823
1831
|
|
|
1824
1832
|
Market and competitor research powered by DataForSEO and AI-powered Google Search grounding. Shows who's competing for keywords, search intent classification, competitor keyword strategies, keyword gaps, landing page performance, and open-ended AI research.
|
|
@@ -1950,16 +1958,6 @@ Every search or thread item carries verbatim `text`, `author`, `score`, `created
|
|
|
1950
1958
|
|
|
1951
1959
|
---
|
|
1952
1960
|
|
|
1953
|
-
### `baker research customers`
|
|
1954
|
-
|
|
1955
|
-
Finds what real customers say and writes each quote as an insight in `src/content/insights/` (type, quote, source, date, `status: observed`). Sources: the company's reviews library, Reddit, and with `--web` pages from web research. A quote is kept only when it appears word for word in the text that was fetched; the model classifies quotes and never writes them.
|
|
1956
|
-
|
|
1957
|
-
```bash
|
|
1958
|
-
baker research customers --focus "solar panels" # reviews + Reddit
|
|
1959
|
-
baker research customers --query "payroll mistakes" --web --limit 15
|
|
1960
|
-
baker research customers --dry-run # print, write nothing
|
|
1961
|
-
```
|
|
1962
|
-
|
|
1963
1961
|
### `baker research advertisers "keyword"`
|
|
1964
1962
|
|
|
1965
1963
|
Returns a ranked list of **domains** competing for one keyword in Google, with average SERP position, relevance rating, estimated traffic value and visibility — domain economics, **NOT ad copy**. Despite the name it returns no headlines, descriptions or creative of any kind; for a competitor's actual ad copy use `baker winning-ads content <adId>` (Meta/LinkedIn only — Google SERP ad copy is not available anywhere in Baker).
|
|
@@ -3573,6 +3571,7 @@ baker scheduled-agents create --name "Celebrate the first sale" --description "O
|
|
|
3573
3571
|
baker scheduled-agents update <id-or-temp_agent_id> --enabled false
|
|
3574
3572
|
baker scheduled-agents update <id-or-temp_agent_id> --run-at "2026-09-02T07:00:00Z" --prompt "Use the launch checklist"
|
|
3575
3573
|
baker scheduled-agents update <id-or-temp_agent_id> --trigger experiment.ended
|
|
3574
|
+
baker scheduled-agents update <id-or-temp_agent_id> --slack "#marketing" # each run opens a thread there; also here | me | off
|
|
3576
3575
|
baker scheduled-agents delete <id-or-temp_agent_id>
|
|
3577
3576
|
baker scheduled-agents unstage <id-or-temp_agent_id> # everything this chat staged on it
|
|
3578
3577
|
baker scheduled-agents unstage <id-or-temp_agent_id> --index 2 # one staged change only
|
|
@@ -3950,7 +3949,7 @@ baker setup-links list # active lin
|
|
|
3950
3949
|
|
|
3951
3950
|
---
|
|
3952
3951
|
|
|
3953
|
-
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot deals properties | fields` · `baker hubspot meetings list | view`
|
|
3952
|
+
### `baker hubspot forms list | view | submissions` · `baker hubspot workflows list | view` · `baker hubspot pipelines list` · `baker hubspot contacts summary | lookup | properties | fields` · `baker hubspot companies search` · `baker hubspot deals list | properties | fields` · `baker hubspot meetings list | view`
|
|
3954
3953
|
|
|
3955
3954
|
Read-only view of the company's **connected HubSpot account** — the forms and meeting links (calendars) that live there, the leads those forms received, and the workflows, deal pipelines, and contact records a lead runs through afterwards. Fetched from HubSpot live on every call; nothing is cached and nothing is ever written back.
|
|
3956
3955
|
|
|
@@ -3972,6 +3971,8 @@ baker hubspot pipelines list # where a lead lands, and the st
|
|
|
3972
3971
|
baker hubspot contacts summary --days 30 # are recent leads being worked? (counts only)
|
|
3973
3972
|
baker hubspot contacts lookup lead@example.com # was this one lead picked up?
|
|
3974
3973
|
baker hubspot contacts properties # what a HubSpot-read source can watch, with each value
|
|
3974
|
+
baker hubspot companies search --domains acme.com,globex.io --with-deals # CRM accounts by domain, with their deals
|
|
3975
|
+
baker hubspot deals list --pipeline default --modified-since 2026-01-01 # deals: amount, stage, closed won
|
|
3975
3976
|
baker hubspot meetings list # every calendar + whether booking redirects
|
|
3976
3977
|
baker hubspot meetings view <slug> # booking-form fields for one calendar
|
|
3977
3978
|
baker hubspot meetings view <slug> --as-node # the form.external blob for a flow node
|
|
@@ -3982,7 +3983,9 @@ baker hubspot meetings view <slug> --as-node # the form.external blob for a f
|
|
|
3982
3983
|
**`forms submissions`:** positional `<formId>`; `--days <1-365>` (default 30) sets the window; `--full` adds each submission's field values. The default response is counts and dates only — `total`, `inWindow`, `lastSubmittedAt`, and per-day counts — because submissions carry the lead's own contact details. `truncated: true` means the page budget ran out before the window was covered, so `inWindow` is a floor rather than an exact count.
|
|
3983
3984
|
**`workflows list` flags:** `--search <text>`, `--enabled-only`. Disabled workflows are listed too — a form wired to one that is off looks connected and does nothing.
|
|
3984
3985
|
**`workflows view`:** positional `<workflowId>` (the id from `workflows list`, and the one in HubSpot's own URL — the command translates it to HubSpot's separate internal flow id for you). Returns the enrolment criteria and whether it re-enrols (`null` = HubSpot does not report it for that criteria type, so it is unknown rather than off), plus every step with the property it writes. On a `LIST_BRANCH` step, branches come back in **evaluation order** (`order: 1` wins over `order: 2`) — a broad branch above a narrower one silently swallows the records the narrow one was written for, and HubSpot reports nothing. A `STATIC_BRANCH` matches on a value rather than top down, so its `order` is display only. `steps` is **not** in execution order; follow `nextActionId`. `--full` adds the raw payload, the only place the branch filter criteria (which property is compared to which value) can be read.
|
|
3985
|
-
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot.
|
|
3986
|
+
**`pipelines list`:** no flags; stages come back in the order they run in HubSpot, each with `isClosed` and `isWon` (closed with probability 1).
|
|
3987
|
+
**`companies search` flags:** `--domains <a.com,b.com>` (up to 100; a URL or `www.` is reduced to the bare domain), `--names <"Acme, Inc.;Globex">` (semicolon-separated), `--with-deals`, `--limit <1-500>` (default 100), `--output json|csv|jsonl|md`. Rows: `{ id, name, domain, linkedinPage, deals? }`; `notFound` lists the searched domains no CRM company carries; `truncated: true` means more matched than `--limit`.
|
|
3988
|
+
**`deals list` flags:** `--pipeline <id,id>`, `--modified-since <YYYY-MM-DD>`, `--limit <1-1000>` (default 200), `--after <cursor>`, `--output json|csv|jsonl|md`. Rows, oldest change first: `{ id, name, amount, currency, pipelineId, stageId, stageLabel, isClosed, isWon, createdAt, closedAt, modifiedAt, companyIds }` — `amount: null` is none entered. `next` is the `--after` for the following page; `restartFrom` appears when HubSpot's 10,000-result search ceiling is reached (read on with `--modified-since <restartFrom>`); `companiesReadable: false` means the connection cannot read companies, so no deal carries `companyIds`.
|
|
3986
3989
|
**`contacts summary` flags:** `--days <1-365>` (default 30) over contact creation date. Returns counts only — total contacts, how many have an owner, and the lifecycle-stage split — never a name or an email. `truncated: true` means the page budget ran out, so the counts are floors.
|
|
3987
3990
|
**`contacts lookup`:** positional `<email>`; returns that contact's lifecycle stage, owner, the account (company) it was filed under, and associated deals with their stage. `dealsReadable: false` / `companyReadable: false` mean the connection does not cover deals or accounts, so an empty `deals` or a null `company` is unknown rather than absent. `found: false` means no contact with that email — the lead never reached the CRM, which is a finding rather than an error.
|
|
3988
3991
|
**`contacts properties`:** no flags; returns every contact property with a closed list of values — `{ name, label, groupName, options: [{ value, label }] }` — the ones a HubSpot-read source can watch. Names are HubSpot's internal ones; hand them to `baker analytics sources --create-hubspot --property <name> --value <value>` as printed. No personal data.
|
|
@@ -4014,6 +4017,31 @@ No write surface against HubSpot itself — a form's fields, redirect, or consen
|
|
|
4014
4017
|
|
|
4015
4018
|
---
|
|
4016
4019
|
|
|
4020
|
+
### `baker accounts influenced | engagement | warm | company | campaigns | crm`
|
|
4021
|
+
|
|
4022
|
+
Account insights: which companies the LinkedIn ads are warming, and which CRM deals they influenced. Reads Baker's nightly copy of LinkedIn and the CRM, so it is fast and covers up to 24 months; nothing is changed anywhere. Every company has it once a LinkedIn ad account is chosen; before the first nightly read the reports are empty — read live with `baker ads linkedin companies`. `--start`/`--end` must be real calendar days within 24 calendar months; a bad flag or an unsupported `--output` exits 1 with `INVALID_ARGS`.
|
|
4023
|
+
|
|
4024
|
+
```bash
|
|
4025
|
+
baker accounts influenced # influenced pipeline, last 12 calendar months
|
|
4026
|
+
baker accounts influenced --basis revenue --start 2026-01-01 --end 2026-06-30
|
|
4027
|
+
baker accounts influenced --full # every deal in the period, influenced or not
|
|
4028
|
+
baker accounts engagement --last-months 3 --limit 50 # companies reached, New/Rising, CRM match and deals
|
|
4029
|
+
baker accounts engagement --last-months 3 --limit 50 --page 2 # the next 50 (pageInfo.total counts them all)
|
|
4030
|
+
baker accounts engagement --campaign-id 123456 --output csv
|
|
4031
|
+
baker accounts warm --output csv > warm.csv # companyName,domain,organizationUrn
|
|
4032
|
+
baker accounts company acme.com # or a LinkedIn company id / urn:li:organization:N
|
|
4033
|
+
baker accounts campaigns --last-months 6 --output md # per campaign: reached, engaged, influenced pipeline (--basis revenue)
|
|
4034
|
+
baker accounts influenced --model awareness --threshold 10 --output csv # another rule, this call only
|
|
4035
|
+
baker accounts warm --min 0 # every New/Rising company, below the threshold too
|
|
4036
|
+
baker accounts crm status # deals from HubSpot or sent by the client; what arrived, linked Google Sheets
|
|
4037
|
+
baker accounts crm check --file deals.csv # judge a deals or company CSV offline, nothing sent
|
|
4038
|
+
baker accounts crm contract # the send-your-CRM contract, as Markdown for a developer
|
|
4039
|
+
```
|
|
4040
|
+
|
|
4041
|
+
A deal is **influenced** when its company reached the company's influence rule — by default 3+ LinkedIn ad engagements (Awareness: 7+ impressions; Intent: 3+ clicks) — in the 12 months up to and including the month the deal was created (`--basis pipeline`, deals not won) or closed won (`--basis revenue`). It is a labelled rule, not credit: campaign rows overlap, and money is reported per currency, never summed across currencies. Deals whose company match is only a guess come back under `lowConfidence` and stay out of `summary` until the match is confirmed in Measure → Accounts. Without the deals set up there — HubSpot's pipelines and won/lost stages, or Another CRM — `influenced` and `campaigns` exit 1 with `ACCOUNTS_NOT_SET_UP`. A CRM Baker cannot read sends its companies and deals to `POST /v1/accounts/crm` (a `crm_ingest` key, `X-Baker-Mode: test` to rehearse, up to 500 records per request, records naming a person refused), is uploaded as a CSV in Measure → Accounts, or lives in a Google Sheet linked there that Baker reads every night (a row deleted from the sheet leaves Baker). A file is a deals export or a company list — a company needs only its website or LinkedIn page; `crm check` runs the same rules offline and `crm contract` prints the whole contract. The CLI never sends deals or links a sheet. `--model engagement|awareness|intent` and `--threshold N` on `influenced`, `campaigns` and `warm` read with another rule for that call only; the saved setting is unchanged. A **warm** account is New or Rising, reached the company's influence threshold this period on the rule's metric (`--min N` overrides, `--min 0` drops it), and has no open deal, no won deal and no deal lost in the last 6 months; `engagement` and `warm` rows carry `dealStatus` (`won` > `open` > `lost` > `none`), `engagement --output csv` adds `lastMonth` and `previousImpressions/Clicks/Engagements`, and `campaigns` / `company` carry each campaign's `campaignName` (null when LinkedIn could not name it). `company` returns `months` (the company's totals), `byCampaign` (the same numbers per campaign — overlapping, never add them) and its `deals` with `touchValue` and `influenced`. `warm --output csv` is the file `baker ads linkedin audiences create --type company-list --list-file` takes.
|
|
4042
|
+
|
|
4043
|
+
---
|
|
4044
|
+
|
|
4017
4045
|
### `baker chats list | view | transcript | diff`
|
|
4018
4046
|
|
|
4019
4047
|
Read-only inspection of **other Sessions (chats) on the same account** — so an agent can reuse prior work ("like we did in that other chat, but for X"). Same-account only; never crosses a company boundary and has no write surface.
|
|
@@ -5992,7 +6020,7 @@ The two scaffold passes are billed (the full `video_deconstruct` is the heavy on
|
|
|
5992
6020
|
|
|
5993
6021
|
Turn a source/inspiration image into a **runnable, self-validated static-ad canvas** — the static counterpart of `scaffold-video`. It runs **billed Gemini passes** up front:
|
|
5994
6022
|
|
|
5995
|
-
1. **`image_describe`** (`~google/gemini-pro-latest`) — reverse-engineers the image (layout by what fills each region, copy verbatim, typefaces, capture in words, the `mechanism` and the `audience` it is built for) and writes it next to the canvas as **`prompt.json`**, an **ad spec** (`"schema": "baker-static-ad/3"`; `/2` still reads) you rewrite into the ad you want: `viewer` (who sees it and what they already know), `message` (the one thing it tells them, never printed), `copy` (each line with `lettering`: `in_scene`, written on something in the picture (`on`, `look`), or `designed`, set in the brand's face), `scene` (`description`, `look`: photo | designed | illustration, `capture` in words, `mood`, `layout`), `brand`, `colors` (each tied to an object, from the brand palette), `references` (one picture each, labelled by role: `PERSON_1`, `ANIMAL_1`, `PRODUCT_1`, `BADGE_1`, `PROP_1`), `logo` (and `logo_position`), `third_party_brands` (other brands the offer is about, a car brand on a car loan: `name`, `what`, and `logo` naming the `BADGE_n` reference of its logo; their logos may appear), an optional `direction`, the `mechanism` to rebuild for our brand, and in `/3`: `viewer.market` (`country`, `language`, `currency`), `brand_presence` (`standard` | `native`: a native disguise carries the logo as its sender and no big button), `recognition` (the kind of product a stranger must take it for at a glance, never the format), `native` (the ad is a screen people know, drawn exactly from HTML at every size by the free `static_native` node instead of painted: `x_post` (a post or a thread, with `reply_to`), `whatsapp`, `imessage`, `trustpilot`, `google_reviews`, `notes`, `slack`; on a 9:16 the app is drawn full screen as a phone shows it: status bar and app header pinned at the top, the reply bar and tab bar (or a keyboard under a chat or note) pinned at the bottom, the posts or messages as large as the clear band holds, and the frame's headline, caption and logo a card in the brand's colours under the app's header; a wall of reviews (`trustpilot`, `google_reviews`) takes up to 16 real reviews, the first two (`FEATURED_REVIEWS`) the ad's copy set whole in the safe area and the rest filling the whole frame behind them as large, sharp cards, turned and cut by its edges, because a wall wins on how many there are; a review longer than 160 characters is shown cut at a word with "…", and that cut text is the copy the review reads back; a chat (`whatsapp`, `imessage`) on a 9:16 never shrinks below about 62% of the phone scale: its earliest messages scroll up under the header, as the app does, the node's optional `scrolled` output lists the copy lines scrolled off that size, and the run review expects those lines nowhere on it; a logo file whose drawing runs off its own edge is set aside for a whole one, and a square mark where a wordmark goes is drawn larger; the run review never flags the app's own interface words (status bar, keyboard) as under Reels; the image model renders only the screen's `photos`, each a plain phone photo of ordinary people of the market; adding the block switches an existing ad on its next run, `--fix` does not apply, an invented brand handle or a rating the Knowledge does not hold (either decimal mark) is refused as `native_screen_invalid`, and a photo of ours goes in as its `image`: a native photo takes no `avatar`, since nothing would cast it), a role on each third-party brand (`partner` | `proof` | `category_signal`), a reference's `image` (wired as a picture of ours) and a person's `avatar` (the company avatar cast), and `concept` (`why_it_wins` in five layers, `load_bearing` elements with `have`/`adapt`/`missing` and `ours`, `verdict` `use`|`adapt`|`reject`, `transfer_note`; seeded from `baker winning-ads fit`). Every render keeps the load-bearing elements; `validate`/`run` refuse a `reject` or a `missing` element with nothing named (`concept_does_not_fit`) and a rating
|
|
6023
|
+
1. **`image_describe`** (`~google/gemini-pro-latest`) — reverse-engineers the image (layout by what fills each region, copy verbatim, typefaces, capture in words, the `mechanism` and the `audience` it is built for) and writes it next to the canvas as **`prompt.json`**, an **ad spec** (`"schema": "baker-static-ad/3"`; `/2` still reads) you rewrite into the ad you want: `viewer` (who sees it and what they already know), `message` (the one thing it tells them, never printed), `copy` (each line with `lettering`: `in_scene`, written on something in the picture (`on`, `look`), or `designed`, set in the brand's face), `scene` (`description`, `look`: photo | designed | illustration, `capture` in words, `mood`, `layout`), `brand`, `colors` (each tied to an object, from the brand palette), `references` (one picture each, labelled by role: `PERSON_1`, `ANIMAL_1`, `PRODUCT_1`, `BADGE_1`, `PROP_1`), `logo` (and `logo_position`), `third_party_brands` (other brands the offer is about, a car brand on a car loan: `name`, `what`, and `logo` naming the `BADGE_n` reference of its logo; their logos may appear), an optional `direction`, the `mechanism` to rebuild for our brand, and in `/3`: `viewer.market` (`country`, `language`, `currency`), `brand_presence` (`standard` | `native`: a native disguise carries the logo as its sender and no big button), `recognition` (the kind of product a stranger must take it for at a glance, never the format), `native` (the ad is a screen people know, drawn exactly from HTML at every size by the free `static_native` node instead of painted: `x_post` (a post or a thread, with `reply_to`), `whatsapp`, `imessage`, `trustpilot`, `google_reviews`, `notes`, `slack`; on a 9:16 the app is drawn full screen as a phone shows it: status bar and app header pinned at the top, the reply bar and tab bar (or a keyboard under a chat or note) pinned at the bottom, the posts or messages as large as the clear band holds, and the frame's headline, caption and logo a card in the brand's colours under the app's header; a wall of reviews (`trustpilot`, `google_reviews`) takes up to 16 real reviews, the first two (`FEATURED_REVIEWS`) the ad's copy set whole in the safe area and the rest filling the whole frame behind them as large, sharp cards, turned and cut by its edges, because a wall wins on how many there are; a review longer than 160 characters is shown cut at a word with "…", and that cut text is the copy the review reads back; a chat (`whatsapp`, `imessage`) on a 9:16 never shrinks below about 62% of the phone scale: its earliest messages scroll up under the header, as the app does, the node's optional `scrolled` output lists the copy lines scrolled off that size, and the run review expects those lines nowhere on it; a logo file whose drawing runs off its own edge is set aside for a whole one, and a square mark where a wordmark goes is drawn larger; the run review never flags the app's own interface words (status bar, keyboard) as under Reels; the image model renders only the screen's `photos`, each a plain phone photo of ordinary people of the market; adding the block switches an existing ad on its next run, `--fix` does not apply, an invented brand handle or a rating the Knowledge does not hold (either decimal mark) is refused as `native_screen_invalid`, and a photo of ours goes in as its `image`: a native photo takes no `avatar`, since nothing would cast it), a role on each third-party brand (`partner` | `proof` | `category_signal`), a reference's `image` (wired as a picture of ours) and a person's `avatar` (the company avatar cast), and `concept` (`why_it_wins` in five layers, `load_bearing` elements with `have`/`adapt`/`missing` and `ours`, `verdict` `use`|`adapt`|`reject`, `transfer_note`; seeded from `baker winning-ads fit`). Every render keeps the load-bearing elements; `validate`/`run` refuse a `reject` or a `missing` element with nothing named (`concept_does_not_fit`) and a rating the Knowledge does not hold (`proof_without_source`); a line citing a review platform gets its real badge wired. **You never write a model prompt**: every render prompt is compiled from this file for the model it runs on (short labelled sections for gpt-image, sentences with reasons for Gemini; each line quoted once; pictures numbered with one role each; colours from the brand; realism as capture cues; the safe area from its one owner; constraints last), on every `validate` and `run`, and shown on its node. A `prompt.json` written before the ad spec (the describe blueprint) is read through an adapter and keeps working. The source's own read (`source`, and the library's `reference`) stays as bookkeeping and is never sent to a model.
|
|
5996
6024
|
2. **element selection** (`~google/gemini-flash-latest`) — picks the **main, identity-critical** elements (the brand logo, a showcased product, a trust badge) **plus any foreground/hero person or animal** — the emotional focal point — even a generic one, because a free-generated face/muzzle reads as AI and grows artifacts; the emotional hero always gets a real-reference slot. When the advertiser's logo appears in **more than one lockup** (a square/icon **mark** and a horizontal **wordmark**), each is emitted as its **own** element (e.g. `LOGO_MARK`, `LOGO_WORDMARK`). The describe pass also records the ad's **typography** (each typeface's classification, a best-guess family, and its weight/case), which seeds the spec's `brand.typeface`. Background extras are dropped. Each element becomes a `references` entry in the spec, named by its role and naming the canvas slot it fills, so the file says which slot grounds which subject.
|
|
5997
6025
|
|
|
5998
6026
|
It then scaffolds a canvas on which **the image model designs the finished ad**, holding only what `prompt.json` holds; no element is mandatory:
|
|
@@ -2,13 +2,13 @@ import {
|
|
|
2
2
|
handleConnectionError,
|
|
3
3
|
needsConnectionFix,
|
|
4
4
|
writeAdsJson
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-YBJ7KJNA.js";
|
|
6
6
|
import {
|
|
7
7
|
ApiError
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-X46KRGZU.js";
|
|
9
9
|
import {
|
|
10
10
|
getEnv
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-SPPJRGQF.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/ads/meta/shared.ts
|
|
14
14
|
var DAY_MS = 864e5;
|
|
@@ -133,4 +133,4 @@ export {
|
|
|
133
133
|
resolveEffectiveStatus,
|
|
134
134
|
statusFilterHints
|
|
135
135
|
};
|
|
136
|
-
//# sourceMappingURL=chunk-
|
|
136
|
+
//# sourceMappingURL=chunk-4JBT2CSW.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
debugLogHttp,
|
|
3
3
|
readBodyForLog
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LDS7LUKK.js";
|
|
5
5
|
import {
|
|
6
6
|
API_KEY_FORMAT_MESSAGE,
|
|
7
7
|
DIRECT_ROUTE,
|
|
@@ -22,11 +22,11 @@ import {
|
|
|
22
22
|
refuseNonPublicUrl,
|
|
23
23
|
requireConnection,
|
|
24
24
|
shouldEscalate
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-SPPJRGQF.js";
|
|
26
26
|
|
|
27
|
-
//
|
|
27
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js
|
|
28
28
|
var require_safe_stable_stringify = __commonJS({
|
|
29
|
-
"
|
|
29
|
+
"../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/index.js"(exports, module) {
|
|
30
30
|
"use strict";
|
|
31
31
|
var { hasOwnProperty } = Object.prototype;
|
|
32
32
|
var stringify = configure2();
|
|
@@ -1408,7 +1408,7 @@ function resolveAdaptFormats(params) {
|
|
|
1408
1408
|
return params.formats ?? [];
|
|
1409
1409
|
}
|
|
1410
1410
|
|
|
1411
|
-
//
|
|
1411
|
+
// ../../node_modules/.pnpm/safe-stable-stringify@2.5.0/node_modules/safe-stable-stringify/esm/wrapper.js
|
|
1412
1412
|
var import__ = __toESM(require_safe_stable_stringify(), 1);
|
|
1413
1413
|
var configure = import__.default.configure;
|
|
1414
1414
|
var wrapper_default = import__.default;
|
|
@@ -2826,12 +2826,6 @@ var MODEL_ROLES = {
|
|
|
2826
2826
|
* only and asked once per ad and viewer, so Flash; the calibration set (E8) decides.
|
|
2827
2827
|
*/
|
|
2828
2828
|
viewerCheck: { kind: "text_generate", model: "~google/gemini-flash-latest" },
|
|
2829
|
-
/**
|
|
2830
|
-
* Picks the customer truths out of reviews, testimonials and threads (`baker research
|
|
2831
|
-
* customers`): returns exact spans the code then checks against the source. Text only and
|
|
2832
|
-
* one call per batch, so Flash.
|
|
2833
|
-
*/
|
|
2834
|
-
customerInsights: { kind: "text_generate", model: "~google/gemini-flash-latest" },
|
|
2835
2829
|
/**
|
|
2836
2830
|
* The same viewer shown the FINISHED static ad at phone size for about three seconds, with no
|
|
2837
2831
|
* brand and no brief: what is sold, whether it is for them, what to do next, what stopped them
|
|
@@ -2873,7 +2867,6 @@ var PLATE_JUDGE_MODEL = MODEL_ROLES.plateJudge.model;
|
|
|
2873
2867
|
var AD_DESCRIBE_MODEL = MODEL_ROLES.adDescribe.model;
|
|
2874
2868
|
var BLUEPRINT_ANALYSIS_MODEL = MODEL_ROLES.blueprintAnalysis.model;
|
|
2875
2869
|
var VIEWER_CHECK_MODEL = MODEL_ROLES.viewerCheck.model;
|
|
2876
|
-
var CUSTOMER_INSIGHTS_MODEL = MODEL_ROLES.customerInsights.model;
|
|
2877
2870
|
var GLANCE_CHECK_MODEL = MODEL_ROLES.glanceCheck.model;
|
|
2878
2871
|
var TEXT_BOX_READER_MODEL = MODEL_ROLES.textBoxReader.model;
|
|
2879
2872
|
var VIDEO_DECONSTRUCT_MODEL = MODEL_ROLES.videoDeconstruct.model;
|
|
@@ -6919,18 +6912,6 @@ function conceptProblems(spec) {
|
|
|
6919
6912
|
}
|
|
6920
6913
|
return problems;
|
|
6921
6914
|
}
|
|
6922
|
-
var PLACEHOLDER = "[TODO";
|
|
6923
|
-
function unwrittenProblems(spec) {
|
|
6924
|
-
const problems = [];
|
|
6925
|
-
if (spec.scene.description.includes(PLACEHOLDER)) {
|
|
6926
|
-
problems.push("The scene is still a placeholder: write what the viewer sees in `scene.description`.");
|
|
6927
|
-
}
|
|
6928
|
-
const lines = spec.copy.filter((line) => line.text.includes(PLACEHOLDER));
|
|
6929
|
-
if (lines.length > 0) {
|
|
6930
|
-
problems.push(`${lines.length} line(s) of copy are still placeholders: write each as it prints, or drop it.`);
|
|
6931
|
-
}
|
|
6932
|
-
return problems;
|
|
6933
|
-
}
|
|
6934
6915
|
function readAdSpec(raw) {
|
|
6935
6916
|
if (isAdSpec(raw)) return AdSpecSchema.parse(raw);
|
|
6936
6917
|
const advertiser = raw?.reference?.advertiser;
|
|
@@ -18404,7 +18385,6 @@ export {
|
|
|
18404
18385
|
AD_DESCRIBE_MODEL,
|
|
18405
18386
|
BLUEPRINT_ANALYSIS_MODEL,
|
|
18406
18387
|
VIEWER_CHECK_MODEL,
|
|
18407
|
-
CUSTOMER_INSIGHTS_MODEL,
|
|
18408
18388
|
GLANCE_CHECK_MODEL,
|
|
18409
18389
|
VIDEO_DECONSTRUCT_MODEL,
|
|
18410
18390
|
VOICEOVER_MODEL,
|
|
@@ -18476,7 +18456,6 @@ export {
|
|
|
18476
18456
|
isAdSpec,
|
|
18477
18457
|
conceptSummary,
|
|
18478
18458
|
conceptProblems,
|
|
18479
|
-
unwrittenProblems,
|
|
18480
18459
|
readAdSpec,
|
|
18481
18460
|
withRenderedLettering,
|
|
18482
18461
|
conceptClassOf,
|
|
@@ -18564,4 +18543,4 @@ export {
|
|
|
18564
18543
|
defaultRegistry,
|
|
18565
18544
|
createEngineFromEnv
|
|
18566
18545
|
};
|
|
18567
|
-
//# sourceMappingURL=chunk-
|
|
18546
|
+
//# sourceMappingURL=chunk-7BQIIJLV.js.map
|