Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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.
8 changes: 4 additions & 4 deletions cmd/slack-post/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name>`.
# Example for `slack-post --target claude`:
SLACK_E2E_CLAUDE_BOT_MEMBER_ID=
SLACK_POST_CLAUDE_BOT_MEMBER_ID=
10 changes: 5 additions & 5 deletions cmd/slack-post/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <name>` | `SLACK_E2E_<TARGET>_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 <name>` | `SLACK_POST_<TARGET>_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.

Expand Down
20 changes: 10 additions & 10 deletions internal/cli/slackpost/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,22 @@ 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, " ")
}

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" {
Expand All @@ -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")
Expand Down Expand Up @@ -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_<TARGET>_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_<TARGET>_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.")
Expand Down
12 changes: 6 additions & 6 deletions internal/cli/slackpost/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand All @@ -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")
Expand Down
Loading