feat(coding-agent): add startup.quiet setting to silence startup output (#777)

* feat(coding-agent): add startup.quiet setting to silence startup output

* feat(coding-agent): add startup.quiet setting to silence startup output

Adds a new setting `startup.quiet` that when set to `true` hides:
- Version and keybinding hints header
- Loaded context/skills/templates/extensions discovery info
- Model scope line

Changelog notifications are still shown so users know about updates.

Usage in ~/.pi/agent/settings.json:
{
  "startup": {
    "quiet": true
  }
}

* refactor: flatten startup.quiet to quietStartup on Settings
This commit is contained in:
Rafał Krzyważnia 2026-01-16 22:03:29 +01:00 committed by GitHub
parent 3326b8f521
commit d2f9ab110c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 105 additions and 75 deletions

View file

@ -60,6 +60,7 @@ export interface Settings {
retry?: RetrySettings; retry?: RetrySettings;
hideThinkingBlock?: boolean; hideThinkingBlock?: boolean;
shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows) shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)
quietStartup?: boolean;
collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full) collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)
extensions?: string[]; // Array of extension file paths extensions?: string[]; // Array of extension file paths
skills?: SkillsSettings; skills?: SkillsSettings;
@ -346,6 +347,15 @@ export class SettingsManager {
this.save(); this.save();
} }
getQuietStartup(): boolean {
return this.settings.quietStartup ?? false;
}
setQuietStartup(quiet: boolean): void {
this.globalSettings.quietStartup = quiet;
this.save();
}
getCollapseChangelog(): boolean { getCollapseChangelog(): boolean {
return this.settings.collapseChangelog ?? false; return this.settings.collapseChangelog ?? false;
} }

View file

@ -503,7 +503,7 @@ export async function main(args: string[]) {
if (mode === "rpc") { if (mode === "rpc") {
await runRpcMode(session); await runRpcMode(session);
} else if (isInteractive) { } else if (isInteractive) {
if (scopedModels.length > 0) { if (scopedModels.length > 0 && !settingsManager.getQuietStartup()) {
const modelList = scopedModels const modelList = scopedModels
.map((sm) => { .map((sm) => {
const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : ""; const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";

View file

@ -357,56 +357,69 @@ export class InteractiveMode {
this.fdPath = await ensureTool("fd"); this.fdPath = await ensureTool("fd");
this.setupAutocomplete(this.fdPath); this.setupAutocomplete(this.fdPath);
// Add header with keybindings from config // Add header with keybindings from config (unless silenced)
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`); if (!this.settingsManager.getQuietStartup()) {
const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
// Build startup instructions using keybinding hint helpers // Build startup instructions using keybinding hint helpers
const kb = this.keybindings; const kb = this.keybindings;
const hint = (action: AppAction, desc: string) => appKeyHint(kb, action, desc); const hint = (action: AppAction, desc: string) => appKeyHint(kb, action, desc);
const instructions = [ const instructions = [
hint("interrupt", "to interrupt"), hint("interrupt", "to interrupt"),
hint("clear", "to clear"), hint("clear", "to clear"),
rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"), rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
hint("exit", "to exit (empty)"), hint("exit", "to exit (empty)"),
hint("suspend", "to suspend"), hint("suspend", "to suspend"),
keyHint("deleteToLineEnd", "to delete to end"), keyHint("deleteToLineEnd", "to delete to end"),
hint("cycleThinkingLevel", "to cycle thinking"), hint("cycleThinkingLevel", "to cycle thinking"),
rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"), rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
hint("selectModel", "to select model"), hint("selectModel", "to select model"),
hint("expandTools", "to expand tools"), hint("expandTools", "to expand tools"),
hint("toggleThinking", "to toggle thinking"), hint("toggleThinking", "to toggle thinking"),
hint("externalEditor", "for external editor"), hint("externalEditor", "for external editor"),
rawKeyHint("/", "for commands"), rawKeyHint("/", "for commands"),
rawKeyHint("!", "to run bash"), rawKeyHint("!", "to run bash"),
rawKeyHint("!!", "to run bash (no context)"), rawKeyHint("!!", "to run bash (no context)"),
hint("followUp", "to queue follow-up"), hint("followUp", "to queue follow-up"),
hint("dequeue", "to edit all queued messages"), hint("dequeue", "to edit all queued messages"),
hint("pasteImage", "to paste image"), hint("pasteImage", "to paste image"),
rawKeyHint("drop files", "to attach"), rawKeyHint("drop files", "to attach"),
].join("\n"); ].join("\n");
this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0); this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
// Setup UI layout // Setup UI layout
this.ui.addChild(new Spacer(1)); this.ui.addChild(new Spacer(1));
this.ui.addChild(this.builtInHeader); this.ui.addChild(this.builtInHeader);
this.ui.addChild(new Spacer(1)); this.ui.addChild(new Spacer(1));
// Add changelog if provided // Add changelog if provided
if (this.changelogMarkdown) { if (this.changelogMarkdown) {
this.ui.addChild(new DynamicBorder()); this.ui.addChild(new DynamicBorder());
if (this.settingsManager.getCollapseChangelog()) { if (this.settingsManager.getCollapseChangelog()) {
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
const latestVersion = versionMatch ? versionMatch[1] : this.version;
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
this.ui.addChild(new Text(condensedText, 1, 0));
} else {
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
this.ui.addChild(new Spacer(1));
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
this.ui.addChild(new Spacer(1));
}
this.ui.addChild(new DynamicBorder());
}
} else {
// Minimal header when silenced
this.builtInHeader = new Text("", 0, 0);
if (this.changelogMarkdown) {
// Still show changelog notification even in silent mode
this.ui.addChild(new Spacer(1));
const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/); const versionMatch = this.changelogMarkdown.match(/##\s+\[?(\d+\.\d+\.\d+)\]?/);
const latestVersion = versionMatch ? versionMatch[1] : this.version; const latestVersion = versionMatch ? versionMatch[1] : this.version;
const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`; const condensedText = `Updated to v${latestVersion}. Use ${theme.bold("/changelog")} to view full changelog.`;
this.ui.addChild(new Text(condensedText, 1, 0)); this.ui.addChild(new Text(condensedText, 1, 0));
} else {
this.ui.addChild(new Text(theme.bold(theme.fg("accent", "What's New")), 1, 0));
this.ui.addChild(new Spacer(1));
this.ui.addChild(new Markdown(this.changelogMarkdown.trim(), 1, 0, getMarkdownTheme()));
this.ui.addChild(new Spacer(1));
} }
this.ui.addChild(new DynamicBorder());
} }
this.ui.addChild(this.chatContainer); this.ui.addChild(this.chatContainer);
@ -573,36 +586,41 @@ export class InteractiveMode {
* Initialize the extension system with TUI-based UI context. * Initialize the extension system with TUI-based UI context.
*/ */
private async initExtensions(): Promise<void> { private async initExtensions(): Promise<void> {
// Show loaded project context files // Show discovery info unless silenced
const contextFiles = loadProjectContextFiles(); if (!this.settingsManager.getQuietStartup()) {
if (contextFiles.length > 0) { // Show loaded project context files
const contextList = contextFiles.map((f) => theme.fg("dim", ` ${f.path}`)).join("\n"); const contextFiles = loadProjectContextFiles();
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded context:\n") + contextList, 0, 0)); if (contextFiles.length > 0) {
this.chatContainer.addChild(new Spacer(1)); const contextList = contextFiles.map((f) => theme.fg("dim", ` ${f.path}`)).join("\n");
} this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded context:\n") + contextList, 0, 0));
this.chatContainer.addChild(new Spacer(1));
}
// Show loaded skills (already discovered by SDK) // Show loaded skills (already discovered by SDK)
const skills = this.session.skills; const skills = this.session.skills;
if (skills.length > 0) { if (skills.length > 0) {
const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n"); const skillList = skills.map((s) => theme.fg("dim", ` ${s.filePath}`)).join("\n");
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0)); this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded skills:\n") + skillList, 0, 0));
this.chatContainer.addChild(new Spacer(1)); this.chatContainer.addChild(new Spacer(1));
} }
// Show skill warnings if any // Show skill warnings if any
const skillWarnings = this.session.skillWarnings; const skillWarnings = this.session.skillWarnings;
if (skillWarnings.length > 0) { if (skillWarnings.length > 0) {
const warningList = skillWarnings.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`)).join("\n"); const warningList = skillWarnings
this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0)); .map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`))
this.chatContainer.addChild(new Spacer(1)); .join("\n");
} this.chatContainer.addChild(new Text(theme.fg("warning", "Skill warnings:\n") + warningList, 0, 0));
this.chatContainer.addChild(new Spacer(1));
}
// Show loaded prompt templates // Show loaded prompt templates
const templates = this.session.promptTemplates; const templates = this.session.promptTemplates;
if (templates.length > 0) { if (templates.length > 0) {
const templateList = templates.map((t) => theme.fg("dim", ` /${t.name} ${t.source}`)).join("\n"); const templateList = templates.map((t) => theme.fg("dim", ` /${t.name} ${t.source}`)).join("\n");
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded prompt templates:\n") + templateList, 0, 0)); this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded prompt templates:\n") + templateList, 0, 0));
this.chatContainer.addChild(new Spacer(1)); this.chatContainer.addChild(new Spacer(1));
}
} }
const extensionRunner = this.session.extensionRunner; const extensionRunner = this.session.extensionRunner;
@ -748,12 +766,14 @@ export class InteractiveMode {
// Set up extension-registered shortcuts // Set up extension-registered shortcuts
this.setupExtensionShortcuts(extensionRunner); this.setupExtensionShortcuts(extensionRunner);
// Show loaded extensions // Show loaded extensions (unless silenced)
const extensionPaths = extensionRunner.getExtensionPaths(); if (!this.settingsManager.getQuietStartup()) {
if (extensionPaths.length > 0) { const extensionPaths = extensionRunner.getExtensionPaths();
const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n"); if (extensionPaths.length > 0) {
this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0)); const extList = extensionPaths.map((p) => theme.fg("dim", ` ${p}`)).join("\n");
this.chatContainer.addChild(new Spacer(1)); this.chatContainer.addChild(new Text(theme.fg("muted", "Loaded extensions:\n") + extList, 0, 0));
this.chatContainer.addChild(new Spacer(1));
}
} }
// Emit session_start event // Emit session_start event