@analyticscli/growth-engineer 0.1.0-preview.1 → 0.1.0-preview.3
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/config.js +1 -1
- package/dist/runtime/openclaw-growth-runner.mjs +20 -20
- package/dist/runtime/openclaw-growth-runner.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-start.mjs +1 -1
- package/dist/runtime/openclaw-growth-wizard.mjs +205 -250
- package/dist/runtime/openclaw-growth-wizard.mjs.map +1 -1
- package/package.json +1 -1
- package/templates/config.example.json +20 -20
|
@@ -11,7 +11,17 @@ import { loadOpenClawGrowthSecrets } from './openclaw-growth-env.mjs';
|
|
|
11
11
|
const DEFAULT_CONFIG_PATH = 'data/openclaw-growth-engineer/config.json';
|
|
12
12
|
const SELF_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
13
13
|
const ENABLE_ISOLATED_SECRET_RUNNER_WIZARD = false;
|
|
14
|
+
const DEFAULT_GROWTH_INTERVAL_MINUTES = 1440;
|
|
15
|
+
const DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES = 360;
|
|
14
16
|
const CONNECTOR_KEYS = ['analytics', 'github', 'revenuecat', 'sentry', 'asc'];
|
|
17
|
+
class WizardAbortError extends Error {
|
|
18
|
+
exitCode;
|
|
19
|
+
constructor(message, exitCode = 130) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'WizardAbortError';
|
|
22
|
+
this.exitCode = exitCode;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
15
25
|
const CONNECTOR_DEFINITIONS = [
|
|
16
26
|
{
|
|
17
27
|
key: 'analytics',
|
|
@@ -47,33 +57,33 @@ const CONNECTOR_DEFINITIONS = [
|
|
|
47
57
|
const DEFAULT_CADENCE_PLAN = [
|
|
48
58
|
{
|
|
49
59
|
key: 'daily',
|
|
50
|
-
title: 'Daily production guardrail',
|
|
60
|
+
title: 'Daily Sentry and production guardrail',
|
|
51
61
|
intervalDays: 1,
|
|
52
62
|
criticalOnly: true,
|
|
53
|
-
focusAreas: ['crash', 'conversion', 'paywall'],
|
|
54
|
-
sourcePriorities: ['sentry', 'glitchtip', 'analytics', 'asc_cli', '
|
|
55
|
-
objective: '
|
|
56
|
-
instructions: '
|
|
63
|
+
focusAreas: ['sentry_errors', 'crash', 'onboarding', 'conversion', 'paywall', 'purchase'],
|
|
64
|
+
sourcePriorities: ['sentry', 'glitchtip', 'analytics', 'revenuecat', 'asc_cli', 'feedback', 'github'],
|
|
65
|
+
objective: 'Analyze every configured project for critical production blockers: Sentry/GlitchTip errors, crashes, onboarding or purchase drop-offs, zero-conversion days, missing buyers, very low users, and other silent business anomalies.',
|
|
66
|
+
instructions: 'Compare against recent baselines across connected sources and code changes. If the finding is critical, produce the exact fix or next debugging step and prefer a GitHub issue or draft PR when GitHub write access is configured; otherwise hand off via OpenClaw chat. Avoid generic growth ideas.',
|
|
57
67
|
},
|
|
58
68
|
{
|
|
59
69
|
key: 'weekly',
|
|
60
|
-
title: 'Weekly
|
|
70
|
+
title: 'Weekly executive product and growth summary',
|
|
61
71
|
intervalDays: 7,
|
|
62
72
|
criticalOnly: false,
|
|
63
|
-
focusAreas: ['conversion', 'paywall', 'onboarding', 'marketing', 'retention'],
|
|
64
|
-
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'sentry'],
|
|
65
|
-
objective: '
|
|
66
|
-
instructions: 'Pick one to three high-confidence
|
|
73
|
+
focusAreas: ['conversion', 'paywall', 'onboarding', 'marketing', 'retention', 'stability'],
|
|
74
|
+
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'sentry', 'github'],
|
|
75
|
+
objective: 'Create an executive summary across all configured projects, connectors, recent releases, code changes, revenue, activation, retention, reviews, and production stability.',
|
|
76
|
+
instructions: 'Pick one to three high-confidence improvements with evidence, expected KPI movement, likely code/store surfaces, owner-ready next steps, and a verification plan. Create GitHub issues or draft PR proposals only when the evidence is specific enough.',
|
|
67
77
|
},
|
|
68
78
|
{
|
|
69
79
|
key: 'monthly',
|
|
70
|
-
title: 'Monthly business and
|
|
80
|
+
title: 'Monthly deep product, business, and code review',
|
|
71
81
|
intervalDays: 30,
|
|
72
82
|
criticalOnly: false,
|
|
73
|
-
focusAreas: ['conversion', 'paywall', 'retention', 'marketing', 'onboarding'],
|
|
74
|
-
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'sentry'],
|
|
75
|
-
objective: 'Compare MRR, trial conversion, churn, acquisition quality, store conversion, retention, review themes, feature usage,
|
|
76
|
-
instructions: 'Decide what should be built, changed, or
|
|
83
|
+
focusAreas: ['conversion', 'paywall', 'retention', 'marketing', 'onboarding', 'codebase'],
|
|
84
|
+
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'sentry', 'github'],
|
|
85
|
+
objective: 'Compare all configured projects month-over-month: MRR, trial conversion, churn, acquisition quality, store conversion, retention, review themes, feature usage, crash totals, and codebase changes.',
|
|
86
|
+
instructions: 'Decide what should be built, changed, deleted, or instrumented next. Tie conclusions to connector data plus codebase evidence and explain why each recommendation should move revenue, activation, retention, stability, or acquisition quality.',
|
|
77
87
|
},
|
|
78
88
|
{
|
|
79
89
|
key: 'quarterly',
|
|
@@ -81,8 +91,8 @@ const DEFAULT_CADENCE_PLAN = [
|
|
|
81
91
|
intervalDays: 91,
|
|
82
92
|
criticalOnly: false,
|
|
83
93
|
focusAreas: ['marketing', 'paywall', 'retention', 'conversion', 'onboarding'],
|
|
84
|
-
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback'],
|
|
85
|
-
objective: 'Revisit positioning, pricing/packaging, onboarding architecture, roadmap assumptions, tracking quality, and major funnel bets.',
|
|
94
|
+
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'github', 'sentry'],
|
|
95
|
+
objective: 'Revisit positioning, pricing/packaging, onboarding architecture, roadmap assumptions, tracking quality, codebase constraints, and major funnel bets across every configured project.',
|
|
86
96
|
instructions: 'Find structural constraints and durable opportunities. Tie recommendations to cohort behavior, monetization, reviews, channel quality, and shipped changes.',
|
|
87
97
|
},
|
|
88
98
|
{
|
|
@@ -92,7 +102,7 @@ const DEFAULT_CADENCE_PLAN = [
|
|
|
92
102
|
criticalOnly: false,
|
|
93
103
|
focusAreas: ['retention', 'conversion', 'paywall', 'marketing', 'general'],
|
|
94
104
|
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'sentry'],
|
|
95
|
-
objective: 'Audit connector coverage, SDK instrumentation, event taxonomy, data reliability, memory, growth loops, and whether strategy still matches the best users.',
|
|
105
|
+
objective: 'Audit connector coverage, SDK instrumentation, event taxonomy, data reliability, memory, growth loops, and whether product/code strategy still matches the best users across configured projects.',
|
|
96
106
|
instructions: 'Prioritize measurement fixes and system changes that make future analysis more trustworthy. Identify stale events, missing attribution, weak identity, and misleading dashboards.',
|
|
97
107
|
},
|
|
98
108
|
{
|
|
@@ -102,7 +112,7 @@ const DEFAULT_CADENCE_PLAN = [
|
|
|
102
112
|
criticalOnly: false,
|
|
103
113
|
focusAreas: ['marketing', 'retention', 'paywall', 'conversion', 'general'],
|
|
104
114
|
sourcePriorities: ['analytics', 'revenuecat', 'asc_cli', 'feedback', 'sentry'],
|
|
105
|
-
objective: 'Reset strategy from evidence: market/channel fit, monetization model, retention ceiling, product scope, and whether to double down, reposition, rebuild, or sunset major surfaces/features.',
|
|
115
|
+
objective: 'Reset strategy from evidence across every configured project: market/channel fit, monetization model, retention ceiling, product scope, and whether to double down, reposition, rebuild, or sunset major surfaces/features.',
|
|
106
116
|
instructions: 'Use the full year of memory, releases, revenue, acquisition, reviews, code changes, and cohort behavior. Produce strategic experiments and stop-doing decisions.',
|
|
107
117
|
},
|
|
108
118
|
];
|
|
@@ -268,19 +278,24 @@ function withMissingRequiredAnalyticsConnector(selected) {
|
|
|
268
278
|
return orderConnectors(selected);
|
|
269
279
|
return orderConnectors(['analytics', ...selected]);
|
|
270
280
|
}
|
|
271
|
-
async function askConnectorSelection(rl) {
|
|
272
|
-
return askConnectorSelectionWithHealth(rl, {}, []);
|
|
273
|
-
}
|
|
274
281
|
async function askConnectorSelectionWithHealth(rl, healthByConnector = {}, initialSelected = []) {
|
|
275
282
|
if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
|
|
276
283
|
return await askConnectorSelectionByText(rl, healthByConnector);
|
|
277
284
|
}
|
|
278
285
|
rl.pause();
|
|
286
|
+
let completed = false;
|
|
279
287
|
try {
|
|
280
|
-
|
|
288
|
+
const selected = await askConnectorSelectionByKeys(healthByConnector, initialSelected);
|
|
289
|
+
completed = true;
|
|
290
|
+
return selected;
|
|
281
291
|
}
|
|
282
292
|
finally {
|
|
283
|
-
|
|
293
|
+
if (completed) {
|
|
294
|
+
rl.resume();
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
process.stdin.pause();
|
|
298
|
+
}
|
|
284
299
|
}
|
|
285
300
|
}
|
|
286
301
|
async function askConnectorSelectionByText(rl, healthByConnector = {}) {
|
|
@@ -326,22 +341,107 @@ function printConnectorIntro() {
|
|
|
326
341
|
process.stdout.write(`\n${ANSI.bold}OpenClaw connector setup${ANSI.reset}\n`);
|
|
327
342
|
process.stdout.write(`${ANSI.dim}Secrets stay local on this host. Do not paste them into any chat or social channel.${ANSI.reset}\n\n`);
|
|
328
343
|
}
|
|
329
|
-
async function
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
344
|
+
async function askMenuChoice(rl, { title, subtitle = 'Use Up/Down to move, Enter to continue.', options, defaultValue, renderHeader, }) {
|
|
345
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
|
|
346
|
+
process.stdout.write(`\n${title}\n`);
|
|
347
|
+
options.forEach((option, index) => {
|
|
348
|
+
process.stdout.write(` ${index + 1}) ${option.label}: ${option.detail}\n`);
|
|
349
|
+
});
|
|
350
|
+
const defaultIndex = Math.max(0, options.findIndex((option) => option.value === defaultValue));
|
|
351
|
+
const answer = await ask(rl, `Setup area (1-${options.length})`, String(defaultIndex + 1));
|
|
352
|
+
const selected = options[Number(answer.trim()) - 1] || options[defaultIndex];
|
|
353
|
+
return selected.value;
|
|
354
|
+
}
|
|
355
|
+
rl.pause();
|
|
356
|
+
let completed = false;
|
|
337
357
|
try {
|
|
338
|
-
|
|
358
|
+
const selected = await askMenuChoiceByKeys({ title, subtitle, options, defaultValue, renderHeader });
|
|
359
|
+
completed = true;
|
|
360
|
+
return selected;
|
|
339
361
|
}
|
|
340
362
|
finally {
|
|
341
|
-
|
|
342
|
-
|
|
363
|
+
if (completed) {
|
|
364
|
+
rl.resume();
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
process.stdin.pause();
|
|
368
|
+
}
|
|
343
369
|
}
|
|
344
370
|
}
|
|
371
|
+
async function askMenuChoiceByKeys({ title, subtitle, options, defaultValue, renderHeader, }) {
|
|
372
|
+
emitKeypressEvents(process.stdin);
|
|
373
|
+
const wasRaw = process.stdin.isRaw;
|
|
374
|
+
const wasPaused = process.stdin.isPaused();
|
|
375
|
+
process.stdin.setRawMode(true);
|
|
376
|
+
process.stdin.resume();
|
|
377
|
+
let cursorIndex = Math.max(0, options.findIndex((option) => option.value === defaultValue));
|
|
378
|
+
return await new Promise((resolve, reject) => {
|
|
379
|
+
const cleanup = () => {
|
|
380
|
+
process.stdin.off('keypress', onKeypress);
|
|
381
|
+
process.stdin.setRawMode(Boolean(wasRaw));
|
|
382
|
+
if (wasPaused) {
|
|
383
|
+
process.stdin.pause();
|
|
384
|
+
}
|
|
385
|
+
process.stdout.write(ANSI.showCursor);
|
|
386
|
+
};
|
|
387
|
+
const render = () => {
|
|
388
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
389
|
+
renderHeader?.();
|
|
390
|
+
process.stdout.write(`\n${ANSI.bold}${title}${ANSI.reset}\n`);
|
|
391
|
+
process.stdout.write(`${ANSI.dim}${subtitle}${ANSI.reset}\n\n`);
|
|
392
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
393
|
+
const option = options[index];
|
|
394
|
+
const pointer = index === cursorIndex ? `${ANSI.cyan}>${ANSI.reset}` : ' ';
|
|
395
|
+
const number = `${index + 1})`;
|
|
396
|
+
process.stdout.write(`${pointer} ${number} ${ANSI.bold}${option.label}${ANSI.reset}\n`);
|
|
397
|
+
writeWrapped(option.detail, ' ', ANSI.dim);
|
|
398
|
+
}
|
|
399
|
+
process.stdout.write(`\n${ANSI.dim}Esc/Q cancels. Number keys 1-${options.length} select directly.${ANSI.reset}\n`);
|
|
400
|
+
};
|
|
401
|
+
const cancel = () => {
|
|
402
|
+
cleanup();
|
|
403
|
+
process.stdout.write('\n');
|
|
404
|
+
reject(new WizardAbortError('Setup cancelled.'));
|
|
405
|
+
};
|
|
406
|
+
const finish = () => {
|
|
407
|
+
cleanup();
|
|
408
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
409
|
+
resolve(options[cursorIndex]?.value || defaultValue);
|
|
410
|
+
};
|
|
411
|
+
const onKeypress = (_text, key) => {
|
|
412
|
+
if (key?.ctrl && key?.name === 'c') {
|
|
413
|
+
cancel();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
if (key?.name === 'escape' || key?.name === 'q') {
|
|
417
|
+
cancel();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (key?.name === 'up' || key?.name === 'k') {
|
|
421
|
+
cursorIndex = (cursorIndex - 1 + options.length) % options.length;
|
|
422
|
+
}
|
|
423
|
+
else if (key?.name === 'down' || key?.name === 'j') {
|
|
424
|
+
cursorIndex = (cursorIndex + 1) % options.length;
|
|
425
|
+
}
|
|
426
|
+
else if (key?.name === 'return' || key?.name === 'enter') {
|
|
427
|
+
finish();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
else if (/^[1-9]$/.test(String(_text || ''))) {
|
|
431
|
+
const selectedIndex = Number(_text) - 1;
|
|
432
|
+
if (options[selectedIndex]) {
|
|
433
|
+
cursorIndex = selectedIndex;
|
|
434
|
+
finish();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
render();
|
|
439
|
+
};
|
|
440
|
+
process.stdin.on('keypress', onKeypress);
|
|
441
|
+
process.stdout.write(ANSI.hideCursor);
|
|
442
|
+
render();
|
|
443
|
+
});
|
|
444
|
+
}
|
|
345
445
|
function normalizeConnectorProgressKey(key) {
|
|
346
446
|
const normalized = String(key || '').trim().toLowerCase();
|
|
347
447
|
if (normalized === 'analytics' || normalized === 'analyticscli')
|
|
@@ -439,9 +539,6 @@ function connectorStatusLabel(key, healthByConnector = {}) {
|
|
|
439
539
|
return 'not configured';
|
|
440
540
|
return `configured, ${connectorHealthLabel(health.status)}`;
|
|
441
541
|
}
|
|
442
|
-
function formatConnectorHealthLine(key, healthByConnector = {}) {
|
|
443
|
-
return `${ANSI.dim}${formatConnectorHealthText(key, healthByConnector)}${ANSI.reset}`;
|
|
444
|
-
}
|
|
445
542
|
function formatConnectorHealthText(key, healthByConnector = {}) {
|
|
446
543
|
const health = getConnectorHealth(key, healthByConnector);
|
|
447
544
|
const label = connectorStatusLabel(key, healthByConnector);
|
|
@@ -596,7 +693,7 @@ async function askConnectorSelectionByKeys(healthByConnector = {}, initialSelect
|
|
|
596
693
|
const cancel = () => {
|
|
597
694
|
cleanup();
|
|
598
695
|
process.stdout.write('\n');
|
|
599
|
-
reject(new
|
|
696
|
+
reject(new WizardAbortError('Connector setup cancelled.'));
|
|
600
697
|
};
|
|
601
698
|
const toggleCurrent = () => {
|
|
602
699
|
const connector = selectedDisplayConnector();
|
|
@@ -1048,11 +1145,6 @@ function printSetupSuccess(payload) {
|
|
|
1048
1145
|
process.stdout.write(`${payload.message}\n`);
|
|
1049
1146
|
}
|
|
1050
1147
|
}
|
|
1051
|
-
function healthCheckFailures(payload) {
|
|
1052
|
-
return Array.isArray(payload?.checks)
|
|
1053
|
-
? payload.checks.filter((check) => check?.status === 'fail')
|
|
1054
|
-
: [];
|
|
1055
|
-
}
|
|
1056
1148
|
function connectorFromCheckName(name) {
|
|
1057
1149
|
const value = String(name || '');
|
|
1058
1150
|
if (value.includes('analytics') || value.includes('ANALYTICSCLI'))
|
|
@@ -1111,146 +1203,12 @@ function cleanHealthDetail(detail) {
|
|
|
1111
1203
|
}
|
|
1112
1204
|
return truncate(raw, 180);
|
|
1113
1205
|
}
|
|
1114
|
-
function actionForHealthFailure(failure, configPath) {
|
|
1115
|
-
const name = String(failure?.name || '');
|
|
1116
|
-
const detail = String(failure?.detail || '');
|
|
1117
|
-
if (name === 'project:github-repo' || /project\.githubRepo/i.test(detail)) {
|
|
1118
|
-
return `No action required for Sentry setup. Set project.githubRepo in ${configPath} only if you want GitHub issue/PR delivery now.`;
|
|
1119
|
-
}
|
|
1120
|
-
if (name.includes('analytics') || /ANALYTICSCLI|analytics/i.test(detail)) {
|
|
1121
|
-
return 'Paste a fresh AnalyticsCLI readonly token, then let the wizard retest AnalyticsCLI.';
|
|
1122
|
-
}
|
|
1123
|
-
if (name.includes('sentry') || /Sentry|GlitchTip/i.test(detail)) {
|
|
1124
|
-
return 'Only fix this if token, org, or base URL is missing or invalid.';
|
|
1125
|
-
}
|
|
1126
|
-
if (name.includes('github')) {
|
|
1127
|
-
return 'Configure GitHub token/repo access, or leave GitHub delivery disabled.';
|
|
1128
|
-
}
|
|
1129
|
-
if (name.includes('revenuecat')) {
|
|
1130
|
-
return 'Paste a RevenueCat v2 secret API key with read-only project permissions.';
|
|
1131
|
-
}
|
|
1132
|
-
if (name.includes('asc')) {
|
|
1133
|
-
return 'Paste ASC API key details or rerun ASC setup when ready.';
|
|
1134
|
-
}
|
|
1135
|
-
return 'Use the connector setup flow below to refresh this configuration.';
|
|
1136
|
-
}
|
|
1137
1206
|
function isDeferredGitHubFailure(failure) {
|
|
1138
1207
|
const name = String(failure?.name || '');
|
|
1139
1208
|
const detail = String(failure?.detail || '');
|
|
1140
1209
|
return (name === 'project:github-repo' ||
|
|
1141
1210
|
(name === 'connection:github' && /project\.githubRepo|repo is missing|repo is not configured/i.test(detail)));
|
|
1142
1211
|
}
|
|
1143
|
-
function isDeferredSentryProjectFailure(failure) {
|
|
1144
|
-
const name = String(failure?.name || '');
|
|
1145
|
-
const detail = String(failure?.detail || '');
|
|
1146
|
-
return name.includes('sentry') && /No Sentry projects configured/i.test(detail);
|
|
1147
|
-
}
|
|
1148
|
-
function summarizeHealthFailure(failure, configPath) {
|
|
1149
|
-
const name = String(failure?.name || '');
|
|
1150
|
-
const detail = String(failure?.detail || '');
|
|
1151
|
-
const connector = connectorFromCheckName(`${name} ${detail}`) || 'setup';
|
|
1152
|
-
if (connector === 'analytics' && /invalid token|unauthorized|token has been revoked/i.test(detail)) {
|
|
1153
|
-
return {
|
|
1154
|
-
connector,
|
|
1155
|
-
status: 'token invalid or expired',
|
|
1156
|
-
action: 'paste a fresh readonly token',
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
if (connector === 'sentry' && /No Sentry projects configured/i.test(detail)) {
|
|
1160
|
-
return {
|
|
1161
|
-
connector,
|
|
1162
|
-
status: 'project scope deferred',
|
|
1163
|
-
action: 'no user action; OpenClaw discovers visible projects from org + token',
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
if (connector === 'github' && isDeferredGitHubFailure(failure)) {
|
|
1167
|
-
return {
|
|
1168
|
-
connector,
|
|
1169
|
-
status: 'repo not known yet',
|
|
1170
|
-
action: `optional; set project.githubRepo in ${configPath} only for GitHub delivery`,
|
|
1171
|
-
};
|
|
1172
|
-
}
|
|
1173
|
-
return {
|
|
1174
|
-
connector,
|
|
1175
|
-
status: cleanHealthDetail(detail),
|
|
1176
|
-
action: actionForHealthFailure(failure, configPath),
|
|
1177
|
-
};
|
|
1178
|
-
}
|
|
1179
|
-
function printHealthFailures(failures, configPath) {
|
|
1180
|
-
const summarized = [];
|
|
1181
|
-
const seen = new Set();
|
|
1182
|
-
for (const failure of failures) {
|
|
1183
|
-
if (isDeferredGitHubFailure(failure))
|
|
1184
|
-
continue;
|
|
1185
|
-
if (isDeferredSentryProjectFailure(failure))
|
|
1186
|
-
continue;
|
|
1187
|
-
const summary = summarizeHealthFailure(failure, configPath);
|
|
1188
|
-
const key = `${summary.connector}:${summary.status}:${summary.action}`;
|
|
1189
|
-
if (seen.has(key))
|
|
1190
|
-
continue;
|
|
1191
|
-
seen.add(key);
|
|
1192
|
-
summarized.push(summary);
|
|
1193
|
-
}
|
|
1194
|
-
if (summarized.length === 0) {
|
|
1195
|
-
process.stdout.write('\nOnly deferred optional checks remain.\n\n');
|
|
1196
|
-
return;
|
|
1197
|
-
}
|
|
1198
|
-
process.stdout.write('\nNeeds attention\n');
|
|
1199
|
-
process.stdout.write('---------------\n');
|
|
1200
|
-
for (const summary of summarized) {
|
|
1201
|
-
process.stdout.write(`- ${connectorTitle(summary.connector)}: ${summary.status}\n`);
|
|
1202
|
-
process.stdout.write(` Next: ${summary.action}\n`);
|
|
1203
|
-
}
|
|
1204
|
-
process.stdout.write('\n');
|
|
1205
|
-
}
|
|
1206
|
-
function inferConnectorsFromHealthFailures(failures) {
|
|
1207
|
-
const inferred = new Set();
|
|
1208
|
-
for (const failure of failures) {
|
|
1209
|
-
if (isDeferredGitHubFailure(failure))
|
|
1210
|
-
continue;
|
|
1211
|
-
if (isDeferredSentryProjectFailure(failure))
|
|
1212
|
-
continue;
|
|
1213
|
-
const connector = connectorFromCheckName(`${failure?.name || ''} ${failure?.detail || ''}`);
|
|
1214
|
-
if (connector)
|
|
1215
|
-
inferred.add(connector);
|
|
1216
|
-
}
|
|
1217
|
-
return orderConnectors([...inferred]);
|
|
1218
|
-
}
|
|
1219
|
-
async function getHealthCheckPlan(configPath, selected) {
|
|
1220
|
-
const config = await readJsonIfPresent(configPath).catch(() => null);
|
|
1221
|
-
const items = [
|
|
1222
|
-
{
|
|
1223
|
-
key: 'preflight',
|
|
1224
|
-
label: 'Local preflight',
|
|
1225
|
-
detail: 'config, dependencies, source wiring',
|
|
1226
|
-
status: 'pending',
|
|
1227
|
-
},
|
|
1228
|
-
];
|
|
1229
|
-
const selectedSet = new Set(selected);
|
|
1230
|
-
const hasAnalytics = selectedSet.has('analytics') ||
|
|
1231
|
-
Boolean(process.env.ANALYTICSCLI_ACCESS_TOKEN?.trim() || process.env.ANALYTICSCLI_READONLY_TOKEN?.trim()) ||
|
|
1232
|
-
(config?.sources?.analytics && config.sources.analytics.enabled !== false);
|
|
1233
|
-
const sentryAccounts = Array.isArray(config?.sources?.sentry?.accounts) ? config.sources.sentry.accounts : [];
|
|
1234
|
-
const hasSentry = selectedSet.has('sentry') ||
|
|
1235
|
-
sentryAccounts.length > 0 ||
|
|
1236
|
-
Boolean(process.env.SENTRY_AUTH_TOKEN?.trim() || process.env.GLITCHTIP_AUTH_TOKEN?.trim());
|
|
1237
|
-
const hasRevenueCat = selectedSet.has('revenuecat') ||
|
|
1238
|
-
Boolean(process.env.REVENUECAT_API_KEY?.trim()) ||
|
|
1239
|
-
(config?.sources?.revenuecat && config.sources.revenuecat.enabled !== false);
|
|
1240
|
-
const githubRepo = String(config?.project?.githubRepo || '').trim();
|
|
1241
|
-
const hasGitHub = selectedSet.has('github') || Boolean(process.env.GITHUB_TOKEN?.trim()) || Boolean(githubRepo);
|
|
1242
|
-
if (hasAnalytics)
|
|
1243
|
-
items.push({ key: 'analytics', label: 'AnalyticsCLI', detail: 'token auth + readonly query', status: 'pending' });
|
|
1244
|
-
if (hasSentry)
|
|
1245
|
-
items.push({ key: 'sentry', label: 'Sentry / GlitchTip', detail: 'token/org API + project discovery', status: 'pending' });
|
|
1246
|
-
if (hasRevenueCat)
|
|
1247
|
-
items.push({ key: 'revenuecat', label: 'RevenueCat', detail: 'API key auth + project read', status: 'pending' });
|
|
1248
|
-
if (hasGitHub && githubRepo)
|
|
1249
|
-
items.push({ key: 'github', label: 'GitHub', detail: `repo access (${githubRepo})`, status: 'pending' });
|
|
1250
|
-
if (hasGitHub && !githubRepo)
|
|
1251
|
-
items.push({ key: 'github', label: 'GitHub', detail: 'skipped until repo is known', status: 'pending' });
|
|
1252
|
-
return items;
|
|
1253
|
-
}
|
|
1254
1212
|
function healthStatusLabel(status) {
|
|
1255
1213
|
if (status === 'running')
|
|
1256
1214
|
return 'running';
|
|
@@ -1299,9 +1257,6 @@ function updateHealthProgress(items, event) {
|
|
|
1299
1257
|
}
|
|
1300
1258
|
return false;
|
|
1301
1259
|
}
|
|
1302
|
-
function allProgressItemsFinished(items) {
|
|
1303
|
-
return items.length > 0 && items.every((item) => !['pending', 'running'].includes(String(item.status || '')));
|
|
1304
|
-
}
|
|
1305
1260
|
function buildSetupTestProgressPlan(selected) {
|
|
1306
1261
|
const selectedSet = new Set(selected);
|
|
1307
1262
|
const items = [
|
|
@@ -1430,39 +1385,6 @@ async function runImmediateConnectorHealthCheck({ rl, configPath, connector, sec
|
|
|
1430
1385
|
process.stdout.write(`\n${connectorLabel(connector)} immediate health check passed or is only waiting on optional/deferred context.\n`);
|
|
1431
1386
|
return { ok: true, retry: false, result, payload };
|
|
1432
1387
|
}
|
|
1433
|
-
async function offerConfiguredConnectionFixes(rl, configPath, selected) {
|
|
1434
|
-
if (!(await fileExists(configPath)))
|
|
1435
|
-
return selected;
|
|
1436
|
-
clearTerminal();
|
|
1437
|
-
const plan = await getHealthCheckPlan(configPath, selected);
|
|
1438
|
-
renderHealthProgress(plan, 'Starting live checks...');
|
|
1439
|
-
const command = `node scripts/openclaw-growth-preflight.mjs --config ${quote(configPath)} --test-connections --progress-json`;
|
|
1440
|
-
const result = await runCommandCaptureWithProgress(command, (event) => {
|
|
1441
|
-
if (updateHealthProgress(plan, event)) {
|
|
1442
|
-
renderHealthProgress(plan);
|
|
1443
|
-
}
|
|
1444
|
-
});
|
|
1445
|
-
renderHealthProgress(plan, 'Checks finished.');
|
|
1446
|
-
const payload = parseJsonFromStdout(result.stdout);
|
|
1447
|
-
const failures = healthCheckFailures(payload).filter((failure) => !isDeferredGitHubFailure(failure) && !isDeferredSentryProjectFailure(failure));
|
|
1448
|
-
if (payload?.ok === true || failures.length === 0) {
|
|
1449
|
-
process.stdout.write('Configured connectors look healthy.\n\n');
|
|
1450
|
-
return selected;
|
|
1451
|
-
}
|
|
1452
|
-
printHealthFailures(failures, configPath);
|
|
1453
|
-
const inferred = inferConnectorsFromHealthFailures(failures);
|
|
1454
|
-
if (inferred.length === 0) {
|
|
1455
|
-
process.stdout.write('Continuing with the connector(s) you selected.\n\n');
|
|
1456
|
-
return selected;
|
|
1457
|
-
}
|
|
1458
|
-
const fixNow = await askYesNo(rl, `Fix now (${inferred.join(', ')})?`, true);
|
|
1459
|
-
clearTerminal();
|
|
1460
|
-
if (!fixNow) {
|
|
1461
|
-
process.stdout.write('Continuing with selected connector(s).\n\n');
|
|
1462
|
-
return selected;
|
|
1463
|
-
}
|
|
1464
|
-
return orderConnectors([...new Set([...selected, ...inferred])]);
|
|
1465
|
-
}
|
|
1466
1388
|
function getUserLocalBinDir() {
|
|
1467
1389
|
return process.env.HOME ? path.join(process.env.HOME, '.local', 'bin') : null;
|
|
1468
1390
|
}
|
|
@@ -1904,7 +1826,7 @@ function apiListItems(payload) {
|
|
|
1904
1826
|
return payload.teams;
|
|
1905
1827
|
return [];
|
|
1906
1828
|
}
|
|
1907
|
-
async function fetchSentryJsonPage({
|
|
1829
|
+
async function fetchSentryJsonPage({ token, url }) {
|
|
1908
1830
|
const normalizedToken = String(token || '').trim();
|
|
1909
1831
|
const response = await fetch(url, {
|
|
1910
1832
|
method: 'GET',
|
|
@@ -1938,7 +1860,7 @@ async function fetchSentryJsonList({ baseUrl, token, url }) {
|
|
|
1938
1860
|
const pages = [];
|
|
1939
1861
|
let nextUrl = url;
|
|
1940
1862
|
for (let page = 0; nextUrl && page < 10; page += 1) {
|
|
1941
|
-
const result = await fetchSentryJsonPage({
|
|
1863
|
+
const result = await fetchSentryJsonPage({ token, url: nextUrl });
|
|
1942
1864
|
pages.push(result.detail);
|
|
1943
1865
|
if (!result.ok)
|
|
1944
1866
|
return { ...result, payload: items, detail: pages.join('; ') };
|
|
@@ -2693,7 +2615,7 @@ async function runConnectorSetupWizard(args) {
|
|
|
2693
2615
|
const chosenConnectors = requestedConnectors.length > 0
|
|
2694
2616
|
? orderConnectors([...new Set([...requestedConnectors, ...existingFixes])])
|
|
2695
2617
|
: await askConnectorSelectionWithHealth(rl, healthByConnector, existingFixes);
|
|
2696
|
-
|
|
2618
|
+
const selected = withMissingRequiredAnalyticsConnector(chosenConnectors);
|
|
2697
2619
|
if (selected.length === 0) {
|
|
2698
2620
|
throw new Error('No supported connectors selected. Use analytics, github, revenuecat, sentry, asc, or all.');
|
|
2699
2621
|
}
|
|
@@ -2964,19 +2886,38 @@ async function askCadencePlan(rl) {
|
|
|
2964
2886
|
return cadences;
|
|
2965
2887
|
}
|
|
2966
2888
|
async function askWizardGoal(rl) {
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2889
|
+
return await askMenuChoice(rl, {
|
|
2890
|
+
title: 'What do you want to configure?',
|
|
2891
|
+
subtitle: 'Use Up/Down to move, Enter to continue, or press 1-4.',
|
|
2892
|
+
defaultValue: 'full',
|
|
2893
|
+
renderHeader: printWizardHeader,
|
|
2894
|
+
options: [
|
|
2895
|
+
{
|
|
2896
|
+
value: 'connectors',
|
|
2897
|
+
label: 'Connectors',
|
|
2898
|
+
detail: 'Credentials, provider setup, and health checks.',
|
|
2899
|
+
},
|
|
2900
|
+
{
|
|
2901
|
+
value: 'outputs_intervals',
|
|
2902
|
+
label: 'Outputs and intervals',
|
|
2903
|
+
detail: 'Daily/weekly/monthly jobs, GitHub issue/PR delivery, and OpenClaw chat notifications.',
|
|
2904
|
+
},
|
|
2905
|
+
{
|
|
2906
|
+
value: 'full',
|
|
2907
|
+
label: 'Full setup',
|
|
2908
|
+
detail: 'Project, connectors, outputs, intervals, and sources.',
|
|
2909
|
+
},
|
|
2910
|
+
{
|
|
2911
|
+
value: 'intervals',
|
|
2912
|
+
label: 'Advanced intervals only',
|
|
2913
|
+
detail: 'Runner wake-up interval and connector health check cadence.',
|
|
2914
|
+
},
|
|
2915
|
+
],
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
function printWizardHeader() {
|
|
2919
|
+
process.stdout.write('OpenClaw Growth Engineer - Setup Wizard\n');
|
|
2920
|
+
process.stdout.write('This wizard writes non-secret config only. Connector secrets stay in the local secrets file.\n\n');
|
|
2980
2921
|
}
|
|
2981
2922
|
async function buildDefaultWizardConfig() {
|
|
2982
2923
|
const detectedRepo = await detectGitHubRepo();
|
|
@@ -3003,7 +2944,7 @@ async function buildDefaultWizardConfig() {
|
|
|
3003
2944
|
command: getDefaultSourceCommand('revenuecat'),
|
|
3004
2945
|
},
|
|
3005
2946
|
sentry: {
|
|
3006
|
-
enabled:
|
|
2947
|
+
enabled: true,
|
|
3007
2948
|
mode: 'command',
|
|
3008
2949
|
command: getDefaultSourceCommand('sentry'),
|
|
3009
2950
|
},
|
|
@@ -3019,8 +2960,8 @@ async function buildDefaultWizardConfig() {
|
|
|
3019
2960
|
],
|
|
3020
2961
|
},
|
|
3021
2962
|
schedule: {
|
|
3022
|
-
intervalMinutes:
|
|
3023
|
-
connectorHealthCheckIntervalMinutes:
|
|
2963
|
+
intervalMinutes: DEFAULT_GROWTH_INTERVAL_MINUTES,
|
|
2964
|
+
connectorHealthCheckIntervalMinutes: DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES,
|
|
3024
2965
|
skipIfNoDataChange: true,
|
|
3025
2966
|
skipIfIssueSetUnchanged: true,
|
|
3026
2967
|
cadences: DEFAULT_CADENCE_PLAN.map((cadence) => ({ ...cadence })),
|
|
@@ -3028,6 +2969,8 @@ async function buildDefaultWizardConfig() {
|
|
|
3028
2969
|
actions: {
|
|
3029
2970
|
autoCreateIssues: false,
|
|
3030
2971
|
autoCreatePullRequests: false,
|
|
2972
|
+
autoCreateWhenGitHubWriteAccess: true,
|
|
2973
|
+
disableAutoCreateGitHubArtifacts: false,
|
|
3031
2974
|
mode: 'issue',
|
|
3032
2975
|
usageMode: 'production_autopilot',
|
|
3033
2976
|
draftPullRequests: true,
|
|
@@ -3149,9 +3092,9 @@ async function askNotificationChannels(rl, config) {
|
|
|
3149
3092
|
}
|
|
3150
3093
|
async function askOutputConfig(rl, config) {
|
|
3151
3094
|
process.stdout.write('\nOutput type\n');
|
|
3152
|
-
process.stdout.write(' 1)
|
|
3153
|
-
process.stdout.write(' 2) GitHub
|
|
3154
|
-
process.stdout.write(' 3) GitHub pull
|
|
3095
|
+
process.stdout.write(' 1) OpenClaw chat plus automatic GitHub issue fallback when repo + token allow it\n');
|
|
3096
|
+
process.stdout.write(' 2) GitHub issues: create issues automatically when new findings are found\n');
|
|
3097
|
+
process.stdout.write(' 3) GitHub pull requests: create draft PR-oriented proposal branches when enabled\n');
|
|
3155
3098
|
const currentMode = config?.actions?.mode || config?.deliveries?.github?.mode || 'issue';
|
|
3156
3099
|
const currentAutoCreate = Boolean(config?.actions?.autoCreateIssues || config?.actions?.autoCreatePullRequests || config?.deliveries?.github?.autoCreate);
|
|
3157
3100
|
const defaultChoice = currentAutoCreate ? (currentMode === 'pull_request' ? '3' : '2') : '1';
|
|
@@ -3186,6 +3129,8 @@ async function askOutputConfig(rl, config) {
|
|
|
3186
3129
|
mode,
|
|
3187
3130
|
autoCreateIssues: mode === 'issue' && autoCreate,
|
|
3188
3131
|
autoCreatePullRequests: mode === 'pull_request' && autoCreate,
|
|
3132
|
+
autoCreateWhenGitHubWriteAccess: config.actions?.autoCreateWhenGitHubWriteAccess !== false,
|
|
3133
|
+
disableAutoCreateGitHubArtifacts: config.actions?.disableAutoCreateGitHubArtifacts === true,
|
|
3189
3134
|
draftPullRequests: true,
|
|
3190
3135
|
proposalBranchPrefix: config?.actions?.proposalBranchPrefix || 'openclaw/proposals',
|
|
3191
3136
|
};
|
|
@@ -3240,8 +3185,8 @@ async function askOutputConfig(rl, config) {
|
|
|
3240
3185
|
}
|
|
3241
3186
|
async function askIntervalConfig(rl, config) {
|
|
3242
3187
|
const currentSchedule = config?.schedule || {};
|
|
3243
|
-
const intervalMinutes = Number.parseInt(await ask(rl, 'Growth runner wake-up interval in minutes', String(currentSchedule.intervalMinutes ||
|
|
3244
|
-
const connectorHealthCheckIntervalMinutes = Number.parseInt(await ask(rl, 'Connector health check interval in minutes', String(currentSchedule.connectorHealthCheckIntervalMinutes ||
|
|
3188
|
+
const intervalMinutes = Number.parseInt(await ask(rl, 'Growth runner wake-up interval in minutes', String(currentSchedule.intervalMinutes || DEFAULT_GROWTH_INTERVAL_MINUTES)), 10) || DEFAULT_GROWTH_INTERVAL_MINUTES;
|
|
3189
|
+
const connectorHealthCheckIntervalMinutes = Number.parseInt(await ask(rl, 'Connector health check interval in minutes', String(currentSchedule.connectorHealthCheckIntervalMinutes || DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES)), 10) || DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES;
|
|
3245
3190
|
const usageMode = await askToolUsage(rl);
|
|
3246
3191
|
const cadences = await askCadencePlan(rl);
|
|
3247
3192
|
config.schedule = {
|
|
@@ -3258,11 +3203,15 @@ async function askIntervalConfig(rl, config) {
|
|
|
3258
3203
|
};
|
|
3259
3204
|
return config;
|
|
3260
3205
|
}
|
|
3206
|
+
async function askOutputsAndIntervalsConfig(rl, config) {
|
|
3207
|
+
const withIntervals = await askIntervalConfig(rl, config);
|
|
3208
|
+
return await askOutputConfig(rl, withIntervals);
|
|
3209
|
+
}
|
|
3261
3210
|
async function writeOpenClawJobManifest(configPath, config) {
|
|
3262
3211
|
const manifestPath = path.resolve('.openclaw/jobs/openclaw-growth-engineer.json');
|
|
3263
3212
|
const displayConfigPath = path.relative(process.cwd(), configPath) || configPath;
|
|
3264
|
-
const intervalMinutes = Math.max(1, Number(config?.schedule?.intervalMinutes ||
|
|
3265
|
-
const connectorHealthCheckIntervalMinutes = Math.max(1, Number(config?.schedule?.connectorHealthCheckIntervalMinutes ||
|
|
3213
|
+
const intervalMinutes = Math.max(1, Number(config?.schedule?.intervalMinutes || DEFAULT_GROWTH_INTERVAL_MINUTES));
|
|
3214
|
+
const connectorHealthCheckIntervalMinutes = Math.max(1, Number(config?.schedule?.connectorHealthCheckIntervalMinutes || DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES));
|
|
3266
3215
|
const actionMode = config?.actions?.mode || config?.deliveries?.github?.mode || 'issue';
|
|
3267
3216
|
const growthRunCommand = getGrowthRunCommand(config, displayConfigPath);
|
|
3268
3217
|
const connectorHealthCommand = getConnectorHealthCommand(config, displayConfigPath);
|
|
@@ -3317,8 +3266,7 @@ async function main() {
|
|
|
3317
3266
|
}
|
|
3318
3267
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3319
3268
|
try {
|
|
3320
|
-
|
|
3321
|
-
process.stdout.write('This wizard writes non-secret config only. Connector secrets stay in the local secrets file.\n\n');
|
|
3269
|
+
printWizardHeader();
|
|
3322
3270
|
const goal = await askWizardGoal(rl);
|
|
3323
3271
|
if (goal === 'connectors') {
|
|
3324
3272
|
rl.close();
|
|
@@ -3336,15 +3284,15 @@ async function main() {
|
|
|
3336
3284
|
process.stdout.write('OpenClaw can run and update growth jobs plus non-secret connector config from the manifest; connector API keys stay behind the connector wizard.\n');
|
|
3337
3285
|
return;
|
|
3338
3286
|
}
|
|
3339
|
-
if (goal === '
|
|
3340
|
-
const config = await
|
|
3287
|
+
if (goal === 'outputs_intervals') {
|
|
3288
|
+
const config = await askOutputsAndIntervalsConfig(rl, await loadEditableConfig(configPath));
|
|
3341
3289
|
const secretAccess = await askSecretAccessModel(rl, configPath, config);
|
|
3342
3290
|
await writeJsonFile(configPath, config);
|
|
3343
3291
|
const manifestPath = await writeOpenClawJobManifest(configPath, config);
|
|
3344
|
-
process.stdout.write(`\nSaved output config: ${configPath}\n`);
|
|
3292
|
+
process.stdout.write(`\nSaved output and interval config: ${configPath}\n`);
|
|
3345
3293
|
process.stdout.write(`Saved OpenClaw job manifest: ${manifestPath}\n`);
|
|
3346
3294
|
printSecretRunnerKitInstructions(secretAccess.kit);
|
|
3347
|
-
process.stdout.write('
|
|
3295
|
+
process.stdout.write('Daily checks prioritize Sentry and production anomalies; larger cadences analyze all configured projects and connectors.\n');
|
|
3348
3296
|
return;
|
|
3349
3297
|
}
|
|
3350
3298
|
const detectedRepo = await detectGitHubRepo();
|
|
@@ -3355,7 +3303,9 @@ async function main() {
|
|
|
3355
3303
|
.map((value) => value.trim())
|
|
3356
3304
|
.filter(Boolean);
|
|
3357
3305
|
const maxIssues = Number.parseInt(await ask(rl, 'Max issues per run', '4'), 10) || 4;
|
|
3358
|
-
const intervalMinutes = Number.parseInt(await ask(rl, '
|
|
3306
|
+
const intervalMinutes = Number.parseInt(await ask(rl, 'Growth runner wake-up interval in minutes', String(DEFAULT_GROWTH_INTERVAL_MINUTES)), 10) ||
|
|
3307
|
+
DEFAULT_GROWTH_INTERVAL_MINUTES;
|
|
3308
|
+
const connectorHealthCheckIntervalMinutes = Number.parseInt(await ask(rl, 'Connector health check interval in minutes', String(DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES)), 10) || DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES;
|
|
3359
3309
|
const usageMode = await askToolUsage(rl);
|
|
3360
3310
|
const cadences = await askCadencePlan(rl);
|
|
3361
3311
|
const actionMode = await askChoice(rl, 'Preferred GitHub artifact mode', ['issue', 'pull_request'], 'issue');
|
|
@@ -3364,7 +3314,10 @@ async function main() {
|
|
|
3364
3314
|
defaultCommand: getDefaultSourceCommand('analytics'),
|
|
3365
3315
|
});
|
|
3366
3316
|
const revenuecat = await askSourceConfig(rl, 'revenuecat', 'data/openclaw-growth-engineer/revenuecat_summary.example.json', getDefaultSourceHint('revenuecat'));
|
|
3367
|
-
const sentry = await askSourceConfig(rl, 'sentry', 'data/openclaw-growth-engineer/sentry_summary.example.json', getDefaultSourceHint('sentry')
|
|
3317
|
+
const sentry = await askSourceConfig(rl, 'sentry', 'data/openclaw-growth-engineer/sentry_summary.example.json', getDefaultSourceHint('sentry'), {
|
|
3318
|
+
defaultEnabled: true,
|
|
3319
|
+
defaultCommand: getDefaultSourceCommand('sentry'),
|
|
3320
|
+
});
|
|
3368
3321
|
const feedback = await askSourceConfig(rl, 'feedback', 'data/openclaw-growth-engineer/feedback_summary.example.json', getDefaultSourceHint('feedback'), {
|
|
3369
3322
|
defaultEnabled: true,
|
|
3370
3323
|
defaultCommand: getDefaultSourceCommand('feedback'),
|
|
@@ -3410,7 +3363,7 @@ async function main() {
|
|
|
3410
3363
|
},
|
|
3411
3364
|
schedule: {
|
|
3412
3365
|
intervalMinutes,
|
|
3413
|
-
connectorHealthCheckIntervalMinutes
|
|
3366
|
+
connectorHealthCheckIntervalMinutes,
|
|
3414
3367
|
skipIfNoDataChange: true,
|
|
3415
3368
|
skipIfIssueSetUnchanged: true,
|
|
3416
3369
|
cadences,
|
|
@@ -3418,6 +3371,8 @@ async function main() {
|
|
|
3418
3371
|
actions: {
|
|
3419
3372
|
autoCreateIssues,
|
|
3420
3373
|
autoCreatePullRequests,
|
|
3374
|
+
autoCreateWhenGitHubWriteAccess: true,
|
|
3375
|
+
disableAutoCreateGitHubArtifacts: false,
|
|
3421
3376
|
mode: actionMode,
|
|
3422
3377
|
usageMode,
|
|
3423
3378
|
draftPullRequests: true,
|
|
@@ -3514,6 +3469,6 @@ async function main() {
|
|
|
3514
3469
|
}
|
|
3515
3470
|
main().catch((error) => {
|
|
3516
3471
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
3517
|
-
process.exitCode = 1;
|
|
3472
|
+
process.exitCode = error instanceof WizardAbortError ? error.exitCode : 1;
|
|
3518
3473
|
});
|
|
3519
3474
|
//# sourceMappingURL=openclaw-growth-wizard.mjs.map
|