fere-cli 0.3.1.dev33__tar.gz → 0.4.0.dev35__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.3.1.dev33 → fere_cli-0.4.0.dev35}/.gitignore +4 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/PKG-INFO +2 -2
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/pyproject.toml +2 -2
- fere_cli-0.4.0.dev35/src/fere_cli/commands/perp.py +337 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/main.py +4 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/README.md +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/install.sh +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/__init__.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/async_util.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/banner.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/__init__.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/auth.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/chat.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/earn.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/hooks.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/limit_order.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/portfolio.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/swap.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/commands/utility.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/config.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/output.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/src/fere_cli/update.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/tests/test_install_sh.py +0 -0
- {fere_cli-0.3.1.dev33 → fere_cli-0.4.0.dev35}/tests/test_update.py +0 -0
|
@@ -8,6 +8,8 @@ __pycache__
|
|
|
8
8
|
# Devcontainer local env (contains secrets)
|
|
9
9
|
.devcontainer/.env.local
|
|
10
10
|
.devcontainer/.env.local.override
|
|
11
|
+
.devcontainer/.env.runtime.local
|
|
12
|
+
.devcontainer/.env.runtime.*.local
|
|
11
13
|
.devcontainer/.env.gitpod
|
|
12
14
|
|
|
13
15
|
# Bruno gateway collection (GATEWAY_AGENT_ID / GATEWAY_PRIVATE_KEY_B64)
|
|
@@ -43,6 +45,7 @@ dev/logs/*
|
|
|
43
45
|
# Cursor SEO skill — regenerated report JSON (not source)
|
|
44
46
|
.cursor/skills/seo-agent/artifacts/
|
|
45
47
|
.cursor/skills/docker-images-cve-scan/artifacts/
|
|
48
|
+
docs/plans/artifacts/
|
|
46
49
|
|
|
47
50
|
# Skills installed locally by the skills package manager (see skills-lock.json)
|
|
48
51
|
.claude/skills/stripe-best-practices
|
|
@@ -52,6 +55,7 @@ dev/logs/*
|
|
|
52
55
|
|
|
53
56
|
# agentmemory iii state (SQLite + streams under .agentmemory/data/)
|
|
54
57
|
.agentmemory/data/
|
|
58
|
+
.devcontainer/agentmemory-mcp/node_modules/
|
|
55
59
|
|
|
56
60
|
# Local GEO upstream reference (optional clone; not part of the product tree)
|
|
57
61
|
geo-seo/
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fere-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0.dev35
|
|
4
4
|
Summary: Terminal CLI for FereAI crypto trading and research
|
|
5
5
|
Author-email: Fere AI <info@fere.ai>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
8
|
Requires-Dist: click>=8.1
|
|
9
|
-
Requires-Dist: fere-sdk>=0.
|
|
9
|
+
Requires-Dist: fere-sdk>=0.4.0.dev35
|
|
10
10
|
Requires-Dist: httpx>=0.27
|
|
11
11
|
Requires-Dist: rich>=13.0
|
|
12
12
|
Provides-Extra: dev
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "fere-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0.dev35"
|
|
4
4
|
description = "Terminal CLI for FereAI crypto trading and research"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
license = "MIT"
|
|
8
8
|
authors = [{ name = "Fere AI", email = "info@fere.ai" }]
|
|
9
9
|
dependencies = [
|
|
10
|
-
"fere-sdk>=0.
|
|
10
|
+
"fere-sdk>=0.4.0.dev35",
|
|
11
11
|
"httpx>=0.27",
|
|
12
12
|
"click>=8.1",
|
|
13
13
|
"rich>=13.0",
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Perp commands: fere perp (markets, status, setup, orders, open, close,
|
|
2
|
+
cancel, fund, withdraw).
|
|
3
|
+
|
|
4
|
+
Venue-neutral perpetual futures. ``size`` is in contracts.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
from ..async_util import async_command, get_client, is_tty
|
|
13
|
+
from ..output import print_error, print_success
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.group()
|
|
19
|
+
def perp():
|
|
20
|
+
"""Trade perpetual futures (open, close, fund, withdraw)."""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@perp.command()
|
|
25
|
+
@click.option("--search", default=None, help="Filter the asset universe.")
|
|
26
|
+
@click.pass_context
|
|
27
|
+
@async_command
|
|
28
|
+
async def markets(ctx, search):
|
|
29
|
+
"""List the tradable perp market universe."""
|
|
30
|
+
try:
|
|
31
|
+
client = await get_client(ctx)
|
|
32
|
+
data = await client.get_perp_markets(search_text=search)
|
|
33
|
+
await client.close()
|
|
34
|
+
print_success(data)
|
|
35
|
+
except click.ClickException:
|
|
36
|
+
raise
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print_error(str(e))
|
|
39
|
+
raise SystemExit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@perp.command()
|
|
43
|
+
@click.pass_context
|
|
44
|
+
@async_command
|
|
45
|
+
async def status(ctx):
|
|
46
|
+
"""Show whether perp trading is provisioned."""
|
|
47
|
+
try:
|
|
48
|
+
client = await get_client(ctx)
|
|
49
|
+
data = await client.get_perp_setup_status()
|
|
50
|
+
await client.close()
|
|
51
|
+
print_success(data)
|
|
52
|
+
except click.ClickException:
|
|
53
|
+
raise
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print_error(str(e))
|
|
56
|
+
raise SystemExit(1)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@perp.command()
|
|
60
|
+
@click.option("--force", is_flag=True, help="Re-run setup even if complete.")
|
|
61
|
+
@click.option(
|
|
62
|
+
"--timeout", default=120, type=int, help="Wait timeout (seconds)."
|
|
63
|
+
)
|
|
64
|
+
@click.pass_context
|
|
65
|
+
@async_command
|
|
66
|
+
async def setup(ctx, force, timeout):
|
|
67
|
+
"""Provision perp trading for the account."""
|
|
68
|
+
try:
|
|
69
|
+
client = await get_client(ctx)
|
|
70
|
+
with console.status("Provisioning perp trading..."):
|
|
71
|
+
result = await client.perp_setup(force_rerun=force, timeout=timeout)
|
|
72
|
+
await client.close()
|
|
73
|
+
print_success(result)
|
|
74
|
+
except click.ClickException:
|
|
75
|
+
raise
|
|
76
|
+
except Exception as e:
|
|
77
|
+
print_error(str(e))
|
|
78
|
+
raise SystemExit(1)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@perp.command()
|
|
82
|
+
@click.pass_context
|
|
83
|
+
@async_command
|
|
84
|
+
async def orders(ctx):
|
|
85
|
+
"""List open perp orders (incl. TP/SL triggers)."""
|
|
86
|
+
try:
|
|
87
|
+
client = await get_client(ctx)
|
|
88
|
+
data = await client.get_perp_orders()
|
|
89
|
+
await client.close()
|
|
90
|
+
print_success(data)
|
|
91
|
+
except click.ClickException:
|
|
92
|
+
raise
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print_error(str(e))
|
|
95
|
+
raise SystemExit(1)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@perp.command()
|
|
99
|
+
@click.option("--asset", required=True, help="Asset symbol, e.g. BTC.")
|
|
100
|
+
@click.option(
|
|
101
|
+
"--side",
|
|
102
|
+
required=True,
|
|
103
|
+
type=click.Choice(["long", "short"]),
|
|
104
|
+
help="Position direction.",
|
|
105
|
+
)
|
|
106
|
+
@click.option("--size", required=True, type=float, help="Size in contracts.")
|
|
107
|
+
@click.option("--leverage", default=1, type=int, help="Leverage multiplier.")
|
|
108
|
+
@click.option(
|
|
109
|
+
"--order-type",
|
|
110
|
+
default="market",
|
|
111
|
+
type=click.Choice(["market", "limit"]),
|
|
112
|
+
)
|
|
113
|
+
@click.option("--limit-price", default=None, type=float)
|
|
114
|
+
@click.option("--slippage-pct", default=0.5, type=float)
|
|
115
|
+
@click.option("--tp-price", default=None, type=float, help="Take-profit.")
|
|
116
|
+
@click.option("--sl-price", default=None, type=float, help="Stop-loss.")
|
|
117
|
+
@click.option(
|
|
118
|
+
"--timeout", default=120, type=int, help="Wait timeout (seconds)."
|
|
119
|
+
)
|
|
120
|
+
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation.")
|
|
121
|
+
@click.pass_context
|
|
122
|
+
@async_command
|
|
123
|
+
async def open(
|
|
124
|
+
ctx,
|
|
125
|
+
asset,
|
|
126
|
+
side,
|
|
127
|
+
size,
|
|
128
|
+
leverage,
|
|
129
|
+
order_type,
|
|
130
|
+
limit_price,
|
|
131
|
+
slippage_pct,
|
|
132
|
+
tp_price,
|
|
133
|
+
sl_price,
|
|
134
|
+
timeout,
|
|
135
|
+
yes,
|
|
136
|
+
):
|
|
137
|
+
"""Open a perpetual position."""
|
|
138
|
+
if not yes and is_tty():
|
|
139
|
+
click.echo(f"Open {side} {size} {asset} at {leverage}x ({order_type})")
|
|
140
|
+
if not click.confirm("Proceed?"):
|
|
141
|
+
raise SystemExit(0)
|
|
142
|
+
try:
|
|
143
|
+
client = await get_client(ctx)
|
|
144
|
+
with console.status("Opening position..."):
|
|
145
|
+
result = await client.perp_open(
|
|
146
|
+
asset=asset,
|
|
147
|
+
is_buy=(side == "long"),
|
|
148
|
+
size=size,
|
|
149
|
+
leverage=leverage,
|
|
150
|
+
order_type=order_type,
|
|
151
|
+
limit_price=limit_price,
|
|
152
|
+
slippage_pct=slippage_pct,
|
|
153
|
+
tp_price=tp_price,
|
|
154
|
+
sl_price=sl_price,
|
|
155
|
+
timeout=timeout,
|
|
156
|
+
)
|
|
157
|
+
await client.close()
|
|
158
|
+
print_success(result)
|
|
159
|
+
except click.ClickException:
|
|
160
|
+
raise
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print_error(str(e))
|
|
163
|
+
raise SystemExit(1)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@perp.command()
|
|
167
|
+
@click.option("--asset", required=True, help="Asset symbol to close.")
|
|
168
|
+
@click.option(
|
|
169
|
+
"--size",
|
|
170
|
+
default=None,
|
|
171
|
+
type=float,
|
|
172
|
+
help="Contracts to close; omit for full close.",
|
|
173
|
+
)
|
|
174
|
+
@click.option("--slippage-pct", default=0.5, type=float)
|
|
175
|
+
@click.option(
|
|
176
|
+
"--timeout", default=120, type=int, help="Wait timeout (seconds)."
|
|
177
|
+
)
|
|
178
|
+
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation.")
|
|
179
|
+
@click.pass_context
|
|
180
|
+
@async_command
|
|
181
|
+
async def close(ctx, asset, size, slippage_pct, timeout, yes):
|
|
182
|
+
"""Close a perpetual position."""
|
|
183
|
+
if not yes and is_tty():
|
|
184
|
+
amt = f"{size}" if size is not None else "full"
|
|
185
|
+
click.echo(f"Close {amt} {asset} position")
|
|
186
|
+
if not click.confirm("Proceed?"):
|
|
187
|
+
raise SystemExit(0)
|
|
188
|
+
try:
|
|
189
|
+
client = await get_client(ctx)
|
|
190
|
+
with console.status("Closing position..."):
|
|
191
|
+
result = await client.perp_close(
|
|
192
|
+
asset=asset,
|
|
193
|
+
size=size,
|
|
194
|
+
slippage_pct=slippage_pct,
|
|
195
|
+
timeout=timeout,
|
|
196
|
+
)
|
|
197
|
+
await client.close()
|
|
198
|
+
print_success(result)
|
|
199
|
+
except click.ClickException:
|
|
200
|
+
raise
|
|
201
|
+
except Exception as e:
|
|
202
|
+
print_error(str(e))
|
|
203
|
+
raise SystemExit(1)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@perp.command()
|
|
207
|
+
@click.option("--asset", required=True, help="Asset symbol of the order.")
|
|
208
|
+
@click.option(
|
|
209
|
+
"--order-id", required=True, type=int, help="Venue order id (oid)."
|
|
210
|
+
)
|
|
211
|
+
@click.option(
|
|
212
|
+
"--timeout", default=120, type=int, help="Wait timeout (seconds)."
|
|
213
|
+
)
|
|
214
|
+
@click.pass_context
|
|
215
|
+
@async_command
|
|
216
|
+
async def cancel(ctx, asset, order_id, timeout):
|
|
217
|
+
"""Cancel an open perp order by its venue order id."""
|
|
218
|
+
try:
|
|
219
|
+
client = await get_client(ctx)
|
|
220
|
+
with console.status("Cancelling order..."):
|
|
221
|
+
result = await client.perp_cancel_order(
|
|
222
|
+
asset=asset, order_id=order_id, timeout=timeout
|
|
223
|
+
)
|
|
224
|
+
await client.close()
|
|
225
|
+
print_success(result)
|
|
226
|
+
except click.ClickException:
|
|
227
|
+
raise
|
|
228
|
+
except Exception as e:
|
|
229
|
+
print_error(str(e))
|
|
230
|
+
raise SystemExit(1)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@perp.command()
|
|
234
|
+
@click.option(
|
|
235
|
+
"--amount",
|
|
236
|
+
required=True,
|
|
237
|
+
help="Source token amount in smallest units.",
|
|
238
|
+
)
|
|
239
|
+
@click.option(
|
|
240
|
+
"--source-chain-id", required=True, type=int, help="Source chain id."
|
|
241
|
+
)
|
|
242
|
+
@click.option(
|
|
243
|
+
"--source-token",
|
|
244
|
+
default=None,
|
|
245
|
+
help="ERC-20 address; omit for native token.",
|
|
246
|
+
)
|
|
247
|
+
@click.option(
|
|
248
|
+
"--usd",
|
|
249
|
+
"display_amount_usd",
|
|
250
|
+
default=None,
|
|
251
|
+
type=float,
|
|
252
|
+
help="USD amount the user entered.",
|
|
253
|
+
)
|
|
254
|
+
@click.option(
|
|
255
|
+
"--timeout", default=120, type=int, help="Wait timeout (seconds)."
|
|
256
|
+
)
|
|
257
|
+
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation.")
|
|
258
|
+
@click.pass_context
|
|
259
|
+
@async_command
|
|
260
|
+
async def fund(
|
|
261
|
+
ctx,
|
|
262
|
+
amount,
|
|
263
|
+
source_chain_id,
|
|
264
|
+
source_token,
|
|
265
|
+
display_amount_usd,
|
|
266
|
+
timeout,
|
|
267
|
+
yes,
|
|
268
|
+
):
|
|
269
|
+
"""Fund the perp account from a connected wallet."""
|
|
270
|
+
if not yes and is_tty():
|
|
271
|
+
click.echo(f"Fund perp account from chain {source_chain_id}")
|
|
272
|
+
if not click.confirm("Proceed?"):
|
|
273
|
+
raise SystemExit(0)
|
|
274
|
+
try:
|
|
275
|
+
client = await get_client(ctx)
|
|
276
|
+
with console.status("Funding..."):
|
|
277
|
+
result = await client.perp_fund(
|
|
278
|
+
amount=amount,
|
|
279
|
+
source_chain_id=source_chain_id,
|
|
280
|
+
source_token=source_token,
|
|
281
|
+
display_amount_usd=display_amount_usd,
|
|
282
|
+
timeout=timeout,
|
|
283
|
+
)
|
|
284
|
+
await client.close()
|
|
285
|
+
print_success(result)
|
|
286
|
+
except click.ClickException:
|
|
287
|
+
raise
|
|
288
|
+
except Exception as e:
|
|
289
|
+
print_error(str(e))
|
|
290
|
+
raise SystemExit(1)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
@perp.command()
|
|
294
|
+
@click.option(
|
|
295
|
+
"--amount-usd", required=True, type=float, help="USD to withdraw."
|
|
296
|
+
)
|
|
297
|
+
@click.option(
|
|
298
|
+
"--destination-chain-id",
|
|
299
|
+
required=True,
|
|
300
|
+
type=int,
|
|
301
|
+
help="Destination chain id.",
|
|
302
|
+
)
|
|
303
|
+
@click.option(
|
|
304
|
+
"--destination-token",
|
|
305
|
+
default=None,
|
|
306
|
+
help="ERC-20/mint; omit for native token.",
|
|
307
|
+
)
|
|
308
|
+
@click.option(
|
|
309
|
+
"--timeout", default=120, type=int, help="Wait timeout (seconds)."
|
|
310
|
+
)
|
|
311
|
+
@click.option("-y", "--yes", is_flag=True, help="Skip confirmation.")
|
|
312
|
+
@click.pass_context
|
|
313
|
+
@async_command
|
|
314
|
+
async def withdraw(
|
|
315
|
+
ctx, amount_usd, destination_chain_id, destination_token, timeout, yes
|
|
316
|
+
):
|
|
317
|
+
"""Withdraw perp collateral to a destination chain/token."""
|
|
318
|
+
if not yes and is_tty():
|
|
319
|
+
click.echo(f"Withdraw {amount_usd} USD to chain {destination_chain_id}")
|
|
320
|
+
if not click.confirm("Proceed?"):
|
|
321
|
+
raise SystemExit(0)
|
|
322
|
+
try:
|
|
323
|
+
client = await get_client(ctx)
|
|
324
|
+
with console.status("Withdrawing..."):
|
|
325
|
+
result = await client.perp_withdraw(
|
|
326
|
+
amount_usd=amount_usd,
|
|
327
|
+
destination_chain_id=destination_chain_id,
|
|
328
|
+
destination_token=destination_token,
|
|
329
|
+
timeout=timeout,
|
|
330
|
+
)
|
|
331
|
+
await client.close()
|
|
332
|
+
print_success(result)
|
|
333
|
+
except click.ClickException:
|
|
334
|
+
raise
|
|
335
|
+
except Exception as e:
|
|
336
|
+
print_error(str(e))
|
|
337
|
+
raise SystemExit(1)
|
|
@@ -10,6 +10,7 @@ from .commands.chat import chat, threads
|
|
|
10
10
|
from .commands.earn import earn
|
|
11
11
|
from .commands.hooks import hooks
|
|
12
12
|
from .commands.limit_order import limit_order
|
|
13
|
+
from .commands.perp import perp
|
|
13
14
|
from .commands.portfolio import holdings, notifications, wallets
|
|
14
15
|
from .commands.swap import swap
|
|
15
16
|
from .commands.utility import chains, config, status
|
|
@@ -90,6 +91,9 @@ cli.add_command(notifications)
|
|
|
90
91
|
# Earn
|
|
91
92
|
cli.add_command(earn)
|
|
92
93
|
|
|
94
|
+
# Perp (venue-neutral perpetual futures)
|
|
95
|
+
cli.add_command(perp)
|
|
96
|
+
|
|
93
97
|
# Utility
|
|
94
98
|
cli.add_command(chains)
|
|
95
99
|
cli.add_command(status)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|