Metadata-Version: 2.4
Name: askfaro-cli
Version: 0.1.0
Summary: CLI for the Faro AI tool marketplace
License: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28.1
Requires-Dist: jsonschema>=4.25.1
Requires-Dist: pyyaml>=6.0.2
Requires-Dist: rich>=15.0.0
Requires-Dist: typer>=0.23.2
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.22.0; extra == 'dev'
Description-Content-Type: text/markdown

# askfaro-cli

Command-line interface for the [Faro](https://askfaro.com) AI tool marketplace.

Two surfaces in one tool:

- **Buyers** — search for tools by natural language and invoke them from the terminal or any agent that can run shell commands.
- **Publishers** — create namespaces, edit listings declaratively in `faro.yaml`, and submit for review without leaving the shell.

## Install

```bash
pip install askfaro-cli
```

## Quick start

```bash
# Save your API key
faro auth login

# Search for tools
faro search "send an email"

# Invoke a tool
faro invoke acme/send-email --params '{"to": "user@example.com", "subject": "Hello"}'
```

## Commands

### `faro auth login`
Save your Faro API key to `~/.config/faro/credentials` (mode 0600).

```bash
faro auth login
# or non-interactively:
faro auth login --api-key faro_yourkey
```

### `faro auth whoami`
Show the currently authenticated user.

### `faro auth logout`
Remove the saved API key.

### `faro search <query>`
Semantic search over the Faro tool catalog. Results include each tool's `input_schema` so you can invoke immediately without a second lookup.

```bash
faro search "transcribe audio"
faro search "weather forecast" --top 5 --category data
```

### `faro describe <namespace>/<tool>`
Show the full schema and pricing for a single tool.

```bash
faro describe acme/send-email
```

### `faro invoke <namespace>/<tool>`
Invoke a tool. Arguments can be passed as a JSON string, a file, or piped via stdin.

```bash
# JSON string
faro invoke acme/send-email --params '{"to": "user@example.com", "subject": "Hello"}'

# File
faro invoke acme/send-email --params-file args.json

# Stdin
echo '{"to": "user@example.com", "subject": "Hello"}' | faro invoke acme/send-email

# Validate arguments without invoking (no credits spent)
faro invoke acme/send-email --params '{}' --dry-run
```

### `faro doctor`
One-shot health check: API key found, authenticated, publisher registered, namespace count.

```bash
faro doctor
```

---

## Publishing a listing

The fastest path is the declarative `faro.yaml` workflow:

```bash
# 1. Become a publisher (one time)
faro publisher register --display-name 'Acme Corp'

# 2. Scaffold a namespace from your MCP server
faro ns quick-setup --namespace acme --mcp-url https://mcp.acme.dev

# 3. Pull a manifest you can edit
faro init acme            # writes ./faro.yaml + ./README.md

# 4. Edit faro.yaml — descriptions, tags, per-tool example_prompts.
#    Add `icon_file: ./logo.png` to upload + link the icon on push.
$EDITOR faro.yaml

# 5. Validate, preview, push, and submit
faro ns check acme        # local readiness check (mirrors server)
faro diff                 # preview manifest vs. server
faro push --publish       # save listing draft + submit for review
```

For one-off updates without the manifest, every API endpoint has a CLI counterpart:

| Action | Command |
|---|---|
| List namespaces | `faro ns list` |
| Inspect a namespace | `faro ns get <slug>` |
| Edit description / tags | `faro ns edit-listing <slug> --description '…' --tags 'a,b'` |
| Set per-tool description / prompts | `faro ns edit-listing <slug> --tools-file tools.json` |
| Toggle a tool's visibility | `faro ns tools set-visibility <slug> <tool_id> --visibility public` |
| Add a REST tool by hand | `faro ns tools add-rest <slug> --name search --method GET --path /search` |
| Set group pricing | `faro ns groups set-pricing <slug> <group_id> --mode fixed_per_request --fixed-cost 10` |
| Store upstream API key | `faro ns creds set <slug> --api-key '…'` |
| Submit for review | `faro publish <slug>` |
| Earnings & sales | `faro publisher earnings`, `faro publisher sales --period last_month` |

Every namespace argument accepts a slug **or** a UUID. Run `faro <command> --help` for examples on any individual command.

### `faro.yaml` shape

```yaml
namespace: acme
namespace_id: 5f6f1234-…
listing_name: Acme MCP
description: |
  Multi-line description rendered as `|` block scalars on round-trip.
category: developer-tools
tags: [email, comms]
icon_file: ./logo.png       # uploaded on `faro push` → URL stored back here
readme_file: README.md       # contents become the listing's readme_content
tools:
  - id: 1d…                  # UUID is required to update an existing tool
    name: send-email         # informational
    visibility: public
    short_description: Send an email
    display_order: 0
    example_prompts:
      - Send Bob a status update
      - Email everyone in marketing
```

For active namespaces, edits land on a server-side draft overlay until you publish.

---

## Global options

| Option | Env var | Description |
|--------|---------|-------------|
| `--api-key` | `FARO_API_KEY` | Faro API key |
| `--api-url` | `FARO_API_URL` | Override API base URL (default: `https://api.askfaro.com`) |
| `--pretty` / `--json` | — | Force pretty or JSON output (auto-detected by TTY) |
| `-v` / `--verbose` | — | Log HTTP requests to stderr |

## Authentication precedence

1. `--api-key` flag
2. `FARO_API_KEY` environment variable
3. `~/.config/faro/credentials`

## Exit codes

| Code | Meaning |
|------|---------|
| 0 | Success |
| 1 | General error |
| 2 | Auth error (invalid or missing key) |
| 3 | Not found |
| 4 | Validation error (bad parameters) |
| 5 | Quota / billing limit reached |
| 6 | Upstream tool error |
| 7 | Network / timeout error (retriable) |

When stderr is a pipe (agent / script), errors are printed as JSON:
```json
{"error": {"code": "quota_exceeded", "message": "...", "retriable": false}}
```
On a TTY they render as `✗ <message>` so the JSON noise doesn't dominate.

---

## Use Faro from your agent

Copy this system-prompt snippet to give any LLM shell access to the Faro marketplace:

```
You have access to the Faro AI tool marketplace via the `faro` CLI.

To find tools:
  faro search "<describe what you want to do>"
  Returns a JSON list. Each item includes `display_name` (use this to invoke),
  `short_description`, and `input_schema` (the exact parameters required).

To invoke a tool:
  faro invoke <display_name> --params '<json object matching input_schema>'
  Returns a JSON object with `result` (the tool output) and `status`.

Rules:
- Always search before invoking unless you already know the exact tool name.
- Pass all required fields from input_schema. Use --dry-run to validate first.
- If exit code is 7, retry once. If exit code is 5, tell the user they need more credits.
- Output JSON only; parse `result` for the tool's actual response.

Examples:
  faro search "send a slack message"
  faro invoke slackbot/send-message --params '{"channel": "#general", "text": "Hello"}'
```

### Worked example — Claude API

```python
import anthropic
import subprocess
import json

client = anthropic.Anthropic()

system = """
You have access to the Faro AI tool marketplace via the `faro` CLI.
[paste the snippet above]
"""

def run_faro(command: str) -> str:
    result = subprocess.run(
        ["bash", "-c", f"faro {command}"],
        capture_output=True, text=True
    )
    if result.returncode not in (0,):
        return result.stderr  # error envelope JSON
    return result.stdout

response = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=1024,
    system=system,
    messages=[{"role": "user", "content": "Send a Slack message to #alerts saying the deploy is done."}],
)

# The model will emit faro commands; run them and feed results back
```

### Worked example — environment variable auth

```bash
export FARO_API_KEY=faro_yourkey

# In your agent's subprocess call:
result=$(faro search "translate text to Spanish" --json)
tool_name=$(echo $result | jq -r '.[0].display_name')
faro invoke "$tool_name" --params '{"text": "Hello world"}'
```

---

## MCP vs CLI

| | MCP | CLI |
|--|-----|-----|
| Setup | Config block in your host app | `pip install askfaro-cli` |
| Best for | Claude Desktop, Cursor, MCP-native hosts | Custom agents, Claude API, any shell |
| Discovery | Dynamic at connect time | `faro search` |
| Auth | API key in MCP config | `FARO_API_KEY` or `faro auth login` |
| Debuggable | Harder | Run the exact same command yourself |

Both use the same API keys and the same tool catalog.
