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
12 changes: 12 additions & 0 deletions configs/envconst/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package envconst

const (
// UseCpCmd Name cp command
UseCpCmd = `copy`

// ShortCpCmd Short description cp command
ShortCpCmd = `Download|Upload file`

// LongCpCmd Long description cp command
LongCpCmd = `This command Download or Upload file on the remote machine`
)
40 changes: 21 additions & 19 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module github.com/misha-ssh/cli
go 1.24.2

require (
github.com/charmbracelet/fang v0.3.0
github.com/charmbracelet/fang v0.4.2
github.com/charmbracelet/huh v0.7.0
github.com/charmbracelet/lipgloss v1.1.0
github.com/misha-ssh/kernel v1.0.3
github.com/spf13/cobra v1.9.1
github.com/misha-ssh/kernel v1.2.0
github.com/spf13/cobra v1.10.1
)

require (
Expand All @@ -16,39 +16,41 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.0 // indirect
github.com/charmbracelet/bubbletea v1.3.4 // indirect
github.com/charmbracelet/colorprofile v0.3.1 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1 // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/ansi v0.10.1 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250714123521-bc8a1995e079 // indirect
github.com/charmbracelet/x/exp/color v0.0.0-20250714123521-bc8a1995e079 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/exp/charmtone v0.0.0-20250929112927-8219a564b457 // indirect
github.com/charmbracelet/x/exp/color v0.0.0-20250929112927-8219a564b457 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20250929112927-8219a564b457 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/danieljoos/wincred v1.2.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/mango v0.2.0 // indirect
github.com/muesli/mango-cobra v1.2.0 // indirect
github.com/muesli/mango-pflag v0.1.0 // indirect
github.com/muesli/mango-cobra v1.3.0 // indirect
github.com/muesli/mango-pflag v0.2.0 // indirect
github.com/muesli/roff v0.1.0 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/pkg/sftp v1.13.9 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
github.com/zalando/go-keyring v0.2.6 // indirect
golang.org/x/crypto v0.41.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.28.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
)
158 changes: 117 additions & 41 deletions go.sum

Large diffs are not rendered by default.

56 changes: 56 additions & 0 deletions internal/command/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package command

import (
"fmt"

"github.com/misha-ssh/cli/configs/envconst"
"github.com/misha-ssh/cli/internal/component/copy"
"github.com/misha-ssh/cli/internal/component/list"
"github.com/misha-ssh/cli/internal/component/output"
"github.com/misha-ssh/kernel/pkg/kernel"
"github.com/spf13/cobra"
)

// cpCmd Command for copy file
var cpCmd = &cobra.Command{
Use: envconst.UseCpCmd,
Short: envconst.ShortCpCmd,
Long: envconst.LongCpCmd,
RunE: func(cmd *cobra.Command, args []string) error {
connections, err := kernel.List()
if err != nil {
return err
}

selectedConn, err := list.Run(connections)
if err != nil {
return errNotFoundConnection
}

fields, err := copy.Run()
if err != nil {
return err
}

switch fields.TypeCopy {
case copy.Download:
err := kernel.Download(selectedConn, fields.RemoteFile, fields.LocalFile)
if err != nil {
return err
}

output.Success("Success download file: " + fields.LocalFile)
case copy.Upload:
err := kernel.Upload(selectedConn, fields.LocalFile, fields.RemoteFile)
if err != nil {
return err
}

output.Success("Success upload file: " + fields.LocalFile)
default:
return fmt.Errorf("unknown copy type: %s", fields.TypeCopy)
}

return nil
},
}
4 changes: 2 additions & 2 deletions internal/command/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ var createCmd = &cobra.Command{
Login: fields.Login,
Address: fields.Address,
Password: fields.Password,
CreatedAt: time.Now().Format("2006.01.02 15:04:05"),
UpdatedAt: time.Now().Format("2006.01.02 15:04:05"),
CreatedAt: time.Now().Format(time.RFC3339),
UpdatedAt: time.Now().Format(time.RFC3339),
Type: connect.TypeSSH,
SshOptions: &connect.SshOptions{
Port: fields.Port,
Expand Down
2 changes: 1 addition & 1 deletion internal/command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ func Run() {
Example: envconst.ExampleRootCmd,
}

// Disable default options cmd
cmd.Root().CompletionOptions.DisableDefaultCmd = true

cmd.AddCommand(
connectCmd,
createCmd,
deleteCmd,
updateCmd,
cpCmd,
)

if err := fang.Execute(
Expand Down
2 changes: 1 addition & 1 deletion internal/command/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ var updateCmd = &cobra.Command{
Login: fields.Login,
Address: fields.Address,
Password: fields.Password,
UpdatedAt: time.Now().Format("2006.01.02 15:04:05"),
UpdatedAt: time.Now().Format(time.RFC3339),
CreatedAt: selectedConn.CreatedAt,
Type: connect.TypeSSH,
SshOptions: &connect.SshOptions{
Expand Down
74 changes: 74 additions & 0 deletions internal/component/copy/copy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package copy

import (
"errors"
"os"

"github.com/charmbracelet/huh"
)

var (
errCpFile = errors.New(`cannot copy file`)
errGetHomeDir = errors.New(`cannot get home directory`)
)

// Run get form for cp files
func Run() (*Fields, error) {
var isDownloadCopy bool

fields := &Fields{}

homedir, err := os.UserHomeDir()
if err != nil {
return nil, errGetHomeDir
}

err = huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Type copy").
Description("Choice method copy").
Affirmative("Download").
Negative("Upload").
Value(&isDownloadCopy),
),
huh.NewGroup(
huh.NewFilePicker().
Title("Local File").
Description("Select local file").
CurrentDirectory(homedir).
Validate(localFileValidate).
Value(&fields.LocalFile).
Picking(true),
huh.NewInput().
Title("Remote File").
Description("Absolute path to remote file").
Value(&fields.RemoteFile),
).WithHideFunc(func() bool {
return isDownloadCopy
}),
huh.NewGroup(
huh.NewInput().
Title("Remote File").
Description("Absolute path to remote file").
Value(&fields.RemoteFile),
huh.NewInput().
Title("Local File").
Description("Absolute path to local file").
Value(&fields.LocalFile),
).WithHideFunc(func() bool {
return !isDownloadCopy
}),
).WithShowHelp(true).Run()
if err != nil {
return nil, errCpFile
}

fields.TypeCopy = Upload

if isDownloadCopy {
fields.TypeCopy = Download
}

return fields, nil
}
15 changes: 15 additions & 0 deletions internal/component/copy/fields.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package copy

type TypeCopy string

const (
Download TypeCopy = "download"
Upload TypeCopy = "upload"
)

// Fields A struct that copy files
type Fields struct {
TypeCopy TypeCopy
LocalFile string
RemoteFile string
}
25 changes: 25 additions & 0 deletions internal/component/copy/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package copy

import (
"errors"
"os"
"strings"
)

var (
errFileNotExist = errors.New("file not exists")
)

// localFileValidate validate on exists file
func localFileValidate(filename string) error {
if strings.TrimSpace(filename) == "" || filename == "" {
return nil
}

_, err := os.Stat(filename)
if err != nil {
return errFileNotExist
}

return nil
}