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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## [Unreleased]

- **Agent Experience**
- Bkper CLI Agent has a new look: the startup screen shows the Bkper logo in its account type colors, and new Bkper light and dark themes replace Pi's default colors, following your terminal's light or dark background. Custom themes you selected are kept
- The startup screen no longer breaks in narrow terminals
- Updated Pi to 0.99.2

## [5.2.0] - 2026-10-01

- **Agent Experience**
Expand Down
Binary file modified assets/bkper-agent-cli.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 25 additions & 9 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@
"clean": "rm -rf ./lib & rm -rf ./node_modules & wait",
"sync:docs": "bun ./scripts/sync-docs.ts",
"prebuild": "bun install && bun sync:docs",
"build": "run-s build:clean build:compile build:copy-docs",
"build": "run-s build:clean build:compile build:copy-docs build:copy-themes",
"build:clean": "gts clean && rimraf temp",
"build:compile": "tsc",
"build:copy-docs": "mkdir -p lib && rm -rf lib/docs && cp -R skill/references lib/docs",
"build:copy-themes": "mkdir -p lib/agent && rm -rf lib/agent/themes && cp -R src/agent/themes lib/agent/themes",
"skill:package": "bun ./scripts/generate-skill-distribution.ts",
"api:platform": "openapi-typescript https://platform.bkper.app/openapi.json -o src/platform/types.d.ts",
"api:platform:local": "openapi-typescript http://localhost:8790/openapi.json -o src/platform/types.d.ts",
Expand All @@ -62,8 +63,8 @@
"upgrade:api": "bun update @bkper/bkper-api-types --latest && bun update bkper-js --latest"
},
"dependencies": {
"@earendil-works/pi-coding-agent": "0.87.1",
"@earendil-works/pi-tui": "0.87.1",
"@earendil-works/pi-coding-agent": "0.99.2",
"@earendil-works/pi-tui": "0.99.2",
"bkper-js": "^2.42.0",
"commander": "^13.1.0",
"dotenv": "^8.2.0",
Expand Down
7 changes: 5 additions & 2 deletions src/agent/extensions/bkper-ai-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export interface BkperAiModelMetadata {
bkperDefaultThinkingLevel?: BkperAiThinkingLevel;
}

interface BkperAiModelConfig extends ProviderModelConfig {
/** The chat member of Pi's provider model union; the package root exports only the union. */
type ProviderChatModelConfig = Extract<ProviderModelConfig, {type?: 'chat'}>;

interface BkperAiModelConfig extends ProviderChatModelConfig {
bkperDefault: boolean;
bkperDefaultThinkingLevel?: BkperAiThinkingLevel;
}
Expand Down Expand Up @@ -99,7 +102,7 @@ function getDefaultThinkingLevel(model: BkperAiCatalogModel): BkperAiThinkingLev

function getThinkingLevelMap(
levels: string[]
): NonNullable<ProviderModelConfig['thinkingLevelMap']> {
): NonNullable<ProviderChatModelConfig['thinkingLevelMap']> {
const supports = (level: string): string | null => (levels.includes(level) ? level : null);
return {
off: levels.includes('none') ? 'none' : null,
Expand Down
2 changes: 2 additions & 0 deletions src/agent/extensions/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ function normalizeBkperAgentExtension(extension: Extension): Extension {

return {
...extension,
// Built-ins are part of the agent, not user extensions to list at startup.
hidden: true,
path: BKPER_AGENT_BUILTINS_EXTENSION_PATH,
resolvedPath: BKPER_AGENT_BUILTINS_EXTENSION_PATH,
sourceInfo: {
Expand Down
111 changes: 111 additions & 0 deletions src/agent/extensions/startup-logo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
backgroundAnsi,
foregroundAnsi,
rgbColor,
type Color,
type TerminalColorMode,
} from '@earendil-works/pi-tui';

const RESET = '\x1b[0m';

/** Official logo colors; the four mark colors are the four Bkper account type colors. */
const BKPER_LOGO_COLORS: Record<string, Color> = {
B: rgbColor(34, 133, 192), // #2285c0 Asset blue
D: rgbColor(30, 119, 173), // #1e77ad stem shade under the bar
R: rgbColor(209, 72, 54), // #d14836 Outgoing red
Y: rgbColor(247, 179, 0), // #f7b300 Liability yellow
G: rgbColor(58, 170, 87), // #3aaa57 Incoming green
W: rgbColor(153, 153, 153), // #999999 arrow and wordmark gray
};

/**
* The Bkper logo as square pixels, two pixel rows per terminal line. The mark is the official
* icon geometry sampled at 2 pixels per logo unit; the wordmark sits on the mark's baseline.
* '.' is transparent.
*/
const BKPER_LOGO_PIXELS = [
'.......BB.............................................',
'......BBBB............................................',
'......BBBB............................................',
'......BBBB............................................',
'......BBBB............................................',
'......BBBB............................................',
'......BBBB............................................',
'......BBBB............................................',
'......BRRRRRRYYYY.....................................',
'......RRRRRRYYYYYY....................................',
'......RRRRRRYYYYYYY...................................',
'......BRRRRRRYYYYYYY..................................',
'......BBDD.....YYYYY..................................',
'......BBDD......YYYY..W......W........................',
'......BBBD......YYYY..W......W........................',
'W.....BBBB.....YYYYY..W.WW...W..W..W.WW....WWW...W.WW.',
'WW....BBBBGGGGGYYYYY..WW..W..W.W...WW..W..W...W..WW...',
'WWW...BBBBGGGGGGYYY...W...W..WW....W...W..WWWWW..W....',
'WW....BBBBGGGGGGYY....WW..W..W.W...WW..W..W......W....',
'W......BBBGGGGGGY.....W.WW...W..W..W.WW....WWW...W....',
'...................................W..................',
'...................................W..................',
];

/** Width of the arrow and mark, without the wordmark. */
const BKPER_MARK_WIDTH = 20;

/**
* Renders a pixel grid with half blocks: each terminal line shows two pixel rows, the upper one
* as the foreground of '▀' and the lower one as its background.
*/
export function renderPixelArt(
pixels: readonly string[],
colors: Readonly<Record<string, Color>>,
mode: TerminalColorMode
): string[] {
const lines: string[] = [];
for (let row = 0; row < pixels.length; row += 2) {
const upper = pixels[row];
const lower = pixels[row + 1] ?? '';
const width = Math.max(upper.length, lower.length);
let line = '';
for (let column = 0; column < width; column++) {
const top = colors[upper[column]];
const bottom = colors[lower[column]];
if (top && bottom && top === bottom) {
line += `${foregroundAnsi(top, mode)}█${RESET}`;
} else if (top && bottom) {
line += `${foregroundAnsi(top, mode)}${backgroundAnsi(bottom, mode)}▀${RESET}`;
} else if (top) {
line += `${foregroundAnsi(top, mode)}▀${RESET}`;
} else if (bottom) {
line += `${foregroundAnsi(bottom, mode)}▄${RESET}`;
} else {
line += ' ';
}
}
lines.push(line);
}
return lines;
}

function cropPixels(pixels: readonly string[], width: number): string[] {
const cropped = pixels.map(row => row.slice(0, width));
while (cropped.length > 0 && /^\.*$/.test(cropped[cropped.length - 1])) {
cropped.pop();
}
return cropped;
}

/** The logo with wordmark when it fits, the mark alone when only that fits, otherwise nothing. */
export function getBkperLogoLines(maxWidth: number, mode: TerminalColorMode): string[] {
const fullWidth = Math.max(...BKPER_LOGO_PIXELS.map(row => row.length));
if (maxWidth >= fullWidth) {
return renderPixelArt(cropPixels(BKPER_LOGO_PIXELS, fullWidth), BKPER_LOGO_COLORS, mode);
}
if (maxWidth >= BKPER_MARK_WIDTH) {
return renderPixelArt(
cropPixels(BKPER_LOGO_PIXELS, BKPER_MARK_WIDTH),
BKPER_LOGO_COLORS,
mode
);
}
return [];
}
Loading
Loading