@hogsend/cli 0.14.0 → 0.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/cli",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "tsup": "^8.5.1",
35
35
  "tsx": "^4.22.4",
36
36
  "vitest": "^4.1.7",
37
- "@hogsend/studio": "^0.14.0",
37
+ "@hogsend/studio": "^0.16.0",
38
38
  "@repo/typescript-config": "0.0.0"
39
39
  },
40
40
  "engines": {
@@ -44,8 +44,8 @@
44
44
  "@clack/prompts": "^1.5.0",
45
45
  "better-auth": "^1.6.11",
46
46
  "picocolors": "^1.1.1",
47
- "@hogsend/db": "^0.14.0",
48
- "@hogsend/engine": "^0.14.0"
47
+ "@hogsend/db": "^0.16.0",
48
+ "@hogsend/engine": "^0.16.0"
49
49
  },
50
50
  "scripts": {
51
51
  "prebuild": "node scripts/bundle-studio.mjs",
@@ -184,3 +184,12 @@ Semantics at click time:
184
184
  - The answering journey reads the payload from
185
185
  `ctx.waitForEvent(...) → { timedOut, properties }` — see the
186
186
  hogsend-authoring-journeys skill.
187
+ - **No landing page?** `href={HOSTED_ANSWER_HREF}` (from `@hogsend/email`)
188
+ resolves to the engine-hosted answer page: a thanks page with an optional
189
+ free-text box whose submission ingests `<event>.comment` (one comment per
190
+ send + event). Possession of the link is the auth, like unsubscribe.
191
+ - **Cross-device stitch (opt-in):** `TRACKING_IDENTITY_TOKEN=true` appends an
192
+ encrypted one-hour `hs_t` token to tracked redirects; the landing site
193
+ exchanges it at `POST /v1/t/identify` for the distinct id and calls
194
+ `posthog.identify`. Never put a raw email in a URL — the token exists so
195
+ you don't have to.
@@ -37,10 +37,12 @@ Not every surface accepts all four types — match the type to the field:
37
37
 
38
38
  - **`trigger.where`** (journey) → `PropertyCondition[]` ONLY. Property
39
39
  conditions, AND-ed together, evaluated against the triggering event's
40
- properties. No event/engagement/composite legs here.
40
+ properties. No event/engagement/composite legs here. Authoring sugar: a
41
+ builder function — `where: (b) => b.prop("score").lte(6)` (or an array of
42
+ terminals) — resolves ONCE at `defineJourney` time to the identical POJOs.
41
43
  - **`exitOn[].where`** (journey) → `PropertyCondition[]` ONLY. Same shape;
42
44
  AND-ed against the incoming event's properties. Omit `where` to exit on the
43
- event name alone.
45
+ event name alone. Accepts the same builder-function sugar.
44
46
  - **`criteria`** (bucket) → a single `ConditionEval` tree — ALL FOUR types,
45
47
  composed with `composite` / `b.all()` / `b.any()`. This is the only surface
46
48
  that runs against the database (event counts, engagement, windows).
@@ -57,7 +59,8 @@ Not every surface accepts all four types — match the type to the field:
57
59
 
58
60
  ## Golden rules
59
61
 
60
- 1. `trigger.where` and `exitOn[].where` take `PropertyCondition[]` only — if
62
+ 1. `trigger.where` and `exitOn[].where` take `PropertyCondition[]` (write them
63
+ with the `(b) => b.prop(...)` builder for short) — if
61
64
  you need an event count or a time window, that logic belongs in the
62
65
  journey's `run` body (`ctx.history.hasEvent`) or in a bucket's `criteria`,
63
66
  not in `where`.
@@ -14,6 +14,23 @@ properties. Only `property` conditions are valid here — there is no `within`,
14
14
  no event count, no composite. (Need a count or a window? Put that logic in the
15
15
  `run` body via `ctx.history.hasEvent`, or model it as a bucket.)
16
16
 
17
+ Author it either way — the builder form resolves once at `defineJourney` time
18
+ to the identical data (same machinery as bucket criteria):
19
+
20
+ ```ts
21
+ // Builder form (recommended)
22
+ trigger: {
23
+ event: Events.NPS_DETRACTOR,
24
+ where: (b) => b.prop("score").lte(3),
25
+ },
26
+
27
+ // Multiple conditions: return an array (AND-ed)
28
+ trigger: {
29
+ event: Events.CHECKOUT_ABANDONED,
30
+ where: (b) => [b.prop("plan").eq("pro"), b.prop("cartValue").gte(100)],
31
+ },
32
+ ```
33
+
17
34
  ```ts
18
35
  import { days, hours } from "@hogsend/core";
19
36
  import { defineJourney, sendEmail } from "@hogsend/engine";
@@ -56,7 +73,8 @@ export const proCheckoutAbandoned = defineJourney({
56
73
 
57
74
  `exitOn` is an array of `{ event, where? }`. The engine matches the incoming
58
75
  event name; if `where` is present it must pass (`PropertyCondition[]`, AND-ed)
59
- for the exit to fire. Omit `where` to exit on the event name alone.
76
+ for the exit to fire. Omit `where` to exit on the event name alone. The
77
+ builder form works here too: `where: (b) => b.prop("plan").eq("pro")`.
60
78
 
61
79
  ```ts
62
80
  meta: {