@analyticscli/growth-engineer 0.1.1-preview.14 → 0.1.1-preview.18
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/dist/runtime/discord-openclaw-bridge.mjs +182 -33
- package/dist/runtime/export-asc-summary.mjs +239 -79
- package/dist/runtime/export-asc-summary.mjs.map +1 -1
- package/dist/runtime/openclaw-exporters-lib.d.mts +62 -2
- package/dist/runtime/openclaw-exporters-lib.mjs +166 -67
- package/dist/runtime/openclaw-exporters-lib.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-runner.mjs +0 -16
- package/dist/runtime/openclaw-growth-runner.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-start.mjs +74 -5
- package/dist/runtime/openclaw-growth-start.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-status.mjs +2 -2
- package/dist/runtime/openclaw-growth-status.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-wizard.mjs +160 -45
- package/dist/runtime/openclaw-growth-wizard.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -148,12 +148,12 @@ function truncateDiscordText(value, maxLength) {
|
|
|
148
148
|
return `${text.slice(0, Math.max(0, maxLength - 3)).trim()}...`;
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
function
|
|
151
|
+
function chunkMessage(content) {
|
|
152
152
|
const chunks = [];
|
|
153
|
-
const maxLength =
|
|
153
|
+
const maxLength = 1900;
|
|
154
154
|
let remaining = String(content || "").trim();
|
|
155
155
|
|
|
156
|
-
while (remaining.length > maxLength
|
|
156
|
+
while (remaining.length > maxLength) {
|
|
157
157
|
let splitAt = remaining.lastIndexOf("\n", maxLength);
|
|
158
158
|
if (splitAt < maxLength * 0.5) {
|
|
159
159
|
splitAt = remaining.lastIndexOf(" ", maxLength);
|
|
@@ -166,49 +166,179 @@ function chunkEmbedDescription(content) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
if (remaining) {
|
|
169
|
-
chunks.push(
|
|
169
|
+
chunks.push(remaining);
|
|
170
170
|
}
|
|
171
|
-
return chunks
|
|
171
|
+
return chunks;
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
function discordField(name, value, inline = false) {
|
|
175
|
+
return {
|
|
176
|
+
name: truncateDiscordText(name, 256) || "Detail",
|
|
177
|
+
value: truncateDiscordText(value, 1024) || "-",
|
|
178
|
+
inline,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function splitNamedLine(line) {
|
|
183
|
+
const clean = String(line || "").replace(/^-\s*/, "").trim();
|
|
184
|
+
const bracketMarker = clean.indexOf(": [");
|
|
185
|
+
if (bracketMarker > 0) {
|
|
186
|
+
return [clean.slice(0, bracketMarker).trim(), clean.slice(bracketMarker + 2).trim()];
|
|
187
|
+
}
|
|
188
|
+
const splitAt = clean.lastIndexOf(": ");
|
|
189
|
+
if (splitAt > 0) {
|
|
190
|
+
return [clean.slice(0, splitAt).trim(), clean.slice(splitAt + 2).trim()];
|
|
178
191
|
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
179
194
|
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
195
|
+
function buildStructuredOpenClawDailyPayload(text, lines) {
|
|
196
|
+
const title = truncateDiscordText(lines[0], 256);
|
|
197
|
+
const fields = [];
|
|
198
|
+
let inTopByProject = false;
|
|
199
|
+
let pendingFinding = null;
|
|
200
|
+
const flushPendingFinding = () => {
|
|
201
|
+
if (pendingFinding) {
|
|
202
|
+
fields.push(discordField(pendingFinding.name, pendingFinding.value));
|
|
203
|
+
pendingFinding = null;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
187
206
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
207
|
+
for (const line of lines.slice(1)) {
|
|
208
|
+
if (/^Top by project:/i.test(line)) {
|
|
209
|
+
inTopByProject = true;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
if (/^Action:/i.test(line)) {
|
|
213
|
+
flushPendingFinding();
|
|
214
|
+
fields.push(discordField("Action", line.replace(/^Action:\s*/i, ""), true));
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (/^Suppressed today:/i.test(line)) {
|
|
218
|
+
flushPendingFinding();
|
|
219
|
+
fields.push(discordField("Suppressed today", line.replace(/^Suppressed today:\s*/i, ""), true));
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (/^Charts:/i.test(line)) {
|
|
223
|
+
flushPendingFinding();
|
|
224
|
+
fields.push(discordField("Charts", line.replace(/^Charts:\s*/i, ""), true));
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (/^Runner completed/i.test(line)) {
|
|
228
|
+
flushPendingFinding();
|
|
229
|
+
fields.push(discordField("Run status", line));
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (/^Link:/i.test(line)) {
|
|
233
|
+
if (pendingFinding) {
|
|
234
|
+
pendingFinding.value = `${pendingFinding.value}\n${line}`;
|
|
235
|
+
} else {
|
|
236
|
+
fields.push(discordField("Link", line.replace(/^Link:\s*/i, "")));
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (/^\d+\s+events?,/i.test(line) && pendingFinding) {
|
|
241
|
+
pendingFinding.value = `${pendingFinding.value}\n${line}`;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (inTopByProject && /^-\s*/.test(line)) {
|
|
245
|
+
flushPendingFinding();
|
|
246
|
+
const named = splitNamedLine(line);
|
|
247
|
+
if (named) {
|
|
248
|
+
fields.push(discordField(named[0], named[1]));
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const named = splitNamedLine(line);
|
|
253
|
+
if (named && /^(sentry|glitchtip|analytics|github|asc|appStoreConnect|revenue|coolify|stripe|paddle)/i.test(named[0])) {
|
|
254
|
+
flushPendingFinding();
|
|
255
|
+
pendingFinding = { name: named[0], value: named[1] };
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
if (line.trim()) {
|
|
259
|
+
flushPendingFinding();
|
|
260
|
+
fields.push(discordField("Detail", line));
|
|
202
261
|
}
|
|
203
262
|
}
|
|
263
|
+
flushPendingFinding();
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
content: "",
|
|
267
|
+
embeds: [
|
|
268
|
+
{
|
|
269
|
+
title,
|
|
270
|
+
color: /^OpenClaw (daily|healthcheck): OK/i.test(title) ? 0x12b76a : 0xf79009,
|
|
271
|
+
fields: fields.slice(0, 20),
|
|
272
|
+
footer: { text: "GROWTH_RUN" },
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
fallbackText: text,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
204
279
|
|
|
280
|
+
function buildStructuredConnectorPayload(text, lines) {
|
|
281
|
+
const title = truncateDiscordText(lines[0], 256);
|
|
282
|
+
const fields = [];
|
|
283
|
+
let description = "";
|
|
284
|
+
for (const line of lines.slice(1)) {
|
|
285
|
+
if (/^Secrets stay/i.test(line)) {
|
|
286
|
+
description = line;
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (/^Fix:/i.test(line)) {
|
|
290
|
+
fields.push(discordField("Fix", line.replace(/^Fix:\s*/i, "")));
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (/^At\s+\d{4}-/i.test(line)) {
|
|
294
|
+
fields.push(discordField("Proof", line));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (/CONNECTOR_HEALTH_ALERT/i.test(line)) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
const named = splitNamedLine(line);
|
|
301
|
+
if (named) {
|
|
302
|
+
fields.push(discordField(named[0], named[1]));
|
|
303
|
+
} else if (line.trim()) {
|
|
304
|
+
fields.push(discordField("Detail", line));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
205
307
|
return {
|
|
206
308
|
content: "",
|
|
207
|
-
embeds
|
|
309
|
+
embeds: [
|
|
310
|
+
{
|
|
311
|
+
title,
|
|
312
|
+
description,
|
|
313
|
+
color: /blocked|failed|issue/i.test(text) ? 0xd92d20 : 0xf79009,
|
|
314
|
+
fields: fields.slice(0, 20),
|
|
315
|
+
footer: { text: "CONNECTOR_HEALTH_ALERT" },
|
|
316
|
+
timestamp: new Date().toISOString(),
|
|
317
|
+
},
|
|
318
|
+
],
|
|
208
319
|
fallbackText: text,
|
|
209
320
|
};
|
|
210
321
|
}
|
|
211
322
|
|
|
323
|
+
function structuredTextToEmbedPayload(input) {
|
|
324
|
+
const text = String(input || "").trim();
|
|
325
|
+
if (!text) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const lines = text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
330
|
+
if (lines.length === 0) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
if (/^OpenClaw connector health:/i.test(lines[0]) || /CONNECTOR_HEALTH_ALERT/.test(text)) {
|
|
334
|
+
return buildStructuredConnectorPayload(text, lines);
|
|
335
|
+
}
|
|
336
|
+
if (/^OpenClaw (daily|healthcheck)(:|\s)/i.test(lines[0])) {
|
|
337
|
+
return buildStructuredOpenClawDailyPayload(text, lines);
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
212
342
|
function normalizeEmbedPayload(input) {
|
|
213
343
|
const raw = String(input || "");
|
|
214
344
|
if (process.env.OPENCLAW_DISCORD_DELIVERY_FORMAT === "embed" || process.argv.includes("--json") || raw.trim().startsWith("{")) {
|
|
@@ -228,7 +358,7 @@ function normalizeEmbedPayload(input) {
|
|
|
228
358
|
}
|
|
229
359
|
}
|
|
230
360
|
}
|
|
231
|
-
return
|
|
361
|
+
return structuredTextToEmbedPayload(raw);
|
|
232
362
|
}
|
|
233
363
|
|
|
234
364
|
async function sendDiscordPayload(payload) {
|
|
@@ -254,7 +384,26 @@ async function sendMessage(content) {
|
|
|
254
384
|
return await sendDiscordPayload(embedPayload);
|
|
255
385
|
}
|
|
256
386
|
|
|
257
|
-
|
|
387
|
+
const chunks = chunkMessage(content);
|
|
388
|
+
if (chunks.length === 0) {
|
|
389
|
+
throw new Error("Refusing to send an empty message.");
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const sent = [];
|
|
393
|
+
for (const target of DISCORD_TARGETS) {
|
|
394
|
+
await validateTargetChannel(target);
|
|
395
|
+
for (const chunk of chunks) {
|
|
396
|
+
const message = await discordFetch(`/channels/${target.channelId}/messages`, {
|
|
397
|
+
method: "POST",
|
|
398
|
+
body: JSON.stringify({
|
|
399
|
+
allowed_mentions: { parse: [], users: target.allowedMentionUsers },
|
|
400
|
+
content: chunk,
|
|
401
|
+
}),
|
|
402
|
+
});
|
|
403
|
+
sent.push({ target, message });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return sent;
|
|
258
407
|
}
|
|
259
408
|
|
|
260
409
|
async function readStdin() {
|