feat(coding-agent): add experimental first-time setup flow (#5587)

Behind PI_EXPERIMENTAL=1, show a first-time setup dialog on interactive startup when the default agent directory is used and settings.json does not exist. The dialog preselects the detected terminal appearance with an explicit dark/light choice (live preview) and asks for opt-in analytics data sharing. Submitting writes settings.json, which serves as the completion marker; opting in stores a generated trackingId. Escape at any point skips out of setup.
This commit is contained in:
Vegard Stikbakke
2026-06-10 12:11:49 +02:00
committed by GitHub
Unverified
parent a3cd03e7ff
commit 0ab2aa86af
8 changed files with 342 additions and 2 deletions
+61 -1
View File
@@ -1,9 +1,16 @@
import { ProcessTerminal, setKeybindings, TUI } from "@earendil-works/pi-tui";
import { existsSync } from "fs";
import { ENV_AGENT_DIR, getSettingsPath } from "../config.ts";
import { areExperimentalFeaturesEnabled } from "../core/experimental.ts";
import { KeybindingsManager } from "../core/keybindings.ts";
import type { SettingsManager } from "../core/settings-manager.ts";
import { ExtensionInputComponent } from "../modes/interactive/components/extension-input.ts";
import { ExtensionSelectorComponent } from "../modes/interactive/components/extension-selector.ts";
import { initTheme } from "../modes/interactive/theme/theme.ts";
import {
FirstTimeSetupComponent,
type FirstTimeSetupResult,
} from "../modes/interactive/components/first-time-setup.ts";
import { detectTerminalBackground, initTheme, setTheme } from "../modes/interactive/theme/theme.ts";
function createStartupTui(settingsManager: SettingsManager): TUI {
initTheme(settingsManager.getTheme());
@@ -19,6 +26,22 @@ async function clearStartupTui(ui: TUI): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, 25));
}
/**
* First-time setup runs when all of these hold:
* - experimental features are enabled (PI_EXPERIMENTAL=1)
* - the default agent directory is used (no custom agent dir override)
* - setup was not completed before (settings.json does not exist)
*/
export function shouldRunFirstTimeSetup(settingsPath: string = getSettingsPath()): boolean {
if (!areExperimentalFeaturesEnabled()) {
return false;
}
if (process.env[ENV_AGENT_DIR]) {
return false;
}
return !existsSync(settingsPath);
}
export async function showStartupSelector<T>(
settingsManager: SettingsManager,
title: string,
@@ -51,6 +74,43 @@ export async function showStartupSelector<T>(
});
}
/** Show the first-time setup dialog and persist the result */
export async function showFirstTimeSetup(settingsManager: SettingsManager): Promise<void> {
return new Promise((resolve) => {
const ui = createStartupTui(settingsManager);
let settled = false;
const finish = async (result: FirstTimeSetupResult | undefined) => {
if (settled) {
return;
}
settled = true;
if (result) {
settingsManager.setTheme(result.theme);
settingsManager.setEnableAnalytics(result.shareAnalytics);
await settingsManager.flush();
}
await clearStartupTui(ui);
ui.stop();
resolve();
};
const component = new FirstTimeSetupComponent({
detectedTheme: detectTerminalBackground().theme,
onThemePreview: (themeName) => {
setTheme(themeName);
ui.invalidate();
ui.requestRender();
},
onSubmit: (result) => void finish(result),
onCancel: () => void finish(undefined),
});
ui.addChild(component);
ui.setFocus(component);
ui.start();
});
}
export async function showStartupInput(
settingsManager: SettingsManager,
title: string,