@analyticscli/growth-engineer 0.1.0-preview.7 → 0.1.0-preview.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/openclaw-growth-runner.mjs +16 -4
- package/dist/runtime/openclaw-growth-runner.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-start.mjs +15 -3
- package/dist/runtime/openclaw-growth-start.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-status.mjs +15 -3
- package/dist/runtime/openclaw-growth-status.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-wizard.mjs +256 -175
- package/dist/runtime/openclaw-growth-wizard.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
+
import { existsSync, promises as fs } from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { createInterface } from 'node:readline/promises';
|
|
7
7
|
import { emitKeypressEvents } from 'node:readline';
|
|
8
8
|
import { createPrivateKey } from 'node:crypto';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
9
10
|
import { buildExtraSourceConfig, getDefaultSourceCommand, } from './openclaw-growth-shared.mjs';
|
|
10
11
|
import { loadOpenClawGrowthSecrets } from './openclaw-growth-env.mjs';
|
|
11
12
|
const DEFAULT_CONFIG_PATH = 'data/openclaw-growth-engineer/config.json';
|
|
@@ -13,6 +14,8 @@ const SELF_UPDATE_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
|
13
14
|
const ENABLE_ISOLATED_SECRET_RUNNER_WIZARD = false;
|
|
14
15
|
const DEFAULT_GROWTH_INTERVAL_MINUTES = 1440;
|
|
15
16
|
const DEFAULT_CONNECTOR_HEALTH_INTERVAL_MINUTES = 360;
|
|
17
|
+
const GROWTH_ENGINEER_PACKAGE_SPEC = process.env.OPENCLAW_GROWTH_ENGINEER_PACKAGE || '@analyticscli/growth-engineer@preview';
|
|
18
|
+
const RUNTIME_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
19
|
const CONNECTOR_KEYS = ['analytics', 'github', 'revenuecat', 'sentry', 'asc'];
|
|
17
20
|
class WizardAbortError extends Error {
|
|
18
21
|
exitCode;
|
|
@@ -220,6 +223,39 @@ function quote(value) {
|
|
|
220
223
|
}
|
|
221
224
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
222
225
|
}
|
|
226
|
+
function resolveRuntimeScriptPath(scriptName) {
|
|
227
|
+
const candidates = [
|
|
228
|
+
path.join(RUNTIME_DIR, scriptName),
|
|
229
|
+
path.resolve('scripts', scriptName),
|
|
230
|
+
path.resolve('skills/openclaw-growth-engineer/scripts', scriptName),
|
|
231
|
+
];
|
|
232
|
+
return candidates.find((candidate) => existsSync(candidate)) || path.join(RUNTIME_DIR, scriptName);
|
|
233
|
+
}
|
|
234
|
+
function nodeRuntimeScriptCommand(scriptName) {
|
|
235
|
+
return `node ${quote(resolveRuntimeScriptPath(scriptName))}`;
|
|
236
|
+
}
|
|
237
|
+
function growthEngineerPackageCommand(args) {
|
|
238
|
+
return `npx -y ${quote(GROWTH_ENGINEER_PACKAGE_SPEC)} ${args}`;
|
|
239
|
+
}
|
|
240
|
+
function getWizardDefaultSourceCommand(sourceName) {
|
|
241
|
+
const normalized = String(sourceName || '').trim().toLowerCase();
|
|
242
|
+
if (normalized === 'analytics' || normalized === 'analyticscli') {
|
|
243
|
+
return nodeRuntimeScriptCommand('export-analytics-summary.mjs');
|
|
244
|
+
}
|
|
245
|
+
if (normalized === 'revenuecat' || normalized === 'revenue-cat') {
|
|
246
|
+
return nodeRuntimeScriptCommand('export-revenuecat-summary.mjs');
|
|
247
|
+
}
|
|
248
|
+
if (normalized === 'sentry' || normalized === 'glitchtip') {
|
|
249
|
+
return nodeRuntimeScriptCommand('export-sentry-summary.mjs');
|
|
250
|
+
}
|
|
251
|
+
if (normalized === 'feedback') {
|
|
252
|
+
return getDefaultSourceCommand('feedback');
|
|
253
|
+
}
|
|
254
|
+
if (['asc', 'asc-cli', 'app-store-connect', 'app_store_connect'].includes(normalized)) {
|
|
255
|
+
return nodeRuntimeScriptCommand('export-asc-summary.mjs');
|
|
256
|
+
}
|
|
257
|
+
return getDefaultSourceCommand(sourceName);
|
|
258
|
+
}
|
|
223
259
|
function normalizeConnectorKey(value) {
|
|
224
260
|
const normalized = String(value || '').trim().toLowerCase().replace(/[_\s]+/g, '-');
|
|
225
261
|
if (!normalized)
|
|
@@ -620,7 +656,7 @@ async function getConnectorPickerHealth(configPath, onProgress = () => { }) {
|
|
|
620
656
|
},
|
|
621
657
|
]));
|
|
622
658
|
}
|
|
623
|
-
const result = await runCommandCaptureWithProgress(
|
|
659
|
+
const result = await runCommandCaptureWithProgress(`${nodeRuntimeScriptCommand('openclaw-growth-status.mjs')} --config ${quote(configPath)} --json --progress-json`, onProgress);
|
|
624
660
|
const payload = parseJsonFromStdout(result.stdout);
|
|
625
661
|
const connectors = payload?.connectors && typeof payload.connectors === 'object' ? payload.connectors : {};
|
|
626
662
|
const healthByConnector = {
|
|
@@ -1366,7 +1402,7 @@ async function runImmediateConnectorHealthCheck({ rl, configPath, connector, sec
|
|
|
1366
1402
|
...process.env,
|
|
1367
1403
|
...secrets,
|
|
1368
1404
|
};
|
|
1369
|
-
const command =
|
|
1405
|
+
const command = `${nodeRuntimeScriptCommand('openclaw-growth-start.mjs')} --config ${quote(configPath)} --setup-only --connectors ${quote(connector)} --only-connectors ${quote(connector)}`;
|
|
1370
1406
|
let result = await runSetupCommandWithProgress(command, env, [connector], `Checking ${connectorLabel(connector)} immediately after setup...`);
|
|
1371
1407
|
let payload = parseJsonFromStdout(result.stdout);
|
|
1372
1408
|
if (connector === 'asc') {
|
|
@@ -1748,13 +1784,13 @@ function getGrowthRunCommand(config, displayConfigPath) {
|
|
|
1748
1784
|
if (config?.security?.connectorSecrets?.mode === 'isolated-runner' && config.security.connectorSecrets.runCommand) {
|
|
1749
1785
|
return config.security.connectorSecrets.runCommand;
|
|
1750
1786
|
}
|
|
1751
|
-
return `
|
|
1787
|
+
return growthEngineerPackageCommand(`run --config ${quote(displayConfigPath)}`);
|
|
1752
1788
|
}
|
|
1753
1789
|
function getConnectorHealthCommand(config, displayConfigPath) {
|
|
1754
1790
|
if (config?.security?.connectorSecrets?.mode === 'isolated-runner' && config.security.connectorSecrets.healthCommand) {
|
|
1755
1791
|
return config.security.connectorSecrets.healthCommand;
|
|
1756
1792
|
}
|
|
1757
|
-
return `
|
|
1793
|
+
return growthEngineerPackageCommand(`run --config ${quote(displayConfigPath)}`);
|
|
1758
1794
|
}
|
|
1759
1795
|
async function maybePromptSecret(rl, label, envName) {
|
|
1760
1796
|
const existing = process.env[envName]?.trim();
|
|
@@ -2601,6 +2637,144 @@ async function maybeSelfUpdateFromClawHub(args) {
|
|
|
2601
2637
|
const code = await rerunCurrentWizardWithoutSelfUpdate();
|
|
2602
2638
|
process.exit(code ?? 0);
|
|
2603
2639
|
}
|
|
2640
|
+
async function runConnectorSetupSteps({ rl, args, selected, healthByConnector, allowIsolationPrompt = true, }) {
|
|
2641
|
+
clearTerminal();
|
|
2642
|
+
printConnectorIntro();
|
|
2643
|
+
process.stdout.write(`${ANSI.bold}Selected connectors${ANSI.reset}\n`);
|
|
2644
|
+
for (const key of selected) {
|
|
2645
|
+
process.stdout.write(` - ${connectorLabel(key)}\n`);
|
|
2646
|
+
}
|
|
2647
|
+
process.stdout.write('\n');
|
|
2648
|
+
const secrets = {};
|
|
2649
|
+
let sentryAccounts = [];
|
|
2650
|
+
if (selected.includes('analytics')) {
|
|
2651
|
+
let forceFreshAnalyticsToken = shouldForceFreshAnalyticsToken(healthByConnector);
|
|
2652
|
+
while (true) {
|
|
2653
|
+
clearTerminal();
|
|
2654
|
+
await guideAnalyticsConnector(rl, secrets, { forceFresh: forceFreshAnalyticsToken });
|
|
2655
|
+
const check = await runImmediateConnectorHealthCheck({
|
|
2656
|
+
rl,
|
|
2657
|
+
configPath: args.config,
|
|
2658
|
+
connector: 'analytics',
|
|
2659
|
+
secrets,
|
|
2660
|
+
});
|
|
2661
|
+
if (!check.retry)
|
|
2662
|
+
break;
|
|
2663
|
+
forceFreshAnalyticsToken = true;
|
|
2664
|
+
}
|
|
2665
|
+
}
|
|
2666
|
+
if (selected.includes('github')) {
|
|
2667
|
+
while (true) {
|
|
2668
|
+
clearTerminal();
|
|
2669
|
+
await guideGitHubConnector(rl, secrets);
|
|
2670
|
+
const check = await runImmediateConnectorHealthCheck({
|
|
2671
|
+
rl,
|
|
2672
|
+
configPath: args.config,
|
|
2673
|
+
connector: 'github',
|
|
2674
|
+
secrets,
|
|
2675
|
+
});
|
|
2676
|
+
if (!check.retry)
|
|
2677
|
+
break;
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
if (selected.includes('revenuecat')) {
|
|
2681
|
+
while (true) {
|
|
2682
|
+
clearTerminal();
|
|
2683
|
+
await guideRevenueCatConnector(rl, secrets);
|
|
2684
|
+
const check = await runImmediateConnectorHealthCheck({
|
|
2685
|
+
rl,
|
|
2686
|
+
configPath: args.config,
|
|
2687
|
+
connector: 'revenuecat',
|
|
2688
|
+
secrets,
|
|
2689
|
+
});
|
|
2690
|
+
if (!check.retry)
|
|
2691
|
+
break;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (selected.includes('sentry')) {
|
|
2695
|
+
while (true) {
|
|
2696
|
+
clearTerminal();
|
|
2697
|
+
sentryAccounts = await guideSentryConnector(rl, secrets);
|
|
2698
|
+
const check = await runImmediateConnectorHealthCheck({
|
|
2699
|
+
rl,
|
|
2700
|
+
configPath: args.config,
|
|
2701
|
+
connector: 'sentry',
|
|
2702
|
+
secrets,
|
|
2703
|
+
sentryAccounts,
|
|
2704
|
+
});
|
|
2705
|
+
if (!check.retry)
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
if (selected.includes('asc')) {
|
|
2710
|
+
while (true) {
|
|
2711
|
+
clearTerminal();
|
|
2712
|
+
await guideAscConnector(rl, secrets);
|
|
2713
|
+
const check = await runImmediateConnectorHealthCheck({
|
|
2714
|
+
rl,
|
|
2715
|
+
configPath: args.config,
|
|
2716
|
+
connector: 'asc',
|
|
2717
|
+
secrets,
|
|
2718
|
+
});
|
|
2719
|
+
if (!check.retry)
|
|
2720
|
+
break;
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
const secretsFile = resolveSecretsFile();
|
|
2724
|
+
const wroteSecrets = Object.keys(secrets).length > 0;
|
|
2725
|
+
clearTerminal();
|
|
2726
|
+
if (wroteSecrets) {
|
|
2727
|
+
await writeSecretsFile(secretsFile, secrets);
|
|
2728
|
+
process.stdout.write(`\nSaved local secrets to ${secretsFile} with chmod 600.\n`);
|
|
2729
|
+
}
|
|
2730
|
+
else {
|
|
2731
|
+
process.stdout.write('\nNo new secrets were written.\n');
|
|
2732
|
+
}
|
|
2733
|
+
if (sentryAccounts.length > 0 && await upsertSentryAccountsConfig(args.config, sentryAccounts)) {
|
|
2734
|
+
process.stdout.write(`Configured ${sentryAccounts.length} Sentry-compatible account(s) in ${args.config}.\n`);
|
|
2735
|
+
}
|
|
2736
|
+
const env = {
|
|
2737
|
+
...process.env,
|
|
2738
|
+
...secrets,
|
|
2739
|
+
};
|
|
2740
|
+
const command = `${nodeRuntimeScriptCommand('openclaw-growth-start.mjs')} --config ${quote(args.config)} --setup-only --connectors ${quote(selected.join(','))}`;
|
|
2741
|
+
let setupResult = await runSetupCommandWithProgress(command, env, selected, 'Testing connector setup...');
|
|
2742
|
+
let setupPayload = parseJsonFromStdout(setupResult.stdout);
|
|
2743
|
+
if (sentryAccounts.length > 0 && await upsertSentryAccountsConfig(args.config, sentryAccounts)) {
|
|
2744
|
+
process.stdout.write(`Sentry-compatible account config is up to date in ${args.config}.\n`);
|
|
2745
|
+
}
|
|
2746
|
+
if (selected.includes('asc')) {
|
|
2747
|
+
try {
|
|
2748
|
+
const ascWebAuthChanged = await ensureAscWebAnalyticsAuth(rl, secrets);
|
|
2749
|
+
if (ascWebAuthChanged) {
|
|
2750
|
+
setupResult = await runSetupCommandWithProgress(command, env, selected, 'Retesting connector setup after ASC web analytics login...');
|
|
2751
|
+
setupPayload = parseJsonFromStdout(setupResult.stdout);
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
catch (error) {
|
|
2755
|
+
process.stdout.write(`ASC web analytics still needs attention: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
if (setupResult.ok && setupPayload?.ok !== false) {
|
|
2759
|
+
printSetupSuccess(setupPayload);
|
|
2760
|
+
if (wroteSecrets) {
|
|
2761
|
+
process.stdout.write('Future OpenClaw Growth commands load this secrets file automatically.\n');
|
|
2762
|
+
}
|
|
2763
|
+
const configureIsolation = allowIsolationPrompt && ENABLE_ISOLATED_SECRET_RUNNER_WIZARD && await askYesNo(rl, 'Generate an isolated secret runner so OpenClaw can run health checks without reading API keys?', true);
|
|
2764
|
+
if (configureIsolation) {
|
|
2765
|
+
const config = await loadEditableConfig(args.config);
|
|
2766
|
+
const secretAccess = await askSecretAccessModel(rl, path.resolve(args.config), config);
|
|
2767
|
+
await writeJsonFile(path.resolve(args.config), config);
|
|
2768
|
+
const manifestPath = await writeOpenClawJobManifest(path.resolve(args.config), config);
|
|
2769
|
+
process.stdout.write(`Saved OpenClaw job manifest: ${manifestPath}\n`);
|
|
2770
|
+
printSecretRunnerKitInstructions(secretAccess.kit);
|
|
2771
|
+
}
|
|
2772
|
+
return true;
|
|
2773
|
+
}
|
|
2774
|
+
printSetupFailure({ result: setupResult, payload: setupPayload, command });
|
|
2775
|
+
process.exitCode = 1;
|
|
2776
|
+
return false;
|
|
2777
|
+
}
|
|
2604
2778
|
async function runConnectorSetupWizard(args) {
|
|
2605
2779
|
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2606
2780
|
throw new Error('Connector wizard requires an interactive terminal.');
|
|
@@ -2619,141 +2793,7 @@ async function runConnectorSetupWizard(args) {
|
|
|
2619
2793
|
if (selected.length === 0) {
|
|
2620
2794
|
throw new Error('No supported connectors selected. Use analytics, github, revenuecat, sentry, asc, or all.');
|
|
2621
2795
|
}
|
|
2622
|
-
|
|
2623
|
-
printConnectorIntro();
|
|
2624
|
-
process.stdout.write(`${ANSI.bold}Selected connectors${ANSI.reset}\n`);
|
|
2625
|
-
for (const key of selected) {
|
|
2626
|
-
process.stdout.write(` - ${connectorLabel(key)}\n`);
|
|
2627
|
-
}
|
|
2628
|
-
process.stdout.write('\n');
|
|
2629
|
-
const secrets = {};
|
|
2630
|
-
let sentryAccounts = [];
|
|
2631
|
-
if (selected.includes('analytics')) {
|
|
2632
|
-
let forceFreshAnalyticsToken = shouldForceFreshAnalyticsToken(healthByConnector);
|
|
2633
|
-
while (true) {
|
|
2634
|
-
clearTerminal();
|
|
2635
|
-
await guideAnalyticsConnector(rl, secrets, { forceFresh: forceFreshAnalyticsToken });
|
|
2636
|
-
const check = await runImmediateConnectorHealthCheck({
|
|
2637
|
-
rl,
|
|
2638
|
-
configPath: args.config,
|
|
2639
|
-
connector: 'analytics',
|
|
2640
|
-
secrets,
|
|
2641
|
-
});
|
|
2642
|
-
if (!check.retry)
|
|
2643
|
-
break;
|
|
2644
|
-
forceFreshAnalyticsToken = true;
|
|
2645
|
-
}
|
|
2646
|
-
}
|
|
2647
|
-
if (selected.includes('github')) {
|
|
2648
|
-
while (true) {
|
|
2649
|
-
clearTerminal();
|
|
2650
|
-
await guideGitHubConnector(rl, secrets);
|
|
2651
|
-
const check = await runImmediateConnectorHealthCheck({
|
|
2652
|
-
rl,
|
|
2653
|
-
configPath: args.config,
|
|
2654
|
-
connector: 'github',
|
|
2655
|
-
secrets,
|
|
2656
|
-
});
|
|
2657
|
-
if (!check.retry)
|
|
2658
|
-
break;
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
if (selected.includes('revenuecat')) {
|
|
2662
|
-
while (true) {
|
|
2663
|
-
clearTerminal();
|
|
2664
|
-
await guideRevenueCatConnector(rl, secrets);
|
|
2665
|
-
const check = await runImmediateConnectorHealthCheck({
|
|
2666
|
-
rl,
|
|
2667
|
-
configPath: args.config,
|
|
2668
|
-
connector: 'revenuecat',
|
|
2669
|
-
secrets,
|
|
2670
|
-
});
|
|
2671
|
-
if (!check.retry)
|
|
2672
|
-
break;
|
|
2673
|
-
}
|
|
2674
|
-
}
|
|
2675
|
-
if (selected.includes('sentry')) {
|
|
2676
|
-
while (true) {
|
|
2677
|
-
clearTerminal();
|
|
2678
|
-
sentryAccounts = await guideSentryConnector(rl, secrets);
|
|
2679
|
-
const check = await runImmediateConnectorHealthCheck({
|
|
2680
|
-
rl,
|
|
2681
|
-
configPath: args.config,
|
|
2682
|
-
connector: 'sentry',
|
|
2683
|
-
secrets,
|
|
2684
|
-
sentryAccounts,
|
|
2685
|
-
});
|
|
2686
|
-
if (!check.retry)
|
|
2687
|
-
break;
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
if (selected.includes('asc')) {
|
|
2691
|
-
while (true) {
|
|
2692
|
-
clearTerminal();
|
|
2693
|
-
await guideAscConnector(rl, secrets);
|
|
2694
|
-
const check = await runImmediateConnectorHealthCheck({
|
|
2695
|
-
rl,
|
|
2696
|
-
configPath: args.config,
|
|
2697
|
-
connector: 'asc',
|
|
2698
|
-
secrets,
|
|
2699
|
-
});
|
|
2700
|
-
if (!check.retry)
|
|
2701
|
-
break;
|
|
2702
|
-
}
|
|
2703
|
-
}
|
|
2704
|
-
const secretsFile = resolveSecretsFile();
|
|
2705
|
-
const wroteSecrets = Object.keys(secrets).length > 0;
|
|
2706
|
-
clearTerminal();
|
|
2707
|
-
if (wroteSecrets) {
|
|
2708
|
-
await writeSecretsFile(secretsFile, secrets);
|
|
2709
|
-
process.stdout.write(`\nSaved local secrets to ${secretsFile} with chmod 600.\n`);
|
|
2710
|
-
}
|
|
2711
|
-
else {
|
|
2712
|
-
process.stdout.write('\nNo new secrets were written.\n');
|
|
2713
|
-
}
|
|
2714
|
-
if (sentryAccounts.length > 0 && await upsertSentryAccountsConfig(args.config, sentryAccounts)) {
|
|
2715
|
-
process.stdout.write(`Configured ${sentryAccounts.length} Sentry-compatible account(s) in ${args.config}.\n`);
|
|
2716
|
-
}
|
|
2717
|
-
const env = {
|
|
2718
|
-
...process.env,
|
|
2719
|
-
...secrets,
|
|
2720
|
-
};
|
|
2721
|
-
const command = `node scripts/openclaw-growth-start.mjs --config ${quote(args.config)} --setup-only --connectors ${quote(selected.join(','))}`;
|
|
2722
|
-
let setupResult = await runSetupCommandWithProgress(command, env, selected, 'Testing connector setup...');
|
|
2723
|
-
let setupPayload = parseJsonFromStdout(setupResult.stdout);
|
|
2724
|
-
if (sentryAccounts.length > 0 && await upsertSentryAccountsConfig(args.config, sentryAccounts)) {
|
|
2725
|
-
process.stdout.write(`Sentry-compatible account config is up to date in ${args.config}.\n`);
|
|
2726
|
-
}
|
|
2727
|
-
if (selected.includes('asc')) {
|
|
2728
|
-
try {
|
|
2729
|
-
const ascWebAuthChanged = await ensureAscWebAnalyticsAuth(rl, secrets);
|
|
2730
|
-
if (ascWebAuthChanged) {
|
|
2731
|
-
setupResult = await runSetupCommandWithProgress(command, env, selected, 'Retesting connector setup after ASC web analytics login...');
|
|
2732
|
-
setupPayload = parseJsonFromStdout(setupResult.stdout);
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
catch (error) {
|
|
2736
|
-
process.stdout.write(`ASC web analytics still needs attention: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
2737
|
-
}
|
|
2738
|
-
}
|
|
2739
|
-
if (setupResult.ok && setupPayload?.ok !== false) {
|
|
2740
|
-
printSetupSuccess(setupPayload);
|
|
2741
|
-
if (wroteSecrets) {
|
|
2742
|
-
process.stdout.write('Future OpenClaw Growth commands load this secrets file automatically.\n');
|
|
2743
|
-
}
|
|
2744
|
-
const configureIsolation = ENABLE_ISOLATED_SECRET_RUNNER_WIZARD && await askYesNo(rl, 'Generate an isolated secret runner so OpenClaw can run health checks without reading API keys?', true);
|
|
2745
|
-
if (configureIsolation) {
|
|
2746
|
-
const config = await loadEditableConfig(args.config);
|
|
2747
|
-
const secretAccess = await askSecretAccessModel(rl, path.resolve(args.config), config);
|
|
2748
|
-
await writeJsonFile(path.resolve(args.config), config);
|
|
2749
|
-
const manifestPath = await writeOpenClawJobManifest(path.resolve(args.config), config);
|
|
2750
|
-
process.stdout.write(`Saved OpenClaw job manifest: ${manifestPath}\n`);
|
|
2751
|
-
printSecretRunnerKitInstructions(secretAccess.kit);
|
|
2752
|
-
}
|
|
2753
|
-
return;
|
|
2754
|
-
}
|
|
2755
|
-
printSetupFailure({ result: setupResult, payload: setupPayload, command });
|
|
2756
|
-
process.exitCode = 1;
|
|
2796
|
+
await runConnectorSetupSteps({ rl, args, selected, healthByConnector });
|
|
2757
2797
|
}
|
|
2758
2798
|
finally {
|
|
2759
2799
|
rl.close();
|
|
@@ -2801,16 +2841,28 @@ function printCadencePlan(cadences) {
|
|
|
2801
2841
|
process.stdout.write('\n');
|
|
2802
2842
|
}
|
|
2803
2843
|
async function askToolUsage(rl) {
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2844
|
+
return await askMenuChoice(rl, {
|
|
2845
|
+
title: 'How should OpenClaw Growth Engineer run?',
|
|
2846
|
+
subtitle: 'Use Up/Down to move, Enter to continue, or press 1-3.',
|
|
2847
|
+
defaultValue: 'production_autopilot',
|
|
2848
|
+
options: [
|
|
2849
|
+
{
|
|
2850
|
+
value: 'production_autopilot',
|
|
2851
|
+
label: 'Production autopilot',
|
|
2852
|
+
detail: 'Notify, draft issues/PR handoffs, and analyze on schedule.',
|
|
2853
|
+
},
|
|
2854
|
+
{
|
|
2855
|
+
value: 'advisory',
|
|
2856
|
+
label: 'Advisory only',
|
|
2857
|
+
detail: 'Analyze and write OpenClaw chat summaries; no GitHub artifacts by default.',
|
|
2858
|
+
},
|
|
2859
|
+
{
|
|
2860
|
+
value: 'manual_reports',
|
|
2861
|
+
label: 'Manual reports',
|
|
2862
|
+
detail: 'Mostly one-off runs with conservative scheduling.',
|
|
2863
|
+
},
|
|
2864
|
+
],
|
|
2865
|
+
});
|
|
2814
2866
|
}
|
|
2815
2867
|
async function askCadencePlan(rl) {
|
|
2816
2868
|
const cadences = DEFAULT_CADENCE_PLAN.map((cadence) => ({ ...cadence }));
|
|
@@ -2884,17 +2936,17 @@ async function buildDefaultWizardConfig() {
|
|
|
2884
2936
|
analytics: {
|
|
2885
2937
|
enabled: true,
|
|
2886
2938
|
mode: 'command',
|
|
2887
|
-
command:
|
|
2939
|
+
command: getWizardDefaultSourceCommand('analytics'),
|
|
2888
2940
|
},
|
|
2889
2941
|
revenuecat: {
|
|
2890
2942
|
enabled: false,
|
|
2891
2943
|
mode: 'command',
|
|
2892
|
-
command:
|
|
2944
|
+
command: getWizardDefaultSourceCommand('revenuecat'),
|
|
2893
2945
|
},
|
|
2894
2946
|
sentry: {
|
|
2895
2947
|
enabled: true,
|
|
2896
2948
|
mode: 'command',
|
|
2897
|
-
command:
|
|
2949
|
+
command: getWizardDefaultSourceCommand('sentry'),
|
|
2898
2950
|
},
|
|
2899
2951
|
feedback: {
|
|
2900
2952
|
enabled: true,
|
|
@@ -2904,7 +2956,7 @@ async function buildDefaultWizardConfig() {
|
|
|
2904
2956
|
initialLookback: '30d',
|
|
2905
2957
|
},
|
|
2906
2958
|
extra: [
|
|
2907
|
-
buildExtraSourceConfig('asc-cli', { enabled: false, mode: 'command', command:
|
|
2959
|
+
buildExtraSourceConfig('asc-cli', { enabled: false, mode: 'command', command: getWizardDefaultSourceCommand('asc') }),
|
|
2908
2960
|
],
|
|
2909
2961
|
},
|
|
2910
2962
|
schedule: {
|
|
@@ -2997,17 +3049,17 @@ function buildRecommendedSourceConfig() {
|
|
|
2997
3049
|
analytics: {
|
|
2998
3050
|
enabled: true,
|
|
2999
3051
|
mode: 'command',
|
|
3000
|
-
command:
|
|
3052
|
+
command: getWizardDefaultSourceCommand('analytics'),
|
|
3001
3053
|
},
|
|
3002
3054
|
revenuecat: {
|
|
3003
3055
|
enabled: false,
|
|
3004
3056
|
mode: 'command',
|
|
3005
|
-
command:
|
|
3057
|
+
command: getWizardDefaultSourceCommand('revenuecat'),
|
|
3006
3058
|
},
|
|
3007
3059
|
sentry: {
|
|
3008
3060
|
enabled: true,
|
|
3009
3061
|
mode: 'command',
|
|
3010
|
-
command:
|
|
3062
|
+
command: getWizardDefaultSourceCommand('sentry'),
|
|
3011
3063
|
},
|
|
3012
3064
|
feedback: {
|
|
3013
3065
|
enabled: true,
|
|
@@ -3017,7 +3069,7 @@ function buildRecommendedSourceConfig() {
|
|
|
3017
3069
|
initialLookback: '30d',
|
|
3018
3070
|
},
|
|
3019
3071
|
extra: [
|
|
3020
|
-
buildExtraSourceConfig('asc-cli', { enabled: false, mode: 'command', command:
|
|
3072
|
+
buildExtraSourceConfig('asc-cli', { enabled: false, mode: 'command', command: getWizardDefaultSourceCommand('asc') }),
|
|
3021
3073
|
],
|
|
3022
3074
|
};
|
|
3023
3075
|
}
|
|
@@ -3080,7 +3132,7 @@ function buildSourceConfigFromInputChannels(selectedConnectors, existingSources
|
|
|
3080
3132
|
...buildExtraSourceConfig('asc-cli', {
|
|
3081
3133
|
enabled: selected.has('asc'),
|
|
3082
3134
|
mode: 'command',
|
|
3083
|
-
command:
|
|
3135
|
+
command: getWizardDefaultSourceCommand('asc'),
|
|
3084
3136
|
}),
|
|
3085
3137
|
...(ascSource || {}),
|
|
3086
3138
|
enabled: selected.has('asc'),
|
|
@@ -3139,20 +3191,33 @@ async function askOutputConfig(rl, config) {
|
|
|
3139
3191
|
'OpenClaw chat is always enabled so the agent has a readable handoff.',
|
|
3140
3192
|
'GitHub issues or draft PRs are optional and only run when a token plus an inferred repo are available.',
|
|
3141
3193
|
]);
|
|
3142
|
-
process.stdout.write(' 1) OpenClaw chat only, with GitHub left as runtime fallback\n');
|
|
3143
|
-
process.stdout.write(' 2) Auto-create GitHub issues for concrete findings\n');
|
|
3144
|
-
process.stdout.write(' 3) Auto-create draft PR proposals for implementation-ready fixes\n');
|
|
3145
3194
|
const currentMode = config?.actions?.mode || config?.deliveries?.github?.mode || 'issue';
|
|
3146
3195
|
const currentAutoCreate = Boolean(config?.actions?.autoCreateIssues || config?.actions?.autoCreatePullRequests || config?.deliveries?.github?.autoCreate);
|
|
3147
|
-
const
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3196
|
+
const outputChoice = await askMenuChoice(rl, {
|
|
3197
|
+
title: 'Output mode',
|
|
3198
|
+
subtitle: 'Use Up/Down to move, Enter to continue, or press 1-3.',
|
|
3199
|
+
defaultValue: currentAutoCreate ? (currentMode === 'pull_request' ? 'pull_request' : 'issue') : 'chat',
|
|
3200
|
+
options: [
|
|
3201
|
+
{
|
|
3202
|
+
value: 'chat',
|
|
3203
|
+
label: 'OpenClaw chat',
|
|
3204
|
+
detail: 'Write readable summaries and leave GitHub as runtime fallback.',
|
|
3205
|
+
},
|
|
3206
|
+
{
|
|
3207
|
+
value: 'issue',
|
|
3208
|
+
label: 'GitHub issues',
|
|
3209
|
+
detail: 'Auto-create issues for concrete findings when GitHub access allows it.',
|
|
3210
|
+
},
|
|
3211
|
+
{
|
|
3212
|
+
value: 'pull_request',
|
|
3213
|
+
label: 'Draft PR proposals',
|
|
3214
|
+
detail: 'Auto-create draft PR-oriented proposal branches for implementation-ready fixes.',
|
|
3215
|
+
},
|
|
3216
|
+
],
|
|
3217
|
+
});
|
|
3218
|
+
const summaryOnly = outputChoice === 'chat';
|
|
3219
|
+
const mode = outputChoice === 'pull_request' ? 'pull_request' : 'issue';
|
|
3220
|
+
const autoCreate = !summaryOnly;
|
|
3156
3221
|
if (!summaryOnly) {
|
|
3157
3222
|
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');
|
|
3158
3223
|
}
|
|
@@ -3310,7 +3375,7 @@ async function askInputSourceConfig(rl, config, configPath) {
|
|
|
3310
3375
|
mode: 'input',
|
|
3311
3376
|
});
|
|
3312
3377
|
config.sources = buildSourceConfigFromInputChannels(selected, config.sources || {});
|
|
3313
|
-
return config;
|
|
3378
|
+
return { config, selected, healthByConnector };
|
|
3314
3379
|
}
|
|
3315
3380
|
async function writeOpenClawJobManifest(configPath, config) {
|
|
3316
3381
|
const manifestPath = path.resolve('.openclaw/jobs/openclaw-growth-engineer.json');
|
|
@@ -3403,7 +3468,23 @@ async function main() {
|
|
|
3403
3468
|
let config = await loadEditableConfig(configPath);
|
|
3404
3469
|
config.version = Number(config.version || 7);
|
|
3405
3470
|
config.generatedAt = new Date().toISOString();
|
|
3406
|
-
|
|
3471
|
+
const inputSetup = await askInputSourceConfig(rl, config, configPath);
|
|
3472
|
+
config = inputSetup.config;
|
|
3473
|
+
await ensureDirForFile(configPath);
|
|
3474
|
+
await writeJsonFile(configPath, config);
|
|
3475
|
+
const connectorsOk = await runConnectorSetupSteps({
|
|
3476
|
+
rl,
|
|
3477
|
+
args: { ...args, config: configPath },
|
|
3478
|
+
selected: inputSetup.selected,
|
|
3479
|
+
healthByConnector: inputSetup.healthByConnector,
|
|
3480
|
+
allowIsolationPrompt: false,
|
|
3481
|
+
});
|
|
3482
|
+
if (!connectorsOk) {
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
config = await loadEditableConfig(configPath);
|
|
3486
|
+
config.version = Number(config.version || 7);
|
|
3487
|
+
config.generatedAt = new Date().toISOString();
|
|
3407
3488
|
config = await askIntervalConfig(rl, config);
|
|
3408
3489
|
config = await askOutputConfig(rl, config);
|
|
3409
3490
|
config = await askGitHubArtifactDetails(rl, config);
|
|
@@ -3416,8 +3497,8 @@ async function main() {
|
|
|
3416
3497
|
printSecretRunnerKitInstructions(secretAccess.kit);
|
|
3417
3498
|
process.stdout.write('\nNext steps:\n');
|
|
3418
3499
|
process.stdout.write(`1) Set secrets in OpenClaw secret store (env var names in config.secrets)\n`);
|
|
3419
|
-
process.stdout.write(`2) Run once:
|
|
3420
|
-
process.stdout.write(`3) Run interval loop:
|
|
3500
|
+
process.stdout.write(`2) Run once: ${growthEngineerPackageCommand(`run --config ${quote(configPath)}`)}\n`);
|
|
3501
|
+
process.stdout.write(`3) Run interval loop: ${growthEngineerPackageCommand(`run --config ${quote(configPath)} --loop`)}\n`);
|
|
3421
3502
|
}
|
|
3422
3503
|
finally {
|
|
3423
3504
|
rl.close();
|