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
2 changes: 1 addition & 1 deletion .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "0.4.0"
".": "0.5.0"
}
6 changes: 3 additions & 3 deletions .stats.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
configured_endpoints: 18
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-5058541cc0e298a6fc3e9cda1af0e32586d1f39c5666946e15f546c1aedc18ea.yml
openapi_spec_hash: 7f572ac0c7f9dc4f5fc7d9883a53d6c7
config_hash: 35db4c99791f175865381f13a8ad6075
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/kernel%2Fhypeman-ce51f144a3d2de556750203edbaa5bfeefe874660737c35a4fc37dfb30057dd5.yml
openapi_spec_hash: 27663b6503056317abcb578ac7b67c06
config_hash: b4e65d240d7bca1ba6162ee2098c8ac2
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 0.5.0 (2025-11-26)

Full Changelog: [v0.4.0...v0.5.0](https://github.com/onkernel/hypeman-cli/compare/v0.4.0...v0.5.0)

### Features

* Generate log streaming ([31951c5](https://github.com/onkernel/hypeman-cli/commit/31951c5caf65c008f9811ffd023f54a10c3f1474))

## 0.4.0 (2025-11-26)

Full Changelog: [v0.3.0...v0.4.0](https://github.com/onkernel/hypeman-cli/compare/v0.3.0...v0.4.0)
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ require (
github.com/charmbracelet/bubbletea v1.3.6
github.com/charmbracelet/lipgloss v1.1.0
github.com/charmbracelet/x/term v0.2.1
github.com/gorilla/websocket v1.5.3
github.com/itchyny/json2yaml v0.1.4
github.com/muesli/reflow v0.3.0
github.com/onkernel/hypeman-go v0.2.0
github.com/onkernel/hypeman-go v0.4.0
github.com/tidwall/gjson v1.18.0
github.com/tidwall/pretty v1.2.1
github.com/tidwall/sjson v1.2.5
Expand All @@ -25,7 +26,6 @@ require (
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/onkernel/hypeman-go v0.2.0 h1:wiDMSi7eGTKfVfdxhCg8vcFKa6xbXjWG2sSHk7EXi4Y=
github.com/onkernel/hypeman-go v0.2.0/go.mod h1:pxRRFfVcLvafZpDD1O6IjwHnem3hKEuZTCClrnGiIKA=
github.com/onkernel/hypeman-go v0.4.0 h1:3zDpB/WOPhkdJ/Lug3DmBDUcGR5zHwpOTzLEbsO4AnE=
github.com/onkernel/hypeman-go v0.4.0/go.mod h1:pxRRFfVcLvafZpDD1O6IjwHnem3hKEuZTCClrnGiIKA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand Down
98 changes: 49 additions & 49 deletions pkg/cmd/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,48 +81,48 @@ var instancesDelete = cli.Command{
HideHelpCommand: true,
}

var instancesPutInStandby = cli.Command{
Name: "put-in-standby",
Usage: "Put instance in standby (pause, snapshot, delete VMM)",
var instancesLogs = cli.Command{
Name: "logs",
Usage: "Streams instance console logs as Server-Sent Events. Returns the last N lines\n(controlled by `tail` parameter), then optionally continues streaming new lines\nif `follow=true`.",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
},
&cli.BoolFlag{
Name: "follow",
Usage: "Continue streaming new lines after initial output",
},
&cli.Int64Flag{
Name: "tail",
Usage: "Number of lines to return from end",
Value: 100,
},
},
Action: handleInstancesPutInStandby,
Action: handleInstancesLogs,
HideHelpCommand: true,
}

var instancesRestoreFromStandby = cli.Command{
Name: "restore-from-standby",
Usage: "Restore instance from standby",
var instancesPutInStandby = cli.Command{
Name: "put-in-standby",
Usage: "Put instance in standby (pause, snapshot, delete VMM)",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
},
},
Action: handleInstancesRestoreFromStandby,
Action: handleInstancesPutInStandby,
HideHelpCommand: true,
}

var instancesStreamLogs = cli.Command{
Name: "stream-logs",
Usage: "Stream instance logs (SSE)",
var instancesRestoreFromStandby = cli.Command{
Name: "restore-from-standby",
Usage: "Restore instance from standby",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "id",
},
&cli.BoolFlag{
Name: "follow",
Usage: "Follow logs (stream with SSE)",
},
&cli.Int64Flag{
Name: "tail",
Usage: "Number of lines to return from end",
Value: 100,
},
},
Action: handleInstancesStreamLogs,
Action: handleInstancesRestoreFromStandby,
HideHelpCommand: true,
}

Expand Down Expand Up @@ -226,7 +226,7 @@ func handleInstancesDelete(ctx context.Context, cmd *cli.Command) error {
)
}

func handleInstancesPutInStandby(ctx context.Context, cmd *cli.Command) error {
func handleInstancesLogs(ctx context.Context, cmd *cli.Command) error {
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)
unusedArgs := cmd.Args().Slice()
if !cmd.IsSet("id") && len(unusedArgs) > 0 {
Expand All @@ -236,24 +236,27 @@ func handleInstancesPutInStandby(ctx context.Context, cmd *cli.Command) error {
if len(unusedArgs) > 0 {
return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs)
}
var res []byte
_, err := client.Instances.PutInStandby(
params := hypeman.InstanceLogsParams{}
if cmd.IsSet("follow") {
params.Follow = hypeman.Opt(cmd.Value("follow").(bool))
}
if cmd.IsSet("tail") {
params.Tail = hypeman.Opt(cmd.Value("tail").(int64))
}
stream := client.Instances.LogsStreaming(
ctx,
cmd.Value("id").(string),
params,
option.WithMiddleware(debugMiddleware(cmd.Bool("debug"))),
option.WithResponseBodyInto(&res),
)
if err != nil {
return err
defer stream.Close()
for stream.Next() {
fmt.Printf("%s\n", stream.Current())
}

json := gjson.Parse(string(res))
format := cmd.Root().String("format")
transform := cmd.Root().String("transform")
return ShowJSON("instances put-in-standby", json, format, transform)
return stream.Err()
}

func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) error {
func handleInstancesPutInStandby(ctx context.Context, cmd *cli.Command) error {
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)
unusedArgs := cmd.Args().Slice()
if !cmd.IsSet("id") && len(unusedArgs) > 0 {
Expand All @@ -264,7 +267,7 @@ func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) er
return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs)
}
var res []byte
_, err := client.Instances.RestoreFromStandby(
_, err := client.Instances.PutInStandby(
ctx,
cmd.Value("id").(string),
option.WithMiddleware(debugMiddleware(cmd.Bool("debug"))),
Expand All @@ -277,10 +280,10 @@ func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) er
json := gjson.Parse(string(res))
format := cmd.Root().String("format")
transform := cmd.Root().String("transform")
return ShowJSON("instances restore-from-standby", json, format, transform)
return ShowJSON("instances put-in-standby", json, format, transform)
}

func handleInstancesStreamLogs(ctx context.Context, cmd *cli.Command) error {
func handleInstancesRestoreFromStandby(ctx context.Context, cmd *cli.Command) error {
client := hypeman.NewClient(getDefaultRequestOptions(cmd)...)
unusedArgs := cmd.Args().Slice()
if !cmd.IsSet("id") && len(unusedArgs) > 0 {
Expand All @@ -290,22 +293,19 @@ func handleInstancesStreamLogs(ctx context.Context, cmd *cli.Command) error {
if len(unusedArgs) > 0 {
return fmt.Errorf("Unexpected extra arguments: %v", unusedArgs)
}
params := hypeman.InstanceStreamLogsParams{}
if cmd.IsSet("follow") {
params.Follow = hypeman.Opt(cmd.Value("follow").(bool))
}
if cmd.IsSet("tail") {
params.Tail = hypeman.Opt(cmd.Value("tail").(int64))
}
stream := client.Instances.StreamLogsStreaming(
var res []byte
_, err := client.Instances.RestoreFromStandby(
ctx,
cmd.Value("id").(string),
params,
option.WithMiddleware(debugMiddleware(cmd.Bool("debug"))),
option.WithResponseBodyInto(&res),
)
defer stream.Close()
for stream.Next() {
fmt.Printf("%s\n", stream.Current())
if err != nil {
return err
}
return stream.Err()

json := gjson.Parse(string(res))
format := cmd.Root().String("format")
transform := cmd.Root().String("transform")
return ShowJSON("instances restore-from-standby", json, format, transform)
}
62 changes: 16 additions & 46 deletions pkg/cmd/logs.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ package cmd
import (
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"

"github.com/onkernel/hypeman-go"
"github.com/onkernel/hypeman-go/option"
"github.com/urfave/cli/v3"
)

Expand Down Expand Up @@ -46,54 +43,27 @@ func handleLogs(ctx context.Context, cmd *cli.Command) error {
return err
}

// Build URL for logs endpoint
baseURL := cmd.Root().String("base-url")
if baseURL == "" {
baseURL = os.Getenv("HYPEMAN_BASE_URL")
params := hypeman.InstanceLogsParams{}
if cmd.IsSet("follow") {
params.Follow = hypeman.Opt(cmd.Bool("follow"))
}
if baseURL == "" {
baseURL = "http://localhost:8080"
if cmd.IsSet("tail") {
params.Tail = hypeman.Opt(int64(cmd.Int("tail")))
}

u, err := url.Parse(baseURL)
if err != nil {
return fmt.Errorf("invalid base URL: %w", err)
}
u.Path = fmt.Sprintf("/instances/%s/logs", instanceID)

// Add query parameters
q := u.Query()
q.Set("tail", fmt.Sprintf("%d", cmd.Int("tail")))
if cmd.Bool("follow") {
q.Set("follow", "true")
}
u.RawQuery = q.Encode()

// Make HTTP request
req, err := http.NewRequestWithContext(ctx, "GET", u.String(), nil)
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}

apiKey := os.Getenv("HYPEMAN_API_KEY")
if apiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey))
}

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to fetch logs: %w", err)
}
defer resp.Body.Close()
stream := client.Instances.LogsStreaming(
ctx,
instanceID,
params,
option.WithMiddleware(debugMiddleware(cmd.Root().Bool("debug"))),
)
defer stream.Close()

if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("failed to fetch logs (HTTP %d): %s", resp.StatusCode, string(body))
for stream.Next() {
fmt.Println(stream.Current())
}

// Stream the response to stdout
_, err = io.Copy(os.Stdout, resp.Body)
return err
return stream.Err()
}


2 changes: 1 addition & 1 deletion pkg/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

package cmd

const Version = "0.4.0" // x-release-please-version
const Version = "0.5.0" // x-release-please-version