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
2 changes: 1 addition & 1 deletion .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.22"
go-version-file: 'go.mod'
- name: Test
run: go test -v ./...
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.22"
go-version-file: 'go.mod'
- name: Import Apple certificate
env:
APPLE_CERTIFICATE_BASE64: ${{ secrets.APPLE_CERTIFICATE_BASE64 }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.22"
go-version-file: 'go.mod'
- name: Test
run: go test -v ./...
- name: Build
Expand Down
90 changes: 55 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,79 +30,99 @@ Download the latest binary from the [Releases](https://github.com/apialerts/cli/

## Setup

Configure your API key once. The key is stored in `~/.apialerts/config.json`.
You'll need an API key from your workspace. After logging in to [apialerts.com](https://apialerts.com), navigate to your workspace and open the **API Keys** section. You can also find it in the mobile app under your workspace settings.

```bash
apialerts config --key your_api_key
```
Your API key is stored locally in `~/.apialerts/config.json`.

Verify your configuration
### Interactive (recommended)

```bash
apialerts config
apialerts init
```

## Send Events
You will be prompted to paste your API key (input is hidden):

Send an event with a message
```
Enter your API key:
API key saved: abcdef...wxyz
```

### Non-interactive (CI/CD or scripts)

```bash
apialerts send -m "Deploy completed"
apialerts config --key "your-api-key"
```

Send an event with a name and title
### View your current key

```bash
apialerts send -e user.purchase -t "New Sale" -m "$49.99 from john@example.com" -c payments
apialerts config
```

### Optional Properties
```
API Key: abcdef...wxyz
```

You can optionally specify an event name, title, channel, tags, and a link.
### Remove your key

```bash
apialerts send -m "Payment failed" -c payments -g billing,error -l https://dashboard.example.com
apialerts config --unset
```

| Flag | Short | Description |
|------|-------|-------------|
| `--message` | `-m` | Event message (required) |
| `--event` | `-e` | Event name for routing (optional, e.g. `user.purchase`) |
| `--title` | `-t` | Event title (optional) |
| `--channel` | `-c` | Target channel (optional, uses default channel if not set) |
| `--tags` | `-g` | Comma-separated tags (optional) |
| `--link` | `-l` | Associated URL (optional) |
| `--key` | | API key override (optional, uses stored config if not set) |
## Send Events

### Override API Key
```bash
apialerts send -m "Deploy completed"
```

You can override the stored API key for a single request.
```bash
apialerts send -e "user.purchase" -t "New Sale" -m "$49.99 from john@example.com" -c "payments"
```

```bash
apialerts send -m "Hello World" --key other_api_key
apialerts send -e "user.signup" -m "New user registered" -d '{"plan":"pro","source":"organic"}'
```

### Flags

| Flag | Short | Description |
|------|-------|-------------|
| `--message` | `-m` | Event message **(required)** |
| `--event` | `-e` | Event name for routing (e.g. `user.purchase`) |
| `--title` | `-t` | Event title |
| `--channel` | `-c` | Target channel (uses your default channel if not set) |
| `--tags` | `-g` | Comma-separated tags (e.g. `billing,error`) |
| `--link` | `-l` | Associated URL |
| `--data` | `-d` | JSON object with additional event data (e.g. `'{"plan":"pro"}'`) |
| `--key` | | API key override (uses stored config if not set) |

## Test Connectivity

Send a test event to verify your API key and connection.
Send a test event to verify your API key and connection:

```bash
apialerts test
```

## CI/CD Examples
```
✓ Test event sent to My Workspace (general)
```

### GitHub Actions
## Examples

```yaml
- name: Send deploy alert
run: |
apialerts send -m "Deployed ${{ github.sha }}" -c deployments -g ci,deploy --key ${{ secrets.APIALERTS_API_KEY }}
```
### Claude Code

Because the CLI is installed on your machine, Claude Code can run it directly as part of any task. Just ask:

- "Refactor the auth module and send me an API Alert when you're done."
- "Run the full test suite and notify me via API Alerts with a summary of the results."
- "Migrate the database schema and send me an apialert if anything fails."

Claude will run `apialerts send` at the right moment — no extra configuration needed.

### Shell Script

```bash
#!/bin/bash
apialerts send -m "Backup completed" -c ops -g backup,cron
apialerts send -m "Backup completed" -c "ops" -g "backup,cron"
```
77 changes: 66 additions & 11 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,93 @@ import (
)

var configKey string
var configServerURL string
var unsetKey bool

func maskAPIKey(key string) string {
n := len(key)
switch {
case key == "":
return "No API key configured."
case n <= 3:
return "***"
case n <= 10:
return key[:1] + "..." + key[n-1:]
default:
return key[:6] + "..." + key[n-4:]
}
}

var configCmd = &cobra.Command{
Use: "config",
Short: "Configure the CLI",
Long: "Set your API key for authentication. The key is stored in ~/.apialerts/config.json.",
RunE: func(cmd *cobra.Command, args []string) error {
if configKey == "" {
// Show current config
if unsetKey {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if cfg.APIKey == "" {
fmt.Println("No API key configured.")
fmt.Println("Run: apialerts config --key <your-api-key>")
cfg.APIKey = ""
if err := config.Save(cfg); err != nil {
return fmt.Errorf("failed to unset API key: %w", err)
}
fmt.Println("API key removed.")
return nil
}

if cmd.Flags().Changed("server-url") {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
cfg.ServerURL = configServerURL
if err := config.Save(cfg); err != nil {
return fmt.Errorf("failed to save server URL: %w", err)
}
if configServerURL == "" {
fmt.Println("Server URL reset to default.")
} else {
masked := cfg.APIKey[:6] + "..." + cfg.APIKey[len(cfg.APIKey)-4:]
fmt.Printf("API Key: %s\n", masked)
fmt.Printf("Server URL set to: %s\n", configServerURL)
}
return nil
}

cfg := &config.CLIConfig{APIKey: configKey}
if err := config.Save(cfg); err != nil {
return fmt.Errorf("failed to save config: %w", err)
if configKey != "" {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
cfg.APIKey = configKey
if err := config.Save(cfg); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
fmt.Println("API key saved.")
return nil
}

// Show current config
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if cfg.APIKey == "" {
fmt.Println("No API key configured.")
fmt.Println("Run: apialerts init")
} else {
fmt.Printf("API Key: %s\n", maskAPIKey(cfg.APIKey))
if cfg.ServerURL != "" {
fmt.Printf("Server URL: %s\n", cfg.ServerURL)
}
}
fmt.Println("API key saved.")
return nil
},
}

func init() {
configCmd.Flags().StringVar(&configKey, "key", "", "Your API Alerts API key")
configCmd.Flags().BoolVar(&unsetKey, "unset", false, "Remove the stored API key")
configCmd.Flags().StringVar(&configServerURL, "server-url", "", "Override the API server URL")
configCmd.Flags().MarkHidden("server-url")
rootCmd.AddCommand(configCmd)
}
4 changes: 2 additions & 2 deletions cmd/constants.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package cmd

const (
IntegrationName = "cli"
Version = "1.1.0"
IntegrationName = "apialerts-cli"
Version = "1.2.0"
)
50 changes: 50 additions & 0 deletions cmd/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package cmd

import (
"fmt"
"os"
"strings"

"github.com/apialerts/cli/internal/config"
"github.com/spf13/cobra"
"golang.org/x/term"
)

var initCmd = &cobra.Command{
Use: "init",
Short: "Set up your API key",
Long: "Interactively prompt for your API key and save it to ~/.apialerts/config.json.",
RunE: func(cmd *cobra.Command, args []string) error {
if !term.IsTerminal(int(os.Stdin.Fd())) {
return fmt.Errorf("no terminal detected — use: apialerts config --key \"your-api-key\"")
}

fmt.Print("Enter your API key: ")
keyBytes, err := term.ReadPassword(int(os.Stdin.Fd()))
fmt.Println()
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}

key := strings.TrimSpace(string(keyBytes))
if key == "" {
return fmt.Errorf("API key cannot be empty")
}

cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
cfg.APIKey = key
if err := config.Save(cfg); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}

fmt.Printf("API key saved: %s\n", maskAPIKey(key))
return nil
},
}

func init() {
rootCmd.AddCommand(initCmd)
}
10 changes: 8 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ import (
var rootCmd = &cobra.Command{
Use: "apialerts",
Short: "API Alerts CLI — send events from your terminal",
Long: "A command-line interface for apialerts.com. Configure your API key, send events, and test connectivity.",
Long: `A command-line interface for apialerts.com. Send events from your terminal, scripts, and CI/CD pipelines.

Get started:
apialerts init
apialerts send -m "Hello from the terminal"`,
Version: Version,
}

func Execute() {
rootCmd.SilenceErrors = true
rootCmd.CompletionOptions.HiddenDefaultCmd = true
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
}
Loading
Loading