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

43 lines
1.3 KiB
Go

// Package cmds wires Slack tool handlers as cobra subcommands. Each command
// resolves credentials, builds the reused ApiProvider, invokes the upstream
// handler in-process via internal/toolcall, and prints the result.
package cmds
import (
"github.com/paymog/slack-cli/internal/config"
"github.com/paymog/slack-cli/internal/output"
"github.com/paymog/slack-cli/internal/toolcall"
"github.com/spf13/cobra"
)
// AddCommands registers every tool command group on the root command.
func AddCommands(root *cobra.Command, cfg *config.Config) {
root.AddCommand(
newChannelsCommand(cfg),
newConversationsCommand(cfg),
newUsersCommand(cfg),
newUsergroupsCommand(cfg),
newSavedCommand(cfg),
newReactionsCommand(cfg),
newAttachmentsCommand(cfg),
newCacheCommand(cfg),
)
}
// emit invokes a tool handler with args and prints its text result.
func emit(cmd *cobra.Command, cfg *config.Config, name string, h toolcall.Handler, args map[string]any) error {
out, err := toolcall.Invoke(cmd.Context(), h, name, args)
if err != nil {
return err
}
return output.Print(cmd.OutOrStdout(), out, cfg.Raw)
}
// putIf adds key=val to args only when val is non-empty (string) — keeps the
// arguments map free of zero-value noise the handlers would otherwise read.
func putStr(args map[string]any, key, val string) {
if val != "" {
args[key] = val
}
}