feat: configurable status lines for CLI footer (#904)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
jnjpng
2026-02-11 17:35:34 -08:00
committed by GitHub
parent 74b369d1ca
commit c3a7f6c646
16 changed files with 1689 additions and 15 deletions

View File

@@ -0,0 +1,183 @@
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
DEFAULT_STATUS_LINE_DEBOUNCE_MS,
DEFAULT_STATUS_LINE_TIMEOUT_MS,
isStatusLineDisabled,
MAX_STATUS_LINE_TIMEOUT_MS,
MIN_STATUS_LINE_DEBOUNCE_MS,
MIN_STATUS_LINE_INTERVAL_MS,
normalizeStatusLineConfig,
resolveStatusLineConfig,
} from "../../cli/helpers/statusLineConfig";
import { settingsManager } from "../../settings-manager";
import { setServiceName } from "../../utils/secrets.js";
const originalHome = process.env.HOME;
let testHomeDir: string;
let testProjectDir: string;
beforeEach(async () => {
setServiceName("letta-code-test");
await settingsManager.reset();
testHomeDir = await mkdtemp(join(tmpdir(), "letta-sl-home-"));
testProjectDir = await mkdtemp(join(tmpdir(), "letta-sl-project-"));
process.env.HOME = testHomeDir;
});
afterEach(async () => {
await settingsManager.reset();
process.env.HOME = originalHome;
await rm(testHomeDir, { recursive: true, force: true }).catch(() => {});
await rm(testProjectDir, { recursive: true, force: true }).catch(() => {});
});
describe("normalizeStatusLineConfig", () => {
test("fills defaults for timeout/debounce and command type", () => {
const result = normalizeStatusLineConfig({ command: "echo hi" });
expect(result.command).toBe("echo hi");
expect(result.type).toBe("command");
expect(result.timeout).toBe(DEFAULT_STATUS_LINE_TIMEOUT_MS);
expect(result.debounceMs).toBe(DEFAULT_STATUS_LINE_DEBOUNCE_MS);
expect(result.refreshIntervalMs).toBeUndefined();
expect(result.padding).toBe(0);
});
test("respects explicit refreshIntervalMs", () => {
const result = normalizeStatusLineConfig({
command: "echo hi",
refreshIntervalMs: 2500,
});
expect(result.refreshIntervalMs).toBe(2500);
});
test("clamps timeout to maximum", () => {
const result = normalizeStatusLineConfig({
command: "echo hi",
timeout: 999_999,
});
expect(result.timeout).toBe(MAX_STATUS_LINE_TIMEOUT_MS);
});
test("clamps debounce minimum", () => {
const result = normalizeStatusLineConfig({
command: "echo hi",
debounceMs: 1,
});
expect(result.debounceMs).toBe(MIN_STATUS_LINE_DEBOUNCE_MS);
});
test("preserves disabled flag", () => {
const result = normalizeStatusLineConfig({
command: "echo hi",
disabled: true,
});
expect(result.disabled).toBe(true);
});
});
describe("resolveStatusLineConfig", () => {
test("returns null when no config is defined", async () => {
await settingsManager.initialize();
await settingsManager.loadProjectSettings(testProjectDir);
await settingsManager.loadLocalProjectSettings(testProjectDir);
expect(resolveStatusLineConfig(testProjectDir)).toBeNull();
});
test("returns global config when only global is set", async () => {
await settingsManager.initialize();
settingsManager.updateSettings({
statusLine: { command: "echo global" },
});
await settingsManager.flush();
await settingsManager.loadProjectSettings(testProjectDir);
await settingsManager.loadLocalProjectSettings(testProjectDir);
const result = resolveStatusLineConfig(testProjectDir);
expect(result).not.toBeNull();
expect(result?.command).toBe("echo global");
});
test("local overrides project and global", async () => {
await settingsManager.initialize();
settingsManager.updateSettings({
statusLine: { command: "echo global" },
});
await settingsManager.loadProjectSettings(testProjectDir);
settingsManager.updateProjectSettings(
{ statusLine: { command: "echo project" } },
testProjectDir,
);
await settingsManager.loadLocalProjectSettings(testProjectDir);
settingsManager.updateLocalProjectSettings(
{ statusLine: { command: "echo local" } },
testProjectDir,
);
await settingsManager.flush();
const result = resolveStatusLineConfig(testProjectDir);
expect(result).not.toBeNull();
expect(result?.command).toBe("echo local");
});
test("returns null when disabled at user level", async () => {
await settingsManager.initialize();
settingsManager.updateSettings({
statusLine: { command: "echo global", disabled: true },
});
await settingsManager.flush();
await settingsManager.loadProjectSettings(testProjectDir);
await settingsManager.loadLocalProjectSettings(testProjectDir);
expect(resolveStatusLineConfig(testProjectDir)).toBeNull();
});
});
describe("isStatusLineDisabled", () => {
test("returns false when no disabled flag is set", async () => {
await settingsManager.initialize();
await settingsManager.loadProjectSettings(testProjectDir);
await settingsManager.loadLocalProjectSettings(testProjectDir);
expect(isStatusLineDisabled(testProjectDir)).toBe(false);
});
test("returns true when user has disabled: true", async () => {
await settingsManager.initialize();
settingsManager.updateSettings({
statusLine: { command: "echo hi", disabled: true },
});
await settingsManager.flush();
await settingsManager.loadProjectSettings(testProjectDir);
await settingsManager.loadLocalProjectSettings(testProjectDir);
expect(isStatusLineDisabled(testProjectDir)).toBe(true);
});
test("user disabled: false overrides project disabled: true", async () => {
await settingsManager.initialize();
settingsManager.updateSettings({
statusLine: { command: "echo hi", disabled: false },
});
await settingsManager.loadProjectSettings(testProjectDir);
settingsManager.updateProjectSettings(
{ statusLine: { command: "echo proj", disabled: true } },
testProjectDir,
);
await settingsManager.loadLocalProjectSettings(testProjectDir);
await settingsManager.flush();
expect(isStatusLineDisabled(testProjectDir)).toBe(false);
});
test("returns true when project has disabled: true (user undefined)", async () => {
await settingsManager.initialize();
await settingsManager.loadProjectSettings(testProjectDir);
settingsManager.updateProjectSettings(
{ statusLine: { command: "echo proj", disabled: true } },
testProjectDir,
);
await settingsManager.loadLocalProjectSettings(testProjectDir);
await settingsManager.flush();
expect(isStatusLineDisabled(testProjectDir)).toBe(true);
});
});

View File

@@ -0,0 +1,31 @@
import { describe, expect, test } from "bun:test";
import {
DEFAULT_STATUS_LINE_DEBOUNCE_MS,
normalizeStatusLineConfig,
} from "../../cli/helpers/statusLineConfig";
describe("statusline controller-related config", () => {
test("normalizes debounce and refresh interval defaults", () => {
const normalized = normalizeStatusLineConfig({ command: "echo hi" });
expect(normalized.debounceMs).toBe(DEFAULT_STATUS_LINE_DEBOUNCE_MS);
expect(normalized.refreshIntervalMs).toBeUndefined();
});
test("keeps explicit refreshIntervalMs", () => {
const normalized = normalizeStatusLineConfig({
command: "echo hi",
refreshIntervalMs: 4500,
});
expect(normalized.refreshIntervalMs).toBe(4500);
});
test("clamps padding and debounce", () => {
const normalized = normalizeStatusLineConfig({
command: "echo hi",
padding: 999,
debounceMs: 10,
});
expect(normalized.padding).toBe(16);
expect(normalized.debounceMs).toBe(50);
});
});

View File

@@ -0,0 +1,34 @@
import { describe, expect, test } from "bun:test";
import { formatStatusLineHelp } from "../../cli/helpers/statusLineHelp";
describe("statusLineHelp", () => {
test("includes configuration and input sections", () => {
const output = formatStatusLineHelp();
expect(output).toContain("/statusline help");
expect(output).toContain("CONFIGURATION");
expect(output).toContain("INPUT (via JSON stdin)");
expect(output).toContain("model.display_name");
expect(output).toContain("context_window.used_percentage");
});
test("lists all fields without section separation", () => {
const output = formatStatusLineHelp();
// Native and derived fields both present in a single list
expect(output).toContain("cwd");
expect(output).toContain("session_id");
expect(output).toContain("context_window.remaining_percentage");
expect(output).toContain("exceeds_200k_tokens");
// No native/derived subheadings
expect(output).not.toContain("\nnative\n");
expect(output).not.toContain("\nderived\n");
});
test("does not include effective config section", () => {
const output = formatStatusLineHelp();
expect(output).not.toContain("Effective config:");
});
});

View File

@@ -0,0 +1,62 @@
import { describe, expect, test } from "bun:test";
import {
buildStatusLinePayload,
calculateContextPercentages,
} from "../../cli/helpers/statusLinePayload";
describe("statusLinePayload", () => {
test("builds payload with all fields", () => {
const payload = buildStatusLinePayload({
modelId: "anthropic/claude-sonnet-4",
modelDisplayName: "Sonnet",
currentDirectory: "/repo",
projectDirectory: "/repo",
sessionId: "conv-123",
agentName: "Test Agent",
totalDurationMs: 10_000,
totalApiDurationMs: 3_000,
totalInputTokens: 1200,
totalOutputTokens: 450,
contextWindowSize: 200_000,
usedContextTokens: 40_000,
permissionMode: "default",
networkPhase: "download",
terminalWidth: 120,
});
expect(payload.cwd).toBe("/repo");
expect(payload.workspace.current_dir).toBe("/repo");
expect(payload.workspace.project_dir).toBe("/repo");
expect(payload.model.id).toBe("anthropic/claude-sonnet-4");
expect(payload.model.display_name).toBe("Sonnet");
expect(payload.context_window.used_percentage).toBe(20);
expect(payload.context_window.remaining_percentage).toBe(80);
expect(payload.permission_mode).toBe("default");
expect(payload.network_phase).toBe("download");
expect(payload.terminal_width).toBe(120);
});
test("marks unsupported fields as null", () => {
const payload = buildStatusLinePayload({
currentDirectory: "/repo",
projectDirectory: "/repo",
});
expect(payload.transcript_path).toBeNull();
expect(payload.output_style.name).toBeNull();
expect(payload.vim).toBeNull();
expect(payload.cost.total_cost_usd).toBeNull();
expect(payload.context_window.current_usage).toBeNull();
});
test("calculates context percentages safely", () => {
expect(calculateContextPercentages(50, 200)).toEqual({
used: 25,
remaining: 75,
});
expect(calculateContextPercentages(500, 200)).toEqual({
used: 100,
remaining: 0,
});
});
});

View File

@@ -0,0 +1,119 @@
import { describe, expect, test } from "bun:test";
import { executeStatusLineCommand } from "../../cli/helpers/statusLineRuntime";
const isWindows = process.platform === "win32";
describe.skipIf(isWindows)("executeStatusLineCommand", () => {
test("echo command returns stdout", async () => {
const result = await executeStatusLineCommand(
"echo hello",
{},
{
timeout: 5000,
},
);
expect(result.ok).toBe(true);
expect(result.text).toBe("hello");
expect(result.durationMs).toBeGreaterThanOrEqual(0);
});
test("receives JSON payload on stdin", async () => {
// cat reads stdin and outputs it; we verify the command receives JSON
const result = await executeStatusLineCommand(
"cat",
{
agent_id: "test-agent",
streaming: false,
},
{
timeout: 5000,
},
);
expect(result.ok).toBe(true);
const parsed = JSON.parse(result.text);
expect(parsed.agent_id).toBe("test-agent");
expect(parsed.streaming).toBe(false);
});
test("non-zero exit code returns ok: false", async () => {
const result = await executeStatusLineCommand(
"exit 1",
{},
{
timeout: 5000,
},
);
expect(result.ok).toBe(false);
expect(result.error).toContain("Exit code");
});
test("command timeout", async () => {
const result = await executeStatusLineCommand(
"sleep 10",
{},
{
timeout: 500,
},
);
expect(result.ok).toBe(false);
expect(result.error).toContain("timed out");
});
test("AbortSignal cancellation", async () => {
const ac = new AbortController();
const promise = executeStatusLineCommand(
"sleep 10",
{},
{
timeout: 10000,
signal: ac.signal,
},
);
// Abort after a short delay
setTimeout(() => ac.abort(), 100);
const result = await promise;
expect(result.ok).toBe(false);
expect(result.error).toBe("Aborted");
});
test("stdout is capped at 4KB", async () => {
// Generate 8KB of output (each 'x' char is ~1 byte)
const result = await executeStatusLineCommand(
"python3 -c \"print('x' * 8192)\"",
{},
{ timeout: 5000 },
);
expect(result.ok).toBe(true);
// Stdout should be truncated to approximately 4KB
expect(result.text.length).toBeLessThanOrEqual(4096);
});
test("empty command returns error", async () => {
const result = await executeStatusLineCommand(
"",
{},
{
timeout: 5000,
},
);
expect(result.ok).toBe(false);
});
test("pre-aborted signal returns immediately", async () => {
const ac = new AbortController();
ac.abort();
const result = await executeStatusLineCommand(
"echo hi",
{},
{
timeout: 5000,
signal: ac.signal,
},
);
expect(result.ok).toBe(false);
expect(result.error).toBe("Aborted");
expect(result.durationMs).toBe(0);
});
});

View File

@@ -0,0 +1,21 @@
import { describe, expect, test } from "bun:test";
import {
STATUSLINE_DERIVED_FIELDS,
STATUSLINE_NATIVE_FIELDS,
} from "../../cli/helpers/statusLineSchema";
describe("statusLineSchema", () => {
test("contains native and derived fields", () => {
expect(STATUSLINE_NATIVE_FIELDS.length).toBeGreaterThan(0);
expect(STATUSLINE_DERIVED_FIELDS.length).toBeGreaterThan(0);
});
test("field paths are unique", () => {
const allPaths = [
...STATUSLINE_NATIVE_FIELDS,
...STATUSLINE_DERIVED_FIELDS,
].map((f) => f.path);
const unique = new Set(allPaths);
expect(unique.size).toBe(allPaths.length);
});
});