Skip to content

feat(statusline): add user-extensible statusline sections#1011

Closed
gehnster wants to merge 2 commits intodanielmiessler:mainfrom
gehnster:feat/statusline-user-extensions
Closed

feat(statusline): add user-extensible statusline sections#1011
gehnster wants to merge 2 commits intodanielmiessler:mainfrom
gehnster:feat/statusline-user-extensions

Conversation

@gehnster
Copy link
Copy Markdown

@gehnster gehnster commented Mar 31, 2026

Summary

Adds a user extension architecture to the statusline so users can add custom sections without modifying statusline-command.sh directly. Extensions survive PAI upgrades.

  • Users create PAI/USER/STATUSLINE/extensions.sh with two functions: user_statusline_prefetch (fetch data in parallel) and user_statusline_display (render output)
  • A SessionStart hook (StatuslineExtensions.hook.ts) auto-injects the extension wiring if statusline-command.sh gets overwritten during an upgrade
  • Comprehensive README with authoring guide, minimal example, and a real-world ElevenLabs voice quota example
  • 23 automated tests (bun:test) covering the extension contract, pipeline, display modes, self-healing hook, idempotency, and graceful degradation

Changes to statusline-command.sh (13 lines added)

4 insertion points, zero changes to existing logic:

  1. Source line — loads extensions.sh if it exists (after .env source)
  2. Prefetch call — runs user_statusline_prefetch in the parallel block (guarded by type -t)
  3. Source results — sources user-ext.sh from parallel tmp alongside other results
  4. Display call — runs user_statusline_display between usage and git sections (guarded by type -t)

All calls are guarded — if no extensions file exists, zero overhead, zero output.

Why one PR instead of splitting architecture + example

The ElevenLabs voice quota example in the README demonstrates the architecture on an already-optional PAI feature (voice/TTS). Shipping the architecture without a working example makes it harder to evaluate. The example is documentation, not code in the repo — users create their own extensions.sh in PAI/USER/.

Example: ElevenLabs voice quota in statusline

The README includes a complete example that shows remaining TTS characters. When ELEVENLABS_API_KEY is set:

♪ VOICE: 10% │ 1000/10000 chars │ 9.0K left │ ↻30d
────────────────────────────────────────────────────────────────────────

When no API key is configured, the section produces zero output.

Self-healing hook

After an upgrade overwrites statusline-command.sh, the StatuslineExtensions.hook.ts hook runs at next SessionStart and re-injects the 4 extension wiring points. It is:

  • Idempotent — no-ops if wiring is already present
  • Conditional — no-ops if no extensions.sh exists
  • Syntax-safe — tested to produce valid bash after injection

Test plan

23 automated tests via bun test tests/statusline-extensions.test.ts:

  • Extension contract: file syntax, functions defined
  • Prefetch → source → display pipeline with mock extension
  • Display adapts to all 4 terminal modes (nano/micro/mini/normal)
  • Statusline-command.sh has all wiring points with guards
  • Self-healing hook injects all markers into stripped script
  • Hook is idempotent (no changes on second run)
  • Hook no-ops when no extensions file exists
  • Patched script passes bash -n syntax check
  • Graceful degradation without extensions file
$ bun test tests/statusline-extensions.test.ts
 23 pass
 0 fail
 32 expect() calls
Ran 23 tests across 1 file. [96.00ms]

🤖 Generated with Claude Code

Users can add custom sections to the statusline by creating
PAI/USER/STATUSLINE/extensions.sh with two functions:
user_statusline_prefetch and user_statusline_display.

Includes a self-healing SessionStart hook that re-injects
extension wiring if statusline-command.sh is overwritten
during upgrades. Adds 23 automated tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 31, 2026 04:10
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an upgrade-safe user extension architecture to the PAI statusline, allowing users to inject custom statusline sections via PAI/USER/STATUSLINE/extensions.sh, with a SessionStart hook that re-applies wiring after upgrades overwrite statusline-command.sh.

Changes:

  • Adds extension wiring points to statusline-command.sh (source user extensions, prefetch in parallel block, source prefetch results, display hook).
  • Introduces StatuslineExtensions.hook.ts to self-heal wiring on SessionStart, and registers it in settings.json.
  • Adds documentation and a bun test suite validating the extension contract, wiring, and hook idempotency.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
Releases/v4.0.3/.claude/tests/statusline-extensions.test.ts New bun:test suite covering extension contract + hook patching behavior
Releases/v4.0.3/.claude/statusline-command.sh Adds extension source/prefetch/results/display wiring points
Releases/v4.0.3/.claude/settings.json Registers StatuslineExtensions SessionStart hook
Releases/v4.0.3/.claude/PAI/USER/STATUSLINE/README.md Documentation for authoring extensions + examples
Releases/v4.0.3/.claude/hooks/StatuslineExtensions.hook.ts New self-healing hook that injects wiring if missing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +21
const PAI_DIR = process.env.PAI_DIR || join(process.env.HOME!, '.claude');
const EXTENSIONS_DIR = join(PAI_DIR, 'PAI/USER/STATUSLINE');
const EXTENSIONS_PATH = join(EXTENSIONS_DIR, 'extensions.sh');
const STATUSLINE_PATH = join(PAI_DIR, 'statusline-command.sh');
const HOOK_PATH = join(PAI_DIR, 'hooks/StatuslineExtensions.hook.ts');
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file defaults PAI_DIR to "$HOME/.claude" when PAI_DIR isn’t set, and then reads/writes real files under that directory (extensions.sh, statusline-command.sh). Running the test locally could overwrite or delete a user’s actual PAI config/statusline. Update the tests to run against a temporary isolated PAI_DIR (mkdtemp), copying in fixture versions of statusline-command.sh and the hook, so the suite never touches real user state.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Comment on lines +37 to +63
const ENV_SOURCE_LINE = '[ -f "${PAI_CONFIG_DIR:-$HOME/.config/PAI}/.env" ] && source "${PAI_CONFIG_DIR:-$HOME/.config/PAI}/.env"';

function main() {
// No extensions file — nothing to wire
if (!existsSync(EXTENSIONS_PATH)) {
process.exit(0);
}

if (!existsSync(STATUSLINE_PATH)) {
console.error('[StatuslineExtensions] statusline-command.sh not found');
process.exit(0);
}

let content = readFileSync(STATUSLINE_PATH, 'utf-8');
let modified = false;

// 1. Check for source line
if (!content.includes(SOURCE_MARKER)) {
const envIdx = content.indexOf(ENV_SOURCE_LINE);
if (envIdx === -1) {
console.error('[StatuslineExtensions] Could not find .env source line to anchor injection');
process.exit(0);
}
const insertAfter = envIdx + ENV_SOURCE_LINE.length;
const injection = `\n\n# Source user statusline extensions (upgrade-safe customizations)\n_USER_EXTENSIONS="$PAI_DIR/PAI/USER/STATUSLINE/extensions.sh"\n[ -f "$_USER_EXTENSIONS" ] && source "$_USER_EXTENSIONS"`;
content = content.slice(0, insertAfter) + injection + content.slice(insertAfter);
modified = true;
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hook anchors injection by searching for an exact, full-string match of the .env source line. Any small formatting change in statusline-command.sh (spacing, quoting, variable name, comment) will cause the hook to fail to inject and silently exit. Consider matching more robustly (e.g., regex for sourcing the .env file, or anchoring on a nearby comment block) so the “self-healing” behavior survives upstream script edits.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont know about the system enough to know what to do here

Comment on lines +78 to +87
// 3. Check for user-ext.sh source in parallel results
if (!content.includes(USER_EXT_SOURCE)) {
const lastSource = content.lastIndexOf('" ] && source "$_parallel_tmp/');
if (lastSource !== -1) {
const lineEnd = content.indexOf('\n', lastSource);
const injection = `\n[ -f "$_parallel_tmp/user-ext.sh" ] && source "$_parallel_tmp/user-ext.sh"`;
content = content.slice(0, lineEnd) + injection + content.slice(lineEnd);
modified = true;
console.error('[StatuslineExtensions] Injected user-ext.sh source');
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user-ext.sh source injection relies on lastIndexOf('" ] && source "$_parallel_tmp/') to find the insertion point. This is brittle to minor formatting changes in statusline-command.sh and could insert in the wrong place or not at all. Prefer a more stable anchor (e.g., insert before the rm -rf "$_parallel_tmp" line or after a named marker comment) and/or use a regex that matches the parallel results block structure.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i honestly dont know anything about this so dont know how to move forward

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@gehnster
Copy link
Copy Markdown
Author

sorry but it seems this might not be a great PR. I just started using AI and PAI recently but found this to be a missing part of the statusline and thought others may want it too. This was all generated with Claude and i tried to be mindful of coding standards and exisiting PRs but it seems even copilot found faults

@gehnster gehnster closed this Mar 31, 2026
@gehnster gehnster deleted the feat/statusline-user-extensions branch March 31, 2026 16:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants