From 14649538bee250a9c5f6a51b0784ab4f530f184e Mon Sep 17 00:00:00 2001 From: Eden Reich Date: Sun, 5 Oct 2025 15:32:56 +0200 Subject: [PATCH 1/6] docs: Improve screenshot and CSV writing skills with artifact integration and context handling Signed-off-by: Eden Reich --- example/README.md | 6 ++ example/docker-compose.yaml | 15 ++-- example/downloads/.gitignore | 2 + example/integration_demo.md | 76 ----------------- go.mod | 2 +- go.sum | 4 +- skills/take_screenshot.go | 47 ++++++++--- skills/take_screenshot_test.go | 69 ++++++++++------ skills/write_to_csv.go | 145 +++++++++++++++++---------------- skills/write_to_csv_test.go | 105 +++++++++--------------- 10 files changed, 213 insertions(+), 258 deletions(-) create mode 100644 example/downloads/.gitignore delete mode 100644 example/integration_demo.md diff --git a/example/README.md b/example/README.md index dfe0510..2209d3c 100644 --- a/example/README.md +++ b/example/README.md @@ -37,6 +37,12 @@ Check the logs to see that the browser indeed went to the demo site and took a s docker compose logs -f demo-site ``` +Also you can check the task was successfully submitted to the agent and it's available using the a2a debugger: + +```bash +docker compose run --rm a2a-debugger tasks list +``` + Finally clean up: ```bash diff --git a/example/docker-compose.yaml b/example/docker-compose.yaml index 8a43004..5c4e1bc 100644 --- a/example/docker-compose.yaml +++ b/example/docker-compose.yaml @@ -5,6 +5,10 @@ services: context: .. dockerfile: Dockerfile container_name: agent + ports: + - 8081:8081 + volumes: + - ./artifacts:/tmp/artifacts environment: BROWSER_USER_AGENT: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" BROWSER_VIEWPORT_WIDTH: "1920" @@ -61,13 +65,12 @@ services: A2A_QUEUE_CLEANUP_INTERVAL: 500s A2A_AUTH_ENABLE: false A2A_ARTIFACTS_ENABLE: true - A2A_ARTIFACTS_SERVER_HOST: localhost + A2A_ARTIFACTS_SERVER_HOST: agent A2A_ARTIFACTS_SERVER_PORT: 8081 A2A_ARTIFACTS_STORAGE_PROVIDER: filesystem - A2A_ARTIFACTS_STORAGE_BASE_PATH: ./artifacts - A2A_ARTIFACTS_STORAGE_BASE_URL: http://agent:8081 + A2A_ARTIFACTS_STORAGE_BASE_PATH: /tmp/artifacts A2A_ARTIFACTS_RETENTION_MAX_ARTIFACTS: 10 - A2A_ARTIFACTS_RETENTION_MAX_AGE: 7d + A2A_ARTIFACTS_RETENTION_MAX_AGE: 24h A2A_ARTIFACTS_RETENTION_CLEANUP_INTERVAL: 24h networks: - a2a-network @@ -91,7 +94,7 @@ services: image: ghcr.io/inference-gateway/cli:latest pull_policy: always volumes: - - ./downloads:/tmp/artifacts + - ./downloads:/tmp/downloads environment: INFER_LOGGING_DEBUG: true INFER_GATEWAY_URL: http://inference-gateway:8080 @@ -100,7 +103,7 @@ services: INFER_AGENT_MODEL: deepseek/deepseek-chat INFER_A2A_AGENTS: | http://agent:8080 - INFER_DOWNLOAD_DIR: /tmp/artifacts + INFER_DOWNLOAD_DIR: /tmp/downloads command: - chat networks: diff --git a/example/downloads/.gitignore b/example/downloads/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/example/downloads/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/example/integration_demo.md b/example/integration_demo.md deleted file mode 100644 index 00f1148..0000000 --- a/example/integration_demo.md +++ /dev/null @@ -1,76 +0,0 @@ -# CSV Export Integration Demo - -This document demonstrates how to use the new `write_to_csv` skill in combination with the existing `extract_data` skill for complete data collection workflows. - -## Workflow Example - -1. **Navigate to a webpage**: - ```json - { - "skill": "navigate_to_url", - "args": { - "url": "https://example.com/products" - } - } - ``` - -2. **Extract data from the page**: - ```json - { - "skill": "extract_data", - "args": { - "extractors": [ - { - "name": "product_name", - "selector": ".product-title", - "multiple": true - }, - { - "name": "price", - "selector": ".product-price", - "multiple": true - }, - { - "name": "rating", - "selector": ".product-rating", - "attribute": "data-rating", - "multiple": true - } - ], - "format": "json" - } - } - ``` - -3. **Write the extracted data to CSV**: - ```json - { - "skill": "write_to_csv", - "args": { - "data": [ - {"product_name": "Product A", "price": "$29.99", "rating": "4.5"}, - {"product_name": "Product B", "price": "$39.99", "rating": "4.2"}, - {"product_name": "Product C", "price": "$19.99", "rating": "4.8"} - ], - "file_path": "/tmp/products.csv", - "headers": ["product_name", "price", "rating"], - "include_headers": true - } - } - ``` - -## Features Supported - -- **Custom Headers**: Specify column order and names -- **Append Mode**: Add to existing CSV files without overwriting -- **Flexible Data**: Handles arrays, objects, and primitive values -- **Error Handling**: Validates data format and file operations -- **Directory Creation**: Automatically creates parent directories - -## Use Cases - -- **E-commerce Data Collection**: Extract product information, prices, and reviews -- **News Aggregation**: Collect headlines, dates, and article links -- **Financial Data**: Gather stock prices, market data, and trading volumes -- **Contact Information**: Extract business details from directory sites -- **Event Listings**: Collect event names, dates, venues, and prices \ No newline at end of file diff --git a/go.mod b/go.mod index 399639b..a43232a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/inference-gateway/browser-agent go 1.25 require ( - github.com/inference-gateway/adk v0.12.1 + github.com/inference-gateway/adk v0.13.0 github.com/playwright-community/playwright-go v0.5200.1 github.com/sethvargo/go-envconfig v1.3.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 6abbde5..7487119 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/inference-gateway/adk v0.12.1 h1:rdubWG4YTLLEhSURe3vatTL1aTB71ivIVqfrHZn2KDM= -github.com/inference-gateway/adk v0.12.1/go.mod h1:Eh91HM5d3R0I5OOAh3YNUqZCJBBdGPHrKBALnVL8dl0= +github.com/inference-gateway/adk v0.13.0 h1:UMaiaM0/gpPxyCzTDm48aIXw9X2xjLGESOLcyTMcqaI= +github.com/inference-gateway/adk v0.13.0/go.mod h1:Eh91HM5d3R0I5OOAh3YNUqZCJBBdGPHrKBALnVL8dl0= github.com/inference-gateway/sdk v1.10.0 h1:88m1XTS5J7Q9+sFaKXKHAPXdDpji6SASXVWz2pe8ZFk= github.com/inference-gateway/sdk v1.10.0/go.mod h1:3TTD7Kbr7FRt+9ZbCPAm3u0tXUIWx7flZuwrRgZgrdk= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/skills/take_screenshot.go b/skills/take_screenshot.go index 9ef7d8b..072bb0a 100644 --- a/skills/take_screenshot.go +++ b/skills/take_screenshot.go @@ -9,26 +9,25 @@ import ( "time" server "github.com/inference-gateway/adk/server" + types "github.com/inference-gateway/adk/types" playwright "github.com/inference-gateway/browser-agent/internal/playwright" zap "go.uber.org/zap" ) // TakeScreenshotSkill struct holds the skill with dependencies type TakeScreenshotSkill struct { - logger *zap.Logger - playwright playwright.BrowserAutomation - artifactHelper *server.ArtifactHelper - screenshotDir string + logger *zap.Logger + playwright playwright.BrowserAutomation + screenshotDir string } // NewTakeScreenshotSkill creates a new take_screenshot skill func NewTakeScreenshotSkill(logger *zap.Logger, playwright playwright.BrowserAutomation) server.Tool { cfg := playwright.GetConfig() skill := &TakeScreenshotSkill{ - logger: logger, - playwright: playwright, - artifactHelper: server.NewArtifactHelper(), - screenshotDir: cfg.Browser.DataDir, + logger: logger, + playwright: playwright, + screenshotDir: cfg.Browser.DataDir, } return server.NewBasicTool( "take_screenshot", @@ -64,6 +63,18 @@ func NewTakeScreenshotSkill(logger *zap.Logger, playwright playwright.BrowserAut // TakeScreenshotHandler handles the take_screenshot skill execution func (s *TakeScreenshotSkill) TakeScreenshotHandler(ctx context.Context, args map[string]any) (string, error) { + artifactHelper, ok := ctx.Value(server.ArtifactHelperContextKey).(*server.ArtifactHelper) + if !ok { + s.logger.Warn("unable to get artifact helper from context") + return "", fmt.Errorf("artifact helper not available in context") + } + + task, ok := ctx.Value(server.TaskContextKey).(*types.Task) + if !ok { + s.logger.Warn("unable to get task from context") + return "", fmt.Errorf("task not available in context") + } + generatedPath, err := s.generateDeterministicPath(args) if err != nil { s.logger.Error("failed to generate screenshot path", zap.Error(err)) @@ -135,7 +146,7 @@ func (s *TakeScreenshotSkill) TakeScreenshotHandler(ctx context.Context, args ma mimeType := s.getMimeType(imageType) filename := filepath.Base(generatedPath) - screenshotArtifact := s.artifactHelper.CreateFileArtifactFromBytes( + screenshotArtifact := artifactHelper.CreateFileArtifactFromBytes( fmt.Sprintf("Screenshot: %s", filename), fmt.Sprintf("Screenshot captured from browser session %s", session.ID), filename, @@ -147,15 +158,27 @@ func (s *TakeScreenshotSkill) TakeScreenshotHandler(ctx context.Context, args ma screenshotArtifact.Metadata = metadata } + artifactHelper.AddArtifactToTask(task, screenshotArtifact) + s.logger.Info("artifact added to task", + zap.String("taskID", task.ID), + zap.String("artifactID", screenshotArtifact.ArtifactID)) + + if err := os.Remove(generatedPath); err != nil { + s.logger.Warn("failed to clean up temporary screenshot file", + zap.String("path", generatedPath), + zap.Error(err)) + } else { + s.logger.Debug("cleaned up temporary screenshot file", zap.String("path", generatedPath)) + } + s.logger.Info("screenshot completed successfully", - zap.String("path", generatedPath), zap.String("sessionID", session.ID), zap.String("artifactID", screenshotArtifact.ArtifactID), zap.Int("fileSize", len(screenshotData))) response := map[string]any{ "success": true, - "path": generatedPath, + "filename": filename, "full_page": fullPage, "type": imageType, "quality": quality, @@ -164,7 +187,7 @@ func (s *TakeScreenshotSkill) TakeScreenshotHandler(ctx context.Context, args ma "artifact_id": screenshotArtifact.ArtifactID, "file_size": len(screenshotData), "timestamp": s.getCurrentTimestamp(), - "message": "Screenshot captured successfully with deterministic naming and stored as artifact", + "message": "Screenshot captured successfully and stored as artifact", } responseJSON, err := json.Marshal(response) diff --git a/skills/take_screenshot_test.go b/skills/take_screenshot_test.go index b80c57f..9e569cc 100644 --- a/skills/take_screenshot_test.go +++ b/skills/take_screenshot_test.go @@ -10,6 +10,7 @@ import ( "time" server "github.com/inference-gateway/adk/server" + types "github.com/inference-gateway/adk/types" config "github.com/inference-gateway/browser-agent/config" playwright "github.com/inference-gateway/browser-agent/internal/playwright" mocks "github.com/inference-gateway/browser-agent/internal/playwright/mocks" @@ -41,10 +42,9 @@ func createTestSkill() *TakeScreenshotSkill { }) return &TakeScreenshotSkill{ - logger: logger, - playwright: mockPlaywright, - artifactHelper: server.NewArtifactHelper(), - screenshotDir: "test_screenshots", + logger: logger, + playwright: mockPlaywright, + screenshotDir: "test_screenshots", } } @@ -54,6 +54,11 @@ func TestTakeScreenshotHandler_BasicFunctionality(t *testing.T) { args := map[string]any{} ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ + ID: "test-task-123", + }) + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -69,21 +74,17 @@ func TestTakeScreenshotHandler_BasicFunctionality(t *testing.T) { t.Errorf("Expected success to be true, got: %v", response["success"]) } - resultPath, ok := response["path"].(string) + resultFilename, ok := response["filename"].(string) if !ok { - t.Errorf("Expected path in response, got: %v", response["path"]) - } - - if !filepath.IsAbs(resultPath) && !strings.HasPrefix(resultPath, "test_screenshots/") { - t.Errorf("Expected path to start with test_screenshots/, got: %s", resultPath) + t.Errorf("Expected filename in response, got: %v", response["filename"]) } - if !strings.Contains(resultPath, "viewport_") { - t.Errorf("Expected viewport screenshot filename, got: %s", resultPath) + if !strings.Contains(resultFilename, "viewport_") { + t.Errorf("Expected viewport screenshot filename, got: %s", resultFilename) } - if _, err := os.Stat(resultPath); os.IsNotExist(err) { - t.Errorf("Expected screenshot file to be created at %s", resultPath) + if !strings.HasSuffix(resultFilename, ".png") { + t.Errorf("Expected .png extension in filename, got: %s", resultFilename) } _ = os.RemoveAll("test_screenshots") @@ -97,6 +98,9 @@ func TestTakeScreenshotHandler_FullPageScreenshot(t *testing.T) { } ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -112,9 +116,9 @@ func TestTakeScreenshotHandler_FullPageScreenshot(t *testing.T) { t.Errorf("Expected full_page to be true, got: %v", response["full_page"]) } - resultPath, ok := response["path"].(string) - if !ok || !strings.Contains(resultPath, "fullpage_") { - t.Errorf("Expected fullpage screenshot filename, got: %s", resultPath) + resultFilename, ok := response["filename"].(string) + if !ok || !strings.Contains(resultFilename, "fullpage_") { + t.Errorf("Expected fullpage screenshot filename, got: %s", resultFilename) } _ = os.RemoveAll("test_screenshots") @@ -129,6 +133,9 @@ func TestTakeScreenshotHandler_JPEGWithQuality(t *testing.T) { } ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -148,9 +155,9 @@ func TestTakeScreenshotHandler_JPEGWithQuality(t *testing.T) { t.Errorf("Expected quality to be 95, got: %v", response["quality"]) } - resultPath, ok := response["path"].(string) - if !ok || !strings.HasSuffix(resultPath, ".jpeg") { - t.Errorf("Expected .jpeg extension in filename, got: %s", resultPath) + resultFilename, ok := response["filename"].(string) + if !ok || !strings.HasSuffix(resultFilename, ".jpeg") { + t.Errorf("Expected .jpeg extension in filename, got: %s", resultFilename) } _ = os.RemoveAll("test_screenshots") @@ -164,6 +171,9 @@ func TestTakeScreenshotHandler_ElementSelector(t *testing.T) { } ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -179,9 +189,9 @@ func TestTakeScreenshotHandler_ElementSelector(t *testing.T) { t.Errorf("Expected selector to be #main-content, got: %v", response["selector"]) } - resultPath, ok := response["path"].(string) - if !ok || !strings.Contains(resultPath, "element_") { - t.Errorf("Expected element screenshot filename, got: %s", resultPath) + resultFilename, ok := response["filename"].(string) + if !ok || !strings.Contains(resultFilename, "element_") { + t.Errorf("Expected element screenshot filename, got: %s", resultFilename) } _ = os.RemoveAll("test_screenshots") @@ -193,6 +203,9 @@ func TestTakeScreenshotHandler_DeterministicPath(t *testing.T) { args := map[string]any{} ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -204,8 +217,8 @@ func TestTakeScreenshotHandler_DeterministicPath(t *testing.T) { t.Fatalf("Failed to parse response JSON: %v", err) } - if _, ok := response["path"]; !ok { - t.Error("Expected path to be generated in response") + if _, ok := response["filename"]; !ok { + t.Error("Expected filename to be generated in response") } _ = os.RemoveAll("test_screenshots") @@ -219,6 +232,9 @@ func TestTakeScreenshotHandler_InvalidImageType(t *testing.T) { } ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + _, err := skill.TakeScreenshotHandler(ctx, args) if err == nil { @@ -242,6 +258,9 @@ func TestTakeScreenshotHandler_InvalidQuality(t *testing.T) { } ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + _, err := skill.TakeScreenshotHandler(ctx, args) if err == nil { diff --git a/skills/write_to_csv.go b/skills/write_to_csv.go index d76e0b8..62c6b08 100644 --- a/skills/write_to_csv.go +++ b/skills/write_to_csv.go @@ -1,6 +1,7 @@ package skills import ( + "bytes" "context" "encoding/csv" "fmt" @@ -9,6 +10,7 @@ import ( "strconv" server "github.com/inference-gateway/adk/server" + types "github.com/inference-gateway/adk/types" playwright "github.com/inference-gateway/browser-agent/internal/playwright" zap "go.uber.org/zap" ) @@ -64,6 +66,18 @@ func NewWriteToCsvSkill(logger *zap.Logger, playwright playwright.BrowserAutomat // WriteToCsvHandler handles the write_to_csv skill execution func (s *WriteToCsvSkill) WriteToCsvHandler(ctx context.Context, args map[string]any) (string, error) { + artifactHelper, ok := ctx.Value(server.ArtifactHelperContextKey).(*server.ArtifactHelper) + if !ok { + s.logger.Warn("unable to get artifact helper from context") + return "", fmt.Errorf("artifact helper not available in context") + } + + task, ok := ctx.Value(server.TaskContextKey).(*types.Task) + if !ok { + s.logger.Warn("unable to get task from context") + return "", fmt.Errorf("task not available in context") + } + data, ok := args["data"].([]any) if !ok || len(data) == 0 { s.logger.Error("data parameter is required and must be a non-empty array") @@ -118,19 +132,69 @@ func (s *WriteToCsvSkill) WriteToCsvHandler(ctx context.Context, args map[string headers = s.extractHeadersFromRows(rows) } - rowsWritten, err := s.writeCSVFile(filePath, headers, rows, append, includeHeaders) - if err != nil { - s.logger.Error("failed to write CSV file", - zap.String("file_path", filePath), - zap.Error(err)) - return "", fmt.Errorf("failed to write CSV file: %w", err) + var csvBuffer bytes.Buffer + writer := csv.NewWriter(&csvBuffer) + + if includeHeaders && len(headers) > 0 { + if err := writer.Write(headers); err != nil { + return "", fmt.Errorf("failed to write headers: %w", err) + } } - result := fmt.Sprintf("Successfully wrote %d rows to %s", rowsWritten, filePath) - s.logger.Info("CSV file written successfully", - zap.String("file_path", filePath), - zap.Int("rows_written", rowsWritten)) + rowsWritten := 0 + for _, row := range rows { + csvRow := make([]string, len(headers)) + for i, header := range headers { + if value, exists := row[header]; exists { + csvRow[i] = s.valueToString(value) + } else { + csvRow[i] = "" + } + } + + if err := writer.Write(csvRow); err != nil { + return "", fmt.Errorf("failed to write row: %w", err) + } + rowsWritten++ + } + + writer.Flush() + if err := writer.Error(); err != nil { + return "", fmt.Errorf("CSV writer error: %w", err) + } + csvData := csvBuffer.Bytes() + + mimeType := "text/csv" + baseFilename := filepath.Base(filePath) + csvArtifact := artifactHelper.CreateFileArtifactFromBytes( + fmt.Sprintf("CSV File: %s", baseFilename), + fmt.Sprintf("CSV file with %d rows written to %s", rowsWritten, filePath), + baseFilename, + csvData, + &mimeType, + ) + + csvArtifact.Metadata = map[string]any{ + "rows_written": rowsWritten, + "headers": headers, + "include_headers": includeHeaders, + "append_mode": append, + "file_size": len(csvData), + "original_records": len(data), + } + + artifactHelper.AddArtifactToTask(task, csvArtifact) + s.logger.Info("CSV artifact added to task", + zap.String("taskID", task.ID), + zap.String("artifactID", csvArtifact.ArtifactID)) + + s.logger.Info("CSV data created successfully as artifact", + zap.String("filename", baseFilename), + zap.Int("rows_written", rowsWritten), + zap.String("artifactID", csvArtifact.ArtifactID)) + + result := fmt.Sprintf("Successfully created CSV with %d rows as artifact %s (%s)", rowsWritten, csvArtifact.ArtifactID, baseFilename) return result, nil } @@ -196,67 +260,6 @@ func (s *WriteToCsvSkill) extractHeadersFromRows(rows []map[string]any) []string return headers } -func (s *WriteToCsvSkill) writeCSVFile(filePath string, headers []string, rows []map[string]any, append bool, includeHeaders bool) (int, error) { - dir := filepath.Dir(filePath) - if err := os.MkdirAll(dir, 0755); err != nil { - return 0, fmt.Errorf("failed to create directory %s: %w", dir, err) - } - - flag := os.O_CREATE | os.O_WRONLY - if append { - flag |= os.O_APPEND - } else { - flag |= os.O_TRUNC - } - - fileExists := false - if append { - if info, err := os.Stat(filePath); err == nil && info.Size() > 0 { - fileExists = true - } - } - - file, err := os.OpenFile(filePath, flag, 0644) - if err != nil { - return 0, fmt.Errorf("failed to open file %s: %w", filePath, err) - } - defer func() { - if closeErr := file.Close(); closeErr != nil { - s.logger.Error("failed to close file", zap.String("file_path", filePath), zap.Error(closeErr)) - } - }() - - writer := csv.NewWriter(file) - defer writer.Flush() - - rowsWritten := 0 - - if includeHeaders && (!append || !fileExists) { - if len(headers) > 0 { - if err := writer.Write(headers); err != nil { - return 0, fmt.Errorf("failed to write headers: %w", err) - } - } - } - - for _, row := range rows { - csvRow := make([]string, len(headers)) - for i, header := range headers { - if value, exists := row[header]; exists { - csvRow[i] = s.valueToString(value) - } else { - csvRow[i] = "" - } - } - - if err := writer.Write(csvRow); err != nil { - return rowsWritten, fmt.Errorf("failed to write row: %w", err) - } - rowsWritten++ - } - - return rowsWritten, nil -} func (s *WriteToCsvSkill) valueToString(value any) string { if value == nil { diff --git a/skills/write_to_csv_test.go b/skills/write_to_csv_test.go index 5b91cef..e01c028 100644 --- a/skills/write_to_csv_test.go +++ b/skills/write_to_csv_test.go @@ -2,12 +2,11 @@ package skills import ( "context" - "encoding/csv" - "os" - "path/filepath" "strings" "testing" + server "github.com/inference-gateway/adk/server" + types "github.com/inference-gateway/adk/types" config "github.com/inference-gateway/browser-agent/config" mocks "github.com/inference-gateway/browser-agent/internal/playwright/mocks" zap "go.uber.org/zap" @@ -15,11 +14,10 @@ import ( func TestWriteToCsvHandler(t *testing.T) { logger := zap.NewNop() - tempDir := t.TempDir() mockPlaywright := &mocks.FakeBrowserAutomation{} mockPlaywright.GetConfigReturns(&config.Config{ Browser: config.BrowserConfig{ - DataDir: tempDir, + DataDir: "/tmp", }, }) @@ -33,7 +31,7 @@ func TestWriteToCsvHandler(t *testing.T) { args map[string]any expectedError bool expectedRows int - validateOutput func(t *testing.T, filePath string) + validateOutput func(t *testing.T, result string) }{ { name: "basic CSV writing", @@ -46,20 +44,15 @@ func TestWriteToCsvHandler(t *testing.T) { }, expectedError: false, expectedRows: 2, - validateOutput: func(t *testing.T, filePath string) { - fullPath := filepath.Join(tempDir, "basic.csv") - content, err := os.ReadFile(fullPath) - if err != nil { - t.Fatalf("Failed to read output file: %v", err) + validateOutput: func(t *testing.T, result string) { + if !strings.Contains(result, "2 rows") { + t.Errorf("Expected result to mention 2 rows, got: %s", result) } - - lines := strings.Split(strings.TrimSpace(string(content)), "\n") - if len(lines) != 3 { - t.Errorf("Expected 3 lines, got %d", len(lines)) + if !strings.Contains(result, "basic.csv") { + t.Errorf("Expected result to mention basic.csv, got: %s", result) } - - if !strings.Contains(lines[0], "name") { - t.Error("Expected headers to contain 'name'") + if !strings.Contains(result, "artifact") { + t.Errorf("Expected result to mention artifact, got: %s", result) } }, }, @@ -75,30 +68,15 @@ func TestWriteToCsvHandler(t *testing.T) { }, expectedError: false, expectedRows: 2, - validateOutput: func(t *testing.T, filePath string) { - fullPath := filepath.Join(tempDir, "custom_headers.csv") - file, err := os.Open(fullPath) - if err != nil { - t.Fatalf("Failed to open output file: %v", err) + validateOutput: func(t *testing.T, result string) { + if !strings.Contains(result, "2 rows") { + t.Errorf("Expected result to mention 2 rows, got: %s", result) } - defer func() { - if closeErr := file.Close(); closeErr != nil { - t.Logf("Failed to close file: %v", closeErr) - } - }() - - reader := csv.NewReader(file) - records, err := reader.ReadAll() - if err != nil { - t.Fatalf("Failed to read CSV: %v", err) - } - - if len(records) != 3 { - t.Errorf("Expected 3 records, got %d", len(records)) + if !strings.Contains(result, "custom_headers.csv") { + t.Errorf("Expected result to mention custom_headers.csv, got: %s", result) } - - if records[0][0] != "name" || records[0][1] != "age" { - t.Errorf("Headers not in expected order: %v", records[0]) + if !strings.Contains(result, "artifact") { + t.Errorf("Expected result to mention artifact, got: %s", result) } }, }, @@ -114,16 +92,15 @@ func TestWriteToCsvHandler(t *testing.T) { }, expectedError: false, expectedRows: 2, - validateOutput: func(t *testing.T, filePath string) { - fullPath := filepath.Join(tempDir, "no_headers.csv") - content, err := os.ReadFile(fullPath) - if err != nil { - t.Fatalf("Failed to read output file: %v", err) + validateOutput: func(t *testing.T, result string) { + if !strings.Contains(result, "2 rows") { + t.Errorf("Expected result to mention 2 rows, got: %s", result) } - - lines := strings.Split(strings.TrimSpace(string(content)), "\n") - if len(lines) != 2 { - t.Errorf("Expected 2 lines, got %d", len(lines)) + if !strings.Contains(result, "no_headers.csv") { + t.Errorf("Expected result to mention no_headers.csv, got: %s", result) + } + if !strings.Contains(result, "artifact") { + t.Errorf("Expected result to mention artifact, got: %s", result) } }, }, @@ -138,20 +115,15 @@ func TestWriteToCsvHandler(t *testing.T) { }, expectedError: false, expectedRows: 1, - validateOutput: func(t *testing.T, filePath string) { - fullPath := filepath.Join(tempDir, "basic.csv") - content, err := os.ReadFile(fullPath) - if err != nil { - t.Fatalf("Failed to read output file: %v", err) + validateOutput: func(t *testing.T, result string) { + if !strings.Contains(result, "1 rows") { + t.Errorf("Expected result to mention 1 rows, got: %s", result) } - - lines := strings.Split(strings.TrimSpace(string(content)), "\n") - if len(lines) != 4 { - t.Errorf("Expected 4 lines after append, got %d", len(lines)) + if !strings.Contains(result, "basic.csv") { + t.Errorf("Expected result to mention basic.csv, got: %s", result) } - - if !strings.Contains(string(content), "Charlie") { - t.Error("Expected appended data to contain 'Charlie'") + if !strings.Contains(result, "artifact") { + t.Errorf("Expected result to mention artifact, got: %s", result) } }, }, @@ -183,7 +155,11 @@ func TestWriteToCsvHandler(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - result, err := skill.WriteToCsvHandler(context.Background(), tt.args) + ctx := context.Background() + ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) + ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) + + result, err := skill.WriteToCsvHandler(ctx, tt.args) if tt.expectedError { if err == nil { @@ -197,13 +173,12 @@ func TestWriteToCsvHandler(t *testing.T) { return } - if !strings.Contains(result, "Successfully wrote") { + if !strings.Contains(result, "Successfully created CSV") { t.Errorf("Expected success message, got: %s", result) } if tt.validateOutput != nil { - filename := tt.args["filename"].(string) - tt.validateOutput(t, filename) + tt.validateOutput(t, result) } }) } From c7078f22f8059f157d771a79ca71bc6404339221 Mon Sep 17 00:00:00 2001 From: Eden Reich Date: Sun, 5 Oct 2025 17:13:42 +0200 Subject: [PATCH 2/6] chore: Update ADL CLI version references to 0.22.2 in generated files and clean up whitespace in tests Signed-off-by: Eden Reich --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- .releaserc.yaml | 2 +- CLAUDE.md | 4 ++-- Taskfile.yml | 2 +- config/config.go | 2 +- internal/logger/logger.go | 2 +- main.go | 2 +- skills/take_screenshot.go | 4 ++-- skills/take_screenshot_test.go | 14 +++++++------- skills/write_to_csv.go | 1 - skills/write_to_csv_test.go | 2 +- 12 files changed, 19 insertions(+), 20 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 924050d..faa71a1 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.1. DO NOT EDIT. +# Code generated by ADL CLI v0.22.2. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9a215d..9eb8635 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.1. DO NOT EDIT. +# Code generated by ADL CLI v0.22.2. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/.releaserc.yaml b/.releaserc.yaml index 0ea830c..f2ffd0f 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.1. DO NOT EDIT. +# Code generated by ADL CLI v0.22.2. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/CLAUDE.md b/CLAUDE.md index 402ddb1..cb0db6d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ browser-agent is an A2A (Agent-to-Agent) server implementing the [A2A Protocol]( ### ADL-Generated Structure -The codebase is generated using ADL CLI 0.22.1 and follows a strict generation pattern: +The codebase is generated using ADL CLI 0.22.2 and follows a strict generation pattern: - **Generated Files**: Marked with `DO NOT EDIT` headers - manual changes will be overwritten - **Configuration Source**: `agent.yaml` - defines agent capabilities, skills, and metadata - **Server Implementation**: Built on the ADK (Agent Development Kit) framework from `github.com/inference-gateway/adk` @@ -118,7 +118,7 @@ Activate with: `flox activate` (if Flox is installed) - **Generated Files**: Never manually edit files with "DO NOT EDIT" headers - **Configuration Changes**: Always modify `agent.yaml` and regenerate -- **ADL Version**: Ensure ADL CLI 0.22.1 or compatible version for regeneration +- **ADL Version**: Ensure ADL CLI 0.22.2 or compatible version for regeneration - **Port Configuration**: Default 8080, configurable via `A2A_PORT` or `A2A_SERVER_PORT` ## Debugging Tips diff --git a/Taskfile.yml b/Taskfile.yml index 69afb77..9d2b1fd 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.1. DO NOT EDIT. +# Code generated by ADL CLI v0.22.2. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/config/config.go b/config/config.go index 5f8d9a3..c321a97 100644 --- a/config/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.1. DO NOT EDIT. +// Code generated by ADL CLI v0.22.2. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/internal/logger/logger.go b/internal/logger/logger.go index d9b4cd5..26acc4b 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.1. DO NOT EDIT. +// Code generated by ADL CLI v0.22.2. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/main.go b/main.go index ee10cd5..c70bdc6 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.1. DO NOT EDIT. +// Code generated by ADL CLI v0.22.2. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/skills/take_screenshot.go b/skills/take_screenshot.go index 072bb0a..e260114 100644 --- a/skills/take_screenshot.go +++ b/skills/take_screenshot.go @@ -164,8 +164,8 @@ func (s *TakeScreenshotSkill) TakeScreenshotHandler(ctx context.Context, args ma zap.String("artifactID", screenshotArtifact.ArtifactID)) if err := os.Remove(generatedPath); err != nil { - s.logger.Warn("failed to clean up temporary screenshot file", - zap.String("path", generatedPath), + s.logger.Warn("failed to clean up temporary screenshot file", + zap.String("path", generatedPath), zap.Error(err)) } else { s.logger.Debug("cleaned up temporary screenshot file", zap.String("path", generatedPath)) diff --git a/skills/take_screenshot_test.go b/skills/take_screenshot_test.go index 9e569cc..a90d23b 100644 --- a/skills/take_screenshot_test.go +++ b/skills/take_screenshot_test.go @@ -58,7 +58,7 @@ func TestTakeScreenshotHandler_BasicFunctionality(t *testing.T) { ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ ID: "test-task-123", }) - + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -100,7 +100,7 @@ func TestTakeScreenshotHandler_FullPageScreenshot(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -135,7 +135,7 @@ func TestTakeScreenshotHandler_JPEGWithQuality(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -173,7 +173,7 @@ func TestTakeScreenshotHandler_ElementSelector(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -205,7 +205,7 @@ func TestTakeScreenshotHandler_DeterministicPath(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + result, err := skill.TakeScreenshotHandler(ctx, args) if err != nil { @@ -234,7 +234,7 @@ func TestTakeScreenshotHandler_InvalidImageType(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + _, err := skill.TakeScreenshotHandler(ctx, args) if err == nil { @@ -260,7 +260,7 @@ func TestTakeScreenshotHandler_InvalidQuality(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + _, err := skill.TakeScreenshotHandler(ctx, args) if err == nil { diff --git a/skills/write_to_csv.go b/skills/write_to_csv.go index 62c6b08..bc6df67 100644 --- a/skills/write_to_csv.go +++ b/skills/write_to_csv.go @@ -260,7 +260,6 @@ func (s *WriteToCsvSkill) extractHeadersFromRows(rows []map[string]any) []string return headers } - func (s *WriteToCsvSkill) valueToString(value any) string { if value == nil { return "" diff --git a/skills/write_to_csv_test.go b/skills/write_to_csv_test.go index e01c028..3eb0384 100644 --- a/skills/write_to_csv_test.go +++ b/skills/write_to_csv_test.go @@ -158,7 +158,7 @@ func TestWriteToCsvHandler(t *testing.T) { ctx := context.Background() ctx = context.WithValue(ctx, server.ArtifactHelperContextKey, server.NewArtifactHelper()) ctx = context.WithValue(ctx, server.TaskContextKey, &types.Task{ID: "test-task-123"}) - + result, err := skill.WriteToCsvHandler(ctx, tt.args) if tt.expectedError { From ea0a253cdf108c51425bcb797bdc9ad7d935299f Mon Sep 17 00:00:00 2001 From: Eden Reich Date: Sun, 5 Oct 2025 17:34:18 +0200 Subject: [PATCH 3/6] chore(deps): Update dependencies and improve error handling - Bump golangci-lint version from 2.4.0 to 2.5.0 - Update go-task version to ^3.45.4 - Upgrade git version to 2.51.0 - Update docker version to 28.4.0 - Add claude-code version ^2.0.1 - Regenerate ADL CLI version from 0.22.2 to 0.22.3 in various files - Enhance error handling in main.go to avoid fatal errors when artifacts server cannot be created - Ensure graceful shutdown of artifacts server only if it was successfully created Signed-off-by: Eden Reich --- .flox/env/manifest.lock | 370 ++++++++++++++++++++------------------ .flox/env/manifest.toml | 6 +- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- .releaserc.yaml | 2 +- CLAUDE.md | 4 +- Taskfile.yml | 2 +- config/config.go | 2 +- internal/logger/logger.go | 2 +- main.go | 24 ++- 10 files changed, 225 insertions(+), 191 deletions(-) diff --git a/.flox/env/manifest.lock b/.flox/env/manifest.lock index 3daf270..262ff68 100644 --- a/.flox/env/manifest.lock +++ b/.flox/env/manifest.lock @@ -5,15 +5,18 @@ "install": { "claude-code": { "pkg-path": "claude-code", - "pkg-group": "common" + "pkg-group": "common", + "version": "^2.0.1" }, "docker": { "pkg-path": "docker", - "pkg-group": "common" + "pkg-group": "common", + "version": "28.4.0" }, "git": { "pkg-path": "git", - "pkg-group": "common" + "pkg-group": "common", + "version": "2.51.0" }, "go": { "pkg-path": "go", @@ -21,12 +24,13 @@ }, "go-task": { "pkg-path": "go-task", - "pkg-group": "common" + "pkg-group": "common", + "version": "^3.45.4" }, "golangci-lint": { "pkg-path": "golangci-lint", "pkg-group": "common", - "version": "^2.4.0" + "version": "2.5.0" } }, "hook": { @@ -38,27 +42,28 @@ { "attr_path": "claude-code", "broken": false, - "derivation": "/nix/store/qi44gvbs0q5zpikwks0y1njbgjb22bds-claude-code-1.0.109.drv", + "derivation": "/nix/store/f3yv1ga4r9hl67pn75a1l63i5g27670a-claude-code-2.0.1.drv", "description": "Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster", "install_id": "claude-code", "license": "Unfree", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "claude-code-1.0.109", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "claude-code-2.0.1", "pname": "claude-code", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:00:42.473223Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:40:08.767056Z", "stabilities": [ + "staging", "unstable" ], "unfree": true, - "version": "1.0.109", + "version": "2.0.1", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/yag2wjpywi7zkv7s5d5zixxkksl6anf1-claude-code-1.0.109" + "out": "/nix/store/ipi2giva57qs24pcicrb1wp38lggbg72-claude-code-2.0.1" }, "system": "aarch64-darwin", "group": "common", @@ -67,27 +72,28 @@ { "attr_path": "claude-code", "broken": false, - "derivation": "/nix/store/d4fhv573rfkbbmnaf27x9bi5ylm0ph13-claude-code-1.0.109.drv", + "derivation": "/nix/store/x522nb7kzqgmjjghjdxif0s6r9m7fwki-claude-code-2.0.1.drv", "description": "Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster", "install_id": "claude-code", "license": "Unfree", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "claude-code-1.0.109", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "claude-code-2.0.1", "pname": "claude-code", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:29:40.426963Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:56:38.363718Z", "stabilities": [ + "staging", "unstable" ], "unfree": true, - "version": "1.0.109", + "version": "2.0.1", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/yyvhsjrcngkj1ab857p60cc4vs509x5f-claude-code-1.0.109" + "out": "/nix/store/fy9ngf85a96l7ljc2q1427ahc44qiw1g-claude-code-2.0.1" }, "system": "aarch64-linux", "group": "common", @@ -96,27 +102,28 @@ { "attr_path": "claude-code", "broken": false, - "derivation": "/nix/store/vx3dqm8dmcwhh02aqyvw38if2rm1ri0k-claude-code-1.0.109.drv", + "derivation": "/nix/store/csqwzfwqc1nwd770d63rv06b06sgmmbn-claude-code-2.0.1.drv", "description": "Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster", "install_id": "claude-code", "license": "Unfree", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "claude-code-1.0.109", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "claude-code-2.0.1", "pname": "claude-code", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:56:35.897650Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:12:15.436552Z", "stabilities": [ + "staging", "unstable" ], "unfree": true, - "version": "1.0.109", + "version": "2.0.1", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/fysag687fdwvdlywy19nz1gf9f76n6m9-claude-code-1.0.109" + "out": "/nix/store/036ybyhzibr2mqw2ry1zc79qj1rjzjaj-claude-code-2.0.1" }, "system": "x86_64-darwin", "group": "common", @@ -125,27 +132,28 @@ { "attr_path": "claude-code", "broken": false, - "derivation": "/nix/store/biw66ax6rhsc4yjbzkl4ah9nx57b0zyq-claude-code-1.0.109.drv", + "derivation": "/nix/store/i32jl01vvyvi7sd2x9lkzfc7q5xz1457-claude-code-2.0.1.drv", "description": "Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster", "install_id": "claude-code", "license": "Unfree", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "claude-code-1.0.109", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "claude-code-2.0.1", "pname": "claude-code", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T03:26:56.526616Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:27:31.798081Z", "stabilities": [ + "staging", "unstable" ], "unfree": true, - "version": "1.0.109", + "version": "2.0.1", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/didsrxk70gcr7y3xfj0vcm3a8fm9jyr3-claude-code-1.0.109" + "out": "/nix/store/s7gl3s5wim61yyin8kv3chp3v3iak7dj-claude-code-2.0.1" }, "system": "x86_64-linux", "group": "common", @@ -154,27 +162,28 @@ { "attr_path": "docker", "broken": false, - "derivation": "/nix/store/5b3fwg8sl512mfk5j9m4yq0i8x4bd437-docker-28.3.3.drv", + "derivation": "/nix/store/d923sy3dr7zpnlgdzx1braqlywqaqz80-docker-28.4.0.drv", "description": "Open source project to pack, ship and run any application as a lightweight container", "install_id": "docker", "license": "Apache-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "docker-28.3.3", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "docker-28.4.0", "pname": "docker", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:00:42.722349Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:40:09.052395Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "28.3.3", + "version": "28.4.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/yhq84djizcvvhwcnkmwklgg2bgwcp6iq-docker-28.3.3" + "out": "/nix/store/94cnmzldkz75h6fy00f3y09f5b1rvxr0-docker-28.4.0" }, "system": "aarch64-darwin", "group": "common", @@ -183,27 +192,28 @@ { "attr_path": "docker", "broken": false, - "derivation": "/nix/store/3f4ccdljcrbc0s5jabx2fx2n00f3g6ns-docker-28.3.3.drv", + "derivation": "/nix/store/6w033rpbdj9rl5iy4ycq2bs6j8anyryr-docker-28.4.0.drv", "description": "Open source project to pack, ship and run any application as a lightweight container", "install_id": "docker", "license": "Apache-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "docker-28.3.3", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "docker-28.4.0", "pname": "docker", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:29:40.810112Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:56:38.734608Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "28.3.3", + "version": "28.4.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/95y15ihbnhr63k51x4rpa42p082ky6ia-docker-28.3.3" + "out": "/nix/store/47qdbdwl5lbgij7mqadyb4rp8y3jvv9w-docker-28.4.0" }, "system": "aarch64-linux", "group": "common", @@ -212,27 +222,28 @@ { "attr_path": "docker", "broken": false, - "derivation": "/nix/store/bpshn5r6ix9dbihj54nn3pwq5aprmiq4-docker-28.3.3.drv", + "derivation": "/nix/store/jbz26alavqdv195cqsip64g6j714xlz7-docker-28.4.0.drv", "description": "Open source project to pack, ship and run any application as a lightweight container", "install_id": "docker", "license": "Apache-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "docker-28.3.3", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "docker-28.4.0", "pname": "docker", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:56:36.150936Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:12:15.687888Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "28.3.3", + "version": "28.4.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/rbf2x5dzickvx7s1n3gyd2gzhc1kizcp-docker-28.3.3" + "out": "/nix/store/m4agjr9lj4i8vhgcs8ppm74f34a872iv-docker-28.4.0" }, "system": "x86_64-darwin", "group": "common", @@ -241,27 +252,28 @@ { "attr_path": "docker", "broken": false, - "derivation": "/nix/store/jag5x2q4dy7nbfi6wy4x64wgqd0y4sza-docker-28.3.3.drv", + "derivation": "/nix/store/crxxixn7bjmxyyiils9n0pxw0zchj7zl-docker-28.4.0.drv", "description": "Open source project to pack, ship and run any application as a lightweight container", "install_id": "docker", "license": "Apache-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "docker-28.3.3", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "docker-28.4.0", "pname": "docker", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T03:26:56.938344Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:27:32.224248Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "28.3.3", + "version": "28.4.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/pl0xs3161aslw59ry1173shjffkb2wdb-docker-28.3.3" + "out": "/nix/store/6ab1pkn6d3rjvxsz9icj45y5i5zg0c67-docker-28.4.0" }, "system": "x86_64-linux", "group": "common", @@ -270,18 +282,19 @@ { "attr_path": "git", "broken": false, - "derivation": "/nix/store/6d98i7ksgr6d4xjjh0y6fl6ianxxzzbf-git-2.51.0.drv", + "derivation": "/nix/store/gq4vln87pixc41c0gcshf1y0s54j8y3x-git-2.51.0.drv", "description": "Distributed version control system", "install_id": "git", "license": "GPL-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", "name": "git-2.51.0", "pname": "git", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:00:43.250181Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:40:09.518544Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, @@ -300,18 +313,19 @@ { "attr_path": "git", "broken": false, - "derivation": "/nix/store/jcd1hmakc99sfm5hzxgscpgkdhiq7bk4-git-2.51.0.drv", + "derivation": "/nix/store/p84fj53acdpr4c2xl2mhqbky5mk7ndfg-git-2.51.0.drv", "description": "Distributed version control system", "install_id": "git", "license": "GPL-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", "name": "git-2.51.0", "pname": "git", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:29:41.582900Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:56:39.492394Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, @@ -331,18 +345,19 @@ { "attr_path": "git", "broken": false, - "derivation": "/nix/store/wqvqcz0zhylgjfzgbw22dkn9ms5slzda-git-2.51.0.drv", + "derivation": "/nix/store/agic4v5n35m8znm8pg8dqsn7vn9m6jlp-git-2.51.0.drv", "description": "Distributed version control system", "install_id": "git", "license": "GPL-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", "name": "git-2.51.0", "pname": "git", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:56:36.641268Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:12:16.168873Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, @@ -361,18 +376,19 @@ { "attr_path": "git", "broken": false, - "derivation": "/nix/store/yyw6nzfcdckb36blm5mf5b5mss1m9asq-git-2.51.0.drv", + "derivation": "/nix/store/c2pz70nkq0ax1a7ra3m3djnhj51mayiy-git-2.51.0.drv", "description": "Distributed version control system", "install_id": "git", "license": "GPL-2.0", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", "name": "git-2.51.0", "pname": "git", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T03:26:57.785953Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:27:33.078022Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, @@ -392,27 +408,28 @@ { "attr_path": "go-task", "broken": false, - "derivation": "/nix/store/vrcm2w09scjp559arqp2a98ws2zcl5z4-go-task-3.44.1.drv", + "derivation": "/nix/store/7vy1xgwwf179n32yf1csqs7zvywx0w3g-go-task-3.45.4.drv", "description": "Task runner / simpler Make alternative written in Go", "install_id": "go-task", "license": "MIT", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "go-task-3.44.1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "go-task-3.45.4", "pname": "go-task", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:00:43.939589Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:40:09.646158Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "3.44.1", + "version": "3.45.4", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/p58fz5g7zai0bgh6rqyg9hfz0hmvng4m-go-task-3.44.1" + "out": "/nix/store/q6ln03442mj1lyckjssqvhjmamwv4shy-go-task-3.45.4" }, "system": "aarch64-darwin", "group": "common", @@ -421,27 +438,28 @@ { "attr_path": "go-task", "broken": false, - "derivation": "/nix/store/j6122sq7fc9yh6s3463zqlcizsk9h7jr-go-task-3.44.1.drv", + "derivation": "/nix/store/l14wvy2z9avqx70sp450p84ji1r6z3hk-go-task-3.45.4.drv", "description": "Task runner / simpler Make alternative written in Go", "install_id": "go-task", "license": "MIT", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "go-task-3.44.1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "go-task-3.45.4", "pname": "go-task", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:29:42.124429Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:56:40.027905Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "3.44.1", + "version": "3.45.4", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/qi3hx4hvcnzy317yihii2gm3gqgvvnw5-go-task-3.44.1" + "out": "/nix/store/kc677152flyszzldgphlzlcnf2ap87c0-go-task-3.45.4" }, "system": "aarch64-linux", "group": "common", @@ -450,27 +468,28 @@ { "attr_path": "go-task", "broken": false, - "derivation": "/nix/store/dxn2vf4jbc0lcjj5ajll067sfc8qnqyg-go-task-3.44.1.drv", + "derivation": "/nix/store/lrvqsavsn5746mdqhw6x638s6d51l53g-go-task-3.45.4.drv", "description": "Task runner / simpler Make alternative written in Go", "install_id": "go-task", "license": "MIT", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "go-task-3.44.1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "go-task-3.45.4", "pname": "go-task", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:56:36.805585Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:12:16.332841Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "3.44.1", + "version": "3.45.4", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/n6xnr1iwm1yhcf6mji9l649djizbfg3b-go-task-3.44.1" + "out": "/nix/store/v4fpcid9nb9in5wi3f5bbz8m5yx880wb-go-task-3.45.4" }, "system": "x86_64-darwin", "group": "common", @@ -479,27 +498,28 @@ { "attr_path": "go-task", "broken": false, - "derivation": "/nix/store/0wqw7c5276j3wa354dxdqw2d4vll0d1f-go-task-3.44.1.drv", + "derivation": "/nix/store/6qqrxabc1m4irc8ckkpdih3q6m4mv5kr-go-task-3.45.4.drv", "description": "Task runner / simpler Make alternative written in Go", "install_id": "go-task", "license": "MIT", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "go-task-3.44.1", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "go-task-3.45.4", "pname": "go-task", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T03:26:58.397065Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:27:33.681590Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "3.44.1", + "version": "3.45.4", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/jjy9p73v67b6p555qs9ff3dasnlzafid-go-task-3.44.1" + "out": "/nix/store/fyywhmm96sgz0jj60d9x7fb9ijc0h0pw-go-task-3.45.4" }, "system": "x86_64-linux", "group": "common", @@ -508,27 +528,28 @@ { "attr_path": "golangci-lint", "broken": false, - "derivation": "/nix/store/dqn9gvcq9slpjly6di31fra4m1n02mrc-golangci-lint-2.4.0.drv", + "derivation": "/nix/store/68mwbxxr481mrqd1pw4nic2wbb7rikpm-golangci-lint-2.5.0.drv", "description": "Fast linters Runner for Go", "install_id": "golangci-lint", "license": "GPL-3.0-or-later", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "golangci-lint-2.4.0", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "golangci-lint-2.5.0", "pname": "golangci-lint", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:00:43.968360Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:40:09.675371Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "2.4.0", + "version": "2.5.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/2iiw320mwgw7flh47zbz6l62fakrb3dx-golangci-lint-2.4.0" + "out": "/nix/store/5hv7qb9w7lbjxdjdvjk6ihf2bnx73fk9-golangci-lint-2.5.0" }, "system": "aarch64-darwin", "group": "common", @@ -537,27 +558,28 @@ { "attr_path": "golangci-lint", "broken": false, - "derivation": "/nix/store/dpx8zl6iqn5mc46pq2lbd7whllk61avl-golangci-lint-2.4.0.drv", + "derivation": "/nix/store/la719rkh3ylzfy76ibb945ivaqwwz0rv-golangci-lint-2.5.0.drv", "description": "Fast linters Runner for Go", "install_id": "golangci-lint", "license": "GPL-3.0-or-later", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "golangci-lint-2.4.0", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "golangci-lint-2.5.0", "pname": "golangci-lint", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:29:42.179032Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T04:56:40.084934Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "2.4.0", + "version": "2.5.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/hwr3wdhqnlcay07xpgv2wm1mx7k5nkhf-golangci-lint-2.4.0" + "out": "/nix/store/60ar5w3sgcahn98n4lvxh9i2z9iqb9gy-golangci-lint-2.5.0" }, "system": "aarch64-linux", "group": "common", @@ -566,27 +588,28 @@ { "attr_path": "golangci-lint", "broken": false, - "derivation": "/nix/store/bq2v9hgnf5azc154p2wpdiwrn5cww31n-golangci-lint-2.4.0.drv", + "derivation": "/nix/store/4grfi25hz9f8gm4znvsc55hpc3i4sbjz-golangci-lint-2.5.0.drv", "description": "Fast linters Runner for Go", "install_id": "golangci-lint", "license": "GPL-3.0-or-later", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "golangci-lint-2.4.0", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "golangci-lint-2.5.0", "pname": "golangci-lint", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T02:56:36.834440Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:12:16.363414Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "2.4.0", + "version": "2.5.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/skcc363l41rm6hjyrhzlfbk3rrwci2lb-golangci-lint-2.4.0" + "out": "/nix/store/fzmqddh5xixwajqn9c4fs2ddsi551pq6-golangci-lint-2.5.0" }, "system": "x86_64-darwin", "group": "common", @@ -595,27 +618,28 @@ { "attr_path": "golangci-lint", "broken": false, - "derivation": "/nix/store/k1i6kgkcdjrplk28chxbpimw5sm9adny-golangci-lint-2.4.0.drv", + "derivation": "/nix/store/ymgb66jf2bc9a1zmb1iihdnw29bxdg1c-golangci-lint-2.5.0.drv", "description": "Fast linters Runner for Go", "install_id": "golangci-lint", "license": "GPL-3.0-or-later", - "locked_url": "https://github.com/flox/nixpkgs?rev=c23193b943c6c689d70ee98ce3128239ed9e32d1", - "name": "golangci-lint-2.4.0", + "locked_url": "https://github.com/flox/nixpkgs?rev=7df7ff7d8e00218376575f0acdcc5d66741351ee", + "name": "golangci-lint-2.5.0", "pname": "golangci-lint", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "rev_count": 861038, - "rev_date": "2025-09-13T06:43:22Z", - "scrape_date": "2025-09-15T03:26:58.457663Z", + "rev": "7df7ff7d8e00218376575f0acdcc5d66741351ee", + "rev_count": 870157, + "rev_date": "2025-10-02T04:57:58Z", + "scrape_date": "2025-10-03T05:27:33.743172Z", "stabilities": [ + "staging", "unstable" ], "unfree": false, - "version": "2.4.0", + "version": "2.5.0", "outputs_to_install": [ "out" ], "outputs": { - "out": "/nix/store/dlz6z4dih7rd6q9dnigvz49npfmv8m52-golangci-lint-2.4.0" + "out": "/nix/store/swhcgp8k39qj678ys1kbn4vcdai3ml2c-golangci-lint-2.5.0" }, "system": "x86_64-linux", "group": "common", diff --git a/.flox/env/manifest.toml b/.flox/env/manifest.toml index 73b40c7..e118a0b 100644 --- a/.flox/env/manifest.toml +++ b/.flox/env/manifest.toml @@ -5,20 +5,24 @@ go.pkg-path = "go" go.version = "1.25" golangci-lint.pkg-path = "golangci-lint" -golangci-lint.version = "^2.4.0" +golangci-lint.version = "2.5.0" golangci-lint.pkg-group = "common" go-task.pkg-path = "go-task" +go-task.version = "^3.45.4" go-task.pkg-group = "common" git.pkg-path = "git" +git.version = "2.51.0" git.pkg-group = "common" docker.pkg-path = "docker" +docker.version = "28.4.0" docker.pkg-group = "common" claude-code.pkg-path = "claude-code" claude-code.pkg-group = "common" +claude-code.version = "^2.0.1" [hook] on-activate = ''' diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index faa71a1..3fe8d32 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.2. DO NOT EDIT. +# Code generated by ADL CLI v0.22.3. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb8635..e410a0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.2. DO NOT EDIT. +# Code generated by ADL CLI v0.22.3. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/.releaserc.yaml b/.releaserc.yaml index f2ffd0f..250839c 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.2. DO NOT EDIT. +# Code generated by ADL CLI v0.22.3. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/CLAUDE.md b/CLAUDE.md index cb0db6d..4d40ce0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ browser-agent is an A2A (Agent-to-Agent) server implementing the [A2A Protocol]( ### ADL-Generated Structure -The codebase is generated using ADL CLI 0.22.2 and follows a strict generation pattern: +The codebase is generated using ADL CLI 0.22.3 and follows a strict generation pattern: - **Generated Files**: Marked with `DO NOT EDIT` headers - manual changes will be overwritten - **Configuration Source**: `agent.yaml` - defines agent capabilities, skills, and metadata - **Server Implementation**: Built on the ADK (Agent Development Kit) framework from `github.com/inference-gateway/adk` @@ -118,7 +118,7 @@ Activate with: `flox activate` (if Flox is installed) - **Generated Files**: Never manually edit files with "DO NOT EDIT" headers - **Configuration Changes**: Always modify `agent.yaml` and regenerate -- **ADL Version**: Ensure ADL CLI 0.22.2 or compatible version for regeneration +- **ADL Version**: Ensure ADL CLI 0.22.3 or compatible version for regeneration - **Port Configuration**: Default 8080, configurable via `A2A_PORT` or `A2A_SERVER_PORT` ## Debugging Tips diff --git a/Taskfile.yml b/Taskfile.yml index 9d2b1fd..0211bf4 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.2. DO NOT EDIT. +# Code generated by ADL CLI v0.22.3. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/config/config.go b/config/config.go index c321a97..442b1fa 100644 --- a/config/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.2. DO NOT EDIT. +// Code generated by ADL CLI v0.22.3. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 26acc4b..9989082 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.2. DO NOT EDIT. +// Code generated by ADL CLI v0.22.3. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/main.go b/main.go index c70bdc6..3d49949 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.2. DO NOT EDIT. +// Code generated by ADL CLI v0.22.3. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. @@ -166,7 +166,9 @@ Your automation solutions should be maintainable, efficient, and production-read NewArtifactsServerBuilder(&cfg.A2A.ArtifactsConfig, l). Build() if err != nil { - l.Fatal("failed to create artifacts server", zap.Error(err)) + l.Warn("artifacts server could not be created - check ARTIFACTS_ENABLE environment variable", zap.Error(err)) + l.Info("continuing without artifacts server support") + artifactsServer = nil } go func() { @@ -176,12 +178,14 @@ Your automation solutions should be maintainable, efficient, and production-read } }() - go func() { - l.Info("starting A2A artifacts server", zap.String("port", cfg.A2A.ArtifactsConfig.ServerConfig.Port)) - if err := artifactsServer.Start(ctx); err != nil { - l.Fatal("artifacts server failed to start", zap.Error(err)) - } - }() + if artifactsServer != nil { + go func() { + l.Info("starting A2A artifacts server", zap.String("port", cfg.A2A.ArtifactsConfig.ServerConfig.Port)) + if err := artifactsServer.Start(ctx); err != nil { + l.Fatal("artifacts server failed to start", zap.Error(err)) + } + }() + } l.Info("browser-agent agent running successfully", zap.String("port", cfg.A2A.ServerConfig.Port), @@ -193,6 +197,8 @@ Your automation solutions should be maintainable, efficient, and production-read l.Info("shutdown signal received, gracefully stopping server...") a2aServer.Stop(ctx) - artifactsServer.Stop(ctx) + if artifactsServer != nil { + artifactsServer.Stop(ctx) + } l.Info("browser-agent agent stopped") } From 004e04c9805ec8796ffc0174fee1e947f4450861 Mon Sep 17 00:00:00 2001 From: Eden Reich Date: Sun, 5 Oct 2025 17:44:35 +0200 Subject: [PATCH 4/6] chore: Update ADL CLI version and version argument in Dockerfile Signed-off-by: Eden Reich --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1c44827..4fee79c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ -# Code generated by ADL CLI v0.21.7. DO NOT EDIT. +# Code generated by ADL CLI v0.22.3. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. FROM golang:1.25-alpine AS builder # Build arguments for version injection -ARG VERSION="0.2.0" +ARG VERSION="0.3.0" ARG AGENT_NAME="browser-agent" ARG AGENT_DESCRIPTION="AI agent for browser automation and web testing using Playwright" @@ -65,6 +65,7 @@ EXPOSE 8080 # Set environment variables ENV A2A_SERVER_PORT=8080 +ENV PLAYWRIGHT_BROWSERS_PATH=/root/.cache/ms-playwright # Run the application CMD ["./main"] From 401f28e4366d1cb027975242d13d8664153fa1ab Mon Sep 17 00:00:00 2001 From: Eden Reich Date: Sun, 5 Oct 2025 17:55:53 +0200 Subject: [PATCH 5/6] docs: Clarify instructions for taking a screenshot in the README Signed-off-by: Eden Reich --- example/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/README.md b/example/README.md index 2209d3c..dc1fec6 100644 --- a/example/README.md +++ b/example/README.md @@ -26,7 +26,7 @@ docker compose run --rm cli Ask the following: ```text -Please visit http://demo-site and take a screenshot of the homepage. Use the agent. +Please visit http://demo-site which is running locally and take a screenshot of the homepage. Use the agent. ``` You would see the CLI (A2A agent client) submitting a task to the A2A agent server and the screenshot will appear in the `screenshots` directory since it's mounted as a volume. From 7476cc8e9dc37f881e8eafa008ed67616973734d Mon Sep 17 00:00:00 2001 From: Eden Reich Date: Mon, 6 Oct 2025 15:34:36 +0200 Subject: [PATCH 6/6] chore: Update ADL CLI version references to 0.23.0 in generated files and adjust go.mod dependency Signed-off-by: Eden Reich --- .github/workflows/cd.yml | 2 +- .github/workflows/ci.yml | 2 +- .releaserc.yaml | 2 +- CLAUDE.md | 4 ++-- Taskfile.yml | 2 +- config/config.go | 2 +- go.mod | 2 +- go.sum | 4 ++-- internal/logger/logger.go | 2 +- main.go | 21 +++++++++++---------- 10 files changed, 22 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 3fe8d32..7d104b0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.3. DO NOT EDIT. +# Code generated by ADL CLI v0.23.0. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e410a0e..39ae06a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.3. DO NOT EDIT. +# Code generated by ADL CLI v0.23.0. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/.releaserc.yaml b/.releaserc.yaml index 250839c..ef8a68f 100644 --- a/.releaserc.yaml +++ b/.releaserc.yaml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.3. DO NOT EDIT. +# Code generated by ADL CLI v0.23.0. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/CLAUDE.md b/CLAUDE.md index 4d40ce0..a89d36f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,7 +10,7 @@ browser-agent is an A2A (Agent-to-Agent) server implementing the [A2A Protocol]( ### ADL-Generated Structure -The codebase is generated using ADL CLI 0.22.3 and follows a strict generation pattern: +The codebase is generated using ADL CLI 0.23.0 and follows a strict generation pattern: - **Generated Files**: Marked with `DO NOT EDIT` headers - manual changes will be overwritten - **Configuration Source**: `agent.yaml` - defines agent capabilities, skills, and metadata - **Server Implementation**: Built on the ADK (Agent Development Kit) framework from `github.com/inference-gateway/adk` @@ -118,7 +118,7 @@ Activate with: `flox activate` (if Flox is installed) - **Generated Files**: Never manually edit files with "DO NOT EDIT" headers - **Configuration Changes**: Always modify `agent.yaml` and regenerate -- **ADL Version**: Ensure ADL CLI 0.22.3 or compatible version for regeneration +- **ADL Version**: Ensure ADL CLI 0.23.0 or compatible version for regeneration - **Port Configuration**: Default 8080, configurable via `A2A_PORT` or `A2A_SERVER_PORT` ## Debugging Tips diff --git a/Taskfile.yml b/Taskfile.yml index 0211bf4..b8376c9 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -1,4 +1,4 @@ -# Code generated by ADL CLI v0.22.3. DO NOT EDIT. +# Code generated by ADL CLI v0.23.0. DO NOT EDIT. # This file was automatically generated from an ADL (Agent Definition Language) specification. # Manual changes to this file may be overwritten during regeneration. diff --git a/config/config.go b/config/config.go index 442b1fa..23f4662 100644 --- a/config/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.3. DO NOT EDIT. +// Code generated by ADL CLI v0.23.0. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/go.mod b/go.mod index a43232a..b3a5c55 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/inference-gateway/browser-agent go 1.25 require ( - github.com/inference-gateway/adk v0.13.0 + github.com/inference-gateway/adk v0.13.1 github.com/playwright-community/playwright-go v0.5200.1 github.com/sethvargo/go-envconfig v1.3.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 7487119..8782ca3 100644 --- a/go.sum +++ b/go.sum @@ -65,8 +65,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/inference-gateway/adk v0.13.0 h1:UMaiaM0/gpPxyCzTDm48aIXw9X2xjLGESOLcyTMcqaI= -github.com/inference-gateway/adk v0.13.0/go.mod h1:Eh91HM5d3R0I5OOAh3YNUqZCJBBdGPHrKBALnVL8dl0= +github.com/inference-gateway/adk v0.13.1 h1:TcnHfxNDGEN1Sde0TdPH9QuzepyLn8Lf+2oIXmTgQy8= +github.com/inference-gateway/adk v0.13.1/go.mod h1:Eh91HM5d3R0I5OOAh3YNUqZCJBBdGPHrKBALnVL8dl0= github.com/inference-gateway/sdk v1.10.0 h1:88m1XTS5J7Q9+sFaKXKHAPXdDpji6SASXVWz2pe8ZFk= github.com/inference-gateway/sdk v1.10.0/go.mod h1:3TTD7Kbr7FRt+9ZbCPAm3u0tXUIWx7flZuwrRgZgrdk= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 9989082..d073d03 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.3. DO NOT EDIT. +// Code generated by ADL CLI v0.23.0. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. diff --git a/main.go b/main.go index 3d49949..629dea7 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,4 @@ -// Code generated by ADL CLI v0.22.3. DO NOT EDIT. +// Code generated by ADL CLI v0.23.0. DO NOT EDIT. // This file was automatically generated from an ADL (Agent Definition Language) specification. // Manual changes to this file may be overwritten during regeneration. @@ -147,6 +147,15 @@ Your automation solutions should be maintainable, efficient, and production-read l.Fatal("failed to create agent", zap.Error(err)) } + artifactsServer, err := server. + NewArtifactsServerBuilder(&cfg.A2A.ArtifactsConfig, l). + Build() + if err != nil { + l.Warn("artifacts server could not be created - check ARTIFACTS_ENABLE environment variable", zap.Error(err)) + l.Info("continuing without artifacts server support") + artifactsServer = nil + } + a2aServer, err := server.NewA2AServerBuilder(cfg.A2A, l). WithAgent(agent). WithAgentCardFromFile(".well-known/agent-card.json", map[string]any{ @@ -155,6 +164,7 @@ Your automation solutions should be maintainable, efficient, and production-read "description": AgentDescription, "url": cfg.A2A.AgentURL, }). + WithArtifactStorage(artifactsServer.GetStorage()). WithDefaultBackgroundTaskHandler(). WithDefaultStreamingTaskHandler(). Build() @@ -162,15 +172,6 @@ Your automation solutions should be maintainable, efficient, and production-read l.Fatal("failed to create A2A server", zap.Error(err)) } - artifactsServer, err := server. - NewArtifactsServerBuilder(&cfg.A2A.ArtifactsConfig, l). - Build() - if err != nil { - l.Warn("artifacts server could not be created - check ARTIFACTS_ENABLE environment variable", zap.Error(err)) - l.Info("continuing without artifacts server support") - artifactsServer = nil - } - go func() { l.Info("starting A2A server", zap.String("port", cfg.A2A.ServerConfig.Port)) if err := a2aServer.Start(ctx); err != nil {