Files
Paymahn Moghadasian 6a972a1f05 feat: add slack-cli, a no-daemon CLI over the slack-mcp-server engine
Turn the forked slack-mcp-server into a CLI so running many agents no
longer means one resident MCP process each. Every command is a
short-lived process that reads the shared on-disk cache.

- rename module to github.com/paymog/slack-cli (go install/homebrew/ldflags)
- internal/toolcall: invoke the upstream tool handlers in-process; the only
  mcp-go coupling lives here, so pkg/handler and pkg/provider are reused
  byte-for-byte (clean upstream merges, fork-and-extend)
- internal/{cli,cmds,config,credstore,runtime,output}: cobra command tree,
  keyring-backed credential profiles, provider bootstrap, result printing
- 21 tools as subcommands (channels, conversations, users, usergroups,
  saved, reactions, attachments, cache); write tools keep their env gating
- goreleaser + homebrew release workflow; ships a skills/slack-cli skill
- unit tests for config/credstore/toolcall; MCP server still builds

The MCP server (cmd/slack-mcp-server) is kept intact.
2026-06-26 11:04:05 -05:00

92 lines
2.7 KiB
Go

package cmds
import (
"github.com/paymog/slack-cli/internal/config"
"github.com/paymog/slack-cli/internal/runtime"
"github.com/paymog/slack-cli/pkg/handler"
"github.com/spf13/cobra"
)
func newChannelsCommand(cfg *config.Config) *cobra.Command {
cmd := &cobra.Command{
Use: "channels",
Aliases: []string{"channel"},
Short: "List Slack channels",
}
cmd.AddCommand(channelsListCommand(cfg), channelsMeCommand(cfg))
return cmd
}
func channelsListCommand(cfg *config.Config) *cobra.Command {
var (
types string
sortBy string
cursor string
query string
queryTargets string
limit int
)
cmd := &cobra.Command{
Use: "list",
Short: "List channels (public/private/im/mpim), as CSV",
RunE: func(cmd *cobra.Command, _ []string) error {
p, logger, err := runtime.PrepareRead(cmd.Context(), cfg)
if err != nil {
return err
}
h := handler.NewChannelsHandler(p, logger)
args := map[string]any{
"channel_types": types,
"sort": sortBy,
}
putStr(args, "cursor", cursor)
if limit != 0 {
args["limit"] = limit
}
if query != "" {
args["query"] = query
args["query_targets"] = queryTargets
}
return emit(cmd, cfg, "channels_list", h.ChannelsHandler, args)
},
}
f := cmd.Flags()
f.StringVar(&types, "types", "public_channel,private_channel", "comma-separated channel types: public_channel,private_channel,im,mpim")
f.StringVar(&sortBy, "sort", "popularity", "sort order (popularity)")
f.StringVar(&cursor, "cursor", "", "pagination cursor from a previous page")
f.IntVar(&limit, "limit", 0, "max channels to return (default 100, max 999)")
f.StringVar(&query, "query", "", "filter channels by keyword")
f.StringVar(&queryTargets, "query-targets", "name", "comma-separated query targets: name,topic,purpose")
return cmd
}
func channelsMeCommand(cfg *config.Config) *cobra.Command {
var (
types string
cursor string
limit int
)
cmd := &cobra.Command{
Use: "me",
Short: "List channels the authenticated user belongs to, as CSV",
RunE: func(cmd *cobra.Command, _ []string) error {
p, logger, err := runtime.PrepareRead(cmd.Context(), cfg)
if err != nil {
return err
}
h := handler.NewChannelsHandler(p, logger)
args := map[string]any{"channel_types": types}
putStr(args, "cursor", cursor)
if limit != 0 {
args["limit"] = limit
}
return emit(cmd, cfg, "channels_me", h.ChannelsMeHandler, args)
},
}
f := cmd.Flags()
f.StringVar(&types, "types", "public_channel,private_channel", "comma-separated channel types")
f.StringVar(&cursor, "cursor", "", "pagination cursor from a previous page")
f.IntVar(&limit, "limit", 0, "max channels to return (default 100, max 999)")
return cmd
}