|
| 1 | +import { loadOutputStyles } from "../../config/output-styles.js"; |
| 2 | +import { ConfigStore } from "../../config/store.js"; |
| 3 | +import type { Command } from "../types.js"; |
| 4 | + |
| 5 | +/** |
| 6 | + * /output-style — list, set, or clear the active response style. |
| 7 | + * |
| 8 | + * Styles are Markdown files in ~/.codebase/output-styles/ (or the |
| 9 | + * project's .codebase/output-styles/) whose body is appended to the |
| 10 | + * system prompt to reshape how the agent writes its answers. |
| 11 | + * |
| 12 | + * Setting a style persists it to config and rebuilds the agent so it |
| 13 | + * takes effect immediately while keeping the conversation. The rebuild |
| 14 | + * preserves the active model (via the persisted model preference, or |
| 15 | + * the proxy default). |
| 16 | + */ |
| 17 | +export const outputStyleCmd: Command = { |
| 18 | + name: "output-style", |
| 19 | + aliases: ["style"], |
| 20 | + description: "Show, set, or clear the response style (from ~/.codebase/output-styles/*.md).", |
| 21 | + handler: async (args, ctx) => { |
| 22 | + const arg = args.trim(); |
| 23 | + const config = new ConfigStore({ cwd: ctx.bundle.toolContext.cwd }); |
| 24 | + const styles = loadOutputStyles({ cwd: ctx.bundle.toolContext.cwd }); |
| 25 | + const active = config.outputStyle(); |
| 26 | + |
| 27 | + // No args → list available styles + which is active. |
| 28 | + if (!arg) { |
| 29 | + if (styles.length === 0) { |
| 30 | + ctx.emit("No output styles found."); |
| 31 | + ctx.emit("Create one at ~/.codebase/output-styles/<name>.md with a Markdown body, e.g.:"); |
| 32 | + ctx.emit(" ---"); |
| 33 | + ctx.emit(" name: Terse"); |
| 34 | + ctx.emit(" description: One-liners, no preamble."); |
| 35 | + ctx.emit(" ---"); |
| 36 | + ctx.emit(" Answer in as few words as possible."); |
| 37 | + return { handled: true }; |
| 38 | + } |
| 39 | + ctx.emit("Output styles (* = active):"); |
| 40 | + for (const s of styles) { |
| 41 | + const marker = s.id === active ? "*" : " "; |
| 42 | + ctx.emit(` ${marker} ${s.id}${s.description ? ` · ${s.description}` : ""}`); |
| 43 | + } |
| 44 | + ctx.emit(active ? `Clear with /output-style off.` : "Set with /output-style <id>."); |
| 45 | + return { handled: true }; |
| 46 | + } |
| 47 | + |
| 48 | + // Clear. |
| 49 | + if (arg === "off" || arg === "none" || arg === "clear" || arg === "default") { |
| 50 | + if (!active) { |
| 51 | + ctx.emit("No output style is active."); |
| 52 | + return { handled: true }; |
| 53 | + } |
| 54 | + config.setOutputStyle(null); |
| 55 | + await rebuild(ctx, config); |
| 56 | + ctx.emit("Output style cleared."); |
| 57 | + return { handled: true }; |
| 58 | + } |
| 59 | + |
| 60 | + // Set. |
| 61 | + const want = arg.toLowerCase(); |
| 62 | + const match = styles.find((s) => s.id === want); |
| 63 | + if (!match) { |
| 64 | + ctx.emit(`No output style named "${arg}".`); |
| 65 | + if (styles.length > 0) ctx.emit(`Available: ${styles.map((s) => s.id).join(", ")}`); |
| 66 | + return { handled: true }; |
| 67 | + } |
| 68 | + config.setOutputStyle(match.id); |
| 69 | + await rebuild(ctx, config); |
| 70 | + ctx.emit(`Output style set to "${match.id}"${match.description ? ` — ${match.description}` : ""}.`); |
| 71 | + return { handled: true }; |
| 72 | + }, |
| 73 | +}; |
| 74 | + |
| 75 | +/** |
| 76 | + * Rebuild the agent so the new system prompt (with/without the style) |
| 77 | + * takes effect now, preserving the conversation. Re-applies the model |
| 78 | + * the user was already on: the persisted preference if any, else the |
| 79 | + * proxy default (null). |
| 80 | + */ |
| 81 | +async function rebuild(ctx: Parameters<typeof outputStyleCmd.handler>[1], config: ConfigStore): Promise<void> { |
| 82 | + const preferred = config.preferredModel(); |
| 83 | + const spec = preferred?.modelId ? { provider: preferred.provider, modelId: preferred.modelId } : null; |
| 84 | + await ctx.switchModel(spec); |
| 85 | +} |
0 commit comments