@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 + discovered org. If projects are discovered, accept/select those projects.';
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, org, and project list, then rerun setup.';
1020
+ return 'Verify the Sentry/GlitchTip token, base URL, and org, then rerun setup.';
1021
1021
  }
1022
1022
  if (connector === 'github') {
1023
- return 'Set project.githubRepo only if you want GitHub issue/PR delivery now; otherwise leave GitHub deferred.';
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(`Discovering Sentry projects for ${label}...\n`);
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
- projects = discovery.projects;
2413
- process.stdout.write(`Configured ${projects.length} project(s): ${projects.slice(0, 8).join(', ')}${projects.length > 8 ? ', ...' : ''}\n`);
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
- projects = fallbackDiscovery.projects;
2424
- process.stdout.write(`Using org ${fallbackOrg}; configured ${projects.length} project(s): ${projects.slice(0, 8).join(', ')}${projects.length > 8 ? ', ...' : ''}\n`);
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 (projects.length === 0) {
2429
- process.stdout.write(`Could not discover projects automatically (${discovery.detail}).\n`);
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: detectedRepo || '',
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
- process.stdout.write('\nOutput type\n');
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');
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
- const detectedRepo = await detectGitHubRepo();
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
- return await askOutputConfig(rl, withIntervals);
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
- const detectedRepo = await detectGitHubRepo();
3299
- const githubRepo = await ask(rl, 'GitHub repo (owner/name, optional; leave empty to infer later)', detectedRepo || '');
3300
- const labelsRaw = await ask(rl, 'Issue labels (comma-separated)', 'ai-growth,autogenerated,product');
3301
- const labels = labelsRaw
3302
- .split(',')
3303
- .map((value) => value.trim())
3304
- .filter(Boolean);
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
  }