Skip to content
Open
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 61 additions & 4 deletions cmd/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion internal/client/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
27 changes: 26 additions & 1 deletion internal/repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/c-bata/go-prompt"
fileCompleter "github.com/c-bata/go-prompt/completer"
"golang.org/x/term"
)

var (
Expand Down Expand Up @@ -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,
Expand All @@ -761,11 +769,28 @@ func Start() error {
},
}),
prompt.OptionCompletionWordSeparator(fileCompleter.FilePathCompletionSeparator),
prompt.OptionSetExitCheckerOnInput(exitChecker),
)
p.Run()

// Restore terminal after prompt exits
if err := term.Restore(int(os.Stdin.Fd()), oldState); err != nil {
fmt.Println("Failed to restore terminal:", err)
}

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 == "" {
Expand All @@ -779,7 +804,7 @@ func executor(input string) {
switch input {
case "exit", "quit":
fmt.Println("Goodbye!")
os.Exit(0)
return
case "help":
printHelp()
return
Expand Down
Loading