diff --git a/cmd/job/log.go b/cmd/job/log.go index 81b1d25d..d0e2df57 100644 --- a/cmd/job/log.go +++ b/cmd/job/log.go @@ -10,6 +10,7 @@ import ( bkIO "github.com/buildkite/cli/v3/internal/io" "github.com/buildkite/cli/v3/pkg/cmd/factory" "github.com/buildkite/cli/v3/pkg/cmd/validation" + "github.com/mcncl/terminal-to-llm/digest" ) type LogCmd struct { @@ -17,6 +18,10 @@ type LogCmd struct { Pipeline string `help:"Deprecated; ignored because job UUIDs no longer require pipeline or build context" short:"p"` BuildNumber string `help:"Deprecated; ignored because job UUIDs no longer require pipeline or build context" short:"b"` NoTimestamps bool `help:"Strip timestamp prefixes from log output" name:"no-timestamps"` + LLMOptimized bool `help:"Format output to be optimal for LLM consumption (strips ANSI, deduplicates loops)" name:"agent" aliases:"llm"` + Format string `help:"Output rendering for --agent: plain or markdown" name:"format" enum:"plain,markdown" default:"plain"` + MaxTokens int `help:"Hard ceiling on the estimated token count of --agent output (0 = unlimited)" name:"max-tokens"` + NoWindow bool `help:"Disable failure-focused windowing in --agent output (keep all lines)" name:"no-window"` } func (c *LogCmd) Help() string { @@ -27,6 +32,12 @@ Examples: # Strip timestamp prefixes from output $ bk job log 0190046e-e199-453b-a302-a21a4d649d31 --no-timestamps + + # Format for LLM consumption + $ bk job log 0190046e-e199-453b-a302-a21a4d649d31 --agent + + # Format for LLM as markdown, capped at 2000 tokens, keeping all lines + $ bk job log 0190046e-e199-453b-a302-a21a4d649d31 --agent --format markdown --max-tokens 2000 --no-window ` } @@ -73,6 +84,14 @@ func (c *LogCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error { logContent = stripTimestamps(logContent) } + if c.LLMOptimized { + opt := digest.Default() + opt.Format = digest.ParseFormat(c.Format) + opt.MaxTokens = c.MaxTokens + opt.Window = !c.NoWindow + logContent = digest.Process([]byte(logContent), opt) + } + writer, cleanup := bkIO.Pager(f.NoPager) defer func() { _ = cleanup() }() @@ -80,7 +99,10 @@ func (c *LogCmd) Run(kongCtx *kong.Context, globals cli.GlobalFlags) error { return nil } -var timestampRegex = regexp.MustCompile(`bk;t=\d+\x07`) +// timestampRegex matches Buildkite's inline timestamp markers, including the +// optional APC introducer (`\x1b_`) so the whole sequence is removed rather than +// leaving a dangling escape byte behind. +var timestampRegex = regexp.MustCompile(`(?:\x1b_)?bk;t=\d+\x07`) func stripTimestamps(content string) string { return timestampRegex.ReplaceAllString(content, "") diff --git a/cmd/job/log_test.go b/cmd/job/log_test.go new file mode 100644 index 00000000..d320ae96 --- /dev/null +++ b/cmd/job/log_test.go @@ -0,0 +1,12 @@ +package job + +import "testing" + +func TestStripTimestamps(t *testing.T) { + t.Parallel() + + in := "\x1b_bk;t=1700000000000\x07hello" + if got := stripTimestamps(in); got != "hello" { + t.Errorf("stripTimestamps(%q) = %q, want %q", in, got, "hello") + } +} diff --git a/go.mod b/go.mod index 053a99f6..3d2df9fc 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-git/go-git/v5 v5.19.1 github.com/goccy/go-yaml v1.19.2 github.com/google/uuid v1.6.0 + github.com/mcncl/terminal-to-llm v0.0.0-20260625015351-3819cf9f5f9b github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/posthog/posthog-go v1.16.1 github.com/vektah/gqlparser/v2 v2.5.35 diff --git a/go.sum b/go.sum index 56054d51..8d5963e5 100644 --- a/go.sum +++ b/go.sum @@ -126,6 +126,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mcncl/terminal-to-llm v0.0.0-20260625015351-3819cf9f5f9b h1:EY/R1QdVIzyKIYx+EPk/I3jq7W2HTHeMOFGA6Rr1Eps= +github.com/mcncl/terminal-to-llm v0.0.0-20260625015351-3819cf9f5f9b/go.mod h1:+zIpLjV+/ByEl/BRo9rxZdpZAxxQAG0yZg4kxW+hOmM= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=