Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ These settings affect where long lines are truncated, and where right-alignment
- **Full width minus 40** - Reserves 40 characters for auto-compact message to prevent wrapping (default)
- **Full width until compact** - Dynamically switches between full width and minus 40 based on context percentage threshold (configurable, default 60%)

If ccstatusline cannot detect your terminal width, set `CCSTATUSLINE_WIDTH` to a positive integer to override probing:

```bash
CCSTATUSLINE_WIDTH=160 ccstatusline
```

The override is checked before automatic width detection, so it also works in wrapper processes, IDE integrations, nested PTYs, and Windows environments where probing may be unavailable. Invalid values such as `0`, negative numbers, or non-numeric strings are ignored and ccstatusline falls back to normal detection.

## Global Options

Configure global formatting preferences that apply to all widgets:
Expand Down
10 changes: 10 additions & 0 deletions docs/WINDOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ winget install DEVCOM.JetBrainsMonoNerdFont

### Common Issues & Solutions

**Issue**: Status lines wrap because terminal width cannot be detected

```powershell
# Set an explicit width before launching Claude Code
$env:CCSTATUSLINE_WIDTH="160"
claude
```

`CCSTATUSLINE_WIDTH` accepts a positive integer column width and is checked before automatic width detection. Set it in the same environment that starts Claude Code so the status line command inherits it. This is useful on Windows because native width probing is disabled when ccstatusline runs outside WSL.

**Issue**: Powerline symbols showing as question marks or boxes

```powershell
Expand Down
62 changes: 62 additions & 0 deletions src/utils/__tests__/terminal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ describe('terminal utils', () => {
beforeEach(() => {
vi.clearAllMocks();
vi.restoreAllMocks();
delete process.env.CCSTATUSLINE_WIDTH;
});

afterEach(() => {
vi.restoreAllMocks();
delete process.env.CCSTATUSLINE_WIDTH;
});

it('returns width from the immediate parent tty when available', () => {
Expand Down Expand Up @@ -188,6 +190,66 @@ describe('terminal utils', () => {
expect(canDetectTerminalWidth()).toBe(false);
});

it('honors CCSTATUSLINE_WIDTH override before probing', () => {
process.env.CCSTATUSLINE_WIDTH = '220';

expect(getTerminalWidth()).toBe(220);
expect(mockExecSync.mock.calls.length).toBe(0);
});

it('ignores a non-positive CCSTATUSLINE_WIDTH and falls back to probing', () => {
process.env.CCSTATUSLINE_WIDTH = '0';

mockExecSync.mockImplementation((command: string) => {
if (command === `ps -o ppid= -p ${process.pid}`) {
return '1234\n';
}

if (command === 'ps -o tty= -p 1234') {
return 'ttys001\n';
}

if (command === `stty -F /dev/ttys001 size 2>/dev/null | awk '{print $2}'`) {
return '160\n';
}

throw new Error(`Unexpected command: ${command}`);
});

expect(getTerminalWidth()).toBe(160);
});

it('ignores a non-numeric CCSTATUSLINE_WIDTH and falls back to probing', () => {
process.env.CCSTATUSLINE_WIDTH = 'wide';

mockExecSync.mockImplementation((command: string) => {
if (command === `ps -o ppid= -p ${process.pid}`) {
return '1234\n';
}

if (command === 'ps -o tty= -p 1234') {
return 'ttys001\n';
}

if (command === `stty -F /dev/ttys001 size 2>/dev/null | awk '{print $2}'`) {
return '160\n';
}

throw new Error(`Unexpected command: ${command}`);
});

expect(getTerminalWidth()).toBe(160);
});

it('CCSTATUSLINE_WIDTH override applies on Windows where probing is disabled', () => {
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');
process.env.CCSTATUSLINE_WIDTH = '180';

expect(getTerminalWidth()).toBe(180);
expect(canDetectTerminalWidth()).toBe(true);
expect(mockExecSync.mock.calls.length).toBe(0);
});

it('disables width detection on Windows', () => {
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32');

Expand Down
14 changes: 14 additions & 0 deletions src/utils/terminal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,20 @@ export function getPackageVersion(): string {
}

function probeTerminalWidth(): number | null {
// Explicit override. Useful when ccstatusline is spawned in a context where
// no ancestor process owns a TTY at all — e.g. some Claude Code >= 2.1.139
// spawn paths, IDE integrations, or nested-shell scenarios where both the
// ancestor-walk probe and `tput cols` return nothing usable. Users can set
// CCSTATUSLINE_WIDTH on the statusLine command (e.g.
// `CCSTATUSLINE_WIDTH=200 ccstatusline ...`) to bypass probing entirely.
const overrideRaw = process.env.CCSTATUSLINE_WIDTH;
if (overrideRaw) {
const override = parsePositiveInteger(overrideRaw);
if (override !== null) {
return override;
}
}

// Preserve historical behavior on Windows: width detection is unavailable.
// This avoids Unix fallback command behavior (e.g. 2>/dev/null) on Windows.
if (process.platform === 'win32') {
Expand Down