@analyticscli/growth-engineer 0.1.0-preview.4 → 0.1.0-preview.6
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.
|
@@ -1011,16 +1011,16 @@ function summarizeFailureFix(connector, blockers) {
|
|
|
1011
1011
|
if (/revoked|unauthorized|UNAUTHORIZED/i.test(combined)) {
|
|
1012
1012
|
return 'Paste a fresh AnalyticsCLI readonly CLI token in the wizard, then let setup retest.';
|
|
1013
1013
|
}
|
|
1014
|
-
return 'Verify the AnalyticsCLI token can list projects. Per-project query failures are reported as warnings and should not block connector setup.';
|
|
1014
|
+
return 'Verify the AnalyticsCLI token can list accessible projects. Per-project query failures are reported as warnings and should not block connector setup.';
|
|
1015
1015
|
}
|
|
1016
1016
|
if (connector === 'sentry') {
|
|
1017
1017
|
if (/404|Not Found/i.test(combined)) {
|
|
1018
|
-
return 'Rerun Sentry/GlitchTip setup and use the correct base URL +
|
|
1018
|
+
return 'Rerun Sentry/GlitchTip setup and use the correct base URL + visible org. Project scope stays unpinned and is resolved from app context later.';
|
|
1019
1019
|
}
|
|
1020
|
-
return 'Verify the Sentry/GlitchTip token, base URL,
|
|
1020
|
+
return 'Verify the Sentry/GlitchTip token, base URL, and org, then rerun setup.';
|
|
1021
1021
|
}
|
|
1022
1022
|
if (connector === 'github') {
|
|
1023
|
-
return '
|
|
1023
|
+
return 'Verify the GitHub token. Repo scope is inferred from OPENCLAW_GITHUB_REPO, the local git remote, or runtime context.';
|
|
1024
1024
|
}
|
|
1025
1025
|
if (connector === 'revenuecat') {
|
|
1026
1026
|
return 'Paste a RevenueCat v2 secret API key with read-only project permissions, then rerun setup.';
|
|
@@ -1796,12 +1796,6 @@ function printSentryTokenGuidance({ baseUrl, tokenEnv }) {
|
|
|
1796
1796
|
'Optional for richer release context: `project:releases`.',
|
|
1797
1797
|
]);
|
|
1798
1798
|
}
|
|
1799
|
-
function parseCommaList(value) {
|
|
1800
|
-
return String(value || '')
|
|
1801
|
-
.split(',')
|
|
1802
|
-
.map((entry) => entry.trim())
|
|
1803
|
-
.filter(Boolean);
|
|
1804
|
-
}
|
|
1805
1799
|
function buildUrl(baseUrl, pathname, params = {}) {
|
|
1806
1800
|
const url = new URL(pathname, `${String(baseUrl || 'https://sentry.io').replace(/\/$/, '')}/`);
|
|
1807
1801
|
for (const [key, value] of Object.entries(params)) {
|
|
@@ -2404,13 +2398,13 @@ async function guideSentryConnector(rl, secrets) {
|
|
|
2404
2398
|
org = await ask(rl, `Sentry org slug for ${label} (leave empty to defer)`, index === 0 ? process.env.SENTRY_ORG || '' : '');
|
|
2405
2399
|
}
|
|
2406
2400
|
const environment = await ask(rl, `Sentry environment for ${label}`, index === 0 ? process.env.SENTRY_ENVIRONMENT || 'production' : 'production');
|
|
2407
|
-
let projects = [];
|
|
2408
2401
|
if (org.trim() && token) {
|
|
2409
|
-
process.stdout.write(`
|
|
2402
|
+
process.stdout.write(`Checking visible Sentry projects for ${label} without pinning project scope...\n`);
|
|
2410
2403
|
const discovery = await discoverSentryProjects({ baseUrl, token, org });
|
|
2404
|
+
let verifiedVisibleProjects = false;
|
|
2411
2405
|
if (discovery.ok && discovery.projects.length > 0) {
|
|
2412
|
-
|
|
2413
|
-
process.stdout.write(`
|
|
2406
|
+
verifiedVisibleProjects = true;
|
|
2407
|
+
process.stdout.write(`Found ${discovery.projects.length} visible project(s). Project scope remains unpinned so OpenClaw/Hermes can decide per run.\n`);
|
|
2414
2408
|
}
|
|
2415
2409
|
else {
|
|
2416
2410
|
const fallbackOrgs = discoveredOrganizations
|
|
@@ -2420,15 +2414,14 @@ async function guideSentryConnector(rl, secrets) {
|
|
|
2420
2414
|
process.stdout.write(`Trying visible org ${fallbackOrg}...\n`);
|
|
2421
2415
|
const fallbackDiscovery = await discoverSentryProjects({ baseUrl, token, org: fallbackOrg });
|
|
2422
2416
|
if (fallbackDiscovery.ok && fallbackDiscovery.projects.length > 0) {
|
|
2423
|
-
|
|
2424
|
-
|
|
2417
|
+
org = fallbackOrg;
|
|
2418
|
+
verifiedVisibleProjects = true;
|
|
2419
|
+
process.stdout.write(`Using org ${fallbackOrg}; found ${fallbackDiscovery.projects.length} visible project(s). Project scope remains unpinned.\n`);
|
|
2425
2420
|
break;
|
|
2426
2421
|
}
|
|
2427
2422
|
}
|
|
2428
|
-
if (
|
|
2429
|
-
process.stdout.write(`Could not
|
|
2430
|
-
const manualProjects = parseCommaList(await ask(rl, `Project slugs for ${label} (comma-separated, leave empty to let app context decide)`, ''));
|
|
2431
|
-
projects = manualProjects;
|
|
2423
|
+
if (!verifiedVisibleProjects && !discovery.ok) {
|
|
2424
|
+
process.stdout.write(`Could not verify visible projects automatically (${discovery.detail}). Project scope will be resolved from app context later.\n`);
|
|
2432
2425
|
}
|
|
2433
2426
|
}
|
|
2434
2427
|
}
|
|
@@ -2441,7 +2434,6 @@ async function guideSentryConnector(rl, secrets) {
|
|
|
2441
2434
|
baseUrl,
|
|
2442
2435
|
tokenEnv,
|
|
2443
2436
|
...(org.trim() ? { org: org.trim() } : {}),
|
|
2444
|
-
...(projects.length > 0 ? { projects } : {}),
|
|
2445
2437
|
...(environment.trim() ? { environment: environment.trim() } : {}),
|
|
2446
2438
|
});
|
|
2447
2439
|
if (index === 0) {
|
|
@@ -2792,20 +2784,6 @@ async function askYesNo(rl, label, defaultYes = true) {
|
|
|
2792
2784
|
}
|
|
2793
2785
|
}
|
|
2794
2786
|
}
|
|
2795
|
-
async function askChoice(rl, label, options, defaultValue) {
|
|
2796
|
-
const normalizedDefault = options.includes(defaultValue) ? defaultValue : options[0];
|
|
2797
|
-
while (true) {
|
|
2798
|
-
const answer = (await rl.question(`${label} (${options.join('/')}) [${normalizedDefault}]: `))
|
|
2799
|
-
.trim()
|
|
2800
|
-
.toLowerCase();
|
|
2801
|
-
if (!answer) {
|
|
2802
|
-
return normalizedDefault;
|
|
2803
|
-
}
|
|
2804
|
-
if (options.includes(answer)) {
|
|
2805
|
-
return answer;
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
2787
|
async function askSourceConfig(rl, sourceName, defaultPath, hint, options = {}) {
|
|
2810
2788
|
const forceEnabled = Boolean(options.forceEnabled);
|
|
2811
2789
|
const defaultCommand = String(options.defaultCommand || getDefaultSourceCommand(sourceName) || '').trim();
|
|
@@ -2920,12 +2898,11 @@ function printWizardHeader() {
|
|
|
2920
2898
|
process.stdout.write('This wizard can configure connector secrets. Normal config is written to config JSON; API keys stay in the local chmod 600 secrets file.\n\n');
|
|
2921
2899
|
}
|
|
2922
2900
|
async function buildDefaultWizardConfig() {
|
|
2923
|
-
const detectedRepo = await detectGitHubRepo();
|
|
2924
2901
|
return {
|
|
2925
2902
|
version: 7,
|
|
2926
2903
|
generatedAt: new Date().toISOString(),
|
|
2927
2904
|
project: {
|
|
2928
|
-
githubRepo:
|
|
2905
|
+
githubRepo: '',
|
|
2929
2906
|
repoRoot: '.',
|
|
2930
2907
|
outFile: 'data/openclaw-growth-engineer/issues.generated.json',
|
|
2931
2908
|
maxIssues: 4,
|
|
@@ -3044,6 +3021,35 @@ async function buildDefaultWizardConfig() {
|
|
|
3044
3021
|
},
|
|
3045
3022
|
};
|
|
3046
3023
|
}
|
|
3024
|
+
function buildRecommendedSourceConfig() {
|
|
3025
|
+
return {
|
|
3026
|
+
analytics: {
|
|
3027
|
+
enabled: true,
|
|
3028
|
+
mode: 'command',
|
|
3029
|
+
command: getDefaultSourceCommand('analytics'),
|
|
3030
|
+
},
|
|
3031
|
+
revenuecat: {
|
|
3032
|
+
enabled: false,
|
|
3033
|
+
mode: 'command',
|
|
3034
|
+
command: getDefaultSourceCommand('revenuecat'),
|
|
3035
|
+
},
|
|
3036
|
+
sentry: {
|
|
3037
|
+
enabled: true,
|
|
3038
|
+
mode: 'command',
|
|
3039
|
+
command: getDefaultSourceCommand('sentry'),
|
|
3040
|
+
},
|
|
3041
|
+
feedback: {
|
|
3042
|
+
enabled: true,
|
|
3043
|
+
mode: 'command',
|
|
3044
|
+
command: getDefaultSourceCommand('feedback'),
|
|
3045
|
+
cursorMode: 'auto_since_last_fetch',
|
|
3046
|
+
initialLookback: '30d',
|
|
3047
|
+
},
|
|
3048
|
+
extra: [
|
|
3049
|
+
buildExtraSourceConfig('asc-cli', { enabled: false, mode: 'command', command: getDefaultSourceCommand('asc') }),
|
|
3050
|
+
],
|
|
3051
|
+
};
|
|
3052
|
+
}
|
|
3047
3053
|
async function loadEditableConfig(configPath) {
|
|
3048
3054
|
const existing = await readJsonIfPresent(configPath).catch(() => null);
|
|
3049
3055
|
if (existing && typeof existing === 'object')
|
|
@@ -3091,10 +3097,13 @@ async function askNotificationChannels(rl, config) {
|
|
|
3091
3097
|
return channels;
|
|
3092
3098
|
}
|
|
3093
3099
|
async function askOutputConfig(rl, config) {
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3100
|
+
printSection('Outputs and notifications', [
|
|
3101
|
+
'OpenClaw chat is always enabled so the agent has a readable handoff.',
|
|
3102
|
+
'GitHub issues or draft PRs are optional and only run when a token plus an inferred repo are available.',
|
|
3103
|
+
]);
|
|
3104
|
+
process.stdout.write(' 1) OpenClaw chat only, with GitHub left as runtime fallback\n');
|
|
3105
|
+
process.stdout.write(' 2) Auto-create GitHub issues for concrete findings\n');
|
|
3106
|
+
process.stdout.write(' 3) Auto-create draft PR proposals for implementation-ready fixes\n');
|
|
3098
3107
|
const currentMode = config?.actions?.mode || config?.deliveries?.github?.mode || 'issue';
|
|
3099
3108
|
const currentAutoCreate = Boolean(config?.actions?.autoCreateIssues || config?.actions?.autoCreatePullRequests || config?.deliveries?.github?.autoCreate);
|
|
3100
3109
|
const defaultChoice = currentAutoCreate ? (currentMode === 'pull_request' ? '3' : '2') : '1';
|
|
@@ -3107,12 +3116,7 @@ async function askOutputConfig(rl, config) {
|
|
|
3107
3116
|
? 'Automatically create draft pull requests when new findings are found?'
|
|
3108
3117
|
: 'Automatically create GitHub issues when new findings are found?', currentAutoCreate);
|
|
3109
3118
|
if (!summaryOnly) {
|
|
3110
|
-
|
|
3111
|
-
const currentRepo = config?.project?.githubRepo || detectedRepo || '';
|
|
3112
|
-
config.project = {
|
|
3113
|
-
...(config.project || {}),
|
|
3114
|
-
githubRepo: await ask(rl, 'GitHub repo for issue/PR delivery (owner/name)', currentRepo),
|
|
3115
|
-
};
|
|
3119
|
+
process.stdout.write('GitHub repo scope is not pinned by the wizard; OpenClaw/Hermes will infer it from OPENCLAW_GITHUB_REPO, the local git remote, or runtime context when creating issues/PRs.\n');
|
|
3116
3120
|
}
|
|
3117
3121
|
const channels = await askNotificationChannels(rl, config);
|
|
3118
3122
|
const connectorHealthChannels = channels.map((channel) => {
|
|
@@ -3183,11 +3187,61 @@ async function askOutputConfig(rl, config) {
|
|
|
3183
3187
|
};
|
|
3184
3188
|
return config;
|
|
3185
3189
|
}
|
|
3190
|
+
async function askGitHubArtifactDetails(rl, config) {
|
|
3191
|
+
const githubEnabled = Boolean(config?.actions?.autoCreateIssues ||
|
|
3192
|
+
config?.actions?.autoCreatePullRequests ||
|
|
3193
|
+
config?.deliveries?.github?.enabled ||
|
|
3194
|
+
config?.deliveries?.github?.autoCreate);
|
|
3195
|
+
config.project = {
|
|
3196
|
+
...(config.project || {}),
|
|
3197
|
+
githubRepo: '',
|
|
3198
|
+
repoRoot: config.project?.repoRoot || '.',
|
|
3199
|
+
outFile: config.project?.outFile || 'data/openclaw-growth-engineer/issues.generated.json',
|
|
3200
|
+
maxIssues: Number(config.project?.maxIssues || 4),
|
|
3201
|
+
titlePrefix: config.project?.titlePrefix || '[Growth]',
|
|
3202
|
+
labels: Array.isArray(config.project?.labels) && config.project.labels.length > 0
|
|
3203
|
+
? config.project.labels
|
|
3204
|
+
: ['ai-growth', 'autogenerated', 'product'],
|
|
3205
|
+
};
|
|
3206
|
+
if (!githubEnabled) {
|
|
3207
|
+
return config;
|
|
3208
|
+
}
|
|
3209
|
+
process.stdout.write('\nGitHub repo scope is not pinned by the wizard. OpenClaw/Hermes infers it from OPENCLAW_GITHUB_REPO, the local git remote, or runtime context.\n');
|
|
3210
|
+
const customize = await askYesNo(rl, 'Customize GitHub issue/PR limits, labels, or chart attachment settings?', false);
|
|
3211
|
+
if (!customize) {
|
|
3212
|
+
config.charting = {
|
|
3213
|
+
...(config.charting || {}),
|
|
3214
|
+
enabled: config.charting?.enabled === true,
|
|
3215
|
+
command: config.charting?.command || null,
|
|
3216
|
+
};
|
|
3217
|
+
return config;
|
|
3218
|
+
}
|
|
3219
|
+
const labelsRaw = await ask(rl, 'GitHub labels for created issues/PRs', config.project.labels.join(','));
|
|
3220
|
+
config.project.labels = labelsRaw
|
|
3221
|
+
.split(',')
|
|
3222
|
+
.map((value) => value.trim())
|
|
3223
|
+
.filter(Boolean);
|
|
3224
|
+
config.project.maxIssues = Number.parseInt(await ask(rl, 'Maximum GitHub artifacts per run', String(config.project.maxIssues || 4)), 10) || 4;
|
|
3225
|
+
config.project.titlePrefix = await ask(rl, 'GitHub artifact title prefix', config.project.titlePrefix || '[Growth]');
|
|
3226
|
+
const enableCharting = await askYesNo(rl, 'Attach generated charts to GitHub artifacts when useful?', config.charting?.enabled === true);
|
|
3227
|
+
config.charting = {
|
|
3228
|
+
...(config.charting || {}),
|
|
3229
|
+
enabled: enableCharting,
|
|
3230
|
+
command: enableCharting
|
|
3231
|
+
? await ask(rl, 'Optional chart command override', config.charting?.command || '')
|
|
3232
|
+
: null,
|
|
3233
|
+
};
|
|
3234
|
+
return config;
|
|
3235
|
+
}
|
|
3186
3236
|
async function askIntervalConfig(rl, config) {
|
|
3237
|
+
printSection('Schedule and analysis depth', [
|
|
3238
|
+
'The runner wakes up often, but larger reviews only run on their daily/weekly/monthly cadence.',
|
|
3239
|
+
'Connector health checks are separate and default to every 6 hours.',
|
|
3240
|
+
]);
|
|
3187
3241
|
const currentSchedule = config?.schedule || {};
|
|
3242
|
+
const usageMode = await askToolUsage(rl);
|
|
3188
3243
|
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
3244
|
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;
|
|
3190
|
-
const usageMode = await askToolUsage(rl);
|
|
3191
3245
|
const cadences = await askCadencePlan(rl);
|
|
3192
3246
|
config.schedule = {
|
|
3193
3247
|
...currentSchedule,
|
|
@@ -3205,7 +3259,79 @@ async function askIntervalConfig(rl, config) {
|
|
|
3205
3259
|
}
|
|
3206
3260
|
async function askOutputsAndIntervalsConfig(rl, config) {
|
|
3207
3261
|
const withIntervals = await askIntervalConfig(rl, config);
|
|
3208
|
-
|
|
3262
|
+
const withOutput = await askOutputConfig(rl, withIntervals);
|
|
3263
|
+
return await askGitHubArtifactDetails(rl, withOutput);
|
|
3264
|
+
}
|
|
3265
|
+
async function askInputSourceConfig(rl, config) {
|
|
3266
|
+
printSection('Input channels', [
|
|
3267
|
+
'These are the data streams Growth Engineer will read during scheduled runs.',
|
|
3268
|
+
'Connector credentials are configured through the connector setup; this section only chooses which inputs are enabled and how the runner fetches them.',
|
|
3269
|
+
]);
|
|
3270
|
+
process.stdout.write('Recommended defaults: AnalyticsCLI product analytics, Sentry-compatible production stability, and feedback are enabled. RevenueCat and App Store Connect are ready to enable once their connectors are configured.\n\n');
|
|
3271
|
+
const useRecommended = await askYesNo(rl, 'Use recommended input channels and default fetch commands?', true);
|
|
3272
|
+
if (useRecommended) {
|
|
3273
|
+
config.sources = {
|
|
3274
|
+
...buildRecommendedSourceConfig(),
|
|
3275
|
+
...(config.sources || {}),
|
|
3276
|
+
analytics: {
|
|
3277
|
+
...buildRecommendedSourceConfig().analytics,
|
|
3278
|
+
...(config.sources?.analytics || {}),
|
|
3279
|
+
enabled: config.sources?.analytics?.enabled !== false,
|
|
3280
|
+
},
|
|
3281
|
+
sentry: {
|
|
3282
|
+
...buildRecommendedSourceConfig().sentry,
|
|
3283
|
+
...(config.sources?.sentry || {}),
|
|
3284
|
+
enabled: config.sources?.sentry?.enabled !== false,
|
|
3285
|
+
},
|
|
3286
|
+
feedback: {
|
|
3287
|
+
...buildRecommendedSourceConfig().feedback,
|
|
3288
|
+
...(config.sources?.feedback || {}),
|
|
3289
|
+
enabled: config.sources?.feedback?.enabled !== false,
|
|
3290
|
+
},
|
|
3291
|
+
revenuecat: {
|
|
3292
|
+
...buildRecommendedSourceConfig().revenuecat,
|
|
3293
|
+
...(config.sources?.revenuecat || {}),
|
|
3294
|
+
},
|
|
3295
|
+
extra: Array.isArray(config.sources?.extra)
|
|
3296
|
+
? config.sources.extra
|
|
3297
|
+
: buildRecommendedSourceConfig().extra,
|
|
3298
|
+
};
|
|
3299
|
+
return config;
|
|
3300
|
+
}
|
|
3301
|
+
process.stdout.write('\nAdvanced input setup\n');
|
|
3302
|
+
process.stdout.write('Only change these when the default CLI exporters do not match this host.\n');
|
|
3303
|
+
const analytics = await askSourceConfig(rl, 'analytics', 'data/openclaw-growth-engineer/analytics_summary.example.json', getDefaultSourceHint('analytics'), {
|
|
3304
|
+
forceEnabled: true,
|
|
3305
|
+
defaultCommand: getDefaultSourceCommand('analytics'),
|
|
3306
|
+
});
|
|
3307
|
+
const revenuecat = await askSourceConfig(rl, 'revenuecat', 'data/openclaw-growth-engineer/revenuecat_summary.example.json', getDefaultSourceHint('revenuecat'));
|
|
3308
|
+
const sentry = await askSourceConfig(rl, 'sentry', 'data/openclaw-growth-engineer/sentry_summary.example.json', getDefaultSourceHint('sentry'), {
|
|
3309
|
+
defaultEnabled: true,
|
|
3310
|
+
defaultCommand: getDefaultSourceCommand('sentry'),
|
|
3311
|
+
});
|
|
3312
|
+
const feedback = await askSourceConfig(rl, 'feedback', 'data/openclaw-growth-engineer/feedback_summary.example.json', getDefaultSourceHint('feedback'), {
|
|
3313
|
+
defaultEnabled: true,
|
|
3314
|
+
defaultCommand: getDefaultSourceCommand('feedback'),
|
|
3315
|
+
cursorMode: 'auto_since_last_fetch',
|
|
3316
|
+
initialLookback: '30d',
|
|
3317
|
+
});
|
|
3318
|
+
const extraSourcesRaw = await ask(rl, 'Extra input connectors to define now', '');
|
|
3319
|
+
const extraSources = extraSourcesRaw
|
|
3320
|
+
.split(',')
|
|
3321
|
+
.map((value) => value.trim())
|
|
3322
|
+
.filter(Boolean)
|
|
3323
|
+
.map((service) => {
|
|
3324
|
+
const defaultCommand = getDefaultSourceCommand(service);
|
|
3325
|
+
return buildExtraSourceConfig(service, defaultCommand ? {} : { mode: 'file', path: getDefaultSourcePath(service) });
|
|
3326
|
+
});
|
|
3327
|
+
config.sources = {
|
|
3328
|
+
analytics,
|
|
3329
|
+
revenuecat,
|
|
3330
|
+
sentry,
|
|
3331
|
+
feedback,
|
|
3332
|
+
extra: extraSources,
|
|
3333
|
+
};
|
|
3334
|
+
return config;
|
|
3209
3335
|
}
|
|
3210
3336
|
async function writeOpenClawJobManifest(configPath, config) {
|
|
3211
3337
|
const manifestPath = path.resolve('.openclaw/jobs/openclaw-growth-engineer.json');
|
|
@@ -3295,156 +3421,13 @@ async function main() {
|
|
|
3295
3421
|
process.stdout.write('Daily checks prioritize Sentry and production anomalies; larger cadences analyze all configured projects and connectors.\n');
|
|
3296
3422
|
return;
|
|
3297
3423
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
const maxIssues = Number.parseInt(await ask(rl, 'Max issues per run', '4'), 10) || 4;
|
|
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;
|
|
3309
|
-
const usageMode = await askToolUsage(rl);
|
|
3310
|
-
const cadences = await askCadencePlan(rl);
|
|
3311
|
-
const actionMode = await askChoice(rl, 'Preferred GitHub artifact mode', ['issue', 'pull_request'], 'issue');
|
|
3312
|
-
const analytics = await askSourceConfig(rl, 'analytics', 'data/openclaw-growth-engineer/analytics_summary.example.json', getDefaultSourceHint('analytics'), {
|
|
3313
|
-
forceEnabled: true,
|
|
3314
|
-
defaultCommand: getDefaultSourceCommand('analytics'),
|
|
3315
|
-
});
|
|
3316
|
-
const revenuecat = await askSourceConfig(rl, 'revenuecat', 'data/openclaw-growth-engineer/revenuecat_summary.example.json', getDefaultSourceHint('revenuecat'));
|
|
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
|
-
});
|
|
3321
|
-
const feedback = await askSourceConfig(rl, 'feedback', 'data/openclaw-growth-engineer/feedback_summary.example.json', getDefaultSourceHint('feedback'), {
|
|
3322
|
-
defaultEnabled: true,
|
|
3323
|
-
defaultCommand: getDefaultSourceCommand('feedback'),
|
|
3324
|
-
cursorMode: 'auto_since_last_fetch',
|
|
3325
|
-
initialLookback: '30d',
|
|
3326
|
-
});
|
|
3327
|
-
const extraSourcesRaw = await ask(rl, 'Extra connectors (comma-separated, e.g. firebase-crashlytics,app-store-reviews,play-console)', '');
|
|
3328
|
-
const extraSources = extraSourcesRaw
|
|
3329
|
-
.split(',')
|
|
3330
|
-
.map((value) => value.trim())
|
|
3331
|
-
.filter(Boolean)
|
|
3332
|
-
.map((service) => {
|
|
3333
|
-
const defaultCommand = getDefaultSourceCommand(service);
|
|
3334
|
-
return buildExtraSourceConfig(service, defaultCommand ? {} : { mode: 'file', path: getDefaultSourcePath(service) });
|
|
3335
|
-
});
|
|
3336
|
-
const autoCreateIssues = actionMode === 'issue'
|
|
3337
|
-
? await askYesNo(rl, 'Create GitHub issues automatically when new ideas are found?', false)
|
|
3338
|
-
: false;
|
|
3339
|
-
const autoCreatePullRequests = actionMode === 'pull_request'
|
|
3340
|
-
? await askYesNo(rl, 'Create draft pull requests with implementation proposal files automatically?', false)
|
|
3341
|
-
: false;
|
|
3342
|
-
const enableCharting = await askYesNo(rl, 'Generate matplotlib charts from analytics signals and include them in generated GitHub artifacts?', false);
|
|
3343
|
-
const chartCommand = enableCharting
|
|
3344
|
-
? await ask(rl, 'Optional chart command override (leave empty for default python script)', '')
|
|
3345
|
-
: '';
|
|
3346
|
-
const config = {
|
|
3347
|
-
version: 1,
|
|
3348
|
-
generatedAt: new Date().toISOString(),
|
|
3349
|
-
project: {
|
|
3350
|
-
githubRepo,
|
|
3351
|
-
repoRoot: '.',
|
|
3352
|
-
outFile: 'data/openclaw-growth-engineer/issues.generated.json',
|
|
3353
|
-
maxIssues,
|
|
3354
|
-
titlePrefix: '[Growth]',
|
|
3355
|
-
labels,
|
|
3356
|
-
},
|
|
3357
|
-
sources: {
|
|
3358
|
-
analytics,
|
|
3359
|
-
revenuecat,
|
|
3360
|
-
sentry,
|
|
3361
|
-
feedback,
|
|
3362
|
-
extra: extraSources,
|
|
3363
|
-
},
|
|
3364
|
-
schedule: {
|
|
3365
|
-
intervalMinutes,
|
|
3366
|
-
connectorHealthCheckIntervalMinutes,
|
|
3367
|
-
skipIfNoDataChange: true,
|
|
3368
|
-
skipIfIssueSetUnchanged: true,
|
|
3369
|
-
cadences,
|
|
3370
|
-
},
|
|
3371
|
-
actions: {
|
|
3372
|
-
autoCreateIssues,
|
|
3373
|
-
autoCreatePullRequests,
|
|
3374
|
-
autoCreateWhenGitHubWriteAccess: true,
|
|
3375
|
-
disableAutoCreateGitHubArtifacts: false,
|
|
3376
|
-
mode: actionMode,
|
|
3377
|
-
usageMode,
|
|
3378
|
-
draftPullRequests: true,
|
|
3379
|
-
proposalBranchPrefix: 'openclaw/proposals',
|
|
3380
|
-
},
|
|
3381
|
-
deliveries: {
|
|
3382
|
-
openclawChat: {
|
|
3383
|
-
enabled: true,
|
|
3384
|
-
markdownPath: '.openclaw/chat/latest.md',
|
|
3385
|
-
jsonPath: '.openclaw/chat/latest.json',
|
|
3386
|
-
},
|
|
3387
|
-
github: {
|
|
3388
|
-
enabled: autoCreateIssues || autoCreatePullRequests,
|
|
3389
|
-
mode: actionMode,
|
|
3390
|
-
autoCreate: autoCreateIssues || autoCreatePullRequests,
|
|
3391
|
-
draftPullRequests: true,
|
|
3392
|
-
proposalBranchPrefix: 'openclaw/proposals',
|
|
3393
|
-
},
|
|
3394
|
-
slack: {
|
|
3395
|
-
enabled: false,
|
|
3396
|
-
webhookEnv: 'SLACK_WEBHOOK_URL',
|
|
3397
|
-
},
|
|
3398
|
-
webhook: {
|
|
3399
|
-
enabled: false,
|
|
3400
|
-
urlEnv: 'OPENCLAW_WEBHOOK_URL',
|
|
3401
|
-
method: 'POST',
|
|
3402
|
-
headers: {},
|
|
3403
|
-
},
|
|
3404
|
-
discord: {
|
|
3405
|
-
enabled: false,
|
|
3406
|
-
command: 'node scripts/discord-openclaw-bridge.mjs send --stdin',
|
|
3407
|
-
},
|
|
3408
|
-
},
|
|
3409
|
-
charting: {
|
|
3410
|
-
enabled: enableCharting,
|
|
3411
|
-
command: chartCommand || null,
|
|
3412
|
-
},
|
|
3413
|
-
notifications: {
|
|
3414
|
-
connectorHealth: {
|
|
3415
|
-
enabled: true,
|
|
3416
|
-
channels: [
|
|
3417
|
-
{
|
|
3418
|
-
type: 'openclaw-chat',
|
|
3419
|
-
enabled: true,
|
|
3420
|
-
markdownPath: '.openclaw/chat/connector-health.md',
|
|
3421
|
-
jsonPath: '.openclaw/chat/connector-health.json',
|
|
3422
|
-
},
|
|
3423
|
-
],
|
|
3424
|
-
},
|
|
3425
|
-
growthRun: {
|
|
3426
|
-
enabled: true,
|
|
3427
|
-
channels: [
|
|
3428
|
-
{
|
|
3429
|
-
type: 'openclaw-chat',
|
|
3430
|
-
enabled: true,
|
|
3431
|
-
markdownPath: '.openclaw/chat/growth-summary.md',
|
|
3432
|
-
jsonPath: '.openclaw/chat/growth-summary.json',
|
|
3433
|
-
},
|
|
3434
|
-
],
|
|
3435
|
-
},
|
|
3436
|
-
},
|
|
3437
|
-
secrets: {
|
|
3438
|
-
githubTokenEnv: 'GITHUB_TOKEN',
|
|
3439
|
-
githubTokenRef: { source: 'env', provider: 'default', id: 'GITHUB_TOKEN' },
|
|
3440
|
-
analyticsTokenEnv: 'ANALYTICSCLI_ACCESS_TOKEN',
|
|
3441
|
-
analyticsTokenRef: { source: 'env', provider: 'default', id: 'ANALYTICSCLI_ACCESS_TOKEN' },
|
|
3442
|
-
revenuecatTokenEnv: 'REVENUECAT_API_KEY',
|
|
3443
|
-
revenuecatTokenRef: { source: 'env', provider: 'default', id: 'REVENUECAT_API_KEY' },
|
|
3444
|
-
sentryTokenEnv: 'SENTRY_AUTH_TOKEN',
|
|
3445
|
-
sentryTokenRef: { source: 'env', provider: 'default', id: 'SENTRY_AUTH_TOKEN' },
|
|
3446
|
-
},
|
|
3447
|
-
};
|
|
3424
|
+
let config = await loadEditableConfig(configPath);
|
|
3425
|
+
config.version = Number(config.version || 7);
|
|
3426
|
+
config.generatedAt = new Date().toISOString();
|
|
3427
|
+
config = await askInputSourceConfig(rl, config);
|
|
3428
|
+
config = await askIntervalConfig(rl, config);
|
|
3429
|
+
config = await askOutputConfig(rl, config);
|
|
3430
|
+
config = await askGitHubArtifactDetails(rl, config);
|
|
3448
3431
|
const secretAccess = await askSecretAccessModel(rl, configPath, config);
|
|
3449
3432
|
await ensureDirForFile(configPath);
|
|
3450
3433
|
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
@@ -3454,12 +3437,6 @@ async function main() {
|
|
|
3454
3437
|
printSecretRunnerKitInstructions(secretAccess.kit);
|
|
3455
3438
|
process.stdout.write('\nNext steps:\n');
|
|
3456
3439
|
process.stdout.write(`1) Set secrets in OpenClaw secret store (env var names in config.secrets)\n`);
|
|
3457
|
-
if (extraSources.length > 0) {
|
|
3458
|
-
process.stdout.write(`2) Fill each extra connector under \`sources.extra[]\` with the final file path or command and optional \`secretEnv\`\n`);
|
|
3459
|
-
process.stdout.write(`3) Run once: node scripts/openclaw-growth-runner.mjs --config ${configPath}\n`);
|
|
3460
|
-
process.stdout.write(`4) Run interval loop: node scripts/openclaw-growth-runner.mjs --config ${configPath} --loop\n`);
|
|
3461
|
-
return;
|
|
3462
|
-
}
|
|
3463
3440
|
process.stdout.write(`2) Run once: node scripts/openclaw-growth-runner.mjs --config ${configPath}\n`);
|
|
3464
3441
|
process.stdout.write(`3) Run interval loop: node scripts/openclaw-growth-runner.mjs --config ${configPath} --loop\n`);
|
|
3465
3442
|
}
|