Initial commit
This commit is contained in:
69
skills/cli-patterns/examples/api-cli/README.md
Normal file
69
skills/cli-patterns/examples/api-cli/README.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# API Client CLI Tool Example
|
||||
|
||||
Complete REST API client CLI demonstrating:
|
||||
- HTTP client sharing via context
|
||||
- Authentication in Before hook
|
||||
- Multiple HTTP methods (GET, POST, PUT, DELETE)
|
||||
- Headers and request configuration
|
||||
- Arguments handling
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
# Set environment variables
|
||||
export API_URL=https://api.example.com
|
||||
export API_TOKEN=your_token_here
|
||||
|
||||
# GET request
|
||||
api get /users
|
||||
api get /users/123
|
||||
|
||||
# POST request
|
||||
api post /users '{"name": "John", "email": "john@example.com"}'
|
||||
api post /posts '{"title": "Hello", "body": "World"}' --content-type application/json
|
||||
|
||||
# PUT request
|
||||
api put /users/123 '{"name": "Jane"}'
|
||||
|
||||
# DELETE request
|
||||
api delete /users/123
|
||||
|
||||
# Test authentication
|
||||
api auth-test
|
||||
|
||||
# Custom timeout
|
||||
api --timeout 60s get /slow-endpoint
|
||||
|
||||
# Additional headers
|
||||
api get /users -H "Accept:application/json" -H "X-Custom:value"
|
||||
```
|
||||
|
||||
## Features Demonstrated
|
||||
|
||||
1. **Context Management**: Shared HTTPClient and auth across requests
|
||||
2. **Before Hook**: Authenticates and sets up HTTP client
|
||||
3. **Arguments**: Commands accept endpoint and data as arguments
|
||||
4. **Required Flags**: --url and --token are required
|
||||
5. **Environment Variables**: API_URL, API_TOKEN, API_TIMEOUT fallbacks
|
||||
6. **Duration Flags**: --timeout uses time.Duration type
|
||||
7. **Multiple Values**: --header can be specified multiple times
|
||||
8. **Helper Functions**: maskToken() for secure token display
|
||||
|
||||
## HTTP Client Pattern
|
||||
|
||||
```go
|
||||
type APIContext struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
// Initialize in Before hook
|
||||
client := &http.Client{Timeout: timeout}
|
||||
ctx := &APIContext{...}
|
||||
c.App.Metadata["ctx"] = ctx
|
||||
|
||||
// Use in commands
|
||||
ctx := c.App.Metadata["ctx"].(*APIContext)
|
||||
resp, err := ctx.HTTPClient.Get(url)
|
||||
```
|
||||
205
skills/cli-patterns/examples/api-cli/main.go
Normal file
205
skills/cli-patterns/examples/api-cli/main.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
type APIContext struct {
|
||||
BaseURL string
|
||||
Token string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "api",
|
||||
Usage: "REST API client CLI",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Usage: "API base URL",
|
||||
EnvVars: []string{"API_URL"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "token",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Authentication token",
|
||||
EnvVars: []string{"API_TOKEN"},
|
||||
Required: true,
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Usage: "Request timeout",
|
||||
Value: 30 * time.Second,
|
||||
EnvVars: []string{"API_TIMEOUT"},
|
||||
},
|
||||
},
|
||||
|
||||
Before: func(c *cli.Context) error {
|
||||
baseURL := c.String("url")
|
||||
token := c.String("token")
|
||||
timeout := c.Duration("timeout")
|
||||
|
||||
fmt.Println("🔐 Authenticating with API...")
|
||||
|
||||
// Create HTTP client
|
||||
client := &http.Client{
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
// Store context
|
||||
ctx := &APIContext{
|
||||
BaseURL: baseURL,
|
||||
Token: token,
|
||||
HTTPClient: client,
|
||||
}
|
||||
c.App.Metadata["ctx"] = ctx
|
||||
|
||||
fmt.Println("✅ Authentication successful")
|
||||
|
||||
return nil
|
||||
},
|
||||
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "get",
|
||||
Usage: "GET request",
|
||||
ArgsUsage: "<endpoint>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringSliceFlag{
|
||||
Name: "header",
|
||||
Aliases: []string{"H"},
|
||||
Usage: "Additional headers (key:value)",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.App.Metadata["ctx"].(*APIContext)
|
||||
|
||||
if c.NArg() < 1 {
|
||||
return fmt.Errorf("endpoint required")
|
||||
}
|
||||
|
||||
endpoint := c.Args().Get(0)
|
||||
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
|
||||
|
||||
fmt.Printf("GET %s\n", url)
|
||||
fmt.Printf("Authorization: Bearer %s\n", maskToken(ctx.Token))
|
||||
|
||||
// In real app: make HTTP request
|
||||
fmt.Println("Response: 200 OK")
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "post",
|
||||
Usage: "POST request",
|
||||
ArgsUsage: "<endpoint> <data>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "content-type",
|
||||
Aliases: []string{"ct"},
|
||||
Usage: "Content-Type header",
|
||||
Value: "application/json",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.App.Metadata["ctx"].(*APIContext)
|
||||
|
||||
if c.NArg() < 2 {
|
||||
return fmt.Errorf("usage: post <endpoint> <data>")
|
||||
}
|
||||
|
||||
endpoint := c.Args().Get(0)
|
||||
data := c.Args().Get(1)
|
||||
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
|
||||
contentType := c.String("content-type")
|
||||
|
||||
fmt.Printf("POST %s\n", url)
|
||||
fmt.Printf("Content-Type: %s\n", contentType)
|
||||
fmt.Printf("Data: %s\n", data)
|
||||
|
||||
// In real app: make HTTP POST request
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "put",
|
||||
Usage: "PUT request",
|
||||
ArgsUsage: "<endpoint> <data>",
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.App.Metadata["ctx"].(*APIContext)
|
||||
|
||||
if c.NArg() < 2 {
|
||||
return fmt.Errorf("usage: put <endpoint> <data>")
|
||||
}
|
||||
|
||||
endpoint := c.Args().Get(0)
|
||||
data := c.Args().Get(1)
|
||||
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
|
||||
|
||||
fmt.Printf("PUT %s\n", url)
|
||||
fmt.Printf("Data: %s\n", data)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "DELETE request",
|
||||
ArgsUsage: "<endpoint>",
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.App.Metadata["ctx"].(*APIContext)
|
||||
|
||||
if c.NArg() < 1 {
|
||||
return fmt.Errorf("endpoint required")
|
||||
}
|
||||
|
||||
endpoint := c.Args().Get(0)
|
||||
url := fmt.Sprintf("%s%s", ctx.BaseURL, endpoint)
|
||||
|
||||
fmt.Printf("DELETE %s\n", url)
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "auth-test",
|
||||
Usage: "Test authentication",
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx := c.App.Metadata["ctx"].(*APIContext)
|
||||
|
||||
fmt.Println("Testing authentication...")
|
||||
fmt.Printf("API URL: %s\n", ctx.BaseURL)
|
||||
fmt.Printf("Token: %s\n", maskToken(ctx.Token))
|
||||
fmt.Println("Status: Authenticated ✅")
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func maskToken(token string) string {
|
||||
if len(token) < 8 {
|
||||
return "****"
|
||||
}
|
||||
return token[:4] + "****" + token[len(token)-4:]
|
||||
}
|
||||
Reference in New Issue
Block a user