@cowliss/cli 0.1.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/LICENSE +21 -0
- package/README.md +105 -0
- package/dist/guest/constants-OZrYz4I2.d.ts +42 -0
- package/dist/guest/driver-BOe7jBFw.js +5647 -0
- package/dist/guest/driver.d.ts +21 -0
- package/dist/guest/driver.js +3 -0
- package/dist/guest/emails.d.ts +24 -0
- package/dist/guest/emails.js +0 -0
- package/dist/guest/index-heC1gFE_.d.ts +296 -0
- package/dist/guest/journeys-CxuS-mHn.js +571 -0
- package/dist/guest/journeys.d.ts +127 -0
- package/dist/guest/journeys.js +3 -0
- package/dist/guest/prelude.d.ts +1 -0
- package/dist/guest/prelude.js +91 -0
- package/dist/guest/wasi.d.ts +6 -0
- package/dist/guest/wasi.js +43 -0
- package/dist/index.js +11235 -0
- package/examples/abandoned-checkout/emails/abandoned-checkout.tsx +104 -0
- package/examples/abandoned-checkout/journeys/abandoned-checkout.ts +39 -0
- package/examples/abandoned-checkout/scenarios/abandoned-checkout.recovered.json +10 -0
- package/examples/abandoned-checkout/scenarios/abandoned-checkout.timeout.json +16 -0
- package/examples/activity-decay/journeys/activity-decay.ts +36 -0
- package/examples/activity-decay/scenarios/activity-decay.json +8 -0
- package/examples/cross-app-pitch/emails/cross-app-pitch.tsx +77 -0
- package/examples/cross-app-pitch/journeys/cross-app-pitch.ts +30 -0
- package/examples/cross-app-pitch/scenarios/cross-app-pitch.json +16 -0
- package/examples/winback/emails/winback.tsx +78 -0
- package/examples/winback/journeys/winback.ts +33 -0
- package/examples/winback/scenarios/winback.json +18 -0
- package/package.json +82 -0
- package/project/tsconfig.json +18 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { SendClass, Subject } from "@cowliss/cli/emails";
|
|
2
|
+
import {
|
|
3
|
+
Body,
|
|
4
|
+
Button,
|
|
5
|
+
Container,
|
|
6
|
+
Head,
|
|
7
|
+
Heading,
|
|
8
|
+
Html,
|
|
9
|
+
Preview,
|
|
10
|
+
Text,
|
|
11
|
+
} from "@react-email/components";
|
|
12
|
+
import type { CSSProperties, ReactElement } from "react";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The abandoned-checkout example's one email: a checkout opened, the 24-hour
|
|
17
|
+
* window closed without a purchase, so this is the single reminder the
|
|
18
|
+
* journey spends. Short and plain on purpose.
|
|
19
|
+
*
|
|
20
|
+
* No unsubscribe link in the body: one-click unsubscribe rides in the RFC
|
|
21
|
+
* 8058 headers the send path attaches, not the template. Inline styles only,
|
|
22
|
+
* because email clients strip Tailwind and external stylesheets alike.
|
|
23
|
+
*
|
|
24
|
+
* `checkoutUrl` belongs to your own store, so the journey supplies it and the
|
|
25
|
+
* button is simply omitted when it is absent; a hardcoded link here would
|
|
26
|
+
* send every org's users to the same place.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export const props = z.object({
|
|
30
|
+
checkoutUrl: z.url().optional(),
|
|
31
|
+
firstName: z.string().optional(),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const subject: Subject<typeof props> = (p) =>
|
|
35
|
+
p.firstName
|
|
36
|
+
? `${p.firstName}, you left something in your cart`
|
|
37
|
+
: "You left something in your cart";
|
|
38
|
+
|
|
39
|
+
export const sendClass: SendClass = "marketing";
|
|
40
|
+
|
|
41
|
+
export const tags = ["demo", "checkout"];
|
|
42
|
+
|
|
43
|
+
const body: CSSProperties = {
|
|
44
|
+
backgroundColor: "#f6f6f6",
|
|
45
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const container: CSSProperties = {
|
|
49
|
+
margin: "0 auto",
|
|
50
|
+
padding: "32px 24px",
|
|
51
|
+
maxWidth: "480px",
|
|
52
|
+
backgroundColor: "#ffffff",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const heading: CSSProperties = {
|
|
56
|
+
fontSize: "20px",
|
|
57
|
+
fontWeight: 600,
|
|
58
|
+
color: "#111111",
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const text: CSSProperties = {
|
|
62
|
+
fontSize: "14px",
|
|
63
|
+
lineHeight: "22px",
|
|
64
|
+
color: "#333333",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const button: CSSProperties = {
|
|
68
|
+
display: "inline-block",
|
|
69
|
+
marginTop: "16px",
|
|
70
|
+
padding: "10px 20px",
|
|
71
|
+
borderRadius: "6px",
|
|
72
|
+
backgroundColor: "#111111",
|
|
73
|
+
color: "#ffffff",
|
|
74
|
+
fontSize: "14px",
|
|
75
|
+
fontWeight: 600,
|
|
76
|
+
textDecoration: "none",
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export default function AbandonedCheckout(
|
|
80
|
+
p: z.infer<typeof props>,
|
|
81
|
+
): ReactElement {
|
|
82
|
+
return (
|
|
83
|
+
<Html>
|
|
84
|
+
<Head />
|
|
85
|
+
<Preview>You left something in your cart</Preview>
|
|
86
|
+
<Body style={body}>
|
|
87
|
+
<Container style={container}>
|
|
88
|
+
<Heading style={heading}>
|
|
89
|
+
{p.firstName ? `Hi ${p.firstName},` : "Hi there,"}
|
|
90
|
+
</Heading>
|
|
91
|
+
<Text style={text}>
|
|
92
|
+
You started checking out but didn't finish. Your cart is still
|
|
93
|
+
here, and it only takes a moment to pick up where you left off.
|
|
94
|
+
</Text>
|
|
95
|
+
{p.checkoutUrl ? (
|
|
96
|
+
<Button style={button} href={p.checkoutUrl}>
|
|
97
|
+
Finish checkout
|
|
98
|
+
</Button>
|
|
99
|
+
) : null}
|
|
100
|
+
</Container>
|
|
101
|
+
</Body>
|
|
102
|
+
</Html>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { defineJourney } from "@cowliss/cli/journeys";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Abandoned-checkout recovery: `checkout_started` opens a 24-hour window. A
|
|
5
|
+
* `purchase_completed` inside it closes the window quietly and marks the cart
|
|
6
|
+
* recovered, a trait your segments and other journeys can read. Silence
|
|
7
|
+
* spends exactly one reminder email and closes.
|
|
8
|
+
*
|
|
9
|
+
* Example: add it with `cow add example abandoned-checkout`; rename the
|
|
10
|
+
* events and the email to your domain's vocabulary.
|
|
11
|
+
*/
|
|
12
|
+
export default defineJourney({
|
|
13
|
+
trigger: { event: "checkout_started" },
|
|
14
|
+
purpose: "emailMarketing",
|
|
15
|
+
tags: ["demo", "checkout"],
|
|
16
|
+
run: async (event, api) => {
|
|
17
|
+
const purchased = await api.waitForEvent("purchase_completed", {
|
|
18
|
+
timeout: "24h",
|
|
19
|
+
});
|
|
20
|
+
if (purchased) {
|
|
21
|
+
await api.traits.set("cart_recovered", true);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
// The checkout link belongs to your store, so the trigger event carries
|
|
25
|
+
// it; the email drops its button when the prop is missing.
|
|
26
|
+
const checkoutUrl = event.properties.checkoutUrl;
|
|
27
|
+
const me = await api.profile.get();
|
|
28
|
+
await api.email.send({
|
|
29
|
+
template: "abandoned-checkout",
|
|
30
|
+
props: {
|
|
31
|
+
checkoutUrl: typeof checkoutUrl === "string" ? checkoutUrl : undefined,
|
|
32
|
+
firstName:
|
|
33
|
+
typeof me.traits.firstName === "string"
|
|
34
|
+
? me.traits.firstName
|
|
35
|
+
: undefined,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"user": {
|
|
3
|
+
"id": "usr_1",
|
|
4
|
+
"traits": { "email": "ada@example.com", "firstName": "Ada" }
|
|
5
|
+
},
|
|
6
|
+
"events": [],
|
|
7
|
+
"expect": [
|
|
8
|
+
{
|
|
9
|
+
"activity": "sendEmail",
|
|
10
|
+
"input": {
|
|
11
|
+
"template": "abandoned-checkout",
|
|
12
|
+
"props": { "firstName": "Ada" }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { defineJourney } from "@cowliss/cli/journeys";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Timer decay: "active in the last 30 days" as a per-user journey instead of
|
|
5
|
+
* a nightly sweep. The trigger sets the flag and the execution waits; every
|
|
6
|
+
* later event restarts the execution, which resets the 30-day silence clock
|
|
7
|
+
* with a fresh, empty journal; 30 days of silence unsets the flag (removed,
|
|
8
|
+
* not set to false, so `exists` predicates stop matching) and the execution
|
|
9
|
+
* completes. The next event starts a new one.
|
|
10
|
+
*
|
|
11
|
+
* `"*"` is activity as it really arrives: whatever names your apps send,
|
|
12
|
+
* page_view and purchase and login alike. It never matches an event Cowliss
|
|
13
|
+
* wrote itself (those are all under `system.`), which is what stops the
|
|
14
|
+
* trait write below from flipping a segment whose entry event restarts this
|
|
15
|
+
* journey, forever.
|
|
16
|
+
*
|
|
17
|
+
* Example: add it with `cow add example activity-decay`; rename the trait to
|
|
18
|
+
* your domain's vocabulary, or narrow the pattern (`checkout.*`, or a list)
|
|
19
|
+
* if only some events should count as activity.
|
|
20
|
+
*/
|
|
21
|
+
export default defineJourney({
|
|
22
|
+
trigger: { event: "*" },
|
|
23
|
+
purpose: "emailMarketing",
|
|
24
|
+
tags: ["demo", "retention"],
|
|
25
|
+
run: async (_event, api) => {
|
|
26
|
+
await api.traits.set("active_30d", true);
|
|
27
|
+
const active = await api.waitForEvent("*", { timeout: "30d" });
|
|
28
|
+
if (active) {
|
|
29
|
+
// This is the loop, and it is bounded: restart ends this execution and
|
|
30
|
+
// starts a fresh one under the same workflow id with an empty journal,
|
|
31
|
+
// so a user who stays active for years never grows one.
|
|
32
|
+
await api.restart({ event: active });
|
|
33
|
+
}
|
|
34
|
+
await api.traits.unset("active_30d");
|
|
35
|
+
},
|
|
36
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { SendClass, Subject } from "@cowliss/cli/emails";
|
|
2
|
+
import {
|
|
3
|
+
Body,
|
|
4
|
+
Container,
|
|
5
|
+
Head,
|
|
6
|
+
Heading,
|
|
7
|
+
Html,
|
|
8
|
+
Preview,
|
|
9
|
+
Text,
|
|
10
|
+
} from "@react-email/components";
|
|
11
|
+
import type { CSSProperties, ReactElement } from "react";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The cross-app-pitch example's one email: someone upgraded in one of your
|
|
16
|
+
* apps, and a day later the other app is worth a mention. The plan name is a
|
|
17
|
+
* prop rather than a hardcoded string, so the same email serves every tier
|
|
18
|
+
* you pitch.
|
|
19
|
+
*
|
|
20
|
+
* Inline styles only, because email clients strip Tailwind and external
|
|
21
|
+
* stylesheets alike. One-click unsubscribe rides in the RFC 8058 headers the
|
|
22
|
+
* send path attaches, not in the body.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export const props = z.object({
|
|
26
|
+
plan: z.string(),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const subject: Subject<typeof props> = (p) =>
|
|
30
|
+
`Your ${p.plan} plan works in our other app too`;
|
|
31
|
+
|
|
32
|
+
export const sendClass: SendClass = "marketing";
|
|
33
|
+
|
|
34
|
+
export const tags = ["demo", "cross-app"];
|
|
35
|
+
|
|
36
|
+
const body: CSSProperties = {
|
|
37
|
+
backgroundColor: "#f6f6f6",
|
|
38
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const container: CSSProperties = {
|
|
42
|
+
margin: "0 auto",
|
|
43
|
+
padding: "32px 24px",
|
|
44
|
+
maxWidth: "480px",
|
|
45
|
+
backgroundColor: "#ffffff",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const heading: CSSProperties = {
|
|
49
|
+
fontSize: "20px",
|
|
50
|
+
fontWeight: 600,
|
|
51
|
+
color: "#111111",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const text: CSSProperties = {
|
|
55
|
+
fontSize: "14px",
|
|
56
|
+
lineHeight: "22px",
|
|
57
|
+
color: "#333333",
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default function CrossAppPitch(p: z.infer<typeof props>): ReactElement {
|
|
61
|
+
return (
|
|
62
|
+
<Html>
|
|
63
|
+
<Head />
|
|
64
|
+
<Preview>One upgrade, two apps</Preview>
|
|
65
|
+
<Body style={body}>
|
|
66
|
+
<Container style={container}>
|
|
67
|
+
<Heading style={heading}>One upgrade, two apps</Heading>
|
|
68
|
+
<Text style={text}>
|
|
69
|
+
Thanks for going {p.plan}. The same account already works in our
|
|
70
|
+
other app, so there is nothing to sign up for: open it and your plan
|
|
71
|
+
follows you.
|
|
72
|
+
</Text>
|
|
73
|
+
</Container>
|
|
74
|
+
</Body>
|
|
75
|
+
</Html>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineJourney } from "@cowliss/cli/journeys";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Cross-app pitch: `plan_upgraded` from one app (the trigger's `appId` field
|
|
5
|
+
* scopes a journey to one app of a multi-app org) starts the execution; after
|
|
6
|
+
* a durable day it re-reads the profile and pitches the other app only to
|
|
7
|
+
* users still on the free plan. This is the portfolio-operator pattern: one
|
|
8
|
+
* org's journeys reacting across its own apps.
|
|
9
|
+
*
|
|
10
|
+
* Example: add it with `cow add example cross-app-pitch`; rename the app id,
|
|
11
|
+
* the event, and the email to your own.
|
|
12
|
+
*/
|
|
13
|
+
export default defineJourney({
|
|
14
|
+
trigger: { event: "plan_upgraded", appId: "app_b" },
|
|
15
|
+
purpose: "emailMarketing",
|
|
16
|
+
tags: ["demo", "cross-app"],
|
|
17
|
+
run: async (_event, api) => {
|
|
18
|
+
await api.sleep("1d");
|
|
19
|
+
// A fresh read, not a snapshot taken at the trigger: the plan may well
|
|
20
|
+
// have changed during the wait, which is the whole point of waiting.
|
|
21
|
+
const me = await api.profile.get();
|
|
22
|
+
if (me.traits.plan !== "free") {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
await api.email.send({
|
|
26
|
+
template: "cross-app-pitch",
|
|
27
|
+
props: { plan: "pro" },
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"user": {
|
|
3
|
+
"id": "usr_1",
|
|
4
|
+
"traits": { "email": "ada@example.com", "plan": "free" }
|
|
5
|
+
},
|
|
6
|
+
"events": [],
|
|
7
|
+
"expect": [
|
|
8
|
+
{
|
|
9
|
+
"activity": "sendEmail",
|
|
10
|
+
"input": {
|
|
11
|
+
"template": "cross-app-pitch",
|
|
12
|
+
"props": { "plan": "pro" }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { SendClass, Subject } from "@cowliss/cli/emails";
|
|
2
|
+
import {
|
|
3
|
+
Body,
|
|
4
|
+
Container,
|
|
5
|
+
Head,
|
|
6
|
+
Heading,
|
|
7
|
+
Html,
|
|
8
|
+
Preview,
|
|
9
|
+
Text,
|
|
10
|
+
} from "@react-email/components";
|
|
11
|
+
import type { CSSProperties, ReactElement } from "react";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The winback example's one email, sent at the moment 30 days of silence
|
|
16
|
+
* make a customer lapsed. No discount and no urgency: the journey already
|
|
17
|
+
* knows this person bought before, so the job is a reminder, not a pitch.
|
|
18
|
+
*
|
|
19
|
+
* Inline styles only, because email clients strip Tailwind and external
|
|
20
|
+
* stylesheets alike. One-click unsubscribe rides in the RFC 8058 headers the
|
|
21
|
+
* send path attaches, not in the body.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export const props = z.object({
|
|
25
|
+
firstName: z.string().optional(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const subject: Subject<typeof props> = (p) =>
|
|
29
|
+
p.firstName ? `${p.firstName}, we miss you` : "We miss you";
|
|
30
|
+
|
|
31
|
+
export const sendClass: SendClass = "marketing";
|
|
32
|
+
|
|
33
|
+
export const tags = ["demo", "retention"];
|
|
34
|
+
|
|
35
|
+
const body: CSSProperties = {
|
|
36
|
+
backgroundColor: "#f6f6f6",
|
|
37
|
+
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const container: CSSProperties = {
|
|
41
|
+
margin: "0 auto",
|
|
42
|
+
padding: "32px 24px",
|
|
43
|
+
maxWidth: "480px",
|
|
44
|
+
backgroundColor: "#ffffff",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const heading: CSSProperties = {
|
|
48
|
+
fontSize: "20px",
|
|
49
|
+
fontWeight: 600,
|
|
50
|
+
color: "#111111",
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const text: CSSProperties = {
|
|
54
|
+
fontSize: "14px",
|
|
55
|
+
lineHeight: "22px",
|
|
56
|
+
color: "#333333",
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default function Winback(p: z.infer<typeof props>): ReactElement {
|
|
60
|
+
return (
|
|
61
|
+
<Html>
|
|
62
|
+
<Head />
|
|
63
|
+
<Preview>It has been a while</Preview>
|
|
64
|
+
<Body style={body}>
|
|
65
|
+
<Container style={container}>
|
|
66
|
+
<Heading style={heading}>
|
|
67
|
+
{p.firstName ? `Hi ${p.firstName},` : "Hi there,"}
|
|
68
|
+
</Heading>
|
|
69
|
+
<Text style={text}>
|
|
70
|
+
It has been a month since your last order. Nothing has changed about
|
|
71
|
+
how easy it is to come back, and we kept your details where you left
|
|
72
|
+
them.
|
|
73
|
+
</Text>
|
|
74
|
+
</Container>
|
|
75
|
+
</Body>
|
|
76
|
+
</Html>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineJourney } from "@cowliss/cli/journeys";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Winback: `purchase_completed` starts (or restarts) an execution and marks
|
|
5
|
+
* the profile as having bought recently; every further purchase inside 30
|
|
6
|
+
* days resets the silence clock; after 30 days of silence the flag is unset
|
|
7
|
+
* and one winback email goes out. The next purchase starts a fresh execution.
|
|
8
|
+
*
|
|
9
|
+
* Example: add it with `cow add example winback`; rename the purchase event,
|
|
10
|
+
* the flag trait, and the email to your domain's vocabulary.
|
|
11
|
+
*/
|
|
12
|
+
export default defineJourney({
|
|
13
|
+
trigger: { event: "purchase_completed" },
|
|
14
|
+
purpose: "emailMarketing",
|
|
15
|
+
tags: ["demo", "retention"],
|
|
16
|
+
run: async (_event, api) => {
|
|
17
|
+
await api.traits.set("purchased_recently", true);
|
|
18
|
+
while (await api.waitForEvent("purchase_completed", { timeout: "30d" })) {
|
|
19
|
+
// Another purchase inside 30 days: loop to reset the silence timer.
|
|
20
|
+
}
|
|
21
|
+
await api.traits.unset("purchased_recently");
|
|
22
|
+
const me = await api.profile.get();
|
|
23
|
+
await api.email.send({
|
|
24
|
+
template: "winback",
|
|
25
|
+
props: {
|
|
26
|
+
firstName:
|
|
27
|
+
typeof me.traits.firstName === "string"
|
|
28
|
+
? me.traits.firstName
|
|
29
|
+
: undefined,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"user": {
|
|
3
|
+
"id": "usr_1",
|
|
4
|
+
"traits": { "email": "ada@example.com", "firstName": "Ada" }
|
|
5
|
+
},
|
|
6
|
+
"events": [
|
|
7
|
+
{ "at": "10d", "event": "purchase_completed" },
|
|
8
|
+
{ "at": "25d", "event": "purchase_completed" }
|
|
9
|
+
],
|
|
10
|
+
"expect": [
|
|
11
|
+
{
|
|
12
|
+
"activity": "setTrait",
|
|
13
|
+
"input": { "key": "purchased_recently", "value": true }
|
|
14
|
+
},
|
|
15
|
+
{ "activity": "unsetTrait", "input": { "key": "purchased_recently" } },
|
|
16
|
+
{ "activity": "sendEmail", "input": { "template": "winback" } }
|
|
17
|
+
]
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cowliss/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"cow": "./dist/index.js"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
"./journeys": {
|
|
10
|
+
"types": "./dist/guest/journeys.d.ts",
|
|
11
|
+
"default": "./dist/guest/journeys.js"
|
|
12
|
+
},
|
|
13
|
+
"./emails": {
|
|
14
|
+
"types": "./dist/guest/emails.d.ts",
|
|
15
|
+
"default": "./dist/guest/emails.js"
|
|
16
|
+
},
|
|
17
|
+
"./guest": {
|
|
18
|
+
"types": "./dist/guest/driver.d.ts",
|
|
19
|
+
"default": "./dist/guest/driver.js"
|
|
20
|
+
},
|
|
21
|
+
"./guest-wasi": {
|
|
22
|
+
"types": "./dist/guest/wasi.d.ts",
|
|
23
|
+
"default": "./dist/guest/wasi.js"
|
|
24
|
+
},
|
|
25
|
+
"./guest-prelude": {
|
|
26
|
+
"types": "./dist/guest/prelude.d.ts",
|
|
27
|
+
"default": "./dist/guest/prelude.js"
|
|
28
|
+
},
|
|
29
|
+
"./tsconfig.json": "./project/tsconfig.json",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
34
|
+
"commander": "^14.0.1",
|
|
35
|
+
"esbuild": "^0.28.2",
|
|
36
|
+
"tar": "^7.5.22",
|
|
37
|
+
"zod": "^4.4.3"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@cowliss/api-client": "0.0.0",
|
|
41
|
+
"@cowliss/shared": "0.0.0",
|
|
42
|
+
"@react-email/components": "^1.0.12",
|
|
43
|
+
"@types/html-to-text": "^9.0.4",
|
|
44
|
+
"@types/node": "^26.2.0",
|
|
45
|
+
"@types/react": "^19.2.7",
|
|
46
|
+
"@types/react-dom": "^19.2.3",
|
|
47
|
+
"html-to-text": "^9.0.5",
|
|
48
|
+
"react": "^19.2.8",
|
|
49
|
+
"react-dom": "^19.2.8",
|
|
50
|
+
"tsdown": "^0.22.14",
|
|
51
|
+
"tsx": "^4.23.12",
|
|
52
|
+
"vitest": "^4.1.11"
|
|
53
|
+
},
|
|
54
|
+
"files": [
|
|
55
|
+
"dist",
|
|
56
|
+
"project",
|
|
57
|
+
"examples",
|
|
58
|
+
"LICENSE",
|
|
59
|
+
"README.md"
|
|
60
|
+
],
|
|
61
|
+
"description": "Admin CLI, project workflow and MCP server for Cowliss.",
|
|
62
|
+
"license": "MIT",
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/bogdanrn/cowliss.git",
|
|
66
|
+
"directory": "apps/cli"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://docs.cowliss.com",
|
|
69
|
+
"publishConfig": {
|
|
70
|
+
"access": "public"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=22"
|
|
74
|
+
},
|
|
75
|
+
"scripts": {
|
|
76
|
+
"start": "tsx src/index.ts",
|
|
77
|
+
"build": "tsdown",
|
|
78
|
+
"typecheck": "tsc -b tsconfig.json",
|
|
79
|
+
"test": "vitest run",
|
|
80
|
+
"pretest": "tsdown"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2023",
|
|
5
|
+
"lib": ["ES2023"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noUncheckedIndexedAccess": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": true,
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"types": []
|
|
17
|
+
}
|
|
18
|
+
}
|