@koda-sl/baker-cli 0.315.0 → 0.319.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 +20 -3
- package/dist/cli.js +3437 -2880
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3351,7 +3351,7 @@ Read and shape this workspace's Forms (flows). Operates on the local `_data.json
|
|
|
3351
3351
|
|
|
3352
3352
|
```bash
|
|
3353
3353
|
baker flows list # every Form + how many confidential fields still need setup
|
|
3354
|
-
baker flows show contact #
|
|
3354
|
+
baker flows show contact # confidential fields + whether the destination changed under this Form
|
|
3355
3355
|
baker flows show contact --full # include the full flow tree (secret values redacted)
|
|
3356
3356
|
|
|
3357
3357
|
baker flows schema # both vocabularies + the customForm field types
|
|
@@ -3363,7 +3363,8 @@ baker flows add-side-effect contact --node <nodeId> --type googleSpreadsheet #
|
|
|
3363
3363
|
baker flows map contact --from map.json --write # a whole mapping at once — past ~3 fields, use this
|
|
3364
3364
|
baker flows map contact --map A=nombre --write # one or two fields
|
|
3365
3365
|
baker flows map contact --hubspot-form <guid> --from map.json --write # HubSpot: field types read off the form
|
|
3366
|
-
baker flows map contact --
|
|
3366
|
+
baker flows map contact --side-effect <id> --hubspot-form <guid> --prune-missing --write # drop dead mappings
|
|
3367
|
+
baker flows map contact --from map.json --replace --write # the slot becomes exactly this; drops everything not named
|
|
3367
3368
|
baker flows normalize contact --write # repairs a nulled optional; runs pre-commit anyway
|
|
3368
3369
|
```
|
|
3369
3370
|
|
|
@@ -3376,7 +3377,9 @@ Notes:
|
|
|
3376
3377
|
- **Most CRM fields are not fed by a form field.** The value grammar is a bare form field (`nombre`), `field:Step.name` when two steps share a name, `tracking:utm_source`, `global:Name`, `code:exposed_value`, a `"literal"`, or any of those joined with `+`. Tracking names are validated against the engine's own `TRACKING_FIELD_IDS`, read out of the workspace rather than copied — attribution columns are the most common CRM mapping there is, and they were unexpressible before.
|
|
3377
3378
|
- **A CRM mapping's field type is derived from the destination, never defaulted.** Every CRM record types `fieldType` as required, and it is the account's answer, not the agent's — HubSpot builds its submission key as `<objectTypeId>.<fieldType>.<fieldName>`, so a wrong type on a date property skips the `YYYY-MM-DD` coercion and the value is dropped. `--hubspot-form <formGuid>` (or `"hubspotForm"` in the mapping file) reads every property's type and object type off the form, so nothing has to state one. Where a destination cannot be read, put the type on the LEFT — `c0ffee:varchar=email` — which leaves `:` on the right for the value kinds. A mapping with no type from either route is refused, never written without one.
|
|
3378
3379
|
- **`email` maps like anything else**, into `body.fields`, while `body.format` is `"json"`. A `"body"`-format email is one written message with no keys to address, and `map` refuses it rather than reshaping the body.
|
|
3379
|
-
- **
|
|
3380
|
+
- **A Form can look finished and be losing every lead.** HubSpot refuses a submission that names a property it does not have — the whole submission, not the field — and the visitor still reaches the thank-you screen. Baker re-checks each Form's destination and `baker flows show <slug>` reports what it found: `data.drift[]` carries **one entry per drifted destination** — a Form with two HubSpot side effects has two mappings, two form ids and two repairs — each with the severity, a sentence naming the fields, the side-effect id and that destination's HubSpot form id; the hints carry one repair command per entry. `drift: null` means the check could not be made and is not the same answer as `[]`.
|
|
3381
|
+
- **`--prune-missing` is the repair; `--replace` is not.** With `--side-effect <id>` and `--hubspot-form <guid>`, `--prune-missing` drops exactly the mappings whose destination field is absent from that form's live field list, names each one in `dropped`, and leaves every other mapping alone. It needs no `--map` and runs on its own. `--replace` drops everything the call does not name, so using it to remove three dead mappings means first restating the ten good ones correctly.
|
|
3382
|
+
- **Mapping twice adds; it does not start over.** Every slot merges with what is already in it, addressed by destination name — re-mapping a name overwrites it in place, a new name is appended, and a name the call did not mention is left alone. That is the default because a mapping is built in stages (configure the destination, read the real names back, map, come back for the rest), and replacing on every call turned "add the ninth field" into "delete the other eight". `--replace` (or `"replace": true` in the mapping file) writes the slot exactly as passed; to remove a mapping the destination has deleted, reach for `--prune-missing` instead. The response says how many were kept either way.
|
|
3380
3383
|
- **`triggerId` is set from the step, not from the side-effect type.** A side effect runs only when its trigger is the id its own step raises: a `customForm` step raises `customForm.onFormSubmitted`, a Calendly step `calendlyEvent.eventScheduled`, a HubSpot widget `hubspotForm.onFormSubmitted`, a `text` step one of two depending on `allowNextNode`, and an `iframe` step one per declared event. A mismatch is the quietest failure in the engine — the file is valid, verify is green, and the side effect never runs — so `add-side-effect` derives it and refuses an id the step cannot raise. `--trigger` is for the non-completion moments (a HubSpot form's `onFormReady`, a Calendly page view). **A SavvyCal step raises no trigger at all**, so a side effect there is refused outright; put it on the following step, which the booking still advances to. `baker flows schema node <type>` lists a step's ids, and `baker flows show <slug>` names any side effect already wired to a moment its step never raises.
|
|
3381
3384
|
- **A step's name becomes a constant, so `add-node` judges it as one.** The Form's generated code carries `export const <NAME> = "<node id>"` per step, keyed by upper-casing the name and splitting camelCase — so `contactInfo` and `Contact Info` are the same constant, and `1. Your details` or `Thanks!` are not valid ones at all. Both are refused when the step is created, where the message can name the step; left to the generator they surface as a duplicate-constant error or a syntax error in a file nobody wrote.
|
|
3382
3385
|
- **`normalize` exists so authoring a Form does not require remembering an exception.** `oauthProviderId` is an optional `string`, so a `null` there fails typecheck one build later — it is repaired and staged instead of rejected.
|
|
@@ -6133,6 +6136,8 @@ This CLI is designed for AI agent consumption. Key patterns:
|
|
|
6133
6136
|
|
|
6134
6137
|
## Internal Notes
|
|
6135
6138
|
|
|
6139
|
+
- **0.316.1**: `baker flows show <slug>` reports **destination drift** — what the HubSpot form a Form submits to has changed under it — as `data.drift[]` plus a `DESTINATION DRIFT` hint carrying the repair as a ready-to-run command. It is the first time a Runtime can see this at all: the findings live in Baker and the HubSpot form id the repair needs is inside an encrypted config, so a Company Workspace could read neither. The classification is the one the dashboard badge reads (`classifyDrift` in `@baker/api`), so the two surfaces cannot disagree about whether a Form is healthy; a failed check answers `drift: null`, which is "unknown", never "clean". `baker flows map <slug> --side-effect <id> --hubspot-form <guid> --prune-missing [--write]` is the repair: it drops only the mappings whose destination field is absent from the form's live field list, names each in `dropped`, needs no `--map`, and refuses to run without a readable destination rather than guessing. Reach for it instead of `--replace`, which drops every mapping the call does not name.
|
|
6140
|
+
|
|
6136
6141
|
- **0.103.0**: `baker actions ...` commands now type their `/api/actions/...` request/response payloads from the shared `@baker/api` contract package (also consumed by the backend) instead of hand-written local interfaces. No command, flag, or output-shape changes.
|
|
6137
6142
|
- **0.104.0**: `baker actions tags ...` commands now type their `/api/actions/tags...` request/response payloads from the shared `@baker/api` contract package instead of hand-written local interfaces. No command, flag, or output-shape changes.
|
|
6138
6143
|
- **0.105.0**: `baker images ...`, `baker videos ...`, and `baker testimonials ...` commands now type their `/api/{images,videos,testimonials}/...` request/response payloads from the shared `@baker/api` contract package instead of hand-written local interfaces. No command, flag, or output-shape changes.
|
|
@@ -6238,6 +6243,18 @@ Pushing to `main` with changes in `packages/cli/` triggers the GitHub Actions wo
|
|
|
6238
6243
|
- **0.306.6**: the render review moved onto OpenRouter, where every other model call on this platform already goes. It was calling Gemini directly on the stated grounds that the watch pass uploads the finished mp4 inline and OpenRouter had no route that takes one — it does: a `video_url` content part accepts a base64 data URL, verified end to end (HTTP 200, video tokens counted, the model describing the clip it was sent). That one wrong premise had cost a hand-maintained price table in the backend, token counts round-tripping through the run record to be priced there, a second credential in every Runtime, and a model id pinned to `gemini-3.7-flash` so the table's key would not drift — which is also why the review had quietly stayed off `gemini-3.8-flash`. Now the reply carries `usage.cost`, the Runtime relays the gateway's figure instead of computing one, and the models float (`~google/gemini-flash-latest`, `~google/gemini-pro-latest`). The key it reads is `OPENROUTER_API_KEY`; `GEMINI_API_KEY` is still shipped for HyperFrames' own `snapshot --describe` pass, which is a third-party tool and reads that name.
|
|
6239
6244
|
- **0.306.0**: review fixes across the avatar and ad paths. **A silent clip is 5 seconds again** — the 8s speaking floor was applied to every `studio animate` call, not just ones that cast an avatar, so plain b-roll was lengthened by three seconds and billed for them. **`scaffold-ad` validates the mark it will actually draw**: the logo gates read the path the spec named rather than the one `fetchClientLogo` had just put there, so a fetched mark either skipped both checks or was refused as unreadable. **The render review is told where the ad is set, who it is for and what the script says** — read off the blueprint beside the canvas; without them the watch pass judged an accent against nothing and still graded the verdict as blocking. **A beat is "too short" by its LINE, not by the clip floor** — the old test needed a 25-word clause, so it fired on all three beats of this command's own documented example. **A voiceover stays a voiceover** — a phrase nobody is shown speaking was being folded into the presenter's clip and said on camera, words that shot was never generated for; the fold now applies only where there is a mouth for the words to contradict. `--voice-id` / `--voice-description` are gone from `baker avatars create` (0.252.0 said so; the flags were still declared and still sent), `end_card.cta` is documented as having no default, and the dead Veo/Omni content-block fallbacks are removed.
|
|
6240
6245
|
|
|
6246
|
+
- **0.319.0**: **A source that refuses everything it receives now says so — and can be read backwards.** A rejection is a per-event verdict, so it reached neither field a source row carries: `lastError` means "the record could not be read", `lastArrivalAt` means "something was counted", and a source refusing every delivery was indistinguishable from one nothing had ever been sent to. Its card said exactly that, in a sentence telling the reader to go and move a record in HubSpot to see the first event — while three of one client's events had been refused for two days, with six ad-platform sends hanging off conversions that were never counted. `sources` now carries `lastRefusal` and says it in words: "5 changes arrived and could not be counted: its name has a character Baker cannot count an event under. Rename it and the next change counts." Cleared by the next delivery that counts.
|
|
6247
|
+
|
|
6248
|
+
**And `--backfill --source <id> --since <date> [--write]` is the way back.** The refused events cannot be replayed from Baker — the arrival log masks every value on the way in and keeps 48 hours — but HubSpot still holds them, so this reads its property history and counts every moment the trigger *came to hold* since `--since`, each at the instant HubSpot says it happened. That instant is half the event's id, so a backfilled event and the one the journal would have delivered are the **same** event: running it twice counts nothing twice, and it cannot collide with the live read. It is not the thing a source deliberately refuses to do — the rule that holds is *never invent a moment*, and every moment here is HubSpot's own: a record that merely satisfies the trigger today is still not an event today, and one already in the value before the window is not counted at its edge. Candidates are "changed since", never "satisfies the trigger now", so a deal that entered the stage on Monday and moved on by Wednesday — the exact event a client is missing — is found. Dry unless `--write`, which is refused inside a chat: an event inside the last 72 hours is uploaded to its sends within minutes, and that is real budget. It also answers the other half of the same gap, which has nothing to do with any incident: **every** HubSpot event counts forwards only, so a client connecting on Monday has had no funnel until Monday's deals reached the end of a sales cycle.
|
|
6249
|
+
|
|
6250
|
+
- **0.318.0**: **An event arrives, and then it is populated.** A CRM posts a deal naming the person only by the CRM's own contact id — HubSpot's default mapping is `external_id: {{id}}` — and that event used to reach Meta and be **skipped by Google Ads, LinkedIn and X**, all three of which want an email or a phone before they will take it. The address book had that person's address the whole time; nothing asked. It could only be opened at an email or a phone digest, and could only hand back click ids and Meta cookies, so the three commonest shapes a server event arrives in — named by a contact id, by the click that produced it, or by the browser (`user_data.baker_id`) — resolved to nothing at all. Now ingest opens it at **every** key the event names anybody by and stamps back everything the row is missing: the other contact digests, the click ids, both Meta cookies, and an `identity_hash` when the event arrived without one, so it lands on that person's timeline rather than beside it. It crosses one pivot to get there — a contact id reaches the email directly, and the click is one hop from *the email* rather than from an id no ad platform has ever seen. Three rules hold it: the hub guard now covers the seed and the pivot as well as the returned value (a shared browser resolving to somebody's email stamps the wrong person, which is worse than stamping nobody); a click is time-guarded and a person is not, because an address first seen a week later is the same address; and a browser id is never inferred, since `anon_id` is the session join and a server event did not happen in a session. Measured on one store, 16 of 17 `deal_won` events carried `external_id` and nothing else.
|
|
6251
|
+
|
|
6252
|
+
**And an event name may be written in the language its marketer speaks.** `event_name` was `[A-Za-z0-9 ._-]` — ASCII, which is to say English — so a HubSpot source called "Matrícula Realizada" had every event it ever raised refused as `invalid_name`, for ever, with a reason whose only honest reading was "use a different alphabet". Any letter and any digit in any script is a name now; `:` and `,` stay out because the vocabulary around the name is built on them. Every door that takes a name now knows the rule — `sourceNameSchema` on the wire, `--name` in this CLI before the request leaves, the Create button on the dashboard, and the mutation itself — because the one that was missing is the one somebody types into: a source whose name the ingest door cannot accept is refused **when it is named**, rather than counting nothing behind a card that reads "Counting"; and the name HubSpot's trigger builder *suggests* goes through the same rule, so "Deal value > 1000" is offered as "Deal value is greater than 1000" instead of as a source that could never count anything.
|
|
6253
|
+
|
|
6254
|
+
- **0.317.0**: **`baker analytics feed` says what happened to a delivery.** Every upload and every Form action on the feed read **"On the way"**, for ever — the settlement carrying the platform's verdict was joined only when the caller had already asked for uploads, so the default read never brought an outcome back at all. It is joined always now. Three things ship with it. `--person <anon-id>` takes a browser id off any row and returns that whole person — resolved through the identity graph rather than filtered by cookie, so the deal their CRM posted, which carries no cookie, is on the list beside the page view that named them. `--everything` keeps the rows the feed normally folds away. And **`--sent-to meta` no longer hides the Meta rows**: a delivery our own server made is logged under the dispatcher's name (`metaCapi`, `ga4Mp`), the filter only matched the tag's name and the send's, and the three platforms that deliver *only* server-side returned nothing at all while their rows sat in the unfiltered list. All four spellings of a destination — the tag, the sender, the send's upload and the name a person is shown — now resolve through one table in `@baker/api`, held against `computeSigForTag`, `DISPATCHERS` and the store's own copy by `destinations.parity.test.ts`.
|
|
6255
|
+
|
|
6256
|
+
Underneath: **four click ids Baker had no column for** — Reddit's `rdt_cid`, Pinterest's `epik`, Snapchat's `ScCid` and Google's `dclid`. Reddit, Pinterest and Snapchat were already ad platforms here, emitting campaign macros and drawing their own logos, with nothing to attribute a click by; the scaffold had been seeding Pinterest's `_epik` cookie and offering `tracking.pinterest_click_id` to Forms the whole time, with nowhere to store it. They reach `analytics` output, `matchedBy` and the person panel like every other click id, and a URL parameter is now read case-insensitively — Snapchat spells its `ScCid`, which an exact match never saw.
|
|
6257
|
+
|
|
6241
6258
|
### Testing a pre-release in sandboxes
|
|
6242
6259
|
|
|
6243
6260
|
```bash
|