Conversation
Contributor
🧙 Wizard CIRun the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands: Test all apps:
Test all apps in a directory:
Test an individual app:
Show more apps
Results will be posted here when complete. |
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Strict mode rejects documented env vars on subcommands
- Added hidden global option shadows for
agent,install-dir, andclassic(matching the existing pattern fordev,log, andtoken) so that.strict()accepts env-var-injected values on all subcommands.
- Added hidden global option shadows for
Or push these changes by commenting:
@cursor push 7270fe2378
Preview (7270fe2378)
diff --git a/bin.ts b/bin.ts
--- a/bin.ts
+++ b/bin.ts
@@ -98,7 +98,7 @@
// Dynamic import to avoid preloading wizard-session.ts as CJS, which
// prevents the TUI's ESM dynamic imports from resolving named exports.
const lazyRunWizard = async (
- ...args: Parameters<typeof import('./src/run')['runWizard']>
+ ...args: Parameters<(typeof import('./src/run'))['runWizard']>
) => {
const { runWizard } = await import('./src/run.js');
return runWizard(...args);
@@ -198,9 +198,8 @@
// the project API key.
const envAccessToken =
process.env.AMPLITUDE_TOKEN ?? process.env.AMPLITUDE_WIZARD_TOKEN;
- const { resolveCredentials, resolveEnvironmentSelection } = await import(
- './src/lib/credential-resolution.js'
- );
+ const { resolveCredentials, resolveEnvironmentSelection } =
+ await import('./src/lib/credential-resolution.js');
await resolveCredentials(session, {
requireOrgId: false,
org: options.org as string | undefined,
@@ -652,9 +651,9 @@
type: 'boolean',
},
// Hidden shadows of env-only flags. .env('AMPLITUDE_WIZARD') auto-maps
- // AMPLITUDE_WIZARD_DEV / _LOG / _TOKEN to these option names; declaring
- // them here lets .strict() accept the env-var invocation. The actual
- // values are read via process.env elsewhere.
+ // AMPLITUDE_WIZARD_DEV / _LOG / _TOKEN / _AGENT / _INSTALL_DIR / _CLASSIC
+ // to these option names; declaring them here lets .strict() accept the
+ // env-var injection on every command (not just $0 where they're visible).
dev: {
hidden: true,
describe: 'internal: AMPLITUDE_WIZARD_DEV env-var passthrough',
@@ -670,6 +669,21 @@
describe: 'internal: AMPLITUDE_WIZARD_TOKEN env-var passthrough',
type: 'string',
},
+ agent: {
+ hidden: true,
+ describe: 'internal: AMPLITUDE_WIZARD_AGENT env-var passthrough',
+ type: 'boolean',
+ },
+ 'install-dir': {
+ hidden: true,
+ describe: 'internal: AMPLITUDE_WIZARD_INSTALL_DIR env-var passthrough',
+ type: 'string',
+ },
+ classic: {
+ hidden: true,
+ describe: 'internal: AMPLITUDE_WIZARD_CLASSIC env-var passthrough',
+ type: 'boolean',
+ },
})
.command(
['$0'],
@@ -815,9 +829,8 @@
// If --api-key was provided, skip the OAuth/TUI auth flow entirely.
if (session.apiKey) {
- const { DEFAULT_HOST_URL } = await import(
- './src/lib/constants.js'
- );
+ const { DEFAULT_HOST_URL } =
+ await import('./src/lib/constants.js');
session.credentials = {
accessToken: session.apiKey,
projectApiKey: session.apiKey,
@@ -830,9 +843,8 @@
const { logToFile } = await import('./src/utils/debug.js');
// Check for crash-recovery checkpoint
- const { loadCheckpoint } = await import(
- './src/lib/session-checkpoint.js'
- );
+ const { loadCheckpoint } =
+ await import('./src/lib/session-checkpoint.js');
const checkpoint = loadCheckpoint(session.installDir);
if (checkpoint) {
Object.assign(session, checkpoint);
@@ -845,21 +857,18 @@
// Resolve credentials using shared logic (token refresh,
// env auto-select, pendingOrgs population)
- const { resolveCredentials } = await import(
- './src/lib/credential-resolution.js'
- );
+ const { resolveCredentials } =
+ await import('./src/lib/credential-resolution.js');
await resolveCredentials(session);
// Resolve org/workspace display names so /whoami shows them.
// Also extracts the numeric analytics project ID for MCP event detection.
// Fire-and-forget so it doesn't block startup.
if (session.region && session.selectedOrgId) {
- const { getStoredUser, getStoredToken } = await import(
- './src/utils/ampli-settings.js'
- );
- const { fetchAmplitudeUser, extractAppId } = await import(
- './src/lib/api.js'
- );
+ const { getStoredUser, getStoredToken } =
+ await import('./src/utils/ampli-settings.js');
+ const { fetchAmplitudeUser, extractAppId } =
+ await import('./src/lib/api.js');
const storedUser = getStoredUser();
const realUser =
storedUser && storedUser.id !== 'pending' ? storedUser : null;
@@ -915,9 +924,9 @@
changed = true;
// Fall back to the first workspace if the stored ID is stale.
const ws = session.selectedWorkspaceId
- ? org.workspaces.find(
+ ? (org.workspaces.find(
(w) => w.id === session.selectedWorkspaceId,
- ) ?? org.workspaces[0]
+ ) ?? org.workspaces[0])
: org.workspaces[0];
if (ws) {
session.selectedWorkspaceName = ws.name;
@@ -958,9 +967,8 @@
// Dynamic-import keeps the Claude Agent SDK out of bin.ts load.
try {
const fs = await import('fs');
- const { parseEventPlanContent } = await import(
- './src/lib/agent-interface.js'
- );
+ const { parseEventPlanContent } =
+ await import('./src/lib/agent-interface.js');
const evtPath = resolve(
session.installDir,
'.amplitude-events.json',
@@ -978,9 +986,8 @@
}
// Initialize Amplitude Experiment feature flags (non-blocking).
- const { initFeatureFlags } = await import(
- './src/lib/feature-flags.js'
- );
+ const { initFeatureFlags } =
+ await import('./src/lib/feature-flags.js');
await initFeatureFlags().catch(() => {
// Flag init failure is non-fatal — all flags default to off
});
@@ -988,18 +995,16 @@
// Apply SDK-level opt-out based on feature flags
analytics.applyOptOut();
- const { FRAMEWORK_REGISTRY } = await import(
- './src/lib/registry.js'
- );
+ const { FRAMEWORK_REGISTRY } =
+ await import('./src/lib/registry.js');
const { detectAllFrameworks } = await import('./src/run.js');
const installDir = session.installDir ?? process.cwd();
// Verbose startup diagnostics — always written to the log file;
// visible in the RunScreen "Logs" tab.
if (session.verbose || session.debug) {
- const { enableDebugLogs, logToFile } = await import(
- './src/utils/debug.js'
- );
+ const { enableDebugLogs, logToFile } =
+ await import('./src/utils/debug.js');
enableDebugLogs();
logToFile('[verbose] Amplitude Wizard starting');
logToFile(`[verbose] node : ${process.version}`);
@@ -1009,9 +1014,8 @@
logToFile(`[verbose] argv : ${process.argv.join(' ')}`);
}
- const { DETECTION_TIMEOUT_MS } = await import(
- './src/lib/constants.js'
- );
+ const { DETECTION_TIMEOUT_MS } =
+ await import('./src/lib/constants.js');
// ── OAuth + account setup ──────────────────────────────
// Runs concurrently with framework detection while AuthScreen shows.
@@ -1025,19 +1029,15 @@
if (tui.store.session.credentials !== null) return;
try {
- const { ampliConfigExists } = await import(
- './src/lib/ampli-config.js'
- );
- const { performAmplitudeAuth } = await import(
- './src/utils/oauth.js'
- );
+ const { ampliConfigExists } =
+ await import('./src/lib/ampli-config.js');
+ const { performAmplitudeAuth } =
+ await import('./src/utils/oauth.js');
const { fetchAmplitudeUser } = await import('./src/lib/api.js');
- const { DEFAULT_AMPLITUDE_ZONE } = await import(
- './src/lib/constants.js'
- );
- const { storeToken } = await import(
- './src/utils/ampli-settings.js'
- );
+ const { DEFAULT_AMPLITUDE_ZONE } =
+ await import('./src/lib/constants.js');
+ const { storeToken } =
+ await import('./src/utils/ampli-settings.js');
const forceFresh = !ampliConfigExists(installDir);
@@ -1198,9 +1198,8 @@
};
const depNames = Object.keys(allDeps);
- const { DiscoveredFeature } = await import(
- './src/lib/wizard-session.js'
- );
+ const { DiscoveredFeature } =
+ await import('./src/lib/wizard-session.js');
if (
depNames.some((d) =>
@@ -1212,12 +1211,10 @@
// LLM SDK detection — sourced from Amplitude LLM analytics skill
// Gated by the wizard-llm-analytics feature flag.
- const { isFlagEnabled } = await import(
- './src/lib/feature-flags.js'
- );
- const { FLAG_LLM_ANALYTICS } = await import(
- './src/lib/feature-flags.js'
- );
+ const { isFlagEnabled } =
+ await import('./src/lib/feature-flags.js');
+ const { FLAG_LLM_ANALYTICS } =
+ await import('./src/lib/feature-flags.js');
if (isFlagEnabled(FLAG_LLM_ANALYTICS)) {
const LLM_PACKAGES = [
'openai',
@@ -1254,9 +1251,8 @@
// Session checkpointing — save at key transitions so crash
// recovery can skip already-completed steps.
- const { saveCheckpoint, clearCheckpoint } = await import(
- './src/lib/session-checkpoint.js'
- );
+ const { saveCheckpoint, clearCheckpoint } =
+ await import('./src/lib/session-checkpoint.js');
// After auth completes (most expensive step to repeat)
tui.store.onEnterScreen(Screen.DataSetup, () => {
saveCheckpoint(tui.store.session);
@@ -1327,9 +1323,8 @@
// Before calling the AI agent, do a quick static check to see if
// Amplitude is already installed in the project. If so, skip the
// agent entirely and advance directly to MCP setup.
- const { detectAmplitudeInProject } = await import(
- './src/lib/detect-amplitude.js'
- );
+ const { detectAmplitudeInProject } =
+ await import('./src/lib/detect-amplitude.js');
const localDetection = detectAmplitudeInProject(installDir);
if (localDetection.confidence !== 'none') {
@@ -1339,9 +1334,8 @@
localDetection.reason ?? 'unknown'
}) — prompting on MCP screen (continue vs run wizard)`,
);
- const { RunPhase, OutroKind } = await import(
- './src/lib/wizard-session.js'
- );
+ const { RunPhase, OutroKind } =
+ await import('./src/lib/wizard-session.js');
tui.store.setAmplitudePreDetected();
tui.store.setRunPhase(RunPhase.Completed);
const runWizardAnyway =
@@ -1399,9 +1393,8 @@
const zone = argv.zone as 'us' | 'eu';
try {
- const { getStoredUser, getStoredToken } = await import(
- './src/utils/ampli-settings.js'
- );
+ const { getStoredUser, getStoredToken } =
+ await import('./src/utils/ampli-settings.js');
// If a valid cached session exists, display the stored user without
// re-fetching from the API (the cached idToken may be expired).
const cachedToken = getStoredToken(undefined, zone);
@@ -1464,13 +1457,11 @@
() => {},
(argv) => {
void (async () => {
- const { getStoredUser, clearStoredCredentials } = await import(
- './src/utils/ampli-settings.js'
- );
+ const { getStoredUser, clearStoredCredentials } =
+ await import('./src/utils/ampli-settings.js');
const { clearApiKey } = await import('./src/utils/api-key-store.js');
- const { clearCheckpoint } = await import(
- './src/lib/session-checkpoint.js'
- );
+ const { clearCheckpoint } =
+ await import('./src/lib/session-checkpoint.js');
const installDir =
(argv.installDir as string | undefined) ?? process.cwd();
const user = getStoredUser();
@@ -1497,9 +1488,8 @@
() => {},
(_argv) => {
void (async () => {
- const { getStoredUser, getStoredToken } = await import(
- './src/utils/ampli-settings.js'
- );
+ const { getStoredUser, getStoredToken } =
+ await import('./src/utils/ampli-settings.js');
const user = getStoredUser();
const token = getStoredToken();
if (user && token && user.id !== 'pending') {
@@ -1555,9 +1545,8 @@
return;
}
try {
- const { trackWizardFeedback } = await import(
- './src/utils/track-wizard-feedback.js'
- );
+ const { trackWizardFeedback } =
+ await import('./src/utils/track-wizard-feedback.js');
await trackWizardFeedback(message);
console.log(chalk.green('✔ Thanks — your feedback was sent.'));
process.exit(0);
@@ -1963,9 +1952,8 @@
void (async () => {
try {
const { startTUI } = await import('./src/ui/tui/start-tui.js');
- const { buildSession } = await import(
- './src/lib/wizard-session.js'
- );
+ const { buildSession } =
+ await import('./src/lib/wizard-session.js');
const { Flow } = await import('./src/ui/tui/router.js');
const tui = startTUI(WIZARD_VERSION, Flow.McpAdd);
@@ -1977,9 +1965,8 @@
} catch {
// TUI unavailable — fallback to logging
setUI(new LoggingUI());
- const { addMCPServerToClientsStep } = await import(
- './src/steps/add-mcp-server-to-clients/index.js'
- );
+ const { addMCPServerToClientsStep } =
+ await import('./src/steps/add-mcp-server-to-clients/index.js');
await addMCPServerToClientsStep({
local: options.local,
});
@@ -2005,9 +1992,8 @@
void (async () => {
try {
const { startTUI } = await import('./src/ui/tui/start-tui.js');
- const { buildSession } = await import(
- './src/lib/wizard-session.js'
- );
+ const { buildSession } =
+ await import('./src/lib/wizard-session.js');
const { Flow } = await import('./src/ui/tui/router.js');
const tui = startTUI(WIZARD_VERSION, Flow.McpRemove);
@@ -2019,9 +2005,8 @@
} catch {
// TUI unavailable — fallback to logging
setUI(new LoggingUI());
- const { removeMCPServerFromClientsStep } = await import(
- './src/steps/add-mcp-server-to-clients/index.js'
- );
+ const { removeMCPServerFromClientsStep } =
+ await import('./src/steps/add-mcp-server-to-clients/index.js');
await removeMCPServerFromClientsStep({
local: options.local,
});
@@ -2036,9 +2021,8 @@
() => {
void (async () => {
try {
- const { startAgentMcpServer } = await import(
- './src/lib/wizard-mcp-server.js'
- );
+ const { startAgentMcpServer } =
+ await import('./src/lib/wizard-mcp-server.js');
await startAgentMcpServer();
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
@@ -2059,9 +2043,8 @@
() => {},
() => {
void (async () => {
- const { getAgentManifest } = await import(
- './src/lib/agent-manifest.js'
- );
+ const { getAgentManifest } =
+ await import('./src/lib/agent-manifest.js');
process.stdout.write(
JSON.stringify(getAgentManifest(), null, 2) + '\n',
);You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit e06881d. Configure here.
Collaborator
Author
2876557 to
c919d4a
Compare
The Claude Agent SDK caches the system prompt implicitly, but per-session dynamic sections (cwd, date, auto-memory, git status) were invalidating the cache on every run. Setting excludeDynamicSections: true moves those into the first user message so the static preset + our ~3KB commandments block stays cacheable across runs and machines. Impact is measurable via cache_read_input_tokens in benchmarks/cache-tracker.ts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completed Q&A turns are now rendered through Ink's <Static> component, so they're written once above the live region and survive TUI exit (terminal scrollback). Previously only the latest response was shown inline and got clobbered on each new question; long responses also displaced the wizard screen entirely. This mirrors the append-only pattern Claude Code uses for completed agent output. Also removes the now-unnecessary responseIsLong preview/dismissal branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.strict() rejects unknown flags and subcommands, catching typos like --app-ids that would previously fall through silently. .check() validates --app-id as a positive integer so a bad value fails fast with a yargs-native error instead of becoming 0 downstream. The feedback command's positional message is now declared via [words..] / .positional() so it survives strict parsing. Middleware for consolidating credential resolution is paired with the upcoming bin.ts command-module split. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… them
.env('AMPLITUDE_WIZARD') auto-maps AMPLITUDE_WIZARD_DEV / _LOG / _TOKEN to
--dev / --log / --token. With .strict() enabled in the prior commit, these
undeclared options were being rejected, breaking pnpm try / pnpm try:prod
with "Unknown argument: dev".
Declare them as hidden options so strict accepts the env-var invocation.
Actual values are still read via process.env elsewhere — this is purely a
strict-mode compatibility shim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…v vars
.env('AMPLITUDE_WIZARD') auto-maps all AMPLITUDE_WIZARD_* env vars into
argv regardless of which command is active. With .strict() enabled, any
env var that maps to an option only declared in the $0 command builder
(agent, install-dir, classic) would cause yargs to reject it as unknown
when running subcommands like login, feedback, or detect.
Hidden global shadows were already added for dev, log, and token but
agent, install-dir, and classic were missed. This adds the same
treatment for all three.
Applied via @cursor push command
c919d4a to
b9ff4a9
Compare
This was referenced Apr 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Three independent improvements surfaced by a library-audit pass — each pulls on a feature we already had installed but weren't using. Grouped in one PR because they're small and share context, but each is an isolated commit so they can be split/reverted independently.
feat(agent)— Enable prompt caching on the system prompt viaexcludeDynamicSections: true. The Claude Agent SDK caches implicitly, but per-session dynamic sections (cwd, date, git status) were busting the cache every run. Moves them into the first user message so the static preset + our ~3KB commandments stay cacheable across runs and machines. Impact measurable viacache_read_input_tokensinbenchmarks/cache-tracker.ts.feat(tui)— Render completed Q&A turns via Ink<Static>so they persist in terminal scrollback after the TUI exits (same pattern Claude Code uses). Drops theresponseIsLongmodal-style branch that hid the wizard screen for long replies.feat(cli)— Enable yargs.strict()and.check()on--app-id. Typos like--app-idsnow fail fast instead of falling through silently.--app-id=foosurfaces a yargs-native error instead of silently becoming0. Also declares thefeedbackcommand's variadic[words..]positional so strict parsing accepts it. Middleware for consolidating credential resolution is paired with the upcomingbin.tscommand-module split.Context
Product of a subagent audit (
general-purpose× 2 — TS/AI engineer + OSS CLI maintainer) asking "what features of libraries we already have are we underusing?" Top findings were prioritized and this PR implements the three highest-leverage ones. Remaining audit items tracked locally: PreCompact hook, MCP resources, discriminated-union message schemas, zod brands, nanostorescomputed,measureElement/useFocus,<Static>/screen snapshot tests,.completion(),bin.tscommand-module split.Test plan
pnpm tsc --noEmit— cleanpnpm test— 1139 passed, 17 skippedpnpm lint— cleanpnpm build+ smoke test — passes.strict()live:--app-ids=foo→Unknown arguments: app-ids, appIds;--app-id=notanumber→--app-id must be a positive integer;detect --helpstill renders<Static>scrollback persistence on exitcache_read_input_tokensclimbs vs. prior runs in the benchmark log🤖 Generated with Claude Code
Note
Medium Risk
Medium risk because it tightens CLI argument validation (
yargs.strict()and--app-idchecks) which may break previously-tolerated invocations, and it changes agent/TUI runtime behavior (prompt caching + console rendering) that could surface edge-case regressions.Overview
Improves wizard efficiency and UX in three places. The agent now enables system-prompt caching via
excludeDynamicSections: true, avoiding per-run dynamic context invalidating the Claude Agent SDK cache.The TUI console switches to an append-only Q&A log using Ink
Static, preserving completed user/assistant turns in terminal scrollback and removing the prior “long response” modal-style rendering.The CLI becomes stricter: it adds a yargs
.check()to reject non-numeric/invalid--app-id, enables.strict()to fail fast on unknown flags/subcommands, and updatesfeedbackto accept variadic positional words while adding hidden passthrough options so env-var injected flags still parse under strict mode.Reviewed by Cursor Bugbot for commit b9ff4a9. Bugbot is set up for automated code reviews on this repo. Configure here.