@analyticscli/growth-engineer 0.1.1-preview.19 → 0.1.1-preview.21
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.
|
@@ -2126,6 +2126,9 @@ function getPassingConnectorKeys(payload, failedConnectors = new Set()) {
|
|
|
2126
2126
|
}
|
|
2127
2127
|
function summarizeFailureReason(detail) {
|
|
2128
2128
|
const text = String(detail || '').replace(/\s+/g, ' ').trim();
|
|
2129
|
+
if (/ASC .*\.p8 private key is invalid|invalid or truncated|sequence truncated|malformed/i.test(text)) {
|
|
2130
|
+
return 'ASC .p8 file is invalid or truncated';
|
|
2131
|
+
}
|
|
2129
2132
|
if (/token has been revoked/i.test(text))
|
|
2130
2133
|
return 'token has been revoked';
|
|
2131
2134
|
if (/unauthorized|UNAUTHORIZED/i.test(text))
|
|
@@ -2168,6 +2171,9 @@ function summarizeFailureFix(connector, blockers) {
|
|
|
2168
2171
|
return 'Paste a Coolify base URL and read-only API token from Keys & Tokens / API tokens, then rerun setup.';
|
|
2169
2172
|
}
|
|
2170
2173
|
if (connector === 'asc') {
|
|
2174
|
+
if (/invalid|truncated|malformed|private key/i.test(combined)) {
|
|
2175
|
+
return 'Choose the original valid AuthKey_<KEY_ID>.p8 file, or paste the full key content from BEGIN PRIVATE KEY to END PRIVATE KEY.';
|
|
2176
|
+
}
|
|
2171
2177
|
return 'Rerun ASC setup and verify ASC credentials, key role access, and `asc apps list --output json`.';
|
|
2172
2178
|
}
|
|
2173
2179
|
if (isAccountSignalConnector(connector)) {
|
|
@@ -2216,21 +2222,15 @@ function printConciseSetupBlockers(payload, command, options = {}) {
|
|
|
2216
2222
|
process.stdout.write(`Live checks passed: ${passingConnectors.map(connectorTitle).join(', ')}.\n`);
|
|
2217
2223
|
}
|
|
2218
2224
|
if (groups.size > 0) {
|
|
2219
|
-
process.stdout.write('\nNeeds
|
|
2225
|
+
process.stdout.write('\nNeeds attention:\n');
|
|
2220
2226
|
for (const [connector, connectorBlockers] of groups.entries()) {
|
|
2221
2227
|
const primary = connectorBlockers[0] || {};
|
|
2222
|
-
const reasons = [
|
|
2223
|
-
...new Set(connectorBlockers
|
|
2224
|
-
.map((blocker) => summarizeFailureReason(blocker.detail || blocker.check))
|
|
2225
|
-
.filter(Boolean)),
|
|
2226
|
-
];
|
|
2227
2228
|
process.stdout.write(`- ${connectorTitle(connector)}: ${summarizeFailureReason(primary.detail || primary.check)}\n`);
|
|
2228
|
-
process.stdout.write(` Why: ${reasons.join('; ')}.\n`);
|
|
2229
2229
|
process.stdout.write(` Fix: ${summarizeFailureFix(connector, connectorBlockers)}\n`);
|
|
2230
2230
|
}
|
|
2231
2231
|
}
|
|
2232
2232
|
printDeferredSetupNotes(blockers, focusConnectors);
|
|
2233
|
-
if (groups.size > 0 || !options.hideRerunWhenClean) {
|
|
2233
|
+
if (!options.hideRerun && (groups.size > 0 || !options.hideRerunWhenClean)) {
|
|
2234
2234
|
process.stdout.write(`\nRerun: ${command}\n`);
|
|
2235
2235
|
}
|
|
2236
2236
|
}
|
|
@@ -2375,9 +2375,9 @@ function isDeferredGitHubFailure(failure) {
|
|
|
2375
2375
|
return (name === 'project:github-repo' ||
|
|
2376
2376
|
(name === 'connection:github' && /project\.githubRepo|repo is missing|repo is not configured/i.test(detail)));
|
|
2377
2377
|
}
|
|
2378
|
-
function healthStatusLabel(status) {
|
|
2378
|
+
function healthStatusLabel(status, spinner = '') {
|
|
2379
2379
|
if (status === 'running')
|
|
2380
|
-
return 'running';
|
|
2380
|
+
return spinner ? `running ${spinner}` : 'running';
|
|
2381
2381
|
if (status === 'pass')
|
|
2382
2382
|
return 'done';
|
|
2383
2383
|
if (status === 'warn')
|
|
@@ -2386,18 +2386,27 @@ function healthStatusLabel(status) {
|
|
|
2386
2386
|
return 'needs attention';
|
|
2387
2387
|
if (status === 'deferred')
|
|
2388
2388
|
return 'deferred';
|
|
2389
|
-
return 'pending';
|
|
2389
|
+
return spinner ? `pending ${spinner}` : 'pending';
|
|
2390
2390
|
}
|
|
2391
|
-
function renderHealthProgress(items, message = 'Live checks running...', title = 'Health check') {
|
|
2391
|
+
function renderHealthProgress(items, message = 'Live checks running...', title = 'Health check', options = {}) {
|
|
2392
2392
|
if (process.stdout.isTTY)
|
|
2393
2393
|
clearTerminal();
|
|
2394
|
-
const
|
|
2394
|
+
const final = Boolean(options.final);
|
|
2395
|
+
const visibleItems = final
|
|
2396
|
+
? items.filter((item) => !['pending', 'running'].includes(String(item.status || '')) && item.key !== 'finalize')
|
|
2397
|
+
: items;
|
|
2398
|
+
const finished = visibleItems.filter((item) => !['pending', 'running'].includes(String(item.status || ''))).length;
|
|
2395
2399
|
process.stdout.write(`${title}\n`);
|
|
2396
2400
|
process.stdout.write('------------\n');
|
|
2397
2401
|
process.stdout.write(`${message}\n\n`);
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2402
|
+
if (final) {
|
|
2403
|
+
process.stdout.write('Checks complete.\n\n');
|
|
2404
|
+
}
|
|
2405
|
+
else {
|
|
2406
|
+
process.stdout.write(`${finished}/${visibleItems.length} checks finished.\n\n`);
|
|
2407
|
+
}
|
|
2408
|
+
for (const item of visibleItems) {
|
|
2409
|
+
process.stdout.write(`[${healthStatusLabel(item.status, options.spinner || '')}] ${item.label}: ${item.detail}\n`);
|
|
2401
2410
|
}
|
|
2402
2411
|
}
|
|
2403
2412
|
function updateHealthProgress(items, event) {
|
|
@@ -2508,22 +2517,35 @@ function updateProgressItem(items, key, status, detail) {
|
|
|
2508
2517
|
}
|
|
2509
2518
|
async function runSetupCommandWithProgress(command, env, selected, message) {
|
|
2510
2519
|
const plan = buildSetupTestProgressPlan(selected);
|
|
2511
|
-
|
|
2520
|
+
const spinnerFrames = ['-', '\\', '|', '/'];
|
|
2521
|
+
let spinnerIndex = 0;
|
|
2522
|
+
let currentMessage = message;
|
|
2523
|
+
const render = (nextMessage = currentMessage, options = {}) => {
|
|
2524
|
+
currentMessage = nextMessage;
|
|
2525
|
+
renderHealthProgress(plan, currentMessage, 'Connector setup test', {
|
|
2526
|
+
...options,
|
|
2527
|
+
spinner: spinnerFrames[spinnerIndex++ % spinnerFrames.length],
|
|
2528
|
+
});
|
|
2529
|
+
};
|
|
2530
|
+
render(message);
|
|
2531
|
+
const spinnerInterval = process.stdout.isTTY
|
|
2532
|
+
? setInterval(() => render(currentMessage), 800)
|
|
2533
|
+
: null;
|
|
2512
2534
|
const progressCommand = command.includes('--progress-json') ? command : `${command} --progress-json`;
|
|
2513
2535
|
const result = await runCommandCaptureWithProgress(progressCommand, (event) => {
|
|
2514
|
-
if (updateHealthProgress(plan, event))
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
const message = primaryFinished
|
|
2520
|
-
? 'Checks finished. Finalizing result; do not close this terminal yet.'
|
|
2521
|
-
: 'Connector setup test is still running. Do not close this terminal yet.';
|
|
2522
|
-
renderHealthProgress(plan, message, 'Connector setup test');
|
|
2536
|
+
if (!updateHealthProgress(plan, event))
|
|
2537
|
+
return;
|
|
2538
|
+
const primaryFinished = primaryProgressItemsFinished(plan);
|
|
2539
|
+
if (primaryFinished) {
|
|
2540
|
+
updateProgressItem(plan, 'finalize', 'running', 'finishing');
|
|
2523
2541
|
}
|
|
2524
|
-
|
|
2542
|
+
render(primaryFinished ? 'Finishing setup test...' : 'Testing connector setup...');
|
|
2543
|
+
}, { env, timeoutMs: 180_000 }).finally(() => {
|
|
2544
|
+
if (spinnerInterval)
|
|
2545
|
+
clearInterval(spinnerInterval);
|
|
2546
|
+
});
|
|
2525
2547
|
updateProgressItem(plan, 'finalize', 'pass', 'result received');
|
|
2526
|
-
renderHealthProgress(plan, 'Connector setup test finished.', 'Connector setup test');
|
|
2548
|
+
renderHealthProgress(plan, 'Connector setup test finished.', 'Connector setup test', { final: true });
|
|
2527
2549
|
return result;
|
|
2528
2550
|
}
|
|
2529
2551
|
async function saveSecretsImmediately(secrets) {
|
|
@@ -2556,6 +2578,7 @@ async function runImmediateConnectorHealthCheck({ rl, configPath, connector, sec
|
|
|
2556
2578
|
printConciseSetupBlockers(payload, command, {
|
|
2557
2579
|
focusConnectors: [connector],
|
|
2558
2580
|
hideRerunWhenClean: true,
|
|
2581
|
+
hideRerun: true,
|
|
2559
2582
|
});
|
|
2560
2583
|
const retry = await askYesNo(rl, `Re-enter ${connectorLabel(connector)} configuration now?`, true);
|
|
2561
2584
|
return { ok: false, retry, result, payload };
|
|
@@ -3529,11 +3552,9 @@ function bold(text) {
|
|
|
3529
3552
|
}
|
|
3530
3553
|
async function guideGitHubConnector(rl, secrets) {
|
|
3531
3554
|
printSection('GitHub code access', [
|
|
3532
|
-
'
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
'Open the token page, select the scopes you want, then paste the token here.',
|
|
3536
|
-
'You can rerun this wizard later to change GitHub permissions.',
|
|
3555
|
+
`${bold('Create token')} here: https://github.com/settings/tokens/new`,
|
|
3556
|
+
`${bold('Scopes')}: public repos = public_repo, private repos/issues/PRs = repo.`,
|
|
3557
|
+
`${bold('Only add workflow')} if OpenClaw should edit GitHub Actions files.`,
|
|
3537
3558
|
]);
|
|
3538
3559
|
let hasGh = await commandExists('gh');
|
|
3539
3560
|
if (!hasGh) {
|
|
@@ -3542,15 +3563,6 @@ async function guideGitHubConnector(rl, secrets) {
|
|
|
3542
3563
|
if (hasGh) {
|
|
3543
3564
|
process.stdout.write('GitHub CLI is available for helper commands.\n\n');
|
|
3544
3565
|
}
|
|
3545
|
-
process.stdout.write('Token URL: https://github.com/settings/tokens/new\n\n');
|
|
3546
|
-
process.stdout.write(`${ANSI.bold}Suggested scopes${ANSI.reset}\n`);
|
|
3547
|
-
printBullets([
|
|
3548
|
-
'Public repo only: select `public_repo`.',
|
|
3549
|
-
'Private repo access: select `repo` (classic GitHub tokens make private repo access broad).',
|
|
3550
|
-
'Create issues / draft PRs in private repos: `repo` is the relevant classic-token scope.',
|
|
3551
|
-
'Edit GitHub Actions workflow files: add `workflow` only if you explicitly want this.',
|
|
3552
|
-
'Usually do not select: packages, admin:org, hooks, gist, user, delete_repo, enterprise, codespace, copilot.',
|
|
3553
|
-
]);
|
|
3554
3566
|
const token = await maybePromptSecret(rl, 'Paste GITHUB_TOKEN into this local terminal', 'GITHUB_TOKEN');
|
|
3555
3567
|
if (token)
|
|
3556
3568
|
secrets.GITHUB_TOKEN = token;
|
|
@@ -3571,12 +3583,10 @@ function shouldForceFreshAnalyticsToken(healthByConnector = {}) {
|
|
|
3571
3583
|
return ['blocked', 'partial'].includes(String(health?.status || '')) || /revoked|unauthorized|invalid token/i.test(detail);
|
|
3572
3584
|
}
|
|
3573
3585
|
async function guideAnalyticsConnector(rl, secrets, options = {}) {
|
|
3574
|
-
printSection('AnalyticsCLI'
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
process.stdout.write('3. Create Access Token\n');
|
|
3579
|
-
process.stdout.write('4. Copy the Readonly CLI Token and paste it below\n\n');
|
|
3586
|
+
printSection('AnalyticsCLI', [
|
|
3587
|
+
`${bold('Create readonly CLI token')}: https://dash.analyticscli.com/`,
|
|
3588
|
+
`${bold('Path')}: Account -> API Keys -> Create Access Token.`,
|
|
3589
|
+
]);
|
|
3580
3590
|
const forceFresh = Boolean(options.forceFresh);
|
|
3581
3591
|
if (forceFresh && process.env.ANALYTICSCLI_ACCESS_TOKEN) {
|
|
3582
3592
|
process.stdout.write('Stored token failed. Paste a new token.\n\n');
|
|
@@ -3593,17 +3603,9 @@ async function guideAnalyticsConnector(rl, secrets, options = {}) {
|
|
|
3593
3603
|
}
|
|
3594
3604
|
async function guideRevenueCatConnector(rl, secrets) {
|
|
3595
3605
|
printSection('RevenueCat monetization data', [
|
|
3596
|
-
'
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
printBullets([
|
|
3600
|
-
'Select your app.',
|
|
3601
|
-
'In the sidebar, choose "Apps & providers".',
|
|
3602
|
-
'Click "API keys" and generate a new secret API key.',
|
|
3603
|
-
'Name it "analyticscli" and choose API version 2.',
|
|
3604
|
-
'Set Charts metrics permissions to read.',
|
|
3605
|
-
'Set Customer information permissions to read.',
|
|
3606
|
-
'Set Project configuration permissions to read.',
|
|
3606
|
+
`${bold('Create secret API key')}: https://app.revenuecat.com/`,
|
|
3607
|
+
`${bold('Path')}: Apps & providers -> API keys -> New secret key.`,
|
|
3608
|
+
`${bold('Permissions')}: API v2, read for Charts metrics, Customer information, Project configuration.`,
|
|
3607
3609
|
]);
|
|
3608
3610
|
const apiKey = await maybePromptSecret(rl, 'Paste REVENUECAT_API_KEY into this local terminal', 'REVENUECAT_API_KEY');
|
|
3609
3611
|
if (apiKey)
|
|
@@ -3626,17 +3628,9 @@ function paddleTokenEnvForAccount(index, label) {
|
|
|
3626
3628
|
}
|
|
3627
3629
|
async function guidePaddleConnector(rl, secrets) {
|
|
3628
3630
|
printSection('Paddle Billing metrics', [
|
|
3629
|
-
'
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
printBullets([
|
|
3633
|
-
'Open Paddle > Developer Tools > Authentication.',
|
|
3634
|
-
'Use the API keys tab and create a new live API key.',
|
|
3635
|
-
'Minimum: grant `metrics.read` so account-level revenue, MRR, refunds, chargebacks, subscribers, and checkout conversion work.',
|
|
3636
|
-
'Recommended for better Growth Engineer analysis: grant all available read-only permissions (`*.read`), including products, prices, discounts, customers, transactions, subscriptions, adjustments, reports, and notifications.',
|
|
3637
|
-
'Do not grant any write permissions (`*.write`) unless another workflow explicitly needs them.',
|
|
3638
|
-
'Do not select or hard-code a single product in the wizard; the Growth Engineer should keep account-level metrics context.',
|
|
3639
|
-
'Paste the key here so it is stored only in the local chmod 600 secrets file.',
|
|
3631
|
+
`${bold('Create live API key')}: https://vendors.paddle.com/authentication-v2`,
|
|
3632
|
+
`${bold('Minimum')}: metrics.read. Better: all read-only *.read scopes.`,
|
|
3633
|
+
`${bold('Do not grant write scopes')} unless you explicitly need them elsewhere.`,
|
|
3640
3634
|
]);
|
|
3641
3635
|
const accounts = [];
|
|
3642
3636
|
let index = 0;
|
|
@@ -3663,17 +3657,10 @@ async function guidePaddleConnector(rl, secrets) {
|
|
|
3663
3657
|
}
|
|
3664
3658
|
async function guideSeoConnector(rl, secrets) {
|
|
3665
3659
|
printSection('SEO / Google Search Console / DataForSEO', [
|
|
3666
|
-
'
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
'Preferred: give the token/service account access to all Search Console properties you want analyzed.',
|
|
3671
|
-
'Leave the property URL empty to let the exporter list and query all verified GSC properties in the account.',
|
|
3672
|
-
'Enter a property URL only when you intentionally want to restrict analysis to one site.',
|
|
3673
|
-
'For OAuth token mode, paste a read-only Search Console token with `webmasters.readonly` scope.',
|
|
3674
|
-
'For service-account mode, add the service account email as a restricted/full user in Search Console, then set GOOGLE_APPLICATION_CREDENTIALS or GSC_SERVICE_ACCOUNT_JSON outside this wizard.',
|
|
3675
|
-
'DataForSEO is optional and paid. The exporter refuses paid calls unless the source command includes --confirm-paid and a small --max-paid-requests cap.',
|
|
3676
|
-
'CSV-only mode is also supported with --gsc-csv or --csv in sources.seo.command.',
|
|
3660
|
+
`${bold('GSC')}: https://search.google.com/search-console`,
|
|
3661
|
+
`${bold('Service account')}: https://console.cloud.google.com/iam-admin/serviceaccounts`,
|
|
3662
|
+
`${bold('Optional paid keyword data')}: https://app.dataforseo.com/api-dashboard`,
|
|
3663
|
+
`${bold('Default')}: leave property URL empty to use all verified GSC properties.`,
|
|
3677
3664
|
]);
|
|
3678
3665
|
const siteUrl = await ask(rl, 'Optional GSC property URL (empty = all verified properties)', process.env.GSC_SITE_URL || '');
|
|
3679
3666
|
if (siteUrl.trim())
|
|
@@ -3772,11 +3759,10 @@ async function guideAccountSignalConnector(rl, secrets, key) {
|
|
|
3772
3759
|
if (!definition)
|
|
3773
3760
|
return [];
|
|
3774
3761
|
printSection(definition.label, [
|
|
3775
|
-
definition.
|
|
3776
|
-
'Setup is account-wide. Do not paste project IDs, app IDs, product IDs, package names, paywall IDs, service names, or tags here
|
|
3762
|
+
`${bold('Docs')}: ${definition.docsUrl}`,
|
|
3763
|
+
`${bold('Setup is account-wide')}. Do not paste project IDs, app IDs, product IDs, package names, paywall IDs, service names, or tags here.`,
|
|
3764
|
+
`${bold('Paste only credentials')} below. The agent discovers accounts/apps/projects later.`,
|
|
3777
3765
|
]);
|
|
3778
|
-
process.stdout.write(`Docs: ${definition.docsUrl}\n\n`);
|
|
3779
|
-
printBullets(definition.steps);
|
|
3780
3766
|
const accounts = [];
|
|
3781
3767
|
let index = 0;
|
|
3782
3768
|
while (true) {
|
|
@@ -3816,8 +3802,8 @@ async function guideAccountSignalConnector(rl, secrets, key) {
|
|
|
3816
3802
|
}
|
|
3817
3803
|
async function guideSentryConnector(rl, secrets) {
|
|
3818
3804
|
printSection('Sentry / GlitchTip', [
|
|
3819
|
-
'
|
|
3820
|
-
'
|
|
3805
|
+
`${bold('Base URL')}: https://sentry.io for Sentry Cloud, otherwise your GlitchTip/self-hosted URL.`,
|
|
3806
|
+
`${bold('Token + org')} are needed. Project scope remains unpinned.`,
|
|
3821
3807
|
]);
|
|
3822
3808
|
const accounts = [];
|
|
3823
3809
|
let index = 0;
|
|
@@ -3869,7 +3855,7 @@ async function guideSentryConnector(rl, secrets) {
|
|
|
3869
3855
|
let verifiedVisibleProjects = false;
|
|
3870
3856
|
if (discovery.ok && discovery.projects.length > 0) {
|
|
3871
3857
|
verifiedVisibleProjects = true;
|
|
3872
|
-
process.stdout.write(`Found ${discovery.projects.length} visible project(s). Project scope remains unpinned
|
|
3858
|
+
process.stdout.write(`Found ${discovery.projects.length} visible project(s). Project scope remains unpinned.\n`);
|
|
3873
3859
|
}
|
|
3874
3860
|
else {
|
|
3875
3861
|
const fallbackOrgs = discoveredOrganizations
|
|
@@ -3926,19 +3912,12 @@ function normalizeCoolifyBaseUrl(value) {
|
|
|
3926
3912
|
}
|
|
3927
3913
|
async function guideCoolifyConnector(rl, secrets) {
|
|
3928
3914
|
printSection('Coolify deployment monitoring', [
|
|
3929
|
-
'
|
|
3930
|
-
'
|
|
3915
|
+
`${bold('Create read-only API token')} in Coolify.`,
|
|
3916
|
+
`${bold('Do not use * or sensitive-token permissions')} for normal monitoring.`,
|
|
3931
3917
|
]);
|
|
3932
3918
|
const baseUrl = normalizeCoolifyBaseUrl(await ask(rl, 'Coolify base URL', process.env.COOLIFY_BASE_URL || 'https://coolify.wotaso.com'));
|
|
3933
3919
|
const tokenUrl = baseUrl ? `${baseUrl}/security/api-tokens` : 'https://<your-coolify-host>/security/api-tokens';
|
|
3934
|
-
process.stdout.write(
|
|
3935
|
-
printBullets([
|
|
3936
|
-
'Open the Coolify dashboard.',
|
|
3937
|
-
'In the sidebar, go to "Keys & Tokens".',
|
|
3938
|
-
'Open "API tokens".',
|
|
3939
|
-
'Create a new API key/token with read-only permissions.',
|
|
3940
|
-
'Copy the token once and paste it into this local terminal.',
|
|
3941
|
-
]);
|
|
3920
|
+
process.stdout.write(`${bold('Token page')}: ${tokenUrl}\n\n`);
|
|
3942
3921
|
const token = await maybePromptSecret(rl, 'Paste COOLIFY_API_TOKEN into this local terminal', 'COOLIFY_API_TOKEN');
|
|
3943
3922
|
if (baseUrl)
|
|
3944
3923
|
secrets.COOLIFY_BASE_URL = baseUrl;
|
|
@@ -3955,7 +3934,7 @@ async function guideAscConnector(rl, secrets) {
|
|
|
3955
3934
|
process.stdout.write(`${bold('Enter the Reports key now:')}\n`);
|
|
3956
3935
|
printBullets([
|
|
3957
3936
|
`${bold('.p8 path')} to Apple\'s original ${bold('AuthKey_<KEY_ID>.p8')} file. ${bold('Do not rename it')}; KEY_ID is read from the filename.`,
|
|
3958
|
-
`${bold('Issuer ID')} from the API keys page.`,
|
|
3937
|
+
`${bold('Issuer ID')} from the API keys page. Same value for both keys.`,
|
|
3959
3938
|
`${bold('Vendor Number')} from Sales and Trends > Reports.`,
|
|
3960
3939
|
]);
|
|
3961
3940
|
const normalKeyPath = await askAscPrivateKeyPathWithKeyId(rl, {
|
|
@@ -3969,7 +3948,7 @@ async function guideAscConnector(rl, secrets) {
|
|
|
3969
3948
|
secrets.ASC_KEY_ID = keyId;
|
|
3970
3949
|
process.stdout.write(`Inferred ASC_KEY_ID=${keyId} from ${path.basename(normalKeyPath.privateKeyPath)}.\n`);
|
|
3971
3950
|
}
|
|
3972
|
-
const issuerId = await ask(rl, 'ASC_ISSUER_ID (
|
|
3951
|
+
const issuerId = await ask(rl, 'ASC_ISSUER_ID (same for both keys, empty = skip)', process.env.ASC_ISSUER_ID || '');
|
|
3973
3952
|
if (issuerId.trim())
|
|
3974
3953
|
secrets.ASC_ISSUER_ID = issuerId.trim();
|
|
3975
3954
|
if (!normalKeyPath.privateKeyPath) {
|
|
@@ -4007,7 +3986,10 @@ async function guideAscBootstrapAdminKey(rl, issuerIdDefault = '') {
|
|
|
4007
3986
|
keyLabel: 'the temporary Admin key',
|
|
4008
3987
|
});
|
|
4009
3988
|
let bootstrapKeyId = bootstrapKeyPath.keyId;
|
|
4010
|
-
|
|
3989
|
+
let bootstrapIssuerId = String(issuerIdDefault || '').trim();
|
|
3990
|
+
if (!bootstrapIssuerId) {
|
|
3991
|
+
bootstrapIssuerId = await ask(rl, 'ASC_ISSUER_ID (same API keys page)', process.env.ASC_ISSUER_ID || '');
|
|
3992
|
+
}
|
|
4011
3993
|
if (bootstrapKeyPath.privateKeyPath) {
|
|
4012
3994
|
bootstrapEnv.ASC_BOOTSTRAP_PRIVATE_KEY_PATH = bootstrapKeyPath.privateKeyPath;
|
|
4013
3995
|
process.stdout.write(`Inferred ASC_BOOTSTRAP_KEY_ID=${bootstrapKeyId} from ${path.basename(bootstrapKeyPath.privateKeyPath)}.\n`);
|