@nebutra/outreach-engine 0.1.1
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/.nebutra/debug/content-store.jsonl +3 -0
- package/.nebutra/debug/event-log.jsonl +1 -0
- package/.nebutra/debug/outreach-engine.jsonl +1 -0
- package/.turbo/turbo-build.log +22 -0
- package/.turbo/turbo-test.log +16 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/CHANGELOG.md +9 -0
- package/LICENSE +676 -0
- package/README.md +14 -0
- package/dist/chunk-ZH5KSGRI.js +189 -0
- package/dist/chunk-ZH5KSGRI.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +55 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +105 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/examples/compliance.ts +18 -0
- package/examples/icp.ts +9 -0
- package/examples/quickstart.ts +30 -0
- package/package.json +63 -0
- package/plays/outreach_campaign/SKILL.md +44 -0
- package/src/cli.ts +49 -0
- package/src/index.test.ts +120 -0
- package/src/index.ts +316 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @nebutra/outreach-engine
|
|
2
|
+
|
|
3
|
+
Status: **WIP**
|
|
4
|
+
|
|
5
|
+
Compliance-first outreach Play product.
|
|
6
|
+
|
|
7
|
+
The package creates auditable campaign drafts. It does not own lead databases,
|
|
8
|
+
email sender credentials, or CRM write adapters.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm outreach-engine:doctor
|
|
12
|
+
pnpm outreach-engine:quickstart "decision makers at mid-size D2C companies"
|
|
13
|
+
pnpm outreach-engine:debug
|
|
14
|
+
```
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { mkdir } from "fs/promises";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
|
|
5
|
+
import { ContentStore } from "@nebutra/content-store";
|
|
6
|
+
import { CapabilityError } from "@nebutra/errors";
|
|
7
|
+
import { EventLog } from "@nebutra/event-log";
|
|
8
|
+
import { assetId } from "@nebutra/generation-context";
|
|
9
|
+
function requireTenant(explicit, fallback) {
|
|
10
|
+
const tenantId = explicit ?? fallback;
|
|
11
|
+
if (!tenantId?.trim()) {
|
|
12
|
+
throw new CapabilityError("outreach-engine", "Outreach Engine requires tenant context", {
|
|
13
|
+
suggestion: "Pass tenantId before creating or persisting a campaign.",
|
|
14
|
+
statusCode: 400
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
return tenantId;
|
|
18
|
+
}
|
|
19
|
+
function slug(value) {
|
|
20
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 48);
|
|
21
|
+
}
|
|
22
|
+
function defineIcp(description) {
|
|
23
|
+
const text = description.toLowerCase();
|
|
24
|
+
const industries = text.includes("e-commerce") || text.includes("d2c") ? ["e-commerce", "D2C"] : text.includes("saas") ? ["SaaS"] : ["startup"];
|
|
25
|
+
const titles = text.includes("cto") ? ["CTO", "VP Engineering", "Head of Engineering"] : ["VP Marketing", "Head of Growth", "CMO"];
|
|
26
|
+
const companySize = text.includes("mid-size") || text.includes("medium") ? { min: 50, max: 500 } : { min: 1, max: 50 };
|
|
27
|
+
const geo = text.includes("eu") ? ["EU"] : text.includes("us") ? ["US"] : ["US", "EU"];
|
|
28
|
+
const techStack = text.includes("shopify") ? ["Shopify"] : [];
|
|
29
|
+
return { industries, companySize, titles, geo, techStack };
|
|
30
|
+
}
|
|
31
|
+
function buildEmailSequence(input) {
|
|
32
|
+
const length = Math.max(1, Math.min(5, input.sequenceLength));
|
|
33
|
+
const voice = input.brandVoice?.join(", ") ?? "clear, useful";
|
|
34
|
+
return Array.from({ length }, (_, index) => {
|
|
35
|
+
const step = index + 1;
|
|
36
|
+
return {
|
|
37
|
+
step,
|
|
38
|
+
subject: step === 1 ? "Quick question" : `Following up ${step}`,
|
|
39
|
+
body: [
|
|
40
|
+
`Hi {{first_name}},`,
|
|
41
|
+
"",
|
|
42
|
+
step === 1 ? `I noticed {{company}} may care about ${input.product}.` : `Sharing one more angle on ${input.product}.`,
|
|
43
|
+
`Tone: ${voice}.`,
|
|
44
|
+
"",
|
|
45
|
+
"If this is not useful, unsubscribe here: {{unsubscribe_url}}.",
|
|
46
|
+
"{{physical_address}}"
|
|
47
|
+
].join("\n"),
|
|
48
|
+
daysAfterPrevious: step === 1 ? 0 : step + 1
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function checkEmailCompliance(email, profile) {
|
|
53
|
+
const body = email?.body ?? "";
|
|
54
|
+
const checks = {
|
|
55
|
+
hasUnsubscribeLink: body.includes("unsubscribe") && profile.unsubscribeUrl.startsWith("http"),
|
|
56
|
+
hasPhysicalAddress: profile.physicalAddress.trim().length > 4,
|
|
57
|
+
hasClearSubject: Boolean(email?.subject.trim()),
|
|
58
|
+
gdprBasisDocumented: Boolean(profile.gdprBasis)
|
|
59
|
+
};
|
|
60
|
+
const ok = Object.values(checks).every(Boolean);
|
|
61
|
+
return {
|
|
62
|
+
ok,
|
|
63
|
+
checks,
|
|
64
|
+
...!ok ? {
|
|
65
|
+
suggestion: "Add unsubscribe URL, physical address, clear subject, and GDPR basis before any send adapter can run."
|
|
66
|
+
} : {}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function plannedLeads(icp, count) {
|
|
70
|
+
const size = Math.max(1, Math.min(250, count));
|
|
71
|
+
return Array.from({ length: size }, (_, index) => ({
|
|
72
|
+
id: `lead_${String(index + 1).padStart(3, "0")}`,
|
|
73
|
+
company: `${icp.industries[0] ?? "startup"} account ${index + 1}`,
|
|
74
|
+
title: icp.titles[index % icp.titles.length] ?? "Founder",
|
|
75
|
+
source: "local-plan",
|
|
76
|
+
confidence: 0.72
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
var OutreachEngine = class _OutreachEngine {
|
|
80
|
+
#tenantId;
|
|
81
|
+
#debugRoot;
|
|
82
|
+
#contentStore;
|
|
83
|
+
#eventLog;
|
|
84
|
+
constructor(options) {
|
|
85
|
+
this.#tenantId = options.tenantId;
|
|
86
|
+
this.#debugRoot = options.debugRoot ?? process.cwd();
|
|
87
|
+
this.#contentStore = options.contentStore;
|
|
88
|
+
this.#eventLog = options.eventLog;
|
|
89
|
+
}
|
|
90
|
+
static async open(root = ".nebutra/outreach-engine", options = {}) {
|
|
91
|
+
const tenantId = options.tenantId ?? "local";
|
|
92
|
+
await mkdir(root, { recursive: true });
|
|
93
|
+
const contentStore = await ContentStore.open(join(root, "content"), { tenantId });
|
|
94
|
+
const eventLog = await EventLog.open(join(root, "event-log"), { tenantId });
|
|
95
|
+
return new _OutreachEngine({ ...options, tenantId, root, contentStore, eventLog });
|
|
96
|
+
}
|
|
97
|
+
async createCampaign(input) {
|
|
98
|
+
const tenantId = requireTenant(input.tenantId, this.#tenantId);
|
|
99
|
+
const icp = defineIcp(input.icpDescription);
|
|
100
|
+
const sequence = buildEmailSequence({
|
|
101
|
+
product: input.product,
|
|
102
|
+
sequenceLength: input.sequenceLength,
|
|
103
|
+
brandVoice: ["specific", "useful", "respectful"]
|
|
104
|
+
});
|
|
105
|
+
const compliance = checkEmailCompliance(sequence[0], input.complianceProfile);
|
|
106
|
+
if (!compliance.ok) {
|
|
107
|
+
throw new CapabilityError("outreach-engine", "Campaign failed compliance checks", {
|
|
108
|
+
suggestion: compliance.suggestion ?? "Add unsubscribe URL, physical address, clear subject, and GDPR basis.",
|
|
109
|
+
statusCode: 400,
|
|
110
|
+
metadata: compliance.checks
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const id = assetId("outreach_campaign", slug(input.icpDescription));
|
|
114
|
+
const leads = plannedLeads(icp, input.targetCount);
|
|
115
|
+
const artifactPath = `outreach/${id}.json`;
|
|
116
|
+
const draft = {
|
|
117
|
+
id,
|
|
118
|
+
status: "draft",
|
|
119
|
+
icp,
|
|
120
|
+
leads,
|
|
121
|
+
sequence,
|
|
122
|
+
schedule: {
|
|
123
|
+
dailyLimit: Math.max(1, Math.min(30, input.sendPerDay)),
|
|
124
|
+
senderEmails: input.senderEmails,
|
|
125
|
+
requiresApprovalBeforeSend: true
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const content = `${JSON.stringify(draft, null, 2)}
|
|
129
|
+
`;
|
|
130
|
+
await this.#contentStore.write(artifactPath, content);
|
|
131
|
+
const eventId = await this.#eventLog.commit({
|
|
132
|
+
traceId: id,
|
|
133
|
+
kind: "content_write",
|
|
134
|
+
affected: [artifactPath],
|
|
135
|
+
parent: null,
|
|
136
|
+
snapshot: { [artifactPath]: content }
|
|
137
|
+
});
|
|
138
|
+
const campaign = {
|
|
139
|
+
tenantId,
|
|
140
|
+
play: "outreach_campaign",
|
|
141
|
+
id,
|
|
142
|
+
status: "draft",
|
|
143
|
+
icp,
|
|
144
|
+
leads,
|
|
145
|
+
sequence,
|
|
146
|
+
compliance,
|
|
147
|
+
schedule: draft.schedule,
|
|
148
|
+
artifactPath,
|
|
149
|
+
eventId
|
|
150
|
+
};
|
|
151
|
+
await this.#debug({ type: "campaign_draft", tenantId, campaignId: id, eventId });
|
|
152
|
+
return campaign;
|
|
153
|
+
}
|
|
154
|
+
async doctor() {
|
|
155
|
+
return {
|
|
156
|
+
capability: "outreach-engine",
|
|
157
|
+
ok: true,
|
|
158
|
+
checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
159
|
+
plays: ["outreach_campaign"],
|
|
160
|
+
sendMode: "draft-only",
|
|
161
|
+
adapters: [
|
|
162
|
+
{ provider: "local-plan", ok: true },
|
|
163
|
+
{ provider: "integration-vault-email", ok: false },
|
|
164
|
+
{ provider: "crm-sync", ok: false }
|
|
165
|
+
]
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async close() {
|
|
169
|
+
await this.#contentStore.close();
|
|
170
|
+
}
|
|
171
|
+
async #debug(entry) {
|
|
172
|
+
await mkdir(dirname(join(this.#debugRoot, ".nebutra", "debug", "outreach-engine.jsonl")), {
|
|
173
|
+
recursive: true
|
|
174
|
+
});
|
|
175
|
+
await appendCapabilityDebug("outreach-engine", entry, { root: this.#debugRoot });
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
async function readOutreachEngineDebug(root = process.cwd(), limit = 20) {
|
|
179
|
+
return readCapabilityDebug("outreach-engine", { root, limit });
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export {
|
|
183
|
+
defineIcp,
|
|
184
|
+
buildEmailSequence,
|
|
185
|
+
checkEmailCompliance,
|
|
186
|
+
OutreachEngine,
|
|
187
|
+
readOutreachEngineDebug
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=chunk-ZH5KSGRI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId } from \"@nebutra/generation-context\";\n\nexport interface IcpDefinition {\n readonly industries: readonly string[];\n readonly companySize: { readonly min: number; readonly max: number };\n readonly titles: readonly string[];\n readonly geo: readonly string[];\n readonly techStack: readonly string[];\n}\n\nexport interface ComplianceProfile {\n readonly physicalAddress: string;\n readonly unsubscribeUrl: string;\n readonly gdprBasis?: \"legitimate_interest\" | \"consent\" | \"existing_customer\";\n}\n\nexport interface EmailVariant {\n readonly step: number;\n readonly subject: string;\n readonly body: string;\n readonly daysAfterPrevious: number;\n}\n\nexport interface ComplianceReport {\n readonly ok: boolean;\n readonly checks: {\n readonly hasUnsubscribeLink: boolean;\n readonly hasPhysicalAddress: boolean;\n readonly hasClearSubject: boolean;\n readonly gdprBasisDocumented: boolean;\n };\n readonly suggestion?: string;\n}\n\nexport interface CampaignInput {\n readonly tenantId?: string;\n readonly icpDescription: string;\n readonly targetCount: number;\n readonly product: string;\n readonly sequenceLength: number;\n readonly sendPerDay: number;\n readonly senderEmails: readonly string[];\n readonly complianceProfile: ComplianceProfile;\n}\n\nexport interface Lead {\n readonly id: string;\n readonly company: string;\n readonly title: string;\n readonly source: \"local-plan\";\n readonly confidence: number;\n}\n\nexport interface CampaignPackage {\n readonly tenantId: string;\n readonly play: \"outreach_campaign\";\n readonly id: string;\n readonly status: \"draft\";\n readonly icp: IcpDefinition;\n readonly leads: readonly Lead[];\n readonly sequence: readonly EmailVariant[];\n readonly compliance: ComplianceReport;\n readonly schedule: {\n readonly dailyLimit: number;\n readonly senderEmails: readonly string[];\n readonly requiresApprovalBeforeSend: true;\n };\n readonly artifactPath: string;\n readonly eventId: string;\n}\n\nexport interface SequenceInput {\n readonly product: string;\n readonly sequenceLength: number;\n readonly brandVoice?: readonly string[];\n}\n\nexport interface OutreachDoctorReport {\n readonly capability: \"outreach-engine\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly plays: readonly string[];\n readonly sendMode: \"draft-only\";\n readonly adapters: readonly { readonly provider: string; readonly ok: boolean }[];\n}\n\nexport interface OutreachEngineOptions {\n readonly tenantId?: string;\n readonly root?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nfunction requireTenant(explicit: string | undefined, fallback: string | undefined): string {\n const tenantId = explicit ?? fallback;\n if (!tenantId?.trim()) {\n throw new CapabilityError(\"outreach-engine\", \"Outreach Engine requires tenant context\", {\n suggestion: \"Pass tenantId before creating or persisting a campaign.\",\n statusCode: 400,\n });\n }\n return tenantId;\n}\n\nfunction slug(value: string): string {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .slice(0, 48);\n}\n\nexport function defineIcp(description: string): IcpDefinition {\n const text = description.toLowerCase();\n const industries =\n text.includes(\"e-commerce\") || text.includes(\"d2c\")\n ? [\"e-commerce\", \"D2C\"]\n : text.includes(\"saas\")\n ? [\"SaaS\"]\n : [\"startup\"];\n const titles = text.includes(\"cto\")\n ? [\"CTO\", \"VP Engineering\", \"Head of Engineering\"]\n : [\"VP Marketing\", \"Head of Growth\", \"CMO\"];\n const companySize =\n text.includes(\"mid-size\") || text.includes(\"medium\")\n ? { min: 50, max: 500 }\n : { min: 1, max: 50 };\n const geo = text.includes(\"eu\") ? [\"EU\"] : text.includes(\"us\") ? [\"US\"] : [\"US\", \"EU\"];\n const techStack = text.includes(\"shopify\") ? [\"Shopify\"] : [];\n return { industries, companySize, titles, geo, techStack };\n}\n\nexport function buildEmailSequence(input: SequenceInput): readonly EmailVariant[] {\n const length = Math.max(1, Math.min(5, input.sequenceLength));\n const voice = input.brandVoice?.join(\", \") ?? \"clear, useful\";\n return Array.from({ length }, (_, index) => {\n const step = index + 1;\n return {\n step,\n subject: step === 1 ? \"Quick question\" : `Following up ${step}`,\n body: [\n `Hi {{first_name}},`,\n \"\",\n step === 1\n ? `I noticed {{company}} may care about ${input.product}.`\n : `Sharing one more angle on ${input.product}.`,\n `Tone: ${voice}.`,\n \"\",\n \"If this is not useful, unsubscribe here: {{unsubscribe_url}}.\",\n \"{{physical_address}}\",\n ].join(\"\\n\"),\n daysAfterPrevious: step === 1 ? 0 : step + 1,\n };\n });\n}\n\nexport function checkEmailCompliance(\n email: EmailVariant | undefined,\n profile: ComplianceProfile,\n): ComplianceReport {\n const body = email?.body ?? \"\";\n const checks = {\n hasUnsubscribeLink: body.includes(\"unsubscribe\") && profile.unsubscribeUrl.startsWith(\"http\"),\n hasPhysicalAddress: profile.physicalAddress.trim().length > 4,\n hasClearSubject: Boolean(email?.subject.trim()),\n gdprBasisDocumented: Boolean(profile.gdprBasis),\n };\n const ok = Object.values(checks).every(Boolean);\n return {\n ok,\n checks,\n ...(!ok\n ? {\n suggestion:\n \"Add unsubscribe URL, physical address, clear subject, and GDPR basis before any send adapter can run.\",\n }\n : {}),\n };\n}\n\nfunction plannedLeads(icp: IcpDefinition, count: number): readonly Lead[] {\n const size = Math.max(1, Math.min(250, count));\n return Array.from({ length: size }, (_, index) => ({\n id: `lead_${String(index + 1).padStart(3, \"0\")}`,\n company: `${icp.industries[0] ?? \"startup\"} account ${index + 1}`,\n title: icp.titles[index % icp.titles.length] ?? \"Founder\",\n source: \"local-plan\",\n confidence: 0.72,\n }));\n}\n\nexport class OutreachEngine {\n readonly #tenantId: string | undefined;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n\n private constructor(\n options: OutreachEngineOptions & { contentStore: ContentStore; eventLog: EventLog },\n ) {\n this.#tenantId = options.tenantId;\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/outreach-engine\",\n options: Omit<OutreachEngineOptions, \"root\" | \"contentStore\" | \"eventLog\"> = {},\n ): Promise<OutreachEngine> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new OutreachEngine({ ...options, tenantId, root, contentStore, eventLog });\n }\n\n async createCampaign(input: CampaignInput): Promise<CampaignPackage> {\n const tenantId = requireTenant(input.tenantId, this.#tenantId);\n const icp = defineIcp(input.icpDescription);\n const sequence = buildEmailSequence({\n product: input.product,\n sequenceLength: input.sequenceLength,\n brandVoice: [\"specific\", \"useful\", \"respectful\"],\n });\n const compliance = checkEmailCompliance(sequence[0], input.complianceProfile);\n if (!compliance.ok) {\n throw new CapabilityError(\"outreach-engine\", \"Campaign failed compliance checks\", {\n suggestion:\n compliance.suggestion ??\n \"Add unsubscribe URL, physical address, clear subject, and GDPR basis.\",\n statusCode: 400,\n metadata: compliance.checks,\n });\n }\n const id = assetId(\"outreach_campaign\", slug(input.icpDescription));\n const leads = plannedLeads(icp, input.targetCount);\n const artifactPath = `outreach/${id}.json`;\n const draft = {\n id,\n status: \"draft\",\n icp,\n leads,\n sequence,\n schedule: {\n dailyLimit: Math.max(1, Math.min(30, input.sendPerDay)),\n senderEmails: input.senderEmails,\n requiresApprovalBeforeSend: true,\n } as const,\n };\n const content = `${JSON.stringify(draft, null, 2)}\\n`;\n await this.#contentStore.write(artifactPath, content);\n const eventId = await this.#eventLog.commit({\n traceId: id,\n kind: \"content_write\",\n affected: [artifactPath],\n parent: null,\n snapshot: { [artifactPath]: content },\n });\n const campaign: CampaignPackage = {\n tenantId,\n play: \"outreach_campaign\",\n id,\n status: \"draft\",\n icp,\n leads,\n sequence,\n compliance,\n schedule: draft.schedule,\n artifactPath,\n eventId,\n };\n await this.#debug({ type: \"campaign_draft\", tenantId, campaignId: id, eventId });\n return campaign;\n }\n\n async doctor(): Promise<OutreachDoctorReport> {\n return {\n capability: \"outreach-engine\",\n ok: true,\n checkedAt: new Date().toISOString(),\n plays: [\"outreach_campaign\"],\n sendMode: \"draft-only\",\n adapters: [\n { provider: \"local-plan\", ok: true },\n { provider: \"integration-vault-email\", ok: false },\n { provider: \"crm-sync\", ok: false },\n ],\n };\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await mkdir(dirname(join(this.#debugRoot, \".nebutra\", \"debug\", \"outreach-engine.jsonl\")), {\n recursive: true,\n });\n await appendCapabilityDebug(\"outreach-engine\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readOutreachEngineDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n return readCapabilityDebug(\"outreach-engine\", { root, limit });\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OutreachEngine,
|
|
3
|
+
readOutreachEngineDebug
|
|
4
|
+
} from "./chunk-ZH5KSGRI.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
var command = process.argv[2] ?? "doctor";
|
|
8
|
+
var root = process.env.OUTREACH_ENGINE_ROOT ?? ".nebutra/outreach-engine";
|
|
9
|
+
var tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
|
|
10
|
+
if (command === "doctor") {
|
|
11
|
+
const engine = await OutreachEngine.open(root, { tenantId });
|
|
12
|
+
try {
|
|
13
|
+
process.stdout.write(`${JSON.stringify(await engine.doctor(), null, 2)}
|
|
14
|
+
`);
|
|
15
|
+
} finally {
|
|
16
|
+
await engine.close();
|
|
17
|
+
}
|
|
18
|
+
} else if (command === "quickstart") {
|
|
19
|
+
const engine = await OutreachEngine.open(root, { tenantId });
|
|
20
|
+
try {
|
|
21
|
+
process.stdout.write(
|
|
22
|
+
`${JSON.stringify(
|
|
23
|
+
await engine.createCampaign({
|
|
24
|
+
tenantId,
|
|
25
|
+
icpDescription: process.argv.slice(3).join(" ") || "decision makers at mid-size D2C e-commerce companies",
|
|
26
|
+
targetCount: 10,
|
|
27
|
+
product: "Loop helps teams debug production issues",
|
|
28
|
+
sequenceLength: 3,
|
|
29
|
+
sendPerDay: 5,
|
|
30
|
+
senderEmails: ["founder@loop.test"],
|
|
31
|
+
complianceProfile: {
|
|
32
|
+
physicalAddress: "123 Market St, San Francisco, CA",
|
|
33
|
+
unsubscribeUrl: "https://loop.test/unsubscribe",
|
|
34
|
+
gdprBasis: "legitimate_interest"
|
|
35
|
+
}
|
|
36
|
+
}),
|
|
37
|
+
null,
|
|
38
|
+
2
|
|
39
|
+
)}
|
|
40
|
+
`
|
|
41
|
+
);
|
|
42
|
+
} finally {
|
|
43
|
+
await engine.close();
|
|
44
|
+
}
|
|
45
|
+
} else if (command === "debug") {
|
|
46
|
+
process.stdout.write(
|
|
47
|
+
`${JSON.stringify({ capability: "outreach-engine", entries: await readOutreachEngineDebug() }, null, 2)}
|
|
48
|
+
`
|
|
49
|
+
);
|
|
50
|
+
} else {
|
|
51
|
+
process.stderr.write(`Unknown outreach-engine command: ${command}
|
|
52
|
+
`);
|
|
53
|
+
process.exitCode = 1;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { OutreachEngine, readOutreachEngineDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst root = process.env.OUTREACH_ENGINE_ROOT ?? \".nebutra/outreach-engine\";\nconst tenantId = process.env.NEBUTRA_TENANT_ID ?? \"local\";\n\nif (command === \"doctor\") {\n const engine = await OutreachEngine.open(root, { tenantId });\n try {\n process.stdout.write(`${JSON.stringify(await engine.doctor(), null, 2)}\\n`);\n } finally {\n await engine.close();\n }\n} else if (command === \"quickstart\") {\n const engine = await OutreachEngine.open(root, { tenantId });\n try {\n process.stdout.write(\n `${JSON.stringify(\n await engine.createCampaign({\n tenantId,\n icpDescription:\n process.argv.slice(3).join(\" \") ||\n \"decision makers at mid-size D2C e-commerce companies\",\n targetCount: 10,\n product: \"Loop helps teams debug production issues\",\n sequenceLength: 3,\n sendPerDay: 5,\n senderEmails: [\"founder@loop.test\"],\n complianceProfile: {\n physicalAddress: \"123 Market St, San Francisco, CA\",\n unsubscribeUrl: \"https://loop.test/unsubscribe\",\n gdprBasis: \"legitimate_interest\",\n },\n }),\n null,\n 2,\n )}\\n`,\n );\n } finally {\n await engine.close();\n }\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"outreach-engine\", entries: await readOutreachEngineDebug() }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown outreach-engine command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAEA,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,QAAQ,IAAI,wBAAwB;AACjD,IAAM,WAAW,QAAQ,IAAI,qBAAqB;AAElD,IAAI,YAAY,UAAU;AACxB,QAAM,SAAS,MAAM,eAAe,KAAK,MAAM,EAAE,SAAS,CAAC;AAC3D,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,OAAO,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC5E,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF,WAAW,YAAY,cAAc;AACnC,QAAM,SAAS,MAAM,eAAe,KAAK,MAAM,EAAE,SAAS,CAAC;AAC3D,MAAI;AACF,YAAQ,OAAO;AAAA,MACb,GAAG,KAAK;AAAA,QACN,MAAM,OAAO,eAAe;AAAA,UAC1B;AAAA,UACA,gBACE,QAAQ,KAAK,MAAM,CAAC,EAAE,KAAK,GAAG,KAC9B;AAAA,UACF,aAAa;AAAA,UACb,SAAS;AAAA,UACT,gBAAgB;AAAA,UAChB,YAAY;AAAA,UACZ,cAAc,CAAC,mBAAmB;AAAA,UAClC,mBAAmB;AAAA,YACjB,iBAAiB;AAAA,YACjB,gBAAgB;AAAA,YAChB,WAAW;AAAA,UACb;AAAA,QACF,CAAC;AAAA,QACD;AAAA,QACA;AAAA,MACF,CAAC;AAAA;AAAA,IACH;AAAA,EACF,UAAE;AACA,UAAM,OAAO,MAAM;AAAA,EACrB;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,mBAAmB,SAAS,MAAM,wBAAwB,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACzG;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,oCAAoC,OAAO;AAAA,CAAI;AACpE,UAAQ,WAAW;AACrB;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ContentStore } from '@nebutra/content-store';
|
|
2
|
+
import { EventLog } from '@nebutra/event-log';
|
|
3
|
+
|
|
4
|
+
interface IcpDefinition {
|
|
5
|
+
readonly industries: readonly string[];
|
|
6
|
+
readonly companySize: {
|
|
7
|
+
readonly min: number;
|
|
8
|
+
readonly max: number;
|
|
9
|
+
};
|
|
10
|
+
readonly titles: readonly string[];
|
|
11
|
+
readonly geo: readonly string[];
|
|
12
|
+
readonly techStack: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
interface ComplianceProfile {
|
|
15
|
+
readonly physicalAddress: string;
|
|
16
|
+
readonly unsubscribeUrl: string;
|
|
17
|
+
readonly gdprBasis?: "legitimate_interest" | "consent" | "existing_customer";
|
|
18
|
+
}
|
|
19
|
+
interface EmailVariant {
|
|
20
|
+
readonly step: number;
|
|
21
|
+
readonly subject: string;
|
|
22
|
+
readonly body: string;
|
|
23
|
+
readonly daysAfterPrevious: number;
|
|
24
|
+
}
|
|
25
|
+
interface ComplianceReport {
|
|
26
|
+
readonly ok: boolean;
|
|
27
|
+
readonly checks: {
|
|
28
|
+
readonly hasUnsubscribeLink: boolean;
|
|
29
|
+
readonly hasPhysicalAddress: boolean;
|
|
30
|
+
readonly hasClearSubject: boolean;
|
|
31
|
+
readonly gdprBasisDocumented: boolean;
|
|
32
|
+
};
|
|
33
|
+
readonly suggestion?: string;
|
|
34
|
+
}
|
|
35
|
+
interface CampaignInput {
|
|
36
|
+
readonly tenantId?: string;
|
|
37
|
+
readonly icpDescription: string;
|
|
38
|
+
readonly targetCount: number;
|
|
39
|
+
readonly product: string;
|
|
40
|
+
readonly sequenceLength: number;
|
|
41
|
+
readonly sendPerDay: number;
|
|
42
|
+
readonly senderEmails: readonly string[];
|
|
43
|
+
readonly complianceProfile: ComplianceProfile;
|
|
44
|
+
}
|
|
45
|
+
interface Lead {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly company: string;
|
|
48
|
+
readonly title: string;
|
|
49
|
+
readonly source: "local-plan";
|
|
50
|
+
readonly confidence: number;
|
|
51
|
+
}
|
|
52
|
+
interface CampaignPackage {
|
|
53
|
+
readonly tenantId: string;
|
|
54
|
+
readonly play: "outreach_campaign";
|
|
55
|
+
readonly id: string;
|
|
56
|
+
readonly status: "draft";
|
|
57
|
+
readonly icp: IcpDefinition;
|
|
58
|
+
readonly leads: readonly Lead[];
|
|
59
|
+
readonly sequence: readonly EmailVariant[];
|
|
60
|
+
readonly compliance: ComplianceReport;
|
|
61
|
+
readonly schedule: {
|
|
62
|
+
readonly dailyLimit: number;
|
|
63
|
+
readonly senderEmails: readonly string[];
|
|
64
|
+
readonly requiresApprovalBeforeSend: true;
|
|
65
|
+
};
|
|
66
|
+
readonly artifactPath: string;
|
|
67
|
+
readonly eventId: string;
|
|
68
|
+
}
|
|
69
|
+
interface SequenceInput {
|
|
70
|
+
readonly product: string;
|
|
71
|
+
readonly sequenceLength: number;
|
|
72
|
+
readonly brandVoice?: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
interface OutreachDoctorReport {
|
|
75
|
+
readonly capability: "outreach-engine";
|
|
76
|
+
readonly ok: boolean;
|
|
77
|
+
readonly checkedAt: string;
|
|
78
|
+
readonly plays: readonly string[];
|
|
79
|
+
readonly sendMode: "draft-only";
|
|
80
|
+
readonly adapters: readonly {
|
|
81
|
+
readonly provider: string;
|
|
82
|
+
readonly ok: boolean;
|
|
83
|
+
}[];
|
|
84
|
+
}
|
|
85
|
+
interface OutreachEngineOptions {
|
|
86
|
+
readonly tenantId?: string;
|
|
87
|
+
readonly root?: string;
|
|
88
|
+
readonly debugRoot?: string;
|
|
89
|
+
readonly contentStore?: ContentStore;
|
|
90
|
+
readonly eventLog?: EventLog;
|
|
91
|
+
}
|
|
92
|
+
declare function defineIcp(description: string): IcpDefinition;
|
|
93
|
+
declare function buildEmailSequence(input: SequenceInput): readonly EmailVariant[];
|
|
94
|
+
declare function checkEmailCompliance(email: EmailVariant | undefined, profile: ComplianceProfile): ComplianceReport;
|
|
95
|
+
declare class OutreachEngine {
|
|
96
|
+
#private;
|
|
97
|
+
private constructor();
|
|
98
|
+
static open(root?: string, options?: Omit<OutreachEngineOptions, "root" | "contentStore" | "eventLog">): Promise<OutreachEngine>;
|
|
99
|
+
createCampaign(input: CampaignInput): Promise<CampaignPackage>;
|
|
100
|
+
doctor(): Promise<OutreachDoctorReport>;
|
|
101
|
+
close(): Promise<void>;
|
|
102
|
+
}
|
|
103
|
+
declare function readOutreachEngineDebug(root?: string, limit?: number): Promise<unknown[]>;
|
|
104
|
+
|
|
105
|
+
export { type CampaignInput, type CampaignPackage, type ComplianceProfile, type ComplianceReport, type EmailVariant, type IcpDefinition, type Lead, type OutreachDoctorReport, OutreachEngine, type OutreachEngineOptions, type SequenceInput, buildEmailSequence, checkEmailCompliance, defineIcp, readOutreachEngineDebug };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OutreachEngine,
|
|
3
|
+
buildEmailSequence,
|
|
4
|
+
checkEmailCompliance,
|
|
5
|
+
defineIcp,
|
|
6
|
+
readOutreachEngineDebug
|
|
7
|
+
} from "./chunk-ZH5KSGRI.js";
|
|
8
|
+
export {
|
|
9
|
+
OutreachEngine,
|
|
10
|
+
buildEmailSequence,
|
|
11
|
+
checkEmailCompliance,
|
|
12
|
+
defineIcp,
|
|
13
|
+
readOutreachEngineDebug
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { buildEmailSequence, checkEmailCompliance } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const sequence = buildEmailSequence({
|
|
4
|
+
product: "Loop helps teams debug production issues",
|
|
5
|
+
sequenceLength: 2,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
process.stdout.write(
|
|
9
|
+
`${JSON.stringify(
|
|
10
|
+
checkEmailCompliance(sequence[0], {
|
|
11
|
+
physicalAddress: "123 Market St",
|
|
12
|
+
unsubscribeUrl: "https://loop.test/unsubscribe",
|
|
13
|
+
gdprBasis: "legitimate_interest",
|
|
14
|
+
}),
|
|
15
|
+
null,
|
|
16
|
+
2,
|
|
17
|
+
)}\n`,
|
|
18
|
+
);
|
package/examples/icp.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { OutreachEngine } from "../src/index";
|
|
2
|
+
|
|
3
|
+
const engine = await OutreachEngine.open(".nebutra/outreach-engine-example", {
|
|
4
|
+
tenantId: "local",
|
|
5
|
+
});
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
process.stdout.write(
|
|
9
|
+
`${JSON.stringify(
|
|
10
|
+
await engine.createCampaign({
|
|
11
|
+
tenantId: "local",
|
|
12
|
+
icpDescription: "decision makers at mid-size D2C e-commerce companies",
|
|
13
|
+
targetCount: 10,
|
|
14
|
+
product: "Loop helps teams debug production issues",
|
|
15
|
+
sequenceLength: 3,
|
|
16
|
+
sendPerDay: 5,
|
|
17
|
+
senderEmails: ["founder@loop.test"],
|
|
18
|
+
complianceProfile: {
|
|
19
|
+
physicalAddress: "123 Market St",
|
|
20
|
+
unsubscribeUrl: "https://loop.test/unsubscribe",
|
|
21
|
+
gdprBasis: "legitimate_interest",
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
null,
|
|
25
|
+
2,
|
|
26
|
+
)}\n`,
|
|
27
|
+
);
|
|
28
|
+
} finally {
|
|
29
|
+
await engine.close();
|
|
30
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nebutra/outreach-engine",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Compliance-first outreach campaign Play package for founder-led sales workflows",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"nebutra": {
|
|
9
|
+
"status": "wip",
|
|
10
|
+
"productionReady": false,
|
|
11
|
+
"surface": "play-product",
|
|
12
|
+
"requires": [
|
|
13
|
+
"@nebutra/play-loader for SKILL.md play parsing",
|
|
14
|
+
"@nebutra/generation-context for product voice",
|
|
15
|
+
"@nebutra/content-store for campaign file truth",
|
|
16
|
+
"@nebutra/event-log for campaign checkpoints"
|
|
17
|
+
],
|
|
18
|
+
"gaps": [
|
|
19
|
+
"Lead sourcing uses deterministic local planning until integration-vault lead providers are configured",
|
|
20
|
+
"Send operations are draft-only until sender adapters and explicit approval are wired"
|
|
21
|
+
],
|
|
22
|
+
"featureId": "outreach-engine",
|
|
23
|
+
"category": "ai",
|
|
24
|
+
"summary": "Compliance-first outreach Play product that produces auditable campaign drafts"
|
|
25
|
+
},
|
|
26
|
+
"main": "./src/index.ts",
|
|
27
|
+
"types": "./src/index.ts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": "./src/index.ts"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@nebutra/capability-kit": "0.2.0",
|
|
33
|
+
"@nebutra/content-store": "0.1.0",
|
|
34
|
+
"@nebutra/errors": "0.1.0",
|
|
35
|
+
"@nebutra/event-log": "0.1.0",
|
|
36
|
+
"@nebutra/generation-context": "0.1.0",
|
|
37
|
+
"@nebutra/play-loader": "0.1.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.19.15",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
42
|
+
"tsx": "^4.21.0",
|
|
43
|
+
"typescript": "^5.9.3",
|
|
44
|
+
"vitest": "^4.1.4"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/outreach-engine#readme",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
|
|
50
|
+
"directory": "packages/ai/outreach-engine"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"typecheck": "tsc --noEmit"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: outreach_campaign
|
|
3
|
+
kind: play
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: Create a compliance-gated founder-led outbound campaign draft
|
|
6
|
+
inputs:
|
|
7
|
+
icp_description: { type: string, max_length: 500 }
|
|
8
|
+
target_count: { type: number }
|
|
9
|
+
product: { type: string, max_length: 500 }
|
|
10
|
+
sequence_length: { type: number }
|
|
11
|
+
send_per_day: { type: number }
|
|
12
|
+
outputs:
|
|
13
|
+
campaign: { type: file, path: outreach/campaign.json, mime: application/json }
|
|
14
|
+
budget:
|
|
15
|
+
duration_s: 600
|
|
16
|
+
cost_usd: 2.0
|
|
17
|
+
required_skills:
|
|
18
|
+
- content_store.write
|
|
19
|
+
- research_agent.run
|
|
20
|
+
- integration_vault.invoke
|
|
21
|
+
sub_agents:
|
|
22
|
+
- role: icp_strategist
|
|
23
|
+
allowed_skills: [content_store.write]
|
|
24
|
+
- role: lead_researcher
|
|
25
|
+
allowed_skills: [research_agent.run]
|
|
26
|
+
- role: compliance_reviewer
|
|
27
|
+
allowed_skills: [content_store.write]
|
|
28
|
+
depends_on_plays: []
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What this play does
|
|
32
|
+
|
|
33
|
+
Creates a draft outbound campaign from an ICP description.
|
|
34
|
+
|
|
35
|
+
The Play is draft-first. It may produce leads, enrichment notes, email variants,
|
|
36
|
+
and a schedule, but actual sending requires explicit approval and a configured
|
|
37
|
+
sender adapter.
|
|
38
|
+
|
|
39
|
+
## Rules
|
|
40
|
+
|
|
41
|
+
- Never bypass unsubscribe, physical address, or consent basis checks.
|
|
42
|
+
- Never send directly from the Play run.
|
|
43
|
+
- Keep lead data tenant-scoped.
|
|
44
|
+
- Write campaign artifacts through content-store.
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { OutreachEngine, readOutreachEngineDebug } from "./index";
|
|
2
|
+
|
|
3
|
+
const command = process.argv[2] ?? "doctor";
|
|
4
|
+
const root = process.env.OUTREACH_ENGINE_ROOT ?? ".nebutra/outreach-engine";
|
|
5
|
+
const tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
|
|
6
|
+
|
|
7
|
+
if (command === "doctor") {
|
|
8
|
+
const engine = await OutreachEngine.open(root, { tenantId });
|
|
9
|
+
try {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(await engine.doctor(), null, 2)}\n`);
|
|
11
|
+
} finally {
|
|
12
|
+
await engine.close();
|
|
13
|
+
}
|
|
14
|
+
} else if (command === "quickstart") {
|
|
15
|
+
const engine = await OutreachEngine.open(root, { tenantId });
|
|
16
|
+
try {
|
|
17
|
+
process.stdout.write(
|
|
18
|
+
`${JSON.stringify(
|
|
19
|
+
await engine.createCampaign({
|
|
20
|
+
tenantId,
|
|
21
|
+
icpDescription:
|
|
22
|
+
process.argv.slice(3).join(" ") ||
|
|
23
|
+
"decision makers at mid-size D2C e-commerce companies",
|
|
24
|
+
targetCount: 10,
|
|
25
|
+
product: "Loop helps teams debug production issues",
|
|
26
|
+
sequenceLength: 3,
|
|
27
|
+
sendPerDay: 5,
|
|
28
|
+
senderEmails: ["founder@loop.test"],
|
|
29
|
+
complianceProfile: {
|
|
30
|
+
physicalAddress: "123 Market St, San Francisco, CA",
|
|
31
|
+
unsubscribeUrl: "https://loop.test/unsubscribe",
|
|
32
|
+
gdprBasis: "legitimate_interest",
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
null,
|
|
36
|
+
2,
|
|
37
|
+
)}\n`,
|
|
38
|
+
);
|
|
39
|
+
} finally {
|
|
40
|
+
await engine.close();
|
|
41
|
+
}
|
|
42
|
+
} else if (command === "debug") {
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
`${JSON.stringify({ capability: "outreach-engine", entries: await readOutreachEngineDebug() }, null, 2)}\n`,
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
process.stderr.write(`Unknown outreach-engine command: ${command}\n`);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
}
|