mirror of
https://github.com/paymog/slack-cli.git
synced 2026-09-18 23:43:49 +08:00
d781728c6c
* 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.
* chore(napkin): record real-workspace verification
* docs: explain how the CLI works (in-process handler invocation, shared cache)
62 lines
1.9 KiB
Go
62 lines
1.9 KiB
Go
// Package runtime bootstraps the reused provider stack for CLI commands:
|
|
// a quiet logger and an ApiProvider built from resolved credentials, plus
|
|
// on-disk cache loading for read commands.
|
|
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
|
|
"github.com/paymog/slack-cli/internal/config"
|
|
"github.com/paymog/slack-cli/pkg/provider"
|
|
"go.uber.org/zap"
|
|
"go.uber.org/zap/zapcore"
|
|
)
|
|
|
|
// Logger builds a stderr zap logger. By default only Fatal is emitted (so a
|
|
// normal run prints just the command's result on stdout, while provider auth
|
|
// failures still surface). --verbose raises it to Debug for full handler logs.
|
|
func Logger(verbose bool) *zap.Logger {
|
|
level := zapcore.FatalLevel
|
|
if verbose {
|
|
level = zapcore.DebugLevel
|
|
}
|
|
encCfg := zap.NewProductionEncoderConfig()
|
|
encCfg.EncodeTime = zapcore.ISO8601TimeEncoder
|
|
core := zapcore.NewCore(
|
|
zapcore.NewConsoleEncoder(encCfg),
|
|
zapcore.Lock(os.Stderr),
|
|
level,
|
|
)
|
|
return zap.New(core)
|
|
}
|
|
|
|
// Provider builds an ApiProvider from resolved config. It requires a usable
|
|
// credential set and writes tokens into the SLACK_MCP_* env so the reused
|
|
// provider.New picks them up.
|
|
func Provider(cfg *config.Config) (*provider.ApiProvider, *zap.Logger, error) {
|
|
if err := cfg.RequireAuth(); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
cfg.ApplyToEnv()
|
|
logger := Logger(cfg.Verbose)
|
|
return provider.New("stdio", logger), logger, nil
|
|
}
|
|
|
|
// PrepareRead builds a provider and loads the on-disk user/channel caches so
|
|
// that #channel-name and @username lookups resolve. With --no-cache it marks
|
|
// the provider ready without loading (only channel/user IDs will resolve).
|
|
func PrepareRead(ctx context.Context, cfg *config.Config) (*provider.ApiProvider, *zap.Logger, error) {
|
|
p, logger, err := Provider(cfg)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if cfg.NoCache {
|
|
p.SkipCache()
|
|
return p, logger, nil
|
|
}
|
|
_ = p.RefreshUsers(ctx)
|
|
_ = p.RefreshChannels(ctx)
|
|
return p, logger, nil
|
|
}
|