Compare commits

...
Author SHA1 Message Date
moloch-- d0652f2fd3 Add sqlite pragmas to db config 2026-01-21 16:08:07 -08:00
Joe 936f067363 Merge pull request #2130 from BishopFox/feature/website-info
Feature/website info
2026-01-21 10:25:56 -07:00
moloch-- 041c3c7048 Improve tab completer for websites cmd 2026-01-20 21:29:39 -08:00
moloch-- b48f74af58 Add original file name and sha256 to websites cmd 2026-01-20 19:13:42 -08:00
moloch-- 6ec8849b20 v1.6.6 2026-01-16 09:18:39 -08:00
Joe db7cf1ed62 Merge pull request #2125 from BishopFox/fix/winlsdir
Improved `err` and `nil` checks in `dirListHandler`
2026-01-16 10:17:01 -07:00
moloch-- 16e1e77068 Implement better err and nil checks in dirListHandler issue #2122 2026-01-16 07:06:43 -08:00
moloch-- 1cfa694aa2 Merge branch 'update/deps' into tui/adv-forms 2026-01-16 06:57:06 -08:00
moloch-- 5898877136 Fix --rc scripts 2026-01-16 06:56:41 -08:00
moloch-- a7d8cac565 Update generate form 2026-01-16 06:41:58 -08:00
moloch-- 0e73afca47 Update deps 2026-01-16 06:07:10 -08:00
moloch-- 432941d694 Tab complete parent commands from submenu 2026-01-16 05:58:35 -08:00
moloch-- ac521bf53c v1.6.4 2026-01-15 16:10:06 -08:00
Joe 9720c9f14f Merge pull request #2121 from BishopFox/feature/flag-completers
Improved flag completers
2026-01-15 17:09:30 -07:00
67 changed files with 1958 additions and 1296 deletions

No files matched your search

+1
View File
@@ -42,3 +42,4 @@ See the [wiki](https://sliver.sh/docs?name=Compile+from+Source).
### License - GPLv3
Sliver is licensed under [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html), some sub-components may have separate licenses. See their respective subdirectories in this project for details.
+1 -1
View File
@@ -32,7 +32,7 @@ func PrintCanaries(con *console.SliverClient, canaries []*clientpb.DNSCanary, bu
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{
"Sliver Name",
"Implant Name",
"Domain",
"Triggered",
"First Trigger",
+2 -1
View File
@@ -22,7 +22,8 @@ var (
// GenerateBeaconCmd - The main command used to generate implant binaries
func GenerateBeaconCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateBeaconForm(cmd, con, args) {
result, err := forms.GenerateBeaconForm()
compiler, _ := compilerTargets(con)
result, err := forms.GenerateBeaconForm(compiler)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
+6 -4
View File
@@ -91,7 +91,8 @@ var (
// GenerateCmd - The main command used to generate implant binaries
func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
if shouldRunGenerateForm(cmd, con, args) {
result, err := forms.GenerateForm()
compiler, _ := compilerTargets(con)
result, err := forms.GenerateForm(compiler)
if err != nil {
if errors.Is(err, forms.ErrUserAborted) {
return
@@ -118,14 +119,15 @@ func GenerateCmd(cmd *cobra.Command, con *console.SliverClient, args []string) {
} else {
_, err := externalBuild(name, config, save, con)
if err != nil {
if err == ErrNoExternalBuilder {
switch err {
case ErrNoExternalBuilder:
con.PrintErrorf("There are no external builders currently connected to the server\n")
con.PrintErrorf("See 'builders' command for more information\n")
} else if err == ErrNoValidBuilders {
case ErrNoValidBuilders:
con.PrintErrorf("There are external builders connected to the server, but none can build the target you specified\n")
con.PrintErrorf("Invalid target %s\n", fmt.Sprintf("%s:%s/%s", config.Format, config.GOOS, config.GOARCH))
con.PrintErrorf("See 'builders' command for more information\n")
} else {
default:
con.PrintErrorf("%s\n", err)
}
return
+1 -1
View File
@@ -124,7 +124,7 @@ func PrintImplantBuilds(builds *clientpb.ImplantBuilds, filters ImplantBuildFilt
config.Format,
strings.Join(c2URLs, "\n"),
fmt.Sprintf("%v", config.Debug),
fmt.Sprintf(config.HTTPC2ConfigName),
fmt.Sprintf("%s", config.HTTPC2ConfigName),
fmt.Sprintf("%v", builds.ResourceIDs[sliverName].Value),
fmt.Sprintf("%v", builds.Staged[sliverName]),
})
+39
View File
@@ -1,10 +1,14 @@
package websites
import (
"context"
"strings"
"github.com/bishopfox/sliver/client/command/flags"
"github.com/bishopfox/sliver/client/command/help"
"github.com/bishopfox/sliver/client/console"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/bishopfox/sliver/protobuf/commonpb"
"github.com/rsteube/carapace"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@@ -26,6 +30,41 @@ func Commands(con *console.SliverClient) []*cobra.Command {
})
carapace.Gen(websitesCmd).PositionalCompletion(WebsiteNameCompleter(con))
websitesListCmd := &cobra.Command{
Use: consts.ListStr,
Short: "List configured websites",
Long: help.GetHelpFor([]string{consts.WebsitesStr, consts.ListStr}),
Run: func(cmd *cobra.Command, args []string) {
ListWebsites(cmd, con, args)
},
}
websitesCmd.AddCommand(websitesListCmd)
websitesShowCmd := &cobra.Command{
Use: "show [name]",
Short: "Show contents of a website",
Long: help.GetHelpFor([]string{consts.WebsitesStr}),
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ListWebsiteContent(args[0], con)
},
}
carapace.Gen(websitesShowCmd).PositionalCompletion(WebsiteNameCompleter(con))
websitesShowCmd.ValidArgsFunction = func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
ws, err := con.Rpc.Websites(context.Background(), &commonpb.Empty{})
if err != nil {
return nil, cobra.ShellCompDirectiveError
}
out := []string{}
for _, w := range ws.Websites {
if strings.HasPrefix(w.Name, toComplete) {
out = append(out, w.Name)
}
}
return out, cobra.ShellCompDirectiveNoFileComp
}
websitesCmd.AddCommand(websitesShowCmd)
websitesRmCmd := &cobra.Command{
Use: consts.RmStr,
Short: "Remove an entire website and all of its contents",
@@ -121,9 +121,10 @@ func webAddFile(web *clientpb.WebsiteAddContent, webpath string, contentType str
}
web.Contents[webpath] = &clientpb.WebContent{
Path: webpath,
ContentType: contentType,
Content: data,
Path: webpath,
ContentType: contentType,
OriginalFile: filepath.Base(contentPath),
Content: data,
}
return nil
}
+10 -4
View File
@@ -21,7 +21,6 @@ package websites
import (
"context"
"sort"
"strings"
"github.com/bishopfox/sliver/client/command/settings"
"github.com/bishopfox/sliver/client/console"
@@ -58,11 +57,14 @@ func ListWebsites(cmd *cobra.Command, con *console.SliverClient, args []string)
con.PrintInfof("No websites\n")
return
}
con.Println("Websites")
con.Println(strings.Repeat("=", len("Websites")))
tw := table.NewWriter()
tw.SetStyle(settings.GetTableStyle(con))
tw.AppendHeader(table.Row{"Name", "Objects"})
for _, site := range websites.Websites {
con.Printf("%s%s%s - %d page(s)\n", console.Bold, site.Name, console.Normal, len(site.Contents))
tw.AppendRow(table.Row{site.Name, len(site.Contents)})
}
con.Println(tw.Render())
}
// ListWebsiteContent - List the static contents of a website.
@@ -91,6 +93,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
"Path",
"Content-type",
"Size",
"Original File",
"SHA256",
})
sortedContents := []*clientpb.WebContent{}
for _, content := range web.Contents {
@@ -104,6 +108,8 @@ func PrintWebsite(web *clientpb.Website, con *console.SliverClient) {
content.Path,
content.ContentType,
content.Size,
content.OriginalFile,
content.Sha256,
})
}
con.Println(tw.Render())
+195
View File
@@ -0,0 +1,195 @@
package console
import (
"strings"
consts "github.com/bishopfox/sliver/client/constants"
"github.com/reeflective/console"
"github.com/spf13/cobra"
)
func (con *SliverClient) allowServerRootCommands(args []string) ([]string, error) {
con.applyCommandFallback(args)
return args, nil
}
func (con *SliverClient) prepareCompletion(line []rune, cursor int) {
if con == nil || con.App == nil {
return
}
menu := con.App.ActiveMenu()
if menu == nil {
return
}
con.setMenuCommandForCompletion(menu, line, cursor)
}
func (con *SliverClient) applyCommandFallback(args []string) {
if con == nil || con.App == nil {
return
}
con.setMenuCommand(con.App.ActiveMenu(), args)
}
func (con *SliverClient) setMenuCommand(menu *console.Menu, args []string) {
if menu == nil {
return
}
if menu.Name() != consts.ImplantMenu {
if menu.Command == nil && con.serverCmds != nil {
menu.Command = con.serverCmds()
}
return
}
if menu.Command == nil {
if con.sliverCmds == nil {
return
}
menu.Command = con.sliverCmds()
}
if con.serverCmds == nil {
return
}
name := firstNonFlagArg(args)
if name == "" {
return
}
if rootHasCommand(menu.Command, name) {
return
}
serverRoot := con.serverCmds()
if serverRoot == nil || !rootHasCommand(serverRoot, name) {
return
}
menu.Command = serverRoot
}
func (con *SliverClient) setMenuCommandForCompletion(menu *console.Menu, line []rune, cursor int) {
if menu == nil || menu.Name() != consts.ImplantMenu {
return
}
if menu.Command == nil {
if con.sliverCmds == nil {
return
}
menu.Command = con.sliverCmds()
}
if con.serverCmds == nil {
return
}
token := firstTokenFromLine(line, cursor)
if token == "" {
return
}
if rootHasCommandPrefix(menu.Command, token) {
return
}
serverRoot := con.serverCmds()
if serverRoot == nil || !rootHasCommandPrefix(serverRoot, token) {
return
}
menu.Command = serverRoot
hideFilteredCommands(menu, menu.Command)
}
func firstNonFlagArg(args []string) string {
for i := 0; i < len(args); i++ {
arg := args[i]
if arg == "--" {
if i+1 < len(args) {
return args[i+1]
}
return ""
}
if strings.HasPrefix(arg, "-") {
continue
}
return arg
}
return ""
}
func firstTokenFromLine(line []rune, cursor int) string {
if cursor > len(line) {
cursor = len(line)
}
if cursor < 0 {
cursor = 0
}
pos := 0
for pos < cursor && isSpace(line[pos]) {
pos++
}
start := pos
for pos < cursor && !isSpace(line[pos]) {
pos++
}
if start == pos {
return ""
}
return string(line[start:pos])
}
func isSpace(r rune) bool {
return r == ' ' || r == '\t' || r == '\n' || r == '\r'
}
func rootHasCommand(root *cobra.Command, name string) bool {
if root == nil || name == "" {
return false
}
for _, cmd := range root.Commands() {
if cmd.Name() == name || cmd.HasAlias(name) {
return true
}
}
return false
}
func rootHasCommandPrefix(root *cobra.Command, prefix string) bool {
if root == nil || prefix == "" {
return false
}
for _, cmd := range root.Commands() {
if strings.HasPrefix(cmd.Name(), prefix) {
return true
}
for _, alias := range cmd.Aliases {
if strings.HasPrefix(alias, prefix) {
return true
}
}
}
return false
}
func hideFilteredCommands(menu *console.Menu, root *cobra.Command) {
if menu == nil || root == nil {
return
}
for _, cmd := range root.Commands() {
if cmd.Hidden {
continue
}
if filters := menu.ActiveFiltersFor(cmd); len(filters) > 0 {
cmd.Hidden = true
}
}
}
+35 -5
View File
@@ -98,6 +98,8 @@ type SliverClient struct {
Settings *assets.ClientSettings
IsServer bool
IsCLI bool
serverCmds console.Commands
sliverCmds console.Commands
jsonHandler slog.Handler
printf func(format string, args ...any) (int, error)
@@ -166,6 +168,8 @@ func NewConsole(isServer bool) *SliverClient {
func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, sliverCmds console.Commands, run bool, rcScript string) error {
con.Rpc = rpc
con.IsCLI = !run
con.serverCmds = serverCmds
con.sliverCmds = sliverCmds
// The console application needs to query the terminal for cursor positions
// when asynchronously printing logs (that is, when no command is running).
@@ -184,6 +188,15 @@ func StartClient(con *SliverClient, rpc rpcpb.SliverRPCClient, serverCmds, slive
sliver := con.App.Menu(consts.ImplantMenu)
sliver.SetCommands(sliverCmds)
con.App.PreCmdRunLineHooks = append(con.App.PreCmdRunLineHooks, con.allowServerRootCommands)
if shell := con.App.Shell(); shell != nil && shell.Completer != nil {
baseCompleter := shell.Completer
shell.Completer = func(line []rune, cursor int) readline.Completions {
con.prepareCompletion(line, cursor)
return baseCompleter(line, cursor)
}
}
// Events
go con.startEventLoop()
go core.TunnelLoop(rpc)
@@ -250,6 +263,19 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
return fmt.Errorf("parse error: %w", err)
}
if con.serverCmds == nil {
con.serverCmds = serverCmds
}
if con.sliverCmds == nil {
con.sliverCmds = sliverCmds
}
menu := con.App.ActiveMenu()
if menu != nil {
// Reset per line to avoid stale roots when rc scripts switch menus.
menu.Command = nil
}
for _, hook := range con.App.PreCmdRunLineHooks {
args, err = hook(args)
if err != nil {
@@ -261,11 +287,15 @@ func (con *SliverClient) runRCLine(serverCmds, sliverCmds console.Commands, line
return nil
}
menu := con.App.ActiveMenu()
if menu.Name() == consts.ImplantMenu {
menu.Command = sliverCmds()
} else {
menu.Command = serverCmds()
menu = con.App.ActiveMenu()
if menu == nil {
return fmt.Errorf("no active menu")
}
if menu.Command == nil {
con.setMenuCommand(menu, args)
}
if menu.Command == nil {
return fmt.Errorf("no commands available")
}
target, _, _ := menu.Command.Find(args)
+30 -34
View File
@@ -4,6 +4,7 @@ import (
"errors"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
@@ -23,62 +24,57 @@ type GenerateFormResult struct {
}
// GenerateForm prompts for core generate flags and returns the collected values.
func GenerateForm() (*GenerateFormResult, error) {
func GenerateForm(compiler *clientpb.Compiler) (*GenerateFormResult, error) {
result := &GenerateFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
commonPlatformsOnly := true
lastCommonPlatformsOnly := commonPlatformsOnly
compilerTargets := compilerTargetList(compiler)
targetBindings := &targetOptionBindings{
Common: &commonPlatformsOnly,
OS: &result.OS,
Arch: &result.Arch,
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Common Platforms Only").
Value(&commonPlatformsOnly),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
OptionsFunc(func() []huh.Option[string] {
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonOSOptions()
}
return osOptionsFromTargets(compilerTargets)
}, targetBindings).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonArchOptions(result.OS)
}
}, &result.OS).
return archOptionsFromTargets(compilerTargets, result.OS)
}, targetBindings).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonFormatOptions(result.OS)
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
}, targetBindings).
Height(3).
Value(&result.Format),
),
+30 -34
View File
@@ -5,6 +5,7 @@ import (
"strconv"
"strings"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/bishopfox/sliver/util"
"github.com/charmbracelet/huh"
)
@@ -26,62 +27,57 @@ type GenerateBeaconFormResult struct {
}
// GenerateBeaconForm prompts for generate beacon flags and returns the collected values.
func GenerateBeaconForm() (*GenerateBeaconFormResult, error) {
func GenerateBeaconForm(compiler *clientpb.Compiler) (*GenerateBeaconFormResult, error) {
result := &GenerateBeaconFormResult{
OS: "windows",
Arch: "amd64",
Format: "exe",
C2Type: "mtls",
}
commonPlatformsOnly := true
lastCommonPlatformsOnly := commonPlatformsOnly
compilerTargets := compilerTargetList(compiler)
targetBindings := &targetOptionBindings{
Common: &commonPlatformsOnly,
OS: &result.OS,
Arch: &result.Arch,
}
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Common Platforms Only").
Value(&commonPlatformsOnly),
huh.NewSelect[string]().
Title("Target operating system").
Options(
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
).
OptionsFunc(func() []huh.Option[string] {
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonOSOptions()
}
return osOptionsFromTargets(compilerTargets)
}, targetBindings).
Value(&result.OS),
huh.NewSelect[string]().
Title("CPU architecture").
OptionsFunc(func() []huh.Option[string] {
switch result.OS {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonArchOptions(result.OS)
}
}, &result.OS).
return archOptionsFromTargets(compilerTargets, result.OS)
}, targetBindings).
Height(3).
Value(&result.Arch),
huh.NewSelect[string]().
Title("Output format").
OptionsFunc(func() []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
maybeResetTargets(commonPlatformsOnly, &lastCommonPlatformsOnly, compilerTargets, &result.OS, &result.Arch, &result.Format)
if commonPlatformsOnly || len(compilerTargets) == 0 {
return commonFormatOptions(result.OS)
}
if result.OS == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}, &result.OS).
return formatOptionsFromTargets(compilerTargets, result.OS, result.Arch)
}, targetBindings).
Height(3).
Value(&result.Format),
),
+220
View File
@@ -0,0 +1,220 @@
package forms
import (
"sort"
"github.com/bishopfox/sliver/protobuf/clientpb"
"github.com/charmbracelet/huh"
)
type formatOption struct {
format clientpb.OutputFormat
label string
value string
}
type targetOptionBindings struct {
Common *bool
OS *string
Arch *string
}
var formatOptionOrder = []formatOption{
{format: clientpb.OutputFormat_EXECUTABLE, label: "Executable", value: "exe"},
{format: clientpb.OutputFormat_SHARED_LIB, label: "Shared library", value: "shared"},
{format: clientpb.OutputFormat_SERVICE, label: "Service", value: "service"},
{format: clientpb.OutputFormat_SHELLCODE, label: "Shellcode", value: "shellcode"},
}
func commonOSOptions() []huh.Option[string] {
return []huh.Option[string]{
huh.NewOption("Windows", "windows"),
huh.NewOption("Linux", "linux"),
huh.NewOption("macOS", "darwin"),
}
}
func commonArchOptions(goos string) []huh.Option[string] {
switch goos {
case "darwin":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("arm64", "arm64"),
}
case "linux":
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
default:
return []huh.Option[string]{
huh.NewOption("amd64", "amd64"),
huh.NewOption("386", "386"),
}
}
}
func commonFormatOptions(goos string) []huh.Option[string] {
options := []huh.Option[string]{
huh.NewOption("Executable", "exe"),
huh.NewOption("Shared library", "shared"),
}
if goos == "windows" {
options = append(options,
huh.NewOption("Service", "service"),
huh.NewOption("Shellcode", "shellcode"),
)
}
return options
}
func firstOptionValue(options []huh.Option[string]) (string, bool) {
if len(options) == 0 {
return "", false
}
return options[0].Value, true
}
func maybeResetTargets(commonPlatformsOnly bool, lastCommonPlatformsOnly *bool, compilerTargets []*clientpb.CompilerTarget, goos, goarch, format *string) {
if lastCommonPlatformsOnly == nil || goos == nil || goarch == nil || format == nil {
return
}
if commonPlatformsOnly == *lastCommonPlatformsOnly {
return
}
*lastCommonPlatformsOnly = commonPlatformsOnly
if commonPlatformsOnly {
return
}
osOptions := commonOSOptions()
if len(compilerTargets) > 0 {
osOptions = osOptionsFromTargets(compilerTargets)
if len(osOptions) == 0 {
osOptions = commonOSOptions()
}
}
if value, ok := firstOptionValue(osOptions); ok {
*goos = value
}
archOptions := commonArchOptions(*goos)
if len(compilerTargets) > 0 {
archOptions = archOptionsFromTargets(compilerTargets, *goos)
if len(archOptions) == 0 {
archOptions = commonArchOptions(*goos)
}
}
if value, ok := firstOptionValue(archOptions); ok {
*goarch = value
}
formatOptions := commonFormatOptions(*goos)
if len(compilerTargets) > 0 {
formatOptions = formatOptionsFromTargets(compilerTargets, *goos, *goarch)
if len(formatOptions) == 0 {
formatOptions = commonFormatOptions(*goos)
}
}
if value, ok := firstOptionValue(formatOptions); ok {
*format = value
}
}
func compilerTargetList(compiler *clientpb.Compiler) []*clientpb.CompilerTarget {
if compiler == nil {
return nil
}
targets := make([]*clientpb.CompilerTarget, 0, len(compiler.Targets)+len(compiler.UnsupportedTargets))
targets = append(targets, compiler.Targets...)
targets = append(targets, compiler.UnsupportedTargets...)
return targets
}
func osOptionsFromTargets(targets []*clientpb.CompilerTarget) []huh.Option[string] {
if len(targets) == 0 {
return nil
}
seen := map[string]struct{}{}
for _, target := range targets {
if target.GOOS == "" {
continue
}
seen[target.GOOS] = struct{}{}
}
values := make([]string, 0, len(seen))
for value := range seen {
values = append(values, value)
}
sort.Strings(values)
options := make([]huh.Option[string], 0, len(values))
for _, value := range values {
options = append(options, huh.NewOption(osLabel(value), value))
}
return options
}
func archOptionsFromTargets(targets []*clientpb.CompilerTarget, goos string) []huh.Option[string] {
if len(targets) == 0 {
return nil
}
seen := map[string]struct{}{}
for _, target := range targets {
if goos != "" && target.GOOS != goos {
continue
}
if target.GOARCH == "" {
continue
}
seen[target.GOARCH] = struct{}{}
}
values := make([]string, 0, len(seen))
for value := range seen {
values = append(values, value)
}
sort.Strings(values)
options := make([]huh.Option[string], 0, len(values))
for _, value := range values {
options = append(options, huh.NewOption(value, value))
}
return options
}
func formatOptionsFromTargets(targets []*clientpb.CompilerTarget, goos, goarch string) []huh.Option[string] {
if len(targets) == 0 {
return nil
}
available := map[clientpb.OutputFormat]struct{}{}
for _, target := range targets {
if goos != "" && target.GOOS != goos {
continue
}
if goarch != "" && target.GOARCH != goarch {
continue
}
available[target.Format] = struct{}{}
}
options := make([]huh.Option[string], 0, len(available))
for _, option := range formatOptionOrder {
if _, ok := available[option.format]; ok {
options = append(options, huh.NewOption(option.label, option.value))
}
}
return options
}
func osLabel(value string) string {
switch value {
case "windows":
return "Windows"
case "linux":
return "Linux"
case "darwin":
return "macOS"
default:
return value
}
}
+3 -3
View File
@@ -39,7 +39,7 @@ require (
github.com/moloch--/sgn v0.0.4
github.com/ncruces/go-sqlite3 v0.30.4
github.com/reeflective/console v0.1.25
github.com/reeflective/readline v1.1.3
github.com/reeflective/readline v1.1.4
github.com/rsteube/carapace v0.50.2
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.2
@@ -99,8 +99,8 @@ require (
github.com/aws/aws-sdk-go-v2/config v1.31.12 // indirect
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/carapace-sh/carapace v1.9.0 // indirect
github.com/carapace-sh/carapace-shlex v1.1.0 // indirect
github.com/carapace-sh/carapace v1.11.0 // indirect
github.com/carapace-sh/carapace-shlex v1.1.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.10 // indirect
+4
View File
@@ -93,8 +93,12 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/carapace-sh/carapace v1.9.0 h1:IuEP6YeeK4fhrH7MoOyL0I3GMhvROerkw6YGxkx8Fbw=
github.com/carapace-sh/carapace v1.9.0/go.mod h1:Zs3DpsawpFm+8fEfRVYE9EzMBYH8u7S3Aw9yAAKX0JM=
github.com/carapace-sh/carapace v1.11.0 h1:dTUFXvIrUTLd9BGLsfDt4wCgEyPl8bJeZOFDmN7fIfo=
github.com/carapace-sh/carapace v1.11.0/go.mod h1:NVkuv1s8wyFvu7d98rdgYwqGKMB6GO1MEUZp5RcWri8=
github.com/carapace-sh/carapace-shlex v1.1.0 h1:58QSJTUxghpa/rEn6+Z9UM/Hy0ipVHDUn9S1mp4i+ZE=
github.com/carapace-sh/carapace-shlex v1.1.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/carapace-sh/carapace-shlex v1.1.1 h1:ccmNeetAYZOk4IcV36youFDsXusT9uCNW2Njkw+QS+Q=
github.com/carapace-sh/carapace-shlex v1.1.1/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+26 -17
View File
@@ -166,10 +166,14 @@ func dirListHandler(data []byte, resp RPCResponse) {
dirList.Exists = true
} else {
dirList.Exists = false
dirList.Response = &commonpb.Response{Err: err.Error()}
}
dirList.Files = []*sliverpb.FileInfo{}
rootDirInfo, err := rootDirEntry.Info()
if err == nil && filter == "" {
var rootDirInfo fs.FileInfo
if rootDirEntry != nil {
rootDirInfo, err = rootDirEntry.Info()
}
if err == nil && rootDirInfo != nil && filter == "" {
// We should not get an error because we created the DirEntry object from the FileInfo object
dirList.Files = append(dirList.Files, &sliverpb.FileInfo{
Name: ".", // Cannot use the name from the FileInfo / DirEntry because that is the name of the directory
@@ -193,6 +197,7 @@ func dirListHandler(data []byte, resp RPCResponse) {
if err != nil {
// Then this is a bad filter, and it will be a bad filter
// on every iteration of the loop, so we might as well break now
dirList.Response = &commonpb.Response{Err: err.Error()}
break
}
}
@@ -249,22 +254,26 @@ func getDirList(target string) (string, fs.DirEntry, []fs.DirEntry, error) {
// {{end}}
return "", nil, nil, err
}
if rootInfo, err := os.Stat(dir); !os.IsNotExist(err) {
/*
We could place the entry for the directory itself
at the beginning of the returned slice of DirEntry
objects, but then it is not clear if that is the
root directory or a directory / file in the root
directory with the same name as the root directory.
Using WalkDir is not great here because you cannot
tell it to not be recursive, so we will be wasting
cycles telling it to skip directories and files
*/
files, err := os.ReadDir(dir)
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
rootInfo, err := os.Stat(dir)
if err != nil {
if os.IsNotExist(err) {
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
}
return dir, nil, []fs.DirEntry{}, err
}
return dir, nil, []fs.DirEntry{}, errors.New("directory does not exist")
/*
We could place the entry for the directory itself
at the beginning of the returned slice of DirEntry
objects, but then it is not clear if that is the
root directory or a directory / file in the root
directory with the same name as the root directory.
Using WalkDir is not great here because you cannot
tell it to not be recursive, so we will be wasting
cycles telling it to skip directories and files
*/
files, err := os.ReadDir(dir)
return dir, fs.FileInfoToDirEntry(rootInfo), files, err
}
func rmHandler(data []byte, resp RPCResponse) {
File diff suppressed because it is too large. Load diff
+4 -1
View File
@@ -586,6 +586,9 @@ message WebContent {
string ContentType = 4;
uint64 Size = 5 [ jstype = JS_STRING ];
string OriginalFile = 6;
string Sha256 = 7;
bytes Content = 9;
}
@@ -1316,4 +1319,4 @@ message CertificateData {
message CertificateInfo {
repeated CertificateData info = 1;
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v6.33.2
// protoc v3.15.8
// source: commonpb/common.proto
package commonpb
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v6.33.2
// protoc v3.15.8
// source: dnspb/dns.proto
package dnspb
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v6.33.2
// protoc v3.15.8
// source: rpcpb/services.proto
package rpcpb
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.26.0
// protoc v6.33.2
// protoc v3.15.8
// source: sliverpb/sliver.proto
package sliverpb
+71 -2
View File
@@ -26,6 +26,7 @@ import (
"os"
"path"
"path/filepath"
"sort"
"github.com/bishopfox/sliver/server/assets"
"github.com/bishopfox/sliver/server/log"
@@ -47,6 +48,13 @@ var (
ErrInvalidDialect = errors.New("invalid SQL Dialect")
databaseConfigLog = log.NamedLogger("config", "database")
defaultSQLitePragmas = map[string]string{
"journal_mode": "WAL", // reduce writer blocking, better concurrency
"busy_timeout": "5000", // wait for locks instead of failing fast (ms)
"synchronous": "NORMAL", // faster WAL syncs while retaining durability
"temp_store": "MEMORY", // keep temp structures off disk for quicker startup
}
)
// GetDatabaseConfigPath - File path to config.json
@@ -66,7 +74,8 @@ type DatabaseConfig struct {
Host string `json:"host"`
Port uint16 `json:"port"`
Params map[string]string `json:"params"`
Params map[string]string `json:"params"`
Pragmas map[string]string `json:"pragmas"`
MaxIdleConns int `json:"max_idle_conns"`
MaxOpenConns int `json:"max_open_conns"`
@@ -80,7 +89,7 @@ func (c *DatabaseConfig) DSN() (string, error) {
switch c.Dialect {
case Sqlite:
filePath := filepath.Join(assets.GetRootAppDir(), "sliver.db")
params := encodeParams(c.Params)
params := encodeSQLiteParams(c.Params, c.Pragmas)
return fmt.Sprintf("file:%s?%s", filePath, params), nil
case MySQL:
user := url.QueryEscape(c.Username)
@@ -112,6 +121,44 @@ func encodeParams(rawParams map[string]string) string {
return params.Encode()
}
func encodeSQLiteParams(rawParams map[string]string, pragmas map[string]string) string {
if rawParams == nil {
rawParams = map[string]string{}
}
params := url.Values{}
// Preserve any user-provided parameters first.
for key, value := range rawParams {
params.Add(key, value)
}
// Apply safer defaults only when the user has not set any custom pragma.
if _, ok := rawParams["_pragma"]; !ok {
selectedPragmas := pragmas
if len(selectedPragmas) == 0 {
selectedPragmas = defaultSQLitePragmas
}
keys := make([]string, 0, len(selectedPragmas))
for key := range selectedPragmas {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
value := selectedPragmas[key]
params.Add("_pragma", fmt.Sprintf("%s(%s)", key, value))
}
}
// Encourage shared cache to play nicer with WAL and multiple connections.
if _, ok := rawParams["cache"]; !ok {
params.Add("cache", "shared")
}
return params.Encode()
}
// Save - Save config file to disk
func (c *DatabaseConfig) Save() error {
configPath := GetDatabaseConfigPath()
@@ -161,6 +208,8 @@ func GetDatabaseConfig() *DatabaseConfig {
config.MaxOpenConns = 1
}
ensureSQLiteDefaults(config)
err := config.Save() // This updates the config with any missing fields
if err != nil {
databaseConfigLog.Errorf("Failed to save default config %s", err)
@@ -171,9 +220,29 @@ func GetDatabaseConfig() *DatabaseConfig {
func getDefaultDatabaseConfig() *DatabaseConfig {
return &DatabaseConfig{
Dialect: Sqlite,
Pragmas: defaultSQLitePragmas,
MaxIdleConns: 10,
MaxOpenConns: 100,
LogLevel: "warn",
}
}
func ensureSQLiteDefaults(c *DatabaseConfig) {
if c.Dialect != Sqlite {
return
}
if c.Params == nil {
c.Params = map[string]string{}
}
if _, ok := c.Params["_pragma"]; ok {
// User provided explicit pragmas; leave Pragmas untouched to avoid confusion.
return
}
if len(c.Pragmas) == 0 {
c.Pragmas = defaultSQLitePragmas
}
if _, ok := c.Params["cache"]; !ok {
c.Params["cache"] = "shared"
}
}
+12 -2
View File
@@ -789,8 +789,18 @@ func AddContent(pbWebContent *clientpb.WebContent, webContentDir string) (*clien
return nil, err
}
} else {
dbWebContent.ContentType = pbWebContent.ContentType
dbWebContent.Size = pbWebContent.Size
if pbWebContent.ContentType != "" {
dbWebContent.ContentType = pbWebContent.ContentType
}
if pbWebContent.Size != 0 {
dbWebContent.Size = pbWebContent.Size
}
if pbWebContent.OriginalFile != "" {
dbWebContent.OriginalFile = pbWebContent.OriginalFile
}
if pbWebContent.Sha256 != "" {
dbWebContent.Sha256 = pbWebContent.Sha256
}
dbModelWebContent := models.WebContentFromProtobuf(dbWebContent)
err = Session().Save(&dbModelWebContent).Error
+20 -14
View File
@@ -67,9 +67,11 @@ type WebContent struct {
ID uuid.UUID `gorm:"primaryKey;->;<-:create;type:uuid;"`
WebsiteID uuid.UUID `gorm:"type:uuid;"`
Path string `gorm:"primaryKey"`
Size uint64
ContentType string
Path string `gorm:"primaryKey"`
Size uint64
ContentType string
OriginalFile string
Sha256 string
}
// BeforeCreate - GORM hook to automatically set values
@@ -81,12 +83,14 @@ func (wc *WebContent) BeforeCreate(tx *gorm.DB) (err error) {
// ToProtobuf - Converts to protobuf object
func (wc *WebContent) ToProtobuf(content *[]byte) *clientpb.WebContent {
return &clientpb.WebContent{
ID: wc.ID.String(),
WebsiteID: wc.WebsiteID.String(),
Path: wc.Path,
Size: uint64(wc.Size),
ContentType: wc.ContentType,
Content: *content,
ID: wc.ID.String(),
WebsiteID: wc.WebsiteID.String(),
Path: wc.Path,
Size: uint64(wc.Size),
ContentType: wc.ContentType,
OriginalFile: wc.OriginalFile,
Sha256: wc.Sha256,
Content: *content,
}
}
@@ -95,10 +99,12 @@ func WebContentFromProtobuf(pbWebContent *clientpb.WebContent) WebContent {
websiteUUID, _ := uuid.FromString(pbWebContent.WebsiteID)
return WebContent{
ID: siteUUID,
WebsiteID: websiteUUID,
Path: pbWebContent.Path,
Size: pbWebContent.Size,
ContentType: pbWebContent.ContentType,
ID: siteUUID,
WebsiteID: websiteUUID,
Path: pbWebContent.Path,
Size: pbWebContent.Size,
ContentType: pbWebContent.ContentType,
OriginalFile: pbWebContent.OriginalFile,
Sha256: pbWebContent.Sha256,
}
}
+21 -3
View File
@@ -19,6 +19,8 @@ package website
*/
import (
"crypto/sha256"
"encoding/hex"
"net/url"
"os"
"path/filepath"
@@ -87,14 +89,30 @@ func AddContent(name string, pbWebContent *clientpb.WebContent) error {
pbWebContent.WebsiteID = website.ID
}
if pbWebContent.Size == 0 && len(pbWebContent.Content) > 0 {
pbWebContent.Size = uint64(len(pbWebContent.Content))
}
if pbWebContent.OriginalFile == "" {
pbWebContent.OriginalFile = filepath.Base(pbWebContent.Path)
}
if len(pbWebContent.Content) > 0 {
sha := sha256.Sum256(pbWebContent.Content)
pbWebContent.Sha256 = hex.EncodeToString(sha[:])
}
webContent, err := db.AddContent(pbWebContent, webContentDir)
if err != nil {
return err
}
// Write content to disk
webContentPath := filepath.Join(webContentDir, webContent.ID)
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
// Write content to disk when provided (metadata-only updates skip disk writes)
if len(pbWebContent.Content) > 0 {
webContentPath := filepath.Join(webContentDir, webContent.ID)
return os.WriteFile(webContentPath, pbWebContent.Content, 0600)
}
return nil
}
// RemoveContent - Remove website content for a path
+5 -1
View File
@@ -111,8 +111,12 @@ func (t TokenSlice) WordbreakPrefix() string {
break
}
if token.Type == WORDBREAK_TOKEN {
if !found && token.Type == WORDBREAK_TOKEN {
found = true
if token.Value == "@" {
// Seems although `@` is a wordbreak, it weirdly is not part of the prefix.
continue
}
}
if found {
+1 -1
View File
@@ -2,7 +2,7 @@ package shlex
import "encoding/json"
const BASH_WORDBREAKS = " \t\r\n" + `"'><=;|&(:`
const BASH_WORDBREAKS = " \t\r\n" + `"'@><=;|&(:`
type WordbreakType int
+27 -3
View File
@@ -437,6 +437,15 @@ func (a Action) Timeout(d time.Duration, alternative Action) Action {
})
}
// Unique ensures the Action only contains unique values.
func (a Action) Unique() Action {
return ActionCallback(func(c Context) Action {
invoked := a.Invoke(c)
invoked.action.rawValues = invoked.action.rawValues.Unique()
return invoked.ToA()
})
}
// UniqueList wraps the Action in an ActionMultiParts with given divider.
func (a Action) UniqueList(divider string) Action {
return ActionMultiParts(divider, func(c Context) Action {
@@ -513,9 +522,14 @@ func (a Action) UidF(f func(s string, uc uid.Context) (*url.URL, error)) Action
}
// Usage sets the usage.
func (a Action) Usage(usage string, args ...interface{}) Action {
func (a Action) Usage(usage string, args ...any) Action {
return a.UsageF(func() string {
return fmt.Sprintf(usage, args...)
if len(args) == 0 {
return usage
}
return func(u string, a ...any) string { // avoids failed inferred printf check: non-constant format string
return fmt.Sprintf(u, a...)
}(usage, args...)
})
}
@@ -548,6 +562,9 @@ func (a Action) Query(scheme, host, path string, opts ...string) Action {
values.Set(opts[i], opts[i+1])
}
}
if c.Value != "" {
values.Set("C_VALUE", c.Value)
}
query.RawQuery = values.Encode()
}
a.meta.Queries.Add(query.String())
@@ -558,10 +575,17 @@ func (a Action) Query(scheme, host, path string, opts ...string) Action {
// QueryF TODO experimental
func (a Action) QueryF(f func(s string, uc uid.Context) (*url.URL, error)) Action { // TODO remove the string
return ActionCallback(func(c Context) Action {
query, err := f(c.Value, c)
query, err := f("", c) // TODO string parameter from uid isn't really needed here
if err != nil {
return ActionMessage(err.Error())
}
if c.Value != "" {
values := query.Query()
values.Set("C_VALUE", c.Value)
query.RawQuery = values.Encode()
}
a.meta.Queries.Add(query.String())
return a
})
+3 -4
View File
@@ -2,6 +2,7 @@
package carapace
import (
"maps"
"os"
"github.com/carapace-sh/carapace/internal/shell"
@@ -79,9 +80,7 @@ func (c Carapace) FlagCompletion(actions ActionMap) {
if e.flag == nil {
e.flag = actions
} else {
for name, action := range actions {
e.flag[name] = action
}
maps.Copy(e.flag, actions)
}
}
@@ -126,7 +125,7 @@ func IsCallback() bool {
// func TestCarapace(t *testing.T) {
// carapace.Test(t)
// }
func Test(t interface{ Error(args ...interface{}) }) {
func Test(t interface{ Error(args ...any) }) {
for _, e := range storage.check() {
t.Error(e)
}
+20 -32
View File
@@ -8,6 +8,7 @@ import (
"net/url"
"os"
"os/exec"
"slices"
"strings"
"github.com/carapace-sh/carapace/internal/common"
@@ -135,6 +136,10 @@ func ActionExecute(cmd *cobra.Command) Action {
// ActionDirectories completes directories.
func ActionDirectories() Action {
return ActionCallback(func(c Context) Action {
cDir, err := c.Abs(c.Dir)
if err != nil {
return ActionMessage(err.Error())
}
return actionPath([]string{""}, true).
MultiParts("/").
StyleF(style.ForPath).
@@ -145,19 +150,17 @@ func ActionDirectories() Action {
}
return url.Parse("file://" + uid.PathEscape(abs))
}).
QueryF(func(s string, uc uid.Context) (*url.URL, error) {
abs, err := c.Abs(c.Dir)
if err != nil {
return nil, err
}
return url.Parse(fmt.Sprintf("file://%v?directories=true", uid.PathEscape(abs)))
})
Query("file", "directories", "", "C_DIR", cDir)
}).Tag("directories")
}
// ActionFiles completes files with optional suffix filtering.
func ActionFiles(suffix ...string) Action {
return ActionCallback(func(c Context) Action {
cDir, err := c.Abs(c.Dir)
if err != nil {
return ActionMessage(err.Error())
}
return actionPath(suffix, false).
MultiParts("/").
StyleF(style.ForPath).
@@ -168,23 +171,9 @@ func ActionFiles(suffix ...string) Action {
}
return url.Parse("file://" + uid.PathEscape(abs))
}).
QueryF(func(s string, uc uid.Context) (*url.URL, error) {
abs, err := c.Abs(c.Dir)
if err != nil {
return nil, err
}
query := &url.URL{
Scheme: "file",
Path: abs,
}
if len(suffix) > 0 {
values := query.Query()
values.Set("suffix", strings.Join(suffix, ","))
query.RawQuery = values.Encode()
}
return query, nil
})
// Query("file", "", c.Dir, "suffix", strings.Join(suffix, ","))
Query("file", "files", "",
"C_DIR", cDir,
"suffix", strings.Join(suffix, ","))
}).Tag("files")
}
@@ -247,7 +236,7 @@ func ActionStyledValuesDescribed(values ...string) Action {
}
// ActionMessage displays a help messages in places where no completions can be generated.
func ActionMessage(msg string, args ...interface{}) Action {
func ActionMessage(msg string, args ...any) Action {
return ActionCallback(func(c Context) Action {
if len(args) > 0 {
msg = fmt.Sprintf(msg, args...)
@@ -477,14 +466,15 @@ func ActionExecutables(dirs ...string) Action {
}
manDescriptions := man.Descriptions(c.Value) // TODO allow additional descriptions to be registered somewhere for carapace-bin (key, value,...)
batch := Batch()
for i := len(dirs) - 1; i >= 0; i-- {
batch = append(batch, actionDirectoryExecutables(dirs[i], c.Value, manDescriptions))
for _, dir := range dirs {
batch = append(batch, actionDirectoryExecutables(dir, c.Value, manDescriptions))
}
return batch.ToA().
UidF(func(s string, uc uid.Context) (*url.URL, error) {
return &url.URL{Scheme: "cmd", Host: uid.PathEscape(s)}, nil
}).
Query("cmd", "", "", "directories", strings.Join(dirs, ", "))
Query("cmd", "", "", "directories", strings.Join(dirs, ", ")).
Unique()
}).Tag("executables")
}
@@ -553,10 +543,8 @@ func ActionCommands(cmd *cobra.Command) Action {
return ActionCallback(func(c Context) Action {
if len(c.Args) > 0 {
for _, subCommand := range cmd.Commands() {
for _, name := range append(subCommand.Aliases, subCommand.Name()) {
if name == c.Args[0] { // cmd.Find is too lenient
return ActionCommands(subCommand).Shift(1)
}
if slices.Contains(append(subCommand.Aliases, subCommand.Name()), c.Args[0]) { // cmd.Find is too lenient
return ActionCommands(subCommand).Shift(1)
}
}
return ActionMessage("unknown subcommand %#v for %#v", c.Args[0], cmd.Name())
+26
View File
@@ -1,6 +1,8 @@
package carapace
import (
"fmt"
"github.com/carapace-sh/carapace/internal/common"
"github.com/carapace-sh/carapace/pkg/style"
)
@@ -42,3 +44,27 @@ func Diff(original, new Action) Action {
return mergedBatch.ToA()
})
}
// TODO experimental - needs different signature
// TODO return patch format
func DiffPatch(original, new Action, c Context) []string {
diff := Diff(original, new).Invoke(c)
patch := make([]string, 0)
for _, v := range diff.action.rawValues {
s := v.Value
if v.Description != "" {
s += fmt.Sprintf(" (%v)", v.Description)
}
switch v.Style {
case style.Red:
s = "- " + s
case style.Green:
s = "+ " + s
default:
s = " " + s
}
patch = append(patch, s)
}
return patch
}
+1 -1
View File
@@ -1,4 +1,4 @@
go 1.19
go 1.24
use (
.
+2 -1
View File
@@ -1,3 +1,4 @@
github.com/carapace-sh/carapace-shlex v1.0.0/go.mod h1:lJ4ZsdxytE0wHJ8Ta9S7Qq0XpjgjU0mdfCqiI2FHx7M=
github.com/carapace-sh/carapace-pflag v1.1.0 h1:uB33wch8ADjP878NadQnbZ5vyROcj/Ac+wUQKMcIrQM=
github.com/carapace-sh/carapace-pflag v1.1.0/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+1 -1
View File
@@ -20,7 +20,7 @@ func (m Mock) WorkDir() string {
type t interface {
Name() string
Fatal(...interface{})
Fatal(...any)
}
func NewMock(t t) *Mock {
+1 -1
View File
@@ -11,7 +11,7 @@ import (
// FromInvokedAction provides access to RawValues within an InvokedAction.
// It is intended for testing purposes in Sandbox (circumventing dependency issues).
var FromInvokedAction func(action interface{}) (Meta, RawValues)
var FromInvokedAction func(action any) (Meta, RawValues)
// RawValue represents a completion candidate.
type RawValue struct {
+2 -2
View File
@@ -12,7 +12,7 @@ import (
"github.com/carapace-sh/carapace/pkg/xdg"
)
type configMap map[string]interface{}
type configMap map[string]any
func (c configMap) Keys() []string {
keys := make([]string, 0)
@@ -52,7 +52,7 @@ var config = struct {
Styles: make(configMap),
}
func RegisterStyle(name string, i interface{}) {
func RegisterStyle(name string, i any) {
config.Styles[name] = i
}
+5 -1
View File
@@ -81,7 +81,11 @@ func CoverDir() string {
return os.Getenv(CARAPACE_COVERDIR) // custom env for GOCOVERDIR so that it works together with `-coverprofile`
}
func isGoRun() bool { return strings.HasPrefix(os.Args[0], os.TempDir()+"/go-build") }
func isGoRun() bool {
// go 1.24+: /home/rsteube/.cache/go-build/a7/a7883331b606dc3250005f1abdf4d17dea05c9eccf4ece0df5518311a118d211-d/example
// go 1.23-: /tmp/go-build1035720725/b001/exe/example
return strings.Contains(os.Args[0], "/go-build")
}
func Match() string { // see match.Match
return os.Getenv(CARAPACE_MATCH)
+2 -2
View File
@@ -20,8 +20,8 @@ func Descriptions(s string) (descriptions map[string]string) {
}
r := regexp.MustCompile(`^(?P<name>[^ ]+) [^-]+- (?P<description>.*)$`)
lines := strings.Split(string(output), "\n")
for _, line := range lines {
lines := strings.SplitSeq(string(output), "\n")
for line := range lines {
if matches := r.FindStringSubmatch(line); len(matches) > 2 {
descriptions[matches[1]] = matches[2]
}
+1 -1
View File
@@ -36,7 +36,7 @@ func (f FlagSet) IsShorthandSeries(arg string) bool {
func (f FlagSet) IsMutuallyExclusive(flag *pflag.Flag) bool {
if groups, ok := flag.Annotations["cobra_annotation_mutually_exclusive"]; ok {
for _, group := range groups {
for _, name := range strings.Split(group, " ") {
for name := range strings.SplitSeq(group, " ") {
if other := f.Lookup(name); other != nil && other.Changed && other != flag {
return true
}
+9 -9
View File
@@ -11,7 +11,7 @@ import (
// Snippet creates the bash completion script.
func Snippet(cmd *cobra.Command) string {
result := fmt.Sprintf(`#!/bin/bash
_%v_completion() {
_%[1]v_completion() {
export COMP_LINE
export COMP_POINT
export COMP_TYPE
@@ -19,12 +19,12 @@ _%v_completion() {
local nospace data compline="${COMP_LINE:0:${COMP_POINT}}"
if echo ${compline}"''" | xargs echo 2>/dev/null > /dev/null; then
data=$(echo ${compline}"''" | xargs %v _carapace bash)
elif echo ${compline} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
data=$(echo ${compline} | sed "s/\$/'/" | xargs %v _carapace bash)
else
data=$(echo ${compline} | sed 's/$/"/' | xargs %v _carapace bash)
data=$(echo "${compline}''" | xargs %[2]v _carapace bash 2>/dev/null)
if [ $? -eq 1 ]; then
data=$(echo "${compline}'" | xargs %[2]v _carapace bash 2>/dev/null)
if [ $? -eq 1 ]; then
data=$(echo "${compline}\"" | xargs %[2]v _carapace bash 2>/dev/null)
fi
fi
IFS=$'\001' read -r -d '' nospace data <<<"${data}"
@@ -36,8 +36,8 @@ _%v_completion() {
[[ "${COMPREPLY[*]}" == "" ]] && COMPREPLY=() # fix for mapfile creating a non-empty array from empty command output
}
complete -o noquote -F _%v_completion %v
`, cmd.Name(), uid.Executable(), uid.Executable(), uid.Executable(), cmd.Name(), cmd.Name())
complete -o noquote -F _%[1]v_completion %[1]v
`, cmd.Name(), uid.Executable())
return result
}
+32 -19
View File
@@ -8,28 +8,41 @@ import (
)
func Snippet(cmd *cobra.Command) string {
result := fmt.Sprintf(`local function %v_completion(word, word_index, line_state, match_builder)
local compline = string.sub(line_state:getline(), 1, line_state:getcursor())
result := fmt.Sprintf(`local function %[1]s_completion(word, word_index, line_state, match_builder)
match_builder:setnosort()
match_builder:setvolatile()
os.setenv('CARAPACE_COMPLINE', line_state:getline():sub(1, line_state:getcursor()))
local output = io.popen("env CARAPACE_COMPLINE=" .. string.format("%%q", compline) .. " %v _carapace cmd-clink \"\""):read("*a")
for line in string.gmatch(output, '[^\r\n]+') do
local matches = {}
for m in string.gmatch(line, '[^\t]+') do
table.insert(matches, m)
local file, pclose = io.popenyield('%[2]s _carapace cmd-clink %[1]s')
if not file then
return false
end
match_builder:addmatch({
match = matches[1],
display = matches[2],
description = matches[3],
type = "word",
appendchar = matches[4],
suppressappend = false
})
end
return true
for line in file:lines() do
local matches = string.explode(line, '\t')
if matches[1] then
match_builder:addmatch({
match = matches[1],
display = matches[2],
description = matches[3],
type = 'word',
appendchar = matches[4] or ''
})
end
end
if pclose then
pclose()
else
file:close()
end
return not match_builder:isempty()
end
clink.argmatcher("%v"):addarg({nowordbreakchars="'&backprime;+;,", %v_completion}):loop(1)
`, cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name())
clink.argmatcher(50, '%[1]s', '%[1]s.exe'):addarg({nowordbreakchars="'`+"`"+`=+;,", %[1]s_completion}):loop(1)
`, cmd.Name(), uid.Executable())
return result
}
+11 -15
View File
@@ -10,23 +10,19 @@ import (
// Snippet creates the fish completion script.
func Snippet(cmd *cobra.Command) string {
return fmt.Sprintf(`function _%v_quote_suffix
if not commandline -cp | xargs echo 2>/dev/null >/dev/null
if commandline -cp | sed 's/$/"/'| xargs echo 2>/dev/null >/dev/null
echo '"'
else if commandline -cp | sed "s/\$/'/"| xargs echo 2>/dev/null >/dev/null
echo "'"
return fmt.Sprintf(`function _%[1]v_completion
set --local data
IFS='' set data (echo (commandline -cp)'' | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
if [ $status -eq 1 ]
IFS='' set data (echo (commandline -cp)"'" | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
if [ $status -eq 1 ]
IFS='' set data (echo (commandline -cp)'"' | sed "s/ \$/ ''/" | xargs %[2]v _carapace fish 2>/dev/null)
end
else
echo ""
end
echo $data
end
function _%v_callback
commandline -cp | sed "s/\$/"(_%v_quote_suffix)"/" | sed "s/ \$/ ''/" | xargs %v _carapace fish
end
complete -e '%v'
complete -c '%v' -f -a '(_%v_callback)' -r
`, cmd.Name(), cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
complete -e '%[1]v'
complete -c '%[1]v' -f -a '(_%[1]v_completion)' -r
`, cmd.Name(), uid.Executable())
}
@@ -36,7 +36,7 @@ func (nd *namedDirectories) Replace(s string) string {
func init() {
if hashDirs := env.Hashdirs(); hashDirs != "" {
for _, line := range strings.Split(hashDirs, "\n") {
for line := range strings.SplitSeq(hashDirs, "\n") {
if splitted := strings.SplitN(line, "=", 2); len(splitted) == 2 {
NamedDirectories[splitted[0]] = splitted[1]
}
+12 -11
View File
@@ -11,17 +11,18 @@ import (
// Snippet creates the zsh completion script
func Snippet(cmd *cobra.Command) string {
return fmt.Sprintf(`#compdef %v
function _%v_completion {
local words=${words[@]:0:$CURRENT}
function _%[1]v_completion {
local compline=${words[@]:0:$CURRENT}
local IFS=$'\n'
local lines
# shellcheck disable=SC2086,SC2154,SC2155
if echo ${words}"''" | xargs echo 2>/dev/null > /dev/null; then
local lines="$(echo ${words}"''" | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh )"
elif echo ${words} | sed "s/\$/'/" | xargs echo 2>/dev/null > /dev/null; then
local lines="$(echo ${words} | sed "s/\$/'/" | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh)"
else
local lines="$(echo ${words} | sed 's/$/"/' | CARAPACE_COMPLINE="${words}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %v _carapace zsh)"
lines="$(echo "${compline}''" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
if [ $? -eq 1 ]; then
lines="$(echo "${compline}'" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
if [ $? -eq 1 ]; then
lines="$(echo "${compline}\"" | CARAPACE_COMPLINE="${compline}" CARAPACE_ZSH_HASH_DIRS="$(hash -d)" xargs %[2]v _carapace zsh 2>/dev/null)"
fi
fi
local zstyle message data
@@ -41,7 +42,7 @@ function _%v_completion {
[[ ${#valuesArr[@]} -gt 1 ]] && _describe -t "${tag}" "${tag}" displaysArr valuesArr -Q -S ''
done <<<"${data}"
}
compquote '' 2>/dev/null && _%v_completion
compdef _%v_completion %v
`, cmd.Name(), cmd.Name(), uid.Executable(), uid.Executable(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
compquote '' 2>/dev/null && _%[1]v_completion
compdef _%[1]v_completion %[1]v
`, cmd.Name(), uid.Executable())
}
+2 -2
View File
@@ -172,7 +172,7 @@ func (ia InvokedAction) ToMultiPartsA(dividers ...string) Action {
})
}
func (ia InvokedAction) Query(q string) InvokedAction {
func (ia InvokedAction) QueryS(q string) InvokedAction {
ia.action.meta.Queries.Add(q)
return ia
}
@@ -182,7 +182,7 @@ func (ia InvokedAction) value(shell string, value string) string {
}
func init() {
common.FromInvokedAction = func(i interface{}) (common.Meta, common.RawValues) {
common.FromInvokedAction = func(i any) (common.Meta, common.RawValues) {
if invoked, ok := i.(InvokedAction); ok {
return invoked.action.meta, invoked.action.rawValues
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
// func init() {
// Register("carapace", &Carapace)
// }
func Register(name string, i interface{}) { config.RegisterStyle(name, i) }
func Register(name string, i any) { config.RegisterStyle(name, i) }
// Set a style
//
+1 -1
View File
@@ -68,7 +68,7 @@ func SGR(s string) string { return Parse(s).SGR() }
func Parse(s string) ui.Style {
stylings := make([]ui.Styling, 0)
for _, word := range strings.Split(s, " ") {
for word := range strings.SplitSeq(s, " ") {
if styling := ui.ParseStyling(word); styling != nil {
stylings = append(stylings, styling)
}
+1 -1
View File
@@ -45,7 +45,7 @@ func ConfigDirs() (dirs []string, err error) {
if v, ok := os.LookupEnv("XDG_CONFIG_DIRS"); ok {
xdgDirs := make([]string, 0)
for _, dir := range strings.Split(v, string(os.PathSeparator)) {
for dir := range strings.SplitSeq(v, string(os.PathSeparator)) {
if filepath.IsAbs(dir) {
xdgDirs = append(xdgDirs, dir)
}
@@ -76,13 +76,10 @@ func toSubstr(s string, args ...string) string {
if pos < 0 {
// if pos is negative (counts from the end) add it
// to length to get first character offset
pos = len(s) + pos
// if negative offset exceeds the length of the string
// start from 0
if pos < 0 {
pos = 0
}
pos = max(
// if negative offset exceeds the length of the string
// start from 0
len(s)+pos, 0)
}
if len(args) == 1 {
@@ -122,9 +119,9 @@ func replaceAll(s string, args ...string) string {
case 0:
return s
case 1:
return strings.Replace(s, args[0], "", -1)
return strings.ReplaceAll(s, args[0], "")
default:
return strings.Replace(s, args[0], args[1], -1)
return strings.ReplaceAll(s, args[0], args[1])
}
}
@@ -159,8 +156,8 @@ func replaceSuffix(s string, args ...string) string {
if len(args) != 2 {
return s
}
if strings.HasSuffix(s, args[0]) {
s = strings.TrimSuffix(s, args[0])
if before, ok := strings.CutSuffix(s, args[0]); ok {
s = before
s = s + args[1]
}
return s
@@ -235,10 +235,6 @@ func acceptOneHash(r rune, i int) bool {
return r == '#' && i == 1
}
func acceptNone(r rune, i int) bool {
return false
}
func acceptNotClosing(r rune, i int) bool {
return r != '}'
}
@@ -3,7 +3,7 @@ package envsubst
import (
"bytes"
"io"
"io/ioutil"
"os"
"github.com/carapace-sh/carapace/third_party/github.com/drone/envsubst/parse"
)
@@ -38,7 +38,7 @@ func Parse(s string) (t *Template, err error) {
// ParseFile creates a new shell format template and parses the template
// definition from the named file.
func ParseFile(path string) (*Template, error) {
b, err := ioutil.ReadFile(path)
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
@@ -79,14 +79,14 @@ var featureForName = map[string]feature{
// fields are silently ignored.
func parseLsColor(s string) *colorist {
lc := &colorist{make(map[feature]string), make(map[string]string)}
for _, spec := range strings.Split(s, ":") {
for spec := range strings.SplitSeq(s, ":") {
words := strings.Split(spec, "=")
if len(words) != 2 {
continue
}
key, value := words[0], words[1]
filterValues := []string{}
for _, splitValue := range strings.Split(value, ";") {
for splitValue := range strings.SplitSeq(value, ";") {
if strings.Count(splitValue, "0") == len(splitValue) {
continue
}
@@ -1,11 +1,10 @@
//go:build !solaris
// +build !solaris
package lscolors
import "os"
func isDoor(info os.FileInfo) bool {
func isDoor(_ os.FileInfo) bool {
// Doors are only supported on Solaris.
return false
}
@@ -1,5 +1,4 @@
//go:build !windows && !plan9
// +build !windows,!plan9
package lscolors
@@ -2,7 +2,7 @@ package lscolors
import "os"
func isMultiHardlink(info os.FileInfo) bool {
func isMultiHardlink(_ os.FileInfo) bool {
// Windows supports hardlinks, but it is not exposed directly. We omit the
// implementation for now.
// TODO: Maybe implement it?
@@ -5,8 +5,6 @@ import (
"strings"
)
const sgrPrefix = "\033["
var sgrStyling = map[int]Styling{
0: Reset,
1: Bold,
@@ -73,7 +71,7 @@ func StylingFromSGR(s string) Styling {
func getSGRCodes(s string) []int {
var codes []int
for _, part := range strings.Split(s, ";") {
for part := range strings.SplitSeq(s, ";") {
if part == "" {
codes = append(codes, 0)
} else {
@@ -44,8 +44,8 @@ func (s Style) SGR() string {
// MergeFromOptions merges all recognized values from a map to the current
// Style.
func (s *Style) MergeFromOptions(options map[string]interface{}) error {
assignColor := func(val interface{}, colorField *Color) string {
func (s *Style) MergeFromOptions(options map[string]any) error {
assignColor := func(val any, colorField *Color) string {
if val == "default" {
*colorField = nil
return ""
@@ -58,7 +58,7 @@ func (s *Style) MergeFromOptions(options map[string]interface{}) error {
}
return "valid color string"
}
assignBool := func(val interface{}, attrField *bool) string {
assignBool := func(val any, attrField *bool) string {
if b, ok := val.(bool); ok {
*attrField = b
} else {
@@ -144,7 +144,7 @@ func ParseStyling(s string) Styling {
return parseOneStyling(s)
}
var joint jointStyling
for _, subs := range strings.Split(s, " ") {
for subs := range strings.SplitSeq(s, " ") {
parsed := parseOneStyling(subs)
if parsed == nil {
return nil
@@ -1,5 +1,4 @@
//go:build darwin
// +build darwin
package ps
@@ -1,5 +1,4 @@
//go:build freebsd
// +build freebsd
package ps
@@ -188,7 +187,7 @@ func processes() ([]Process, error) {
count := int(length / uint64(procinfo_len))
// parse buf to procs
for i := 0; i < count; i++ {
for i := range count {
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
k, err := parse_kinfo_proc(b)
if err != nil {
@@ -1,5 +1,4 @@
//go:build linux
// +build linux
package ps
@@ -1,5 +1,4 @@
//go:build solaris
// +build solaris
package ps
@@ -88,7 +87,7 @@ func (p *UnixProcess) Refresh() error {
}
func toString(array []byte, len int) string {
for i := 0; i < len; i++ {
for i := range len {
if array[i] == 0 {
return string(array[:i])
}
@@ -1,5 +1,4 @@
//go:build linux || solaris
// +build linux solaris
package ps
@@ -1,5 +1,4 @@
//go:build windows
// +build windows
package ps
-3
View File
@@ -60,9 +60,6 @@ func (c *Console) complete(input []rune, pos int) readline.Completions {
comps = comps.Usage("%s", completions.Usage)
comps = c.justifyCommandComps(comps)
// Display extension and alias arguments as a list (one per line)
comps = comps.DisplayList("extension arguments", "alias arguments")
// If any errors arose from the completion call itself.
if err != nil {
comps = readline.CompleteMessage("failed to load config: " + err.Error())
+4 -4
View File
@@ -113,8 +113,8 @@ github.com/bahlo/generic-list-go
# github.com/buger/jsonparser v1.1.1
## explicit; go 1.13
github.com/buger/jsonparser
# github.com/carapace-sh/carapace v1.9.0
## explicit; go 1.16
# github.com/carapace-sh/carapace v1.11.0
## explicit; go 1.24
github.com/carapace-sh/carapace
github.com/carapace-sh/carapace/internal/cache
github.com/carapace-sh/carapace/internal/common
@@ -157,7 +157,7 @@ github.com/carapace-sh/carapace/third_party/github.com/elves/elvish/pkg/cli/lsco
github.com/carapace-sh/carapace/third_party/github.com/elves/elvish/pkg/ui
github.com/carapace-sh/carapace/third_party/github.com/mitchellh/go-ps
github.com/carapace-sh/carapace/third_party/golang.org/x/sys/execabs
# github.com/carapace-sh/carapace-shlex v1.1.0
# github.com/carapace-sh/carapace-shlex v1.1.1
## explicit; go 1.15
github.com/carapace-sh/carapace-shlex
# github.com/catppuccin/go v0.3.0
@@ -589,7 +589,7 @@ github.com/reeflective/console/internal/completion
github.com/reeflective/console/internal/line
github.com/reeflective/console/internal/strutil
github.com/reeflective/console/internal/ui
# github.com/reeflective/readline v1.1.3 => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
# github.com/reeflective/readline v1.1.4 => github.com/moloch--/readline v0.0.0-20260104185423-d3ac1d6c561d
## explicit; go 1.23.6
github.com/reeflective/readline
github.com/reeflective/readline/inputrc