Skip to content

Commit c7671ce

Browse files
committed
create initial cli package
1 parent e52f53d commit c7671ce

File tree

11 files changed

+197
-43
lines changed

11 files changed

+197
-43
lines changed

bun.lock

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/package.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"name": "@codebuff/cli",
3+
"version": "0.0.0",
4+
"private": true,
5+
"license": "UNLICENSED",
6+
"type": "module",
7+
"exports": {
8+
".": {
9+
"bun": "./src/index.ts",
10+
"import": "./src/index.ts",
11+
"types": "./src/index.ts",
12+
"default": "./src/index.ts"
13+
},
14+
"./*": {
15+
"bun": "./src/*.ts",
16+
"import": "./src/*.ts",
17+
"types": "./src/*.ts",
18+
"default": "./src/*.ts"
19+
}
20+
},
21+
"scripts": {
22+
"typecheck": "tsc --noEmit -p .",
23+
"test": "bun test"
24+
},
25+
"sideEffects": false,
26+
"engines": {
27+
"bun": ">=1.2.11"
28+
},
29+
"dependencies": {
30+
"@codebuff/display": "workspace:*",
31+
"@codebuff/sdk": "workspace:*"
32+
},
33+
"devDependencies": {
34+
"@types/node": "22",
35+
"@types/bun": "^1.2.11"
36+
}
37+
}

cli/src/index.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
Renderer,
3+
toGraphemeString,
4+
BACKGROUND_COLOR,
5+
COLOR,
6+
COLOR_LIST,
7+
} from '@codebuff/display'
8+
9+
import type { Color, Grapheme, TerminalFrame } from '@codebuff/display'
10+
11+
let displayCharacter = 'x'
12+
let color: Color = COLOR.BLACK
13+
14+
function getFrame(rows: number, columns: number): TerminalFrame {
15+
const terminalFrame: TerminalFrame = {
16+
frame: Array.from({ length: rows }).map((_, i) => {
17+
return Array.from({ length: columns }).map((_, j) => {
18+
if (i === 0 || i === rows - 1 || j === 0 || j === columns - 1) {
19+
return {
20+
grapheme: toGraphemeString(displayCharacter),
21+
textColor: {
22+
type: 'color',
23+
color,
24+
},
25+
backgroundColor: {
26+
type: 'color',
27+
color: BACKGROUND_COLOR.BG_BLACK,
28+
},
29+
textStyles: [],
30+
} satisfies Grapheme
31+
}
32+
return {
33+
grapheme: toGraphemeString(' '),
34+
textColor: {
35+
type: 'color',
36+
color: COLOR.WHITE,
37+
},
38+
backgroundColor: {
39+
type: 'color',
40+
color: BACKGROUND_COLOR.BG_BLACK,
41+
},
42+
}
43+
})
44+
}),
45+
cursor: {
46+
row: 1,
47+
column: 1,
48+
visible: true,
49+
},
50+
}
51+
return terminalFrame
52+
}
53+
54+
// TODO: pipe the old stdout stuff to a file?
55+
const renderer = new Renderer({ stdout: process.stdout, getFrame })
56+
57+
renderer.start()
58+
59+
for (const newChar of 'H e l l o W o r l d') {
60+
await new Promise((resolve) => setTimeout(resolve, 1000))
61+
displayCharacter = newChar
62+
color = COLOR_LIST[Math.floor(Math.random() * COLOR_LIST.length)]
63+
renderer.refreshScreen()
64+
}
65+
await new Promise((resolve) => setTimeout(resolve, 1000))
66+
67+
renderer.exit()

cli/tsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../tsconfig.base.json",
3+
"compilerOptions": {
4+
"types": ["bun", "node"]
5+
},
6+
"include": ["src/**/*.ts"],
7+
"exclude": ["node_modules"]
8+
}

display/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"@types/bun": "^1.2.11"
3232
},
3333
"dependencies": {
34+
"grapheme-splitter": "^1.0.4",
3435
"string-width": "^8.1.0"
3536
}
3637
}

display/src/ansi.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { some } from 'lodash'
22

33
export const RESET = 'RESET' as const
44

5-
export const MODIFIERS = [
5+
export const MODIFIER_LIST = [
66
'BOLD',
77
'DIM',
88
'ITALIC',
@@ -14,9 +14,14 @@ export const MODIFIERS = [
1414
'STRIKETHROUGH',
1515
'DOUBLE_UNDERLINE',
1616
] as const
17-
export type Modifier = (typeof MODIFIERS)[number]
17+
export type Modifier = (typeof MODIFIER_LIST)[number]
18+
export const MODIFIER = Object.fromEntries(
19+
MODIFIER_LIST.map((modifier) => [modifier, modifier]),
20+
) as {
21+
[K in Modifier]: K
22+
}
1823

19-
export const COLORS = [
24+
export const COLOR_LIST = [
2025
'BLACK',
2126
'RED',
2227
'GREEN',
@@ -34,9 +39,14 @@ export const COLORS = [
3439
'BRIGHT_CYAN',
3540
'BRIGHT_WHITE',
3641
] as const
37-
export type Color = (typeof COLORS)[number]
42+
export type Color = (typeof COLOR_LIST)[number]
43+
export const COLOR = Object.fromEntries(
44+
COLOR_LIST.map((color) => [color, color]),
45+
) as {
46+
[K in Color]: K
47+
}
3848

39-
export const BACKGROUND_COLORS = [
49+
export const BACKGROUND_COLOR_LIST = [
4050
'BG_BLACK',
4151
'BG_RED',
4252
'BG_GREEN',
@@ -54,13 +64,18 @@ export const BACKGROUND_COLORS = [
5464
'BG_BRIGHT_CYAN',
5565
'BG_BRIGHT_WHITE',
5666
] as const
57-
export type BackgroundColor = (typeof BACKGROUND_COLORS)[number]
67+
export type BackgroundColor = (typeof BACKGROUND_COLOR_LIST)[number]
68+
export const BACKGROUND_COLOR = Object.fromEntries(
69+
BACKGROUND_COLOR_LIST.map((color) => [color, color]),
70+
) as {
71+
[K in BackgroundColor]: K
72+
}
5873

5974
export const STYLES = [
6075
RESET,
61-
...MODIFIERS,
62-
...COLORS,
63-
...BACKGROUND_COLORS,
76+
...MODIFIER_LIST,
77+
...COLOR_LIST,
78+
...BACKGROUND_COLOR_LIST,
6479
] as const
6580
export type Style = (typeof STYLES)[number]
6681
export const STYLE = {
@@ -134,7 +149,7 @@ export function ansiCode(
134149
}
135150

136151
export function moveCursor(row: number, column: number): string {
137-
return `\x1b[${row};${column}H`
152+
return `\x1b[${row + 1};${column + 1}H`
138153
}
139154

140155
export const HIDE_CURSOR = '\x1b[?25l'

display/src/grapheme-image.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ export function toGraphemeString(grapheme: string): $GraphemeString {
4444
function equalStyles(a: Grapheme, b: Grapheme): boolean {
4545
type GraphemeStyle = Omit<Grapheme, 'grapheme'> &
4646
Partial<Pick<Grapheme, 'grapheme'>>
47-
const aStyles: GraphemeStyle = { ...a }
47+
const aStyles: GraphemeStyle = { textStyles: [], ...a }
4848
delete aStyles.grapheme
49-
const bStyles: GraphemeStyle = { ...b }
49+
const bStyles: GraphemeStyle = { textStyles: [], ...b }
5050
delete bStyles.grapheme
5151
return isEqual(aStyles, bStyles)
5252
}
@@ -141,13 +141,18 @@ export function diffImageCommands(
141141
}
142142

143143
const commands: string[] = []
144-
let prevGrapheme: Grapheme | null = null
145-
let skipped = false
146-
for (const [r, row] of oldImage.entries()) {
144+
let prevWrittenGrapheme: Grapheme | null = null
145+
let skipped = true
146+
for (const [r, newRow] of newImage.entries()) {
147147
const oldRow = oldImage[r]
148-
for (const [c, grapheme] of row.entries()) {
149-
const oldGrapheme = oldRow[c]
150-
if (isEqual(grapheme, oldGrapheme)) {
148+
for (const [c, newGrapheme] of newRow.entries()) {
149+
const prevFrameGrapheme = oldRow[c]
150+
if (
151+
isEqual(
152+
{ textStyles: [], ...newGrapheme },
153+
{ textStyles: [], ...prevFrameGrapheme },
154+
)
155+
) {
151156
skipped = true
152157
continue
153158
}
@@ -157,8 +162,8 @@ export function diffImageCommands(
157162
skipped = false
158163
}
159164

160-
commands.push(...graphemeDiffCommands(prevGrapheme, grapheme))
161-
prevGrapheme = grapheme
165+
commands.push(...graphemeDiffCommands(prevWrittenGrapheme, newGrapheme))
166+
prevWrittenGrapheme = newGrapheme
162167
}
163168
}
164169
return commands

display/src/image-renderer.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ export class Renderer {
126126
const frame = this.getFrame(this.stdout.rows, this.stdout.columns)
127127

128128
const now = Date.now()
129-
// dt / 1000 < 1 / refreshAllFps
130-
if ((now - this.lastFullRefreshTime) * this.refreshAllFps < 1000) {
129+
// dt / 1000 > 1 / refreshAllFps
130+
if ((now - this.lastFullRefreshTime) * this.refreshAllFps > 1000) {
131131
renderAll = true
132132
}
133133

@@ -140,6 +140,7 @@ export class Renderer {
140140
} else {
141141
commands.push(HIDE_CURSOR)
142142
}
143+
this.lastRefreshTime = Date.now()
143144
if (renderAll) {
144145
this.lastFullRefreshTime = Date.now()
145146
}
@@ -163,15 +164,14 @@ export class Renderer {
163164
}
164165

165166
const now = Date.now()
166-
// dt / 1000 < 1 / fps
167-
if ((now - this.lastRefreshTime) * this.fps < 1000) {
168-
this.forceRenderFrame(renderAll)
167+
if (this.timer) {
169168
return
170169
}
171-
170+
// dt / 1000 < 1 / fps
171+
const timeToWait = Math.max(this.lastRefreshTime + 1000 / this.fps - now, 0)
172172
this.timer = setTimeout(() => {
173173
this.forceRenderFrame(renderAll)
174-
}, now - this.lastRefreshTime)
174+
}, timeToWait)
175175
}
176176

177177
public exit() {

display/src/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export * from './ansi'
2+
export * from './grapheme-image'
23
export * from './image-renderer'
3-
4-
export type * from './grapheme-image'

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
"license": "Apache-2.0",
66
"type": "module",
77
"workspaces": [
8-
"display",
8+
"cli",
99
"common",
1010
"backend",
11+
"evals",
12+
"display",
1113
"npm-app",
1214
"web",
1315
"packages/*",
1416
"scripts",
15-
"evals",
1617
"sdk",
1718
".agents"
1819
],

0 commit comments

Comments
 (0)