diff --git a/README.md b/README.md index d8a08d1..3b548a4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,24 @@ # Agent CLI Tools -Agent CLI Tools is a home for small Go CLIs that should be easy for AI agents to install and call as executable tools. +Agent CLI Tools packages focused, agent-friendly command-line utilities as small Go binaries. Each tool is designed to be easy for an AI agent or automation runner to install, configure, call noninteractively, and parse. + +The project favors standalone executables with explicit flags, environment-based configuration, stable exit behavior, and optional JSON output. That keeps each tool safe to expose independently while still sharing one release pipeline and contribution model. + +## Install + +Install released tools with Homebrew: + +```bash +brew install berrydev-ai/tap/slack-post +``` + +This README describes the current source tree. Homebrew installs the latest released binary; for unreleased source changes, use `make build` below. + +Command-specific usage and environment variables live with each tool: + +- [`slack-post`](cmd/slack-post/README.md) posts messages to Slack with `chat.postMessage`. + +Use the release archive or Docker examples below when Homebrew is not available. ## Shape @@ -56,11 +74,11 @@ make test Each binary owns a README in its command path: -- `cmd/slack-post/README.md` +- [`cmd/slack-post/README.md`](cmd/slack-post/README.md) Each binary also owns its environment template next to that README: -- `cmd/slack-post/.env.example` +- [`cmd/slack-post/.env.example`](cmd/slack-post/.env.example) Leave secret values blank. @@ -119,4 +137,4 @@ docker build --secret id=github_token,env=GITHUB_TOKEN . ## Tools -- `slack-post`: see `cmd/slack-post/README.md` +- [`slack-post`](cmd/slack-post/README.md): post messages to Slack from scripts, agents, and release checks. diff --git a/cmd/slack-post/.env.example b/cmd/slack-post/.env.example index ba3760c..bb67a4d 100644 --- a/cmd/slack-post/.env.example +++ b/cmd/slack-post/.env.example @@ -3,14 +3,14 @@ # Copy values into the environment that runs this CLI. Do not commit real tokens. # Slack bot token used for chat.postMessage. -SLACK_E2E_BOT_TOKEN= +SLACK_POST_BOT_TOKEN= # Slack channel ID where slack-post sends messages, for example C0123456789. -SLACK_E2E_TARGET_CHANNEL= +SLACK_POST_TARGET_CHANNEL= # Optional default Slack member ID to mention before the message. -SLACK_E2E_TARGET_BOT_MEMBER_ID= +SLACK_POST_TARGET_BOT_MEMBER_ID= # Optional per-target member IDs. Used by `slack-post --target `. # Example for `slack-post --target claude`: -SLACK_E2E_CLAUDE_BOT_MEMBER_ID= +SLACK_POST_CLAUDE_BOT_MEMBER_ID= diff --git a/cmd/slack-post/README.md b/cmd/slack-post/README.md index 1a9d463..e5c2e38 100644 --- a/cmd/slack-post/README.md +++ b/cmd/slack-post/README.md @@ -7,7 +7,7 @@ ```bash slack-post --prompt "run the e2e test" slack-post --target claude --prompt "run the e2e test" -slack-post --format json --target-member-id "$SLACK_E2E_TARGET_BOT_MEMBER_ID" "run the e2e test" +slack-post --format json --target-member-id "$SLACK_POST_TARGET_BOT_MEMBER_ID" "run the e2e test" ``` ## Required Inputs @@ -16,16 +16,16 @@ Provide these values as flags or environment variables: | Flag | Environment variable | Description | | --- | --- | --- | -| `--token` | `SLACK_E2E_BOT_TOKEN` | Slack bot token. | -| `--channel` | `SLACK_E2E_TARGET_CHANNEL` | Slack channel ID. | +| `--token` | `SLACK_POST_BOT_TOKEN` | Slack bot token. | +| `--channel` | `SLACK_POST_TARGET_CHANNEL` | Slack channel ID. | | `--prompt` | none | Message text. Positional text is also accepted. | Optional mention target: | Flag | Environment variable | Description | | --- | --- | --- | -| `--target-member-id`, `--member-id` | `SLACK_E2E_TARGET_BOT_MEMBER_ID` | Slack member ID to mention. | -| `--target ` | `SLACK_E2E__BOT_MEMBER_ID` | Resolves a named target, such as `SLACK_E2E_CLAUDE_BOT_MEMBER_ID`. | +| `--target-member-id`, `--member-id` | `SLACK_POST_TARGET_BOT_MEMBER_ID` | Slack member ID to mention. | +| `--target ` | `SLACK_POST__BOT_MEMBER_ID` | Resolves a named target, such as `SLACK_POST_CLAUDE_BOT_MEMBER_ID`. | See the local `.env.example` for the maintained environment template. diff --git a/internal/cli/slackpost/command.go b/internal/cli/slackpost/command.go index e672507..308de64 100644 --- a/internal/cli/slackpost/command.go +++ b/internal/cli/slackpost/command.go @@ -79,10 +79,10 @@ func NewCommand(opts Options) *cobra.Command { Example: strings.TrimSpace(` slack-post --prompt "run the e2e test" slack-post --target claude --prompt "run the e2e test" -slack-post --format json --target-member-id "$SLACK_E2E_TARGET_BOT_MEMBER_ID" "run the e2e test"`), +slack-post --format json --target-member-id "$SLACK_POST_TARGET_BOT_MEMBER_ID" "run the e2e test"`), RunE: func(cmd *cobra.Command, args []string) error { - token = firstNonEmpty(token, opts.Env("SLACK_E2E_BOT_TOKEN")) - channel = firstNonEmpty(channel, opts.Env("SLACK_E2E_TARGET_CHANNEL")) + token = firstNonEmpty(token, opts.Env("SLACK_POST_BOT_TOKEN")) + channel = firstNonEmpty(channel, opts.Env("SLACK_POST_TARGET_CHANNEL")) if prompt == "" && len(args) > 0 { prompt = strings.Join(args, " ") @@ -90,11 +90,11 @@ slack-post --format json --target-member-id "$SLACK_E2E_TARGET_BOT_MEMBER_ID" "r var targetEnvName string if targetMemberID == "" && target != "" { - targetEnvName = "SLACK_E2E_" + normalizeEnvPart(target) + "_BOT_MEMBER_ID" + targetEnvName = "SLACK_POST_" + normalizeEnvPart(target) + "_BOT_MEMBER_ID" targetMemberID = opts.Env(targetEnvName) } if targetMemberID == "" { - targetMemberID = opts.Env("SLACK_E2E_TARGET_BOT_MEMBER_ID") + targetMemberID = opts.Env("SLACK_POST_TARGET_BOT_MEMBER_ID") } if format != "text" && format != "json" { @@ -103,10 +103,10 @@ slack-post --format json --target-member-id "$SLACK_E2E_TARGET_BOT_MEMBER_ID" "r var missing []string if token == "" { - missing = append(missing, "--token or SLACK_E2E_BOT_TOKEN") + missing = append(missing, "--token or SLACK_POST_BOT_TOKEN") } if channel == "" { - missing = append(missing, "--channel or SLACK_E2E_TARGET_CHANNEL") + missing = append(missing, "--channel or SLACK_POST_TARGET_CHANNEL") } if prompt == "" { missing = append(missing, "--prompt or positional message text") @@ -134,9 +134,9 @@ slack-post --format json --target-member-id "$SLACK_E2E_TARGET_BOT_MEMBER_ID" "r cmd.SetOut(opts.Out) cmd.SetErr(opts.Err) - cmd.Flags().StringVar(&token, "token", "", "Slack bot token. Defaults to SLACK_E2E_BOT_TOKEN.") - cmd.Flags().StringVar(&channel, "channel", "", "Slack channel ID. Defaults to SLACK_E2E_TARGET_CHANNEL.") - cmd.Flags().StringVar(&target, "target", "", "Target name used to resolve SLACK_E2E__BOT_MEMBER_ID.") + cmd.Flags().StringVar(&token, "token", "", "Slack bot token. Defaults to SLACK_POST_BOT_TOKEN.") + cmd.Flags().StringVar(&channel, "channel", "", "Slack channel ID. Defaults to SLACK_POST_TARGET_CHANNEL.") + cmd.Flags().StringVar(&target, "target", "", "Target name used to resolve SLACK_POST__BOT_MEMBER_ID.") cmd.Flags().StringVar(&targetMemberID, "target-member-id", "", "Slack member ID to mention, for example U123ABC456.") cmd.Flags().StringVar(&targetMemberID, "member-id", "", "Alias for --target-member-id.") cmd.Flags().StringVar(&prompt, "prompt", "", "Prompt/message text to send.") diff --git a/internal/cli/slackpost/command_test.go b/internal/cli/slackpost/command_test.go index d70081f..e3647f2 100644 --- a/internal/cli/slackpost/command_test.go +++ b/internal/cli/slackpost/command_test.go @@ -28,9 +28,9 @@ func TestCommandPostsPositionalPromptWithTargetEnv(t *testing.T) { defer server.Close() out, _, err := execute(t, map[string]string{ - "SLACK_E2E_BOT_TOKEN": "xoxb-test", - "SLACK_E2E_TARGET_CHANNEL": "C123", - "SLACK_E2E_CLAUDE_BOT_MEMBER_ID": "UCLAUDE", + "SLACK_POST_BOT_TOKEN": "xoxb-test", + "SLACK_POST_TARGET_CHANNEL": "C123", + "SLACK_POST_CLAUDE_BOT_MEMBER_ID": "UCLAUDE", }, "--url", server.URL, "--target", "claude", "--format", "json", "run", "the", "e2e", "test") if err != nil { t.Fatalf("expected success, got %v", err) @@ -65,7 +65,7 @@ func TestCommandRequiresNoninteractiveInputs(t *testing.T) { } errText := err.Error() - for _, want := range []string{"--token or SLACK_E2E_BOT_TOKEN", "--channel or SLACK_E2E_TARGET_CHANNEL"} { + for _, want := range []string{"--token or SLACK_POST_BOT_TOKEN", "--channel or SLACK_POST_TARGET_CHANNEL"} { if !strings.Contains(errText, want) { t.Fatalf("error %q does not contain %q", errText, want) } @@ -80,8 +80,8 @@ func TestCommandReportsSlackAPIError(t *testing.T) { defer server.Close() _, _, err := execute(t, map[string]string{ - "SLACK_E2E_BOT_TOKEN": "xoxb-test", - "SLACK_E2E_TARGET_CHANNEL": "C123", + "SLACK_POST_BOT_TOKEN": "xoxb-test", + "SLACK_POST_TARGET_CHANNEL": "C123", }, "--url", server.URL, "--prompt", "hello") if err == nil { t.Fatal("expected Slack API error")