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
38 changes: 36 additions & 2 deletions cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
"os"
"path/filepath"

"github.com/agenticgokit/agenticgokit/observability"

Check failure on line 8 in cmd/init.go

View workflow job for this annotation

GitHub Actions / Build

github.com/agenticgokit/agenticgokit@v0.0.0: replacement directory ../agenticgokit does not exist
"github.com/fatih/color"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"

"github.com/agenticgokit/agk/pkg/scaffold"
)
Expand Down Expand Up @@ -66,15 +69,29 @@

// runInitCommand executes the init command
func runInitCommand(cmd *cobra.Command, args []string) error {
// Create observability span for command execution
tracer := observability.GetTracer("agk-cli")
ctx, span := tracer.Start(cmd.Context(), "agk.init")
defer span.End()

// Handle --list flag
if initListTemplates {
span.SetAttributes(attribute.Bool("list_templates", true))
span.SetStatus(codes.Ok, "listed templates")
return listTemplates()
}

projectName := args[0]
span.SetAttributes(
attribute.String("project_name", projectName),
attribute.String("template", initTemplate),
attribute.Bool("force", initForce),
)

// Validate project name
if err := validateProjectName(projectName); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "invalid project name")
color.Red("✗ Invalid project name: %v", err)
return err
}
Expand All @@ -83,21 +100,29 @@

// Check if path already exists
if _, err := os.Stat(projectPath); err == nil && !initForce {
err := fmt.Errorf("project directory already exists")
span.RecordError(err)
span.SetStatus(codes.Error, "directory exists")
color.Red("✗ Directory already exists: %s", projectPath)
color.Yellow("Use --force to overwrite")
return fmt.Errorf("project directory already exists")
return err
}

// Validate and get template type
templateType, err := scaffold.ValidateTemplate(initTemplate)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "invalid template")
color.Red("✗ %v", err)
return err
}
span.SetAttributes(attribute.String("template_type", string(templateType)))

// Get template generator
generator, err := scaffold.GetTemplateGenerator(templateType)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get generator")
color.Red("✗ Failed to get template generator: %v", err)
return err
}
Expand All @@ -121,7 +146,9 @@
color.Cyan(" Files: %d | Features: %v\n", metadata.FileCount, metadata.Features)

// Generate project using the template generator
if err := generator.Generate(cmd.Context(), opts); err != nil {
if err := generator.Generate(ctx, opts); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "generation failed")
color.Red("✗ Project generation failed: %v", err)
if logger != nil {
logger.Error().Err(err).Msg("project generation failed")
Expand All @@ -136,6 +163,13 @@
// Print success message
color.Green("\n✅ Project initialized successfully!\n")

// Record success metrics
span.SetAttributes(
attribute.Int("file_count", metadata.FileCount),
attribute.StringSlice("features", metadata.Features),
)
span.SetStatus(codes.Ok, "project initialized")

// Print next steps
printNextSteps(projectName, projectPath, templateType, metadata)

Expand Down
93 changes: 88 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@
package cmd

import (
"context"
"fmt"
"os"
"time"

"github.com/agenticgokit/agenticgokit/observability"
"github.com/agenticgokit/agk/internal/utils"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var (
cfgFile string
verbose bool
debug bool
logger *zerolog.Logger
cfgFile string
verbose bool
debug bool
trace bool
traceExporter string
traceEndpoint string
traceSample float64
storePrompts bool
tracerShutdown func(context.Context) error
logger *zerolog.Logger
)

// rootCmd represents the base command
Expand Down Expand Up @@ -53,9 +61,65 @@ Get started with: agk init my-project`,
*logger = logger.With().Timestamp().Logger()
// Use RFC3339 time format consistently
zerolog.TimeFieldFormat = time.RFC3339

// Initialize tracing if enabled
// Check both command flag and environment variable
trace = viper.GetBool("trace")
if !trace && os.Getenv("AGK_TRACE") == "true" {
trace = true
}

traceExporter = viper.GetString("trace_exporter")
traceEndpoint = viper.GetString("trace_endpoint")
traceSample = viper.GetFloat64("trace_sample")

if trace {
ctx := cmd.Context()
if ctx == nil {
ctx = context.Background()
}

runID := generateRunID()
ctx = observability.WithRunID(ctx, runID)
ctx = observability.WithLogger(ctx, logger)
cmd.SetContext(ctx)

// For file exporter, create runs directory and trace file path
filePath := traceEndpoint
if traceExporter == "" || traceExporter == "file" {
if filePath == "" {
runDir := fmt.Sprintf(".agk/runs/%s", runID)
os.MkdirAll(runDir, 0755)
filePath = fmt.Sprintf("%s/trace.jsonl", runDir)
}
traceExporter = "file"
}

cfg := observability.TracerConfig{
ServiceName: "agk-cli",
ServiceVersion: Version,
Environment: viper.GetString("environment"),
Endpoint: traceEndpoint,
Exporter: traceExporter,
SampleRate: traceSample,
Debug: debug,
FilePath: filePath,
}

tracerShutdown, err = observability.SetupTracer(ctx, cfg)
if err != nil {
logger.Error().Err(err).Msg("failed to set up tracer")
}
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
// No cleanup required for zerolog
if tracerShutdown != nil {
ctx := cmd.Context()
if ctx == nil {
ctx = context.Background()
}
_ = tracerShutdown(ctx)
}
},
}

Expand All @@ -71,10 +135,20 @@ func init() {
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.agk.toml)")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "debug mode")
rootCmd.PersistentFlags().BoolVar(&trace, "trace", false, "enable tracing")
rootCmd.PersistentFlags().StringVar(&traceExporter, "trace-exporter", "console", "trace exporter: console|otlp|file")
rootCmd.PersistentFlags().StringVar(&traceEndpoint, "trace-endpoint", "", "OTLP endpoint URL or file path (for file exporter)")
rootCmd.PersistentFlags().Float64Var(&traceSample, "trace-sample", 1.0, "trace sample rate (0.0-1.0)")
rootCmd.PersistentFlags().BoolVar(&storePrompts, "store-prompts", false, "store prompts for debugging (if supported by commands)")

// Bind flags to viper
_ = viper.BindPFlag("verbose", rootCmd.PersistentFlags().Lookup("verbose"))
_ = viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
_ = viper.BindPFlag("trace", rootCmd.PersistentFlags().Lookup("trace"))
_ = viper.BindPFlag("trace_exporter", rootCmd.PersistentFlags().Lookup("trace-exporter"))
_ = viper.BindPFlag("trace_endpoint", rootCmd.PersistentFlags().Lookup("trace-endpoint"))
_ = viper.BindPFlag("trace_sample", rootCmd.PersistentFlags().Lookup("trace-sample"))
_ = viper.BindPFlag("store_prompts", rootCmd.PersistentFlags().Lookup("store-prompts"))
}

func initConfig() {
Expand All @@ -89,8 +163,13 @@ func initConfig() {
viper.SetConfigName(".agk")
}

viper.SetEnvPrefix("AGK")
viper.AutomaticEnv()

viper.SetDefault("trace_exporter", "console")
viper.SetDefault("trace_sample", 1.0)
viper.SetDefault("environment", "dev")

if err := viper.ReadInConfig(); err == nil && verbose {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
Expand All @@ -109,3 +188,7 @@ func GetLogger() *zerolog.Logger {
}
return logger
}

func generateRunID() string {
return fmt.Sprintf("run-%d", time.Now().UnixNano())
}
Loading
Loading