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
20 changes: 20 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,26 @@ The StepLogger system provides user-facing progress feedback during runtime oper

**For detailed StepLogger integration guidance, use:** `/working-with-steplogger`

### Logger System

The Logger system (`pkg/logger`) routes stdout and stderr from runtime CLI commands (e.g., `podman build`) to the user. It is controlled by the `--show-logs` flag.

**Key Points:**
- Commands inject a `logger.Logger` into context based on the `--show-logs` flag
- Runtime methods retrieve it from context and pass its writers to CLI command execution
- When `--show-logs` is set, output is written to the command's stdout/stderr; otherwise it is discarded
- `--show-logs` cannot be combined with `--output json` (enforced in `preRun`)

**Interface** (`pkg/logger/logger.go`):
```go
type Logger interface {
Stdout() io.Writer
Stderr() io.Writer
}
```

**Context integration** (`pkg/logger/context.go`): `WithLogger()` / `FromContext()` — mirrors the StepLogger pattern.

### Config System

The config system manages workspace configuration for **injecting environment variables and mounting directories** into workspaces (different from runtime-specific configuration).
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,7 @@ kortex-cli init [sources-directory] [flags]
- `--project, -p <identifier>` - Custom project identifier to override auto-detection (default: auto-detected from git repository or source directory)
- `--verbose, -v` - Show detailed output including all workspace information
- `--output, -o <format>` - Output format (supported: `json`)
- `--show-logs` - Show stdout and stderr from runtime commands (cannot be combined with `--output json`)
- `--storage <path>` - Storage directory for kortex-cli data (default: `$HOME/.kortex-cli`)

#### Examples
Expand Down Expand Up @@ -1205,6 +1206,11 @@ Output:
kortex-cli init -r fake -a claude -o json -v
```

**Show runtime command output (e.g., image build logs):**
```bash
kortex-cli init --runtime podman --agent claude --show-logs
```

#### Workspace Naming

- If `--name` is not provided, the name is automatically generated from the last component of the sources directory path
Expand Down Expand Up @@ -1308,6 +1314,7 @@ kortex-cli init /tmp/workspace --runtime fake --agent claude
- JSON output format is useful for scripting and automation
- Without `--verbose`, JSON output returns only the workspace ID
- With `--verbose`, JSON output includes full workspace details (ID, name, agent, paths)
- Use `--show-logs` to display the full stdout and stderr from runtime commands (e.g., `podman build` output during image creation)
- **JSON error handling**: When `--output json` is used, errors are written to stdout (not stderr) in JSON format, and the CLI exits with code 1. Always check the exit code to determine success/failure

### `workspace list` - List All Registered Workspaces
Expand Down Expand Up @@ -1412,6 +1419,7 @@ kortex-cli start ID [flags]
#### Flags

- `--output, -o <format>` - Output format (supported: `json`)
- `--show-logs` - Show stdout and stderr from runtime commands (cannot be combined with `--output json`)
- `--storage <path>` - Storage directory for kortex-cli data (default: `$HOME/.kortex-cli`)

#### Examples
Expand Down Expand Up @@ -1452,6 +1460,11 @@ Output:
kortex-cli start a1b2c3d4e5f6... -o json
```

**Show runtime command output:**
```bash
kortex-cli workspace start a1b2c3d4e5f6... --show-logs
```

#### Error Handling

**Workspace not found (text format):**
Expand Down Expand Up @@ -1503,6 +1516,7 @@ kortex-cli stop ID [flags]
#### Flags

- `--output, -o <format>` - Output format (supported: `json`)
- `--show-logs` - Show stdout and stderr from runtime commands (cannot be combined with `--output json`)
- `--storage <path>` - Storage directory for kortex-cli data (default: `$HOME/.kortex-cli`)

#### Examples
Expand Down Expand Up @@ -1543,6 +1557,11 @@ Output:
kortex-cli stop a1b2c3d4e5f6... -o json
```

**Show runtime command output:**
```bash
kortex-cli workspace stop a1b2c3d4e5f6... --show-logs
```

#### Error Handling

**Workspace not found (text format):**
Expand Down Expand Up @@ -1693,6 +1712,7 @@ kortex-cli remove ID [flags]
#### Flags

- `--output, -o <format>` - Output format (supported: `json`)
- `--show-logs` - Show stdout and stderr from runtime commands (cannot be combined with `--output json`)
- `--storage <path>` - Storage directory for kortex-cli data (default: `$HOME/.kortex-cli`)

#### Examples
Expand Down Expand Up @@ -1733,6 +1753,11 @@ Output:
kortex-cli remove a1b2c3d4e5f6... -o json
```

**Show runtime command output:**
```bash
kortex-cli workspace remove a1b2c3d4e5f6... --show-logs
```

#### Error Handling

**Workspace not found (text format):**
Expand Down
35 changes: 27 additions & 8 deletions pkg/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
workspace "github.com/kortex-hub/kortex-cli-api/workspace-configuration/go"
"github.com/kortex-hub/kortex-cli/pkg/config"
"github.com/kortex-hub/kortex-cli/pkg/instances"
"github.com/kortex-hub/kortex-cli/pkg/logger"
"github.com/kortex-hub/kortex-cli/pkg/runtimesetup"
"github.com/kortex-hub/kortex-cli/pkg/steplogger"
"github.com/spf13/cobra"
Expand All @@ -47,6 +48,7 @@ type initCmd struct {
workspaceConfig *workspace.WorkspaceConfiguration
verbose bool
output string
showLogs bool
}

// preRun validates the parameters and flags
Expand All @@ -56,6 +58,10 @@ func (i *initCmd) preRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unsupported output format: %s (supported: json)", i.output)
}

if i.showLogs && i.output == "json" {
return fmt.Errorf("--show-logs cannot be used with --output json")
}

// Silence Cobra's default error output to stderr when JSON mode is enabled,
// because we write the error in the JSON response to stdout instead
if i.output == "json" {
Expand Down Expand Up @@ -165,18 +171,26 @@ func (i *initCmd) preRun(cmd *cobra.Command, args []string) error {

// run executes the init command logic
func (i *initCmd) run(cmd *cobra.Command, args []string) error {
// Create appropriate logger based on output mode
var logger steplogger.StepLogger
// Create appropriate step logger based on output mode
var stepLogger steplogger.StepLogger
if i.output == "json" {
// No step logging in JSON mode
logger = steplogger.NewNoOpLogger()
stepLogger = steplogger.NewNoOpLogger()
} else {
logger = steplogger.NewTextLogger(cmd.ErrOrStderr())
stepLogger = steplogger.NewTextLogger(cmd.ErrOrStderr())
}
defer logger.Complete()
defer stepLogger.Complete()

// Attach logger to context
ctx := steplogger.WithLogger(cmd.Context(), logger)
ctx := steplogger.WithLogger(cmd.Context(), stepLogger)

// Create appropriate logger based on --show-logs flag
var l logger.Logger
if i.showLogs {
l = logger.NewTextLogger(cmd.OutOrStdout(), cmd.ErrOrStderr())
} else {
l = logger.NewNoOpLogger()
}
ctx = logger.WithLogger(ctx, l)

// Create a new instance
instance, err := instances.NewInstance(instances.NewInstanceParams{
Expand Down Expand Up @@ -269,7 +283,10 @@ kortex-cli init --runtime fake --agent claude --name my-project
kortex-cli init --runtime fake --agent goose --project my-custom-project

# Show detailed output
kortex-cli init --runtime fake --agent claude --verbose`,
kortex-cli init --runtime fake --agent claude --verbose

# Show runtime command output
kortex-cli init --runtime fake --agent claude --show-logs`,
Args: cobra.MaximumNArgs(1),
PreRunE: c.preRun,
RunE: c.run,
Expand All @@ -296,6 +313,8 @@ kortex-cli init --runtime fake --agent claude --verbose`,

// Add output flag
cmd.Flags().StringVarP(&c.output, "output", "o", "", "Output format (supported: json)")
cmd.Flags().BoolVar(&c.showLogs, "show-logs", false, "Show stdout and stderr from runtime commands")

cmd.RegisterFlagCompletionFunc("output", newOutputFlagCompletion([]string{"json"}))

return cmd
Expand Down
25 changes: 24 additions & 1 deletion pkg/cmd/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,29 @@ func TestInitCmd_PreRun(t *testing.T) {
}
})

t.Run("rejects --show-logs with --output json", func(t *testing.T) {
t.Parallel()

tempDir := t.TempDir()

c := &initCmd{
output: "json",
showLogs: true,
}
cmd := &cobra.Command{}
cmd.Flags().String("workspace-configuration", "", "test flag")
cmd.Flags().String("storage", tempDir, "test storage flag")

err := c.preRun(cmd, []string{})
if err == nil {
t.Fatal("Expected preRun() to fail when --show-logs used with --output json")
}

if !strings.Contains(err.Error(), "--show-logs") {
t.Errorf("Expected error to mention '--show-logs', got: %v", err)
}
})

t.Run("outputs JSON error when manager creation fails with json output", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -1983,7 +2006,7 @@ func TestInitCmd_Examples(t *testing.T) {
}

// Verify we have the expected number of examples
expectedCount := 5
expectedCount := 6
if len(commands) != expectedCount {
t.Errorf("Expected %d example commands, got %d", expectedCount, len(commands))
}
Expand Down
41 changes: 30 additions & 11 deletions pkg/cmd/workspace_remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@ import (

api "github.com/kortex-hub/kortex-cli-api/cli/go"
"github.com/kortex-hub/kortex-cli/pkg/instances"
"github.com/kortex-hub/kortex-cli/pkg/logger"
"github.com/kortex-hub/kortex-cli/pkg/runtimesetup"
"github.com/kortex-hub/kortex-cli/pkg/steplogger"
"github.com/spf13/cobra"
)

// workspaceRemoveCmd contains the configuration for the workspace remove command
type workspaceRemoveCmd struct {
manager instances.Manager
id string
output string
manager instances.Manager
id string
output string
showLogs bool
}

// preRun validates the parameters and flags
Expand All @@ -45,6 +47,10 @@ func (w *workspaceRemoveCmd) preRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unsupported output format: %s (supported: json)", w.output)
}

if w.showLogs && w.output == "json" {
return fmt.Errorf("--show-logs cannot be used with --output json")
}

// Silence Cobra's default error output to stderr when JSON mode is enabled,
// because we write the error in the JSON response to stdout instead
if w.output == "json" {
Expand Down Expand Up @@ -83,18 +89,26 @@ func (w *workspaceRemoveCmd) preRun(cmd *cobra.Command, args []string) error {

// run executes the workspace remove command logic
func (w *workspaceRemoveCmd) run(cmd *cobra.Command, args []string) error {
// Create appropriate logger based on output mode
var logger steplogger.StepLogger
// Create appropriate step logger based on output mode
var stepLogger steplogger.StepLogger
if w.output == "json" {
// No step logging in JSON mode
logger = steplogger.NewNoOpLogger()
stepLogger = steplogger.NewNoOpLogger()
} else {
logger = steplogger.NewTextLogger(cmd.ErrOrStderr())
stepLogger = steplogger.NewTextLogger(cmd.ErrOrStderr())
}
defer logger.Complete()
defer stepLogger.Complete()

// Attach logger to context
ctx := steplogger.WithLogger(cmd.Context(), logger)
ctx := steplogger.WithLogger(cmd.Context(), stepLogger)

// Create appropriate logger based on --show-logs flag
var l logger.Logger
if w.showLogs {
l = logger.NewTextLogger(cmd.OutOrStdout(), cmd.ErrOrStderr())
} else {
l = logger.NewNoOpLogger()
}
ctx = logger.WithLogger(ctx, l)

// Delete the instance
err := w.manager.Delete(ctx, w.id)
Expand Down Expand Up @@ -143,14 +157,19 @@ func NewWorkspaceRemoveCmd() *cobra.Command {
Short: "Remove a workspace",
Long: "Remove a workspace by its ID",
Example: `# Remove workspace by ID
kortex-cli workspace remove abc123`,
kortex-cli workspace remove abc123

# Remove workspace and show runtime command output
kortex-cli workspace remove abc123 --show-logs`,
Args: cobra.ExactArgs(1),
ValidArgsFunction: completeNonRunningWorkspaceID,
PreRunE: c.preRun,
RunE: c.run,
}

cmd.Flags().StringVarP(&c.output, "output", "o", "", "Output format (supported: json)")
cmd.Flags().BoolVar(&c.showLogs, "show-logs", false, "Show stdout and stderr from runtime commands")

cmd.RegisterFlagCompletionFunc("output", newOutputFlagCompletion([]string{"json"}))

return cmd
Expand Down
24 changes: 23 additions & 1 deletion pkg/cmd/workspace_remove_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,28 @@ func TestWorkspaceRemoveCmd_PreRun(t *testing.T) {
}
})

t.Run("rejects --show-logs with --output json", func(t *testing.T) {
t.Parallel()

storageDir := t.TempDir()

c := &workspaceRemoveCmd{
output: "json",
showLogs: true,
}
cmd := &cobra.Command{}
cmd.Flags().String("storage", storageDir, "test storage flag")

err := c.preRun(cmd, []string{"test-id"})
if err == nil {
t.Fatal("Expected preRun() to fail when --show-logs used with --output json")
}

if !strings.Contains(err.Error(), "--show-logs") {
t.Errorf("Expected error to mention '--show-logs', got: %v", err)
}
})

t.Run("outputs JSON error when manager creation fails with json output", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -723,7 +745,7 @@ func TestWorkspaceRemoveCmd_Examples(t *testing.T) {
}

// Verify we have the expected number of examples
expectedCount := 1
expectedCount := 2
if len(commands) != expectedCount {
t.Errorf("Expected %d example commands, got %d", expectedCount, len(commands))
}
Expand Down
Loading
Loading