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
2 changes: 1 addition & 1 deletion bun.lock

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

12 changes: 10 additions & 2 deletions npm-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,13 +240,21 @@ For all commands and options, run 'codebuff' and then type 'help'.
)
const initialInput = isCommand ? '' : filteredArgs.join(' ')

// Handle --agent flag by prefilling user input instead of directly invoking
let finalInitialInput = initialInput
if (options.agent && !initialInput) {
finalInitialInput = `@${options.agent}`
} else if (options.agent && initialInput) {
finalInitialInput = `@${options.agent} ${initialInput}`
}

codebuff({
initialInput,
initialInput: finalInitialInput,
git,
costMode,
runInitFlow: options.init,
model: options.model,
agent: options.agent,
agent: undefined, // Don't pass agent to CLI - use prefilled input instead
params: parsedAgentParams,
print: options.print,
cwd: options.cwd,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import { useQuery } from '@tanstack/react-query'
import { TrendingUp, Users, DollarSign, Play, Calendar } from 'lucide-react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'

interface AgentUsageMetricsProps {
Expand Down Expand Up @@ -89,21 +88,14 @@ export const AgentUsageMetrics = ({

if (isLoading) {
return (
<Card className="mb-6">
<CardHeader>
<CardTitle className="text-lg">Usage Metrics</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex flex-col items-center gap-2">
<Skeleton className="h-6 w-16" />
<Skeleton className="h-4 w-20" />
</div>
))}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
{Array.from({ length: 4 }).map((_, i) => (
<div key={i} className="flex flex-col items-center gap-2">
<Skeleton className="h-6 w-16" />
<Skeleton className="h-4 w-20" />
</div>
</CardContent>
</Card>
))}
</div>
)
}

Expand All @@ -112,68 +104,63 @@ export const AgentUsageMetrics = ({
}

return (
<Card className="mb-6">
<CardHeader>
<CardTitle className="text-lg">Usage Metrics</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-emerald-400" />
<span className="font-medium text-emerald-300">
{formatCurrency(usageMetrics.weekly_spent)}
</span>
</div>
<span className="text-xs text-muted-foreground">Weekly Usage</span>
<div>
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-emerald-400" />
<span className="font-medium text-emerald-300">
{formatCurrency(usageMetrics.weekly_spent)}
</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<Play className="h-4 w-4 text-muted-foreground" />
<span>{formatUsageCount(usageMetrics.usage_count)}</span>
</div>
<span className="text-xs text-muted-foreground">Total Runs</span>
<span className="text-xs text-muted-foreground">Weekly Usage</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<Play className="h-4 w-4 text-muted-foreground" />
<span>{formatUsageCount(usageMetrics.usage_count)}</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-muted-foreground" />
<span>{usageMetrics.unique_users || 0}</span>
</div>
<span className="text-xs text-muted-foreground">Unique Users</span>
<span className="text-xs text-muted-foreground">Total Runs</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<Users className="h-4 w-4 text-muted-foreground" />
<span>{usageMetrics.unique_users || 0}</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-muted-foreground" />
<span>
{formatCurrency(usageMetrics.avg_cost_per_invocation).replace(
'$',
''
)}
</span>
</div>
<span className="text-xs text-muted-foreground">
Avg Cost per Run
<span className="text-xs text-muted-foreground">Unique Users</span>
</div>
<div className="flex flex-col items-center gap-2">
<div className="flex items-center gap-2">
<DollarSign className="h-4 w-4 text-muted-foreground" />
<span>
{formatCurrency(usageMetrics.avg_cost_per_invocation).replace(
'$',
''
)}
</span>
</div>
<span className="text-xs text-muted-foreground">
Avg Cost per Run
</span>
</div>
{usageMetrics.last_used && (
<div className="mt-4 pt-4 border-t border-border/40">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="h-4 w-4" />
<span>
Last used:{' '}
{new Date(usageMetrics.last_used).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</span>
</div>
</div>
{usageMetrics.last_used && (
<div className="mt-4 pt-4 border-t border-border/40">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<Calendar className="h-4 w-4" />
<span>
Last used:{' '}
{new Date(usageMetrics.last_used).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
})}
</span>
</div>
)}
</CardContent>
</Card>
</div>
)}
</div>
)
}
53 changes: 35 additions & 18 deletions web/src/app/publishers/[id]/agents/[agentId]/[version]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { AgentUsageMetrics } from './agent-usage-metrics'
import { RunAgentButton } from './run-agent-button'
import { CopyIdButton } from './copy-id-button'
import { SaveAgentButton } from './save-agent-button'
import { VersionUsageBadge } from './version-usage-badge'
import { Button } from '@/components/ui/button'

interface AgentDetailPageProps {
Expand Down Expand Up @@ -200,12 +201,6 @@ const AgentDetailPage = async ({ params }: AgentDetailPageProps) => {
</div>
</CardHeader>
</Card>
{/* Usage Metrics */}
<AgentUsageMetrics
publisherId={params.id}
agentId={params.agentId}
version={params.version}
/>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Version Navigation */}
<div className="lg:col-span-1">
Expand Down Expand Up @@ -236,9 +231,18 @@ const AgentDetailPage = async ({ params }: AgentDetailPageProps) => {
className="w-full justify-start group transition-colors"
>
<div className="flex items-center justify-between w-full">
<span className="font-mono">
v{version.version}
</span>
<div className="flex items-center">
<span className="font-mono">
v{version.version}
</span>
{index !== 0 && (
<VersionUsageBadge
publisherId={params.id}
agentId={params.agentId}
version={version.version}
/>
)}
</div>
{index === 0 && (
<Badge
className={cn(
Expand All @@ -260,17 +264,30 @@ const AgentDetailPage = async ({ params }: AgentDetailPageProps) => {
</Card>
</div>

{/* Agent Definition */}
{/* Agent Definition and Usage Stats Combined */}
<div className="lg:col-span-3">
<Card>
<CardHeader>
<CardTitle className="text-lg">Agent Definition</CardTitle>
<p className="text-sm text-muted-foreground">
Complete agent data in TypeScript format
</p>
</CardHeader>
<CardContent>
<TypeScriptViewer data={agentData} />
<CardContent className="space-y-6 pt-6">
{/* Usage Metrics for this version */}
<div>
<h3 className="text-base font-semibold mb-3 flex items-center gap-2">
Usage Statistics
<Badge variant="secondary" className="text-xs">
v{params.version}
</Badge>
</h3>
<AgentUsageMetrics
publisherId={params.id}
agentId={params.agentId}
version={params.version}
/>
</div>

{/* Agent Definition */}
<div className="border-t pt-6">
<h3 className="text-base font-semibold mb-3">Definition</h3>
<TypeScriptViewer data={agentData} />
</div>
</CardContent>
</Card>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { Copy } from 'lucide-react'
import { Play } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'

Expand All @@ -12,7 +12,7 @@ export function RunAgentButton({ agentId }: RunAgentButtonProps) {
const handleCopy = () => {
navigator.clipboard.writeText(`codebuff --agent ${agentId}`)
toast({
description: `Command copied to clipboard: "codebuff --agent ${agentId}"`,
description: `Command copied! Go to your terminal and paste to run this agent.`,
})
}

Expand All @@ -23,7 +23,7 @@ export function RunAgentButton({ agentId }: RunAgentButtonProps) {
onClick={handleCopy}
className="flex items-center gap-2"
>
<Copy className="h-4 w-4" />
<Play className="h-4 w-4" />
Run this agent
</Button>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client'

import { Copy } from 'lucide-react'
import { Bookmark } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { toast } from '@/components/ui/use-toast'

Expand All @@ -12,7 +12,7 @@ export function SaveAgentButton({ agentId }: SaveAgentButtonProps) {
const handleCopy = () => {
navigator.clipboard.writeText(`codebuff save-agent ${agentId}`)
toast({
description: `Command copied to clipboard: "codebuff save-agent ${agentId}"`,
description: `Command copied! Go to your terminal and paste to save this agent to your project.`,
})
}

Expand All @@ -23,7 +23,7 @@ export function SaveAgentButton({ agentId }: SaveAgentButtonProps) {
onClick={handleCopy}
className="flex items-center gap-2"
>
<Copy className="h-4 w-4" />
<Bookmark className="h-4 w-4" />
Save this agent
</Button>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
'use client'

import { useQuery } from '@tanstack/react-query'
import { Badge } from '@/components/ui/badge'
import { Skeleton } from '@/components/ui/skeleton'

interface VersionUsageBadgeProps {
publisherId: string
agentId: string
version: string
}

interface AgentData {
id: string
publisher: {
id: string
}
version_stats?: Record<
string,
{
total_invocations: number
}
>
}

const formatUsageCount = (count?: number) => {
if (!count) return '0'
if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`
if (count >= 1000) return `${(count / 1000).toFixed(1)}K`
return count.toString()
}

export const VersionUsageBadge = ({
publisherId,
agentId,
version,
}: VersionUsageBadgeProps) => {
const { data: agents, isLoading } = useQuery<AgentData[]>({
queryKey: ['agents'],
queryFn: async () => {
const response = await fetch('/api/agents')
if (!response.ok) {
throw new Error('Failed to fetch agents')
}
return await response.json()
},
})

const agent = agents?.find(
(agent) => agent.id === agentId && agent.publisher.id === publisherId
)

const totalRuns = agent?.version_stats?.[version]?.total_invocations || 0

if (isLoading) {
return <Skeleton className="h-4 w-8" />
}

if (totalRuns === 0) {
return null
}

return (
<Badge variant="secondary" className="text-xs px-1.5 py-0 ml-2">
{formatUsageCount(totalRuns)} runs
</Badge>
)
}
Loading