brand-manager-worker 0.1.0 → 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/package.json +1 -1
- package/worker/agent.js +15 -2
- package/worker/handlers.js +3 -15
- package/worker/mirror.js +16 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brand-manager-worker",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "The Goose Tools brand-deal worker — your computer reads your brand email and drafts replies in your voice for goosetools.com, using your own Claude account. Drafts only; it never sends.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/worker/agent.js
CHANGED
|
@@ -12,8 +12,21 @@ import { BRAND_DIR } from "./paths.js";
|
|
|
12
12
|
// Automated / no-reply senders never send brand deals — skip them WITHOUT
|
|
13
13
|
// spending a Claude call. Conservative on purpose: real brands/agencies use
|
|
14
14
|
// human addresses.
|
|
15
|
-
const AUTOMATED_SENDER =
|
|
16
|
-
|
|
15
|
+
const AUTOMATED_SENDER = new RegExp(
|
|
16
|
+
[
|
|
17
|
+
// Classic no-reply shapes.
|
|
18
|
+
"no-?reply", "do-?not-?reply", "noreply", "mailer-daemon", "postmaster",
|
|
19
|
+
"notifications?@", "accounts\\.google\\.com",
|
|
20
|
+
"@.*\\.(beehiiv|substack)\\.com",
|
|
21
|
+
// Bulk-mail SUBDOMAINS. Retail blasts send from things like
|
|
22
|
+
// news.emailmarket.shein.com, which slipped past the old @mail./@email.
|
|
23
|
+
// patterns and cost a full Claude call each — 7 SHEIN cart reminders in
|
|
24
|
+
// one scan. Matched as a leading label so a real company domain like
|
|
25
|
+
// news-corp.com or mailchimp.com (an agency's own address) is untouched.
|
|
26
|
+
"@(e|news|newsletter|email|emails|mail|mailer|marketing|promo|promos|campaign|campaigns|updates|offers|deals|shop|store|hello|info)[.-]",
|
|
27
|
+
].join("|"),
|
|
28
|
+
"i",
|
|
29
|
+
);
|
|
17
30
|
|
|
18
31
|
export const isAutomated = (from) => AUTOMATED_SENDER.test(from);
|
|
19
32
|
|
package/worker/handlers.js
CHANGED
|
@@ -35,18 +35,6 @@ const noGmail = () => ({
|
|
|
35
35
|
error: "Gmail isn't connected — connect your email on goosetools.com/dashboard/brand-manager",
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
/** Thread metadata rows for the mirror, from fetched threads. */
|
|
39
|
-
const threadMeta = (threads) =>
|
|
40
|
-
threads.map((t) => ({
|
|
41
|
-
threadId: t.id,
|
|
42
|
-
accountEmail: t.account,
|
|
43
|
-
fromName: t.last.from.replace(/\s*<[^>]*>\s*/, "").trim() || null,
|
|
44
|
-
fromEmail: (t.last.from.match(/<([^>]+)>/)?.[1] ?? t.last.from).trim() || null,
|
|
45
|
-
subject: t.subject,
|
|
46
|
-
lastMessageAt: t.last.date,
|
|
47
|
-
hasUnsentDraft: t.hasUnsentDraft,
|
|
48
|
-
}));
|
|
49
|
-
|
|
50
38
|
/** Resolve decision attachment paths ("stats/screenshots/x.png") under BRAND_DIR. */
|
|
51
39
|
const resolveAttachments = (paths = []) =>
|
|
52
40
|
paths.map((p) => (p.startsWith("/") ? p : join(BRAND_DIR, p))).filter((p) => existsSync(p));
|
|
@@ -73,7 +61,7 @@ async function draftForThread(job, thread) {
|
|
|
73
61
|
return {
|
|
74
62
|
ok: true,
|
|
75
63
|
summary: `skip — ${decision.summary}`,
|
|
76
|
-
mirror: buildMirror({ threads:
|
|
64
|
+
mirror: buildMirror({ threads: [thread] }),
|
|
77
65
|
};
|
|
78
66
|
}
|
|
79
67
|
|
|
@@ -105,7 +93,7 @@ async function draftForThread(job, thread) {
|
|
|
105
93
|
summary: decision.summary,
|
|
106
94
|
resultBody: decision.body ?? "",
|
|
107
95
|
flags: decision.flags ?? [],
|
|
108
|
-
mirror: buildMirror({ threads:
|
|
96
|
+
mirror: buildMirror({ threads: [thread] }),
|
|
109
97
|
};
|
|
110
98
|
}
|
|
111
99
|
|
|
@@ -177,7 +165,7 @@ export async function runScan(job) {
|
|
|
177
165
|
ok: true,
|
|
178
166
|
summary: lines.length ? lines.join(" · ").slice(0, 1900) : "No new brand mail needing a draft.",
|
|
179
167
|
flags,
|
|
180
|
-
mirror: buildMirror({ threads
|
|
168
|
+
mirror: buildMirror({ threads }),
|
|
181
169
|
};
|
|
182
170
|
}
|
|
183
171
|
|
package/worker/mirror.js
CHANGED
|
@@ -74,15 +74,22 @@ export function collectKnowledge() {
|
|
|
74
74
|
* machine is defined in exactly one place, right here.
|
|
75
75
|
*/
|
|
76
76
|
export function collectThreads(threads) {
|
|
77
|
-
return threads.map((t) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
77
|
+
return threads.map((t) => {
|
|
78
|
+
// Raw Thread objects in, mirror rows out. This is the ONLY place that
|
|
79
|
+
// shape is decided — callers must not pre-map, or threadId silently
|
|
80
|
+
// becomes undefined and the server drops the row (which is exactly what
|
|
81
|
+
// happened: deals and knowledge mirrored, threads never did).
|
|
82
|
+
const from = t.last?.from ?? "";
|
|
83
|
+
return {
|
|
84
|
+
threadId: t.id,
|
|
85
|
+
accountEmail: t.account ?? null,
|
|
86
|
+
fromName: from.replace(/\s*<[^>]*>\s*/, "").replace(/^"|"$/g, "").trim() || null,
|
|
87
|
+
fromEmail: (from.match(/<([^>]+)>/)?.[1] ?? from).trim() || null,
|
|
88
|
+
subject: t.subject ?? null,
|
|
89
|
+
lastMessageAt: t.last?.date ?? null,
|
|
90
|
+
hasUnsentDraft: Boolean(t.hasUnsentDraft),
|
|
91
|
+
};
|
|
92
|
+
});
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
/** The full mirror payload for a complete() call. */
|