fere-cli 0.1.0.dev6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fere_cli-0.1.0.dev6/.gitignore +18 -0
- fere_cli-0.1.0.dev6/PKG-INFO +133 -0
- fere_cli-0.1.0.dev6/README.md +118 -0
- fere_cli-0.1.0.dev6/install.sh +114 -0
- fere_cli-0.1.0.dev6/pyproject.toml +26 -0
- fere_cli-0.1.0.dev6/src/fere_cli/__init__.py +3 -0
- fere_cli-0.1.0.dev6/src/fere_cli/async_util.py +63 -0
- fere_cli-0.1.0.dev6/src/fere_cli/banner.py +23 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/__init__.py +0 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/auth.py +108 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/chat.py +198 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/earn.py +143 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/hooks.py +74 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/limit_order.py +160 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/portfolio.py +69 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/swap.py +114 -0
- fere_cli-0.1.0.dev6/src/fere_cli/commands/utility.py +87 -0
- fere_cli-0.1.0.dev6/src/fere_cli/config.py +60 -0
- fere_cli-0.1.0.dev6/src/fere_cli/main.py +92 -0
- fere_cli-0.1.0.dev6/src/fere_cli/output.py +123 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
*.log
|
|
2
|
+
*.pyc
|
|
3
|
+
__pycache__
|
|
4
|
+
|
|
5
|
+
# Devcontainer local env (contains secrets)
|
|
6
|
+
.devcontainer/.env.local
|
|
7
|
+
.devcontainer/.env.gitpod
|
|
8
|
+
|
|
9
|
+
# Git worktrees
|
|
10
|
+
.worktrees/
|
|
11
|
+
|
|
12
|
+
# tmp/ — keep .gitkeep, ignore all else
|
|
13
|
+
tmp/*
|
|
14
|
+
!tmp/.gitkeep
|
|
15
|
+
coverage.xml
|
|
16
|
+
|
|
17
|
+
# Eval output
|
|
18
|
+
compare_results.json
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fere-cli
|
|
3
|
+
Version: 0.1.0.dev6
|
|
4
|
+
Summary: Terminal CLI for FereAI crypto trading and research
|
|
5
|
+
Author-email: Fere AI <info@fere.ai>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: click>=8.1
|
|
9
|
+
Requires-Dist: fere-sdk>=0.1.0.dev6
|
|
10
|
+
Requires-Dist: rich>=13.0
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# FereAI CLI
|
|
17
|
+
|
|
18
|
+
Terminal-first interface for FereAI crypto trading and research. Wraps the [FereAI Gateway API](https://api.fereai.xyz) — same capabilities as the Python and TypeScript SDKs.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
curl -fsSL https://api.fereai.xyz/install.sh | sh
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or with pip:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install fere-cli
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
# Authenticate (first-run — creates agent, generates keys)
|
|
36
|
+
fere auth
|
|
37
|
+
|
|
38
|
+
# Chat with the AI agent
|
|
39
|
+
fere chat "what is the price of ETH?"
|
|
40
|
+
|
|
41
|
+
# Interactive chat REPL
|
|
42
|
+
fere chat
|
|
43
|
+
|
|
44
|
+
# Stream responses in real-time
|
|
45
|
+
fere chat --stream "top 5 memecoins on Base"
|
|
46
|
+
|
|
47
|
+
# Check your wallets and holdings
|
|
48
|
+
fere wallets
|
|
49
|
+
fere holdings
|
|
50
|
+
|
|
51
|
+
# Execute a swap
|
|
52
|
+
fere swap \
|
|
53
|
+
--chain-in 8453 --chain-out 8453 \
|
|
54
|
+
--token-in 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
|
|
55
|
+
--token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
|
|
56
|
+
--amount 1000000000000000000
|
|
57
|
+
|
|
58
|
+
# Check credits
|
|
59
|
+
fere credits
|
|
60
|
+
|
|
61
|
+
# View supported chains
|
|
62
|
+
fere chains
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## JSON Output (for scripts & AI agents)
|
|
66
|
+
|
|
67
|
+
Every command supports `--json` for machine-readable output:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
fere --json credits
|
|
71
|
+
# {"ok": true, "data": {"credits_available": 150.0}}
|
|
72
|
+
|
|
73
|
+
fere --json holdings
|
|
74
|
+
# {"ok": true, "data": {...}}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Streaming chat in JSON mode outputs NDJSON (one event per line):
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
fere --json chat --stream "price of SOL"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Commands
|
|
84
|
+
|
|
85
|
+
| Command | Description |
|
|
86
|
+
|---------|-------------|
|
|
87
|
+
| `fere auth` | Authenticate and register agent |
|
|
88
|
+
| `fere whoami` | Show agent identity and wallets |
|
|
89
|
+
| `fere credits` | Check credit balance |
|
|
90
|
+
| `fere chat [QUERY]` | Chat with AI (one-shot or REPL) |
|
|
91
|
+
| `fere threads` | List chat threads |
|
|
92
|
+
| `fere swap` | Execute token swap |
|
|
93
|
+
| `fere limit-order create` | Create limit order |
|
|
94
|
+
| `fere limit-order list` | List limit orders |
|
|
95
|
+
| `fere limit-order cancel ID` | Cancel limit order |
|
|
96
|
+
| `fere hooks set` | Set stop-loss / take-profit |
|
|
97
|
+
| `fere wallets` | Show wallet addresses |
|
|
98
|
+
| `fere holdings` | Show token holdings |
|
|
99
|
+
| `fere notifications` | Show notifications |
|
|
100
|
+
| `fere earn info` | Show APY and vault info |
|
|
101
|
+
| `fere earn deposit` | Deposit USDC |
|
|
102
|
+
| `fere earn withdraw` | Withdraw USDC |
|
|
103
|
+
| `fere earn positions` | Show yield positions |
|
|
104
|
+
| `fere chains` | List supported chains |
|
|
105
|
+
| `fere status` | Check API connectivity |
|
|
106
|
+
| `fere config` | Show/edit CLI config |
|
|
107
|
+
|
|
108
|
+
## Global Flags
|
|
109
|
+
|
|
110
|
+
| Flag | Env Var | Description |
|
|
111
|
+
|------|---------|-------------|
|
|
112
|
+
| `--json` | — | Machine-readable JSON output |
|
|
113
|
+
| `--quiet` | — | Minimal output |
|
|
114
|
+
| `--agent NAME` | `FERE_AGENT_NAME` | Agent name override |
|
|
115
|
+
| `--base-url URL` | `FERE_BASE_URL` | API URL override |
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
Config is stored at `~/.fere/config.json`. Keys are stored at `~/.fere/keys.json` (managed by the SDK).
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
fere config # Show current config
|
|
123
|
+
fere config set agent_name my-bot # Change agent name
|
|
124
|
+
fere config set base_url http://localhost:8003 # Point to local gateway
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Agent-Friendly
|
|
128
|
+
|
|
129
|
+
- Exit codes: 0 success, 1 error
|
|
130
|
+
- Non-interactive when stdin is not a TTY (all prompts skipped)
|
|
131
|
+
- `--json` output: `{"ok": true, "data": {...}}` or `{"ok": false, "error": "..."}`
|
|
132
|
+
- Streaming JSON: NDJSON format
|
|
133
|
+
- No ANSI colors when piped
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# FereAI CLI
|
|
2
|
+
|
|
3
|
+
Terminal-first interface for FereAI crypto trading and research. Wraps the [FereAI Gateway API](https://api.fereai.xyz) — same capabilities as the Python and TypeScript SDKs.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
curl -fsSL https://api.fereai.xyz/install.sh | sh
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with pip:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install fere-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Authenticate (first-run — creates agent, generates keys)
|
|
21
|
+
fere auth
|
|
22
|
+
|
|
23
|
+
# Chat with the AI agent
|
|
24
|
+
fere chat "what is the price of ETH?"
|
|
25
|
+
|
|
26
|
+
# Interactive chat REPL
|
|
27
|
+
fere chat
|
|
28
|
+
|
|
29
|
+
# Stream responses in real-time
|
|
30
|
+
fere chat --stream "top 5 memecoins on Base"
|
|
31
|
+
|
|
32
|
+
# Check your wallets and holdings
|
|
33
|
+
fere wallets
|
|
34
|
+
fere holdings
|
|
35
|
+
|
|
36
|
+
# Execute a swap
|
|
37
|
+
fere swap \
|
|
38
|
+
--chain-in 8453 --chain-out 8453 \
|
|
39
|
+
--token-in 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \
|
|
40
|
+
--token-out 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 \
|
|
41
|
+
--amount 1000000000000000000
|
|
42
|
+
|
|
43
|
+
# Check credits
|
|
44
|
+
fere credits
|
|
45
|
+
|
|
46
|
+
# View supported chains
|
|
47
|
+
fere chains
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## JSON Output (for scripts & AI agents)
|
|
51
|
+
|
|
52
|
+
Every command supports `--json` for machine-readable output:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
fere --json credits
|
|
56
|
+
# {"ok": true, "data": {"credits_available": 150.0}}
|
|
57
|
+
|
|
58
|
+
fere --json holdings
|
|
59
|
+
# {"ok": true, "data": {...}}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Streaming chat in JSON mode outputs NDJSON (one event per line):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
fere --json chat --stream "price of SOL"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Commands
|
|
69
|
+
|
|
70
|
+
| Command | Description |
|
|
71
|
+
|---------|-------------|
|
|
72
|
+
| `fere auth` | Authenticate and register agent |
|
|
73
|
+
| `fere whoami` | Show agent identity and wallets |
|
|
74
|
+
| `fere credits` | Check credit balance |
|
|
75
|
+
| `fere chat [QUERY]` | Chat with AI (one-shot or REPL) |
|
|
76
|
+
| `fere threads` | List chat threads |
|
|
77
|
+
| `fere swap` | Execute token swap |
|
|
78
|
+
| `fere limit-order create` | Create limit order |
|
|
79
|
+
| `fere limit-order list` | List limit orders |
|
|
80
|
+
| `fere limit-order cancel ID` | Cancel limit order |
|
|
81
|
+
| `fere hooks set` | Set stop-loss / take-profit |
|
|
82
|
+
| `fere wallets` | Show wallet addresses |
|
|
83
|
+
| `fere holdings` | Show token holdings |
|
|
84
|
+
| `fere notifications` | Show notifications |
|
|
85
|
+
| `fere earn info` | Show APY and vault info |
|
|
86
|
+
| `fere earn deposit` | Deposit USDC |
|
|
87
|
+
| `fere earn withdraw` | Withdraw USDC |
|
|
88
|
+
| `fere earn positions` | Show yield positions |
|
|
89
|
+
| `fere chains` | List supported chains |
|
|
90
|
+
| `fere status` | Check API connectivity |
|
|
91
|
+
| `fere config` | Show/edit CLI config |
|
|
92
|
+
|
|
93
|
+
## Global Flags
|
|
94
|
+
|
|
95
|
+
| Flag | Env Var | Description |
|
|
96
|
+
|------|---------|-------------|
|
|
97
|
+
| `--json` | — | Machine-readable JSON output |
|
|
98
|
+
| `--quiet` | — | Minimal output |
|
|
99
|
+
| `--agent NAME` | `FERE_AGENT_NAME` | Agent name override |
|
|
100
|
+
| `--base-url URL` | `FERE_BASE_URL` | API URL override |
|
|
101
|
+
|
|
102
|
+
## Configuration
|
|
103
|
+
|
|
104
|
+
Config is stored at `~/.fere/config.json`. Keys are stored at `~/.fere/keys.json` (managed by the SDK).
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
fere config # Show current config
|
|
108
|
+
fere config set agent_name my-bot # Change agent name
|
|
109
|
+
fere config set base_url http://localhost:8003 # Point to local gateway
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Agent-Friendly
|
|
113
|
+
|
|
114
|
+
- Exit codes: 0 success, 1 error
|
|
115
|
+
- Non-interactive when stdin is not a TTY (all prompts skipped)
|
|
116
|
+
- `--json` output: `{"ok": true, "data": {...}}` or `{"ok": false, "error": "..."}`
|
|
117
|
+
- Streaming JSON: NDJSON format
|
|
118
|
+
- No ANSI colors when piped
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# FereAI CLI Installer
|
|
3
|
+
# Usage: curl -fsSL https://api.fereai.xyz/install.sh | sh
|
|
4
|
+
#
|
|
5
|
+
# Installs the `fere` CLI tool via pipx (recommended) or pip.
|
|
6
|
+
# Supports macOS and Linux.
|
|
7
|
+
|
|
8
|
+
set -e
|
|
9
|
+
|
|
10
|
+
VERSION="0.1.0"
|
|
11
|
+
PACKAGE="fere-cli"
|
|
12
|
+
|
|
13
|
+
# Colors (disabled if not a TTY)
|
|
14
|
+
if [ -t 1 ]; then
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[0;33m'
|
|
18
|
+
DIM='\033[2m'
|
|
19
|
+
RESET='\033[0m'
|
|
20
|
+
else
|
|
21
|
+
RED=''
|
|
22
|
+
GREEN=''
|
|
23
|
+
YELLOW=''
|
|
24
|
+
DIM=''
|
|
25
|
+
RESET=''
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
info() { printf "${DIM}%s${RESET}\n" "$1"; }
|
|
29
|
+
success() { printf "${GREEN}%s${RESET}\n" "$1"; }
|
|
30
|
+
warn() { printf "${YELLOW}%s${RESET}\n" "$1"; }
|
|
31
|
+
error() { printf "${RED}Error: %s${RESET}\n" "$1" >&2; exit 1; }
|
|
32
|
+
|
|
33
|
+
# Detect OS
|
|
34
|
+
OS="$(uname -s)"
|
|
35
|
+
case "$OS" in
|
|
36
|
+
Darwin) OS_NAME="macOS" ;;
|
|
37
|
+
Linux) OS_NAME="Linux" ;;
|
|
38
|
+
*) error "Unsupported OS: $OS. Use pip install $PACKAGE instead." ;;
|
|
39
|
+
esac
|
|
40
|
+
|
|
41
|
+
info "Installing FereAI CLI v${VERSION} on ${OS_NAME}..."
|
|
42
|
+
|
|
43
|
+
# Check Python 3.10+
|
|
44
|
+
check_python() {
|
|
45
|
+
for cmd in python3 python; do
|
|
46
|
+
if command -v "$cmd" >/dev/null 2>&1; then
|
|
47
|
+
ver=$("$cmd" -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)
|
|
48
|
+
major=$(echo "$ver" | cut -d. -f1)
|
|
49
|
+
minor=$(echo "$ver" | cut -d. -f2)
|
|
50
|
+
if [ "$major" -ge 3 ] && [ "$minor" -ge 10 ]; then
|
|
51
|
+
echo "$cmd"
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
fi
|
|
55
|
+
done
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
PYTHON=$(check_python) || error "Python 3.10+ is required. Install it from https://python.org"
|
|
60
|
+
info "Found Python: $($PYTHON --version)"
|
|
61
|
+
|
|
62
|
+
# Prefer pipx for isolated installs
|
|
63
|
+
install_with_pipx() {
|
|
64
|
+
if command -v pipx >/dev/null 2>&1; then
|
|
65
|
+
info "Installing with pipx..."
|
|
66
|
+
pipx install "$PACKAGE" || pipx upgrade "$PACKAGE"
|
|
67
|
+
return 0
|
|
68
|
+
fi
|
|
69
|
+
return 1
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
install_pipx() {
|
|
73
|
+
info "Installing pipx..."
|
|
74
|
+
if command -v brew >/dev/null 2>&1; then
|
|
75
|
+
brew install pipx
|
|
76
|
+
elif [ "$OS" = "Linux" ]; then
|
|
77
|
+
$PYTHON -m pip install --user pipx 2>/dev/null || \
|
|
78
|
+
$PYTHON -m ensurepip --default-pip 2>/dev/null && \
|
|
79
|
+
$PYTHON -m pip install --user pipx
|
|
80
|
+
else
|
|
81
|
+
$PYTHON -m pip install --user pipx
|
|
82
|
+
fi
|
|
83
|
+
pipx ensurepath 2>/dev/null || true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
install_with_pip() {
|
|
87
|
+
info "Installing with pip..."
|
|
88
|
+
$PYTHON -m pip install --user "$PACKAGE"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Try pipx first, then install pipx, then fall back to pip
|
|
92
|
+
if install_with_pipx; then
|
|
93
|
+
: # success
|
|
94
|
+
elif install_pipx && install_with_pipx; then
|
|
95
|
+
: # success
|
|
96
|
+
else
|
|
97
|
+
warn "pipx not available, falling back to pip..."
|
|
98
|
+
install_with_pip
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# Verify installation
|
|
102
|
+
if command -v fere >/dev/null 2>&1; then
|
|
103
|
+
success "FereAI CLI installed successfully!"
|
|
104
|
+
echo ""
|
|
105
|
+
info "Get started:"
|
|
106
|
+
echo " fere auth # Set up your agent"
|
|
107
|
+
echo " fere chat \"hello\" # Chat with the AI"
|
|
108
|
+
echo " fere --help # See all commands"
|
|
109
|
+
else
|
|
110
|
+
warn "Installation complete, but 'fere' is not in PATH."
|
|
111
|
+
warn "You may need to restart your shell or add ~/.local/bin to PATH:"
|
|
112
|
+
echo ""
|
|
113
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
114
|
+
fi
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "fere-cli"
|
|
3
|
+
version = "0.1.0.dev6"
|
|
4
|
+
description = "Terminal CLI for FereAI crypto trading and research"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "Fere AI", email = "info@fere.ai" }]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"fere-sdk>=0.1.0.dev6",
|
|
11
|
+
"click>=8.1",
|
|
12
|
+
"rich>=13.0",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.scripts]
|
|
16
|
+
fere = "fere_cli.main:cli"
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
dev = ["pytest>=8.0", "pytest-asyncio>=0.24"]
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["hatchling"]
|
|
23
|
+
build-backend = "hatchling.build"
|
|
24
|
+
|
|
25
|
+
[tool.hatch.build.targets.wheel]
|
|
26
|
+
packages = ["src/fere_cli"]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Async bridge utilities for the CLI.
|
|
2
|
+
|
|
3
|
+
Click commands are synchronous; the SDK is async. This module
|
|
4
|
+
bridges the two with a thin ``run_async`` wrapper and a
|
|
5
|
+
``get_client`` factory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import sys
|
|
12
|
+
from functools import wraps
|
|
13
|
+
from typing import Any, Callable
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
from fere_sdk import FereClient
|
|
17
|
+
|
|
18
|
+
from .config import load_config
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run_async(coro):
|
|
22
|
+
"""Run an async coroutine from synchronous context."""
|
|
23
|
+
return asyncio.run(coro)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def async_command(f: Callable) -> Callable:
|
|
27
|
+
"""Decorator: wraps an async function for use as a Click command."""
|
|
28
|
+
|
|
29
|
+
@wraps(f)
|
|
30
|
+
def wrapper(*args, **kwargs):
|
|
31
|
+
return run_async(f(*args, **kwargs))
|
|
32
|
+
|
|
33
|
+
return wrapper
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
async def get_client(ctx: click.Context) -> FereClient:
|
|
37
|
+
"""Create an authenticated FereClient from config + CLI overrides.
|
|
38
|
+
|
|
39
|
+
Raises click.ClickException if no agent name is configured.
|
|
40
|
+
"""
|
|
41
|
+
cfg = load_config()
|
|
42
|
+
|
|
43
|
+
# CLI flag overrides
|
|
44
|
+
agent_name = ctx.obj.get("agent_override") or cfg.get("agent_name")
|
|
45
|
+
base_url = ctx.obj.get("base_url_override") or cfg.get("base_url")
|
|
46
|
+
key_path = cfg.get("key_path")
|
|
47
|
+
|
|
48
|
+
if not agent_name:
|
|
49
|
+
raise click.ClickException(
|
|
50
|
+
"No agent configured. Run 'fere auth' first "
|
|
51
|
+
"or pass --agent NAME."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return await FereClient.create(
|
|
55
|
+
agent_name=agent_name,
|
|
56
|
+
base_url=base_url,
|
|
57
|
+
key_path=key_path,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def is_tty() -> bool:
|
|
62
|
+
"""Check if stdin is a TTY (interactive terminal)."""
|
|
63
|
+
return sys.stdin.isatty()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""ASCII art banner for the Fere CLI."""
|
|
2
|
+
|
|
3
|
+
from . import __version__
|
|
4
|
+
|
|
5
|
+
# Simplified spiral motif + stylized "FERE AI" text
|
|
6
|
+
# The spiral evokes the Fere AI logo's concentric swirl pattern
|
|
7
|
+
# Kept under 70 chars wide for comfortable 80-col terminal display
|
|
8
|
+
LOGO = r"""
|
|
9
|
+
. · .
|
|
10
|
+
· · _____ _____ ____ _____ _ ___
|
|
11
|
+
· . · . · | ___| ___| _ \| ____| / \ |_ _|
|
|
12
|
+
· · · · | |_ | |_ | |_) | _| / _ \ | |
|
|
13
|
+
· · . · · | _| | _| | _ <| |___ / ___ \ | |
|
|
14
|
+
· · · · |_| |___|_|_| \_\_____| /_/ \_\___|
|
|
15
|
+
· · . · ·
|
|
16
|
+
· ·
|
|
17
|
+
· . ·
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def get_banner() -> str:
|
|
22
|
+
"""Return the full banner string with version."""
|
|
23
|
+
return f"{LOGO}\n v{__version__} — crypto trading & research from your terminal\n"
|
|
File without changes
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Auth commands: fere auth, fere whoami, fere credits."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from fere_sdk import FereClient
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
|
|
9
|
+
from ..async_util import async_command, get_client, is_tty, run_async
|
|
10
|
+
from ..config import load_config, save_config
|
|
11
|
+
from ..output import print_error, print_success
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.command()
|
|
17
|
+
@click.option("--name", prompt=False, default=None, help="Agent name.")
|
|
18
|
+
@click.pass_context
|
|
19
|
+
@async_command
|
|
20
|
+
async def auth(ctx, name: str | None):
|
|
21
|
+
"""Authenticate and register a new agent (first-run setup)."""
|
|
22
|
+
cfg = load_config()
|
|
23
|
+
|
|
24
|
+
agent_name = (
|
|
25
|
+
ctx.obj.get("agent_override")
|
|
26
|
+
or name
|
|
27
|
+
or cfg.get("agent_name")
|
|
28
|
+
)
|
|
29
|
+
if not agent_name:
|
|
30
|
+
if is_tty():
|
|
31
|
+
agent_name = click.prompt("Agent name")
|
|
32
|
+
else:
|
|
33
|
+
raise click.ClickException(
|
|
34
|
+
"Agent name required. Pass --name or --agent."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
base_url = ctx.obj.get("base_url_override") or cfg.get("base_url")
|
|
38
|
+
key_path = cfg.get("key_path")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
client = await FereClient.create(
|
|
42
|
+
agent_name=agent_name,
|
|
43
|
+
base_url=base_url,
|
|
44
|
+
key_path=key_path,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Persist agent name to config
|
|
48
|
+
cfg["agent_name"] = agent_name
|
|
49
|
+
save_config(cfg)
|
|
50
|
+
|
|
51
|
+
user = await client.get_user()
|
|
52
|
+
await client.close()
|
|
53
|
+
|
|
54
|
+
print_success(
|
|
55
|
+
{
|
|
56
|
+
"status": "authenticated",
|
|
57
|
+
"agent_name": agent_name,
|
|
58
|
+
"user": user,
|
|
59
|
+
},
|
|
60
|
+
quiet_value=agent_name,
|
|
61
|
+
)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
print_error(f"Authentication failed: {e}")
|
|
64
|
+
raise SystemExit(1)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@click.command()
|
|
68
|
+
@click.pass_context
|
|
69
|
+
@async_command
|
|
70
|
+
async def whoami(ctx):
|
|
71
|
+
"""Show current agent identity and wallet addresses."""
|
|
72
|
+
try:
|
|
73
|
+
client = await get_client(ctx)
|
|
74
|
+
user = await client.get_user()
|
|
75
|
+
wallets_data = await client.get_wallets()
|
|
76
|
+
await client.close()
|
|
77
|
+
|
|
78
|
+
result = {**user}
|
|
79
|
+
if wallets_data:
|
|
80
|
+
result["wallets"] = wallets_data
|
|
81
|
+
|
|
82
|
+
print_success(result)
|
|
83
|
+
except click.ClickException:
|
|
84
|
+
raise
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print_error(str(e))
|
|
87
|
+
raise SystemExit(1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@click.command()
|
|
91
|
+
@click.pass_context
|
|
92
|
+
@async_command
|
|
93
|
+
async def credits(ctx):
|
|
94
|
+
"""Show available credit balance."""
|
|
95
|
+
try:
|
|
96
|
+
client = await get_client(ctx)
|
|
97
|
+
balance = await client.get_credits()
|
|
98
|
+
await client.close()
|
|
99
|
+
|
|
100
|
+
print_success(
|
|
101
|
+
{"credits_available": balance},
|
|
102
|
+
quiet_value=str(balance),
|
|
103
|
+
)
|
|
104
|
+
except click.ClickException:
|
|
105
|
+
raise
|
|
106
|
+
except Exception as e:
|
|
107
|
+
print_error(str(e))
|
|
108
|
+
raise SystemExit(1)
|