From 3abd4811714b0499f4597ef4fc1dad976d94273e Mon Sep 17 00:00:00 2001 From: Ramon Nogueira Date: Thu, 28 May 2026 13:25:02 -0400 Subject: [PATCH 1/2] feat(sandbox): store default sandbox in profile --- internal/cmd/sandbox.go | 1 + internal/cmd/sandbox_box.go | 131 +++++++++++++++++++----- internal/cmd/sandbox_console.go | 10 +- internal/cmd/sandbox_console_windows.go | 10 +- internal/cmd/sandbox_snapshot.go | 10 +- internal/cmd/sandbox_ssh.go | 10 +- internal/cmd/sandbox_test.go | 90 ++++++++++++++++ internal/cmd/sandbox_tunnel.go | 8 +- internal/config/config.go | 9 +- 9 files changed, 235 insertions(+), 44 deletions(-) diff --git a/internal/cmd/sandbox.go b/internal/cmd/sandbox.go index d437bfc..2015096 100644 --- a/internal/cmd/sandbox.go +++ b/internal/cmd/sandbox.go @@ -23,6 +23,7 @@ Common workflows: # Run a command inside the sandbox langsmith sandbox exec my-vm -- uname -a + langsmith sandbox exec -- uname -a # Open an interactive shell langsmith sandbox console my-vm diff --git a/internal/cmd/sandbox_box.go b/internal/cmd/sandbox_box.go index 3738387..b7abfc9 100644 --- a/internal/cmd/sandbox_box.go +++ b/internal/cmd/sandbox_box.go @@ -9,6 +9,7 @@ import ( "github.com/langchain-ai/langsmith-cli/internal/client" "github.com/langchain-ai/langsmith-cli/internal/cmdutil" + lsconfig "github.com/langchain-ai/langsmith-cli/internal/config" "github.com/langchain-ai/langsmith-cli/internal/structured" "github.com/langchain-ai/langsmith-go" "github.com/spf13/cobra" @@ -101,6 +102,45 @@ func sandboxCreateParams(name string, in *sandboxCreateInput) (langsmith.Sandbox return params, nil } +func resolveSandboxName(cmd *cobra.Command, args []string) (string, error) { + if len(args) > 0 { + return args[0], nil + } + return defaultSandboxName(cmd) +} + +func defaultSandboxName(cmd *cobra.Command) (string, error) { + cfg, err := lsconfig.Load() + if err != nil { + return "", err + } + profileName, profile, ok := cfg.ResolveProfile(flagProfile, profileEnvName()) + if !ok || profile.DefaultSandbox == "" { + if profileName != "" { + return "", fmt.Errorf("no sandbox specified and profile %q has no default sandbox", profileName) + } + return "", fmt.Errorf("no sandbox specified and no default profile is selected") + } + return profile.DefaultSandbox, nil +} + +func setDefaultSandbox(cmd *cobra.Command, name string) error { + if name == "" { + return nil + } + cfg, err := lsconfig.Load() + if err != nil { + return err + } + profileName, profile, ok := cfg.ResolveProfile(flagProfile, profileEnvName()) + if !ok { + return nil + } + profile.DefaultSandbox = name + cfg.Profiles[profileName] = profile + return cfg.Save() +} + var sandboxCreateCommand = structured.Command[*sandboxCreateInput]{ Use: "create [name]", Short: "Create a sandbox VM", @@ -167,6 +207,13 @@ Examples: if err != nil { return nil, fmt.Errorf("creating sandbox: %w", err) } + defaultName := resp.Name + if defaultName == "" { + defaultName = resp.ID + } + if err := setDefaultSandbox(cmd, defaultName); err != nil { + return nil, fmt.Errorf("setting default sandbox: %w", err) + } return resp, nil }, @@ -185,7 +232,7 @@ var sandboxServiceURLRender = structured.PropertyList{ } var sandboxServiceURLCommand = structured.Command[*sandboxServiceURLInput]{ - Use: "service-url --port ", + Use: "service-url [name] --port ", Short: "Generate an authenticated URL for a sandbox HTTP service", Long: `Generate an authenticated URL for an HTTP service running inside a sandbox. @@ -195,7 +242,7 @@ the browser URL directly. Examples: langsmith sandbox service-url my-vm --port 8000 langsmith sandbox service-url my-vm --port 8000 --expires-in-seconds 3600`, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Input: func(cmd *cobra.Command) *sandboxServiceURLInput { in := &sandboxServiceURLInput{} cmd.Flags().IntVar(&in.Port, "port", in.Port, "Port inside the sandbox") @@ -215,6 +262,10 @@ Examples: if err != nil { return nil, err } + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } params := langsmith.SandboxBoxGenerateServiceURLParams{ Port: langsmith.F(int64(in.Port)), @@ -223,7 +274,7 @@ Examples: params.ExpiresInSeconds = langsmith.F(in.ExpiresInSeconds) } - resp, err := c.SDK.Sandboxes.Boxes.GenerateServiceURL(ctx, args[0], params) + resp, err := c.SDK.Sandboxes.Boxes.GenerateServiceURL(ctx, name, params) if err != nil { return nil, fmt.Errorf("generating service URL: %w", err) } @@ -263,16 +314,20 @@ var sandboxListCommand = structured.Command[struct{}]{ } var sandboxGetCommand = structured.Command[struct{}]{ - Use: "get ", + Use: "get [name]", Short: "Get a sandbox by name", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Action: func(ctx context.Context, cmd *cobra.Command, in struct{}, args []string) (any, error) { c, err := cmdutil.GetClient(cmd) if err != nil { return nil, err } + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } - resp, err := c.SDK.Sandboxes.Boxes.Get(ctx, args[0]) + resp, err := c.SDK.Sandboxes.Boxes.Get(ctx, name) if err != nil { return nil, fmt.Errorf("getting sandbox: %w", err) } @@ -290,7 +345,7 @@ type sandboxUpdateInput struct { } var sandboxUpdateCommand = structured.Command[*sandboxUpdateInput]{ - Use: "update ", + Use: "update [name]", Short: "Update sandbox resources (takes effect on next start)", Long: `Update sandbox resources or proxy configuration. @@ -299,7 +354,7 @@ Proxy config changes take effect immediately. The --proxy-config flag accepts inline JSON or @file.json. See "create --help" for the proxy config JSON format.`, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Input: func(cmd *cobra.Command) *sandboxUpdateInput { in := &sandboxUpdateInput{} cmd.Flags().IntVar(&in.VCPUs, "vcpus", in.VCPUs, "Number of vCPU cores") @@ -313,6 +368,10 @@ for the proxy config JSON format.`, if err != nil { return nil, err } + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } params := langsmith.SandboxBoxUpdateParams{} if cmd.Flags().Changed("vcpus") { @@ -344,7 +403,7 @@ for the proxy config JSON format.`, return nil, fmt.Errorf("nothing to update (use --vcpus, --memory, --rootfs-capacity, or --proxy-config)") } - resp, err := c.SDK.Sandboxes.Boxes.Update(ctx, args[0], params) + resp, err := c.SDK.Sandboxes.Boxes.Update(ctx, name, params) if err != nil { return nil, fmt.Errorf("updating sandbox: %w", err) } @@ -355,63 +414,75 @@ for the proxy config JSON format.`, } var sandboxDeleteCommand = structured.Command[struct{}]{ - Use: "delete ", + Use: "delete [name]", Short: "Delete a sandbox", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Action: func(ctx context.Context, cmd *cobra.Command, in struct{}, args []string) (any, error) { c, err := cmdutil.GetClient(cmd) if err != nil { return nil, err } + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } - if err := c.SDK.Sandboxes.Boxes.Delete(ctx, args[0]); err != nil { + if err := c.SDK.Sandboxes.Boxes.Delete(ctx, name); err != nil { return nil, fmt.Errorf("deleting sandbox: %w", err) } - return sandboxMessage{Name: args[0], Message: "Sandbox deleted."}, nil + return sandboxMessage{Name: name, Message: fmt.Sprintf("Sandbox %s deleted.", name)}, nil }, Render: structured.Template(`{{.Message}} `), } var sandboxStartCommand = structured.Command[struct{}]{ - Use: "start ", + Use: "start [name]", Short: "Start a stopped sandbox", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Action: func(ctx context.Context, cmd *cobra.Command, in struct{}, args []string) (any, error) { c, err := cmdutil.GetClient(cmd) if err != nil { return nil, err } + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } - if _, err := c.SDK.Sandboxes.Boxes.Start(ctx, args[0]); err != nil { + if _, err := c.SDK.Sandboxes.Boxes.Start(ctx, name); err != nil { return nil, fmt.Errorf("starting sandbox: %w", err) } - if _, err := waitForBoxReady(ctx, c, args[0]); err != nil { + if _, err := waitForBoxReady(ctx, c, name); err != nil { return nil, err } - return sandboxMessage{Name: args[0], Message: fmt.Sprintf("Sandbox %s started", args[0])}, nil + return sandboxMessage{Name: name, Message: fmt.Sprintf("Sandbox %s started", name)}, nil }, Render: structured.Template(`{{.Message}} `), } var sandboxStopCommand = structured.Command[struct{}]{ - Use: "stop ", + Use: "stop [name]", Short: "Stop a running sandbox (preserves data)", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Action: func(ctx context.Context, cmd *cobra.Command, in struct{}, args []string) (any, error) { c, err := cmdutil.GetClient(cmd) if err != nil { return nil, err } + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } - if err := c.SDK.Sandboxes.Boxes.Stop(ctx, args[0]); err != nil { + if err := c.SDK.Sandboxes.Boxes.Stop(ctx, name); err != nil { return nil, fmt.Errorf("stopping sandbox: %w", err) } - return sandboxMessage{Name: args[0], Message: "Sandbox stopped."}, nil + return sandboxMessage{Name: name, Message: fmt.Sprintf("Sandbox %s stopped.", name)}, nil }, Render: structured.Template(`{{.Message}} `), @@ -420,7 +491,7 @@ var sandboxStopCommand = structured.Command[struct{}]{ type sandboxExecInput struct{} var sandboxExecCommand = structured.Command[sandboxExecInput]{ - Use: "exec -- ", + Use: "exec [name] -- ", Short: "Execute a command inside a sandbox", Long: `Execute a one-off command inside a running sandbox and print its output. @@ -432,11 +503,19 @@ Examples: Input: func(cmd *cobra.Command) sandboxExecInput { return sandboxExecInput{} }, CustomOutput: true, Action: func(ctx context.Context, cmd *cobra.Command, in sandboxExecInput, args []string) (any, error) { - name := args[0] - cmdArgs := cmd.ArgsLenAtDash() if cmdArgs < 0 || cmdArgs >= len(args) { - return nil, fmt.Errorf("usage: langsmith sandbox exec -- ") + return nil, fmt.Errorf("usage: langsmith sandbox exec [name] -- ") + } + var name string + var err error + if cmdArgs == 0 { + name, err = defaultSandboxName(cmd) + } else { + name = args[0] + } + if err != nil { + return nil, err } command := args[cmdArgs:] if len(command) == 0 { diff --git a/internal/cmd/sandbox_console.go b/internal/cmd/sandbox_console.go index 8904de2..0347fa3 100644 --- a/internal/cmd/sandbox_console.go +++ b/internal/cmd/sandbox_console.go @@ -23,7 +23,7 @@ type sandboxConsoleInput struct { } var sandboxConsoleCommand = structured.Command[*sandboxConsoleInput]{ - Use: "console ", + Use: "console [name]", Short: "Open an interactive shell inside a sandbox", Long: `Open an interactive terminal session inside a running sandbox. @@ -34,7 +34,7 @@ Examples: langsmith sandbox console my-vm langsmith sandbox console my-vm --shell /bin/sh langsmith sandbox console my-vm --forward-ssh-agent`, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Input: func(cmd *cobra.Command) *sandboxConsoleInput { in := &sandboxConsoleInput{} cmd.Flags().StringVar(&in.Shell, "shell", in.Shell, "Shell to use (default: sandbox default, usually /bin/bash)") @@ -43,7 +43,11 @@ Examples: }, CustomOutput: true, Action: func(ctx context.Context, cmd *cobra.Command, in *sandboxConsoleInput, args []string) (any, error) { - return nil, runConsole(args[0], in.Shell, in.ForwardSSHAgent) + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } + return nil, runConsole(name, in.Shell, in.ForwardSSHAgent) }, } diff --git a/internal/cmd/sandbox_console_windows.go b/internal/cmd/sandbox_console_windows.go index 4e547d9..7329ba4 100644 --- a/internal/cmd/sandbox_console_windows.go +++ b/internal/cmd/sandbox_console_windows.go @@ -9,12 +9,16 @@ import ( ) var sandboxConsoleCommand = structured.Command[struct{}]{ - Use: "console ", + Use: "console [name]", Short: "Open an interactive shell inside a sandbox (not supported on Windows)", - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), CustomOutput: true, Action: func(ctx context.Context, cmd *cobra.Command, in struct{}, args []string) (any, error) { - return nil, fmt.Errorf("sandbox console is not supported on Windows; use SSH instead: langsmith sandbox ssh-setup %s", args[0]) + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("sandbox console is not supported on Windows; use SSH instead: langsmith sandbox ssh-setup %s", name) }, } diff --git a/internal/cmd/sandbox_snapshot.go b/internal/cmd/sandbox_snapshot.go index 298934a..10cc4bc 100644 --- a/internal/cmd/sandbox_snapshot.go +++ b/internal/cmd/sandbox_snapshot.go @@ -147,14 +147,18 @@ fresh checkpoint from the running VM's current state. Args: cobra.ExactArgs(1), Input: func(cmd *cobra.Command) *snapshotCaptureInput { in := &snapshotCaptureInput{} - cmd.Flags().StringVar(&in.BoxName, "box", in.BoxName, "Sandbox name to capture from (required)") + cmd.Flags().StringVar(&in.BoxName, "box", in.BoxName, "Sandbox name to capture from (defaults to profile default sandbox)") cmd.Flags().StringVar(&in.Checkpoint, "checkpoint", in.Checkpoint, "Checkpoint timestamp to use (omit for fresh checkpoint)") return in }, Action: func(ctx context.Context, cmd *cobra.Command, in *snapshotCaptureInput, args []string) (any, error) { name := args[0] if in.BoxName == "" { - return nil, fmt.Errorf("--box is required") + boxName, err := defaultSandboxName(cmd) + if err != nil { + return nil, err + } + in.BoxName = boxName } c, err := cmdutil.GetClient(cmd) @@ -225,7 +229,7 @@ var snapshotDeleteCommand = structured.Command[struct{}]{ return nil, fmt.Errorf("deleting snapshot: %w", err) } - return sandboxMessage{Name: args[0], Message: "Snapshot deleted."}, nil + return sandboxMessage{Name: args[0], Message: fmt.Sprintf("Snapshot %s deleted.", args[0])}, nil }, Render: structured.Template(`{{.Message}} `), diff --git a/internal/cmd/sandbox_ssh.go b/internal/cmd/sandbox_ssh.go index a57ffef..bd68f2f 100644 --- a/internal/cmd/sandbox_ssh.go +++ b/internal/cmd/sandbox_ssh.go @@ -20,7 +20,7 @@ type sandboxSSHSetupInput struct { } var sandboxSSHSetupCommand = structured.Command[*sandboxSSHSetupInput]{ - Use: "ssh-setup ", + Use: "ssh-setup [name]", Short: "Upload your SSH public key and configure ~/.ssh/config for a sandbox", Long: `Upload your SSH public key to a running sandbox so you can connect with standard SSH tools (ssh, scp, rsync, sftp). @@ -32,7 +32,7 @@ immediately connect with: ssh sandbox- Examples: langsmith sandbox ssh-setup my-sandbox langsmith sandbox ssh-setup my-sandbox --identity ~/.ssh/id_ed25519.pub`, - Args: cobra.ExactArgs(1), + Args: cobra.MaximumNArgs(1), Input: func(cmd *cobra.Command) *sandboxSSHSetupInput { in := &sandboxSSHSetupInput{} cmd.Flags().StringVar(&in.Identity, "identity", in.Identity, "Path to SSH public key (default: auto-detect)") @@ -40,7 +40,11 @@ Examples: }, CustomOutput: true, Action: func(ctx context.Context, cmd *cobra.Command, in *sandboxSSHSetupInput, args []string) (any, error) { - return nil, runSSHSetup(cmd, args[0], in.Identity) + name, err := resolveSandboxName(cmd, args) + if err != nil { + return nil, err + } + return nil, runSSHSetup(cmd, name, in.Identity) }, } diff --git a/internal/cmd/sandbox_test.go b/internal/cmd/sandbox_test.go index b1a9a4c..6f90072 100644 --- a/internal/cmd/sandbox_test.go +++ b/internal/cmd/sandbox_test.go @@ -9,6 +9,7 @@ import ( "runtime" "testing" + lsconfig "github.com/langchain-ai/langsmith-cli/internal/config" langsmith "github.com/langchain-ai/langsmith-go" "github.com/spf13/cobra" "github.com/stretchr/testify/assert" @@ -159,6 +160,72 @@ func TestSandboxCreateCmd_AllowsNoArgs(t *testing.T) { require.NotContains(t, body, "snapshot_id") } +func TestSandboxCreateCmd_SetsDefaultSandboxOnCurrentProfile(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.json") + t.Setenv("LANGSMITH_CONFIG_FILE", configPath) + require.NoError(t, os.WriteFile(configPath, []byte(`{ + "current_profile": "dev", + "profiles": { + "dev": {"api_key": "profile-key"} + } +}`), 0600)) + + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/v2/sandboxes/boxes", r.URL.Path) + w.Header().Set("Content-Type", "application/json") + _, err := w.Write([]byte(`{"id":"box-id","name":"created-vm","status":"running"}`)) + require.NoError(t, err) + }) + + _, err := executeCommand(t, "--api-key", "test-key", "--api-url", ts.URL, "sandbox", "create", "created-vm", "--format", "json") + + require.NoError(t, err) + cfg, err := lsconfig.LoadFrom(configPath) + require.NoError(t, err) + assert.Equal(t, "created-vm", cfg.Profiles["dev"].DefaultSandbox) +} + +func TestSandboxGetCmd_UsesDefaultSandboxWhenNameOmitted(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.json") + t.Setenv("LANGSMITH_CONFIG_FILE", configPath) + require.NoError(t, os.WriteFile(configPath, []byte(`{ + "current_profile": "dev", + "profiles": { + "dev": {"api_key": "profile-key", "api_url": "http://placeholder", "default_sandbox": "current-vm"} + } +}`), 0600)) + + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + require.Equal(t, "/v2/sandboxes/boxes/current-vm", r.URL.Path) + w.Header().Set("Content-Type", "application/json") + _, err := w.Write([]byte(`{"id":"box-id","name":"current-vm","status":"running"}`)) + require.NoError(t, err) + }) + + out, err := executeCommand(t, "--api-url", ts.URL, "sandbox", "get") + + require.NoError(t, err) + assert.Contains(t, out, "current-vm") +} + +func TestSandboxGetCmd_RequiresNameWhenNoDefaultSandbox(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.json") + t.Setenv("LANGSMITH_CONFIG_FILE", configPath) + require.NoError(t, os.WriteFile(configPath, []byte(`{ + "current_profile": "dev", + "profiles": { + "dev": {"api_key": "profile-key"} + } +}`), 0600)) + + _, err := executeCommand(t, "sandbox", "get") + + require.Error(t, err) + assert.Contains(t, err.Error(), `profile "dev" has no default sandbox`) +} + func TestSandboxExecCmd_PositionalNameAndCommandSeparator(t *testing.T) { cmd := newSandboxExecCmd() require.NoError(t, cmd.Args(cmd, []string{"my-vm", "echo", "hi"})) @@ -269,6 +336,29 @@ func TestSnapshotCaptureCmd_PositionalName(t *testing.T) { } } +func TestSnapshotCaptureCmd_UsesDefaultSandboxWhenBoxOmitted(t *testing.T) { + configPath := filepath.Join(t.TempDir(), "config.json") + t.Setenv("LANGSMITH_CONFIG_FILE", configPath) + require.NoError(t, os.WriteFile(configPath, []byte(`{ + "current_profile": "dev", + "profiles": { + "dev": {"api_key": "profile-key", "default_sandbox": "current-vm"} + } +}`), 0600)) + + ts := newTestServer(t, func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + require.Equal(t, "/v2/sandboxes/boxes/current-vm/snapshot", r.URL.Path) + w.Header().Set("Content-Type", "application/json") + _, err := w.Write([]byte(`{"id":"snap-id","name":"snap-name","status":"pending"}`)) + require.NoError(t, err) + }) + + _, err := executeCommand(t, "--api-url", ts.URL, "sandbox", "snapshot", "capture", "snap-name") + + require.NoError(t, err) +} + func TestSandboxCreateCmd_SizeFlags(t *testing.T) { cmd := sandboxCreateCommand.Cobra() for _, name := range []string{"memory", "rootfs-capacity"} { diff --git a/internal/cmd/sandbox_tunnel.go b/internal/cmd/sandbox_tunnel.go index 586cb53..04a1e23 100644 --- a/internal/cmd/sandbox_tunnel.go +++ b/internal/cmd/sandbox_tunnel.go @@ -25,7 +25,7 @@ type sandboxTunnelInput struct { } var sandboxTunnelCommand = structured.Command[*sandboxTunnelInput]{ - Use: "tunnel --remote-port ", + Use: "tunnel [name] --remote-port ", Short: "Create a TCP tunnel to a service inside a sandbox", Long: `Create a TCP tunnel from a local port to a port inside a remote sandbox. @@ -62,7 +62,11 @@ Examples: name = args[0] } if name == "" && in.SandboxURL == "" { - return nil, fmt.Errorf("provide a sandbox name or --url") + var err error + name, err = defaultSandboxName(cmd) + if err != nil { + return nil, err + } } client := MustGetClient() ctx, cancel := signal.NotifyContext(ctx, syscall.SIGTERM, syscall.SIGINT) diff --git a/internal/config/config.go b/internal/config/config.go index e80ee86..4180d0c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,10 +16,11 @@ const ( // Profile represents one named LangSmith CLI profile. type Profile struct { - APIKey string `json:"api_key,omitempty"` - APIURL string `json:"api_url,omitempty"` - WorkspaceID string `json:"workspace_id,omitempty"` - OAuth OAuth `json:"oauth,omitempty"` + APIKey string `json:"api_key,omitempty"` + APIURL string `json:"api_url,omitempty"` + WorkspaceID string `json:"workspace_id,omitempty"` + DefaultSandbox string `json:"default_sandbox,omitempty"` + OAuth OAuth `json:"oauth,omitempty"` } // OAuth stores OAuth tokens written by `langsmith login`. From 5751613db776ecd7f4c3503819a727be53033033 Mon Sep 17 00:00:00 2001 From: Ramon Nogueira Date: Thu, 28 May 2026 13:30:24 -0400 Subject: [PATCH 2/2] refactor(sandbox): rename default sandbox field --- internal/cmd/sandbox_box.go | 10 +++++----- internal/cmd/sandbox_test.go | 4 ++-- internal/config/config.go | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/internal/cmd/sandbox_box.go b/internal/cmd/sandbox_box.go index b7abfc9..1e42927 100644 --- a/internal/cmd/sandbox_box.go +++ b/internal/cmd/sandbox_box.go @@ -115,16 +115,16 @@ func defaultSandboxName(cmd *cobra.Command) (string, error) { return "", err } profileName, profile, ok := cfg.ResolveProfile(flagProfile, profileEnvName()) - if !ok || profile.DefaultSandbox == "" { + if !ok || profile.DefaultSandboxName == "" { if profileName != "" { return "", fmt.Errorf("no sandbox specified and profile %q has no default sandbox", profileName) } return "", fmt.Errorf("no sandbox specified and no default profile is selected") } - return profile.DefaultSandbox, nil + return profile.DefaultSandboxName, nil } -func setDefaultSandbox(cmd *cobra.Command, name string) error { +func setDefaultSandboxName(cmd *cobra.Command, name string) error { if name == "" { return nil } @@ -136,7 +136,7 @@ func setDefaultSandbox(cmd *cobra.Command, name string) error { if !ok { return nil } - profile.DefaultSandbox = name + profile.DefaultSandboxName = name cfg.Profiles[profileName] = profile return cfg.Save() } @@ -211,7 +211,7 @@ Examples: if defaultName == "" { defaultName = resp.ID } - if err := setDefaultSandbox(cmd, defaultName); err != nil { + if err := setDefaultSandboxName(cmd, defaultName); err != nil { return nil, fmt.Errorf("setting default sandbox: %w", err) } diff --git a/internal/cmd/sandbox_test.go b/internal/cmd/sandbox_test.go index 6f90072..7804be4 100644 --- a/internal/cmd/sandbox_test.go +++ b/internal/cmd/sandbox_test.go @@ -183,7 +183,7 @@ func TestSandboxCreateCmd_SetsDefaultSandboxOnCurrentProfile(t *testing.T) { require.NoError(t, err) cfg, err := lsconfig.LoadFrom(configPath) require.NoError(t, err) - assert.Equal(t, "created-vm", cfg.Profiles["dev"].DefaultSandbox) + assert.Equal(t, "created-vm", cfg.Profiles["dev"].DefaultSandboxName) } func TestSandboxGetCmd_UsesDefaultSandboxWhenNameOmitted(t *testing.T) { @@ -210,7 +210,7 @@ func TestSandboxGetCmd_UsesDefaultSandboxWhenNameOmitted(t *testing.T) { assert.Contains(t, out, "current-vm") } -func TestSandboxGetCmd_RequiresNameWhenNoDefaultSandbox(t *testing.T) { +func TestSandboxGetCmd_RequiresNameWhenNoDefaultSandboxName(t *testing.T) { configPath := filepath.Join(t.TempDir(), "config.json") t.Setenv("LANGSMITH_CONFIG_FILE", configPath) require.NoError(t, os.WriteFile(configPath, []byte(`{ diff --git a/internal/config/config.go b/internal/config/config.go index 4180d0c..a7ede2b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -16,11 +16,11 @@ const ( // Profile represents one named LangSmith CLI profile. type Profile struct { - APIKey string `json:"api_key,omitempty"` - APIURL string `json:"api_url,omitempty"` - WorkspaceID string `json:"workspace_id,omitempty"` - DefaultSandbox string `json:"default_sandbox,omitempty"` - OAuth OAuth `json:"oauth,omitempty"` + APIKey string `json:"api_key,omitempty"` + APIURL string `json:"api_url,omitempty"` + WorkspaceID string `json:"workspace_id,omitempty"` + DefaultSandboxName string `json:"default_sandbox,omitempty"` + OAuth OAuth `json:"oauth,omitempty"` } // OAuth stores OAuth tokens written by `langsmith login`.