diff --git a/go.mod b/go.mod index 58677e2c..60a83f02 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/smacker/go-tree-sitter v0.0.0-20240827094217-dd81d9e9be82 github.com/sourcegraph/go-lsp v0.0.0-20240223163137-f80c5dd31dfd github.com/sourcegraph/jsonrpc2 v0.2.0 + github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.10.0 github.com/vifraa/gopom v1.0.0 golang.org/x/mod v0.24.0 @@ -55,6 +56,7 @@ require ( github.com/go-openapi/swag v0.23.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/goph/emperror v0.17.2 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -75,6 +77,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/slongfield/pyfmt v0.0.0-20220222012616-ea85ff4c361f // indirect github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/go.sum b/go.sum index dc4799d3..b1f2dfcb 100644 --- a/go.sum +++ b/go.sum @@ -154,6 +154,7 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -333,6 +334,8 @@ github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKEN github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= @@ -511,6 +514,7 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -538,6 +542,10 @@ github.com/sourcegraph/jsonrpc2 v0.2.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= diff --git a/main.go b/main.go index 9148f157..9b50c8d2 100644 --- a/main.go +++ b/main.go @@ -32,7 +32,6 @@ package main import ( "context" - "flag" "fmt" "os" "os/exec" @@ -49,260 +48,352 @@ import ( "github.com/cloudwego/abcoder/llm/mcp" "github.com/cloudwego/abcoder/llm/tool" "github.com/cloudwego/abcoder/version" + "github.com/spf13/cobra" ) -const Usage = `abcoder [Language] [Flags] -Action: - parse parse the specific repo and write its UniAST (to stdout by default) - write write the specific UniAST back to codes - mcp run as a MCP server for all repo ASTs (*.json) in the specific directory - agent run as an Agent for all repo ASTs (*.json) in the specific directory. WIP: only support code-analyzing at present. - init-spec initialize ABCoder integration for Claude Code (copies .claude directory and configures MCP servers) - version print the version of abcoder -Language: - go for golang codes - rust for rust codes - cxx for c codes (cpp support is on the way) - python for python codes - ts for typescript codes - js for javascript codes - java for java codes -` - func main() { - flags := flag.NewFlagSet("abcoder", flag.ExitOnError) - - flagHelp := flags.Bool("h", false, "Show help message.") - flagVerbose := flags.Bool("verbose", false, "Verbose mode.") - flagOutput := flags.String("o", "", "Output path.") - flagLsp := flags.String("lsp", "", "Specify the language server path.") - javaHome := flags.String("java-home", "", "java home") - - var opts lang.ParseOptions - flags.BoolVar(&opts.LoadExternalSymbol, "load-external-symbol", false, "load external symbols into results") - flags.BoolVar(&opts.NoNeedComment, "no-need-comment", false, "not need comment (only works for Go now)") - flags.BoolVar(&opts.NotNeedTest, "no-need-test", false, "not need parse test files (only works for Go now)") - flags.BoolVar(&opts.LoadByPackages, "load-by-packages", false, "load by packages (only works for Go now)") - flags.Var((*StringArray)(&opts.Excludes), "exclude", "exclude files or directories, support multiple values") - flags.StringVar(&opts.RepoID, "repo-id", "", "specify the repo id") - flags.StringVar(&opts.TSConfig, "tsconfig", "", "tsconfig path (only works for TS now)") - flags.Var((*StringArray)(&opts.TSSrcDir), "ts-src-dir", "src-dir path (only works for TS now)") - - var wopts lang.WriteOptions - flags.StringVar(&wopts.Compiler, "compiler", "", "destination compiler path.") - - var aopts agent.AgentOptions - flags.IntVar(&aopts.MaxSteps, "agent-max-steps", 50, "specify the max steps that the agent can run for each time") - flags.IntVar(&aopts.MaxHistories, "agent-max-histories", 10, "specify the max histories that the agent can use") - - flags.Usage = func() { - fmt.Fprint(os.Stderr, Usage) - fmt.Fprintf(os.Stderr, "Flags:\n") - flags.PrintDefaults() + cmd := NewRootCmd() + if err := cmd.Execute(); err != nil { + os.Exit(1) } +} - if len(os.Args) < 2 { - flags.Usage() - os.Exit(1) +func NewRootCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "abcoder", + Short: "Universal AST parser and writer for multi-language", + Long: `ABCoder is a universal code analysis tool that converts source code to UniAST format. + +It supports multiple programming languages and provides various subcommands for parsing, +writing, and analyzing code structures.`, } - action := strings.ToLower(os.Args[1]) - switch action { - case "version": - fmt.Fprintf(os.Stdout, "%s\n", version.Version) + // Global flags + cmd.PersistentFlags().BoolP("verbose", "v", false, "Verbose mode.") - case "parse": - language, uri := parseArgsAndFlags(flags, true, flagHelp, flagVerbose) + // Add subcommands + cmd.AddCommand(newVersionCmd()) + cmd.AddCommand(newParseCmd()) + cmd.AddCommand(newWriteCmd()) + cmd.AddCommand(newMcpCmd()) + cmd.AddCommand(newInitSpecCmd()) + cmd.AddCommand(newAgentCmd()) - if flagVerbose != nil && *flagVerbose { - log.SetLogLevel(log.DebugLevel) - opts.Verbose = true - } + return cmd +} - opts.Language = language +func newVersionCmd() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Print abcoder version information", + Long: `Output the version number and build metadata in the format: vX.X.Y-BUILD. - if language == uniast.TypeScript { - if err := parseTSProject(context.Background(), uri, opts, flagOutput); err != nil { - log.Error("Failed to parse: %v\n", err) - os.Exit(1) +Use this command to verify installation or when reporting issues.`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Fprintf(os.Stdout, "%s\n", version.Version) + }, + } +} + +func newParseCmd() *cobra.Command { + var ( + flagOutput string + flagLsp string + javaHome string + opts lang.ParseOptions + ) + + cmd := &cobra.Command{ + Use: "parse ", + Short: "Parse repository and export to UniAST JSON format", + Long: `Parse the specified repository and generate its Universal AST representation. + +By default, outputs to stdout. Use --output to write to a file. + +Language Support: + go - Go projects + rust - Rust projects + cxx - C/C++ projects + python - Python projects + ts - TypeScript projects + js - JavaScript projects + java - Java projects`, + Example: `abcoder parse go ./my-project -o ast.json`, + Args: cobra.ExactArgs(2), + PreRunE: func(cmd *cobra.Command, args []string) error { + // Validate language + language := uniast.NewLanguage(args[0]) + if language == uniast.Unknown { + return fmt.Errorf("unsupported language: %s", args[0]) + } + opts.Language = language + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + verbose, _ := cmd.Flags().GetBool("verbose") + if verbose { + log.SetLogLevel(log.DebugLevel) + opts.Verbose = true } - return - } - if flagLsp != nil { - opts.LSP = *flagLsp - } + language := uniast.NewLanguage(args[0]) + uri := args[1] - lspOptions := make(map[string]string) - if javaHome != nil { - lspOptions["java.home"] = *javaHome - } - opts.LspOptions = lspOptions + if language == uniast.TypeScript { + if err := parseTSProject(context.Background(), uri, opts, flagOutput); err != nil { + log.Error("Failed to parse: %v\n", err) + return err + } + return nil + } - out, err := lang.Parse(context.Background(), uri, opts) - if err != nil { - log.Error("Failed to parse: %v\n", err) - os.Exit(1) - } + if flagLsp != "" { + opts.LSP = flagLsp + } - if flagOutput != nil && *flagOutput != "" { - if err := utils.MustWriteFile(*flagOutput, out); err != nil { - log.Error("Failed to write output: %v\n", err) + lspOptions := make(map[string]string) + if javaHome != "" { + lspOptions["java.home"] = javaHome } - } else { - fmt.Fprintf(os.Stdout, "%s\n", out) - } + opts.LspOptions = lspOptions - case "write": - _, uri := parseArgsAndFlags(flags, false, flagHelp, flagVerbose) - if uri == "" { - log.Error("Argument Path is required\n") - os.Exit(1) - } + out, err := lang.Parse(context.Background(), uri, opts) + if err != nil { + log.Error("Failed to parse: %v\n", err) + return err + } - repo, err := uniast.LoadRepo(uri) - if err != nil { - log.Error("Failed to load repo: %v\n", err) - os.Exit(1) - } + if flagOutput != "" { + if err := utils.MustWriteFile(flagOutput, out); err != nil { + log.Error("Failed to write output: %v\n", err) + return err + } + } else { + fmt.Fprintf(os.Stdout, "%s\n", out) + } - if flagOutput != nil && *flagOutput != "" { - wopts.OutputDir = *flagOutput - } else { - wopts.OutputDir = filepath.Base(repo.Path) - } + return nil + }, + } - if err := lang.Write(context.Background(), repo, wopts); err != nil { - log.Error("Failed to write: %v\n", err) - os.Exit(1) - } + // Flags + cmd.Flags().StringVarP(&flagOutput, "output", "o", "", "Output path for UniAST JSON (default: stdout).") + cmd.Flags().StringVar(&flagLsp, "lsp", "", "Path to Language Server Protocol executable. Required for languages with LSP support (e.g., Java).") + cmd.Flags().StringVar(&javaHome, "java-home", "", "Java installation directory (JAVA_HOME). Required when using LSP for Java.") + cmd.Flags().BoolVar(&opts.LoadExternalSymbol, "load-external-symbol", false, "Load external symbol references into AST results (slower but more complete).") + cmd.Flags().BoolVar(&opts.NoNeedComment, "no-need-comment", false, "Skip parsing code comments (only works for Go).") + cmd.Flags().BoolVar(&opts.NotNeedTest, "no-need-test", false, "Skip test files during parsing (only works for Go).") + cmd.Flags().BoolVar(&opts.LoadByPackages, "load-by-packages", false, "Load packages one by one instead of all at once (only works for Go, uses more memory).") + cmd.Flags().StringSliceVar(&opts.Excludes, "exclude", []string{}, "Files or directories to exclude from parsing (can be specified multiple times).") + cmd.Flags().StringVar(&opts.RepoID, "repo-id", "", "Custom identifier for this repository (useful for multi-repo scenarios).") + cmd.Flags().StringVar(&opts.TSConfig, "tsconfig", "", "Path to tsconfig.json file for TypeScript project configuration.") + cmd.Flags().StringSliceVar(&opts.TSSrcDir, "ts-src-dir", []string{}, "Additional TypeScript source directories (can be specified multiple times).") + + return cmd +} - case "mcp": - _, uri := parseArgsAndFlags(flags, false, flagHelp, flagVerbose) - if uri == "" { - log.Error("Argument Path is required\n") - os.Exit(1) - } +func newWriteCmd() *cobra.Command { + var ( + flagOutput string + wopts lang.WriteOptions + ) + + cmd := &cobra.Command{ + Use: "write ", + Short: "write the specific UniAST back to codes", + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if args[0] == "" { + return fmt.Errorf("argument Path is required") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + verbose, _ := cmd.Flags().GetBool("verbose") + if verbose { + log.SetLogLevel(log.DebugLevel) + } - svr := mcp.NewServer(mcp.ServerOptions{ - ServerName: "abcoder", - ServerVersion: version.Version, - Verbose: *flagVerbose, - ASTReadToolsOptions: tool.ASTReadToolsOptions{ - RepoASTsDir: uri, - }, - }) - if err := svr.ServeStdio(); err != nil { - log.Error("Failed to run MCP server: %v\n", err) - os.Exit(1) - } + uri := args[0] - case "init-spec": - // Parse flags only, uri is optional and defaults to current directory - flags.Parse(os.Args[2:]) + repo, err := uniast.LoadRepo(uri) + if err != nil { + log.Error("Failed to load repo: %v\n", err) + return err + } - var uri string - if flagHelp != nil && *flagHelp { - flags.Usage() - os.Exit(0) - } + if flagOutput != "" { + wopts.OutputDir = flagOutput + } else { + wopts.OutputDir = filepath.Base(repo.Path) + } - if flagVerbose != nil && *flagVerbose { - log.SetLogLevel(log.DebugLevel) - } + if err := lang.Write(context.Background(), repo, wopts); err != nil { + log.Error("Failed to write: %v\n", err) + return err + } - if len(os.Args) > 2 && !strings.HasPrefix(os.Args[2], "-") { - uri = os.Args[2] - } + return nil + }, + } - if err := interutils.RunInitSpec(uri); err != nil { - log.Error("Failed to init-spec: %v\n", err) - os.Exit(1) - } + cmd.Flags().StringVarP(&flagOutput, "output", "o", "", "Output directory for generated code files (default: ).") + cmd.Flags().StringVar(&wopts.Compiler, "compiler", "", "Path to compiler executable (language-specific).") - case "agent": - _, uri := parseArgsAndFlags(flags, false, flagHelp, flagVerbose) - if uri == "" { - log.Error("Argument Path is required\n") - os.Exit(1) - } + return cmd +} - aopts.ASTsDir = uri - aopts.Model.APIType = llm.NewModelType(os.Getenv("API_TYPE")) - if aopts.Model.APIType == llm.ModelTypeUnknown { - log.Error("env API_TYPE is required") - os.Exit(1) - } - aopts.Model.APIKey = os.Getenv("API_KEY") - if aopts.Model.APIKey == "" { - log.Error("env API_KEY is required") - os.Exit(1) - } - aopts.Model.ModelName = os.Getenv("MODEL_NAME") - if aopts.Model.ModelName == "" { - log.Error("env MODEL_NAME is required") - os.Exit(1) - } - aopts.Model.BaseURL = os.Getenv("BASE_URL") +func newMcpCmd() *cobra.Command { + return &cobra.Command{ + Use: "mcp ", + Short: "Start MCP server for AST files", + Long: `Start a Model Context Protocol (MCP) server that provides AST reading tools. - ag := agent.NewAgent(aopts) - ag.Run(context.Background()) +The server communicates via stdio and can be integrated with Claude Code or other MCP clients. +It serves all *.json AST files in the specified directory.`, + Example: `abcoder mcp ./asts/`, + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if args[0] == "" { + return fmt.Errorf("argument Path is required") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + verbose, _ := cmd.Flags().GetBool("verbose") + + uri := args[0] + + svr := mcp.NewServer(mcp.ServerOptions{ + ServerName: "abcoder", + ServerVersion: version.Version, + Verbose: verbose, + ASTReadToolsOptions: tool.ASTReadToolsOptions{ + RepoASTsDir: uri, + }, + }) + if err := svr.ServeStdio(); err != nil { + log.Error("Failed to run MCP server: %v\n", err) + return err + } + + return nil + }, } } -func parseArgsAndFlags(flags *flag.FlagSet, needLang bool, flagHelp *bool, flagVerbose *bool) (language uniast.Language, uri string) { - if len(os.Args) < 3 { - flags.Usage() - os.Exit(1) - } +func newInitSpecCmd() *cobra.Command { + return &cobra.Command{ + Use: "init-spec [project-path]", + Short: "Initialize ABCoder integration for Claude Code", + Long: `Initialize ABCoder integration by copying .claude directory and configuring MCP servers. + +This sets up Claude Code to use ABCoder for code analysis. + +The path defaults to the current directory if not specified. + +The command will: +1. Copy the .claude configuration directory +2. Configure MCP server settings in Claude's config.json`, + Example: `abcoder init-spec /path/to/project`, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + verbose, _ := cmd.Flags().GetBool("verbose") + if verbose { + log.SetLogLevel(log.DebugLevel) + } - if needLang { - language = uniast.NewLanguage(os.Args[2]) - if language == uniast.Unknown { - fmt.Fprintf(os.Stderr, "unsupported language: %s\n", os.Args[2]) - os.Exit(1) - } - if len(os.Args) < 4 { - fmt.Fprintf(os.Stderr, "argument Path is required\n") - os.Exit(1) - } - uri = os.Args[3] - if len(os.Args) > 4 { - flags.Parse(os.Args[4:]) - } - } else { - uri = os.Args[2] - if len(os.Args) > 3 { - flags.Parse(os.Args[3:]) - } - } + var uri string + if len(args) > 0 { + uri = args[0] + } - if flagHelp != nil && *flagHelp { - flags.Usage() - os.Exit(0) - } + if err := interutils.RunInitSpec(uri); err != nil { + log.Error("Failed to init-spec: %v\n", err) + return err + } - if flagVerbose != nil && *flagVerbose { - log.SetLogLevel(log.DebugLevel) + return nil + }, } - - return language, uri } -type StringArray []string +func newAgentCmd() *cobra.Command { + var ( + aopts agent.AgentOptions + ) + + cmd := &cobra.Command{ + Use: "agent ", + Short: "Run AI agent with code analysis capabilities", + Long: `Start an autonomous AI agent that can perform code analysis tasks using LLM. + +The agent reads AST files from the specified directory and can perform various +code analysis operations. + +Required Environment Variables: + API_TYPE LLM provider type (e.g., openai, anthropic) + API_KEY LLM API authentication key + MODEL_NAME Model identifier (e.g., gpt-4, claude-3-opus-20240229) + BASE_URL (Optional) Custom API base URL + +Examples: + # Basic usage with OpenAI + API_TYPE=openai API_KEY=sk-xxx MODEL_NAME=gpt-4 \ + abcoder agent ./asts/ + + # With custom API endpoint and step limit + API_TYPE=custom API_KEY=xxx MODEL_NAME=my-model BASE_URL=https://api.example.com \ + abcoder agent ./asts/ --agent-max-steps 100`, + Args: cobra.ExactArgs(1), + PreRunE: func(cmd *cobra.Command, args []string) error { + if args[0] == "" { + return fmt.Errorf("argument Path is required") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + verbose, _ := cmd.Flags().GetBool("verbose") + if verbose { + log.SetLogLevel(log.DebugLevel) + } + + uri := args[0] -func (s *StringArray) Set(value string) error { - *s = append(*s, value) - return nil -} + aopts.ASTsDir = uri + aopts.Model.APIType = llm.NewModelType(os.Getenv("API_TYPE")) + if aopts.Model.APIType == llm.ModelTypeUnknown { + log.Error("env API_TYPE is required") + return fmt.Errorf("env API_TYPE is required") + } + aopts.Model.APIKey = os.Getenv("API_KEY") + if aopts.Model.APIKey == "" { + log.Error("env API_KEY is required") + return fmt.Errorf("env API_KEY is required") + } + aopts.Model.ModelName = os.Getenv("MODEL_NAME") + if aopts.Model.ModelName == "" { + log.Error("env MODEL_NAME is required") + return fmt.Errorf("env MODEL_NAME is required") + } + aopts.Model.BaseURL = os.Getenv("BASE_URL") + + ag := agent.NewAgent(aopts) + ag.Run(context.Background()) + + return nil + }, + } + + cmd.Flags().IntVar(&aopts.MaxSteps, "agent-max-steps", 50, "Maximum number of agent reasoning steps per task (default: 50). Higher values allow more complex tasks but increase cost.") + cmd.Flags().IntVar(&aopts.MaxHistories, "agent-max-histories", 10, "Maximum number of conversation histories to maintain for context (default: 10).") -func (s *StringArray) String() string { - return strings.Join(*s, ",") + return cmd } -func parseTSProject(ctx context.Context, repoPath string, opts lang.ParseOptions, outputFlag *string) error { - if outputFlag == nil { +func parseTSProject(ctx context.Context, repoPath string, opts lang.ParseOptions, outputPath string) error { + if outputPath == "" { return fmt.Errorf("output path is required") } @@ -328,8 +419,8 @@ func parseTSProject(ctx context.Context, repoPath string, opts lang.ParseOptions if opts.TSConfig != "" { args = append(args, "--tsconfig", opts.TSConfig) } - if *outputFlag != "" { - args = append(args, "--output", *outputFlag) + if outputPath != "" { + args = append(args, "--output", outputPath) } cmd := exec.CommandContext(ctx, parserPath, args...)