fere-cli 0.2.0.dev17__py3-none-any.whl → 0.2.1.dev19__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,3 +1,10 @@
1
1
  """FereAI CLI — terminal interface for crypto trading and research."""
2
2
 
3
- __version__ = "0.1.0"
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ # Always report the installed distribution version.
7
+ __version__ = version("fere-cli")
8
+ except PackageNotFoundError:
9
+ # Fallback for local/uninstalled source usage.
10
+ __version__ = "0.0.0"
fere_cli/async_util.py CHANGED
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
  import asyncio
11
11
  import sys
12
12
  from functools import wraps
13
- from typing import Any, Callable
13
+ from typing import Callable
14
14
 
15
15
  import click
16
16
  from fere_sdk import FereClient
fere_cli/banner.py CHANGED
@@ -20,4 +20,5 @@ LOGO = r"""
20
20
 
21
21
  def get_banner() -> str:
22
22
  """Return the full banner string with version."""
23
- return f"{LOGO}\n v{__version__} — crypto trading & research from your terminal\n"
23
+ tagline = "crypto trading & research from your terminal"
24
+ return f"{LOGO}\n v{__version__} — {tagline}\n"
fere_cli/commands/auth.py CHANGED
@@ -1,12 +1,14 @@
1
- """Auth commands: fere auth, fere whoami, fere credits."""
1
+ """Auth commands: fere auth, fere whoami, fere credits, fere claim-daily."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import dataclasses
6
+
5
7
  import click
6
8
  from fere_sdk import FereClient
7
9
  from rich.console import Console
8
10
 
9
- from ..async_util import async_command, get_client, is_tty, run_async
11
+ from ..async_util import async_command, get_client, is_tty
10
12
  from ..config import load_config, save_config
11
13
  from ..output import print_error, print_success
12
14
 
@@ -21,11 +23,7 @@ async def auth(ctx, name: str | None):
21
23
  """Authenticate and register a new agent (first-run setup)."""
22
24
  cfg = load_config()
23
25
 
24
- agent_name = (
25
- ctx.obj.get("agent_override")
26
- or name
27
- or cfg.get("agent_name")
28
- )
26
+ agent_name = ctx.obj.get("agent_override") or name or cfg.get("agent_name")
29
27
  if not agent_name:
30
28
  if is_tty():
31
29
  agent_name = click.prompt("Agent name")
@@ -68,18 +66,13 @@ async def auth(ctx, name: str | None):
68
66
  @click.pass_context
69
67
  @async_command
70
68
  async def whoami(ctx):
71
- """Show current agent identity and wallet addresses."""
69
+ """Show current agent identity."""
72
70
  try:
73
71
  client = await get_client(ctx)
74
72
  user = await client.get_user()
75
- wallets_data = await client.get_wallets()
76
73
  await client.close()
77
74
 
78
- result = {**user}
79
- if wallets_data:
80
- result["wallets"] = wallets_data
81
-
82
- print_success(result)
75
+ print_success(user)
83
76
  except click.ClickException:
84
77
  raise
85
78
  except Exception as e:
@@ -106,3 +99,43 @@ async def credits(ctx):
106
99
  except Exception as e:
107
100
  print_error(str(e))
108
101
  raise SystemExit(1)
102
+
103
+
104
+ @click.command(name="claim-daily")
105
+ @click.pass_context
106
+ @async_command
107
+ async def claim_daily_credits(ctx):
108
+ """Claim your daily credits bonus."""
109
+ try:
110
+ client = await get_client(ctx)
111
+ result = await client.claim_daily_credits()
112
+ await client.close()
113
+
114
+ data = dataclasses.asdict(result)
115
+ if not result.success:
116
+ if result.error == "claim_cooldown":
117
+ msg = (
118
+ "Already claimed today."
119
+ f" Next claim available at {result.next_claim_available_at}"
120
+ if result.next_claim_available_at
121
+ else "Already claimed today."
122
+ )
123
+ elif result.error == "balance_too_high":
124
+ bal = result.current_balance
125
+ msg = (
126
+ f"Balance too high to claim (current: {bal} credits)."
127
+ if bal is not None
128
+ else "Balance too high to claim."
129
+ )
130
+ elif result.error == "user_not_found":
131
+ msg = "User not found. Try logging in again."
132
+ else:
133
+ msg = result.error or "Daily claim failed."
134
+ print_error(msg, details=data)
135
+ raise SystemExit(1)
136
+ print_success(data)
137
+ except click.ClickException:
138
+ raise
139
+ except Exception as e:
140
+ print_error(str(e))
141
+ raise SystemExit(1)
fere_cli/commands/chat.py CHANGED
@@ -3,7 +3,6 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
- import sys
7
6
 
8
7
  import click
9
8
  from rich.console import Console
@@ -14,7 +13,6 @@ from ..output import (
14
13
  print_error,
15
14
  print_streaming_text,
16
15
  print_success,
17
- print_table_from_dicts,
18
16
  )
19
17
 
20
18
  console = Console()
@@ -61,20 +59,16 @@ async def chat(ctx, query, stream_mode, thread_id, agent_type):
61
59
  if query:
62
60
  # One-shot mode
63
61
  if stream_mode:
64
- await _stream_chat(
65
- client, query, thread_id, agent_type
66
- )
62
+ await _stream_chat(client, query, thread_id, agent_type)
67
63
  else:
68
- await _oneshot_chat(
69
- client, query, thread_id, agent_type
70
- )
64
+ await _oneshot_chat(client, query, thread_id, agent_type)
71
65
  elif is_tty():
72
66
  # Interactive REPL
73
67
  await _repl_chat(client, thread_id, agent_type)
74
68
  else:
75
69
  raise click.ClickException(
76
70
  "Query required in non-interactive mode. "
77
- "Usage: fere chat \"your question\""
71
+ 'Usage: fere chat "your question"'
78
72
  )
79
73
  except click.ClickException:
80
74
  raise
@@ -89,9 +83,7 @@ async def chat(ctx, query, stream_mode, thread_id, agent_type):
89
83
 
90
84
  async def _oneshot_chat(client, query, thread_id, agent_type):
91
85
  """Send a query and print the final answer."""
92
- result = await client.chat(
93
- query, thread_id=thread_id, agent=agent_type
94
- )
86
+ result = await client.chat(query, thread_id=thread_id, agent=agent_type)
95
87
  print_success(
96
88
  result,
97
89
  quiet_value=result.get("answer", ""),
@@ -121,9 +113,7 @@ async def _stream_chat(client, query, thread_id, agent_type):
121
113
  text = event.data.get("text", "")
122
114
  if text:
123
115
  click.echo() # newline after chunks
124
- console.print(
125
- f"\n[bold]Answer:[/bold] {text}"
126
- )
116
+ console.print(f"\n[bold]Answer:[/bold] {text}")
127
117
  elif event.event == "tool_response":
128
118
  console.print(
129
119
  f"[dim]Tool: {json.dumps(event.data, default=str)}[/dim]"
@@ -17,21 +17,23 @@ def hooks():
17
17
 
18
18
 
19
19
  @hooks.command()
20
- @click.option(
21
- "--chain-id", required=True, type=int, help="Chain ID."
22
- )
23
- @click.option(
24
- "--token", required=True, help="Token contract address."
25
- )
20
+ @click.option("--chain-id", required=True, type=int, help="Chain ID.")
21
+ @click.option("--token", required=True, help="Token contract address.")
26
22
  @click.option(
27
23
  "--stop-loss",
28
24
  default=None,
29
- help='Stop-loss config as JSON (e.g. \'{"price_percentage": 0.3, "sell_percentage": 1.0}\').',
25
+ help=(
26
+ "Stop-loss config as JSON"
27
+ ' (e.g. \'{"price_percentage": 0.3, "sell_percentage": 1.0}\').'
28
+ ),
30
29
  )
31
30
  @click.option(
32
31
  "--take-profit",
33
32
  default=None,
34
- help='Take-profit config as JSON (e.g. \'{"price_percentage": 1.0, "sell_percentage": 0.5}\').',
33
+ help=(
34
+ "Take-profit config as JSON"
35
+ ' (e.g. \'{"price_percentage": 1.0, "sell_percentage": 0.5}\').'
36
+ ),
35
37
  )
36
38
  @click.pass_context
37
39
  @async_command
@@ -28,9 +28,7 @@ def limit_order():
28
28
  @click.option(
29
29
  "--token-out", required=True, help="Destination token contract address."
30
30
  )
31
- @click.option(
32
- "--amount", required=True, help="Amount in smallest unit."
33
- )
31
+ @click.option("--amount", required=True, help="Amount in smallest unit.")
34
32
  @click.option(
35
33
  "--price",
36
34
  "price_usd_trigger",
@@ -52,7 +50,10 @@ def limit_order():
52
50
  "--condition",
53
51
  required=True,
54
52
  type=click.Choice(["gte", "lte"]),
55
- help="Trigger condition: 'gte' (price >= target) or 'lte' (price <= target).",
53
+ help=(
54
+ "Trigger condition: 'gte' (price >= target)"
55
+ " or 'lte' (price <= target)."
56
+ ),
56
57
  )
57
58
  @click.option("--slippage-bps", default=50, type=int, help="Slippage (bps).")
58
59
  @click.option(
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import click
6
6
  import httpx
7
7
 
8
- from ..async_util import async_command, get_client, run_async
8
+ from ..async_util import async_command, get_client
9
9
  from ..config import load_config, set_config_value
10
10
  from ..output import print_error, print_success
11
11
 
fere_cli/main.py CHANGED
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import click
6
6
 
7
7
  from . import __version__
8
- from .commands.auth import auth, credits, whoami
8
+ from .commands.auth import auth, claim_daily_credits, credits, whoami
9
9
  from .commands.chat import chat, threads
10
10
  from .commands.earn import earn
11
11
  from .commands.hooks import hooks
@@ -13,6 +13,7 @@ from .commands.limit_order import limit_order
13
13
  from .commands.portfolio import holdings, notifications, wallets
14
14
  from .commands.swap import swap
15
15
  from .commands.utility import chains, config, status
16
+ from .update import maybe_check_for_updates
16
17
 
17
18
 
18
19
  @click.group(invoke_without_command=True)
@@ -53,6 +54,9 @@ def cli(ctx, output_json, quiet, agent, base_url):
53
54
  if base_url:
54
55
  ctx.obj["base_url_override"] = base_url
55
56
 
57
+ # Fetch runs in background; prompt fires on main thread after subcommand.
58
+ maybe_check_for_updates(output_json=output_json, quiet=quiet, ctx=ctx)
59
+
56
60
  # Show banner when no subcommand is given
57
61
  if ctx.invoked_subcommand is None and not output_json and not quiet:
58
62
  from rich.console import Console
@@ -68,6 +72,7 @@ def cli(ctx, output_json, quiet, agent, base_url):
68
72
  cli.add_command(auth)
69
73
  cli.add_command(whoami)
70
74
  cli.add_command(credits)
75
+ cli.add_command(claim_daily_credits, name="claim-daily")
71
76
 
72
77
  # Chat
73
78
  cli.add_command(chat)
fere_cli/output.py CHANGED
@@ -72,10 +72,21 @@ def print_json(data: Any) -> None:
72
72
 
73
73
 
74
74
  def print_dict(data: dict, title: str | None = None) -> None:
75
- """Print a dict as a Rich panel with key-value pairs."""
75
+ """Print a dict as a Rich panel with key-value pairs.
76
+
77
+ None values are omitted from human output.
78
+ """
76
79
  lines = []
77
80
  for k, v in data.items():
78
- if isinstance(v, (dict, list)):
81
+ if v is None:
82
+ continue
83
+ if isinstance(v, dict):
84
+ v = json_lib.dumps(
85
+ {k2: v2 for k2, v2 in v.items() if v2 is not None},
86
+ indent=2,
87
+ default=str,
88
+ )
89
+ elif isinstance(v, list):
79
90
  v = json_lib.dumps(v, indent=2, default=str)
80
91
  lines.append(f"[bold]{k}:[/bold] {v}")
81
92
  content = "\n".join(lines)
fere_cli/update.py ADDED
@@ -0,0 +1,335 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import threading
10
+ import time
11
+ import urllib.error
12
+ import urllib.request
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Optional, Tuple
15
+
16
+ if TYPE_CHECKING:
17
+ import click as _click
18
+
19
+ import click
20
+
21
+ from . import __version__
22
+
23
+ PYPI_URL = "https://pypi.org/pypi/fere-cli/json"
24
+ CHECK_INTERVAL_SECONDS = 86400 # once per day
25
+
26
+
27
+ def _state_file() -> Path:
28
+ """Return path to the update-check state file."""
29
+ config_dir = Path.home() / ".config" / "fere-cli"
30
+ return config_dir / "update-check.json"
31
+
32
+
33
+ def _should_check() -> bool:
34
+ """Return True if enough time has elapsed since last check."""
35
+ sf = _state_file()
36
+ if not sf.exists():
37
+ return True
38
+ try:
39
+ data = json.loads(sf.read_text())
40
+ last = data.get("last_check", 0)
41
+ return (time.time() - last) >= CHECK_INTERVAL_SECONDS
42
+ except Exception:
43
+ return True
44
+
45
+
46
+ def _record_check() -> None:
47
+ """Persist current timestamp so we skip checks for a day."""
48
+ sf = _state_file()
49
+ try:
50
+ sf.parent.mkdir(parents=True, exist_ok=True)
51
+ sf.write_text(json.dumps({"last_check": time.time()}))
52
+ except Exception:
53
+ pass
54
+
55
+
56
+ def _is_stable_version(version: str) -> bool:
57
+ """Return True if version is a pure X.Y.Z."""
58
+ parts = version.split(".")
59
+ if len(parts) != 3:
60
+ return False
61
+ try:
62
+ _ = (int(parts[0]), int(parts[1]), int(parts[2]))
63
+ except ValueError:
64
+ return False
65
+ return True
66
+
67
+
68
+ def _is_prerelease(version: str) -> bool:
69
+ """Return True if version has a pre-release suffix."""
70
+ return not _is_stable_version(version)
71
+
72
+
73
+ def _parse_version_tuple(
74
+ version: str,
75
+ ) -> Optional[Tuple[int, int, int]]:
76
+ """Parse the X.Y.Z prefix from a version string."""
77
+ base = version.split("a")[0].split("b")[0].split("rc")[0]
78
+ try:
79
+ major, minor, patch = base.split(".")
80
+ return int(major), int(minor), int(patch)
81
+ except Exception:
82
+ return None
83
+
84
+
85
+ def _parse_prerelease_index(version: str) -> int:
86
+ """Return the numeric pre-release index (e.g. a2→2, rc1→1, stable→0).
87
+
88
+ Used as a tiebreaker so a2 sorts above a1 regardless of PyPI dict order.
89
+ Stable versions return 0 (they are ranked via the stability flag instead).
90
+ """
91
+ m = re.search(r"(?:a|b|rc)(\d+)$", version)
92
+ return int(m.group(1)) if m else 0
93
+
94
+
95
+ def _fetch_pypi_releases(
96
+ timeout: float = 2.0,
97
+ ) -> Optional[dict]:
98
+ """Fetch release data from PyPI. Returns None on error."""
99
+ try:
100
+ req = urllib.request.Request(
101
+ PYPI_URL,
102
+ headers={"User-Agent": "fere-cli"},
103
+ )
104
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
105
+ return json.loads(resp.read().decode("utf-8"))
106
+ except (
107
+ urllib.error.URLError,
108
+ TimeoutError,
109
+ json.JSONDecodeError,
110
+ OSError,
111
+ ):
112
+ return None
113
+
114
+
115
+ def _latest_from_releases(
116
+ releases: dict,
117
+ include_prerelease: bool = False,
118
+ ) -> Optional[str]:
119
+ """Pick the latest version from a PyPI releases dict."""
120
+ versions = list(releases.keys())
121
+ if not include_prerelease:
122
+ versions = [v for v in versions if _is_stable_version(v)]
123
+
124
+ parsed: list[Tuple[Tuple[int, int, int], int, int, str]] = []
125
+ for v in versions:
126
+ t = _parse_version_tuple(v)
127
+ if t is not None:
128
+ # stability: 1 for stable, 0 for pre-release (stable wins on tie)
129
+ # pre_idx: numeric pre-release index (a2 > a1 regardless of order)
130
+ stability = 1 if _is_stable_version(v) else 0
131
+ pre_idx = _parse_prerelease_index(v)
132
+ parsed.append((t, stability, pre_idx, v))
133
+
134
+ if not parsed:
135
+ return None
136
+
137
+ parsed.sort(key=lambda item: (item[0], item[1], item[2]))
138
+ return parsed[-1][3]
139
+
140
+
141
+ def get_latest_stable_version(
142
+ timeout: float = 2.0,
143
+ ) -> Optional[str]:
144
+ """Fetch latest stable fere-cli version from PyPI."""
145
+ data = _fetch_pypi_releases(timeout)
146
+ if not data:
147
+ return None
148
+ return _latest_from_releases(
149
+ data.get("releases", {}),
150
+ include_prerelease=False,
151
+ )
152
+
153
+
154
+ def get_latest_prerelease_version(
155
+ timeout: float = 2.0,
156
+ ) -> Optional[str]:
157
+ """Fetch latest fere-cli version (including pre-releases)."""
158
+ data = _fetch_pypi_releases(timeout)
159
+ if not data:
160
+ return None
161
+ return _latest_from_releases(
162
+ data.get("releases", {}),
163
+ include_prerelease=True,
164
+ )
165
+
166
+
167
+ def is_newer_version(latest: str, current: str) -> bool:
168
+ """Return True if latest > current.
169
+
170
+ When X.Y.Z base is equal, stable > pre-release so that a user on
171
+ e.g. 0.2.0a1 is prompted to upgrade to the stable 0.2.0 release.
172
+ """
173
+ latest_t = _parse_version_tuple(latest)
174
+ current_t = _parse_version_tuple(current)
175
+ if latest_t is None or current_t is None:
176
+ return False
177
+ if latest_t != current_t:
178
+ return latest_t > current_t
179
+ # Same X.Y.Z base: stable release is newer than any pre-release.
180
+ return _is_stable_version(latest) and not _is_stable_version(current)
181
+
182
+
183
+ def _perform_pipx_upgrade(pre: bool = False) -> bool:
184
+ pipx_path = shutil.which("pipx")
185
+ if not pipx_path:
186
+ return False
187
+ cmd = [pipx_path, "upgrade", "fere-cli"]
188
+ if pre:
189
+ cmd.append("--pip-args=--pre")
190
+ try:
191
+ subprocess.run(
192
+ cmd,
193
+ check=True,
194
+ stdout=subprocess.DEVNULL,
195
+ stderr=subprocess.DEVNULL,
196
+ )
197
+ return True
198
+ except subprocess.CalledProcessError:
199
+ return False
200
+
201
+
202
+ def _perform_pip_upgrade(pre: bool = False) -> bool:
203
+ python = sys.executable or "python"
204
+ cmd = [python, "-m", "pip", "install", "--upgrade"]
205
+ if pre:
206
+ cmd.append("--pre")
207
+ cmd.append("fere-cli")
208
+ try:
209
+ subprocess.run(
210
+ cmd,
211
+ check=True,
212
+ stdout=subprocess.DEVNULL,
213
+ stderr=subprocess.DEVNULL,
214
+ )
215
+ return True
216
+ except subprocess.CalledProcessError:
217
+ return False
218
+
219
+
220
+ def perform_self_upgrade(pre: bool = False) -> bool:
221
+ """Upgrade fere-cli via pipx (preferred) or pip."""
222
+ if _perform_pipx_upgrade(pre=pre):
223
+ return True
224
+ return _perform_pip_upgrade(pre=pre)
225
+
226
+
227
+ def _do_prompt(current_is_pre: bool, latest: Optional[str]) -> None:
228
+ """Handle version comparison and interactive prompt on the main thread."""
229
+ if not latest:
230
+ _record_check()
231
+ return
232
+
233
+ if not is_newer_version(latest, __version__):
234
+ _record_check()
235
+ return
236
+
237
+ channel = "pre-release" if current_is_pre else "stable"
238
+ click.echo(
239
+ f"A new {channel} FereAI CLI version {latest} "
240
+ f"is available (you have {__version__})."
241
+ )
242
+
243
+ try:
244
+ should_upgrade = click.confirm(
245
+ "Do you want to upgrade now?",
246
+ default=False,
247
+ show_default=True,
248
+ )
249
+ except (click.Abort, EOFError):
250
+ _record_check()
251
+ return
252
+
253
+ if not should_upgrade:
254
+ _record_check()
255
+ return
256
+
257
+ click.echo(f"Upgrading fere-cli ({channel})...")
258
+ ok = perform_self_upgrade(pre=current_is_pre)
259
+ _record_check()
260
+ if ok:
261
+ # Omit flags intentionally — they may contain secrets.
262
+ subcmd = sys.argv[1] if len(sys.argv) > 1 else ""
263
+ rerun = f"fere {subcmd}" if subcmd else "fere"
264
+ click.echo(f"Upgrade completed. Please re-run: {rerun}")
265
+ # sys.exit(0) intentionally skips remaining call_on_close
266
+ # handlers — the process must restart after upgrade anyway.
267
+ sys.exit(0)
268
+ else:
269
+ click.echo(
270
+ "Upgrade failed. Manually upgrade with "
271
+ "'pipx upgrade fere-cli' or "
272
+ "'pip install --upgrade fere-cli'.",
273
+ err=True,
274
+ )
275
+
276
+
277
+ def maybe_check_for_updates(
278
+ output_json: bool,
279
+ quiet: bool,
280
+ ctx: "Optional[_click.Context]" = None,
281
+ ) -> None:
282
+ """Check PyPI for newer CLI version, at most once per day.
283
+
284
+ The PyPI fetch runs in a background thread so it never blocks the
285
+ subcommand. When *ctx* is provided the interactive prompt fires via
286
+ ``ctx.call_on_close()`` on the main thread after the subcommand
287
+ completes, avoiding any race with terminal I/O. Without *ctx* the
288
+ check runs synchronously (useful for tests / direct calls).
289
+
290
+ - Skips non-interactive / piped usage.
291
+ - Skips when output_json or quiet is enabled.
292
+ - Skips when FERE_NO_UPDATE_CHECK=1 is set.
293
+ - When current version is a pre-release, checks for
294
+ the latest pre-release; otherwise checks stable only.
295
+ - Never raises; on any error it silently returns.
296
+ """
297
+ if output_json or quiet:
298
+ return
299
+
300
+ if os.environ.get("FERE_NO_UPDATE_CHECK") == "1":
301
+ return
302
+
303
+ if not sys.stdin.isatty() or not sys.stdout.isatty():
304
+ return
305
+
306
+ if not _should_check():
307
+ return
308
+
309
+ current_is_pre = _is_prerelease(__version__)
310
+ result: dict = {}
311
+ fetch_done = threading.Event()
312
+
313
+ def _bg_fetch() -> None:
314
+ try:
315
+ if current_is_pre:
316
+ result["latest"] = get_latest_prerelease_version()
317
+ else:
318
+ result["latest"] = get_latest_stable_version()
319
+ except Exception:
320
+ pass
321
+ finally:
322
+ fetch_done.set()
323
+
324
+ threading.Thread(target=_bg_fetch, daemon=True).start()
325
+
326
+ def _prompt() -> None:
327
+ # By the time the subcommand finishes the fetch is usually done;
328
+ # wait at most 2 s for any remaining network time.
329
+ fetch_done.wait(timeout=2.0)
330
+ _do_prompt(current_is_pre, result.get("latest"))
331
+
332
+ if ctx is not None:
333
+ ctx.call_on_close(_prompt)
334
+ else:
335
+ _prompt()
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fere-cli
3
- Version: 0.2.0.dev17
3
+ Version: 0.2.1.dev19
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.2.0.dev17
9
+ Requires-Dist: fere-sdk>=0.2.1.dev19
10
10
  Requires-Dist: rich>=13.0
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
@@ -0,0 +1,20 @@
1
+ fere_cli/__init__.py,sha256=tQ7Vp1LCPsoI2hRFNekAZOEep_rO7TGuqhUqlm0ZMtU,341
2
+ fere_cli/async_util.py,sha256=Ai832fD956lcDdYrTSbe59iU1Dl59rRZRODFz3KA9lU,1562
3
+ fere_cli/banner.py,sha256=CE_sW4j460C3wGMwPVriZH-AJpWlGBHBxP1-h7DZUUM,823
4
+ fere_cli/config.py,sha256=BIVr6ViLCWCEloWbxF0aekS4uSrvbUVzbSN1q9dHh_o,1574
5
+ fere_cli/main.py,sha256=CDiWgb-4OPiVUakDq6wT6p5fYLGH3HKcdLcqfxGowTo,2510
6
+ fere_cli/output.py,sha256=j_2iad_g_IqySnHf0Zqh9HD7H2q_DeUHv2OlNGhBcBM,3647
7
+ fere_cli/update.py,sha256=vsdQTC_ue6xLpwvJGVmNt89XT64vfYV_S-vtumJEDiM,9731
8
+ fere_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ fere_cli/commands/auth.py,sha256=nQYY9Sx1e3p3p7ij4WggfCbj7YBV949dymfqe-pXRrw,3994
10
+ fere_cli/commands/chat.py,sha256=YF_bI3HBY6vOrPMYjRrvPWP74kfqwIBIcAfb9ZI47_c,5106
11
+ fere_cli/commands/earn.py,sha256=wDUmB7ZyQ4K2xGxm54WnnBFQiSZqThuC-h1J_yu4ito,3501
12
+ fere_cli/commands/hooks.py,sha256=e3A1TD_qBt-I3zbQ_Wyf3oWhSceCQwFx0Wvn17Z74R4,2007
13
+ fere_cli/commands/limit_order.py,sha256=Hv-9_erKUlyYE97IhSS9XOMJDrJy3qF7z-SA_ed7nzc,4188
14
+ fere_cli/commands/portfolio.py,sha256=7JJOIolmlud6yWStw7iv9GCOH1igOilKUJzPr7ixx2g,1758
15
+ fere_cli/commands/swap.py,sha256=v4urnENxLiwiGhSPTMDGEgRA0iglAC41xN_65h6xWw0,2507
16
+ fere_cli/commands/utility.py,sha256=0icyDsouq-NchWg_CY2WdrH9VoQjg1WBybkkmZgknAg,2198
17
+ fere_cli-0.2.1.dev19.dist-info/METADATA,sha256=sA1vjnn27fqqWPj1FR_jUGQpMvj7ESYKHIKkAHoVf7Y,3719
18
+ fere_cli-0.2.1.dev19.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
19
+ fere_cli-0.2.1.dev19.dist-info/entry_points.txt,sha256=vOltZxMv_RJwS1AqO3UstqpRp74xPd4UMlf62Ma-fWM,43
20
+ fere_cli-0.2.1.dev19.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- fere_cli/__init__.py,sha256=8YO0NbqDOIoOAlhEvrTGibA7Iz-RK46OvnhAgPNlfMU,96
2
- fere_cli/async_util.py,sha256=1HFLWIyWfL30Qkz_QcWAb2EVaQ1jysCzxCbCqBjAPGc,1567
3
- fere_cli/banner.py,sha256=56sSuANPj_DuUgn6sG15Y0MM6wOoqFrYydqIl4svWwQ,797
4
- fere_cli/config.py,sha256=BIVr6ViLCWCEloWbxF0aekS4uSrvbUVzbSN1q9dHh_o,1574
5
- fere_cli/main.py,sha256=9zxTaYRvwm04AXnK3_vFDE4EOy-txnFgXCqVe-itAwQ,2234
6
- fere_cli/output.py,sha256=Q3Uo34huTasjqKnhbX92CtfQrquDDjM2UGaPTD5Atos,3356
7
- fere_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- fere_cli/commands/auth.py,sha256=rdu3v7ARk9p56djEyI41ECzObWUOUHLCE0bB-d-Kz3Y,2763
9
- fere_cli/commands/chat.py,sha256=kLxLnXzpxnGZSJ9vhMtZo3LABL9PdQzQ-r5lVos3k3E,5283
10
- fere_cli/commands/earn.py,sha256=wDUmB7ZyQ4K2xGxm54WnnBFQiSZqThuC-h1J_yu4ito,3501
11
- fere_cli/commands/hooks.py,sha256=oVSeI7jpYUSygqG8pqU9grtltZ7f_GRma5Z5JGlLGvE,1965
12
- fere_cli/commands/limit_order.py,sha256=5d4F669K-fW4iNeZA44y5L7WQLMXqughJlwS51tUmwY,4167
13
- fere_cli/commands/portfolio.py,sha256=7JJOIolmlud6yWStw7iv9GCOH1igOilKUJzPr7ixx2g,1758
14
- fere_cli/commands/swap.py,sha256=v4urnENxLiwiGhSPTMDGEgRA0iglAC41xN_65h6xWw0,2507
15
- fere_cli/commands/utility.py,sha256=8ofNJwxAYSBHjmX6VPIANUEPQkPXtVjIJYkK5UWXZJ4,2209
16
- fere_cli-0.2.0.dev17.dist-info/METADATA,sha256=33hk4SpMmdYpe849Z7KBPkHE9OTe-ihtAjRkp1jSD2Q,3719
17
- fere_cli-0.2.0.dev17.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
18
- fere_cli-0.2.0.dev17.dist-info/entry_points.txt,sha256=vOltZxMv_RJwS1AqO3UstqpRp74xPd4UMlf62Ma-fWM,43
19
- fere_cli-0.2.0.dev17.dist-info/RECORD,,