@analyticscli/growth-engineer 0.1.0-preview.10 → 0.1.0-preview.11
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 +18 -14
- package/dist/runtime/openclaw-growth-runner.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-shared.d.mts +2 -1
- package/dist/runtime/openclaw-growth-shared.mjs +32 -2
- package/dist/runtime/openclaw-growth-shared.mjs.map +1 -1
- package/dist/runtime/openclaw-growth-wizard.mjs +251 -23
- package/dist/runtime/openclaw-growth-wizard.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -462,6 +462,170 @@ async function askMenuChoice(rl, { title, subtitle = 'Use Up/Down to move, Enter
|
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
}
|
|
465
|
+
async function askMultiChoice(rl, { title, subtitle = 'Use Up/Down to move, Space to toggle, Enter to continue.', options, defaultValues, requiredValues = [], minSelections = 1, renderHeader, }) {
|
|
466
|
+
const required = new Set(requiredValues);
|
|
467
|
+
const normalizeSelection = (values) => {
|
|
468
|
+
const selected = new Set(values);
|
|
469
|
+
requiredValues.forEach((value) => selected.add(value));
|
|
470
|
+
return options.map((option) => option.value).filter((value) => selected.has(value));
|
|
471
|
+
};
|
|
472
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY || !process.stdin.setRawMode) {
|
|
473
|
+
process.stdout.write(`\n${title}\n`);
|
|
474
|
+
options.forEach((option, index) => {
|
|
475
|
+
const checked = defaultValues.includes(option.value) || required.has(option.value) ? 'x' : ' ';
|
|
476
|
+
const requiredLabel = required.has(option.value) ? ' required' : '';
|
|
477
|
+
process.stdout.write(` ${index + 1}) [${checked}] ${option.label}${requiredLabel}: ${option.detail}\n`);
|
|
478
|
+
});
|
|
479
|
+
const answer = await ask(rl, `Select one or more (comma-separated 1-${options.length})`, normalizeSelection(defaultValues).map((value) => String(options.findIndex((option) => option.value === value) + 1)).join(','));
|
|
480
|
+
const selected = answer
|
|
481
|
+
.split(',')
|
|
482
|
+
.map((value) => Number.parseInt(value.trim(), 10) - 1)
|
|
483
|
+
.filter((index) => options[index])
|
|
484
|
+
.map((index) => options[index].value);
|
|
485
|
+
const normalized = normalizeSelection(selected);
|
|
486
|
+
return normalized.length >= minSelections ? normalized : normalizeSelection(defaultValues);
|
|
487
|
+
}
|
|
488
|
+
rl.pause();
|
|
489
|
+
let completed = false;
|
|
490
|
+
try {
|
|
491
|
+
const selected = await askMultiChoiceByKeys({
|
|
492
|
+
title,
|
|
493
|
+
subtitle,
|
|
494
|
+
options,
|
|
495
|
+
defaultValues: normalizeSelection(defaultValues),
|
|
496
|
+
requiredValues,
|
|
497
|
+
minSelections,
|
|
498
|
+
renderHeader,
|
|
499
|
+
});
|
|
500
|
+
completed = true;
|
|
501
|
+
return selected;
|
|
502
|
+
}
|
|
503
|
+
finally {
|
|
504
|
+
if (completed) {
|
|
505
|
+
rl.resume();
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
process.stdin.pause();
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async function askMultiChoiceByKeys({ title, subtitle, options, defaultValues, requiredValues, minSelections, renderHeader, }) {
|
|
513
|
+
emitKeypressEvents(process.stdin);
|
|
514
|
+
const wasRaw = process.stdin.isRaw;
|
|
515
|
+
const wasPaused = process.stdin.isPaused();
|
|
516
|
+
process.stdin.setRawMode(true);
|
|
517
|
+
process.stdin.resume();
|
|
518
|
+
const required = new Set(requiredValues);
|
|
519
|
+
const selected = new Set(defaultValues);
|
|
520
|
+
requiredValues.forEach((value) => selected.add(value));
|
|
521
|
+
let cursorIndex = 0;
|
|
522
|
+
let warning = '';
|
|
523
|
+
return await new Promise((resolve, reject) => {
|
|
524
|
+
const cleanup = () => {
|
|
525
|
+
process.stdin.off('keypress', onKeypress);
|
|
526
|
+
process.stdin.setRawMode(Boolean(wasRaw));
|
|
527
|
+
if (wasPaused) {
|
|
528
|
+
process.stdin.pause();
|
|
529
|
+
}
|
|
530
|
+
process.stdout.write(ANSI.showCursor);
|
|
531
|
+
};
|
|
532
|
+
const selectedValues = () => options.map((option) => option.value).filter((value) => selected.has(value));
|
|
533
|
+
const render = () => {
|
|
534
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
535
|
+
renderHeader?.();
|
|
536
|
+
process.stdout.write(`\n${ANSI.bold}${title}${ANSI.reset}\n`);
|
|
537
|
+
process.stdout.write(`${ANSI.dim}${subtitle}${ANSI.reset}\n\n`);
|
|
538
|
+
if (warning) {
|
|
539
|
+
process.stdout.write(`${ANSI.cyan}${warning}${ANSI.reset}\n\n`);
|
|
540
|
+
}
|
|
541
|
+
for (let index = 0; index < options.length; index += 1) {
|
|
542
|
+
const option = options[index];
|
|
543
|
+
const pointer = index === cursorIndex ? `${ANSI.cyan}>${ANSI.reset}` : ' ';
|
|
544
|
+
const checkbox = selected.has(option.value) ? '[x]' : '[ ]';
|
|
545
|
+
const requiredLabel = required.has(option.value) ? ` ${ANSI.dim}(required)${ANSI.reset}` : '';
|
|
546
|
+
process.stdout.write(`${pointer} ${checkbox} ${index + 1}) ${ANSI.bold}${option.label}${ANSI.reset}${requiredLabel}\n`);
|
|
547
|
+
writeWrapped(option.detail, ' ', ANSI.dim);
|
|
548
|
+
}
|
|
549
|
+
process.stdout.write(`\n${ANSI.dim}Esc/Q cancels. Space toggles, A toggles all optional items, Enter continues. Number keys 1-${options.length} toggle items.${ANSI.reset}\n`);
|
|
550
|
+
};
|
|
551
|
+
const cancel = () => {
|
|
552
|
+
cleanup();
|
|
553
|
+
process.stdout.write('\n');
|
|
554
|
+
reject(new WizardAbortError('Setup cancelled.'));
|
|
555
|
+
};
|
|
556
|
+
const finish = () => {
|
|
557
|
+
const values = selectedValues();
|
|
558
|
+
if (values.length < minSelections) {
|
|
559
|
+
warning = `Select at least ${minSelections} item${minSelections === 1 ? '' : 's'} to continue.`;
|
|
560
|
+
render();
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
cleanup();
|
|
564
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
565
|
+
resolve(values);
|
|
566
|
+
};
|
|
567
|
+
const toggleIndex = (index) => {
|
|
568
|
+
const option = options[index];
|
|
569
|
+
if (!option)
|
|
570
|
+
return;
|
|
571
|
+
warning = '';
|
|
572
|
+
if (required.has(option.value)) {
|
|
573
|
+
selected.add(option.value);
|
|
574
|
+
warning = `${option.label} is required.`;
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (selected.has(option.value))
|
|
578
|
+
selected.delete(option.value);
|
|
579
|
+
else
|
|
580
|
+
selected.add(option.value);
|
|
581
|
+
requiredValues.forEach((value) => selected.add(value));
|
|
582
|
+
};
|
|
583
|
+
const onKeypress = (_text, key) => {
|
|
584
|
+
if (key?.ctrl && key?.name === 'c') {
|
|
585
|
+
cancel();
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (key?.name === 'escape' || key?.name === 'q') {
|
|
589
|
+
cancel();
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (key?.name === 'up' || key?.name === 'k') {
|
|
593
|
+
cursorIndex = (cursorIndex - 1 + options.length) % options.length;
|
|
594
|
+
warning = '';
|
|
595
|
+
}
|
|
596
|
+
else if (key?.name === 'down' || key?.name === 'j') {
|
|
597
|
+
cursorIndex = (cursorIndex + 1) % options.length;
|
|
598
|
+
warning = '';
|
|
599
|
+
}
|
|
600
|
+
else if (key?.name === 'space') {
|
|
601
|
+
toggleIndex(cursorIndex);
|
|
602
|
+
}
|
|
603
|
+
else if (String(_text || '').toLowerCase() === 'a') {
|
|
604
|
+
const optional = options.filter((option) => !required.has(option.value));
|
|
605
|
+
const allSelected = optional.every((option) => selected.has(option.value));
|
|
606
|
+
optional.forEach((option) => {
|
|
607
|
+
if (allSelected)
|
|
608
|
+
selected.delete(option.value);
|
|
609
|
+
else
|
|
610
|
+
selected.add(option.value);
|
|
611
|
+
});
|
|
612
|
+
requiredValues.forEach((value) => selected.add(value));
|
|
613
|
+
warning = '';
|
|
614
|
+
}
|
|
615
|
+
else if (key?.name === 'return' || key?.name === 'enter') {
|
|
616
|
+
finish();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
else if (/^[1-9]$/.test(String(_text || ''))) {
|
|
620
|
+
toggleIndex(Number(_text) - 1);
|
|
621
|
+
}
|
|
622
|
+
render();
|
|
623
|
+
};
|
|
624
|
+
process.stdin.on('keypress', onKeypress);
|
|
625
|
+
process.stdout.write(ANSI.hideCursor);
|
|
626
|
+
render();
|
|
627
|
+
});
|
|
628
|
+
}
|
|
465
629
|
async function askMenuChoiceByKeys({ title, subtitle, options, defaultValue, renderHeader, }) {
|
|
466
630
|
emitKeypressEvents(process.stdin);
|
|
467
631
|
const wasRaw = process.stdin.isRaw;
|
|
@@ -2885,12 +3049,32 @@ async function askYesNo(rl, label, defaultYes = true) {
|
|
|
2885
3049
|
}
|
|
2886
3050
|
}
|
|
2887
3051
|
}
|
|
3052
|
+
function truncateTableCell(value, width) {
|
|
3053
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
3054
|
+
if (text.length <= width)
|
|
3055
|
+
return text.padEnd(width, ' ');
|
|
3056
|
+
return `${text.slice(0, Math.max(0, width - 3))}...`.padEnd(width, ' ');
|
|
3057
|
+
}
|
|
3058
|
+
function printAsciiTable(headers, rows, widths) {
|
|
3059
|
+
const border = `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`;
|
|
3060
|
+
const renderRow = (cells) => `| ${cells.map((cell, index) => truncateTableCell(cell, widths[index])).join(' | ')} |`;
|
|
3061
|
+
process.stdout.write(`${border}\n`);
|
|
3062
|
+
process.stdout.write(`${renderRow(headers)}\n`);
|
|
3063
|
+
process.stdout.write(`${border}\n`);
|
|
3064
|
+
for (const row of rows) {
|
|
3065
|
+
process.stdout.write(`${renderRow(row)}\n`);
|
|
3066
|
+
}
|
|
3067
|
+
process.stdout.write(`${border}\n`);
|
|
3068
|
+
}
|
|
2888
3069
|
function printCadencePlan(cadences) {
|
|
2889
3070
|
process.stdout.write('\nDefault growth cadence:\n');
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
3071
|
+
printAsciiTable(['Cadence', 'Every', 'Mode', 'Primary focus', 'What it decides'], cadences.map((cadence) => [
|
|
3072
|
+
cadence.key,
|
|
3073
|
+
`${cadence.intervalDays}d`,
|
|
3074
|
+
cadence.criticalOnly ? 'critical only' : 'full review',
|
|
3075
|
+
Array.isArray(cadence.focusAreas) ? cadence.focusAreas.slice(0, 4).join(', ') : '',
|
|
3076
|
+
cadence.objective,
|
|
3077
|
+
]), [12, 7, 13, 30, 42]);
|
|
2894
3078
|
process.stdout.write('\n');
|
|
2895
3079
|
}
|
|
2896
3080
|
async function askToolUsage(rl) {
|
|
@@ -2917,18 +3101,37 @@ async function askToolUsage(rl) {
|
|
|
2917
3101
|
],
|
|
2918
3102
|
});
|
|
2919
3103
|
}
|
|
2920
|
-
async function askCadencePlan(rl) {
|
|
2921
|
-
const
|
|
3104
|
+
async function askCadencePlan(rl, existingCadences = []) {
|
|
3105
|
+
const existingByKey = new Map((Array.isArray(existingCadences) ? existingCadences : [])
|
|
3106
|
+
.filter((cadence) => cadence?.key)
|
|
3107
|
+
.map((cadence) => [String(cadence.key), cadence]));
|
|
3108
|
+
const cadences = DEFAULT_CADENCE_PLAN.map((cadence) => ({
|
|
3109
|
+
...cadence,
|
|
3110
|
+
...(existingByKey.get(cadence.key) || {}),
|
|
3111
|
+
}));
|
|
2922
3112
|
printCadencePlan(cadences);
|
|
2923
|
-
const
|
|
2924
|
-
|
|
3113
|
+
const selectedCadences = await askMultiChoice(rl, {
|
|
3114
|
+
title: 'Scheduled review cadences',
|
|
3115
|
+
subtitle: 'Use Up/Down to move, Space to toggle cadences, A to toggle all, Enter to continue.',
|
|
3116
|
+
defaultValues: cadences.filter((cadence) => cadence.enabled !== false).map((cadence) => cadence.key),
|
|
3117
|
+
minSelections: 1,
|
|
3118
|
+
options: cadences.map((cadence) => ({
|
|
3119
|
+
value: cadence.key,
|
|
3120
|
+
label: cadence.title,
|
|
3121
|
+
detail: `${cadence.intervalDays}d, ${cadence.criticalOnly ? 'critical only' : 'full review'} - ${cadence.objective}`,
|
|
3122
|
+
})),
|
|
3123
|
+
});
|
|
3124
|
+
const selected = new Set(selectedCadences);
|
|
3125
|
+
cadences.forEach((cadence) => {
|
|
3126
|
+
cadence.enabled = selected.has(cadence.key);
|
|
3127
|
+
});
|
|
3128
|
+
const customize = await askYesNo(rl, 'Customize objectives, instructions, focus areas, or source priorities for enabled cadences?', false);
|
|
3129
|
+
if (!customize)
|
|
2925
3130
|
return cadences;
|
|
2926
3131
|
for (const cadence of cadences) {
|
|
2927
|
-
|
|
2928
|
-
const enabled = await askYesNo(rl, `Enable ${cadence.key}?`, true);
|
|
2929
|
-
cadence.enabled = enabled;
|
|
2930
|
-
if (!enabled)
|
|
3132
|
+
if (cadence.enabled === false)
|
|
2931
3133
|
continue;
|
|
3134
|
+
process.stdout.write(`\n${cadence.title}\n`);
|
|
2932
3135
|
cadence.objective = await ask(rl, `${cadence.key} objective`, cadence.objective);
|
|
2933
3136
|
cadence.instructions = await ask(rl, `${cadence.key} instructions`, cadence.instructions);
|
|
2934
3137
|
const focusAreas = await ask(rl, `${cadence.key} focus areas (comma-separated)`, cadence.focusAreas.join(','));
|
|
@@ -3266,11 +3469,25 @@ async function askOutputConfig(rl, config) {
|
|
|
3266
3469
|
'GitHub issues or draft PRs are optional and only run when a token plus an inferred repo are available.',
|
|
3267
3470
|
]);
|
|
3268
3471
|
const currentMode = config?.actions?.mode || config?.deliveries?.github?.mode || 'issue';
|
|
3269
|
-
const
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3472
|
+
const configuredDestinations = Array.isArray(config?.actions?.outputDestinations)
|
|
3473
|
+
? config.actions.outputDestinations
|
|
3474
|
+
: [];
|
|
3475
|
+
const currentAutoCreateIssue = Boolean(config?.actions?.autoCreateIssues ||
|
|
3476
|
+
configuredDestinations.includes('github_issue') ||
|
|
3477
|
+
(config?.deliveries?.github?.autoCreate && currentMode !== 'pull_request'));
|
|
3478
|
+
const currentAutoCreatePullRequest = Boolean(config?.actions?.autoCreatePullRequests ||
|
|
3479
|
+
configuredDestinations.includes('github_pull_request') ||
|
|
3480
|
+
(config?.deliveries?.github?.autoCreate && currentMode === 'pull_request'));
|
|
3481
|
+
const outputChoices = await askMultiChoice(rl, {
|
|
3482
|
+
title: 'Output destinations',
|
|
3483
|
+
subtitle: 'Use Up/Down to move, Space to toggle outputs, A to toggle all optional outputs, Enter to continue.',
|
|
3484
|
+
defaultValues: [
|
|
3485
|
+
'chat',
|
|
3486
|
+
...(currentAutoCreateIssue ? ['issue'] : []),
|
|
3487
|
+
...(currentAutoCreatePullRequest ? ['pull_request'] : []),
|
|
3488
|
+
],
|
|
3489
|
+
requiredValues: ['chat'],
|
|
3490
|
+
minSelections: 1,
|
|
3274
3491
|
options: [
|
|
3275
3492
|
{
|
|
3276
3493
|
value: 'chat',
|
|
@@ -3289,9 +3506,11 @@ async function askOutputConfig(rl, config) {
|
|
|
3289
3506
|
},
|
|
3290
3507
|
],
|
|
3291
3508
|
});
|
|
3292
|
-
const
|
|
3293
|
-
const
|
|
3294
|
-
const
|
|
3509
|
+
const wantsIssue = outputChoices.includes('issue');
|
|
3510
|
+
const wantsPullRequest = outputChoices.includes('pull_request');
|
|
3511
|
+
const summaryOnly = !wantsIssue && !wantsPullRequest;
|
|
3512
|
+
const mode = wantsPullRequest ? 'pull_request' : 'issue';
|
|
3513
|
+
const autoCreate = wantsIssue || wantsPullRequest;
|
|
3295
3514
|
if (!summaryOnly) {
|
|
3296
3515
|
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');
|
|
3297
3516
|
}
|
|
@@ -3308,8 +3527,13 @@ async function askOutputConfig(rl, config) {
|
|
|
3308
3527
|
config.actions = {
|
|
3309
3528
|
...(config.actions || {}),
|
|
3310
3529
|
mode,
|
|
3311
|
-
|
|
3312
|
-
|
|
3530
|
+
outputDestinations: [
|
|
3531
|
+
'openclaw_chat',
|
|
3532
|
+
...(wantsIssue ? ['github_issue'] : []),
|
|
3533
|
+
...(wantsPullRequest ? ['github_pull_request'] : []),
|
|
3534
|
+
],
|
|
3535
|
+
autoCreateIssues: wantsIssue,
|
|
3536
|
+
autoCreatePullRequests: wantsPullRequest,
|
|
3313
3537
|
autoCreateWhenGitHubWriteAccess: config.actions?.autoCreateWhenGitHubWriteAccess !== false,
|
|
3314
3538
|
disableAutoCreateGitHubArtifacts: config.actions?.disableAutoCreateGitHubArtifacts === true,
|
|
3315
3539
|
draftPullRequests: true,
|
|
@@ -3327,6 +3551,10 @@ async function askOutputConfig(rl, config) {
|
|
|
3327
3551
|
...(config.deliveries?.github || {}),
|
|
3328
3552
|
enabled: !summaryOnly,
|
|
3329
3553
|
mode,
|
|
3554
|
+
modes: [
|
|
3555
|
+
...(wantsIssue ? ['issue'] : []),
|
|
3556
|
+
...(wantsPullRequest ? ['pull_request'] : []),
|
|
3557
|
+
],
|
|
3330
3558
|
autoCreate,
|
|
3331
3559
|
draftPullRequests: true,
|
|
3332
3560
|
proposalBranchPrefix: config?.actions?.proposalBranchPrefix || 'openclaw/proposals',
|
|
@@ -3419,7 +3647,7 @@ async function askIntervalConfig(rl, config) {
|
|
|
3419
3647
|
const usageMode = await askToolUsage(rl);
|
|
3420
3648
|
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;
|
|
3421
3649
|
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;
|
|
3422
|
-
const cadences = await askCadencePlan(rl);
|
|
3650
|
+
const cadences = await askCadencePlan(rl, currentSchedule.cadences);
|
|
3423
3651
|
config.schedule = {
|
|
3424
3652
|
...currentSchedule,
|
|
3425
3653
|
intervalMinutes,
|