From bf681e26fc7027b43780e5c5043ce3a722955be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=8F=8C=E9=99=86?= Date: Thu, 7 May 2026 11:40:40 +0800 Subject: [PATCH 1/2] feat: bugfix ags interactive command exit shell environment pollution , Add instance list --all function --- CHANGELOG.md | 12 ++++++++ cmd/instance.go | 65 +++++++++++++++++++++++++++++++++++++--- internal/client/types.go | 2 +- internal/repl/repl.go | 27 ++++++++++++++++- 4 files changed, 100 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f8b07f..b63b2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ All notable changes to this project will be documented in this file. +## [0.4.1] - 2026-05-06 + +### Added +- Add `--all` flag to `ags instance list` for automatically fetching all paginated instances (Cloud backend supports max 100 per request, use `--all` to iterate through offsets) + +### Fixed +- Fix REPL exit leaving terminal in raw mode (unable to type after typing `exit` or `quit`) + - Save terminal state with `term.GetState()` before REPL starts + - Use `prompt.OptionSetExitCheckerOnInput` to handle exit/quit gracefully + - Only exit when user presses Enter (breakLine=true), not during input completion + - Restore terminal state with `term.Restore()` after REPL exits + ## [0.4.0] - 2026-04-28 ### Added diff --git a/cmd/instance.go b/cmd/instance.go index e1d6fea..d77c591 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -31,6 +31,8 @@ var ( instanceListNoHeader bool instanceListOffset int instanceListLimit int + instanceListAll bool // Fetch all instances using pagination + instanceListNoAll bool // Explicit flag to disable --all // login command flags instanceLoginMode string @@ -250,9 +252,21 @@ Examples: Limit: instanceListLimit, } - result, err := apiClient.ListInstances(ctx, opts) - if err != nil { - return fmt.Errorf("failed to list instances: %w", err) + var result *client.ListInstancesResult + + // If --all flag is set, fetch all instances using pagination + // Note: This only works with Cloud backend where the API supports + // pagination. E2B backend will return partial results if limit is hit. + if instanceListAll { + result, err = fetchAllInstances(ctx, apiClient, opts) + if err != nil { + return fmt.Errorf("failed to list all instances: %w", err) + } + } else { + result, err = apiClient.ListInstances(ctx, opts) + if err != nil { + return fmt.Errorf("failed to list instances: %w", err) + } } totalDuration := time.Since(start) @@ -344,6 +358,48 @@ Examples: }, } +// fetchAllInstances fetches all instances using pagination. +// It loops with offset until all instances are retrieved. +func fetchAllInstances(ctx context.Context, apiClient client.ControlPlaneClient, opts *client.ListInstancesOptions) (*client.ListInstancesResult, error) { + const maxPageSize = 100 // Cloud API max limit + + allInstances := make([]client.Instance, 0, 64) + offset := opts.Offset + + for { + // Update offset for this page + opts.Offset = offset + opts.Limit = maxPageSize + + // Fetch one page + page, err := apiClient.ListInstances(ctx, opts) + if err != nil { + return nil, err + } + + // Append instances to the result + allInstances = append(allInstances, page.Instances...) + + // Check if we have all data + // If returned count is less than maxPageSize, we're done + // Also check TotalCount if available + if len(page.Instances) < maxPageSize { + break + } + if page.TotalCount > 0 && offset+len(page.Instances) >= page.TotalCount { + break + } + + // Next page + offset += maxPageSize + } + + return &client.ListInstancesResult{ + Instances: allInstances, + TotalCount: len(allInstances), + }, nil +} + // formatTimeout formats timeout seconds to human readable format func formatTimeout(seconds uint64) string { if seconds >= 3600 && seconds%3600 == 0 { @@ -857,7 +913,8 @@ func addInstanceCommand(parent *cobra.Command) { listCmd.Flags().BoolVar(&instanceListShort, "short", false, "Only show instance IDs") listCmd.Flags().BoolVar(&instanceListNoHeader, "no-header", false, "Hide table header") listCmd.Flags().IntVar(&instanceListOffset, "offset", 0, "Pagination offset") - listCmd.Flags().IntVar(&instanceListLimit, "limit", 20, "Pagination limit (max 100)") + listCmd.Flags().IntVar(&instanceListLimit, "limit", 40, "Pagination limit (max 100)") + listCmd.Flags().BoolVar(&instanceListAll, "all", false, "Fetch all instances using pagination (ignores --limit, --offset)") listCmd.Flags().BoolVar(&instanceTime, "time", false, "Print elapsed time") cmd.AddCommand(listCmd) diff --git a/internal/client/types.go b/internal/client/types.go index 3104a0b..bea7b29 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -252,7 +252,7 @@ type ListInstancesOptions struct { InstanceIDs []string // Specific instance IDs to query (max 100) ToolID string // Filter by tool ID Offset int // Pagination offset (ignored when InstanceIDs specified) - Limit int // Pagination limit, default 20, max 100 (ignored when InstanceIDs specified) + Limit int // Pagination limit, default 40, max 100 (ignored when InstanceIDs specified) Status string // Filter by status: STARTING, RUNNING, FAILED, STOPPING, STOPPED, STARTING_FAILED, STOPPING_FAILED CreatedSince string // Relative time filter, e.g., "5s", "2m", "3h" CreatedSinceTime string // Absolute time filter (RFC3339), e.g., "2024-01-15T10:30:00Z" diff --git a/internal/repl/repl.go b/internal/repl/repl.go index 52fe007..d785d61 100644 --- a/internal/repl/repl.go +++ b/internal/repl/repl.go @@ -8,6 +8,7 @@ import ( "github.com/c-bata/go-prompt" fileCompleter "github.com/c-bata/go-prompt/completer" + "golang.org/x/term" ) var ( @@ -744,6 +745,13 @@ func Start() error { fmt.Println("Type 'help' for available commands, 'exit' to quit") fmt.Println() + // Save current terminal state before prompt takes over + oldState, err := term.GetState(int(os.Stdin.Fd())) + if err != nil { + // Continue anyway, terminal might not be a tty + oldState = nil + } + p := prompt.New( executor, completer, @@ -761,11 +769,28 @@ func Start() error { }, }), prompt.OptionCompletionWordSeparator(fileCompleter.FilePathCompletionSeparator), + prompt.OptionSetExitCheckerOnInput(exitChecker), ) p.Run() + + // Restore terminal after prompt exits + if oldState != nil { + term.Restore(int(os.Stdin.Fd()), oldState) + } + return nil } +// exitChecker handles exit/quit commands +// Only exit when user presses Enter (breakLine=true), not during input completion +func exitChecker(in string, breakLine bool) bool { + if breakLine && (in == "exit" || in == "quit") { + fmt.Println("Goodbye!") + return true + } + return false +} + func executor(input string) { input = strings.TrimSpace(input) if input == "" { @@ -779,7 +804,7 @@ func executor(input string) { switch input { case "exit", "quit": fmt.Println("Goodbye!") - os.Exit(0) + return case "help": printHelp() return From 5f1496c5e30affce6dfa0376b0c63aa47564b24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=8F=8C=E9=99=86?= Date: Thu, 7 May 2026 12:07:35 +0800 Subject: [PATCH 2/2] fix: resolve golangci-lint errors --- cmd/instance.go | 2 +- internal/repl/repl.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/instance.go b/cmd/instance.go index d77c591..826f32a 100644 --- a/cmd/instance.go +++ b/cmd/instance.go @@ -32,7 +32,7 @@ var ( instanceListOffset int instanceListLimit int instanceListAll bool // Fetch all instances using pagination - instanceListNoAll bool // Explicit flag to disable --all + // instanceListNoAll bool // Explicit flag to disable --all // login command flags instanceLoginMode string diff --git a/internal/repl/repl.go b/internal/repl/repl.go index d785d61..c5c1e3d 100644 --- a/internal/repl/repl.go +++ b/internal/repl/repl.go @@ -774,8 +774,8 @@ func Start() error { p.Run() // Restore terminal after prompt exits - if oldState != nil { - term.Restore(int(os.Stdin.Fd()), oldState) + if err := term.Restore(int(os.Stdin.Fd()), oldState); err != nil { + fmt.Println("Failed to restore terminal:", err) } return nil