Skip to content

Commit 5f83f19

Browse files
committed
feat(cli): initial welcome screen
1 parent 06f21c5 commit 5f83f19

File tree

3 files changed

+83
-40
lines changed

3 files changed

+83
-40
lines changed

cli/src/chat.tsx

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { useSystemThemeDetector } from './hooks/use-system-theme-detector'
2626
import { useChatStore } from './state/chat-store'
2727
import { flushAnalytics } from './utils/analytics'
2828
import { getUserCredentials } from './utils/auth'
29+
import { LOGO } from './login/constants'
2930
import { createChatScrollAcceleration } from './utils/chat-scroll-accel'
3031
import { formatQueuedPreview } from './utils/helpers'
3132
import { loadLocalAgents } from './utils/local-agent-registry'
@@ -42,6 +43,10 @@ type ChatVariant = 'ai' | 'user' | 'agent'
4243
const MAX_VIRTUALIZED_TOP_LEVEL = 60
4344
const VIRTUAL_OVERSCAN = 12
4445

46+
const LOGO_BLOCK = LOGO.split('\n')
47+
.filter((line) => line.length > 0)
48+
.join('\n')
49+
4550
type AgentMessage = {
4651
agentName: string
4752
agentType: string
@@ -137,17 +142,20 @@ export const App = ({
137142
const authQuery = useAuthQuery()
138143
const logoutMutation = useLogoutMutation()
139144

140-
// If requireAuth is null (checking), assume not authenticated until proven otherwise
141-
const [isAuthenticated, setIsAuthenticated] = useState(
142-
requireAuth === false ? true : false,
145+
// If requireAuth is null (checking), defer showing auth UI until resolved
146+
const initialAuthState =
147+
requireAuth === false ? true : requireAuth === true ? false : null
148+
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(
149+
initialAuthState,
143150
)
144151
const [user, setUser] = useState<User | null>(null)
145152

146153
// Update authentication state when requireAuth changes
147154
useEffect(() => {
148-
if (requireAuth !== null) {
149-
setIsAuthenticated(!requireAuth)
155+
if (requireAuth === null) {
156+
return
150157
}
158+
setIsAuthenticated(!requireAuth)
151159
}, [requireAuth])
152160

153161
// Update authentication state based on query results
@@ -188,18 +196,44 @@ export const App = ({
188196
useEffect(() => {
189197
if (loadedAgentsData && messages.length === 0) {
190198
const agentListId = 'loaded-agents-list'
199+
const userCredentials = getUserCredentials()
200+
const greeting = userCredentials?.name?.trim().length
201+
? `Welcome back, ${userCredentials.name.trim()}!`
202+
: null
203+
204+
const blocks: ContentBlock[] = [
205+
{
206+
type: 'text',
207+
content: '\n\n' + LOGO_BLOCK,
208+
},
209+
]
210+
211+
if (greeting) {
212+
blocks.push({
213+
type: 'text',
214+
content: greeting,
215+
})
216+
}
217+
218+
blocks.push(
219+
{
220+
type: 'text',
221+
content:
222+
'Codebuff can read and write files in this repository, and run terminal commands to help you build.',
223+
},
224+
{
225+
type: 'agent-list',
226+
id: agentListId,
227+
agents: loadedAgentsData.agents,
228+
agentsDir: loadedAgentsData.agentsDir,
229+
},
230+
)
231+
191232
const initialMessage: ChatMessage = {
192233
id: `system-loaded-agents-${Date.now()}`,
193234
variant: 'ai',
194235
content: '', // Content is in the block
195-
blocks: [
196-
{
197-
type: 'agent-list',
198-
id: agentListId,
199-
agents: loadedAgentsData.agents,
200-
agentsDir: loadedAgentsData.agentsDir,
201-
},
202-
],
236+
blocks,
203237
timestamp: new Date().toISOString(),
204238
}
205239

@@ -298,7 +332,7 @@ export const App = ({
298332
)
299333

300334
useEffect(() => {
301-
if (!isAuthenticated) return
335+
if (isAuthenticated !== true) return
302336

303337
setInputFocused(true)
304338

@@ -1010,7 +1044,7 @@ export const App = ({
10101044
</box>
10111045

10121046
{/* Login Modal Overlay - show when not authenticated */}
1013-
{!isAuthenticated && (
1047+
{isAuthenticated === false && (
10141048
<LoginModal
10151049
onLoginSuccess={handleLoginSuccess}
10161050
theme={theme}

cli/src/components/message-block.tsx

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -249,45 +249,51 @@ export const MessageBlock = ({
249249
): React.ReactNode {
250250
const TRUNCATE_LIMIT = 5
251251
const isCollapsed = collapsedAgents.has(agentListBlock.id)
252-
const { agents, agentsDir } = agentListBlock
252+
const { agents } = agentListBlock
253253

254-
const agentCount = agents.length
255-
const shouldTruncate = agentCount > TRUNCATE_LIMIT
256-
const displayAgents =
257-
shouldTruncate && isCollapsed ? agents.slice(0, TRUNCATE_LIMIT) : agents
254+
const sortedAgents = [...agents].sort((a, b) => {
255+
const aLabel = (a.displayName || a.id).toLowerCase()
256+
const bLabel = (b.displayName || b.id).toLowerCase()
257+
return aLabel.localeCompare(bLabel)
258+
})
258259

260+
const agentCount = sortedAgents.length
261+
const previewAgents = sortedAgents.slice(0, TRUNCATE_LIMIT)
259262
const remainingCount =
260-
shouldTruncate && isCollapsed ? agentCount - TRUNCATE_LIMIT : 0
263+
agentCount > TRUNCATE_LIMIT ? agentCount - TRUNCATE_LIMIT : 0
264+
265+
const formatIdentifier = (agent: { id: string; displayName: string }) =>
266+
agent.displayName && agent.displayName !== agent.id
267+
? `${agent.displayName} (${agent.id})`
268+
: agent.displayName || agent.id
261269

262270
const agentListContent = (
263271
<box style={{ flexDirection: 'column', gap: 0 }}>
264-
{displayAgents.map((agent, idx) => {
265-
const identifier =
266-
agent.displayName && agent.displayName !== agent.id
267-
? `${agent.displayName} (${agent.id})`
268-
: agent.displayName || agent.id
272+
{sortedAgents.map((agent, idx) => {
273+
const identifier = formatIdentifier(agent)
269274
return (
270275
<text key={`agent-${idx}`} wrap fg={theme.agentText}>
271276
{` • ${identifier}`}
272277
</text>
273278
)
274279
})}
275-
{remainingCount > 0 && (
276-
<text
277-
wrap
278-
fg={theme.agentResponseCount}
279-
attributes={TextAttributes.ITALIC}
280-
>
281-
{` ... ${pluralize(remainingCount, 'more agent')}`}
282-
</text>
283-
)}
284280
</box>
285281
)
286282

287-
const headerText = `Loaded ${pluralize(agentCount, 'local agent')}`
283+
const headerText = pluralize(agentCount, 'local agent')
284+
const previewLines = previewAgents.map(
285+
(agent) => ` • ${formatIdentifier(agent)}`,
286+
)
288287
const finishedPreview =
289-
shouldTruncate && isCollapsed
290-
? `${TRUNCATE_LIMIT} agents shown, ${remainingCount} more available`
288+
isCollapsed
289+
? [
290+
...previewLines,
291+
remainingCount > 0
292+
? ` ... ${pluralize(remainingCount, 'more agent')} available`
293+
: null,
294+
]
295+
.filter(Boolean)
296+
.join('\n')
291297
: ''
292298

293299
return (

cli/src/utils/codebuff-client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { CodebuffClient } from '@codebuff/sdk'
22

3+
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
34
import { findGitRoot } from './git'
4-
import { logger } from './logger'
55
import { getAuthTokenDetails } from './auth'
6-
import { API_KEY_ENV_VAR } from '@codebuff/common/old-constants'
6+
import { loadAgentDefinitions } from './load-agent-definitions'
7+
import { logger } from './logger'
78

89
let clientInstance: CodebuffClient | null = null
910

@@ -21,9 +22,11 @@ export function getCodebuffClient(): CodebuffClient | null {
2122

2223
const gitRoot = findGitRoot()
2324
try {
25+
const agentDefinitions = loadAgentDefinitions()
2426
clientInstance = new CodebuffClient({
2527
apiKey,
2628
cwd: gitRoot,
29+
agentDefinitions,
2730
})
2831
} catch (error) {
2932
logger.error(error, 'Failed to initialize CodebuffClient')

0 commit comments

Comments
 (0)